Oracle9i SQL Reference

雪狼 作品

资料来源

资料来源：http://asktom.oracle.com

收集整理：Snowywolf

整理时间：2004-01

版权声明

您可以自由地将此文档复制发行，但请保留此声明页。

请勿将此文档用于任何商业用途。

联系作者

ITPUB号：Snowywolf

E m a i l：lisj@supermap.com

N a m e ：李绍俊

雪狼 作品


Listing

AskTom Home
ViewForPrinting
Home
Recent Archives Advanced Search FAQ by Category Most Popular Hot Articles Your Questions [ Listing ] RSS Feed
Subject | Article | Last Updated (East coast time)
Who fired the trigger | article only | Jan 01, 2004 12:48 pm
Union vs outer join | article only | Jan 02, 2004 09:40 am
Barcode Scanner | article only | Jan 02, 2004 04:23 pm
Use of package OWA | article only | Jan 03, 2004 09:10 am
Ref cursors as O/P params in SP- when are they closed? | article only | Jan 03, 2004 09:15 am
dbms_utility.get_time question | article only | Jan 03, 2004 09:22 am
Clarification on rollback segments and tablespace extents | article only | Jan 03, 2004 10:02 am
SQL Loader and NLS_LANG | article only | Jan 03, 2004 10:09 am
Background processes memory consumption | article only | Jan 03, 2004 12:31 pm
Hash partitioning in OLTP, a way of suicide? | article only | Jan 03, 2004 03:41 pm
Random numbers | article only | Jan 03, 2004 03:44 pm
NOT IN Subquery | article only | Jan 04, 2004 12:17 pm
spfile | article only | Jan 04, 2004 12:54 pm
About Redo log generation | article only | Jan 04, 2004 12:55 pm
PGA/UGA Memory | article only | Jan 04, 2004 03:02 pm
HASH_MULTIBLOCK_IO_COUNT | article only | Jan 04, 2004 04:19 pm
why views stick around | article only | Jan 04, 2004 08:42 pm
owa_sylk | article only | Jan 05, 2004 07:16 am
question regarding storage parameters etc. | article only | Jan 05, 2004 07:25 am
Error while starting listener | article only | Jan 05, 2004 07:35 am
Previledges and roles | article only | Jan 05, 2004 10:04 am
LOOPS and Exceptions | article only | Jan 05, 2004 01:47 pm
size of a table..avg_row_len,compute statistics | article only | Jan 06, 2004 08:00 am
File Upload | article only | Jan 06, 2004 08:25 am
Determining values of bind variables for tuning | article only | Jan 06, 2004 08:39 am
Answers to Interview Questions | article only | Jan 06, 2004 08:45 am
Using Oracle collections and result sets | article only | Jan 06, 2004 08:49 am
query for specific result format | article only | Jan 06, 2004 09:24 am
dynamic ORDER BY in a Cursor | article only | Jan 06, 2004 09:48 am
Flexible Parameter Passing in mod_plsql | article only | Jan 06, 2004 10:40 am
Architecture Design Question | article only | Jan 06, 2004 01:07 pm
start/stop db from command line in NT | article only | Jan 06, 2004 02:12 pm
group records by interval of 3 seconds | article only | Jan 06, 2004 02:23 pm
synonyms performance | article only | Jan 06, 2004 02:26 pm
Pinning Revisted ... | article only | Jan 06, 2004 02:27 pm
Min, Max, AVG | article only | Jan 06, 2004 08:38 pm
How do I find what kind of refresh has been done? | article only | Jan 07, 2004 07:39 am
data encryption package | article only | Jan 07, 2004 07:45 am
To keep tables in cache | article only | Jan 07, 2004 07:48 am
Difference between NOT NULL constraint and check constraint | article only | Jan 07, 2004 07:50 am
Auto startup database | article only | Jan 07, 2004 08:01 am
Database Free space | article only | Jan 07, 2004 06:27 pm
row(s) 1 - 42 of 42
January 01, 2004 Anne -- Thanks for the question regarding "Who fired the trigger", version 8.0.5
You Asked
Hi,

The situation is whenever a "delete" statement
is issued there should be a log that should store the info
in another table.  We are trying to do this
while using a statement trigger..
But, we don't want log this this info if the delete is issued
from another trigger.
Is there any way to know who fired the trigger, direct order or
order from another trigger ?

Thanks in advance,
Anne Cantrelle
and we said...
If you get the who_called_me routine from
http://osi.oracle.com/~tkyte/who_called_me/index.html
we can determine this.  Consider this example in 8.0.6:

scott@ORA806.WORLD> create table t1 ( x int );

Table created.

scott@ORA806.WORLD> create table t2 ( x int );

Table created.

scott@ORA806.WORLD>
scott@ORA806.WORLD> create or replace trigger t1_trigger
  2  before insert on t1
  3  for each row
  4  declare
  5      owner    varchar2(30);
  6      name     varchar2(30);
  7      lineno   number;
  8      caller_t varchar2(30);
  9  begin
 10      who_called_me( owner, name, lineno, caller_t );
 11      dbms_output.put_line( '--------- in T1 trigger ---');
 12      dbms_output.put_line( 'My Caller is ' ||
 13                             owner || '.' || name  );
 14      dbms_output.put_line( 'It is a ' || caller_t );
 15      dbms_output.put_line( '--------- end T1 trigger --');
 16  end;
 17  /

Trigger created.

scott@ORA806.WORLD>
scott@ORA806.WORLD> create or replace trigger t2_trigger
  2  before insert on t2
  3  for each row
  4  declare
  5      owner    varchar2(30);
  6      name     varchar2(30);
  7      lineno   number;
  8      caller_t varchar2(30);
  9  begin
 10      who_called_me( owner, name, lineno, caller_t );
 11      dbms_output.put_line( '--------- in T2 trigger ---');
 12      dbms_output.put_line( 'My Caller is ' ||
 13                             owner || '.' || name  );
 14      dbms_output.put_line( 'It is a ' || caller_t );
 15
 16      insert into t1 values ( :new.x );
 17
 18      dbms_output.put_line( '--------- end T2 trigger --');
 19  end;
 20  /

Trigger created.

So, the triggers should attempt to print out what is causing
them to fire -- the trigger on T2 inserts into T1 so we expect
to be able to discover that.  We'll run inserts into t1 and t2
from the command line and procedures to see what happens:

scott@ORA806.WORLD>
scott@ORA806.WORLD> create or replace procedure p1
  2  as
  3  begin
  4      insert into t1 values(1);
  5  end;
  6  /

Procedure created.

scott@ORA806.WORLD> create or replace procedure p2
  2  as
  3  begin
  4      insert into t2 values(2);
  5  end;
  6  /

Procedure created.

scott@ORA806.WORLD>
scott@ORA806.WORLD> set serveroutput on
scott@ORA806.WORLD>
scott@ORA806.WORLD> insert into t1 values (1);
--------- in T1 trigger ---
My Caller is .
It is a
--------- end T1 trigger --

1 row created.

The straight insert shows "NULL" is our caller type.  We were
called from the command line...

scott@ORA806.WORLD> insert into t2 values (2);
--------- in T2 trigger ---
My Caller is .
It is a
--------- in T1 trigger ---
My Caller is SCOTT.T2_TRIGGER
It is a TRIGGER
--------- end T1 trigger --
--------- end T2 trigger --

1 row created.

In this one, the insert into T2 shows NULL but the insert into
T1 shows that it was initiated by a TRIGGER -- T2_TRIGGER
specifically

scott@ORA806.WORLD>
scott@ORA806.WORLD> exec p1
--------- in T1 trigger ---
My Caller is SCOTT.P1
It is a PROCEDURE
--------- end T1 trigger --

PL/SQL procedure successfully completed.

When run by a procedure -- we can tell...

scott@ORA806.WORLD> exec p2
--------- in T2 trigger ---
My Caller is SCOTT.P2
It is a PROCEDURE
--------- in T1 trigger ---
My Caller is SCOTT.T2_TRIGGER
It is a TRIGGER
--------- end T1 trigger --
--------- end T2 trigger --

PL/SQL procedure successfully completed.

And again, T2 can tell it was inserted into by a trigger and
T1 can tell it was inserted into by the trigger T2_TRIGGER....
Review & Followup
Rating: 5
exactly what i need... March 24, 2003
Reviewer: A reader

Rating: 5
Related question: Who (user) fired the trigger June 23, 2003
Reviewer: Anil from FL, USA
I use a table trigger to limit DML actions on one (or more)  columns of the
table by a user who is not the owner of the table. (I cannot use views). To add
to the complexity, the trigger is defined in a separate schema with create any
trigger privilege and this user does not have any other special privileges.
Simply put, I am maintaining column level privileges to user tables with a
PL/SQL APIs to grant and revoke privileges on some special columns. The triggers
are maintained in a special schema along with the dictionary (privilege)
information.

In the current implementation, I am using SYS_CONTEXT('USER_ENV','CURRENT_USER')
to check if the user modifying the content in the column has the privilege to do
so. This works fine as long as the end user is performing direct INSERT/UPDATE
into this table. But this approach fails if the owner of the table creates a
procedure to perform the DML and grants execute privileges on this procedure to
other users. In this case, no matter who invokes the procedure, the insert into
the column should go through as the owner (of the procedure and the table)
always has the full access to the column. But, the use of
SYS_CONTEXT('USER_ENV','CURRENT_USER') is restricting this operation because it
always computes to the original caller of the procedure and not the owner of the
procedure (or the invoker of the DML statement).

So, to mimic the typical privilege model in the presence of user defined
function/procedures, I would like to get the user who "issued" the INSERT/UPDATE
statement (dynamic/static) within the trigger body. Is the who_am_i (like)
procedure my only option ? Is there any trigger variable (like UPDATING) that
can give this information ?

Your help is greatly appreciated.

Thanks,
-Anil.

Followup:
wow, and all of this could have be done with GRANT, too bad you did all of that
work.

who am i will get you the owner of the invoking procedure.  But you know, just
erasing your code and using GRANT would be an option as well!

Rating: 3
Thanks June 24, 2003
Reviewer: Anil
Yes, I could have used grant privileges on the columns. But when the user does
grant insert on the table, I still want to restrict privileges on some columns
(based on the datatype). The content of these columns is special and this is
required to ensure that the other users do not get privileges on them when the
owner simply does table level grant. I want the owner to take some explicit
action so that he knows what he is doing.

Thanks for the response.
-Anil

Rating: 3
MY_CALLER Function August 01, 2003
Reviewer: Fred from irvine ca
Tom,

I have your sample MY_CALLER function from your book.  Does this work on Oracle
9i?

Followup:
yes, it just parses the dbms_utility.format_call_stack output which exists in 9i

Rating: 3
who_called_me October 16, 2003
Reviewer: Ashwin N. from UK
Hi Tom

I was trying to use the who_called_me procedure.
For some reason it falls up with the following error

ORA-06502: PL/SQL: numeric or value error: character to number conversion
error-6502
(I have pasted my code below which calls this.)
I have traced the error and it comes up on the following line in who_called_me

lineno := to_number(substr( line, 13, 6 ));
I put debug messages just before this line and I get
the following values for
lineno=c0000000197b5a30        11  anonymous block
lineno=          <BLANK>

I am sure I am doing something wrong here.
Can you please help on this

Thanks
Ashwin N.

PACKAGE BODY measure_time

 AS

 PROCEDURE set_time IS

 BEGIN

 time_before := DBMS_UTILITY.GET_TIME();

 END;

 PROCEDURE get_time(process_name IN VARCHAR2) IS

  time_after NUMBER;

  time_elapsed NUMBER;

  prog_name VARCHAR2(30) ;

  prog_owner VARCHAR2(30);

  lineno    VARCHAR2(30);

  caller_t  VARCHAR2(30);

  PRAGMA AUTONOMOUS_TRANSACTION;

  BEGIN

 DBMS_OUTPUT.PUT_LINE(1);

  who_called_me(prog_owner,prog_name, lineno, caller_t);

 DBMS_OUTPUT.PUT_LINE(2);

  time_after        := DBMS_UTILITY.GET_TIME();

                       DBMS_OUTPUT.PUT_LINE(3);

  time_elapsed      := (time_after-time_before)/100;

 DBMS_OUTPUT.PUT_LINE(4);

  INSERT INTO  measure_time_table
VALUES(prog_name,process_name,time_elapsed,SYSDATE);

  COMMIT;

  EXCEPTION WHEN OTHERS THEN

     DBMS_OUTPUT.PUT_LINE(SQLERRM||SQLCODE);

 END;

 END;

Followup:
the address is different then I anticipated -- just adjust the code a bit to
parse it correctly on your server.

Rating: 3
who_called_me October 16, 2003
Reviewer: Ashwin N
Just a small change

It Should have been

line=c0000000197b5a30        11  anonymous block
lineno=          <BLANK>

Regards
Ashwin N

Rating: 4
Pls clarify December 29, 2003
Reviewer: A reader
In the last follup you said
Followup:
the address is different then I anticipated -- just adjust the code a bit to
parse it correctly on your server.

I am also getting the same pl/sql numeric value error.
Can u pls elaborate how to adjust the parse statement ?What
should be the substr.
lineno := to_number(substr( line, 13, 6 )); is giving the error.
To correct the problem  i used substr(line,11,9).
Did i do the correct thing. How do I the "handle"address wont increase.

Do i need to adjust some more place

  handle    number  name
0x51125dd8         6  procedure SCOTT.WHO_CALLED_ME
0x527b1d1c        12  SCOTT.TRG_AIU_TEST

Followup:
put some dbms_output's in there and see what is being parsed and adjust the
substr's appropriately!

dbmsoutput the "line" before substring:

  dbms_output.put_line( '"' || line || '"' );

and use that output to see where to substr

Rating: 4
Remote DB not working December 29, 2003
Reviewer: A reader
I tried using your same example but did the insert on table t2 from a remote DB
procedure p2_remote and it is not giving me the remote procedure name nor the
owner/schema name.
but it works if i am in the same DB but different schema
Any clues to this one ?

Followup:
just use dbms_utility to print out the call stack and see what you see -- i've
never used the format call stack over a dblink and would not be at all surprised
if the remote site didn't see the other "remote" site.

Rating: 4
U r correct December 29, 2003
Reviewer: A reader
The remote DB doesnot see that.I printed the stack to confirm.I think its tied
to its own DB and stack info cannot cross from one DB to another remote DB

So what is the solution here to know who called the trigger in case of RPCs.
Thanx much

Followup:
there isn't an out of the box one.

Rating: 3
Still any pointers to the soln if any ? December 30, 2003
Reviewer: A reader
This looks like a tough one.
Thanx

Followup:
not that i'm aware of, no.

Rating: 3
How about dbms_pipe and remote DB January 01, 2004
Reviewer: A reader
Can we use dbms_pipe ,abms_alert , AQ on a Distributed DB for the above issue.

Can we sen a message to a pipe to be retrieved by a remote DB.
Still struggling with this issue of knowing who fired if the firing is done from
a Remote DB.
Thanx

Followup:
well, the caller can certainly PASS this information along -- but you'd have to
trust the call "not to lie to you".

I don't see how pipes, alerts would give you anything.

AQ could, since the dequeuer could be a "known, trusted account" and you would
just look at "user" to see if it is to be allowed to proceed

Rating: 3
To fix the numeric error in who_called_me... January 02, 2004
Reviewer: Mark from USA
I made the following changes to correct the problem:

Line 30
FROM lineno := to_number(substr( line, 13, 6 ));
TO   lineno := to_number(substr( line, 21, 6 ));

Line 31
FROM line   := substr( line, 21 );
TO   line   := substr( line, 29 );

I'm on Oracle8i Enterprise Edition Release 8.1.7.3.0 - 64bit Production

January 02, 2004 Holger -- Thanks for the question regarding "Union vs outer join", version 8.0.5
You Asked
Analysing some applications I've come accross, I found several times views
created after a certain scheme. Basically they are doing the same query twice,
once for the rows that have a corresponding row in a lookup table, and a second
time for rows that have no corresponding rows in the same lookup table,
returning an constant string for a certain column. Or in other words,to me it
looks like they are doing an outer join by unioning the two result sets (and
that is just the basic scheme, some of those views consist of 10 or more unioned
selects). Looking at the stats these are heavy resource consuming beasts.

By just replacing the two unioned selects by an outer join and decoding the
column in question, Resource usage drops to about 10 % of the original
(espacially consistent gets).

Whats puzzling me:
Why do absolutely independent vendors the very same thing, especially when it
seems that it is not the best solution? Is this just a special case where a rule
of thumb doesn't hold, an oracle myth at work (like using an index is always
better than a full T scan and of course the select without the where not exists
clause is screamingly
fast), or am I missing something important?

Original query

select V.NAME V,
    P.NAME P,
    P.POSITION_NR POSITION_NR,
    TGRUPPE.NAME GRUPPE,
    TDEFINITION.NAME NAME,
    TDEFINITION.BESCHREIBUNG BESCHREIBUNG,
    'Integer' TYP, TO_CHAR(TINT.WERT) WERT,
    TO_CHAR(N_TINT.WERT) N_WERT,        -> this is the important column...
    TDEFINITION_INT.EINHEIT EINHEIT
from P, TGRUPPE,
    TDEFINITION,
    V,
    TDEFINITION_INT,
    TINT,
    N_TINT
where TGRUPPE.TGRUPPE_ID = TDEFINITION.TGRUPPE_ID
and TDEFINITION.TID = TDEFINITION_INT.TID
and TINT.TID = TDEFINITION.TID
and TINT.P_ID = P.P_ID (+)
and TINT.TINT_ID = N_TINT.TINT_ID
and V.V_Id = TINT.V_Id
union
select V.NAME V,
    P.NAME P,
    P.POSITION_NR POSITION_NR,
    TGRUPPE.NAME GRUPPE,
    TDEFINITION.NAME NAME,
    TDEFINITION.BESCHREIBUNG BESCHREIBUNG,
    'Integer' TYP,
    TO_CHAR(TINT.WERT) WERT,
    '-kein Wert-' N_WERT,                -> which is now set to a constant value
    TDEFINITION_INT.EINHEIT EINHEIT
from P, TGRUPPE, TDEFINITION, V, TDEFINITION_INT,
    TINT
where TGRUPPE.TGRUPPE_ID = TDEFINITION.TGRUPPE_ID
and TDEFINITION.TID = TDEFINITION_INT.TID
and TINT.TID = TDEFINITION.TID
and TINT.P_ID = P.P_ID (+)
and V.V_Id = TINT.V_Id
and not exists (select 'x'
        from N_TINT
        where TINT.TINT_ID = N_TINT.TINT_ID)

My suggestion:

select V.NAME V,
    P.NAME P,
    P.POSITION_NR POSITION_NR,
    TGRUPPE.NAME GRUPPE,
    TDEFINITION.NAME NAME,
    TDEFINITION.BESCHREIBUNG BESCHREIBUNG,
    'Integer' TYP, TO_CHAR(TINT.WERT) WERT,
    to_char (decode (N_TINT.WERT, NULL, '--kein Wert--', N_Tint.wert) N_WERT,
    -> this is the changed column...
    TDEFINITION_INT.EINHEIT EINHEIT
from P, TGRUPPE,
    TDEFINITION,
    V,
    TDEFINITION_INT,
    TINT,
    N_TINT
where TGRUPPE.TGRUPPE_ID = TDEFINITION.TGRUPPE_ID
and TDEFINITION.TID = TDEFINITION_INT.TID
and TINT.TID = TDEFINITION.TID
and TINT.P_ID = P.P_ID (+)
and TINT.TINT_ID = N_TINT.TINT_ID(+)
and V.V_Id = TINT.V_Id

Thanks for explaining
and we said...
You are 100% correct -- but they may be counting on a side effect of a UNION.

Consider:

scott@ORA920> select dept.deptno, emp.job
  2    from emp, dept
  3   where dept.deptno = emp.deptno
  4   UNION
  5  select dept.deptno, 'no-jobs'
  6    from dept
  7   where not exists ( select  null
  8                        from emp
  9                       where emp.deptno = dept.deptno )
 10  /

    DEPTNO JOB
---------- ---------
        10 CLERK
        10 MANAGER
        10 PRESIDENT
        20 ANALYST
        20 CLERK
        20 MANAGER
        30 CLERK
        30 MANAGER
        30 SALESMAN
        40 no-jobs

10 rows selected.

scott@ORA920>
scott@ORA920> select dept.deptno, nvl( emp.job, 'no-jobs' )
  2    from dept left outer join emp on (emp.deptno = dept.deptno)
  3  /

    DEPTNO NVL(EMP.J
---------- ---------
        20 CLERK
        30 SALESMAN
        30 SALESMAN
        20 MANAGER
        30 SALESMAN
        30 MANAGER
        10 MANAGER
        20 ANALYST
        10 PRESIDENT
        30 SALESMAN
        20 CLERK
        30 CLERK
        20 ANALYST
        10 CLERK
        40 no-jobs

15 rows selected.

scott@ORA920> select distinct dept.deptno, nvl( emp.job, 'no-jobs' )
  2    from dept left outer join emp on (emp.deptno = dept.deptno)
  3  /

    DEPTNO NVL(EMP.J
---------- ---------
        10 CLERK
        10 MANAGER
        10 PRESIDENT
        20 ANALYST
        20 CLERK
        20 MANAGER
        30 CLERK
        30 MANAGER
        30 SALESMAN
        40 no-jobs

10 rows selected.

a UNION b is

  distinct( a + b )

Why do they do this?  To be DB independent - exactly.  They use very few
database features -- which is a bad thing in my opinion.  They have to have lots
of db dependent code anyway, they should use the databases to their fullest.
But they do not.
Review & Followup
Rating: 5
Re: Union vs outer join December 19, 2002
Reviewer: Holger Bar from Germany
Thanks a lot for clarifying.

I was just so surprised by finding the very same approach used by totally
independent vendors, I thought there must be a school somewhere where people are
teached this stuff.

Speaking of side effects: In one case there are only nulls in the column in
question, the joined table is empty... A feature waiting to be used I suppose.
:-)

"Why do they do this?  To be DB independent - exactly.  They use very few
database features -- which is a bad thing in my opinion.  They have to have lots

of db dependent code anyway, they should use the databases to their fullest.
But they do not."

Only a year ago I was as ignorant as they are. Then I came across the AskTom
column in the Oracle Magazine. I started to dig into this site and suddenly the
world changed for me. Perhaps it's time to hint people a bit more to your
valuable work (whith a large club if necessary!)

Rating: 4
can I write this using outer join? January 02, 2004
Reviewer: Pingu_SAN
Hi

Working with HR demo schema, I have this query

select a.employee_id, a.first_name, a.last_name, NULL curr_job_id,
       to_char(b.start_date) start_date, b.job_id hist_job_id, c.job_title
  from hr.employees a,
       hr.job_history b,
       hr.jobs c
 where a.employee_id = b.employee_id
   and b.job_id = c.job_id
   and a.employee_id between 100 and 102
union
select a.employee_id, a.first_name, a.last_name, a.job_id curr_job_id,
       'Currently', NULL, b.job_title
  from hr.employees a,
       hr.jobs b
 where a.job_id = b.job_id
   and a.employee_id between 100 and 102;

can we write this using outer join?

Cheers

Followup:
look at the other place you asked this

January 02, 2004 Mo -- Thanks for the question regarding "Barcode Scanner", version 8.17
You Asked
Tom:

1.  Do you have any information/reference on how can barcode scanners interface
with oracle database.  I have a web inventory management system with a shipment
table.  When someone picks something from a bin, I need to create a record in
the shipment table which has date shipped, stock number, storage code, and
quantity.  The storage code is the one that can be scanned.  The way it should
work is that invenotry people can create records into oracle using scanners.
Would the scanner show exactly the web page showing on the pc?

2.  I have a column "description" in a table . Several records has " in it which
cause a problem in javascript lookup because javascript uses it as string
terminators as  x = "this is a table"

What would you use to replace all " with blank.  Would you use replace function
here.

Thank you,
and we said...
1) they do not interface with Oracle anymore then your KEYBOARD does.

A barcode scanner is just a rudimentary input device, like a keyboard, scanner,
or a mouse.

You need a UI that the barcode will "type into", then you can process the newly
"keyed in" data.

The barcode scanner just types a number into a field and tabs to the next field,
not any different then a keyboard really.

2) umm, how about instead of destroying the data you:

"How about this string with a \" in it?"

then you do not destroy the data -- you use \ to escape things in javascript.
So, you can replace " with \"
Review & Followup
Rating: 3
Scanners March 18, 2003
Reviewer: kiro from Bulgaria
Hi
Just to give you additional info: There are keyboard handheld scanners that
could be connected together with keyboard and every scan of barcode will enter
encoded barcode like numbers.
We are using such kind of scanners with Oracle Forms 6i (text field) to enter
products barcode information.

Rating: 4
barcode scanner January 02, 2004
Reviewer: kero from Egypt
hi
how to connect between barcode and oracle forms?
i mean how to collect data from barcode into oracle form builder

Followup:
there is generally *nothing* to be done.

the barcode just "pretends to type in a number and hit a tab" in general (most
of them).

so, you position cursor in field, scan and wa-lah -- the code appears, as if you
typed it in on a keyboard.

if it doesn't work that way, you need to ask your barcode vendor "so, how do you
use this thing"

January 03, 2004 KK -- Thanks for the question regarding "Use of package OWA", version 4.0.8
You Asked
Can I use the procedure init_cgi_env in the package OWA?
and we said...
Yes you can -- I do it sometimes when I want to run a web
procedure from within SQLPLus and it requires some environment
variables to be set...

Note that if you call this -- you must supply the environnemt --
anything you supply will overwrite the existing environment.

Here is an example:

declare
    nm  owa.vc_arr;
    vl  owa.vc_arr;
begin
    nm(1) := 'REMOTE_ADDR';
    vl(1) := '1.2.3.4';
    owa.init_cgi_env( nm.count, nm, vl );
end;
/
Review & Followup
Rating: 5
Now the mystery is solved... March 05, 2002
Reviewer: Andrew from ca, usa
Tom

Thanks for this most useful tip. Now I've solved the mystery of the ORA-06502. I
wish I understood why NULL or an exception like "No cgi_env environment
detected" wasn't implemented when owa_util.get_cgi_env was coded.

SQL> set serveroutput on
SQL> begin
  2  DBMS_OUTPUT.PUT_LINE ( owa_util.get_cgi_env( 'REMOTE_ADDR' ));
  3  end;
  4  /
begin
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "SYS.OWA_UTIL", line 315
ORA-06512: at line 2

SQL>
SQL> declare
  2         nm  owa.vc_arr;
  3         vl  owa.vc_arr;
  4  begin
  5         nm(1) := 'REMOTE_ADDR';
  6         vl(1) := '1.2.3.4';
  7         owa.init_cgi_env( nm.count, nm, vl );
  8      DBMS_OUTPUT.PUT_LINE ( owa_util.get_cgi_env( 'REMOTE_ADDR' ));
  9  end;
 10  /
1.2.3.4

PL/SQL procedure successfully completed.

Rating: 3
Nice January 01, 2004
Reviewer: F.Maer from CT,USA
Dear Tom,
Well and wish the same from you.Regarding documentation for
 OWA_* PACKAGES,In OTN nothing exists as valid documents.The
Links are spurious and misleading and take lots of time to output a page.They
never lead to any of the OWA_* Packages.Could you  please look into this and
provide some valid links?
Waiting for your reply.
Bye!

Followup:
spurious?
misleading?

not sure I'm following what you mean there, what is spurious or misleading?
http://download-west.oracle.com/docs/cd/A97329_03/mix.902/q20102/toc.htm
specifically:

Oracle9i Application Server PL/SQL Web Toolkit Reference
http://download-west.oracle.com/docs/cd/A97329_03/web.902/a90101/toc.htm
Oracle9i Application Server mod_plsql User's Guide
http://download-west.oracle.com/docs/cd/A97329_03/web.902/a90855/toc.htm

      Rating: 4
Proper way to
      detect running from web  January 01, 2004

Reviewer:  Jack Wells  from Boca Raton, FL, USA

Tom,

What is the proper way to have a PL/SQL procedure detect that it is running from
the web (i.e. being processed with mod_plsql) vs. being run from SQL*Plus,
Forms, Reports, etc.?

I can trap for the 6502 error but it doesn't feel right to have production code
depend on an erroneous error like this.  Do you have a more elegant way to do
the following?

DECLARE
  FUNCTION called_from_web
    RETURN BOOLEAN
  IS
    lv   VARCHAR2 (100);
  BEGIN
    lv := owa_util.get_cgi_env ('server_name');
    RETURN TRUE;
  EXCEPTION
    WHEN VALUE_ERROR THEN
      RETURN FALSE;
  END called_from_web;
BEGIN
  IF called_from_web THEN
    htp.p ('This procedure is being called from the web');
  ELSE
    DBMS_OUTPUT.put_line ('This procedure is not being called from the web');
  END IF;
END;
/

This procedure is not being called from the web

PL/SQL procedure successfully completed.

          Followup:

that is what exceptions are all about?

begin
   select x into y from t where a = b;
exception
   when no_data_found then y := null;
end;

you'd have no worries with that right?  same difference here.  Your method is
not 100% (there is no method that is really) since I can call owa_util.init
myself and trick you into thinking I'm on the web (in fact, for testing, i often
do just that)

      Rating:
      3
Nice  January 02, 2004
Reviewer:
      Ram  from Bangalore,India
Dear Sir,
 1)What does "owa" stand for?
 2)Do you have a simple example for owa_util.calendarprint?
    How to use this procedure?
 Thanks in advance.
 Regards!

          Followup:

1) Oracle Web Agent

2)
http://download-west.oracle.com/docs/cd/A97329_03/web.902/a90101/psutil.htm#1005729

ops$tkyte@ORA9IR2> exec owa_util.calendarprint( 'select created, username, null
from all_users order by 1' );

PL/SQL procedure successfully completed.

put that in a page and see what it does (makes a nice little calendar with the
name of the user placed on their creation date)

      Rating: 4
Proper way
      to...  January 02, 2004
Reviewer:  Jack
      Wells  from Boca Raton, FL
Tom,

<quote>you'd have no worries with that right?</quote>

Well, I guess the only worry I have is that the OWA_UTIL package might get
updated in a future release and have a named exception or something put in which
avoids the "value error" that is currently happening.

I'm trying to create VPD policy/predicate procedure that sets the context for
the session.  If the procedure is called from the web, I get the web environment
and set the context based on the website that called it.  I want this same
procedure to work if called from SQL*Plus, Forms, etc. so I need to test from
where it was called from first (to avoid calling the owa_util.get_cgi_env
function).

Is my previous example (trapping the value error with an exception) "the best
way" to accomplish this?

          Followup:

as stated however -- very very very trivial to trick this out.

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;
/

your forms developers or someone in sqlplus can set the environment to anything
they feel like setting it to.  sorry -- but you'll have to come up with plan B
if you want this to be "secure" (which apparently you do since you are using VPD
and all).....

January 03, 2004 Kasthuri -- Thanks for the question regarding
      "Ref cursors as O/P params in SP- when are they closed?", version 8.1.5

      You Asked
Hi Tom,

 When we use ref cursors as output params in a stored proc,
 when does it get closed from the java app?
 does the cursor remain opened till they close the connection
 or gets closed if they close the resultset or their prepared
 statement? We have a # of procs returning cursors and an internet
 based app which is of concern with the max cursor init parameter.
 with minimal testing in our devpt site,i could see a min of 128 cursors
opened when i check the v$open_cursor. It is our concern to know
if this could become an issue when we move to production. we are still
under development.

Thanks in advance for you suggestion.
Regards
J.Kasthuri

      and we said...
v$open_cursor can be misleading -- it is cursors that have been opened at some
point and may (or may not be) still open.  It is useful in helping to track down
cursor leaks -- but it shows you more then just "really truely open" cursors.

A better query is something like:

     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");

That'll tell you how many cursors you really truely have open... Consider this
example (get_cur opens "select * from emp" in the stored procedure as a ref
cursor)

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 demo_pkg.get_cur( :1 ); end;";

      DriverManager.registerDriver
          (new oracle.jdbc.driver.OracleDriver());

      Connection conn=
        DriverManager.getConnection
        ("jdbc:oracle:oci8:@ora8idev",
          "scott", "tiger");

      showOpenCnt( conn, "Before Anything" );

      CallableStatement cstmt = conn.prepareCall(query);
      cstmt.registerOutParameter(1,OracleTypes.CURSOR);
      cstmt.execute();

      showOpenCnt( conn, "After prepare and execute" );

      ResultSet 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" );
  }
}

When I run it (with thick or thin, tried both) I get:

$ java curvar
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 fetching 10 rows...
3 opened cursors current
-----------------------
After but before any close
2 opened cursors current
-----------------------
After rset.close
2 opened cursors current
-----------------------
After cstmt.close
1 opened cursors current
-----------------------

I'll ALWAYS have 1 cursor open (the query itself to show the count) but note how
we get 2 cursors (3) after the prepare and execute.  1 for the begin ... end;
block and one for the select we open in the procedure.  As we are fetching rows
-- we still have 2 application cursors plus 1 for the v$mystat query.

Notice what happens when we exhaust the result set though -- the cursor for the
query seems to have disappeared (it did but don't count on it) before we closed
it.  After we close it the cursor count does not change but after we close the
begin ... end; block -- we get back to no cursors open.

follow to comment one

I cannot find any note 48871.996 (that doesn't look like valid note id?) in meta
link but no worry.

You say
"This is because the statistic 'opened cursors current' displays the TOTAL
number of opened cursors for the session, not necessarily what is currently
'open.'"

but if that were true, my example would show a continously INCREASING number
wouldn't it?  (yes, that was a rhetorical question).

Rather, my example shows the number of cursors opened going up and down (as we
expect).

The issue is v$open_cursor will show you MORE open cursors then you really have
(due to some cursor caching done by plsql and other environments).  You need to
goto v$sessstat to get the REAL currently opened number of cursors.

There may be cursors in v$open_cursor that are not counted in the v$sesstat view
for stat #3, they will be really closed if we need those slots again.

I stand by this example and what it shows.  If you want to know if you have a
cursor leak, use the code above.

Review &
      Followup

      Rating: 2
Statistic# 3 is
      not as accurate as it seems!   July 18, 2001

Reviewer:  Klaas  from The Netherlands
This query might work for your example, but it is not as accurate as you say it
is.

See Doc ID: 48871.996 on Metalink called
 V$OPEN_CURSOR and V$SESSTAT Stat #3 doesn't match

Statistic# 3 might results in a even higher number of open cursors than shown in
v$open_cursor.
This is because the statistic 'opened cursors current' displays the TOTAL number
of opened cursors for the session, not necessarily what is currently 'open.'

If you have access to Metalink you can check
Doc ID: 48871.996 called 'V$OPEN_CURSOR and V$SESSTAT Stat #3 doesn't match' for
some more detail.

      Rating: 5
Ref cursor or
      Output parameters which is better?  July 10, 2002

Reviewer:  Nagaraju  from USA
Tom,

thanks for the explanative example.
if have procedure that returns 5 output parameters.
is it a good way passing all the five with a single out put ref cursor
open myrefcur as select param1,param2,param3,param4,param5 from dual.

such that in my application when the front needs one more parameter to added as
an output without any change in the middle tier, i can add one more
parameter say param6..

is this a good way or it is better to have 6 different parameter.

pleas suggest.

thanks

          Followup:

tell me this -- if the middle tier DOESN'T change -- what is the purpose of this
6th output parameter?  I mean, why go to the bother of providing it if they
cannot use it (as they are not aware of it)

      Rating: 4
Ref cursor or
      Output parameters which is better?   July 10, 2002

Reviewer:  nagaraju
yeah, i know what you mean. for thie pecular procedure. middle get everything
from middle tier pass it to the front end(i.e to ASP page), so we handle it at
the ASP without changing the component(where this component would have been
called by someother ASP)

          Followup:

well, a ref cursor will be more "flexible" (you can change the size and shape
and not change the guy getting it in the middle).  It is overhead however -
something that if you can reasonable avoid doing, I would avoid doing it.

      Rating: 4
Ref cursor or
      Output parameters which is better?   July 10, 2002

Reviewer:  nagaraju
yeah, i know what you mean. for this pecular procedure. middle gets everything
from database pass it to the front end(i.e to ASP page), so we handle it at
the ASP on this 6th parameter without changing the component(where this
component would have been
called by someother ASPs)

      Rating: 4
What if my
      connection if from connection pool..  January 03, 2004

Reviewer:  Sankar  from India
Tom,
Thanks for your nice explaination on REF cursor closure. But in my Java
application, I'm getting the connection from connection pool. So closing of
connection in the application returns the connection back to the pool and the
pool does not closes the connection physically. Then the same connection is
handed over to the next client. So the number of cursors keep on increasing in
the system.

So what will be better way to handle this 'Maximium cursor exceded' problem in
case of connection pools?

Thanks!

Warm Regards,
Sankar

          Followup:

umm, say you opened a file in your jsp or whatever after you grabbed a
connection and you did not close that file.

What would you do to avoid getting "max files open in the operating system"

why -- you would CLOSE Them wouldn't you?

close the ref cursors (result sets) just as you close your prepared and callable
statements (no need to close a statement since we know the best java programmers
ignore the existence of the statement class alltogether -- he says tongue in
cheek)

January 03, 2004 Jacob Thomas -- Thanks for the question regarding
      "dbms_utility.get_time question", version

      You Asked
When I use the utility DBMS_UTILITY.GET_TIME  I get a
return number value where as I would like it to have it in the
form

2000-03-10 09:20:24.340

the script was
SQL> declare
2  test_time integer;
3  begin
4  test_time := DBMS_UTILITY.GET_TIME;
5  dbms_output.put_line(' The time is ' || test_time );
6   end;
7  /
The time is 2686499

Where as if I make test_time as date data type it gives error

SQL> declare
2  test_time date;
3  begin
4  test_time := DBMS_UTILITY.GET_TIME;
5  dbms_output.put_line(' The time is ' || test_time );
6   end;
7  /
declare
*
ERROR at line 1:
ORA-06550: line 4, column 27:
PLS-00382: expression is of wrong type
ORA-06550: line 4, column 1:
PL/SQL: Statement ignored
How can I get this in the form shown above as it is to be stored

as a real time storage into a column of the table during
inserting a row of data.

      and we said...
the timer returned from dbms_utility.get_time (which is the same
value you find in the v$ table v$timer) is simply a number of
hsecs from some ARBITRARY "epoch" in time.  Its like a real time
clock -- useful for measuring elapsed times between events --
but totally useless for telling what time it is.

This timer rolls to ZERO at some point and starts over.

You CANNOT use it to figure out the HSECS for a SYSDATE either.
This time is NOT in sync with the system clock.  It just clicks
100 times per second.  What I mean by this is some people have
tried to use something like:

to_char( sysdate, 'dd-mon-yyyy hh24:mi:ss' ) || '.' to_char(
mod(dbms_utility.get_time,100),'fm99'))

But the problem is that you'll get output like:

begin
    for i in 1 .. 1000 loop
        dbms_output.put_line( to_char( sysdate, 'dd-mon-yyyy
hh24:mi:ss' ) || '.'
          || to_char( mod(dbms_utility.get_time,100),'fm99'));
    end loop;
end;
/

10-mar-2000 07:41:51.87
10-mar-2000 07:41:51.87
10-mar-2000 07:41:51.87
10-mar-2000 07:41:51.87
10-mar-2000 07:41:51.87
10-mar-2000 07:41:51.87
10-mar-2000 07:41:51.87
10-mar-2000 07:41:51.87
10-mar-2000 07:41:51.88
10-mar-2000 07:41:52.88
10-mar-2000 07:41:52.88
10-mar-2000 07:41:52.88

Notice how the SYSDATE seconds rolled to 52 but the dbms_utility
clock did not roll to zero at the same time.  on my system --
they are about .12 seconds off from each other (yours will be
off by a different amount.  You have a 1 in 100 chance of
getting lucky and they'll be in sync but they won't be after a
restart for example).

If you want a finer grained time for now, you'll have to write
an EXTERNAL procedure in C with 8.0 or use Java in 8i.

Review &
      Followup

      Rating: 5
How to do this
      in Java  August 24, 2001
Reviewer:
      A reader
"If you want a finer grained time for now, you'll have to write an EXTERNAL
procedure in C with 8.0 or use Java in 8i."

Can you discuss how it is done in java, how can we capture time just before an
update and after an update and store them in the database table.

          Followup:

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:341817437103
for how to in C and java.

If you are trying to time an update -- see:

http://asktom.oracle.com/~tkyte/tkprof.html
the tool you want is TKPROF.  Otherwise, just get the time before and after the
update and insert it using plsql.

      Rating:
      3
OK  January 03, 2004
Reviewer:  Siva
       from Hyderbad India
Dear Tom,
 What does h in hsecs denote in V$timer view?
Bye!

          Followup:

hundredths of a second

hsecs

January 03, 2004 Michael S. -- Thanks for the question regarding
      "Clarification on rollback segments and tablespace extents", version 8.1.7

      You Asked
Greetings,

We are running Oracle 8.1.7 on Sun Solaris and all of our databases use locally
managed tablespaces for everything.  (Except system, of course.  When will
Oracle ever get around to "fixing" that?)

I would like to clarify several things regarding rollback segments and the
number of extents in locally managed tablespaces.  First rollback.

In several of your responses to previous questions, you have indicated that you
like to place individual rollback segments into their own tablespaces with
INITIAL = NEXT = 1M.

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:233214228495
Also, you like to start with 15 or so rollback segments -- each with
minextents=25 and maxextents=100.  That would create 15 25meg rollback segments
that can grow to 100meg each.

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:768625836258
Translating that to locally managed tablespaces, does that mean you do something
like the following?

create tablespace RBS_01
datafile '/xxx/xxx/xxx/rbs_01.dbf'
size 25m reuse
autoextend on
maxsize 100m
extent management local uniform size 1m;

Then:

Create rollback segment rbs_01 tablespace rbs_01;
alter rollback segment rbs_01 online;

You would then increment the names to accommodate 15 tablespaces/rollback
segments and repeat as necessary. Side question:  Is there any way to put the
above code in a loop to do 15 tablespace/rollback segment creations?

I really like this method of creating and managing rollback segments however,
Yet, I don't fully understand __WHY__ you do it this way.  Could you provide an
explanation or some real-world benefits to such a methodology? I suppose that I
can't see the forest for the trees. =)

Now for the number of extents in locally managed tablespaces.
I am getting ready to build a large hybrid OLTP/Warehouse database which will
grow at a rate of 1.5 Tb to 2.0 Tb a year with a projected 3 year life span. It
will be used for data collection from multiple product assembly lines and the
data must be held, and red idly accessible, for three years for quality control
reasons. (The application front-end does not support a "production" instance and
an "archive" instance.) Are there any potential problems with huge (terabyte and
larger) tablespaces with tens of thousands of extents?

The old school of thought dictates that a tablespace must have as few extents as
possible for better performance.  Does this hold true for locally managed
tablespaces of such huge size -- or any size?  Or is this a benefit of locally
managed tablespaces:  It no longer matters how many extents there are.  Any
suggestions?

Thanks for your time,
Mike

      and we said...
the create tablespace for rbs_01 would actually be 25m + 64k (for the locally
managed tablespace overhead) -- else, it'll immediately grow to 26m.  but yes,
that would do it.

When creating the rollback segment itself, you would include storage parameters
that set minextents and maxextents as well.

In order to "loop" -- I generally use a script like:

----------------------r esize_rbs.sql ------------------------
alter rollback segment rbs_&&1 offline;
drop rollback segment rbs_&&1;
alter database datafile '/d&&1/dbs/rbs_ts_&&1..dbf' resize 24968;
create rollback segment rbs_&&1
storage ( initial 1024k next 1024k minextents 25 ) tablespace rbs_ts_&&1;
alter rollback segment rbs_&&1 online;
-------------------------------------------------------

and then call

SQL> @resize_rbs 01

for example.  You can do this in dynamic sql with plsq:

begin
   for i in 1 .. 2 loop
      execute immediate 'create tablespace rbs_' || to_char(i,'fm00') || '....';
      .....
   end loop;
end;

why do i do this:

o rollback segments must have equi-sized extents.
o rollback segments should be allocated large to avoid 1555's (which I've never
hit).  search for 1555 on my site for more details.
o rollback segments should be plentiful for the amount of transactions you do.

I've just found the above rules of thumb to be a good starting point for
databases in general.  Some need more -- few need less.  It is easy to
understand, trivial to manage.  I never use optimial, prefering to resize them
manually before a backup or something.

I would avoid 10's of thousands of extents -- your application is SCREAMING out
for PARTITIONING.  For large objects -- you might use a 2gig extent size.  Don't
be afraid of hundreds of extents.  10's of thousands, not sure if I would go
there (or not).  Partitioning -- you *need* that for what you describe.  You
need the rolling windows it will provide and the "divide and conquer" approach
it will facilitate.

The old school of thought was *never* correct.  It was always a hunch and never
true.  It never mattered.

/

Review &
      Followup

      Rating:
      4
Interesting  November 11, 2001
Reviewer:
      Doug C  from CT, USA
Couple of follow ups
You said that you should add 64K for locally managed overhead.  According to the
SAFE method document, you should add 2*block size.  Which would be appropriate?
Second - it seems that locally managed tablespace storage parameters seem to
take precedence over the rollback storage clause in some cases.  For example, if
I have a locally managed tablespace defined uniform with minextents 1, no matter
what I define in the rollback segment storage clause, the mins are all added
into the initial, and the next and minextents come straight from the locally
managed clause.  Therefore, is the best thing to do to create a locally managed
tablespace say initial=next=1M minextents say 5, maxextents unlimited and put an
upper limit on the datafile?  That way, it's just create the rollback segment
and it'll inherit the right thing to do and let it ride.

          Followup:

I don't know what SAFE is personally, but the overhead is 64k.  Its easy to test
this yourself.  I have an 8k blocksize so, 2*blocksize = 16k.  If I try an LMT
with 128k extents and a table with 2 extents, I get:

ops$tkyte@ORA717DEV.US.ORACLE.COM> create tablespace testing_lmt
  2  datafile '/tmp/testing_lmt.dbf'
  3  size 272k                        /*  2*128 + 16 */
  4  extent management local
  5  uniform size 128k
  6  /

Tablespace created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> create table t ( x int ) storage ( minextents
2 ) tablespace testing_lmt
  2  /
create table t ( x int ) storage ( minextents 2 ) tablespace testing_lmt
*
ERROR at line 1:
ORA-01659: unable to allocate MINEXTENTS beyond 1 in tablespace TESTING_LMT

not enough room -- but it should be according to SAFE?

           ops$tkyte@ORA717DEV.US.ORACLE.COM> drop tablespace testing_lmt
including contents;
Tablespace dropped.

ops$tkyte@ORA717DEV.US.ORACLE.COM> host rm -f /tmp/testing_lmt.dbf;

ops$tkyte@ORA717DEV.US.ORACLE.COM> create tablespace testing_lmt
  2  datafile '/tmp/testing_lmt.dbf'
  3  size 320k                /* 2*128 + 64 */
  4  extent management local
  5  uniform size 128k
  6  /
Tablespace created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> create table t ( x int ) storage ( minextents
2 ) tablespace testing_lmt
  2  /

Table created.

That fits perfectly and if you look at the free space/used space in the
tablespace -- it would show you 320k is totally used.

As for the taking precendence -- LMT storage parameters ALWAYS take precendence
over the storage clause of anything!

We'll take the minextents applied to next and add the initial to get the LMT
equivalent of an "initial".  The storage parameters in the data dictionary can
seem misleading cause we'll just turn your initial  and next and minextents into
ONE initial request which will be satisfied by some number of equi-sized
extents.

the maxextents for a rollback segment in an LMT is 32k and its unlimited for all
other objects -- no changing that.  Use the datafile maxsize to regulate it
smaller.

I say just set initial = next = LMT extent size, minextents = number if extents
you want it to be minimally and forget it from then on it.

      Rating:
      4
  November 11, 2001
Reviewer:
      Abdal-Rahman Agha  from Yemen
Helpful information.

      Rating:
      4
  November 12, 2001
Reviewer:
      Venkat  from NewDelhi,India
TOM, Good explanation and ur book is a "BIBLE of ORACLE".
Doug,can u tell me where can I find the SAFE document.

TIA

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:730289259844
and look for the "start living...." comment.  Its that document.

      Rating: 5
I think SAFE
      document made error  November 12, 2001
Reviewer:
      A reader  from TX
From the SAFE document, in section 2.1.11,

2.1.11 Size Files to be a multiple of the tablespace extent size plus 1 block
The only possible extent fragmentation in a tablespace with uniform extent sizes
is fragmentation at the end of every file because the extent size does not match
the size of the file. Oracle uses the first block of every file to maintain
internal bookkeeping information. Therefore, when allocating files to a
tablespace, make sure to set the size of the file to be a multiple of the extent
size for the tablespace plus one block. Thus, if the DBA wants to create a
tablespace with an extent size of 5120K, and a total useful space of 200MB, and
if the block size for the database is 4K, then the following
specification should be used (204804 = 200*1024 + 4)
CREATE TABLESPACE foo datafile ‘bar’ size 204804K;

I add 64k to each LMT datafile as TOM said, I think the Oracle SAFE document
made a mistake!

      Rating:
      4
  February 01, 2002
Reviewer:
      Subbarao  from India
Dear Tom,
I have created a tablespace with this syntax
CREATE TABLESPACE "TT"
    LOGGING
    DATAFILE 'D:\ORA81\ORADATA\TT.ora' SIZE 5M EXTENT   MANAGEMENT   LOCAL
UNIFORM SIZE 8K

then i am trying to create a rollback segment like this, I am getting an error.

CREATE PUBLIC ROLLBACK SEGMENT "RBS2"
    TABLESPACE "TT"
    STORAGE ( INITIAL 16K NEXT 8K OPTIMAL 64K MINEXTENTS 2
    MAXEXTENTS UNLIMITED)
ALTER ROLLBACK SEGMENT "RBS2" ONLINE

ora-03237
03237, 00000, "Initial Extent of specified size cannot be allocated"
// *Cause:  Too large a size for an initial extent due to freelist//
  group specification
// *Action: Reduce number of freelist groups specified for segment

I doubted and created a table in the same tablespace, that is going thru.
Regarding freelist groups i didn't specify any parameter. what could be the
problem.

Is there any concept like having dedicated rollback segment to a tablespace.
Right now i am planning to have 10 rbs in a single tablespace. I am thinking all
the transactions will be using these rbs. Am I correct in this ? Please comment
on this.

          Followup:

Sorry -- i'm not going to look at this one too much.  Reasoning:

o 8k extents are way too small for a rollback segment.
o rollback segments should have equi sized extents.
o 64k is trivially small for a rollback segment, I wouldn't even use an EXTENT
that small for an RBS.

Use REAL sizes.  Don't use impractical ones like you are.

(btw, rollback segment extents must have 4 blocks.  I'll guess that you don't
have a 2k block size.... go from there)...

It is ok to have all rbs in one tablespace if you like, in 9i -- we promote the
use of a single UNDO tablespace instead of individual rollback segments in fact.

      Rating: 5
UNDO tablespace
      - always the way to go?  September 04, 2002

Reviewer:  steve  from Montreal
Hi Tom,

We are moving from 8.1.7 to 9.2.0.1 (Solaris) and are
now faced with the decision of how to handle rollback
segments. This is a data warehouse in the 2 terabyte
range (3 months retention). I read through most of the
archives on UNDO TABLESPACES. Based on your last statement
in this article:

"we promote the use of a single UNDO tablespace instead of individual rollback
segments in fact. "

Q1: Does this mean that it's pretty much a "no brainer"
    to use an UNDO tablespace instead of rollback segments.
    Or is it still a bit of a "it depends" type situation.

Q2: For some reason, AUTOEXTEND ON makes me nervous
    What's your take on it (for the UNDO TABLESPACE)?

Thanks.

Steve

          Followup:

q1) I find it a no-brainer.  I cannot imagine going back to individual rollback
segments personally.

q2) depends on your needs.  Do you have a DBA sitting there 24x7 who will grow
the tablespace as it nears needing extension?  If not, autoextend for the
datafiles (such as system, undo, temp) onto disks that have some capacity -- for
the "hey, we needed some space for a bit" type operations is pretty cool in my
opinion.  You can always come back and reclaim it later.  So, if you have some
disk sitting around that is used for "scratch stuff", you might let your files
autoextend there -- for when it is needed.

      Rating: 5
Please
      advise!  November 26, 2002
Reviewer:
      Yogeeraj  from Mauritius
Hello,

Thank you the clear explanation about the creation for rollback segments that
you have given in answer to the Original question. Now my question about how
many will i need?

Below a few statistics from my current database (that will be soon transferred
to another server):
===========================================================
system@CMTDB.CMT.MU> SELECT substr(name,1,10), extents, rssize, aveactive,
aveshrink, extends,shrinks
FROM v$rollname rn, v$rollstat rs
WHERE rn.usn = rs.usn;

SUBSTR(NAM    EXTENTS     RSSIZE  AVEACTIVE  AVESHRINK    EXTENDS    SHRINKS
__________ __________ __________ __________ __________ __________ __________
SYSTEM             10     827392          0          0          0          0
RBS0                8    4186112     526157          0          0          0
RBS1                8    4186112    1956913    4653056         71          8
RBS2                8    4186112     605252          0          0          0
RBS3                8    4186112     717729    3670016          7          1
RBS4                8    4186112     542683          0          0          0
RBS5                8    4186112     617348    3145728          6          1
RBS6                8    4186112     590018          0          0          0
RBS7                8    4186112     723508          0          0          0
RBS8                8    4186112     865569    4805973         55          6
RBS9                8    4186112     537650          0          0          0
RBS10               8    4186112     729842          0          0          0
RBS11               8    4186112     871015    1048576          2          1
RBS12               8    4186112     767571    1048576          6          3
RBS13               8    4186112     607657          0          0          0
RBS14               8    4186112     611434          0          0          0
RBS15               8    4186112     602380          0          0          0
RBS16               8    4186112     577176          0          0          0
RBS17               8    4186112     544919    4718592         18          2
RBS18               8    4186112     597384    1572864          6          2
RBS19               8    4186112     651884          0          0          0
RBS20               8    4186112    2148267    3670016          7          1
RBS21               8    4186112     619422          0          0          0
RBS22               8    4186112     956216    1572864          3          1
RBS23               8    4186112     641275     524288          1          1
RBS24               8    4186112     579881          0          0          0
RBS25               8    4186112     626852          0          0          0
RBS26               8    4186112     617793     524288          1          1
RBS27               8    4186112     598643    2621440          5          1
RBS28               8    4186112     841133    2097152          8          2

30 rows selected.

Elapsed: 00:00:00.08
system@CMTDB.CMT.MU>
============================================================

On my "new database":
============================================================
SQL> SELECT substr(name,1,10), extents, rssize, aveactive, aveshrink,
extends,shrinks
FROM v$rollname rn, v$rollstat rs
WHERE rn.usn = rs.usn;

SUBSTR(NAM    EXTENTS     RSSIZE  AVEACTIVE  AVESHRINK    EXTENDS    SHRINKS
---------- ---------- ---------- ---------- ---------- ---------- ----------
SYSTEM             10     827392          0          0          0          0
RBS_01             26   27254784          0          0          0          0
RBS_02             25   26206208          0          0          0          0
RBS_03             25   26206208          0          0          0          0
RBS_04             25   26206208          0          0          0          0
RBS_05             25   26206208          0          0          0          0
RBS_06             25   26206208          0          0          0          0
RBS_07             25   26206208          0          0          0          0
RBS_08             26   27254784          0          0          0          0

9 rows selected.

SQL>
============================================================

how can i query my current production database to get the right number?

thank you a lot for a reply

Best Regards
Yogeeraj

          Followup:

the right number of what?

      Rating: 5
Number of
      rollback segments  November 26, 2002
Reviewer:
      Yogeeraj  from Mauritius
oops! sorry.
I meant the right number of rollback segments.

thanks
Yogeeraj

          Followup:

I guess you would create more.

      Rating: 5
excellent
      again  March 27, 2003
Reviewer:  mary
       from dover de
Tom, per way above --

the create tablespace for rbs_01 would actually be 25m + 64k (for the locally
managed tablespace overhead) -- else, it'll immediately grow to 26m.  but yes,
that would do it.

----

Would this be 25064, 25664 (1024*25+64) or 24968(appears in your code sample
right below above description)?

          Followup:

that 24968 was cut and pasted from a live script that was using dmt's in days
gone by.  that file would have immediately gone to 25m

      Rating: 5
autoextend
      increment size  April 01, 2003
Reviewer:
      mary  from mwagner@mail.dot.state.de.us
Tom, do you have any recommendations for the auto-extend increment size for the
rollback datafiles?

          Followup:

the size of the extents or N times the size of the extents.  since the file will
grow by that much anyway at least...

      Rating: 4
Rollback
      Segment sizing...  April 07, 2003
Reviewer:
      Shailesh  from India
Hi Tom,

Sizing of Rollback segemnt I find it really confusing. We are using Oracle 8.1.6
on Windows 2000. We have defined following enough para, But still get error
"ORA-01555: snapshot too old: rollback segment number 5 with name "RB4" too
small".

Tablespace :-
--------------
CREATE TABLESPACE RBS DATAFILE 'E:\ORANT\oradata\PortOra\RBS\rbs01.dat' SIZE
600M REUSE
EXTENT MANAGEMENT LOCAL UNIFORM;

ALTER DATABASE DATAFILE 'E:\ORANT\oradata\PortOra\RBS\rbs01.dat' AUTOEXTEND ON
NEXT 100M Maxsize 4000M;

Rollback Segement :-
------------------
CREATE PUBLIC ROLLBACK SEGMENT RB1 TABLESPACE RBS
STORAGE ( OPTIMAL 4096K );

Total 15 Rollback segments are created using above syntax. Still get an error
"Rollback Segment too small".

1) How can I set proper size for Rollback segments?
2) How to Calculate transaction size?

Please guide me.

Thanks & Regards,

Shailesh

          Followup:

you need to make them (the rb1..rb15 segments) permanently larger.  as it
exists, you have 15, 2 megabyte rbs's.  Me, on a fairly "busy" system, I might
start with 10 or 15, 25 megabyte rbs's.

The problem is -- yours are too small, they wrap too quickly for your queries.
You need to permanently allocate them larger using minextents.  Since your
extent sizes are 1m in size -- minextents of 15 or so (and LOSE that optimal
thing all together) would do it perhaps.

If you know how fast you wrap (v$rollstat and/or statspack can help you there)
and you know the duration of your long running queries - you can figure it out.

Say you wrap every 5 minutes on average.  Say you have a 10 minute long query.
You need 2+ times the RBS so you wrap 10+ minutes on average.

      Rating: 4
Optimal
      value?  April 11, 2003
Reviewer:  Shailesh
       from India
Hi Tom,

Thanks for your explanation, I tried it and increased optimal size. To make this
I need to take all RBS segments offline, drop and recreate RBS tablespace.
following way...

CREATE TABLESPACE RBS DATAFILE 'E:\ORANT\oradata\PortOra\RBS\rbs01.dat' SIZE
600M REUSE
EXTENT MANAGEMENT LOCAL UNIFORM SIZE 20M;

and may be specify more optimal value

CREATE PUBLIC ROLLBACK SEGMENT RB1 TABLESPACE RBS
STORAGE ( OPTIMAL 20M );

Is there any other way to do this?

Do I need to go on trying till error disappears?

Is there any way or method to calculate transaction size and then define RBS
parameters accordingly.

Thanks & Regards,

Shailesh

          Followup:

well, i did say (and LOSE that optimal
thing all together)

but now you have 2 20m extents -- and since an extent cannot be reused whilst an
active transaction is in there -- I might prefer 20 2m extents instead (extent
size 2m, minextents 20)

You can run statspack for say 15-30 minutes.
See how much UNDO you generate during your busy times.
Figure out your long running query.
Compute how much permanently allocated undo you need in order to go for the time
your longest running query goes for without reusing the undo.

Eg: if you generate 2 meg of undo per minute.  You have a query that runs 10
minutes.  You need at least 20meg of undo allocated.

      Rating: 4
Some
      clarifications  September 29, 2003
Reviewer:
      Sanjaya Balasuriya  from Columbo, Sri Lanka
Tom,

I want a clarification on 2 conflicting concepts.

As I read in your site and I was knowing the correct way to create a rollback
segment is as;

create rollback segment rbs_001
tablespace rbs
storage ( initial 1M
          next 1M
          minextents 20
          maxextents 100 );

This creates rollback segmenet with equal initial and next extents.

But in a book I recently read (Oracle8i DBA Bible), it gives an example like;

create rollback segment rbs1
tablespace somrthing
storage ( initial 1M
          minextetns 5);

and they say that initial extent size is 5M.

Which is true ?

Thanks in advance.

-San

          Followup:

they meant to say perhaps that the initial allocation of space will be 5m (if
the tablespace isn't a LMT -- in which case, it could be almost ANY size of
course :)

the initial extent is 1m (in a DMT)...
the space allocated initially would be 5m (in a DMT)...

      Rating: 5
OEM undo
      sizing  January 03, 2004
Reviewer:  reader
       from jobmiller@yahoo.com
Tom,

What does OEM look at to determine the amount of UNDO needed to maintain a
retention period of X minutes?

OEM gives a worst case scenario, and an average number that shows how much space
a given UNDO_RETENTION will likely require, but I was just wondering how/when it
tracks this.  Does it take its own snapshots periodically to get these numbers?

          Followup:

looks at the current/recent workload (so you need to use that advisor at or
around "normal processing times").

it looks at what you are generating and multiplies basically (to put it very
simply)

its all in the v$ tables, you can see how much undo you generate in x units of
time, they record it all.  look at views like v$rollstat and v$undostat

January 03, 2004 Gaetano -- Thanks for the question regarding "SQL
      Loader and NLS_LANG", version 8.1.7

      You Asked
Hi,
I need to load into oracle a text file with some decimal number in it.
My database recognize the decimal separator as "." and in the flat file numbers
are in the format 19.89
The problem is that the pc that I use for the import uses the "," as separator
because uses another "regional settings". I usually solve this kind of problem
with an ALTER SESSION SET .......
How can I tell to SQLLOADER that I need to change his session ?

Maybe this is a newbie question but I've searched for half the day without any
result.

Thanx
Gaetano

      and we said...
set NLS environment variables.

See

http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76966/ch2.htm#91066

On unix for example:

$ setenv NLS_NUMERIC_CHARACTERS ",."

Review &
      Followup

      Rating: 4
It works
        July 08, 2002
Reviewer:  Gaetano Galletta
       from Italy
Thanx a lot, I've tried and now I succed in loading data correctly.
I've got a Win XP environment so I simply "substitute" your unix-style sintax
with windows style.

Thanx again
Gaetano

      Rating: 5
Loading Mutiple
      Languages from the same datafile using SQL*Loader  January
      02, 2004
Reviewer:  Sami  from NJ,USA
Dear Tom,

Happy New Year!

I have to load both French and Japanese characters from datafile into tables
using SQL*loader.

DB character set is UTF8 (version 8.1.7.3)
Client : Unix (Sun Solaris)

If I set NLS_LANG=AMERICAN_AMERICA.UTF8 then French characters are NOT getting
loaded properly(garbled-> non-readable format) but Japanese characters are
getting loaded without any problem.

If I set NLS_LANG=French then French characters are  getting loaded properly( no
garbled issue) but Japanese characters are NOT getting loaded(garbled).

French Data
===========
Laforêt
François
modèle
démineur

Japanase Data
===============
çµžã‚Šè¾¼ã¿å±¥æ´
çµžã‚Šè¾¼ã¿ã®è¨å®š

I copied sample FRENCH & JAPANESE characters above.
Even here in BROWSER,
(A)if the BROWSER Encoding (please select View->Encoding) is set to
Unicode(UTF-8)then Japanese Data (characters) are in READABLE format.

(B)if the BROWSER Encoding (please select View->Encoding) is set to Wester
European(ISO),then French Data (characters) are in READABLE format.

Questions:
==========
1)Why french characters are not getting loaded properly when I set
NLS_LANG=AMERICAN_AMEICA.UTF8

2)How do I load mutiple langauges content from the same datafile into db table.

Kindly through some light on this?

Thanks for your time.

          Followup:

1) are the input files in fact UTF8 encoded when the data is french?

2) make sure they are all encoded the same way?

      Rating: 5
Diff between
      "Unicode" and "UTF-8" encoding  January 03, 2004

Reviewer:  Sami  from NJ,USA
Dear Tom,
<asktom>
"make sure they are all encoded the same way"
</asktom>

You are correct. They were not encoded in same way, that was the problem. Thanks
a lot. It is working now.

Sorry for asking bit off-topic question.If possible please...

We are getting input data (text) file from user( they use NOTEPAD/Textpad). In
NOTEPAD there are two encoding stuff (UTF-8 and Unicode).

1)What is the difference between "Unicode" and "UTF-8" encoding?

2)In Unix Vi editor, how to identify the encoding type?
For example, I want to store abc.txt in UTF-8 encoding using VI. How do I do
that?

          Followup:

1) beyond the scope of "ask the database guy" :)

google it, there is lots of information out there.  They are just different
encoding schemes, like "ascii" and "ebcidic"

2) the files are "flagged", i don't know all of the specifics (something to
research on the web ;)

for example:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96652/ch06.htm#1010644
shows there may be byte order markers in there (BOM's) and tools that are
"utf/unicode" aware respect that.

Sorry -- sort of out of my domain knowledge area, this is when I would go start
asking questions myself :)

      Rating: 3
Loading French
      & Diff between "Unicode" and "UTF-8" encoding  January
      06, 2004
Reviewer:  Sergiusz  from Warszawa, Poland

As this is my area of expertise, let me comment :-)

To load French encoded in WE8ISO8859P1 (most probably) and Japanese encoded in
UTF8 (most probably), put them into separate files and use the control files's
CHARACTERSET clause to specify the character set for each file separately.
Oracle8i/9i Utilities Guide has more info on the clause.

If the text in mulitple encodings is mixed in one file, then you cannot load it
with SQL*Loader in one run.

Unicode is a character set, i.e. a set of characters that are numbered from 0 to
2**31-1 (not all numbers are already assigned, actually most are not assigned
but reserved).  A Unicode encoding scheme is a way to store these numbers as
bytes in computer memory and on disk (serialization).  Unicode has a few
encoding schemes: UTF-7, UTF-8, UTF-16, UTF-16LE, UTF-16BE, UTF-32, UTF-32LE,
UTF-32LE, CESU-8.  More details on www.unicode.org and otn.oracle.com (under
Globalization).

Best regards,
Sergiusz

      Rating: 3
Unicode vs.
      UTF-8  January 06, 2004
Reviewer:  Sergiusz
       from Warszawa, Poland
And specifically for standard Windows Notepad:

"Unicode" means UTF-16LE + Byte Order Mark, i.e. character codes are two bytes
each, little-endian (Intel), plus (0xFF,0xFE) in the two first bytes of the
file.  These two bytes denote the little-endian byte ordering.  The reverse
sequence (0xFE, 0xFF) means the big-endian order.

SQL*Loader 9i can recognize the Byte Order Mark of an UTF-16 files and interpret
the file accordingly if the CHARACTERSET clause in the control file is set to
UTF16.

"UTF-8" means UTF-8, i.e. character codes are one, two, three or four bytes each
depending on the range of their original unicode code point (i.e. number in the
character set).

In vi you cannot set the encoding.  If your vi version (Unix) is properly
globalized, it will use the encoding from the LANG environment variable (Unix
locale).  Otherwise it will work in ASCII.

Best regards,
Sergiusz

      Rating: 5
Thanks
      Sergiusz  January 07, 2004
Reviewer:  Sami
       from NJ,USA
I read your comments in Metalink as well. Really helpful.

January 03, 2004 Saibabu -- Thanks for the question regarding
      "Background processes memory consumption", version 8.0.6

      You Asked
We are using Solaris 2.6 and Oracle 8.0.6 database.There 300 connections are
established with database.290 connections are from JDBC 1-0 client.Some of the
JDBC connections are as old as 2 months.
We have 1.5Gb swap space.All Oracle background processes are taking 312Mb of
memory,which is very abnormal.Even one or two JDBC connections are taking 300Mb
memory.We have no idea why it's comsuming this much memory.We are periodically
restarting the database to avoid this.Could you please explaing why it's
happening,what measures can be taken and in what situations it could happen.
Thanks
Sai.

      and we said...
It is NOT taking 300 meg of memory.  Your SGA is 300 meg I bet.  Solaris shows
the SGA (SHARED memory) as part of the process memory.

If you have dead clients, you could implement dead client detection at the
networking layer.  refer to the net8 admin guides for configuration guides on
this.

Use pmap to measure how much memory a process REALLY is taking.  top, ps, et.
al. show you meaningless information when it comes to processes that use shared
memory.  think about it, 300 processes at 300meg each would be 90,000 meg -- 88
GIG, you don't have that much physical+swap -- it would be simply amazing if
your machine actually had 88 gig of memory allocated to processes.

follow up to comment about "not getting it"

I believe I got it  -- he said

<quote>
All Oracle background processes are taking 312Mb of  memory,which is very
abnormal.

Even one or two JDBC connections are taking 300Mb memory.
</quote>

Now, maybe if he just said the first sentence, it could be taken one way or the
other (it would be very nice if 300 Oracle background processes took 300 meg of
RAM -- my experience is they would take more and 300 meg would be very nice)..

Given the second sentence, it leads me to believe that they are using TOP,
looking at line like this:

load averages:  1.15,  1.35,  1.44                     07:12:52
145 processes: 144 sleeping, 1 on cpu
CPU states: 95.0% idle,  2.6% user,  1.2% kernel,  1.2% iowait,  0.0% swap
Memory: 2048M real, 119M free, 193M swap in use, 3903M swap free

  PID USERNAME THR PRI NICE  SIZE   RES STATE   TIME    CPU COMMAND
 5241 ora816     1 -14    0  297M  279M sleep   0:02  0.72% oracle
 4945 ora816     1  -4    0  297M  279M sleep   0:03  0.42% oracle
 5322 ora816     1  -7    0  297M  280M sleep   0:01  0.40% oracle

seeing the 297meg (my SGA is 285 meg) and assuming that process has 297m of ram
allocated to it.  This question comes up SO FREQUENTLY, that I am very sure this
is the case here as well.

The numbers just don't add up -- 300 processes would take more then 300meg so I
cannot be 300 meg total.

300 meg / process is way too high, they must be adding in the SGA.

Also, I never said that backgrounds are shown as part of the SGA.

       "background processes are not shown as part of Sga.."

that does not even make sense.  the SGA is memory -- processes are not part of
memory.  I said "the SGA (SHARED memory) as part of the process memory".  I know
that on solaris we are multi-process (i live there, going to windows when I
must).  I also know that the SGA will show up as part of the process memory in
top, ps, and many other utilities.  A more in depth tool such as pmap is needed
to get to the bottom of memory here.

I stand by the response.

Review &
      Followup

      Rating: 2
You Didn't get
      it i guess?  April 30, 2001
Reviewer:  Usama
      Munir Dar  from Islamabad, Pakistan
Hi Tom, i don't think u get it, the guy was asking that the background processes
ar cummulativly taking 300 megs..not as indviduals? and besides in Solaris the
background processes are not shown as part of Sga..they are identified as
seperate unlike NT. what do u say?

      Rating: 5
Great
      response  April 30, 2001
Reviewer:  Joel
       from Columbus, OH USA
Tom,

Good response and good followup.  Actually, I found your response to the comment
very insightful.

Thanks for the great explanation.

      Rating:
      4
Reader  May 09, 2002
Reviewer:
      A reader
Tom,

On 8.1.6 system,  I did a "ps -efl" and for smon
alone the RSS part of the memory is 9*16k(pagesize) = 144K

On a 8.1.7 system for smon RSS part is 1374*16k = 21 M

Is the RSS part of the processes function of any usage ?
I thought it should be almost constant for a given
version of the database on a given platform

Thanks

          Followup:

show me the 816 vs 817 output.  Not that I don't believe you -- but I don't ;)

ps reports the shared memory segment.  use PMAP, not ps.

      Rating:
      4
Reader  May 10, 2002
Reviewer:
      A reader
Tom,

Sorry, I compared the wrong listings. The values of
8.1.6 and 8.1.7 are almost the same. Still 21 M is
bit on the high side, I expected from 1.5 to 2 M

If RSS include shared  memory from SGA (DB_BLOCK_BUFFERS
and SHARED_POOL), what is a formuala to estimate how
much of SGA will show up in RSS. Also does memory
pressure has effect on inflating the value
as well

Thanks

          Followup:

PMAP PMAP PMAP PMAP PMAP

there is *no* formula

PMAP PMAP PMAP PMAP PMAP

just use it.

      Rating:
      4
Reader  May 10, 2002
Reviewer:
      A reader
Tom,

Not that I do not want to use "pmap", I like to
understand the effect of usage and memory presure on
"ps -efl" results.

From ixora site
"
The RSS (resident set size) shown by ps is not a valid indication of PGA size.
It includes the SGA (db_block_buffers & shared_pool_size) and is sensitive to
memory pressure on the system as well as to PGA size.
"

          Followup:

ps is useless in this respect.

use pmap. It is a matter of having to use it if you want anything meaningful.
Period.

I basically say the same exact thing as Steve Adams did there.

      Rating:
      4
Reader  May 13, 2002
Reviewer:
      A reader
SQL*Plus: Release 8.1.6.0.0 - Production on Mon May 13 09:46:59 2002

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

Connected to:
Oracle8i Enterprise Edition Release 8.1.6.0.0 - Production
JServer Release 8.1.6.0.0 - Production

SQL> sho sga

Total System Global Area  109445748 bytes
Fixed Size                   103028 bytes
Variable Size              34533376 bytes
Database Buffers           73728000 bytes
Redo Buffers                1081344 bytes
SQL> !/etc/whodo
Mon May 13 09:47:25 2002
cindy

ttyq0        oracle8   9:45
    pts/0        18860397    0:00 csh
    pts/0        18852790    0:00 sh
    pts/0        18654984    0:00 sqlplus
    pts/0        18224126    0:00 csh
    pts/0        18807234    0:00 whodo
      ?          18835160    0:00 oracle

SQL> !ps -efl|grep 18654984
  0 S  oracle8   18835160   18654984  0  20 20  * 10621:804   1e0dd520 09:46:59
?       0:00 oracleTEST (DESCRIPTION=(LOCAL=Y
  0 S  oracle8   18654984   18852790  0  20 20  *  1186:489   485af798 09:46:58
pts/0   0:00 sqlplus internal
  0 S  oracle8   18884159   18812778  0  20 20  *   111:52     ee58d20 09:47:49
pts/0   0:00 grep 18654984
  0 S  oracle8   18812778   18654984  0  20 20  *    38:29    5a6798b8 09:47:49
pts/0   0:00 /bin/csh -c ps -efl|grep 18654984

Comment  test1:
--------------
    Here the SGA = 109,445,748 bytes
      The new session shows RSS = 804

SQL> shutdown immediate
Database closed.
Database dismounted.
ORACLE instance shut down.
SQL> !vi $ORACLE_HOME/dbs/initTEST.ora

#db_block_buffers        = 9000
db_block_buffers        = 900

SQL> startup
ORACLE instance started.

Total System Global Area   41009780 bytes
Fixed Size                   103028 bytes
Variable Size              32452608 bytes
Database Buffers            7372800 bytes
Redo Buffers                1081344 bytes
Database mounted.
Database opened.
SQL> exit
Disconnected from Oracle8i Enterprise Edition Release 8.1.6.0.0 - Production
JServer Release 8.1.6.0.0 - Production
$ sql

SQL*Plus: Release 8.1.6.0.0 - Production on Mon May 13 09:51:50 2002

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

Connected to:
Oracle8i Enterprise Edition Release 8.1.6.0.0 - Production
JServer Release 8.1.6.0.0 - Production

SQL> !/etc/whodo
Mon May 13 09:51:57 2002
cindy

ttyq0        oracle8   9:45
    pts/0        18860397    0:00 csh
    pts/0        18852790    0:00 sh
    pts/0        18851864    0:00 sqlplus
    pts/0        18850756    0:00 csh
    pts/0        18740862    0:00 whodo
      ?          18842492    0:00 oracle

SQL> !ps -efl|grep 18851864
  0 S  oracle8   18842492   18851864  0  20 20  *  6531:844    5266720 09:51:50
?       0:00 oracleTEST (DESCRIPTION=(LOCAL=Y
  0 S  oracle8   18851864   18852790  0  20 20  *  1186:483   485af798 09:51:50
pts/0   0:00 sqlplus internal
  0 S  oracle8   18844740   18851864  0  20 20  *    38:29     c1a58b8 09:52:09
pts/0   0:00 /bin/csh -c ps -efl|grep 18851864
  0 S  oracle8   18823671   18844740  0  20 20  *   111:52     8fcdc20 09:52:09
pts/0   0:00 grep 18851864

SQL>

comment test2:
--------------

    In this case SGA = 41,009,780 bytes
    The new session started RSS = 844

The new session shows an increase in RSS when there is a change in SGA. This is
waht I wanted to
know how Oracle quantifies it

Thanks

          Followup:

*we don't quantify it*

You are using ps for things beyond it's ability.

I'll say it only once more -- PMAP.

Don't know how else to say it.

Your test takes the SGA from 109,445,748 to 41,009,780, the memory pressures are
different -- hence the OS won't page out as much of a process as it did before,
hence the resident set size is allowed to be bigger then before.

However -- we, Oracle, have nothing, zero, zippo to do with this -- it is 100%
the OS.  Pmap is the tool you want.  ps isn't very useful in this regards.

      Rating: 5
PMAP equivalent
      in HP-UX 11i.  January 03, 2004
Reviewer:
      Sankar  from India.
I tried PMAP in HP-UX 11i. It is not there. What is the equivalent one? Any
help?

          Followup:

sorry, not an HP/UX user (never seem to get that backspace key working right in
my telnet sessions on that os), maybe someone out there is tho and will follow
up.

      Rating: 5
PMAP equivalent
      in Linux? Thanks.  January 03, 2004
Reviewer:
      Reader

          Followup:

http://web.hexapodia.org/~adi/pmap.c
seems to work well.

January 03, 2004 pedro -- Thanks for the question regarding "Hash
      partitioning in OLTP, a way of suicide?", version 9.2.0.2

      You Asked
Hello

I am currently working with Peoplesoft CRM v8

One of consultants decided to partitioned the tables using hash partitioning, he
also partitioned the indexes locally. Obviously he just showed me how clueless
he is making the indexes local to the tables and time has shown I am right, we
had to re-partition all indexes into global.

I asked him a couple of days ago what is the technical reason of using hash
partitioning (never worked well for me in OLTP). He said

1. We increase availability.
-- I would agree if he stored the partitions in different tablespaces however as
clueless he is he stored everything in same tablespace. So availability is not
applicable here.

2. Administration facilities.
-- Well to certain degree I would agree since dealing with a 50 million table is
harder than dealing with 64 small partitions. Again he showed me how clueless by
creating 64 partitions in an OLTP environment. (I would not create more than 8)

3. Increase performance and reduces contention.
-- I never believed Hash partitioning increases performance and the reality and
time has shown I am right.
I do agree about reducing contention.. BUT.... comment at the end

I proceeded to making a benchmark comparison, we have two identical database one
hash partitioned and the other not. I took two statspack snapshots at each
session in each instance to get the session statistics. Ran a identical SQL
statements which shares the same execution plan in each sessions/instance. By
the way I was the only user in the whole server and each database.

Here are the relevant statistics,

Hash Partitioned results:
sesstats
-------------------------------------------------------------------
consistent gets                              601,857          963.0
consistent gets - examination                193,513          309.6
cpu used by this session                       8,593           13.8
cpu used when call started                     8,593           13.8
parse count (hard)                                 1            0.0
parse count (total)                               13            0.0
parse time cpu                                     2            0.0
parse time elapsed                                 2            0.0
physical reads                               492,418          787.9
pinned buffers inspected                          73            0.1
prefetched blocks                                 10            0.0
recursive calls                                  258            0.4
redo entries                                      18            0.0
redo size                                      8,960           14.3
rows fetched via callback                     64,503          103.2
session cursor cache count                         7            0.0
session cursor cache hits                          2            0.0
session logical reads                        601,877          963.0
session pga memory                           379,608          607.4
session pga memory max                       379,608          607.4
session uga memory                             4,264            6.8
session uga memory max                       104,616          167.4
shared hash latch upgrades - no w             84,288          134.9
sorts (memory)                                    15            0.0
sorts (rows)                                      36            0.1
table fetch by rowid                       5,142,160        8,227.5
table scan blocks gotten                         170            0.3
table scan rows gotten                         5,024            8.0
table scans (short tables)                        16            0.0
user calls

Top 5 Timed Events
~~~~~~~~~~~~~~~~~~                                                     % Total
Event                                               Waits    Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
db file sequential read                           493,086         504    84.65
CPU time                                                           89    14.89
control file parallel write                           203           2      .28
db file scattered read                                 23           0      .05
process startup                                         1           0      .05

No-Hash Partitioned results:
sesstats
-------------------------------------------------------------------
consistent gets                              593,514          948.1
consistent gets - examination                192,675          307.8
cpu used by this session                       6,766           10.8
cpu used when call started                     6,766           10.8
parse count (hard)                                 3            0.0
parse count (total)                               14            0.0
parse time cpu                                     1            0.0
parse time elapsed                                 3            0.0
physical reads                               422,118          674.3
recursive calls                                  265            0.4
recursive cpu usage                                1            0.0
redo entries                                      18            0.0
redo size                                      8,392           13.4
rows fetched via callback                     64,224          102.6
session logical reads                        593,533          948.1
session pga memory                           248,352          396.7
session pga memory max                       248,352          396.7
session uga memory                             2,024            3.2
session uga memory max                        98,448          157.3
shared hash latch upgrades - no w             25,609           40.9
sorts (memory)                                    15            0.0
sorts (rows)                                      36            0.1
table fetch by rowid                       5,141,482        8,213.2
table scan blocks gotten                          16            0.0
table scan rows gotten                           236            0.4
table scans (short tables)                        16            0.0
user calls                                        25            0.0

Top 5 Timed Events
~~~~~~~~~~~~~~~~~~                                                     % Total
Event                                               Waits    Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
db file sequential read                           422,162         346    83.06
CPU time                                                           70    16.81
control file parallel write                           204           0      .10
log file parallel write                                26           0      .02
control file sequential read                          177           0      .01

From all results no-hash partitioned is better

My question is:

Is it worthy lose performance just gain reduction in contention...?

IMHO Hash partition is a no-no in OLTP, a way of suicide

      and we said...
(Hash partitioned tables) PLUS (global range partitioned INDEXES) EQUALS
(success in OLTP)

It is funny -- this is almost verbaitim the example I used in my new book
"Effective Oracle By Design".  I was trying to drive home that

a) partitioning is NOT fast=true
b) you MUST understand the physics behind the data, whats happening.

Say you took a table T with columns ( ID primary key, CUST_ID, ... )

You hash partitioned into 64 partitions by ID.
You have a local index on CUST_ID.

MOST of your queries are "where cust_id = :x"

Guess what you just accomplished.

You accomplished the feat of increasing your IO by a factor of 64!!!  by 64
times!!

why?  well, we have 64 tiny little index segments to range scan -- your customer
id could be in any, all or none of them.

Solution -- hash partition table, range partition index by cust_id -- now you
will NOT have affected read performance at all (probably, it could be a tiny bit
better with partitioning but nothing phenomenal) but you might find that you've
reduced contention on modifications since you have N indexes and N table
segments (and hence N freelists at least and so on)

If you have my new book -- you'll laugh at how closely your example above
mirrors the one in the book, almost scary (but I only did 8 partitions, to show
an 8 times increase in IO)
Review & Followup

      Rating: 5
hello and thx
      for reply  December 06, 2003
Reviewer:
      A reader
Hello again

Glad you reply so fast, wasnt expecting this kind of help from you on weekends.
Wish Metalink's support has this sort of quality help as yours... BUT that's
another story

Index range index.... I understand we could use it, the thing is if I could
index range partition in the first place I would have not used hash particion at
all. I would range partitioned the tables. The problem which worried me was
Peoplesoft has these funny column IDs, using varchar2(25) and it goes like this

'1000000000000000000040001'
'1000000000000000000040002'
'1000000000000000000040003'

I also thought of using range partitioning on the indexes but it involves
maintenance, our production environment is outsourced to some company and as you
may know under these situations the production guys just wanna sit and doing
nothing except reciveing maintenance scripts from development (outsourcing is
bad, hope people will understand in the next couple of years). Anyway the thing
is the tables involved has around 100 indexes... I am sure if I say I am going
to range partitioned those indexes I will start getting attacks from everywhere
hehe

I will give this a try tomorrow and I will post the statspack results again.

Thank you

Regards

      Rating: 5
forgot to
      say  December 06, 2003
Reviewer:
      A reader
I forgot to say that the indexes are already global partitioned in the test case
I posted and the performance on hash partitioned is still poor.

Will index range partitioning at least equals the non-hash-partitioned table
results?

In god we trust :-)

Regards

Note:
Just want to give a real life example so people can use it to decide on
partitioning tables and indexes.

When used local indexes and hash partitioning a query statistics was this

123 minutes

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
   12936039  consistent gets
     775258  physical reads
          0  redo size
        341  bytes sent via SQL*Net to client
        460  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

after making into global

0.5 seconds

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        123  consistent gets
          0  physical reads
          0  redo size
        341  bytes sent via SQL*Net to client
        460  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

both used same plan

However the global index PLUS hash partitioned table performance is still worse
than plain tables and plain indexes

          Followup:

using globally partitioned indexes will meet or beat the non-partitioned table
(he says with a high degree of confidence)

it is all about being able to eliminate all but ONE index partition from
consideration.  If you can do that -- it'll be just like it wasn't partitioned.

At the simplest level, a single global index.

      Rating: 5
I dont think
      this is good to range partition  December 06, 2003

Reviewer:  pedro
I just looked the indexes on the hash partitioned tables. Just saw that many of
indexes are composite index of 2 to 4 columns... BIG PROBLEM

To range partition multi columns keys is way too complicated plus some of them
are foreign keys which means that if I dont equi-partition the referenced PKs I
am not sure how would Peoplesoft batch jobs joins gonna work. In other words if
I have this column

000001
000002 etc to 001000

If I partitioned the FK in

000001 ~ 000100
000101 ~ 000200 etc to 001000

I would have to partitioned my PKs as well in the same fashion otherwise I bet
this is going to be very very slow when the batch jobs runs join between big
tables

Hmm, so complicated, guess will stick with my simple global partition indexes
:-\

          Followup:

just range on the leading edge OR use a single partition.

  where index_c1 = :x and index_c2 = :y and index_c3 = :z

will use index partition elimination purely on the index_c1 = :x part.

      Rating: 5
I meant this is
      NOT good to range partition  December 06, 2003

Reviewer:  pedro

      Rating: 3
stick with
      plain tables pedro  December 07, 2003
Reviewer:
      Marko
Hi Pedro

My suggestion is stick with plain heap tables in OLTP and an application such as
Peoplesoft which is OLTP/Batch mixed.

Some good indexes should make your Peoplesoft run fast enough. Actually
Peoplesoft's default indexes are quite good already.

If you start range partitioning your indexes in Peoplesoft as Tom says, only the
leading columns you will make things much worse, as you may know the composite
indexes are default from Peoplesoft which are only selective when they are
composite so if you go and index (and range partition) only the leading column
this is going to be a disaster.

Hash partitioning table + Range partitioning indexes are only good if your
application is simple, Peoplesoft is way too complicated. When those massive
batch jobs joining 3 or 4 20 million rows tables between them in Peoplesoft you
will feel the wrath of partitioning in a Hybrid system, probably runs ok in OLTP
but your batch jobs will take days to finish :-)

      Rating: 4
do this in
      SQL  December 07, 2003
Reviewer:  pedro

Thank you Marko for your comments.

I am still going to give this a try, partitioning the index into ranges.

Tom, I am trying to determine the range of values, i wrote a pl/sql block I
would like to know if it is possible to do with simply SQL? (The FOR LOOP part)

declare
 l_number number;
 l_no_blanks number := 0;
 l_start varchar2(20) := '0000000000';
 l_stop varchar2(20)  := '1000000000';
begin
for i in 1..15
loop
   select  count(*)
     into l_number
     from sysadm.PS_RF_INST_PROD
    where
       parent_inst_prodid >= l_start
      and parent_inst_prodid  < l_stop;
   l_start := to_number(l_start) + 1000000000;
   l_stop  := to_number(l_stop) + 1000000000;
   l_no_blanks := l_no_blanks + l_number;
   dbms_output.put_line('From ' || l_start || ' to ' || l_stop ||': ' ||
l_number);
end loop;
   select  count(*)
     into l_number
     from sysadm.PS_RF_INST_PROD
    where parent_inst_prodid = ' ';
   dbms_output.put_line('No values (blanks): ' || l_number);
   dbms_output.put_line(lpad('-', 50, '-'));
   dbms_output.put_line('Total number of rows is: ' || to_char(l_no_blanks +
l_number));
end;
/

The results is as follows as you can see the data is screwed :-)

From 1000000000 to 2000000000: 696
From 2000000000 to 3000000000: 1418631
From 3000000000 to 4000000000: 1429009
From 4000000000 to 5000000000: 1406610
From 5000000000 to 6000000000: 1396820
From 6000000000 to 7000000000: 1429143
From 7000000000 to 8000000000: 1433470
From 8000000000 to 9000000000: 616147
From 9000000000 to 10000000000: 395680
From 10000000000 to 11000000000: 0
From 11000000000 to 12000000000: 1418631
From 12000000000 to 13000000000: 0
From 13000000000 to 14000000000: 0
From 14000000000 to 15000000000: 0
From 15000000000 to 16000000000: 0
No values (blanks): 5077656
--------------------------------------------------
Total number of rows is: 16022493

greets

          Followup:

fruedian slip?  "data is 'screwed'" -- do you mean "skewed" :)

if you would like to "bin" the existing data into more or less equal ranges,
ntile is very handy:

  1  select nt, min(id), max(id), count(*)
  2    from (
  3  select object_id id, ntile(4) over (order by object_id) nt
  4    from all_objects
  5         )
  6*  group by nt
ops$tkyte@ORA9IR2> /

        NT    MIN(ID)    MAX(ID)   COUNT(*)
---------- ---------- ---------- ----------
         1          3       7515       7340
         2       7516      14854       7339
         3      14855      22195       7339
         4      22196      30906       7339

ops$tkyte@ORA9IR2>

is that what you are trying to do?

      Rating: 5
sorry results
      should be like this  December 07, 2003
Reviewer:
      pedro
From 0000000000 to 1000000000: 696
From 1000000000 to 2000000000: 1418631
From 2000000000 to 3000000000: 1429009
From 3000000000 to 4000000000: 1406610
From 4000000000 to 5000000000: 1396820
From 5000000000 to 6000000000: 1429143
From 6000000000 to 7000000000: 1433470
From 7000000000 to 8000000000: 616147
From 8000000000 to 9000000000: 395680
From 9000000000 to 10000000000: 0
From 10000000000 to 11000000000: 1418631
From 11000000000 to 12000000000: 0
From 12000000000 to 13000000000: 0
From 13000000000 to 14000000000: 0
From 14000000000 to 15000000000: 0
No values (blanks): 5077656
--------------------------------------------------
Total number of rows is: 16022493

      Rating: 5
hmm dont get
      your SQL  December 07, 2003
Reviewer:
      A reader
Hi

never used ntile before...

what I am trying is

select  count(*)
     into l_number
     from sysadm.PS_RF_INST_PROD
    where
       parent_inst_prodid >= l_start
      and parent_inst_prodid  < l_stop;

make l_start and l_stop dynamic but using SQL, not sure if that´s possible

this is acadmeic question by the way :-)

          Followup:

ok, my question is "why", what is the goal

looks like you are trying to figure out "how should I range partition, by what
values, to get even segment sizes"

if so, NTILE does that.

      Rating: 5
how about range
      range  December 08, 2003
Reviewer:
      A reader
quote:
(Hash partitioned tables) PLUS (global range partitioned INDEXES) EQUALS
(success in OLTP)

How about

Range partitioned tables PLUS Global range partitioned INDEXES

Range partitioned tables PLUS Global INDEXES

Range partitioned tables PLUS locally range partitioned INDEX on the partition
key AND Global range partitioned INDEXES

Range partitioned tables PLUS locally range partitioned INDEXES

Anyone with some experience?

          Followup:

ok, let me change it, simplified

global partitioned indexes equals success.

but even that is too simplified -- since locally partitioned indexes work nicely
if the data is partitioned by the index keys.

but -- really the equation should be:

knowledge of the physic behind partitioning = success.

period.  it is a physical structure, once you look at how it is stored, it
becomes rather "intuitive" at times how Oracle will need to access it.  Once you
have this knowledge down, you can use this tools to achieve

o easier admin
o more available
o more concurrent
o perhaps even better performance on some queries

      Rating: 5
global
      partitioned indexes   December 09, 2003
Reviewer:
      A reader
I think global partitioned indexes are only feasible if the indexes are NOT
composite.

      Rating: 4
Global
      partitioned index  December 09, 2003
Reviewer:
      A reader
I think global partitioned indexes are only feasible if the indexes are NOT
composite.

For example a composite 3 column indexes is very very difficult to make that
partitioned!

          Followup:

why do you think that? (when something starts with "i think" and is not followed
by "for these reasons" or "because as this example shows" -- well, i do not
believe it)

I think they are feasible, becase as this example shows:

ops$tkyte@ORA9IR2> create table t nologging
  2  partition by hash(object_id) partitions 8
  3  as
  4  select * from all_objects;

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create index t_idx on t(owner,object_type,object_name)
  2  global partition by range (owner)
  3  ( partition values less than ( 'F' ),
  4    partition values less than ( 'M' ),
  5    partition values less than ( 'T' ),
  6    partition values less than ( MAXVALUE )
  7  ) compute statistics;

Index created.

You partition on the LEADING EDGE.  and partition elimination kicks in

ops$tkyte@ORA9IR2> create table t nologging
  2  partition by hash(object_id) partitions 8
  3  as
  4  select * from all_objects;

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> analyze table t compute statistics for table;

Table analyzed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create index t_idx on t(owner,object_type,object_name)
  2  global partition by range (owner)
  3  ( partition values less than ( 'P' ),
  4    partition values less than ( 'S' ),
  5    partition values less than ( 'U' ),
  6    partition values less than ( MAXVALUE )
  7  ) compute statistics
  8  /

Index created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> delete from plan_table;

8 rows deleted.

ops$tkyte@ORA9IR2> explain plan for select * from t where owner = 'SCOTT' and
object_type = 'TABLE';

Explained.

ops$tkyte@ORA9IR2> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------

------------------------------------------------------------------------------
| Id  | Operation                      |  Name  |Rows|Bytes|Cost|Pstrt| Pstop |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |        |   3| 288 |  3 |     |       |
|   1 |  TABLE ACCESS BY GLBL INDX ROWD| T      |   3| 288 |  3 |ROWID| ROW L |
|*  2 |   INDEX RANGE SCAN             | T_IDX  |   1|     |  2 |   3 |     3 |
------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("T"."OWNER"='SCOTT' AND "T"."OBJECT_TYPE"='TABLE')

Note: cpu costing is off

15 rows selected.

      Rating:
      3
example  December 09, 2003
Reviewer:
      alan
PK is formed by

a  =====>   2 distinct values (AXA and SHARE)
b  =====>   55000 distinct values

If we partition this PK then just partitoning a will do no good!

If we partition the PK we would have to do this

CREATE INDEX y_idx1 ON y(a, b)
GLOBAL PARTITION BY RANGE(a, b)
( partition arbor_a0 values less than ('AXA', 4000),
  partition arbor_a1 values less than ('AXA', 8000),
  partition arbor_a2 values less than ('AXA', 12000),
  partition arbor_a3 values less than ('AXA', 16000),
  partition arbor_a4 values less than ('AXA', 20000),
  partition arbor_a5 values less than ('AXA', 24000),
  partition arbor_a6 values less than ('AXA', 28000),
  partition arbor_a7 values less than ('AXA', 32000),
  partition arbor_s0 values less than ('SHARE', 4000),
  partition arbor_s1 values less than ('SHARE', 8000),
  partition arbor_s2 values less than ('SHARE', 12000),
  partition arbor_s3 values less than ('SHARE', 16000),
  partition arbor_s4 values less than ('SHARE', 20000),
  partition arbor_s5 values less than ('SHARE', 24000),
  partition arbor_s6 values less than ('SHARE', 28000),
  partition arbor_s7 values less than ('SHARE', 32000),
  partition arbor_max values less than (maxvalue, maxvalue)
);

          Followup:

or simply flip flop the columns in the index and partition by b alone of course.

      Rating:
      5
  December 09, 2003
Reviewer:
      A reader
Very nice discussion!

Regarding
o easier admin ---> CLEAR
o more available ---> CLEAR
o more concurrent
o perhaps even better performance on some queries

More concurrent on what?
What queries can improve their performance?

      Rating: 5
hash partition
      local indexes   December 26, 2003
Reviewer:
      A reader
Hi

Is it a good idea local partition Primary Keys? I think it should be fast
because if I join with other tables it's like doing

where PK in (............)

which is some sort of equality operation (IN-List), am I right?

          Followup:

it is a function of "what is the PARTITION KEY"

if you partition by X
but you have a primary key Y
the locally partitioned approach, well, it won't even fly.....

Your partition key is pretty safe being indexed locally in both DW and OLTP.

If your partition key is your primary key, then the primary key "inherits" this
niceness.

      Rating: 5
how about local
      non-prefixed composite index  December 27, 2003

Reviewer:  A reader
If we partition by let's say DEPT_ID but the PK is a composite key composed of
two columns set_id and dept_id where distinct value of set_id is just one if the
unique index is created such

create unique index x_idx1 on x(set_id, dept_id)
local;

Will this affect performance since this is non-prefixed? (although the leading
column has only one distinct value)

          Followup:

the optimizer won't use the index in as many cases perhaps -- there are times
when a local prefix index is called for, but the raw performance of the index
when used will be the same.

      Rating: 2
local index no
      good  January 03, 2004
Reviewer:
      A reader
this is a test case

select a.bo_id ps_bo_id, b.bo_id b_bo_id, b.cm_id, a.cust_status
  from  sysadm.ps_bc a,
       (select X.BO_ID, X.CM_ID
          from (select bo_id, cm_id, PROFILE_CM_SEQ
              from sysadm.PS_BO_CM_PROFL_DTL
                 where CM_TYPE_ID in (1, 2)
                   and PRIMARY_IND ='Y'
                   and EFFDT < sysdate) X) b
where a.bo_id = b.bo_id
  and b.cm_id between 2102032000000007960641 and 2102032000000008500000;

LOCAL
=================================================================================
=================
CREATE UNIQUE INDEX SYSADM.PS_BC ON SYSADM.PS_BC
(BO_ID)
  TABLESPACE PSINDEX
NOLOGGING
LOCAL STORE IN (PSINDEX)
PARALLEL 2;
ALTER INDEX SYSADM.PS_BC LOGGING;
ALTER INDEX SYSADM.PS_BC NOPARALLEL;

146696 rows selected.

Elapsed: 00:00:14.79

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=7 Card=1 Bytes=54)
   1    0   NESTED LOOPS (Cost=7 Card=1 Bytes=54)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'PS_BO_CM_PROFL_DTL' (Cost=5
Card=1 Bytes=39)
   3    2       INDEX (RANGE SCAN) OF 'PSDBO_CM_PROFL_DTL' (NON-UNIQUE) (Cost=3
Card=2)
   4    1     PARTITION HASH (ITERATOR)
   5    4       TABLE ACCESS (BY LOCAL INDEX ROWID) OF 'PS_BC' (Cost=2 Card=1
Bytes=15)
   6    5         INDEX (UNIQUE SCAN) OF 'PS_BC' (UNIQUE) (Cost=1 Card=1)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     633661  consistent gets
      36035  physical reads
          0  redo size
    8290653  bytes sent via SQL*Net to client
     108224  bytes received via SQL*Net from client
       9781  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
     146696  rows processed

GLOBAL
=================================================================================
=================
CREATE UNIQUE INDEX SYSADM.PS_BC ON SYSADM.PS_BC
(BO_ID)
  TABLESPACE PSINDEX
NOLOGGING
PARALLEL 2;

ALTER INDEX SYSADM.PS_BC LOGGING;
ALTER INDEX SYSADM.PS_BC NOPARALLEL;

146696 rows selected.

Elapsed: 00:00:13.02

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=7 Card=1 Bytes=54)
   1    0   NESTED LOOPS (Cost=7 Card=1 Bytes=54)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'PS_BO_CM_PROFL_DTL' (Cost=5
Card=1 Bytes=39)
   3    2       INDEX (RANGE SCAN) OF 'PSDBO_CM_PROFL_DTL' (NON-UNIQUE) (Cost=3
Card=2)
   4    1     TABLE ACCESS (BY GLOBAL INDEX ROWID) OF 'PS_BC' (Cost=2 Card=1
Bytes=15)
   5    4       INDEX (UNIQUE SCAN) OF 'PS_BC' (UNIQUE) (Cost=1 Card=1)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     496745  consistent gets
      36346  physical reads
          0  redo size
    8290653  bytes sent via SQL*Net to client
     108224  bytes received via SQL*Net from client
       9781  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
     146696  rows processed

basically with local indexes you need roughly one consistent get more than
normal global index..... now dont tell why would someone use local index at all
in OLTP

          Followup:

think "primary key" and then tell us what you think about using local indexes
for example...

there are valid reasons, a test case showing that in a single example you
produce more LIO's does not mean something is "evil" -- far from it.  It just
shows "don't use it in this case"

Take a primary key, say populated via a sequence, that is used to define the
HASH key for the table.  Hmmm, that local index is looking pretty good --
instead of a single index structure (eg: point of contention) I have N of them
-- i've spread the contention way out, I've perhaps reduced the height of my
b*trees (you might find a keyed read doing LESS LIO), hmmm.  Local indexes must
be good for OLTP....

we are right back to my original statement:

It is funny -- this is almost verbaitim the example I used in my new book
"Effective Oracle By Design".  I was trying to drive home that........

But your logic is doubly flawed - you cannot prove something is "evil" in that
fashion -- you just had a bad design decision there.  Used in other ways, that
local index is going to be simply AWESOME and a way to increase scalability,
benefit performance, all of the good things.

      Rating: 5
to the test
      reader  January 04, 2004
Reviewer:
      A reader
Hi

I thiink your statistics are screwed up, the cardinality doesnt seem to match at
all, returning over 140000 rows and it says 1 as cardinality LOL :DDDD

January 03, 2004 Steve -- Thanks for the question regarding
      "Random numbers", version 9.2

      You Asked
How could you create say six unique random numbers between 1 and 49 with one sql
statement?

Cheers
Steve

      and we said...
I'd generate the set of numbers to pick from (inner most query -- any table with
49 or more records would do.  you could write a pipelined function as well, this
is easier tho)...

then order them randomly....

then take the first 6...

ops$tkyte@ORA920> select r
  2    from ( select r
  3             from ( select rownum r
  4                      from all_objects
  5                     where rownum < 50 )
  6            order by dbms_random.value )
  7   where rownum <= 6
  8  /

         R
----------
         8
        20
        32
        12
        44
        26

6 rows selected.

ops$tkyte@ORA920> /

         R
----------
         8
        27
        49
        11
         7
        14

6 rows selected.

Review &
      Followup

      Rating: 5
Excellent
      Work  August 17, 2003
Reviewer:
      A reader

      Rating: 4
ordering
      randomly?  August 17, 2003
Reviewer:  Tom
      Jordan  from Germany
Tom,

how does "order by dbms_random.value" work exactly? If I select
dbms_random.value from dual, I get something like .59542, but "order by .59542"
does not work. Is this a special case for the order by clause? I could not find
this in the docs. Also, I could not find the "value" member in the dbms_random
package documentation.

Thanks,
Tom (no relation)

          Followup:

select text from all_source where name = 'DBMS_RANDOM' and type = 'PACKAGE'
order by line;

gets you the latest "doc" on dbms_random....

try this:

select rownum r, dbms_random.value some_random_value
  from all_objects
 where rownum < 50

so, you have this result set, with 49 rows -- the first column is the number
1..49.  the second is some random number.

I just ordered by that second column.

since we really didn't need to know the value of the random number -- we just
ordered by it and didn't actually even select it.

      Rating: 5
Matured
      experienced and Elegant Approach :-)  August 17, 2003

Reviewer:  A reader

      Rating: 5
Random
      numbers  August 18, 2003
Reviewer:
      Santhanam  from India
Excellent example

      Rating: 5
Asif
      Momen  August 18, 2003
Reviewer:  Asif Momen
       from Riyadh
Superb

      Rating:
      5
excellent  September 29, 2003
Reviewer:
      K Ravi Sudhakar
great

      Rating: 5
Alternate
      Approach !  November 24, 2003
Reviewer:
      Prem  from New Jersey
Tom,

Your reply to the query and ORDER BY DBMS_RANDOM.VALUE were EXCELLENT.

SQL> l
  1  select round(dbms_random.value * 50) random_values from
  2    ( select rownum r
  3      from all_objects
  4      where rownum <= 6
  5*   )
SQL> /

RANDOM_VALUES
-------------
            2
           32
           39
           28
           36
           40

6 rows selected.

      Rating:
      5
Fantastic  December 11, 2003
Reviewer:
      Kookie Mamtora  from UK
Pure Random number generations..absolutely love it.
Thanks Tom

      Rating: 4
Clarification:
      returns random numbers from a set, but not a random function
      generator  January 03, 2004
Reviewer:  Guttorm
      Haaversen  from Kristiansand, Norway
The function returns a random number between 1 and 49,
but any given number will never be repeated.

Ie: if rownum < 50, all 49 numbers will be returned in random order.

In order to be a true random number generator, the query would have to select
from the set 49 times.

          Followup:

it would get the same number more then once then.

"six unique random numbers between 1 and 49"

It is like the lottery -- pick 6 numbers.

I generate the set of numbers (the little balls bouncing around in the box at
the lottery).

I order them randomly.

I pick six.

I don't get your point about having to select from the set 49 times?  In order
to achieve the requested functionality:

"six unique random numbers between 1 and 49"

you just need to generate the numbers, organize them randomly and take the first
6, last 6, middle six -- whatever six you want.

      Rating: 5
depends if you
      want with replacement or not  January 03, 2004

Reviewer:  Jim  from Portland, OR USA
The original post wanted 6 random numbers without replacement.  Tom's solution
does this.  If the original post wanted 6 random numbers then they could use the
dbms_random to generate random numbers WITH replacement.

Here is an illustration of the difference between with replacement and without
replacement.  If you have a bucket with 49 marbles each labled with numbers 1 to
49.  If you take a marlbe out write the number down and put it back in the
bucket and choose another marble.  Then you are choosing random numbers 1 to 49
WITH replacement.

On the other hand, if you pick 6 marbles, one at a time, and do NOT put each
marble back before you choose the next one then that is choosing random numbers
WITHOUT replacement.

These are important distinctions.

      Rating: 4
random
      ordering  March 23, 2004
Reviewer:  Venus
       from Budapest
itz great!!

January 04, 2004 Garuang -- Thanks for the question regarding "NOT
      IN Subquery ", version 8.1.7.4

      You Asked
When an Sql contains a Not in Subquery to retrieve selected rows in the where
clause and if returns nothing "No rows returned" even though there are some
records that can be returned,

it is a bug ??

SQL> Create table A (A number);

Table created.

SQL>  Insert into A Values(1);

1 row created.

SQL>  Insert into A Values(2);

1 row created.

SQL> Create table B (A number);

Table created.

SQL>  Insert into B Values(1);

1 row created.

SQL>  Insert into B Values(Null);

1 row created.

SQL> Select A
  2   from A
  3   Where A not in (
  4    Select A from B
  5   );

no rows selected

SQL>

Thanks

      and we said...
that is the defined behavior with regards to NULLS

it is "not known" if A is "not in" B -- tri valued logic, always been that way.

NOT IN and NOT EXISTS are NOT the same :)

In general, the column(s) selected in a NOT IN should be NOT NULL or you should
add a "where column is not null" in the subquery.
January 04, 2004 -- Thanks for the question regarding "spfile",
      version 9.1.2

      You Asked
Hello Tom,

I ve got few questions lined up

1. SPFILE

Connected to:
Personal Oracle9i 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 DECODE(COUNT(*), 1, 'spfile', 'pfile' )
  2  FROM v$spparameter
  3  WHERE ROWNUM=1
  4  AND isspecified='TRUE'
  5  /

DECODE
------
spfile

SQL> alter system set nls_date_format = 'DD/MM/YYYY' scope = BOTH
  2  /
alter system set nls_date_format = 'DD/MM/YYYY' scope = BOTH
                 *
ERROR at line 1:
ORA-02096: specified initialization parameter is not modifiable with this
option

--WHY ?

SQL> connect system@ninei as sysdba
Enter password: *******
Connected.
SQL> alter system set nls_date_format = 'DD/MM/YYYY' scope = SPFILE
  2  /
alter system set nls_date_format = 'DD/MM/YYYY' scope = SPFILE
                 *
ERROR at line 1:
ORA-02096: specified initialization parameter is not modifiable with this
option

2) In 8.1.7 if i have an multiple PFILE, how would i know which PFILE oracle is
using for starting up..

3) Version 8.1.7

Is there is anyway where i can convert the CLOB to LONG without any data
truncation or invalid datatype..this is for testing purpose only to know why the
process is slow in the production(since we have multiple clob cols)..

4) 9.1.2 Question Assume i ve lost my SPFILE and also an INIT file (which is an
create pfile from spfile). How do i boot ?
Rgds

Vijays

      and we said...
1) ummm -- because it is not allowed?

ops$tkyte@ORA920> @printtbl8 'select * from v$parameter where name =
"nls_date_format" '
NUM                           : 99
NAME                          : nls_date_format
TYPE                          : 2
VALUE                         :
ISDEFAULT                     : TRUE
ISSES_MODIFIABLE              : TRUE
ISSYS_MODIFIABLE              : FALSE   <<== is NOT system modifiable
ISMODIFIED                    : FALSE
ISADJUSTED                    : FALSE
DESCRIPTION                   : NLS Oracle date format
UPDATE_COMMENT                :
-----------------

PL/SQL procedure successfully completed.

It is modifiable at the SESSION level only.  The problem is -- many processes
are already running, have been running.  They use dates.  They have been using
the OTHER date format.  You cannot just change it on them, they have to be
restarted.

2) you do not, you cannot, you don't even know what machine the pfile is on!
(eg: even if we told you it was /tmp/pfile.ora -- that doesn't mean a thing
cause you don't know what MACHINE /tmp/pfile.ora is on!)

Not really relevant though since you can simply query v$parameter to find out
what was IN the pfile (you don't need the pfile)

Actually -- if you don't know, you really don't need to know because you are not
the person that startsup/shutsdown the database (else you would know)

3) use the to_lob function

4) you use a text editor, create a new pfile and start.

Review &
      Followup

      Rating:
      4
  January 14, 2003
Reviewer:
      A reader
Tom,

Isn't it possible(for oracle) to store the hostname or ipaddress and location of
pfile.

Thanks.

          Followup:

is it possible -- sure, anything is.  does it do it?  no (and pfiles are
obsolete if you ask me -- I don't use them anymore, too much of a hassle)

      Rating:
      4
  January 15, 2003
Reviewer:
      Vijay'S  from India
could you please throw some light on your comment "(and pfiles are obsolete if
you ask me -- I don't use them anymore, too much of a hassle) I didn't got you.

Thanks in advance as always

          Followup:

search this site for

spfile

but wait -- you asked about spfiles so you know what they are already??  the
replacement for pfiles.

      Rating: 5
New Feature of
      Oracle 9i  January 15, 2003
Reviewer:  Sikandar
      Hayat Awan  from Pindi Bhattian - Pakistan
Vijay Mr. TOM is talking about Oracle 9i new feature,

http://www.dbasupport.com/oracle/ora9i/spfile.shtml

      Rating: 5
About
      "db_create_file_dest" parameter  March 18, 2003

Reviewer:  Sikandar Hayat Awan  from Pindi Bhattian -
      Pakistan
Dear TOM I have installed Oracle 9i Rel 1 and unable to understand the
following,

+++++
SQL> show parameter db_create_file_dest

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------
db_create_file_dest                  string      d:\
SQL>
++++++
SQL> select * from v$spparameter where name = 'db_create_file_dest';
NAME                           VALUE
------------------------------ -------------
db_create_file_dest            d:'
                               *.db_domain=

db_create_file_dest            bahuman'

the values in the v$spparameter is confusing me while in v$parameter2 is ok.

          Followup:

well, since you didn't cut and paste -- just sort of typed stuff in -- I cannot
comment.

Try something like this so you can actually post what is really there and not
type it in by hand.

ops$tkyte@ORA9I> column sid format a10
ops$tkyte@ORA9I> column name noprint
ops$tkyte@ORA9I> column value format a20 word_wrapped
ops$tkyte@ORA9I> column update_comment format a20 word_wrapped
ops$tkyte@ORA9I> select * from v$spparameter where name = 'db_create_file_dest';

SID        VALUE                ISSPEC    ORDINAL UPDATE_COMMENT
---------- -------------------- ------ ---------- --------------------
*                               FALSE           0

Also, try

create pfile from spfile

and inspect the pfile to see if it looks "ok", if not, fix it and create spfile
from pfile.

      Rating: 5
Copy
      paste  March 18, 2003
Reviewer:  Sikandar Hayat
      Awan  from Pindi Bhattian - Pakistan
Here is the copy paste,

SQL*Plus: Release 9.0.1.0.1 - Production on Wed Mar 19 09:21:40 2003

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

Enter 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

SQL> column sid format a10
SQL>  column name noprint
SQL>  column value format a20 word_wrapped
SQL>  column update_comment format a20 word_wrapped
SQL>  select * from v$spparameter where name='db_create_file_dest';

SID        VALUE                ISSPEC    ORDINAL UPDATE_COMMENT
---------- -------------------- ------ ---------- --------------------
*          D:'                  TRUE            1
           *.db_domain=

*          bahuman'             TRUE            2

SQL> shutdown immediate;
Database closed.
Database dismounted.
ORACLE instance shut down.
SQL> create spfile from pfile;
create spfile from pfile
*
ERROR at line 1:
ORA-03113: end-of-file on communication channel

SQL> connect sys as sysdba
Enter password: *******
Connected to an idle instance.
SQL> create spfile from pfile;

File created.

SQL> startup;
ORACLE instance started.

Total System Global Area  118255568 bytes
Fixed Size                   282576 bytes
Variable Size              83886080 bytes
Database Buffers           33554432 bytes
Redo Buffers                 532480 bytes
Database mounted.
Database opened.
SQL>  select * from v$spparameter where name='db_create_file_dest';

SID        VALUE                ISSPEC    ORDINAL UPDATE_COMMENT
---------- -------------------- ------ ---------- --------------------
*                               FALSE           0

SQL> alter system set db_create_file_dest='d:\';

System altered.

SQL> select * from v$spparameter where name='db_create_file_dest';

SID        VALUE                ISSPEC    ORDINAL UPDATE_COMMENT
---------- -------------------- ------ ---------- --------------------
*          d:'                  TRUE            1
           *.db_domain=

*          bahuman'             TRUE            2

SQL> show parameter db_create_file_dest

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
db_create_file_dest                  string      d:\
SQL>
PFILE output
*.background_dump_dest='C:\oracle\admin\ora9i\bdump'
*.compatible='9.0.0'
*.control_files='C:\oracle\oradata\ora9i\CONTROL01.CTL','C:\oracle\oradata\ora9i\
CONTROL02.CTL','C:\oracle\oradata\ora9i\CONTROL03.CTL'
*.core_dump_dest='C:\oracle\admin\ora9i\cdump'
*.db_block_size=4096
*.db_cache_size=33554432
*.db_create_file_dest='d:\'
*.db_domain='bahuman'
*.db_name='ora9i'
*.dispatchers='(PROTOCOL=TCP)(SER=MODOSE)','(PROTOCOL=TCP)(PRE=oracle.aurora.serv
er.GiopServer)','(PROTOCOL=TCP)(PRE=oracle.aurora.server.SGiopServer)'
*.fast_start_mttr_target=300
*.instance_name='ora9i'
*.java_pool_size='33554432'
*.large_pool_size='1048576'
*.open_cursors=300
*.processes=150
*.remote_login_passwordfile='EXCLUSIVE'
*.shared_pool_size=33554432
*.sort_area_size=524288
*.timed_statistics=TRUE
*.undo_management='AUTO'
*.undo_tablespace='UNDOTBS'
*.user_dump_dest='C:\oracle\admin\ora9i\udump'

          Followup:

guess it is time for you to open a tar with support -- if the pfile is as you
say it is -- and the line breaks are all there correctly AND that is the pfile
in $ORACLE_HOME/dba/init$ORACLE_SID.ora

      Rating: 5
Location of
      pfile is  March 19, 2003
Reviewer:  Sikandar
      Hayat Awan  from Pindi Bhattian - Pakistan
I will contact Support but I want to tell you the exact locations my window box
as path sprcified by you is Unix.

c:\oracle\admin\ora9i\pfile\init.ora
c:\oracle\database\initora9i.ora
c:\oracle\database\spfileora9i.ora

There is no other finding of these files.

      Rating: 5
self tuning
      spfile????  January 04, 2004
Reviewer:
      Shankar  from Houston, TX
Following is extract from Oracle online doc,

http://download-west.oracle.com/docs/cd/B10501_01/rac.920/a96597/psimpstg.htm#10178
<quote>Oracle Corporation recommends that you administer parameters using server
parameter files to simplify parameter administration and to take advantage of
Oracle's advanced self-tuning capabilities. When Oracle self-tunes, it
automatically modifies parameter settings in the server parameter file.......
Note: Avoid modifying the values for self-tuning parameters in the server
parameter file; overriding these settings can adversely affect
performance.<quote>

Tom, could you please clarify what are self tuning parameters and how oracle
self tunes parameters in the spfile? Thanks for your time.

          Followup:

In 9i, i must assume they are talking about OEM setting some things.

In 10g -- there are lots of "self tuning" parameters to do with the SGA and
such.

January 04, 2004 Om -- Thanks for the question regarding "About
      Redo log generation", version 8.1.7

      You Asked
Hi Tom,

My question is

1) if a database is in archive log mode,Is there any select
statement which we can write to calculate how much Redo log
is generated every day or some other method to fine this out.

2) If a database is in NONARCHIVE MODE and there is a need
to put this database in ARCHIVE Mode,what all steps are
required and how to find out how much space in the file system
will be required to archive the logs.

Many thanks in advance
Om

      and we said...
1) statspack will tell you this.  Just run a statspack snapshot once a day.  If
you generate a report between two periods, it'll tell you everything.  This is
true for archivelog/noarchivelog

2) It is not a matter of if but when.

Read

http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76956/archredo.htm#9848
in the admin guide, entitled appropriately enough " Changing the Database
Archiving Mode"

If you use statspack and generate a couple of days of history with it -- you'll
be able to tell how much redo you generate and hence how much archive space
you'll need.

Review &
      Followup

      Rating: 3
archive log
      status  March 04, 2002
Reviewer:
      Well_Wisher
For non-archive log mode it shows that the archive has fallen behind by the
number of redo logs.
However it gives the timings correctly.
/* redostats.sql
     see the critical stats for redo and archive logs
     SDR-Oracle
     Note: this script is the v8-compatible version
*/
set pages 0
set lines 132
set pause off
set feedback off
set verify off
col dsnbr noprint

/* is the archiver falling behind, count should be 1 or 0 */
select decode(count(*),0,'Archiver is current',
               1,'Archiver is archiving ' || count(*) || ' log...',
               'Archiver has fallen behind by ' || count(*) || ' logs!')
  from v$log
 where status != 'CURRENT'
   and archived = 'NO';

/* what is the current log  COL:<ufs mt pt>:<log name>:<seq #>:<# members> */
select 'Current Online Log:' || chr(9) ||
       substr(f.member,1,(instr(f.member,'/',1,2) -1))
       || '/.../' ||
       substr(f.member,(instr(f.member,'/',-1,1)+1))
       || chr(9) || 'Sequence #' ||
       l.sequence#
  from v$logfile f, v$log l
 where l.group# = f.group#
   and l.status = 'CURRENT'
   and l.archived = 'NO'
   and rownum = 1;

/* what are the previous online logs */
select lc_1.sequence# dsnbr,
       'Previous Online Logs:' || chr(9) ||
       substr(f.member,1,(instr(f.member,'/',1,2) -1))
       || '/.../' ||
       substr(f.member,(instr(f.member,'/',-1,1)+1))
       || chr(9) || 'Sequence #' ||
       lc_1.sequence#
  from v$log lc, v$log lc_1, v$logfile f
 where lc.sequence# = (select max(l.sequence#) from v$log l)
   and lc_1.sequence# = lc.sequence# -1
   and f.group# = lc_1.group#
union
select lc_2.sequence# dsnbr,
       '                  ' || chr(9) ||
       substr(f.member,1,(instr(f.member,'/',1,2) -1))
       || '/.../' ||
       substr(f.member,(instr(f.member,'/',-1,1)+1))
       || chr(9) || 'Sequence #' ||
       lc_2.sequence#
  from v$log lc, v$log lc_2, v$logfile f
 where lc.sequence# = (select max(l.sequence#) from v$log l)
   and lc_2.sequence# = lc.sequence# -2
   and f.group# = lc_2.group#
union
select lc_3.sequence# dsnbr,
       '                  ' || chr(9) ||
       substr(f.member,1,(instr(f.member,'/',1,2) -1))
       || '/.../' ||
       substr(f.member,(instr(f.member,'/',-1,1)+1))
       || chr(9) || 'Sequence #' ||
       lc_3.sequence#
  from v$log lc, v$log lc_3, v$logfile f
 where lc.sequence# = (select max(l.sequence#) from v$log l)
   and lc_3.sequence# = lc.sequence# -3
   and f.group# = lc_3.group#
order by 1 desc;

/* what is the latest archived log LAL:<archlog name>:<seq #> */
select 'Latest Archived Log:' || chr(9) ||
       substr(h.name,(instr(h.name,'/',-1,1)+1))
       || chr(9) || 'Sequence #' ||
       max(h.sequence#)
  from v$archived_log h
 where h.sequence# = (select max(l.sequence#)
                         from v$log l
                 where l.archived = 'YES'
                   and l.status = 'INACTIVE')
group by 'Latest Archived Log:' || chr(9) ||
         substr(h.name,(instr(h.name,'/',-1,1)+1));

/* how many minutes since the last log switch MSL:<elapsed minutes> */
select 'Elapsed Minutes Since Last Log Switch:' || chr(9) ||
       trunc((sysdate - first_time) * 24 * 60)
from v$log
where status = 'CURRENT'
and sequence# = (select max(sequence#) + 1
           from v$log_history);

/* what are the last 3 individual switch intervals L3I:<1>:<2>:<3> */
select 'Prior 3 Actual Switch Intervals:' || chr(9) || '[' ||
   round((lc.first_time - lc_1.first_time) * 24 * 60,1)
   || ']' || chr(9) || '[' ||
   round((lc_1.first_time - lc_2.first_time) * 24 * 60,1)
   || ']' || chr(9) || '[' ||
   round((lc_2.first_time - lc_3.first_time) * 24 * 60,1)
   || ']'
from v$log lc, v$log_history lc_1, v$log_history lc_2, v$log_history lc_3
where lc.sequence# = (select max(l.sequence#) from v$log l)
and lc_1.sequence# = lc.sequence# -1
and lc_2.sequence# = lc.sequence# -2
and lc_3.sequence# = lc.sequence# -3;

/* what is the avg interval for last 3 log switches L3S:<minutes> */
select 'Prior 3 Average Switch Interval:' || chr(9) ||
round( (
   round((lc.first_time - lc_1.first_time) * 24 * 60,2) +
   round((lc_1.first_time - lc_2.first_time) * 24 * 60,2) +
   round((lc_2.first_time - lc_3.first_time) * 24 * 60,2) ) / 3, 1)
from v$log lc, v$log_history lc_1, v$log_history lc_2, v$log_history lc_3
where lc.sequence# = (select max(l.sequence#) from v$log l)
and lc_1.sequence# = lc.sequence# -1
and lc_2.sequence# = lc.sequence# -2
and lc_3.sequence# = lc.sequence# -3;

set pages 24

          Followup:

In noarchive log mode -- ARCH doesn't do anything.  So, ignore it.  Don't run a
query to monitor ARCH in a no-archive log mode database (only makes sense
doesn't it -- not to do that?)

      Rating: 3
size of redo
      per transaction  September 17, 2003
Reviewer:
      yazid  from france
hi,
Ok statspack lets you to know the redo size during a period.
How ca i get how much redo per transaction in a session.

How can you explain a big number of archived redo log files (big number of
transactions ??)
thanks

          Followup:

stats pack gives you redo / transaction and redo /second right there at the top
of the report.

how can I explain a big number of archived redo log files?

you must have generated lots of online redo, then we archived it?  sorry, don't
know what you were looking for me to say there...

      Rating: 3
Redo log
      generation  September 18, 2003
Reviewer:
      yazid  from france
Sorry may be my english is bad ?

1) right stats pack gives you redo / transaction and redo /second at the top. My
need is to find the transaction with its list of SQL orders and how much every
order generate in the redo log files. We have a third party application which
use an Oracle database and it generate so much redo (200Mo per 10minutes), so i
want to find out which transactions are generating a lot of logs and then try to
tune this. which rate of redo log generation is good in your opinion. Will the
option nologing during inserts, updates, deletes useful to resolve this problem
?
For my third party application Statspack gives me this results :
Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:             20,635.33             17,608.66
              Logical reads:                 68.52                 58.47
              Block changes:                 15.97                 13.62
             Physical reads:                  5.28                  4.51
            Physical writes:                  4.26                  3.64
                 User calls:                 24.19                 20.64
                     Parses:                 12.66                 10.80
                Hard parses:                  0.02                  0.02
                      Sorts:                  0.63                  0.53
                     Logons:                  0.79                  0.67
                   Executes:                  5.45                  4.65
               Transactions:                  1.17

  % Blocks changed per Read:   23.30    Recursive Call %:               41.01
 Rollback per transaction %:    0.01       Rows per Sort:               30.04

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:               99.99
            Buffer  Hit   %:   92.29    In-memory Sort %:               99.73
            Library Hit   %:   99.94        Soft Parse %:               99.84
         Execute to Parse %: -132.33         Latch Hit %:              100.00
Parse CPU to Parse Elapsd %:   46.65     % Non-Parse CPU:               85.30

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   69.56   82.52
    % SQL with executions>1:   78.35   78.00
  % Memory for SQL w/exec>1:   89.25   88.43

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
SQL*Net more data to client                       275,929       48,358   36.58
direct path read                                   24,495       28,856   21.83
direct path read (lob)                             23,756       15,500   11.72
direct path write                                  18,159        8,037    6.08
log file sync                                      29,641        7,402    5.60
          -------------------------------------------------------------

thanks

          Followup:

1) the application would have to do that for you.

the application would have to query v$mystat to find the redo it had generated
so far in its session.

it would then do the transaction

it would then commit and query v$mystat again -- and subtract the two.  the
application would then have to record these details for you.

if the application does not do this, we cannot report this number.

the "rate" of redo generation that is good??  there is no such thing -- there is
the amount of redo you must generate to get the job done.  rates, much like
ratios, are not useful here.  there is work you must do -- and side effects from
that work (undo, redo) that must be accomidated.

your average redo / transaction is 17k, which is small.  according to this
statspack you are generating 11m of log in 10minutes (20k per second * 60
seconds * 10 minutes / 1024 is about 11/12m)

it does look like your sort area size might be a tad small based on the direct
read/write waits...

      Rating: 4
redo log
      generation  September 23, 2003
Reviewer:
      yazid  from France
thanks tom,
Well 12Mo/10Min => 70Mo/hours just for 0,79logon/s. Imagine we have 500 users
who use the third-party application at the same time and the database is in the
archivelog mode => 70*500=35Go of archived redo/hours for 0,79logon/s. A lot,
lot of redo ?????
How can the application evoid that and the database continue to use ARCHIVELOG
mode ???

          Followup:

you size your system for the work you do.

you buy sufficient disk and systems to support 500 users doing work that CANNOT
BE LOST.

i fail to see the logic with "0.79 logins per second"?????

you login once
you perform many transactions.

there is no correlation to be made there.

      Rating: 4
Some
      applications  September 23, 2003
Reviewer:  Jim
      Kennedy  from Portland, OR
Some applications written for some other RDBMS do work like:
logon
do select and retrieve all data
logoff

logon
do more SQL stuff (dml)
logoff
...

Not a good idea in my opinion, but that might be what he is refering to.

          Followup:

right -- i agree, but what he needs to do to "size" this is say "how many users
this snapshot represents - N" and then multiply redo/second by the ratio 500/N

given that logons != transactions in this case -- i don't think we can correlate
logins to "users"

I cannot imagine the system doing 500 logins/second, that is about all it would
be doing!

      Rating: 5
redo
      generation  January 04, 2004
Reviewer:
      Reader
Tom, does oracle generate redo and undo for changes in the controlfile also? For
example, when I structurally change the database, controlfile is updated. Does
this change get recorded in the redologs? Also, whenever RMAN info is deleted
from the controlfile, does it generate redo and undo as well? Thanks.

          Followup:

nope, the controlfiles can predate the existence of redo log files!

controlfiles are written/read directly.  they are not cached in the buffer cache
like table/index blocks.  they have no need for redo (but it is an excellent
practice to multi-plex them!)

      Rating:
      5
Thanks  January 04, 2004
Reviewer:
      Reader
<quote>the controlfiles can predate the existence of redo log files<quote>

Good point. It did not strike me. Thanks.

January 04, 2004 Rukshan -- Thanks for the question regarding
      "PGA/UGA Memory", version 8.1.7

      You Asked
Tom,

I not using MTS but when I run the following query why do I get such a high
value for UGA mem. max usage ?

 select name, sum(value/1024) "Value - KB"
 from v$statname n,
 v$session s,
 v$sesstat t
 where s.sid=t.sid
 and n.statistic# = t.statistic#
 and s.type = 'USER'
 and s.username is not NULL
 and n.name in ('session pga memory', 'session pga memory max'
 'session uga memory', 'session uga memory max')
 group by name
 /

NAME                           Value - KB
------------------------------ ----------
session pga memory             265824.676
session pga memory max         266809.711
session uga memory             2823.03125
session uga memory max         254586.848

      and we said...
We use UGA memory in dedicated server mode.  It is just where is the UGA located
-- in dedicated server mode, it's in the PGA.  In shared server mode, it's in
the SGA.

If you have my book -- i describe these structures in some detail, here is a
short extract:

...
PGA and UGA

As stated earlier, the PGA is a process piece of memory. This is memory specific
to a single operating system process or thread. This memory is not accessible by
any other process/thread in the system.  It is typically allocated via the C
run-time call malloc(), and may grow (and shrink even) at run-time. The PGA is
never allocated out of Oracle's SGA – it is always allocated locally by the
process or thread.

The UGA is in effect, your session's state. It is memory that your session must
always be able to get to. The location of the UGA is wholly dependent on how
Oracle has been configured to accept connections. If you have configured MTS,
then the UGA must be stored in a memory structure that everyone has access to –
and that would be the SGA. In this way, your session can use any one of the
shared servers, since any one of them can read and write your sessions data.
On the other hand, if you are using a dedicated server connection, this need
for universal access to your session state goes away, and the UGA becomes
virtually synonymous with the PGA – it will in fact be contained in the PGA. In
fact, when you look at the system statistics, you'll find the UGA reported in
the PGA in dedicated server mode (the PGA will be greater then or equal to the
UGA memory used, the PGA memory size will include the UGA size as well).

.....

Review &
      Followup

      Rating:
      4
  August 02, 2002
Reviewer:
      chao_ping  from china.
  1  select name, sum(value/1024) "Value - KB"
  2   from v$statname n,
  3   v$session s,
  4   v$sesstat t
  5   where s.sid=t.sid
  6   and n.statistic# = t.statistic#
  7   and s.type = 'USER'
  8   and s.username is not NULL
  9   and n.name in ('session pga memory', 'session pga memory max',
 10   'session uga memory', 'session uga memory max')
 11*  group by name
SQL> /

NAME                                                             Value - KB
---------------------------------------------------------------- ----------
session pga memory                                               24606.8555
session pga memory max                                           368674.586
session uga memory                                               2417.37891
session uga memory max                                           445290.664
why in my case, uga max is larger than pga max?
Thanks.

          Followup:

you are using MTS (shared server connections)

The UGA is in the SGA with that.  In dedicated server connections, the UGA is in
the PGA.

      Rating: 4
PGA / UGE
      Memory  January 06, 2003
Reviewer:  rukshan
      soza  from London.
Tom,
When does the PGA memory gets deallocated?. Is it possible to deallocate PGA
memory without disconnecting from the session?. Is there an Oracle Note which
describes PGA/UGA memory allocation/deallocation?

Thanks & Rgds

Rukshan

          Followup:

Well, there is my book "expert one on one oracle"

basically -- pga memory is malloc()'ed in a processes memory space.  You can
shrink it but it is just a waste of your time.  see
dbms_session.free_unused_user_memory.

Why is it a waste?  Becuase memory is malloc'ed in a heap and really cannot be
shrunk back down unless the LAST byte of malloc'ed memory isn't used.  So, say
you have a heap o memory like this:

lo address
xxxxffffffffffffffxx
                  high address

and xxxx = used stuff, fff = free stuff.  You call free_unused_user_memory and
we'll tell you that you are using less PGA however the OS will beg to differ
as the process heap cannot be reduced until that xx stuff at the high address
isn't being used anymore.

The reason it is a waste of your time also is because on all modern operating
systems -- the fffff stuff, if needed by another process will be used.  It'll
page that stuff you are not using out.

So, do not be overly concerned by large PGA's -- look at the resident set size
of the process, that's what counts.

      Rating:
      4
PGA/UGA  January 06, 2003
Reviewer:
      Rukshan  from London
Hi Tom
I have your book and its brilliant.
However, when we run a stored proc thro' a loop after a few iterations we get
ORA-4030. I was wondering of a way of avoiding this.
eg:
begin
for i in 1..100 loop
stored_proc();
end loop;
end;
/
fails after about 60 iterations.
Rgds

Rukshan

          Followup:

so, care to share what is in the procedure?  I've been known to do loops 100,000
or more times -- there is nothing "wrong" -- but perhaps you are doing something
in there like filling a global plsql table up and just quite simply "exhausting
memory".  That is you programmed a memory leak.

So, give us the test case.

      Rating: 3
Stored
      Proc  January 06, 2003
Reviewer:  Rukshan
       from London
Unfortunately its a Java program. The Oracle stored procedure calls this Java
program. But, its not possible to run standalone. Needs apps server etc.

          Followup:

try to reproduce with as few moving pieces as possible then -- that is what I do
when I hit a problem.  By removing bits and pieces, you'll find the cause, and
can then zero in on a solution.

Take it apart -- find the THING that is causing the issue.  Make it small (like
I do)

      Rating: 3
Java
      Program  January 06, 2003
Reviewer:  Rukshan

CREATE OR REPLACE procedure MatchcodeLookup(whereIn IN VARCHAR2) AS LANGUAGE
JAVA NAME 'MatchcodeLookup.run (java.lang.String)';
/

Java Program:
=============
import capscan.client.McConnection;
import oracle.jdbc.driver.OracleDriver;

import java.io.FileInputStream;
import java.sql.*;
import java.util.Date;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;

public class MatchcodeLookup
{
    //-------------------------------------------------------------------------
    // Mode values
    //-------------------------------------------------------------------------
    private final static int MODE_INTERACTIVE = 0;
    private final static int MODE_BATCH = 1;
    private static int prevMode = -1;

    //-------------------------------------------------------------------------
    // Private static attributes
    //-------------------------------------------------------------------------
    private static boolean alreadySetup = false;
    private static boolean inDebug = false;
    private static int pfSelFields; // pre fixed selected fields number

    // select & where vectors
    private static Vector select = null;
    private static Vector where = null;

    // default connection parameters
    private static int mcType = McConnection.SEARCH;
    private static String mcHost = "localhost";
    private static String mcPool = "PAF";
    private static String mcName = "conntest";

    // matchcode tables
    private static String confTable = "MATCHCODE_CONFIG";
    private static String resTable  = "MATCHCODE_TEMP";
    private static String errTable  = "MATCHCODE_ERROR";

    /*
     * This is the actual code run as a Java Stored Proceedure, parameters can
     * be set up by changing the values set up in the matchcode_config table.
     * Results will be written to the matchcode_temp table. Exceptions will be
     * written to the matchcode_error table.
     */
    public static void run(String query)
    {
        performSearch(query, MODE_INTERACTIVE);
    }

    /**
     * This method is used when wanting to verify a single address. It is for
     * using when performing batch processing, to avoid looping through
     * ambiguity lists.
     */
    public static void verifyAddress(String query)
    {
        performSearch(query, MODE_BATCH);
    }

    private static void performSearch(String query, int mode)
    {
        Connection cn = null;
        PreparedStatement ps = null;
        Statement s = null;
        McConnection mcConn = null;

        // First time stored procedure is run - get setup configuration
        if (!alreadySetup || mode != prevMode)
        {
            // Set as already if setup completed succesfully
            alreadySetup = setup(cn, mode);
        }

        // If still setup wasn't made, exit run
        if (!alreadySetup)
            return;

        prevMode = mode;

        try
        {
            // Use parameter as ADDR criteria
            Vector data = new Vector();
            data.addElement(query);

            // Get DB connection if not yet done (in setup)
            if (cn == null)
                cn = new OracleDriver().defaultConnection();
            s = cn.createStatement();

            // Prepare statement for inserting results to temporary table
            StringBuffer statement =
             new StringBuffer("INSERT INTO " + resTable + " VALUES(");
            for(int i = 0; i < select.size() - pfSelFields - 1; i++)
            {
                statement.append("?, ");
            }
            statement.append("? )");
            ps = cn.prepareStatement(statement.toString());

            // Connect to matchcode server and preform search
            mcConn = new McConnection(mcHost, mcPool, mcName,
                                      McConnection.CONNORIENTED);
            String result[];
            // In batch mode, use only crossmatch
            if (mode == MODE_BATCH)
                result = mcConn.search(McConnection.CROSS_MATCH,
                                       select, where, data, 30);
            else
                result = mcConn.search(mcType, select, where, data, 30);

            // Check for ambiguity - interactive mode only
            int ambigCount = 0;
            if (mode == MODE_INTERACTIVE)
            {
                try
                {
                    ambigCount = Integer.parseInt(result[0]);
                }
                catch(Exception excParse) {}
            }

            // In case of ambiguity, analyze ambig list field. In batch mode,
            // ambigCount will be set to 0 and so this will be false.
            if (ambigCount > 0)
            {
                String ambigList = result[1];

                // Break ambiguity list to address tokens
                StringTokenizer ambigST = new StringTokenizer(ambigList, "@");
                int noTokens = ambigST.countTokens();
                if (noTokens > 1)
                {
                    // Build new where and data vectors
                    Vector where2 = new Vector();
                    where2.addElement("POSTCODE");
                    Vector data2;
                    String results2[];

                    // Loop through address records
                    String addrToken, postcode;
                    StringTokenizer addrST;
                    while (ambigST.hasMoreTokens())
                    {
                        addrToken = ambigST.nextToken();
                        addrST = new StringTokenizer(addrToken, ";");

                        // First token is full address, second is full postcode
                        addrST.nextToken();
                        postcode = addrST.nextToken();

                        // Update criteria with current postcode and search
                        data2 = new Vector();
                        data2.addElement(postcode);
                        results2 = mcConn.search(McConnection.POSTCODE_LOOKUP,
                                                 select, where2, data2, 5);

                        // Insert each row to temp results table
                        insertRow(ps, results2);
                    }
                }
            }
            // Batch mode, or alternatively interactive more with no ambiguity,
            // insert single address to temp results table
            else
                insertRow(ps, result);

            // Debug message
            if (inDebug)
                System.out.println("MatchcodeLookup results written");
        }
        catch (Exception excSearch)
        {
            try
            {
                s.executeUpdate(
                 "INSERT INTO " + errTable + " VALUES('" + new Date() + " " +
                 excSearch.getMessage().replace('\'','\"') + "')");
            }
            catch (Exception excSQL)
            {
                System.err.println("MatchcodeLookup error " + new Date() + "\n"
                 + excSearch.getMessage() + "\n" + excSQL.getMessage());
            }
        }
        finally
        {
            try
            {
                mcConn.disconnect();
                s.close();
                ps.close();
            }
            catch (Exception excClose)
            {
                System.err.println("MatchcodeLookup error disconnecting " +
                 new Date() + "\n" + excClose.getMessage());
            }
        }
    }

    private static boolean setup(Connection cn, int mode)
    {
        PreparedStatement ps = null;
        Statement s = null;
        boolean rc = true;

        try
        {
            cn = new OracleDriver().defaultConnection();
            ps = cn.prepareStatement(
             "SELECT ConfigVal from " + confTable + " where ConfigKey = ?");

            s = cn.createStatement();
            ResultSet rs = null;

            // Get selected field list
            ps.setString(1, "SELECT");
            rs = ps.executeQuery();

            select = new Vector();

            // For interactive mode, always select ambiglist
            if (mode == MODE_INTERACTIVE)
            {
                select.addElement("LISTCOUNT");
                select.addElement("AMBIGLIST(LF=S)");
                pfSelFields = 2;
            }
            else
                pfSelFields = 0;

            while (rs.next())
            {
                select.addElement(rs.getString(1));
            }
            rs.close();

            // Get where field list (should be only ADDR)
            ps.setString(1, "WHERE");
            rs = ps.executeQuery();

            where = new Vector();
            while (rs.next())
            {
                where.addElement(rs.getString(1));
            }
            rs.close();

            // Get host name, pool name, application name and query type
            mcHost = getSetupData(ps, "MCHOST");
            mcPool = getSetupData(ps, "MCPOOL");
            mcName = getSetupData(ps, "MCNAME");
            mcType = Integer.parseInt(getSetupData(ps, "MCTYPE"));

            // Get debug mode, if exists
            ps.setString(1, "DEBUG");
            rs = ps.executeQuery();
            if (rs.next() && rs.getString(1).toUpperCase().charAt(0) == 'Y')
                inDebug = true;
            rs.close();

            // Debug message
            if (inDebug)
                System.out.println("MatchcodeLookup setup completed");
            //System.out.println(
            // "Host: " + mcHost + "\n" +
            // "Pool: " + mcPool + "\n" +
            // "Name: " + mcName + "\n" +
            // "Type: " + mcType);
        }
        catch (Exception excSetup)
        {
            rc = false;
            try
            {
                s.executeUpdate(
                 "INSERT INTO " + errTable + " VALUES('Setup: " + new Date() +
                 " " + excSetup.getMessage().replace('\'','\"') + "')");
            }
            catch (Exception excSSQL)
            {
                System.err.println("MatchcodeLookup setup error " + new Date()
                 + "\n" + excSetup.getMessage() + "\n" + excSSQL.getMessage());
            }
        }
        finally
        {
            try
            {
                s.close();
                ps.close();
                // do not close db connection
                // cn.close();
            }
            catch (Exception excSClose)
            {
                rc = false;
                System.err.println("MatchcodeLookup setup error2 " +
                 new Date() + "\n" + excSClose.getMessage());
            }
            finally
            {
                return rc;
            }
        }
    }

    private static void insertRow(PreparedStatement ps, String[] result)
     throws SQLException
    {
        for (int i = pfSelFields; i < result.length; i++)
        {
            ps.setString(i - pfSelFields + 1, result[i]);
        }
        ps.executeUpdate();
    }

    private static String getSetupData(PreparedStatement ps, String str)
     throws SQLException
    {
        String result = null;
        ResultSet rs;
        ps.setString(1, str);
        rs = ps.executeQuery();
        rs.next();
        result = rs.getString(1);
        rs.close();

        return result;
    }
}

      Rating: 4
UGA larger than
      pga in dedicated server mode  April 14, 2003

Reviewer:  Rob  from Pittsburgh, PA
Tom:

I am running 8.1.7.0 on Tru64 unix. My understanding from reading this and other
postings is that when using dedicated server connections the uga is contained in
the pga. Can you explain why the uga session max is so much higher than pga
session max in the following:

SQL> select sid,username,server,status from v$session
  2  where sid=34;

       SID USERNAME                       SERVER    STATUS
---------- ------------------------------ --------- --------
        34 DW                             DEDICATED ACTIVE

SQL> select name, sum(value/1024) "Value - KB"
  2   from v$statname n,
  3   v$session s,
  4   v$sesstat t
  5   where s.sid=t.sid
  6   and n.statistic# = t.statistic#
  7   and s.type = 'USER'
  8   and s.username is not NULL
  9   and s.sid=34
 10   and n.name in ('session pga memory', 'session pga memory max', 'session
uga memory', 'session uga memory max')
 11   group by name
 12
SQL> /

NAME                                                             Value - KB
---------------------------------------------------------------- ----------
session pga memory                                               1343.60938
session pga memory max                                           1343.60938
session uga memory                                               130.742188
session uga memory max                                           1573063.65

SQL>

Thanks as always,

Rob

          Followup:

curious -- why are you summing?

lets just see this output:

select name, value
from v$statname n, v$sesstat t
where n.statistic# = t.statistic#
and t.sid = ( select sid from v$mystat where rownum = 1 )
and n.name in ( 'session pga memory', 'session pga memory max',
                'session uga memory', 'session uga memory max')
/

no group by, no sum, no v$session -  just the stats.

      Rating: 3
More
      info  April 15, 2003
Reviewer:  Rob
       from Pittsburgh, PA
Tom:

The session is running an export. Here is the query. I changed it a little since
v$mystat is info on the current session and the session in question is not my
current session.

SQL> select name, value
from v$statname n, v$sesstat t
where n.statistic# = t.statistic#
and t.sid = 28
and n.name in ( 'session pga memory', 'session pga memory max',
                'session uga memory', 'session uga memory max')
/

NAME                                   VALUE
-----------------------------------------------------
session uga memory                    146512
session uga memory max            1260850632
session pga memory                   1535936
session pga memory max            -721944024

          Followup:

see the pga is negative, it rolled over. (overflowed the number they used)

the pga is really big.

      Rating:
      5
  September 16, 2003
Reviewer:
      A reader
Tom,

this is my pga info
NAME                                                                  VALUE
---------------------------------------------------------------- ----------
session uga memory                                                   207980
session uga memory max                                               207980
session pga memory                                                   312152
session pga memory max                                             66101520

When i execute this query i get this error
ORA-4030 Out of process memory
Called heap temporary memory

SELECT WP.FCTY_CODE PRO_FCTY_CODE,
       WP.WELL_CODE PRO_WELL,
       WCS.WELL_CONDITION PRO_WELL_STATUS,
       WDC.PRODUCING_HOURS PRO_HOURS,
       WCS.CHOKE_CHANGE_TIME PRO_CC_TIME,
       WCS.CHOKE_SETTING PRO_CH_SETTING,
       WDC.WELLHEAD_TEMPERATURE PRO_WHT,
       WDC.WELLHEAD_PRESSURE PRO_WHP,
       WDC.SUBSEA_RISER_PRESSURE PRO_SUB_R_PRESSURE,
       WDC.CASING_INNER_PRESSURE PRO_SUB_I_PRESSURE,
       WDC.CASING_MIDDLE_PRESSURE PRO_SUB_M_PRESSURE,
       WDC.CASING_OUTER_PRESSURE PRO_OUTER_PRESSURE,
       WDC.GAS_LIFT_VOLUME PRO_GAS_LIFT_VOL,
       WAR.GAS_PRODUCED_COMMERCIAL PRO_GAS_COMMERCIAL,
       WAR.OIL_PRODUCTION_VOLUME PRO_OIL_VOL,
       WARY.OIL_PRODUCTION_VOLUME PRO_OIL_VOL_YTD
FROM   odtkrun.V_WELLS_PRODUCTION WP,
       odtkrun.V_WELLS_SEGREGATION WS,
       odtkrun.O_WELL_DAILY_CONDITION WDC,
       odtkrun.O_WELL_CHOKE_SETTINGS WCS,
       odtkrun.O_WELL_ALLOC_RESULT WAR,
       odtkrun.O_WELL_ALLOC_RESULT_YTD WARY
WHERE  WP.FCTY_CODE = 'BERA'
AND    WP.SYST_CODE = 'BERYL'
AND    WP.SYST_CODE = WS.SYST_CODE
AND    WP.FCTY_CODE = WS.FCTY_CODE
AND    WP.WELL_CODE = WS.WELL_CODE
AND    WS.FLD_CODE IN ('BUK', 'SKN')
AND       NVL(WS.DATE_EFFECTIVE, LAST_DAY('31-AUG-2003')) <=
LAST_DAY('31-AUG-2003')
AND    NVL(WS.DATE_RETIRED , LAST_DAY('31-AUG-2003')) >= LAST_DAY('31-AUG-2003')
AND    WS.DATE_EFFECTIVE = (SELECT  MAX(WS2.DATE_EFFECTIVE)
                            FROM    odtkrun.V_WELLS_SEGREGATION WS2
                            WHERE   WS2.SYST_CODE = WS.SYST_CODE
                            AND     WS2.FCTY_CODE = WS.FCTY_CODE
                            AND     WS2.WELL_CODE = WS.WELL_CODE
                            AND     WS2.FLD_CODE = WS.FLD_CODE)
AND    WS.SYST_CODE = WDC.SYST_CODE
AND    WS.FCTY_CODE = WDC.FCTY_CODE
AND    WS.WELL_CODE = WDC.WELL_CODE
AND    WDC.DAY = LAST_DAY('31-AUG-2003')
AND    WS.SYST_CODE = WCS.SYST_CODE
AND    WS.FCTY_CODE = WCS.FCTY_CODE
AND    WS.WELL_CODE = WCS.WELL_CODE
AND    WCS.CHOKE_CHANGE_TIME =   (SELECT MAX(WCS1.CHOKE_CHANGE_TIME)
                                  FROM   odtkrun.O_WELL_CHOKE_SETTINGS WCS1
                                  WHERE  WCS1.SYST_CODE = WCS.SYST_CODE
                                  AND    WCS1.FCTY_CODE = WCS.FCTY_CODE
                                  AND    WCS1.WELL_CODE = WCS.WELL_CODE
                                  AND    WCS1.CHOKE_CHANGE_TIME <
LAST_DAY('31-AUG-2003')+1.25)
AND    WS.SYST_CODE = WAR.SYST_CODE
AND    WS.FCTY_CODE = WAR.FCTY_CODE
AND    WS.WELL_CODE = WAR.WELL_CODE
AND    WAR.DAY = LAST_DAY('31-AUG-2003')
AND    WS.SYST_CODE = WARY.SYST_CODE
AND    WS.FCTY_CODE = WARY.FCTY_CODE
AND    WS.WELL_CODE = WARY.WELL_CODE
AND    WARY.DAY = LAST_DAY('31-AUG-2003')
ORDER BY WP.WELL_CODE
/

Thanks.

          Followup:

that is not your pga info.

that shows how much pga you are using

not how much you are capable of using.

perhaps your sort/hash area size is unrealistic
perhaps your pga_aggregate_target is (no versions, have to guess what parameters
might apply)

      Rating:
      5
  September 16, 2003
Reviewer:
      A reader
Sorry tom, my db version is 817

          Followup:

so, what is your sort/hash area sizes

is the machine memory starved

whats the os

      Rating:
      5
  September 17, 2003
Reviewer:
      A reader
hash_area_size=20971520
sort_area_size=10485760

Operating System is HP-UX 11.0

No, the machine is not memory starved, but the Sys Admin has set user process to
64Mb.

Thanks.

          Followup:

that will do it, why would they do that?

ask sys admin to remove all restrictions on database server processes.

      Rating:
      5
  September 17, 2003
Reviewer:
      A reader
The limitation was taken off but i am still getting the same error, or can you
please comment on the query.

Thanks.

          Followup:

the query lines up very nicely.

now, can you analyze your pga usage (v$ tables) and monitor the process
(dedicated server) memory usage at the OS level?  tell us what you see there.

      Rating: 5
clarification
      on pga memory max  January 04, 2004
Reviewer:
      reader
Please see below. How do I interpret the result from the query? This a new
session and I have not done any query that needs sort space. I was wondering why
there is a difference between uga memory and uga memory max. Also, what is the
meaning of pga memory max? Is pga memory max 417068 bytes already allocated for
the process in its pga? Thanks.

SQL> select b.sid, a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic#= b.statistic# and a.name like '%ga %';

       SID NAME                           VALUE
---------- ------------------------- ----------
        12 session uga memory             76960
        12 session uga memory max        273352
        12 session pga memory            220460
        12 session pga memory max        417068

SQL> show parameter sort

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
nls_sort                             string
sort_area_retained_size              integer     0
sort_area_size                       integer     524288

          Followup:

uga memory is "user global area" memory -- session specific stuff.  If you are
running shared server, this amount of memory is in the SGA.  If you are running
dedicated server, this memory is in the PGA (hence, it would be "double
counted", the pga HAS the uga inside of it).

The PGA memory is the process global memory -- the memory specific to a process
(shared or dedicated server).  It consists of many things -- not just sort areas
(and probably contains your UGA in most cases).

What you see there, assuming dedicated server, is that your dedicated server at
one point, for whatever reason (open cursors, plsql variables, whatever) had
417,068 bytes of memory allocated and used.  Currently, it has 220,460 allocated
and used.

If you are using shared server, what you are seeing is that in the SGA, you have
76,960 bytes allocated -- at one point you had 273,352.  The shared server you
are attached to,  had 417,068 bytes of memory allocated and used at some point.
Currently, it has 220,460 allocated and used.

January 04, 2004 Ramasamy -- Thanks for the question regarding
      "HASH_MULTIBLOCK_IO_COUNT", version 8.1.7

      You Asked
Tom,

I am looking at Pro*C code. After a 6 table join query
(5 tables is hinted with USE_HASH) before opening the cursor
they have the following statement. It looks like they set this
for tuning. I do not beleive this will help in tuning.
Is there any merits of doing this statement inside the application code.I am
planning to comment this part.
Your feedback is greatly appreciated.

EXEC SQL EXECUTE IMMEDIATE "alter session set HASH_MULTIBLOCK_IO_COUNT=1";

By the way what is the use of HASH_MULTIBLOCK_IO_COUNT?.

Thanks in advance.

      and we said...
well, this parameter is strongly documented in 8i as being:

<quote>
For Oracle 8i 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.

        This parameter strongly affects performance because it controls the
        number of partitions into which the input is divided.
        For Oracle 8i, 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 = sizeof(<left input to the join>)
        M = HASH_AREA_SIZE * 0.9
        Po2(<n>) = largest power of 2 that is smaller than <n>
        C = HASH_MULTIBLOCK_IO_COUNT * DB_BLOCK_SIZE

        You can change the value of this parameter without shutting down your
        Oracle instance by using the ALTER SESSION or ALTER SYSTEM commands
</quote>

In 9i, we took this away - you cannot set it (so their code is heading for "not
working" soon)

In my opinion they've made two really big mistakes

1) they are hinting
2) they are setting something that should not be set.
Review &
      Followup

      Rating: 5
we are using
      8.1.7  January 03, 2004
Reviewer:
      A reader
Hi

We have some problems with our batch jobs, after doing many things, qury tuning,
parameter settings etc Does seems to get better. I came across this parameter
and want to test it. I see this formula

R / M < Po2(M/C)

where:

R = sizeof(<left input to the join>)
M = HASH_AREA_SIZE * 0.9
Po2(<n>) = largest power of 2 that is smaller than <n>
C = HASH_MULTIBLOCK_IO_COUNT * DB_BLOCK_SIZE

What is R? left input to the join... what does that mean
And what is n?

cheers

          Followup:

is that like e=mc2?

i'd need a little context

      Rating: 4
I think he
      means  January 04, 2004
Reviewer:
      A reader
Hello

I think he means that formula is not explained fully.
For example how would someone calculate R, if we dont know what is it talking
about.
And n, that can be anything and the docu doesnt say how to obtain a value for n

I think that's what he means, what are the meaning of these variables in the
formula

          Followup:

exactly -- and NEITHER CAN I since I quite simply do not have any sort of
context here.  where did this formula come from, what is the supportting text
that surrounds this formula.  what are the metrics, what is the thought.

point me to the TEXT that has this formula and maybe I can decipher it from
there.

But until then, the answer is "5" or "42", whatever.  without any context this
is no more meaningful then e=mc^2 to me.

"i came across this parameter" -- i see no parameter, i see a formula -- which I
haven't a clue what I would even do with the outputs yet!

      Rating: 1
Tom, this
      formula is from *your* quote.  January 04, 2004

Reviewer:  A reader

          Followup:

ignore it then.  I said "don't set this parameter, leave it be"

I didn't look "up" in the page and didn't think to.  It is something I've never
used.

as it GOES AWAY and the default is just fine, ignore it.  Not sure what "R" was
-- most likely the size in bytes of the "left side" (preserved table) to the
join. (number of rows * average row width)

99.99% of tuning takes place in the application.  are your batches doing things
IN BATCH or slow by slow (oops, I keep doing that, meant row by row)

January 04, 2004 Marcio -- Thanks for the question regarding "why
      views stick around", version 920

      You Asked
Is it possible when drop table take all depends views together?
Like this:

create table emp as select * from scott.emp;
create or replace view v_emp as
  select * from emp;

I would imagine some kind:
drop table emp --> including something.

See:
empty@MARCI9I1> create table t as select rownum x from all_users;

Table created.

empty@MARCI9I1> create or replace view v_t as select * from t;

View created.

empty@MARCI9I1> @dbls t

OBJECT_TYPE  OBJECT_NAME                    S TABLESPACE_NAME
------------ ------------------------------ - ------------------------------
TABLE        T                                USERS

VIEW         V_T

2 rows selected.

empty@MARCI9I1> drop table t;

Table dropped.

empty@MARCI9I1> @dbls t

OBJECT_TYPE  OBJECT_NAME                    S TABLESPACE_NAME
------------ ------------------------------ - ------------------------------
VIEW         V_T                            *

The view still there!
Thanks

      and we said...
views are separate from tables -- you could come along and create a synonym EMP
and the view would be OK.

I can even create a view that has no existing table to point to in fact (import
does this all of the time)

No, there is no way to drop the views that reference a table (and most people
would consider that a "bad idea" -- self included).

synonyms, views, materialized views -- they all survive the object being
"dropped" (and think about distributed databases, what then?  we don't even know
where all of the views might be)
Review &
      Followup

      Rating: 5
little baffled
      as to why this question was titled
      "Instrumentation"  January 04, 2004
Reviewer:
      A reader

          Followup:

valid point, i've renamed it for Marcio

      Rating: 5
Sorry and may
      be a pl/sql to trace them  January 04, 2004

Reviewer:  Marcio  from Br
Sorry about title it was for another question and forgot to change it -- thank
you!

just to foment discuss, how about user_dependecies and pl/sql code? You say when
it can't be done in SQL try PL/SQL... so...

empty@MARCI9I1>
empty@MARCI9I1> create or replace procedure drop_table ( p_table in varchar2 )
  2  as
  3  begin
  4          for x in ( select name
  5                       from user_dependencies
  6                      where referenced_name = p_table
  7                        and type = 'VIEW' )
  8          loop
  9                  execute immediate 'drop table ' || p_table;
 10                  execute immediate 'drop view ' || x.name;
 11          end loop;
 12  end;
 13  /

Procedure created.

empty@MARCI9I1>
empty@MARCI9I1>
empty@MARCI9I1> create table t ( x int );

Table created.

empty@MARCI9I1>
empty@MARCI9I1> create view v as select * from t;

View created.

empty@MARCI9I1>
empty@MARCI9I1> select object_name, object_type
  2    from user_objects
  3  /

OBJECT_NAME                    OBJECT_TYPE
------------------------------ ------------
DROP_TABLE                     PROCEDURE
T                              TABLE
V                              VIEW

3 rows selected.

empty@MARCI9I1>
empty@MARCI9I1> exec drop_table('T')

PL/SQL procedure successfully completed.

empty@MARCI9I1>
empty@MARCI9I1> select object_name, object_type
  2    from user_objects
  3  /

OBJECT_NAME                    OBJECT_TYPE
------------------------------ ------------
DROP_TABLE                     PROCEDURE

1 row selected.

          Followup:

views of views, views on synonyms that point to tables, etc etc etc.

procedurally you can do anything you like.

as a SQL command -- you could probably even do it via a DROP trigger.

I would just strongly and 100% wholeheartedly discourage this.  It is not what
anyone is expecting.  it could be catastrophic.

      Rating: 1
Bug on my
      drop_table  January 04, 2004
Reviewer:
      Marcio  from br
over 'drop table' inside looping.
Fixed it.

create or replace procedure drop_table ( p_table in varchar2 )
as
begin
        for x in ( select name, type
                     from user_dependencies
                    where referenced_name = p_table
                      and type = 'VIEW' )
        loop
                execute immediate 'drop view ' || x.name;
        end loop;
        execute immediate 'drop table ' || p_table;
end;
/

      Rating: 5
Drop
      trigger  January 04, 2004
Reviewer:  Marcio
       from br
I've tried drop trigger but doesn't work, I don't know if did correct, but
Oracle doesn't seem allow ddl inside system trigger. But deal with you, anyone
expect that.

empty@MARCI9I1> create or replace trigger drop_table
  2  before drop on schema
  3  begin
  4          for x in ( select name, type
  5                       from user_dependencies
  6                      where referenced_name = ora_dict_obj_name
  7                        and type = 'VIEW' )
  8          loop
  9                  execute immediate 'drop view ' || x.name;
 10          end loop;
 11  end;
 12  /

Trigger created.

empty@MARCI9I1>
empty@MARCI9I1> show error
No errors.
empty@MARCI9I1>
empty@MARCI9I1> create table t ( x int );

Table created.

empty@MARCI9I1>
empty@MARCI9I1> create view v as select * from t;

View created.

empty@MARCI9I1>
empty@MARCI9I1> select object_name, object_type
  2    from user_objects
  3  /

OBJECT_NAME                    OBJECT_TYPE
------------------------------ ------------
DROP_TABLE                     TRIGGER
T                              TABLE
V                              VIEW

3 rows selected.

empty@MARCI9I1>
empty@MARCI9I1> drop table t;
drop table t
*
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

          Followup:

you'd need to use an autonomous transaction OR BETTER YET, dbms_job to schedule
the drop to take place shortly AFTER the drop table does.

(if you use an atran, you could end up with lots of dropped views but a "not
dropped" table)

      Rating: 2
Dropping views
      on a table  January 05, 2004
Reviewer:
      KenHP  from Australia
Agree with you, Tom, on the point about dropping views on a table when the table
is dropped.

Ingres does this and it is a significant problem (trap for players, young and
old!):

"If a table is dropped, any indexes, views, permits, and integrities defined on
that table are automatically dropped also."

January 05, 2004 -- Thanks for the question regarding "owa_sylk",
      version 8.1.7

      You Asked
Hi

I saw the article on owa_sylk on your site
It does a great job but i have a typical
requirement where-in i want to put the data
of more than one query in the same excel sheet
For eg.
Query 1 say Select deptno,dname from dept
Query 2 say Select empno,ename,job,sal from emp
In the same spread sheet the output of next sql statement should be appended

Is this possible
Thanks a lot

      and we said...
sure, you have the code for owa_sylk and SYLK is an industry standard,
documented file format (search google)

You know have a kickstart (owa_sylk) to get going and access to all of the
documentation on SYLK so you can do anything at all you want!
Review &
      Followup

      Rating: 1
The code is
      giving unexpected results.   August 12, 2002

Reviewer:  Pratap Ghorpade  from India
I was trying to use the same code on your site by opening the same file and when
I was writing the contents in the  file by calling show() method with cursors of
different queries it was giving very unexpected results. It use to write the
contents of first query only when i expected it would overwrite the file atleast
instead of appending the contents on the next line

          Followup:

so you modified my code and it is not working?

what you say above isn't making sense to me.  This sentence:

It use to write the
contents of first query only when i expected it would overwrite the file atleast

instead of appending the contents on the next line

isn't parsable.

If you've modified the code (that works) and it no longer works - it is time to
debug the code.  I would hope you could do a little of that on your own?

Examples are great (they are how I relay information to you).  I would need a
clear, concise (stress concise) example to work from here.

      Rating:
      3
Ok  January 04, 2004
Reviewer:
      Roselyn  from CT,USA
Dear Sir,
There is no documentation for owa_sylk in pl/sql web toolkit
reference.Could you please provide that?
Bye!

          Followup:

owa_sylk is a free utility we wrote with tons of copious comments in the spec as
to the usage.  eg:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:769425837805

      Rating:
      3
OK  January 05, 2004
Reviewer:  Jose
       from London,UK
Dear sir,
Are there any prerequisites for using owa * packages like
  'JServer must be installed' and 'Apache server must be running'or so on?
Bye!

          Followup:

nope, they are just plsql.

however, without mod_plsql running under Apache, the output they produce doesn't
really "go anywhere"

So, in order to make them USEFUL, you would be using mod_plsql with apache.

January 05, 2004 senthil -- Thanks for the question regarding
      "question regarding storage parameters etc.", version 8i

      You Asked
sir

        I have the following questions :

1. I want to know the various storage parameters viz. pctfree,
pctused ,initrans,maxtrans etc. in simple manner.I have beeg going
thru manuals but I am not able to understand their specific purpose.

2. Regarding database triggers , what are database event triggers ?
How to implement them?

3. I would like to know exactly how many of codd's rules are
satisfied by the oracle database.

Thanks + regards

      and we said...
1) Well, I can cover this briefly here, for the whole story -- I go into some
depth on this topic in my book.

First you need to understand that Oracle manages blocks in a table in one of
three states:

o USED and not on the free list -- no inserts will be performed on these blocks.
 This block contains some row data

o USED and on the free list -- this block can be inserted into and it contains
data now (or at leat it used to, might have had all rows deleted)

o NEVER USED (above the high water mark) and can be inserted into if the
freelist your process is using is empty.

Now, pctfree is the percentage of a block that Oracle will reserve for future
updates.  It dictates when Oracle will take the block OFF of the free list.
Lets say you have an 8k database block size and pctfree = 10.  About 800 bytes
will be reserved on this block for updates.  Once there is less then 800 bytes
free, Oracle will remove the block from the freelist for the table (preventing
subsequent inserts onto the block) and the free space will be used by future
updates of the data on the block that makes that data grow.

Now, what about PCTUSED? Whereas PCTFREE tells Oracle when to take a block off
of the FREELIST making it no longer a candidate for insertion, PCTUSED tells
Oracle when to put a block ON the FREELIST again. If the PCTUSED is set to 40
(the default) and the block hit the PCTFREE level (it is not on the FREELIST
currently) then 61% of the block must be free space before Oracle will put the
block back on the FREELIST. If we are using the default values for PCTFREE (10)
and PCTUSED (40) then a block will remain on the FREELIST until it is 90% full
(10% free space). Once it hits 90%, it will be taken off of the FREELIST and
remain off of the FREELIST, until the free space on the block exceeds 60% of the
block.

The purpose of these two settings is to control row migration.  For example,
lets take an extreme case.  We will load a table T, see how many migrated
(chained) rows there are, update every row and then look again.  We start using
the default pctfree of 10% and will find that almost EVERY row migrated due to
the update (over 75% of them anyway).  And this was caused by updating a
varchar2(20) to somewhere between 1 and 20 bytes!  We'll run the test again,
this time with a pctfree that better matches our use of the table, and find that
no rows migrate:

ops$tkyte@ORA8I.WORLD> create table t ( x int, data varchar2(20) ) pctfree 10
pctused 40;

Table created.

ops$tkyte@ORA8I.WORLD> insert into t select rownum, null from all_objects where
rownum <= 10000;

10000 rows created.

ops$tkyte@ORA8I.WORLD> 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(*)
------------------------------------ ----------
                               16266        662
                               16267        662
                               16268        662
                               16269        662
                               16270        662
                               16271        662
                               16272        662
                               16273        662
                               16274        662
                               16275        662
                               16276        662
                               16277        662
                               16278        662
                               16279        662
                               16280        662
                               16281         70

16 rows selected.

just shows we got 662 rows/block with a pctfree of 10

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

ops$tkyte@ORA8I.WORLD> update t set data = rpad( '*', mod(rownum,21), '*' );

10000 rows updated.

ops$tkyte@ORA8I.WORLD> commit;

Commit complete.

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(*)
----------
      7692

that shows 76% of the rows are now on more then one block -- due to the nature
of the data, I know that 100% of these rows are migrated rows

ops$tkyte@ORA8I.WORLD> drop table t;
Table dropped.

ops$tkyte@ORA8I.WORLD> truncate table chained_rows;
Table truncated.

ops$tkyte@ORA8I.WORLD> create table t ( x int, data varchar2(20) ) pctfree 60
pctused 40;

Table created.

ops$tkyte@ORA8I.WORLD> insert into t select rownum, null from all_objects where
rownum <= 10000;

10000 rows created.

ops$tkyte@ORA8I.WORLD> 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(*)
------------------------------------ ----------
                               16266        294
                               16267        294
                               16268        294
                               16269        294
                               16270        294
                               16271        294
                               16272        294
                               16273        294
                               16274        294
                               16275        294
                               16276        294
                               16277        294
                               16278        294
                               16279        294
                               16280        294
                               16281        294
                               16282        294
                               16283        294
                               16284        294
                               16285        294
                               16286        294
                               16287        294
                               16288        294
                               16289        294
                               16290        294
                               16291        294
                               16292        294
                               16293        294
                               16294        294
                               16295        294
                               16296        294
                               16297        294
                               16298        294
                               16299        294
                               16300          4

35 rows selected.

Now we get 294 rows per block since 60% of the block is reserved for updates

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

ops$tkyte@ORA8I.WORLD> update t set data = rpad( '*', mod(rownum,21), '*' );

10000 rows updated.

ops$tkyte@ORA8I.WORLD> commit;

Commit complete.

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

and this time, no row migration -- they all fit on their original blocks after
the update

In general:

ƒá High PCTFREE, Low PCTUSED ¡V 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).

ƒá Low PCTFREE, High PCTUSED ¡V 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.

As for initrans and maxtrans -- Each block in an object has a block header.
Part of this block header is a transaction table ¡V entries will be made in the
transaction table to describe which transactions have what rows/elements on the
block locked.  The initial size of this transaction table is specified by the
INITRANS setting for the object.  For tables this defaults to 1 (indexes default
to 2).  This transaction table will grow dynamically as needed up to MAXTRANS
entries in size (given sufficient free space on the block that is).  Each
allocated transaction entry consumes 23 bytes of storage in the block header.
Unless you know that you will have "hot blocks" (this block will be modified by
5 transactions concurrently for example) -- the default settings are generally
sufficient.

2)

http://download-east.oracle.com/docs/cd/A81042_01/DOC/appdev.816/a76939/adg14evt.htm#998000
3)

See

http://members.aol.com/mbaddenda/art120.html
for a list of them

I would say we satisfy (if you do not use ANY of the object relational stuff)

1.1 The foundation rule
1.2 Information rule
1.3 Guaranteed access rule
1.5 Dynamic catalogue rule
1.6 Comprehensive data sub-language rule
1.7 View updating rule ** v8 and up **
1.8 High-level language rule
1.9 Physical data independence rule
1.10 Logical data independence rule
1.11 Integrity independence rule
1.12 Distribution independence rule
1.13 Non-subversion rule

the one we don't is:

1.4 Systematic nulls rule
Null values are distinct from an empty character string, a string of blank
characters, a zero or any other number, and must be supported by representing
missing and/or inapplicable information in a systematic way.

the empty character string in general is treated as a NULL in Oracle.

Review &
      Followup

      Rating: 4
good
      explaination  July 11, 2001
Reviewer:
      senthil
The answer to my question was very purposeful.

      Rating:
      5
  September 10, 2001
Reviewer:
      A reader

      Rating:
      5
Reader  December 26, 2001
Reviewer:
      Reader  from USA
Tom,

"
Unless you know that you will have "hot blocks" (this block will be modified by
5 transactions concurrently for example) -- the default settings are generally
sufficient
"

Oracle allocates dynamically the transaction entries in
the block as required. Could you clarify how 5 inittrans
can help 5 concurrent TXs but 1 inittrans would not.

Thanks

          Followup:

because maxtrans is a theoretical upper bound, one that in practice you'll never
get to since we'll run out of room long before you get there.  You might not
have room for 5, 23byte transaction entries -- so, the number of concurrent
transactions will be artifically limited on that block.

Its a catch-22, yes, we'll grow transaction entries as needed -- but if you need
more, that means you have lots of rows on that block, if you have lots of rows
on that block, you have less space, less space means less change there is room
for another transaction entry.

      Rating: 4
no
      updates  June 12, 2002
Reviewer:  john
       from usa
tom,

we have a table, everytime we delete all rows and insert the rows into that
table. it is a flush and fill table.
flush and fill happens every five minutes....

so, can you please tell what could be the values of pctfree, pctused and why?

is pctfree should be 0....

thanks

          Followup:

Hmm, what I might do is actually use partitions on this one.

Consider a table with two partitions -- partitioned by range on X.  You let X
takes the values 1 and 2.  Data with X=1 goes into partition 1, data with X=2
goes into partition 2.  All indexes will be locally partitioned indexes.

Now the flush and refill goes like this:

select x into l_x from t where rownum=1;  -- figure out what X is currently
insert /*+ append */ into t ( x, ..... ) values select mod(l_x+1,2)+1, .....
from where_ever;
execute immediate 'alter table t truncate partition part_' || l_x;

That will leave the indexes in the best shape possible on this table --
otherwise, since we do not reuse index space in the same transaction -- the
indexes will always be 2x.

In my book -- I describe how to pick pctfree/pctused (it takes a couple of words
- more then I put here).  In short, your pctfree should be 0 (never update,
don't need the space) and pctused should be high (you want to re-insert into a
block you delete from ASAP, to conserve space)

      Rating: 5
how do i create
      the chained_rows table?  August 14, 2003
Reviewer:
      Yogeeraj  from Mauritius
Hi,

I have just completed the update of some my tables and have observed the
following:
==============
Before update:
==============
yd@MYDB.MU>
  1  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 = '&tab'
yd@MYDB.MU>
Enter value for tab: ORT120

 CHAIN_CNT PCT_CHAINED AVG_ROW_LEN   PCT_FREE   PCT_USED
__________ ___________ ___________ __________ __________
         0           0          70         10         70

Elapsed: 00:00:00.02
yd@MYDB.MU>
Enter value for tab: ORT160

 CHAIN_CNT PCT_CHAINED AVG_ROW_LEN   PCT_FREE   PCT_USED
__________ ___________ ___________ __________ __________
         0           0          34         10         70

Elapsed: 00:00:00.04
yd@MYDB.MU>

================
After update:
================
yd@MYDB.MU> select CHAIN_CNT,
round(chain_cnt/num_rows*100,2) pct_chained,
AVG_ROW_LEN, PCT_FREE , PCT_used
from user_tables
where table_name = '&tab'

yd@MYDB.MU> /
Enter value for tab: ORT120

     22996       18.69          93         10         70

Elapsed: 00:00:00.01
yd@MYDB.MU> /
Enter value for tab: ORT160

    102838       42.86          59         10         70

Elapsed: 00:00:00.01
yd@MYDB.MU>
=========================================================

I want to use the above procedure to fix this problem.
I don't know how to create the CHAINED_ROWS table...

Please help.

best regards
Yogeeraj

      Rating: 5
sorry! i found
      the solution from metalink  August 14, 2003

Reviewer:  Yogeeraj  from Mauritius
hi,

i found it from metalink.

http://metalink.oracle.com/metalink/plsql/ml2_documents.showDocument?p_database_id=FOR&p_id=452960.999
create table CHAINED_ROWS (
  owner_name         varchar2(30),
  table_name         varchar2(30),
  cluster_name       varchar2(30),
  partition_name     varchar2(30),
  subpartition_name  varchar2(30),
  head_rowid         rowid,
  analyze_timestamp  date
);

$ more $ORACLE_HOME/rdbms/admin/utlchain.sql

sorry for having wasted some of your precious database space...

regards
Yogeeraj

      Rating:
      4
Nice  January 05, 2004
Reviewer:
      Sivaraman  from OH,USA
Dear Sir,
 How to define logical data independence? I think both views
and synonyms are used to implement that.Could you please
elucidate on that in a concise manner?
Thanks
Bye!

          Followup:

it is generally thought of as the ability to modify the physical schema without
causing application programs to be rewritten.

views can help.  synonyms somewhat.  but....

if you use stored procedures and consider them part of the schema, and don't let
applications use insert/update/delete/select/merge..... you can change anything
you want pretty much.

January 05, 2004 Gunjeet -- Thanks for the question regarding
      "Error while starting listener", version v 8.1.6

      You Asked
Hi Tom,

I'm not able to start the Oracle listener process on one of the
Solaris 2.7 machines. The listener.ora and tnsnames.ora files
look fine.
Any attempt to start the listener generates the following error
:

$ $ORACLE_HOME/bin/lsnrctl start smcdblistener

LSNRCTL for Solaris: Version 8.1.6.0.0 - Production on
27-SEP-2000 15:14:44

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

Starting
/opt/SUNWsymon/SunMCDB01/app/oracle/product/8.1.6/bin/tnslsnr:
please wait...

TNSLSNR for Solaris: Version 8.1.6.0.0 - Production
System parameter file is
/opt/SUNWsymon/SunMCDB01/app/oracle/product/8.1.6/network/admin/l
istener.ora
Log messages written to
/opt/SUNWsymon/SunMCDB01/app/oracle/product/8.1.6/network/log/smc
dblistener.log
Listening on:
(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=xf6-b0)(PORT=2521)))
Attempted to listen on:
(ADDRESS=(PROTOCOL=ipc)(PARTIAL=yes)(QUEUESIZE=1))
No longer listening on:
(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=xf6-b0)(PORT=2521)))
TNS-12541: TNS:no listener
 TNS-12560: TNS:protocol adapter error
  TNS-00511: No listener
   Solaris Error: 2: No such file or directory

I have tried replacing the hostname with the ip address in
listener.ora and tnsnames.ora but that also generates the same
errors.

ipcs shows no other processes on the machine that might have
used up the shared memory/semaphore resources.

The problem seems to be specific to this machine as the same
build is running on a number of other machines.

Do you have any ideas on what might be wrong ?

Thanks,

Gunjeet

      and we said...
The Listener requires access to the directory '/var/tmp/.oracle'
in order to write socket files.  The socket files are used for
internal pipe connections to the database. Make sure that the
Oracle user is the owner in this directory and has full
permissions to it.

Review &
      Followup

      Rating: 3
What about
      Windows NT  September 09, 2002
Reviewer:
      Jesse  from Malaysia
I faced the same problem in Windows NT. Which directory does
Oracle writing the sockets?

Thks.

          Followup:

As this is a unix specific issue, it must be something different on windows.

you are not facing the same issue, its different but without more info -- one
cannot say what.

      Rating: 1
Sorry this is
      an NT issue too  May 30, 2003
Reviewer:  David
      Jackson
Bad reply

          Followup:

care to explain why -- tell me, where do you find /var exactly on windoze.

this is *not* an NT issue.

      Rating: 3
Why
      /var/tmp/.oracle?  May 30, 2003
Reviewer:
      A reader
What tips you off that this is a issue with /var/tmp/.oracle and not something
else?

The error simply says

Solaris Error: 2: No such file or directory

why do you think this is referring to /var/tmp/.oracle?

Just curious

      Rating: 5
RE: Why
      /var/tmp/oracle?  May 30, 2003
Reviewer:  Mark
      A. Williams  from Indianapolis, IN USA
From Metalink:

Doc ID:  Note: 1073522.6
Subject:  TNS-12541: When Starting Listener
Type:  PROBLEM
Status:  PUBLISHED
Content Type:  TEXT/PLAIN
Creation Date:  08-JUL-1999
Last Revision Date:  20-MAR-2003

Problem Description:
====================
You receive the following messages in the "listener.log" file:
TNSLSNR for Solaris: Version 8.0.5.0.0 - Production on 07-JUL-99 18:43:51 (c)
Copyright 1997 Oracle Corporation.  All rights reserved.
System parameter file is /infosys/oracle/tnsadmin/listener.ora
Log messages written to /var/log/oracle/sqlnet/lsnr01.log
Attempted to listen on:
(DESCRIPTION=(CONNECT_TIMEOUT=10)(ADDRESS=(PROTOCOL=IPC)(KEY=ipckey)))
TNS-12541: TNS:no listener
TNS-12560: TNS:protocol adapter error
TNS-00511: No listener
Solaris Error: 2: No such file or directory

Solution:
=========
The Listener requires access to the directory '/var/tmp/oracle' in order to
write socket files.  The socket files are used for internal pipe connections to
the database. Make sure that the Oracle user is the owner in this directory and
has full permissions to it.

Problem Explanation:
====================
The ORA-12541 is actually cause by a Unix/Solaris error #2 :No such file or
directory.  This can be caused when the Listener attempts to access  internal
pipes socket files.

Note: 1073522.6 is a published note, so it is publicly accessible.

- Mark

      Rating:
      2
listener  August 01, 2003
Reviewer:
      aby  from india
i can't start the listener in windows NT. it is showing the same error mentioned
above.what should i do?

      Rating: 3
List of
      listeners  January 05, 2004
Reviewer:  Venki
       from India
Is there any way we can find out list of listners running using LSNRCTL
utility??

          Followup:

you sort of need to know the port they are running on.

on windoze, you can list the running services (net .... something. use "help
net")

on unix'es you can "ps ... | grep tns"

      Rating: 5
Fixed my
      LISTENER problem  March 25, 2004
Reviewer:
      Faisal Ali  from Santa Monica, CA
my /var/tmp/.oracle directory got deleted on HP-UX 11 and when the server
rebooted, the listener could not start.

Thanks for the help.

January 05, 2004 Harsh -- Thanks for the question regarding
      "Previledges and roles", version 8.1.6

      You Asked
I am giving following privs to all the users thru a role.

alter session
create cluster
create database link
create procedure
create sequence
create session
create synonym
create table
create trigger
create view
create type

Now I also want to give select privs to DBA_ tables to all the users.
These are the possible ways, pl. correct me if i am wrong.

1. Give grant select to all DBA_ table to each and every user.
2. Give select to all DBA_ table to PUBLIC, so the user can use it as
sys.table_name.
3. Give select to all DBA_ table to PUBLIC and create public synonyms of all
tables; Now all DBA_ tables can be selected by all users.

All this work requires each every table to be given grant or used, though we can
do this by creating the grant command thru a select.
Problem:
Is there any simple way by which all users can have SELECT permissions on all
the DBA_ tables.

It is the requirement, though u can say that use USER_ or ALL_ tables.

Thanks in advance
- Harsh

      and we said...
1) would work
2) would work (but since there already is a public synonym, sys.table_name is
not required)
3) would not work as there already is a public synonym for each of the dba
views.

4) run

select table_name, privilege
  from dba_tab_privs where grantee= 'SELECT_CATALOG_ROLE';

and if you want all users to have access to all of those views, grant that role
to PUBLIC.

Review &
      Followup

      Rating: 5
Great
      !!!  April 13, 2001
Reviewer:  Harsh
       from US
Yes, it works.
Thanks a lot.

I have a question on this Answer but since the Warning letters are now made even
more bigger so I will not ask u.

Thanks a lot once again.

      Rating: 5
Security
      SELECT_CATALOG_ROLE  January 05, 2004
Reviewer:
      Olga  from Vienna
Hi Tom,

are there any security reasons not to give SELECT_CATALOG_ROLE to the Oracle
developers on a development and test database? I want to query some V$-Views or
try your "DBA_EXTENTS-ROWID-RANGE-Query" but I have no rights for it.

Bye,

Olga

          Followup:

I personally cannot imagine not having access to those views in
development/test.  Not sure how you would tune/fix anything.

I would definitely give it.

      Rating:
      5
  January 05, 2004
Reviewer:  Robert
       from Memphis, USA

January 05, 2004 Maher -- Thanks for the question regarding "LOOPS
      and Exceptions", version 8i

      You Asked
Tom:

1.  I am using your way of implicit cursors to fetch records and manipulate data
such as:

FOR x in  (SELECT  col1,col2 from table)
LOOP
   htp.p(x.col1);
   htp.p(x.col2);
END LOOP;

However if i am doing a select statement with a where clause on a primary key
where I am sure I will have only one record fetched will you also use the same
technique (FOR LOOP) since you only deal with one record or there is a better
way?

2.If I have a statement that checks a count such as:
select count(*) into table_count from table where col1='ABC';
 IF  table_count=0 then
    htp.p('no records');
  else
 htp.p('there are reords');
 end if;

I thought that if a select statement does not return records "NO_DATA_FOUND"
exception is raised and program goes to the exception handler whether it is
"NO_DATA_FOUND" or "WHEN_OTHERS".

However I am noticing that the if statement is always getting processed whether
count=0 or count>0.  Can you explain why?

3. to update 3 tables at once or none at all would you be using the SQL%ROWCOUNT
after each update before you do the second update such as:
begin
 update tab 1 set col1='val1' where col1='val2';
  IF SQL%ROWCOUNT > 0 then
    update table 2
     IF SQL%ROWCOUNT > 0 then
    update table 3
   else
   rollback;
   end if;
 else
 rollback;
 end;
  commit;
end;

      and we said...
1)

...
is
   l_rec  table%rowtype;
begin

   select * into l_rec from table where pk = some_pk_value;
   ....

is a good way, you don't need the loop and since you are going by PK and only 1
record will be returned -- its safe.  The only exception that will fling is
"NO_DATA_FOUND" -- a condition I assume you would want to be alerted to anyhow.

2) A query of the type:

select AGGREGATE_FUNCTIONS from table where ....;

without a GROUP BY will ALWAYS return exactly one row, that is why.  The count
from your table is EITHER 0 or some positive number.  You either have zero rows
or not.  consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from dual where 1=0;

  COUNT(*)
----------
         0

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from dual where 1=1;

  COUNT(*)
----------
         1

ops$tkyte@ORA817DEV.US.ORACLE.COM>

it has to be that way.  Now, if we through  GROUP BY in there:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from dual where 1=0 group by
DUMMY;

no rows selected

we get zero rows as there are NO groups to be grouped by.  So, AGGREGATES with
NO GROUP BY = 1 row ALWAYS.  Aggregates with group bys = 0, 1 or more rows.

BTW:

   select count(*) into l_cnt
     from dual
    WHERE EXISTS ( select null from table where col1 = 'ABC' );

is a better way to check to see if AT LEAST one record exists.  Instead of
counting them all, just count the first one and stop.  If just checking for
existence and the count could be greater then one -- consider that approach.

3) depends on your logic.

I do not consider an update that updates ZERO rows to be an error in general (it
isn't, its just an update that doesn't update anything, nothing wrong with
that).

If you consider that to be an error (not updating any data with a particular
update), then your logic is sound.

Review &
      Followup

      Rating:
      3
  January 13, 2002
Reviewer:  Pichaimani
      Balasubramanian  from San Jose, CA
I like the idea of using WHERE EXISTS clause for count(*), a real good one
instead of doing the total count which I was doing now to check for existence.

      Rating: 5
clarification
      of rownum = 1 for record test  January 05, 2004

Reviewer:  Meyer  from Baltimore, MD
Tom, is this (your way)

  select count(*) into l_cnt
     from dual
    WHERE EXISTS ( select null from table where col1 = 'ABC' );

optimized better (or better for another reason) then:

  select count(*) into l_cnt
     from table_n
    where col1 = 'ABC'
      and other conditions
      and rownum = 1;

Thanks,
Meyer

          Followup:

they both work and the where rownum is slightly more efficient (at least in 9i
and before anyway, not so in 10g)

January 06, 2004 play -- Thanks for the question regarding "size
      of a table..avg_row_len,compute statistics", version 8.1.7

      You Asked
Tom

I know that you have a answer with show_space procedure for my question.. I have
seen that..

Keeping that aside...

If I want to know that to ascertain the space occupied by a table
would select num_rows * avg_row_len from dba_tables where table_name=table;

I guess this will give only the space occupied by the data.. what will be the
rest of the space be occupied with , or is it empty..

In this context what is block management, and what is overhead for block
management?

Is it necessary to compute statistics to determine avg_row_len?

I have only privileges of select over table a belonging to another schema, can I
compute statistics on that table.. what is the privilege required to run compute
statistics on a table?

thank ya

      and we said...
That will give you the space actually used by your data.

The remaining space will be:

o block overhead.  Space used by Oracle to manage your data
o free space.

See

http://download-east.oracle.com/docs/cd/A81042_01/DOC/server.816/a76965/c02block.htm#2595
for the format of an Oracle data block, whats in there....

To get avg_row_len, you must compute stats, yes.

You need to either OWN the object to analyze it or have the "ANALYZE ANY" system
privilege or have the owner of the object write a stored procedure which does
the analyze and grant you execute on that procedure.

Review &
      Followup

      Rating:
      5
DBA_TABLES  April 22, 2003
Reviewer:
      Saminathan Seerangan  from CA,USA
Dear Tom,
Question reg. DBA_TABLES.

a)How AVE_SPACE_FREELIST_BLOCKS and NUM_FREELIST_BLOCKS column values are
calculated for each tables?

b)Does it have any relationship with AVG_SPACE column?

Expecting your valuable comment or pointer.

          Followup:

they are populated as part of a gather statistics operation:

ops$tkyte@ORA920LAP> select NUM_FREELIST_BLOCKS, AVG_SPACE_FREELIST_BLOCKS
  2  from dba_tables where table_name = 'A';

NUM_FREELIST_BLOCKS AVG_SPACE_FREELIST_BLOCKS
------------------- -------------------------

ops$tkyte@ORA920LAP> analyze table a compute statistics;

Table analyzed.

ops$tkyte@ORA920LAP> select NUM_FREELIST_BLOCKS, AVG_SPACE_FREELIST_BLOCKS
  2  from dba_tables where table_name = 'A';

NUM_FREELIST_BLOCKS AVG_SPACE_FREELIST_BLOCKS
------------------- -------------------------
                  0                         0

this reports on FREELIST space, the number of blocks on the freelist (less than
or equal to the number of blocks in the table) and the average freespace on the
blocks on the freelist -- it is not really related to avg_space -- the average
free space on ALL blocks in the table -- directly.

      Rating:
      4
Hi  January 05, 2004
Reviewer:
      A reader  from Pittsburgh,PA
I have a situation where a Pro *C program is taking about 1 hour to execute on
one schema (say schema A) and about 6-8 hours on the other (say schema B).
There is absolutely no difference in the table structures on both the schemas.
The tables are analyzed, the tables have same number of indexes and the tables
have almost same number of records.
The only difference I found is the value of the "avg_space_freelist_blocks" and
"num_freelist_blocks" are differnet.  The values of these two attributes are
higher for schema B.

Can you throw some light on this?

thanks,

          Followup:

tkprof -- just turn on SQL_TRACE=TRUE and run both apps (make sure
max_dump_file_size is set high enough -- i'm fond of "unlimited")

then look at the tkprof.  All will become apparent at that point (99.99% chance
of that being an accurate statement :)

January 06, 2004 chen -- Thanks for the question regarding "File
      Upload", version Version8.1.5

      You Asked
Dear sir,
I am now using Oracle8.1.5 database and Oracle Web Application
Server3.02 to develope a business website.Now,given that the
customer needs to online upload a file onto our webserver,I need
to add this function into the website.But I don't know how to
implement it.Please tell me.

Additionally,I have a form which is used by the exporer to fill
out some contents,and I want to organize these filled contents
into a simple file and save it on the webserver's file
system.Please tell me how to finish this in the PL/SQL.

Thank you very much! Any suggestion are welcome!

Best Regards
John Chen

      and we said...
To perform file upload you must either

o use OAS 4.x which has this feature builtin to the PLSQL
cartridge

o write/use an existing utility to do this in OAS 3.x.  I have a
cartridge which works exactly like the PLSQL cartridge but
allows you to upload to a BLOB, LONG RAW or to the file system
as well.  See

http://osi.oracle.com/~tkyte/owarepl/doc/index.html
for info on that solution.

As for creating an ASCII file on the server (database server,
not webserver) we can use UTL_FILE.  See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:235814350980
for an example demonstrating this feature.

Review &
      Followup

      Rating: 5
File upload
      using mod_plsql and store as BLOB object in db  January 04,
      2004
Reviewer:  Sami  from NJ,USA
Dear Tom,

I like to upload PDF files thru web using mod_plsql and store in database as
BLOB object.
I went thru the guideline provided by you at

http://osi.oracle.com/~tkyte/owarepl/doc/index.html
Do you have any "sample stored procedure code" to do the same? I understand the
concept but don't know how to implement using mod_plsql.

You help is realy appreciated.
Sami

          Followup:

that's really old -- doesn't work with apache/mod_plsql.  I wrote that back in
1995...

Anyway.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:232814159006#6891533553691
and

http://download-west.oracle.com/docs/cd/A97329_03/web.902/a90855/feature.htm#1005870

      Rating: 5
All files are
      stored as LONG RAW instead of BLOB  January 05, 2004

Reviewer:  Sami  from NJ,USA
Dear Tom,

"Extensions to be uploaded as Long Raw=*.pdf" but still all my documents are
stored as LONG RAW. I like to store .doc,.ppt as BLOB object.

<OracleDocument>
You can specify the document storage table on a per DAD basis. The document
storage table must have the following definition:

CREATE TABLE [table_name] (

NAME           VARCHAR2(256) UNIQUE NOT NULL,
MIME_TYPE      VARCHAR2(128),
DOC_SIZE       NUMBER,
DAD_CHARSET    VARCHAR2(128),
LAST_UPDATED   DATE,
CONTENT_TYPE   VARCHAR2(128),
[content_column_name] [content_column_type]
[ , [content_column_name] [content_column_type]]

);

Users can choose the table_name. The content_column_type type must be either
LONG RAW or BLOB.

The content_column_name depends on the corresponding content_column_type:

If the content_column_type is LONG RAW, the content_column_name must be CONTENT.

If the content_column_type is BLOB, the content_column_name must be
BLOB_CONTENT.

An example of legal document table definition is:

  NAME               VARCHAR(128)   UNIQUE NOT NULL,
  MIME_TYPE          VARCHAR(128),
  DOC_SIZE           NUMBER,
  DAD_CHARSET        VARCHAR(128),
  LAST_UPDATED       DATE,
  CONTENT_TYPE       VARCHAR(128),
  CONTENT            LONG RAW,
  BLOB_CONTENT       BLOB ;

</OracleDocument>

My table name is
SQL> desc file_storage
 Name                   Null?    Type
 ------------------------------- ----------------------------
 NAME                   NOT NULL VARCHAR2(256)
 MIME_TYPE                       VARCHAR2(128)
 DOC_SIZE                        NUMBER
 DAD_CHARSET                     VARCHAR2(128)
 LAST_UPDATED                    DATE
 CONTENT_TYPE                    VARCHAR2(128)
 CONTENT                         LONG RAW
 NLOB_CONTENT                    BLOB

SQL>

My Mod_plsql configuration
==========================
Document Table  =file_storage
Document Access Procedure=write_info
Extensions to be uploaded as Long Raw=*.pdf

Everything works fine BUT CONTENT_TYPE becomes always "LONG RAW" regardless of
the file (Word,Excel,Text,etc)

1. how do I store document in BLOB data type instead of LONG RAW?

Always your help is really appreciated.

          Followup:

why would you store anything as long raw?  why don't you just get rid of the
long raw column all together?

      Rating: 5
It
      works!  January 06, 2004
Reviewer:  Sami
       from NJ,USA
I did get rid of the LONG RAW column as you suggested. It works like a charm.

January 06, 2004 Danial -- Thanks for the question regarding
      "Determining values of bind variables for tuning", version 8.1.7.4

      You Asked
I've been working on a query to catch active sql on the fly from v$session,
v$transaction and v$sql and have this:

select s.username, s.sid, s.serial#, s.machine, q.sql_text
from  v$sql q,v$session s,v$transaction t
where t.SES_ADDR=s.SADDR and s.SQL_ADDRESS=Q.ADDRESS;

I'm curious, other then turning on sql trace, is there a view or something
directly in the database that can be used to capture the values that the bind
variables represent?

      and we said...
the binds are in the uga which is in the pga generally (not always, but
generally).  the binds are not visible across sessions therefore.

10046 trace is the only way to get the binds.

OR

DBMS_FGA (fine grained auditing) in 9i can be used to capture the sql AND the
binds.
Review
      & Followup

      Rating:
      5
  January 05, 2004
Reviewer:
      A reader

      Rating: 5
Usefull
      info  January 05, 2004
Reviewer:  Christo
      Kutrovsky  from Ottawa, ON Canada

      Rating: 4
Any chance of
      an example using DBMS_FGA (fine grained auditing)   January
      06, 2004
Reviewer:  A reader

          Followup:

search for

dbms_fga

and read away.

January 06, 2004 Rich -- Thanks for the question regarding
      "Answers to Interview Questions ", version 8.05

      You Asked
Tom,

I have an interview coming up and I read from one of your prvious responses that
you ask three questions to those you interview . I was wondering if you had
given the answers and I had missed them somewhere. Here are your questions and
how I interpreted them.

I ask this question in interviews.  You have a table:

create table t ( .....,  month number, ..... );

______________________________________________________________
My table is:

CREATE TABLE T (TEST_DATE DATE, TEST_MONTH NUMBER(2), TEST_DAY VARCHAR2(20))

Table created.
_____________________________________________________________

Month is always a number between 1 and 12.

I ask three questions:

1) how many rows are in the table

_____________________________________________________________
  Answer) Well, none yet :)
          Now I insert with

     1  INSERT INTO T
  2  (SELECT TO_DATE('01/01/2002','MM/DD/YYYY') + (ROWNUM - 1),
  3         TO_NUMBER(TO_CHAR(TO_DATE('01/01/2002','MM/DD/YYYY') + (ROWNUM - 1),
'MM')),
  4         TO_CHAR(TO_DATE('12/31/2002','MM/DD/YYYY') + (ROWNUM - 1), 'Day')
  5  FROM ALL_OBJECTS
  6  WHERE TO_DATE('01/01/2002','MM/DD/YYYY') + (ROWNUM - 1) <=
  7*       TO_DATE('12/31/2002','MM/DD/YYYY'))
SQL> /

365 rows created.

__________________________________________________________________

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)

__________________________________________________________________

Answer)

  1  select test_month,
  2         count(test_day)
  3  from t
  4* group by test_month
SQL> /

TEST_MONTH COUNT(TEST_DAY)
---------- ---------------
         1              31
         2              28
         3              31
         4              30
         5              31
         6              30
         7              31
         8              31
         9              30
        10              31
        11              30
        12              31

_____________________________________________________________________
3) what MONTH has the most rows (and for a special bonus, tell me why this
question is ambigous)

Answer)

  1  select i.test_month,
  2         count(i.test_day)
  3  from t i
  4  group by i.test_month
  5  having count(i.test_day) = (select max(count(o.test_day))
  6*                             from t o group by o.test_month)
SQL> /

TEST_MONTH COUNT(I.TEST_DAY)
---------- -----------------
         1                31
         3                31
         5                31
         7                31
         8                31
        10                31
        12                31

7 rows selected.

More than one month has 31 days in it?

________________________________________________________________

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...)

_____________________________________________________________________
How close was I to what you were asking and can you show the ">= ALL" solution.
Also, any other questions you throw out to the people you interview? As always,
it's appreciated

      and we said...
you never answered #1.  I said "i have a table", I have data in my table.

#2 is technically inaccurate.  There is nothing to say that test_day (that
column doesn't even exist in my example) is NOT NULL -- if there is a null in
there, the count(test_day) is wrong.

#3 is correct -- except for your improper use of test_day (should be count(*),
not count(test_day(.

The answer I like for #3 is

select month
  from t
 group by month
having count(*) >= all( select count(*)
                          from t
                         group by month );

And yes -- that there may be more then one frequently occuring month is the
bonus part of the answer.

I ask people to "draw a picture of Oracle".  Show me the processes/threads --
the main ones.  Explain what they are.  Tell me about the SGA, what are the main
areas in there, what are their sizes.  Now, tell me about the files -- draw the
entire picture on the whiteboard and explain to me about this "Oracle database"
thing, how is it architected....

Review &
      Followup

      Rating: 4
Answers to
      Interview Questions   April 21, 2002
Reviewer:
      Rich  from NJ
Tom,

Thanks for the quick response but I'm still a little confused about the first
question. How can I tell how many rows are in your table?

          Followup:

you would issue select count(*) from t;

      Rating: 3
Answers to
      Interview Questions   April 21, 2002
Reviewer:
      Rich  from NJ
Thanks, I had it in my head this was a trick question and was overlooking the
obvious.

      Rating:
      5
  April 22, 2002
Reviewer:  Famy

From you above example for ALL , I ran the following queries.

why isnt the 2a not returning a row just like 1a

case 1:
Wrote file afiedt.buf

  1  select TO_CHAR(HIREDATE,'MM')
  2    from EMP
  3   group by  TO_CHAR(HIREDATE,'MM')
  4  having count(*) >= all(select count(*)
  5                            from EMP
  6*                          group by TO_CHAR(HIREDATE,'MM') )
 /

TO
--
12

Case 1a:

select TO_CHAR(HIREDATE,'MM')
  2    from EMP
  3   group by  TO_CHAR(HIREDATE,'MM')
  4  having count(*) = (select max(count(*))
  5                            from EMP
  6                           group by TO_CHAR(HIREDATE,'MM') )
  7  /

TO
--
12

case 2:

Wrote file afiedt.buf

  1  select TO_CHAR(HIREDATE,'MM')
  2     from EMP
  3    group by  TO_CHAR(HIREDATE,'MM')
  4   having count(*) >= all( select count(*)
  5                             from EMP
  6*                           group by TO_CHAR(HIREDATE,'MM') )

TO
--
12

case 2a:

Wrote file afiedt.buf

  1  select TO_CHAR(HIREDATE,'MM')
  2     from EMP
  3    group by  TO_CHAR(HIREDATE,'MM')
  4   having count(*) = all( select count(*)
  5                             from EMP
  6*                           group by TO_CHAR(HIREDATE,'MM') )

no rows selected

          Followup:

the count(*) is not equal to ALL of the count(*)'s -- makes sense doesn't it.

      Rating: 5
Tom, the
      original poster of the quesion is right in his answer to your question
      number 2  April 22, 2002
Reviewer:  Famy

look at this

pmekala2@CPNRSKD1> select deptno, count(comm)
  2  from emp
  3  group by deptno ;

   DEPTNO COUNT(COMM)
--------- -----------
       10           0
       20           0
       30           4

pmekala2@CPNRSKD1> select deptno, count(*)
  2  from emp
  3  group by deptno
  4  /

We get accurante count of  comm for a dept when we do count(comm) ( and comm is
a nullable column), and not when we do a count(*).

So the posters solution to question 2
select test_month,
  2         count(test_day)
  3  from t
  4* group by test_month

is right , even if test_day is a nullable column

Your comments please.....

          Followup:

He is wrong, you are wrong.  Look at your own example:

  1* select deptno, count(comm), count(*) from emp group by deptno
scott@ORA817DEV.US.ORACLE.COM> /

    DEPTNO COUNT(COMM)   COUNT(*)
---------- ----------- ----------
        10           0          3
        20           0          5
        30           4          6

scott@ORA817DEV.US.ORACLE.COM>

the count(comm) is WAY off.

You only get the right count with COUNT(*) or COUNT( non-nullable-column ).

Consider:

scott@ORA817DEV.US.ORACLE.COM> create table t ( month int, some_other_column int
);

Table created.

scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM> insert into t values ( 12, NULL );

1 row created.

scott@ORA817DEV.US.ORACLE.COM> insert into t values ( 12, NULL );

1 row created.

scott@ORA817DEV.US.ORACLE.COM> insert into t values ( 12, NULL );

1 row created.

scott@ORA817DEV.US.ORACLE.COM> insert into t values ( 12, NULL );

1 row created.

scott@ORA817DEV.US.ORACLE.COM> insert into t values ( 12, NULL );

1 row created.

scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM> insert into t values ( 11, 1 );

1 row created.

scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM> select month, count(some_other_column)
  2    from t
  3   group by month
  4  /

     MONTH COUNT(SOME_OTHER_COLUMN)
---------- ------------------------
        11                        1
        12                        0

the only correct answer to the question:

 how many rows BY MONTH are in the table (i want to  know how many rows for
month one, month two and so on)

is represented by the following output:

scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM> select month, count(*)
  2    from t
  3   group by month
  4  /

     MONTH   COUNT(*)
---------- ----------
        11          1
        12          5

which differs significantly from the nullable column one...

      Rating: 5
Thanks for the
      question regarding "Answers to Interview Questions ", version
      8.05  April 23, 2002
Reviewer:  Subhash S B
       from India
Hi Tom,

Dont you think

select count(ROWID) from t1 group by Col1;

is a much better way to group on a particular col. without ignoring the nulls.

Thanks,
Subhash

          Followup:

No -- this is exactly what count(*) is designed to do.

the ONLY time I use count(column_name)  is when i expect NULLS in column_name
and want a non-null count.

Otherwise count(*) is the only thing that you need to use.

Why do you believe count(rowid) would be better or superior to count(*).

      Rating: 4
Answers to
      Interview Questions  April 26, 2002
Reviewer:
      Rich  from NJ
Again, thanks for this great website and pointing out the errors I made with
regard to my use of NULLs. Can you explain the benefit of having a field
populated with null. My application never purposely inserts null values and I
only find them useful for searching for missing data. For example:

select rj.gap "Sequence",
       decode(l.location, NULL, 'Gap',l.location) "Location Code"
from location_tbl l,
     (select lpad(to_char(rownum),3,0) gap from all_objects where rownum < 1000)
rj
where rj.gap = l.location (+)
  and (l.effdt = (select max(l1.effdt) from location_tbl l1
                 where l.setid = l1.setid
                   and l.location = l1.location
                   and l1.effdt <= sysdate) or l.effdt is null)
order by rj.gap

We ran out of location codes that were set up and I ran a report to find out
what other values could be used.

          Followup:

Thats exactly what NULLS are for "missing data".  It is when the data is
unknown, that is the very essence of NULL.

      Rating:
      5
Excellent  March 01, 2003
Reviewer:
      Ik  from BG
Tom,

Just curious...Why is this your prefferred solution?

select month
  from t
 group by month
having count(*) >= all( select count(*)
                          from t
                         group by month );

Why not something like

select month,cnt FROM
(SELECT month, cnt, RANK(cnt) OVER () rn
FROM
(select month, count(*) cnt
  from t
 group by month))
WHERE
  rn = 1

This hits the table only once. Please correct me if iam wrong.

          Followup:

because all databases can do that one.  It should be answerable by anyone that
knows SQL.

      Rating: 4
abt Package
      Version  January 06, 2004
Reviewer:
      P.S.Vinodh  from India, Chennai
Hi Tom,
From couple of my frineds iam got same question which i could not able to get
answers.

 Is there any way to find Package specification and package Body version" OR is
there any thing like that which maintain the version of these packages in Oracle
or in Unix.

In couple of interview they asked these question seems.. i thought to know the
answer but in asktom i could not able to get it..

can you tell us something about that.

Thanks in advance

          Followup:

if you want "versions" and "source code control", you'll be using a source code
control tool (just like you would for java, c, vb, etc)

there is no "source code control" builtin the database itself.  there is no
concept of a "version" on the package body/spec

January 06, 2004 Navin -- Thanks for the question regarding "Using
      Oracle collections and result sets", version 8.1.6

      You Asked
Tom,
   we are implementing an application using EJB's. We need to be
able to insert , update , delete and view result sets from the
Oracle database. One way suggested is to use stored procedures
and functions and use Java or Session/entity beans to access the
tables directly.

Is there a better way to implement this functionality using
Oracle collections like arrays and accessing them from Java. We
need to be able to view result sets, do batch inserts/updates
and deletes. Are there any new features in Oracle 8i that I can
take advantage of ?
Appreciate your suggestions.

Thanks,
Navin.

      and we said...
Arrays/collections won't necessarily be any better (in fact not
as good as) a regular result set using straight JDBC.  Where
collections can come in handy is in returning a single "object"
or "glob" of data.  For example, say you have the EMP, DEPT and
PROJECTS table.  Assume DEPT is one to many with EMP and DEPT is
one to many with PROJECTS (a department has many emps and a
department is working on many projects).  Using collections, we
can return in one row the DEPT data, along with a collection of
EMP data and a collection of PROJECTS data.  Collections are
best used to fetch these non-relational (not "square") rows.

If you just have "flat" tables -- result sets will do it for
you.  You can use setExecuteBatch() on a prepared statement to
do bulk (array) inserts, updates, and deletes.  ResultSets can
use the setRowPrefetch() method to set their arraysize which
defaults to 10.

You can put the "data" beans right into the database, but
ultimately they are going to use JDBC to access the data
regardless of their physical location.
Review &
      Followup

      Rating: 3
follow up
      questions  September 27, 2001
Reviewer:  sj

"  Assume DEPT is one to many with EMP and DEPT is
one to many with PROJECTS (a department has many emps and a
department is working on many projects).  Using collections, we can return in
one row the DEPT data, along with a collection of EMP data and a collection of
PROJECTS data.  Collections are best used to fetch these non-relational (not
"square") rows.

If you just have "flat" tables -- result sets will do it for you.
"

Can  you show how the following can be done..

"Using collections, we can return in one row the DEPT data, along with a
collection of EMP data and a collection of PROJECTS data."

what do you mean by "non-relational (not "square") rows" and  ""flat" tables "

and by "result sets" do you mean ref cursors

          Followup:

Here is an example then.  First we start by creating the collection types:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create type empType as object
  2  ( ename varchar2(30),
  3    empno number
  4  )
  5  /
Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create type empTblType as table of empType
  2  /
Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create type projType as object
  2  ( name     varchar2(30),
  3    start_date date,
  4    end_date   date
  5  )
  6  /
Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create type projTblType as table of projType
  2  /
Type created.

Now I need some data to query:

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 table proj as
  2  select username name,
  3         created start_date,
  4         created+7 end_date,
  5         (mod(rownum,4)+1)*10 deptno
  6    from all_users
  7   where rownum <= 20
  8  /
Table created.

And now we can query the data:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select dname,
  2   cast( multiset( select ename, empno
  3                     from emp
  4                    where emp.deptno = dept.deptno ) as empTblType ) emps,
  5   cast( multiset( select name, start_date, end_date
  6                     from proj
  7                    where proj.deptno = dept.deptno ) as projTblType ) projs
  8    from dept
  9  /
DNAME          EMPS(ENAME, EMPNO)             PROJS(NAME, START_DATE, END_DA
-------------- ------------------------------ ------------------------------
ACCOUNTING     EMPTBLTYPE(EMPTYPE('CLARK', 77 PROJTBLTYPE(PROJTYPE('DBSNMP',
               82), EMPTYPE('KING', 7839), EM  '28-AUG-01', '04-SEP-01'), PR
               PTYPE('MILLER', 7934))         OJTYPE('OPS$TKYTE', '24-SEP-01
                                              ', '01-OCT-01'))

RESEARCH       EMPTBLTYPE(EMPTYPE('A', 7369), PROJTBLTYPE(PROJTYPE('SYS', '2
                EMPTYPE('JONES', 7566), EMPTY 8-AUG-01', '04-SEP-01'), PROJT
               PE('SCOTT', 7788), EMPTYPE('AD YPE('OPS$ORA817', '28-AUG-01',
               AMS', 7876), EMPTYPE('FORD', 7  '04-SEP-01'), PROJTYPE('OPS$C
               902))                          LBECK', '09-SEP-01', '16-SEP-0
                                              1'))

SALES          EMPTBLTYPE(EMPTYPE('ALLEN', 74 PROJTBLTYPE(PROJTYPE('SYSTEM',
               99), EMPTYPE('WARD', 7521), EM  '28-AUG-01', '04-SEP-01'), PR
               PTYPE('MARTIN', 7654), EMPTYPE OJTYPE('SCOTT', '28-AUG-01', '
               ('BLAKE', 7698), EMPTYPE('TURN 04-SEP-01'), PROJTYPE('A', '20
               ER', 7844), EMPTYPE('JAMES', 7 -SEP-01', '27-SEP-01'))
               900))

OPERATIONS     EMPTBLTYPE()                   PROJTBLTYPE(PROJTYPE('OUTLN',
                                              '28-AUG-01', '04-SEP-01'), PRO
                                              JTYPE('CTXSYS', '28-AUG-01', '
                                              04-SEP-01'))

ops$tkyte@ORA817DEV.US.ORACLE.COM>

See how there are 4 rows -- just the rows from the DEPT table came back.  The
result set is "jagged" -- not square -- in this case because we have these
collections which in effect are just like result sets themselves.  We have an
array of employees and an array of projects with each dept row.

A ref cursor is a result set.  A result set need not be a ref cursor (no ref
cursors above -- just a "cursor")

      Rating:
      5
Awesome  September 27, 2001
Reviewer:
      A reader
Can we pass this single row of information  as input into another procedure or
function, or is this output only usefull as a report.

          Followup:

You can definitely pass this around.  as soon as you define a collection type --
you have just invented a new PLSQL datatype as well.  You can define variables
of this type, sure.

      Rating: 5
great
      explanation  September 27, 2001
Reviewer:
      A reader
Lets assume I have dname,empTblType,projtbltype. Dname has one value in it, and
there are multiple values in the other 2.

How can we address the values inside the emptbltype.
Is emptbltype.empno , emptbltype.ename   valid ?

Give us an example procedure which will table dname,empTblType,projtbltype, and
do some processing.

when do we need to pass collections like this as parameters, i.e. where are they
very useful.

          Followup:

See

http://download-west.oracle.com/docs/cd/A87860_01/doc/appdev.817/a76976/adobjint.htm#452711
I've got lots of examples littered on this site with collections, search for
create or replace type as table of
and read some of them.

If you have a variable:

declare
    x   empTblType := empTblType(empType( 'Tom', 1234 ), empType( 'Bob', 5678
));begin
    dbms_output.put_line( x(1).ename );
end;
/

is how you would access it -- just like an array of records.

You would use this anywhere you felt you needed an array.  anywhere you needed
to pass an array of employees  -- this might be useful.

      Rating: 5
make us
      understand  September 27, 2001
Reviewer:
      A reader
what do you mean by "non-relational (not "square") rows" and  ""flat" tables "

,
The
result set is "jagged" -- not square --

What does the above terminology mean, try to explain in layman's language
please.

          Followup:

Look at the first row:

ACCOUNTING     EMPTBLTYPE(EMPTYPE('CLARK', 77 PROJTBLTYPE(PROJTYPE('DBSNMP',
               82), EMPTYPE('KING', 7839), EM  '28-AUG-01', '04-SEP-01'), PR
               PTYPE('MILLER', 7934))         OJTYPE('OPS$TKYTE', '24-SEP-01
                                              ', '01-OCT-01'))

that is really like:

ACOUNTING  CLARK   7782      DBSNMP     28-aug-01  04-sep-01
           KING    7839      OPS$TKYTE  24-sep-01  01-oct-01
           MILLER  7634

In a single row -- its not a "square", its not simple rows and columns, its rows
with columns that look like rows themselves.

      Rating: 5
for the above
      sqlplus output what are your format settings  September 27,
      2001
Reviewer:  A reader

          Followup:

the only formating was

column emps format a30
column projs format a30

      Rating: 5
retrieve from
      java  May 31, 2002
Reviewer:  Charlie

ACCOUNTING     EMPTBLTYPE(EMPTYPE('CLARK', 77 PROJTBLTYPE(PROJTYPE('DBSNMP',
               82), EMPTYPE('KING', 7839), EM  '28-AUG-01', '04-SEP-01'), PR
               PTYPE('MILLER', 7934))         OJTYPE('OPS$TKYTE', '24-SEP-01
                                              ', '01-OCT-01'))

>>>>>>>>>>>>>>>>>
Hi Tom,

After getting this "jagged" resultset via JDBC, how do we build JAVA object
while looping through this resultset?
Say we have several java objects such as JEmpTLbType, JEmpType, JDepartment.  A
small example would be greatly
appreciated.

Thanks,

          Followup:

I recommend you look at jpublisher

http://download-west.oracle.com/docs/cd/A87860_01/doc/java.817/a81357/toc.htm
it'll generate the mapping classes for you.

      Rating:
      5
ora-00913  May 31, 2002
Reviewer:
      Charlie
Thanks for pointing out JPublisher!  Another question is:

SQL> desc campaign  <-- regular table
 Name                                                  Null?    Type
 ----------------------------------------------------- --------
------------------------------------
 ID                                                    NOT NULL VARCHAR2(30)
 COMPANY                                                        VARCHAR2(40)
 START_DATE                                                     DATE
 END_DATE                                                       DATE
 CANCEL_DATE                                                    DATE
 DESCRIPTION                                                    VARCHAR2(200)

SQL> desc target  <-- regular table
 Name                                                  Null?    Type
 ----------------------------------------------------- --------
------------------------------------
 ID                                                    NOT NULL VARCHAR2(30)
 CAMPAIGN_ID                                                    VARCHAR2(30)
 MAIN_TARGET_ID                                                 VARCHAR2(30)
 SOP_ID                                                         NUMBER(10)
 DESCRIPTION                                                    VARCHAR2(200)

SQL> desc obj_campaign <-- object type
 Name                                                  Null?    Type
 ----------------------------------------------------- --------
------------------------------------
 ID                                                             VARCHAR2(30)
 COMPANY                                                        VARCHAR2(40)
 START_DATE                                                     DATE
 END_DATE                                                       DATE
 CANCEL_DATE                                                    DATE
 DESCRIPTION                                                    VARCHAR2(200)

SQL> desc obj_target  <-- object type
 Name                                                  Null?    Type
 ----------------------------------------------------- --------
------------------------------------
 ID                                                             VARCHAR2(30)
 CAMPAIGN                                                       OBJ_CAMPAIGN

SQL> CREATE OR REPLACE VIEW v_target
  2  OF obj_target
  3  WITH object identifier(id)
  4  AS
  5  SELECT
  6     t.id,
  7     cast((
  8             SELECT
  9                     c.id
 10                     company,
 11                     start_date,
 12                     end_date,
 13                     cancel_date,
 14                     c.description
 15             FROM CAMPAIGN c
 16             WHERE t.campaign_id = c.id
 17      )AS obj_campaign) CAMPAIGN
 18  FROM TARGET t;
                SELECT
                *
ERROR at line 8:
ORA-00913: too many values

When I try to create this OR view, it failed with error 913.  I can't figure it
out why.  Any clue?

Thanks,

          Followup:

CREATE OR REPLACE VIEW v_target
 OF obj_target
 WITH object identifier(id)
 AS
select t.id, obj_campaign( c.id, c.company, c.start_date, c.end_date,
c.cancel_date, c.description )
  from target t, campaign c
 where t.id = c.id(+)
/

is the way to do that.  (lose the outer join if you don't need it)

From the docs:

<quote>
CAST allows you to convert built-in datatypes or collection-typed values of
one type into another built-in datatype or collection type. You can cast an
unnamed operand (such as a date or the result set of a subquery) or a named
collection (such as a varray or a nested table) into a type-compatible datatype
or named collection. The type_name must be the name of a built-in datatype or
collection type and the operand must be a built-in datatype or must evaluate
to a collection value.

For the operand, expr can be either a built-in datatype or a collection type,
and
subquery must return a single value of collection type or built-in type.
MULTISET
informs Oracle to take the result set of the subquery and return a collection
value. Table 5–1 shows which built-in datatypes can be cast into which other
built-in datatypes. (CAST does not support LONG, LONG RAW, or any of the LOB
datatypes.)
</quote>

what that means is -- you can only use subquery in a CAST when

a) subquery returns a single row and a single column
b) subquery returns a NESTED TABLE or VARRAY type

In any event, you need not use it at all to do this.

      Rating: 5
invalid
      column  May 31, 2002
Reviewer:  Charlie

This is absolutely great stuff!!  Thanks a lot, Tom!!

One thing is when I refer to a column inside a object type, it raises the error
904.
So how can I use the column of object to search?

SQL> r
  1  select id, campaign
  2  from v_target
  3  where id = 'TBase1'
  4* and campaign.start_date < sysdate
and campaign.start_date < sysdate
             *
ERROR at line 4:
ORA-00904: invalid column name

          Followup:

You must use correlation names with objects in the queries:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select id, campaign
  2    from v_target T
  3          where id = 'TBase1'
  4            and T.campaign.start_date < sysdate
  5  /

no rows selected

      Rating:
      5
collection  May 31, 2002
Reviewer:
      Charlie
Cool!  Thanks!

SQL> r
  1  create or replace type obj_result as object
  2* (rid number(2), description varchar2(200));

Type created.

SQL> create or replace type result_collection as table of obj_result;
  2  /

Type created.

SQL> desc obj_target
 Name                                                  Null?    Type
 ----------------------------------------------------- --------
---------------------------
 ID                                                             VARCHAR2(30)
 CAMPAIGN                                                       OBJ_CAMPAIGN
 RESULTS
RESULT_COLLECTION

 If this obj_target contains a nested table/collection, how can I refer to its
member
 inside of this collection?

 SQL> select id, campaign
   2    from v_target T
   3          where id = 'TBase1'
   4            and T.campaign.start_date < sysdate
   5            and T.results(1).rid = 2
   6  /
ERROR at line 5:
ORA-00904: invalid column name

          Followup:

You cannot use subscripts in SQL.

You can join the nested table to the view, this is called "flattening".

You must remember NESTED TABLES HAVE NO ORDER.  There is no such thing as the
"first" or the "second".  Nested tables do NOT preserve order.  Therefore (1) on
one query might be (2) on another run.

      Rating:
      5
ORA-03113  June 01, 2002
Reviewer:
      Charlie
Hi Tom,

I try to "flatten" the results, but I got error 3113.  Any clue?

Thanks,

SQL> select id, r.description
  2  from v_target t, table(t.results) r
  3* where r.rid = 2
SQL> /
select id, r.description
*
ERROR at line 1:
ORA-03113: end-of-file on communication channel

          Followup:

Time to contact support and open a tar.  they can help you identify why this is.

      Rating: 3
Return value of
      collection  April 09, 2003
Reviewer:  Raj
       from India
Hi Tom,

I have a function at the database side. It returns a table of records.

When I tried to call this function from forms I'm not getting the value.

Here is the example

I have a package which has a function which returns table of ename from emp

CREATE OR REPLACE PACKAGE pkg_test AS

   TYPE typ_ename_tab IS TABLE OF emp.ename%TYPE;

   FUNCTION fn_GetEname RETURN typ_ename_tab;

END pkg_test;
/

CREATE OR REPLACE PACKAGE BODY pkg_test AS

   FUNCTION fn_GetEname RETURN typ_ename_tab IS

      op_ename typ_ename_tab := NULL;

   BEGIN

   SELECT ename
   BULK COLLECT INTO op_ename
   FROM emp;

   RETURN op_ename;

   END fn_GetEname;

END pkg_test;
/

I tested this package at the back end with the foll. code
DECLARE

   a pkg_test.typ_ename_tab;

BEGIN

   a := pkg_test.fn_GetEname;

   FOR j IN a.FIRST..a.LAST
   LOOP
      DBMS_OUTPUT.PUT_LINE(a(j));
      DBMS_OUTPUT.PUT_LINE(' ');
   END LOOP;

END;
and the out put was

SMITH
ALLEN
WARD
JONES
MARTIN
BLAKE
CLARK
SCOTT
KING
TURNER
ADAMS
JAMES
FORD
MILLER
So far so good.

But when I tried to get this in a When_button_pressed Trigger I was able to
compile it without error but I got the output as NULL

The Code in when_button_pressed is
DECLARE

   a pkg_test.typ_ename_tab;

BEGIN

   a := pkg_test.fn_GetEname;

   IF a IS NOT NULL THEN
      FOR j IN a.FIRST..a.LAST
    LOOP
        MESSAGE(a(j));
         MESSAGE(' ');
    END LOOP;
    ELSE
      MESSAGE('A IS NULL');
    END IF;

END;

The Out put is a message A IS NULL at the status Bar.

Pls tell me if it is possible to get the table of records in forms.

ONE IMPORTANT POINT IS I WANT TO USE BULK COLLECT OPTION IN THE SELECT
STATEMENT.

Thanks a Lot in advance.

Regards
Raj

          Followup:

record groups do array fetches -- that would be my approach in forms actually.
The record group will work similar to a 2 dimensional array.
create_group_from_query, populate_group might be what you are looking for.

I haven't done forms since about 1995 -- so, I'm not really upto speed on what
it can/cannot do with records.  You can try otn.oracle.com -> discussion forums
-> developer.

(record groups would be my path however)

      Rating: 2
Nested tables
      vs. one-to-many  June 10, 2003
Reviewer:
      A reader
Suppose my data/business has a one-to-many relationship. The traditional way to
model this is a parent table and a child table with a FK pointing to the parent
table.

Of course, with 8i, this could also be modeled as a parent table with a nested
table/varray as one of the columns.

At what point does the nested table approach become unviable? When the "many" in
one-to-many reaches, say, 100s of rows?

Suppose I have a 1000 row nested table stored in a main table and I want to
update one row in that. Oracle would unnest that "table", update the one row and
write the entire object back, right? Isnt this expensive?

What if I need to add 100s of rows each day to that nested table? Is this a
misuse of the feature?

Just trying to get a sense of when pure relational makes sense vs.
object-relational.

Thanks

          Followup:

If you have my book "Expert one on one Oracle" -- I go into this in some detail.

A nested table is nothing more then a parent/child table where the server will
supply a surrogate key to the parent table ( a hidden 16 byte raw with a unique
constraint on it per parent row ) and a child table with the same 16 byte raw
foreign key that you need to know to INDEX yourself.

Since virtually ever parent/child relationship I've seen has natural keys that
can (should) be used -- I would "do it myself", no nested tables.

I'll save on the 2 hidden columns
and their indexes
and I'll be able to use the child table "as a table" when I want.  nested tables
make that "hard"

      Rating:
      2
  June 11, 2003
Reviewer:
      A reader
Seems like you are saying that nested tables is useless in practice.

So why did Oracle add this feature at all? As you said, it doesnt seem to have
much real-world applications.

In general, all the object-oriented features, other than saying that "yes, we
support objects in Oracle", are there any real-world benefits over pure and
simple ER relational modelling?

Yes, I can see the benefits of user defined types but just as a convenience,
nothing that cannot be achieved using pure native datatypes.

Comments? Thanks

          Followup:

I use them in my code all of the time.

they are very useful in OR views.

They are useful as a programming construct, I choose NOT to use them to
persistently store data.

In a view, yes.
In my code, oh yes.
As a datatype for a column in a physical table, no.

The comment about the udt's is pretty straight on -- considering they are
actually implemented on top of pure native datatypes.

      Rating:
      2
  June 11, 2003
Reviewer:
      A reader
---x---
I use them in my code all of the time.
they are very useful in OR views.
---x---

I'm sorry but I dont think I quite understand you.

How do you use them in your code, for what?

What do you mean, "useful in OR views"? Can you explain these 2 uses with
examples? Thanks.

          Followup:

Object Relational (OR) Views

in my code?  for collections, tables of objects that can be queried from.  user
defined types.

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') );

of course, with 9iR2 having index by tables that can be indexed by varchar2,
this is now obsolete.

If you have my book "Expert one on one Oracle" however, I go into things like
building an in memory hash table type (like the type java has for example).  It
uses nested tables as well.

I use them to return procedurally built result sets from stored procedures, so
you can:

  select * from TABLE( some_function(inputs...) );

OR views -- documented in:

http://download-west.oracle.com/docs/cd/B10501_01/nav/docindex.htm#index-APP
the application developers guide "object relational features"

      Rating:
      3
Reviewer  July 09, 2003
Reviewer:
      DC  from Canada
I found DML on collection is hard. How can we update a nested table using one
update DML with mulitple rows referenced by a join table and for each row, there
are nested entries to be updated base on reference to another table ?

Thanks,

          Followup:

give me a for example example to work with.

AND remember what nested tables are all about -- it is virtually "a virtual
table per row".

It sounds like you might really be wanting "an honest to goodness standalone
table"

      Rating:
      4
DC  July 09, 2003
Reviewer:  DC
       from CANADA
An object table purchase order with line items stored as nested table.
create lineitem_objtyp as object (stock_no number,
order_qty number, unit_cost number(11,2), discount number(3,2));
create type lineitem_ntabtyp as table of lineitem_objtab;
create type po_line_objtyp authid current_user as object
(po_no number, po_rec_date date, po_firm_date date, customer_id number,
lineitem_ntab lineitem_ntabtyp);
create table po_line_objtab of po_line_objtyp nested table
lineitem_ntab store as lineitem_store_ntab;
There are 2.3M PO with each PO may contain 1 or many hundreds of line items.
po_no is unique. I simplify the ddl as this object table has many columns.
The unit cost of certain items need to be adjusted every month end base on
customer_id and their purchase order number. I have a reference file every month
end from the marketing area which contains the following info, (customer id,
po_no, stock_no, adjusted_unit_cost).
How can I process the po_line_objtab base on po_no from the reference file and
update the unit_cost with adjust_unit_cost base on stock_no from the reference
file.
I hope one update statement will work as we have many reference files to be
processed. I'm trying to see if it can be done using merge, cast etc without
writing pl/sql.

Thanks,

          Followup:

it seems to me that you want "objects", but I want my relational stuff too.

don't worry -- it is very typical.  Lets say you were creating a banking
application.  For the tellers, it would be very conviently to model

"A customer, having multiple accounts, each account having multiple
transactions"

sounds great -- but now you have a bunch of customer objects, when you want to
figure out at the end of the day "how many transactions did I do", you need to
"message" each customer and ask them "did you do any today?  if so, how many".
If you want to find out "how much money is in the bank", you have to once again
message each customer....  "how many customers used branch X in the last 30
days" -- message every customer.

If we only viewed data from one perspective, objects would be great.  There are
times we do -- a CAD/CAM drawing for example.  But most of the times we need
multiple views of the data.

that is where the strength of an rdbms, when used properly, comes in.

You appear to want to use line items as a strong entity, all by itself, as the
driving table.  But by hiding as an attribute of a top level entity like that -
you lose.  Sure, there are undocumented hints and tricks -- but in general,
you've modelled your data from the perspective that "PO is king"

I would be looking at using real transactional tables with object relational
views on top of them to give you this object flavor.  Then -- the sky is the
limit with what you'll be able to do -- you'd have your PO perspective, but you
could easily model an "item perspective", a customer persepective and so on --
things you'll ultimately want in a real system.

      Rating:
      4
DC  July 10, 2003
Reviewer:  DC
       from CANADA
Thanks for your valuable comment.  So, what will be the sql looks like to update
lineitem's unit cost base on stock_no and po_no ?

          Followup:

Well that would be a correlated update:

update lineitem
   set unit_cost = ( select unit_cost from other_table
                      where other_table.stock_no = lineitem.stock_no
                        and other_table.po_no = lineitem.po_no )
 where exists  ( select unit_cost from other_table
                      where other_table.stock_no = lineitem.stock_no
                        and other_table.po_no = lineitem.po_no );

      Rating: 5
very useful -
      one question though  July 10, 2003
Reviewer:
      A reader
"Where collections can come in handy is
in returning a single "object" or "glob" of data."

How about returning cursor containing cursors - how does
that perform against the solution of using collections
since that also can return "non-square" data?

thanx!

          Followup:

absolutely

select dname, cursor( select ename from emp where emp.deptno = dept.deptno )
  from dept;

works nicely as well -- but if you always get the ename list for every dept --
it can involve extra round trips where as a collection will not.

      Rating: 5
thanx -
        July 10, 2003
Reviewer:  A reader

when you use collections - you are returning the
entire dataset - whereas in case of cursor, you
may still have to make round trips - is that
what you meant? Here by round trip do you mean the
round trip implied in "fetching" the data from
the returned resultsets (or cursor)?

thanx!

          Followup:

when using collections -- the entire row is returned to the client.

when using cursor variables, pointers to result sets are returned.  these result
sets reside on the server initially so the client has to turn around and go back
to the server to get them.

yes, I mean the round trip involved in fetching the result sets for each column
that is a cursor.

      Rating: 4
Updates
      Colleciton  July 10, 2003
Reviewer:
      A reader
I tried your method, it doesn't work.  I have a sample of testing data for you
to review and your sql can not reference the object. Please see below.  Trying
to update stock_no 2 as reference in the reference_table. Can you help ?
 SQL> create type lineitem_objtyp as object (stock_no number, order_qty number,
  2  unit_cost number(11,2), discount number(3,2));
  3  /

Type created.

SQL> create type lineitem_ntabtyp as table of lineitem_objtyp;
  2  /

Type created.

SQL> create type po_line_objtyp authid current_user as object
  2  (po_no number, po_rec_date date, po_firm_date date, customer_id number,
  3  lineitem_ntab lineitem_ntabtyp);
  4  /

Type created.

SQL> create table po_line_objtab of po_line_objtyp nested table
  2  lineitem_ntab store as lineitem_store_ntab;

Table created.
  1  create table reference_table (customer_id number, po_no number, stock_no
number,
  2* adjusted_unit_cost number)
SQL> /

Table created.
  1  insert into po_line_objtab values (10, null, null, 10001,
  2  lineitem_ntabtyp(lineitem_objtyp(1, 1, 3.2, 0), lineitem_objtyp(2, 1, 2.4,
0),
  3* lineitem_objtyp(3, 1, 2.2, 0)))
SQL> /

1 row created.

SQL> ed
Wrote file afiedt.buf

  1  insert into po_line_objtab values (11, null, null, 10001,
  2  lineitem_ntabtyp(lineitem_objtyp(5, 1, 2.2, 0), lineitem_objtyp(2, 1, 2.4,
0),
  3* lineitem_objtyp(7, 1, 2.2, 0)))
SQL> /

1 row created.

SQL> ed
Wrote file afiedt.buf

  1  insert into po_line_objtab values (12, null, null, 10001,
  2  lineitem_ntabtyp(lineitem_objtyp(6, 1, 2.1, 0), lineitem_objtyp(7, 1, 2.4,
0),
  3* lineitem_objtyp(8, 1, 2.2, 0)))
SQL> /

1 row created.
SQL> insert into po_line_objtab values (13, null, null, 10001,
  2  lineitem_ntabtyp());

1 row created.
Wrote file afiedt.buf

  1  insert into po_line_objtab values (14, null, null, 10001,
  2  lineitem_ntabtyp(lineitem_objtyp(6, 1, 2.1, 0), lineitem_objtyp(2, 1, 2.4,
0),
  3* lineitem_objtyp(8, 1, 2.2, 0)))
SQL> /

1 row created.
SQL> insert into reference_table values (10, 10001, 2, 5.5);

1 row created.

SQL> ed
Wrote file afiedt.buf

  1* insert into reference_table values (11, 10001, 2, 5.5)
SQL> /

1 row created.

SQL> ed
Wrote file afiedt.buf

  1* insert into reference_table values (12, 10001, 2, 5.5)
SQL> /

1 row created.
SQL> insert into reference_table values (13, 10001, 2, 4.5);

1 row created.

SQL> commit;

Commit complete.

SQL> select * from reference_table;

CUSTOMER_ID      PO_NO   STOCK_NO ADJUSTED_UNIT_COST
----------- ---------- ---------- ------------------
         10      10001          2                5.5
         11      10001          2                5.5
         12      10001          2                5.5
         13      10001          2                4.5

Wrote file afiedt.buf

  1  update lineitem o
  2  set o.unit_cost = (select r.adjusted_unit_cost from reference_table r
  3  where r.stock_no = o.stock_no and
  4  r.po_no = o.po_no)
  5  where exists
  6  (select u.adjusted_unit_cost from reference_table u
  7  where u.stock_no = o.stock_no and
  8* r.po_no = o.po_no)
SQL> /
update lineitem o
       *
ERROR at line 1:
ORA-00942: table or view does not exist

You have any suggestion.  Thanks.

          Followup:

I must be missing something here -- cause in the one followup I said "i don't
think you want to model it like that since your requirements cause you to have a
different view or perspective."

You said "ok, so what would it look like then"

I (assuming you meant, Ok -- with real relational tables as you described --, so
what would it look like then) showed you how to with "real tables"

You have Purchase order objects.
They happen to have collections of lineitems.

Lineitems do not exist "by themselves"

You will be hard pressed to make them "exist by themselves"

they only exist in the context of a PO as you have them modeled.

      Rating: 3
Updates
      Collection  July 10, 2003
Reviewer:  DC
       from CANADA
Some mistakes in my sample. The ddl of the reference_table should be,
create table reference_table(po_no, customer_id, stock_no, adjusted_unit_cost).

Thanks,

      Rating: 3
Updates
      Collection  July 11, 2003
Reviewer:  DC
       from CANADA
Hi, the purchase order may be my bad example. The way what we have here is a
Object Model in a utility environment.  The reference table is the consumption
reading from the customers.  Most of the tables are designed using collections
encapsulated with methods.  I tried to simplify the schema so that you know my
Q.  Is there a way to update the nested table's content massively base on
certain criteria using my example ?  I tried my sql as in below ( an idea) but
it gave me ORA-03001 unsupported features.  Below sql I think is not optimized
too as it has to union to those non-updated nested entries.

update po_line_objtab set lineitem_ntab =
(cast(multiset
(
select stock_no, order_qty, unit_cost + adjusted_unit_cost from
(select r.po_no, k.stock_no, k.order_qty, k.unit_cost, r.adjusted_unit_cost
from reference_table r,
table(select lineitem_ntab from po_line_objtab p
where p.po_no = r.po_no) k where k.stock_no = r.stock_no)
union
select k.stock_no, k.order_qty, k.unit_cost
from reference_table r,
table(select lineitem_ntab from po_line_objtab p1
where p1.po_no = r.po_no) k where k.stock_no <> r.stock_no
) as lineitem_ntabtyp))

Thanks,

          Followup:

do you happen to have access to my book "Expert one on one Oracle"?

      Rating: 3
Updates
      Collection  July 11, 2003
Reviewer:  DC
       from CANADA
Finally got it working. Here is the sql, missing the multiset join clause.

      Rating:
      5
  July 24, 2003
Reviewer:  Randy
       from STL
Tom,  Thanks for all your time and patients.

I think that I may be able to use this to solve one requirement we have.  I
currently return a result set to middle teir for reporting.  Result set is from
two tables with a cross-reference table in-between.  Here is a simplified
example of the current result set.

Work_id   Emp    Location    Area       District
    1     John     NYC       Queens       3rd
    1     John     NYC       Queens       4th
    1     John     NYC       Queens       5th
    1     John     NYC       Broncs       2nd
    1     John     NYC       Broncs       4th
    1     John     LA        East         2nd
    1     John     LA        East         3rd
    2     Tom      NYC       Queens       3rd
    2     Tom      NYC       Queens       4th

The middle teir is currently used to flatten this out.  I would like to have the
result set grouped by Work_id, emp and return like this:

Job_id  Emp  Location  Area                District
  1     John NYC,LA    Queens,Broncs,East  2nd,3rd,4th,5th
  2     Tom  NYC       Queens              3rd, 4th

Any creative ideas?

Thanks again.

          Followup:

patients -- freudian slip there? :)

got a version to go with that inquiry?

      Rating:
      5
  July 25, 2003
Reviewer:  Randy
       from STL
Sorry about that.  We are currently on 8.1.7.4  We will be moving to 9.2 Q1
2004.  This fix would be something they would like this fall.  We have a release
Oct 1st of this year.  But if it will require 9i thats ok, it will push.

Thanks.

B.T.W.  Aren&#8217;t you suppose to be on vacation?

          Followup:

in 9i, we can use a user defined aggregate to do this somewhat easily (search
for stragg on this site to get an idea -- that code needs a tweak to "distinct"
the strings but that is easy)

in 8i, it would be "hard", we'd have to write a plsql function for each
column...

      Rating:
      5
  July 28, 2003
Reviewer:  Randy
       from STL
Ok, got it working as SQL.  My select now looks like this:

SELECT  JOB_ID,
    STRAGG (DISTINCT LOCATION),
    STRAGG (DISTINCT AREA),
        STRAGG (DISTINCT DISTRICT)
FROM T
GROUP BY JOB_ID.

Problem I have is that this does not seem to work inside a PL/SQL Block.  I get
the error

ORA-06550: line 15, column 47:
PL/SQL: ORA-30482: DISTINCT option not allowed for this function
ORA-06550: line 12, column 4:
PL/SQL: SQL Statement ignored

Is there a different way to do the distinct?  I thought maybe inside the
ODCIAGGREGATEMERGE function, but I could not figure out how.

Thanks.

          Followup:

Interesting, looks like a "product issue" to me.  use a dynamically opened ref
cursor as a temporary solution:

variable x refcursor
declare
    l_cursor sys_refcursor;
begin
    open l_cursor for '
SELECT  JOB_ID,
        STRAGG (DISTINCT LOCATION) l,
        STRAGG (DISTINCT AREA) a,
        STRAGG (DISTINCT DISTRICT) d
   FROM T
  GROUP BY JOB_ID';

  :x := l_cursor;
end;
/

print x

(please file an issue with support as well!)

      Rating: 4
pls-00157
      AUTHID only allowed on schema-level programs  December 23,
      2003
Reviewer:  Craig  from Lowell, AR
Tom -

I have a package with a private procedure in 9iR2.  This private procedure is
the only portion of the package I need invokers rights on.  However, when I
declare my private procedure:

PROCEDURE flag_change(p_nbr IN number
                    , p_seq IN number)
           authid CURRENT_USER IS
.
.
.
END flag_change;

I get a PLS-00157: AUTHID only allowed on schema-level programs

Is this to say that invokers rights must be declared/defined at the package
level?  I'm not sure I 100% understand what this error is telling me, and what I
need to do to fix it.  Can you once again help me out?

Thanks!!

ps.  happy holidays!  =)

          Followup:

the package is either authid current user or not.

Now, the funny thing is, even if we could make flag_change be authid
current_user, the very fact that it is a PRIVATE PROCEDURE (callable only by
other routines in this package) means that the current user will ALWAYS be the
package owner!!!! (once you enter a definers rights routine, the current_user is
the owner of the package/procedure!)

so, even if it worked -- it would be a "no-op".  it would not change anything as
the only value of current user that could ever happen would be the schema owner
of the package!

      Rating: 5
Thanks Tom.Very
      useful  December 26, 2003
Reviewer:  Sami

      Rating:
      3
  January 06, 2004
Reviewer:
      Praveen  from bangalore, india
Hi Tom

    We are seriously considering a design revamping of a table's VARCHAR(255)
column into a nested table type. But
we are least aware of the performance impacts such a change may cause to a 'data
resolution' application that uses
this table. We highly appreciate an advice or some hints about how you would be
approaching the problem.

Here is the scenario:

The table is an error log table, say err_log. When a business exception occurs
(we have all the known business exceptions),
an error logging procedure takes the responsibility of logging the error into
err_log table with such
informations like time_stamp, source procedure, a record_id etc. But in
addition to those above informations we can have a set of very important
parametes and their values at the time of error.
This set of parameters can vary in number (sometimes none); hence we store them
as a string in a VARCHAR2 column
(EG: say we have 2 parameters in hand at the time of error: parameter_1 &
parameter_2 with values 12345 & 'ABCD',
then we create a string like 'PARAM1:12345;PARAM2:ABCD'). (see the hard codings
PARAM1:, PARAM2: etc.)

Now you might have guessed the problem one can face during a data resolution
process. We cannot directly query the
err_log table based on the values of parameter_1 & parameter_2. This indeed
makes the data resolution application a
bit complicated (String manipulation, data_type conversion etc are to applied).
Design revamping is because of the fear that new bussiness rules (hence new
exceptions) may come
and the above method is indeed going to be cost dearly for a maintanence team.

Our considerations are:

1) Use a nested table instead of VARCHAR2 column.
    Thus    PARAM1    12345
            PARAM2  'ABCD'
            PARAM3    '21-MAY-03'
    will be a nested table field values in err_log table which will replace the
old varchar2 field value like
    'PARAM1:12345;PARAM2:ABCD;PARAM3:21-MAY-2003'.

2) Normalize the table err_log. Perhaps we may need to create additional tables
like 'PARAMETER' table, 'ERROR_PARAMETER' table etc

How does the above 2 approach are going to affect the performance, if the
err_log table is going to be populated
hugely after every ETL process. ? (size of the err_log table is not much of a
concern since this is regularly archived).

OR

Is there any other better method?

   -Thanks Tom
   -Praveen

          Followup:

1 and 2 are equivalent.

(if you have my book expert one on one oracle -- i show you exactly what a
nested table is, expose the implementation totally)

a nested table will simply add a 16 byte UNIQUE raw column to the "parent
table".
it will set up a child table with this same 16 byte raw column (a fkey to
parent).
you will have to index this column in the nested table.

I find using "plain old relational tables" to be the "best" (do it yourself).
In general, your parent table already has a primary key (no need for that 16
byte raw unique column) and you can simply use that.

If you like the nested table syntax, by all means create an object relational
view to give yourself the "appearance" of having the nested table (but retain
the flexibility of plain old relational tables which cannot be beat by any
object model)

January 06, 2004 Vijay Anandh -- Thanks for the question regarding
      "query for specific result format", version 9.0.1.4.0

      You Asked
Hi Tom,

I have the foll. situation.

A employee may belongs to all 5 different depts.

I want to print a report which should show the all the depts that the person
belongs to.

For Eg.

Name   Dept1    Dept2     Dept3   Dept4   Dept5
Vijay  Yes      Yes       Yes     No      No
Tom    No       No        No      Yes     Yes.

Please let me know how to do this in a sql query.

One possible soln is using the dept table 5 times.
But i don't want to use this soln,Since in my case the dept table going to
contain lakhs of records.

please let me know your response.

Thanks in advance.

Vijay

      and we said...
select ename,
       nvl(max(decode(deptno,1,'Yes')),'No') dept1,
       ....
       nvl(max(decode(deptno,5,'Yes')),'No') dept5
  from emp
 group by ename;

January 06, 2004 Carlos -- Thanks for the question regarding
      "dynamic ORDER BY in a Cursor", version 7.3.4

      You Asked
I'm declaring a Cursor in a stored procedure and I want that the cursor return
me the
rows in the order depending on a parameter.
For example:

For Valued Stock Method:

cursor c_m is
      select id,qty
         from t_stock
         where qty>0
         order by id P_METHOD;

being P_METHOD='desc' if the Valued Stock Method is LIFO or
P_METHOD='asc' if the Method is FIFO.

The message it displays me is:

PLS-00103: Encountered the symbol "p_method" when expecting one of
the following:
. ( , * @ % & - + ; / for mod rem an exponent (**) asc desc

Is it possible to do it?

Thanks a lot,

Carlos

      and we said...
order by decode( p_method, 'asc', 1, -1 ) * id

OR, you would have to use a ref cursor:

  if ( p_method = 'desc' )
  then
     open c_m for select id,qty
         from t_stock
         where qty>0
         order by id DESC;
  else
     open c_m for select id,qty
         from t_stock
         where qty>0
         order by id ASC;
  end if;

But since I cannot see an index being used to present the data "sorted" anyway
(the range scan on qty>0 will not have id sorted if the index is on (qty,id) so
it'll sort at the end anyway and if the index is on id,qty -- it will have to
search the entire index.... so thats not a good solution either) the decode
method should be "just fine"
Review &
      Followup

      Rating: 4
Good
      indeed  January 07, 2004
Reviewer:  Carlos
       from Montevideo, Uruguay
Very smart the idea of using decode

January 06, 2004 mo -- Thanks for the question regarding "Flexible
      Parameter Passing in mod_plsql", version 8.1.7

      You Asked
Tom:

Thanks a lot about pointing me this neat web feature:

1.  Is this what you use always in asktom site?

2.  I was trying to see more examples of this but the above link you refer to

http://www.olab.com/doc/books/plsql/pl_invk.htm
does not seem to work?

3.  Let us say I have the following html form and after user fills it out I want

to post values to a save procedure:

htp.formtext(p_lastname,30,30,null);
htp.formtext(p_firstname,30,30,null);
htp.formtext(p_city,30,30,null);
htp.formtext(p_state,10,10,null);
htp.formtext(p_zip,5,5,null);
FOR x in (1..5) LOOP
htp.formtext(p_itemno,2,2,null);
htp.formtext(p_description,30,30,null);
htp.formtext(p_qty,5,5,null);
htp.formtext(p_unit_price,5,5,null);
END LOOP;

---------*this is how I used to do it.

create or replace procedure save_form (
  p_lastname    VARCHAR2  DEFAULT NULL,
  p_firstname   VARCHAR2 DEFAULT NULL,
  p_city        VARCHAR2 DEFAULT NULL,
  p_state       VARCHAR2 DEFAULT NULL,
  p_zip         VARCHAR2 DEFAULT NULL,
  p_itemno      TYPES.ARRAY DEFAULT TYPES.EMPTY_ARRAY,
  p_description  TYPES.ARRAY DEFAULT TYPES.EMPTY_ARRAY,
  p_qty          TYPES.ARRAY DEFAULT TYPES.EMPTY_ARRAY,
  p_unit_price  TYPES.ARRAY DEFAULT TYPES.EMPTY_ARRAY,)

as

v_personid   number(5);

begin
    select person_id into v_personid from dual;

    insert into person (personid,lastname,
                        firstname,state,zip )
                    values
                      (v_personid,p_lastname,p_firstname,
                       p_city,p_state,p_zip);
   If (SQL%ROWCOUNT > 0) THEN
FOR i in 1..p_description.count
LOOP
       insert into items (personid,itemno,description,
                       qty,unit_price)
                      values
                      (v_personid,p_itemno(i),
                       p_description(i),p_qty(i),
                       p_unit_price(i) );
  END LOOP;
ELSE
  rollback;
END IF;

END;
-----Now to use flexible parameters is this how you do it?

create or replace procedure save_form (
  name_array        TYPES.ARRAY DEFAULT TYPES.EMPTY_ARRAY,
  values_array      TYPES.ARRAY DEFAULT TYPES.EMPTY_ARRAY)

as

v_personid   number(5);

begin
    select person_id into v_personid from dual;

    insert into person (personid,lastname,
                        firstname,city,state,zip )
                    values
                      (v_personid,
                       value_array(1),value_array(2),
                       value_array(3),value_array(4),
                       value_array(5) );
   If (SQL%ROWCOUNT > 0) THEN
FOR i in 1..name_array.count
LOOP
    If (name_array(i)='p_description') THEN
       insert into items (personid,itemno,description,
                       qty,unit_price)
                      values
                      (v_personid,value_array(i),
                       value_array(i+1),value_array(i+2),
                       value_array(i+3) );
  END LOOP;
ELSE
  rollback;
END IF;

END;

4.  Would not using flexible parameters make code much easier to maintain. If
you change forms by adding or deleteing text fields you do not have to worry
about adding/deleteing the parameters in the save/update procedure?

5.  when would you use a four parameter interface instead of two?

Thank you,

      and we said...
1) nope, i like formal named parameters.  I like things clear, explicit.  I type
fast -- I don't mind having real argument lists that help me validate my inputs
and outputs.  generic code is fine, generic code breaks lots.  Less chance for
error if you are explicit.

2)
http://download-east.oracle.com/docs/cd/A97329_03/web.902/a90855/feature.htm#1005765
is the only source I'm aware of.  OLAB was the name of the app server groups
webserver a couple of years ago -- long time ago.

3) sort of -- you'll need checks to see how many rows are actually in your form
so you don't end up inserting lots of empty rows into items -- your current
logic doesn't look right but thats a programmer bug -- you can fix that up.

4) i seriously 100% doubt it -- sincerely.

I would hate to inherit a zillion lines of spagehtti code that indirectly
referenced everything through arrays like that.

You tell me -- which is really clear to you as to what it expects as input:

create or replace procedure save_form (
  p_lastname    VARCHAR2  DEFAULT NULL,
  p_firstname   VARCHAR2 DEFAULT NULL,
  p_city        VARCHAR2 DEFAULT NULL,
  p_state       VARCHAR2 DEFAULT NULL,
  p_zip         VARCHAR2 DEFAULT NULL,
  p_itemno      TYPES.ARRAY DEFAULT TYPES.EMPTY_ARRAY,
  p_description  TYPES.ARRAY DEFAULT TYPES.EMPTY_ARRAY,
  p_qty          TYPES.ARRAY DEFAULT TYPES.EMPTY_ARRAY,
  p_unit_price  TYPES.ARRAY DEFAULT TYPES.EMPTY_ARRAY,)

create or replace procedure save_form (
  name_array        TYPES.ARRAY DEFAULT TYPES.EMPTY_ARRAY,
  values_array      TYPES.ARRAY DEFAULT TYPES.EMPTY_ARRAY

I would find maintaining version 1 about a million times easier I believe then
version 2.

Your logic of "If
you change forms by adding or deleteing text fields you do not have to worry
about adding/deleteing the parameters in the save/update procedure?" is
fundementally flawed.

Tell me -- if you add something (a new field) to your FORM and you do not modify
you save_form routine -- whats going to happen???? (bug).  You have to edit the
code anyway to add the logic to deal with this new field (ESPECIALLY IF THIS
FIELD is added to the middle of the form, sort of shifts all of your indexes no?
yes, in fact it would destroying the logic in there... hmmm.)  You have to
update the code when you do this anyhow -- so what pain is there in documenting
for programmers in the future who would otherwise have the unenviable job of
getting this "array based" code to try and figure out what it does.  (i would
hate it myself)

5) never -- as it says clearly:

2.5.2.2 Four parameter interface
The four parameter interface is supported for compatibility.

it is the was OAS (iAS's precursor) did it.

Review &
      Followup

      Rating: 5
flexible
      parameter  November 16, 2002
Reviewer:  mo

Excellent answer. BTW I enjoy reading your book.  It is like reading a story.

1. The only thing i am not sure of is how you retrieve values for the passed
parameters. DO yu always refer to the named part of the array to get the value
or you you use a pointer like:

For this:
name_array ==> (`x', `y', `z')
values_array ==> ('john', '10', 'doe')

Is it like :

 insert into table (fname,id,lname) values (x,y,z);
or
insert into table (x,y,z) values (name_array(1),name_array(2),name_array(3));

          Followup:

the arrays are "correlated" -- if you have the two arrays as you depict above
then the name_array tells you that value_array(1) has the VALUE of the field 'X'
from your form.

You need to figure out how YOU want to process that.

You should realize it clearly cannot be

 insert into table (fname,id,lname) values (x,y,z);

you have no variables x, y, z -- you have an array that has values of names --
x, y, and.

It is very doubtful you want to use

insert into table (x,y,z) values (name_array(1),name_array(2),name_array(3));

as well -- you don't want to insert the strings 'x', 'y', 'z' do you???

You see -- this is why I believe named, formal parameters are better.

the code is definitely more clear
there is no ambiguity
you won't get confused about what value you are dealing with.

      Rating: 5
example why
      you'd use flexible  February 06, 2003
Reviewer:
      Scott Wesley  from Perth, Western Australia
G'day Tom

Would you have an example in mind where flexible parameter passing would be the
preferred method.
It seems that you'd either need to really trust the calling procedure, and/or
have lot's of if statements in the called procedure to verify the names and
values of each element in the array, and then decide what to do with them.
Flexible parameter passing exists, so *surely* there must be a good reason /
example of where to use it?

Please don't refer to olab, because the link is unavailable.

Thanks.

          Followup:

Nope, I've never really used it personally.

As I said I prefer formal, named parameters.  Peoples fondness for this stems
from not wanting to type in formal named parameter lists.  Some people are just
more comfortable with it because they language they were using only supported
this "cgi-environment" way of doing it.

      Rating: 4
I've used
      it  February 07, 2003
Reviewer:  Steve
       from UK
Don't like it - but I have used it in the past.  I needed it where I had a web
form where the number of parameters to be passed were unknown (there was a
javascript routine which dynamically changed the number of inputs based on the
users initial input).

Now I know you're thinking Tom about Javascript, but the web developers and
client insisted it be done that way, and I have to say it did work pretty well
but I wouldn't want to use flexible parameter passing as a matter of course.

      Rating: 3
multiple
      parameters of same name  July 07, 2003
Reviewer:
      Scott  from Perth, WA
G'day

Is flexible parameter passing also used to enable you to pass multiple values
for the same parameter name?

eg:

http:///pls/DAD_name/procedure_name?x=1&x=4
I remember reading somewhere about receiving the information via an array.

Have I remembered correctly? (where could I get further info) or was I dreaming?

I'm investigating the feasibility (and how to) of submitting a multi row web
form.

          Followup:

No, you can send arrays of values without it.

the flexible parameter passing is setup so you can get all of the values as a
pair of correlated arrays -- instead of formal named parameters.

You could always pass arrays -- even using formal named parameters.

<form method=post action=foo.bar>
<input type=text name=abc>
<input type=text name=abc>
<input type=text name=abc>
<input type=text name=abc>
<input type=submit>
</form>

can invoke the package:

create or replace package foo
as
   type array is table of varchar2(4000) index by binary_integer;
   procedure bar( l_data in array );
end;
/

      Rating: 4
How to pass %
      wild card ?  January 05, 2004
Reviewer:  Fan

http://127.0.0.1/poc/poc_cart/webpkg.searchbasic?
in_term=%ask%&p_skip=15&p_max=15

tom,
I got a problem trying to pass in URL link like above,
'%ask%' to do wildcard search, I am not doing it right, am I ?
How do i escape it ?

Thanks

          Followup:

You have to escape url strings (it is called URL encoding)

A % in a url would be %25 (25 being the hex ascii code for %)

A blank can be %20

and so on...

I use a function like this:

ops$tkyte@ORA920PC> 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.

ops$tkyte@ORA920PC> select urlencode( '%ask a question%' ) from dual;

URLENCODE('%ASKAQUESTION%')
---------------------------------------------------------------------------------
--------------------------------------------------
%25ask%20a%20question%25

there is a function htf.escape_url, but it doesn't get all of the characters you
need (like a space)

      Rating: 4
urlencode
      parameter values only ?  January 06, 2004

Reviewer:  fan
Thanks Tom for the encode function..
Looks like this function is to be used ONLY on
parameters ?

Can not use on the complete mod_plsql call like :

webpkg.searchbasic?in_term=%ask%&p_skip=15&p_max=15

or the string after "?" (in_term=%ask%&p_skip=15&p_max=15)

thanks

          Followup:

correct -- only on parameters

you only need (only CAN) to urlencode the ? if it is in the parameter, same with
& and so on.

chicken and egg problem staring at you here.

In order to parse a URL, it must be encoded

If we were able to encode the above
"webpkg.searchbasic?in_term=%ask%&p_skip=15&p_max=15" we'd be able to parse it
as a URL.

but in order to parse it as a URL, it must be encoded (goto 1 :)

you must build the string using urlencode on each of the parameters.

January 06, 2004 Ganesh -- Thanks for the question regarding
      "Architecture Design Question", version 9.2.0.3.0

      You Asked
Hi Tom

We have lots of Production Databases on numerous different Produciton Servers
running great catering various Business needs. Now there is a need to design and
build Web Based Application using Java J2EE architecture which needs information
from these multiple databases. We are discussing lots of options of achieving
the same.

We are divided into two groups of techies -

1. Since it is the Homogenous Data Source - Oracle, one group proposes the usage
of Oracle Database Links to get the data from multiple databases, which would
give better access control, concurrency and has great transparancy for the
application.

2. The other group opposes creating any Database links and likes to have joins
and data captured at the java pool connection level and let the application do
the work. In this way, the tight integration with the vendor based softwares are
completely avoided.

Well, I would like to get your thoughts on this. I like to know what would be
best way to Design/Build a web based application which uses multiple data
sources (in this case, it is homogenous, Oracle, Datasource).

Your views on this is much appreciated.

thanks
Ganesh

      and we said...
the people in group 2 want to write lots of code and have job security for the
rest of their natural lives.  They will never actually finish and requests for
new functionality will take 10 times the amount of time it should.  The end
users will be wondering why this technology thing is so hard.  They will make us
all look bad.

the people in group 1 want to do it in the most cost effective, fast method.
They'll also be able to support "new technology 532143" which will come out next
year.  Java/J2EE is "new technology 532140" and is just a language.

I quit my last job in 1993 because of people like group #2.

How the heck is pretending you have one Oracle database instead of N going to
give you "tight integration with vendor based software" -- whatever that means.
(well, I know what it means -- it means you are actually USING what you paid
for, imagine that!  it means you are not reinventing an optimizer, a join
engine, a database.  imagine that!  it means you can actually use the advanced
features of the product you paid lots of money for)

Would these people invent their own steering mechanism simply because they drive
more than one car?

I'm sure this will generate a little bit of discussion.
Review &
      Followup

      Rating: 5
Architecture
      Design Question  January 06, 2004
Reviewer:
      Ketan Bengali  from Sunnyvale, CA USA
Case II has lot many problems. If you don't use database link and try to
implement everything in J2EE then performance will be nightmare. All the query
screens should have a message saying "Please come back next day to see the
result...".

Consider a case where Query1 from Database1 returns 300K records and Query2 from
Database2 returns 10000 records. The final output you want to return to user is
join of both the queries which should return only 1000 records at the end. If
you use DBLink then oracle will do all the optimization and return you 1000
records with shortest possible time.

If someone in your group starts writing this code then he will fetch all 300K+
records into server memory and paging will pick in. This will kill entire system
performance.All users will suffer. If your application is heavy data centric
then implementing at connection level is not the right choice. Implementing at
J2EE connection level may make sense when any select returns not more than 5K
records.

      Rating: 5
unfortunately
      case #2 usually wins  January 06, 2004
Reviewer:
      Ryan  from Tysons Corner, VA
There tend to be alot more software engineers on projects than database people
so they tend to win these arguments based on pure numbers. Also, I've noticed
that upper level management tends to come out of the software engineering ranks
alot more than out of the database ranks... so they are biased.

I've also noticed that alot of software engineers get stuck in 'cliches'. They
repeat the same tired cliches, but never test them. They assume that cliches
have to start somewhere and they wouldn't be a cliche if they weren't true. So
they don't feel the need to test it to see if its accurate.

Anyone else notice this?

          Followup:

warning -- opinion coming up

I beg to differ.

What you describe is "alot more coders".  I hate that term engineer, where is
their certificate?

You know, when I drive onto a bridge, I am fairly sure that I will get off the
other end.  Why?  Because a certified, trained engineer designed and built the
thing.

It is funny -- i was in a "panel conference" recently -- a "think tank" sort of
thing.  I proposed that software developers be trained and certified just like a
civil engineer.  Used the bridge analogy.  The "artist" from another company (a
pure think tank) took exception to this.  "Software developers must be free to
create, to be like artists".  The punch line -- there was a note taker, using
word, on a projector so we could see the notes.  Word "gpf'ed" at that point.
Touche' as they like to say.

These are not engineers, they are coders.  In the past, they were people who
took specs from analysts and pumped out code.  Somewhere along the line, we lost
"analysts" and just have coders.

Now, don't get me wrong -- i started life as a coder, who got specs from an
analyst and implemented them.  I'm not a DBA really beating up on developers
(far from it).

      Rating:
      5
  January 06, 2004
Reviewer:
      A reader
WOW!! Interesting ...

      Rating: 5
I'll drink to
      that!  January 06, 2004
Reviewer:  Anonymous
       from USA
I am working with developers who think the database is a data dumping ground.
Something the company paid a lot of money for.

      Rating: 5
What we need is
      licensing...  January 06, 2004
Reviewer:  Jim
      Nasby  from Austin, TX
I swear we need to enact a law whereby only a licensed database
architect/developer/admin can touch a database in any way, shape, or form.
Anyone without said licensing would have to follow a database professional's
exact instructions regarding data access and architecture. It would save so many
people so much time and money.

(In case you can't tell, I'm being sarcastic. Sort-of.)

      Rating: 5
Query, Security
      should be applied at database  January 06, 2004

Reviewer:  Ketan Bengali  from Sunnyvale, CA USA

We were evaluating a J2EE product from a vendor who implemented all security,
filter, sort in J2EE architecture. Vendor also cameout with a unique datamodel
whereby all objects were stored in just 4 tables as name value pair. No need for
DBAs, database developers and claimed everything is transperent to development
team.

The system was running great on demo machine where he had hardly 5000 records.
Once I asked him to load 400K plus records and set up a security in such a way
that each user gets only 100 record out of 400K plus records. This is the time,
we could see the performance difference. JDBC connection was fetching all 400K
plus records into server memory and then was returning 100 records per user
request. The same happened with filter and sort. At the end, vendor realised his
mistake and moved all these functionality back to database to improve the
performance.

J2EE architecture works very well when working with less number of records and
seamless integration with external applications are a must. It fails when
unnecessarily, we start putting everything in J2EE.

Forutnately, I was able to convince in-house J2EE experts that our system
doesn't need such integration and decided not to go with such products.

Now, we have fine tuned application whereby JDBC only gets what it needs (i.e.
no more than 100 records at any request). All the optimization, performance,
security, tuning are applied at database level and response time is less than 2
seconds.

Regards,

Ketan.

      Rating: 5
Truth is in the
      middle  January 06, 2004
Reviewer:  Alberto
      Dell'Era  from Milan, Italy
Let me share with Ganesh the experience we had with a big project that used your
very same environment - J2EE on top of a lot of Oracle databases. I'm sure that
everything may get the "Tom's Seal of Approval", since it's IMHO an example of
the Best Practices He has always advocated (stress on IMHO).

We put all the sql statements in packages, something like

create package interface_pkg as

 procedure get_orders (p_orders out sys_refcursor) is

  open p_orders for
    select ...
      from customers c, orders@db_link o
     where c.id = o.cust_id
       and c.status ='ACTIVE';

 end get_orders;

end interface_pkg;

and we had all J2EE developers calling the procedures to get the ref cursor
(ResultSet in J2EE terminology) and so the rows. We did the same for inserts,
updates etc.

The beauty of this arrangement is that it's perfectly database-independent even
if it uses PL/SQL; say you want to migrate to SqlServer or MySql, all you have
to do is modify the packages, which is a simple and mechanical task (I know
because we made it, actually migrating from SqlServer to Oracle). You must
examine anyway the sql since (as Tom explains beautifully in his books)
different databases have different locking mechanisms, so even if the sql is
syntactically compatible, it is usually semantically incompatible - so, it's
much much better to have all of the sql in the same place (packages) than having
it spreaded in different .java files.
This discussion should satisfy the "database independence" requirement.

But now that all the sql is in the database, it's also close to the data, so you
can take advantage of Oracle's unmatched performance, using the DATAbase to
crunch the data. Try doing the same join above as a nested loop in Java - will
you (1) first get the ACTIVE customers and then the orders, or (2) the opposite
? It depends on how many ACTIVE customers and orders you have, of course ...
what if today (1) is optimal and tomorrow (2) is optimal, will you change the
Java code ? If the join is in sql, Oracle will automatically switch plans for
you. I know because it happened to us - and i'm sure that many other times it
happened without we knowing.

Or, let's say that you notice that the network is too slow - you create a local
copy (snapshot) of orders and simply change 'orders@db_link o' in 'orders_local
o', without touching a single line of Java. The same applies if you need to
optimize/hint the sql. I know, because it happened to us.

But the most interesting thing that happened, and that was only partially
predicted by us, it's that this 3-layered physical arrangement (Java using
packages using the tables) fostered a division-of-labour between the Java
developers, the database developers (who develop the packages), and the dbas. As
an afterthought, I think that that was the most important factor that drove our
project to success, since anyone played in his/her preferred technology, so:

a) the J2EE played with beans, XML parsers, Struts, etc, happy to leave the
"boring sql" to the database developers;
b) the database developers enjoyed and exploited the Oracle features;
c) the dbas were more than happy to receive high-quality sql code produced by
the (b) layer, and sleep at night instead of chasing the root cause of the
latest inefficiency/data loss/deadlock.

Please note that i'm presenting the experience of a *real* and *successfull*
project, and that is the real value of my contribution - it worked, and worked
perfectly. I know the dangers of "religion wars", that can be peacefully avoided
by simply looking at facts - in this case, past experience.

I wish you peace ;-)
Alberto

      Rating:
      4
  January 07, 2004
Reviewer:  Ganesh
      Sundaresan  from Cincinnati, OH
Folks

Thanks for the practical advise and it is very helpful. This was the kind of
feedback I was waiting for.

Ganesh

      Rating: 5
as
      is  January 24, 2004
Reviewer:  Alberto
      Dell'Era  from Milan, Italy
> I proposed that software developers be trained and certified
> just like a civil engineer.  Used the bridge analogy.

What about declaring void the "AS IS" disclaimer of sw licenses - wouldn't this
achieve the same goal ? If the bridge collapses, the manufacturing company pays
for the damage, hence their employing only certified civil engineers ... or,
expert ones at least!

January 06, 2004 za -- Thanks for the question regarding
      "start/stop db from command line in NT", version

      You Asked
I'm looking for some script *.bat that I can schedul to
startup and shutdown my Oracle database in NT and 2000 Server.

      and we said...
Under Windows NT, the databases are started and stopped using
Services.  the databases should automatically start upon a
system reboot.  Please check for the existence of these services
in the conrol panel and ensure they are set to automatically
start.

You can start and stop these services from the command line
using:

SQL> net start|stop OracleService(ORACLE_SID)

where Oracle_sid is the sid for a database instance.  For
example:

C:\>net start OracleServiceORA8INT
The OracleServiceORA8INT service is
starting...........................................
The OracleServiceORA8INT service was started successfully.

shows me starting my Ora8iNT database from the command line.

Review &
      Followup

      Rating: 3
"Starting" on
      Windows2000 Server  February 25, 2003
Reviewer:
      Muhammad Mustafa  from Pakistan
Dear Sir
   When i start my 2000 server computer with Oracle 8.1.6 oracleserviceSID stuck
on "Starting" all other services properly started. In registery
Ora_SID_autostart = TRUE and in control panel it is also set to Automatically.
I have to manually start the service after connecting to an idle instance.
 TNSListener service is started in the 2000 server but when i try to connect to
oracle from 2000 professional it does not connect to database.

  Thanks & Regards
   Muhammad Mustafa

          Followup:

check your alert log and the windows event log.

      Rating: 5
To
      schedule& automate startup  February 25, 2003

Reviewer:  Tarry  from Netherlands
A simple *.cmd script can be written like.......

E:\oracle\ora817\bin\svrmgrl @E:\oracle\startup\startup.sql

and scheduled for on system startup

calling script startup.sql can be as simple as......

spool E:\oracle\checkbox\check--to check if it went ok..
set echo on
connect appdev/pwd@service_name as sysdba
startup

This will startup the script on server startup.

What you (probably) didn't mention was that you had more than one home on your
machine.......

      Rating: 3
Startup and/or
      Shutdown Oracle database on windows NT/2k
      Machines.  February 25, 2003
Reviewer:  Mirza
      Shoeb Baig  from New York, NY USA
Create two files (one bat file and other sql file in a folder) startupdb.bat
content of bat file:

REM This bat file will call the sql file.
specify the location of svrmgrl command=@startup.sql
exit

file name: startup.sql
Content of sql file:

#This sqlfile will start the database.
connect internal
startup pfile=specify the location of your init.ora
#eg.,
#connect internal
#startup pfile=O:\oracle\admin\ATNYIDW\pfile\init.ora
#exit
/
exit

The above solution is working on NT 4.0 in production
and similar configuration will be good on w2k too.

If there are multiple database and/or multiple homes on the machine, then
specify the appropriate init.ora in the startup.sql file.
Thanks
Mirza Shoeb Baig

      Rating: 3
Internal -
        February 26, 2003
Reviewer:  Senthil Kumar
       from OH USA
As far startuping the Oracle database it is okay to use services but  Stopping
the Oracle service in WIndows - in fact doesnot close the database properly it
only wait for some time and
kills the process. This may lead to Oracle crash. Tom
hope you will comment on this.
Internal is going to be depreciated very soon. Oracle recommends not to use
Internal

          Followup:

it will NOT lead to "oracle crash".  Oracle is built to be a tad resiliant.
This will not crash it anymore then a power failure would.

internal is depricated already and in 9i is not even supported.

      Rating: 4
Watch Out!
      stopping service may kill db  February 26, 2003

Reviewer:  Dan  from Yukon Territory, Canada

I concur with Senthil, under some NT/2000 variations with Oracle 8.1.x a NET
STOP OracleServiceXXX will kill the service without shutting down the db (even
with a very long timeout).  ick ick ick.

There is metalink traffic on this.  I know it was a problem in 8.1.7.0, We've
recently moved to 8.1.7.4 and I have not retested this problem at the 8.1.7.4
patch level.

BUG:724051 claims this is fixed up in 8.1.6, but I seem to recall (actually I'm
SURE I observed it in 8.1.7.0) it cropped up again after 8.1.6 in some
configurations.

If you want to use the NET START/NET STOP in 8i Try this out in a test
environment as close a possible to your production environment and check you
alert log for "Crash Recovery" on restart.  And then keep an eye on your
production Alert logs as well for a while... Guess you should do that anyway...

This could really suck if you are doing cold backups... your shutdowns might not
be clean - could hoop your backups.

I'm nervous enough about this that I use start/stop scripts that connect as
SYSOPER and startup/shutdown.  I'll try again NET START/NET STOP with 9i.

P.S. NET START OracleServiceXXX works just peachy - I just like to keep the
startup/shutdown as similar as possible for our sysadmins... and for me when i'm
tired.

          Followup:

if you are doing cold backups and backup your online redo logs -- it is
perfectly ok.

      Rating: 4
Try ORADIM
      instead of Net {Start|Stop}  February 27, 2003

Reviewer:  Archer  from Virginia
  ORADIM, the Oracle utility for creating and deleting Oracle DB Services on NT,
also has -STARTUP and -SHUTDOWN options.  The two nice things about this are 1)
the -STARTUP allows you to specify a different PFILE, and 2) the -SHUTDOWN
option supports a -SHUTMODE of a, i, or n for SHUTDOWN ABORT, IMMEDIATE, or
NORMAL, respectively.  This allows you to shutdown the DB from a .BAT file or
the command line and specify the shutdown type, which can be handy.  I am not
sure, but I think the default behavior (and the one used by NET STOP) is a
SHUTDOWN ABORT. If you are nervous about this and would rather SHUTDOWN
IMMEDIATE, try:

    oradim -shutdown -sid MY_SID -shutmode i

  To start up the DB the same way, simply use:

    oradim -startup -sid MY_SID

  Tom - two questions about ORADIM that I've never quite figured out:

1) Where does the name ORADIM come from?  I can't think of any registry or
service-related acronym anything like DIM.

2) What is the purpose of the different -STARTTYPE and -SHUTTYPE options of
{SRVC|INST|SRVC,INST}.  I would have thought from the name that you could start
the DB w/out starting the NT service, or vice versa, but on my system all three
options appear to start or stop both.

          Followup:

1) don't know, I'll guess (hey, here comes the rumor mill)

Database
Instance
Manager

dim  ;)

2) again, don't know -- don't run windows for anything.  /etc/init.d/ is so
simple by comparision -- sorry.  maybe a windows dba can help us out here.

      Rating: 3
ORADIM in
      Windows  February 28, 2003
Reviewer:  Ogun
       from Turkey
I have Oracle8i running on a Windows2000 machine. I don't have any startup or
shutdown scripts but when i have to, i always follow these ways.
For startup
1 - first start the service. If your Oracle service is not running on Win2K,
then you are unable to connect an idle instance with connect internal or
something from svrmgrl.
oradim -startup -sid {SID} -starttype srvc
2 - then start the instance and the listener via svrmgrl and lsnrctl
For shutdown
1 - fisrt close, dismount the database and shutdown your running instance
2 - Now you are ready to stop the service

On win2k, it doesn't mean that your db is up and running when the service is
running. If your startype identified in oradim is srvc,inst or inst (which
requires the service to be already running) then your db. can be opened. Also
when you set oracle service to run automatically at boot, it starts the service
due to the last starttype. Which means it can only start the service, not the
instance. That's why your db can not be guaranteed to start this way.

      Rating: 3
About
      Internal  February 28, 2003
Reviewer:  Ogun
       from Turkey
Hi Tom,

You said that internal is no longer supported in Oracle9i. Why does Oracle need
to do this - since it's a security problem or what? Does this mean we can no
longer use OS authentication? Than how can we connect to a db?

Thanks.

          Followup:

connect internal
is more or less the same as
connect / as sysdba

there were two ways to do things

a) as internal and internal was implemented differently on different OS's
b) as sysdba -- and sysdba is consistently implemented

so, in order to make things consistent, more secure (the fewer paths to protect,
the fewer holes to be found), internal was depricated and then removed

      Rating:
      3
Problem  March 06, 2003
Reviewer:
      Muhammad Mustafa  from Pakistan
Hello
when i open the bat file svrmgrl closes at once.
i have written in batch file...
E:\oracle\ora816\bin\svrmgrl @E:\oracle\startup\startup.sql

 Regards
  Mustafa

      Rating: 2
Drop the
      Service OracleServiceSID  March 07, 2003
Reviewer:
      Reviewer  from India
 Hi
    Try drop the service and recreate it using
  oradim -new -sid <sid_name> -intpwd -startmode auto -pfile ../init.ora

      Rating: 4
Idle Instance
      ?  May 29, 2003
Reviewer:  kumar  from
      India
Tom,

Can you explain what  'Connected to an Idle Instance' means ?

          Followup:

it means Oracle was not started.

      Rating: 3
Startup Db from
      client ..  June 09, 2003
Reviewer:  A reader
       from Pal
hi Tom ..

how can we startup db from client side. ?

Thanx ..

      Rating: 4
Back to
      UNIX/LINUX process model...  June 09, 2003

Reviewer:  fan  from PA
Tom,
On Windoz server, "net start oracleservie<SID>"
will load the exe and start the database......

On 9.2 on LINUX, I looked at dbstart script,
I see it launches Sqlplus and issue "startup"

My question : so is this the single command that actually load/started the
Oracle process(es) first and mount, open the database ? Is this the sequence of
events ?....
and dbshut does the reverse.
Thanks

          Followup:

yes, on linux, you need no "services", you just type startup

      Rating: 2
Re: Startup Db
      from client .. June 09, 2003   June 14, 2003

Reviewer:  Sorry  from Sorry
i m answering

/*
Startup Db from client ..  June 09, 2003
Reviewer:  A reader  from Pal

hi Tom ..

how can we startup db from client side. ?

Thanx ..

*/

configure a nodefault listener .. on database and start the listener ..

connect sys/<<Password>>@host_string as sysdba

and then issue

Startup

          Followup:

it is a lot more complex then that...

*windows*, it is all about services, if the service isn't running on the server,
it isn't going to start, there is "nothing" to connect to.

8i, where is your pfile?  how are you going to get that?

      Rating: 5
I use different
      soft to connect to server...  June 14, 2003

Reviewer:  Sikandar Hayat  from Pindi Bhattian -
      Pakistan
I have 5 different servers so I use NetOP and Radmin softwares which give me the
desktop of each server and very easy to do any actitiy.

In Win2000 there is remote assistance feature so you may use it to get the
desktop of your db server on your machine.

I don't like to play with NT/2K services becuase these are not trustable.
Sometime you stop service and unable to restart it.

          Followup:

if you don't start a server on nt, you ain't going to start the database.  you
need the database service running, else "startup" isn't going to be happening.

      Rating:
      5
  November 02, 2003
Reviewer:
      A reader

      Rating: 3
My oracle
      services stops by themselves after restarting them  December
      24, 2003
Reviewer:  Godwin  from Ghana
Hi Tom,
We are running two instances on Oracle 8.0.5.0.0 Enterprise Edition. One of the
instances has to be restarted each morning because it stops by itself after it
has been started.My operating system is windows 2000 server and each day i have
to go the control panel-services to restart these oracle services for the
database to work. the services that i normally restart are: OracleStartIDD and
OracleServiceIDD. what do you think is the cause? At the eventviewer i see this
error message:"The service did'nt respond to the start or control request in a
timely fashion"
thanks

          Followup:

the database just doesn't "stop by itself"

do you reboot these boxes every night?  if so -- why?

think about it -- the database won't just shutdown every night all by itself,
you are doing something

      Rating:
      3
  December 24, 2003
Reviewer:
      A reader
Hi,
If you are not restarting the servers, something really strange is happening
check your alert.log, investigate what happens when the database goes down.

"The service did'nt respond to the start or control request in a timely
fashion",
means that the time the database took to start, is more than the average,
probably a recovery is happening, this doesn't means necesarily a problem.
If your database started anyway, you can ignore this problem, or start to
analyze why is taking as much to start the service.

      Rating: 3
Problem still
      occuring-Here is my alert log file named
      iddALRT.log  January 06, 2004
Reviewer:
      Godwin  from Ghana
Hi,
Am still facing the same problem. You asked me to check my alert.log file and
here is the details. I can't make any sense of what is in it since am not a DBA.
Can you help analyze the problem? I will be grateful. I need your help as soon
as possible.
---iddALRT.log file----
Dump file d:\idd\pdump\trace\iddALRT.LOG
Tue Jun 17 12:58:51 2003
ORACLE V8.0.5.0.0 - Production vsnsta=0
vsnsql=c vsnxtr=3
Windows NT V5.0, OS V8.147, CPU type 586
Starting up ORACLE RDBMS Version: 8.0.5.0.0.
System parameters with non-default values:
  processes                = 100
  shared_pool_size         = 10000000
  control_files            = c:\orant\database\ctl1IDD.ora,
d:\idd\controls\ctl2IDD.ora
  db_block_buffers         = 550
  db_block_size            = 2048
  compatible               = 8.0.5.0.0
  log_buffer               = 32768
  log_checkpoint_interval  = 8000
  log_checkpoint_timeout   = 0
  db_files                 = 1020
  db_file_multiblock_read_count= 16
  dml_locks                = 200
  sequence_cache_entries   = 30
  sequence_cache_hash_buckets= 23
  remote_login_passwordfile= SHARED
  sort_area_size           = 65536
  db_name                  = IDD
  background_dump_dest     = d:\idd\pdump\trace
  user_dump_dest           = d:\idd\udump\trace
  max_dump_file_size       = 10240
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
Tue Jun 17 12:58:52 2003
CREATE DATABASE IDD
LOGFILE 'c:\orant\database\logIDD1.ora' SIZE 1024K,
    'd:\idd\logfiles\logIDD2.ora' SIZE 1024K
MAXLOGFILES 32
MAXLOGMEMBERS 2
MAXLOGHISTORY 1
DATAFILE 'c:\orant\database\Sys1IDD.ora' SIZE 50M
MAXDATAFILES 254
MAXINSTANCES 1
CHARACTER SET WE8ISO8859P1
NATIONAL CHARACTER SET WE8ISO8859P1
Tue Jun 17 12:58:54 2003
Database mounted in Exclusive Mode.
Tue Jun 17 12:58:57 2003
Successful mount of redo thread 1, with mount id 1607943948.
Tue Jun 17 12:58:57 2003
Picked broadcast on commit scheme to generate SCNs
Tue Jun 17 12:58:58 2003
Thread 1 opened at log sequence 1
  Current log# 1 seq# 1 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Successful open of redo thread 1.
Tue Jun 17 12:58:58 2003
SMON: enabling cache recovery
Tue Jun 17 12:58:58 2003
create tablespace SYSTEM datafile  'c:\orant\database\Sys1IDD.ora' SIZE 50M
  default storage (initial 10K next 10K) online

Tue Jun 17 12:59:10 2003
Completed: create tablespace SYSTEM datafile  'c:\orant\datab
Tue Jun 17 12:59:10 2003
create rollback segment SYSTEM tablespace SYSTEM
  storage (initial 50K next 50K)

Completed: create rollback segment SYSTEM tablespace SYSTEM

Tue Jun 17 12:59:12 2003
Thread 1 advanced to log sequence 2
  Current log# 2 seq# 2 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 cannot allocate new log, sequence 3
Checkpoint not complete
  Current log# 2 seq# 2 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 3
  Current log# 1 seq# 3 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 4
  Current log# 2 seq# 4 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 cannot allocate new log, sequence 5
Checkpoint not complete
  Current log# 2 seq# 4 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 5
  Current log# 1 seq# 5 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 6
  Current log# 2 seq# 6 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Tue Jun 17 12:59:26 2003
SMON: enabling tx recovery
Tue Jun 17 12:59:29 2003
Completed: CREATE DATABASE IDD
LOGFILE 'c:\orant\database\log
Tue Jun 17 12:59:31 2003
ALTER DATABASE DATAFILE 'c:\orant\database\Sys1IDD.ora' AUTOEXTEND ON
Completed: ALTER DATABASE DATAFILE 'c:\orant\database\Sys1IDD
Tue Jun 17 12:59:31 2003
CREATE ROLLBACK SEGMENT SYSROL TABLESPACE "SYSTEM" STORAGE (INITIAL 100K NEXT
100K)
Completed: CREATE ROLLBACK SEGMENT SYSROL TABLESPACE "SYSTEM"
Tue Jun 17 12:59:31 2003
ALTER ROLLBACK SEGMENT "SYSROL" ONLINE
Completed: ALTER ROLLBACK SEGMENT "SYSROL" ONLINE
Tue Jun 17 12:59:33 2003
Thread 1 advanced to log sequence 7
  Current log# 1 seq# 7 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 cannot allocate new log, sequence 8
Checkpoint not complete
  Current log# 1 seq# 7 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 8
  Current log# 2 seq# 8 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 9
  Current log# 1 seq# 9 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 cannot allocate new log, sequence 10
Checkpoint not complete
  Current log# 1 seq# 9 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 10
  Current log# 2 seq# 10 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 11
  Current log# 1 seq# 11 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 cannot allocate new log, sequence 12
Checkpoint not complete
  Current log# 1 seq# 11 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 12
  Current log# 2 seq# 12 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 13
  Current log# 1 seq# 13 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 cannot allocate new log, sequence 14
Checkpoint not complete
  Current log# 1 seq# 13 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 14
  Current log# 2 seq# 14 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 15
  Current log# 1 seq# 15 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 16
  Current log# 2 seq# 16 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 17
  Current log# 1 seq# 17 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 18
  Current log# 2 seq# 18 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 19
  Current log# 1 seq# 19 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 20
  Current log# 2 seq# 20 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 21
  Current log# 1 seq# 21 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 cannot allocate new log, sequence 22
Checkpoint not complete
  Current log# 1 seq# 21 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 22
  Current log# 2 seq# 22 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 23
  Current log# 1 seq# 23 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 cannot allocate new log, sequence 24
Checkpoint not complete
  Current log# 1 seq# 23 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 24
  Current log# 2 seq# 24 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 25
  Current log# 1 seq# 25 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 26
  Current log# 2 seq# 26 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 27
  Current log# 1 seq# 27 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 28
  Current log# 2 seq# 28 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 29
  Current log# 1 seq# 29 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 30
  Current log# 2 seq# 30 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 cannot allocate new log, sequence 31
Checkpoint not complete
  Current log# 2 seq# 30 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 31
  Current log# 1 seq# 31 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 32
  Current log# 2 seq# 32 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 33
  Current log# 1 seq# 33 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 34
  Current log# 2 seq# 34 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 cannot allocate new log, sequence 35
Checkpoint not complete
  Current log# 2 seq# 34 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 35
  Current log# 1 seq# 35 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 36
  Current log# 2 seq# 36 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 37
  Current log# 1 seq# 37 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 38
  Current log# 2 seq# 38 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 39
  Current log# 1 seq# 39 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 40
  Current log# 2 seq# 40 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 41
  Current log# 1 seq# 41 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 42
  Current log# 2 seq# 42 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 43
  Current log# 1 seq# 43 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 44
  Current log# 2 seq# 44 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 45
  Current log# 1 seq# 45 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 46
  Current log# 2 seq# 46 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 47
  Current log# 1 seq# 47 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 48
  Current log# 2 seq# 48 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 49
  Current log# 1 seq# 49 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 cannot allocate new log, sequence 50
Checkpoint not complete
  Current log# 1 seq# 49 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 50
  Current log# 2 seq# 50 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 51
  Current log# 1 seq# 51 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 52
  Current log# 2 seq# 52 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 53
  Current log# 1 seq# 53 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 54
  Current log# 2 seq# 54 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 55
  Current log# 1 seq# 55 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Thread 1 advanced to log sequence 56
  Current log# 2 seq# 56 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Thread 1 advanced to log sequence 57
  Current log# 1 seq# 57 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Tue Jun 17 13:03:22 2003
CREATE TABLESPACE RBS_IDD DATAFILE 'd:\idd\rollback\Rbs1IDD.ora' SIZE 1000M
DEFAULT STORAGE ( INITIAL 1024K NEXT 1024K MINEXTENTS 2 MAXEXTENTS 121
PCTINCREASE 0)
Tue Jun 17 13:07:07 2003
Completed: CREATE TABLESPACE RBS_IDD DATAFILE 'd:\idd\rollbac
Tue Jun 17 13:07:07 2003
ALTER DATABASE DATAFILE 'd:\idd\rollback\Rbs1IDD.ora' AUTOEXTEND ON
Completed: ALTER DATABASE DATAFILE 'd:\idd\rollback\Rbs1IDD.o
Tue Jun 17 13:07:07 2003
ALTER TABLESPACE SYSTEM
DEFAULT STORAGE ( INITIAL 100K NEXT 100K MINEXTENTS 1 MAXEXTENTS 300 PCTINCREASE
1)
Completed: ALTER TABLESPACE SYSTEM
DEFAULT STORAGE ( INITIAL
Tue Jun 17 13:07:08 2003
CREATE TABLESPACE User_idd DATAFILE 'd:\idd\datafiles\Usr1IDD.ora' SIZE 2000M
DEFAULT STORAGE ( INITIAL 50K NEXT 50K MINEXTENTS 1 MAXEXTENTS 121 PCTINCREASE
1)
Tue Jun 17 13:16:16 2003
Completed: CREATE TABLESPACE User_idd DATAFILE 'd:\idd\datafi
Tue Jun 17 13:16:16 2003
ALTER DATABASE DATAFILE 'd:\idd\datafiles\Usr1IDD.ora' AUTOEXTEND ON
Completed: ALTER DATABASE DATAFILE 'd:\idd\datafiles\Usr1IDD.
Tue Jun 17 13:16:17 2003
CREATE TABLESPACE Temp_IDD DATAFILE 'd:\idd\temp\Tmp1IDD.ora' SIZE 500M
DEFAULT STORAGE ( INITIAL 100K NEXT 100K MINEXTENTS 1 MAXEXTENTS 121 PCTINCREASE
0) TEMPORARY
Tue Jun 17 13:17:50 2003
Completed: CREATE TABLESPACE Temp_IDD DATAFILE 'd:\idd\temp\T
Tue Jun 17 13:17:50 2003
ALTER DATABASE DATAFILE 'd:\idd\temp\Tmp1IDD.ora' AUTOEXTEND ON
Completed: ALTER DATABASE DATAFILE 'd:\idd\temp\Tmp1IDD.ora'
Tue Jun 17 13:17:51 2003
CREATE TABLESPACE Index_idd DATAFILE 'd:\idd\index\Indx1IDD.ora' SIZE 2000M
DEFAULT STORAGE ( INITIAL 50K NEXT 50K MINEXTENTS 1 MAXEXTENTS 121 PCTINCREASE
1)
Tue Jun 17 13:23:56 2003
Completed: CREATE TABLESPACE Index_idd DATAFILE 'd:\idd\index
Tue Jun 17 13:23:56 2003
ALTER DATABASE DATAFILE 'd:\idd\index\Indx1IDD.ora' AUTOEXTEND ON
Completed: ALTER DATABASE DATAFILE 'd:\idd\index\Indx1IDD.ora
Tue Jun 17 13:23:56 2003
CREATE PUBLIC ROLLBACK SEGMENT RB0 TABLESPACE "RBS_IDD"
STORAGE ( INITIAL 50K NEXT 50K MINEXTENTS 2 MAXEXTENTS 121 )
Completed: CREATE PUBLIC ROLLBACK SEGMENT RB0 TABLESPACE "RBS
Tue Jun 17 13:23:57 2003
CREATE PUBLIC ROLLBACK SEGMENT RB1 TABLESPACE "RBS_IDD"
STORAGE ( INITIAL 50K NEXT 50K MINEXTENTS 2 MAXEXTENTS 121 )
Completed: CREATE PUBLIC ROLLBACK SEGMENT RB1 TABLESPACE "RBS
Tue Jun 17 13:23:57 2003
ALTER ROLLBACK SEGMENT "RB0" ONLINE
Completed: ALTER ROLLBACK SEGMENT "RB0" ONLINE
Tue Jun 17 13:23:57 2003
ALTER ROLLBACK SEGMENT "RB1" ONLINE
Completed: ALTER ROLLBACK SEGMENT "RB1" ONLINE
Tue Jun 17 13:23:57 2003
alter rollback segment "SYSROL" offline
Completed: alter rollback segment "SYSROL" offline
Dump file d:\idd\pdump\trace\iddALRT.LOG
Wed Jun 18 05:45:26 2003
ORACLE V8.0.5.0.0 - Production vsnsta=0
vsnsql=c vsnxtr=3
Windows NT V5.0, OS V8.147, CPU type 586
Starting up ORACLE RDBMS Version: 8.0.5.0.0.
System parameters with non-default values:
  processes                = 100
  shared_pool_size         = 10000000
  control_files            = c:\orant\database\ctl1IDD.ora,
d:\idd\controls\ctl2IDD.ora
  db_block_buffers         = 550
  db_block_size            = 2048
  compatible               = 8.0.5.0.0
  log_buffer               = 32768
  log_checkpoint_interval  = 8000
  log_checkpoint_timeout   = 0
  db_files                 = 1020
  db_file_multiblock_read_count= 16
  dml_locks                = 200
  sequence_cache_entries   = 30
  sequence_cache_hash_buckets= 23
  remote_login_passwordfile= SHARED
  sort_area_size           = 65536
  db_name                  = IDD
  background_dump_dest     = d:\idd\pdump\trace
  user_dump_dest           = d:\idd\udump\trace
  max_dump_file_size       = 10240
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
Wed Jun 18 05:45:26 2003
alter database "idd" mount exclusive
Wed Jun 18 05:45:31 2003
Successful mount of redo thread 1, with mount id 1607975675.
Wed Jun 18 05:45:31 2003
Database mounted in Exclusive Mode.
Completed: alter database "idd" mount exclusive
Wed Jun 18 05:45:31 2003
alter database "idd" open
Beginning crash recovery of 1 threads
Recovery of Online Redo Log: Thread 1 Group 1 Seq 57 Reading mem 0
  Mem# 0 errs 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Crash recovery completed successfully
Picked broadcast on commit scheme to generate SCNs
Wed Jun 18 05:46:00 2003
kccrsz: expanded controlfile section 9 from 56 to 112 records
  requested to grow by 56 record(s); added 1 block(s) of records
Thread 1 advanced to log sequence 58
Thread 1 opened at log sequence 58
  Current log# 2 seq# 58 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Successful open of redo thread 1.
Wed Jun 18 05:46:01 2003
SMON: enabling cache recovery
SMON: enabling tx recovery
Wed Jun 18 05:46:04 2003
Completed: alter database "idd" open
Dump file d:\idd\pdump\trace\iddALRT.LOG
Mon Jun 23 07:09:39 2003
ORACLE V8.0.5.0.0 - Production vsnsta=0
vsnsql=c vsnxtr=3
Windows NT V5.0, OS V8.147, CPU type 586
Starting up ORACLE RDBMS Version: 8.0.5.0.0.
System parameters with non-default values:
  processes                = 100
  shared_pool_size         = 10000000
  control_files            = c:\orant\database\ctl1IDD.ora,
d:\idd\controls\ctl2IDD.ora
  db_block_buffers         = 550
  db_block_size            = 2048
  compatible               = 8.0.5.0.0
  log_buffer               = 32768
  log_checkpoint_interval  = 8000
  log_checkpoint_timeout   = 0
  db_files                 = 1020
  db_file_multiblock_read_count= 16
  dml_locks                = 200
  sequence_cache_entries   = 30
  sequence_cache_hash_buckets= 23
  remote_login_passwordfile= SHARED
  sort_area_size           = 65536
  db_name                  = IDD
  background_dump_dest     = d:\idd\pdump\trace
  user_dump_dest           = d:\idd\udump\trace
  max_dump_file_size       = 10240
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
Mon Jun 23 07:09:43 2003
alter database "idd" mount exclusive
Mon Jun 23 07:09:49 2003
Successful mount of redo thread 1, with mount id 1608464699.
Mon Jun 23 07:09:49 2003
Database mounted in Exclusive Mode.
Completed: alter database "idd" mount exclusive
Mon Jun 23 07:09:49 2003
alter database "idd" open
Beginning crash recovery of 1 threads
Recovery of Online Redo Log: Thread 1 Group 2 Seq 58 Reading mem 0
  Mem# 0 errs 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Crash recovery completed successfully
Picked broadcast on commit scheme to generate SCNs
Mon Jun 23 07:09:53 2003
Thread 1 advanced to log sequence 59
Thread 1 opened at log sequence 59
  Current log# 1 seq# 59 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Successful open of redo thread 1.
Mon Jun 23 07:09:54 2003
SMON: enabling cache recovery
SMON: enabling tx recovery
Mon Jun 23 07:10:00 2003
Completed: alter database "idd" open
Dump file d:\idd\pdump\trace\iddALRT.LOG
Wed Jun 25 20:41:54 2003
ORACLE V8.0.5.0.0 - Production vsnsta=0
vsnsql=c vsnxtr=3
Windows NT V5.0, OS V8.147, CPU type 586
Starting up ORACLE RDBMS Version: 8.0.5.0.0.
System parameters with non-default values:
  processes                = 100
  shared_pool_size         = 10000000
  control_files            = c:\orant\database\ctl1IDD.ora,
d:\idd\controls\ctl2IDD.ora
  db_block_buffers         = 550
  db_block_size            = 2048
  compatible               = 8.0.5.0.0
  log_buffer               = 32768
  log_checkpoint_interval  = 8000
  log_checkpoint_timeout   = 0
  db_files                 = 1020
  db_file_multiblock_read_count= 16
  dml_locks                = 200
  sequence_cache_entries   = 30
  sequence_cache_hash_buckets= 23
  remote_login_passwordfile= SHARED
  sort_area_size           = 65536
  db_name                  = IDD
  background_dump_dest     = d:\idd\pdump\trace
  user_dump_dest           = d:\idd\udump\trace
  max_dump_file_size       = 10240
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
Wed Jun 25 20:41:54 2003
alter database "idd" mount exclusive
Wed Jun 25 20:41:59 2003
Successful mount of redo thread 1, with mount id 1608658327.
Wed Jun 25 20:41:59 2003
Database mounted in Exclusive Mode.
Completed: alter database "idd" mount exclusive
Wed Jun 25 20:41:59 2003
alter database "idd" open
Beginning crash recovery of 1 threads
Recovery of Online Redo Log: Thread 1 Group 1 Seq 59 Reading mem 0
  Mem# 0 errs 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Crash recovery completed successfully
Picked broadcast on commit scheme to generate SCNs
Wed Jun 25 20:42:03 2003
Thread 1 advanced to log sequence 60
Thread 1 opened at log sequence 60
  Current log# 2 seq# 60 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Successful open of redo thread 1.
Wed Jun 25 20:42:04 2003
SMON: enabling cache recovery
SMON: enabling tx recovery
Wed Jun 25 20:42:08 2003
Completed: alter database "idd" open
Dump file d:\idd\pdump\trace\iddALRT.LOG
Sun Jun 29 18:14:17 2003
ORACLE V8.0.5.0.0 - Production vsnsta=0
vsnsql=c vsnxtr=3
Windows NT V5.0, OS V8.147, CPU type 586
Starting up ORACLE RDBMS Version: 8.0.5.0.0.
System parameters with non-default values:
  processes                = 100
  shared_pool_size         = 10000000
  control_files            = c:\orant\database\ctl1IDD.ora,
d:\idd\controls\ctl2IDD.ora
  db_block_buffers         = 550
  db_block_size            = 2048
  compatible               = 8.0.5.0.0
  log_buffer               = 32768
  log_checkpoint_interval  = 8000
  log_checkpoint_timeout   = 0
  db_files                 = 1020
  db_file_multiblock_read_count= 16
  dml_locks                = 200
  sequence_cache_entries   = 30
  sequence_cache_hash_buckets= 23
  remote_login_passwordfile= SHARED
  sort_area_size           = 65536
  db_name                  = IDD
  background_dump_dest     = d:\idd\pdump\trace
  user_dump_dest           = d:\idd\udump\trace
  max_dump_file_size       = 10240
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
Sun Jun 29 18:14:17 2003
alter database "idd" mount exclusive
Sun Jun 29 18:14:22 2003
Successful mount of redo thread 1, with mount id 1609011454.
Sun Jun 29 18:14:22 2003
Database mounted in Exclusive Mode.
Completed: alter database "idd" mount exclusive
Sun Jun 29 18:14:22 2003
alter database "idd" open
Beginning crash recovery of 1 threads
Recovery of Online Redo Log: Thread 1 Group 2 Seq 60 Reading mem 0
  Mem# 0 errs 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Crash recovery completed successfully
Picked broadcast on commit scheme to generate SCNs
Sun Jun 29 18:14:26 2003
Thread 1 advanced to log sequence 61
Thread 1 opened at log sequence 61
  Current log# 1 seq# 61 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Successful open of redo thread 1.
Sun Jun 29 18:14:27 2003
SMON: enabling cache recovery
SMON: enabling tx recovery
Sun Jun 29 18:14:32 2003
Completed: alter database "idd" open
Dump file d:\idd\pdump\trace\iddALRT.LOG
Thu Jul 03 07:51:58 2003
ORACLE V8.0.5.0.0 - Production vsnsta=0
vsnsql=c vsnxtr=3
Windows NT V5.0, OS V8.147, CPU type 586
Starting up ORACLE RDBMS Version: 8.0.5.0.0.
System parameters with non-default values:
  processes                = 100
  shared_pool_size         = 10000000
  control_files            = c:\orant\database\ctl1IDD.ora,
d:\idd\controls\ctl2IDD.ora
  db_block_buffers         = 550
  db_block_size            = 2048
  compatible               = 8.0.5.0.0
  log_buffer               = 32768
  log_checkpoint_interval  = 8000
  log_checkpoint_timeout   = 0
  db_files                 = 1020
  db_file_multiblock_read_count= 16
  dml_locks                = 200
  sequence_cache_entries   = 30
  sequence_cache_hash_buckets= 23
  remote_login_passwordfile= SHARED
  sort_area_size           = 65536
  db_name                  = IDD
  background_dump_dest     = d:\idd\pdump\trace
  user_dump_dest           = d:\idd\udump\trace
  max_dump_file_size       = 10240
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
Thu Jul 03 07:51:58 2003
alter database "idd" mount exclusive
Thu Jul 03 07:52:03 2003
Successful mount of redo thread 1, with mount id 1609378466.
Thu Jul 03 07:52:03 2003
Database mounted in Exclusive Mode.
Completed: alter database "idd" mount exclusive
Thu Jul 03 07:52:03 2003
alter database "idd" open
Beginning crash recovery of 1 threads
Recovery of Online Redo Log: Thread 1 Group 1 Seq 61 Reading mem 0
  Mem# 0 errs 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Crash recovery completed successfully
Picked broadcast on commit scheme to generate SCNs
Thu Jul 03 07:52:07 2003
Thread 1 advanced to log sequence 62
Thread 1 opened at log sequence 62
  Current log# 2 seq# 62 mem# 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Successful open of redo thread 1.
Thu Jul 03 07:52:08 2003
SMON: enabling cache recovery
SMON: enabling tx recovery
Thu Jul 03 07:52:11 2003
Completed: alter database "idd" open
Dump file d:\idd\pdump\trace\iddALRT.LOG
Mon Jul 07 07:43:05 2003
ORACLE V8.0.5.0.0 - Production vsnsta=0
vsnsql=c vsnxtr=3
Windows NT V5.0, OS V8.147, CPU type 586
Starting up ORACLE RDBMS Version: 8.0.5.0.0.
System parameters with non-default values:
  processes                = 100
  shared_pool_size         = 10000000
  control_files            = c:\orant\database\ctl1IDD.ora,
d:\idd\controls\ctl2IDD.ora
  db_block_buffers         = 550
  db_block_size            = 2048
  compatible               = 8.0.5.0.0
  log_buffer               = 32768
  log_checkpoint_interval  = 8000
  log_checkpoint_timeout   = 0
  db_files                 = 1020
  db_file_multiblock_read_count= 16
  dml_locks                = 200
  sequence_cache_entries   = 30
  sequence_cache_hash_buckets= 23
  remote_login_passwordfile= SHARED
  sort_area_size           = 65536
  db_name                  = IDD
  background_dump_dest     = d:\idd\pdump\trace
  user_dump_dest           = d:\idd\udump\trace
  max_dump_file_size       = 10240
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
Mon Jul 07 07:43:05 2003
alter database "idd" mount exclusive
Mon Jul 07 07:43:10 2003
Successful mount of redo thread 1, with mount id 1609705101.
Mon Jul 07 07:43:10 2003
Database mounted in Exclusive Mode.
Completed: alter database "idd" mount exclusive
Mon Jul 07 07:43:10 2003
alter database "idd" open
Beginning crash recovery of 1 threads
Recovery of Online Redo Log: Thread 1 Group 2 Seq 62 Reading mem 0
  Mem# 0 errs 0: D:\IDD\LOGFILES\LOGIDD2.ORA
Crash recovery completed successfully
Picked broadcast on commit scheme to generate SCNs
Mon Jul 07 07:43:13 2003
Thread 1 advanced to log sequence 63
Thread 1 opened at log sequence 63
  Current log# 1 seq# 63 mem# 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Successful open of redo thread 1.
Mon Jul 07 07:43:14 2003
SMON: enabling cache recovery
SMON: enabling tx recovery
Mon Jul 07 07:43:16 2003
Completed: alter database "idd" open
Dump file d:\idd\pdump\trace\iddALRT.LOG
Mon Jul 07 23:55:26 2003
ORACLE V8.0.5.0.0 - Production vsnsta=0
vsnsql=c vsnxtr=3
Windows NT V5.0, OS V8.147, CPU type 586
Starting up ORACLE RDBMS Version: 8.0.5.0.0.
System parameters with non-default values:
  processes                = 100
  shared_pool_size         = 10000000
  control_files            = c:\orant\database\ctl1IDD.ora,
d:\idd\controls\ctl2IDD.ora
  db_block_buffers         = 550
  db_block_size            = 2048
  compatible               = 8.0.5.0.0
  log_buffer               = 32768
  log_checkpoint_interval  = 8000
  log_checkpoint_timeout   = 0
  db_files                 = 1020
  db_file_multiblock_read_count= 16
  dml_locks                = 200
  sequence_cache_entries   = 30
  sequence_cache_hash_buckets= 23
  remote_login_passwordfile= SHARED
  sort_area_size           = 65536
  db_name                  = IDD
  background_dump_dest     = d:\idd\pdump\trace
  user_dump_dest           = d:\idd\udump\trace
  max_dump_file_size       = 10240
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
Mon Jul 07 23:55:26 2003
alter database "idd" mount exclusive
Mon Jul 07 23:55:31 2003
Successful mount of redo thread 1, with mount id 1609749363.
Mon Jul 07 23:55:31 2003
Database mounted in Exclusive Mode.
Completed: alter database "idd" mount exclusive
Mon Jul 07 23:55:31 2003
alter database "idd" open
Beginning crash recovery of 1 threads
Recovery of Online Redo Log: Thread 1 Group 1 Seq 63 Reading mem 0
  Mem# 0 errs 0: C:\ORANT\DATABASE\LOGIDD1.ORA
Crash recovery completed successfully
Picked broadcast on commit scheme to generate SCNs
Mon Jul 07 23:55:35 2003
Thread 1 advanced t

          Followup:

umm, it is january.

this says the last time the db was started was july

if you have a really big alert file that got truncated (thank goodness!!!!!)
please don't post it here, i don't want it.

do you REBOOT you windoze server each night (as asked)

      Rating:
      5
  January 06, 2004
Reviewer:
      juancarlosreyesp@yahoo.com  from Here :)
Hi,
Tom is right it is not a good idea to post all your alert.log
you should only post the last two startup process maximum.

What you have to check in your alert log is

-->Mon Jul 07 07:43:05 2003
ORACLE V8.0.5.0.0 - Production vsnsta=0
vsnsql=c vsnxtr=3
Windows NT V5.0, OS V8.147, CPU type 586
-->Starting up ORACLE RDBMS Version: 8.0.5.0.0.
System parameters with non-default values:

This means your database had started up

-->Shutting down instance: further logons disabled
Shutting down instance (immediate)
License high water mark = 1
Tue Jan 06 18:38:26 2004
alter database close normal
Tue Jan 06 18:38:26 2004
SMON: disabling tx recovery
SMON: disabling cache recovery
Tue Jan 06 18:38:26 2004
-->Shutting down archive processes
Archiving is disabled
Tue Jan 06 18:38:26 2004
ARCH shutting down
-->alter database dismount

This means your database had been shutdown manually, not crashed.

If after your start up you find things like this, as your have.

Beginning crash recovery of 1 threads
Started recovery at
 Thread 1: logseq 855, block 3049, scn 0.0
Recovery of Online Redo Log: Thread 1 Group 2 Seq 855 Reading mem 0
  Mem# 0 errs 0: D:\ORAEDVB\LOGFILES\LOG_2EDVB.ORA
  Mem# 1 errs 0: E:\ORAEDVB\LOGFILES\LOG_2EDVB.ORA

means its doing a crash recovery, it seems your server is shutdown, or someone
is playing with it.

Based on my experience what I see is your alert log says you are not shutting
down your database, else crashing.
If you are not doing that, go to your office with your bed and see what happens
one night, how the database gets shuts down, some one is restarting your
database or other strange thing is happening.

You can do a normal startup and shutdown to see the difference, I suggest you to
rename the current alert log.
:)

      Rating:
      5
  January 23, 2004
Reviewer:
      A reader

January 06, 2004 LB -- Thanks for the question regarding "group
      records by interval of 3 seconds", version 8.1.7

      You Asked
Hi Tom,

I am not able to find the exact answer to my question although I think I might
need to use LAG and LEAD:

I have records like this:

Time              Amount
11/22/2003 12:22:01    100
11/22/2003 12:22:03    200
11/22/2003 12:22:04    300
11/22/2003 12:22:45    100
11/22/2003 12:22:46    200
11/22/2003 12:23:12    100
11/22/2003 12:23:12    200

What I need to do is sum the amounts where the time of the records is within 3
seconds of each other.

In the case where the data is like this:

11/22/2003 12:22:03    200
11/22/2003 12:22:04    200
11/22/2003 12:22:05    200
11/22/2003 12:22:06    200
11/22/2003 12:22:07    200
11/22/2003 12:22:08    200
11/22/2003 12:22:09    200

There would only be one row with the total for all the rows.

(Basically, we are looking for "instances" where we define an instance such that
all the records within the instance are no more than three seconds apart.  So
there can be 1 or many records all of the same instance and the resulting
summation would have one summary record per instance.)

Would you please point me in the right direction?

      and we said...
well, the way I decided to deal with this is:

take the first row, if the subsequent row is within 3 seconds, it stays in this
group -- else, create a new group.

then, after "grouping", we sum....

Analytics rock
Analytics roll

ops$tkyte@ORA920PC> create table t ( x date, y int );
Table created.

ops$tkyte@ORA920PC> declare
  2          l_date date := to_date( '12:22:03', 'hh24:mi:ss' );
  3  begin
  4          for i in 1 .. 10
  5          loop
  6               insert into t values ( l_date, dbms_random.value( 0, 100 ) );
  7               l_date := l_date + 1/24/60/60;
  8          end loop;
  9
 10          l_date := l_date + 5/24/60/60;
 11          for i in 1 .. 3
 12          loop
 13               insert into t values ( l_date, dbms_random.value( 0, 100 ) );
 14               l_date := l_date + 1/24/60/60;
 15          end loop;
 16
 17          l_date := l_date + 15/24/60/60;
 18          for i in 1 .. 12
 19          loop
 20               insert into t values ( l_date, dbms_random.value( 0, 100 ) );
 21               l_date := l_date + 1/24/60/60;
 22          end loop;
 23  end;
 24  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> alter session set nls_date_format = 'hh24:mi:ss';
Session altered.

ops$tkyte@ORA920PC> select x, y, max(rn) over (order by x)
  2    from (
  3  select x, y,
  4         lag(x) over (order by x),
  5         case when abs(lag(x) over (order by x) - x) > 3/24/60/60 then
row_number() over (order by x)
  6                  when row_number() over (order by x) = 1 then 1
  7                          else null
  8                  end rn
  9    from t
 10         )
 11  /

X                 Y MAX(RN)OVER(ORDERBYX)
-------- ---------- ---------------------
12:22:03         69                     1
12:22:04         62                     1
12:22:05         73                     1
12:22:06         69                     1
12:22:07         95                     1
12:22:08         87                     1
12:22:09         49                     1
12:22:10         85                     1
12:22:11         94                     1
12:22:12         57                     1
12:22:18         86                    11
12:22:19         50                    11
12:22:20         90                    11
12:22:36         63                    14
12:22:37         26                    14
12:22:38         14                    14
12:22:39         96                    14
12:22:40         18                    14
12:22:41         77                    14
12:22:42         50                    14
12:22:43         11                    14
12:22:44          1                    14
12:22:45         74                    14
12:22:46        100                    14
12:22:47         20                    14

25 rows selected.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select min(x), max(x), sum(y)
  2    from (
  3  select x, y, max(rn) over (order by x) max_rn
  4    from (
  5  select x, y,
  6         lag(x) over (order by x),
  7         case when abs(lag(x) over (order by x) - x) > 3/24/60/60 then
row_number() over (order by x)
  8                  when row_number() over (order by x) = 1 then 1
  9                          else null
 10                  end rn
 11    from t
 12         )
 13         )
 14   group by max_rn
 15  /

MIN(X)   MAX(X)       SUM(Y)
-------- -------- ----------
12:22:03 12:22:12        740
12:22:18 12:22:20        226
12:22:36 12:22:47        550

Review &
      Followup

      Rating: 5
Very helpful
      and quite impressive  November 25, 2003
Reviewer:
      LB  from New Jersey
Wow!  Thanks a lot!!!

      Rating: 5
HELP - worked
      in view but not stored proc  December 10, 2003

Reviewer:  LB  from New Jersey
Tom,

Your previous solution was most helpful and we have been using that logic as
part of a view.
But then we decided instead of a view to load the data into a table every day,
since the view takes a while to run.

I have created a stored procedure but for some reason it is giving me this
error:

"Line 61: Pos 15: PLS-00103: Encountered the symbol "(" when expecting one of
the following:

   , from"

When I run the query by itself no problem, but when inserting within the
procedure it gives me a problem.

Does it have to do with the case?  (I am using Oracle 8.1.7)

Here is my procedure:

CREATE OR REPLACE PROCEDURE UETADMIN2.LOAD_UET_COLLECTION_DATA_PAGE

/********************************************************************************
********
* By:               L. Betesh
* Procedure Name:   LOAD_UET_COLLECTION_DATA_PAGE
*
* Description: This procedure is called by CW2_Daily. It produces daily CW2
performance
*              statistics sourced from uet_collection_DATA into UET_AVG_TEMP.
*              All successful or erroneous execution  messages will be logged in
procedure_log.
*
* Parameters: None
*
* Dependent Tables or views:
*   procedure_log - Log table
*   UET_COLECTION_DATA - Raw data
*   V_UET_MAPPINGS - Contains function mappings
*
*********************************************************************************
*******/

AS

--VARIABLES FOR EXCEPTION HANDLING AND LOG REPORTING
V_NUM_ROW              NUMBER :=0;
V_PROCESS_NAME         VARCHAR2(50) := 'LOAD_UET_COLLECTION_DATA_PAGE';

BEGIN

INSERT INTO PROCEDURE_LOG
VALUES(v_process_name||' '||TO_CHAR(SYSDATE, 'MM/DD/YYYY FMHH24:MI:SS'),
'START');
COMMIT;

EXECUTE IMMEDIATE  'TRUNCATE TABLE UET_COLLECTION_DATA_PAGE';

INSERT INTO UET_COLLECTION_DATA_PAGE

SELECT min(req_time) AS req_time,
       map_id,
       ltrim(rtrim(IPADDRESS)) AS ipaddress,
       user_platform,
       usa_id,
       SUM(T_FIRSTBYTE) AS T_FIRSTBYTE,
       SUM(T_DOWNLOAD) AS T_DOWNLOAD,
       SUM(T_RENDER) AS T_RENDER,
       ROUND(MAX(REQ_TIME + T_OVERALL/1000) - MIN(REQ_TIME), 2)*1000 AS
T_OVERALL,
       ltrim(rtrim(brn_i)) AS BRN_I,
       lower(ltrim(rtrim(usr_uunm_I))) AS usr_uunm_I,
       usr_rmt_initr_c
  FROM ( SELECT IPADDRESS, req_time,  map_id, user_platform, usa_id, T_OVERALL,
t_firstbyte, t_download,
               t_render, brn_i, usr_uunm_I, usr_rmt_initr_c,
               max(rn) over (order by req_time)
               AS max_rn
           FROM (
                   SELECT app_pg_req_m AS req_time, app_pg_I AS map_id,
                        usr_ip_ads_t AS ipaddress, usr_pltfrm_x AS
user_platform,
                        ed_usa_i AS usa_id, app_1st_byt_time_n AS t_firstbyte,
                        app_dnld_time_n AS t_download, app_rndr_time_n AS
t_render,
                        app_ovrll_time_n AS t_overall, LTRIM(brn_i) AS BRN_I,
                        LOWER(usr_uunm_I) AS USR_UUNM_I, usr_rmt_initr_c,
                        lag(TO_CHAR(app_pg_req_m,'MM/DD/YYYY FMHH24:MI:SS'))
    /* LINE 61*/            over (order by TO_CHAR(app_pg_req_m,'MM/DD/YYYY
FMHH24:MI:SS')) AS previous,
                        CASE
                        when abs(lag(app_pg_req_m)
                                 over (order by TO_CHAR(app_pg_req_m,'MM/DD/YYYY
FMHH24:MI:SS')) - app_pg_req_m) > 3/24/60/60
                              THEN row_number() over (order by
TO_CHAR(app_pg_req_m,'MM/DD/YYYY FMHH24:MI:SS'))
                        when row_number() over (order by
TO_CHAR(app_pg_req_m,'MM/DD/YYYY FMHH24:MI:SS')) = 1
                             then 1
                        else null
                        end AS rn
                   FROM uetadmin2.uet_collection_data, UETADMIN2.V_UET_MAPPINGS,
                        (SELECT COUNT(DISTINCT app_pg_i) AS PG_CNT, app_func_i
                           FROM uetadmin2.application_function_page
                       GROUP BY app_func_i) afp
                 WHERE uetadmin2.v_uet_mappings.APP_FUNC_I = afp.APP_FUNC_I
                   AND pg_cnt > 1
                   AND uet_collection_data.app_pg_I = V_UET_MAPPINGS.MAP_ID
                )
       )
GROUP BY IPADDRESS, map_id, user_platform, usa_id, brn_i, usr_uunm_I,
usr_rmt_initr_c, max_rn
;

COMMIT;

v_num_row := SQL%ROWCOUNT;

INSERT INTO PROCEDURE_LOG
VALUES(v_process_name||' '||TO_CHAR(SYSDATE, 'MM/DD/YYYY FMHH24:MI:SS'),
'FINISHED.  ROWS ADDED: ' || V_NUM_ROW   );
COMMIT;

END;
/

THANKS!!!

-LB

          Followup:

case, group by rollup, group by cube, analytic functions, order by in a
subquery,etc are sql constructs that are not recognized by plsql in 8i

they are in 9i

until then you must use native dynamic sql to "hide" the construct from plsql

execute immediate 'insert into .... ' using bind1, bind2, bind3, ....;

      Rating: 5
NO Case in
      Stored Proc  December 10, 2003
Reviewer:
      A reader
Hi Tom,

I just found another place on this site where you said:

"case is not a plsql construct until 9i.  It is available in sql in 816 and 817,
not plsql.  "

Now I know why I am getting an error.

So the only option I can think of is to create this query as a view and then
load the table as select from the view.

Am I right that that is the only option?

Thanks again!

          Followup:

execute immediate, dynamic sql

      Rating:
      5
Question...  December 11, 2003
Reviewer:
      Kashif  from Reston, VA
Hi Tom,

Excellent use of row_number and lag to achieve the required results. Though I'm
a little unclear on how the 'max_rn' ( max(rn) over (order by x) ) column's
value was derived, and it's my lack of understanding of how the max analytic
function works. The 'rn' column has '1', followed by 9 nulls, then '11',
followed by 2 nulls, and finally '14', followed 11 nulls. The 'max_rn' column
though manages to substitute every null occurrence in the 'rn' column with the
previous highest 'rn' value. I'm not clear on why the max function would do
that, i.e. substitute the nulls in a column with the previous highest value of
rn. I would've thought that if there was a null in 'rn', it would've returned a
null in the 'max_rn' column. Any explanation would be helpful. Thanks.

Kashif

          Followup:

max(rn) over (order by x)

says to take the result set (don't partition, no partition clause),

order by X (so you get the first row with 1, followed by some nulls, then 11,
more nulls, then 14, more nulls)

using range between current row and unbounded preceding (that is the DEFAULT
range) give me the max value.  That basically means -- look for the max value in
this result set starting with the current row and looking at ALL preceding rows.
 So, for row 1 -- that is 1 (current row plus preceding -- of which there are
none).  for the second row that is 1 (current row = null, preceding = 1, max is
1).  for the 11th row that is 11 (current row = 11, preceding rows = NULL except
for row 1 which is 1, max = 11)

and so on.

      Rating: 5
Awesome, thanks
      a lot Tom!  December 11, 2003
Reviewer:
      Kashif  from Reston, VA

      Rating:
      5
  December 11, 2003
Reviewer:
      A reader
Tom,

Out of curiosity, were you involved in the designing of Analytic functions in
Oracle.

TIA.

          Followup:

nope.

mod_plsql.
html db to a degree..
various "i sure wish it worked like this" emails...

but no, I don't work in development really.

Wish I did think of them -- coolest things to happen to sql since 'select'

      Rating: 5
my favorite
      line in the reply was  December 12, 2003
Reviewer:
      peon  from peonville
"the way I decided to deal with this is"

One day I hope to be able to speak or write such a phrase with confidence in
that I will be able to solve any problem thrown at me with a correct and fast
solution.

The only dissatisfying aspect of the masses making such comments is that the
site owner never replies to such fan club clamoring... ;(

      Rating: 4
Time Based
      Statistics?  December 28, 2003
Reviewer:  Raj
      Ganga  from Mumbai,India
Tom,
i have table t

PID                                      NUMBER
DID                                      NUMBER
CONS_TIME                                DATE
CONS_FEE                                 NUMBER

pid is the unqiue id for patient and did for Doctor
cons fee is the consoltation fee
how to find the sum of fee colloected per 5 per doctor
min on a given day.

i have tried with anlytical functions but each has its own window and sum is not
reflecting correct.

Please guide me,
thank you for spending your most valuable time on my problem.

warm Regards
Raj.

          Followup:

what is a "5 per doctor min"

no idea how to use a DATE (point in time) as a duration here.  you'll really
need to be a little more explicit -- an example set of inputs, the desired
outputs and a "how to" go from inputs to outputs would be needed.

      Rating: 4
Timed
      Stastics  December 29, 2003
Reviewer:
      Rajshekhar GANGA  from Mumbai,India
Tom,
table t :

PID                                      NUMBER
DID                                      NUMBER
CONS_TIME                                DATE
CONS_FEE                                 NUMBER

Following are rows for one doctor id.

       PID        DID CONS_TIME             CONS_FEE
--------- ---------- ------------------- ----------
   202737          1 26/12/2003 09:39:36     202737
    33867          1 26/12/2003 09:40:36      16934
    33894          1 26/12/2003 09:41:36      11298
    33895          1 26/12/2003 09:42:36       8474
    34242          1 26/12/2003 09:43:36       6848
    34327          1 26/12/2003 09:44:36       5721
    33868          1 26/12/2003 09:45:36       4838
   687891          1 26/12/2003 09:46:36      85986
   220913          1 26/12/2003 09:47:36      24546
    35122          1 26/12/2003 09:48:36       3512

i want to have time based statistics(per 5min.) like.
Did                    Total Collection
1     26/12/2003 09:44:36    252012
(202737+16934+11298+8474+6848+5721)
1     26/12/2003 09:48:36    118882
(4838+85986+ 24546+3512)

Thanks
Raj.

          Followup:

using slightly different data to test boundaries, but here you go:

ops$tkyte@ORA9IR2> select did,
  2         cons_time,
  3             cons_fee,
  4             trunc(abs( (((cons_time-min(cons_time) over (partition by
did))*24*60)-0.0001)/5 )) grp
  5    from t
  6  /

       DID CONS_TIME             CONS_FEE        GRP
---------- ------------------- ---------- ----------
         1 26/12/2003 09:39:36     202737          0
         1 26/12/2003 09:40:36      16934          0
         1 26/12/2003 09:41:36      11298          0
         1 26/12/2003 09:42:36       8474          0
         1 26/12/2003 09:43:36       6848          0
         1 26/12/2003 09:44:36       5721          0
         1 26/12/2003 09:45:36       4838          1
         1 26/12/2003 09:46:36      85986          1
         1 26/12/2003 09:47:36      24546          1
         1 26/12/2003 09:48:36       3512          1
         1 26/12/2003 09:49:36          0          1
         1 26/12/2003 09:50:36       3512          2

12 rows selected.

ops$tkyte@ORA9IR2> select did, min(cons_time), max(cons_time), sum(cons_fee)
  2    from (
  3  select did,
  4         cons_time,
  5             cons_fee,
  6             trunc(abs( (((cons_time-min(cons_time) over (partition by
did))*24*60)-0.0001)/5 )) grp
  7    from t
  8         )
  9   group by did, grp
 10  /

       DID MIN(CONS_TIME)      MAX(CONS_TIME)      SUM(CONS_FEE)
---------- ------------------- ------------------- -------------
         1 26/12/2003 09:39:36 26/12/2003 09:44:36        252012
         1 26/12/2003 09:45:36 26/12/2003 09:49:36        118882
         1 26/12/2003 09:50:36 26/12/2003 09:50:36          3512

      Rating: 5
Test
      Connection  December 30, 2003
Reviewer:
      Rajashekhar GANGA  from Mumbai,INDIA
Tom,
thanks a lot

sorry for adding in response,
i have a question
we have leased lines connecting to our manufacturing units,
there are trigger and procedures which inserts rows from one location to other.
The problem is when the line is closed our procedures and triggers raises “timed
out”
error
How can we check the connection to the remote computer similar to “ping”
utility.

Happy New Year.

Regards
Raj.

          Followup:

sounds like you need a new leaser -- the QOS sounds pretty bad.

basically, there is no "ping" in the database and even if there was, so ?  you
ping, its alive, you start and it dies.  pinging buys you nothing really.

      Rating: 2
use
      tnsping  December 31, 2003
Reviewer:
      A reader
.

          Followup:

in a stored procedure or trigger, hmmm.

      Rating: 2
Test
      Connection  December 31, 2003
Reviewer:  Raj
      GANAGA  from Mumbai, INDIA
i think i have not made it clear,

i have database link which connects to remote database user.

if table t has an update then the relevant table t on the remote database to be
updated or inserted based on some business logic. if this logic is place in a
procedure and call it from update or insert trigger,
i need to check the connectivity to the remote database before calling the
procedure other wise "Connection Timed Out" will be raised after some time,
this delay in raising the exception makes catching the exception not of much
use.

Thankx for reading the request.
Raj.

          Followup:

i understood that -- there is nothing like "ping" in there -- and if there was,
it would suffer the same exact "timeout issue".

Suggestion -- don't have client applications do distributed operations.
Instead, have the client application schedule a job via dbms_job, have the job
do the distributed operation.  Now, you don't really care if it take .05 seconds
of 50 seconds as no humans are sitting there waiting for it and your system will
appear to be infinitely more available (as you are no longer relying on the wire
to be there in real time).

Syncronous replication is a really really bad idea -- it really means "i should
have only one database in the first place"

      Rating:
      5
  January 05, 2004
Reviewer:  Olaf
       from Germany
Tom,

I have a very big table with entries of patients. The data are inserted online
from many mobile devises (OLTP system)
How can I select (most performant) last 5 entries for one patient (SQL query)

Thanks in advance

Olaf

          Followup:

select *
  from ( select /*+ FIRST_ROWS */ *
           from T
          where patient_id = :x
          order by patiend_id DESC, visit_date DESC )
 where rownum <= 5;

using the CBO.  Consider:

big_table@ORA920PC> select count(*) from v;

  COUNT(*)
----------
   3500000

1 row selected.

big_table@ORA920PC>
big_table@ORA920PC> set autotrace on
big_table@ORA920PC> select *
  2    from ( select /*+ FIRST_ROWS */ *
  3             from v
  4                    where patient_id = :x
  5                    order by patient_id DESC, visit_date DESC )
  6   where rownum <= 5;

PATIENT_ID                     VISIT_DAT DATA1                          DATA2
------------------------------ --------- ------------------------------
------------------
BIG_TABLE                      09-JUL-03 BIG_TABLE                      TABLE
BIG_TABLE                      09-JUL-03 BIG_TABLE                      TABLE
BIG_TABLE                      09-JUL-03 BIG_TABLE                      TABLE
BIG_TABLE                      09-JUL-03 BIG_TABLE                      TABLE
BIG_TABLE                      09-JUL-03 BIG_TABLE                      TABLE

5 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=58797 Card=5
Bytes=270)
   1    0   COUNT (STOPKEY)
   2    1     VIEW (Cost=58797 Card=106061 Bytes=5727294)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=58797
Card=106061 Bytes=4242440)
   4    3         INDEX (RANGE SCAN DESCENDING) OF 'BT_OWNER_CREATED'
(NON-UNIQUE) (Cost=365 Card=106061)

Statistics
----------------------------------------------------------
          7  recursive calls
          0  db block gets
         12  consistent gets
          0  physical reads
          0  redo size
        623  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)
          5  rows processed

big_table@ORA920PC> set autotrace off

      Rating: 4
Why not "OVER
      (PARTITION BY VISIT_ID ORDER BY DATE_CLICK RANGE INTERVAL '1'
      DAY"  January 05, 2004
Reviewer:  Lee
       from NY
Tom,
In all the response for the questions that dealt with getting records within a
time frame you did not mentioned:

OVER (PARTITION BY COL_1 ORDER BY DATE_COL RANGE INTERVAL '1' DAY

I found this method (with some additions and variations) to work very good for
getting a sliding window of records, don’t you agree?

Thanks.

          Followup:

that would get a sliding window -- yes.

But, we didn't WANT a sliding window.  We wanted to

a) sort records by time
b) take ALL records within 3 seconds of eachother
c) turn them into a single record

Lets see you do that with a sliding window.  the sliding window here would not
be an appropriate tool for the question asked.

      Rating: 5
added another
      variable  January 06, 2004
Reviewer:  LB
       from New Jersey
Hi Tom,

Thanks for your help before.
Now we have another variable added in and I can't figure out how to add it to
the query.
Below is the data you used before plus a sample of the new variable:

X                 Y                     Z
-------- ---------- ---------------------
12:22:03         69                 USER1
12:22:04         62                 USER1
12:22:05         73                 USER1
12:22:06         69                 USER1
12:22:07         95                 USER1
12:22:08         87                 USER1
12:22:09         49                 USER2
12:22:10         85                 USER3
12:22:11         94                 USER2
12:22:12         57                 USER4
12:22:18         86                 USER1
12:22:19         50                 USER1
12:22:20         90                 USER1
12:22:36         63                 USER1
12:22:37         26                 USER1
12:22:38         14                 USER1
12:22:39         96                 USER1
12:22:40         18                 USER1
12:22:41         77                 USER1
12:22:42         50                 USER1
12:22:43         11                 USER1
12:22:44          1                 USER1
12:22:45         74                 USER1
12:22:46        100                 USER1
12:22:47         20                 USER1

Basically, we only want to sum those records that are of the same user.

I have tried adding the user field to the order by of the lag but that did not
work.  Should I do group by z in the subquery?  All I know is that I have to
modify the innermost query first to get the groupings by user.

Have I explained the question clearly?

Thanks a million.

Leora

          Followup:

you want to PARTITION BY Z, you want to break the input query into N partitions
-- one for each value of Z.

ops$tkyte@ORA920PC> alter table t add z varchar2(10);

Table altered.

ops$tkyte@ORA920PC> update t set z = 'user1';

25 rows updated.

ops$tkyte@ORA920PC> insert into t select x, y, 'user2' from t;

25 rows created.

ops$tkyte@ORA920PC> commit;

Commit complete.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> alter session set nls_date_format = 'hh24:mi:ss';

Session altered.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select z, min(x), max(x), sum(y)
  2    from (
  3  select x, y, z, max(rn) over (partition by z order by x) max_rn
  4    from (
  5  select x, y, z,
  6         lag(x) over (partition by z order by x),
  7         case when abs(lag(x) over (partition by z
  8                                        order by x) - x) > 3/24/60/60
  9                      then row_number() over (partition by z order by x)
 10                  when row_number() over (partition by z order by x) = 1 then
1
 11                          else null
 12                  end rn
 13    from t
 14         )
 15         )
 16   group by z, max_rn
 17  /

Z          MIN(X)   MAX(X)       SUM(Y)
---------- -------- -------- ----------
user1      12:22:03 12:22:12        446
user1      12:22:18 12:22:20        221
user1      12:22:36 12:22:47        342
user2      12:22:03 12:22:12        446
user2      12:22:18 12:22:20        221
user2      12:22:36 12:22:47        342

6 rows selected.

      Rating:
      5
Thanks  January 06, 2004
Reviewer:
      LB  from New Jersey
Another lesson in analytics.
I think once you do it once it becomes easier to do again.

Thanks a lot.

      Rating: 5
Simple genial
      and simple solution  January 10, 2004
Reviewer:
      www.db-nemec.com  from Austria
I  have been looking for this solution since December 10, 2001 (see
http://asktom.oracle.com/pls/ask/f?p=4950:61:3018938560581774526::::P61_ID:2165133263446
and
ctrl F 'Are there limits on analytic functions'

It is nice to see that there is a analytic solution to this "session assignment"
problem

Once again simple and ingenious solution

Thanks

Nemec

January 06, 2004 cosmin -- Thanks for the question regarding
      "synonyms performance", version 8.16

      You Asked
hi Tom,
I currently have an instance of over a hundred Ora custom packages that have
been written over many years, all under one user, without any real security in
mind, at that time.

I'm trying to now implement security with the least headaches, ie, without
re-writing packages to use views and instead create (private) synonyms on the
objects, creating roles and giving those roles to users.

I was wondering how synonyms performance scales over 8i, 9i, 10g, ie, if it
would be beneficial to wait for 10g should 10g have addressed
performance/scalability in this area.

Do you have any other suggestions in implementing security that would not become
an endless task, considering the amount of code already in place?

thanks much,
Cosmin

      and we said...
actually, the hit of private synonyms has always been pretty low, not very
measurable anymore and in 10g -- I'll have to totally change my stance on using
them!

Watch this:

ops$tkyte@ORA9IR2> create table t1 ( x int );
Table created.

ops$tkyte@ORA9IR2> create table t2 ( x int );
Table created.

ops$tkyte@ORA9IR2> create synonym t for t1;
Synonym created.

ops$tkyte@ORA9IR2> create or replace procedure p
  2  as
  3  begin
  4    for x in (select * from t) loop null; end loop;
  8  end;
  9  /
Procedure created.

ops$tkyte@ORA9IR2> select status from user_objects where object_name = 'P';

STATUS
-------
VALID

ops$tkyte@ORA9IR2> create or replace synonym t for t2;
Synonym created.

ops$tkyte@ORA9IR2> select status from user_objects where object_name = 'P';

STATUS
-------
INVALID

in 9i and before, repointing a synonym invalidates the objects but in 10g

ops$tkyte@ORA10G> select status from user_objects where object_name = 'P';

STATUS
-------
VALID

ops$tkyte@ORA10G> create or replace synonym t for t2;
Synonym created.

ops$tkyte@ORA10G> select status from user_objects where object_name = 'P';

STATUS
-------
VALID

the SIGNATURE of what the synonym points to is checked -- and you can avoid
lots of cascading invalidations when repointing synonyms to new -- yet
equivalent objects

Review &
      Followup

      Rating: 4
coding
      issue  January 06, 2004
Reviewer:  Cosmin
       from Florida
hi Tom, thanks for the great feedback. I was actually equally concerned with the
amount of code that I need to re(write) as well and I just noticed, an issue
with private synonyms that would force me to work with public synonyms, I think.
 How's the hit on public synonym performance or are there any workarounds so
that I won't have to modify the tons of packages and prefix each table, package
with new synonyms?

create table test10(x int);
drop public synonym test10;
--cannot create private synonym of same name under same username, I'm forced to
use public synonyms
CREATE public SYNONYM TEST10 FOR mainUser.TEST10;

--ORA-01471: cannot create a synonym with same name as object
create  synonym test10 for mainUser.test10;

create role admin_role;
create user admin identified by admin;
grant create session, create any synonym, create view to admin;
grant admin_role to admin;
grant select on test10 to admin_role;
grant admin_role to cosmin;

create or replace procedure p
  as
 begin
   for x in (select * from test10) loop null; end loop;
 end;
/

create public synonym p for simmons_database.p;
grant execute on p to admin_role;

connect cosmin/pwd

--this below, looks to be working fine, pointing to the public synonym based on
the mainUser's schema. Yeah, I would have to be careful of not having duplicate
table and package names among users but this would really save me a lot of
typing on a ton of packages that I would not have to touch (as far as I can see)
but would only need to initially create public synonyms and create my roles and
appropriate grants.

select * from test10

Do you see any major performance issues in 10g?  I'm still stuck w/ 8.1.6 and
would consider an upgrade to 9i or better yet 10g depending on the resolution of
mammoth tasks like this which theoretically could save me tons of time ...

Any suggestions?
thx much,

Cosmin

          Followup:

why do you believe you need synonyms at all.

      Rating: 3
for object
      schema reference?  January 06, 2004
Reviewer:
      cosmin  from FL
I have all my objects (tables, packages, etc) created under mainUser.
I want to create other roles and users, grant the roles invidual privileges on
the objects and grant the users any combination of these roles, all if possible,
**without** modifying any of the packages or creating hundreds of views over the
tables or prepend tables/views with mainUser which would be a laborious task and
I thought public synonyms would come in handy.

Basically I am trying to eliminate recoding as much as I can as that is
error-prone.

Is this doable without synonyms or a lot of monkey-typing? :-)

thx much

          Followup:

why do you think you would need to modify the packages?

are you moving the code to another schema?

      Rating: 5
Synonym can it
      be used as ROLLBACK?  January 06, 2004
Reviewer:
      Ketan Bengali  from Sunnyvale, CA USA
Hi Tom,

Thanks a lot for sharing all these new information to us.

I am working on network management application. One of the key feature of this
application is always available with HA and 99.999% uptime. It is easy to do
failover/failback between all application processes, the only problem we are
facing today is with database. If we upgrade the customer and apply new
procedures/functions/schema and after upgarding customers if we see any problems
then want an option to rollback the last DDLs/procedures, etc.

Atleast to rollback packages/procedures, I can see a use of synonyms.

Where I might have synonym P1 for package P1_Version_1.
During next upgrade we can create one more package P1_Version_2 and move synonym
P1 to point to P1_Version_2.

Did anyone used such things before? We follow the policy whereby any table or
column once created cannot be dropped. So chances of becoming any package
invalid is rare.

Thanks a lot for your help.

Regards,

Ketan

          Followup:

umm, this is called "test".  you do the upgrade of a clone of production off to
the side, you run this clone, you beat the @$# out of this clone -- and then you
roll to production.

remember 99.999% uptime is like 5 minutes of downtime per year.

you do not rollback patch on a system like that, you never get into the position
of having to (or you'll never get there)

      Rating: 5
Synonym as
      ROLLBACK  January 06, 2004
Reviewer:  Ketan
      Bengali  from Sunnyvale, CA USA
Hi Tom,

Thanks for the reply. We have a requirement of having downtime of just 5 minute
per year. This downtime applies to all call processing processes and not a
database.

We are developing a call processing product which is deployed to N customer
sites. We don't have privilidege to clone customer data as these data is very
sensitive and also has billing information.

Database is responsible for provisioning N call processing processes and making
them talk to each other. Each main process has a backup process running on
different machine. After upgrading the database with some new feature, for any
reason, if any process fails then we are evaluating an option to go back to
previous database release which was stable. If we do backup/restore then it
takes few hours but I was thinking of using synonym as an alternative approach
which might reduce the downtime of call processing processes.

Do you think it is a good idea?

Regards,

Ketan.

          Followup:

installing your procedures and new functionality will already have blown your
availability was my point.  forget about going back for a minute.

customer should be the ones with test.

customer is the one doing an upgrade. customer needs to have the test system.

customer needs to have sufficient infrastucture to support 99.999% uptime -- and
that infrastructure is going to co$t.

      Rating: 5
reply to "for
      object schema reference"  January 06, 2004

Reviewer:  cosmin  from FL
thx v. much Tom for the enlightenment :-)you always have a way of provoking
further research, questioning authority, beliefs, rules, etc, and for that I
thank you.

No, I don't need to change anything as I'm not moving the code from the initial
schema.

Turns out, everything would work "out of the box" with creation of the
additional roles and specific grants on those roles.

Phew...  you reduced a wholelottamuchado issue to a simple reasoning.  That's
what I love about your approach besides the proven test scenario you always
provide!!!

Happy New year!!!

January 06, 2004 -- Thanks for the question regarding "Pinning
      Revisted ...", version Version 8.1.5

      You Asked
Last time I had asked you a question regarding pinning
automatically on NT.

I remember reading somewhere just by pinning objects, Objects
are marked and not loaded into SGA. We need to call a dummy
procedure of the intending pinning package to get loaded into
memory.

Other way is to recompile the object.(will this method makes
again  some dependant objects "INVALID" ?)

How about loading SYS owned packages ?

can please throw insight about above.

I also appreciate if I can get Daily/Weekly Email facility
answered by you so that We can follow FAQ.

Your Site will be definately very very useful for us and would
like to thank you again for same.

best regards,

      and we said...
That is not the case.  Here is an example on 8.1.5 using a
procedure:

ops$tkyte@DEV8I.WORLD> drop procedure toms_procedure;
Procedure dropped.

ops$tkyte@DEV8I.WORLD> create or replace
  2  procedure toms_procedure
  3  as
  4  begin
  5      null;
  6  end;
  7  /

Procedure created.

ops$tkyte@DEV8I.WORLD> begin
  2     print_table( 'select *
  3                     from v$db_object_cache
  4                    where owner = user
  5                      and name = ''TOMS_PROCEDURE''' );
  6  end;
  7  /
OWNER                         : OPS$TKYTE
NAME                          : TOMS_PROCEDURE
DB_LINK                       :
NAMESPACE                     : TABLE/PROCEDURE
TYPE                          : PROCEDURE
SHARABLE_MEM                  : 9900
LOADS                         : 1
EXECUTIONS                    : 0
LOCKS                         : 0
PINS                          : 0
KEPT                          : NO
-----------------

PL/SQL procedure successfully completed.

So, we can see it is in the shared pool now.  Its not yet KEPT
but it is there.  It might get aged out.  We can get it out of
there by flushing the shared pool:

ops$tkyte@DEV8I.WORLD> alter system flush shared_pool
  2  /

System altered.

ops$tkyte@DEV8I.WORLD>
ops$tkyte@DEV8I.WORLD> begin
  2     print_table( 'select *
  3                     from v$db_object_cache
  4                    where owner = user
  5                      and name = ''TOMS_PROCEDURE''' );
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@DEV8I.WORLD>

See, its not there anymore.  Its out of the SGA.  Now, lets
bring it back in:

ops$tkyte@DEV8I.WORLD> exec sys.dbms_shared_pool.keep(
'OPS$TKYTE.TOMS_PROCEDURE' );

PL/SQL procedure successfully completed.

ops$tkyte@DEV8I.WORLD>
ops$tkyte@DEV8I.WORLD> begin
  2     print_table( 'select *
  3                     from v$db_object_cache
  4                    where owner = user
  5                      and name = ''TOMS_PROCEDURE''' );
  6  end;
  7  /
OWNER                         : OPS$TKYTE
NAME                          : TOMS_PROCEDURE
DB_LINK                       :
NAMESPACE                     : TABLE/PROCEDURE
TYPE                          : PROCEDURE
SHARABLE_MEM                  : 8520
LOADS                         : 1
EXECUTIONS                    : 0
LOCKS                         : 0
PINS                          : 0
KEPT                          : YES
-----------------

PL/SQL procedure successfully completed.

ops$tkyte@DEV8I.WORLD>

Its there, its loaded, its KEPT.  If we execute it:.

ops$tkyte@DEV8I.WORLD> exec toms_procedure;
PL/SQL procedure successfully completed.

ops$tkyte@DEV8I.WORLD>
ops$tkyte@DEV8I.WORLD> begin
  2     print_table( 'select *
  3                     from v$db_object_cache
  4                    where owner = user
  5                      and name = ''TOMS_PROCEDURE''' );
  6  end;
  7  /
OWNER                         : OPS$TKYTE
NAME                          : TOMS_PROCEDURE
DB_LINK                       :
NAMESPACE                     : TABLE/PROCEDURE
TYPE                          : PROCEDURE
SHARABLE_MEM                  : 8444
LOADS                         : 1
EXECUTIONS                    : 0
LOCKS                         : 1
PINS                          : 0
KEPT                          : YES
-----------------

PL/SQL procedure successfully completed.

Executing it did nothing to it really -- it is as it was
before -- the loads didn't go up, it was alread loaded

As for 'keeping' sys owned packages -- you can do that.  It
works and is permitted.

Review &
      Followup

      Rating: 4
Two
      questions  August 13, 2002
Reviewer:  Logan
      Palanisamy  from Santa Clara, CA USA
1. In your examples, when you selected the data from v$db_object_cache, how come
the value for the shareable_mem is different each time.

SHARABLE_MEM                  : 9900
SHARABLE_MEM                  : 8520
SHARABLE_MEM                  : 8444

Shouldn't it be the same, given the size of your procedure didn't change?

2. Also, after you "exec toms_procedure;", how come the value of executions in
the last query remains at 0. I was expecting a value of 1.

Thanks in advance

          Followup:

Frankly, don't know why the amount of shareable memory changed.  I wouldn't
expect the first and second to have the same (very different cases, one the
result of a create or replace, the other an execute) but the second two --
maybe.  But since they are pretty close, I'm not worried about it too much.

As for the executions -- they stopped maintaining that column due to the
overhead incurred (bad performance) when they did in some versions until they
fixed it.  If you do same in 817, it would increment....

      Rating: 5
sharable_mem
      Difference  March 20, 2003
Reviewer:
      subbaba  from Ca, USA
Regarding the sharable memory difference, I ran a quick test on two different
database instances and I got very different results.

connect test/test@db_instance1

SQL> select dummy xyz from dual;

X
-
X

SQL> select sql_text, loads, executions, sharable_mem from v$sql
  2  where  sql_text like 'select dummy xyz from dual%';

SQL_TEXT
----------------------------------------------------------------
     LOADS EXECUTIONS SHARABLE_MEM
---------- ---------- ------------
select dummy xyz from dual
         1          1         4780

connect test/test@db_instance2
Connected.
SQL> select dummy xyz from dual;

X
-
X

SQL> select sql_text, loads, executions, sharable_mem from v$sql
  2  where  sql_text like 'select dummy xyz from dual%';

SQL_TEXT
-----------------------------------------------------------------
     LOADS EXECUTIONS SHARABLE_MEM
---------- ---------- ------------
select dummy xyz from dual
         1          1         8524

Why is sharable_mem differnt across different database instances for the exact
same query?  Shouldn't they be the same? How is this number calculated and what
does it represent?

Would a high sharable_mem have an impact on the performance/scalability of a
database system?  How do we tune this number?

Thank you

          Followup:

do those instances have exactly the same init.ora settings in every regards
(beyond the obvious differences in control files and such)

join the v$parameter tables together, dump out those that have different values
and report what you find.

      Rating: 5
sharable_mem
      difference followup  March 20, 2003
Reviewer:
      Subbaba  from Ca, USA
Here it is.

connect apps/apps@db_instance1

col name for a30
col value for a30

SELECT *
FROM (
   SELECT 'db_instance1 -->' dummy, name, value
   FROM   v$parameter
   MINUS
   SELECT 'db_instance1 -->' dummy,name, value
   FROM   v$parameter@db_instance2
   UNION ALL
   SELECT 'db_instance2 -->' dummy,name, value
   FROM   v$parameter@db_instance2
   MINUS
   SELECT 'db_instance2 -->' dummy,name, value
   FROM   v$parameter) x
ORDER BY 2;

DUMMY            NAME                           VALUE
---------------- ------------------------------ ------------------------------
db_instance1 --> _complex_view_merging          TRUE
db_instance1 --> _new_initial_join_orders       TRUE
db_instance1 --> _optimizer_mode_force          TRUE
db_instance1 --> _optimizer_undo_changes        FALSE
db_instance1 --> _or_expand_nvl_predicate       TRUE
db_instance1 --> _ordered_nested_loop           TRUE
db_instance1 --> _push_join_predicate           TRUE
db_instance1 --> _push_join_union_view          TRUE
db_instance2 --> _system_trig_enabled           TRUE
db_instance1 --> _table_scan_cost_plus_one      TRUE
db_instance1 --> _use_column_stats_for_function TRUE
db_instance1 --> always_anti_join               NESTED_LOOPS
db_instance1 --> always_semi_join               STANDARD
db_instance1 --> aq_tm_processes                1
db_instance2 --> aq_tm_processes                4
db_instance2 --> archive_lag_target             0
db_instance2 --> audit_sys_operations           FALSE
db_instance1 --> background_core_dump           partial
db_instance2 --> background_core_dump           full
db_instance1 --> background_dump_dest           ?/log
db_instance2 --> background_dump_dest           /u17/oracle/crmapdev/9.2.0.1.0
                                                /crmapdev/bdump

db_instance2 --> circuits                       0
db_instance2 --> cluster_database               FALSE
db_instance2 --> cluster_database_instances     1
db_instance2 --> cluster_interconnects
db_instance1 --> compatible                     8.1.7
db_instance2 --> compatible                     9.2.0
db_instance1 --> control_files                  ?/dbs/cntrlmktd1r9.dbf
db_instance2 --> control_files                  /u17/oracle/crmapdev/9.2.0.1.0
                                                /dbf/control_01.ctl, /u17/orac
                                                le/crmapdev/9.2.0.1.0/dbf/cont
                                                rol_02.ctl, /u17/oracle/crmapd
                                                ev/9.2.0.1.0/dbf/control_03.ct
                                                l

db_instance1 --> core_dump_dest                 ?/log
db_instance2 --> core_dump_dest                 /u17/oracle/crmapdev/9.2.0.1.0
                                                /crmapdev/cdump

db_instance1 --> cpu_count                      8
db_instance2 --> cpu_count                      6
db_instance2 --> db_16k_cache_size              0
db_instance2 --> db_2k_cache_size               0
db_instance2 --> db_32k_cache_size              0
db_instance2 --> db_4k_cache_size               0
db_instance2 --> db_8k_cache_size               0
db_instance1 --> db_block_buffers               10000
db_instance2 --> db_block_buffers               160000
db_instance1 --> db_block_checksum              FALSE
db_instance2 --> db_block_checksum              TRUE
db_instance1 --> db_block_lru_latches           4
db_instance1 --> db_block_max_dirty_target      10000
db_instance2 --> db_cache_advice                OFF
db_instance2 --> db_cache_size                  0
db_instance2 --> db_create_file_dest
db_instance2 --> db_create_online_log_dest_1
db_instance2 --> db_create_online_log_dest_2
db_instance2 --> db_create_online_log_dest_3
db_instance2 --> db_create_online_log_dest_4
db_instance2 --> db_create_online_log_dest_5
db_instance1 --> db_domain
db_instance2 --> db_domain                      US.ORACLE.COM
db_instance1 --> db_file_direct_io_count        64
db_instance1 --> db_files                       255
db_instance2 --> db_files                       1500
db_instance2 --> db_keep_cache_size             0
db_instance1 --> db_name                        mktd1r9
db_instance2 --> db_name                        CRMAPDEV
db_instance2 --> db_recycle_cache_size          0
db_instance2 --> dg_broker_config_file1         ?/dbs/dr1@.dat
db_instance2 --> dg_broker_config_file2         ?/dbs/dr2@.dat
db_instance2 --> dg_broker_start                FALSE
db_instance2 --> dispatchers
db_instance2 --> distributed_lock_timeout       60
db_instance1 --> distributed_transactions       182
db_instance1 --> dml_locks                      500
db_instance2 --> dml_locks                      4000
db_instance2 --> drs_start                      FALSE
db_instance1 --> event                          10210 trace name context forev
                                                er, level 2, 10211 trace name
                                                context forever, level 2, 1023
                                                5 trace name context forever,
                                                level 1, 10246 trace name cont
                                                ext forever, level 2, 7267 tra
                                                ce name errorstack level 4, 31
                                                06 trace name errorstack level
                                                 4, 10076 trace name context f
                                                orever, level 1

db_instance2 --> event                          1652 trace name errorstack  le
                                                vel 12, 10262 trace name conte
                                                xt forever, level 4000, 7445 t
                                                race name ERRORSTACK level 3,
                                                10500 trace name context forev
                                                er, level 10, 10246 trace name
                                                 context forever, level 1, 28
                                                crash

db_instance2 --> fal_client
db_instance2 --> fal_server
db_instance1 --> fast_start_io_target           10000
db_instance2 --> fast_start_io_target           0
db_instance2 --> fast_start_mttr_target         0
db_instance1 --> fast_start_parallel_rollback   LOW
db_instance2 --> fast_start_parallel_rollback   false
db_instance2 --> file_mapping                   FALSE
db_instance2 --> filesystemio_options           asynch
db_instance1 --> gc_defer_time                  10
db_instance1 --> gc_releasable_locks            0
db_instance1 --> gc_rollback_locks              0-1024=32!8REACH
db_instance2 --> global_context_pool_size
db_instance1 --> hash_area_size                 512000
db_instance2 --> hash_area_size                 131072
db_instance1 --> hash_multiblock_io_count       0
db_instance1 --> hpux_sched_noage
db_instance2 --> hpux_sched_noage               0
db_instance1 --> ifile                          ?/dbs/ORACLE_APPS_CBO.ORA
db_instance2 --> ifile
db_instance1 --> instance_name                  mktd1r9
db_instance2 --> instance_name                  crmapdev
db_instance1 --> java_pool_size                 20000000
db_instance2 --> java_pool_size                 150994944
db_instance1 --> job_queue_interval             60
db_instance1 --> job_queue_processes            0
db_instance2 --> job_queue_processes            10
db_instance1 --> lm_locks                       12000
db_instance1 --> lm_ress                        6000
db_instance1 --> local_listener
db_instance2 --> local_listener                 crmapdev
db_instance2 --> log_archive_dest_10
db_instance2 --> log_archive_dest_6
db_instance2 --> log_archive_dest_7
db_instance2 --> log_archive_dest_8
db_instance2 --> log_archive_dest_9
db_instance2 --> log_archive_dest_state_10      enable
db_instance2 --> log_archive_dest_state_6       enable
db_instance2 --> log_archive_dest_state_7       enable
db_instance2 --> log_archive_dest_state_8       enable
db_instance2 --> log_archive_dest_state_9       enable
db_instance1 --> log_archive_max_processes      1
db_instance2 --> log_archive_max_processes      2
db_instance1 --> log_buffer                     163840
db_instance2 --> log_buffer                     1048576
db_instance1 --> log_checkpoint_interval        100000
db_instance2 --> log_checkpoint_interval        999999999
db_instance1 --> log_checkpoint_timeout         72000
db_instance2 --> log_checkpoint_timeout         1800
db_instance1 --> log_checkpoints_to_alert       FALSE
db_instance2 --> log_checkpoints_to_alert       TRUE
db_instance2 --> log_parallelism                1
db_instance2 --> logmnr_max_persistent_sessions 1
db_instance2 --> max_dispatchers                5
db_instance1 --> max_dump_file_size             10240
db_instance2 --> max_dump_file_size             UNLIMITED
db_instance1 --> max_enabled_roles              40
db_instance2 --> max_enabled_roles              100
db_instance2 --> max_shared_servers             20
db_instance1 --> mts_service                    mktd1r9
db_instance2 --> mts_service                    CRMAPDEV
db_instance1 --> nls_comp
db_instance2 --> nls_comp                       binary
db_instance2 --> nls_length_semantics           BYTE
db_instance2 --> nls_nchar_conv_excp            FALSE
db_instance2 --> olap_page_pool_size            33554432
db_instance1 --> open_cursors                   500
db_instance2 --> open_cursors                   1000
db_instance1 --> ops_interconnects
db_instance2 --> optimizer_dynamic_sampling     1
db_instance1 --> optimizer_features_enable      8.1.7
db_instance2 --> optimizer_features_enable      9.2.0
db_instance1 --> optimizer_max_permutations     79000
db_instance2 --> optimizer_max_permutations     2000
db_instance1 --> optimizer_percent_parallel     0
db_instance1 --> os_authent_prefix              ops$
db_instance2 --> os_authent_prefix              OPS$
db_instance1 --> parallel_broadcast_enabled     FALSE
db_instance1 --> parallel_max_servers           5
db_instance2 --> parallel_max_servers           12
db_instance2 --> pga_aggregate_target           1258291200
db_instance2 --> plsql_compiler_flags           INTERPRETED
db_instance2 --> plsql_native_c_compiler
db_instance2 --> plsql_native_library_dir
db_instance2 --> plsql_native_library_subdir_co 0
                 unt

db_instance2 --> plsql_native_linker
db_instance2 --> plsql_native_make_file_name
db_instance2 --> plsql_native_make_utility
db_instance1 --> query_rewrite_enabled          TRUE
db_instance2 --> query_rewrite_enabled          true
db_instance2 --> remote_archive_enable          true
db_instance2 --> remote_listener
db_instance1 --> remote_login_passwordfile      EXCLUSIVE
db_instance2 --> remote_login_passwordfile      NONE
db_instance1 --> resource_limit                 FALSE
db_instance2 --> resource_limit                 TRUE
db_instance1 --> rollback_segments              rbs1, rbs2, rbs3, rbs4, rbs01,
                                                 rbs02

db_instance2 --> rollback_segments              rbs01, rbs02, rbs03, rbs04, rb
                                                s05, rbs06, rbs07, rbs08, rbs0
                                                9, rbs10, rbs11, rbs12, rbs_hu
                                                ge

db_instance1 --> service_names                  mktd1r9
db_instance2 --> service_names                  CRMAPDEV.US.ORACLE.COM
db_instance1 --> session_cached_cursors         0
db_instance2 --> session_cached_cursors         100
db_instance2 --> sga_max_size                   2386521936
db_instance1 --> shadow_core_dump               partial
db_instance2 --> shadow_core_dump               PARTIAL
db_instance1 --> shared_pool_reserved_size      30000000
db_instance2 --> shared_pool_reserved_size      80000000
db_instance1 --> shared_pool_size               200000000
db_instance2 --> shared_pool_size               805306368
db_instance2 --> shared_server_sessions         0
db_instance2 --> shared_servers                 0
db_instance1 --> sort_area_size                 256000
db_instance2 --> sort_area_size                 65536
db_instance1 --> sort_multiblock_read_count     2
db_instance2 --> spfile
db_instance2 --> standby_file_management        MANUAL
db_instance2 --> statistics_level               TYPICAL
db_instance1 --> text_enable                    FALSE
db_instance2 --> trace_enabled                  TRUE
db_instance2 --> undo_management                MANUAL
db_instance2 --> undo_retention                 900
db_instance2 --> undo_suppress_errors           FALSE
db_instance2 --> undo_tablespace
db_instance1 --> user_dump_dest                 /sqlcom/crmmkt/mktd1r9/log
db_instance2 --> user_dump_dest                 /sqlcom/log/crmapdev/udump
db_instance1 --> utl_file_dir                   /sqlcom/crmmkt/mktd1r9/temp, *
db_instance2 --> utl_file_dir                   /sqlcom/log/crmapdev, /sqlcom/
                                                inbound/crmapdev, /sqlcom/outb
                                                ound/crmapdev, /sqlcom/out/crm
                                                apdev

db_instance2 --> workarea_size_policy           AUTO

200 rows selected.

          Followup:

curious, whats up with so many events and undocumented init.ora setttings?

but you have tons of optimizer related settings set -- I would only expect to
see the same things if all things were equal.  far too many to weed out here.

anything that could change the plan will change the "memory", something as
simple as:

db_instance1 --> db_block_buffers               10000
db_instance2 --> db_block_buffers               160000

to something as obscure as:

db_instance1 --> _or_expand_nvl_predicate       TRUE

      Rating:
      5
sharable_mem  January 06, 2004
Reviewer:
      A reader
Tom
Can you explain what the sharable_mem for a statement
in v$sql represents - what are its components? We are
having issues where some innocuous queries are taking
up sharable_mem of the order of 2M which looks suspicious.
If we run the same query separately - it takes memory
of the order of 10k. We are using bind variables
appropriately. Another thing is - does sharable_mem
depend on number of executions - I suspect not unless
there is a memory leak somewhere.

Thanx and have a wonderful new year!

          Followup:

what do you mean by "run the same query separately"???

      Rating: 5
thanx
      Tom!  January 06, 2004
Reviewer:
      A reader
"what do you mean by "run the same query separately"??? "

We just log into the database and run the same query
again in a separate sqlplus session (not as part of the
application) - So first we see the same sql as part
of the application consuming huge sharable_mem and
when we run the same through a separate sqlplus session
we see much less sharable_mem...

          Followup:

show me -- cut and paste the relevant information

a) logged into sql plus and the output of the query against v$sql that shows
"big" values including the sql_text

b) running the same query in plus and seeing "small" values.

January 06, 2004 Amin -- Thanks for the question regarding "Min,
      Max, AVG", version 7.3.4

      You Asked
Hi Tom,

Is there a better to write below query. I am using Developer 2000 Reports

Thanks
Amin

QUERY

select
substr(oe_trx_number,1,1) Product,
'1. OE -> Feasibility',

Avg(decode(feasibility_date-oe_tracking_date,0,.5,feasibility_date-oe_tracking_da
te) -weekends(feasibility_date,oe_tracking_date)) DM1_OTK_FEA_AVG
,
min(decode(feasibility_date-oe_tracking_date,0,.5,feasibility_date-oe_tracking_da
te) -weekends(feasibility_date,oe_tracking_date)) DM1_OTK_FEA_MIN
,
max(decode(feasibility_date-oe_tracking_date,0,.5,feasibility_date-oe_tracking_da
te) -weekends(feasibility_date,oe_tracking_date)) DM1_OTK_FEA_MAX
from     can_cust_oe_trx_schedules a,
    mtl_system_items b,
    ra_salesreps c
where    a.inventory_item_id = b.inventory_item_id
and    a.oe_work_order_area_id = c.salesrep_id
and    (
    (:P_product_type = '0125' and substr(oe_trx_number,1,1) in
('0','1','2','5'))
         or    (:P_product_type = '0' and substr(oe_trx_number,1,1) in ('0'))
         or    (:P_product_type = '1' and substr(oe_trx_number,1,1) in ('1'))
         or    (:P_product_type = '2' and substr(oe_trx_number,1,1) in ('2'))
         or    (:P_product_type = '5' and substr(oe_trx_number,1,1) in ('5'))
         or    (:P_product_type = '8' and substr(oe_trx_number,1,1) in ('8'))
         or    (:P_product_type = '9' and substr(oe_trx_number,1,1) in ('9'))
         or    (:P_product_type = '89' and substr(oe_trx_number,1,1) in
('8','9'))
         or    (:P_product_type = '012589' and substr(oe_trx_number,1,1) in
('0','1','2','5','8','9'))
)
group by
substr(oe_trx_number,1,1)
union
select
substr(oe_trx_number,1,1) Product,
'2. Feasibility -> Credit',
    Avg(decode(credit_date-feasibility_date,0,.5,credit_date-feasibility_date)
-weekends(credit_date,feasibility_date)) DM1_FEA_CRE_AVG
,    min(decode(credit_date-feasibility_date,0,.5,credit_date-feasibility_date)
-weekends(credit_date,feasibility_date)) DM1_FEA_CRE_MIN
,    max(decode(credit_date-feasibility_date,0,.5,credit_date-feasibility_date)
-weekends(credit_date,feasibility_date)) DM1_FEA_CRE_MAX

from     can_cust_oe_trx_schedules a,
    mtl_system_items b,
    ra_salesreps c

where    a.inventory_item_id = b.inventory_item_id
and    a.oe_work_order_area_id = c.salesrep_id
and    (
    (:P_product_type = '0125' and substr(oe_trx_number,1,1) in
('0','1','2','5'))
         or    (:P_product_type = '0' and substr(oe_trx_number,1,1) in ('0'))
         or    (:P_product_type = '1' and substr(oe_trx_number,1,1) in ('1'))
         or    (:P_product_type = '2' and substr(oe_trx_number,1,1) in ('2'))
         or    (:P_product_type = '5' and substr(oe_trx_number,1,1) in ('5'))
         or    (:P_product_type = '8' and substr(oe_trx_number,1,1) in ('8'))
         or    (:P_product_type = '9' and substr(oe_trx_number,1,1) in ('9'))
         or    (:P_product_type = '89' and substr(oe_trx_number,1,1) in
('8','9'))
         or    (:P_product_type = '012589' and substr(oe_trx_number,1,1) in
('0','1','2','5','8','9'))
)
group by
substr(oe_trx_number,1,1)
union
select
substr(oe_trx_number,1,1) Product,
'3. Credit -> Pricing',
    Avg(decode(Pricing_date-credit_date,0,.5,pricing_date-credit_date)
-weekends(pricing_date,credit_date)) DM1_CRE_PRI_AVG
,    min(decode(Pricing_date-credit_date,0,.5,pricing_date-credit_date)
-weekends(pricing_date,credit_date)) DM1_CRE_PRI_MIN
,    max(decode(Pricing_date-credit_date,0,.5,pricing_date-credit_date)
-weekends(pricing_date,credit_date)) DM1_CRE_PRI_MAX

from     can_cust_oe_trx_schedules a,
    mtl_system_items b,
    ra_salesreps c

where    a.inventory_item_id = b.inventory_item_id
and    a.oe_work_order_area_id = c.salesrep_id
and    (
    (:P_product_type = '0125' and substr(oe_trx_number,1,1) in
('0','1','2','5'))
         or    (:P_product_type = '0' and substr(oe_trx_number,1,1) in ('0'))
         or    (:P_product_type = '1' and substr(oe_trx_number,1,1) in ('1'))
         or    (:P_product_type = '2' and substr(oe_trx_number,1,1) in ('2'))
         or    (:P_product_type = '5' and substr(oe_trx_number,1,1) in ('5'))
         or    (:P_product_type = '8' and substr(oe_trx_number,1,1) in ('8'))
         or    (:P_product_type = '9' and substr(oe_trx_number,1,1) in ('9'))
         or    (:P_product_type = '89' and substr(oe_trx_number,1,1) in
('8','9'))
         or    (:P_product_type = '012589' and substr(oe_trx_number,1,1) in
('0','1','2','5','8','9'))
)
group by
substr(oe_trx_number,1,1)

Results

Product                        AVG        MIN        MAX
  0

        OE--> FEASIBILITY        1.03        .5        6
        FEASIBILITY --> CREDIT        1.85        .5        5
        CREDIT --> PRICING        1.77         1        3

  2
        OE--> FEASIBILITY        1.91        .5        3
        FEASIBILITY --> CREDIT        1.33         1        2
        CREDIT --> PRICING        1.77         1        3

      and we said...
Use union all instead of Union.

Anytime you see "A union B" ask yourself -- do I need a DISTINCT applied to that
result set?  If not -- use "A union all B", that will skip the sort/distinct.

Also:

select product,
       decode( r, 1, '1. OE -> Feasibility', 2, '2. Feasibility -> Credit', 3,
'3. Credit -> Pricing' ),
       decode( r, 1, oe_avg, 2, feas_avg, 3, credit_avg ),
       decode( r, 1, oe_min, 2, feas_min, 3, credit_min ),
       decode( r, 1, oe_max, 2, feas_max, 3, credit_max )
  from (
select
substr(oe_trx_number,1,1) Product,
Avg(decode(feasibility_date-oe_tracking_date,0,.5,feasibility_date-oe_tracking_da
te) -weekends(feasibility_date,oe_tracking_date)) oe_AVG,
min(decode(feasibility_date-oe_tracking_date,0,.5,feasibility_date-oe_tracking_da
te) -weekends(feasibility_date,oe_tracking_date)) oe_MIN,
max(decode(feasibility_date-oe_tracking_date,0,.5,feasibility_date-oe_tracking_da
te) -weekends(feasibility_date,oe_tracking_date)) oe_MAX,
Avg(decode(credit_date-feasibility_date,0,.5,credit_date-feasibility_date)-weeken
ds(credit_date,feasibility_date)) feas_AVG,
min(decode(credit_date-feasibility_date,0,.5,credit_date-feasibility_date)-weeken
ds(credit_date,feasibility_date)) feas_MIN,
max(decode(credit_date-feasibility_date,0,.5,credit_date-feasibility_date)-weeken
ds(credit_date,feasibility_date)) feas_MAX
Avg(decode(Pricing_date-credit_date,0,.5,pricing_date-credit_date)
-weekends(pricing_date,credit_date)) credit_AVG,
min(decode(Pricing_date-credit_date,0,.5,pricing_date-credit_date)
-weekends(pricing_date,credit_date)) credit_MIN,
max(decode(Pricing_date-credit_date,0,.5,pricing_date-credit_date)
-weekends(pricing_date,credit_date)) credit_MAX
from     can_cust_oe_trx_schedules a,
    mtl_system_items b,
    ra_salesreps c
where    a.inventory_item_id = b.inventory_item_id
and    a.oe_work_order_area_id = c.salesrep_id
and    (
    (:P_product_type = '0125' and substr(oe_trx_number,1,1) in
('0','1','2','5'))
         or    (:P_product_type = '0' and substr(oe_trx_number,1,1) in ('0'))
         or    (:P_product_type = '1' and substr(oe_trx_number,1,1) in ('1'))
         or    (:P_product_type = '2' and substr(oe_trx_number,1,1) in ('2'))
         or    (:P_product_type = '5' and substr(oe_trx_number,1,1) in ('5'))
         or    (:P_product_type = '8' and substr(oe_trx_number,1,1) in ('8'))
         or    (:P_product_type = '9' and substr(oe_trx_number,1,1) in ('9'))
         or    (:P_product_type = '89' and substr(oe_trx_number,1,1) in
('8','9'))
         or    (:P_product_type = '012589' and substr(oe_trx_number,1,1) in
('0','1','2','5','8','9'))
)
group by
substr(oe_trx_number,1,1)
), (select 1 r from dual union all select 2 r from dual union all select 3 r
from dual )

is the same but would only process the tables once instead of three times. You
can give that a whirl
Review & Followup

      Rating: 5
Excellent
      Idea  September 05, 2002
Reviewer:  Amin
      Merchant  from Toronto, ON, Canada
Tom,
Thanks for excellent sql. Everything worked out.

The only thing i need to ask you is, i tried to use this query in Unix based
report designer v 2.5.5.8, as i try to click on ok button on query level, the
designer crashes on me. i tried several times and same thing happen. Error
"Segmentation fault (coredump)".
do i need to write this query in a different way for Report designer.

Amin

          Followup:

Can you try dynamic sql in reports (hide the advanced sql constructs from it?)

      Rating: 5
Can you please
      explain  August 25, 2003
Reviewer:  Gabriel
       from Montreal, Canada
Hello,

Can you please explain how the following query works? It finds the 6th largest
value from a table. It was published in the Oracle magazine in the jan/feb 2003
issue and is featured on otn, but without any explanation.

SELECT * FROM  T3 a WHERE  6 = (SELECT count(DISTINCT(b.c1)) FROM T3 b WHERE
a.c1<=b.c1)

Thank you very much,

          Followup:

ouch and it is the worst way to do this:

select *
 from (
select a.* , rownum rnum
  from ( select * from t order by c1 ) a
 where rownum <= 6
      )
where r = 6;

or something similar with anlalytic functions.  but anyway -- the psuedo code
for that query is quite simple:

for x in ( select * from t3 )
loop
   select count( distinct(c1) ) into l_cnt
     from t3
    where x.c1 <= t3.c1;

   if ( l_cnt = 6 ) /* meaning there are 6 "above" this c1 value */
   then
          OUTPUT RECORD
   end if;
end loop;

      Rating: 5
Super
      cool  August 25, 2003
Reviewer:
      A reader

      Rating: 4
Older Version
      (Oracle 8.0.5)  August 26, 2003
Reviewer:
      N.Ezhilan  from India
The reply works fine in Oracle 8.1.x onwards but some of
Client still has Older Version (Oracle 8.0.5). If so
subquery with Order by Will not work. How do we solve TOM?

TIA

      Rating: 4
NULL giving a
      hard time ?  January 06, 2004
Reviewer:  fan

8.1.7

If "currentdue" below is number column...
I want the smallest non-zero figure, am i supposed to ALWAYS get the same answer
from the 2 below ? I got diff answers thou...
Thanks

select
min(decode(currentdue, 0, null, currentdue)) mindue,
FROM tbl_clients;

select
min(currentdue) mindue,
FROM tbl_clients where currentdue != 0;

          Followup:

hows about "an example" - what different answers are you seeing.

      Rating: 4
Yes, decode is
      doing it...  January 06, 2004
Reviewer:
      robert
>>
Followup:hows about "an example" - what different answers are you seeing.
<<

ah,I got it...yes sir, read about it here on AskTom...but forgot...
darn DECODE turns number into string...Thanks!

SQL> SELECT COUNT(*) cnt, min(transactionbalance)
  2      FROM tblclients WHERE RENEWALDATE < SYSDATE AND ARCHIVED = 0 AND
STATUSCODEID = 1
  3      AND transactionbalance != 0
  4  /

       CNT MIN(TRANSACTIONBALANCE)
---------- -----------------------
        46                    1250

SQL> SELECT COUNT(*) cnt, min(decode(transactionbalance, 0, null,
transactionbalance))
  2      FROM tblclients WHERE RENEWALDATE < SYSDATE AND ARCHIVED = 0 AND
STATUSCODEID = 1
  3  /

       CNT MIN(DECODE(TRANSACTIONBALANCE,
---------- ----------------------------------------
        50 10000

          Followup:

ahh, yes

decode( x, 0, TO_NUMBER(null), x )

January 07, 2004 Prince -- Thanks for the question regarding "How
      do I find what kind of refresh has been done?", version 8.1.7

      You Asked
Dear Tom,

If I have a snpashot defined to have the refresh type "FORCE", how do I
determine, what type of refresh has happened?

Is there any simple way to find, whether the refresh was a complete refresh or
fast refresh? (Logminer may be an option, but I am looking for something simpler
like v$ and/or x$ tables)

Thanks,

      and we said...
Here is a support note on the topic:

Article-ID:         <Note:98697.1>
Circulation:        PUBLISHED (EXTERNAL)
Folder:             server.Distributed.Snapshots
Topic:              Performance
Title:              HOW TO FIND THE LAST REFRESH MODE AND TIME OF A
                    SNAPSHOT/MATERIALIZED VIEW
Document-Type:      BULLETIN
Impact:             MEDIUM
Skill-Level:        NOVICE
Updated-Date:       12-SEP-2000 04:10:32
References:
Shared-Refs:
Attachments:        NONE
Content-Type:       TEXT/PLAIN
Keywords:           MVIEW; SNAPSHOTINFO;
Products:           5/RDBMS;
Platforms:          GENERIC;

PURPOSE

  The purpose of this article is to allow you to find the last refresh type and
the last refresh date of a snapshot/materialized view.

RELATED DOCUMENTS

  Oracle8i Reference Manual

HOW TO FIND THE LAST REFRESH MODE AND TIME OF A SNAPSHOT/MATERIALIZED VIEW
==========================================================================

When dealing with snapshots, especially when the refresh mode is FORCE, you
want to know if the snapshot/materialized view was last refreshed with the
FAST method or the COMPLETE method.

Three new replication views were introduced in Oracle Release 8.1.6. They are
called ALL_MVIEWS, DBA_MVIEWS, and USER_MVIEWS.  These views are documented in
the Oracle8i Reference Manual.

SQL> desc user_mviews
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 OWNER                                     NOT NULL VARCHAR2(30)
 MVIEW_NAME                                NOT NULL VARCHAR2(30)
 CONTAINER_NAME                            NOT NULL VARCHAR2(30)
 QUERY                                              LONG
 QUERY_LEN                                          NUMBER(38)
 UPDATABLE                                          VARCHAR2(1)
 UPDATE_LOG                                         VARCHAR2(30)
 MASTER_ROLLBACK_SEG                                VARCHAR2(30)
 MASTER_LINK                                        VARCHAR2(128)
 REWRITE_ENABLED                                    VARCHAR2(1)
 REWRITE_CAPABILITY                                 VARCHAR2(9)
 REFRESH_MODE                                       VARCHAR2(6)
 REFRESH_METHOD                                     VARCHAR2(8)
 BUILD_MODE                                         VARCHAR2(9)
 FAST_REFRESHABLE                                   VARCHAR2(18)
 LAST_REFRESH_TYPE                                  VARCHAR2(8)
 LAST_REFRESH_DATE                                  VARCHAR2(9)
 STALENESS                                          VARCHAR2(9)
 AFTER_FAST_REFRESH VARCHAR2(9)
 COMPILE_STATE                                      VARCHAR2(13)

There are two relevant fields that will give you the information you need:

LAST_REFRESH_TYPE     The method used for the most recent refresh.
                              o COMPLETE:
                                  The most recent refresh was complete.
                              o FAST:
                                  The most recent refresh was fast.
                              o NA:
                                  The materialized view has not yet
                  been refreshed (for example, if it
                  was created DEFERRED).

LAST_REFRESH_DATE     Date on which the materialized view was
            most recently refreshed.  Blank if not yet
                        populated.

Example:
========

Create a snapshot that refreshes with refresh mode FORCE, which will either
do a FAST or COMPLETE refresh.

SQL> create table t1 (col1 number constraint pk_col1 primary key,
  2  col2 varchar2(10));

Table created.

SQL> create snapshot log on t1;

Materialized view log created.

SQL> create snapshot s_t1 refresh force with primary key
  2  start with sysdate next sysdate+1/1440
  3  as select * from rserna.t1;

Materialized view created.

SQL> select mview_name, refresh_mode, refresh_method,
  2  last_refresh_type, last_refresh_date
  3  from user_mviews
  4  ;

MVIEW_NAME                     REFRES REFRESH_ LAST_REF LAST_REFRESH
------------------------------ ------ -------- -------- ------------
S_T1                           DEMAND FORCE FAST     09-feb-2000
                                                        02:22:31

SQL> drop snapshot log on t1; --If the snapshot log is dropped, it will
                              --surely perform a complete refresh

Materialized view log dropped.

SQL> select mview_name, refresh_mode, refresh_method,
  2  last_refresh_type, last_refresh_date
  3  from user_mviews
  4  /

MVIEW_NAME                     REFRES REFRESH_ LAST_REF LAST_REFRESH
------------------------------ ------ -------- -------- ------------
S_T1                           DEMAND FORCE COMPLETE 09-feb-2000
                                                        02:23:32

Review &
      Followup

      Rating: 4
Problem with
      Materialized View  June 27, 2002
Reviewer:
      A reader  from US
Tom,

I have created a materialized view on 4 million rows. I takes about 4 minutes to
create partitioned MV. But when I do the update on the original table, it takes
more than 35 minutes to refresh the materialized view. I used this with or
without materialized log. Refresh timings are not coming down. And this not
acceptable. How to solve this? Also How to refresh MV for a partition rather
than whole table.

Create table t (
col1 varchar2(10),
col2 number,
col3 varchar2(20)
);

create materialized view log on t using rowid(col1,col2);

create materialzed view t_mv
partition by list (col1) (
 partition part1 values ('NY','NJ') tablespace tblspc1,
 partition part2 values ('TX','CA') tablespace tblspc2
)
parallel build immediate refresh fast on commit enable query rewrite
as select col1,col2,count(*) from t
  group by col1,col2;

-- Creation of MV with complete build takes about 4 minutes on 4.5 million rows.

insert into t select * from t where rownum < 50001;

commit;
-- This commit takes about 35 minutes to refresh the MV.

Your response will be highly appreciated.

Thanks

          Followup:

well, that is a huge update don't you think, for an on commit refresh.

Bascally, the 50000 row insert is turned into 50,000 "mini refreshes".  It is
refreshing about 24 rows/second there (lots of code to go through).  So, your
"normal" user won't see this affect -- but a bulk modification most certainly
will (it is like adding an index, update a single row of an indexed column -- no
problem.  Update 50,000 of them -- and wait).

If you plan on bulk modifications like this, then the more appropriate refresh
mechansism is COMPLETE, not incremental.

You refresh an MV, not a partition within an MV.

      Rating:
      5
  August 28, 2002
Reviewer:
      A reader
Hi Tom,

I hope you can help me to understand why the performance is so bad when update
one of the tables that is used by my mv.
There a couple of reports that need to be run on our OLTP application because
they need to have the last information.
So I though that using a materialized view would be a good choice. So I decided
to make some tests on our test database in order to see the behaivor of this
alternative.
I created a mv over four big tables (2 of 2gigabytes and the others two over 200
megabytes), I created the log for the four tables and also I created four
indexes on the mv using the rowid column that I used for each of those tables.
The problem is that the performance for the updates or inserts on the tables on
which the mv is based on is very poor. It took me 2 minutes to commit a simple
"insert into ..values.."  of one row in one of these tables. During the commit I
checked the waits, and the only thing that I saw was db file scattered read
waits for that session. So I checked that session in the top session and I was
able to see that the insert on the mv was performing a full table scan over one
of the table which has a size of 2 gigabytes. When I created the mv I used hints
in order to achieve the best access path for the query and the explain plan for
that code doesn't show a full scan for that table, but the insert on the mv is
not doing a nested loop for that table when it is supposed to be doing so.
Do you know what this is happening?
Can you tell me what to do in order to avoid the full scan that insert on the mv
is doing?

This is the code of the mv.

create materialized view mv_test
build immediate
refresh fast on commit
as
select /*+ ORDERED use_hash(AD) USE_NL(SM A2) */
        ad.rowid ad_rid,a.rowid a_rid,sm.rowid sm_rid,a2.rowid a2_rid,
        SM.account_id, AD.phone, A2.stage_cancel
from table_1 A,   --200 MG
     table_2 AD,  --200 MG
     table_3 SM,  --2G
     table_4 A2   --2G
where AD.ACCOUNT_ID = A.ACCOUNT_ID
  and SM.ACCOUNT_ID = AD.ACCOUNT_ID
  and A2.ACCOUNT_ID = SM.ACCOUNT_ID

This is the explain plan for the query of the mv

Execution Plan
----------------------------------------------------------
0      SELECT STATEMENT Optimizer=CHOOSE
1    0   NESTED LOOPS
2    1     NESTED LOOPS
3    2       HASH JOIN
4    3         TABLE ACCESS (FULL) OF 'TABLE_1'
5    3         TABLE ACCESS (FULL) OF 'TABLE_2'
6    2       TABLE ACCESS (BY INDEX ROWID) OF 'TABLE_3'
7    6         INDEX (UNIQUE SCAN) OF 'U_ACCOUNTID' (UNIQUE)
8    1     TABLE ACCESS (BY INDEX ROWID) OF 'TABLE_4'
9    8       INDEX (RANGE SCAN) OF 'I_ACCOUNT_ID'(NON-UNIQUE)

HERE YOU have the insert statement that I run:

insert into TABLE_1
values
(2363886,64,'8010000012','JEREMY','LARSEN',null,null,null,null,88,88,null,null,6,
52,sysdate,
null,null,null,null,null,null,null,null,null,null,52,null) ;
1 row created.

Elapsed: 00:00:00.20
SQL>commit;

Commit complete.

Elapsed: 00:01:98.99

HERE you have the explain plan that the insert on the mv is doing

Execution Plan
----------------------------------------------------------
 0      SELECT STATEMENT Optimizer=CHOOSE
 1    0   HASH JOIN
 2    1     TABLE ACCESS (FULL) OF 'TABLE_2'
 3    1     HASH JOIN
 4    3       NESTED LOOPS
 5    4         VIEW
 6    5           NESTED LOOPS
 7    6             VIEW OF 'VW_NSO_1'
 8    7               SORT (UNIQUE)
 9    8                 FILTER
10    9                   TABLE ACCESS (FULL) OF 'MLOG$_TABLE_2'
11    6             TABLE ACCESS (BY USER ROWID) OF 'TABLE_1'
12    4         TABLE ACCESS (BY INDEX ROWID) OF 'TABLE_3'
13   12           INDEX (UNIQUE SCAN) OF 'U_ACCOUNTID' (UNIQUE)
14    3       TABLE ACCESS (FULL) OF 'TABLE_4'

Thanks in advance for any help.

          Followup:

so run a sql_trace and TKPROF on the whole thing and see what is really
happening.

      Rating:
      4
  January 06, 2004
Reviewer:
      A reader
hi tom,

I have a simple question here. I am using 8.1.5. Now there is a refresh group
which is used for refreshing the materialied view. This will refresh every 15
minutes over a DB link.

now if the network is down while a refresh takes place, will next refresh happen
without any problem if the network is restored before the next refresh and will
it refresh the data which was supposed to be refreshed in the previous (last and
lost) refresh.

thanks

          Followup:

if the network isn't available, the job will increment "failures" and try it
again in a while (using a "back off" algorithm timewise -it'll try soon, if
still failed, try again -- but not as soon and so on).  it'll do this 16 times.

If after 16 times, it is still "failing", the job will be marked as BROKEN and
you'll have to "unbreak" it to get it going again.

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

letme tell you the scenario and the solution proposed for this.

We have a database say A, where in an application would add/modify/delete data.
We have another read only database, say B, which has to be in sync with this
database with a maximum delay of 1 hour.

So what I have proposed is that have REFRESH FAST ON DEMAND materialized views
in B from A. Now refresh is done using DBMS_MVIEW.REFRESH which is in a shell
script and this will be scheduled using crontab.

My question is when this refresh is being done, if somethin happens to the DB
link used for refresh, crontab will fail. next time (say after 1 hour) when the
crontab is trying to refrehs again will it refresh the data which was left
unrefreshed last time ?

One more question I have is what will happen to dependent transactions. Say for
e.g. I have a table t1 and t2 such t2 references t1. When the refresh started
there were only 100 transactions in t1 and t2, after the refresh has been
completed for t1, 2 more transactions get added into these tables and hence when
the refresh starts for t2 we have 202 transactions, so wouldn't that refresh for
t2 create mismatch transactions as we don't have foreign key relations between
the materialized views.

One last question (sorry for asking multiple questions.) is there any problem in
creating tables that reference materialized views.

Thanks

          Followup:

why would you use cron when the database does the refresh on a timed basis --
with retries during failures and a single place to look for "database stuff"
(eg: don't have to monitor alert logs and crontab logs -- just alerts).

don't use cron, use the start with / interval on the MV.

the refresh operation is designed to do the right thing -- if you group t1 and
t2 into a single refresh group, they are refrehed transactionally consistent.

if you use DEFERRABLE constraints when referencing a MV, it should be OK
(technically) as the MV refresh process defers all constraints at the start.
HOWEVER, you now have a table that can kill the refresh due to a fkey
constraint.  That is, you have T3 pointing to T2 and T2 is a MV.  On the master
site, someone deletes from T2 a primary key that T3 has.  That succeeds (t3
isn't on the master site).  Now T2 cannot refresh anymore.   So, while it would
work -- it would be an excessively "fragile" design.

January 07, 2004 Reji -- Thanks for the question regarding "data
      encryption package", version 8.1.6

      You Asked
Tom:
    Would you please provide some examples for how to use
    dbms_obfuscation_toolkit ?

     -Thx
     -Reji

      and we said...
Sure, the trick is to make sure everything is a multiple of 8...

Here is an example:

ops$tkyte@DEV816> variable x varchar2(25)
ops$tkyte@DEV816>
ops$tkyte@DEV816> exec :x := 'How Now Brown Cow';

PL/SQL procedure successfully completed.

ops$tkyte@DEV816>
ops$tkyte@DEV816> declare
  2      l_data  varchar2(255);
  3  begin
  4      l_data := rpad( :x, (trunc(length(:x)/8)+1)*8, chr(0)
);
  5
  6      dbms_obfuscation_toolkit.DESEncrypt
  7          ( input_string => l_data,
  8        key_string   => 'MagicKey',
  9        encrypted_string=> :x );
 10  end;
 11  /

PL/SQL procedure successfully completed.

ops$tkyte@DEV816>
ops$tkyte@DEV816> print x

X
-------------------------
Òr=ðÝw]ð‘SWñ:ÝÐôÊ?T¬

ops$tkyte@DEV816>
ops$tkyte@DEV816> declare
  2      l_data  varchar2(255);
  3  begin
  4      dbms_obfuscation_toolkit.DESDecrypt
  5          ( input_string => :x,
  6        key_string   => 'MagicKey',
  7        decrypted_string=> l_data );
  8
  9      :x := rtrim( l_data, chr(0) );
 10  end;
 11  /

PL/SQL procedure successfully completed.

ops$tkyte@DEV816>
ops$tkyte@DEV816> print x

X
-------------------------
How Now Brown Cow

You must protect and preserve your "magickey" -- 8 bytes of data
that is used to crypt/decrypt the data...
Review &
      Followup

      Rating:
      2
dbms_obfuscation_toolkit   May 21, 2001

Reviewer:  L. Lugo  from New York
Even though, this might have some use. I would have
prefer to have seen this example in a procedure since
most Encryption is done from external apps.

      Rating:
      3
Encryption  August 07, 2001
Reviewer:
      Senthil  from Qatar
Thanks for the example. Good one

      Rating: 5
Great.. how to
      protect the key?!  November 16, 2001
Reviewer:
      Doug  from CT, USA
Hey Tom - good info.  As you said, the catch is to protect the key.  If I want a
very quick simple solution to encrypting passwords stored in the database I can
use something similar to this, but how can I protect this key?  To keep it from
showing up in tkprof, I can use a bind variable, but if I use a bind variable in
a procedure then it will be defined in the code and show up in the all_source
view if a user has execute on it, no?

          Followup:

Well, I would never use encryption for passwords -- I would use a digest or
hash.  In 817 we have a md5 routine in dbms_obfuscation_toolkit for secure
hashes, before that see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:95412348059
How to protect the key -- the person who can answer that without a single chink
in the armor wins a prize.

You can stuff it in an OS file on the server, you could have the client send it
over, you could make the key a function of the username and other user
attributes (security via obscurity).... There are many techniques you can take
(i outline a couple in my book in the dbms_obfuscation_toolkit) each has their
drawbacks though.

      Rating: 5
Protect the
      Key  November 16, 2001
Reviewer:
      A reader
Have a database function that will return the "Magic Key" and wrap the function.
Will it work ?...

          Followup:

Yes, but you'll want to break the key up even so.  something like:

is
  l_magic_key varchar2(8);
  l_dummy varchar2(8);
begin
  l_magic_key := chr(123);
  l_dummy := 'aafdafda';
  l_magic_key := l_magic_key || chr(23);
  l_dummy := 'erq fa';
  l_magic_key := l_magic_key || chr(122);
  ...
  return l_magic_key;
end;

to further obscure it (else the variable value would just be sitting there in
the wrapped text for easy reading)

And even then, you have to protect the function -- all i need to do is call the
function (if I can see the source, I can probably execute the function!)

      Rating: 5
Closing in on a
      solution  November 16, 2001
Reviewer:  Doug
       from CT, USA
Tom, can you elaborate on why you would *never* use encryption for passwords?
Why are the other two options, hash and digest so much more preferable?  The
situation I have is that there are custom app login passwords in a table.  Not
good.

          Followup:

Because I would never put myself into a situation where data could be
comprimised and it didn't need to be.

It is so much less effort for me to store a hash/digest (effectively the same
thing).   It is a one way function that takes a big string (the bigger the
better) and maps it into a finitite set of numbers.  Hence it is not one to one
-- and it cannot be undone. You cannot go from the digest to the original
string.  You use this when someone gives you information and you want to
authenticate its validity.  If you hash/digest the data they give you and you
get the same hash you were expecting -- the odds are one in many billions that
they "guessed" another lucky string that happened to match.  It is the way
digital signatures are generated as well.  I can take my document (say an email)
and "sign it" by hashing it.  I'll incorporate my certificate (x509) with the
message -- hash it and put the number i get into the message.  When you recieve
it -- you can take the original message + my certificate and hash it.  If you do
not get the same number -- someone either changed the message or forged my
signature.

With hashes, I do not have to worry about someone discovering my key, getting
into my database, dumping my table of passwords and decoding them all.  With a
hash, they would have to guess at each users password -- run it through the
hashing routine and compare the hashes.  Very time consuming -- and if I through
a bit of "salt" in there and combine it in some fashion with the username --
they might never figure it out unless they have my algorithm as well.  For
exmaple, I might hash:

   'Hello' ||
    substr(username,1,4) || substr(password,1,4) || substr(username,5) ||
    substr( password,5 ) || 'World'

unless they knew that as well -- they could never guess the password.  Its just
more secure without me having to protect a key.  Remember encrypted data is ONLY
as secure as the key.  Since this has no key -- its pretty secure.

      Rating: 5
About
      key  November 17, 2001
Reviewer:
      Balasubramanian Rengasamy  from Singapore
Hi Tom,

Instead of protecting and preserving the key, can i use the password itself as
key like :

    if length of password > 8
      key := substr(password, 1, 8) ;
    else
      key := rpad(password, 8, chr(0) ) ;

Is there any problem will happen using this .

Thanks
Bala

          Followup:

Yes you can -- but then it is working just like a hash (but it is isomorphic,
one to one -- you can get the password back if they give you the password).

Since you never really need the password -- and you cannot get the password back
for a user who "lost theirs" (as that is the key) i see NO benefit in encrypting
the password (you only encrypt when you need to decrypt, here you NEVER need to
decrypt, just to verify the password is correct).

So, yes you can but to what end?

      Rating: 4
Help to both of
      us  November 17, 2001
Reviewer:  AanyGary
       from India
This is very help to me and Anu

      Rating: 5
what am i doing
      wrong???  May 08, 2002
Reviewer:  Senthil
       from Boston, MA
Tom-
I am using 8.1.7 for Solaris. I am trying to run the same sql given in the
example. Its not decrypting properly. Am i missing something???

SQL> var a varchar2(255)
SQL> var b varchar2(255)
SQL> var c varchar2(255)
SQL> exec :a := 'How Now Brown Cow';

PL/SQL procedure successfully completed.

SQL> print a

A
--------------------------------------------------------------------------------
How Now Brown Cow

SQL> exec dbms_obfuscation_toolkit.DESEncrypt(input_string=> rpad(:a,
(trunc(length(:a)/8)+1)*8, chr(0)), key_string => 'MagicKey', encrypted_string
=> :b);

PL/SQL procedure successfully completed.

SQL> print b

B
--------------------------------------------------------------------------------
Or?=?Yw]??SW?:Y?oE?T?

SQL> exec dbms_obfuscation_toolkit.DESDecrypt(input_string=> :b, key_string =>
'MagicKey', decrypted_string => :c);

PL/SQL procedure successfully completed.

SQL> print c

C
--------------------------------------------------------------------------------
ji5N?u++1aea?a?A$T???io

          Followup:

My example used character strings -- your NLS_LANG in the client (sqlplus) must
be different then the NLS_LANG in the server.

Hence, the "string" in B is being "character set translated" -- bits are getting
stripped off.

Consider:

$ csh -vx test.csh

echo $NLS_LANG
echo AMERICAN_AMERICA.WE8ISO8859P1
AMERICAN_AMERICA.WE8ISO8859P1
sqlplus / @test.sql
sqlplus / @test.sql

SQL*Plus: Release 8.1.7.0.0 - Production on Wed May 8 12:07:05 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> variable x varchar2(25)
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec :x := 'How Now Brown Cow';

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_data  varchar2(255);
  3  begin
  4      l_data := rpad( :x, (trunc(length(:x)/8)+1)*8, chr(0) );
  5      dbms_obfuscation_toolkit.DESEncrypt( input_string=>l_data,
key_string=>'MagicKey', encrypted_string=>:x );
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> print x

X
--------------------------------
Òr?=ðÝw]ð?SWñ:ÝÐôÊ?T¬

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_data  varchar2(255);
  3  begin
  4      dbms_obfuscation_toolkit.DESDecrypt( input_string=>:x,
key_string=>'MagicKey', decrypted_string=>l_data );
  5      :x := rtrim( l_data, chr(0) );
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> print x

X
--------------------------------
How Now Brown Cow

ops$tkyte@ORA817DEV.US.ORACLE.COM> exit
Disconnected from Oracle8i Enterprise Edition Release 8.1.7.3.0 - Production
With the Partitioning option
JServer Release 8.1.7.3.0 - Production

unsetenv NLS_LANG
unsetenv NLS_LANG
sqlplus / @test.sql
sqlplus / @test.sql

SQL*Plus: Release 8.1.7.0.0 - Production on Wed May 8 12:07:05 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>
ops$tkyte@ORA817DEV.US.ORACLE.COM> variable x varchar2(25)
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec :x := 'How Now Brown Cow';

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_data  varchar2(255);
  3  begin
  4      l_data := rpad( :x, (trunc(length(:x)/8)+1)*8, chr(0) );
  5      dbms_obfuscation_toolkit.DESEncrypt( input_string=>l_data,
key_string=>'MagicKey', encrypted_string=>:x );
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> print x

X
--------------------------------
Or?=?Yw]??SW?:Y?oE?T?

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_data  varchar2(255);
  3  begin
  4      dbms_obfuscation_toolkit.DESDecrypt( input_string=>:x,
key_string=>'MagicKey', decrypted_string=>l_data );
  5      :x := rtrim( l_data, chr(0) );
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> print x

X
--------------------------------
ji5N?u++1aea?a?A$T???io

ops$tkyte@ORA817DEV.US.ORACLE.COM> exit
Disconnected from Oracle8i Enterprise Edition Release 8.1.7.3.0 - Production
With the Partitioning option
JServer Release 8.1.7.3.0 - Production

in this case, my db character set is WE8ISO and when the NLS_LANG matches --
no worries.  When it doesn't, (when I let it default to US7ASCII as it does on
Unix), the next run through does exactly what yours does!  We've gone through
character set conversion.

I recommend use of RAW types for all encrypted data (using utl_raw.cast_to_raw
to magically turn a varchar2 into a raw without touching it).  No character set
conversions with RAW types.

      Rating: 3
data encryption
      package  October 15, 2002
Reviewer:  Jamil
      Shaibani  from Saudi
Hi Tom
I have used dbms_obfuscation_toolkit , to Decrypt this data
29 6B60638A8C932A11 by using this example:
variable x varchar2(55);
exec :x := '29 6B60638A8C932A11';
declare
        l_data  varchar2(255);
    begin
        dbms_obfuscation_toolkit.DESDecrypt
            ( input_string => :x,
          key_string   => 'MagicKey',
          decrypted_string=> l_data );
        :x := rtrim( l_data, chr(0) );
   end;
but I am getting this errors message
ERROR at line 1:
ORA-28232: invalid input length for obfuscation toolkit
ORA-06512: at "SYS.DBMS_OBFUSCATION_TOOLKIT_FFI", line 0
ORA-06512: at "SYS.DBMS_OBFUSCATION_TOOLKIT", line 153
ORA-06512: at line 4

Thanks
Jamil

          Followup:

:x has a blank in it.  that is not proper.

Also, that string doesn't look "encrypted" to me.  It would have squiggles and
funky characters.

That looks like a hex string to me, thats NOT encrypted.  I've no idea what it
is but I know what it isn't -- it isn't an encrypted string.

      Rating: 5
About your data
      encryption package  March 11, 2003
Reviewer:
      Jeff Li  from PA USA
Hi Tom,

Thanks for this great site. I got a lot of help from it.

I've created the encryption and decryption functions based on your method. I
used Oracle 8.1.7.0 server. The functions worked very well on this server. But
after I installed the patch 8.1.7.4 recently, all my *old* encrypted data
(stored in the db before the patch) couldn't be decrypted any more. I didn't
change anything for the functions. And they still worked well for the *new*
data.

It will be a huge trouble for our company if we can't get the old data back.
Please give me a help.

Thanks,

Jeff

          Followup:

they "fixed" the key byte order problem.

You have an 8 byte key right now -- say the key was:

12345678

the key to unlock your data is:

43218765

swap the bytes -- in your decrypt just use the key:

  substr(key,4,1)||substr(key,3,1)||substr(key,2,1)||substr(key,1,1)||
  substr(key,8,1)||substr(key,7,1)||substr(key,6,1)||substr(key,5,1)

let me know if that works -- it only affects certain hardware architectures.
Used to be that before this was fixed -- data encrypted on windoze couldn't be
decrypted on sparc and vice versa due to key addressing (they address it as 2 4
byte integers internally and high endian/low endian messed it up)

      Rating: 5
about data
      encryption package  March 11, 2003
Reviewer:
      Jeff Li  from PA USA
Sorry, forgot to tell you that we use Oracle server on Windows 2000.

Thanks,

Jeff

      Rating: 5
data encryption
      package   March 11, 2003
Reviewer:  Jeff Li
       from PA USA
Tom:

Thank you very much for the help. I got my data back and everything works very
well now.

Great appreciate!!!

Jeff

      Rating:
      5
Excellent  March 11, 2003
Reviewer:
      Mike  from England
Hi Tom,

Hopefully my problem is all kind of related to this.  As part of our software we
want to supply a very powerful toolkit which has extra functionality in it for
specific problems that we may encounter on customer sites.  We have tried before
this sort of thing, but the problem is that once something is "given out" it is
not possible to "get it back".  we don't want people being able to have access
to this all the time, only when we give them specific cause to use it.

As such we were thinking of supplying a totally wrapped package (Header and
body).  as part of this the package would contain some initialsation code which
would

* read a user supplied *password* from dbms_application_info
* use a concationation of the system date truncated to a whole day in various
formats to make it a multiple of 8 as the key
* use some hard-coded concatenated text as the text to encrypt
* check what they supplied is what we worked out, if it's not raise an error.

we would give a routine to our support team such that they could generate the
keys for users on an as needed day by day basis (or even create them for future
/ previous days).  after encyting the string we would then send it out as a list
of numbers each number being the ascii number for that charecter e.g
192-15-133....

My questions I suppose are 3 fold.

1) Is this just way over the top, I started off by quite liking the use of
sysdate as a self changing part of the encryption.  but After reading the above
I start to get the feeling I'm writing it because I think it would be "cool"...
perhaps just a hash function would suffice?

2) We can make no assumptions about how the database will be set up.  It's
NLS_LANG settings could be anything.  Only the fact it will be atleast Oracle
8.1.7.  Would we be able to work around this by being explicit in our date
formats

3) further to two we can't even say what charecter set the database will be in,
it quite possible it will be some Asian 2 bytes per charecter charecter set.  I
assume we would need someway of converting the text into a "known" charecter set
before we cast it to a raw?  Is this possible?

Thanks as always,

Mike.

          Followup:

not sure -- i don't get the entire algorithm here.

why not this....

You have your "salt" -- the date YYYYMMDD plus some magical string
You have your "key" you give them, say 'Hello World'
You have your "hash" you give them.

So, you provide them 2 things -- key and hash.
They in turn must supply this key and hash to your package.

Your package uses dbms_obfuscation_toolkit.md5 to generate a md5 checksum of the
KEY+SALT.  compares that to the hash -- if equal - that code will run that day.
If not, that code will not run that day.

Tomorrow, you need to give them a new key + hash.

      Rating: 4
It's easier if
      I just post the few lines of code...  March 17, 2003

Reviewer:  A reader
Hi Tom,

This is how I would generate the key...  the input string would be a lot longer
than just the DD/MM/YY, but as that is 8 charecters, it was handy for testing
purposes...

PC_KEY is a private constant that is build up through layers of code as you have
previously said so you cannot "see it" in the wrapped code...

they would then type in the return, and the wrapped code would just generate the
key and compare it to what they typed in...

  function gen_key return varchar2 is
    l_encrypt varchar2(100);
    l_return varchar2(100);
  begin
    l_encrypt := dbms_obfuscation_toolkit.desencrypt
    ( input_string => to_char(sysdate,'DD/MM/YY'),
      key_string   => PC_KEY
    );
    for i in 1..length(l_encrypt) loop
      l_return := l_return||ascii(substr(l_encrypt,i,1))||'-';
    end loop;
    l_return := substr(l_return,1,length(l_return) -1);
    return l_return;
  end gen_key;

          Followup:

my opinion -- hashing is easier

you give them key+hash, done.

      Rating: 5
why is this
      ?  April 22, 2003
Reviewer:  A reader

SQL> exec :x := 'How Now Brown Cow';

PL/SQL procedure successfully completed.

SQL>  declare
  2        l_data  varchar2(255);
  3    begin
  4        l_data := rpad( :x, (trunc(length(:x)/8)+1)*8, chr(0) );
  5        dbms_obfuscation_toolkit.DESEncrypt
  6            ( input_string => l_data,
  7          key_string   => 'MagicKey',
  8          encrypted_string=> :x );
  9    end;
 10  /

PL/SQL procedure successfully completed.

SQL> print :x

X
------------------------------------------
Rr=p]w]pSWq:]PtJ?T,

SQL>  declare
  2        l_data  varchar2(255);
  3    begin
  4        dbms_obfuscation_toolkit.DESDecrypt
  5            ( input_string => :x,
  6          key_string   => 'MagicKey',
  7           decrypted_string=> l_data );
  8         :x := rtrim( l_data, chr(0) );
  9     end;
 10  /

PL/SQL procedure successfully completed.

SQL> print :x

X
---------------------------------------------
~ QiPei8L.mZ2Zhz0E

SQL>

** Why is this ?

          Followup:

look up in the page here.... been there, done that.

      Rating:
      3
DBMS_OBFUSCATION_TOOLKIT  July 31, 2003

Reviewer:  Sachin  from Plainsboro, NJ
I have a table with user id , encrypted password and the key string. I have
successfully executed the decryption in SQL*Plus.I need to do the same in Visual
Basic. How can I return a record set so that VB can get it as a varriable.
Please help

Thank you

Sachin

          Followup:

http://asktom.oracle.com/~tkyte/ResultSets/index.html

using a ref cursor.

      Rating:
      4
8  July 31, 2003
Reviewer:
      A reader
Tom,
Not able to ubderstand why this.
l_data := rpad( :x, (trunc(length(:x)/8)+1)*8, chr(0) );
If I am not using the avobe I am getting error
ERROR at line 1:
ORA-28232: invalid input length for obfuscation toolkit
ORA-06512: at "SYS.DBMS_OBFUSCATION_TOOLKIT_FFI", line 0
ORA-06512: at "SYS.DBMS_OBFUSCATION_TOOLKIT", line 33
ORA-06512: at line 6

Could please clarify?
Thank you very much.

          Followup:

because the answer started with:

...
Sure, the trick is to make sure everything is a multiple of 8...
.....

in order to encrypt, you need a block of data that is a multiple of 8 bytes.

      Rating: 5
What does the
      kernel use to generate user passwords?  November 26, 2003

Reviewer:  Justin  from PA
What does the Oracle kernel (? is that was generates them)
use to generate user passwords in the database? How does it created the value in
the password column of _USERS views?

Has anyone figured out how to crack oracle passwords that you know of?

For example:

justin@DEV> create user loser identified by dorko;

User created.

justin@DEV> select password from dba_users where username ='LOSER';

PASSWORD
==============================
EF7D03F009BD9BDB

justin@DEV> create user homer identified by dorko;

User created.

justin@DEV> select password from dba_users where username ='HOMER';

PASSWORD
==============================
7C927170489C87F3

justin@DEV> drop user loser;

User dropped.

justin@DEV> create user loser identified by dorko;

User created.

justin@DEV> select password from dba_users where username ='LOSER';

PASSWORD
==============================
EF7D03F009BD9BDB

and furthermore:

justin@DEV> create user lose identified by rdorko;

User created.

justin@DEV> select password from dba_users where username ='LOSE';

PASSWORD
==============================
EF7D03F009BD9BDB

So the password appears to be based on a combination of the username and the
password without a time element.

It seems that this password mechanism would be easily cracked?

Yet, I must be wrong...

Can you shed some light on my findings and add some discussion/thought to this
topic?

Thanks much!

          Followup:

it is a hashed version of your username and password.

go ahead, crack it.  It is just a one way hash, it is not reversible.  You have
to brute force the system and guess passwords, you cannot "decrypt" them.

      Rating: 3
How come you
      can  December 23, 2003
Reviewer:
      A reader
alter user loser identified by values then?

How does it take the hash and recreate the password?

          Followup:

it does not recreate the password.

we do NOT store the password.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:95412348059
we just store the hash (the thing in the "values" clause)

      Rating: 4
Funny
      Output  January 07, 2004
Reviewer:  Shaji
      Nair  from IN
Hi Tom,
 I have an encryption function which encrypted a string and stored in a table.
The encrypted string is

SQL> select search_condition from ice_lookups
  2  where rownum = 1
  3  /

SEARCH_CONDITION
---------------------------------------------------------------------------------
----------------------------------
=F9:D=<W>D9?@24¿5@24¿@31¿Q@31¿@24¿9F<@24¿LJMF;$32¿FND$32¿=F<W<9L=W9;LAN=$KQK<9L=!
!@24¿56@24¿LJMF;$32¿KQK<9L=!

Now when i decrypt this i get like this

  1  select ice_encrypt_decrypt.decrypt(search_condition) from ice_lookups
  2*  where rownum = 1
SQL> /

ICE_ENCRYPT_DECRYPT.DECRYPT(SEARCH_CONDITION)
---------------------------------------------------------------------------------
------------------------
ENABLED_FLAGH:<=H:<H;9YH;9H:<ANDH:<TRUNC,;:NVL,;:END_DATE_ACTIVE,SYSDATE))H:<=>H:
<TRUNC,;:SYSDATE)

Amazingly when i decrypt the string i get the following correct output

SQL> select
ice_encrypt_decrypt.decrypt('=F9:D=<W>D9?@24¿5@24¿@31¿Q@31¿@24¿9F<@24¿LJMF;$32¿FN
D$32¿=F<W<9L=W9;LAN=$KQK<9L=!!@24¿56@24¿LJMF;$32¿KQK<9L=!') from dual;

ICE_ENCRYPT_DECRYPT.DECRYPT('=F9:D=<W>D9?@24¿5@24¿@31¿Q@31¿@24¿9F<@24¿LJMF;$32¿FN
D$32¿=F<W<9L=W9;LAN=$KQK<9L=!!@24¿56@24¿LJMF;$32¿KQK<9L=!')
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------
--
ENABLED_FLAG = 'Y' AND TRUNC(NVL(END_DATE_ACTIVE,SYSDATE)) => TRUNC(SYSDATE)

This has created a headache, What's the reason for behaving in this improper
manner.

          Followup:

it doesn't look improper.  Looks like you are using a STRING to encrypt into
(which I've learned is a truly, utterly BAD idea) and your character set on your
client differs from the db's!

when you encrypt a string, you get "binary data".  binary data stored in a
string.  it is subject to character set translations.

Look upwards in this page for "what am i doing wrong" and see that
review/followup.  it describes this in detail

I suggest you

a) change the encrypted column to be RAW -- or if you want to leave it as
varchar2 -- double its width (we'll be storing HEX in there instead)

b) use utl_raw.cast_to_raw to convert the varchar2 string to be encrypted into a
RAW.

c) then encrypt the RAW, not a varchar2 -- this results in a raw

d) either store the raw in a raw or in a varchar2 that is 2x the size of the raw
(it'll be rawtohex'ed for you)

e) upon retrieval -- you would fetch a raw or a varchar2 hex string.  you would
use the raw decrypt (raw will just go, the varchar2 hex would be hextoraw'ed for
you)

f) use utl_raw.cast_to_varchar2 in order to convert the decrypted data back into
a safe varchar2 string.

      Rating: 4
Thank You
      Tom  January 08, 2004
Reviewer:  Shaji Nair
       from IN
Thanks for your inputs Tom. I will test this and will let you know.

January 07, 2004 Vijay -- Thanks for the question regarding "To
      keep tables in cache", version 8i

      You Asked
Hi Tom,

As per your suggestion, I have added a seperate buffer pool for
the table.Then I altered the table using ALTER TABLE TESTTAB
STORAGE( BUFFER_POOL KEEP) and I cached the table in the memory.

When I query v$db_object_cache table, i find that this table is
not kept in the memory. The column value for KEEP is not YES (
even after a FULL TABLE SCAN on the table).

When I queried v$db_object_cache, I find a few tables cached and
their KEEP value is set to "YES". These are IND$,FILE$.... How
can i move user tables to memory cache ?

I also read that, it is not possible to KEEP tables using
DBMS_SHARED_POOL package.

Rgds
Vijay

Note: I enjoy reading ur column, it is more informative :-)

      and we said...
the KEPT column in the v$db_object_cache dynamic performance
view relates to objects that have been kept via
dbms_shared_pool.keep (see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:263215360334,%7BDBMS%5C_SHARED%5C_POOL%7D
for an example of that).  It does not relate to keeping buffers
in the buffer pool.

This is a dictionary cache, not data cache.

To inspect the buffer pool utilization, use v$buffer_pool and
v$buffer_pool_statistics.  To get the statistics view, you need
to run catperf.sql script when connected as SYS or internal
using svrmgrl.

Review &
      Followup

      Rating: 3
the column
      BUFFER_POOL of user_tables shows that  December 12, 2001

Reviewer:  Sean Bu  from Atlanta

      Rating: 4
How can you
      "UNKEEP"  May 31, 2002
Reviewer:  AB
       from UK
Tom

To test the performance of caching data in the keep pool against using PL/SQL
tables against not caching anything, I need to clear the keep pool so I can
start afresh. Is there a way to do this without shutdown/startup? Does flush
shared pool do this?

Thanks

AB

          Followup:

take the underlying tablespace offline, the blocks associated with it will be
flushed out of the buffer cache.

      Rating: 3
Buffer Pool
        January 07, 2004
Reviewer:  Bobby
       from India
While responding to the ealier question u said :
the KEPT column in the v$db_object_cache dynamic erformance view relates to
objects that have been kept via dbms_shared_pool.keep.
This is a dictionary cache, not data cache. Can u explain it ?

          Followup:

explain what?

kept means "not subject to flush"

dictionary cache is a segment of the SGA we keep dictionary information in

if you "keep" an object, it is stuck in that dictionary cache and won't be
flushed.

it is a bad idea in 2003.  It might have been a good idea in 1995.

January 07, 2004 Mark -- Thanks for the question regarding
      "Difference between NOT NULL constraint and check constraint", version
      8.1.7.4

      You Asked
Tom,

One of my co-workers was working on a script that would extract DDL.

He asked me, from a data dictionary point of view, is there any way
to determine the difference between a NOT NULL constraint, created like:
create table (a number NOT NULL);
and a check constraint who's conditions 'IS NOT NULL'?

We have not been able to deteremine the difference be looking at
data dictionary views.

Can you help?

-Mark

      and we said...
seems to me that you just want to either

a) use exp
b) get to 9i where dbms_metadata turns this week long or more exercise into a
simple query

exp userid=/ tables=t rows=n
imp userid=/ full=y indexfile=t.sql

t.sql has what you need.

You would have to parse the actual check constraint condition.

But really -- you don't want to write any code, the tools already do this!
Review &
      Followup

      Rating: 3
** How to find
      from user_constraints **  January 06, 2004

Reviewer:  parag patankar  from India
Tom,

your answer is very much practical. But is it possible to find the difference by
referring user_constraints ?

For e.g. I have created following table
17:00:20 atlas@ATP1P1> create table parag
17:03:17   2  ( a number not null,
17:03:24   3    b number(2) check ( b > 10 ));

Table created.

select OWNER, CONSTRAINT_NAME, CONSTRAINT_TYPE, TABLE_NAME, DEFERRABLE, DEFERRED
from user_constraints where table_name = 'PARAG'

It is giving me following output

CONSTRAINT_NAME                C TABLE_NAME                     DEFERRABLE
DEFERRED
------------------------------ - ------------------------------ --------------
---------
SYS_C00255287                  C PARAG                          NOT DEFERRABLE
IMMEDIATE
SYS_C00255288                  C PARAG                          NOT DEFERRABLE
IMMEDIATE

I am sure it is possible but I do not know how !!! Kindly help me.

regards

          Followup:

try select * from user_constraints, the constraint text is right in there.

You'll see you are looking for constraint text of

"A" IS NOT NULL

for a NOT NULL constraint in the search condition.  You would have to join
user_constraints to user_cons_columns (to find out this is a column level check
vs a table level check), select out the search condition, parse it to see if it
is "column-name" IS NOT NULL.   anything else is a plain old check constraint.

(but really, why waste -- utterly waste -- your time??  there are so many other
things you could do...)

      Rating: 3
* Just One
      thing to add **  January 06, 2004
Reviewer:
      Parag Patankar  from India
Tom,

Sorry I had not mentioned one thing in my question. I do not want to give
constraint names to not null and check. Then how to find not null and check
constraints from user_constranits ?

regards & thanks

          Followup:

use exp -- use exp -- use exp -- use exp

you are already done with your task, won't your manager be impressed??

exp userid=/ tables=parag rows=n
imp userid=/ full=y indexfile=parag.sql

done.

      Rating: 3
** Not null
      & Check constraints **  January 07, 2004

Reviewer:  Parag Patankar  from India
Hi Tom,

I fully agree with you that it is utter wast of time and energy. But I have
faced similar kind of question in Oracle9i DBA OCP exam for fundamentals-1 and I
was not in position to arrive the correct answer. I have asked this question for
my learning purpose only. ( I do not recollect exact question but it is sure, it
is on the same line and in SQL it does not mentioned search_condition, I do not
know if search_condition not mentioned you can arrive the answer !!! ).

Will you pl send some time to explain your answer/explaniation  with example on
such stupid question ?

( I have also not understood  --> table level check for joining
user_cons_columns. How to defined not null or check for table level ? )

regards & thanks

          Followup:

no, you said "my coworker is writing these scripts"

tell them to stop, they are done.  it is finished.  no need.

you would have to write procedural code to inspect the data dictionary
information to see what to say .   I gave you the algorithm above.

look at the search condition
if ( search condition = "column name" IS NOT NULL )
then
   tis a not null constraint
else
   tis a check constraint
end if

I don't think the dba ocp stuff askes you to write ddl extraction?  it does ask
that you understand the data dictionary (reasonable), but not to write code to
extract ddl....

      Rating: 5
This may
      help.  January 07, 2004
Reviewer:  Michael
      Dinh  from San Diego, CA
You wrote:
One of my co-workers was working on a script that would extract DDL.

Why would you want to waste time doing that? Oracle does this for you through
OEM.

Anyhow, this is what I did. And Tom please fee free to contribute if I made any
errors.

MDINH@dev> CREATE TABLE T_NN(ID NUMBER NOT NULL);

Table created.

MDINH@dev> CREATE TABLE T_CK(ID NUMBER);

Table created.

MDINH@dev> ALTER TABLE T_CK
  2      ADD (CONSTRAINT CHECK(ID IS NOT NULL)) ;
    ADD (CONSTRAINT CHECK(ID IS NOT NULL))
                                         *
ERROR at line 2:
ORA-02438: Column check constraint cannot reference other columns

NOTICE: In order to add a check constraint, I had to provide a name for the
constraint. The constraint cannot be named by the system. Am I correct on this
Tom?

MDINH@dev> ALTER TABLE T_CK
  2      ADD (CONSTRAINT T_CK_NN CHECK(ID IS NOT NULL)) ;

Table altered.

MDINH@dev> SELECT TABLE_NAME, COLUMN_NAME, NULLABLE
  2  FROM USER_TAB_COLS WHERE TABLE_NAME LIKE 'T_%';

TABLE_NAME                     COLUMN_NAME                    N
------------------------------ ------------------------------ -
T_CK                           ID                             Y
T_NN                           ID                             N

The value for NULLABLE for T_CK is Y.

MDINH@dev> SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE, TABLE_NAME, SEARCH_CONDITION

  2  FROM USER_CONSTRAINTS WHERE TABLE_NAME LIKE 'T_%'
  3  ;

CONSTRAINT_NAME                C TABLE_NAME
------------------------------ - ------------------------------
SEARCH_CONDITION
--------------------------------------------------------------------------------
T_CK_NN                        C T_CK
ID IS NOT NULL

SYS_C003175                    C T_NN
"ID" IS NOT NULL

However, there is a check constraint that prevents NULL data.  Notice the
distinction, Oracle places quotes around column name.

January 07, 2004 Tran Le -- Thanks for the question regarding
      "Auto startup database", version 8.1.7

      You Asked
Hi Tom,
I was install database oracle 8.1.7 for WinNT. The Database run well but after I
restart my computer i must type command :
ORADIM -startup -SID mySID pfile=ORACLE_HOME\oradata=inimySID.ora'
to run svrmgrl.
Can you show me proceduce auto startup database( run SVRMGRL don't need to type
"ORADIM -startup -SID mySID pfile=ORACLE_HOME\oradata=inimySID.ora") for each
turn on or restart computer.

Thanks

      and we said...
use oradim to set the autostart attribute

read

http://download-east.oracle.com/docs/cd/A81042_01/DOC/nt.816/a73008/ch5.htm#1013701
for details
Review & Followup

      Rating:
      2
  January 28, 2003
Reviewer:  Ajay
       from UK
Well the link points to the read the complete manual from oracle. Simple you
could suggest the chap my giving your suggestion which is not that lenghty.

          Followup:

Ajay -- get a grip or get a real browser.

when I click on it, it sort of takes me to:

...
Modifying an Instance

To modify an instance...

Use this syntax:

ORADIM -EDIT -SID SID [-NEWSID NEWSID] [-INTPWD INTERNAL_PWD]

[-STARTMODE AUTO | MANUAL][-PFILE FILENAME]
.........

which hey, is what you typed right below -- bravo ;)

      Rating:
      2
  January 28, 2003
Reviewer:  Ajay
       from UK
If you need to auto start the oracle database on server restart do specify
-STARTMODE AUTO .
And do check under service if the startup is set to automatic.

      Rating: 5
Auto startup on
      Linux RH9?  September 02, 2003
Reviewer:
      Sikandar Hayat  from Pindi Bhattian - Pakistan
I have installed Oracle 9i R2 on Linux RH9 but each time I have to startup
database manually. What is the best way to do autostartup the database?

          Followup:

add to the /etc/rc3.d/S99local script something like:

su - c oracle orastart

and create an orastart script in your $ORACLE_HOME/bin that starts the db.  I
like that better then "dbstart" myself.

      Rating: 5
Your
      script  September 08, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
TOM as you know that I am new to Oracle on Linux so please let me know that why
this technique is better than dbstart?

          Followup:

because you control it completely, i just like it better personally.  i never
use dbstart/dbshut.

starting and stopping a database typically involves MORE then just the database.
 I use an "orastart" script to do whatever I need to have done -- eg:

"start listener,
start db,
start processes that need to be running when db is up"

with dbstart you get

start db

and that is all....

      Rating: 5
Not
      working  September 12, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
add to the /etc/rc3.d/S99local script something like:

su - c oracle orastart

I have used the above techniques but its not working.

$su -c oracle orastart
su: user orastart does not exist

if I use -l then it asks for passord but as the current use is already oracle so
it should not ask for password. Also please paste your orastart script which
will guide me as well.

          Followup:

umm, write orastart

it is just a script, you code it.

      Rating: 5
Oh
      no  September 12, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
Sorry Tom I thought that I have to code like dbstart which has many conditions
so can't code like that.

          Followup:

nope, orastart can be as easy as:

lsnrctl start
export ORACLE_SID=sid_name

sqlplus "/ as sysdba" <<EOF
startup
EOF

<any other things you want to run>

it need not be generic at all -- just put the commands you would normally "type"
in there.

      Rating: 2
Re:su - c
      oracle orastart &#65311;maybe a mistake  January 07,
      2004
Reviewer:  Fenng  from http://www.dbanotes.net
      from China
#man su

then I got the following contents:
----------------------------------------------------
NAME
       su - run a shell with substitute user and group IDs

SYNOPSIS
       su [OPTION]... [-] [USER [ARG]...]

DESCRIPTION
       Change the effective user id and group id to that of USER.

       -, -l, --login
              make the shell a login shell

       -c, --commmand=COMMAND
              pass a single COMMAND to the shell with -c

       -f, --fast
              pass -f to the shell (for csh or tcsh)

       -m, --preserve-environment
              do not reset environment variables

       -p     same as -m

       -s, --shell=SHELL
              run SHELL if /etc/shells allows it

       --help display this help and exit

       --version
              output version information and exit

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

[root@Linux root]# su - c oracle Shutdown
su: user c does not exist
[root@Linux root]# su -c oracle Shutdown
su: user Shutdown does not exist
[root@Linux root]# su -c Shutdown oracle
bash: /root/.bashrc: Permission denied
bash: Shutdown: command not found
[root@Linux root]# su - oracle Shutdown

so ,it's working.....

          Followup:

$ su - ora9ir2 -c ls

sorry, had the -c in the wrong place.

January 07, 2004 Kumar -- Thanks for the question regarding
      "Database Free space", version 8.1.5

      You Asked
Any scripts to find the database size,
exact free space on the database
(unused initial/next extent should be included on free space).
Better if I get it Tablespace/Datafile specific.

      and we said...
Here is the script I use.  Bear in mind that getting "unused but
allocated space" (eg: unused initial/next extent) is very
expensive -- i'll tell you how to do it, but you'll have to
script it out a little more.

Here is the free.sql script:

--------------------------------------------------------
-- 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"
break   on report
compute sum of kbytes on report
compute sum of free on report
compute sum of used on report

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
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,
              tablespace_name
       from sys.dba_data_files
       group by tablespace_name )b
where a.tablespace_name (+) = b.tablespace_name
order by &1
/
-------------------------------------------------------

It generates a report like:

Tablespace Name     KBytes      Used      Free   Used   Largest
---------------- --------- --------- --------- ------ ---------
ATCDEMO_DATA         3,072     2,048     1,024   66.7       704
ATCDEMO_IDX            504         8       496    1.6       496
....

                 --------- --------- ---------
sum              5,099,912 4,381,104   718,808

What that shows me is:

Kbytes = space allocated to the tablespace currently.
Used   = space allocated within the tablespace to specific
         objects
Free   = space NOT yet allocated to any objects
Used   = % of space allocated to objects in tablespace
Largest= Largest free contigous extent available (NEXT_EXTENTS
         larger then this will FAIL)

Now, to find the amount of free space within the allocated
space, we have 2 choices:

o analyze tables frequently.  Then the "EMPTY_BLOCKS" column in
user_tables will be populated

o use the dbms_space package to find the free space available.

For both, see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:231414051079
for examples of usage and reporting...

Review &
      Followup

      Rating:
      4
Excellent  April 29, 2001
Reviewer:
      Ajeet Ojha  from Cincinnati,OH USA
Tom , I'm a Junior DBA and I was looking at this kind of program exactly.Thanks.

      Rating:
      3
  July 20, 2001
Reviewer:  Neeraj
      Nagpal  from LA, CA
It was a great help

      Rating: 5
Very much
      useful  August 07, 2001
Reviewer:  Nikhil
      Saxena  from Noida, India
I am naive DBA, it was very helpful to me

      Rating: 4
Thank you for
      your tip  April 04, 2002
Reviewer:  Vipul
      Patel  from Deerfield,(Chicagoland) IL
This is very useful tip for me, and specially your free.sql script again was a
very useful script to know the largest contigous extent available. I use your
site for getting my problems solved and most of the times it has worked.

Thank you Tom

      Rating: 5
free
      space  August 09, 2002
Reviewer:  munz
       from reston, USA
Tom:

When i run your free.sql I get thr follwoing:

SQL> @free.sql
order by uTESTp
         *
ERROR at line 19:
ORA-00904: invalid column name

2.  When i take the order statement out  Iget :

6 rows selected.

Statistics
----------------------------------------------------------
         14  recursive calls
          8  db block gets
        182  consistent gets
          0  physical reads
          0  redo size
        661  bytes sent via SQL*Net to client
        247  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
         25  sorts (memory)
          0  sorts (disk)
          6  rows processed

Neither is giving me the output you are showing?  Any ideas.

          Followup:

SQL> @free 1

free uses &1, you must have run another script whose first argument was uTESTp
(looks like my update cascade stuff ;)

Just pass in the column you want free to sort by.

      Rating: 5
FREE
      SPACE  August 14, 2002
Reviewer:  Munz
       from Reston, USA
Tom:

Does this tell you that this system in under configured.
Apparently the rollback space is full and there is no index tablespace and temp
is small.

Am I correct? what do you recommend doing? Does it tell you
that DBA is pretty junior?

Tablespace Name        KBytes         Used         Free   Used      Largest
---------------- ------------ ------------ ------------ ------ ------------
PICDATA             1,024,000      272,744      751,256   26.6      196,160
PICRBS                307,200      307,200            0  100.0            0
SYSTEM                204,800       73,328      131,472   35.8      131,368
TEMP                  204,800        1,488      203,312     .7      203,312
                 ------------ ------------ ------------
sum                 1,740,800      654,760    1,086,040

          Followup:

This tells me less then nothing.

RBS should always be full if you ask me.  Mine are all 100% full as they should
be.

Temp -- it is 0.7% used.

Why do you believe you need an index tablespace, I rarely use them (well,
actually I don't use them, I just use a tablespace/ application if the app is
small enough or a small/med/large -- other people use them).

We just had a discussion on this in the usenet newsgroups.  There are no
scientific reasons (performance or otherwise) to segregate index and data, none.
 It just makes people "feel better".

So, the above tells me you have a smallish system, thats all.  Underconfigured?
You cannot say that given the above at all.

      Rating:
      3
  August 14, 2002
Reviewer:
      A reader
Why do you say RBS should be 100% full all the time?

          Followup:

because I don't

a) ever want my rollback to have to extend (hence the tablespace it is in needs
no extra space)

b) ever want my rollback to shrink (hence, we'll never give back space)

I want my rollback to be sized big enough not to wrap during the execution of my
longest running query.  I do not believe in skimping on rollback -- hence I
preallocate my rollback segments to fit my system (not the other way around).

      Rating:
      5
Freespace  August 14, 2002
Reviewer:
      Munz  from Reston, USA
Tom:

1.  Where do you place all indexex created? with data?

2. Also, I thought if rollback is full, you will get an error when you do a
rollback since there is no space to store the old data?

Thansks

          Followup:

1) sure.  doesn't matter.

2) you are confused.  say you create a rollback tablespace rbs_tbs.  It is
25meg.  Now you create rollback segment rbs01 tablespace rbs_tbs storage
(initial 1m next 1m minextents 25);

Your rbs_tbs will be full, full of a 25m rollback segment just waiting for data
to be put into it.

You were looking at the wrong thing, it is not the emptiness of the rbs
tablespace that is interesting, it is the allocated size of the rollback
segments that is relevant.

      Rating: 5
free
      space  August 15, 2002
Reviewer:  Mo
       from Reston, USA
Tom:

Am I reading this right:

It says you can store 1 giga bytes in PICDATA and there are only 272,744 of
bytes stored.

Used tell you how much data size you have right?
Free is what you can store?

          Followup:

272,744 kbytes -- my query does everything in kbytes.

used = used, free = free (leftover, waiting to be used)

      Rating: 5
free
      space  August 15, 2002
Reviewer:  Mo
       from Reston, USA
Tom:

You define:

Used   = space allocated within the tablespace to specific
         objects
Free   = space NOT yet allocated to any objects

Does this mean that 272,744 kbytes of space is allocated which means that there
is that much data physically? or we can basically have 272 k bytes allocated but
we only have 100 K bytes of data maybe only?

For rollback space, you deined it differently.
You said 100% used means it is all allocated. It does not mean that there is
100% data in that space?

          Followup:

there might be 0 bytes of data in that tablespace with 272,744 kbytes of
storage.

There might be 272,744 kbytes of data in that tablespace on the other hand.

There could be any number in between.  We cannot tell from this query.

I did not define it any differently for RBS tablespace (we were talking about
tablespaces here).

That tablespace has 272,744 kbytes of storage allocated.  The RBS tablespace has
its N kbytes of storage allocated.  the RBS tablespace might have nothing it it,
might have something in it.  The fact is, if there are NO transactions active,
the RBS tablesapce is in fact empty -- but the RBS tablespace "appears" full.
This is because undo segments/rollback segments are used as a circular buffer --
unlike a table.  If you create a 25meg rollback segment in a 25meg tablespace --
the tablespace appears full (that is what I said) but the rollback segment isn't
"full".

sorry for the confusion.

      Rating:
      4
Great  August 16, 2002
Reviewer:
      Natalia  from Pakistan
I am a junior DBA and it is exactlly that I wanted to implement.
Thanks

      Rating: 5
free
      space  August 17, 2002
Reviewer:  Mo
       from Reston, usa
Tom:

OK thanks. This tells me how much space is allocated for the initial size of the
tabespace.

Is there a way to modify this to tell me how much data I have in each allocated
space?

Thank you,

          Followup:

each allocated space of what?

      Rating: 5
free
      space  August 17, 2002
Reviewer:  Mo
       from Reston, VA
TOm:

Tablespace Name        KBytes         Used         Free   Used      Largest
---------------- ------------ ------------ ------------ ------ ------------
PICDATA             1,024,000      272,744      751,256   26.6      196,160

OK the tablespace size is 1 giga bytes of which 272,744 is allocated space
(space that is ready to store data).
(e.g. analogy to a 10 x 6 ft room of which 30 square foot is allocated for
storage.  The remaining 30 square foot is not ready yet but can be used later.)

Can I know how much of this 272,744 k bytes is consumed by data (*.dbf files).
Bascially do i have all 272 k free or do i have 100k of data etc.?

          Followup:

you would have to look at each individual segment that is created in that
tablespace.

search this site for show_space to see a utility that lets you do that easily.

      Rating:
      4
auto-allocation?  August 19, 2002

Reviewer:  Dennis  from Missouri, USA
Tom,

It doesn't look like your script accounts for auto-allocation.  Yes, this is a
bad thing to have turned on, but when I got here it was the defacto standard.
However, we soon started getting into situations where we would run a whole
filesystem out of space, as we had multiple files trying to claim space that
just wasn't there.  In effect, we had "logically" allocated more than was
physically available.  Of course, had we not used auto-allocate, then this
wouldn't have been an issue.  The advent of our datamarts kind of reversed this
problem as it was such a huge project that we had to allocate all the space up
front, but we still needed to deal with the tens of  little projects that had
autoextend on every file and shared the filesystems with all other projects in
that particular database.

This is the script we came up with.  It also shows a fragmentation index because
we were having problems with internal file fragmentation (which LMT fixes 100%)
even though we tried using uniform extents (in DMT...which was easily overridden
of course).  I forget because it's been so long, but I think that as long as the
fragmentation index was over 15% all was good.   Of course, now that we're LMT,
this number is pretty much worthless.

SELECT
sysdate                                                     inst_date  ,
ddf.tablespace_name                                         tspace     ,
ROUND(fst.FRAG_INDEX,2)                                     fragindex  ,
allspc.KB                                                   totalaloc  ,
(ddf.fretot - fst.total_free ) /1024                        totalused  ,
fst.total_free /1024                                        totalfree  ,
ROUND((fst.total_free /ddf.fretot),2) * 100                 frepct     ,
fst.max_hole  /1024                                         maxdiv     ,
ROUND((fst.avg_hole  /1024),2)                              avgdiv     ,
fst.holes                                                   num_of_div
FROM dual,
(SELECT
tablespace_name                                 tablespace_name     ,
SUM(bytes)                                          fretot
FROM dba_data_files
WHERE tablespace_name != 'TEMP'
group by tablespace_name) ddf,
(
SELECT tablespace_name tablespace_name,
     SQRT(MAX(BLOCKS)/SUM(BLOCKS))*(100/SQRT(SQRT(COUNT(BLOCKS)) ))
FRAG_INDEX,
     SUM(bytes)             total_free,
     MAX(bytes)             max_hole,
     AVG(bytes)             avg_hole,
     COUNT(*)               holes
FROM dba_free_space
WHERE tablespace_name != 'TEMP'
GROUP BY tablespace_name
) fst,
(select table_space tablespace_name , sum(x) KB from
(select tablespace_name table_space,sum(BYTES/1024) x from
dba_data_files
where maxbytes=0 and
tablespace_name != 'TEMP'
group by tablespace_name
union all
select tablespace_name table_space,sum(MAXBYTES/1024) x from
dba_data_files
where tablespace_name != 'TEMP'
group by tablespace_name
)group by table_space) allspc
WHERE ddf.tablespace_name = fst.tablespace_name(+)
and ddf.tablespace_name = allspc.tablespace_name(+);

This is how the output of your script compares to the output of our script
(yours is formatted much nicer).  You'll notice that your script accounts for
the allocated, but not the "yet to be allocated".  I know it's hard to read due
to the limitations of the display (maybe it will show up better in the "big
window" after posting.  I'm comparing your KBytes with our TotalAloc <hm.
that's a typo - should be alloc...oh well>

                   %
Tablespace Name        KBytes         Used         Free   Used      Largest
---------------- ------------ ------------ ------------ ------ ------------
BRASSDATA01            10,240        3,672        6,568   35.9        6,120
BRASSDATA02            30,720       24,168        6,552   78.7        3,192
BRASSDATA03            10,240        2,664        7,576   26.0        7,576
BRASSDATA04            10,240        1,984        8,256   19.4        8,256

++++++++

INST_DATE TSPACE                          FRAGINDEX  TOTALALOC  TOTALUSED
TOTALFREE     FREPCT     MAXDIV     AVGDIV NUM_OF_DIV
--------- ------------------------------ ---------- ---------- ----------
---------- ---------- ---------- ---------- ----------
19-AUG-02 BRASSDATA01                          41.6     102400       3672
6568         64       6120     226.48         29
19-AUG-02 BRASSDATA02                         32.23     102400      24168
6552         21       3192     297.82         22
19-AUG-02 BRASSDATA03                           100     102400       2664
7576         74       7576       7576          1
19-AUG-02 BRASSDATA04                           100     102400       1984
8256         81       8256       8256          1

Also, once we have a sense of the "total space that logically could be
allocated" we run another script that makes sure we actually have the room in
unix directory.  This script is:

select dir, sum(x) GB from
(select substr(file_name,1,5) dir,sum(((BYTES/1024)/1024))/1024 x from
dba_data_files
where maxbytes=0
group by substr(file_name,1,5)
union all
select substr(file_name,1,5) dir,sum(((MAXBYTES/1024)/1024))/1024 x from
dba_data_files
group by substr(file_name,1,5)
)group by dir
/

Which gives us output in the form of:
DIR           GB
----- ----------
/u30/ 15.0634918
/u31/ 4.02734375

Then we can go run some df -k and df -u and see how much more we can actually
fit into the filesystems.

Is there a better way to do all this, or do we have a fairly good method?  I
realize that LMT caused all the fragmentation concerns to go away, but we
haven't had time to update the script (ok, so it wouldn't be that time
consuming, but it's a mental thing :) )

Thanks,
Dennis

      Rating:
      4
erg!!  August 19, 2002
Reviewer:
      Dennis  from Missouri, USA
I misled you.  The storage_by_directory script (the last one) is used to help us
find the logically allocated.  That's what we use to get our sense of what we've
tried to allocate.  I suppose you could get that from the first script, but who
wants to add up all those numbers manually.

Sorry for the poorly worded statement.

Dennis

      Rating:
      5
DBA  September 06, 2002
Reviewer:  Rajat
      Garg  from INDIA
Dear Tom
This was really usefull tip that solved my problem of knowing the free space of
tablespace. Looking for some more usefull tips

      Rating:
      5
tablespace  March 04, 2003
Reviewer:
      mo
Tom:

Why iam getting too different answers with the following two queries. First one
show 4 spaces while secong shows 7.  Problem is that rollback and temp space is
getting full but it sounds more of allocation problem. What do you think?

ICT> @free 1
old  19: order by &1
new  19: order by 1

                                                             %
Tablespace Name        KBytes         Used         Free   Used      Largest
---------------- ------------ ------------ ------------ ------ ------------
ICDATA               409,600      203,264      206,336   49.6      172,328
ICRBS                266,240       61,448      204,792   23.1      204,792
SYSTEM                204,800       72,304      132,496   35.3      130,896
TEMP                  153,600           16      153,584     .0      102,392
                 ------------ ------------ ------------
sum                 1,034,240      337,032      697,208

4 rows selected.

ICT>   select substr(a.file_name,1,30),
  2             a.file_id,
  3             substr(a.tablespace_name,1,20),
  4             a.bytes allocated,
  5             nvl(b.free,0) free,
  6             a.bytes-nvl(b.free,0) used
  7        from dba_data_files a,
  8             ( select file_id, sum(bytes) free
  9                 from dba_free_space
 10               group by file_id ) b
 11    where a.file_id = b.file_id (+);

SUBSTR(A.FILE_NAME,1,30)          FILE_ID SUBSTR(A.TABLESPACE_  ALLOCATED
 Free         Used
------------------------------ ---------- -------------------- ----------
------------ ------------
/ora817/dbs/ict/ict1.dbf              1 SYSTEM                209715200
135,675,904   74,039,296
/ora817/dbs/ict/ict2.dbf              2 PICDATA               209715200
34,824,192  174,891,008
/ora817/dbs/ict/ict3.dbf              3 TEMP                   52428800
52,420,608        8,192
/ora817/dbs/ict/ict5.dbf              5 ICDATA               209715200
176,463,872   33,251,328
/ora817/dbs/ict/pict6.dbf              6 ICRBS                209715200
209,707,008        8,192
/ora817/admin/ict/pict7.dbf            7 TEMP                  104857600
104,849,408        8,192
/ora817/dbs/ict/pict4.dbf              4 ICRBS                 62914560
  0   62,914,560

------------ ------------
sum
713,940,992  345,120,768

7 rows selected.

          Followup:

i only see one query.

      Rating: 3
FREE
      SPACE  March 04, 2003
Reviewer:  mo

Tom:

what do you mean by seeing only 1 query. 1 is free.sql and the other is the
select statement.

the first one has 4 rows selected and second one has 7 rows selected.

          Followup:

what does

      select sum(bytes)/1024 Kbytes_alloc,
              tablespace_name
       from sys.dba_data_files
       group by tablespace_name

give -- and what I meant was exactly what I said -- I see only one query.
People have been known to make cut and paste errors, CHANGE things, you know.  I
only believe what I see.

      Rating:
      5
  March 04, 2003
Reviewer:
      trevor_welch@hotmail.com
Handy script to have

      Rating: 5
Free
      space  March 11, 2003
Reviewer:  Steve
       from UK
Using LMT with either Uniform Extent Size or Autoallocate, with autoextend on,
is there any need to make sure that you have at least 10% free in a tablespace?
Can it be a problem if a tablespace is "full"?

Cheers

          Followup:

No you cannot, nor do you need to.

If the tablespace is full and we need more, we'll grow the file.
If we cannot grow the file -- because the OS is out of space -- you would not
have been able to have that 10% pad in the first place, so it doesn't matter
that it is there or not.

      Rating:
      3
sys.dba_data_files table or view does not
      exist  April 17, 2003
Reviewer:  Peter L
      Jones  from Switzerland
Hello
I guess that this is for admins only. I am a user
and get the error
select sum(bytes)/1024 Kbytes_alloc,
              tablespace_name
       from sys.dba_data_files
       group by tablespace_name   2    3    4  ;
       from sys.dba_data_files
                *
ERROR at line 3:
ORA-00942: table or view does not exist

I would like to monitor my personal work quota. Can you suggest how best to do
this. I was told by my DBA that max # extents was exceded on an index and I
would prefere to be able monitor this myself. Thanks for any help

          Followup:

use the USER_ views.

DBA_DATA_FILES has nothing to do with max extents.

your DBA hasn't caught onto LMT's yet.  turn them onto them and you'll never hit
max extents again.

      Rating: 5
%
      used  September 22, 2003
Reviewer:  atul
       from India
Hi,

I'm using your script which is really very helpful.

How to change this script so that output will show only
tablespaces which is above 90% used..

Could you help me out in this

Regards,
atul

          Followup:

hmm, add a predicate?  should be pretty easy.  the computation is there in the
query to find the %used, just "where" on that

      Rating: 3
Space in
      tablespace  January 06, 2004
Reviewer:
      A reader  from NJ, USA
Tom,

1. When determining the total space allocated per tablespace on an instance,
should we use BYTES or USER_BYTES in dba_data_files to arrive at total space
allocated. Usually I find some differences between the two except for some
tablespaces like SYSTEM. Why is the difference.
2. I used the following query to find the actual space used for each user in a
database.
   select owner,ROUND(sum(bytes)/(1024*1024),0)
    from dba_segments
     group by owner
Is the above query correct or is there any other way to find it. Are there any
tools available that will show a clean report of the space consumed by each user
in a database.
3. I am using the following query to find out the allocated, used, free space
and pct (%) for each tablespace in a database.
select ts.tablespace_name "Tablespace Name",
   ROUND(tot.Alloc/(1024*1024),1) "Alloc (MB)",
   ROUND(tot.ubytes/(1024*1024),1) "Size (MB)",
   ROUND(NVL(fet.bytes,0)/(1024*1024),1) "Free (MB)",
   ROUND(tot.ubytes/(1024*1024),1) - ROUND(NVL(fet.bytes,0)/(1024*1024),1) "Used
(MB)"
 from (select tablespace_name from dba_tablespaces) ts,
      (select tablespace_name,
              sum(bytes) Alloc,
              sum(user_bytes) ubytes
       from dba_data_files
      group by tablespace_name) tot,
     (select tablespace_name,
             sum(bytes) bytes
      from dba_extents
      group by tablespace_name) uet,
     (select tablespace_name,
             sum(bytes) bytes
    from dba_free_space
    group by tablespace_name) fet
  where ts.tablespace_name = tot.tablespace_name
    and ts.tablespace_name = uet.tablespace_name(+)
    and ts.tablespace_name = fet.tablespace_name(+)
  order by ts.tablespace_name

Is there any better way to do it

          Followup:

bytes is the real file size
user_bytes is the size YOU can use (bytes-user_bytes = number of bytes Oracle
has claimed for itself)

Using an LMT -- you are seeing a 64k "bitmap" taken away.  So, your system is
probably dictionary managed -- and the others are LMT's

2) that one is fine.

3) you can search for

free.sql

on this site -- to see the query i use. (never mind, i see it is above, just
look up for the one i use)

      Rating: 3
Pls
      help  January 07, 2004
Reviewer:
      A reader  from NJ, USA
Tom,

I saw the free.sql in your site and have the following question :
You use the DBA_FREE_SPACE to determine the free space and subtract the free
space (DBA_FREE_SPACE) from the total space allocated (DBA_DATA_FILES) to get
the used space.
   The script you used above and other scripts that I have
   seen before use this logic to determine used space.
   Can't we use DBA_EXTENTS directly to determine the used
   space in a tablespace? Will using DBA_EXTENTS directly
   won't provide correct results?
   Any tools available in Oracle to provide such info?
   Which view can you use to see info about LMT TEMPORARY
   tablespace. If a LMT TEMPORARY tablespace becomes full,
   can we add space to it just like as for any other
   tablespace

          Followup:

there are generally lots more extents allocated then free.

I used free.

Yes you could use dba_extents but then you would have to use user_bytes to
account for overhead.

by using dba_free_space -- I use bytes.

the tools provided by oracle??? well, the data dictionary??  it is the tool.
there are tons of UI's that show the dictionary info (OEM for example) but the
'tool' is the data dictionary.

LMT and DMT's are "viewed" the same way -- using the same queries.  no
difference there.  Yes, you can add space to it just like anything else.

    Copyright © 2003 Oracle Corporation, All rights reserved.


Listing

Recent Archives Advanced Search FAQ by Category Most Popular Hot Articles Your Questions [ Listing ] RSS Feed
Subject | Article | Last Updated (East coast time)
TNS listener, ORA-12500, ORA-00955 | article only | Jan 08, 2004 11:07 am
Snapshot - to - old | article only | Jan 08, 2004 11:20 am
variables (global or otherwise) within a packaged environment | article only | Jan 08, 2004 11:23 am
explain plan and load on the database | article only | Jan 08, 2004 11:25 am
Comparing Update Performance and ORA-01722 | article only | Jan 08, 2004 11:26 am
primary key constraint vs non-unique index+PK | article only | Jan 08, 2004 12:41 pm
IOT pctthreshold calculation | article only | Jan 08, 2004 12:49 pm
Blocks on stored procedure | article only | Jan 08, 2004 01:25 pm
DBMS_RLS package | article only | Jan 08, 2004 01:25 pm
Aggregate text field searching | article only | Jan 08, 2004 01:50 pm
How control file (SQL*LOADER) handle multiple different format date? | article only | Jan 08, 2004 02:52 pm
Yet another ASCII dump issue | article only | Jan 08, 2004 03:24 pm
EXPLAIN PLAN vs TKPROF | article only | Jan 08, 2004 03:31 pm
Blocks - 2 or 4 or 8k? | article only | Jan 08, 2004 03:36 pm
Database Import | article only | Jan 08, 2004 03:41 pm
renaming a database and sid in NT | article only | Jan 08, 2004 08:17 pm
Locking & Others. | article only | Jan 09, 2004 07:58 am
Oracle Enterprise Manager | article only | Jan 09, 2004 07:58 am
using plsql how can I import an ascii file | article only | Jan 09, 2004 08:14 am
How to calculate for a weekly rolling period? | article only | Jan 09, 2004 08:15 am
sql_trace and tkprof | article only | Jan 09, 2004 08:47 am
How to make CBO behave like /*+ first_rows */ | article only | Jan 09, 2004 09:14 am
explain plan on range-partitioned table | article only | Jan 09, 2004 01:45 pm
NO_DATA_FOUND in Functions | article only | Jan 09, 2004 01:51 pm
Shutdown abort prior to an offline backup. | article only | Jan 09, 2004 02:21 pm
Using of UNUSABLE indexes | article only | Jan 09, 2004 02:22 pm
SQL Tuning Strategy and Goal | article only | Jan 10, 2004 10:07 am
Sizing of LOBs | article only | Jan 10, 2004 10:45 am
Loading Multiple Input files delimited by TAB into Oracle Table. | article only | Jan 10, 2004 10:51 am
Direct-path INSERTs are not propagated during a "Materialized View Fast Refresh" | article only | Jan 11, 2004 06:12 am
INDEX RANGE SCAN vs. INDEX FAST FULL SCAN for _cached_ blocks | article only | Jan 12, 2004 01:23 pm
DIFFERENCES BETWEEN PROCESSES, SESSIONS AND CONNECTIONS | article only | Jan 12, 2004 01:24 pm
Execute procedures concurently in a procedure | article only | Jan 13, 2004 01:13 am
Padding a Long Variable | article only | Jan 13, 2004 01:14 am
Break comma/space delimited text out | article only | Jan 13, 2004 01:16 am
Replication | article only | Jan 13, 2004 05:48 pm
Date range per week | article only | Jan 14, 2004 03:31 pm
tables - whats the difference between lookup and master detail. | article only | Jan 14, 2004 03:39 pm
QUERY | article only | Jan 14, 2004 03:49 pm
write to file in PL/SQL and sort that file | article only | Jan 14, 2004 03:58 pm
Table Sizing Guidelines | article only | Jan 14, 2004 04:00 pm
Why is it said that global indexes are suitable for OLTP apps, and local indexes are suitable for DSS i.e. data warehousing apps | article only | Jan 14, 2004 04:12 pm
Runstats valid for 9.2 | article only | Jan 14, 2004 06:35 pm
row(s) 1 - 43 of 43
January 08, 2004 Bobby -- Thanks for the question regarding "TNS listener, ORA-12500, ORA-00955", version 8.1.6
You Asked
Can either or both of these error conditions be generated by sending the TNS
listener too many queries, too fast?  If so, how fast is too fast?

ORA-12500  TNS:listener failed to start a dedicated server process

ORA 00955  name is already used by an existing object
and we said...
Well, in reverse order -- starting with ora-00955:

Error:  ORA 955
Text:   <name> is already used by an existing object
-------------------------------------------------------------------------------
Cause:  An attempt was made to create a database object (such as a table, view,
         cluster, index, or synonym) that already exists.
        A user's database objects must have distinct names.
Action: Enter a unique name for the database object or modify or drop the
        existing object so it can be reused.

that one cannot be caused by hitting the listener, thats a database error after
you are already connected.

The ORA-12500 -- it could be caused by hitting the listener too fast, faster
then the OPERATING system itself can deal with.  If you are using dedicated
server configurations on unix for example, each connection is a unix fork/exec.
If you overload the OS, it won't be able to spawn any new processes.  Its not
really that you hit the LISTENER too fast in this case, more that the OS is
physically out of resources or is taking so long to get these resources in place
that we time out.  How fast is too fast -- thats a function of your OS, the
amount of ram, how many cpu's you have, how fast they are, what else is going on
on the box, etc etc etc...
Review & Followup
Rating: 3
listener hangs February 24, 2002
Reviewer: Amit Agarwal from India
we have similar problem but on earlier version of Oracle & Sql Net.So the
solution you have given above might not help us.

We are working on Oracle 7.3.4 with SCO-Unix with SQL Net v2.
FOr the past 3-4 days, we are infilcted with severe listener problem. Listener
is always hanging in the middle of session.On reading the listener.log, it gives
following error.

TNS-12535: TNS:operation timed out
 TNS-12560: TNS:protocol adapter error
TNS-00505: Operation timed out
   SCO System V/386 Error: 90: Operation would block

Then we have to kill the listener process from OS and again restart it.we
shutdown the database every evening but not listener.

Can we have multiple listeners in SQL net V2??can we set Queuesize in this
verson of listener.How to change queue size for Sco-unix. I think default is 32
which is too low.

Please tell us the solution.

Rating: 5
OS is overloaded and why not our "Database"? February 25, 2002
Reviewer: Yogeeraj from Mauritius
Hello,

OS is overloaded means:
- Memory available is too LOW
- CPU usage is on Average at 100%

In this case, couldn't it be that our Parameters at the Database level is not
properly set:
E.g.
mts_dispatchers = "(protocol=TCP)(disp=20)(mul=ON)(con=30)"
mts_max_dispatchers = 30
mts_servers = 20
mts_max_servers = 25

Are we still concerned about the kernel parameters that we changed as specified
in the Pre-installation guide?

Is it not better to start tuning at the Database level first?

Regards
Yogeeraj

Followup:
Well, 20 dispatchers for 20 shared servers is a little overkill.  You need a
dispatcher for every 1000 or so users.  With 20 shared servers, you probably
need 1 dispatcher.

If your memory or cpu is exhausted you have two choices

a) figure out why, change your application
b) buy more memory / cpu

without tons more info -- cannot really tell you what to do.  You are getting
the 12500 because the OS is out of resources.

Rating: 3
TNS errors February 25, 2002
Reviewer: mak from ca
what do TNS errors really mean?

Followup:
they are simply errors from the networking layer.

Rating: 2
comes from Dedicated or MTS ? April 13, 2003
Reviewer: Eva from indo
Dear Tom,

You said TNS-12500 caused OS is overload. So, can TNS-12500 message comes from
MTS configuration or only from Dedicated Server ?

How to detected, which OS component is overload ? Memory, Processor or trafic
network ?

Followup:
If we needed to start up another shared server and we couldn't - that would
cause it as well.

you would use OS tools to see which OS component was overwhelmed.  Varies by OS.

Rating: 3
Multiple Listener April 14, 2003
Reviewer: Eva from Indo
Dear Tom and thanks for your advice..

just to acertain, TNS-12500 can happen on Dedicated server and Shared server
(MTS) configuration, if we have OS overload. is it right ?

so, if I have small concurent user (100-200 user) in dedicated server mode. Can
I avoid TNS-12500 using multiple listener ? or I create multiple listener on the
other machine ?

My database running on windows platform.

please give me your solution...

best regards

Followup:
Odds are -- if you are GETTING an ora-12500 it means your system is way over
taxed.

Starting another listener would be akin to placing another straw on the camels
back (and thus breaking it)...

Do you have processes set correctly.  Have you poked about in the logs to see
what the underlying issue is exactly.

[tkyte@tkyte-pc-isdn Desktop]$ oerr ora 12500
12500, 00000, "TNS:listener failed to start a dedicated server process"
// *Cause:  The process of starting up a dedicated server process failed.
// The executable could not be found or the environment may be set up
// incorrectly.
// *Action: Turn on tracing at the ADMIN level and reexecute the operation.
// Verify that the ORACLE Server executable is present and has execute
// permissions enabled. Ensure that the ORACLE environment is specified
// correctly in LISTENER.ORA. The Oracle Protocol Adapter that is being
// called may not be installed on the local hard drive.  Please check that
// the correct Protocol Adapter are successfully linked.
// If error persists, contact Oracle Customer Support.

Rating: 4
Turn on tracing at the ADMIN level ? April 15, 2003
Reviewer: reader from India
tom,

can you please explain me how to trun on tracing at admin level?

is there any different levels of tracing - if so i would like to hear that from
you.

thanks as always !

Followup:
http://download-west.oracle.com/docs/cd/A87860_01/doc/network.817/index.htm
specifically:

http://download-west.oracle.com/docs/cd/A87860_01/doc/network.817/a76933/params.htm#437951

      Rating:
      3
TNS-12535  January 07, 2004
Reviewer:
      Pichai Bala  from San Jose, CA
Hi Tom,
   We are intermittently getting TNS-12535 error say for one of 10 and the log
file has grown to nearly 900 M. Even stopping and restarting the listener is not
helpful. So now we added a second listener at a different port and running. How
can we solve this issue?
   Wish you a wonderful New Year.

Thanks
Pichai

          Followup:

well, you can disable the logging (that'll help.  do you really need a gigabyte
of stuff you probably do not use)

You can set the connect timeout LONGER or disable it all together, but it sounds
like an overloaded machine.

      Rating:
      3
TNS-12535  January 07, 2004
Reviewer:
      Pichai Bala  from San Jose, CA
Hi Tom,
   We are intermittently getting TNS-12535 error say for one of 10 and the log
file has grown to nearly 900 M. Even stopping and restarting the listener is not
helpful. So now we added a second listener at a different port and running. How
can we solve this issue?
   Wish you a wonderful New Year.

Thanks
Pichai

January 08, 2004 Anthony -- Thanks for the question regarding
      "Snapshot - to - old", version 8.1.6

      You Asked
Hi  tom ,

     I understand the concept behind the snapshot-to-old error, my question is,
Can we anticipate before hand if we are going to encounter this error.

     I tried to keep a table in the keep pool where can i query that the table
that i pinned is already in that buffer pool.

Tnx TOM....

      and we said...
If your rollback segments wrap faster then your longest query runs - you will
hit an ora-01555 at some point.

So, yes, you can anticipate this.  You should be able to say how fast your RBS
wraps and how long your longest running query is.  If your query is longer then
the time to wrap, you are a 1555 candidate.

Doesn't matter if you buffer it, we need the ROLLBACK to give you the right
answer.  If you overwrite the rollback - it quite simply doesn't exist --
buffered or not.
Review & Followup

      Rating: 5
Snapshot to
      old  February 02, 2002
Reviewer:  Anthony K
      Velarde  from Philippines
So what you're saying is , I can still prevent this oracle error from happening.
If so, can you give the full details, steps or even scripts.

tnx very much.......

          Followup:

Mark is dead on in the following comment, all you need to do is ensure you have
sufficiently sized, PERMANENTLY allocated rollback segment space.

In Oracle9i, the steps are easier:

1) determine your longest running query
2) set the undo_retention_period to that time plus a little more and use an undo
tablespace instead of rollback segments.

      Rating: 4
How to size
      rollback  February 02, 2002
Reviewer:  Mark J.
      Bobak  from Dearborn Heights, MI
1.)  Determine the length of time of your longest running query.
2.)  Determine the total volume of undo your database writes
in that length of time.
3.)  That number should be the total size of the sum of
the OPTIMAL of all your rollback segments.
4.)  Determine the largest number of concurrent transactions
you will encounter, and divide by 4.  That's the number
of rollback segments you need.
5.)  Take the number from step 3 and divide by number in
step 4, that's the size of OPTIMAL for your rollback segments.
You may want to put a little (say, 5%) padding
in the number from step 3, to avoid resize thrashing.
(If you inadvertently make OPTIMAL just slightly too small,
your rollback segments will shrink/wrap themselves to
death, and you'll get a flood of ORA-1555.)

The above should properly size your rollback segments
to avoid ORA-1555.

6.)  What's the largest peak transaction volume your
database needs to support?
7.)  How many concurrent peak transactions do you expect?
8.)  Multiply number from step 6 by number from step 7.
9.)  Total size of your rollback tablespace should be
large enough to contain the sum of the OPTIMALs of all
your rollback segments (from step 3) plus the number from
step 8.

The above should allow you to properly size the rollback
tablespace to avoid ORA-1650.

Hope that helps....

      Rating: 3
Just
      Clarify  January 07, 2004
Reviewer:  Anil
      Pant  from India
To the ealier response u said

In Oracle9i, the steps are easier:
1) determine your longest running query
2) set the undo_retention_period to that time plus a little more and use an undo
tablespace instead of rollback segments.

Use an undo tablespace instead of rollback segments. This line is bit confusing.
As far as my knowledge goes we dont have any thing called rollback segment
(though internally RBS is used) in Oracle 9. Pls calrify

          Followup:

rollback segments as they existed in 8i and before are fully supported and exist
in 9i.

YOU make the choice

a) do it yourself and create rbs's the old fashioned way
b) use undo_management = auto and let the database do it.

      Rating: 5
fetch across
      commit  January 07, 2004
Reviewer:  Darren
       from London, UK.
Tom,

if you have a fetch across commit scenario wont that artifically create 1555 no
matter what the size of your RBS is. So you should also look to rout out all the
code that does this sort of thing:
open cursor
loop
  some dml;
  commit;
end loop;
close cursor;

in addition to sizing your rollback space.

          Followup:

Not really -- if your rbs is big enough to not wrap during the time you are
fetching over commits, you should be OK.

The fetch over commits allows undo to be reused (undo you've generated).
Meaning if the rbs's ARENT big enough to not wrap, YOU wont cause them to extend
(since you are comitting and releasing the storage).

If the rbs's started "more than large enough", they wouldn't have extended had
you NOT committed in the first place -- so, it won't happen.

The thing is -- people typically COMMIT in the for loop specifically because
they get the "failed to extend rbs XXX by YYY" and figure "ah hah, the fix is
'commit' to release the rbs storage" but that just brings on the 1555 with a
vengance.

still, a horrible practice this is:

open cursor
loop
  some dml;
  commit;
end loop;
close cursor;

since most people never consider "what happens when we've done 100 out of 200
rows in the cursor and the system fails".  They cannot restart that process
without double processing the 100 records OR skipping the last 100 (eg: they
corrupt their data).  Use transactions for what they were designed for -- TO
PROTECT your data integrity.

January 08, 2004 Susan -- Thanks for the question regarding
      "variables (global or otherwise) within a packaged environment", version
      SQLPlus 3.3.3.0.0 / Oracle8 Enterprise 8.0.5.1.0 / PL/SQL 8.0.5.1.0 /
      Developer 2000 1.6.1

      You Asked
'Tis me again!  You have been so much help in the past that I'm
hoping you can straighten me out again.

I am creating a package containing a function and a procedure.
I need to take the variable retreived by the function and use it
as a comparison value in the 'where' statement of the procedure.
 I have finally managed to get my package to compile
successfully, but when I try to run the procedure, I get an
error.  If I run just the function, I retrieve data.  What the
heck am I doing wrong?  HELP!!  I am including the following
coding to try to clarify my problems:

Cursor Package (filename scc_cv_types) – created with no
problems

CREATE OR REPLACE PACKAGE scc_cv_types AS
  TYPE LookupCurRec IS RECORD
        (v_emp_id       VARCHAR2(9));
  TYPE lookup_cv IS REF CURSOR RETURN LookupCurRec;
END scc_cv_types;
/

2nd Cursor Package (filename ni_IPPS_cursors) – created with no
problems and in use with stand-alone procedure

CREATE OR REPLACE PACKAGE ni_IPPS_cursors AS
  TYPE NI_Staff_Dir_Rec IS RECORD (
        ni_emp_id        VARCHAR2(9),
        ni_full_name     VARCHAR2(30),
        ni_job_desc      VARCHAR2(16),
        ni_loc_desc      VARCHAR2(16),
        ni_actual_fte    NUMBER(6,5)
        );

  TYPE NI_Staff_Dir_Cursor IS REF CURSOR RETURN
NI_Staff_Dir_Rec;

END ni_IPPS_cursors;
/

Separate Package (filename scc_function_working3)
&#61664; contains function to return variable
&#61664; this ‘function’ variable to be used as part of the
‘where’ clause in the procedure
&#61664; creates successfully

CREATE OR REPLACE PACKAGE scc_function IS

  FUNCTION scc_function_test
        RETURN scc_cv_types.lookup_cv;

/*      V_NI_EMP_ID scc_cv_types.lookup_cv; */

  PROCEDURE scc_proc_test (
        NI_Staff_Dir_CV IN OUT
ni_IPPS_cursors.NI_Staff_Dir_Cursor
        );

END scc_function;
/

CREATE OR REPLACE PACKAGE BODY scc_function AS

  v_ni_emp_id           VARCHAR2(9);

  FUNCTION scc_function_test
        RETURN scc_cv_types.lookup_cv IS
        V_NI_EMP_ID scc_cv_types.lookup_cv;

  BEGIN
    OPEN v_ni_emp_id FOR
      SELECT
        ec_employee.employee_id
      FROM
        ec_employee;
    RETURN (v_ni_emp_id);

  END scc_function_test;

  PROCEDURE scc_proc_test (
        NI_Staff_Dir_CV IN OUT
ni_IPPS_cursors.NI_Staff_Dir_Cursor
        ) AS

    BEGIN
        OPEN NI_Staff_Dir_CV FOR
          SELECT DISTINCT
                ee.employee_id ni_emp_id,
                LTRIM(UPPER(ee.surname)||', '||
                ee.first_name||' '||
                SUBSTR(ee.second_name,1,1),30) ni_full_name,
                ej.description_abbr ni_job_desc,
                el.description_abbr ni_loc_desc,
                eep.actual_fte ni_actual_fte
          FROM
                ec_employee ee,
                ec_employee_positions eep,
                ec_jobs ej,
                ec_locations el
          WHERE
                ee.employee_id = eep.employee_id AND
                eep.employee_id = v_ni_emp_id AND
                ee.emp_activity_code = 'ACTIVE' AND
                eep.emp_group_code NOT IN
('SEC-OCC','ELE-OCC','CASUAL','TESTGRP','ETEACHER','RETIRED')
AND
                eep.record_status = 'A' AND
                eep.position_start_date <=
to_char(sysdate,'IYYYMMDD') AND
                NVL(eep.position_end_date,'99999999') >=
to_char(sysdate,'IYYYMMDD') AND
                ej.job_code = eep.job_code AND
                el.location_code = eep.location_code
          ORDER BY
                ee.surname ASC,
                eep.actual_fte DESC;

  END scc_proc_test;

END;
/

/* SET AUTOPRINT ON                              */
/* VARIABLE empdata REFCURSOR                    */
/* EXECUTE scc_function.scc_proc_test (:empdata) */

SQL> @scc_function_working3

Package created.

Package body created.

Error Messages received when trying to run procedure from above
package/function/procedure

SQL> SET autoprint ON
SQL> VARIABLE rc REFCURSOR
SQL> EXECUTE scc_function.scc_proc_test (:rc)
begin scc_function.scc_proc_test (:rc); end;

*
ERROR at line 1:
ORA-01791: not a SELECTed expression
ORA-06512: at "OPS$IPMANAGER.SCC_FUNCTION", line 18
ORA-06512: at line 1

      and we said...
It's in the query.  Consider this for example:

  1  select distinct ltrim(upper(ename)) || ', ' || job
  2  from emp
  3* order by ename
scott@8i> /
order by ename
         *
ERROR at line 3:
ORA-01791: not a SELECTed expression

That asks us to

1) get all of the ltrim(upper(ename)) || ', ' || job
2) DISTINCT them
3) then order by ename ...

but by the time we get to 3 -- there is no such thing as ENAME
-- the distinct wiped it out.

So, your

order by ee.surname ASC, ...

is the culprit here -- surname no longer exists.

your choices:

1) make surname part of the select (all by itself -- not as a
function of other columsn) so it still exists AFTER the
distinct.

2) order by 2 asc, 5 desc (which is not the same as 1 above --
the output data will be sorted a little differently because of
different length surnames

3) drop the distinct if you can, in which case SURNAME is still
available to the order by....

Review &
      Followup

      Rating: 2
Another related
      question  January 06, 2004
Reviewer:  AKS
       from PA, USA
Hi Tom,
How do i sort on non-selected column with distinct clause?
e.g. One table T habe 3 columns a ,b and c.

select distinct a ,b from T order by c

gives an error ORA-01791

looking forward, thanks, AKS

          Followup:

tell me what sort of "sense" that would make at all.

say you have data:

A       B        C
-----   ------   -------
1       1        500
1       1        0
2       2        250

Tell me, should it be

1,1
2,2

or

2,2
1,1

      Rating:
      2
  January 07, 2004
Reviewer:  AKS
       from PA, USA
The data in Table T will be like this -
A       B        C
-----   ------   -------
1       1        500
1       1        0
2       2        250
2       2        300

The query will be -

select distinct a ,b from T
order by c

And the result i need in the following order-
A       B        C
-----   ------   -------
1       1        0
2       2        250

Thanks

          Followup:

See -- I knew you didn't want to order by C

You want to

a) get the minimum C by distinct A, B
b) order by that.

Easy:

ops$tkyte@ORA920PC> select a, b
  2    from t
  3   group by a, b
  4   order by min(c)
  5  /

         A          B
---------- ----------
         1          1
         2          2

Until you disambiguated the question here (makes NO sense to sort by C, makes
sense to sort by the MINIMUM C by A,B) -- it wasn't "reasonable"

January 08, 2004 -- Thanks for the question regarding "explain
      plan and load on the database", version 9.1.2

      You Asked
Tom,
We have a report that has about 20+ SQLs.  We have noticed that the execution
time for this report ranges anywhere from 15 sec to 3 min on a given day.
What I would like to know is
a) Does the explain plan differ based on the load on the database?
b) If the explain plan differs what is the best way to optimize such SQL's?
c) What is the best way to find out why the SQLs in the report are sometime
taking 15 sec's and sometime taking 3 min.?

Thank you for your time and valuable suggestions

      and we said...
there never was a version 9.1.2

a) no.  however, if you are using pga_aggregate_target, the amount of workarea
space (dynamic sort/hash area's) can and will change with the load

b) they won't

c) using stats pack and seeing what your major waits are.  take a 15 minute snap
(snap once, wait 15 minutes, snap again). do this when you are busy.  see what
people are waiting for.
Review & Followup

      Rating: 5
explain plan
      and load on the database  September 18, 2003

Reviewer:  A reader
oops! - The DB version info I have put in is wrong
Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production

If we are not using 'pga_aggregate_target', does the factor that some of the
data is already in the buffer(logical I/O) and does not require a physical I/O -
contribute to the time difference of 15 sec vs 3 min.?

          Followup:

that would manifest itself as "wait time" for IO

      Rating: 3
Parallel
      query?  September 19, 2003
Reviewer:  Peter
       from UK
I have seen on some systems (mainly 8i) a similar effect if a query would
normally execute in parallel but there not enough parallel query slaves
available to satisfy the request - so the query runs in serial and slower

I am not commenting on whether it right or wrong to use for a given table.

      Rating: 3
regarding
      "explain plan and load on the database", version
      9.1.2  September 19, 2003
Reviewer:  Raj
      Jamadagni
In addition to Tom's recommendations, I'd suggest that run your report with
"dbms_support.start_trace;" .

Let it generate trace files for each invocation on a typical day. Then you
analyze all trace files and compare.

Along with a statspack analysis report for the (specific) report run time, the
trace file analysis will provide you a lot more insight into wha the issues may
be.

HTH
Raj

      Rating:
      5
pga_aggregate_target  January 07, 2004

Reviewer:  A reader
Hi

If I have 512mb for pga_aggregate_target and most of them is free then if my
queries are using sort joins will they change to nested loop when
pga_aggregate_target is low, i.e not enough memory for sort? That means the plan
will be very unstable no?

Also, what process are consuming/managing this PGA pool? Because according to
the docs this memory can be freed to OS, i.e this is a pool like SGA and not per
process as before

TIA

          Followup:

the plan will be the right plan for the current situation.

do you want to do a sort join with 64k of sort area size?  probably not.  do you
want to do nested loops with a 5m sort area size?  probably not (in your case of
course).

the pga workareas are allocated "differently".  no one manages them, they use
different OS calls (like memmap in unix) to allocate storage that can be
directly released (as opposed to heap memory gotten via malloc)

      Rating: 5
Oracle 8
      Personal for winNT  January 07, 2004
Reviewer:
      kv srinivas  from India
Tom'

A)Does the explain plan How to install oracle 8PE for winNT Iam unable to
install on win2K server

Getting Problem the Error mssg was unable to delet or install NT service for
SQL*Net .

B)Oracle8i Enterprise Edition Also unable to install

Plz give me solution.

          Followup:

please contact support.

January 08, 2004 Yogeeraj -- Thanks for the question regarding
      "Comparing Update Performance and ORA-01722", version 8.1.7.4

      You Asked
Hello,

Please allow me to ask 2 items on which i am currently struggling:

A. Performance of update done using either of the following:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I want to know which option will give me better performance, how do i proceed?
option a.
   update ( select t1.f1, t2.f2 from t1, t2 where t1.id = t2.id )
   set f1 = f2

option b.
Update t1
set f1 = ( select f2  from t2  where t1.id = t2.id )
where exists ( select 'x'
                 from t2
                where t2.id = t1.id )

=====================
Tests carried out:
=====================
SQL> create table t1 (id number(1), f1 varchar2(1));

Table created.

Elapsed: 00:00:00.13
SQL> create table t2 (id number(1), f2 varchar2(1));

Table created.

Elapsed: 00:00:00.04
SQL> insert into t1 values (1,'a');

1 row created.

Elapsed: 00:00:00.01
SQL> insert into t2 values (1,'b');

1 row created.

Elapsed: 00:00:00.01
SQL> commit;

Commit complete.

Elapsed: 00:00:00.01
SQL> r
  1  update ( select t1.f1, t2.f2 from t1, t2 where t1.id = t2.id )
  2* set f1 = f2
SQL> /
set f1 = f2
    *
ERROR at line 2:
ORA-01779: cannot modify a column which maps to a non key-preserved table

SQL> alter table t1 add constraint pk_t1 primary key (id);

Table altered.

SQL> alter table t2 add constraint pk_t2 primary key (id);

Table altered.

SQL> r
  1  update ( select t1.f1, t2.f2 from t1, t2 where t1.id = t2.id )
  2* set f1 = f2

1 row updated.

Execution Plan
----------------------------------------------------------
   0      UPDATE STATEMENT Optimizer=CHOOSE
   1    0   UPDATE OF 'T1'
   2    1     NESTED LOOPS
   3    2       TABLE ACCESS (FULL) OF 'T1'
   4    2       TABLE ACCESS (BY INDEX ROWID) OF 'T2'
   5    4         INDEX (UNIQUE SCAN) OF 'PK_T2' (UNIQUE)

Statistics
----------------------------------------------------------
        292  recursive calls
          9  db block gets
         54  consistent gets
          0  physical reads
        560  redo size
        647  bytes sent via SQL*Net to client
        503  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
         10  sorts (memory)
          0  sorts (disk)
          1  rows processed
SQL>

SQL> r
  1   Update t1
  2   set f1 = ( select f2  from t2  where t1.id = t2.id )
  3   where exists ( select 'x'
  4                         from t2
  5*                        where t2.id = t1.id )

1 row updated.

Execution Plan
----------------------------------------------------------
   0      UPDATE STATEMENT Optimizer=CHOOSE
   1    0   UPDATE OF 'T1'
   2    1     FILTER
   3    2       TABLE ACCESS (FULL) OF 'T1'
   4    2       INDEX (UNIQUE SCAN) OF 'PK_T2' (UNIQUE)
   5    0   TABLE ACCESS (BY INDEX ROWID) OF 'T2'
   6    5     INDEX (UNIQUE SCAN) OF 'PK_T2' (UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          7  db block gets
          4  consistent gets
          0  physical reads
        520  redo size
        649  bytes sent via SQL*Net to client
        596  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          1  rows processed
SQL>

B. ORA-01722
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I have query on a view which is working fine on the Development server, however
failing on the production server with error message: "ORA-01722: invalid number"

We have observed a difference in the execution plan (see below) that is causing
the "error"
The way tables crt100 and ort100 are being queried are different in each case.

Development server:
  11   10                TABLE ACCESS (BY INDEX ROWID) OF 'ORT100' (Cost=2
Card=1 Bytes=12)
  14    9                     TABLE ACCESS (BY INDEX ROWID) OF 'CRT100' (Cost=3
Card=1 Bytes=38)

Production server:
  12   11                         INDEX (RANGE SCAN) OF 'PK_CRT100' (NON-UNIQUE)
(Cost=2 Card=1)
  21   20                 TABLE ACCESS (BY INDEX ROWID) OF 'ORT100' (Cost=2
Card=1 Bytes=12)

View code:
================
CREATE OR REPLACE VIEW CRT200V_2 ( UNIT,
TRNCODE, ORDNUM, SHPNUM, PSHPNUM,
GMTSEQ, CLRSEQ, SZESEQ, CTNS,
PCECRT ) AS Select unit,crt200.trncode,crt200.ordnum,
         crt200.shpnum,crt200.pshpnum,
     ort130.gmtseq,to_number(crt100.seqcod) clrseq,
     crt100.szeseq,count(distinct crt200.barcode)ctns,
    sum(decode(crt200.barcode, crt110.barcode,
       decode(crt100.seqcod, crt110.seqcod,
      decode(crt100.szeseq, crt110.szeseq, crt110.qty, 0), 0),
      crt100.qty)) pcecrt
  From   crt200,crt100,ort130,ort100, crt150, crt110
  where     crt200.ordnum  = crt100.ordnum
  and    crt200.ctntype = crt100.ctntype
  and    crt200.ordnum  = crt150.ordnum
  and    crt200.shpnum  = crt150.shpnum
  and    crt200.ctntype = crt150.ctntype
  and     crt200.barcode = crt110.barcode(+)
  and     crt200.trncode  = crt110.trncode(+)
  and     crt200.ctntype  = crt110.ctntype(+)
  and     crt100.ordnum  = ort130.ordnum
  and     crt100.ordnum  = ort100.ordnum
  and    ort100.pckunt  = 1
  and    (  crt200.trncode = 'IN'
          or
           (
        crt200.trncode = 'OUT'
        and exists ( Select 'x'
                       From   sht700
                     where sht700.ordnum = crt200.ordnum
                       and sht700.shpnum = crt200.shpnum
                       and sht700.pshpnum = crt200.pshpnum
                     and nvl(sht700.status,'?') IN ('DEL'))
           )
          )
 Group by unit,crt200.trncode,crt200.ordnum,crt200.shpnum,crt200.pshpnum,
     crt100.seqcod,ort130.gmtseq,crt100.szeseq
 Union
  Select unit,crt200.trncode,crt200.ordnum,
         crt200.shpnum,crt200.pshpnum,ort200.gmtseq,
     ort200.clrseq clrseq,
     crt100.szeseq,count(distinct crt200.barcode)ctns,
        sum(decode(crt200.barcode, crt110.barcode,
         decode(crt100.seqcod, crt110.seqcod,
         decode(crt100.szeseq, crt110.szeseq, crt110.qty*ort200.pckunt, 0),0),
crt100.qty*ort200.pckunt)) pcecrt
  From   crt200,crt100,ort200,ort100,crt150, crt110
  where  crt200.ordnum  = crt100.ordnum
  and    crt200.ctntype = crt100.ctntype
  and    crt200.ordnum  = crt150.ordnum
  and    crt200.shpnum  = crt150.shpnum
  and    crt200.ctntype = crt150.ctntype
  and     crt200.barcode = crt110.barcode(+)
  and     crt200.trncode  = crt110.trncode(+)
  and     crt200.ctntype  = crt110.ctntype(+)
  and     crt100.ordnum  = ort200.ordnum
  and     crt100.seqcod  = ort200.pckcod
  and     crt100.ordnum  = ort100.ordnum
  and    ort100.pckunt  > 1
 and    (  crt200.trncode = 'IN'
          or
      (
       crt200.trncode = 'OUT'
       and exists ( Select 'x'
              From   sht700
              where sht700.ordnum = crt200.ordnum
              and sht700.shpnum = crt200.shpnum
                  and sht700.pshpnum = crt200.pshpnum
              and nvl(sht700.status,'?') IN ('DEL'))
          )
        )
 Group by unit,crt200.trncode,crt200.ordnum,crt200.shpnum,crt200.pshpnum,
     ort200.clrseq,ort200.gmtseq,crt100.szeseq

Tables used in the view and statistics
=========================
ON the development server:
=========================
yd@dev.MU> r
  1  select table_name, num_rows, last_analyzed, monitoring
  2  from user_tables
  3* where table_name in ('CRT110','CRT150','ORT100','ORT130','CRT100','CRT200',
'SHT700','ORT200')

TABLE_NAME                       NUM_ROWS LAST_ANALY MON
______________________________ __________ __________ ___
CRT100                                               YES
CRT110                             324935 19/12/2002 YES
CRT150                             170351 19/12/2002 YES
CRT200                            2828711 19/12/2002 YES
ORT100                              28380 19/12/2002 YES
ORT130                              18494 19/12/2002 YES
ORT200                              15231 30/09/2002 YES
SHT700                              32105 19/12/2002 YES

8 rows selected.

Elapsed: 00:00:00.03

=========================
ON the Production server:
=========================
yd@dev.MU> @connect yd/yd@prod
Connected.

swprod@PFS.CMT.MU> r
  1   select table_name, num_rows, last_analyzed, monitoring
  2   from user_tables
  3   where table_name in
('CRT110','CRT150','ORT100','ORT130','CRT100','CRT200',
  4*  'SHT700','ORT200')

TABLE_NAME                       NUM_ROWS LAST_ANALY MON
______________________________ __________ __________ ___
CRT100                                               YES
CRT110                             328272 05/12/2002 YES
CRT150                             175619 21/12/2002 YES
CRT200                            2881035 05/12/2002 YES
ORT100                              29056 20/12/2002 YES
ORT130                              18494 28/11/2002 YES
ORT200                              15967 28/11/2002 YES
SHT700                              33132 19/12/2002 YES

8 rows selected.

Elapsed: 00:00:00.02
yd@prod.MU>

=========================
ON the development server:
=========================
SQL> r
  1  select * from crt200v_2
  2  where ordnum = '020727E'
  3  and szeseq =1
  4* and clrseq =1

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=39 Card=2 Bytes=142)
   1    0   VIEW OF 'CRT200V_2' (Cost=39 Card=2 Bytes=142)
   2    1     SORT (UNIQUE) (Cost=39 Card=2 Bytes=295)
   3    2       UNION-ALL
   4    3         SORT (GROUP BY) (Cost=18 Card=1 Bytes=145)
   5    4           FILTER
   6    5             NESTED LOOPS (OUTER) (Cost=15 Card=1 Bytes=145)
   7    6               NESTED LOOPS (Cost=11 Card=1 Bytes=113)
   8    7                 NESTED LOOPS (Cost=9 Card=2 Bytes=198)
   9    8                   NESTED LOOPS (Cost=5 Card=1 Bytes=60)
  10    9                     NESTED LOOPS (Cost=2 Card=1 Bytes=22)
  11   10                       TABLE ACCESS (BY INDEX ROWID) OF 'ORT100'
(Cost=2 Card=1 Bytes=12)
  12   11                         INDEX (UNIQUE SCAN) OF 'PK_ORT100' (UNIQUE)
(Cost=1 Card=1)
  13   10                       INDEX (UNIQUE SCAN) OF 'PK_ORT130' (UNIQUE)
  14    9                     TABLE ACCESS (BY INDEX ROWID) OF 'CRT100' (Cost=3
Card=1 Bytes=38)
  15   14                       INDEX (RANGE SCAN) OF 'PK_CRT100' (UNIQUE)
(Cost=2 Card=1)
  16    8                   TABLE ACCESS (BY INDEX ROWID) OF 'CRT200'(Cost=4
Card=217 Bytes=8463)
  17   16                     INDEX (RANGE SCAN) OF 'IND_CRT200_1' (NON-UNIQUE)
(Cost=3 Card=217)
  18    7                 INDEX (UNIQUE SCAN) OF 'PK_CRT150' (UNIQUE)(Cost=1
Card=18 Bytes=252)
  19    6               TABLE ACCESS (BY INDEX ROWID) OF 'CRT110' (Cost=4
Card=324935 Bytes=10397920)
  20   19                 INDEX (RANGE SCAN) OF 'PK_CRT110' (UNIQUE) (Cost=3
Card=324935)
  21    5             TABLE ACCESS (BY INDEX ROWID) OF 'SHT700' (Cost=2 Card=1
Bytes=17)
  22   21               INDEX (UNIQUE SCAN) OF 'PK_SHT700' (UNIQUE) (Cost=1
Card=1)
  23    3         SORT (GROUP BY) (Cost=21 Card=1 Bytes=150)
  24   23           FILTER
  25   24             NESTED LOOPS (OUTER) (Cost=18 Card=1 Bytes=150)
  26   25               NESTED LOOPS (Cost=14 Card=1 Bytes=118)
  27   26                 NESTED LOOPS (Cost=12 Card=2 Bytes=208)
  28   27                   NESTED LOOPS (Cost=8 Card=1 Bytes=65)
  29   28                     NESTED LOOPS (Cost=5 Card=1 Bytes=27)
  30   29                       TABLE ACCESS (BY INDEX ROWID) OF 'ORT100'
(Cost=2 Card=1 Bytes=12)
  31   30                         INDEX (UNIQUE SCAN) OF 'PK_ORT100' (UNIQUE)
(Cost=1 Card=1)
  32   29                       TABLE ACCESS (BY INDEX ROWID) OF 'ORT200'
(Cost=3 Card=1 Bytes=15)
  33   32                         INDEX (RANGE SCAN) OF 'PK_ORT200' (UNIQUE)
(Cost=2 Card=1)
  34   28                     TABLE ACCESS (BY INDEX ROWID) OF 'CRT100' (Cost=3
Card=8 Bytes=304)
  35   34                       INDEX (RANGE SCAN) OF 'PK_CRT100' (UNIQUE)
(Cost=2 Card=8)
  36   27                   TABLE ACCESS (BY INDEX ROWID) OF 'CRT200'(Cost=4
Card=217 Bytes=8463)
  37   36                     INDEX (RANGE SCAN) OF 'IND_CRT200_1' (NON-UNIQUE)
(Cost=3 Card=217)
  38   26                 INDEX (UNIQUE SCAN) OF 'PK_CRT150' (UNIQUE) (Cost=1
Card=18 Bytes=252)
  39   25               TABLE ACCESS (BY INDEX ROWID) OF 'CRT110' (Cost=4
Card=324935 Bytes=10397920)
  40   39                 INDEX (RANGE SCAN) OF 'PK_CRT110' (UNIQUE) (Cost=3
Card=324935)
  41   24             TABLE ACCESS (BY INDEX ROWID) OF 'SHT700' (Cost= 2 Card=1
Bytes=17)
  42   41               INDEX (UNIQUE SCAN) OF 'PK_SHT700' (UNIQUE) (Cost=1
Card=1)

=========================
ON the Production server:
=========================
SQL> r
  1  select * from crt200v_2
  2  where ordnum = '020727E'
  3  and szeseq =1
  4* and clrseq =1
and clrseq =1
            *
ERROR at line 4:
ORA-01722: invalid number

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=44 Card=2 Bytes=142)
   1    0   VIEW OF 'CRT200V_2' (Cost=44 Card=2 Bytes=142)
   2    1     SORT (UNIQUE) (Cost=44 Card=2 Bytes=311)
   3    2       UNION-ALL
   4    3         SORT (GROUP BY) (Cost=21 Card=1 Bytes=152)
   5    4           FILTER
   6    5             MERGE JOIN (CARTESIAN) (Cost=18 Card=1 Bytes=152)
   7    6               MERGE JOIN (CARTESIAN) (Cost=16 Card=1 Bytes=140)
   8    7                 NESTED LOOPS (OUTER) (Cost=15 Card=1 Bytes=132)
   9    8                   NESTED LOOPS (Cost=11 Card=1 Bytes=91)
  10    9                     NESTED LOOPS (Cost=7 Card=2 Bytes=154)
  11   10                       TABLE ACCESS (BY INDEX ROWID) OF 'CRT100'
(Cost=3 Card=1 Bytes=38)
  12   11                         INDEX (RANGE SCAN) OF 'PK_CRT100' (NON-UNIQUE)
(Cost=2 Card=1)
  13   10                       TABLE ACCESS (BY INDEX ROWID) OF 'CRT200'
(Cost=4 Card=219 Bytes=8541)
  14   13                         INDEX (RANGE SCAN) OF
'IND_CRT200_1'(NON-UNIQUE) (Cost=3 Card=219)
  15    9                     INDEX (RANGE SCAN) OF 'PK_CRT150' (NON-UNIQUE)
(Cost=2 Card=18 Bytes=252)
  16    8                   TABLE ACCESS (BY INDEX ROWID) OF 'CRT110'(Cost=4
Card=328272 Bytes=13459152)
  17   16                     INDEX (RANGE SCAN) OF 'PK_CRT110' (NON-UNIQUE)
(Cost=3 Card=328272)
  18    7                 SORT (JOIN) (Cost=12 Card=1 Bytes=8)
  19   18                   INDEX (RANGE SCAN) OF 'PK_ORT130' (NON-UNIQUE)
(Cost=1 Card=1 Bytes=8)
  20    6               SORT (JOIN) (Cost=19 Card=1 Bytes=12)
  21   20                 TABLE ACCESS (BY INDEX ROWID) OF 'ORT100' (Cost=2
Card=1 Bytes=12)
  22   21                   INDEX (RANGE SCAN) OF 'PK_ORT100' (NON-UNIQUE)
(Cost=1 Card=1)
  23    5             TABLE ACCESS (BY INDEX ROWID) OF 'SHT700' (Cost=2 Card=1
Bytes=17)
  24   23               INDEX (RANGE SCAN) OF 'PK_SHT700' (NON-UNIQUE)(Cost=1
Card=1)
  25    3         SORT (GROUP BY) (Cost=23 Card=1 Bytes=159)
  26   25           FILTER
  27   26             MERGE JOIN (CARTESIAN) (Cost=20 Card=1 Bytes=159 )
  28   27               NESTED LOOPS (OUTER) (Cost=18 Card=1 Bytes=147 )
  29   28                 NESTED LOOPS (Cost=14 Card=1 Bytes=106)
  30   29                   NESTED LOOPS (Cost=10 Card=2 Bytes=184)
  31   30                     NESTED LOOPS (Cost=6 Card=1 Bytes=53)
  32   31                       TABLE ACCESS (BY INDEX ROWID) OF 'ORT200'
(Cost=3 Card=1 Bytes=15)
  33   32                         INDEX (RANGE SCAN) OF 'PK_ORT200' (NON-UNIQUE)
(Cost=2 Card=1)
  34   31                       TABLE ACCESS (BY INDEX ROWID) OF 'CRT100'
(Cost=3 Card=8 Bytes=304)
  35   34                         INDEX (RANGE SCAN) OF 'PK_CRT100' (NON-UNIQUE)
(Cost=2 Card=8)
  36   30                     TABLE ACCESS (BY INDEX ROWID) OF 'CRT200' (Cost=4
Card=219 Bytes=8541)
  37   36                       INDEX (RANGE SCAN) OF 'IND_CRT200_1'
(NON-UNIQUE) (Cost=3 Card=219)
  38   29                   INDEX (RANGE SCAN) OF 'PK_CRT150' (NON-UNIQUE)
(Cost=2 Card=18 Bytes=252)
  39   28                 TABLE ACCESS (BY INDEX ROWID) OF 'CRT110' (Cost=4
Card=328272 Bytes=13459152)
  40   39                   INDEX (RANGE SCAN) OF 'PK_CRT110' (NON-UNIQUE)
(Cost=3 Card=328272)
  41   27               SORT (JOIN) (Cost=18 Card=1 Bytes=12)
  42   41                 TABLE ACCESS (BY INDEX ROWID) OF 'ORT100' (Cost=2
Card=1 Bytes=12)
  43   42                   INDEX (RANGE SCAN) OF 'PK_ORT100' (NON-UNIQUE)
(Cost=1 Card=1)
  44   26             TABLE ACCESS (BY INDEX ROWID) OF 'SHT700' (Cost=2 Card=1
Bytes=17)
  45   44               INDEX (RANGE SCAN) OF 'PK_SHT700' (NON-UNIQUE)(Cost=1
Card=1)

Please guide us in troubleshooting this problem.

Best Regards
Yogeeraj

      and we said...
a) almost always (why'd I say "almost always" - because there undoubtably is a
case out there where this isn't true) -- if you can update a join -- go for it.
Consider:

reate table t1 as select * from all_objects;
create table t2 as select * from all_objects;
create table t3 as select * from all_objects where mod(object_id,100) = 1;

alter table t1 add constraint t1_pk primary key(object_id);
alter table t2 add constraint t2_pk primary key(object_id);
alter table t3 add constraint t3_pk primary key(object_id);

analyze table t1 compute statistics;
analyze table t2 compute statistics;
analyze table t3 compute statistics;

alter session set sql_trace=true;

and then:

update ( select t1.object_name t1_object_name,
                lower( t2.object_name ) t2_object_name
           from t1, t2
          where t1.object_id = t2.object_id )
   set t1_object_name = t2_object_name

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      1.13       1.53        287        813      30265       29398
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      1.15       1.55        287        813      30265       29398

********************************************************************************
update t1
   set object_name = ( select lower(object_name)
                         from t2
                        where t2.object_id = t1.object_id )
   where exists      ( select lower(object_name)
                         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      2.40       3.36          0      88606      30265       29398
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      2.40       3.37          0      88606      30265       29398
********************************************************************************
update ( select t1.object_name t1_object_name,
                lower( t3.object_name ) t3_object_name
           from t1, t3
          where t1.object_id = t3.object_id )
   set t1_object_name = t3_object_name

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.06       0.10          0        411        301         292
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.06       0.11          0        411        301         292
********************************************************************************
update t1
   set object_name = ( select lower(object_name)
                         from t3
                        where t3.object_id = t1.object_id )
   where exists      ( select lower(object_name)
                         from t3
                        where t3.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.10       0.15          0        701        301         292
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.11       0.15          0        701        301         292

the update of the join bests the correlated subquery every time....

b) as for b, well, you've made the cardinal mistake of assuming that SQL
predicates are evaluated in some order, eg that "where a = 5 and b = 6" will do
b=6 and then a=5 or vice versa.  You've combined that with an implicit
conversion of a string to a number.  And -- one of those strings IS NOT a
number.

somewhere in that view you are comparing a NUMBER to a STRING and the STRING
cannot be converted to a number.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1299201885045
for examples of this -- then go back to your query and using your knowledge of
what the actual datatypes are -- figure out where you compare numbers to
strings, figure out HOW you got into such a circumstance (so you know better
next time and make sure you don't model the data in such a way to permit this to
happen) and fix it by using to_char() on the NUMBER, since a number can always
be converted to a string.
Review & Followup

      Rating:
      4
  May 07, 2003
Reviewer:
      A reader

      Rating:
      3
Contention  January 07, 2004
Reviewer:
      A reader  from NJ, USA
Tom,

If I run multiple DML operations on a table in  parallel, pls explain which
operation will cause more contention
   1. Update
   2. Delete
   3. Insert
and the ways of resolving them

          Followup:

doesn't make sense. (eg: not a sensible question).

the answer is "none, all, some, 1, 2, 3"

January 08, 2004 -- Thanks for the question regarding "primary key
      constraint vs non-unique index+PK", version 8.1.7.4

      You Asked
Hi

I have some questions regarding definition of primary keys at database level. I
know that in 8i we can enforce uniqueness using non-unique index and primary
keys, this makes thing flexible in datawarehouse, when we have duplicate rows
after doing direct load in partitioned tables we can delete these rows easily by
disabling the PK without losing index (so I dont need to rebuild index for all
partitions again), if I was using conventional PK then after I disable the PK I
would lose my PK in all partitions (well I dont think we can disable PK for one
partition no?) and I would have to create a brand new index for my hundreds of
millions of rows table. Until now I gues smy understanding of this feature is
quite correct, correct me if I am wrong.

My question is, since this is so flexible should we always create PK this way?
Even in OLTP databases? What can this feature harm? Any disadvantages? I dont
see any really....

THX

      and we said...
There is great flexibility -- this is true.  However, food for thought -- things
to think about:

big_table@ORA920> alter table big_table add constraint big_table_pk
  2  primary key(id);
Table altered.

big_table@ORA920> analyze index big_table_pk validate structure;
Index analyzed.

big_table@ORA920> @printtbl8 'select * from index_stats';
...
LF_BLKS                       : 8360
LF_ROWS_LEN                   : 60010430
LF_BLK_LEN                    : 7996
BR_ROWS                       : 8359
BR_BLKS                       : 14
BR_ROWS_LEN                   : 98135
...
BTREE_SPACE                   : 66958952
USED_SPACE                    : 60108565
-----------------

PL/SQL procedure successfully completed.

big_table@ORA920> alter table big_table drop constraint big_table_pk;

Table altered.

big_table@ORA920> alter table big_table add constraint big_table_pk
  2  primary key(id) DEFERRABLE;

Table altered.

big_table@ORA920> analyze index big_table_pk validate structure;

Index analyzed.

big_table@ORA920> @printtbl8 'select * from index_stats';
...
LF_BLKS                       : 8900            (more)
LF_ROWS_LEN                   : 63826622        (bigger)
LF_BLK_LEN                    : 7996
BR_ROWS                       : 8899            (more)
BR_BLKS                       : 16              (more)
BR_ROWS_LEN                   : 113327          (bigger)
...
BTREE_SPACE                   : 71292848        (bigger)
USED_SPACE                    : 63939949        (bigger)
...
-----------------

PL/SQL procedure successfully completed.

that was on a number -- the number ranged from 1 .. 3,816,192 (number of rows
in the table actually).  A non-unique index takes more space...

Also, consider the query plan -- it is a NON-UNIQUE index and the optimizer has
to treat it that way when generating plans.  There is no reason why:

select * from big_table where id = :x

would not return 0, 1, or 1,000 rows -- even given a primary key on ID.  Hence,
it'll be a NON-UNIQUE index range scan.

That does not mean "don't use non-unique indexes for primary keys" for the
benefits seem to outweigh the above by a long shot in your case.
Review &
      Followup

      Rating: 3
primary key
      constraint vs non-unique index+PK", version 8.1.7.4  March
      20, 2003
Reviewer:  Joseph Fabiano  from
      Philadelphia, PA
Tom,

Can you explain with an example the feature that this
person is describing?  I'm interested in knowing how
to disable the PK, keep the index, perform a direct sqlload, then place the PK
back without recreating the index.  I was able to
disable the PK and keep the index but the index was still
unique.

          Followup:

just

alter table t add constraint t_pk primary key (a,b) DEFERRABLE;

you'll note at that point, the index generated is non-unique.

or,

create index t_idx on t(a,b);
alter table t add constraint t_pk primary key(a,b);

that way -- you'll have a non-deferrable constraint that uses a non-unique index
to enforce the primary key.

      Rating: 4
for a
      partitioned table  March 21, 2003
Reviewer:
      A reader
Hi

For a partitioned table I think your only bet is create the non-unique index
first then add the PK

alter table t add constraint t_pk primary key (a,b) DEFERRABLE;

doesnt work for a partitioned table does it?

          Followup:

ps$tkyte@ORA920> CREATE TABLE t
  2  (
  3    x int ,
  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> alter table t
  2  add constraint t_pk
  3  primary key(x) deferrable;

Table altered.

yes, it does work.

      Rating: 5
yea well but I
      was referring partitioned PK  March 21, 2003

Reviewer:  A reader
Hi

I was referring partitioned index, with your way it is not a partitioned index
doh

          Followup:

so sorry, crystal ball is on the blink again.  Everything is being intrepreted
literraly in the meanwhile ;)

unless  you are partitioning by the primary key -- no go in EITHER case

ops$tkyte@ORA920> alter table t
  2  add constraint t_pk
  3  primary key(x) deferrable
  4  using index local;
alter table t
*
ERROR at line 1:
ORA-14039: partitioning columns must form a subset of key columns of a UNIQUE
index

cannot create the locally partitioned index non-unique index using the all
primary key...

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index t_pk on t(x) local;

Index created.

ops$tkyte@ORA920> alter table t
  2  add constraint t_pk
  3  primary key(x) deferrable ;
alter table t
*
ERROR at line 1:
ORA-00955: name is already used by an existing object

and if it is there , it fails

ops$tkyte@ORA920> drop index t_pk;

Index dropped.

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

ops$tkyte@ORA920> alter table t
  2  add constraint t_pk
  3  primary key(x) deferrable ;
alter table t
*
ERROR at line 1:
ORA-01408: such column list already indexed

as  you can see -- it is trying to create a global non-unique index

the index in this case would have to be a global, non-unique index partitioned
by the PRIMARY KEY values itself.  and that can in fact be done in the
constraint itself:

ops$tkyte@ORA920> alter table t
  2  add constraint t_pk
  3  primary key(x) deferrable
  4  using index
  5  global
  6  partition by range(x)
  7  ( partition p1 values less than (1000),
  8    partition p2 values less than (maxvalue)  );

Table altered.

      Rating: 5
is this a
      typo?  March 21, 2003
Reviewer:
      A reader
Hi

from one of your post you mentioned this:
--------------------------------------------------------
alter table t add constraint t_pk primary key (a,b) DEFERRABLE;

you'll note at that point, the index generated is non-unique.
--------------------------------------------------------

In your last post however you said this:
--------------------------------------------------------
ops$tkyte@ORA920> alter table t
  2  add constraint t_pk
  3  primary key(x) deferrable ;
alter table t
*
ERROR at line 1:
ORA-01408: such column list already indexed

as  you can see -- it is trying to create a unique index
--------------------------------------------------------

is DEFERRABLE trying to create a non-unique index or an unique index...?

          Followup:

corrected to read as:

as  you can see -- it is trying to create a global non-unique index

      Rating: 4
why partition
      column must be part of unique ndex?  March 22, 2003

Reviewer:  A reader
hi

ops$tkyte@ORA920> alter table t
  2  add constraint t_pk
  3  primary key(x) deferrable
  4  using index local;
alter table t
*
ERROR at line 1:
ORA-14039: partitioning columns must form a subset of key columns of a UNIQUE
index

Why we cannot create unique index partitioned locally without involving the
partition column?

Also I have a question regarding unique values in partitions, if I have three
partitions partitioned by date and my primary key is customer_id, can
customer_id repeat in different partitions?

          Followup:

because it would obviate all of the usefulness  of partitioning.

In order to insert a row -- we would have to lock ALL index partitions to
prevent that same value from being inserted in there, there goes availability,
there goes scalability, there goes concurrency -- right out the window.

If you hash partitioned into 10 partions
And you had a local index on (X)
And X was not the partition key (so X=5 could go into ANY partition)
And X had to be unique -- the only way to do that would be to lock the 9 index
partitions you are NOT inserting into (to prevent X=5 from going in there,
goodbye concurrency -- AFTER checking if X=5 was already in there, goodbye
availability -- every partition MUST be available ALL of the time)....

The answer to the last question should be self evident.

IF your primary key for the table is customer id
THEN customer id in the table is unique.

      Rating: 5
I dont
      understand last explanation  March 22, 2003

Reviewer:  A reader
Hi

I dont understand the last explanation at all :-(

Why we have to lock all partitions to insert a row?

If I have a table

empno     hiredate
-----     -----------
1         200110
2         200111
3         200112

hiredate is partition key by year-month

if let´s say we can create an partitioned local unique index on empno (ok its
not possible but to simulate the locking issue...)

I then do

insert into table values (4, 200112);

and the other session just does the same in sert a few seconds after me what
happens..?
Why this would lock the ther three partitions (200210, 11 and 12)?

if I do

insert into table values (5, '');

then this would go to the partitition with maxvalue, would this behaves as
previous insert statement?

          Followup:

say session 1 does:

insert ( 4, 200112 );

and session 2 does

insert ( 4, 200111 )

and session 3 does

insert ( 4, 200110 )

Now, tell me what happens....  All of these go on at the same time bear in mind.
 Unless you locked the partions pertaining to 200111 and 200110 when you
modified the index associated with partition 200112 -- what would prevent
sessions 2 and 3 from creating another empno = 4?

And further, if session 1 did not scan these indexes -- how would session 1 know
that empno = 4 doesn't already exist?

      Rating: 4
Maybe i fail to
      understand...  April 23, 2003
Reviewer:  VJ

Hi,

I want to create a partitioned table. I want to maintain a primary key to the
table. One part of the table partition i would like to truncate and load data on
a daily basis. I am having difficult time achieving this.

---
I want partitioned table and i want primary key, so i wont insert duplicate
records...
---

Kindly help....

Thanks a million
VJ

The steps i followed are here below:
-------
1 create table t7 (
2 id number,
3 name varchar2(30),
4 status varchar2(30),
5 pkey number)
6 partition by range (pkey)
7 (partition p1 values less than (3),
8* partition p2 values less than (6))
SQL> /

Table created.

Elapsed: 00:00:00.78

SQL> alter table t7 add constraint pk_t7 primary key (id,name,status);

Table altered.

Elapsed: 00:00:00.41

SQL> begin
2 for i in 1..10 loop
3 insert into t7 values (i,'vj','A',1);
4 insert into t7 values (i,'dto','A',2);
5 insert into t7 values (i,'vld','N',4);
6 end loop;
7 end;
8 /

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.19

SQL> select count(0) from t7 partition (p1);

COUNT(0)
----------
20

Elapsed: 00:00:00.08
SQL> select count(0) from t7 partition (p2);

COUNT(0)
----------
10

Elapsed: 00:00:00.01

SQL> alter table t7 truncate partition p1;

Table truncated.

Elapsed: 00:00:00.17

SQL> begin
2 for i in 1..10 loop
3 insert into t7 values (i,'vj','A',1);
4 insert into t7 values (i,'dto','A',2);
5 end loop;
6 end;
7 /
begin
*
ERROR at line 1:
ORA-01502: index 'CDW_MKT.PK_T7' or partition of such index is in unusable
state
ORA-06512: at line 3

Elapsed: 00:00:00.02

          Followup:

search for LOCAL on this page and use a LOCALly partitioned index so the
truncate doesn't invalidate the default GLOBAL index that is being used.

      Rating: 2
Maybe i fail to
      understand...  April 23, 2003
Reviewer:  VJ

Tom,

I feel a quick example would do more good listing my requirements.

1) Paritiioned table
2) Maintaining Primary Key/Unique

Thanks a lot in advance
VJ

          Followup:

I already HAVE an example, please -- READ this page.  goto the top, search for
LOCAL (like i said).

READ this page (it has an example of exactly what you asked for already)

READ the documentation -- for unless you do that, I can assure you of one fact
-- you will FAIL miserably in your implementation that uses partition due to
lack of understanding of how it really works.

      Rating: 5
Eyes Wide Shut,
      Now open...  April 24, 2003
Reviewer:  VJ

Tom,

I guess i have read too much documentation on internet, that towards the end of
the day i have started confusing my self every step of the way. Now, I got what
i wanted and implemented it and it is working fine.

Thanks a lot once again,
VJ

      Rating: 5
Globally unique
      primary keys  January 07, 2004
Reviewer:
      A reader
Hi Tom,

For some reason I want to make the PK values in the tables unique in the whole
schema. Which one is better?

1. Define one sequence and used it as the PK value for all primary key columns.
2. Define a sequence for each table and prefix it with table short name and use
it as the PK. for example: 'TXN000001'.

Thanks for your comments.

BTW, the IRID and IVID columns in Designer Repository look extremely largely to
me and they are not generated thru sequences. What's the internal mechanism for
generating them?

          Followup:

or 3

3) create sequence_t1 seq start with 1 increment by 10000;
   create sequence t2_seq start with 2 increment by 10000;
   ...
   create sequence tN_seq start with N increment by 10000;

allows you to have upto 10,000 tables -- each with their own sequence, each
generating unique numbers.

or

4) use sys_guid()

Not familar with designers schema -- sorry.

January 08, 2004 Sameer -- Thanks for the question regarding "IOT
      pctthreshold calculation", version 8.1.6.3.0

      You Asked
Hi Tom,

My block size 8K.

My tablespace DATAAPR setting is as
INITIAL_EXTENT = 40960
NEXT_EXTENT    = 40960
MIN_EXTENT     = 1
PCT_INCREASE   = 50
I created an IOT tablespace without specifying Overflow tablespace as follows

create table empiot (empno number,ename varchar2(580),salary number,constraint
empiot_pk primary key (empno))organization index tablespace dataapr
pctthreshold 10;

Table created.

When i change size of ename column from 580 to 590 i am getting the following
error.

create table empiot (empno number,ename varchar2(590),salary number,
*
ERROR at line 1:
ORA-01429: Index-Organized Table: no data segment to store overflow row-pieces

The error can be resolved by specying the overflow tablespaces, but according to
my table definition
if the entire row is greater than 10% of the blocksize then it will go to
overflow tablespace.

How this calculation is going on . How my definition above is more than
threshold limit of 10%.

I go through the Oracle Manual and also though various white papars. but still
this doubts is not clear.

Any help
Thanks in advance
Sameer

      and we said...
block and index structure overhead.  The 10% is the 10% of the space available
on the block for your row data.  that is about 6400 bytes or so.

Don't forget -- add 3 bytes for the length length of the varchar2, 1 byte for
the null indicator.  1 byte each for the numbers -- 22 bytes apiece for storage
and 1 byte for the length each.  there is the rowid that is stored in the index
entry and so on.  rounding kicks in as well.

So, you get 10% of about 6400 -- not 10% of 8k.
Review &
      Followup

      Rating: 4
IOT and
      overflow row-pieces  January 07, 2004
Reviewer:
      Sami  from NJ,USA
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

Dear Tom,

Let me read your book "Expert one-on-one" Chapter 10. Meantime, small
clarification

(1)
SQL> create table IOT_TEST
(c1 number,
c2 number,
c3 varchar2(4000),
primary key(c1,c2)
)
organization index;

create table IOT_TEST(c1 number,c2 number,c3 varchar2(4000), primary key(c1,c2))
organization index
*
ERROR at line 1:
ORA-01429: Index-Organized Table: no data segment to store overflow row-pieces

(2)
SQL> create table IOT_TEST
(
c1 number,
c2 number,
c3 varchar2(2000),
primary key(c1,c2)
)
organization index;

Table created.

SQL>

Questions:

1) Why ORA-01429 error occurs only for varchar2(2000) and above at the time of
table creation?

2)
Does it mean that IOT_TEST created in step (2) will not go for overflow?
OR
Will I get "Index-Organized Table: no data segment to store overflow row-pieces"
error when the data overflows?

Thanks,
Sami

          Followup:

1) has to do with the block size (read the chapters on indexes and tables in
that same book for the details :)

you need to get at least 2 index entries/leaf node and with a varchar2(4000) and
your block size -- you cannot, hence you need an overflow

2) iot_test in #2 will not overflow.

January 08, 2004 Mohammed -- Thanks for the question regarding
      "Blocks on stored procedure", version 6i

      You Asked
>I have a problem of developing a form that contains a block that contains a
form layout with single record.each record on the form would be driven into two
records at the same table.

>the table schema in the 2nd normal form was
create table consume(
id number,
con_month date,
current_consume number,
last_month_consume number,
constraint con_pk primary key(id,con_month));

as shown the value of the current_consume will be the value of the
last_month_consume at the next month for the same id (there will be redunduncy
in the data)

>the table schema at the 3rd normal form is
create table consume(
id number ,
con_month date,
consume number,
constraint con_pk primary key (id,con_month));

>the block should receive the consume for two months at the same form record as
following:-
-each record on the form will consist of (id,consume1,consume2)
where consume1 is the last_month_consume and consume2 is the current_consume.

-when inserting or updating or deleting this record forms should perform the
same action at two database records (the cosume for the last month, and the
consume for the current month)

>when developing a form based on a stored procedure I find that all the
automatically created triggers(query,update,insert,delete,lock triggers) have
the error (wrong number of arguments on the plsql_table.populate_block function)

>how can i solve this problem? is there another way to develop a form like that
rather than stored procedures?

      and we said...
Arg, another case of "puritism" winning out over "pragmatism".  Your physical
model should be what is easy to use, not what is "mathematically complete".

Well, all you need is a view with an instead of trigger on it.   Best part -- if
you don't like my logic, you can make it be whatever you want.  Then any tool
can update this view.  Note in my view I make use of some features that require
Oracle8i release 2 EE (816) and above.  If you don't have the lag feature,
you'll have to do a less efficient self join or something.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table consume
  2  ( id number,
  3    con_month date,
  4    consume number,
  5    constraint con_pk primary key(id,con_month)
  6  )
  7  /

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into consume
  2  select 1,
  3         add_months(trunc(sysdate,'month'),-(rownum-1)),
  4         dbms_random.value(1,10000)
  5    from all_users
  6   where rownum <= 10;

10 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into consume
  2  select 2,
  3         add_months(trunc(sysdate,'month'),-(rownum-1)),
  4         dbms_random.value(1,10000)
  5    from all_users
  6   where rownum <= 10;

10 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace view v
  2  as
  3  select id,
  4         con_month,
  5         consume,
  6         lag(con_month) over (partition by id order by con_month)
last_con_month,
  7         lag(consume  ) over (partition by id order by con_month)
last_consume
  8    from consume
  9  /

View created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from v;

        ID CON_MONTH    CONSUME LAST_CON_ LAST_CONSUME
---------- --------- ---------- --------- ------------
         1 01-OCT-01 1588.74484
         1 01-NOV-01 7836.77643 01-OCT-01   1588.74484
         1 01-DEC-01 5009.63916 01-NOV-01   7836.77643
         1 01-JAN-02 5843.84573 01-DEC-01   5009.63916
         1 01-FEB-02   3988.873 01-JAN-02   5843.84573
         1 01-MAR-02 9751.28069 01-FEB-02     3988.873
         1 01-APR-02 5.24005296 01-MAR-02   9751.28069
         1 01-MAY-02 2236.80541 01-APR-02   5.24005296
         1 01-JUN-02 1059.31979 01-MAY-02   2236.80541
         1 01-JUL-02 7895.42026 01-JUN-02   1059.31979
         2 01-OCT-01 4953.09857
         2 01-NOV-01 4864.87604 01-OCT-01   4953.09857
         2 01-DEC-01 9781.60802 01-NOV-01   4864.87604
         2 01-JAN-02 2291.45303 01-DEC-01   9781.60802
         2 01-FEB-02 7013.68272 01-JAN-02   2291.45303
         2 01-MAR-02 5036.93108 01-FEB-02   7013.68272
         2 01-APR-02 2856.06444 01-MAR-02   5036.93108
         2 01-MAY-02 2710.01854 01-APR-02   2856.06444
         2 01-JUN-02 1186.52456 01-MAY-02   2710.01854
         2 01-JUL-02 284.212951 01-JUN-02   1186.52456

20 rows selected.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create trigger v_insert instead of insert on
v
  2  begin
  3      insert into consume values ( :new.id, :new.con_month, :new.consume );
  4
  5      begin
  6          insert into consume values ( :new.id, :new.last_con_month,
:new.last_consume );
  7      exception
  8          when dup_val_on_index then
  9              update consume
 10                 set consume = :new.last_consume
 11               where id = :new.id
 12                 and con_month = :new.last_con_month;
 13      end;
 14  end;
 15  /

Trigger created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create trigger v_update instead of update on
v
  2  begin
  3      update consume
  4         set consume = :new.consume
  5       where id = :old.id
  6         and con_month = :old.con_month;
  7
  8      update consume
  9         set consume = :new.last_consume
 10       where id = :old.id
 11         and con_month = :old.last_con_month;
 12  end;
 13  /

Trigger created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create trigger v_delete instead of delete on
v
  2  begin
  3      delete from consume where id = :old.id and con_month in (
:old.con_month, :old.last_con_month );
  4  end;
  5  /

Trigger created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into v values ( 3,
trunc(sysdate,'month'), 100, add_months(trunc(sysdate,'month'),-1), 200 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from v where id = 3;

        ID CON_MONTH    CONSUME LAST_CON_ LAST_CONSUME
---------- --------- ---------- --------- ------------
         3 01-JUN-02        200
         3 01-JUL-02        100 01-JUN-02          200

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from consume where id = 3;

        ID CON_MONTH    CONSUME
---------- --------- ----------
         3 01-JUN-02        200
         3 01-JUL-02        100

ops$tkyte@ORA817DEV.US.ORACLE.COM> update v set consume = consume+5,
last_consume = last_consume-5 where id = 3;

2 rows updated.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from v where id = 3;

        ID CON_MONTH    CONSUME LAST_CON_ LAST_CONSUME
---------- --------- ---------- --------- ------------
         3 01-JUN-02        195
         3 01-JUL-02        105 01-JUN-02          195

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from consume where id = 3;

        ID CON_MONTH    CONSUME
---------- --------- ----------
         3 01-JUN-02        195
         3 01-JUL-02        105

ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from v where id = 3;

2 rows deleted.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from v where id = 3;

no rows selected

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from consume where id = 3;

no rows selected

Review &
      Followup

      Rating: 4
Interesting
      approach  July 08, 2002
Reviewer:  andrew
       from ca, usa

      Rating: 2
You didn't
      answer my question  July 28, 2002
Reviewer:
      Mohammed Al Emam  from Egypt
after testing the performance of that solution it takes
3 seconds per transaction(insert,update,delete) from the form 6i, that is not
satisfactory
when i tend to use a block based on a stored procedure it was only to saving the
processinge time your solution didn't help me.

          Followup:

geez, did you try even tuning it?  Perhaps if you took the CONCEPTS and learned
from them, you would be more successful.

Like I said:

Arg, another case of "puritism" winning out over "pragmatism".  Your physical
model should be what is easy to use, not what is "mathematically complete".

Well, all you need is a view with an instead of trigger on it.   Best part -- if

you don't like my logic, you can make it be whatever you want.  Then any tool
can update this view.  Note in my view I make use of some features that require
Oracle8i release 2 EE (816) and above.  If you don't have the lag feature,
you'll have to do a less efficient self join or something.

your data model is wholly inappropriate for your job.  No wonder performance is
somewhat "questionable".  I'll bet -- if you tried just a litttlllleee bit --
you could actual tune this.

Might require a data model change tho -- so your structures actually reflect
your requirements.

And you know what-- if each insert/update/delete takes three seconds -- you have
a problem somewhere.  Look at the logic in the triggers here:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create trigger v_insert instead of insert on
v
  2  begin
  3      insert into consume values ( :new.id, :new.con_month, :new.consume );
  4
  5      begin
  6          insert into consume values ( :new.id, :new.last_con_month,
:new.last_consume );
  7      exception
  8          when dup_val_on_index then
  9              update consume
 10                 set consume = :new.last_consume
 11               where id = :new.id
 12                 and con_month = :new.last_con_month;
 13      end;
 14  end;
 15  /

If that takes 3 seconds dude -- you have a serious problem not at all related to
the question at hand.  Those are TRIVIAL INSERTS -- as long as ID is indexed
(will, it is part of the primary key -- so it is), this is NOT a 3 second
operation.

Same with the other two triggers.  You haven't looked at this problem at ALL or
you've taken my code, rewritten  it horribly and blame it on me!

As a matter of fact, let us try this example with timing on and see what exactly
happens:

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop table consume;

Table dropped.

Elapsed: 00:00:00.19
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table consume
  2  ( id number,
  3    con_month date,
  4    consume number,
  5    constraint con_pk primary key(id,con_month)
  6  )
  7  /

Table created.

Elapsed: 00:00:00.09
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into consume
  2  select 1,
  3         add_months(trunc(sysdate,'month'),-(rownum-1)),
  4         dbms_random.value(1,10000)
  5    from all_users
  6   where rownum <= 10;

10 rows created.

Elapsed: 00:00:00.01
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into consume
  2  select 2,
  3         add_months(trunc(sysdate,'month'),-(rownum-1)),
  4         dbms_random.value(1,10000)
  5    from all_users
  6   where rownum <= 10;

10 rows created.

Elapsed: 00:00:00.01
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace view v
  2  as
  3  select id,
  4         con_month,
  5         consume,
  6         lag(con_month) over (partition by id order by con_month)
last_con_month,
  7         lag(consume  ) over (partition by id order by con_month)
last_consume
  8    from consume
  9  /

View created.

Elapsed: 00:00:00.15
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from v;

        ID CON_MONTH    CONSUME LAST_CON_ LAST_CONSUME
---------- --------- ---------- --------- ------------
         1 01-OCT-01 2619.49826
         1 01-NOV-01 33.2327305 01-OCT-01   2619.49826
         1 01-DEC-01 366.053072 01-NOV-01   33.2327305
         1 01-JAN-02 5045.94192 01-DEC-01   366.053072
         1 01-FEB-02 222.292577 01-JAN-02   5045.94192
         1 01-MAR-02 3963.98258 01-FEB-02   222.292577
         1 01-APR-02 6863.18624 01-MAR-02   3963.98258
         1 01-MAY-02 2075.33373 01-APR-02   6863.18624
         1 01-JUN-02 3862.35327 01-MAY-02   2075.33373
         1 01-JUL-02 5405.54577 01-JUN-02   3862.35327
         2 01-OCT-01 1015.31052
         2 01-NOV-01 479.026351 01-OCT-01   1015.31052
         2 01-DEC-01 5479.70563 01-NOV-01   479.026351
         2 01-JAN-02 1217.85038 01-DEC-01   5479.70563
         2 01-FEB-02 476.046583 01-JAN-02   1217.85038
         2 01-MAR-02 2296.97046 01-FEB-02   476.046583
         2 01-APR-02 1388.89086 01-MAR-02   2296.97046
         2 01-MAY-02 2468.05254 01-APR-02   1388.89086
         2 01-JUN-02 6708.96479 01-MAY-02   2468.05254
         2 01-JUL-02 4571.13817 01-JUN-02   6708.96479

20 rows selected.

Elapsed: 00:00:00.01
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create trigger v_insert instead of insert on
v
  2  begin
  3      insert into consume values ( :new.id, :new.con_month, :new.consume );
  4
  5      begin
  6          insert into consume values ( :new.id, :new.last_con_month,
:new.last_consume );
  7      exception
  8          when dup_val_on_index then
  9              update consume
 10                 set consume = :new.last_consume
 11               where id = :new.id
 12                 and con_month = :new.last_con_month;
 13      end;
 14  end;
 15  /

Trigger created.

Elapsed: 00:00:00.10
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create trigger v_update instead of update on
v
  2  begin
  3      update consume
  4         set consume = :new.consume
  5       where id = :old.id
  6         and con_month = :old.con_month;
  7
  8      update consume
  9         set consume = :new.last_consume
 10       where id = :old.id
 11         and con_month = :old.last_con_month;
 12  end;
 13  /

Trigger created.

Elapsed: 00:00:00.04
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create trigger v_delete instead of delete on
v
  2  begin
  3      delete from consume where id = :old.id and con_month in (
:old.con_month, :old.last_con_month );
  4  end;
  5  /

Trigger created.

Elapsed: 00:00:00.05
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into v values ( 3,
trunc(sysdate,'month'), 100, add_months(trunc(sysdate,'month'),-1), 200 );

1 row created.

Elapsed: 00:00:00.01
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from v where id = 3;

        ID CON_MONTH    CONSUME LAST_CON_ LAST_CONSUME
---------- --------- ---------- --------- ------------
         3 01-JUN-02        200
         3 01-JUL-02        100 01-JUN-02          200

Elapsed: 00:00:00.00
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from consume where id = 3;

        ID CON_MONTH    CONSUME
---------- --------- ----------
         3 01-JUN-02        200
         3 01-JUL-02        100

Elapsed: 00:00:00.01
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> update v set consume = consume+5,
last_consume = last_consume-5 where id = 3;

2 rows updated.

Elapsed: 00:00:00.01
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from v where id = 3;

        ID CON_MONTH    CONSUME LAST_CON_ LAST_CONSUME
---------- --------- ---------- --------- ------------
         3 01-JUN-02        195
         3 01-JUL-02        105 01-JUN-02          195

Elapsed: 00:00:00.00
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from consume where id = 3;

        ID CON_MONTH    CONSUME
---------- --------- ----------
         3 01-JUN-02        195
         3 01-JUL-02        105

Elapsed: 00:00:00.00
ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from v where id = 3;

2 rows deleted.

Elapsed: 00:00:00.01
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from v where id = 3;

no rows selected

Elapsed: 00:00:00.00
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from consume where id = 3;

no rows selected

Elapsed: 00:00:00.00
ops$tkyte@ORA817DEV.US.ORACLE.COM>

Yup, exactly as I thought, if you are seeing 2 to 3 seconds, you've done
something incredibly wrong on your end.

I don't think you know the tools (sql_trace, tkprof, dbms_profiler) available to
you perhaps for diagnosing why this is not performing well on your end?  Start
with tkprof:

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

      Rating: 5
Why doubly
      Insert Tom ?  July 29, 2002
Reviewer:  Pasko
       from HH, Germany
Hi Tom,
From your Insert Trigger , i see :
 begin
  3      insert into consume values ( :new.id, :new.con_month, :new.consume );
  4
  5      begin
  6          insert into consume values ( :new.id, :new.last_con_month,
:new.last_consume );
  7      exception
  8          when dup_val_on_index then
  9              update consume
 10                 set consume = :new.last_consume
 11               where id = :new.id
 12                 and con_month = :new.last_con_month;
 13      end;
 14  end;
 15  /

Do we really need the INSERT Statement with number 3. above ?

Reagards,

          Followup:

Only if you want it to work I suppose?

You insert into the view. We have to insert into the base table else the row
goes *nowhere*.

      Rating: 3
"Blocks on
      stored procedure", version 8i  July 29, 2002

Reviewer:  Jamil Shaibani  from Saudi
Hi Tom

When I try to run this script I got ORACLE Error ORA-00439

 create or replace view v
    as
    select id,
           con_month,
           consume,
           lag(con_month) over (partition by id order by con_month)
last_con_month,
           lag(consume  ) over (partition by id order by con_month)
last_consume
      from consume

This is the error
ERROR at line 1:
ORA-00439: feature not enabled: OLAP Window Functions
Please give me an example how to solve this problem
Thank very much in advance

Jamil

          Followup:

You need to have the Oracle Enterprise Edition in order to use the lag/lead
functions.

      Rating: 5
Hi Jamil, you
      must have Oracle 8i Enterprize Edition  July 29, 2002

Reviewer:  Pasko
Hi Jamil,

I think  the answer to your Question is right there:

"Note in my view I make use of some features that require
Oracle8i release 2 EE (816) and above.  If you don't have the lag feature,
you'll have to do a less efficient self join or something"

      Rating: 3
"Puritism" vs.
      "Pragmatism"  July 29, 2002
Reviewer:
      Robert  from Memphis, TN USA
Tom,

Your initial statement caught my attention...

<quote>
Arg, another case of "puritism" winning out over "pragmatism".  Your physical
model should be what is easy to use, not what is "mathematically complete".
<quote>

I beg to humbly differ a bit.... The reason we design with "puritism" and to be
"mathematically complete" is because this is 'proven' to be the more solid
design... and since we know that our code will always be changing, we design
with the most solid structure, so that we will have a solid foundation on which
to make our modifications; additionally, we have a solid "mathematical"
foundation (design) with which to link our (sub)system with other (sub)systems.
If everything is mathematically and solidly designed, then everything has a
*better chance* of being sucessfully modified and linking with other things. ;)

Thanks,

Robert.

          Followup:

and then there is this whole thing about getting things done, getting them done
today and making sure they perform.

Do you see -- this person has requirements that their "mathematically, provably
complete" model doesn't do very well.  What then?  Data structures should be
designed to support application, not the other way around.`

      Rating:
      3
Ok  January 08, 2004
Reviewer:
      Kumar  from Pune,India
Dear Sir,
Fine and wish the same from you.
I have a table
  sql>create table t(x number);
  sql> select * from t;
      x
     ---
      3
      7
      11
I need a query that gives me output as
  x   Range   Missing_values
  --  -----   --------------
  3   3->7    4,5,6
  7   7->11   8,9,10
Can I use a lag or lead function here?
Could you please with code?
Thanks in advance.
Bye!

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4972509201538
but forget about column 3, missing values (it is sort of "intuitively already
known" anyway).  reason:

select * from t;

X
-----
1
5000

tell me how big column 3 would be and what is the biggest varchar2 you could
have in sql and what value would a string of 2,3,4,.......,4999 provide....

January 08, 2004 Vipin -- Thanks for the question regarding
      "DBMS_RLS package", version 8.1.7

      You Asked
Hi Tom,

We had a specific requirement where in we needed to screen some of our table
rows based on the the user who has logged in. Of course the first glow in any
one's brain would be DBMS_RLS. But our DBA gave the following reasons
discouraging the use of DBMS_RLS in our application.

"The DBMS_RLS package cannot be granted for general use for oracle databases
below oracle version 9i.

The problem is that row level security (or VPD) in version 8i conflicts with
backup tools. The result is that export backups fail for tables covered by
dbms_rls.

Again, the general use of the DBMS_RLS package is permitted in Oracle version
9i--as the constraints with export backup is resolved in the release. "

Our database is Oracle 8i(8.1.7).

Is the above arguement correct and if so could u just enlighten me why is this
restriction?

Thanks in advance as always

      and we said...
the DBA is not correct.

first, export is not a backup tool.  It is a data extraction tool and as such is
properly protected from extracting data the user running it should not see.

An export as SYSDBA would not be subject to such restrictions and a full
database export WHICH IS NOT A BACKUP would be done as SYSDBA.

So, if your DBA uses the proper accounts, they'll get what they need.
If your DBA uses export as their backup, you need a new DBA though.

Here is a short excerpt from my book "Expert one on one Oracle" on this very
topic:

...
Export/Import

We mentioned this issue previously.  Care must be taken when using the EXP tool
to export data and IMP to import it.  Since the two issues are different - we'll
look at each in turn.  For this caveat, we'll extend the prior example by
changing the policy T_POLICY.  We'll have it be in effect for INSERTS as well as
SELECTS this time:

tkyte@TKYTE816> begin
  2     dbms_rls.drop_policy( 'TKYTE', 'T', 'T_POLICY' );
  3  end;
  4  /
PL/SQL procedure successfully completed.

tkyte@TKYTE816> 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.

Once we do that, the following behavior will be observed:

tkyte@TKYTE816> delete from t;
1 row deleted.

tkyte@TKYTE816> commit;
Commit complete.

tkyte@TKYTE816> exec set_ctx( null );
PL/SQL procedure successfully completed.

tkyte@TKYTE816> insert into t values ( 1 );
insert into t values ( 1 )
            *
ERROR at line 1:
ORA-28115: policy with check option violation

tkyte@TKYTE816> exec set_ctx( 0 ) ;
PL/SQL procedure successfully completed.

tkyte@TKYTE816> insert into t values ( 1 );
1 row created.

So now the context must be set to SELECT and INSERT data.

Export  Issues

By default export will execute in a "conventional" path mode.  It will use SQL
to read all of the data.  If we use EXP to extract the table T from the database
- the following will be observed (note that T has 1 row in it right now from our
insert above)

C:\fgac>exp userid=tkyte/tkyte tables=t

Export: Release 8.1.6.0.0 - Production on Mon Apr 16 16:29:25 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 done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set

About to export specified tables via Conventional Path ...
EXP-00079: Data in table "T" is protected. Conventional path may only be
exporting partial table.
. . exporting table                              T          0 rows exported
Export terminated successfully with warnings.

Notice that EXP was kind enough to notify us that the table we exported MAY be
only partially exported since the conventional path was used.  The solution to
this is to use the SYS (or any account connected as SYSDBA) account to export.
Fine Grained Access Control is not in effect for SYS:

C:\fgac>exp userid=sys/manager tables=tkyte.t

Export: Release 8.1.6.0.0 - Production on Mon Apr 16 16:35:21 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 done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set

About to export specified tables via Conventional Path ...
Current user changed to TKYTE
. . exporting table                              T          1 rows exported
Export terminated successfully without warnings.

Another valid option would be to use DBMS_RLS.ENABLE_POLICY to disable the
policy temporarily and re-enable it after the export.  This is not entirely
desirable as the table is left unprotected during that period of time.

Note: in some releases of Oracle8I, release 8.1.5, a direct path export bypassed
fine grain access control erroneously.  That is, by adding direct=true, all of
the data would be exported.  You should not rely on this as it has since been
corrected in all later releases.  In those releases you will get:

About to export specified tables via Direct Path ...
EXP-00080: Data in table "T" is protected. Using conventional mode.
EXP-00079: Data in table "T" is protected. Conventional path may only?

Export will automatically drop into a conventional path export for protected
tables.

Import Issues

This is only an issue if you have a Fine Grained Access Control Policy on a
table that is in effect for INSERTS with the UPDATE_CHECK set to TRUE.  In this
case, IMP may reject some rows if your predicate function returns a predicate
they cannot satisfy.  In the above example, this is the case - unless we set the
context, no rows can be inserted (the context value is NULL).  Hence, if we take
the above EXP we created and try to import the data back in:

C:\fgac>imp userid=tkyte/tkyte full=y ignore=y

Import: Release 8.1.6.0.0 - Production on Mon Apr 16 16:37: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 SYS, not by you

import done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set
. importing SYS's objects into TKYTE
. . importing table                            "T"
IMP-00058: ORACLE error 28115 encountered
ORA-28115: policy with check option violation
IMP-00017: following statement failed with ORACLE error 28101:
 "BEGIN   DBMS_RLS.ADD_POLICY('TKYTE', 'T','T_POLICY','TKYTE','RLS_EXAMP','SE"
 "LECT,INSERT',TRUE,TRUE); END;"
IMP-00003: ORACLE error 28101 encountered
ORA-28101: policy already exists
ORA-06512: at "SYS.DBMS_RLS", line 0
ORA-06512: at line 1
Import terminated successfully with warnings.

And our rows are not inserted.  Once again, the work around is to import as SYS
or SYSDBA:

C:\fgac>imp userid=sys/manager full=y ignore=y

Import: Release 8.1.6.0.0 - Production on Mon Apr 16 16:40:56 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 SYS's objects into SYS
. importing TKYTE's objects into TKYTE
. . importing table                            "T"          1 rows imported

Another valid option would be to use DBMS_RLS.ENABLE_POLICY to disable the
policy temporarily and re-enable it after the import.  This is not entirely
desirable as the table is left unprotected during that period of time.

Review &
      Followup

      Rating:
      5
  June 06, 2003
Reviewer:
      A reader

      Rating:
      4
dbms_rls.add_policy parameters  January 07, 2004

Reviewer:  Andy  from Germany
A quick question with regard to the add_policy parameters STATEMENT_TYPES and
UPDATE_CHECK. You say in your book (Expert 1 on 1) p.921 that UPDATE_CHECK is
used to enable/disable checks when processing INSERTs and UPDATEs. If
STATEMENT_TYPES is set to "select, insert, update, delete", and UPDATE_CHECKS =
False (i.e. policy not applied to INSERT or UPDATE), what happens? When I've
tried it out, it seems that the policy is bypassed on insert/update due to the
setting of UPDATE_CHECKS, overriding the setting of STATEMENT_TYPES? Is my
understanding correct?

          Followup:

it is not bypassed, not at all.

a "with check option" works just like the with check option on a view.  If you
update the data to data you cannot "see", it fails (that is with check option).
without the check option, you can update data you are allowed to update to a
value that makes it so you "cannot see it".  consider the difference:

ops$tkyte@ORA9IR2> create table t ( x int, y int );
Table created.

ops$tkyte@ORA9IR2> create or replace view v as select * from t;
View created.

ops$tkyte@ORA9IR2> create or replace function rls_examp( x in varchar2, y in
varchar2 )
  2  return varchar2
  3  as
  4  begin
  5          return 'x > 0';
  6  end;
  7  /
Function created.

so, we can only "see" x > 0 data (and update it and so on...)

ops$tkyte@ORA9IR2> insert into t values ( -1, 1 );
1 row created.

ops$tkyte@ORA9IR2> insert into t values (       0, 1 );
1 row created.

ops$tkyte@ORA9IR2> insert into t values (       1, 1 );
1 row created.

ops$tkyte@ORA9IR2> commit;

Commit complete.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select * from t;

         X          Y
---------- ----------
        -1          1
         0          1
         1          1

ops$tkyte@ORA9IR2> select * from v;

         X          Y
---------- ----------
         1          1

so, that shows the policy is in place -- we can only see one row in V

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> update v set y = 2;

1 row updated.

we can only update a row.

ops$tkyte@ORA9IR2> select * from t;

         X          Y
---------- ----------
        -1          1
         0          1
         1          2

and only one row is updated.  the RLS is not bypassed -- however:

ops$tkyte@ORA9IR2> select * from v;

         X          Y
---------- ----------
         1          2

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> update v set x = -1;

1 row updated.

ops$tkyte@ORA9IR2> select * from t;

         X          Y
---------- ----------
        -1          1
         0          1
        -1          2

ops$tkyte@ORA9IR2> select * from v;

no rows selected

we can make rows disappear -- thats where the check option would kick in:

ops$tkyte@ORA9IR2> rollback;

Rollback complete.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> begin
  2     dbms_rls.drop_policy( user, 'V', 'T_POLICY' );
  3  end;
  4  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> begin
  2     dbms_rls.add_policy
  3     ( object_name => 'V',
  4       policy_name => 'T_POLICY',
  5       policy_function => 'rls_examp',
  6       statement_types => 'select, insert, update, delete',
  7       update_check    => TRUE );
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> select * from t;

         X          Y
---------- ----------
        -1          1
         0          1
         1          1

ops$tkyte@ORA9IR2> select * from v;

         X          Y
---------- ----------
         1          1

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> update v set y = 2;

1 row updated.

ops$tkyte@ORA9IR2> select * from t;

         X          Y
---------- ----------
        -1          1
         0          1
         1          2

ops$tkyte@ORA9IR2> select * from v;

         X          Y
---------- ----------
         1          2

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> update v set x = -1;
update v set x = -1
       *
ERROR at line 1:
ORA-28115: policy with check option violation

there is the defintion of the check option kicking in

ops$tkyte@ORA9IR2> select * from t;

         X          Y
---------- ----------
        -1          1
         0          1
         1          2

ops$tkyte@ORA9IR2> select * from v;

         X          Y
---------- ----------
         1          2

ops$tkyte@ORA9IR2> rollback;

Rollback complete.

      Rating: 4
Still not
      sure...  January 07, 2004
Reviewer:  Andy
       from Germany
Thanks for your reply, Tom. Still not sure about this, tho'. I understand how
the "check option" works, but it's the STATEMENT_TYPES setting that's confusing
me: in what way is a check option different to "applying a policy to a
statement"? The docs simply say:

STATEMENT_TYPES = Statement types the policy will apply.

In your example above, where you make the row disappear, it seems to me that the
policy is not "applied to" the UPDATE statement at all.

          Followup:

the policy was applied to the update -- it only updated a single row. if the
policy was NOT applied, all three rows would have been updated.

that row "appears" to have disappeared -- because we updated that single row to
a value we can no longer see.

The POLICY is applied to the update.

the POLICY initially did not have "check that you can still see it after the
update" turned on.

      Rating:
      5
  January 07, 2004
Reviewer:
      A reader
Great Stuff Tom!!

      Rating: 5
One more
      question...  January 08, 2004
Reviewer:
      Andy  from Germany
Thanks for your explanation, Tom - things are clearer now! So the policy in
effect reduces my table to a view so that the statements only operate on the
reduced set of rows. But what does it mean in practice to include INSERT against
STATEMENT_TYPES? Won't the affect be the same if INSERT is ommitted?

          Followup:

insert without check option -> insert ANY ROW YOU WANT.  (no meaning really)

insert with check option -> insert only rows you can see (great meaning)

January 08, 2004 Mike -- Thanks for the question regarding
      "Aggregate text field searching", version 9.1.0.2

      You Asked
A developer in my company has recently begun lamenting that Oracle can't search
more than one text field at a time.  He has also mentioned in a mild, ribbing
way, that mySQL has a generic function, built into the database, that allows a
user to search for a fragment of text across all fields of a table without
needing to specify the specific columns.

As a programmer I can think of many ways to "code" this (e.g. extra field where
all text from varchar fields is concatenated as a After Update trigger) but none
that don't overly complicate every table I create.  I have heard that the mySQL
version isn't indexed but is reasonably efficient.  This feature intruiges me as
many of the searching interfaces into the database aren't coded by my group and
any built-in features would implicitly be available to the end users could help
them (and lower my support costs).

If you had to write such functionality how might you start?  Or, hopefully, is
this functionallity already available and I just need to do more research.  I am
a long-time reader of this site and I know you are probably going to say "You
should talk to the users and find out specifically what columns and what tables
they want to search, build indexes, and call it a day".  I can't, unfortunately,
as they don't know what table or even what fields the data resides in that they
are searching for and this "generic" search across a table would be very useful
for them, and the client is (nearly) always right.

Thanks for any ideas you might throw my way in advance.

      and we said...
There are quite a few ways to do this.

The first that jumps to mind is a ctxcat index:

http://download-west.oracle.com/docs/cd/B10501_01/text.920/a96517/ind.htm#1011701
another is a text index on a function you write (this is how asktom works) that
actually indexes any data you want to assemble.  See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:440419921146
for a simple example. Here on asktom I store the QUESTION you asked in one
table.  I store the ANSWER I gave in another table.  I store the followups you
supply in yet a third table (many rows -- one row for each of the followups you
guys put and one row for my followup to your followup).

Now, when you search, I basically say:

select id from question_table where contains( indexed_text, 'your search') > 0

there you go -- I just searched 3 tables -- ACROSS many rows -- treating them as
if they were one row in some virtual table somewhere.  Hmm, does mysql do that?

Also -- put 1,000,000 rows in mysql with 1000 columns.  Now -- using any
technique mysql has, any at all -- perform a wildcard search on that data and
return to me the answer in under 0.5 seconds.  Now, do that in Oracle with
either of the above options (and you can).  Scale that 1 million up and up and
up and see what happens.  Hmmm

So, I would probably use intermedia here -- glue the fields together -- index
that and let them use keyword, fuzzy, soundex, near, ands, ors, etc etc etc etc
etc...

or -- it would be trivial to code a stored procedure that returns a ref cursor
that dynamically built a where clause that where'ed on each field based on the
columns it discovered in the data dictionary.  should take about 3 minutes to
code that one (and hey -- it did!)

scott@ORA920.US.ORACLE.COM> create or replace procedure query_table( p_tname in
varchar2,
  2                                           p_string in varchar2,
  3
     p_result_set out sys_refcursor )
  4  authid current_user
  5  as
  6          l_query long := 'select * from ' || p_tname || ' where 1=0 ';
  7  begin
  8          dbms_application_info.set_client_info( '%' || p_string || '%' );
  9          for x in ( select column_name from user_tab_columns where
table_name = upper(p_tname) )
 10          loop
 11                  l_query := l_query || ' or ' || x.column_name || ' like
sys_context(''userenv'',''client_info'') ';
 12          end loop;
 13          open p_result_set for l_query;
 14  end;
 15  /

Procedure created.

scott@ORA920.US.ORACLE.COM>
scott@ORA920.US.ORACLE.COM> variable x refcursor
scott@ORA920.US.ORACLE.COM> set autoprint on
scott@ORA920.US.ORACLE.COM> exec query_table( 'emp', 'CLERK', :x );

PL/SQL procedure successfully completed.

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
----------
      7369 SMITH      CLERK           7902 17-DEC-80        800
  20
      7876 ADAMS      CLERK           7788 23-MAY-87       1100
  20
      7900 JAMES      CLERK           7698 03-DEC-81        950
  30
      7934 MILLER     CLERK           7782 23-JAN-82       1300
  10

scott@ORA920.US.ORACLE.COM> exec query_table( 'dept', 'BOSTON', :x );

PL/SQL procedure successfully completed.

    DEPTNO DNAME          LOC
---------- -------------- -------------
        40 OPERATIONS     BOSTON

but that would be about as efficient as mysql could get it -- no indexes, full
scan -- brute force every field.
Review &
      Followup

      Rating: 4
Follow
      up  November 04, 2002
Reviewer:  Mike
       from Los Angeles, CA
Thank you.  This should do the trick.  A small follow up:  How exactly to you
create a CTXCAT index across multiple fields in a table?  I have found several
examples that index a single text field and a date field but none that give the
example of multiple text fields.  If possible I would prefer to offer the
developers written by Oracle as opposed to some function I would write (such as
the brute force method).  Do you have an example up your sleve?  Thanks again
for the first reponse.

          Followup:

Well, the ctxcat index is mostly about making what they wanted to do more
efficient:

...
You use the CATSEARCH operator with a CTXCAT index mainly to improve mixed query
performance. You specify your text query condition with text_query and your
structured condition with structured_query. Internally, Oracle Text uses a
combined b-tree index on text and structured columns to quickly produce results
satisfying the query.
.....

so you could mix text and relational predicates together more efficiently.  To
directly address the original question

o the procedure does that for any table, showing that if Oracle forgot to add a
feature, adding that feature can be very easy in most cases.

o the text index on the function makes it very very efficient, if you have a set
of tables you know you want to do this to (and with more then a couple hundred
rows -- you will be needing that cabability)

      Rating: 4
Sweet! But no
      work across a dblink :-(  May 09, 2003
Reviewer:
      steve shiflett  from petaluma, ca
Needed this to work across a dblink.  Reason being that I didn't have rights to
create functions/procedures on that instance.  So, created the functions on my
own instance:
------------------------
function qt_like_dcab( p_tname in  varchar2,
                       p_string in varchar2

                        ) return util.generic_cursor authid current_user
as
    v_query long := 'select * from ' || p_tname || '@dcab where 1=0 ';
    v_result_set util.generic_cursor;
begin
    dbms_application_info.set_client_info( '%' || p_string || '%' );
    for x in ( select column_name from user_tab_columns where table_name =
upper(p_tname))
    loop
        v_query := v_query || ' or ' || x.column_name || ' like
sys_context(''userenv'',''client_info'') ';
    end loop;
    open v_result_set for v_query;
    return v_result_set;
 end;
------------------------
but got the error -

ORA-02070: database DCAB does not support operator SYS_CONTEXT in this context
ORA-06512: at "XMENORA.QT_LIKE_DCAB", line 13
ORA-06512: at line 1

          Followup:

use a regular bind variable.  you do not need sys_context.

      Rating:
      3
OK  January 04, 2004
Reviewer:  Ram
       from Bangalore,India
Dear Sir,
 Suppose if I have a text string like
  str := '4 score and 7 years ago'
 How can I delete the word 'score' from it ?
 and also If I want to insert a word like 'Cruel' into
  'Hello world' so that it appears as 'Hello Cruel World'
  Does Oracle has any inbuilt packages to do deletes and inserts from a string?
Thanks in advance.
Bye!

          Followup:

replace, instr, substr, translate -- lots of string functions and in 10g, there
will be a regular expression suite of functions as well.

      Rating:
      2
OK  January 05, 2004
Reviewer:  Ram
       from Bangalore,India
Dear Sir,
Thanks for your response.Could you please specify some
simple examples for insert and delete of words from a sentence?.Sorry for the
disturbance.
Thanks in advance.
Bye!

          Followup:

ops$tkyte@ORA920PC> select replace( 'Hello there world', 'there', '' ) from
dual;

REPLACE('HEL
------------
Hello  world

this is pretty basic stuff -- functions like replace, substr, instr exist in
virtually all programming languages -- use the same techniques you would in any
language.

      Rating:
      5
Excellent!  January 05, 2004
Reviewer:
      Phil  from London, UK
This is great Tom and I think it may be altered to addresse a problem which I
have a few ideas about but think (actually I know!) you may well have a better
one.

I have been tasked with building an interface from one Oracle instance (actually
we may add more in future) to another. It needs to collect data meeting a
certain condition from instance A and place a subset of this in an interface
table in Instance B periodically. This needs to be automated, and the schedule
of each collection needs to be controlled independently. In addition, the data
following transformation by the core app (which I am afraid is not Oracle) needs
to be updated back to the source systems. I hope this makes enough sense!

One idea I have is as follows; a control table in the interface schema lists the
target tables, where clause and update statement for each external source table.
A dbms job then runs which calls a SP to dynamically create a ref cursor on the
external table and where clause. I've got that far - I need to open and loop
through this query but can not - the number of columns is always different. I
could make it the same as the interface table I guess and pad the query to match
that but it seems a bit awkward.

Following extraction to the interface table the source is updated so that
subsequent calls do not retrieve the data a 2nd time. And after a status change
in the interface table, key elements of the data is updated back in the source
tables.

I am sure you will have seen this requirement before and guess you may have a
more elegant solution. Based on the ref cursor approach in this questionI think
it could be used in this case?

          Followup:

if you can procedurally "build a query", "fetch it", "place into interface
table", then you can build a single insert into select from

that is what you should do -- insert into interface_table (columns.... ) select
<something>

I don't know what you mean by "padding"  -- you would just select whatever
columns you wanted?

don't do this procedurally, it's a single insert

      Rating: 5
SELECT
      statement only?  January 05, 2004
Reviewer:
      A reader
OK, that makes sense - I think sometimes a design can overlook a more
straightforward approach so thanks for bringing me down to earth!

However, we need to ensure records only come across once. Would it be best to
insert into abc(x,y,z) select e,f,g from blah where status=x and then update
status=y to control this or open a cursor for update in your opinion?

We intend to use a dbms_job to look for records that need transfer and want to
call this as frequently as possible. Is there a limitation on this frequency as
we propose using an interval of 10 seconds? We have a horrible limitation that
we can not alter the source app (ie we can't add a trigger on the source table)

          Followup:

do you have to update the records as you pull them?  you don't have a timestamp
or some other field that would let you easily recognize "new records"?

(you cannot add a trigger, but you can update the tables directly??)

      Rating:
      5
  January 06, 2004
Reviewer:
      A reader
Tom, yes there is a timestamp so we could use a control table to hold the last
time we pulled and go for records after that. But, we are accessing tables from
other instances - and so there is a small danger with frequent polling that the
clocks could differ slightly (I think?). A pulled record gets updated in the
source system so I think the most straightforward would be a cursor for update.
However, this complicates the matter a little as we then are not following your
advice of just using an insert. And as we want to make it dynamic to allow extra
tables to be easily added this will add to that complexity.
Just going back a step - as this is dynamic, will the performance be less using
execute immediate or ref cursors?
Thanks for taking the time to address this for us!

          Followup:

no problem with clocks.

You start with:

select max(timestamp_col) from T@remote;

You have the LAST timestamp rembmered from last time -- select that out as well:

select last_timestamp from your_table;
if no data found (first refresh) then last_timestamp = jan 1'st 1970 or
something.

Now, you

insert into your_copy
select * from t@remote where timestamp > last_timestamp and timestamp <=
max_timestamp;

update or insert into your_table set last_timestamp = max_timestamp;

commit;

      Rating: 5
Thanks a
      million!  January 06, 2004
Reviewer:
      A reader

      Rating: 4
Using TIMESTAMP
      to pick up changes  January 06, 2004
Reviewer:
      Gary  from Sydney, Aus
We've faced an issue in a similar situation where entries can be missed in this
process.

Consider:
 Session 1 inserts its record at 11:59:00 but doesn't commit until 12:01:00 (eg
waiting for another lock or a user to hit commit).
 Session 2 inserts its record at 11:59:30 and commits immediately.

 Your job comes along at 12:00, picks up the session 2 change but cannot see the
uncommitted Session 1 change.
 Next run comes along, sees its last timestamp as 11:59:30, and so ignores the
Session 1 change from 30 seconds eralier.

Having a flag to update would not encounter this issue.

          Followup:

ahh -- very very VERY good point.

if the other guy comes back -- can we use a snapshot log?

      Rating: 5
Good
      point  January 07, 2004
Reviewer:
      A reader
Yes this is a potential problem but I was working on the assumption that the
commit was the time the lastupdated field is populated. If not maybe a snapshot
would be better but this would make it more complex and have an effect on
performance.

The goal is to be able to react within seconds of a change and this is without
modifying the source app at all (no triggers afraid). I was going to use a
dbms_job and this lastupdated field but can use a status too which may resolve
it.

Tom, what is the max frequency of a dbms_job? I have a simple test that inserts
a row every 3 seconds and it seems to run less frequently that that, inserting
two at a time.

          Followup:

it is about 10 seconds in the latest software.

are these 9ir2 databases?  there is another option, streams.

      Rating: 5
dbms_job
      minimum interval 8i  January 07, 2004
Reviewer:
      Phil  from UK
No, afraid they are not the latest, the instances will be on 8.1.7. Is this less
frequent?

          Followup:

no, not less frequent -- just no streams, less options.

      Rating:
      3
OK  January 08, 2004
Reviewer:
      Roselyn  from London,UK
Dear sir,
 In the string 'How now brown cow',How can we exchange words
'How and Now' so that string appears as 'now How brown cow'?
Could you please show me the way to do it?
Thanks in advance.
Bye!

          Followup:

ops$tkyte@ORA920PC> select replace( replace( replace( 'How now brown cow',
'How', '$X$X$' ),
  2  'now', 'How'), '$X$X$', 'now' )
  3    from dual;

REPLACE(REPLACE(R
-----------------
now How brown cow

      Rating: 4
Aggregate text
      field searching  February 16, 2004
Reviewer:
      Senthil Swaminathan  from NJ, USA

January 08, 2004 david -- Thanks for the question regarding "How
      control file (SQL*LOADER) handle multiple different format date?", version
      8.1.6

      You Asked
Dear Tom,

I'm encountered with this type of problem how can SQL*Loader
control multiple date formats.  I received the flat file
contained multiple date formats below.  I used "DECODE" function
but could not figure how make it work in control file.  Would
you please help me or give me some hints how the control file
works with multiple date formats.

Thanks
David

LOAD DATA
INFILE 'C:\TEMP\benefit.dat'
INTO TABLE NXBenefit
FIELDS TERMINATED BY ","
(plan,
 effective                    DATE
"DECODE(effective,'DD-MM-RR',")--I'm get stuck

BEGINDATA
dental plan, 01-11-01
vision plan, 03/05/01
vision plan, 03.05.01
vision plan, jan-01-01
vision plan, 01-jan-01
vision plan, 01-jan-2001

      and we said...
What I recommand here for simplicity is to write your own
"to_date" function.  The decode would be plain "ugly" if you
could even write it.  It would be a massively nested decode
(decodes of decodes of decodes) to handle the nested if then
else conditions.

do this:

ops$tkyte@ORA8I.WORLD> create or replace package todate_pkg
  2  as
  3      function thedate
  4               ( p_string in varchar2 )
  5               return date;
  6  end;
  7  /

Package created.

ops$tkyte@ORA8I.WORLD> create or replace package body todate_pkg
  2  as
  3
  4  type fmtArray is table of varchar2(30);
  5
  6  g_fmts fmtArray :=
  7         fmtArray( 'dd-mm-rr', 'dd/mm/rr',
  8                   'dd.mm.rr', 'mon-dd-rr',
  9                   'dd-mon-rrrr' );
 10
 11  function thedate( p_string in varchar2 )
 12  return date
 13  is
 14      return_value date;
 15  begin
 16      for i in 1 .. g_fmts.count
 17      loop
 18      begin
 19          return_value := to_date(p_string,g_fmts(i) );
 20          exit;
 21      exception
 22          when others then null;
 23      end;
 24      end loop;
 25
 26      if ( return_value is null ) then
 27          raise PROGRAM_ERROR;
 28      end if;
 29      return return_value;
 30  end;
 31
 32  end;
 33  /

Package body created.

and then load using:

LOAD DATA
INFILE *
INTO TABLE NXBenefit
FIELDS TERMINATED BY ","
(plan,
 effective "todate_pkg.thedate(:effective)"
)
BEGINDATA
dental plan, 01-11-01
vision plan, 03/05/01
vision plan, 03.05.01
vision plan, jan-01-01
vision plan, 01-jan-01
vision plan, 01-jan-2001

Review &
      Followup

      Rating: 5
Excellent
      Technique  January 24, 2002
Reviewer:
      Robert  from St. Louis, Mo.
Tom,

What would you do if you were given a sql loader file with the dates of the
following formats?

1/10/2001 16:39:22
02/1/2001 10:12:33
01/22/2000 21:00:19

          Followup:

...
that_column date "dd/mm/yyyy hh24:mi:ss",
....

is what I would use.

      Rating:
      5
Thanks  January 25, 2002
Reviewer:
      Robert  from St. Louis Mo.
Tom,

First of all, Thank You!

We assumed the date formats were causing the problem.  However, this date column
had about 7,000 values of #EMPTY out of the 600,000 + rows.  Since the file was
over 200mb we decided to load it into all varchars, replace the invalid values
with nulls and then insert into the target table with the date format as you
suggested.

          Followup:

well, if its just '#EMPTY' -- i would have used decode:

....
x   "decode( :x, '#EMPTY', null, to_date(:x,'dd-mon-yyyy') )"
....

      Rating:
      5
Sweet  April 09, 2002
Reviewer:  Rodney
      Patterson  from Virginia
Loading from a pipe seems like a terrific way to Direct Loader (still the best
enhancement ever) on real world files

          Followup:

And in 9i -- you can direct path load using SQL functions as well -- never a
need to "awk" the file again (and probably lets you just skip that pipe in
99.99% of the cases)

$ cat t.ctl
LOAD DATA
INFILE t.dat
INTO TABLE NXBenefit
FIELDS TERMINATED BY ","
(plan,
 effective "todate_pkg.thedate(:effective)"
)

$ cat t.dat
dental plan, 01-11-01
vision plan, 03/05/01
vision plan, 03.05.01
vision plan, jan-01-01
vision plan, 01-jan-01
vision plan, 01-jan-2001

$ sqlldr / t direct=true

SQL*Loader: Release 9.0.1.2.0 - Production on Tue Apr 9 19:10:26 2002

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

Load completed - logical record count 6.

$ plus

SQL*Plus: Release 9.0.1.0.0 - Production on Tue Apr 9 19:10:30 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

ops$tkyte@ORA9I.WORLD> select * from nxbenefit
  2  /

PLAN                      EFFECTIVE
------------------------- ---------
dental plan               01-NOV-01
vision plan               03-MAY-01
vision plan               03-MAY-01
vision plan               01-JAN-01
vision plan               01-JAN-01
vision plan               01-JAN-01

6 rows selected.

External tables take this to a WHOLE another level -- don't even need sqlldr!
Just insert /*+ append */ into table select * from flat_file -- direct path load
without the muss.

      Rating: 4
how about
      decimals  November 13, 2002
Reviewer:
      Oracle-Leo  from India
Hi Tom,

I have a strange doubt and you are the only one i can fall back on. thanks for
all the support.

I am generating a temp.dat file by doing a 'select * ' from a particular query.
A couple of columns there will have data in 7 decimals (999999.1234567). when i
upload that temp.dat file using sql*loader (positional), SQL*loader is rounding
off the 7 decimals to 4 decimals while loading the data on to the table. What
could be the problem???.

The desc of table structure is below for that column.
DURATION                         NOT NULL NUMBER(8,7)

Thanks for all the support.

Cheers

          Followup:

example please (you are not using 999999.1234567 -- that won't fit in a
number(8,7).

Here is my example:

ops$tkyte@ORA920.LOCALHOST> create table t ( n number(8,7) );
Table created.

ops$tkyte@ORA920.LOCALHOST> !cat t.ctl
LOAD DATA
INFILE *
INTO TABLE T
FIELDS TERMINATED BY ","
(n)
BEGINDATA
999999.1234567
9.1234567

ops$tkyte@ORA920.LOCALHOST> !sqlldr / t

SQL*Loader: Release 9.2.0.1.0 - Production on Wed Nov 13 10:56:01 2002

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Commit point reached - logical record count 2

ops$tkyte@ORA920.LOCALHOST> select * from t;

         N
----------
 9.1234567

ops$tkyte@ORA920.LOCALHOST>

one row rejected -- the other loaded just fine.  A thought I have is that it
is not a number(8,7) but something bigger and you are loading 99999.1234567 and
SQLPlus's default format isn't showing you all of the decimals.  consider:

ops$tkyte@ORA920.LOCALHOST> create table t ( n number );
Table created.

ops$tkyte@ORA920.LOCALHOST> !sqlldr / t

SQL*Loader: Release 9.2.0.1.0 - Production on Wed Nov 13 10:57:18 2002

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Commit point reached - logical record count 2

ops$tkyte@ORA920.LOCALHOST> select * from t;

         N
----------
999999.123
 9.1234567

appears truncated, but it is not:

ops$tkyte@ORA920.LOCALHOST> set numformat 9999999.999999999
ops$tkyte@ORA920.LOCALHOST> /

                 N
------------------
  999999.123456700
       9.123456700

ops$tkyte@ORA920.LOCALHOST>

      Rating:
      5
  November 13, 2002
Reviewer:  Dave
       from Ohio
Quote:

"
 21      exception
 22          when others then null;
 23      end;
"

Who are you, and what have you done with the real Tom? You know, the one who
wrote ..."I truly wish we didn't even support WHEN OTHERS"?

Be careful what you wish for ;)

      Rating:
      5
Thanks  November 13, 2002
Reviewer:
      falguni  from INDIA
Thanks Tom this was one of my Question which I am waiting from last 3 months to
ask you.

      Rating: 5
Thats really
      clever!  November 14, 2002
Reviewer:  Jim

Tom

I have a file which has rows with the date in diff formats

12/9/02 16:29 3125 3000 3001 302
13/9/02 11:21 3000 3001 3002 400
14/9/02 09:53:11 AM 3000 3001 3002 400

Your solution is brilliant

      Rating: 4
Number problem
      with example  November 14, 2002
Reviewer:
      Oracle-Leo  from India
Sorry, I showed the next column. The problematic columns are
event_data_time,start_date_time and not duration one.
Here is the example.

tigger:~/sched> sqlplus /

SQL*Plus: Release 8.1.6.0.0 - Production on Fri Nov 15 14:03:25 2002

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

Connected to:
Oracle8i Release 8.1.6.0.0 - Production
JServer Release 8.1.6.0.0 - Production

system@SKYNEW on tig> desc test;
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 SERVICE_KEY                            NOT NULL NUMBER(4)
 EVENT_DATE_TIME                        NOT NULL NUMBER(12,7)
 EVENT_NUM                              NOT NULL NUMBER(1)
 TRAFFIC_KEY                                        VARCHAR2(20)
 SYNC_CHAN_ID                              NOT NULL NUMBER(4)
 START_DATE_TIME                           NOT NULL NUMBER(12,7)
 DURATION                                  NOT NULL NUMBER(8,7)
 DETAIL_KEY                             NOT NULL VARCHAR2(20)
 EVENT_ID                                        NUMBER(5)
 VIDEOPLUS_CODE                                     NUMBER(16)
 CA_RQST_KEY                                        VARCHAR2(20)
 OPPV_PURCHASE_FLAG                                 NUMBER(1)
 IPPV_PURCHASE_FLAG                                 NUMBER(1)
 FECM_ID                                            NUMBER(5)
 FREE_CA_MODE                              NOT NULL NUMBER(1)
 NAME_ID                                            NUMBER(3)

system@SKYNEW on tig> !cat test.ctl
load data
infile *
into table ssr.test
fields terminated by ","
trailing nullcols
(service_key   ,
 event_date_time,
 event_num      ,
 traffic_key       ,
sync_chan_id      ,
 start_date_time ,
 duration        ,
 detail_key   ,
 event_id     ,
 videoplus_code  ,
 ca_rqst_key     ,
 oppv_purchase_flag,
 ippv_purchase_flag,
 fecm_id           ,
 free_ca_mode    )
BEGINDATA
2,52592.8437444,0,0,0,52592.8437444,00000000.0104166,2_82,,,,,,,0
2,52592.8541611,0,0,0,52592.8541611,00000000.0104166,2_83,,,,,,,0

system@SKYNEW on tig> !sqlldr / test

SQL*Loader: Release 8.1.6.0.0 - Production on Fri Nov 15 14:05:21 2002

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

Commit point reached - logical record count 2

system@SKYNEW on tig> select * from ssr.test;

SERVICE_KEY EVENT_DATE_TIME EVENT_NUM TRAFFIC_KEY          SYNC_CHAN_ID
START_DATE_TIME   DURATION DETAIL_KEY        EVENT_ID VIDEOPLUS_CODE CA_RQST_KEY
         OPPV_PURCHASE_FLAG IPPV_PURCHASE_FLAG    FECM_ID FREE_CA_MODE
NAME_ID
-------------- ------------------ ------------ -------------------- ------------
--------------- ---------- -------------------- ----------- --------------
-------------------- ------------------ ------------------ ----------
------------ ----------
             2         52592.8437            0 0                               0
     52592.8437   .0104166 2_82                         259
                                                                           0
             2         52592.8542            0 0                               0
     52592.8542   .0104166 2_83                         260
                                                                           0

2 rows selected.

The columns have rounded off to four decimals. Am I missing something. Any
clues.

Cheers,

          Followup:

:)

go back, reread my response (i guessed right!!!)

<quote>
...A thought I have is that it
is not a number(8,7) but something bigger and you are loading 99999.1234567 and
SQLPlus's default format isn't showing you all of the decimals.  consider:....
</quote>

the answer and solution is above (look for numformat)

      Rating: 4
Another
      case  January 28, 2003
Reviewer:  Adrian
       from Lima Peru
Dear Tom, actually i'm loading a flat file via text i/o from forms but is too
slow, can u help me to make a ctl file who can handle this format in a flat file

12/01/02 00:15;414;0;303;0;0;0;0;0
00:30;420;0;318;0;0;0;0;0
00:45;432;0;318;0;0;0;0;0
01:00;372;0;309;0;0;0;0;0
....
23:30;159;0;177;0;0;0;0;0
23:45;165;0;165;0;0;0;0;0
12/01/02 24:00;168;0;183;0;0;0;0;0
12/02/02 00:15;159;0;162;0;0;0;0;0
00:30;156;0;117;0;0;0;0;0

where the time header is the day until change the header as u can see
Thanks in advance

          Followup:

how much data are we talking about here -- how big are these here files?

      Rating:
      4
Hi  January 28, 2003
Reviewer:
      Adrian  from Lima, Peru
Well here is a 96 record ina day for a month 2976 aprox but here is a 194 files
to load every one belongs to a specific electronic meter

          Followup:

HUH?

Well here is a 96 record ina day for a month 2976 aprox but here is a 194 files
to load

can you translate that back into something "parseable"?

      Rating:
      4
Ok  January 28, 2003
Reviewer:
      Adrian  from Lima, Peru
i have 2976 records in every flat file
I have too 194 flat files to load every month

I mean every month i receive the 194 flat files then i have to upload it to the
db. ( oracle 8i standard edition ).

          Followup:

suppose you had this table to load into:

create table t
( dt date,
  c1 number,
  c2 number,
  c3 number,
  c4 number,
  c5 number,
  c6 number,
  c7 number,
  c8 number );

Then create this view:

create or replace view v
as
select to_char(dt) dt, c1, c2, c3, c4, c5, c6, c7, c8
from t;

and this trigger on it

create or replace trigger v_trigger
instead of insert on v
begin
    if ( length(:new.dt) > 5 )
    then
        dbms_application_info.set_client_info( substr(:new.dt,1,8) );
        insert into t ( dt, c1, c2, c3, c4, c5, c6, c7, c8 )
        values ( to_date( :new.dt, 'mm/dd/rr hh24:mi' ),
        :new.c1, :new.c2, :new.c3, :new.c4, :new.c5, :new.c6, :new.c7, :new.c8
);
    else
        insert into t ( dt, c1, c2, c3, c4, c5, c6, c7, c8 )
        values ( to_date(userenv('client_info')||:new.dt,'mm/dd/rrhh24:mi'),
        :new.c1, :new.c2, :new.c3, :new.c4, :new.c5, :new.c6, :new.c7, :new.c8
);
    end if;
end;
/

and then this ctl file will load t

options (rows=1)
LOAD DATA
INFILE *
INTO TABLE v
REPLACE
fields terminated by ';'
(
dt,c1,c2,c3,c4,c5,c6,c7,c8
)
BEGINDATA
12/01/02 00:15;414;0;303;0;0;0;0;0
00:30;420;0;318;0;0;0;0;0
00:45;432;0;318;0;0;0;0;0
01:00;372;0;309;0;0;0;0;0
23:30;159;0;177;0;0;0;0;0
23:45;165;0;165;0;0;0;0;0
12/01/02 23:00;168;0;183;0;0;0;0;0
12/02/02 00:15;159;0;162;0;0;0;0;0
00:30;156;0;117;0;0;0;0;0

      Rating: 5
Excellent
      Answer  January 29, 2003
Reviewer:  Adrian
       from Lima, Peru
Thanks Tom...!!!

Very useful...!!

      Rating:
      5
Checksum?  February 08, 2003
Reviewer:
      A reader
Hi Tom,
My load file contains one header record (the name of the file), and one trailor
(the number of records and the checksum). I would like load using 9i running SQL
on external files (not loading the header and trailor but checking the name of
the file and checksum). Would this be possible? Please give an idea, how best to
do this, the file contains about 20 million records.
Thanks

          Followup:

You can use a BFILE or even UTL_FILE (now that it supports seeking in a file) to
read the first and last record of the file (seek to the end of the file and look
"backwards" with dbms_lob.instr for the prior newline).

      Rating: 4
PRAGMA
      RESTRICT_REFERENCES  March 27, 2003
Reviewer:
      David  from AL
In 8.0.6, this function is throwing a ORA-06571, which instructs me to use the
pragma restrict references.  I'm having a tough time placing the pragma.  Can
you tell me a) will this package work in 8.0.6. and b) if I need the pragma,
where does it go?

As always, thanks

          Followup:

create or replace package todate_pkg
as
    function thedate
             ( p_string in varchar2 )
             return date;
    pragma restrict_references( todate_pkg, wnds, rnds, wnps, rnps );
    pragma restrict_references( thedate, wnds, rnds, wnps );
end;
/

and yes, I just tested on 806

      Rating: 4
having trouble
      matching format string in array to the forms of dates I'm
      encountering  March 27, 2003
Reviewer:
      David  from AL
I have rrrr-mm-dd, mm-dd-rr, m-dd-rr, mm-d-rr, etc.  And I can't seem to get a
complete ist into the array.  I you mask 4/12/2003 with dd/mm/rrrr, won't that
reject?

          Followup:

I don't understand your comments.

      Rating: 4
Sorry, it was
      late in the day for me....  March 28, 2003

Reviewer:  David  from AL
I was asking about single-digit days and single-digit months.  I wanted to know
if both 1/1/2003 and 01/01/2003 could be handled with a mask like mm-dd-yyyy.

I've moved past that, though, to other problems to solve!

Thanks.

          Followup:

yes, they can

      Rating: 3
sql
      loader  April 26, 2003
Reviewer:  Somdeb
      Majumdar  from INDIA
Hi there...

Can we use group functions in the control file?
Suppose we are inserting data in 2 tables through a conrol file. (Please
consider the emp table for eg) In the first insert statement the data is coming
from some emp.dat file. And in the second I want to have the sum of salaries
entered for all the employees in the emp table. Is this possible to achieve
through a Control File?
I am citing an example :-

OPTIONS
 (ERRORS = 50)
LOAD DATA
  INFILE "D:\Emp.dat"
APPEND
 INTO TABLE EMP
  (EMPNO "EMP_SEQ.NEXTVAL"
  SALARY POSITION(1:10) INTEGER
  FILE_NM CONSTANT 'EMP.DAT')
 INTO TABLE TRAILER
**  (TOTAL_SALARY  "SELECT SUM(SALARY) FROM EMP WHERE
                 FILE_NM='EMP.DAT'"
  FILE_NM CONSTANT 'EMP.DAT'
  PRCSS_DT    SYSDATE)

You can easily see that I am getting stuck in '**' marked line. How to
incorporate this?
Please explain.

          Followup:

That would be a materialized view -- it would be done for you in the database.

No, you cannot do it in sqlldr like that -- it doesn't make sense.

      Rating: 4
why you dont
      have to specify datatype in this column?  July 11, 2003

Reviewer:  A reader
Hi

I extracted two lines from your examples

that_column date "dd/mm/yyyy hh24:mi:ss",   01/22/2000 21:00:19

effective "todate_pkg.thedate(:effective)"

why in the first column we have to specify data and the second not when both of
them use date datatype???????

          Followup:

the second does NOT use a date:

ops$tkyte@ORA8I.WORLD> create or replace package todate_pkg
  2  as
  3      function thedate
  4               ( p_string in varchar2 )
  5               return date;
  6  end;

theDate function expects a string.

      Rating: 4
a questeion
      about your package  July 11, 2003
Reviewer:
      A reader
Hello

I have a question about this package

create or replace package todate_pkg
as
      function thedate
               ( p_string in varchar2 )
               return date;
end;
/

create or replace package body todate_pkg
as
  type fmtArray is table of varchar2(30);
  g_fmts fmtArray :=
         fmtArray( 'dd-mm-rr', 'dd/mm/rr',
                   'dd.mm.rr', 'mon-dd-rr',
                   'dd-mon-rrrr' );
  function thedate( p_string in varchar2 )
  return date
  is
      return_value date;
  begin
      for i in 1 .. g_fmts.count
      loop
      begin
          return_value := to_date(p_string,g_fmts(i) );
          exit;
      exception
          when others then null;
      end;
      end loop;
      if ( return_value is null ) then
          raise PROGRAM_ERROR;
      end if;
      return return_value;
  end;
end;
/

it seems it only treats input string of formats

'dd-mm-rr', 'dd/mm/rr', 'dd.mm.rr', 'mon-dd-rr', 'dd-mon-rrrr'

however I pass anything to this package it can comvert to proper date format how
so? For example

SQL>select todate_pkg.thedate('01@01@99') from dual;

TODATE_PKG.THEDAT
-----------------
19990101 00:00:00

SQL>select todate_pkg.thedate('01012003') from dual;

TODATE_PKG.THEDAT
-----------------
20030101 00:00:00

I dont get it....... also why you use an ext statement inside the loop :-?

          Followup:

because those formats are rather forgiving

  1* select to_date( '01~01~99', 'dd/mm/rr' ) from dual
ops$tkyte@ORA817DEV> /

TO_DATE('01
-----------
01-JAN-1999

as far as separators go.

I exit the loop as soon as we get a format that "worked".  If I did not, we
would either

o return the date as translated by the last format
o return an error

if any of the intermediate formats worked, we would miss that fact if I did not
exit the loop.

      Rating: 4
Can this be
      done ?  July 12, 2003
Reviewer:  Anand
       from Bangkok, Thailand
Hi Tom,

I am a Avid fan of your excellent service to Oracle developer community, even
though this is my first post. I have a doubt, even though i am not facing this
problem, For example, if the flat file has records with formats like this

Date
----------
08-13-2003                --------------> mm-dd-yyyy
12-30-2003                --------------> dd-mm-yyyy

Is there any way by which I can identify the format and use it in the sql*loader
control file ? Just asking out of curiosity !!! I hope you have a answer to this
question (even though it sounds silly ;-)!)

Anand

          Followup:

only if the input file HAS the format in it -- does each record in the input
file have its corresponding date format in the record?

      Rating:
      1
Oooooppppsss...  July 12, 2003
Reviewer:
      Anand  from Bangkok, Thailand
The format mask has to be interchanged between the first and second records..
sorry for the inconvenience..

      Rating: 2
Is there a
      roundabout way of doing that ?  July 13, 2003

Reviewer:  Anand  from Bangkok, Thailand
Hi Tom,

As i had mentioned earlier it is just a hypothetical situation...But cant that
be handled within the function which we are creating and refering in SQL*Loader.
May be like finding out what is the format in which the date is stored in... any
way i agree that it will not work for dates like "12-12-2003" or dates like
that... but otherwise... shouldnt there be a way ?

          Followup:

umm, think about it

01-02-03

is that jan 2cnd 2003
feb 1st 2003
mar 2cnd 2001

and so on.

NO, without a format, anything is fair game isn't it.

No there "shouldn't" be a way.

      Rating: 5
Converting from
      external table to SQL*Loader  January 08, 2004

Reviewer:  Martin  from UK
Tom,

i've got a load of data which we've got the 9i external table definitions for,
but we need to load this into an 8i database. Is it possible to do the reverse
of the sqlloader external_table=generate_only trick and create sqlloader control
files from an external table definition?

Note, the 9i and 8i databases are seperate clients and we have no mechanism for
database links etc. (unfortunately)

Thanks in advance

          Followup:

i have no tools for doing so, no.

most of the work should be "done" as the bottom of the external table is close
to a control file already.

January 08, 2004 Kevin -- Thanks for the question regarding "Yet
      another ASCII dump issue", version 7.3

      You Asked
Tom,

I've written Pro/C to generically dump any table to delimited
ASCII.  It dynamically builds a select statement with a single
column containing all the table's columns concatenated together
with a delimiter, then fetches this into a HOSTARRAY for output.

The problem:  I get ORA-01489 when a member of the rowset
exceeds 2000 characters.

What would you suggest here?  Any tricks in Pro/C to overcome
this limitation?

I plan to turn this into a system that can very quickly dump
large tables to compressed ASCII via the use of home-grown
parallel processing.  It's not really worth my time to do so
unless I can get past this issue ( and a few others ).

Thanks,

-Kevin

      and we said...
Thats the limit of a character column in 7.3 (it would be 4000
bytes in 8.0 and up).

Not to worry, what we need is a slighty more complex pro*c app
is all -- one that fetches any number of columns from any
arbitrary query.  I happen to have one.  Currently if you run it
with a command line like:

$./array_flat userid=scott/tiger 'sqlstmt=select * from emp'
arraysize=100

(thats on 1 line) it will produce:

Connected to ORACLE as user: scott/tiger

Unloading 'select * from emp'
Array size = 100
EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO
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
14 rows extracted

If you run it as:

$ ./array_flat userid=scott/tiger 'sqlstmt=select * from emp'
arraysize=100 > test.dat

You'll see:

Connected to ORACLE as user: scott/tiger

Unloading 'select * from emp'
Array size = 100
EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO
14 rows extracted

and test.dat will just have the data.

The pro*c program (works in 7.x - 8.x as is) is simply:

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define MAX_VNAME_LEN     30
#define MAX_INAME_LEN     30

static char *     USERID = NULL;
static char *   SQLSTMT = NULL;
static char *   ARRAY_SIZE = "10";

#define vstrcpy( a, b ) \
(strcpy( a.arr, b ), a.len = strlen( a.arr ), a.arr)

EXEC SQL INCLUDE sqlca;
EXEC SQL INCLUDE sqlda;

extern SQLDA *sqlald();
extern void   sqlclu();

static void die( char * msg )
{
    fprintf( stderr, "%s\n", msg );
    exit(1);
}

/*
    this array contains a default mapping
    I am using to constrain the
       lengths of returned columns.  It is mapping,
    for example, the Oracle
       NUMBER type (type code = 2) to be 45 characters
    long in a string.
*/

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 };

static void process_parms( argc, argv )
int    argc;
char *    argv[];
{
int    i;

    for( i = 1; i < argc; i++ )
    {
        if ( !strncmp( argv[i], "userid=", 7 ) )
              USERID = argv[i]+7;
        else
        if ( !strncmp( argv[i], "sqlstmt=", 8 ) )
              SQLSTMT = argv[i]+8;
        else
        if ( !strncmp( argv[i], "arraysize=", 10 ) )
              ARRAY_SIZE = argv[i]+10;
        else
        {
            fprintf( stderr,
                    "usage: %s %s %s\n",
                     argv[0],
                    "userid=xxx/xxx sqlstmt=query ",
                    "arraysize=<NN>\n" );
            exit(1);
        }
    }
    if ( USERID == NULL  || SQLSTMT == NULL )
    {
        fprintf( stderr,
                "usage: %s %s %s\n",
                 argv[0],
                "userid=xxx/xxx sqlstmt=query ",
                "arraysize=<NN>\n" );
        exit(1);
    }
}

static void sqlerror_hard()
{
    EXEC SQL WHENEVER SQLERROR CONTINUE;

    fprintf(stderr,"\nORACLE error detected:");
    fprintf(stderr,"\n% .70s \n", sqlca.sqlerrm.sqlerrmc);

    EXEC SQL ROLLBACK WORK RELEASE;
    exit(1);
}

static SQLDA * process_1(char * sqlstmt, int array_size )
{
SQLDA *    select_dp;
int     i;
int        j;
int        null_ok;
int        precision;
int        scale;
int        size = 10;

    fprintf( stderr, "Unloading '%s'\n", sqlstmt );
    fprintf( stderr, "Array size = %d\n", array_size );

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();
       EXEC SQL PREPARE S FROM :sqlstmt;
       EXEC SQL DECLARE C CURSOR FOR S;

    if ((select_dp = sqlald(size,MAX_VNAME_LEN,MAX_INAME_LEN))
                   == NULL )
        die( "Cannot allocate  memory for select descriptor." );

    select_dp->N = size;
    EXEC SQL DESCRIBE SELECT LIST FOR S INTO select_dp;
    if ( !select_dp->F ) return NULL;

    if (select_dp->F < 0)
    {
        size = -select_dp->F;
        sqlclu( select_dp );
        if ((select_dp =
                sqlald (size, MAX_VNAME_LEN, MAX_INAME_LEN))
                      == NULL )
        die( "Cannot allocate  memory for descriptor." );
        EXEC SQL DESCRIBE SELECT LIST FOR S INTO select_dp;
    }
    select_dp->N = select_dp->F;

    for (i = 0; i < select_dp->N; i++)
        select_dp->I[i] = (short *) malloc(sizeof(short) *
                                                array_size );

    for (i = 0; i < select_dp->F; i++)
    {
        sqlnul (&(select_dp->T[i]),
                &(select_dp->T[i]), &null_ok);
        if ( select_dp->T[i] <
                     sizeof(lengths)/sizeof(lengths[0]) )
        {
            if ( lengths[select_dp->T[i]] )
                 select_dp->L[i]  = lengths[select_dp->T[i]];
            else select_dp->L[i] += 5;
        }
        else select_dp->L[i] += 5;

        select_dp->T[i] = 5;
        select_dp->V[i] = (char *)malloc( select_dp->L[i] *
                                               array_size );

        for( j = MAX_VNAME_LEN-1;
             j > 0 && select_dp->S[i][j] == ' ';
             j--);
        fprintf (stderr,
                "%s%.*s", i?",":"", j+1, select_dp->S[i]);
    }
    fprintf( stderr, "\n" );

    EXEC SQL OPEN C;
    return select_dp;
}

static void process_2( SQLDA * select_dp, int array_size )
{
int    last_fetch_count;
int        row_count = 0;
short    ind_value;
char    * char_ptr;
int    i,
       j;

    for ( last_fetch_count = 0;
          ;
          last_fetch_count = sqlca.sqlerrd[2] )
    {
        EXEC SQL FOR :array_size FETCH C
                      USING DESCRIPTOR select_dp;

        for( j=0; j < sqlca.sqlerrd[2]-last_fetch_count; j++ )
        {
            for (i = 0; i < select_dp->F; i++)
            {
                ind_value = *(select_dp->I[i]+j);
                char_ptr  = select_dp->V[i] +
                                  (j*select_dp->L[i]);

                printf( "%s%s", i?",":"",
                             ind_value?"(null)":char_ptr );
            }
            row_count++;
            printf( "\n" );
        }
        if ( sqlca.sqlcode > 0 ) break;
    }

    sqlclu(select_dp);

    EXEC SQL CLOSE C;

    EXEC SQL COMMIT WORK;
    fprintf( stderr, "%d rows extracted\n", row_count );
}

main( argc, argv )
int    argc;
char *    argv[];
{
EXEC SQL BEGIN DECLARE SECTION;
VARCHAR   oracleid[50];
EXEC SQL END DECLARE SECTION;
SQLDA    * select_dp;

    process_parms( argc, argv );

    /* Connect to ORACLE. */
    vstrcpy( oracleid, USERID );

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();

    EXEC SQL CONNECT :oracleid;
    fprintf(stderr, "\nConnected to ORACLE as user: %s\n\n",
             oracleid.arr);

    EXEC SQL ALTER SESSION
      SET NLS_DATE_FORMAT = 'DD-MON-YYYY HH24:MI:SS';

    select_dp = process_1( SQLSTMT, atoi(ARRAY_SIZE) );
    process_2( select_dp , atoi(ARRAY_SIZE));

    /* Disconnect from ORACLE. */
    EXEC SQL COMMIT WORK RELEASE;
    exit(0);
}

Review &
      Followup

      Rating: 3
PRO*C
      Compilation  August 24, 2001
Reviewer:
      A reader  from England
Tom,
Thanks for your prompt response. However, I still couldn't compile the pro*.
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

      Rating: 4
prepare
      problem  June 22, 2002
Reviewer:  Kapil
       from India
Hi Tom
This is with reference to 8.1.7
My code is similar to Kevins. I go a little further than that and dump data of
multiple tables. The problem I am facing is that when I prepare the SQL
statement for the second table my executable crashes and generates a core file.
I am doing something like this :

delare cursor listTables
open listTables
start loop
    fetch from listTables
    Form dynamic statement for the table
    prepare
    declare
    open
    ....
    close
end loop

The program works fine for the table fetched first but core dumps at the prepare
statement for the table fetched second. Is there anything wrong in preparing a
dynamic statement using the same statement id and host string the second time in
the same transaction or session?

          Followup:

Well, my first guess is -- you have a bug in your code.  C is notorious for
having mysterious "crashes" like that unless you are really good.  I have a
feeling that given any non-trivial source code file written in C, it would be
possible to find a bug.

Lets see some of your code.

      Rating: 4
bug in
      code  July 21, 2002
Reviewer:  Kapil
       from INDIA
Hi Tom
Yes, you are right
There is a bug in the code
The size of a variable was too less to accomodate the data stored in it
Thus the data stored in it was probably corrupting the memory
Thanks for your help

      Rating:
      4
Slow  October 07, 2002
Reviewer:
      A reader
Hi, TOm,

Did you test this code with large table(I mean 100 columns
with million rows)? The speed is very slow?

Any ideas?

Thanks

          Followup:

is this a question?

The code works with any number of columns and any number of rows.

Define "slow" for me -- do you have something "faster"?  I just ran on RH Linux
7.3 with 9iR2 and a 1,000,000 row table.  big_wide_table has over 100 columns,
big_table has 14 (1/8 the data).  Ran against all_objects as well (1/34th the
data of big_table)

Timings were:

[tkyte@dhcp-reston-gen-3-west-120-222 array_flat]$ ./test.sh

Connected to ORACLE as user: big_table/big_table

Unloading 'select * from big_wide_table'
Array size = 100
ID_1,ID_2,ID_3,ID_4,ID_5,ID_6,ID_7,ID_8,OWNER_1,OWNER_2,OWNER_3,OWNER_4,OWNER_5,O
WNER_6,OWNER_7,OWNER_8,OBJECT_NAME_1,OBJECT_NAME_2,OBJECT_NAME_3,OBJECT_NAME_4,OB
JECT_NAME_5,OBJECT_NAME_6,OBJECT_NAME_7,OBJECT_NAME_8,SUBOBJECT_NAME_1,SUBOBJECT_
NAME_2,SUBOBJECT_NAME_3,SUBOBJECT_NAME_4,SUBOBJECT_NAME_5,SUBOBJECT_NAME_6,SUBOBJ
ECT_NAME_7,SUBOBJECT_NAME_8,OBJECT_ID_1,OBJECT_ID_2,OBJECT_ID_3,OBJECT_ID_4,OBJEC
T_ID_5,OBJECT_ID_6,OBJECT_ID_7,OBJECT_ID_8,DATA_OBJECT_ID_1,DATA_OBJECT_ID_2,DATA
_OBJECT_ID_3,DATA_OBJECT_ID_4,DATA_OBJECT_ID_5,DATA_OBJECT_ID_6,DATA_OBJECT_ID_7,
DATA_OBJECT_ID_8,OBJECT_TYPE_1,OBJECT_TYPE_2,OBJECT_TYPE_3,OBJECT_TYPE_4,OBJECT_T
YPE_5,OBJECT_TYPE_6,OBJECT_TYPE_7,OBJECT_TYPE_8,CREATED_1,CREATED_2,CREATED_3,CRE
ATED_4,CREATED_5,CREATED_6,CREATED_7,CREATED_8,LAST_DDL_TIME_1,LAST_DDL_TIME_2,LA
ST_DDL_TIME_3,LAST_DDL_TIME_4,LAST_DDL_TIME_5,LAST_DDL_TIME_6,LAST_DDL_TIME_7,LAS
T_DDL_TIME_8,TIMESTAMP_1,TIMESTAMP_2,TIMESTAMP_3,TIMESTAMP_4,TIMESTAMP_5,TIMESTAM
P_6,TIMESTAMP_7,TIMESTAMP_8,STATUS_1,STATUS_2,STATUS_3,STATUS_4,STATUS_5,STATUS_6
,STATUS_7,STATUS_8,TEMPORARY_1,TEMPORARY_2,TEMPORARY_3,TEMPORARY_4,TEMPORARY_5,TE
MPORARY_6,TEMPORARY_7,TEMPORARY_8,GENERATED_1,GENERATED_2,GENERATED_3,GENERATED_4
,GENERATED_5,GENERATED_6,GENERATED_7,GENERATED_8,SECONDARY_1,SECONDARY_2,SECONDAR
Y_3,SECONDARY_4,SECONDARY_5,SECONDARY_6,SECONDARY_7,SECONDARY_8
1000000 rows extracted

real    5m9.647s
user    4m40.720s
sys     0m12.440s

5 minutes, 9seconds

Connected to ORACLE as user: big_table/big_table

Unloading 'select * from big_table'
Array size = 100
ID,OWNER,OBJECT_NAME,SUBOBJECT_NAME,OBJECT_ID,DATA_OBJECT_ID,OBJECT_TYPE,CREATED,
LAST_DDL_TIME,TIMESTAMP,STATUS,TEMPORARY,GENERATED,SECONDARY
1000000 rows extracted

real    0m41.229s
user    0m38.370s
sys     0m1.530s

Hmmm, 1/8th the data, just about EXACTLY 1/8th the time - seems to not be
affected by the WIDTH of the table, how about the height?

Connected to ORACLE as user: big_table/big_table

Unloading 'select * from all_objects'
Array size = 100
OWNER,OBJECT_NAME,SUBOBJECT_NAME,OBJECT_ID,DATA_OBJECT_ID,OBJECT_TYPE,CREATED,LAS
T_DDL_TIME,TIMESTAMP,STATUS,TEMPORARY,GENERATED,SECONDARY
29272 rows extracted

real    0m6.781s
user    0m0.940s
sys     0m0.030s

Ok, given that there is 1/34th the data in all_objects as opposed to big_table
and this ran in 1/6th the time -- we can see it is not the HEIGHT of the table
either

So, it scales up with the number of rows, it scales across with the number of
columns.  In short -- it is a pure function of the AMOUNT of data.

So, what is slow to you, perhaps you just have unreasonable expectations of how
fast your server can give you the data and how fast you can write it out.

      Rating: 3
Excel
      Format  October 07, 2002
Reviewer:
      A reader  from coludes
Hi, Tom,

The code is great. But my problem is to write an "excel" file(truely excel file,
specify format of each column, not only excel readable file) with fast speed. Is
it possible by this PROC style code?

Thanks

          Followup:

you'll need to goto www.askbillgates.com to see if he can tell you the file
format for excel.

Good luck keeping up with the file formats.

Maybe if you search for

OWA_SYLK

on this site, you'll find a utility that whilst it doesn't write EXCEL
proprietary formats -- it does write SYLK which is an open file format that is
readable by many spreadsheet programs and allows you to use formatting and
formula's and such.

      Rating: 5
Excellent Pro*C
      "table dump" program!!  December 03, 2002

Reviewer:  Geoff Yaworski  from Denver, CO USA

Tom, I grabbed you Pro*C source, proc'd it, then gcc'd it with the appropriate
libraries and ran it against our 2,000,000+ row, 27 column table.  It dumped the
table in 8 minutes and I did this over a network!  I was impressed.  I plan to
port your code to dump a direct path load flat file.  This will be an excellent
replacement (I think) for our current method of doing a SQL*Plus copy from
source to destination databases to get the data.  Thanks again.

      Rating:
      5
  March 18, 2003
Reviewer:
      Balasubramanian.S  from Bangalore,KA, India
Very nice article

      Rating: 3
pro*c unload
        March 28, 2003
Reviewer:  Marcel Rosa
       from Brazil
I made the unload using the pro*c , how do I the load ?
Can you give me a example ?

          Followup:

sqlldr ....

look in $ORACLE_HOME/rdbms/demo/ulcase*.ctl

for example control files.

      Rating:
      5
  April 19, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
I saved the Pro*C program that you posted and tried to compile it, but got
following errors:

proc iname=flat_file.pc

Pro*C/C++: Release 9.2.0.1.0 - Production on Sat Apr 19 21:17:47 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

System default option values taken from:
D:\Oracle\Ora_92\precomp\admin\pcscfg.cfg

Semantic error at line 110, column 33, file flat_file.pc:
       EXEC SQL PREPARE S FROM :sqlstmt;
................................1
PCC-S-02322, found undefined identifier
Semantic error at line 110, column 27, file flat_file.pc:
       EXEC SQL PREPARE S FROM :sqlstmt;
..........................1
PCC-S-02319, expression type does not match usage
Semantic error at line 181, column 23, file flat_file.pc:
        EXEC SQL FOR :array_size FETCH C
......................1
PCC-S-02322, found undefined identifier
Semantic error at line 181, column 23, file flat_file.pc:
        EXEC SQL FOR :array_size FETCH C
......................1
PCC-S-02330, expecting an expression of integer type

          Followup:

what is in your

D:\Oracle\Ora_92\precomp\admin\pcscfg.cfg

file.  As I recall, on windoze, the default settings are different then normal
operating systems like unix.

Here are my default settings:

[tkyte@tkyte-pc-isdn Desktop]$ proc

Pro*C/C++: Release 9.2.0.3.0 - Production on Sun Apr 20 08:50:21 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

System default option values taken from:
/usr/oracle/ora920/OraHome1/precomp/admin/pcscfg.cfg

Option Name    Current Value  Description
-------------------------------------------------------------------------------
auto_connect   no             Allow automatic connection to ops$ account
char_map       charz          Mapping of character arrays and strings
close_on_commitno             Close all cursors on COMMIT
cmax           100            CMAX Value for connection pool
cmin           2              CMIN Value for connection pool
cincr          1              CINCR Value for connection pool
ctimeout       0              CTIMEOUT Value for connection pool
cnowait        0              CNOWAIT Value for connection pool
code           kr_c           The type of code to be generated
comp_charset   multi_byte     The character set type the C compiler supports
config         default        Override system configuration file with another
cpool          no             Support connection pooling
cpp_suffix     *none*         Override the default C++ filename suffix
dbms           native         v6/v7/v8 compatibility mode
def_sqlcode    no             Generate '#define SQLCODE sqlca.sqlcode' macro
define         *none*         Define a preprocessor symbol
duration       transaction    Set pin duration for objects in the cache
dynamic        oracle         Specify Oracle or ANSI Dynamic SQL Semantics
errors         yes            Whether error messages are sent to the terminal
errtype        *none*         Name of the list file for intype file errors
fips           none           FIPS flagging of ANSI noncompliant usage
header         *none*         Specify file extension for Precompiled Headers
hold_cursor    no             Control holding of cursors in the cursor cache
iname          *none*         The name of the input file
include        *none*         Directory paths for included files
intype         *none*         The name of the input file for type information
lines          no             Add #line directives to the generated code
lname          *none*         Override default list file name
ltype          short          The amount of data generated in the list file
maxliteral     1024           Maximum length of a generated string literal
maxopencursors 10             Maximum number of cached open cursors
mode           oracle         Code conformance to Oracle or ANSI rules
nls_char       *none*         Specify National Language character variables
nls_local      no             Control how NLS character semantics are done
objects        yes            Support object types
oname          *none*         The name of the output file
oraca          no             Control the use of the ORACA
pagelen        80             The page length of the list file
parse          full           Control which non-SQL code is parsed
prefetch       1              Number of rows pre-fetched at cursor OPEN time
release_cursor no             Control release of cursors from cursor cache
select_error   yes            Control flagging of select errors
sqlcheck       syntax         Amount of compile-time SQL checking
sys_include    /usr/lib/gcc-liDirectory where system header files are found
               /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include
               /usr/include
threads        no             Indicates a multi-threaded application
type_code      oracle         Use Oracle or ANSI type codes for Dynamic SQL
unsafe_null    no             Allow a NULL fetch without indicator variable
userid         *none*         A username/password [@dbname] connect string
utf16_charset  nchar_charset  The character set form used by UTF16 variables
varchar        no             Allow the use of implicit varchar structures
version        recent         Which version of an object is to be returned
PCC-F-02135, CMD-LINE:  User asked for help

make sure the options match

      Rating: 5
Work
      Around...  April 19, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
When I changed this:
=========================================================
static SQLDA * process_1(char * sqlstmt, int array_size )
=========================================================

To the following:
=========================================================
static SQLDA * process_1(sqlstmt, array_size )
EXEC SQL BEGIN DECLARE SECTION ;
    char * sqlstmt ;
    int    array_size ;
EXEC SQL END DECLARE SECTION ;
=========================================================

The program compiled and was working.

Is this someting new to Oracle9i that all host variables appearing in the EXEC
SQL statements must be enclosed within the DECLARE SECTION?

          Followup:

no, it is something old with windoze -- one of the default options is different.

      Rating: 4
I had the same
      problem  April 20, 2003
Reviewer:  David
      Sansom  from London, England
Tom: Sorry, but I've just asked the same question in a separate thread (I did
search your site for answers to the problem last week, but this response is
new).

Setting these parameters:
code=KR_C parse=full
resolves the "found undefined identifier" error.

However, I'm now getting this error:
Error at line 1, column 10 in file C:\Temp\unload.pc
#include <stdio.h>
.........1
PCC-S-02015, unable to open include file

I presume this is because my sys_include parameter is incorrect. Its currently
set to:
sys_include=c:\Oracle\Ora9iR2\precomp\lib
Do you know what it should be on a Windows XP machine?

Thanks

David

          Followup:

stdio.h is a standard C include file, it'll be whereever your c compiler has it
(eg: /usr/include on unix, probably c:\program files\something\really\long\with
spaces\in it

      Rating: 4
I've managed to
      fix that problem, but still having compile problems  April
      20, 2003
Reviewer:  David Sansom  from London,
      England
I downloaded Borland C++ Compiler. and set my sys_include paramter to point at a
folder where I'd uncompressed the files to. It now finds stdio.h, etc, but I'm
getting other errors now:

++: Release 9.2.0.1.0 - Production on Sun Apr 20 18:54:39 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

System default option values taken from:
c:\Oracle\Ora9iR2\precomp\admin\pcscfg.cfg

Syntax error at line 139, column 29, file
c:\Oracle\Ora9iR2\precomp\borland\mem.h:
Error at line 139, column 29 in file c:\Oracle\Ora9iR2\precomp\borland\mem.h
#endif /* __USING_CNAME__ */
............................1
PCC-S-02014, Encountered the symbol "<eof>" when expecting one of the following:

   ; { } , = : ( ) [ ] * ? | & < > + - / % ~ ! . # @ ^ *= /= %=
   += -= <<= >>= &&= ||= ^= ~= := | & == != <= >= << >> ++ -- ->
   ... .. <> ** => an identifier, a string, a numeric constant,
   newline, a sql string, misc. punctuation, newline, define,
   elif, else, endif, error, if, ifdef, ifndef, include, line,
   pragma, undef, exec, sql, begin, end, var, type, oracle,
   an immediate preprocessor command, a C token, exec sql,
   exec sql include, exec sql var, exec sql begin, exec sql end,
   end-exec, exec sql type, exec oracle, exec oracle else,
   exec oracle endif, exec oracle begin, a sql hint, create,
   function, package, procedure, trigger, or, replace,
   a C++ token,
The symbol "newline," was substituted for "<eof>" to continue.

Syntax error at line 18, column 14, file C:\Temp\unload.pc:
Error at line 18, column 14 in file C:\Temp\unload.pc
extern SQLDA *sqlald();
.............1
PCC-S-02201, Encountered the symbol "*" when expecting one of the following:

   ; , = ( [
The symbol ";" was substituted for "*" to continue.

Syntax error at line 95, column 14, file C:\Temp\unload.pc:
Error at line 95, column 14 in file C:\Temp\unload.pc
static SQLDA * process_1(char * sqlstmt, int array_size )
.............1
PCC-S-02201, Encountered the symbol "*" when expecting one of the following:

   ; , = ( [
The symbol ";" was substituted for "*" to continue.

Syntax error at line 168, column 30, file C:\Temp\unload.pc:
Error at line 168, column 30 in file C:\Temp\unload.pc
static void process_2( SQLDA * select_dp, int array_size )
.............................1
PCC-S-02201, Encountered the symbol "*" when expecting one of the following:

   , )

Error at line 0, column 0 in file C:\Temp\unload.pc
PCC-F-02102, Fatal error while doing C preprocessing

Any ideas?

Thanks

David

          Followup:

the borland c compiler is not supported at all.  ms visual c/c++ is.

      Rating: 4
Unloading to a
      flat file when running on Windows  April 21, 2003

Reviewer:  David Sansom  from London, England

Thanks Tom - I managed to compile it using Microsoft C++.

On our server, exporting a 150MB Text file, took 1hour20mins using UTL_FILE,
whereas it now only takes 3mins15secs using Pro*C !

Cheers

David

      Rating: 5
great
      program  May 26, 2003
Reviewer:  michel
      moureaux  from Monaco
This is the best way i found to download data from huge table (6 Gb) to flat
file with such good performances.
Thanks a lot !

      Rating:
      5
  June 29, 2003
Reviewer:
      A reader
Tom,

OS:R.H 7.3
Database: 9.0.1

i saved your code as t1.pc and when i give this command i get this error

$ proc iname=t1.pc

Pro*C/C++: Release 9.0.1.0.0 - Production on Sun Jun 29 09:19:10 2003

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

System default option values taken from:
/u01/oracle/product/9.0.1/precomp/admin/pcscfg.cfg

Error at line 34, column 11 in file /usr/include/stdio.h
# include <stddef.h>
..........1
PCC-S-02015, unable to open include file
Error at line 29, column 10 in file /usr/include/bits/types.h
#include <stddef.h>
.........1
PCC-S-02015, unable to open include file
Syntax error at line 48, column 3, file /usr/include/bits/pthreadtypes.h:
Error at line 48, column 3 in file /usr/include/bits/pthreadtypes.h
  size_t __guardsize;
..1
PCC-S-02201, Encountered the symbol "size_t" when expecting one of the following
:

   } char, const, double, enum, float, int, long, ulong_varchar,
   OCIBFileLocator OCIBlobLocator, OCIClobLocator, OCIDateTime,
   OCIExtProcContext, OCIInterval, OCIRowid, OCIDate, OCINumber,
   OCIRaw, OCIString, short, signed, sql_context, sql_cursor,
   struct, union, unsigned, utext, uvarchar, varchar, void,
   volatile, a typedef name,
The symbol "enum," was substituted for "size_t" to continue.

Syntax error at line 51, column 3, file /usr/include/bits/pthreadtypes.h:
Error at line 51, column 3 in file /usr/include/bits/pthreadtypes.h
  size_t __stacksize;
..1
PCC-S-02201, Encountered the symbol "size_t" when expecting one of the following
:

   } char, const, double, enum, float, int, long, ulong_varchar,
   OCIBFileLocator OCIBlobLocator, OCIClobLocator, OCIDateTime,
   OCIExtProcContext, OCIInterval, OCIRowid, OCIDate, OCINumber,
   OCIRaw, OCIString, short, signed, sql_context, sql_cursor,
   struct, union, unsigned, utext, uvarchar, varchar, void,
   volatile, a typedef name,
The symbol "enum," was substituted for "size_t" to continue.

Syntax error at line 51, column 3, file /usr/include/bits/pthreadtypes.h:
Error at line 51, column 3 in file /usr/include/bits/pthreadtypes.h
  size_t __stacksize;
..1
PCC-S-02201, Encountered the symbol "size_t" when expecting one of the following
:

   } char, const, double, enum, float, int, long, ulong_varchar,
   OCIBFileLocator OCIBlobLocator, OCIClobLocator, OCIDateTime,
   OCIExtProcContext, OCIInterval, OCIRowid, OCIDate, OCINumber,
   OCIRaw, OCIString, short, signed, sql_context, sql_cursor,
   struct, union, unsigned, utext, uvarchar, varchar, void,
   volatile, a typedef name,
The symbol "enum," was substituted for "size_t" to continue.

Error at line 14, column 10 in file /usr/include/_G_config.h
#include <stddef.h>
.........1
PCC-S-02015, unable to open include file
Error at line 48, column 10 in file /usr/include/wchar.h
#include <stddef.h>
.........1
PCC-S-02015, unable to open include file
Syntax error at line 72, column 5, file /usr/include/wchar.h:
Error at line 72, column 5 in file /usr/include/wchar.h
    wint_t __wch;
....1
PCC-S-02201, Encountered the symbol "wint_t" when expecting one of the following
:

There is alot like this..and my proc is
$proc

Pro*C/C++: Release 9.0.1.0.0 - Production on Sun Jun 29 09:27:32 2003

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

System default option values taken from:
/u01/oracle/product/9.0.1/precomp/admin/pcscfg.cfg

Option Name    Current Value  Description
-------------------------------------------------------------------------------
auto_connect   no             Allow automatic connection to ops$ account
char_map       charz          Mapping of character arrays and strings
close_on_commitno             Close all cursors on COMMIT
code           kr_c           The type of code to be generated
comp_charset   multi_byte     The character set type the C compiler supports
config         default        Override system configuration file with another
cpp_suffix     *none*         Override the default C++ filename suffix
dbms           native         v6/v7/v8 compatibility mode
def_sqlcode    no             Generate '#define SQLCODE sqlca.sqlcode' macro
define         *none*         Define a preprocessor symbol
duration       transaction    Set pin duration for objects in the cache
dynamic        oracle         Specify Oracle or ANSI Dynamic SQL Semantics
errors         yes            Whether error messages are sent to the terminal
errtype        *none*         Name of the list file for intype file errors
fips           none           FIPS flagging of ANSI noncompliant usage
header         *none*         Specify file extension for Precompiled Headers
hold_cursor    no             Control holding of cursors in the cursor cache
iname          *none*         The name of the input file
include        *none*         Directory paths for included files
intype         *none*         The name of the input file for type information
lines          no             Add #line directives to the generated code
lname          *none*         Override default list file name
ltype          none           The amount of data generated in the list file
maxliteral     1024           Maximum length of a generated string literal
maxopencursors 10             Maximum number of cached open cursors
mode           oracle         Code conformance to Oracle or ANSI rules
nls_char       *none*         Specify National Language character variables
nls_local      no             Control how NLS character semantics are done
objects        yes            Support object types
oname          *none*         The name of the output file
oraca          no             Control the use of the ORACA
pagelen        80             The page length of the list file
parse          full           Control which non-SQL code is parsed
prefetch       1              Number of rows pre-fetched at cursor OPEN time
release_cursor no             Control release of cursors from cursor cache
select_error   yes            Control flagging of select errors
sqlcheck       syntax         Amount of compile-time SQL checking
sys_include    /usr/lib/gcc-liDirectory where system header files are found
               /usr/include
threads        no             Indicates a multi-threaded application
type_code      oracle         Use Oracle or ANSI type codes for Dynamic SQL
unsafe_null    no             Allow a NULL fetch without indicator variable
userid         *none*         A username/password [@dbname] connect string
utf16_charset  nchar_charset  The character set form used by UTF16 variables
varchar        no             Allow the use of implicit varchar structures
version        recent         Which version of an object is to be returned
PCC-F-02135, CMD-LINE:  User asked for help

          Followup:

what is the "genealogy" of that machine?  undergo an upgrade?

you might have to peek in your
/u01/oracle/product/9.0.1/precomp/admin/pcscfg.cfg

and see that the path is set right.  check out the sys_include and make sure its
pointing to the right place.  I've noticed when I upgrade the OS, this gets
"stale"

      Rating: 3
Yet another
      ASCII dump issue  July 02, 2003
Reviewer:
      Peter  from Atlanta, GA
I was wondering what is required to use this same function, except getting this
from a DMP file instead of a database table itself

          Followup:

easy -- all you would need to do is run imp ;)

DMP file formats are not documented, can -- do -- and will change from release
to release (thats why a dmp from 9iR2 cannot be read by 8i for example)

I don't read dmp's

      Rating:
      4
pro*cobol  August 05, 2003
Reviewer:  A
      READER
Tom,

Our project plans to extract Oracle table to flat file using POR*COBOL.

This will be the same logic here ? Can you give me some sample code ?

Thanks!

Gre

          Followup:

http://asktom.oracle.com/~tkyte/flat/index.html
has all of my unloaders.

COBOL is not a language I can actually code in anymore ;)  It was the first I
learned (punched cards no less) but the least liked...

You know, if you just need to unload to a flat file, you might find one of the
tools linked to above "more then suffcient" and avoid writing code of your own.

      Rating: 5
Yet another
      ASCII dump issue   August 21, 2003
Reviewer:
      Suvamoy Sen  from Hyderabad, India
This article was just great.  It was exactly the kind of code I was looking for.
 In his Expert One-on-One book in the chapter on data loading Tom has provided
alternate codes to do the same using PL/SQL and SQL*PLUS.  The best part is that
he has even provided a PL/SQL package to generate the control file to load the
flat file data generated using the PL/SQL version of his script.  While
generating the flat file, the PL/SQL version of the script encloses all the
fields in quotes '"' even the null ones.  This I find very useful.  The
SQL*LOADER control file generation procedure also creates a control file which
expects that all fields would be enclosed in quotes.  The current Pro*C program
however does not enclose the fields in quotes and additionally does not replace
every occurrence of the quote symbol in the fields with two quotes as the PL/SQL
version of his program does.
I have added a few lines to Tom's Pro*C  program to do just this:
1.  It would enclose every field within quotes, even the NULL ones.
2.  It would separate each field using ','
and finally each line would be terminated using the charcters "~^@#|\n" (quotes
not included).

I am not a professional C programmer and my routine may be inefficient.  If you
have better ways of achieving this please share it with us all.

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define MAX_VNAME_LEN     30
#define MAX_INAME_LEN     30

static char *     USERID = NULL;
static char *   SQLSTMT = NULL;
static char *   ARRAY_SIZE = "10";

#define vstrcpy( a, b ) \
(strcpy( a.arr, b ), a.len = strlen( a.arr ), a.arr)

EXEC SQL INCLUDE sqlca;
EXEC SQL INCLUDE sqlda;

extern SQLDA *sqlald();
extern void   sqlclu();

static void die( char * msg )
{
    fprintf( stderr, "%s\n", msg );
    exit(1);
}

/*
    this array contains a default mapping
    I am using to constrain the
       lengths of returned columns.  It is mapping,
    for example, the Oracle
       NUMBER type (type code = 2) to be 45 characters
    long in a string.
*/

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 };

static void process_parms( argc, argv )
int    argc;
char *    argv[];
{
int    i;

    for( i = 1; i < argc; i++ )
    {
        if ( !strncmp( argv[i], "userid=", 7 ) )
              USERID = argv[i]+7;
        else
        if ( !strncmp( argv[i], "sqlstmt=", 8 ) )
              SQLSTMT = argv[i]+8;
        else
        if ( !strncmp( argv[i], "arraysize=", 10 ) )
              ARRAY_SIZE = argv[i]+10;
        else
        {
            fprintf( stderr,
                    "usage: %s %s %s\n",
                     argv[0],
                    "userid=xxx/xxx sqlstmt=query ",
                    "arraysize=<NN>\n" );
            exit(1);
        }
    }
    if ( USERID == NULL  || SQLSTMT == NULL )
    {
        fprintf( stderr,
                "usage: %s %s %s\n",
                 argv[0],
                "userid=xxx/xxx sqlstmt=query ",
                "arraysize=<NN>\n" );
        exit(1);
    }
}

static void sqlerror_hard()
{
    EXEC SQL WHENEVER SQLERROR CONTINUE;

    fprintf(stderr,"\nORACLE error detected:");
    fprintf(stderr,"\n% .70s \n", sqlca.sqlerrm.sqlerrmc);

    EXEC SQL ROLLBACK WORK RELEASE;
    exit(1);
}

static SQLDA * process_1(char * sqlstmt, int array_size )
{
SQLDA *    select_dp;
int     i;
int        j;
int        null_ok;
int        precision;
int        scale;
int        size = 10;

    fprintf( stderr, "Unloading '%s'\n", sqlstmt );
    fprintf( stderr, "Array size = %d\n", array_size );

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();
       EXEC SQL PREPARE S FROM :sqlstmt;
       EXEC SQL DECLARE C CURSOR FOR S;

    if ((select_dp = sqlald(size,MAX_VNAME_LEN,MAX_INAME_LEN))
                   == NULL )
        die( "Cannot allocate  memory for select descriptor." );

    select_dp->N = size;
    EXEC SQL DESCRIBE SELECT LIST FOR S INTO select_dp;
    if ( !select_dp->F ) return NULL;

    if (select_dp->F < 0)
    {
        size = -select_dp->F;
        sqlclu( select_dp );
        if ((select_dp =
                sqlald (size, MAX_VNAME_LEN, MAX_INAME_LEN))
                      == NULL )
        die( "Cannot allocate  memory for descriptor." );
        EXEC SQL DESCRIBE SELECT LIST FOR S INTO select_dp;
    }
    select_dp->N = select_dp->F;

    for (i = 0; i < select_dp->N; i++)
        select_dp->I[i] = (short *) malloc(sizeof(short) *
                                                array_size );

    for (i = 0; i < select_dp->F; i++)
    {
        sqlnul (&(select_dp->T[i]),
                &(select_dp->T[i]), &null_ok);
        if ( select_dp->T[i] <
                     sizeof(lengths)/sizeof(lengths[0]) )
        {
            if ( lengths[select_dp->T[i]] )
                 select_dp->L[i]  = lengths[select_dp->T[i]];
            else select_dp->L[i] += 5;
        }
        else select_dp->L[i] += 5;

        select_dp->T[i] = 5;
        select_dp->V[i] = (char *)malloc( select_dp->L[i] *
                                               array_size );

        for( j = MAX_VNAME_LEN-1;
             j > 0 && select_dp->S[i][j] == ' ';
             j--);
        fprintf (stderr,
                "%s%.*s", i?",":"", j+1, select_dp->S[i]);
    }
    fprintf( stderr, "\n" );

    EXEC SQL OPEN C;
    return select_dp;
}

static void process_2( SQLDA * select_dp, int array_size )
{
int    last_fetch_count;
int        row_count = 0;
short    ind_value;
char    * char_ptr, s2[8002];
int    i, j, k, l = 0;

    for ( last_fetch_count = 0;
          ;
          last_fetch_count = sqlca.sqlerrd[2] )
    {
        EXEC SQL FOR :array_size FETCH C
                      USING DESCRIPTOR select_dp;

        for( j=0; j < sqlca.sqlerrd[2]-last_fetch_count; j++ )
        {
            for (i = 0; i < select_dp->F; i++)
            {
                ind_value = *(select_dp->I[i]+j);
                char_ptr  = select_dp->V[i] +
                                  (j*select_dp->L[i]);
                s2[l = 0] = '"';
                for(k = 0; char_ptr[k] != '\0'; k++) {
                    s2[++l] = char_ptr[k];
                    if (char_ptr[k] == '"') s2[++l] = '"';
                }
                s2[++l] = '"';
                s2[++l] = '\0';
                printf( "%s%s", i?",":"", s2 );
            }
            row_count++;
            printf( "~^@#|\n" );
        }
        if ( sqlca.sqlcode > 0 ) break;
    }

    sqlclu(select_dp);

    EXEC SQL CLOSE C;

    EXEC SQL COMMIT WORK;
    fprintf( stderr, "%d rows extracted\n", row_count );
}

main( argc, argv )
int    argc;
char *    argv[];
{
EXEC SQL BEGIN DECLARE SECTION;
VARCHAR   oracleid[50];
EXEC SQL END DECLARE SECTION;
SQLDA    * select_dp;

    process_parms( argc, argv );

    /* Connect to ORACLE. */
    vstrcpy( oracleid, USERID );

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();

    EXEC SQL CONNECT :oracleid;
    fprintf(stderr, "\nConnected to ORACLE as user: %s\n\n",
             oracleid.arr);

    EXEC SQL ALTER SESSION
      SET NLS_DATE_FORMAT = 'ddmmyyyyhh24miss';

    select_dp = process_1( SQLSTMT, atoi(ARRAY_SIZE) );
    process_2( select_dp , atoi(ARRAY_SIZE));

    /* Disconnect from ORACLE. */
    EXEC SQL COMMIT WORK RELEASE;
    exit(0);
}

      Rating: 5
Make
      error  September 22, 2003
Reviewer:  Gerald
      Koerkenmeier  from St. Louis, MO USA
Received the following errors when doing a make on the source code.  Any ideas?

        cc      -I. -I/usr/local/opt/oracle/product/8.1.7/precomp/public
-I/usr/local/opt/oracle/product/8.1.7/rdbms/public
-I/usr/local/opt/oracle/product/8.1.7/rdbms/demo
-I/usr/local/opt/oracle/product/8.1.7/plsql/public
-I/usr/local/opt/oracle/product/8.1.7/network/public     flat_file.c -Wl,+s
-Wl,+n  -o flat_file -L/usr/local/opt/oracle/product/8.1.7/precomp/lib/
-L/usr/local/opt/oracle/product/8.1.7/lib/ -o flat_file
(Bundled) cc: "flat_file.pc", line 23: error 1705: Function prototypes are an
ANSI feature.
(Bundled) cc: "flat_file.pc", line 95: error 1705: Function prototypes are an
ANSI feature.
(Bundled) cc: "flat_file.pc", line 168: error 1000: Unexpected symbol: "*".
(Bundled) cc: "flat_file.pc", line 168: error 1705: Function prototypes are an
ANSI feature.
(Bundled) cc: "flat_file.pc", line 168: error 1573: Type of "select_dp" is
undefined due to an illegal declaration.
(Bundled) cc: "flat_file.pc", line 181: error 1527: Incompatible types in cast:
Must cast from scalar to scalar or to void type.
(Bundled) cc: "flat_file.pc", line 186: error 1532: Reference through a
non-pointer.
(Bundled) cc: "flat_file.pc", line 186: error 1566: Test expression in for must
be scalar.
(Bundled) cc: "flat_file.pc", line 188: error 1532: Reference through a
non-pointer.
(Bundled) cc: "flat_file.pc", line 188: error 1528: Subscript expression must
combine pointer and integer.
(Bundled) cc: "flat_file.pc", line 188: error 1554: Indirection must be through
a pointer.
(Bundled) cc: "flat_file.pc", line 189: error 1532: Reference through a
non-pointer.
(Bundled) cc: "flat_file.pc", line 189: error 1528: Subscript expression must
combine pointer and integer.
(Bundled) cc: "flat_file.pc", line 190: error 1532: Reference through a
non-pointer.
(Bundled) cc: "flat_file.pc", line 190: error 1528: Subscript expression must
combine pointer and integer.
(Bundled) cc: "flat_file.pc", line 236: warning 563: Argument #1 is not the
correct type.
*** Error exit code 1

Stop.

          Followup:

you are not using an ANSI c compiler.

maybe this is HP/UX where the default (for free) compiler is not the ansi c one,
which the code requires.

gcc works on many platforms
the system ANSI c compiler (eg: sparcworks on solaris) works.

      Rating: 4
Its realy
      v.good  September 29, 2003
Reviewer:
      Srikanth  from Srikanth, Singapore
Hi tom,

I have gone through the code and its good and I have a question to you that, can
I print the same result like 'to shi' format in my report.

each field with some spaces...

could you please help me to know where can I change the format in the code.

Regards
Sri.

          Followup:

'to shi' ???

but anyway, the code is right there -- you can modify anything at all you want.

      Rating: 5
One more
      question  September 30, 2003
Reviewer:  Gerald
      Koerkenmeier  from St. Louis, MO
Thanks for the ANSI C tip.  When I compile now I get:

cc: "flat_file.c", line 773: warning 604: Pointers are not
assignment-compatible.
cc: "flat_file.c", line 773: warning 563: Argument #1 is not the correct type.
cc: "flat_file.c", line 773: warning 604: Pointers are not
assignment-compatible.
cc: "flat_file.c", line 773: warning 563: Argument #1 is not the correct type.
/usr/ccs/bin/ld: Unsatisfied symbols:
   sqlnul (first referenced in flat_file.o) (code)
   sqlclu (first referenced in flat_file.o) (code)
   sqlald (first referenced in flat_file.o) (code)
   sqlcxt (first referenced in flat_file.o) (code)

Line 773 is:
    vstrcpy( oracleid, USERID );

Am I doing something wrong?

          Followup:

warnings are OK.

you need to link in our libraries though.  see

http://asktom.oracle.com/~tkyte/proc_makefile/
for an example

      Rating: 5
create CSV
      using PRO*C and stored procedure  October 16, 2003

Reviewer:  Manish  from NY USA
Hi Tom,
I am new to Oracle and Pro*C. I liked your pro*C program to create CSV file but
how do I get it running if I have a Stored Procedure ( in a package ) which
takes parameters and has a Ref Cursor out parameter.We have a lot of stored
procedures like this.
I assume that by passing SQL('sqlstmt=select * from emp') as a parameter will be
slower than static stored procedure.
>>>./array_flat userid=scott/tiger 'sqlstmt=select * from emp'  arraysize=100

          Followup:

a ref cursor is just an opened cursor, a cursor opened by plsql.

it'll perform no differently at all then pro*c opening the cursor itself.  they
are after all just cursors at the end of the day and they'll both have to do the
same amount of work.

you can either recode the code to do plsql calls or use it as is, they'll
perform pretty much the same as they both just use cursors.

      Rating: 5
Please help
      PRO*C CSV using Stored Procedure  October 16, 2003

Reviewer:  Manish  from NY USA
The Stored procedures which we have return 15 million rows by 25 columns. I
tested your both solutions a) sqlplus ( using dos prompt) b) Pro*C ( above
program ). As you mentioned correctly the PRO*C works great!! for huge data
coversion to CSV.
you said following to run above program:
>> $ ./array_flat userid=scott/tiger 'sqlstmt=select * from emp' arraysize=100 >

test.dat
How to modify the above PRO*C program so that it can except
package.procedure(agr1,arg2..) returning refcursor
and convert the procedure output to CSV.

Please advice.

          Followup:

You would read the pro*c docs and go for it.  I don't have any examples at hand
with dynamic sql and all.

      Rating: 5
specify date
      format in SQL  October 21, 2003
Reviewer:
      Wor  from USA
Hi Tom,
I am using PRO*C program and it is very efficient.
I notice that you have specified date format in the PRO*C code. However I tried
to specify the format in SQL
e.g. select to_char(DATE_COL,'MM/DD/YYYY') from ...
I modified the following line in pro*c instead of providing date format.
sqlstm.stmt = alter SESSION SET NLS_DATE_FORMAT..
to sqlstm.stmt = ""

I get the following error.
ORA-00900: invalid SQL statement

Since this is a generic routine for all queries I would like to specify the date
format in the SQL.

Please help.

          Followup:

are you saying you would like to pass in the NLS_DATE_FORMAT?

not sure what you are trying to do?

      Rating: 5
Date
      format  October 23, 2003
Reviewer:  Wor
       from NY
I have many queries and some require different dates formats in the output e.g.
mm/dd/yyyy or mm/dd/yyyy hh:mm:ss or DD-MON-YYYY HH24:MI:SS.
What I notice is you have specified the date format in the main () as EXEC SQL
ALTER SESSION ..SET NLS_DATE_FORMAT = 'DD-MON-YYYY HH24:MI:SS';
If I have this in pro*c than all the queries will return the same format instead
what I want is if I can specify the date format in the query e.g.
to_char(date_col,'MM/DD/YYYY')
so that based on the query the date format will be different in the output.

If I use the existing program as is than when I run the command as follows
dos prompt> exe_name userid=usr1/pass1 "sqlstmt=SELECT
to_char(DATE_COL,'MM/DD/YYYY') FROM TABLE1" arraysize=100 >  test.csv

The program crashes.
When I debugged I found out that the program crashes in
pc file>>
EXEC SQL DESCRIBE SELECT LIST FOR S INTO select_dp;
exact line in .c file is>>
sqlcxt((void **)0, &sqlctx, &sqlstm, &sqlfpn);

Can you please help to avoid the program crash and let me know how to specify
the date format in the query.
I have windows 2000 setup with oracle 8.1.6

For everthing else the Pro*c   works fine...

Please help.

          Followup:

> ./array_flat userid=/ arraysize=100 sqlstmt="select to_char( sysdate,
'mm/dd/yyyy hh24:mi:ss month' ) from dual"

Connected to ORACLE as user: /

Unloading 'select to_char( sysdate, 'mm/dd/yyyy hh24:mi:ss month' ) from dual'
Array size = 100
TO_CHAR(SYSDATE,'MM/DD/YYYYHH2
10/23/2003 12:54:12 october
1 rows extracted

could be the length of the column name - try aliasing that with a short name

      Rating: 5
Thank you very
      much  October 23, 2003
Reviewer:
      A reader
It works great!!!!

      Rating: 5
Array size =
      100 or 10000  October 29, 2003
Reviewer:
      wor  from NY
Hi Tom,
I was testing with a very long SQL query ( it returned only 1000 records ) with
lots of joins and unions.
approx 4000 characters including spaces.

with array size = 1000 it worked fine but with array size = 100 I got a
sqlnet.log with following data

How does array size affect the sql execution can I just increase the size to
10000 or is there any limit or performance hit ?

with array size = 100 following is the sqlnet.log
***********************************************************************
Fatal NI connect error 12560, connecting to:

(DESCRIPTION=(ADDRESS=(PROTOCOL=BEQ)(PROGRAM=oracle)(ARGV0=oracleORCL)(ARGS='(DES
CRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))'))(CONNECT_DATA=(SID=ORCL)(CID=(PRO
GRAM=D:\reportsdll\Project1.exe)(HOST=NY-WORLIKAR2K)(USER=WorliMa))))

  VERSION INFORMATION:
    TNS for 32-bit Windows: Version 8.1.6.0.0 - Production
    Oracle Bequeath NT Protocol Adapter for 32-bit Windows: Version 8.1.6.0.0 -
Production
  Time: 29-OCT-2003 16:59:44
  Tracing not turned on.
  Tns error struct:
    nr err code: 0
    ns main err code: 12560
    TNS-12560: TNS:protocol adapter error
    ns secondary err code: 0
    nt main err code: 530
    TNS-00530: Protocol adapter error
    nt secondary err code: 126
    nt OS err code: 0

          Followup:

the array size cannot possibly affect a connection.

Not even remotely.

the arraysize is a setting for a query -- doesn't even come into play with
connecting.  Something else was happening here.

      Rating: 5
SHARED
      MEMORY  October 30, 2003
Reviewer:  kom
       from US
Hi Tom,
I am using this pro*C program for running batch queries( same queries with input
variables value change ) plus different queries. Each query creates its own .csv
file.

The problem is since the proc* excepts dynamic queries
after certain time it completely fills the shared memory. I found out that using
Stored procedures this problem does not occur since only one instance of
procedure remains in the shared memory even though the input parameter value
changes.

But I am extremely impressed by your pro*c program and I would like to
definitely use it.
How do I overcome the shared memory problem.
( Ideally only one instance of the query should remain in the shared memory if
only the input values change or oracle should auto clean the shared memory )

I checked the shared memory usgae by using
SELECT * FROM sys.V_$SQL_SHARED_MEMORY
WHERE sql_text LIKE 'select OFFICENUMBER%'
..
SELECT * FROM sys.V_$SQL_SHARED_MEMORY
WHERE sql_text LIKE 'select DEPTNUMBER%'

Please help.

          Followup:

add

exec sql alter session set cursor_sharing=force;

to the proc code after the connect, before the parse.

      Rating: 5
in
      addition  October 30, 2003
Reviewer:  kom
       from US
To add to previous question
I am using oracle 8.1.6 on windows 2000.

      Rating:
      5
  October 30, 2003
Reviewer:  kom
       from US
Hi Tom,

>>> exec sql alter session set cursor_sharing=force;

this feature is very good for any dynamic query without bind varables.

why doesn't oracle 8i have this feature set in basic installation . Why should I
call this explicitly.

Ideally what oracle should do is:
for any dynamic query withour bind variables it should cursor_sharing=force
automatically. Because if not set than that query will get chache unnecessarly.

is there any disadvantage by using this ?
why did you say just session why not at system level ?

          Followup:

do you have my book "expert one on one Oracle", if so, read chapter 10.

there are lots of reasons (besides backwards compatibility) that this is not the
default.

consider what happens to:

select substr( ename, 1, 10 ) from t;

right now, that is not using binds.  but, the parser knows "ahh, 10 characters,
describe this query and I'll tell you 10"

using cursor sharing

select substr( ename, :bv1, :bv2 ) from t;

uses binds (un-necessarily!).  the parser now knows NOTHING.  describe this
query and it'll "make up a length".  consider what happens to report generators.

or

select * from t where x = 5;

will, x has 2 values.  1,000,000 of them are 6.  2 are 5.  that query should use
and index, but

select * from t where x = :bv1;

won't (only two values, could be 5 or 6, bummer)

basically, cursor sharing is a CRUTCH to be used to temporarily get you over a
BUGGY application that the developers MUST FIX.

Or, as in the case of this generic utility, can be used for this session.

      Rating: 5
To
      Kom  October 30, 2003
Reviewer:  Menon

Search for cursor_sharing on this site.
In particular read

http://asktom.oracle.com/pls/ask/f?p=4950:8:243122254267634687::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:5180609822543,
or get Expert one on one by Tom where he explains
the disadvantages of using cursor_sharing.

      Rating:
      5
  October 30, 2003
Reviewer:  kom
       from US
>>>> I read your article saying
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
>>>>>>
After reading this now I am afraid to use cursor_sharing=force since I am
developing new robust application.

I really love the speed of your pro*c program.
How do I modify your Pro*c program to support bind variables.

Please help.

          Followup:

this pro*c program is just a data dumper, very simple, very straightforward, not
really designed to dump 1,000 different queries.

do you need to call this "from the command line" -- if so, cursor_sharing=force
is correct.

would you be calling this as an API from a c program?  if so, we can do better.

but as a single command line program, it would be OK with cursor sharing

      Rating:
      5
  October 31, 2003
Reviewer:  kom
       from US
>> would you be calling this as an API from a c program?  if so, we can do
better

yes you are correct. I liked your pro*c code so much that I created a "C" dll to
have more control over execution. I created a new func CreateCSV and exported
that func using .def file. I changed the main func to mainnew and instead of
excepting argv argc and am passing parameters to this func from the exported
funtion CreateCSV.
This CreateCSV( char*  strDSN, char* strSQL, char* strArrSize, char* strFileName
is called from VB dll as an API call.

I need to make this more efficient to handle many dynamic queries with bind
variable support.

also I have alot of stored procedures (in packages )  how do I handle them ( if
possible ) .

          Followup:

I'd change the parameter list to include:

char * bindValues[]

and then some code like:

set_bind_variables(char * bindValues[] )
{
int     size = 10;
int     i;
char    bvname[255];
SQLDA   * bind_dp;

    if ((bind_dp = sqlald(size, 255, 255)) == NULL)
        die( "Cannot allocate memory for bind descriptor" );

    bind_dp->N = size;
    EXEC SQL DESCRIBE BIND VARIABLES FOR S INTO bind_dp;
    if ( sqlca.sqlcode ) return sqlerror_soft(__LINE__);

    if (bind_dp->F < 0)
    {
        size = -bind_dp->F;
        sqlclu(bind_dp);
        if ((bind_dp = sqlald(size, 255, 255)) == NULL)
            die( "Cannot allocate memory for bind descriptor" );

        bind_dp->N = size;  /* Initialize count of array elements. */
        EXEC SQL DESCRIBE BIND VARIABLES FOR S INTO bind_dp;
        if ( sqlca.sqlcode ) return sqlerror_soft(__LINE__);
    }

    bind_dp->N = bind_dp->F;
    for (i = 0; i < bind_dp->N; i++)
        bind_dp->I[i] = (short *) malloc(sizeof (short));

    for (i = 0; i < bind_dp->F; i++)
    {
        bind_dp->V[i] = bindValues[i];
        bind_dp->L[i] = strlen(bind_dp->V[i]);
        *bind_dp->I[i] = (*bind_dp->V[i]) ? 0 : -1;
        if ( *bind_dp->I[i] )
        {
            bind_dp->V[i] = (char *)malloc(5);
            bind_dp->L[i] = 4;
        }
        bind_dp->T[i] = 1;
    }
    EXEC SQL OPEN C USING DESCRIPTOR bind_dp;
    if ( sqlca.sqlcode ) return sqlerror_soft(__LINE__);

    for( i = 0; i < bind_dp->N; i++ ) free( bind_dp->I[i] );
    sqlclu( bind_dp );
}

and then you can pass queries like:

select * from t where vc = :x and num = to_number(:y)
 and dt = to_date(:z,'dd-mon-yyyy hh24:mi:ss' )

(the use of TO_NUMBER and TO_DATE being very very very important since we are
generically binding only strings!!!!)

have you glanced through the pro*c docs?  Not sure what you want to do with the
plsql stored procedures, but you basically just parse and execute them, you
wouldn't fetch from them....  but not much different then the existing code.

      Rating:
      5
set_bind_variables(char * bindValues[] )  November
      03, 2003
Reviewer:  kom  from US
Hi Tom,
I am really very sorry to trouble you. I am completely new to Pro*C and my
project deadline has arrived. I am confused about where to call
set_bind_variables(char * bindValues[] ). should I call in main (..) before
select_dp = process_1( SQLSTMT, atoi(ARRAY_SIZE) );
process_2( select_dp , atoi(ARRAY_SIZE));
or should I not call process_1 and process2 at all.
How do I integrate this new bind function into existing code.

          Followup:

sorry -- don't have time to code it right now, on the road.

use the alter session set cursor_sharing=force, it'll work just dandy for now.

      Rating: 5
Bind Variables
      IN clause  November 04, 2003
Reviewer:  kom
       from US
Hi Tom,
I could get the bind variables working. ( Thanx alot for that function )

Is there a way I can bind the IN clause.
e..g SELECT * FROM table1 where col1 IN ('abc','pqr') to something SELECT * FROM
table1 where col1 IN (:x) for both VARCHAR and INT.
is that possible using pro*c ?

          Followup:

search this site for

variable in list

      Rating: 5
Great!!
      thanks  November 05, 2003
Reviewer:  kom
       from US
I just love your web-site. It just gives me a feeling that nothing is Impossible
and there is always a room for improving efficiency.

      Rating: 5
Strored
      Procedures  November 06, 2003
Reviewer:  kom
       from US
Hi Tom,
I am successfully running the PRO*C program ( I created a C dll ) via VB as an
API call for Dynamic queries including bind variable support.

I also have a lot of Stored procedures in packages.
I wrote another Pro*c program to handle stored procedures.
the main parts of the program are as follows..
e.g.
I declared 2 variables like this...
EXEC SQL BEGIN DECLARE SECTION;
  SQL_CURSOR  chk_access; ( this is ref cursor returned from SP )
  char        firstn [15];
  char        lastn [15];
  long inputVar; ( input variable for SP )
EXEC SQL END DECLARE SECTION;
then ...
/* Execute stored functio to open cursor for recordset */
  EXEC SQL ALLOCATE :chk_access;
  EXEC SQL EXECUTE
  BEGIN
:inputVar := 12;
    :chk_access := TEST_MAN.TEST(:inputVar);
  END;
  END-EXEC;
  for ( ;; )
  {
    EXEC SQL fetch :chk_access
             into  :firstn,:lastn
             ;
    if ( sqlca.sqlcode)
       break;

     printf ("%s,%s\n",firstn,lastn);
  }

problem:
1) This cannot be generic because I have to declare array variables in advance.
This program can call any procedure which will return refcursor. e.g.   firstn
[15] and lastn [15];. I do not know the size of variables returned by ref cursor
in advance.
2) It takes alot of time to generate a csv file. around 10 times plus more
slower than if I run the same sql using your pro*c program.
Is there any way that I can just modify your exisiting pro*c code to  execute
stored procedures. with lightning fast speed.

      Rating: 5
Please
      help  November 10, 2003
Reviewer:  kom
       from US
Hi Tom,
I read several pro*c documents and I find similar code for packages and stored
procedures.
As I mentioned earlier ( my previous question ) that with this approach I
noticed 2 problems:
1) Speed is extremely slow  compared to your pro*c program.
2) Cannot be generic

Please let me know whether and how it is possible to overcome the above
problems.

Thanks in advance,
Kom

          Followup:

for dumping data to disk like this, C is going to naturally be faster and more
efficient the plsql.

It can however be generic.  See

http://asktom.oracle.com/~tkyte/flat/index.html

      Rating:
      5
Clob  November 10, 2003
Reviewer:
      Pal  from US
Hello Tom,

Will this pro*c program work if I have a CLOB column in the select statement.
If possible than how do I modify the program.

Thanks
Pal

          Followup:

you would have to process the lob specially (pro*c docs describe how)

but most clobs have NEWLINES in them, makeing them not very suitable for a
generic "dump into csv"

      Rating: 5
Add SQLLDR
      control information  November 12, 2003
Reviewer:
      Gerald Koerkenmeier  from St. Louis, MO USA
Tom,

How would you add the SQLLDR control header to the output automatically, as you
did with the SQL*Plus script?

$ sqlldr_exp scott/tiger dept
LOAD DATA
INFILE *
INTO TABLE dept
REPLACE
FIELDS TERMINATED BY '|'
(
deptno
,dname
,loc
)
BEGINDATA
10|ACCOUNTING|NEW YORK
20|RESEARCH|DALLAS
30|SALES|RESTON
40|OPERATIONS|BOSTON

          Followup:

you'd have to modify this code:

...
        for( j = MAX_VNAME_LEN-1;
             j > 0 && select_dp->S[i][j] == ' ';
             j--);
        fprintf (stderr,
                "%s%.*s", i?",":"", j+1, select_dp->S[i]);
    }
    fprintf( stderr, "\n" );
....

which shows how to access the column names -- and print it out.

getting the tablename -- that is upto you (since array_flat here works on
arbitrary QUERIES -- there can and often is more then one table referenced!).
So you might:

printf( "load data infile * into table XXXXX replace fields ... (\n" );
print out column names
printf( ")\nbegindata\n" );

and just edit the file to replace the XXXXX or add an additional parameter to
the program -- the name of the table you want to put there.

      Rating:
      5
Multi-user  December 02, 2003
Reviewer:
      Wor  from US
Hi Tom,
I have converted the above pro*C code into a C DLL which I call from VB
component which is called from ASP.
The VB component is in COM+. I am calling this C dll using standard C API
calling conventions from VB. I have exported a C funtion which wraps all the
above Pro*c funtions.

Win2000 and oracle 8.1.6

C dll generates a .csv file with the name and path specified by VB dll,
basically a online report.

When this whole process is called sequetially  say 2000 times or more one after
another in a loop it works great!!.
say e.g. user1 click on the browser button "generate report"
ASP->calls->VB->calls->C DLL and the report is ready.
again user1  clicks button "generate report" like this 2000 times or more...
works great!!

Problem:
When 2 or more users click the button at the same time then
2 or more instances of VB component is created in COM+
and both try to call C DLL using API call together and the PRO*C crashes...
Is this a problem  of multi-threading ?
I read your article on multi-threading but in my case the userid/pass will
always be same only and the simultaneous calls are made from VB.
note: same userid / pass are passed by VB to C DLL.

do you want me to post my pro*C code..
how do I change the above code for multi-user access.
Please help..

          Followup:

depending on how big the "report" is, i would use the PLSQL implementation, see

http://asktom.oracle.com/~tkyte/flat/index.html

but, each extproc runs in its own address space (you should see 2 extproc.exe's
out there). they are not "threads" in the oracle process.

so, i would put in copious debug (if you have my book expert one on one Oracle
-- I demonstrate that heavily in the chapter on external procedures) and try to
find out WHERE it is crashing.

      Rating: 5
Concurrent
      Users, Data Unloading using Pro*C  January 07, 2004

Reviewer:  Wor  from USA
Hi Tom,
To continue with my previous post.
My reports can run from 1 record 1 colum to 10 Million and 25 columns. Since I
need faster speed I want to implement using Pro*C.
As I previously said my workflow
User action on IE browser->VB component in comp+ ( calls C dll using declare API
provided by VB )-> C.dll ( pro*C code )-> generates the .csv file.
Everything works fine with one user at a time ( similar to what you mentioned
using command prompt, one at a time )
But incase multi users try to create .csv files concurrently than data retrieved
in select_dp gets courrupted in between the csv file creation.
I have your book and in chapter "Data Loading->topic Unload Data" you mentioned
that the code for pro*C is on Wrox web-site. can you give me exact URL.
Since the select_dp gets corrupted can you please show me example how to modify
the existing pro*C code to handle concurrent request for csv files ( e.g if
contexts have to be used, how to declare select_dp like structures etc )
As always you are the BEST....
Thanks in advance,
Wor

          Followup:

you are in windoze dll hell.

dll's are NOT re-entrant. they are not nice like shared object code on unix and
such...

You need a C programmer there to help you out.  it is bigger then a bread box.

http://asktom.oracle.com/~tkyte/flat.html
has pointers to the code.

      Rating: 5
in
      addition...  January 07, 2004
Reviewer:  Wor
       from USA
In addition to my previous question
I see a line
static unsigned long sqlctx = 33013571;
in my .C file generated by Pro*C compiler ( this is not there in .pc file )
Is this one more cause of problem ? If so how do I solve it?
Regards,
Wor

          Followup:

you would have to code using "threads=y" and use contexts (instead of a single
global context) and other things to make the code RE-ENTRANT.  Windows is not a
friendly platform for this.  I would suggest OCI, much easier to remove the
re-entrancy issue.

Each user would need their own connection to the database, their own context.
You would have to be thread safe, re-entrant.

Either that or just serialize the entire thing  -- use a mutex to make sure one
person at a time is dumping to disk.

      Rating:
      5
Wor  January 08, 2004
Reviewer:  wor
       from USA
Hi Tom,
1) The link does not work. It says page cannot be found.
Please send me the good URL.
2) Also what I observe is there are lot of global variables generated in .c
file, is this the cause too ?

You said >>
You need a C programmer there to help you out.  it is bigger then a bread box.

http://asktom.oracle.com/~tkyte/flat.html
has pointers to the code.

      Rating:
      4
  January 08, 2004
Reviewer:
      A reader
http://asktom.oracle.com/~tkyte/flat/index.html

      Rating:
      5
  January 08, 2004
Reviewer:  MP
       from Austin, TX
Useful, and as is often the case, entertaining.  The only jarring note -- and
it's frequent -- is the use of the word "then" when you mean "than."  Maybe it's
a keyboard problem.

          Followup:

tis a pebkac problem actually.  i have a really hard time with those two for
some reason.  i'll just try to stop using them.

      Rating: 4
some info for
      Pro*C options  January 14, 2004
Reviewer:
      Andrey Prokopenko  from Moscow, Russia
key option that user must set for this code is
parse=full
by some unknown reason in windoze this parameter is set
to parse=none

January 08, 2004 -- Thanks for the question regarding "EXPLAIN
      PLAN vs TKPROF", version 9.2.0

      You Asked
Tom,

1. My understanding is that Card= in EXPLAIN PLAN provides an ESTIMATED
CARDINALITY whereas the TKPROF provides the "ACTUAL" number of rows processed.
Is my understanding correct. If not pls provide me an example.
2. A non-unique index stores the ROWID as part of the key to make it unique. In
that case, will deleted space still be reused. The reason I am having this doubt
is that the ROWID of the next row that would occupy the deleted slot is not
known until the ROW is inserted. In that case, how will Oracle use the vacant
spot?  I would appreciate if you could provide me an example explaining that
3. Example of an application of 9i TABLE FUNCTIONS,streams and user-defined
aggregate/analytic functions

Thanks much

      and we said...
1) show me a "card" in a tkprof?

In an explain plan, the CARD= is the estimated cardinality, one of the factors
the CBO uses to determine the best overall plan.

In tkprof, there are "rows", these are actual rows that flowed through each
phase of the query plan that was choosen when that query was executed with its
inputs.  These are the actuals -- for that specific execution of that query.

2) the rowid is stored in the index as part of the key -- yes.  When you delete
the row, the key is flagged as deleted.  Now, ANY key that fits where this
deleted key is can reuse that space.

For example, you insert into a table three rows

ops$tkyte@ORA920PC> create table t( x int );
Table created.

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

ops$tkyte@ORA920PC> insert into t values ( 1 );
1 row created.

ops$tkyte@ORA920PC> insert into t values ( 2 );
1 row created.

ops$tkyte@ORA920PC> insert into t values ( 999999999999999999999 );
1 row created.

ops$tkyte@ORA920PC> commit;
Commit complete.

Now, say you delete from t where x = 2.  Well, ANYTHING that fits between 1 and
that big number can take its place (thats a pretty infinite set there).  The
rowid is "last" in the key -- so, its the data in your table that decides
whether it can go there.  We do not need to reuse the number "2", just need a
key that fits between 1 and 999999999999999999999.

3) search for PIPELINED on this site for 9i table functions....

streams - bigger then a breadbox, not something I can really "example" here.
but read:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96571/toc.htm
for user defined aggregages -- search for stragg

for analytics -- well, they are all over.  search for

rock and roll

Review &
      Followup

      Rating:
      5
  January 05, 2004
Reviewer:  Frank
       from NL
<quote>search for rock and roll </quote>

Very intuitive searchterm :-)

      Rating: 3
CPU vs
      Elapsed  January 07, 2004
Reviewer:
      A reader  from NJ, USA
Tom,

I remember somewhere in one of your discussions where you demonstrate using a
PL/SQL block, the difference between CPU time vs Elapsed/Wall clock time by
doing a SLEEP inside a PL/SQL block. Can you provide me the link for that
example or demonstrate it.

Thanks

          Followup:

That, that is pretty darn simple:

ops$tkyte@ORA920PC> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA920PC> exec dbms_lock.sleep(1);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC>

BEGIN dbms_lock.sleep(1); END;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.03       0.39          0         12          0           0
Execute      1      0.00       1.05          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.03       1.44          0         12          0           1

      Rating: 3
Elapsed
      Time  January 08, 2004
Reviewer:
      A reader  from NJ, USA
Tom,

1.If my understanding is correct, the difference of 1.41 seconds between CPU and
Elapsed time is due to the 1 second sleep caused by SLEEP command. Is that
correct? Pls confirm.
2. If there is a major discrepancy between CPU and Elapsed time, then there is
some wait event going on. Also, in the 9i TKPROF report I tried to add up the
wait times and this difference does not match the CPU/Elapsed time differences.
Is it due to some calculation/rounding error?

          Followup:

1) well, one SECOND is.  the other bit (.36 at least) happened during the parse.
 that could have been waiting for cpu, latches, whatever.

2) cpu is a major one.  cpu is not a "wait event".  if you are waiting for cpu,
you aren't registering a "wait"

January 08, 2004 Raymond -- Thanks for the question regarding
      "Blocks - 2 or 4 or 8k?", version version8.0.5

      You Asked
What benefits can be gained from changing from 2k blocks to 4k
blocks or even 8k blocks

      and we said...
Here are a couple of pros and cons (some of them will be both
pros and cons -- depends on your perspective)....

going from 2k to Xk where X > 2...

o the amount of block overhead in your database will decrease.
There is a fixed amount of overhead per block.  If you have a
block that is 4 times LARGER you will have less then 1/4 the
block overhead after you change over.

o you will have less row chaining and perhaps less row
migrations.  Row chaining happens when a row is too large to fit
on one contigous block -- this is much less likely in an Xk
block then a 2k block.  Row migration happens when we attempt to
update a row but discover there is insufficient space on the
block to do so.  We leave the "head" of the row on the original
block (cannot change the rowid) and put the row itself on
another block entirely.  Going from 2k with a 10% pctfree
(default) to an Xk block with 10% free means we have perhaps
more free space on each block to accomidate this (but we also
have more rows per block so it might not matter all of the
time).

o you will have more people contending for the same block
perhaps.  We do lots of stuff at the block level -- you may
notice more contention in this area.

o you will have the same amount of data cached (volume) but the
granularity of the data will be N times larger.  For every row
you want, you might get N times as many rows pulled along.  This
can be good -- this can be bad.  It really depends on what you
are doing.  In a heavy duty OLTP system with lots and lots of
scattered reads all over the place -- this might be bad as no
one is going after the same blocks (eg: I read a row on a block
and no one else ever needs that block -- i cached lots of stuff
no one is going to use and I may have bumped out some other data
that we could have used).  In a query intensive system (DSS,
Data Warehouse), it may be very good as I was going to read the
blocks right around my data anyway.  And - people are going to
be needing that data again and again.

o you'll get more rows / block in indexes.  This will result in
indexes that require less maintenance as they tend to need to
reorganize themselves less frequently.

o larger block sizes allow for larger index keys (although
whether or not THIS is a good thing is in question as well,
there is something to be said for reasonably sized index
entries).  The size of the index keys is a little less then
about 40% of the block size.  In an 8k database for example,
you'll get:
create index t_idx on t(x,y,z)
                      *
ERROR at line 1:
ORA-01450: maximum key length (3218) exceeded

Ok a 2k block -- it would be less.

Also -- try to find the native OS blocksize of your system and
make sure to use a block size that is at least as large as this
AND is a multiple of this if possible.  Some of the old
considerations (eg: pre 7.3 -- you had to consider the blocksize
would limit the maxextents of a table) do not apply.

Review &
      Followup

      Rating: 3
OS block
      size  October 23, 2002
Reviewer:  Erik
       from St. Gallen, Switzerland
Okay, so we need to set the db block size as a multiple of the OS block size,
but how do we find the OS block size, for windows or unix systems?

          Followup:

Ask your OS admin.  the tools vary from platform to platform.

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

If I am not wrong on unix we can use

df -g

to find the OS block size. But how can we check on Win-NT/2000.

I have tried to get this by searching google but was not able to. Would
appreciate your answer.

Regards,
Sagi

          Followup:

[tkyte@tkyte-pc-isdn tkyte]$ df -g
df: invalid option -- g
Try `df --help' for more information

[tkyte@tkyte-pc-isdn tkyte]$ uname -a
Linux tkyte-pc-isdn.us.oracle.com 2.4.18-14 #1 Wed Sep 4 13:35:50 EDT 2002 i686
i686 i386 GNU/Linux

so, not all "unix" (i count linux as unix personally)

as I said, -- varies OS by OS by OS.  As an NT "guru", I don't even have a
windows machine to play with anymore.

      Rating: 5
NT Block
      Size  October 23, 2002
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
IIRC... block size on NT/WinMM is 512 bytes...

HTH,

Mark

      Rating:
      4
  February 14, 2003
Reviewer:
      kkrause  from FL
perl -e '$a=(stat ".")[11]; print $a' will get it on a Linux box

      Rating: 5
NTFS block
      size  March 25, 2003
Reviewer:  Alex
      Smelyansky  from USA
1. For wintel default size is 512 byte, but you can change it by pre-formatting
hard drive.

2. Use fsutil fsinfo ntfsinfo c: to get

c:\>fsutil fsinfo ntfsinfo c:
NTFS Volume Serial Number :       0x1e345b64345b3e49
Version :                         3.1
Number Sectors :                  0x0000000004a852c0
Total Clusters :                  0x0000000000950a58
Free Clusters  :                  0x00000000003ae433
Total Reserved :                  0x0000000000000000
Bytes Per Sector  :               512
Bytes Per Cluster :               4096                -- this is answer
Bytes Per FileRecord Segment    : 1024
Clusters Per FileRecord Segment : 0
Mft Valid Data Length :           0x000000000d72a000
Mft Start Lcn  :                  0x00000000000c0000
Mft2 Start Lcn :                  0x00000000004a852c
Mft Zone Start :                  0x0000000000800020
Mft Zone End   :                  0x000000000083b520

3. To get all possible OS block sizes use
c:\ format /?

4. In explorer select your hard drive, click right button, select "Format..."
from
context menu, and see your OS block size as "Allocation unit size".

5. Tom, I wonder, why they know there systems so badly!

          Followup:

Hey, its windows, you aren't supposed to have to know anything.

      Rating: 4
Seems we're not
      all as clever as we think!  March 26, 2003

Reviewer:  Steve  from UK
Microsoft Windows 2000 [Version 5.00.2195]
(C) Copyright 1985-2000 Microsoft Corp.

C:\>fsutil c:
'fsutil' is not recognized as an internal or external command,
operable program or batch file.

C:\>fsinfo c:
'fsinfo' is not recognized as an internal or external command,
operable program or batch file.

C:\>ntfsinfo c:
'ntfsinfo' is not recognized as an internal or external command,
operable program or batch file.

C:\>

And right clicking on the drive - selecting Format shows a format box which only
says "Default Allocation Size" - not the actual size!

      Rating: 4
Updated
      response.  April 10, 2003
Reviewer:  MSaeed

It's been a while since you responded to the original Question. Would you say
that response still holds true in Oracle 8i Env.

I am faced with a schema that uses a lot of Longs, Varchar2(2000), varchar2(255)
etc.... in an OLTP env.

What should be my primary driving factor for choosing a block size: Row
chaining, row migration, block contention or something else ?

If you feel you have answered this somewhere else, could you please provide me
with a link. I have access to metalink as well, but your answers are usually to
the point.

Greatly appreciate your updated response.

          Followup:

The information holds true -- but if you have lots of  fat rows -- smaller
blocks would be a pain, lots of chaining.

We can fix block contention via other methods (pctfree, minimize records per
block) so the chaining/migration would be it.

LONGS -- they will be chained, period, no avoiding it.  You should use CLOBS
instead.

      Rating: 4
Thin rows
      initially becoming fat later  April 14, 2003

Reviewer:  A reader
Hi Tom:
Thanks for the response.

"The information holds true -- but if you have lots of  fat rows -- smaller
blocks would be a pain, lots of chaining."

We can't control that since we have tons of vaqrchar2(2000) columns that can
expand later on.

"We can fix block contention via other methods(pctfree, minimize records per
block) so the chaining/migration would be it."

Good point.

"LONGS -- they will be chained, period, no avoiding it.  You should use CLOBS
instead. "

We are working on that migration.

Is there any truth in the relation of VLM (very large memory addressing for SGA
sizes) and the d/b block size ?
What I mean is that to have an SGA size of over 6 GB must I have a 16k block
size? is this dependant on the underlying OS?

Thanks.
MSaeed

          Followup:

The larger the SGA, the more optimum larger blocks become -- less blocks to
manage and look at when looking for a block in the cache.

If you have a transactional system, I might wonder why you would need a super
large SGA.  Lots of single row, keyed reads of disparate data...

      Rating:
      4
  April 16, 2003
Reviewer:
      A reader
I guess that is one indication that the sales guys are doing their job...well.

Seriously though.

But why would a super large SGA not help for an OLTP system? Is achieving the
"fully" cached d/b  not good in certain situations?

It seem that, a min of 8Gb with at least quad CPU cofig is becomming the norm
for any server hosting a d/b app and a SAN thrown in for data storage.

Thanks.
MS

          Followup:

transactional systems create tons of data that is not read right back -- maybe
never read, maybe read by batch much later.  The db would be fully cached but
only 1% of it is accessed.  So, you have 99% junk in the SGA getting in the way
-- having to be managed.

bigger is not always better.

and on an 8gb machine, my SGA would still be in the 1-3gig range at best I would
suspect, unless I'm using shared server -- then my large pool needs may will
cause it to exceed that.

      Rating: 5
Different block
      sizes for different tablespaces  May 23, 2003

Reviewer:  Steve  from UK
We have an oltp system and the database has a block size of 4k.  However, we do
run reports off of some of the tables for which I have created mv's to ease the
pressure off the oltp side of things.  Is there a case for putting these mv's in
their own tablespace with a different block size say 8k or even 16k?

          Followup:

I've not seen anything one way or the other on this really.

There are theoretical pros and cons to both.

pros -- bigger blocks better for full scan, less block overhead
cons -- memory set aside for 4k cannot be used for 8k blocks and vice versa.
You might have a ton of free memory that the 4k system wants but cannot use or
vice versa.

      Rating:
      5
But.......  May 23, 2003
Reviewer:
      Steve  from UK
they must've been created to have some advantage surely?

          Followup:

to allow you to transport your 2k block sized tablespaces from your OLTP system
to your 16k block size data warehouse for ETL'ing without doing the dump and
load.

      Rating: 4
Is more than
      one Buffer Cache possible?  June 12, 2003

Reviewer:  Tony  from India
Tom, I think its possible to define different block size for each tablespace in
Oracle9i. Do we have to create separate buffer cache for different blocksizes?

Note:- Tablespaces with different block size is possible and need to create
different Buffer Pool in DB2.

          Followup:

if you use more then one blocksize, you need to configure separate caches for
each one.

      Rating: 4
Hybrid
      Systems  August 28, 2003
Reviewer:
      A reader
Hi

We have an OLTP and Reporting Hybrid System (under two schemas), currently
running Oracle 9.2.0.1 on Redhat Advanced Server 2.1. We set db_block_size to
8k, my question is can I recreate my reporting schema's table in 16k tablespaces
and set a db cache for that? I am not sure if Oracle's multi block size feature
is meant to used in my situation.

Also I want to know if bigger db_block_size can "waste" data block buffer, for
example in 4k data blocks let's say I can fit 100 rows in it and in 8k I can fit
200 rows however queries dont always visit same blocks to get the desired
result. For example I have table X with 1000000 rows, a query returns 50000
rows, if these 50000 rows are in 500 blocks in both 4k and 8k, in the latter
case I am wasting my db cache right?

Cheers

          Followup:

you can -- but you would want to ask yourself "why".  do you know that 16k is
better?  have you tested it out?

the multiple block size feature was designed to allow you to transport a
tablespace from a database with blocksize X to a database with blocksize Y so
you can read that data, transform it and load it into the database quickly.  you
would then get rid of the blocksize X tablespace after you loaded it.

Obviously -- in the example you propose -- if the data is in 500 4k blocks, it
will take 50% of the buffer cache of 500 8k blocks.  Have you "wasted" your
buffer cache?  no -- you have simply cached 2x the information and undoubtably
other sessions may well be interested in it.  It could work to your advantage or
disadvantage just as easily.

      Rating: 5
Windows file
      system utilities  September 12, 2003
Reviewer:
      Paul  from UK
fsutil is available on WindowsXP, but MS indicates that it should run fine on
W2K.

Windows is terrible at these underlying system tasks. You need to correlate the
"Default allocation size" and your hard drive size with this information below
to determine the actual size.

Drive Size              Cluster Size       Number of Sectors
---------------------   ----------------   --------------
512MB  or less          512 bytes          1
513MB  to 1024MB(1GB)   1024 bytes (1KB)   2
1025MB to 2048MB(2GB)   2048 bytes (2KB)   4
2049MB and larger       4096 bytes (4KB)   8

Of course, you could always upgrade to a flavour of *nix. ;)

      Rating: 5
Will Oracle
      Fuse O/S blocks?  September 14, 2003
Reviewer:
      Thaha Hussain  from Manama, BAH
Dear Tom,

If we are giving block size higher than o/s block size, can Oracle tamper the
real block size of O/S Or it will create a logical layer on top of it?

- Thaha

          Followup:

we will just ask the OS to write Nk of data -- the OS will do what it does -- at
the block size it does things at.  we need not do anything special -- not
anymore then you would

      Rating: 4
Very good
      information   September 14, 2003
Reviewer:
      sudhir  from Louisville, KY USA
Some other point and questions

1) 9.2 has tablespace compression option - Having 8k block size would be better
than 2k for more efficient compression (likely)

2) Is fast-full-scan better with 8k vs 2k? I guess at block overhead level, 8k
would offer better. But overall?

3) If the size of SGA is kept same, having bigger block size means, less blocks
to manage, thus better use of latches? May be though, concurrency may be
impacted due to lack of free buffers in SGA?

4) Is DBW performance better, due to fact it has to manage less number of
blocks?

Thanks

          Followup:

1) yes

2) 8k will full scan "faster" due to the fact there will be less blocks, yes.

3) as long as you can manage LESS BLOCKS.  ponder that...  you might have AS
MANY or MORE or LESS blocks with 8k vs 2k....

4) see #3.  you might have more blocks to flush in order to make room.  if you
managed lots of 2k blocks, you might have less flushing going on since you can
keep your working set in ram.  that 8k block might have 7k of non-relevant data.
 that 2k block might have only 1k of non-relevant data.  that is another way to
look at it.

      Rating:
      5
DB_BLOCK_SIZE  September 19, 2003

Reviewer:  Jeff  from "Isn't it Great to be in Miami"

Tom,

Our Unix OS block size is 8K.  We have a Data Warehouse

We have large memory available, please don't ask me how much, that is admin's
job.

What is optimal multiblock read count? Currently set at 32.

What is optimal block size? Currently 8k

Now even tho OS is set at 8k, perhaps that should be larger, then set DBMS
accordingly.  Rather than accepting OS 8k size as given.

Doesn't network transport packet size have some impact here as well in
distributed queries, to avoid internetwork packet fragmentation?

I would think why not make multi block 128 and block size 128k - but not sure if
too large or too small.

Can you keep it simple, since I am user not DBA and this "should" be transparent
to me.  In other words, DBMS should handle this sizing for me, not me and not
DBA.  But I find I do work DBA s/b doing, since this is so complicated and even
DBA not have time to look at in such detail.  Same with UNIX admin, he no have
time or inclination to look at such detail and when I look, he/she say that not
my job.  So nothing gets done or changed.  Sound familiar?

Please advise db block size, multi block read, for 8.1.7 data warehouse
environment.

Best regards,
Jeff

          Followup:

8k is a dandy size.

do you full scan frequently?  you might consider finding out your systems
maximum read size and adjusting up.

i don't see the tie in between "network packets" and "blocksizes" at all.

you cannot have a 128k blocksize.

sounds like you should just strip and mirror everything and forget about it.
since you and the admin don't really want to work together.

and why the dba wouldn't be concerned about the amount of ram available is sort
of "way beyond my ability to comprehend".

      Rating: 4
Rows per
      block  October 01, 2003
Reviewer:  Ma$e

Hi Tom:

I'm sure you have covered this somewhere but I could not find it and this was
the closest thread where I think I could post this question. So forgive me if
I'm wrong.

How can I compute the number of rows per block for a given table ( "block #", "#
of rows" )? Fixed length rows I guess would be easy, but how about rows that
have variable length datatypes ?

Thanks
Ma$e

          Followup:

well, with chained and migrated rows -- its not really possible.  the closed
thing you can get is

select dbms_rowid.rowid_block_number( rowid ), count(*) from t group by ...

      Rating: 5
Thanks a
      lot.  October 01, 2003
Reviewer:  Ma$e

Hi Tom:

Thanks for the response. That helps a lot.

If I may.... I have a followup question.

I'm trying to determine the "fullness" of the block (i.e is there room for
additional ITL to grow beyond the INITRANS).

How do I determine that a block is so full that it can't create another tx slot?
Could this lead to a severe locking on this table ? How do I determine that the
locking is due to a low value of initrans.

Thanks.
Ma$e

      Rating:
      4
  December 23, 2003
Reviewer:
      A reader
Hi Tom,

Two quick questions:

1. What are the cons of having the os blocksize larger than db blocksize? I
mean, can't we adjust it by appropriately changing the db_multiblock_read_count
anyway?

2. If we change the os block size, can we restore a physical backup (taken with
a different os blcoksize) successfully (please talk about both rman or
filesystem)?

Thanks for your great site.

          Followup:

1) the OS is going to read in that size -- and many times -- most all IO's are
single block IO's (not multi-block IO's) via indexes hence the dbfmbrc doesn't
even come into play.

so, you read more then you need.

2) yes, it is just a file to us.  you can do whatever.

      Rating: 5
Not able to use
      IOT because of BLOCK_SIZE  January 08, 2004

Reviewer:  Saminathan Seerangan  from NJ,USA

Dear Tom,

Thanks for your kindly help and admire your knowledge on Oracle!

The reason I am posting my question here is that you suggested NOT to use IOT
because of my DB BLOCK_SIZE(which is 2K).

<ASKTOM>
your blocksize is way too small to make that fat of a table effecitive as an
IOT.  The rows will overflow and this will de-generate into:

overflow segment access by rowid pointer
    index range scan

not any different then

table access by index rowid
     index range scan

except that your inserts will be slower then they would be with normal "table
plus index"

From:
http://asktom.oracle.com/pls/ask/f?p=4950:61:::::P61_ID:14338694728363
</ASKTOM>

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

My OS BLOCKSIZE is 8192

SQL> show parameter block

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
db_block_buffers                     integer 1000000
db_block_checking                    boolean FALSE
db_block_checksum                    boolean FALSE
db_block_lru_latches                 integer 26
db_block_max_dirty_target            integer 1000000
db_block_size                        integer 2048      <=====================
db_file_multiblock_read_count        integer 8        <=====================
hash_multiblock_io_count             integer 0
sort_multiblock_read_count           integer 2
SQL>

Now I am really concerned about our DB BLOCK_SIZE after reading your followup
for my IOT related question.

Also I read PROS & CONS of BLOCK SIZE issue  here.

8K(>2K) BLOCK_SIZE PROS
=======================
1)The amount of block overhead in the database will decrease.  There is a fixed
amount of overhead per block

2)Bigger blocks better for full scan, less block overhead

3)Less row chaining and perhaps less row migrations

4)More rows/block in indexes.  This will result in indexes that
require less maintenance as they tend to need to reorganize themselves less
frequently.

5)In IOT less OVERFLOW  compare to 2K

6)Larger block sizes allow for larger index keys.

7)9.2 has tablespace compression option - Having 8k block size would be better
than 2k for more efficient compression (likely)

8K (>2K) BLOCK_SIZE CONS
========================
1)8k block might have 7k of non-relevant data but 2k block might have only 1k of
non-relevant data.

2)More people contending for the same block perhaps.  We do lots
of stuff at the block level -- you may notice more contention in this area.

Ok. Ultimately I do not see any compelling reason to have 2K block size since we
have OS BLOCK SIZE as 8K. Also i see more PROS then CONS.

So it will be appropriate to change the BLOCK_SIZE from 2K to 8K when we get a
chance(The time when we do upgrade to 9i- may be within a month or so)

Please let me know your comments.

Thanks again,
Sami

          Followup:

so, your oracle blocksize is 2k, thats the gating factor here.

multi block read count only concerns us during full scan, which never happens
during "lookup" type processing. it is not relevant.

when you goto 9i, you can easily test (create an 8k blocksize tablespace and
alter table move stuff into it and see how it goes).

but if you are going to rebuild during the upgrade anyway and your os blocksize
is truly 8k (confirm with your SA), 8k is a fine blocksize to use in general

January 08, 2004 val -- Thanks for the question regarding
      "Database Import", version 9.0.1.0

      You Asked
I have performed a full and user database export successfully.  When I try and
import the file, whether just a table or complete user, the Oracle error 922 is
received.  I've pasted the script from the import. If I just use SQL and run the
script, without the "'s, it works fine.  I am trying to import this into a
different database that has the same user and tablespaces as the source.

I want to use export/import as a process to perform backups of the database.  I
did not shutdown the database during the export process.

Here's the error and scripting.

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 SYSTEM's objects into SYSTEM
. importing OCC's objects into XXX
IMP-00017: following statement failed with ORACLE error 922:
 "CREATE TABLE "CT_LOCATION_TAB" ("CT_LOCATION_CODE" NUMBER(2, 0) NOT NULL EN"
 "ABLE, "LAST_TICKET_NUMBER" NUMBER(8, 0), "LAST_TICKET_DATE" DATE, "LAST_TIC"
 "KET_TIME" NUMBER(6, 0), "MAX_TICKET_NUMBER" NUMBER(8, 0), "LAST_LOAD_ORDER_"
 "NUMBER" NUMBER(6, 0), "MAX_LOAD_ORDER_NUMBER" NUMBER(8, 0), "CREATED_BY" VA"
 "RCHAR2(30) NOT NULL ENABLE, "MODIFIED_BY" VARCHAR2(30) NOT NULL ENABLE, "CR"
 "EATION_DATE" DATE NOT NULL ENABLE, "MODIFICATION_DATE" DATE NOT NULL ENABLE"
 ", "PRODUCER_CONFIGURATION" NUMBER(1, 0))  PCTFREE 10 PCTUSED 40 INITRANS 1 "
 "MAXTRANS 255 STORAGE(INITIAL 65536 FREELISTS 1 FREELIST GROUPS 1) TABLESPAC"
 "E "IOMS_DATA" LOGGING NOCOMPRESS"
IMP-00003: ORACLE error 922 encountered
ORA-00922: missing or invalid option

Thanks for any help.

      and we said...
You are taking a 9.2 SQL command from a 9.2 database and trying to execute it in
9.0

"NOCOMPRESS" (for table compression) is a 9iR2 new feature.  It is not
compatible with 9iR1

That is the cause.

The real issue is you cannot use a version X dmp file to import into version Y
when X > Y.

You must use the 9ir1 EXPORT tool against the 9iR2 database in order to create a
DMP file that 9iR1 can read and process.

If you want to export from database X into database Y the following will help:

X>Y;  use Y's export, use Y's import

X=Y;  use either or for each

X<Y; use X's export, Y's import

Review &
      Followup

      Rating:
      5
  January 08, 2004
Reviewer:
      A reader
Tom,

Since you have not pointed to any links let me do it for you.

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96652/ch01.htm#1013202
:-))

January 08, 2004 ben -- Thanks for the question regarding
      "renaming a database and sid in NT ", version 8.1.5

      You Asked
Tom

You had previously answered this question for our unix users can you pls
describe the procedure for the NT folks.

Will the same procedure be valid for NT ?

thx

Ben

      and we said...
Article-ID:         <Note:61590.1>
Alias:              OLS:11234946.61
Circulation:        PUBLISHED (EXTERNAL)
Folder:             server.DBA.Admin
Title:              Renaming an existing Windows NT database
Document-Type:      BULLETIN
Impact:             MEDIUM
Skill-Level:        NOVICE
Server-Version:     07.02.0X to 08.01.0X
Updated-Date:       09-MAR-2001 09:40:06
References:
Attachments:        NONE
Content-Type:       TEXT/PLAIN
Keywords:           DATAFILEINFO; WINNT;
Products:           5;

PURPOSE
  This bulletin outlines the steps required to rename an existing Oracle
  Database on the Windows NT platform, including the SID, DB_NAME,
  and all files associated with these.

SCOPE & APPLICATION
  This note is intended for all DBAs.

===============================================================================

These instructions assume the following:

    - your database is started
    - you are on the Server
    - the existing database SID is ORCL and DB_NAME is ORACLE
    - the new database SID will be TEST and DB_NAME will be TEST

For database version 7.2  use sqldba72 and oradim72
                                  7.3  use svrmgr23 and oradim73
                                  8.0  use svrmgr30 and oradim80
                                  8.1  use  Svrmgrl   and  oradim

This document uses an 8.0 database, but these instructions can easily be
translated to the another version by changing the utility name.

---------------------------------------------------------------
WARNING:
Before proceeding, you should take a valid full backup.
You should also read the entire bulletin before you attempt to
use it.
---------------------------------------------------------------

1. Back up the controlfile to trace:

     C:\> SET ORACLE_SID=ORCL
     C:\> svrmgr30
     SVRMGR> CONNECT INTERNAL/<internal_password>
     Connected.

   Now, we need to verify the user_dump_destination. This can be done
   in two ways:

     SVRMGR> SHOW PARAMETER USER_DUMP_DEST

     NAME             TYPE    VALUE
     ---------------- ------- ------------------------------
     user_dump_dest   string  D:\ORANT\rdbms80\utrace

   ** OR **

     SVRMGR> SELECT value from v$parameter where name = 'user_dump_dest';

     VALUE
     ----------------------------------
     D:\ORANT\rdbms80\utrace

   Create the dump file copy of the controlfile:

     SVRMGR> ALTER DATABASE BACKUP CONTROLFILE TO TRACE;

   This will create a dump file, ORAxxxxx.TRC, in the user_dump_destination.

2. Verify which files your database contains:

     SVRMGR> select name from v$datafile;

     NAME
     -----------------------------------------
     D:\DATABASES\ORCL\SYS1ORCL.ORA
     D:\DATABASES\ORCL\RBS1ORCL.ORA
     D:\DATABASES\ORCL\USR1ORCL.ORA
     D:\DATABASES\ORCL\TMP1ORCL.ORA
     D:\DATABASES\ORCL\INDX1ORCL.ORA
     D:\DATABASES\ORCL\OEM204.DBF
     D:\DATABASES\ORCL\USR2ORCL.ORA
     D:\DATABASES\ORCL\TEST.DBS
     D:\DATABASES\ORCL\WEBDB.DBF

3. SHUTDOWN NORMAL or SHUTDOWN IMMEDIATE the database and EXIT.

     SVRMGR> shutdown immediate

4. Stop the services:

     Using Control Panel/Services - scroll down to OracleServiceORCL and
     click on the STOP button. You might be asked if you want to stop the
     OracleStartORCL-service too. If so, select yes.   Please note that under
     Oracle 8i, only the OracleServiceORCL will exist.

   Or type at the command prompt:

     C:\>net stop OracleServiceORCL

     The following services are dependent on the OracleServiceORCL service.
     Stopping the OracleServiceORCL service will also stop these services:

         OracleStartORCL

     Do you want to continue this operation? (Y/N):  y

     The OracleStartORCL service was stopped successfully.

     The OracleServiceORCL service is stopping.
     The OracleServiceORCL service was stopped successfully.
     C:\>

5. Generally, the database files contain the SID name; if you want to change
   this, use Explorer, File Manager or DOS, and rename the files listed in
   step 2 to the appropriate filenames.

   If you want to files to be placed elsewhere, copy the files to the new
   Location.

   Under Oracle8I,  the default directory structure is such that :

     D:\Oracle\oradata\ORCL - will contain control files, redo logs, datafiles

     D:\Oracle\admin\ORCL\pfile - will contain the init file.

  These files typically will not contain the <SID> name as part of the filename.

      Example:   The init file for the <SID> of 'LEO' will be:

       D:\Oracle\admin\LEO\pfile\init.ora  -  with the directory structure
defining the <SID>.

      Reference the Getting Started Oracle8I for Windows NT manual for more
information
      Regarding the Oracle8I default directory structure.

6. Create and Edit the INITTEST.ORA:

   a. Copy the %ORACLE_HOME%\database\INITORCL.ORA to
      %ORACLE_HOME%\database\INITTEST.ORA
   b. Replace all occurrences of the string ORCL with TEST and modify all
      necessary directories to the correct path.
   c. Modify DB_NAME = ORACLE to read DB_NAME = TEST
   d. Save the file.

7. Edit the dump file created in Step 1:

a. Change all occurrences of the string ORCL to TEST.
   b.    Remove the header (delete all lines above the STARTUP NOMOUNT)
   c.    Modify the Startup line to read:

        STARTUP NOMOUNT PFILE=%ORACLE_HOME%\DATABASE\INITTEST.ORA

      In our example we would use:

        STARTUP NOMOUNT PFILE=D:\ORANT\DATABASE\INITTEST.ORA

   d. Change the NAME of the database.  Modify the CREATE CONTROLFILE:

        replace REUSE DATABASE "Oracle" with SET DATABASE "Test"
        replace NORESETLOGS with RESETLOGS

   e. Remove the lines

        # Recovery is required if any of the datafiles are restored backups,
        # or if the last shutdown was not normal or immediate.
        RECOVER DATABASE
        # All logs need archiving and a log switch is needed.
        ALTER SYSTEM ARCHIVE LOG ALL;
        # Database can now be opened normally.
        ALTER DATABASE OPEN;

      NOTE: the ALTER SYSTEM ARCHIVE LOG ALL is only present when the original
            database is in ARCHIVEMODE.

   f. Save the file to CHNAME.SQL

   =========================================================================

   Example:
   ========

   STARTUP NOMOUNT PFILE=D:\TEST\DATABASE\INITTEST.ORA
   CREATE CONTROLFILE SET DATABASE "TEST" RESETLOGS ARCHIVELOG
       MAXLOGFILES 32
       MAXLOGMEMBERS 2
       MAXDATAFILES 254
       MAXINSTANCES 1
       MAXLOGHISTORY 226
   LOGFILE
     GROUP 1 (
       'D:\DATABASES\TEST\LOGTEST1.ORA',
       'D:\DATABASES\TEST\CTLBIS\CTL1TESTBIS.ORA'
     ) SIZE 2M,
     GROUP 2 (
       'D:\DATABASES\TEST\LOGTEST2.ORA',
       'D:\DATABASES\TEST\CTLBIS\CTL2TESTBIS.ORA'
     ) SIZE 2M
   DATAFILE
     'D:\DATABASES\TEST\SYS1TEST.ORA',
     'D:\DATABASES\TEST\RBS1TEST.ORA',
     'D:\DATABASES\TEST\USR1TEST.ORA',
     'D:\DATABASES\TEST\TMP1TEST.ORA',
     'D:\DATABASES\TEST\INDX1TEST.ORA',
     'D:\DATABASES\TEST\OEM204.DBF',
     'D:\DATABASES\TEST\USR2TEST.ORA',
     'D:\DATABASES\TEST\TEST.DBS',
     'D:\DATABASES\TEST\WEBDB.DBF'
   ;

  ===========================================================================

8. Use ORADIM80 to create the new instance with the new SID.  From a DOS
   Command Prompt type:

     ORADIM80 -new -sid TEST -intpwd password -startmode auto -pfile
%ORACLE_HOME%\DATABASE\INITTEST.ORA

9. Verify that the new service is started:

   a. From in Control Panel/Services, scroll down to OracleServiceTEST.
   b. If not started, click on the START button.

10. Executing the "chname.sql" script:

    C:\> SET ORACLE_SID=TEST
    C:\> svrmgr30

    SVRMGR> CONNECT INTERNAL/internal_password
    Connected.
    SVRMGR> @<user_demp_destination_path>\chname.sql
    #or the directory where you saved your chname-script.

   This will recreate the controlfile with the new datafiles....

11. Open and Shutdown the database.

      SVRMGR>ALTER DATABASE OPEN RESETLOGS;
      statement processed

    Verify at this point if the database is open:

      SVRMGR>SELECT status FROM V$THREAD;

      STATUS
      ------
      OPEN

      SVRMGR>SHUTDOWN IMMEDIATE;

12. Stop the TEST service and take a full backup

    a. From in Control Panel/Services, scroll down to OracleServiceTEST.
    b. Click on the STOP button.
    c. Take a full backup of your database.

13. Removing the old-instance:

    See <Note:62004.1> Removing an Oracle Instance and Database from
                       Windows NT

    Please note:

    a. If you reused the same files or you didn't rename them (i.e. you
       skipped Step 2 partially or completely), don't remove these files
       when following Step 6 of <Note:62004.1>

    b. The instance removed in <Note:62004.1> is called TEST, which is
       our new instance; you must replace TEST by ORCL in <Note:62004.1>
       if you want to follow the instructions in <Note:62004.1> to the
       letter.

    Also, be sure to check your  NET8  or SQL*Net setup for references to ORCL
and
    TEST (i.e. TNSNAMES.ORA, LISTENER.ORA, etc.).

----------------------------------------------------------------------------
                                                     Oracle Support Services

Review & Followup

      Rating:
      4
  July 14, 2001
Reviewer:  J maruti
       from VA USA
I liked the way article was written and also enjoyed reading and following the
instructions. They are very clear.

      Rating: 4
renaming a
      database and sid in NT   August 08, 2001
Reviewer:
      Shajan Joseph  from Chennai, India
The step by step instructions is very useful

      Rating: 5
renaming a
      database and sid in NT   August 27, 2001
Reviewer:
      Sunny  from Columbus, OHIO USA
This article helped me out big-time .  This also applies to cloning your
database.  THanks Tom

      Rating: 4
renaming a
      database - followup  January 16, 2002
Reviewer:
      Ranajit
I want to create the control file using sql script on the fly and hence want to
know where to look for MAXDATAFILES, MAXINSTANCES in the database ?

          Followup:

just use

alter database backup controlfile to trace;

we create the create controlfile statement on the fly for you.  Look in your
user_dump_destination after running that for the statement.

      Rating: 4
Error
      ORA-1161  January 17, 2002
Reviewer:
      Vladimir  from NY,US
Very clear outline however I got the above error. It states that data file
headers stil contain old SID. Could you elaborate how to rectify this. P.S. I
renamed them to SYS1new_name.ora and etc...  It is my fist time post and I would
like to join many people showing my appreciation of the work you do! Thanks.

          Followup:

> oerr ora 1161
01161, 00000, "database name %s in file header does not match given name of %s"
// *Cause: The database name given at the command line does not match the
//        database name found in the file header.
// *Action: Chance are good that the database name specified at the command
//        line is incorrect.  Resolve the descepency, and resubmit the command.
//        If you are attempting to change the database name, be sure to
//        use the SET DATABASE option.

I believe you skipped step 7d

      Rating: 5
Excellent
      !  January 18, 2002
Reviewer:  Magnus
      Herrstrom  from Sweden
In most manuals and books they say that this can't be done or recommend not to
do it. But once in a while you need to do it to save a lot of time and work.

      Rating:
      5
  January 18, 2002
Reviewer:
      A reader
Sir,

In step No. 13 you have mentioned

"See <Note:62004.1> Removing an Oracle Instance and Database from Windows NT"

How could I find this article ?

          Followup:

Using
http://metalink.oracle.com/
-- the Oracle Support web site (whence this note originated)

      Rating: 4
What do I use
      in place of "Svrmgrl" in 9i ?  January 29, 2002

Reviewer:  Ravi  from NY,NY
Oracle 9i does not have Svrmgrl ? What can I use in its place ?

Thanks!

          Followup:

sqlplus replaces svrmgrl in 9i.

      Rating: 5
RE: What do I
      use in place of "Svrmgrl" in 9i   January 30, 2002

Reviewer:  A reader
What do you do in place of reading the Release Notes and documentation?  This is
only one of the most publicised changes in 9i...

      Rating: 5
RE: RE: What do
      I use in place of "Svrmgrl" in 9i  January 30, 2002

Reviewer:  Mark A. Williams  from Indianapolis, IN
      USA
Re: What do you do in place of reading the Release Notes...

Not only that, but simply putting "svrmgrl 9i" into the AskTom search engine
would have easily answered the question as well.

The AskTom search engine must be one of the most under-utilized tools around...

      Rating: 5
Sorry folks,
      just getting my feet wet in 9i  January 30, 2002

Reviewer:  Ravi  from NY,NY
.... I will wake up early and work hard now on :)

      Rating: 3
How can I
      change the default from ORCL for SQL*Plus  June 22, 2002

Reviewer:  Ravi Kumar  from IL, USA
Hi,

I installed 9i on my Win2000 machine. Created a database with sid HOME. When I
use SQL*Plus to connect database, without the connect string(scott/tiger), it
connects. I created one more database OEMREP for Enterprise Manager. After that,
when I just give scott/tiger, it is connecting to the new database OEMREP. And I
am able to connect to both the databases successfully using @HOME or @OEMREP.

I deleted this new database and when I try to connect with scott/tiger, I am
getting "TNS:Protocol error". I checked sqlnet.ora, it is trying to connect to
sid ORCL.
====
Fatal NI connect error 12560, connecting to:

(DESCRIPTION=(ADDRESS=(PROTOCOL=BEQ)(PROGRAM=oracle)(ARGV0=oracleORCL)(ARGS='(DES
CRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))'))(CONNECT_DATA=(SID=ORCL)(CID=(PRO
GRAM=C:\oracle\ora92\bin\sqlplusw.exe)(HOST=MYHOME)(USER=Administrator))))

  VERSION INFORMATION:
    TNS for 32-bit Windows: Version 9.2.0.1.0 - Production
    Oracle Bequeath NT Protocol Adapter for 32-bit Windows: Version 9.2.0.1.0 -
Production
  Time: 22-JUN-2002 13:05:20
  Tracing not turned on.
  Tns error struct:
    nr err code: 0
    ns main err code: 12560
    TNS-12560: TNS:protocol adapter error
    ns secondary err code: 0
    nt main err code: 530
    TNS-00530: Protocol adapter error
    nt secondary err code: 2
    nt OS err code: 0
===

where is it being set for SQL*Plus executable and how can I change that?

Thanks,
Ravi.

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:723906439755
Just set an environment variable of your choosing.  You can set this in a script
(bat/cmd file) or using control panel once and for all.

      Rating: 5
Cloning is Diff
      for Oracle Applications  December 09, 2002

Reviewer:  Sikandar Hayat Awan  from Pindi Bhattian -
      Pakistan
The thread is very useful but this procedure is not useful for Oracle
Applications.

I want to ask Mr. Tom that is there any site like askTom covering Oracle
Applications(OF) other than Metalink?

          Followup:

Not that I am aware of, no.

      Rating:
      4
v9i  December 28, 2002
Reviewer:
      Anton  from Phil.
Hi Tom,

Is this applicable to v9iR2??

Tnx

          Followup:

more or less -- the names of oradim are different (just oradim), you might be
using an spfile instead of a pfile but in general, the steps are the same.

goes without saying of course if you do this without a full, tested backup --
you are on your own.

      Rating: 5
SID and
      db_name  March 31, 2003
Reviewer:  Steve
       from UK
Is it possible to have 2 instances on one server with the same db name but
different sid's?

I know it's probably asking for trouble but I'd like to know if it's possible
all the same!

          Followup:

well, an instance does not have a dbname -- a database does -- but I
understand..

sure it is.

You can even have 2 instances with the same SID on the same machine that mount
two separate databases that have the same dbname's.

      Rating: 5
DBMS_JOB AND
      CLONING  June 13, 2003
Reviewer:  Sachin
       from Germany
8.1.7.4
is there a way to make database "not" to run any of the jobs..while cloning
databases...using RMAN backups to clone.

Thanks

          Followup:

it would not be a "clone" then.

you would have to set job_queue_processes=0 in the init.ora of the cloned
database (alter system can do that on the fly)

      Rating: 5
this is
      wonderful!!!  June 14, 2003
Reviewer:
      anurag
What a wonderful explanation. Tom, what's a secret of yours answering each
question with so much of patience. Is it UR idea to start with asktom.... Can we
know its history...

      Rating:
      5
MAXDATAFILES  July 02, 2003
Reviewer:
      Abdul Wahab  from Jeddah
Dear Dear Tom,
  I need to change the MAXDATAFILE parameter. The database is 7.3.4 and it has
hit the maximum and we need to increase that parameter. I need to hcange ONLY
this parameter and nothing else.  h o w ?
 Should i follow the above method for changing the control file? if yes what
about that 'reuse' command in control file.
 Please reply soon as i am going to work on this database to which 150 users are
connected.Can't tolerate down time.
Thanks

          Followup:

you need to rebuild your controlfiles (sort of oxymoronic -- "i cannot have down
time, yet I run unsupported software")

0) BACKUP.  make sure you have a backup.  make sure you can restore this backup.
then....

1)
SVRMGR> connect internal
Connected.
SVRMGR> alter database backup controlfile to trace;
Statement processed.
SVRMGR>

2) cd to your user dump destination directory.  find the newest trace file, edit
it

3) remove the trace file header, change maxdatafiles, save as "test.sql" or
something.

4) shutdown

5) move your old controlfiles elsewhere

6) use test.sql to startup the database, the controlfiles will be recreated for
you.

if you skip step 0, you are on your own....

      Rating: 5
i forgot to
      mention  July 02, 2003
Reviewer:
      A reader
the operating system is unix tru64

      Rating: 5
i forgot to
      mention  July 02, 2003
Reviewer:  abdul
      wahab  from jeddah
the operating system is unix tru64

      Rating: 5
OS server
      migration  July 02, 2003
Reviewer:  Saradha
      Bavanandam  from NJ, USA
Tom,

We are planning to replace the existing HP server
(HP V2500) with the new HP superdome server.

To minimize the impact on the production, we are planning
to take the DASD from the old server and plug it into the
new server. I am not an unix admin but this is what the
UNIX admins are planning to do.

There are 10 oracle9i instances running On the old server
and there is a procedural gateway for 4 oracle instances
to communicate with the mainframe CICS transactions.
This is using SNAPlus2 network architecture.

The new server will have a different HOST NAME and
different IP address. My assumption is that, since they
are switching the DASD, the oracle instances will not be
re-created in the new server and they are just brought over
to the new server with all the data.

If I am clear in what I am trying to tell you, what will
be the impact of this new HOST NAME and new IP address
on these Oracle instances and as well as on the
Procedural Gateway in the new server.

The TNSNAMES.ORA and LISTENER.ORA files will be modified on
the new server to reflect theses but other than that what
else is to consider.

Please give your thoughts...

Thanks
Saradha

          Followup:

well, they better TEST THIS stuff eh?

they are changing OS's -- at the very least, they will have to relink Oracle.

10 instances on a single server?  thats 9 too many.

For the database, the tnsnames/listener files pretty much cover it, as long as
the mount points are the same.

      Rating: 5
It's
      done  July 05, 2003
Reviewer:  Abdul Wahab
       from Jeddah,K.S.A
Dear Tom,
   I have done it.I changed the MAXDATAFILE parameter.
Thanks to your documentation and followup which was
very very very (..infinite) helpful for me.
    Thanks a lot for being there. (and sorry for oxymoronic remark ;-)
take care

      Rating: 5
server
      migration  July 08, 2003
Reviewer:  Saradha
      Bavanandam  from NJ, USA
"well, they better TEST THIS stuff eh?
they are changing OS's -- at the very least, they will have to relink Oracle.10
instances on a single server?  thats 9 too many.
For the database, the tnsnames/listener files pretty much cover it, as long as
the mount points are the same."

Tom,

We have received our new HP Server and the DASD
migration is going to happen only in September.

What kind of testing that you can suggest before hand
on the new server as far as oracle is concerned, so that
there are no surpises on the migration day.

Other question is:
There are DB LINKS created to these instances from other
external databases and do they need to re-create their DB
LINKS after the migration because the HOST name and IP address are going to be
changed now.

Thanks
Saradha

          Followup:

you should test your applications functionality and scalabilty.  automated
testing tools like LoadRunner can be very handy for that.

You can update the tnsnames.ora file to repoint to new ip/hostnames.

      Rating: 4
ORA-12560 Error
      when running svrmgrl on WIN NT  August 30, 2003

Reviewer:  A reader
I have installed Oracle 8i on Windows 2000 without the default database. I want
to create database through database configuration assistant or through sqlplus.
When I run svrmgrl it gives the error ORA-12560. Is it due to listener.ora,
tnsnames.ora problem or any other thing. A good troubleshooting guide is
required for this error.

          Followup:

fortunately -- we have a windoze administrator guide that goes through all of
this!

you have to use oradim to create a service first on that platform.

http://download-west.oracle.com/docs/cd/A87860_01/doc/win.817/index.htm
see the windows admin guide

      Rating: 5
Getting clone
      of database running on another machine after crash  August
      31, 2003
Reviewer:  Jon Waterhouse  from Newfoundland

Hi Tom,

I just had a crash of the NT machine that my test Oracle database was on:
unfortunately have a lot of stuff there I don't want to lose. NT is restored on
that machine enough to get at all the files, but something damaged is preventing
Oracle running (the agent service fails, SQLplus cannot connect (just hangs).
Was running in noarchivelog mode.

NT (and presumably the database) shut down fine, just NT failed on the next
boot. I have tried copying all of the files in oradata (includes the control
files and all of the data files) to another machine with Oracle (9i) installed
(same disk and directory). If I follow the script above (with create control
files) then I cannot open the database (ORA-01194: file 1 needs more recovery to
be consistent).
a "recover using backup controlfile" says there is a change needed for file 1
that it suggests might be in a particular archive file (which of course does not
exist).

If I try just a straight startup I get:
ORA-01991: invalid password file

Do either or these approaches have any chance of sucess if I persevere? Would
copying the password file (and any other files) bring me joy, or am I doomed?

Thanks,

Jon

          Followup:

the database did not shutdown clean, else the file would not need recovery

have you tried "recover database"

you can use orapwd to create a new password file.

      Rating: 5
Changing IP
      Address of the 9i DB server  January 08, 2004

Reviewer:  Irfan  from Columbus, OH USA
Hi Tom,

I have to change the IP address of one of my 9i db server. Is it straight
forward or do I have to follow some careful steps??

          Followup:

you should just be able to "do it" unless you've used IP addresses in any
configuration files.

Unless you put in fixed IP's, they'll have hostnames and you are not changing
that so you should be AOK.

tnsnames.ora (clients and servers)
listener.ora (server)
init$ORACLE_SID.ora (for service registration) (or the spfile)

are the places to look

January 09, 2004 Parag -- Thanks for the question regarding
      "Locking & Others.", version 8.1.7

      You Asked
Hi Tom :

I am asking u some question after long time.Because always i used to get message
that " U having back log of question" . But this i am lucky.
My questions are as follow.

1] How to find out that which table has been locked by which user ( Including
SQL query )

2] Give me some more Information on Locking Mechanisum used by oracle.

3] How to findout lastrow of table.

4] How to Configure OEM on Oracle 8.1.7 on NT 4.0

5] How to install Forms Server  on NT ( I am using developer-2000 6i) . I have
installed Forms & Reports. Not howe to run that Form on Web.

6]  How to transfer data from Gupta SQL to Oracle.

Thanks in advance.

Regards
- Parag

      and we said...
1 & 2 ) I can recommend a book with some information on this - explained in the
manner I would explain it (cause I did).  For more detail then I can go into
here, see

http://www.amazon.com/exec/obidos/ASIN/1861004826

In any case, you would use the V$LOCK or DBA_DML_LOCKS table (you may have to
run catblock.sql in $ORACLE_HOME/rdbms/admin to get that last view):

ops$tkyte@ORA817.US.ORACLE.COM> lock table emp in exclusive mode;

Table(s) Locked.

ops$tkyte@ORA817.US.ORACLE.COM> select owner, mode_held, blocking_others
  2
ops$tkyte@ORA817.US.ORACLE.COM> /
select owner, mode_held, blocking_others
                                       *
ERROR at line 1:
ORA-00923: FROM keyword not found where expected

ops$tkyte@ORA817.US.ORACLE.COM> exec print_table( 'select * from dba_dml_locks'
);
SESSION_ID                    : 20
OWNER                         : OPS$TKYTE
NAME                          : EMP
MODE_HELD                     : Exclusive
MODE_REQUESTED                : None
LAST_CONVERT                  : 31
BLOCKING_OTHERS               : Not Blocking
-----------------

PL/SQL procedure successfully completed.

See

http://download-east.oracle.com/docs/cd/A81042_01/DOC/server.816/a76965/c23cnsis.htm#2937
for info on the types of locks (also, i have a chapter on this as well)

3) there is no such thing as the last row of a table, there is no such thing as
the firstrow, the 2cnd row -- and so on.  Data is just there.

You must use an order by to get what you believe is the first/last row.
see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:912210644860
for all of the gory details

4 & 5) please read the documentation -- it is described there how to install the
products.  I cannot describe in this forum how to install things really.

6) Export the data from gupta into a delimited file and use SQLLDR to load it.
See the sample  control files in $ORACLE_HOME/rdbms/demo/ulcase*.ctl for
examples.

Review &
      Followup

      Rating:
      3
dml_locks  June 21, 2002
Reviewer:
      A reader
Does setting dml_locks to a very high value (say 10000) have any side-effect?

Thanks

          Followup:

it takes memory.  The more you ask for -- the bigger the variable portion of the
SGA will be to hold them.

      Rating:
      4
Locks  June 21, 2002
Reviewer:
      Neena  from KL
DATAMART> exec print_table('select * from dba_dml_locks');
BEGIN print_table('select * from dba_dml_locks'); END;

      *
ERROR at line 1:
ORA-06550: line 1, column 7:
PLS-00201: identifier 'PRINT_TABLE' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

Why I am getting this error  ?

          Followup:

cause you don't have my print_table routine

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1035431863958

      Rating: 5
print_table is
      a user defined procedure by TOM ;)  June 22, 2002

Reviewer:  Yogeeraj  from Mauritius
Please refer to
http://asktom.oracle.com/pls/ask/f?p=4950:8:959035::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1035431863958,%7Bprint_table%7D
Cheers
Yogeeraj

===========================================================
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;
/

      Rating:
      5
Locks  January 19, 2003
Reviewer:
      mo
Tom:

On Chapter 3 of your book, example on P126 you explain how oracle is unique in
that it has multiversioning which other do not (read shared locks).

You say that when I try to do a sum on the table and the someone transfered 400
from row 1 to row 342,023 while I am in the middle of the query, oracle will get
to the last record sees a lock goes to the rollabck segment and read the old
value of the row.

My question is what about if a commit happened and the lock was released what
would oracle do in this case? if it reads the new value it  would be incorrect
because it read the old value of row 1 and the new value of last row?

BTW, the isolation level stuff is not that easy. IT requires multiple readings
sometimes. I hope it is the hardest chapter of the book.

          Followup:

Same thing happens -- even with a commit.

The rule is: if the data changed since your query began - you cannot see the
change regardless.

If you get that chapter, you "get" Oracle (and databases)...

      Rating:
      5
locks  January 19, 2003
Reviewer:
      mo
Tom:

Just  curious how does oracle know that it changed if a commit was done.  In the
book you said it checks the lock flag on the block and if the row is locked it
goes to the rollback segment.

Also, when you commit oracle does not keep the original value in the rollback
segment so it must be getting it from somewhere else (redo log ?) ?

Thanks,

          Followup:

It is conceptually a lock flag.

there is a transaction section in the block header.  The block header has
information about its "age" (we use SCN -- system change numbers).  We can tell
if the block was modifier since our statement began (our statement began at SCN
"xxx" ) by comparing SCN's.

Oracle does keep the original in the rollback segment. that's chapter 5

      Rating: 3
Abt Last
      Convert  August 25, 2003
Reviewer:  Vikas
      Sahrma  from Delhi India
Hi Tom,

Pl explain what is meaning of
LAST_CONVERT                  : 31

I went through the Documnetation but was not able to find out.

Regards,
Vikas Sahrma

          Followup:

last_convert   - time (in seconds) since last convert completed

like a lock promotion.

consider:

ops$tkyte@ORA920> create table emp as select * from scott.emp;
 Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select empno from emp where rownum = 1 for update;

     EMPNO
----------
      7369

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec print_table( 'select * from dba_dml_locks where owner =
user' );
SESSION_ID                    : 8
OWNER                         : OPS$TKYTE
NAME                          : EMP
MODE_HELD                     : Row-S (SS)
MODE_REQUESTED                : None
LAST_CONVERT                  : 0
BLOCKING_OTHERS               : Not Blocking
-----------------

PL/SQL procedure successfully completed.

 it was zero seconds about we last "converted" this lock (got it in this
case..)

ops$tkyte@ORA920> exec dbms_lock.sleep(4);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec print_table( 'select * from dba_dml_locks where owner =
user' );
SESSION_ID                    : 8
OWNER                         : OPS$TKYTE
NAME                          : EMP
MODE_HELD                     : Row-S (SS)
MODE_REQUESTED                : None
LAST_CONVERT                  : 3
BLOCKING_OTHERS               : Not Blocking
-----------------

PL/SQL procedure successfully completed.

now, it was about 3 seconds ago...

ops$tkyte@ORA920> exec dbms_lock.sleep(4);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec print_table( 'select * from dba_dml_locks where owner =
user' );
SESSION_ID                    : 8
OWNER                         : OPS$TKYTE
NAME                          : EMP
MODE_HELD                     : Row-S (SS)
MODE_REQUESTED                : None
LAST_CONVERT                  : 7
BLOCKING_OTHERS               : Not Blocking
-----------------

PL/SQL procedure successfully completed.

Now, about 7 (your mileage will vary, it is "about" 7 seconds, it won't be
exactly exact)

ops$tkyte@ORA920> update emp set empno = empno where rownum = 1;

1 row updated.

ops$tkyte@ORA920> exec print_table( 'select * from dba_dml_locks where owner =
user' );
SESSION_ID                    : 8
OWNER                         : OPS$TKYTE
NAME                          : EMP
MODE_HELD                     : Row-X (SX)
MODE_REQUESTED                : None
LAST_CONVERT                  : 0
BLOCKING_OTHERS               : Not Blocking
-----------------

PL/SQL procedure successfully completed.

now, it is reset, we converted the lock up

ops$tkyte@ORA920> exec dbms_lock.sleep(4);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec print_table( 'select * from dba_dml_locks where owner =
user' );
SESSION_ID                    : 8
OWNER                         : OPS$TKYTE
NAME                          : EMP
MODE_HELD                     : Row-X (SX)
MODE_REQUESTED                : None
LAST_CONVERT                  : 3
BLOCKING_OTHERS               : Not Blocking
-----------------

PL/SQL procedure successfully completed.

and it starts all over again

      Rating: 3
Trace SQL
      within PL/SQL  November 04, 2003
Reviewer:  Mike
      Dunn  from San Francisco, CA
How do you trace the explain plan for SQL within a PL/SQL procedure.

Say for instance...

sql>alter session set sql_trace=true ;

sql>exec my_procedure_with_alotof_sqls ;

sql>

This does not trace the sql's within the Procedure automatically.

Is there any other way to do this.

          Followup:

sure it does.

it absolutely does.

100%

      Rating: 3
Memory
      Usage  January 08, 2004
Reviewer:  jack
       from Columbus, OH USA
Tom,
In one of your follow ups' for the following question
***********************************
Does setting dml_locks to a very high value (say 10000) have any side-effect?
****************************
You replied
________________________________________
Followup:
it takes memory.  The more you ask for -- the bigger the variable portion of the

SGA will be to hold them.
___________________________________________
How to find the memory required, if increased the number from 500 to let say
10000. Are there any methods to find this?
( Note : This is a very dml intensive OLTP system)

Any other parameters do we need to increase (enque processes etc..) pl. explain.

thanks

          Followup:

well, trying it and seeing will tell you as this can and will vary from port to
port, 32bit/64bit, release to release.  Doing something like this:

ops$tkyte@ORA920PC> show parameter dml_locks

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
dml_locks                            integer     748
ops$tkyte@ORA920PC> show sga

Total System Global Area  114890068 bytes
Fixed Size                   451924 bytes
Variable Size              92274688 bytes
Database Buffers           16777216 bytes
Redo Buffers                5386240 bytes

ops$tkyte@ORA920PC> alter system set dml_locks=10000 scope=spfile;
System altered.

ops$tkyte@ORA920PC> startup force
ORACLE instance started.

Total System Global Area  114890068 bytes
Fixed Size                   451924 bytes
Variable Size              92274688 bytes
Database Buffers           16777216 bytes
Redo Buffers                5386240 bytes
Database mounted.
Database opened.
ops$tkyte@ORA920PC> show parameter dml_locks

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
dml_locks                            integer     10000

that showed "no difference" in this case, however:

ops$tkyte@ORA920PC> alter system set dml_locks=100000 scope=spfile;
System altered.

ops$tkyte@ORA920PC> startup force
ORACLE instance started.

Total System Global Area  131667364 bytes
Fixed Size                   452004 bytes
Variable Size             109051904 bytes
Database Buffers           16777216 bytes
Redo Buffers                5386240 bytes
Database mounted.
Database opened.
ops$tkyte@ORA920PC>

shows from 10,000 to 100,000 we added 16meg.  In 9i, the SGA is incremented in
"granules" of a fixed size (16m on my system) so we added 16m but we probably
didn't 'need' all of it just for dml_locks

In 8i, you will see different results -- for example:

sys@ORA817DEV> show parameter dml

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
dml_locks                            integer 1320
sys@ORA817DEV> show sga

Total System Global Area   63479968 bytes
Fixed Size                    73888 bytes
Variable Size              58310656 bytes
Database Buffers            4915200 bytes
Redo Buffers                 180224 bytes
sys@ORA817DEV> startup force
ORACLE instance started.

Total System Global Area   65142944 bytes
Fixed Size                    73888 bytes
Variable Size              59973632 bytes
Database Buffers            4915200 bytes
Redo Buffers                 180224 bytes
Database mounted.
Database opened.
sys@ORA817DEV> show parameter dml

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
dml_locks                            integer 10000
sys@ORA817DEV> select (59973632-58310656)/1024 from dual;

(59973632-58310656)/1024
------------------------
                    1624

shows that in 8i to go from 1,320 to 10,000 I added about 1.6 meg of SGA
space...

January 09, 2004 Swami -- Thanks for the question regarding
      "Oracle Enterprise Manager", version 8.1.5

      You Asked
I have installed Oracle Enterprise Manager (OEM) Version 2.1
which was on free download from OTN ( technet.oracle.com ).

Problem is when I launch the console it asks me to logon into
a Management Server, So What is a Management Server and how do
I create a Management Server ?.  Also , In Previous Versions of
OEM , there was a utility to create Repository but in the new
version I could not find any such thing.

Please Help

Thanks

      and we said...
What is the Oracle Management Server (OMS)?

    The Oracle Management Server is a new component to the
Oracle Enterprise Manager architecture.  OEM V1 was based on a
two-tier architecture of Client/Server. Communications with the
Intelligent Agent were performed by the OEM Communication
Daemon, which ran on the OEM Client system.  However, OEM V2 is
based on a three-tier architecture of Client, Management Server,
and Application. Processing and communications that were the
responsibility of the Communication Daemon are now being
performed on the middle-tier by the Management Server. It is the
responsibility of the OMS to communicate with the Intelligent
Agent and store all related information in the OEM Repository.
The OEM Client connects to the OMS to retrieve the information
that is stored in the OEM repository.

How to set it up?

You would use the Oracle Enterprise Manager Configuration
Assistant (OCA) to configure this middle tier.

You use the OCA to create the repository as well.

Review &
      Followup

      Rating:
      4
  June 04, 2001
Reviewer:  Norman Waldie
      nwaldie@technologist.com  from Denver, CO, USA

      Rating:
      5
  July 06, 2001
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating:
      5
OEM  November 27, 2002
Reviewer:
      atul  from India
Sir,

I have installed oracle 8.1.7 on red hat linux 7.1
(typical installation) ..
I'm using this as a test server..so on single machine i'm the client as well
server..

How should i launch OEM in my environment??
could you provide me stpes for that???

Thanks.
atul

          Followup:

just run oemapp console and use the standalone config if you want.

      Rating:
      4
  February 06, 2003
Reviewer:  Rahul

Hi Tom,

Thanks for the reply of OEM, as i fixed it, was my mistake had changed the entry
of tnsnames.ora.

1. I am trying to set paging server which dials through modem and I want to
send text messgs to my cell phone. Which protocol should i use, as I tried all
three (TAP,FLEXTD,GSM) from OEM but it dials my cell phone(no text messg).

2. If i want to send text messg to my cell phone thru (SMTP) how should i go
about.

Thanks.

          Followup:

no idea -- i don't have a pager, don't have text messaging, never used this
facility of oem.

      Rating: 4
OEM
      Notifications  February 06, 2003
Reviewer:
      Terry  from San Jose, CA USA
With Regards to Paging and email from OEM.  This is a very useful feature and
not too difficult to setup.

The simple highlights of what needs to be done.

You need to discover the node against which you will create some events by right
clicking the node folder in the OEM Console once you've logged onto the
management server.  Perhaps in your case after you've created the server with
the Enterprise Management Creation Assistant (EMCA actually).

You need to configure the paging/email from the Configuration/Configure
email/paging item on the OEM Console Title Bar(add an smtp gateway and user)

You need to configure preferences from Configuration/Preferences item on the OEM
Console Title Bar.  Select Notifications (add a receiver to the configuration).

You need to configure scheduling for email periods.  Go to Configure preferences
from Configuration/Preferences item on the OEM Console Title Bar.  Select
Schedule, click on email and add the notification periods to the configuration).

There is a test button under Configuration/preferences/Notification for both
email and paging.

It all looks a bit counter-intuitive (those Java programmers thinking they know
UI design, drives me nuts!) but when it is working it is awesome and saves you
big$$$ over similar products that cost potentially hundreds of thousands of
dollars (BMC Patrol, HP OpenView, iWatch) all which are fine products, just way
too much money and while they have some extra bells whistles almost no one uses
them.

I usually have this up and running in 30 minutes or less and you can't do that
with BMC Patrol or HP OpenView or Quest iWatch.

Have fun

      Rating: 4
oem and rman
      backup error  July 03, 2003
Reviewer:
      Reader
Tom, I receive the following error when i submit a backup job in oem. Could you
please help me where I should set up credentials to "Logon as a batch job"
privilege. THANKS.

I am using oem version 9.2.0 on Windows 2000.

VNI-2015 : The Node preferred credentials for the target node are either invalid
or do not have sufficient privileges to complete the operation.
On Windows platforms, the Node credentials specified for the Windows target
should have the "Logon as a batch job" privilege.

Following info from 'help' button on oem.

For NT users, you must set the preferred credentials for the node (where the NT
Agent resides) to be the same as the user that is set up to "Logon as a batch
job."

      Rating: 2
Where to find
      OCA ?  December 05, 2003
Reviewer:  Alvin
       from Philippines
1. Where can i find the Oracle Enterprise Manager Configuration Assistant (OCA)
?? i've installed the oracle client (for DBA ) and i can't find it.

2. Where do i install the OMS on the target db or on the client (my workstation
)?

      Rating: 5
trying to start
      the agent ....  January 08, 2004
Reviewer:
      Reader
When I try to start OracleOraHome92Agent service on my windows 2000, it throws a
following error.
<quote>Could not start the OracleOraHome92Agent service on Local Computer. The
service did not return an error. This could be an internal windows error or an
inter service error<quote>

Could you please guide me what should I do? Thanks.

          Followup:

contact support please for installation/configuration help.

January 09, 2004 robert -- Thanks for the question regarding
      "using plsql how can I import an ascii file", version

      You Asked
Using dbms_utility package how can i
import an ascii file into oracle database.
the ascii file can be in any format.
which of the modules will be useful in
acheiving the above goal.

thanks

      and we said...
dbms_utility won't be of great use but UTL_FILE and/or BFILEs
will be.  Given you have ascii text, utl_file would be the way
to go probably.

Below is a small example that loads files in this case that look
like:

1234|This is some long text
with embedded newlines and such.
This is all part of the first row to load, until we
get to this tilda right here ->~
6789|This is the second line to load up~

Here is the example:

create table t ( id number, comments varchar2(4000) );

create or replace 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
    l_input := utl_file.fopen( p_dir, p_filename, 'r' );

    loop
        begin
            utl_file.get_line( l_input, l_buffer );
        exception
            when no_data_found then exit;
        end;
        l_id := substr( l_buffer, 1, instr( l_buffer, '|' ) - 1
);
        l_comments := substr( l_buffer, instr( l_buffer, '|' ) +
1 );

        while( l_buffer not like '%~' )
        loop
            utl_file.get_line( l_input, l_buffer );
            l_comments := l_comments || chr(10) || l_buffer;
        end loop;

        insert into T ( id, comments ) values ( l_id,
rtrim(ltrim(l_comments,'~'),'~') );
        l_cnt := l_cnt + 1;
    end loop;

    utl_file.fclose( l_input );
    return l_cnt;
end;
/

Note that you should also investigate SQLLDR which is a command
line tool for loading data.  examples are to be found in
$ORACLE_HOME/rdbms/admin/ul*.ctl

Review &
      Followup

      Rating: 4
Followup -
      using plsql how can I import an ascii file  April 05, 2002

Reviewer:  Dave Brys  from Belgium
I like the idea , but I ran into some practical problems.

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).

I tried making both a long field, but then my trace file contains more than
32767 characters, which gives numeric or value error.

I then added something like this ...
if length(l_comments) + length(l_buffer) > 32000 then
insert into T ...
end if;

I managed to spool the file, but tkprof doesn't work properly because the layout
is not exactly the same as the original trace file. Is this because I inserted
twice or because of the EOL characters ?

I only have access to NT , does that make any difference with the EOL characters
you have to use (chr(10)) ?

Thx in advance

          Followup:

l_comments is a varchar2(32760) -- a long is a varchar2(32760) in plsql.

anyway, i don't know what you mean by:

I managed to spool the file, but tkprof doesn't work properly because the layout

is not exactly the same as the original trace file.

i don't see what

a) spooling has to do with it
b) what tkprof has to do with spooling

as for chr(10) question -- it matters not.  end of line is chr(10) on unix,
chr(13)||chr(10) on DOS -- chr(10) is the end of the line in both.

      Rating: 5
Gives error
      ORA-6510 in sys.utl_file  June 09, 2002
Reviewer:
      Mohammed Ahmed
I did the following and it gave me error I do not know why?
======
SQL> @fn.pls
declare
*
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 line 10
======
/* fn.pls starts here */
declare
    l_input         utl_file.file_type;
    l_buffer        long;
    l_comments        long;
    l_id            number;
    l_cnt            number default 0;
    p_dir      char(10) := '/tmp/';
    p_filename      char(10) := 'fn_ld';
begin
    l_input := utl_file.fopen( p_dir, p_filename, 'r' );
    loop
        begin
            utl_file.get_line( l_input, l_buffer );
        exception
            when no_data_found then exit;
        end;
        l_id := substr( l_buffer, 1, instr( l_buffer, '|' ) - 1 );
        l_comments := substr( l_buffer, instr( l_buffer, '|' ) + 1 );
        while( l_buffer not like '%~' )
        loop
            utl_file.get_line( l_input, l_buffer );
            l_comments := l_comments || chr(10) || l_buffer;
        end loop;
        insert into T ( id, comments ) values ( l_id,rtrim(ltrim(l_comments,'~')
,'~') );
        l_cnt := l_cnt + 1;
    end loop;
    utl_file.fclose( l_input );
end;
/

          Followup:

line 10 is:

  1  declare
  2      l_input         utl_file.file_type;
  3      l_buffer        long;
  4      l_comments        long;
  5      l_id            number;
  6      l_cnt            number default 0;
  7      p_dir      char(10) := '/tmp/';
  8      p_filename      char(10) := 'fn_ld';
  9  begin
 10      l_input := utl_file.fopen( p_dir, p_filename, 'r' );

the file open.  suspect you have not set utl_file_dir in the init.ora or the
file fn_ld does not exist in temp.

      Rating: 4
An answer to
      the problem of "Mohammed Ahmed"  June 10, 2002

Reviewer:  Lefteris Vakalis  from Athens, GREECE

Try to change the datatype of p_dir and p_filename from CHAR(10) to
VARCHAR2(10), because you are sending these parameters right padded with spaces
and you receive a UTL_FILE.INVALID_OPERATION exception.

          Followup:

thanks - didn't even see that, good eye.

I recommend this:

forget the datatype char even exists.  Do not use it -- not for variables, not
for columns in a table.  Just don't even use them.

      Rating:
      4
Question  January 08, 2004
Reviewer:
      A reader
Hi, Tom

1. How could you insert "l_comments(long type)" into table T whose corresponding
column type is VARCHAR2(4000), I mean l_comments may be greater than 4000;

2. In the real ascii text file, there may not have little tidle(~), so how this
tidel(~) comes from? Do we have to add it to the end of each line manually in
the ascii file before we start to physically read the file?

          Followup:

1) i was just being lazy.  LONG in plsql is really just varchar2(32760) -- still
too big but it would just error out if we exceeded 4000.  no big deal.

2)

quote:

Below is a small example that loads files in this case that look like:

I made the assumption that there must be SOME format SOMEWHERE.  I was just
showing that "you can write procedural code to process and load a file"

January 09, 2004 -- Thanks for the question regarding "How to
      calculate for a weekly rolling period?", version 9.0.1

      You Asked
Hi Tom:
 I was wondering how we can calculate an average weekly revenue. Different
clients have different rolling periods, like Client A have a 13 weeks,B have a 2
weeks.
here is an example
1) pro_customer_tb
--customer_id
--rolling_period_weeks
select * from pro_customer_tb
1,13
2,2
2)pro_charges_tb
--customer_id
--amt
--date
select * from pro_charges_tb
1,10.0,6-Jan-2003
1,2.0,7-Jan-2003
1,3.0,8-Jan-2003
1,4.0,9-Jan-2003
1,5.0,10-Jan-2003
1,10.0,13-Jan-2003
1,2.0,14-Jan-2003
1,3.0,15-Jan-2003
1,4.0,16-Jan-2003
1,5.0,17-Jan-2003
1,10.0,20-Jan-2003
1,2.0,21-Jan-2003
1,3.0,22-Jan-2003
1,4.0,23-Jan-2003
1,5.0,24-Jan-2003

   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
for the week of 26th Jan to 1st Feb, if a client have a 2 weeks rolling period,
i have to calculate the average weekly revenue for the period of 12 to 25th jan
. similarly for 19th Jan, it is from 5th to 18th.

i couldnot think of a way using analytic function.

thanks for your help and effort
<snip>
---->still not getting it.
---->how do you know "the 26th", "the 19th"
---->i know the period is N weeks -- but how did you decide where the start
points are.
---->doesn't make sense yet
<snip>
the starting point should be every week. it is like i should calculate the
previous n weeks for every week.
we have 12-48 months worth of data and we should calculate for every week, the
previous n weeks weekly revenue.
Let me know if this doesnot make sense yet.

      and we said...

ops$tkyte@ORA9IR2> select * from t1;

       CID      WEEKS
---------- ----------
         1          2
         2         13

ops$tkyte@ORA9IR2> select * from t2;

       CID        AMT DT
---------- ---------- ---------
         1         10 06-JAN-03
         1          2 07-JAN-03
         1          3 08-JAN-03
         1          4 09-JAN-03
         1          5 10-JAN-03
         1         10 13-JAN-03
         1          2 14-JAN-03
         1          3 15-JAN-03
         1          4 16-JAN-03
         1          5 17-JAN-03
         1         10 20-JAN-03
         1          2 21-JAN-03
         1          3 22-JAN-03
         1          4 23-JAN-03
         1          5 24-JAN-03
         2        110 06-JAN-03
         2         12 07-JAN-03
         2         13 08-JAN-03
         2         14 09-JAN-03
         2         15 10-JAN-03
         2        110 13-JAN-03
         2         12 14-JAN-03
         2         13 15-JAN-03
         2         14 16-JAN-03
         2         15 17-JAN-03
         2        110 20-JAN-03
         2         12 21-JAN-03
         2         13 22-JAN-03
         2         14 23-JAN-03
         2         15 24-JAN-03

30 rows selected.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select cid, wk, amt,
  2         min(wk) over (partition by cid
  3                       order by wk
  4                       range x.days preceding) from_dt,
  5         sum(amt) over (partition by cid
  6                       order by wk
  7                       range x.days preceding) sum_amt
  8    from (
  9  select t2.cid, next_day(t2.dt-1,'Sun') wk, sum(t2.amt) amt,
 10         t1.weeks*7-0.01 days
 11    from t1, t2
 12   where t1.cid = t2.cid
 13   group by t2.cid, next_day(t2.dt-1,'Sun'), t1.weeks
 14         ) x
 15  /

       CID WK               AMT FROM_DT      SUM_AMT
---------- --------- ---------- --------- ----------
         1 12-JAN-03         24 12-JAN-03         24
         1 19-JAN-03         24 12-JAN-03         48
         1 26-JAN-03         24 19-JAN-03         48
         2 12-JAN-03        164 12-JAN-03        164
         2 19-JAN-03        164 12-JAN-03        328
         2 26-JAN-03        164 12-JAN-03        492

6 rows selected.

Review &
      Followup

      Rating:
      5
  January 02, 2004
Reviewer:
      A reader
Hi Tom,
that is great...

      Rating: 4
Nice as
      usual  January 03, 2004
Reviewer:  Shree
       from London,UK
Dear sir,
 I wonder what will it be?
   Tom sans Oracle = ?????????????
 Bye!

      Rating: 5
Why 13.99
      days  January 08, 2004
Reviewer:  dharma
       from CA USA
Understood everything except the 13.99 days for the windowing clause. Why doesnt
it accept the whole 14 day period?

Thanks

          Followup:

we wanted "less than", not "less than or equal" -- else we double count some
days.

January 09, 2004 wallacel -- Thanks for the question regarding
      "sql_trace and tkprof", version 8.1.6

      You Asked
Tom:

under what situation should i use explain=username/password
other than the one runing the sql?

base on my understanding, trc file only record execution
statistics of the query, but didn't have execution path
recorded, so if you what to know what happen when
use run that query, you should always use that username/password
for expalin=username/password,
am i right?

      and we said...
It has the execution plan in it without the explain=.

here is a tkprof without explain = in 8.1.6:

select *
from
 emp where ename = 'KING'

....
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 38583

Rows     Row Source Operation
-------  ---------------------------------------------------
      2  TABLE ACCESS BY INDEX ROWID EMP
      3   INDEX RANGE SCAN (object id 117022)

The explain= has the object id, not the index name in it so it
is hard to read.  If I use explain= I get instead:

select *
from
 emp where ename = 'KING'

...
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 38583  (OPS$TKYTE)

Rows     Row Source Operation
-------  ---------------------------------------------------
      2  TABLE ACCESS BY INDEX ROWID EMP
      3   INDEX RANGE SCAN (object id 117022)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      2   TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
      3    INDEX (RANGE SCAN) OF 'ENAME_IDX' (NON-UNIQUE)

which is a little more readable BUT could result in a misleading
plan.  It is doing an explain plan on the query given the
CURRENT environment -- which might be different then the run
time environmemt.

You might use explain=user/pass with a different user/pass then
the original when the original user does not have access to the
underlying tables (eg: the sql was initiated from PLSQL and the
user running the stored procedure did not have select on the
underlying tables).

I myself don't use explain= anymore unless i need to have the
index names resolved -- and then I check to make sure the two
plans are identical.
Review & Followup

      Rating: 2
No plan without
      "explain=" ?  January 07, 2004
Reviewer:  Kim
      Berg Hansen  from Middelfart, Denmark
Hi, Tom

I understand your reasons for not using explain= for tkprof. It's quite clear
from your book.

My problem is, though, that when I use tkprof without explain=, I simply don't
get a plan in the tkprof output?
I'm working on an Oracle 8.1.7.4 on Tru64 Unix.

Is there something special I have to do to have the trace file include execution
path information ?

Thanks in advance.

Kim Berg Hansen

T.Hansen Gruppen A/S
Denmark

          Followup:

then you did not close the cursors (eg: exit sql plus or the application).

the STAT records are not written to the trace file until the cursors are closed.

      Rating: 3
That explains
      it  January 09, 2004
Reviewer:  Kim Berg
      Hansen  from Middelfart, Denmark
Thanks, Tom - I'm sure that explains it.

My tests used trace on at the beginning and trace off at the end of the tests (I
like not to leave "loose ends" hanging so I used trace off to "clean up" before
exiting the application :-)
Opening and closing of cursors is not within my control - the kernel of the ERP
system does that.

So I now tried just to use trace on at the beginning and then just exiting the
application.
Unfortunately that did not help - I still don't get a plan with TKPROF :-(
And when I look in the raw tracefile I also don't get any STAT records...

So I think I must conclude, that my ERP system does not close it's cursors when
exiting the application... hmmmm...
Well, I can't get your help on that one :-)

Thanks again.
Looking forward to the Miracle Master Class...

Regards

Kim Berg Hansen

          Followup:

(i think the ERP application did not really "exit" -- eg: it is a middle tier
thing and you would have to shut down/startup the app server to get it to really
exit perhaps?)

January 09, 2004 -- Thanks for the question regarding "How to make
      CBO behave like /*+ first_rows */", version 8.1.7

      You Asked
I have a wide table CUST with 100+ columns and 3 other narrower tables
containing optional customer attributes. I have a view CUSTOMER that outer-joins
the cust table with the 3 other tables like so

create view customer as
select ...
from cust a, cntry b, cntry c, cntry d, custtax e, cust_org f
where a.cust_id=e.cust_id(+)
and a.cust_id=f.cust_id(+)
and a.birth_cntry_id=b.cntry_id(+)
and a.citiz_cntry_id=c.cntry_id(+)
and a.resid_cntry_id=d.cntry_id(+)

All tables are analyzed and the cust_id column in all tables is the PK.

When I do 'select ... from customer', I get a plan like

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=448 Card=21874 Bytes=9383946)
   1    0   HASH JOIN (OUTER) (Cost=448 Card=21874 Bytes=9383946)
   2    1     HASH JOIN (OUTER) (Cost=358 Card=21874 Bytes=8115254)
   3    2       HASH JOIN (OUTER) (Cost=275 Card=21874 Bytes=7502782)
   4    3         HASH JOIN (OUTER) (Cost=196 Card=21874 Bytes=7087176)
   5    4           HASH JOIN (OUTER) (Cost=121 Card=21874 Bytes=6671570)
   6    5             TABLE ACCESS (FULL) OF 'CUST' (Cost=51 Card=21874
Bytes=6255964)
   7    5             TABLE ACCESS (FULL) OF 'CNTRY' (Cost=1 Card=246
Bytes=4674)
   8    4           TABLE ACCESS (FULL) OF 'CNTRY' (Cost=1 Card=246 By tes=4674)
   9    3         TABLE ACCESS (FULL) OF 'CNTRY' (Cost=1 Card=246 Byte s=4674)
  10    2       TABLE ACCESS (FULL) OF 'CUST_ORG' (Cost=1 Card=5 Bytes =140)
  11    1     TABLE ACCESS (FULL) OF 'CUSTTAXPHYS' (Cost=1 Card=4 Byte s=232)

This is a pathetic plan. Like you said in another discussion on paginating
result sets, 99% of applications are interested in getting the first row of
output...fast. They do a few page downs and get bored. Also, when I browse this
view in tools like TOAD, just clicking on the DATA tab in the SChema browser,
takes a long time.

Now, I tried adding the /*+ first_rows */ hint. Voila, I got...

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=131295 Card=21874
Bytes=9383946)
   1    0   NESTED LOOPS (OUTER) (Cost=131295 Card=21874 Bytes=9383946)
   2    1     NESTED LOOPS (OUTER) (Cost=109421 Card=21874 Bytes=8968340)
   3    2       NESTED LOOPS (OUTER) (Cost=87547 Card=21874 Bytes=8552734)
   4    3         NESTED LOOPS (OUTER) (Cost=65673 Card=21874 Bytes=8137128)
   5    4           NESTED LOOPS (OUTER) (Cost=43799 Card=21874 Bytes=7524656)
   6    5             TABLE ACCESS (FULL) OF 'CUST' (Cost=51 Card=21874
Bytes=6255964)
   7    5             TABLE ACCESS (BY INDEX ROWID) OF 'CUSTTAXPHYS' (Cost=2
Card=4 Bytes=232)
   8    7               INDEX (RANGE SCAN) OF 'CUSTTAXPHYS#PK' (UNIQUE) (Cost=1
Card=4)
   9    4           TABLE ACCESS (BY INDEX ROWID) OF 'CUST_ORG' (Cost=1 Card=5
Bytes=140)
  10    9             INDEX (UNIQUE SCAN) OF 'CUST_ORG#CUST_ID_PK' (UNIQUE)
  11    3         TABLE ACCESS (BY INDEX ROWID) OF 'CNTRY' (Cost=1 Card=246
Bytes=4674)
  12   11           INDEX (UNIQUE SCAN) OF 'CNTRY#CNTRY_ID_PK' (UNIQUE)
  13    2       TABLE ACCESS (BY INDEX ROWID) OF 'CNTRY' (Cost=1 Card=246
Bytes=4674)
  14   13         INDEX (UNIQUE SCAN) OF 'CNTRY#CNTRY_ID_PK' (UNIQUE)
  15    1     TABLE ACCESS (BY INDEX ROWID) OF 'CNTRY' (Cost=1 Card=246
Bytes=4674)
  16   15       INDEX (UNIQUE SCAN) OF 'CNTRY#CNTRY_ID_PK' (UNIQUE)

Although both have a FTS of 'cust' since I am not specifying any specific
cust_id, the FIRST_ROWS one is almost instantaneous in returning results,
presumably because of all the NESTED LOOPS.

So, why  does this have a cost of 131295 as compared to the plain vanilla CBO
plan of 448? Shouldnt a lower cost plan be faster than a higher cost plan?

Why does the CBO choose the HASH JOIN? (Assuming that is what is making it so
slow)?

In general, I find that many times the /*+ FIRST_ROWS */ hint works miracles.
Why cant the CBO figure this out by itself?

The funny part is, if I use RBO (with the /*+ RULE */ hint), this gives (almost
always) the same plan as /*+ FIRST_ROWS */ and is very fast! Makes me think, why
am I bothering with CBO at all? :-(

Comments? Thanks.

      and we said...
pathetic?  are you KIDDING - that is a beautiful plan, I could not IMAGINE a
nicer looking plan.  I love it.

See:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6749454952894#6760861174154
that is wonderful -- exactly what "select * from customer" should be doing 100%.
I would be really upset if the other plan you like was choosen.

Now, if you are interested in getting the first rows first, one might ask "well,
why didn't you say so?"

I mean come on -- the database sees this query and says -- you know, if I use
indexes like crazy, this'll take an hour.  If I blow them off -- 10 seconds.  It
is a NO BRAINER for the database at that point, given that level of information.

Supposing however you told the database via an alter session "Hey database, I'm
an interactive application -- not a stored procedure, not a batch job -- I'd
like the first rows first" -- what then?  Well, then you would get what you wish
for.

You seem to have a misunderstanding of whats at work here - especially when you
start talking about costs -- the plain vanilla CBO plan is beautiful -- given
the query.  The first rows plan (which is what the RBO would come up with in all
likely hood) is horrible expensive -- totally expensive -- to get to the end of
the result set.

Let me try an analogy.  You just won the lottery,  It was a $100 million dollar
prize.  You can either:

a) collect 50 million right now and be done with it.
b) accept the 20 year annuity and get 100 million paid out over 20 years.

So, what are you going to do?  Do you want 50 cents on the dollar today or do
you want 100 cents on the dollar over 20 years?

Do you want throughput or response time?

If you don't tell me, I'll guess.  I'll have a 50/50 chance of being wrong.  You
might be really mad, you might be overjoyed.  You better tell me what you want
or you might be disappointed.

So - why not "alter session set optimizer_goal=first_rows"?  Or, in the queries
that should be first rows -- say so via the hint?

You need to take a much broader view of things here.  Skip the CBO and miss out
on hundreds of features.  Skip the CBO now and be prepared to face it tomorrow
(10i).

You are right there with Joe Programmer in my example.  You run the query in
silly toad and say "gee, takes a long time, let me hint it to be rule.  Oh
great, it flies -- now, lets put it in my code.  Code goes out the door -- what,
what do you mean my application is slow -- it ran fast in toad?  Oh, you mean
you need to wait for the last row to come out cause you really run the report
all of the way to the end??"

Do you have select * from customer in your application?  probably not (if you do
-- and you only are interested in the first row -- well, you should be using
rownum to limit the result set and first_rows to give us a clue as to your
intention).

I think you've missed the boat (and point) entirely on this one.

Review &
      Followup

      Rating: 4
Some more
      clarification...  April 10, 2003
Reviewer:
      A reader
Thanks for your response. I am suitably rebuked! Yes, I did seem to have missed
the boat/point on this one.

No application will do 'select * from customer', it will _always_ be with some
other limiting criteria (typically last_name or cust_no or some other duly
indexed field).

My selfish concern was only for myself, when I just want to browse using Toad
and clicking on the 'Data' tab takes forever.

Yes, I guess I can type (eek!) select /*+ first_rows */ from customer where
rownum<50;

Few followup questions, if you dont mind.

1. If I give a hint like say /*+ first_rows */ in some view and use that view to
create another view like so

create view v1 as select /*+ first_rows */ from ...

create view v2 as
select ... from v1
union all
select ... from v3

I find that the plan for

select ... from v2

has the /*+ first_rows */ hint.

Why does the hint in the definition of one of the views apply to any other view
using that view? Is there any way to prevent this? I do want to re-use my views
so as to share code, so to speak, but I dont want the optimization I did for one
view to affect other views using that view. I didnt think the hint bubbled like
this.

          Followup:

1) i wouldn't hint a view - there are hints that can be used to push down into a
view in the main query (so you can "hint" a view by hinting the query against
the view).  Putting a hint into a view has only made my life misery -- trying to
figure out "why, why why is it doing that -- DOH, someone put that hint in there
and it is totally incompatible with what I need"

Your example is a great reason NOT to hint the views.

      Rating: 5
So
      true  April 10, 2003
Reviewer:  Jeremy
       from Chicago
This is just great!!  You don't know (well, you probably do) how many times I am
pulled over by some "programmer" who is running a query in toad and sees the
"first rows" return fast and then says, "See.  The query is faster this way and
slower the way you wrote it.", even when they are writing a program that has to
process all the rows.  I try to be patient... but it is hard.

          Followup:

It happens so many times that I frequently ask now "are you running toad?  well
then page down to the LAST PAGE and then tell me what you think".

Silence is the most common result.

Had one guy that could not understand how a query without an order by could take
1 second, same query with order by 2 minutes.

I made him run both in sqlplus to get a tkprof and it turns out both took 2
minutes as coded -- he had never gotten past the first page.  the order by had
to get the last past in order to give the first -- the non-order by didn't.
both queries had exactly the same runtime characteristics when fetched from
fully.

one of the reasons I'll never use toad or anything like it to test with -- its
all about sqlplus!

      Rating: 3
Similar
      problem  April 10, 2003
Reviewer:  Jon
       from Sydney
Couldn't agree more - I like to let the optimiser do what it thinks is best -
usually that's just fine.

But I have a particular example that I can't seem to get the optimiser to
evaluate in an efficient way.  I have some functionality encapsulated in a
complex view.  When queries are run against that view directly using a constant,
it produces an appropriate nested_loop/first_rows kind of plan.  For example:

select *
from lvr_by_lcs
where loan_id = 1237

returns 30 out of a possible 400K+ rows in about 50 msecs - just perfect.

But when I join another table to the view:

select l.*
from redemption_batch rb, lvr_by_lcs l
where l.loan_id = rb.loan_id
and rb.redemption_batch_id = 200

the plan changes to hash_join/full_scan/all_rows kind of approach that is just
ridiculous for this situation, and takes ages to run.  redemption_details_id is
the PK for that table, so there could only ever be one loan_id joining to the
view. I have tried using no_merge, first_rows hints, and I can't get it to
return results efficiently.  It is actually making the view usless for its
intended purpose, because no one can join to it without it disappearing into
query land for half an hour.  All tables are analyzed etc.

It think it needs to drive into the view off the loan_id, but it refuses to do
it.

What is the CBO doing here - is there any way around it?

Thanks
Jon

          Followup:

Well, autotrace would be useful -- to see the estimated cardinality

      Rating: 4
Trouble with
      FIRST_ROWS, how to NOMERGE subquery?  April 11, 2003

Reviewer:  David Penington  from Melbourne, Australia

Very valid comments, and interesting because I just spent 4 hours struggling
with a related problem on 9.0.1.3, including, of course, hunting in askTOM,
which had some useful stuff. I have a large table and need to get the first N
rows, or count how many rows there are, up to a limit.  There are other
criteria, but the killer is a subquery which is equivalent to getting a maximum
date, and I know most rows only have one date.  Analytics might do this better,
but this has to run on Standard Edition. I tried FIRST_ROWS, USE_NL and others,
but it wouldn't change. My conclusion was that the optimiser is merging up the
sub-query with the MAX which is documented as overriding FIRST_ROWS.
Eventually I found NO_UNNEST hint (new in 9.0.1) which let the ROWNUM <= 11
work.
This takes 2 minutes 13 seconds (3.7million rows in table):
select /*+ FIRST_ROWS(10) */ * from SERVICE S1 where  rownum <= 11 and
S1.date_published=
(SELECT MAX(date_published) FROM SERVICE T2 WHERE T2.ID = S1.ID)
This takes 0.03 seconds:
select * from SERVICE S1 where  rownum <= 11 and S1.date_published=
(SELECT /*+ NO_UNNEST */ MAX(date_published) FROM SERVICE T2 WHERE T2.ID =
S1.ID)
I don't know how to give a NOMERGE hint at the top query that references this
nameless subquery - is it possible (and is it relevant) ?

          Followup:

analytics should be available with SE in 9i...  can you try that.

(autotrace traceonly explain plans are always usefull)

      Rating: 3
Autotrace
      results...  April 13, 2003
Reviewer:  Jon
       from Sydney
OK, here are the autotrace results:

  1  select loan_id, portfolio_value, weighted_lvr
  2  from lvr_by_lcs
  3* where loan_id = 4568
empire_owner@EMPIRED> /

10 rows selected.

Execution Plan
----------------------------------------------------------
   0 SELECT STATEMENT Optimizer=CHOOSE (Cost=9 Card=90 Bytes=11880)
   1   VIEW (Cost=9 Card=90 Bytes=11880)
   2     NESTED LOOPS (OUTER) (Cost=9 Card=90 Bytes=12690)
   3       NESTED LOOPS (OUTER) (Cost=8 Card=7 Bytes=868)
   4         NESTED LOOPS (OUTER) (Cost=7 Card=7 Bytes=819)
   5           NESTED LOOPS (Cost=6 Card=7 Bytes=714)
   6             NESTED LOOPS (Cost=5 Card=7 Bytes=483)
   7               NESTED LOOPS (Cost=4 Card=7 Bytes=378)
   8                 NESTED LOOPS (Cost=3 Card=1 Bytes=23)
   9                   NESTED LOOPS (Cost=2 Card=1 Bytes=13)
  10                     TABLE ACCESS (BY INDEX ROWID) OF 'EM$_LOAN' (Cost=1
Card=1 Bytes=8)
  11                       INDEX (UNIQUE SCAN) OF 'EM$_LOAN_PK' (UNIQUE) (Cost=1
Card=1)
  12                     TABLE ACCESS (BY INDEX ROWID) OF 'EM$_TERMS_CONDITIONS'
(Cost=1 Card=32 Bytes=16
     0)

  13                       INDEX (UNIQUE SCAN) OF 'BOOKLET_TYPE_PK' (UNIQUE)
  14                   TABLE ACCESS (BY INDEX ROWID) OF 'LOAN_CLIENT' (Cost=1
Card=2 Bytes=20)
  15                     INDEX (RANGE SCAN) OF 'LOAN_CLIENT_IX6' (NON-UNIQUE)
  16                 TABLE ACCESS (BY INDEX ROWID) OF 'EM$_LOAN_CLIENT_SECURITY'
(Cost=1 Card=471179 Byte
     s=14606549)

  17                   INDEX (RANGE SCAN) OF 'EM$_LOAN_CLIENT_SECURITY_UK1'
(UNIQUE) (Cost=1 Card=471179)
  18               TABLE ACCESS (BY INDEX ROWID) OF 'SECURITY_MASTERFUND'
(Cost=1 Card=34984 Bytes=524760
     )

  19                 INDEX (UNIQUE SCAN) OF 'SECURITY_MASTERFUND_PK' (UNIQUE)
  20             TABLE ACCESS (BY INDEX ROWID) OF 'EM$_SECURITIES' (Cost=1
Card=33659 Bytes=1110747)
  21               INDEX (UNIQUE SCAN) OF 'EM$_SECURITIES_PK' (UNIQUE)
  22           TABLE ACCESS (BY INDEX ROWID) OF 'LVR' (Cost=1 Card=38736
Bytes=581040)
  23             INDEX (UNIQUE SCAN) OF 'LVR_PK' (UNIQUE)
  24         TABLE ACCESS (BY INDEX ROWID) OF 'LVR' (Cost=1 Card=38736
Bytes=271152)
  25           INDEX (UNIQUE SCAN) OF 'LVR_PK' (UNIQUE)
  26       TABLE ACCESS (BY INDEX ROWID) OF 'PROSPECTUS' (Cost=1 Card=1291
Bytes=21947)
  27         INDEX (UNIQUE SCAN) OF 'PROSPECTUS_PK' (UNIQUE)

Statistics
----------------------------------------------------------
         55  recursive calls
          0  db block gets
        136  consistent gets
          0  physical reads
          0  redo size
        826  bytes sent via SQL*Net to client
        425  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
         21  sorts (memory)
          0  sorts (disk)
         10  rows processed

/*********************************************************/
/*********************************************************/

  1  select l.loan_id, l.portfolio_value, l.weighted_lvr
  2  from redemption_batch rb, lvr_by_lcs l
  3  where l.loan_id = rb.loan_id
  4* and rb.redemption_batch_id = 200
empire_owner@EMPIRED> /

10 rows selected.

Execution Plan
----------------------------------------------------------
   0 SELECT STATEMENT Optimizer=CHOOSE (Cost=12672 Card=98 Bytes=13720)
   1   NESTED LOOPS (Cost=12672 Card=98 Bytes=13720)
   2     TABLE ACCESS (BY INDEX ROWID) OF 'REDEMPTION_BATCH' (Cost=1 Card=1
Bytes=8)
   3       INDEX (UNIQUE SCAN) OF 'PK_REDEMPTION' (UNIQUE)
   4     VIEW
   5       HASH JOIN (OUTER) (Cost=12671 Card=6082921 Bytes=857691861)
   6         HASH JOIN (Cost=9649 Card=471179 Bytes=58426196)
   7           TABLE ACCESS (FULL) OF 'EM$_TERMS_CONDITIONS' (Cost=1 Card=32
Bytes=160)
   8           HASH JOIN (Cost=9613 Card=471179 Bytes=56070301)
   9             TABLE ACCESS (FULL) OF 'EM$_SECURITIES' (Cost=100 Card=33659
Bytes=1110747)
  10             HASH JOIN (Cost=6470 Card=471179 Bytes=40521394)
  11               TABLE ACCESS (FULL) OF 'SECURITY_MASTERFUND' (Cost=19
Card=34984 Bytes=524760)
  12               HASH JOIN (OUTER) (Cost=4831 Card=471179 Bytes=33453709)
  13                 HASH JOIN (OUTER) (Cost=3025 Card=471179 Bytes=26386024)
  14                   HASH JOIN (Cost=2161 Card=471179 Bytes=23087771)
  15                     HASH JOIN (Cost=502 Card=97235 Bytes=1750230)
  16                       TABLE ACCESS (FULL) OF 'EM$_LOAN' (Cost=248
Card=62219 Bytes=497752)
  17                       TABLE ACCESS (FULL) OF 'LOAN_CLIENT' (Cost=148
Card=97235 Bytes=972350)
  18                     TABLE ACCESS (FULL) OF 'EM$_LOAN_CLIENT_SECURITY'
(Cost=639 Card=471179 Bytes=14
     606549)

  19                   TABLE ACCESS (FULL) OF 'LVR' (Cost=43 Card=38736
Bytes=271152)
  20                 TABLE ACCESS (FULL) OF 'LVR' (Cost=43 Card=38736
Bytes=581040)
  21         TABLE ACCESS (FULL) OF 'PROSPECTUS' (Cost=2 Card=1291 Bytes=21947)

Statistics
----------------------------------------------------------
         55  recursive calls
         41  db block gets
      14673  consistent gets
      34453  physical reads
          0  redo size
        832  bytes sent via SQL*Net to client
        425  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
         22  sorts (memory)
          0  sorts (disk)
         10  rows processed

As you can see, it chooses not to drive off the loan table (em$_loan) in the
second query - and I can't seem to force it to do so.  I have tried global
hints, from the top level query, but they don't seem to change anything.  Any
suggestions?

Thanks
Jon

          Followup:

sorry -- didn't realize this was a huge gnarly view here.  all bets off -- I
cannot tell anything about nothing without the query -- but -- as this is
getting quite large (and you have a workaround), wait until I'm taking a
question and post the relevant information there (views, indexes, tables, et.
al.)

      Rating: 5
what does
      unnest hint do?  December 06, 2003
Reviewer:
      A reader
Hi

We recently fixed a view by using unnest hint, basically it was this query

select *
from abc x
where mynum = (select max(mynum)
                 from abc y
                where x.col1 = y.col1
                  and x.col2 = y.col2
                  and xdate <= trunc(sysdate))

col1 and col2 are indexed

at first the plan fulls scans x and index fast full scans index on col1 and
col2, takes ages

added unnest hint full scan became range scan

what does unnest do?

          Followup:

un-nested the subquery.

it turned it into a "semi join", sort of like writing the query as a join.

      Rating: 5
do you mean
      this  December 06, 2003
Reviewer:
      A reader
select *
from abc x
where mynum = (select max(mynum)
                 from abc y
                where x.col1 = y.col1
                  and x.col2 = y.col2
                  and xdate <= trunc(sysdate))

into

select *
from abc x, (select col1, col2, max(mynum)
               from abc
              where xdate <= trunc(sysdate)
             group by col1, col2) y
where x.col1 = y.col1
  and x.col2 = y.col2

If this is the case I thought Oracle does this automatically :-0

          Followup:

similar -- but a semi join -- not just a simple join like that -- it would not
need to do the group by.

try this out

select *
  from (
select abc.* ,
       max(case when xdate <= trunc(sysdate) then mynum end)
       over (partition by col1, col2) max_mynum
  from abc
       )
 where mynum = max_mynum;

it will do it automagically -- but, you give us no test case to look at, no
inputs to tell you why it did what it did.  I can only provide an example
myself.  I cannot imagine why it would want to use indexes *at all* here
personally.  Looks like full scan city to me (so something must be missing from
your example)

DOC>create table t ( col1 int, col2 int, mynum int, xdate date, data
varchar2(50) );
DOC>
DOC>insert into t
DOC>select mod(rownum,1000), mod(rownum,1000), object_id, created+50,
rpad('x',50,'x')
DOC>  from all_objects;
DOC>
DOC>create index t_idx on t(col1,col2,xdate);
DOC>
DOC>exec dbms_stats.gather_table_stats( user, 'T', method_opt=>'for all indexed
columns', cascade=>true);
DOC>*/
ops$tkyte@ORA920>
ops$tkyte@ORA920> select count(*),
  2         count(distinct col1||','||col2),
  3             count(case when xdate <= trunc(sysdate) then 1 end)
  4    from t;

  COUNT(*) COUNT(DISTINCTCOL1||','||COL2)
COUNT(CASEWHENXDATE<=TRUNC(SYSDATE)THEN1END)
---------- ------------------------------
--------------------------------------------
     31702                           1000
31577

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> set autotrace traceonly statistics
ops$tkyte@ORA920> select *
  2  from t x
  3  where mynum = (select max(mynum)
  4                   from t y
  5                  where x.col1 = y.col1
  6                    and x.col2 = y.col2
  7                    and xdate <= trunc(sysdate))
  8  /

1000 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        748  consistent gets
        672  physical reads
          0  redo size
      31192  bytes sent via SQL*Net to client
       1229  bytes received via SQL*Net from client
         68  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
       1000  rows processed

ops$tkyte@ORA920>
ops$tkyte@ORA920> select *
  2    from (
  3  select t.* ,
  4         max(case when xdate <= trunc(sysdate) then mynum end)
  5         over (partition by col1, col2) max_mynum
  6    from t
  7         )
  8   where mynum = max_mynum
  9  /

1000 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        341  consistent gets
        336  physical reads
          0  redo size
      36953  bytes sent via SQL*Net to client
       1229  bytes received via SQL*Net from client
         68  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
       1000  rows processed

ops$tkyte@ORA920> set autotrace off

and tkprof says:

select *
from t x
where mynum = (select max(mynum)
                 from t y
                where x.col1 = y.col1
                  and x.col2 = y.col2
                  and xdate <= trunc(sysdate))

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.41       0.40        672        748          0        1000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       70      0.41       0.40        672        748          0        1000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 549

Rows     Row Source Operation
-------  ---------------------------------------------------
   1000  HASH JOIN  (cr=748 r=672 w=0 time=393513 us)
   1000   VIEW  (cr=341 r=336 w=0 time=185614 us)
   1000    SORT GROUP BY (cr=341 r=336 w=0 time=183179 us)
  31577     TABLE ACCESS FULL T (cr=341 r=336 w=0 time=92427 us)
  31702   TABLE ACCESS FULL T (cr=407 r=336 w=0 time=66313 us)
********************************************************************************

select *
  from (
select t.* ,
       max(case when xdate <= trunc(sysdate) then mynum end)
       over (partition by col1, col2) max_mynum
  from t
       )
 where mynum = max_mynum

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.39       0.38        336        341          0        1000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       70      0.39       0.38        336        341          0        1000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 549

Rows     Row Source Operation
-------  ---------------------------------------------------
   1000  VIEW  (cr=341 r=336 w=0 time=376949 us)
  31702   WINDOW SORT (cr=341 r=336 w=0 time=332608 us)
  31702    TABLE ACCESS FULL T (cr=341 r=336 w=0 time=70368 us)

meaning, you might look at anaytics.

      Rating: 5
same query,
      same plan, one with first_rows and is faster?  January 09,
      2004
Reviewer:  A reader
Hi

I have these couple of queries, return same rows and share same execution plan
but one is faster and does less reads... why?

 SELECT /*+ first_rows */ a.signature
  FROM sysadm.ps_rb_wf_signature a, sysadm.ps_rd_directory b
 WHERE a.person_id = b.idtypeattrib AND b.userid = :a
       AND recipient_type = 'PER'

38 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=567 Card=1878
Bytes=52584)
   1    0   NESTED LOOPS (Cost=567 Card=1878 Bytes=52584)
   2    1     TABLE ACCESS (FULL) OF 'PS_RB_WF_SIGNATURE' (Cost=3 Card=1878
Bytes=24414)
   3    1     INDEX (RANGE SCAN) OF 'PS_RD_DIRECTORY' (UNIQUE) (Cost=1 Card=1
Bytes=15)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       3933  consistent gets
         29  physical reads
          0  redo size
       7799  bytes sent via SQL*Net to client
       1073  bytes received via SQL*Net from client
         40  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         38  rows processed

 SELECT a.signature
  FROM sysadm.ps_rb_wf_signature a, sysadm.ps_rd_directory b
 WHERE a.person_id = b.idtypeattrib AND b.userid = :a
       AND recipient_type = 'PER'

38 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=567 Card=1878 Bytes=52584)
   1    0   NESTED LOOPS (Cost=567 Card=1878 Bytes=52584)
   2    1     TABLE ACCESS (FULL) OF 'PS_RB_WF_SIGNATURE' (Cost=3 Card=1878
Bytes=24414)
   3    1     INDEX (RANGE SCAN) OF 'PS_RD_DIRECTORY' (UNIQUE) (Cost=1 Card=1
Bytes=15)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      15884  consistent gets
      12481  physical reads
          0  redo size
       7799  bytes sent via SQL*Net to client
       1073  bytes received via SQL*Net from client
         40  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         38  rows processed

          Followup:

i doubt they really have the same plans.

explain plan "lies" sometimes -- especially with binds.

Use sql_trace and tkprof -- or EXPLICITLY cast the binds (eg: to_number(:a) if
is is really a number)

      Rating: 4
Execution Plan
        January 09, 2004
Reviewer:  Mahomed Suria
       from U.K
Hi Tom,

I have a question on Execution Plan.

I have the following query( V8.1.7) :

select r.rakusr, r.whscde, r.rakblk, r.rakasl, r.rakcol,
       r.raklvl, r.raksid, r.rakocc, r.rakfre, r.maxdep,
       r.numitr, r.raktyp, nvl(r.profil, -1), nvl(r.stgtyp, -1),
       r.palbas, (select count(*) from pallet p
                             where p.whscde = r.whscde
                               and p.rakblk = r.rakblk
                               and p.rakasl = r.rakasl
                               and p.rakcol = r.rakcol
                               and p.raklvl = r.raklvl
                               and p.raksid = r.raksid
                               and trnsts <> 5 ) rakcnt,
                (select nvl(count(*), 0) from pallet p
                            where p.dstwhs = r.whscde
                             and p.dstblk = r.rakblk
                              and p.dstasl = r.rakasl
                              and p.dstcol = r.rakcol
                              and p.dstlvl = r.raklvl
                              and p.dstsid = r.raksid ) dstcnt
               from rakloc r
                where r.whscde = 1
                and (r.rakblk = 1  or 1  is null)
                and (r.rakasl = 1  or 1  is null)
                and (r.rakcol = 1  or 1  is null)
                and (r.raklvl = 1  or 1 is null)
                and (r.raksid = 1 or 1 is null)
                and r.raktyp in (select raktyp from bkrtyp
                                 where  blktyp = 2010)

It produces the following plan:

Elapsed: 00:00:01.86

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 (BY INDEX ROWID) OF 'BKRTYP'
   5    4           INDEX (RANGE SCAN) OF 'BKRTYP_1' (NON-UNIQUE)
   6    1     TABLE ACCESS (BY INDEX ROWID) OF 'RAKLOC'
   7    6       INDEX (RANGE SCAN) OF 'RAKLOC_5' (NON-UNIQUE)

What I'd like to know is where does the SORT come in??

          Followup:

in's do that lots.

ops$tkyte@ORA920PC> set autotrace traceonly explain
ops$tkyte@ORA920PC> select * from dual where dummy in ( select * from dual );

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   MERGE JOIN
   2    1     SORT (JOIN)
   3    2       TABLE ACCESS (FULL) OF 'DUAL'
   4    1     SORT (JOIN)
   5    4       VIEW OF 'VW_NSO_1'
   6    5         SORT (UNIQUE)
   7    6           TABLE ACCESS (FULL) OF 'DUAL'

we need to distinct the subquery so we can "join to it"

The optimizer rewrote the in as a join here.

January 09, 2004 Markus -- Thanks for the question regarding
      "explain plan on range-partitioned table", version 8.1.7

      You Asked
Hi Tom!

I recenty dicovered some (in my eyes) very strange behaviour on our 8.1.7
installation (AIX 4.3):

We have a very large table ("src_trn_det") that is range-partitioned by a date
column ("cob_dt"):

CREATE TABLE SRC_TRN_DET (
  SEQ_NO                        NUMBER (10)   NOT NULL,
  CRES_SYS_ID                   NUMBER (3)    NOT NULL,
  COB_DT                        DATE          NOT NULL,
  TRN_SRC_CD                    VARCHAR2 (40)  NOT NULL,
  TRN_CD                        VARCHAR2 (30)  NOT NULL,
  RUN_ID                        NUMBER (15)   NOT NULL,
  MANUAL_IND                    CHAR (1)      DEFAULT 'N' NOT NULL,
  TRN_LINK_CD                   VARCHAR2 (30),
  FAC_LIMIT_SRC_CD              VARCHAR2 (40),
  FAC_LIMIT_CD                  VARCHAR2 (30),
  FAC_SRC_CD                    VARCHAR2 (40),
  FAC_CD                        VARCHAR2 (30),
  BRANCH_ID                     NUMBER (10),
  ENTITY_CCDB_ID                NUMBER (11),
  RT_TYPE_CD                    CHAR (1),
  RT_CB_CCDB_ID                 NUMBER (11),
  RT_COV_TYPE_CD                VARCHAR2 (2),
  RT_COV_SRC_CD                 NUMBER (28,2),
  RT_COV_CD                     VARCHAR2 (30),
  RT_BUS_AREA_UBR_ID            NUMBER (7),
  CPTY_TYPE_CD                  VARCHAR2 (40),
  CPTY_CD                       VARCHAR2 (50),
  CPTY_LONG_NAME                VARCHAR2 (120),
  CPTY_CCDB_ID                  NUMBER (11),
  ORIG_TYPE_CD                  VARCHAR2 (40),
  ORIG_CD                       VARCHAR2 (50),
  ORIG_CCDB_ID                  NUMBER (11),
  INTERCO_IND                   CHAR (1),
  MA_SRC_CD                     VARCHAR2 (40),
  MA_CD                         VARCHAR2 (30),
  MA_TYPE_CD                    VARCHAR2 (30),
  MA_SPL_NOTE                   VARCHAR2 (255),
  MASTER_MA_CD                  VARCHAR2 (30),
  CA_CD                         VARCHAR2 (30),
  SRC_PROD_TYPE_CD              VARCHAR2 (40),
  GRC_PROD_TYPE_ID              NUMBER (5),
  TRADE_ENTITY_CCDB_ID          NUMBER (11),
  SRC_PROXY_UBR_CD              VARCHAR2 (40),
  LOWEST_LEVEL_UBR_ID           NUMBER (7),
  DIVISION_UBR_ID               NUMBER (7),
  BUS_AREA_UBR_ID               NUMBER (7),
  PROD_AREA_UBR_ID              NUMBER (7),
  BOOK_TYPE_CD                  CHAR (1),
  TRN_TYPE_CD                   CHAR (1),
  DEAL_TYPE_CD                  CHAR (1),
  XCHG_CTRY_CD                  VARCHAR2 (4),
  ORIG_NOTIONAL_AMT             NUMBER (27,2),
  ORIG_NOTIONAL_CCY_CD          CHAR (3),
  OTHER_ORIG_NOTIONAL_AMT       NUMBER (27,2),
  OTHER_ORIG_NOTIONAL_CCY_CD    CHAR (3),
  CUR_NOTIONAL_AMT              NUMBER (27,2),
  CUR_NOTIONAL_CCY_CD           CHAR (3),
  OTHER_CUR_NOTIONAL_AMT        NUMBER (27,2),
  OTHER_CUR_NOTIONAL_CCY_CD     CHAR (3),
  ACCRUED_INT_AMT               NUMBER (27,2),
  ACCRUED_INT_CCY_CD            CHAR (3),
  TRADE_DT                      DATE,
  START_DT                      DATE,
  MATURITY_DT                   DATE,
  UL_MATURITY_DT                DATE,
  AMORT_IND                     CHAR (1),
  AMORT_ID                      NUMBER (3),
  SETTLE_STATUS_ID              NUMBER (1),
  DELIVERY_DT                   DATE,
  UL_DELIVERY_DT                DATE,
  CASH_SETTLE_IND               CHAR (1),
  CONTRACT_AMT                  NUMBER (27,2),
  CONTRACT_COUNT                NUMBER (15),
  PAY_CONTRACT_AMT              NUMBER (27,2),
  PAY_CONTRACT_CCY_CD           CHAR (3),
  PAY_FWD_AMT                   NUMBER (27,2),
  PAY_FWD_CCY_CD                CHAR (3),
  PAY_UL_SEC_TYPE_CD            VARCHAR2 (40),
  PAY_UL_SEC_CD                 VARCHAR2 (50),
  PAY_UL_SEC_CCY_CD             CHAR (3),
  PAY_UL_ISS_CCDB_ID            NUMBER (11),
  PAY_INT_RATE_TYPE_CD          VARCHAR2 (20),
  PAY_INT_RATE                  NUMBER (11,7),
  PAY_NEXT_FIX_CPN_DT           DATE,
  PAY_INT_INTERVAL              NUMBER (3),
  RCV_CONTRACT_AMT              NUMBER (27,2),
  RCV_CONTRACT_CCY_CD           CHAR (3),
  RCV_FWD_AMT                   NUMBER (27,2),
  RCV_FWD_CCY_CD                CHAR (3),
  RCV_UL_SEC_TYPE_CD            VARCHAR2 (40),
  RCV_UL_SEC_CD                 VARCHAR2 (50),
  RCV_UL_SEC_CCY_CD             CHAR (3),
  RCV_UL_ISS_CCDB_ID            NUMBER (11),
  RCV_INT_RATE_TYPE_CD          VARCHAR2 (20),
  RCV_INT_RATE                  NUMBER (11,7),
  RCV_NEXT_FIX_CPN_DT           DATE,
  RCV_INT_INTERVAL              NUMBER (3),
  AVG_MTH_DUE_AMT               NUMBER (27,2),
  AVG_MTH_DUE_CCY_CD            CHAR (3),
  AVG_YTD_DUE_AMT               NUMBER (27,2),
  AVG_YTD_DUE_CCY_CD            CHAR (3),
  OPT_TYPE_CD                   CHAR (1),
  STRIKE_FWD_FUT_PRICE_AMT      NUMBER (27,2),
  STRIKE_FWD_FUT_PRICE_CCY_CD   CHAR (3),
  VALUATION_DT                  DATE,
  MTM_AMT                       NUMBER (27,2),
  MTM_CCY_CD                    CHAR (3),
  PAY_UL_MTM_AMT                NUMBER (27,2),
  PAY_UL_MTM_CCY_CD             CHAR (3),
  RCV_UL_MTM_AMT                NUMBER (27,2),
  RCV_UL_MTM_CCY_CD             CHAR (3),
  BAL_SHEET_EXP_AMT             NUMBER (27,2),
  BAL_SHEET_EXP_CCY_CD          CHAR (3),
  CAD_RISK_WEIGHT_PCT           NUMBER (6,3),
  TRADE_RELATED_IND             CHAR (1),
  RISK_PROVISION_BAL_AMT        NUMBER (27,2),
  NEW_RISK_PROVISION_AMT        NUMBER (27,2),
  REL_RISK_PROVISION_AMT        NUMBER (27,2),
  RECOV_AMT                     NUMBER (27,2),
  RISK_WRITE_OFF_AMT            NUMBER (27,2),
  RISK_PROVISION_CCY_CD         CHAR (3),
  FIRST_RISK_PROVISION_DT       DATE,
  RESTRUCT_IND                  CHAR (1),
  NON_PERF_IND                  CHAR (1),
  NON_PERF_RCV_INT_AMT          NUMBER (27,2),
  OVERDUE_DAY_COUNT             NUMBER (6),
  CTRY_RISK_PROVISION_BAL_AMT   NUMBER (27,2),
  ABS_TRN_CD                    VARCHAR2 (30),
  RISK_MITIGATING_PCT           NUMBER (6,3),
  PAY_INT_AMT                   NUMBER (27,2),
  RCV_INT_AMT                   NUMBER (27,2),
  INT_CCY_CD                    CHAR (3),
  MARGIN_PCT                    NUMBER (6,3),
  MKT_MARGIN_PCT                NUMBER (6,3),
  STRATEGIC_LEND_IND            CHAR (1),
  YEAR00_NOTIONAL_AMT           NUMBER (27,2),
  YEAR01_NOTIONAL_AMT           NUMBER (27,2),
  YEAR02_NOTIONAL_AMT           NUMBER (27,2),
  YEAR03_NOTIONAL_AMT           NUMBER (27,2),
  YEAR04_NOTIONAL_AMT           NUMBER (27,2),
  YEAR05_NOTIONAL_AMT           NUMBER (27,2),
  YEAR06_NOTIONAL_AMT           NUMBER (27,2),
  YEAR07_NOTIONAL_AMT           NUMBER (27,2),
  YEAR08_NOTIONAL_AMT           NUMBER (27,2),
  YEAR09_NOTIONAL_AMT           NUMBER (27,2),
  YEAR10_NOTIONAL_AMT           NUMBER (27,2),
  GEN_RISK_PROVISION_BAL_AMT    NUMBER (27,2),
  ADJ_DESC                      VARCHAR2 (255),
  CREATE_USER_CD                VARCHAR2 (30)  DEFAULT USER NOT NULL,
  CREATE_DATETIME               DATE          DEFAULT SYSDATE NOT NULL,
  UPDATE_USER_CD                VARCHAR2 (30)  DEFAULT USER NOT NULL,
  UPDATE_DATETIME               DATE          DEFAULT SYSDATE NOT NULL,
  CREDIT_LINE_NET_IND           CHAR (1),
  REGULATORY_NET_IND            CHAR (1),
  OVERDUE_AMT                   NUMBER (27,2),
  OVERDUE_CCY_CD                CHAR (3),
  LOCAL_GL_CD                   VARCHAR2 (25),
  BAL_SHEET_EXP_EUR_AMT         NUMBER (27,2),
  BRANCH_LOC_CD                 VARCHAR2 (15),
  RESPONSIBLE_BRANCH_LOC_CD     VARCHAR2 (15),
  RESPONSIBLE_BRANCH_ID         NUMBER (10),
  RESPONSIBLE_BRANCH_CCDB_ID    NUMBER (15),
  TRADE_ENTITY_LOC_CD           VARCHAR2 (15),
  INTER_IND                     VARCHAR2 (1),
  GLOBAL_GL_CD                  VARCHAR2 (30),
  BS_COMPEN_CD                  VARCHAR2 (30),
  INT_COMPEN_CD                 VARCHAR2 (30),
  IMP_DT                        DATE,
  INT_INCOME_THEOR_IMP_AMT      NUMBER (27,2),
  INT_INCOME_ACT_IMP_AMT        NUMBER (27,2),
  INT_INCOME_IMP_CCY_CD         VARCHAR2 (3),
  UNEARNED_INCOME_DIS_LOAN_AMT  NUMBER (27,2),
  UNEARNED_INCOME_CCY_CD        VARCHAR2 (3),
  ORIG_FEE_INCOME_AMT           NUMBER (27,2),
  ORIG_FEE_EXPENSE_AMT          NUMBER (27,2),
  OTHER_FEE_INCOME_AMT          NUMBER (27,2),
  OTHER_EXPENSES_AMT            NUMBER (27,2),
  OTHER_INCOME_EXP_CCY_CD       VARCHAR2 (3),
  BOOK_VAL_AMT                  NUMBER (27,2),
  BOOK_VAL_CCY_CD               VARCHAR2 (3),
  THIRD_PARTY_SRC_CD            VARCHAR2 (40),
  THIRD_PARTY_CD                VARCHAR2 (50),
  THIRD_PARTY_CCDB_ID           NUMBER (15),
  ORIG_FEE_UNREALISED_AMT       NUMBER (27,2),
  EXTRACT_IND                   VARCHAR2 (1)  DEFAULT 'N')
   TABLESPACE CRES_SMALL_DATA
   PCTFREE 10
   PCTUSED 40
   INITRANS 1
   MAXTRANS 255
PARTITION BY RANGE (COB_DT)
  (
PARTITION OCT2001 VALUES LESS THAN (TO_DATE(' 2001-11-01 00:00:00', 'SYYYY-MM-DD
HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')) TABLESPACE CRES_LARGE_DATA
PCTFREE 40
   PCTUSED 40
   INITRANS 1
   MAXTRANS 255
 STORAGE (
   INITIAL 4194304
   NEXT 134217728
   PCTINCREASE 0
   MINEXTENTS 1
   MAXEXTENTS 2147483645
 ),
PARTITION NOV2001 VALUES LESS THAN (TO_DATE(' 2001-12-01 00:00:00', 'SYYYY-MM-DD
HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')) TABLESPACE CRES_LARGE_DATA
PCTFREE 40
   PCTUSED 40
   INITRANS 1
   MAXTRANS 255
 STORAGE (
   INITIAL 134217728
   NEXT 134217728
   PCTINCREASE 0
   MINEXTENTS 1
   MAXEXTENTS 2147483645
 ),
PARTITION DEC2001 VALUES LESS THAN (TO_DATE(' 2002-01-01 00:00:00', 'SYYYY-MM-DD
HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')) TABLESPACE CRES_LARGE_DATA
PCTFREE 40
   PCTUSED 40
   INITRANS 1
   MAXTRANS 255
 STORAGE (
   INITIAL 134217728
   NEXT 134217728
   PCTINCREASE 0
   MINEXTENTS 1
   MAXEXTENTS 2147483645
 )
);

Now when I query

select *  from src_trn_det where cob_dt = to_date('31.12.01','DD.MM.YY')

I get the following explain plan:

SELECT STATEMENT Optimizer=CHOOSE (Cost=72202 Card=6566399 Bytes=3112473126)
  PARTITION RANGE (SINGLE)
    TABLE ACCESS (FULL) OF SRC_TRN_DET (Cost=72202 Card=6566399
Bytes=3112473126)

But when I query:

select *  from src_trn_det where cob_dt = to_date('31.12.2001','DD.MM.YYYY')

I get

SELECT STATEMENT Optimizer=CHOOSE (Cost=71661 Card=6533165 Bytes=3090187045)
  TABLE ACCESS (FULL) OF SRC_TRN_DET (Cost=71661 Card=6533165 Bytes=3090187045)

So my question is: Why do I get a different execution plan if I specify the year
with 4 digits (Oracle even seems to do a full table scan in this case - no
elimination of partitions seems to happen).

Even more strange is that if you query

select *  from src_trn_det where cob_dt =
to_date('31.12.2001','DD.MM.YYYY','FOO')

You get the first plan again (it does not seem to matter what you supply as
third argument to to_date, it just has to be a non-empty string).

We have nls_language = AMERICAN, nls_territory = AMERICA, no other nls
parameters are set.

Could you please explain what is happening here?
Many thanks.

      and we said...
figured it out (sorry so long, I was traveling lots this week).

Ok, BOTH are doing partition elimination.  The first is doing it at run time,
the other at query compile time.

The first one -- according to the optimizer -- will eliminate a partition but
until it actually evaluates the to_date - it doesn't know which one.  Consider
this first query to be like:

 select * from src_trn_det where cob_dt = :bind_variable;

we know we can eliminate but not until we RUN the query do we know which one
will be eliminated. The YY is the cause of this.  Run this in 1999 and you get a
different answer then if you run in 2000!

The second query -- the optimizer KNOWS exactly what partition it will
eliminate.  It matters not when the query is run -- the date is a constant.

Autotrace doesn't show us this (well, not CLEARLY).  Here is how You can see
this fact.  I created this table:

ops$tkyte@ORA817DEV.US.ORACLE.COM> CREATE TABLE SRC_TRN_DET (
  2    SEQ_NO                        NUMBER (10)   NOT NULL,
  3    CRES_SYS_ID                   NUMBER (3)    NOT NULL,
  4    COB_DT                        DATE          NOT NULL,
  5    EXTRACT_IND                   VARCHAR2 (1)  DEFAULT 'N')
  6  PARTITION BY RANGE (COB_DT)
  7    ( PARTITION OCT2001 VALUES LESS THAN
  8      (TO_DATE(' 2001-11-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS',
'NLS_CALENDAR=GREGORIAN')),
  9      PARTITION NOV2001 VALUES LESS THAN
 10      (TO_DATE(' 2001-12-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS',
'NLS_CALENDAR=GREGORIAN')),
 11      PARTITION DEC2001 VALUES LESS THAN
 12      (TO_DATE(' 2002-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS',
'NLS_CALENDAR=GREGORIAN'))
 13   )
 14  /

Table created.

And then taking the CARD from your full scans -- I though the table had 6533165
 (but apparently that is the number of rows in a PARTITION of your table!).  So
I used dbms_stats:

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_stats.set_table_stats( user,
'SRC_TRN_DET', numrows => 6533165, numblks => 500000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_stats.set_table_stats( user,
'SRC_TRN_DET', 'OCT2001', numrows => 2188799, numblks => 167000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_stats.set_table_stats( user,
'SRC_TRN_DET', 'NOV2001', numrows => 2188799, numblks => 167000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_stats.set_table_stats( user,
'SRC_TRN_DET', 'DEC2001', numrows => 2188799, numblks => 167000 );

PL/SQL procedure successfully completed.

Now, when I run your queries -- I see something similar to you:

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select *  from src_trn_det where cob_dt =
to_date('31.12.01','DD.MM.YY')
  2  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4799 Card=65332 Bytes=2417284)
   1    0   PARTITION RANGE (SINGLE)
   2    1     TABLE ACCESS (FULL) OF 'SRC_TRN_DET' (Cost=4799 Card=65332
Bytes=2417284)

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select *  from src_trn_det where cob_dt =
to_date('31.12.2001','DD.MM.YYYY')
  2  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4808 Card=21888 Bytes=809856)
   1    0   TABLE ACCESS (FULL) OF 'SRC_TRN_DET' (Cost=4808 Card=21888
Bytes=809856)

But the kicker is -- look at the CARD= in my example!  The "worse" looking plan
is "better"....  Why?  Well, its because the second plan is really doing a fixed
partition elimination and has actually more info then the first query.  We'll
drop down to an old fashioned explain plan to see this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> explain plan set statement_id = 'DD_MM_YY'
for
  2  select *  from src_trn_det where cob_dt = to_date('31.12.01','DD.MM.YY')
  3  /

Explained.

ops$tkyte@ORA817DEV.US.ORACLE.COM> @/export/home/ora817/rdbms/admin/utlxpls.sql

Plan Table
--------------------------------------------------------------------------------
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |    65K|    2M|   4799 |       |       |
|  PARTITION RANGE SINGLE   |          |       |      |        |   KEY |   KEY |
|   TABLE ACCESS FULL       |SRC_TRN_D |    65K|    2M|   4799 |   KEY |   KEY |
--------------------------------------------------------------------------------

6 rows selected.

ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from plan_table;

13 rows deleted.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> explain plan set statement_id = 'DD_MM_YYYY'
for
  2  select *  from src_trn_det where cob_dt =
to_date('31.12.2001','DD.MM.YYYY')
  3  /

Explained.

Plan Table
--------------------------------------------------------------------------------
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |    21K|  790K|   4808 |       |       |
|  TABLE ACCESS FULL        |SRC_TRN_D |    21K|  790K|   4808 |     3 |     3 |
--------------------------------------------------------------------------------

ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from plan_table;

2 rows deleted.

see the first query knows it'll eliminate partitions, just not which ones.  The
second one, with a much abbreviated plan, knows that only partition 3 will be
accessed and sets it up accordingly.

So -- both plans are the "same" -- they will return different answers if run in
different centuries.

Use YYYY !!!

Review &
      Followup

      Rating: 5
what is the
      difference between run time and query compile time  January
      19, 2002
Reviewer:  A reader
As subject

These technical words are killing me...

Very nice example, clear and simple!

Thank you

          Followup:

when you parse -- you compile the query.  In this example, when we compiled
(parsed)

select *  from src_trn_det where cob_dt = to_date('31.12.01','DD.MM.YY')

the exact value of what to_date('31.12.01','DD.MM.YY') would be is
indeterminate.  If we run it in 1999, it'll be one value, if we run it in 2000,
it'll be yet another (wait another 98 years and it'll be yet a third).  Hence
until we actually EXECUTE the query (actually RUN it), that value is "unbound"
-- not known.

The value to_date('31.12.2001','DD.MM.YYYY') is not ambigous, it is very well
known.  At the time we parse (compile) the query, we KNOWN exactly what value
that will be regardless of the date (guess in 8000 years I'll have to modify
this answer...)

      Rating: 5
Thanks
      Tom  June 25, 2002
Reviewer:  A reader
       from England
A good, clear example.
Can you tell me why you would use SYYYY in the to_date format string rather than
YYYY? My own system uses SYYYY and examples in your book use YYYY. I'm not sure
if there was a specific reason for the SYYYY. Does this do anything extra?
Thanks

          Followup:

They did it:

YYYY
SYYYY
4-digit year. "S" prefixes BC dates with "-".

They were just being as explicit as possible.

      Rating: 4
explain plan on
      range-partitioned table  June 26, 2002
Reviewer:
      Neena  from Malaysia
sql> @c:\oracle\app\oracle\product\8.1.7\rdbms\admin\utlxplan.sql

sql>delete from plan_table;

sql> explain plan set statement_id='DD' for
  2  select * from emp;

Explained.

sql> @c:\oracle\app\oracle\product\8.1.7\rdbms\admin\utlxpls.sql

I am not getting any output .

Can u please tell me , what additional I had to do

          Followup:

show us a true cut and paste

ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from plan_table;

158 rows deleted.

ops$tkyte@ORA817DEV.US.ORACLE.COM> explain plan set statement_id = 'DD' for
  2  select * from emp;

Explained.

ops$tkyte@ORA817DEV.US.ORACLE.COM> @/export/home/ora817/rdbms/admin/utlxpls.sql

Plan Table
--------------------------------------------------------------------------------
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |       |      |        |       |       |
|  TABLE ACCESS FULL        |EMP       |       |      |        |       |       |
--------------------------------------------------------------------------------

ops$tkyte@ORA817DEV.US.ORACLE.COM>

works for me and I've never seen it "not work"

      Rating: 3
why different
      execution plan under accounts in same database  March 19,
      2003
Reviewer:  June  from MD
Hi Tom,

we had one partition table in one production database, with 7 partitions, each
with about 25 mm records. there is one thing we couldn't explain: after analyze
on partitions, run explain plan under 3 accounts (one schema owner, one Oracle
account, one application account, the other two use the public synonym) with
same sql statment (query data from one partition), the execution path is
different:  from application account, it is used 'nest loop', while the other
two uses 'hash loop' which results much longer query time (28 minutes vs 2
minutes).

Do you have any quick answer to this? it is on production database. at least, we
should expect same explain plan, shouldn't we?

          Followup:

lets see the queries and plans -- and also make sure there aren't any hidden
"login triggers" or other such magic that change the environments (optimizer_*,
*_size, etc settings)

push comes to shove, we'll use a 10053 trace to see whats up.

      Rating: 3
re: why
      different execution plan under accounts in same database
        March 19, 2003
Reviewer:  June  from
      MD
Thanks Tom. what specific setting would you like to see? as it is in production,
I can't see them all but have to request it. but what wonders me is that
database setting is there which should not change magically, when the explain
plan came from 3 accounts in same database.

I will try to update with more detail information. Thanks!

by the way, what is the '10053 trace '? isn't it regular trace utility?

          Followup:

I'd like you to just verify that the settings are in fact the same (no login
triggers, no procedures changing things behind our backs)

search this site for 10053 -- if you think everything is constant we'll have to
use that to dump the optimizers thought process and see whats different.

      Rating: 5
PARTITION RANGE
      (ALL)?  June 28, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
Hi,
I have created a table t with the following statement and then created primary
key, global index, local index on idno but plan always shows me "PARTITION RANGE
(ALL)" and it seems that it is not using the index. I have also created an index
on the second name column which is not partition key and when I query on this
field it shows index usage. I want to know that why it is not using index and
how to create global primary key index?

create table t(idno varchar2(2), name varchar2(10)
)
partition by range (idno)
(partition p1 values less than (11),
partition p2 values less than (21),
partition p3 values less than (31)
)
/

insert into t values(1, 'One');
insert into t values(2, 'Two');

insert into t values(11, 'Eleven');
insert into t values(12, 'Tweleve');

insert into t values(21, 'Twenty One');
insert into t values(22, 'Twenty Two');

scott > select * from t;

ID NAME
-- ----------
1  One
2  Two
11 Eleven
12 Tweleve
21 Twenty One
22 Twenty Two

6 rows selected.

scott > set autotrace on
scott > select * from t where idno = 12;

ID NAME
-- ----------
12 Tweleve

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=3 Bytes=30)
   1    0   PARTITION RANGE (ALL)
   2    1     TABLE ACCESS (FULL) OF 'T' (Cost=1 Card=3 Bytes=30)

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
          3  consistent gets
          0  physical reads
          0  redo size
        208  bytes sent via SQL*Net to client
        285  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          1  rows processed

scott > select * from t where name = 'Two';

ID NAME
-- ----------
2  Two

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=3 Bytes=30)
   1    0   TABLE ACCESS (BY GLOBAL INDEX ROWID) OF 'T' (Cost=1 Card=3
   2    1     INDEX (RANGE SCAN) OF 'T_GLOBAL2' (NON-UNIQUE) (Cost=1 C

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          2  consistent gets
          0  physical reads
          0  redo size
        203  bytes sent via SQL*Net to client
        288  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          1  rows processed

scott >

I am reading docs,

http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76956/partiti.htm#9324

          Followup:

try comparing numbers to numbers,
strings to strings,
dates to dates

and your life will be a much happier one.

select * from t where idno = 12;

is the same as

select * from t where TO_NUMBER(idno) = 12;

hence you are applying a function to the partition key so it cannot do partition
elimination.

try

select * from t where idno = '12';

      Rating: 5
Primary Key
      should be Global?  June 29, 2003
Reviewer:
      Sikandar Hayat  from Pindi Bhattian - Pakistan
Thanks TOM I had not noticed it that I defined the field with varchar. Now it is
ok.
I have a table and I have to split it into 12 partitions. There will be a
primary key on a filed which is not partition key. I need partition to truncate
the data after 3 months so after 3 months I will delete the data of one
partition. Now let me know which type of index I should use. Only 2 or 3 users
are using this table and generating data with batch processing. Let me know if I
go with Global then how to change the primary key to Global?

          Followup:

the primary key will have to be a globally partioned index since it is UNIQUE
and uniqueness can only be enforced in locally partitioned indexes that include
the partition key.

you will also want it to be global so as to permit index partition elimination
when you do a "select * from t where pk = :x" type of query.  Otherwise every
keyed lookup would involve 12 index range scans (eg: your IO would go up more
then an order of magnitude)

you will be rebuilding this index after a truncate, or if using 9i, you can
perform many partition level operations while maintaining the global index
(don't need a rebuild, but the partition operation will generate redo to protect
the index structure)

      Rating: 5
Primary key is
      Global by default?  June 30, 2003
Reviewer:
      Sikandar Hayat  from Pindi Bhattian - Pakistan
I have added primary key on the none partitioned key field. I want to know that,

1> How can I know from the plan that it has not scanned all partitions?
2> Is primary key is Global be default?

scott > alter table t add constraint t_pk primary key(name);

Table altered.

scott > select * from t where name = 'One';

        IDNO NAME
------------ ----------
           1 One

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=3 Bytes=60)
   1    0   TABLE ACCESS (BY GLOBAL INDEX ROWID) OF 'T' (Cost=1 Card=3
   2    1     INDEX (RANGE SCAN) OF 'T_PK' (UNIQUE) (Cost=1 Card=3)

Statistics
----------------------------------------------------------
        159  recursive calls
          3  db block gets
         34  consistent gets
          0  physical reads
          0  redo size
        204  bytes sent via SQL*Net to client
        288  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          6  sorts (memory)
          0  sorts (disk)
          1  rows processed

          Followup:

1) use explain plan and @?/rdbms/admin/utlxpls.sql to query the plan.  That
output will show the pstart/pstop

but, in your case, you have but one index partition.  so it wont be very
interesting.  As we discussed right above -- in order to be unique and local, it
would have to be part of the partition key, name is not, hence it will be a
global index and by default that will be a single partition.

ops$tkyte@ORA817DEV> alter table t add constraint t_pk primary key(name);

Table altered.

ops$tkyte@ORA817DEV> create index t_idx on t(idno);

Index created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> delete from plan_table;

3 rows deleted.

ops$tkyte@ORA817DEV> explain plan for
  2  select * from t where name = 'One';

Explained.

ops$tkyte@ORA817DEV> set echo off
@?/rdbms/admin/utlxpls.sql

Plan Table
--------------------------------------------------------------------------------
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |     3 |   30 |      1 |       |       |
|  TABLE ACCESS BY GLOBAL IN|T         |     3 |   30 |      1 | ROWID | ROW L |
|   INDEX RANGE SCAN        |T_PK      |     3 |      |      1 |       |       |
--------------------------------------------------------------------------------

6 rows selected.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> delete from plan_table;

3 rows deleted.

ops$tkyte@ORA817DEV> explain plan for
  2  select * from t where idno = '11';

Explained.

ops$tkyte@ORA817DEV> set echo off
@?/rdbms/admin/utlxpls.sql

Plan Table
--------------------------------------------------------------------------------
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |     1 |   10 |      1 |       |       |
|  TABLE ACCESS BY GLOBAL IN|T         |     1 |   10 |      1 |     2 |     2 |
|   INDEX RANGE SCAN        |T_IDX     |     1 |      |      1 |       |       |
--------------------------------------------------------------------------------

6 rows selected.

see the pstart/pstop columns...

      Rating: 5
partition
      varchar2 columns  December 07, 2003
Reviewer:
      A reader
Hi

We have a few quite big tables (around 50 millions rows) in our order entry
table. We want to partition these tables by the ID, unfortunately these IDs are
varchar2. We have ID ranging from 0 to 650000000. I try to partition and get
errors

ORA-14037: partition bound of partition "xxx" is too high

This is because ordering in varchar2 is not same as number

How can we do this?

If I partition less than '10000000' then all sort of data goes there

data less than 10000000 and also bigger such as 12000000...

Can we do

less than to_number(ID) ?

          Followup:

how do you store "id" -- is zero stored at 000000000000000000 or 0

you chose the wrongenst datatype on the planet to store numbers, unless you made
them "sortable" (using fixed width fields), its going to be a hassle.

      Rating: 3
utlxplan throws
      exception  December 08, 2003
Reviewer:  BK
       from NJ USA
utlxplan script copied from the admin directory. any ideas why it's throwing an
exception?

APPS>explain plan set statement_id = 'XLIKE' for
  2  select ps.party_site_id from hz_party_sites ps, hz_parties p
  3  where p.party_id = ps.PARTY_ID
  4  and p.ORIG_SYSTEM_REFERENCE not like 'PER%';

Explained.

APPS>@c:\bin\utlxpls

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
An uncaught error happened in fetching the records : ORA-00904: "OTHER_TAG": inv
alid identifier

ORA-00904: "OTHER_TAG": invalid identifier

APPS>

          Followup:

you give no version info whatsoever?

but looks like 9ir2 -- so, can you just

select * from table( dbms_xplan.display( 'PLAN_TABLE', 'XLIKE' ) )

      Rating: 2
Still the same
      error  December 09, 2003
Reviewer:  BK
       from NJ USA
What am I missing ?

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.3.0 - 64bit Production
With the Partitioning, Oracle Label Security, OLAP and Oracle Data Mining
options
JServer Release 9.2.0.3.0 - Production

APPS>begin
  2  sa_session.set_access_profile('ORGS','AA_USER');
  3  end;
  4  /

PL/SQL procedure successfully completed.

APPS>explain plan set statement_id = 'XLIKE' for
  2  select count(*),
  3         count( case when orig_system_reference < 'A%' then 1 end )
  4    from hz_parties;

Explained.

APPS>select * from table( dbms_xplan.display( 'PLAN_TABLE', 'XLIKE' ) ) ;

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
An uncaught error happened in fetching the records : ORA-00904: "OTHER_TAG": inv
alid identifier

ORA-00904: "OTHER_TAG": invalid identifier

APPS>

          Followup:

describe your plan_table -- is it the 9i plan table?

      Rating: 3
Plan
      Table  December 09, 2003
Reviewer:  BK
       from NJ USA
Here it is. Thanks

APPS>desc plan_table
 Name                                      Null?    Type
 ---------- -------- ----------------------------
 STATEMENT_ID         VARCHAR2(30)
 TIMESTAMP            DATE
 REMARKS              VARCHAR2(80)
 OPERATION            VARCHAR2(30)
 OPTIONS              VARCHAR2(30)
 OBJECT_NODE          VARCHAR2(128)
 OBJECT_OWNER         VARCHAR2(30)
 OBJECT_NAME          VARCHAR2(30)
 OBJECT_INSTANCE      NUMBER(38)
 OBJECT_TYPE          VARCHAR2(30)
 OPTIMIZER            VARCHAR2(255)
 SEARCH_COLUMNS       NUMBER(38)
 ID                    NUMBER(38)
 PARENT_ID            NUMBER(38)
 POSITION             NUMBER(38)
 OTHER                LONG

APPS>

          Followup:

that would not be the 9i plan_table.

rerun utlxplan.sql to get the right one created, that one is really old.
OTHER_TAG is a column in that table these days.

ops$tkyte@ORA9IR2> desc plan_table
 Name                          Null?    Type
 ----------------------------- -------- --------------------
 STATEMENT_ID                           VARCHAR2(30)
 TIMESTAMP                              DATE
 REMARKS                                VARCHAR2(80)
 OPERATION                              VARCHAR2(30)
 OPTIONS                                VARCHAR2(255)
 OBJECT_NODE                            VARCHAR2(128)
 OBJECT_OWNER                           VARCHAR2(30)
 OBJECT_NAME                            VARCHAR2(30)
 OBJECT_INSTANCE                        NUMBER(38)
 OBJECT_TYPE                            VARCHAR2(30)
 OPTIMIZER                              VARCHAR2(255)
 SEARCH_COLUMNS                         NUMBER
 ID                                     NUMBER(38)
 PARENT_ID                              NUMBER(38)
 POSITION                               NUMBER(38)
 COST                                   NUMBER(38)
 CARDINALITY                            NUMBER(38)
 BYTES                                  NUMBER(38)
 OTHER_TAG                              VARCHAR2(255)
 PARTITION_START                        VARCHAR2(255)
 PARTITION_STOP                         VARCHAR2(255)
 PARTITION_ID                           NUMBER(38)
 OTHER                                  LONG
 DISTRIBUTION                           VARCHAR2(30)
 CPU_COST                               NUMBER(38)
 IO_COST                                NUMBER(38)
 TEMP_SPACE                             NUMBER(38)
 ACCESS_PREDICATES                      VARCHAR2(4000)
 FILTER_PREDICATES                      VARCHAR2(4000)

      Rating: 5
9i Plan Table
      is different and that was the problem  December 10, 2003

Reviewer:  BK  from NJ USA
Great ! Thanks a lot. It works like a charm.

We have a 9i data base, it's not a fresh install but patched up from 8.1.7, some
body must have used an old script to create the plan table. Now it works and I'm
sure I'm going to make use of it a lot.

      Rating: 4
Plan with and
      without binds  December 30, 2003
Reviewer:
      phil  from UK
HI Tom,

I have a date range partitioned table. When the application runs a specific
query against this table, despite the key of the partition being in the query,
the plan indicates a partition range all. This query is being run many times, by
many different users.

SELECT COUNT(*)

FROM

 (SELECT 1 FROM LogicalTransaction Trans, UserAccount UA WHERE UA.ACCOUNT_FK=

  Trans.ACCOUNT_FK AND UA.RTNUSER_FK=:1 AND UA.REVOKED=0 AND

  Trans.ISOCURRENCY_FK IN (:2) AND (Trans.VALUE_DATE_TIME >= :3 AND

  Trans.VALUE_DATE_TIME < :4) AND Trans.TRANSACTIONSTATUSTYPE_FK IN (:5,:6,:7,

  :8,:9,:10,:11,:12) AND rownum < :13) p

Rows     Row Source Operation

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

      1  SORT AGGREGATE

   1650   VIEW

   1650    COUNT STOPKEY

   1650     TABLE ACCESS BY LOCAL INDEX ROWID LOGICALTRANSACTION

  18277      NESTED LOOPS

    451       INDEX RANGE SCAN USERACCOUNT_IX3 (object id 32621)

  17825       PARTITION RANGE ALL PARTITION: 1 6

  17825        INDEX RANGE SCAN LTXN_AC_VDT PARTITION: 1 6 (object id 32997)

however when I hard code values the plan indicates partition elimination

SELECT COUNT(*)
FROM
 (SELECT 1 FROM LogicalTransaction Trans, UserAccount UA WHERE UA.ACCOUNT_FK=
  Trans.ACCOUNT_FK AND UA.RTNUSER_FK=3244 AND UA.REVOKED=0 AND
  Trans.ISOCURRENCY_FK IN (4900) AND (Trans.VALUE_DATE_TIME >=  '09/Dec/2003'
  AND Trans.VALUE_DATE_TIME < '10/Dec/2003'   )
  AND Trans.TRANSACTIONSTATUSTYPE_FK IN (100,200,300,400,500,600,900,1000)
  AND rownum < 5000) p

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.53       0.51          0      33537          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.53       0.51          0      33537          0           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 68

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
   3300   VIEW
   3300    COUNT STOPKEY
   3300     NESTED LOOPS
  11000      PARTITION RANGE ITERATOR PARTITION: KEY KEY
  11000       TABLE ACCESS BY LOCAL INDEX ROWID LOGICALTRANSACTION PARTITION:
KEY KEY
 143459        INDEX RANGE SCAN LTXN_VDT_LOCAL PARTITION: KEY KEY (object id
32712)
   3300      INDEX RANGE SCAN USERACCOUNT_IX3 (object id 32621)

********************************************************************************

Can the database not eliminate partitions when using binds, or am I not looking
at the real plan?

thanks

          Followup:

the plans are totally different -- using totally different driving tables.

the first:

  18277      NESTED LOOPS
    451       INDEX RANGE SCAN USERACCOUNT_IX3 (object id 32621)
  17825       PARTITION RANGE ALL PARTITION: 1 6
  17825        INDEX RANGE SCAN LTXN_AC_VDT PARTITION: 1 6 (object id 32997)

is doing the index scan on useraccount_ix3 first and then doing 6 index probes
into the partitioned index ltxn_ac_vdt.  that index is probably (see, very
little data so I'm guessing on lots of things here) not partitioned by the join
key -- but by date so we inspect all 6 partions and then access the table.  so,
given the access plan it came up with (the best overall general plan for this
generic query where the inputs could be anything), it cannot do the index
partition elimination.

the other plan uses the partitioned table as the driving table and hence does it
very differently.

it is not that binds precluded elimitation, it is that the plan used with binds
didn't have an opportunity to partition prune.

      Rating: 5
Anything I can
      do ?  December 31, 2003
Reviewer:  phil

Thanks Tom,

is there anything I can do in this example to help  pruning - dare I say it
,should I be binding, on a heavliy queried and partitioned table ??

the index LTXN_VDT_LOCAL is VALUE_DATE_TIME, ACCOUNT_FK local  and the index
LTXN_AC_VDT is  ACCOUNT_FK, VALUE_DATE_TIME, TRANSACTIONSTATUSTYPE_FK local.

          Followup:

question for you -- we see the tkprof indicating the runtime performance of the
non-bound query.

how's about for the query with binds (using the same values you did for the
non-bind query) -- the rowsource numbers look pretty good

      Rating:
      3
binds  January 02, 2004
Reviewer:
      Phil  from UK
Hi Tom,

I did some futher investigation and found that if I run the sql statement with
binds through a simple plsql procedure and pass in the values and then compare
the plan with the query with hard coded values they are identical !

The statement plan below ( and the one I was comparing above ) is created from
the sql and values that are passed by the web application. The values passed to
the below query are "supposed" to be the same as I hard coded.....

(I have renamed a table below and appended _P  it is the same as above.)

 SELECT COUNT(*)
 FROM
  (SELECT 1 FROM LogicalTransaction Trans, UserAccount UA WHERE UA.ACCOUNT_FK=
   Trans.ACCOUNT_FK AND UA.RTNUSER_FK=:1 AND UA.REVOKED=0 AND
   Trans.ISOCURRENCY_FK IN (:2) AND (Trans.VALUE_DATE_TIME >= :3 AND
   Trans.VALUE_DATE_TIME < :4) AND Trans.TRANSACTIONSTATUSTYPE_FK IN (:5,:6,:7,
   :8,:9,:10,:11,:12) AND rownum < :13) p

 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.18       2.13       4225      13889          0
1
 ------- ------  -------- ---------- ---------- ---------- ----------
----------
 total        3      2.18       2.14       4225      13889          0
1

 Misses in library cache during parse: 1
 Optimizer goal: CHOOSE
 Parsing user id: 68

 Rows     Row Source Operation
 -------  ---------------------------------------------------
       1  SORT AGGREGATE
    3300   VIEW
    3300    COUNT STOPKEY
    3300     TABLE ACCESS BY LOCAL INDEX ROWID LOGICALTRANSACTION_P
   43453      NESTED LOOPS
     451       INDEX RANGE SCAN USERACCOUNT_IX3 (object id 32621)
   43001       PARTITION RANGE ALL PARTITION: 1 6
   43001        INDEX RANGE SCAN LTXN_AC_VDT PARTITION: 1 6 (object id 33014)

 Elapsed times include waiting on following events:
   Event waited on                             Times   Max. Wait  Total Waited
   ----------------------------------------   Waited  ----------  ------------
   SQL*Net message to client                       6        0.00          0.00
   SQL*Net message from client                     6        0.16          0.17
   db file sequential read                      4225        0.00          0.17
********************************************************************************

The one here compared to the one in my previous post with hard coded values (
which I also ran in plsql with binds ) are clearly different, although I thought
the values were the same.

I have looked at the raw trace file to discover the actual bind values, this is
what I found for this statement :

PARSING IN CURSOR #1 len=336 dep=0 uid=68 oct=3 lid=68 tim=7363261661
hv=1233797181 ad='be7c13f0'
SELECT COUNT(*) FROM (SELECT 1 FROM LogicalTransaction Trans, UserAccount UA
WHERE UA.ACCOUNT_FK=Trans.ACCOUNT_FK AND UA.RTNUSER_FK=:1 AND UA.REVOKED=0 AND
Trans.ISOCURRENCY_FK IN (:2) AND (Trans.VALUE_DATE_TIME >= :3 AND
Trans.VALUE_DATE_TIME < :4) AND Trans.TRANSACTIONSTATUSTYPE_FK IN
(:5,:6,:7,:8,:9,:10,:11,:12) AND rownum < :13) p
END OF STMT
PARSE #1:c=0,e=2244,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=0,tim=7363261651
BINDS #1:
 bind 0: dty=2 mxl=22(22) mal=00 scl=00 pre=00 oacflg=01 oacfl2=0 size=24
offset=0
   bfp=ffffffff7b084fa0 bln=22 avl=03 flg=05
   value=3244
 bind 1: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=01 oacfl2=0 size=4000
offset=0
   bfp=ffffffff7b070480 bln=4000 avl=04 flg=05
   value="4900"
 bind 2: dty=180 mxl=11(11) mal=00 scl=00 pre=00 oacflg=01 oacfl2=0 size=32
offset=0
   bfp=ffffffff7b070448 bln=11 avl=07 flg=05
   value=
Dump of memory from 0xFFFFFFFF7B070448 to 0xFFFFFFFF7B07044F
FFFFFFFF7B070440                   78670C09 01010100          [xg......]
 bind 3: dty=180 mxl=11(11) mal=00 scl=00 pre=00 oacflg=01 oacfl2=0 size=0
offset=16
   bfp=ffffffff7b070458 bln=11 avl=07 flg=01
   value=
Dump of memory from 0xFFFFFFFF7B070458 to 0xFFFFFFFF7B07045F
FFFFFFFF7B070450                   78670C09 183C0100          [xg...<..]
......
......

I noticed 2 things, one it is passing bind 1 as a string and it should be a
number (app issue) also I would expect in bind 2 and bind 3 to see the date
value that is being passed, I do when I run from plsqpl ...

 bind 2: dty=12 mxl=07(07) mal=00 scl=00 pre=00 oacflg=03 oacfl2=1 size=0
offset=48
   bfp=ffffffff7b066680 bln=07 avl=07 flg=01
   value="12/9/2003 0:0:0"

so my question is what is the dump from memory for bind 2 and bind 3 and also
what is type 180 ? Is it this that is not assisting in my partition elimination

thanks
phil

          Followup:

from the data dictionary view user_tab_cols:

                       180, 'TIMESTAMP(' ||c.scale|| ')',

180 is the timestamp datatype (must be a java piece of code there, at least they
are using binds)

The EXPLAIN plan for a query with binds can be very misleading as EP doesn't
understand what datatype are/should be bound to the :bv's.

You need to use to_number, to_date and the like to get the accurate plan (i only
rely on tkprof/sql_trace outputs like this)

      Rating:
      5
  January 02, 2004
Reviewer:  Phil

Many thanks

      Rating:
      5
  January 02, 2004
Reviewer:  phil

sorry .. dumb question

could not find such an entry in the view user_tabs_cols, where in this view are
the datatypes? When I select from here i do not see anything that is related to
180 ?

thanks

          Followup:

it is not an "entry", it is the view definition itself.

ops$tkyte@ORA9IR2> set long 5000
ops$tkyte@ORA9IR2> select text from all_views where view_name = 'USER_TAB_COLS';

TEXT
--------------------------------------------------------------------------------s
elect o.name,
       c.name,
       decode(c.type#, 1, decode(c.charsetform, 2, 'NVARCHAR2', 'VARCHAR2'),
                       2, decode(c.scale, null,
                                 decode(c.precision#, null, 'NUMBER', 'FLOAT'),
                                 'NUMBER'),
                       8, 'LONG',
                       9, decode(c.charsetform, 2, 'NCHAR VARYING', 'VARCHAR'),
                       12, 'DATE', 23, 'RAW', 24, 'LONG RAW',
                       58, nvl2(ac.synobj#, (select o.name from obj$ o
                                where o.obj#=ac.synobj#), ot.name),
                       69, 'ROWID',
                       96, decode(c.charsetform, 2, 'NCHAR', 'CHAR'),
                       105, 'MLSLABEL',
                       106, 'MLSLABEL',
                       111, nvl2(ac.synobj#, (select o.name from obj$ o
                                 where o.obj#=ac.synobj#), ot.name),
                       112, decode(c.charsetform, 2, 'NCLOB', 'CLOB'),
                       113, 'BLOB', 114, 'BFILE', 115, 'CFILE',
                       121, nvl2(ac.synobj#, (select o.name from obj$ o
                                 where o.obj#=ac.synobj#), ot.name),
                       122, nvl2(ac.synobj#, (select o.name from obj$ o
                                 where o.obj#=ac.synobj#), ot.name),
                       123, nvl2(ac.synobj#, (select o.name from obj$ o
                                 where o.obj#=ac.synobj#), ot.name),
                       178, 'TIME(' ||c.scale|| ')',
                       179, 'TIME(' ||c.scale|| ')' || ' WITH TIME ZONE',
                       180, 'TIMESTAMP(' ||c.scale|| ')',
                       181, 'TIMESTAMP(' ||c.scale|| ')' || ' WITH TIME ZONE',
                       231, 'TIMESTAMP(' ||c.scale|| ')' || ' WITH LOCAL TIME
ZONE',
                       182, 'INTERVAL YEAR(' ||c.precision#||') TO MONTH',
                       183, 'INTERVAL DAY(' ||c.precision#||') TO SECOND(' ||
                             c.scale || ')',
                       208, 'UROWID',
                       'UNDEFINED'),

      Rating: 5
partition
      plan  January 09, 2004
Reviewer:  phil

Can you help me understand this plan. I do no understand why Oracle might use 2
indexes from the same table to get the information...

could explain how this plan is working..

LTXN_VDT_ISO_IX3 & LOGICALTRANSACTION are local indexes on the
logicaltransaction table.

Any thoughts on optimising?

SELECT COUNT(*)
   FROM
   (SELECT 1 FROM LogicalTransaction Trans, UserAccount UA WHERE UA.ACCOUNT_FK=
    Trans.ACCOUNT_FK AND UA.RTNUSER_FK= :b15 AND UA.REVOKED=0 AND
    Trans.ISOCURRENCY_FK IN (:b14,:b13,:b12) AND (Trans.VALUE_DATE_TIME >= :b11
AND
    Trans.VALUE_DATE_TIME < :b10 ) AND Trans.TRANSACTIONSTATUSTYPE_FK IN
(:b9,:b8,
    :b7,:b6,:b5,:b4,:b3,:b2) AND rownum < :b1) p

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      2.79       2.72          0      93131          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      2.80       2.73          0      93131          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 68     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
   8996   VIEW
   8996    COUNT STOPKEY
   8996     NESTED LOOPS
  44784      VIEW
  44784       HASH JOIN
 473899        PARTITION RANGE ITERATOR PARTITION: KEY KEY
 473899         INDEX RANGE SCAN LOGICALTRANSACTION_IX3 PARTITION: KEY KEY
(object id 32705)
  71500        PARTITION RANGE ITERATOR PARTITION: KEY KEY
  71500         INLIST ITERATOR
  71500          INDEX RANGE SCAN LTXN_VDT_ISO_IX3 PARTITION: KEY KEY (object id
33081)
   8996      INDEX RANGE SCAN USERACCOUNT_IX3 (object id 32621)

********************************************************************************

          Followup:

without KNOWING what the indexes are -- it is really hard "to say".

one would need to see the tables and the indexes (as SMALL as possible -- no
need for the entire partitioning clauses, tablespaces and so on -- just need to
see columns, partition keys, indexes)

      Rating: 4
as
      requested  January 09, 2004
Reviewer:  phil

the index LTXN_VDT_ISO_IX3 is one I am trying/experimenting with for
performance, I was just suprised and well basically did not fully understand the
reasoning behind the above plan.

I hope this is sufficient.

CREATE TABLE LOGICALTRANSACTION (
  PK                              NUMBER(15)       NOT NULL,
  VALUE_DATE_TIME                 DATE             NOT NULL,
  ACCOUNT_FK                      NUMBER(15)       NOT NULL,
  ISOCURRENCY_FK                  NUMBER(5)        NOT NULL,
  SERVICER_ORG_FK                 NUMBER(15)       NOT NULL,
  TRANSACTIONSTATUSTYPE_FK        NUMBER(4)        NOT NULL,
  TRANSACTIONTYPE_FK              NUMBER(15)       NOT NULL,
  LASTINST_HUB_ARRIVAL_DATE_TIME  DATE             NOT NULL,
  ENTRY_DATE_TIME                 DATE             NOT NULL,
  ORIGINAL_AMOUNT                 NUMBER(28,4)     NOT NULL,
  AMOUNT                          NUMBER(28,4)     NOT NULL,
  PREV_TRANSACTIONSTATUSTYPE_FK   NUMBER(15),
  OWNER_ORG_FK                    NUMBER(15),
  ACCOUNT_OWNER_REF               VARCHAR2(100),
  TRANSACTION_REFERENCE           VARCHAR2(100),
  TRANSACTION_STATUS_DATE_TIME    DATE,
  STATEMENT_FK                    NUMBER(15),
  STATEMENT_ORDER                 NUMBER(8),
  STATEMENT_SEQ                   NUMBER(5),
  IS_UNMATCHABLE                  NUMBER(1),
  BENEFICIARY                     VARCHAR2(160),
  ORIGINATOR                      VARCHAR2(160),
  RELATED_REF                     VARCHAR2(100),
  SERVICING_INSTITUTION_REF       VARCHAR2(100),
  SUPPLEMENTARY_INFO              VARCHAR2(55),
  INFORMATION_TO_ACCOUNT_OWNER    VARCHAR2(420),
  CONSTRAINT LOGICALTRANSACTION_PK PRIMARY KEY (PK,VALUE_DATE_TIME) USING INDEX
TABLESPACE &&indx_ltxn01 STORAGE (PCTINCREASE 0)
)
PARTITION BY RANGE (VALUE_DATE_TIME)
(
   PARTITION logicaltransaction_0311 VALUES LESS THAN
(to_date('01-DEC-2003','dd-mon-yyyy')),
   PARTITION logicaltransaction_0312 VALUES LESS THAN
(to_date('01-JAN-2004','dd-mon-yyyy')),
   PARTITION logicaltransaction_0401 VALUES LESS THAN
(to_date('01-FEB-2004','dd-mon-yyyy')),
   PARTITION logicaltransaction_0402 VALUES LESS THAN
(to_date('01-MAR-2004','dd-mon-yyyy')),
   PARTITION logicaltransaction_0403 VALUES LESS THAN
(to_date('01-APR-2004','dd-mon-yyyy')),
   PARTITION logicaltransaction_0404 VALUES LESS THAN
(to_date('01-MAY-2004','dd-mon-yyyy'))
 )
TABLESPACE &&data_ltxn01 PCTFREE 10 PCTUSED 40 STORAGE (PCTINCREASE 0);

CREATE INDEX  LOGICALTRANSACTION_IX3 ON
 LOGICALTRANSACTION_P(VALUE_DATE_TIME, ACCOUNT_FK, TRANSACTIONSTATUSTYPE_FK)
LOCAL;

CREATE INDEX LTXN_VDT_ISO_IX3 ON
 LOGICALTRANSACTION_P(ISOCURRENCY_FK, VALUE_DATE_TIME, ACCOUNT_FK) LOCAL;

regards
phil

          Followup:

Ok, this plan starts by taking the where clause:

WHERE UA.ACCOUNT_FK= Trans.ACCOUNT_FK
  AND UA.RTNUSER_FK= :b15
  AND UA.REVOKED=0
  AND Trans.ISOCURRENCY_FK IN (:b14,:b13,:b12)
  AND (Trans.VALUE_DATE_TIME >= :b11 AND Trans.VALUE_DATE_TIME < :b10 )
  AND Trans.TRANSACTIONSTATUSTYPE_FK IN (:b9,:b8,:b7,:b6,:b5,:b4,:b3,:b2)
  AND rownum < :b1

and breaking it into (conceptually)

WHERE UA.ACCOUNT_FK= Trans.ACCOUNT_FK
  AND UA.RTNUSER_FK= :b15
  AND UA.REVOKED=0

  AND Trans.ISOCURRENCY_FK IN (:b14,:b13,:b12)
  AND (Trans.VALUE_DATE_TIME >= :b11 AND Trans.VALUE_DATE_TIME < :b10 )

  AND Trans.TRANSACTIONSTATUSTYPE_FK IN (:b9,:b8,:b7,:b6,:b5,:b4,:b3,:b2)
  AND (Trans.VALUE_DATE_TIME >= :b11 AND Trans.VALUE_DATE_TIME < :b10 )

  AND rownum < :b1

Now, the predicate on isocurrency_fk/value_date_time can use LTXN_VDT_ISO_IX3
nicely and the predicate on transactionstatustype_fk/value_date_time can use
logicaltransaction (range scan on the value_date_time and just check the trans
status type fk column as it goes along in that index)

So, that is what it did -- developing two "smallish" result sets.  It then "hash
joined" these two subsets together (that AND's them) and then took this sub
result and nexted loops joined it to UA.

With the rownum contraint in there, I would suggest "/*+ FIRST_ROWS */" be added
to the inline view.  Seems you want first rows first in a query like that.

      Rating:
      5
  January 09, 2004
Reviewer:  phil

thanks for that.

if I drop the index logicaltransaction_ix3 run the query again the plan is as
follows :

SELECT COUNT(*)
   FROM
   (SELECT 1 FROM LogicalTransaction Trans, UserAccount UA WHERE UA.ACCOUNT_FK=
    Trans.ACCOUNT_FK AND UA.RTNUSER_FK= :b15 AND UA.REVOKED=0 AND
    Trans.ISOCURRENCY_FK IN (:b14,:b13,:b12) AND (Trans.VALUE_DATE_TIME >= :b11
AND
    Trans.VALUE_DATE_TIME < :b10 ) AND Trans.TRANSACTIONSTATUSTYPE_FK IN
(:b9,:b8,
    :b7,:b6,:b5,:b4,:b3,:b2) AND rownum < :b1) p

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.86       0.83          0      96108          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.87       0.84          0      96108          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 68     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
   8996   VIEW
   8996    COUNT STOPKEY
   8996     NESTED LOOPS
  44784      PARTITION RANGE ITERATOR PARTITION: KEY KEY
  44784       INLIST ITERATOR
  44784        TABLE ACCESS BY LOCAL INDEX ROWID LOGICALTRANSACTION_P PARTITION:
KEY KEY
  58000         INDEX RANGE SCAN LTXN_VDT_ISO_IX3 PARTITION: KEY KEY (object id
33081)
   8996      INDEX RANGE SCAN USERACCOUNT_IX3 (object id 32621)

this is quite obviously far more efficient than the previous. So why might
oracle not choose this plan ( how long is a pice of string?) in the first place
? Is this just a case of having too many of the "wrong" indexes available to
oracle. I guess i sort of assumed that the CBO would be able to take this into
account. An index strategy and lots of testing/benchmarking is obviously what is
needed

regards
phil

          Followup:

hard to say without an autotrace traceonly explain output (or explain plan PLUS
dbms_xplan.display output)

January 09, 2004 Cefers.br -- Thanks for the question regarding
      "NO_DATA_FOUND in Functions", version 9.2

      You Asked
Tom,

We´ve just migrated from Oracle 9.0.1 to 9.2 and, coincidence or not, I´m facing
a problem I had never faced before.

The NO_DATA_FOUND exception is not being raised from my PL/SQL functions
anymore!!! If I put a exception block to handle the excpetion, it´s trapped and
all the instructions in the block are executed. But if I remove the exception
block, no exception is raised to the caller.

In procedures, this doesn´t happen.

Some examples of this strange behavior follows:

create or replace procedure proc_foo is
  l_value varchar2(1);
begin
  select dummy
  into l_value
  from dual
  where dummy = '-';

  DBMS_OUTPUT.Put_Line(l_value);
end;
/

Procedure created.

create or replace function func_foo return varchar2 is
  l_value varchar2(1);
begin
  select dummy
  into l_value
  from dual
  where dummy = '-';

  return l_value;
end;
/

Function created.

-- This one raises an error
begin
  proc_foo;
end;
/
begin
*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at "AMESP.PROC_FOO", line 5
ORA-06512: at line 2

-- This one does not
select func_foo from dual;

FUNC_FOO
----------------------------------------------

Is there some parameter (or anything else) that controls this?

Thanks.

      and we said...
it has ALWAYS been that way

tkyte@ORA716> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle7 Server Release 7.1.6.2.0 - Production Release
PL/SQL Release 2.1.6.2.0 - Production
CORE Version 2.3.7.1.0 - Production (LSF Alpha)
TNS for SVR4: Version 2.1.6.0.0 - Production
NLSRTL Version 2.3.6.0.0 - Production

tkyte@ORA716> exec proc_foo
begin proc_foo; end;

 *
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at "TKYTE.PROC_FOO", line 4
ORA-06512: at line 1

tkyte@ORA716> select func_foo from dual;

no rows selected

no data found in a function called from sql just says "ok, no more data, please
stop"

this is true for 71, 72, 73, 80, 8ir1, 8ir2, 8ir3, 9ir1, 9ir2....

Review &
      Followup

      Rating: 5
Is this
      behaviour because...  June 10, 2003
Reviewer:
      Kamal Kishore  from New Jersey, USA
Hi Tom,
Is this behaviour because in case of SQL, for ANSI standard, the NO DATA FOUND
is not an error condition (rather it is an indicator of so to say, END-OF-FILE
or EOF). This just tells the system that what you were trying to read is no more
to be found.

In this regard, than it appears that Oracle PL/SQL and SQL behave differently?

SQL, to be ANSI complient, does not treat it as an ERROR whereas PL/SQL (a
copyrighted language), treats it as an Error and jumps to the exception block.

Is this assertion correct? Is this the reason why we get an error with PL/SQL
block but not with SQL SELECT?
Thanks,

          Followup:

no_data_found is not an error - it is an "exceptional condition".   You, the
programmer, decide if something is an error by catching the exceptional
condition and handling it (making it be "not an error") or ignoring it (making
it be an error).

in sql, no data found quite simply means "no data found", stop.

Under the covers, SQL is raising back to the client application "hey buddy --
no_data_found".  The client in this case says "ah hah, no data found means 'end
of data'" and stops.

Under the covers, PLSQL is raising back to the client application "hey --
no_data_found.  The client in this case says "uh-oh, wasn't expecting that from
PLSQL -- sql sure, but not PLSQL.  Lets print out the text that goes with this
exceptional condition and continue on"

You see -- it is all in the way the CLIENT interprets the ORA-xxxxx message.
That message, when raised by SQL, is interpreted by the client as "you are
done".  That message, when raised by PLSQL and not handled by the PLSQL
programmer, is on the other hand interpreted as "a bad thing just happened"

Both PLSQL and SQL actually do the same thing here.
It is the CLIENT that is deciding to do something different.

      Rating: 5
some more
      details required  June 11, 2003
Reviewer:
      Balasubramanian.R  from Chennai, Tamilnadu
Hi Tom,
This is something misleading us. I never faced this. Because i always use proper
exceptions in functions also. In the same function, if i add no_data_found
exception, it throws error. How this is coming ? Also 1 more doubt, as in your
reply,
select func_foo from dual will return no rows selected. But if i executed it
shows 1 row selected. How is this differs
?

also if i call the function in pl/sql block as below, it throws exception.

declare
  l_x varchar2(1) ;
begin
  l_x := func_foo ;
end ;

tx,
Balasubramanian.R

          Followup:

see above -- it IS throwing the exeception, the client is CHOOSING to "ignore
it" as it is an expected condition from a query, but not from PLSQL

      Rating: 5
brilliant as
      usual  June 11, 2003
Reviewer:  Sajid Anwar
       from London
Hi Balasubramanian,

I think TOM's statement clearly states that when you run stored function from
SQL you will never get EXCEPTION. He didnt say that you wont get EXCEPTION when
you call the function in the PL/SQL block. Am I right TOM?

>> no data found in a function called from sql just says
>> "ok, no more data, please stop"

Regards,
Sajid Anwar
London

          Followup:

it is all in how the client interprets the "exception"

      Rating: 5
The reason for
      confusion...  June 11, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Ho Tom,
I think one of the reason for my earlier statement was because this no data
found condition is represented by a different SQLCODE within PL/SQL. The SQLCODE
is NOT -1403 but is rather +100 when this condition is encountered within
PL/SQL. This difference in the value of SQLCODE is what caused me to think that,
yes, there is some difference somewhere, may not be as big, but there seems to
be a difference?
Is this thought process correct?

OPS$KKISHORE @ ORA9203.WORLD> set serveroutput on
OPS$KKISHORE @ ORA9203.WORLD> begin
  2    raise no_data_found ;
  3  exception
  4  when others
  5  then
  6    dbms_output.put_line('Error Code:'||SQLCODE||chr(10)||'Error
Text:'||SQLERRM) ;
  7  end ;
  8  /
Error Code:100
Error Text:ORA-01403: no data found

PL/SQL procedure successfully completed.

OPS$KKISHORE @ ORA9203.WORLD>

          Followup:

it is that the CLIENT of the SQL statement decided "no data found is not worthy
of printing an error message, in fact, we EXPECT to hit no data found, it is
normal and healthy"

      Rating: 5
Oh
      my...  June 11, 2003
Reviewer:  Cefers.br
       from Brasil
Tom,

I swear I´d never noticed that.

Thanks.

      Rating: 5
Never noticed
      this before...  August 20, 2003
Reviewer:
      Jon  from Sydney
Amazing, in 8 years of Oracle, I'd never come across this until a few days ago -
always learning ;-)

2 Questions:
- Where is this documented in the manuals (8.1.7)?  I've been looking for this
but can't find it.
- As you mentioned above, it depends on how the client handles the exception.
When I execute "select func_foo from dual;", is the SQL engine acting as the
client of PL/SQL?

Thx.

          Followup:

o it is not documented cause it is just the way it happens.

when a client recieves the error "no data found", they generally say -- ah hah,
end of data, stop printing.

that is just what is happening here.  no data found is generally not considered
an error but rather an informational message.

It is just unfortunate that SQL queries raise the same no data found normally.

o yes.

      Rating: 1
It is good for
      a single query but not for a loop and Procedure  January 09,
      2004
Reviewer:  Ram  from USA
I am getting No data found error For the following SQL statement
declare
age_no number(5);
begin
for cursor_fields in (select * from People where name like '%R%')
loop
 select age into age_no from PeopleDetails where city = cursor_fields.city
dbms_output.put_line('The city name and age  are');
dbms_output.put_line(cursor_fields.city);
dbms_output.put_line(age_no);
end loop;
exception
when NO_DATA_FOUND then
  dbms_output.put_line('No data available for this record');
  null;
end

My problem is if the Query 'select age .... 'did not find any records then it is
throwing the error. 'ORA-01403: no data found'. I am catching that in exception
but I want to go back into the loop and continue for the next cursor element in
the for loop. And also do I need to modify the 'Select age into age_no.....'
Query . Give some suggestions on this . It is very urgent.

          Followup:

you don't have the begin/end blocks in the right place (you are not
understanding how scoping in plsql works)

but even worse -- you are doing it all wrong -- totally wrong!!!  there should
be NO procedural code here, this is a query!

   select a.city, b.age_no
     from people a, peopleDetails b
    where a.name like '%R%'
      and a.city = b.city(+);

(if you have cities in people that might not exist in peopleDetails) or simply:

   select a.city, b.age_no
     from people a, peopleDetails b
    where a.name like '%R%'
      and a.city = b.city;

if every city in people is represented in peopleDetails.

but -- to do it the wrong way, you would have to code this:

begin
   for x in ( select * from t )
   loop
      BEGIN
         select ... into .... from .... where ....;
         process selected into record
      EXCEPTION
         when no_data_found then
              whatever you want to do when no data selected into
      END;
   end loop;
end;

What is happening in your code is that as soon as you hit "no data found", you
jump OUT of the loop into the exception block.  You need to have the block IN
the loop if you want to continue processing.

Exception handling is all fully documented in the PLSQL manuals but in this
case, you do not even want to consider that -- you just want "an outer join" and
be done with it.  don't write code to join.

databases were BORN to join, it is what they do best

      Rating: 2
I am new to
      Oracle right now. I have written code like what you have said. But My view
      is different  January 09, 2004
Reviewer:
      Ram  from USA
Tom,
I did not ask proper question but it was some helpful to me.
I have written code like what you have said before only. But my intention is to
verify my view. I caught the exception in the inner block only.  I have another
problem here.

select name into l_name from people where city ='xxx';

If the value 'xxx' does not exist in the database then it is throwing the
exception NO_DATA_FOUND. Should we change the query in order to avoid this
exception. BEcause in general sql query like
select name from people where city = 'xxx'; if the record is not found it does
not throw exception . How to solve this problem.

          Followup:

select INTO says "at least one row, at most one row"

Use the select into -- and then:

a) IF you are expecting not to find data for some rows AND this is OK, catch the
no_data_found and set default values for the into variables

b) IF you are NOT expecting "no data found" don't use an exception block at all
-- this is an error, it must be propagated up and out of your code to the caller
-- just like an "out of space" error on an insert would be.

January 09, 2004 Doug -- Thanks for the question regarding
      "Shutdown abort prior to an offline backup.", version All

      You Asked
An old chestnut that I've never had answered adequately. Oracle typically
suggest a clean shutdown (normal or immediate) prior to an offline backup to
guarantee that it's 'clean' or 'good'.

Given the fact that Oracle's perfectly capable of performing instance recovery
in the event of a server or software failure, what's the difference between
restoring a backup that was taken after a shutdown abort and opening it and the
server power failing during the middle of the day and then restarting the
database?

To summarise - I have confidence in oracle's ability to restore a backup and
recover from a shutdown abort using the contents of online redo log files, why
do Oracle say I shouldn't do this?

tia,

Doug

      and we said...
Because you should never backup your online redo logs and if you do a shutdown
abort in NOarchivelog mode you NEED the online redo logs.

In archivelog mode, it doesn't matter (but in archivelog mode, you shouldn't be
shutting down to take a backup at all!  I would not ever recommend shutting down
to take a backup)

In noarchivelog mode, if you are backing up your online redo (bad habit though,
the only thing that this could do is cause confusion later when you switch this
database into archivelog mode -- when backing up ONLINE redo logs can be deadly)
you can backup an aborted instance.  But.... If you care so little about the
data that you are running in noarchivelog mode (a mode that is assured to have
data loss someday) maybe you don't even need to backup ;)   (no media recovery,
not suitable for the real world!)

Review &
      Followup

      Rating: 3
Mmmm
      ....  November 05, 2001
Reviewer:  Doug
      Burns  from Edinburgh, UK
... I hear what you're saying and don't really disagree with any of it on a
technical level. I still think that Oracle documentation spends a lot of time
telling people 'Make sure you have a good offline backup prior to this
operation' (or similar - I don't have a photographic memory) without really
having a logical reason to insist on the shutdown normal or immediate.

I still think that backing up online redo logs might be a reasonable idea - what
about a situation when you want to restore some tapes and have an operator
restart a database without it needing any kind of recovery? I know you could
script it, but ...

Oh, and I've worked at a few sites that would *prefer* to go back to last
night's position, with data loss, because it's a more logical position for their
business (e.g. tieing it in to external data feeds that can be re-submitted from
a known point in time). I know what you're saying, but I think there might be
real world variations.

Thanks for your comments, though. Great book, too ...

          Followup:

The only time -- repeat the only time -- you can backup the online redo logs is
in NOARCHIVELOG mode, it is fatal in archivelog mode, the only thing that could
happen in archivelog mode is the operator restores these old logs -- wiping out
the REAL current online redo logs and making it so you lose data!

The only thing the operator would need to do is open the database with resetlogs
-- the logs would come back by themselves.

      Rating: 5
redo
      logs  November 05, 2001
Reviewer:  Naveed Amin
      Khan  from West Des Moines Iowa
I agree with what you said about redo logs 100%. I think it only makes sense to
treat redo logs for what their real purpose in life is.

      Rating: 5
What Doug
      really asked...  November 05, 2001
Reviewer:
      Andre Whittick Nasser  from Rio de Janeiro, Brazil
What I believe Doug is really asking is what is the difference between shutting
down abort or having an instance crash ("dirty") and shutting down
normal/immediate/transactional ("clean") before taking an off-line, or cold,
backup.

The answer is that when you shut down your database in a dirty way, the datafile
headers are not syncronized and during startup Oracle will complain about this
"inconsistency". Redo log files will be applied to bring your database uo to a
consistent state.  Remember that only the current redo log file will be used,
since a log switch ocurred before begining to fill it up, generating a
checkpoint (DBWR flushing to the datafiles).

If you did a clean backup, your datafiles are ok, and the database will come up
just fine.

If you backup your database with its redo log files, don't restore them, since
information in the redo files of the crashed database is essential to restoring
the system. Archive logs and then the last redo file will be applied to make
datafiles consistent.

      Rating: 4
Backing up
      online redo...  November 05, 2001
Reviewer:  jan
      van mourik  from Houston, Texas
Some shops just insist on doing a full system cold backup every week (or month
or whatever). And they demand the database(s) to be shut down because they will
back up everything, including the Oracle directories. In that case, if you were
to restore *everything*, would a shutdown abort be a problem? I would think not
for the reasons stated by Doug above.
That said, for the Oracle specific backups, would you always skip the online
redo log files? This to avoid accidentaly restoring them?

jan

          Followup:

If you are in ARCHIVELOG MODE, backing up online redo log files is fatal, a
mistake, don't do it.

If you are in ARCHIVELOG MODE, backup after a shutdown <anything you want>.

If you are in NOARCHIVELOG MODE, if you backup after a shutdown abort, backup
the redo log files (but this is strongly discouraged as it can lead to
confusion).  Otherwise do not backup the redo log files.

If you have the opportunity to shutdown abort, you have the same opportunity to
startup and shutdown clean.  Just do it, its so easy, why quibble about it?
Then the rule becomes simply "never backup online redo log".  There, done, very
easy.

(i would always skip backing them up just in case I accidently was to restore
them, destroying the REAL online redo logs with important stuff).

      Rating: 3
A
      simplification  November 05, 2001
Reviewer:
      Doug  from Edinburgh, UK
First time I've used the system, apologies if I'm not responding through the
right method.

Anyway, just to try to wrap this up a bit (because I *know* what I was asking
;-) ), I'm fully aware of all the stuff that you're saying about backups Tom and
it's the way I do it too, but it's not *really* what I was asking. Maybe it's
too abstract but, is there any difference in the 'validity' of an full offline
backup taken after a shutdown abort or an immediate if the online logs are part
of the backup?

As for why you would want to do this - let's just say that in my stupidity I
*do* want to do offline backups (and let's face it, lots of people used to, even
when the architecture was fundamentally the same as it is now). The fastest way
of guaranteeing the database comes down is to do a shutdown abort and backup the
redo logs. Should I ever need to do it, then I pay the rollback price on the
recovery, not at the start of the backup job. I know you wouldn't recommend it,
but I'm sure you can still say whether it works or not ...

But, honestly, I know all the stuff you were saying, I was just trying to say
that a shutdown abort shouldn't invalidate a full offline backup - correct?

(This has turned into *just* the sort of pointless, academic discussion I try to
avoid!)

          Followup:

I'll make it as short and simple as I can.  There are two cases:

ARCHIVELOG MODE:
   go ahead and backup the shutdown aborted instance.  DO NOT BACKUP ONLINE REDO

   logs.  That is a HUGE mistake in archivelog mode.

NOARCHIVELOG MODE:
   go ahead and backup the shutdown aborted instance IF AND ONLY IF you also
   backup the online redo logs.  You'll NEED them to perform instance recovery
   if you ever restore these files.

short and sweet.  Thats it.  Yes, you can backup the shutdown aborted instance.

      Rating:
      4
Reader  November 05, 2001
Reviewer:
      Reader
For one thing, I think, shutdown abort may lose data
Ex: TX commits. LGWR writing to redo log. You abort
the process. Data is lost. Will there be redo log
corruption ? Possibly?

When Oracle recovers itself from failure, the
Buffer Cache is in tact. If we do a shutdown
abort, and do a offline backup, the Buffer cache
is refreshed and will it be still safe to
do shutdown abort, in this case.

          Followup:

No, its just like an instance failure.

Say you pull the plug.  If we could not recover that from, we cannot recover
from a shutdown abort.  We are built to be tolerant of this type of failure.

When Oracle recovers from failure - -the buffer cache does not exist (we had a
failure, we went away).

      Rating: 4
Redo log files
      ....  November 06, 2001
Reviewer:  Andre
      Whittick Nasser  from Rio de Janeiro, Brazil
Dear colleagues...

As Tom says, NO MUSS, NO FUSS...

You may even back up the redo log files but, look at the following recovery
scenarios:

1) In NOARCHIVELOG MODE -

  a) Instance failure (shutdown abort, power outage) - Oracle may, and possibly
will, automatically apply the CURRENT redo log file (only!). It has all the
consistency information (the differences between the commited transactions and
what is in the datafiles), because in the last log switch the data files had
been updated because of DBWR's flushing. Oracle checks to see if datafiles are
consistent with redo files by "looking" at the datafile headers and comparing
their LSN (log sequence number) against that of the redo files. LSN's are
incremented at every log switch. Just startup the instance !

   b) Media failure (curruption, deletion of datafiles) - If are you very lucky
(real darn lucky!) the information in your redo files were not overwritten
(there were not enough log switches to make a full "wrap" around the groups)
since your last backup was taken . Restore your backup files. In this case, the
"differences" mentioned above are in the redo files and will be applied. Notice
the similarity between this and archive log mode, only there are no archives,
you can only recover from a very short time in the past, that is, since when
there is information generated after your backup in the redo logs.

2 - In ARCHIVELOG MODE -

   a) Instance failure - Same as 1.a) Archive log files not necessary, only your
CURRENT redo log.

   b) Media failure - The "differences" between your backup and the current redo
logs were saved as archive log files. You must have all archives since your last
backup. Just apply them.

I EMPHISIZE: THERE IS PROBLEM BACKING UP YOUR REDO LOG FILES (OK, YOU DON'T NEED
TO). JUST DO NOT OVERWRITE THE ONES OF THE CRASHED DATABASE BECAUSE THEY CONTAIN
DATA FOR RECOVERY, AS EXPLAINED ABOVE !

Note: Oracle has this "contract" with you: all commited changes are in the redo
log files. Thet may not be in the data files yet, because of the DBWR's
"asynchronous nature".

      Rating: 4
A case to back
      up the online redo logs in archive log mode  June 27, 2002

Reviewer:  Doug  from Danbury, CT USA
Hey Tom - there is a case where you might want the online redo logs backed up in
archive log mode.  You and I actually discussed it once in a thread that didn't
make the public cut. If I am to take a hot backup and use it to do a restore on
another box (like a QA environment) and RENAME the instance and potentiallly
even roll it forward with a "recover database using backup controlfile until
cancel", I have to resetlogs when re-creating the controlfile from trace such as
CREATE CONTROLFILE SET DATABASE "NEWNAME" RESETLOGS NOARCHIVELOG -   The online
logs MUST be there even though since they were taken during a hot backup they
may not be consistent.  But.. If I take any old logfiles Oracle will complain
that the logs are from a prior point in time.   I can't RENAME a database
without a resetlogs, and I can't resetlogs unless there are logs there.  I do
this hot restore all the time - you actually helped me to figure out how to do
it.

          Followup:

That is not really backing up -- that is restoration.  The online logs would be
there (if you could get them) in a restore to the same machine (if not, you
would have lost some work).

I don't consider that "backup" at all personally.  It is a special case of a
restore.  The case would be "we have to restore to another machine due to total
loss of production machine.  We have access to the disks so we can get the
online redo logs for the damaged database and fully recover".

That your ultimate goal was to rename the database is just a quirk of your
environment.  We could have just restored, recovered, opened, closed and then
renamed as well (the open resetlogs would have given us logfiles the database
would "like")

      Rating: 3
Beating a dead
      horse  August 06, 2002
Reviewer:  Tom Dyess
       from Jacksonville, Florida
This is a very good thread, but I have a scenerio I need input on. In my cold
backup scripts, I do the following --

1. alter system checkpoint
2. kill all sessions (select statement spooled for all users other than current
and oracle processes)
3. shutdown abort
4. startup restrict
5. shutdown immediate
6. backup datafiles

There is a shutdown abort, but since I forced a checkpoint, killed all sessions
and startup restrict after the shutdown abort, is this absolutely safe, or are
there still issues with the shutdown abort?

          Followup:

I would just do

#3, 4, 5, 6 and skip #1 and 2.

If you want a "consistent" backup, you need to do #4 and #5.

Actually -- I would not do a cold backup under any circumstances but hey --
thats me.

Since you mention "script" you are probably doing this un-attended -- baaaaadddd
idea.  read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2876092892845

      Rating: 5
SHUTDOWN ABORT
      and loss of cached sequence numbers  August 20, 2002

Reviewer:  Jarda Smucr  from Czech Republic

Hi:

Regarding to possible losses after a shutdown abort -
could you pls give your comments on the following?

http://ixora.com.au/scripts/library.htm
Oracle Advanced Performance Tuning Scripts
Library Cache Scripts

unload_sequences.sql

This script unloads all cached sequence numbers from the
library cache, by temporarily marking them as NOCACHE.
This script is intended to be used prior to shutdown in
single-instance Oracle, to prevent the loss of cached
sequence numbers, should a SHUTDOWN ABORT become
necessary.

-----------------------------------------------------------
--
-- Script:      unload_sequences.sql
-- Purpose:     to unload cached sequence numbers from the
--              shared pool
--
-- Copyright:   (c) Ixora Pty Ltd
-- Author:      Steve Adams
--
-----------------------------------------------------------
@save_sqlplus_settings

set pagesize 0
set termout off
set echo off

spool unload_sequences.tmp
prompt set echo on
select
  'alter sequence ' || sequence_owner || '.' ||
                       sequence_name || ' nocache;'
from
  sys.dba_sequences
where
  cache_size > 0
/
select
  'alter sequence ' || sequence_owner || '.' ||
                       sequence_name || ' cache ' ||
  cache_size || ';'
from
  sys.dba_sequences
where
  cache_size > 0
/
spool off

@restore_sqlplus_settings
@unload_sequences.tmp

set termout off
host rm -f unload_sequences.tmp -- for Unix
host del unload_sequences.tmp   -- for others

@restore_sqlplus_settings
--

Many thanks for your time.

Jarda

          Followup:

My 2cents on it:

There are three things you can count on:

a) death
b) taxes
c) your sequences will have gaps

Now, consider when you do a shutdown abort (or when it is done for you by
something).  Do you really have time to do this "unloading"?  (no).  If you did
-- what is to stop another session from reloading them? (nothing).  Will to
assure you that your sequences have no gaps? (no)

Me -- I really *do not care at all* if one day my sequence gives me 55 and the
next restart gives me 100 or 1000 or 10000000 or 1000000000000.  A sequence is
exactly:

a) a unique number
b) with gaps
c) to be used as a surrogate key
d) with gaps
e) did I mention -- it'll have gaps?

      Rating: 5
Could I apply
      archive log files on an offline backup?  January 07, 2003

Reviewer:  Michael  from HK
If I get an offline backup only and I get all archive log files after the
offline backupl. Can I restore the database back to the current status?

          Followup:

if and only if the database was in archivelog mode.

the "type" of backup is not meaningful here - only the state of the database --
archivelog mode = point in time recovery, including complete recovery is
possible.

noarchivelog mode = what you backed up is what you get.  nothing more, nothing
less.

      Rating: 5
for Michael
      from HK  January 07, 2003
Reviewer:
      A reader
don't open the database
mount it and then perform recovery

      Rating: 5
Avoid shutdown
      abort and get some peace of mind with 2 more lines of
      code.  January 07, 2003
Reviewer:  Moorthy
      Rekapalli  from Atlanta GA USA
Hi,

If you have to do an offline backup for whatever reason, please do the
following.  You will get some peace of mind.

${ORACLE_HOME}/bin/sqlplus <<EOF
connect / as sysdba
shutdown abort
startup open restrict
shutdown normal
EOF

Now, kickoff your offline_backup_routine.

In my view, the very reason somebody does a "shutdown abort" is to bring down an
active instance instantaneously.  After that, why not open the database in
restrict mode and then shut it down normally.

Opening the database in restrict mode will do two things:

1) Oracle will do crash recovery (if needed) during startup
2) restrict option will prevent a normal end user to get in during that brief
period of time - before doing shutdown normal again.

Also, before invoking this sequence, I usually shutdown listener for that
instance.

That way, to prevent shutdown normal to happen, somebody should have access to
DB server (to make an ipc connection), restricted session privilege and should
get in during that brief period of time.  Other than DBAs, nobody should have
restricted session privilege.  So, we are safe there also.

For these reasons, I always suggest to my customers to do this before they take
any offline backup so that the database is always shutdown with normal option.
When it comes to corporate data, I would prefer peace of mind than tinkering
with the fine technical details.

Thanks,
Moorthy Rekapalli.

      Rating: 5
Re: Could I
      apply archive log files on an offline backup?  January 08,
      2003
Reviewer:  Michael  from HK
After I "startup mount" the database, I typed "recover database until cancel;".

It just prompted "meadium recovered", no any archive log files required and
nothing different from the offline backup.
What and how should I do?

      Rating: 5
for Michael
      from HK  January 08, 2003
Reviewer:
      A reader
sqlplus "/ as sysdba"
startup mount
recover database using backup controlfile until cancel;

      Rating: 5
For A
      reader  January 10, 2003
Reviewer:  Michael
       from HK
How about I don't have a backup control file?

          Followup:

how about you create one then via the create control file statement.

      Rating: 5
Re:
      followup  January 14, 2003
Reviewer:
      Michael  from HK
I have made an backup control file to recover,
unluckliy, it failed and core dump.

          Followup:

what failed, what core dumped.

      Rating: 4
Table
      monitoring requires clean shutdown  March 06, 2003

Reviewer:  Neil  from UK
Another reason for not shutting down with abort is if
you are using monitoring on tables (and indexes for 9i).
Using shutdown abort, the mon_mods$ table doesn't get updated,
and potentially, you could lose 3 hours worth of
changes (15 minutes in 9i), which could affect your statistics
if using gather stale option.

      Rating: 3
checking
      Restore  August 06, 2003
Reviewer:
      A reader
Hi Tom,
Is there any way to judge from the alert files that restore has been performed,a
restore from cold backup only, not applying archive logs, like the a cold back
of day before was taken and put in the location of the current datafiles and the
database was opened or mounted.If not the alert file does any other file store
this information.
we do come to know if the database has been shutdown and started but can we come
to know if the database is taken a few days behind without quering the data, I
know its possible to query the data and tell that but can we use the alert file,
as at times the alert file is the only old information,resource available.

Thanks

          Followup:

you should be able to look at the log sequences

...
Wed Aug  6 08:09:17 2003
Thread 1 opened at log sequence 9865
  Current log# 7 seq# 9865 mem# 0:
/usr/oracle/ora920/OraHome1/oradata/ora920/redo7.log
Successful open of redo thread 1.
Wed Aug  6 08:09:17 2003
.......

if they were going up and then went back down after a startup, that would be an
indication.

      Rating: 5
Offline back
      up  January 09, 2004
Reviewer:  junior
       from VA, USA
Tom:

I used to do a off line back up in Oracle8/Windows NT by shutting down database
and then made copy of those datafiles. However, by accidently, in Oracle
9i/Windows XP environment, I copied all those databfiles without shutting down
the database. I restored those files and it works fine. The database is able to
be opened. Why? Is it because Windows XP or Oracle 9i version or others? I
believe database will not be opened in Oracle 8/NT if I did not shut down
database first then make a copy of files.

The database is in Nonarchive Mode. Thanks.

          Followup:

because you "got very very lucky", "very very lucky"

don't ever even think about relying on that.

      Rating: 5
Wow....that is
      lucky....  January 10, 2004
Reviewer:  Mark J.
      Bobak  from Belleville, MI
I didn't think that was even possible......

January 09, 2004 Sergey -- Thanks for the question regarding
      "Using of UNUSABLE indexes", version 8.1.7.4

      You Asked
Hi, Tom!

I have unique index created as create unique index... instead of as consequence
constraint unique.
Then I do it UNUSABLE,

I carry out
alter session set skip_unusable_indexes=true

Also I try to insert a line into the table, I receive
ORA-01502: index 'IDX_OBJ' or partition of such index is in unusable state

If to do the same actions, on a usual index the line is inserted without
problems.

The parameter skip_unusable_indexes is not distributed on unique index? What
else there are restrictions?
How it is possible to disconnect unique index (created as create unique
index...) not deleting it?

For example:

/* Oracle: Release 8.1.7.4 */

create table t as
select object_id from all_objects where rownum < 9
/
-- attempt 1 ****
create unique index idx_obj on t (object_id)
/
alter index idx_obj unusable
/
insert into t values (-1)
/
ORA-01502: index 'IDX_OBJ' or partition of such index is in unusable state

alter session set skip_unusable_indexes=true
/
insert into t values (-1)
/
ORA-01502: index 'IDX_OBJ' or partition of such index is in unusable state
-- end attempt 1 ****
drop index idx_obj
/
alter session set skip_unusable_indexes=false
/
-- attempt 2 ****
create index idx_obj on t (object_id)
/
alter index idx_obj unusable
/
insert into t values (-1)
/
ORA-01502: index 'IDX_OBJ' or partition of such index is in unusable state

alter session set skip_unusable_indexes=true
/
insert into t values (-1)
/
1 row created.
-- end attempt 2 ****

Regards
Sergey from Kharkov, Ukraine

      and we said...
http://download-west.oracle.com/docs/cd/B10501_01/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>

you'll either have to have it

o usuable

or

o drop and recreate it.
Review & Followup

      Rating:
      3
Sergey  March 11, 2003
Reviewer:
      Sergey  from Ukraine
Thank you very much

      Rating: 5
Incorrect
      results?  June 29, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Hi Tom,
In your book expert one-on-one Oracle page 651, you said:
<quote>
this works in Oracle 8.1.5 and later versions; prior to that, SELECT statement
wold still try to use the UNUSABLE index
</quote>

Does this mean that prior to 8.1.5, we had a chance of not getting an error and
the query will generate incorrect results, without our finding out?
Thanks,

          Followup:

no, it was that skip_unusable_indexes would not skip the indexes, the statement
would fail with an error.

      Rating: 4
Lingering
      questions...  January 09, 2004
Reviewer:
      Craig  from NW Arkansas
How would you recommend we proceed in a data warehouse environment, where we
have big nightly loads?  We have some unique indexes, which I just learned can't
be made to be unusable. It seems we will havee to drop & recreate them.

So once the indexes are dropped, how would you suggest we recreate them?

          Followup:

you perhaps should consider using non-unique indexes

unique constraints and primary keys may both make use of non-unique indexes, no
problems.

otherwise, you are "on your own" to recreate them using "create index"

      Rating: 5
Using
      non-unique index for PK  January 09, 2004

Reviewer:  Arun Gupta  from Harrisburg, PA USA

Tom,
From a best practices standpoint, would you recommend using non-unique indexes
to enforce PK constraint? I asked this question long time ago on Metalink and
someone very emphatically replied that this should be avoided to minimize
confusion.
Thanks

          Followup:

there cannot be any confusion.

you see -- a primary key constraint is a primary key constraint.

The existence (or lack thereof) of an index is "not even a teeny bit relevant".

The constraint is what is relevant.
An index is just a data structure.

Deferrable constraints (especially in a warehouse) are pretty nice, very
flexible and they will generate non-unique indexes for primary keys (if an index
does not already exist)

A primary key constraint might not even create a new index -- it can use any
existing index that has the primary key columns on the leading edge.

      Rating:
      4
  January 09, 2004
Reviewer:
      Pauline  from NY,U.S.A.
Tom,
Recently I find many links you gave from your answer don't provide exact pages
from the link. For example, when I click
the link you provide for the answer of regarding the question 'Using of UNUSABLE
indexes,

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96540/statements_22a.htm#2182515
I get the page which is

http://otn.oracle.com/documentation/index.html
Is any tricky thing to get the link?

Thanks.

          Followup:

otn/technet moved all of the documents one day.

But basically -- the part number is in there "a96540"...

so,

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96540/toc.htm
get's us to the document now (see the part number near the end) and most times

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96540/statements_22a.htm#2182515
gluing the trailing end on will work.  but that is the corrected link.

      Rating:
      5
  January 09, 2004
Reviewer:
      A reader
Pauline,Tom had a note on his front page which said OTN has moved all the links
and i right now i cannot do anything about that, didn't you read that.

          Followup:

(sorry - i took that off, it had been up for a long time already...)

      Rating:
      4
  January 09, 2004
Reviewer:
      A reader
A Reader,
Thanks very much for telling me about it. I didn't know/read it. Can I still see
this front page now?

Thanks.

      Rating:
      4
  January 09, 2004
Reviewer:
      A reader
A Reader,
Thanks very much for telling me about it. I didn't know/read it. Can I still see
this front page now?

Thanks.

      Rating:
      4
  January 09, 2004
Reviewer:  A
      Reader  from NJ, U.S.A.
Tom,
Could you provide how to create unique constraints and primary keys and make
them use of non-unique indexes?

When I add PK to the table, Oracle will automatically create
unique indexe for the PK. If I want to create a non-unique index for this PK,
Oracle will say
ORA-01408: such column list already indexed

Also I could not drop that unique index unless I do drop
that PK constraint. Otherwise will get:

ORA-02429: cannot drop index used for enforcement of unique/primary key

Thanks.

          Followup:

create the constraint "deferrable" and it'll use non-unique indexes.

index the columns and THEN add the constraints and the constraint will "kidnap"
the index (non-unique index)

January 10, 2004 Robert -- Thanks for the question regarding "SQL
      Tuning Strategy and Goal", version 8.1.7

      You Asked
Hi Tom,

I have searched your site but have not found my specific answer.

I am using TKPROF to test the performance of different partitioning
configurations on a particular table. The server/database on which I am testing
has other activity going on besides my testing.

Here is my testing strategy....
I have 3 identical test tables, the only difference being they are configured
differently respecting partitioning (no partition, range partition, composite
partition)
I have a pl/sql script with various cursors and 'selects' that access each of
these tables in various ways, with all tables getting accessed the same way and
the same number of times (e.g.: index scans with no table access, index scans
with table access, ranges within a single partition, etc.).
I repeat the entire battery of tests a few times to smooth out any anomalies.
Then I look at the TKPROF output and see which tables performed better for the
various tests.

Here are my questions...
1) Is this a good approach for testing in a busy environment (i.e. multiple
tests), where a single test could be skued due to someone elses activity ?
2) Assuming you are looking for pure speed, would you go *strictly* by TKPROF
clock time... or would you look at number of block reads(current + consistent)
3) Is it true, that all things being equal, the number of blocks read will be a
*direct* relationship to the actual speed of the query.
4) If question #3 is TRUE... then if pure speed is our goal, why cannot we look
ONLY at the number of block reads and *forget* about clock time, since clock
time could vary depending on load on the system?

Thanks,

Robert.

      and we said...
1) sure, but you might ignore ELAPSED in most cases and concentrate on CPU and
Logical IO's

2) cpu and logical io's -- the more you get those down, the better.

3) logically read -- yes.  the more logical io's, the more latching (latching
being a serialization device....)

4) see #1 ;)  cpu time bears on it, logical io's bear on it.

Publishing this one as I gotta a feeling others will chime in for some reason ;)
Review &
      Followup

      Rating: 4
Thanks... just
      to clarify, please  October 17, 2002
Reviewer:
      Robert  from MEMPHIS, USA
Tom,

3 clarifications, please.

You emphasize *logical* I/O's.
1) Can you please explain the relationship between the TKPROF stats: 'disk' and
('query' and 'current'). e.g.: do all 'disk' reads show up as 'query' or
'current' e.g. There can *never* be more *disk* than (current+query)... correct?
2) If the above is true, then we only need to be concerned logical I/O's, since
the number of disk reads can vary from time to time depending on what happens to
be in the buffer cache at the time, but the logical I/O's will remain the same
regardless of how many disk reads are required ?
3) CPU vs. Wall -- Tom, is it not true that sometimes you can have higher CPU
with less WALL time or lower CPU with more WALL time? Meaning... aren't there
occasions where tuning to CPU could mean consistenlty longer WALL times? (e.g.
sometimes *not* a direct relationship with CPU and WALL). If this is so, then
that would mean we would at least have to include WALL time in our tuning
equation (?).

Thanks,

Robert.

          Followup:

1) disk = physical IOs.

query+current = logical IOs.  We may have had to of done a PHYSICAL io to do a
logical IO.

There can be more disk then current+query believe it -- or not.

select *
from
 t order by 1, 2, 3, 4, 5, 6, 7, 8

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     1954      0.36       0.40        806        404          4       29285
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1956      0.36       0.40        806        404          4       29285

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 69

Rows     Row Source Operation
-------  ---------------------------------------------------
  29285  SORT ORDER BY
  29285   TABLE ACCESS FULL T

(my script, 8k blocksize database):

drop table t;
create table t as select * from all_objects;

alter session set sort_area_size = 1024;
alter session set sql_trace=true;
set autotrace traceonly;
select * from t order by 1, 2, 3, 4, 5, 6, 7, 8;

2) logical io's = FAIRLY CONSTANT!!!!  FAIRLY CONSTANT.  physical IO = psuedo
random number depending on load.

Consider this example:

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     1954      0.29       0.28        400       2334          0       29285
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1956      0.29       0.28        400       2334          0       29285

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 69

Rows     Row Source Operation
-------  ---------------------------------------------------
  29285  TABLE ACCESS FULL T

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     1954      0.48       0.44          0      32019          0       29285
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1956      0.49       0.44          0      32019          0       29285

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 69

Rows     Row Source Operation
-------  ---------------------------------------------------
  29285  TABLE ACCESS FULL T

Now, the script I ran was:

drop table t;
create table t as select * from all_objects;

alter session set sql_trace=true;
set autotrace traceonly;
select * from t t1;
pause
select * from t t2;

I'll let you GUESS as to what happened during the "pause" -- what do you think I
did when we paused to make that happen?

3) CPU higher then wall clock time, hhmmm, not really - not for anything that
runs in a measurable amount of time.  If you get 100% of the CPU, you will equal
wall clock.  Wall clock should always be greater then or equal to CPU time.

So, really you should never have a CPU time higher then WALL clock (it'll happen
in a report but only because of the granularity of the clock -- for anything
"measurable", it'll not happen).

Now, what you might mean is

"I've seen cases where:

           CPU         ELAPSED
query1       5             100
query2      10              15

so, tuning CPU isn't good necessarily"

But that is only 1/2 of the picture.  That shows there was some massive WAITING
going on, fleshing that out, you probably saw:

           CPU         ELAPSED     DISK       QUERY
query1       5             100     1000       50000
query2      10              15        5         500

meaning, query1 used lots less CPU but was disk intensive, that introduced lots
of WAITS causing elapsed to go up.

query2 used a function or something that was CPU intensive but look at the disk
(physical and logical) -- it went wwaaayyy down.

That is why I said look at CPU and LOGICAL -- you are working on them both --
not either|or, BOTH.

      Rating: 4
Did you pin the
      table?  October 17, 2002
Reviewer:  Richard
      Quintin  from VA
During the pause, from another session:

ALTER TABLE t STORAGE (BUFFER_POOL KEEP);

SELECT * FROM t;

          Followup:

wrong

(how that "pins" the table I don't know.  But how "pinning" a table would up the
consistent gets... don't know that either)

HINT:  what does CONSISTENT GETS mean.  what do we do to get a CONSISTENT GET?

      Rating: 4
Answer to your
      question!  October 17, 2002
Reviewer:
      Robert  from Memphis, USA
Tom,

I haven't been reading your site all this time for nothing!

The answer to your question is that during the 'pause', you did a 'delete from
t', then did a 'rollback' (maybe a couple of times)... correct?!

Also... you were exactly correct as to my meaning on CPU vs. WALL time, Thanks
for interpreting that.

Now, am I correct to understand, based on all the above, that this would be an
accurate tuning strategy...

Run several tests (e.g. several versions of a query), multiple times, get the
average CPU and LOGICAL I/O's for each query version and compare those values
among each other, looking for the lowest CPU and LOGICAL I/O. Also, for tuning
purposes you can ignore the disk reads...(and I think I understand from you that
for pure tuning, we can ignore elapsed time as well.. although I'm not sure if I
have enough faith in the other stats to totally forget elaplsed time (ha ha) ;)
).

Thanks!

Robert.

          Followup:

I did not do a

delete from T; rollback;

close, but no cigar.... if you do the rollback -- it won't change the consistent
gets.

Now, if you just did a DELETE..... and no commit.... then went back and
"unpaused"  what about that....

      Rating:
      4
John  October 17, 2002
Reviewer:
      John  from LA, CA USA
You do a COMMIT;
to end the transaction and allow the data to be available in the buffer cache,
hence the subsequent select will get all the consistent get reads from the
cache, incuring only logical (query) I/O's and no physical (disk) I/O's

          Followup:

No, you do a modification to T *without a commit*.

If you commit, we don't need to do a consistent read.

What I actually did was:

update t set object_id = object_id;

in the other window, came back and just hit enter.

      Rating: 4
Delete with no
      commit or rollback...  October 17, 2002
Reviewer:
      Robert  from Memphis, USA
Tom,

After your hint, I was able to reproduce your results on your 't1 pause t2' test
using 'delete from t' with no commit or rollback... only my numbers were not as
dramatic... my t2 query had only 2 times the consistent gets as the first query.
I used 'autotrace'.

Question: How about my final understanding of the testing scenario... Am I on
target?

Thanks,

Robert.

          Followup:

yes you are.

      Rating: 5
Please
      explain  October 17, 2002
Reviewer:
      A reader
Tom,

I have been going thru this thread. Can you please explain me what the example
you gave us explains? sorry could not get to your speed?

2. What is CPU vs. Wall ? did not understand that clearly?

          Followup:

The exmaples show two things

a) that quote "There can *never* be more *disk* than (current+query)...
correct?"

is false, and

b) that quote "then we only need to be concerned logical I/O's, since
the number of disk reads can vary from time to time depending on what happens to

be in the buffer cache at the time, but the logical I/O's will remain the same
regardless of how many disk reads are required ?"

is false.  All the examples are doing is showing that in tkprof

a) DISK (physical io) can exceed query+current
b) Logical IO's will NOT remain constant given the same query.

2) CPU time is CPU seconds.  Wall clock is the elapsed time.

It might take 5 minutes on the wall clock to give you 1 second of CPU time (lots
of waiting going on).  In a "perfect" world (dust free laboratory for example),
CPU time = Elapsed time (no waiting, just running code)

      Rating: 5
Doing the dog
      paddle  October 18, 2002
Reviewer:  Richard
      Quintin  from VA
First let me say in my defense of (what I now realize was) a naive statement
that I'm new to this.  Like you couldn't tell. :-)  Remember when you were a kid
and your dad threw you in the pool and said swim?  Well, I think I've learned to
tread water and now I'm trying to do the tuning dog paddle.

>(how that "pins" the table I don't know.

Am I using the wrong terminology or am I just flat wrong.  Doesn't this force
the t's data to be kept in the buffer cache?

> But how "pinning" a table would up the
>consistent gets... don't know that either)

Yea, ok.  I'll try harder next time :-)

Thanks for the great site.  I keep wanting to ask questions, but I keep finding
that you have already answered them!

          Followup:

No, that just changes the way the blocks are managed in the buffer pool -- there
is NOTHING but NOTHING that forces data to be kept in the cache.

The keep and recycle pool just implement two different aging techniques for
blocks.

      Rating: 5
Elapsed v/s CPU
      difference  October 19, 2002
Reviewer:  MA

>>It might take 5 minutes on the wall clock to give you 1 second of CPU time
(lots of waiting going on).

Assuming the trace files (with event 10046) shows no wait events for this
statement, but the elapsed time is significantly greater than cpu time. The
reason for this could be that the system has other cpu-intensive activity going
on.

But if in a package consisting of several statements, only one (single record)
insert statement showed this delay. This would indicate not a cpu-bound system.
The CURRENT value is high (around 20 blocks) for this statement. How to diagnose
this?
-- Heavily accessed table?
-- Not enough freelists for table?
Please share your thoughts.

TIA

          Followup:

correct assumption....

I would need to see the trace file and not work from a hypothetical "what if"
situation as I cannot imagine the circumstances that would lead me down the path
you describe.

      Rating: 4
Logical I/Os
      vs. Physical I/Os  October 21, 2002
Reviewer:
      Robert  from Memphis, USA
Tom,

Please explain relationship physical I/O has to logical I/O.
If Oracle reads a block from disk... doesn't it then *have* to put that block
into buffer cache before it can even be 'read'?
That is, the above would hold true unless some kind of *direct* operation was
going on which somehow bybassed the buffer.

(1) Am I correct, sir?

(2) Please explain behind the scenes what is going on if you have more disk
reads than logical i/o's based on the truth or falsehood of my above question.

Thanks,

Robert.

          Followup:

No it doesn't have to put the block into the buffer cache (and that was the goal
of my test -- to show that ;)

There are direct reads and writes available.  Sorts do this all of the time
(that was my example, thats why i set sort area size really tiny)

      Rating: 3
Not clear
      still....  October 21, 2002
Reviewer:  A
      reader  from BG
Tom,

Your answer is pasted below -

Followup:
No, you do a modification to T *without a commit*.
If you commit, we don't need to do a consistent read.
What I actually did was:
update t set object_id = object_id;
in the other window, came back and just hit enter.

My question is that unless you commit the transaction why should the data be
visible to the other session. Why is there a need for consistent read here.
Dunno if i make sense, but iam confused.

Thanks....

          Followup:

The consistent READ is to make the updates invisible - that is their purpose,
their goal.

Your update modified every block.

If you hit commit -- we are allowed to see that when we run the query.

If you don't hit commit -- we are not allowed to see that.  So, as we read every
block, we have to hit the UNDO to UNDO your update that we are not allowed to
see yet!

      Rating:
      5
Great....thanks  October 21, 2002

Reviewer:  A reader  from BG

      Rating: 2
Pls
      help  January 07, 2004
Reviewer:
      A reader  from NJ, USA
Tom,

1. Pls explain fully as to what you demonstrated in that example regarding
CPU/Elapsed time and Logical/Physical IO as well as the "pause" etc.
2. In one of the trace file I generated, the CPU time showed 147.05 (almost 2
minutes), but the elapsed time was less than that. How could that happen if
there is considerable CPU time?
3. In this example, my understanding is that the disk > query + current. This
means that there is some direct read to the temp segment. Is it due to the sort
generated by the ORDER BY clause. If so, my understanding is that it will happen
only when SORT_DIRECT_WRITES=TRUE to bypass the cache. Why did this happen in
your demo

          Followup:

1) i showed the side effect of multi-versioning.

Tell you what -- read this:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96524/c21cnsis.htm#17882
and you come back and tell us what might have happened (consider it a
challenge!)

2) from my new book "Effective Oracle By Design"

<quote>

A question that frequently comes up with regards to TKPROF and the report is
this - how could output such as the following be produced:

call     count       cpu    elapsed  disk    query    current        rows
------- ------  -------- ---------- ----- -------- ----------  ----------
Parse        1      0.00       0.00     0        0          0           0
Execute  14755     12.77      12.60     4    29511     856828       14755
Fetch        0      0.00       0.00     0        0          0           0
------- ------  -------- ---------- ----- -------- ----------  ----------
total    14756     12.77      12.60     4    29511     856828       14755

How can CPU time be larger then elapsed time?  This discrepancy is due to the
way timings are collected - and attempts to time very fast operations or lots of
operations.  For example - suppose you were using a stopwatch that measured only
down to the second.  You timed 50 events.  Each event seemed to have taken 2
seconds according to the stop watch.  That means you had 100 seconds of time
elapsed during these events right?  Well, probably not.  Suppose each event
really took 2.99 seconds - you really had almost 150 seconds of time there.
Taking it a step further - suppose the stopwatch was continuously running.  So,
when the event started, you would look at the stopwatch and then when the event
finished, you would look again and subtract the two numbers.  This is closer to
what happens with timing on a computer - you look at the system "watch", do
something and look again - the delta represents the timing of the event.  Now,
we'll perform the same timing test as above.  Again, each event appears to have
taken 2 seconds - but they may have taken just 1.01!  How so?  Well, when the
event started, the clock was really at time 2.99 - but you only saw "2" (the
granularity of the stopwatch).  When the event completed, the stopwatch reported
4 (and the real time was 4.00).  The delta as you see it - 2, the "real" delta -
1.01.

Now, over time these discrepancies can accumulate in the aggregate total.  The
rule of averages would have them effectively cancel each other out more or less
but over time, a small error can creep in.  That is the cause of the discrepancy
above - where the elapsed time is less then the CPU time.  At the lowest level,
Oracle is gathering statistics for timing at either the 1/100 or 1/1000000 of a
second level (depending on version).  And, further, it may time some events
using one clock and other events using a different one - this is unavoidable as
the timing information is gathered from the operating system, using its API's.
In this example, we executed a statement 14,755 times - meaning the average CPU
time to execute that statement was 0.00865469 seconds.  If we were to run this
test over and over - we would find that the timings for CPU and elapsed are more
or less "the same".  In general, this error is not so large as to send you
looking down the wrong path, but it can be confusing the first time you see it.
<qupte>

3) i forced it to happen by ordering by lots of columns -- more then would
exceed the capacity of my sort areas -- forcing "swapping" to disk.

      Rating:
      3
Question  January 08, 2004
Reviewer:
      A reader  from NJ, USA
TOm,

In this discussion you mention that the higher number of logical I/Os is due to
another session doing an update.
My questions are
1. How do we figure out that in a real scenario. ie. In the above case, there
might not be a problem as such with the query, but the logical I/O may be large
just because the other session is changing the data. In that case, how to
determine/tune the query. Any help on that is highly appreciated

          Followup:

1) you test in isolation on your test system

      Rating: 3
Pls
      confirm  January 09, 2004
Reviewer:
      A reader  from NJ, USA
TOm,

In one of the discussion above (related to the select * from t t1;pause;select *
from t t2) where you have 2 queries on the same table but with another session
doing an UPDATE to the table but not committed, you see lot of logical I/Os
going up in the second query.
 1. My understanding is that the additional logical I/Os are due to the fact
that Oracle reads the RBS to provide a read-consistent data (since the data is
changed but not yet committed). Is my understanding true? Pls confirm. If not
pls. explain

          Followup:

that is correct, it is the read aside to the rbs to get the data as it used to
exist.

      Rating: 3
Pls
      confirm  January 09, 2004
Reviewer:
      A reader  from NJ, USA
TOm,

In one of the discussion above (related to the select * from t t1;pause;select *
from t t2) where you have 2 queries on the same table but with another session
doing an UPDATE to the table but not committed, you see lot of logical I/Os
going up in the second query.
 1. My understanding is that the additional logical I/Os are due to the fact
that Oracle reads the RBS to provide a read-consistent data (since the data is
changed but not yet committed). Is my understanding true? Pls confirm. If not
pls. explain

      Rating:
      3
Doubt  January 09, 2004
Reviewer:
      A reader  from NJ, USA
Dear Mr.Tom,

"By read aside to rbs", do you mean that the additional LIOs are due to the read
to the rollback segments to get a consistent version of the data as of a past
point in time when the query began. Pls confirm.

Thanks and Have a Safe Trip

          Followup:

exactly

      Rating: 5
logical +
      physical reads were close , but .....  January 09, 2004

Reviewer:  A reader
Tom,
I have the same query run on two different databses, db1 and db2.
db1 and db2 parameters in init.ora are the same. Only tables sizes in the query
are not same.
The return records for the query was 565 on db1 vs. 4028 on db2.
I looked at the stats of logical and physical reads, the two were relative
close.
 Only the query on db1 ran much and much quicker than on db2:
00:00:23.53 for db1, 00:04:273.74 for db2

apps@db1> set timing on
apps@db1> select distinct
  2      upper('ORG_'||nvl(h1.attribute2,substr(h1.name,1,7))),
  3      upper('ORG '||nvl(h1.attribute2,substr(h1.name,1,7))),
  4      upper(h1.name),
  5      upper(h1.name),
  6      upper('ORG_'||nvl(h2.attribute2,substr(h2.name,1,7))||'P'),
  7      upper(nvl(h2.attribute2,substr(h2.name,1,7))),
  8      upper(h2.name)||' ROLLUP',
  9      upper(h2.name)||' ROLLUP'
 10    from hr_all_organization_units h1,
 11      hr_all_organization_units h2,
 12      pa_resources r,
 13      pa_resource_types rt,
 14      per_org_structure_elements p,
 15      per_org_structure_elements p2
 16    where h1.organization_id = p2.organization_id_parent(+)
 17      and p2.organization_id_parent is null
 18      and h1.organization_id = p.organization_id_child
 19      and h2.organization_id = p.organization_id_parent
 20      and h1.attribute2 is not null
 21      and h2.attribute2 is not null
 22      and r.resource_type_id = rt.resource_type_id
 23      and rt.resource_type_code = 'ORGANIZATION'
 24  /

565 rows selected.

Elapsed: 00:00:23.53

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4735 Card=92677 Bytes=9916439)

   1    0   SORT (UNIQUE) (Cost=4735 Card=92677 Bytes=9916439)
   2    1     HASH JOIN (Cost=1205 Card=92677 Bytes=9916439)
   3    2       TABLE ACCESS (FULL) OF 'HR_ALL_ORGANIZATION_UNITS' (Cost=6
Card=733 Bytes=29320)

   4    2       FILTER
   5    4         HASH JOIN (OUTER)
   6    5           HASH JOIN (Cost=600 Card=81363 Bytes=5207232)
   7    6             TABLE ACCESS (FULL) OF 'HR_ALL_ORGANIZATION_UNITS' (Cost=6
Card=733 Bytes=29320)

   8    6             MERGE JOIN (CARTESIAN) (Cost=558 Card=85470 Bytes=2051280)

   9    8               NESTED LOOPS (Cost=3 Card=111 Bytes=1998)
  10    9                 TABLE ACCESS (FULL) OF 'PA_RESOURCE_TYPES' (Cost=2
Card=1 Bytes=15)

  11    9                 INDEX (RANGE SCAN) OF 'PA_RESOURCES_N1' (NON-UNIQUE)
(Cost=1 Card=556 Bytes=1668)

  12    8               SORT (JOIN) (Cost=557 Card=770 Bytes=4620)
  13   12                 TABLE ACCESS (FULL) OF 'PER_ORG_STRUCTURE_ELEMENTS'
(Cost=5 Card=770 Bytes=4620)

  14    5           INDEX (FULL SCAN) OF 'PER_ORG_STRUCTURE_ELEMENTS_FK2'
(NON-UNIQUE) (Cost=3 Card=770 Bytes=2310)

Statistics
----------------------------------------------------------
          0  recursive calls
         16  db block gets
         89  consistent gets
       2536  physical reads
          0  redo size
      87051  bytes sent via SQL*Net to client
       4068  bytes received via SQL*Net from client
         58  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
        565  rows processed

apps@db1>

APPS@db2> set autotrace trace
APPS@db2> select distinct
  2      upper('ORG_'||nvl(h1.attribute2,substr(h1.name,1,7))),
  3      upper('ORG '||nvl(h1.attribute2,substr(h1.name,1,7))),
  4      upper(h1.name),
  5      upper(h1.name),
  6      upper('ORG_'||nvl(h2.attribute2,substr(h2.name,1,7))||'P'),
  7      upper(nvl(h2.attribute2,substr(h2.name,1,7))),
  8      upper(h2.name)||' ROLLUP',
  9      upper(h2.name)||' ROLLUP'
 10    from hr_all_organization_units h1,
 11      hr_all_organization_units h2,
 12      pa_resources r,
 13      pa_resource_types rt,
 14      per_org_structure_elements p,
 15      per_org_structure_elements p2
 16    where h1.organization_id = p2.organization_id_parent(+)
 17      and p2.organization_id_parent is null
 18      and h1.organization_id = p.organization_id_child
 19      and h2.organization_id = p.organization_id_parent
 20      and h1.attribute2 is not null
 21      and h2.attribute2 is not null
 22      and r.resource_type_id = rt.resource_type_id
 23      and rt.resource_type_code = 'ORGANIZATION'
 24  /

4028 rows selected.

Elapsed: 00:04:273.74

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=357311 Card=5726633
          Bytes=635656263)

   1    0   SORT (UNIQUE) (Cost=357311 Card=5726633 Bytes=635656263)
   2    1     HASH JOIN (Cost=10957 Card=5726633 Bytes=635656263)
   3    2       INDEX (FULL SCAN) OF 'PA_RESOURCES_N1' (NON-UNIQUE) (Cost=72
Card=5345 Bytes=16035)

   4    2       MERGE JOIN (CARTESIAN) (Cost=10873 Card=5357 Bytes=578556)

   5    4         FILTER
   6    5           HASH JOIN (OUTER)
   7    6             HASH JOIN (Cost=127 Card=5299 Bytes=476910)
   8    7               TABLE ACCESS (FULL) OF 'HR_ALL_ORGANIZATION_UNITS'
(Cost=18 Card=5322 Bytes=223524)

   9    7               HASH JOIN (Cost=47 Card=5299 Bytes=254352)
  10    9                 TABLE ACCESS (FULL) OF 'PER_ORG_STRUCTURE_ELEMENTS'
(Cost=9 Card=5299 Bytes=31794)

  11    9                 TABLE ACCESS (FULL) OF 'HR_ALL_ORGANIZATION_UNITS'
(Cost=18 Card=5322 Bytes=223524)

  12    6             TABLE ACCESS (FULL) OF 'PER_ORG_STRUCTURE_ELEMENTS'
(Cost=9 Card=5299 Bytes=15897)

  13    4         SORT (JOIN) (Cost=10864 Card=1 Bytes=15)
  14   13           TABLE ACCESS (FULL) OF 'PA_RESOURCE_TYPES' (Cost=2 Card=1
Bytes=15)

Statistics
----------------------------------------------------------
          0  recursive calls
         25  db block gets
        394  consistent gets
        202  physical reads
          0  redo size
     626291  bytes sent via SQL*Net to client
      27251  bytes received via SQL*Net from client
        404  SQL*Net roundtrips to/from client
          1  sorts (memory)
          1  sorts (disk)
       4028  rows processed

APPS@db2>

Thanks for help

          Followup:

look at the tkprofs -- i'll bet you find that when you see the actual rows
flowing through each step of the plan -- they are very very different.

use the 10046 trace event, use tkprof, (hopefully you are on 9i so you'll see
the wait events) and all will become "more obvious"

      Rating: 5
Never mind, I
      found the problem. missing joins  January 09, 2004

Reviewer:  A reader

January 10, 2004 -- Thanks for the question regarding "Sizing of
      LOBs", version 8.1.7

      You Asked
Hi Tom,

How is the sizing tables using LOBs done. I need info for sizing  tables
containing a BLOB , CLOB , BFILE , RAW columns.
I store pdfs in BLOB columns; say an of average 10kb in BLOB column in each
row.(I mostly have RAW and BLOBs).

To do an approximate sizing of a table like this:
col1    BLOB  (avg 10 kb)
col2    BLOB   (avg 15kb)
col3    RAW(16)  (avg 12)
can i just multiply the number of rows with the avg size of BLOBs?

Is it advicable to store BLOB columns in a seperate table space rather than
storing it in the same table space as other columns in the table? If so why? is
it true that an index is automatically created for each BLOB column? In which
table space is that stored?
Where do i find information on sizing LOBs?

Thanks

      and we said...
blobs, when they get beyond 4,000 bytes, are stored out of line in their OWN
segment.  They are not stored in the table.  the raw(16) is not any different
then a varchar2(16) and we won't even consider that.

So, sizing the table is done the "old fashioned way" -- load up representative
data, and analyze it -- then multiply.  (i've never ever found a formula that
actually works -- you have to load it up and see -- somewhere between 0.1% and
10% of the data depending on the size of the eventual table).

search for
sizing table
on this site for my prior writings on this topic.

Then, you need to size the blob segments themselves.  I would use the show_space
script I have to determine your needs there.  (search for show_space).  You'll
load up representative data and see what it takes.  Remember with lobs however,
we store them in chunks.  So, say you have:

o 8k database
o 32k chunksize on the lob (you PICK)

Each of your lobs will take 32k of space -- no two lobs will share the same
chunk.  In your case, you might decide on an 8k chunk for col1 and a 16k chunk
for col2.

Review &
      Followup

      Rating:
      4
  June 07, 2002
Reviewer:  vinci

Thanks Tom,
I guess you left out the explaination abt indexes on LOB in my question.

          Followup:

the lob index is stored in a separate segment in the SAME tablespace as the
table itself.  You have no control over its placement.

why?  because it is an internal, hidden thing that lives quite nicely in the
same tablespace with the table itself.

Yes, each blob gets a lob index created.

Also, you might be interested in the contents of:

http://download-west.oracle.com/docs/cd/A87860_01/doc/appdev.817/a76940/toc.htm

      Rating:
      3
vinci  June 07, 2002
Reviewer:
      vinci1@dacafe.com
Tom,
I did this:

create table test_lob (varchar_col varchar2(5), blob_col blob);

alter table test_lob modify blob_col default empty_Blob();

Then I SQL Load(ed) data using the control file:-,

LOAD DATA
INFILE *
INTO TABLE test_lob
REPLACE
FIELDS TERMINATED BY ','
( varchar_col    CHAR,
   BLOB_FILE FILLER CHAR,
  "BLOB_COL" LOBFILE (BLOB_FILE) terminated by eof NULLIF BLOB_FILE = 'NONE'
)
BEGINDATA
doc1,test1.pdf

It returns error as:
SQL*Loader: Release 8.1.7.0.0 - Production on Fri Jun 7 10:55:31 2002

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

SQL*Loader-462: error inserting LOB into column "BLOB_COL", row 1, table
TEST_LOB
SQL*Loader-646: lob set to EMPTY in column "BLOB_COL", row 1, table TEST_LOB
Commit point reached - logical record count 3

I did this after that:
SQL> select varchar_col from test_lob;

VARCH
-----
doc1

SQL> select blobtochar(blob_col) from test_lob;

BLOBTOCHAR(BLOB_COL)
--------------------------------------------------------------------------------

Didnt undestand whats happening.
Then i did this:

alter table test_lob modify blob_col ;

and ran SQLLoader...gave me the same error and same results on selecting the
row.

Thanks

          Followup:

I cut and pasted your example "as is" with 8173 and:

$ sqlldr / test

SQL*Loader: Release 8.1.7.3.0 - Production on Fri Jun 7 21:15:08 2002

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

Commit point reached - logical record count 1

(don't know why or even HOW you could be getting "logical record count 3"?

Then I:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select varchar_col,
dbms_lob.getlength(blob_col) from test_lob;

VARCH DBMS_LOB.GETLENGTH(BLOB_COL)
----- ----------------------------
doc1                       3060340

which is exactly what I expected...

Well, wait -- I did just replicate your issue:

sqlldr / test

SQL*Loader: Release 8.1.7.3.0 - Production on Fri Jun 7 21:17:15 2002

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

SQL*Loader-462: error inserting LOB into column "BLOB_COL", row 1, table
TEST_LOB
SQL*Loader-646: lob set to EMPTY in column "BLOB_COL", row 1, table TEST_LOB
Commit point reached - logical record count 3

the Logical record count gave it away.  You have extra blank lines in the ctl
file -- you are trying to load

LINE1: doc1,test1.pdf
LINE2:
LINE3:

that is messing it up.  Correct your input file and all will be well.

      Rating: 3
Load
      blob  August 20, 2003
Reviewer:  Sasa
       from Belgrade, Serbia
Hi Tom,

I searched your site(load blob) but couldn't find (convinient to ourseleves) way
to insert image to table.
We have specific request to insert image not via bfile, although that worked
perfectly.
I wrote one procedure, and it works , but I think it is weird as I made some
tricks, as converting from raw to varchar2 and vice versa.
Our VB guys tell that this is slower then way they do it
through ADO, in a way that they open a empty recordset and add a new record
(rs.add)  and simply assign a blob bytearray value (append chunk), and do
recordset update.
I am sure that way through stored procedure should be faster but my code
apparently is not.
So could you suggest different approach for that?
Is possible to insert blob to table via instead of trigger and if you have an
example please share with us.

create or replace Procedure LoadImage2DB(pImage IN OUT NOCOPY BLOB)
as
Dest_loc       BLOB;
Buffer     RAW(32767);
amt            BINARY_INTEGER := 32000;
bufchar varchar2(32000);
k number;
l number :=1;
BEGIN
 insert into SlipImage (slipimage)  values(empty_blob())  returning slipimage
into dest_loc ;
 DBMS_LOB.OPEN(Dest_loc, DBMS_LOB.LOB_READWRITE);
 k := ceil(dbms_lob.getlength(pImage)/amt);
 for i in 1..k
 loop
  bufchar := utl_raw.cast_to_varchar2(dbms_lob.substrpImage, amt, l));
 l := amt+l;
 Buffer := utl_raw.cast_to_raw (bufchar);
 dbms_lob.writeappend( dest_loc, dbms_lob.getlength(buffer), Buffer );
end loop;
dbms_lob.close(dest_loc);
commit;
END LoadImage2DB;

Thanks

          Followup:

I'm confused

"we have a fast way, programmers like it, it works"

hmmm.

but anyway, how big are these blobs?
how are the VB guys binding to a blob?

if you get a blob as INPUT, why isn't the routine simply:

insert into slipImage( slipimage) values ( pImage );

???

      Rating:
      5
  August 21, 2003
Reviewer:  Sasa
       from Belgrade, Serbia
Finally discovered Holy Grail secret.

I have read a ton of doc (and many conversation from your site) but none said
that I could do simply :

insert into slipImage( slipimage) values ( pImage );

Thanks a lot for this simplicity (or thanks to Oracle).

      Rating:
      5
chunk  September 09, 2003
Reviewer:
      Sandeep  from Germany
Oracle Version: 8.1.7.4

Tom,
Can chunk size be less than the database block size...
Our database block size is 8k....and average size of a file stored in the blob
colum  would be 4k....so can chunk be 4k?

Thanks.

          Followup:

chunk size is an integer multiple of block size.

a chunk is AT LEAST a block.

if the average size is 4,000 bytes and less -- just "enable storage in row" and
it'll be stored "inline", in the table (like a varchar2)  only when it exceeds
that (goes ABOVE average) will it spill out into the lob segment.

      Rating: 3
Loading lot of
      Images  January 09, 2004
Reviewer:  Sharon
       from USA
Hi Tom,

we have lot of image files need to load into Oracle9i, can I use SQL*Loader to
do this? any examples for control file.
table structure is very simple, id and image -- blob.

Thanks

Sharon

          Followup:

if you have my book "Expert one on one Oracle" -- examples in there.

as well search for

load lob sqlldr

many examples.

January 10, 2004 Arun -- Thanks for the question regarding
      "Loading Multiple Input files delimited by TAB into Oracle Table.",
      version Oracle 8.0.5

      You Asked
Hi Tom,
I want to load some input files delimited by Text into Oracle database.  Can you
please help me out in this.

I know one way of doing it is using SQLLOADER writing the control files and give
the control file as input to SQLLOADER.
After loading into the table I take data from the table and
process the data as per the requirements, load into different table.

Here my qwestion is these alli the input files will be placed in a public
folders. All the users will place these files into
this common folder. My job is, I need to load this data into
different table as per the requirements. If I use SQLLOADER
I need to create the control files for each input file I load.

I want to load all the files at a time say in the night by running a Job at a
specified time.

All the input files will be in the same format means corresponds to one table.

What are all the possible ways and can you give some example solutions for this.

Thanks in advance..Thanks a lot

Arun Tummala

      and we said...
If they are all going into the same table and they all have the same format
(columns in the same order) you only need 1 control file.

Since you use the term "folder" I'll assume you are on Windows.  Here is an
example command that will use a single ctl file to load all of the .dat files in
a directory:

C:\oracle\RDBMS\demo\test>for %f in ( *.dat ) do sqlldr tkyte/tkyte data.ctl
data=%f

you'll see the following on your screen if there where 2 dat files to be loaded,
data1.dat and data2.dat:

C:\oracle\RDBMS\demo\test>sqlldr tkyte/tkyte data.ctl data=data1.dat

SQL*Loader: Release 8.1.6.0.0 - Production on Sun Nov 19 09:25:11 2000

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

Commit point reached - logical record count 7

C:\oracle\RDBMS\demo\test>sqlldr tkyte/tkyte data.ctl data=data2.dat

SQL*Loader: Release 8.1.6.0.0 - Production on Sun Nov 19 09:25:11 2000

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

Commit point reached - logical record count 7

C:\oracle\RDBMS\demo\test>

You do not need to put the filename into the ctl file -- you can pass it in on
the command line.
Review & Followup

      Rating:
      5
  May 17, 2002
Reviewer:
      A reader
Hi Tom,

 We have problem with SQL*LOADER.

 Here is the problem.

 We have two table, Which we have to populate using sqlloader.

 The two INFILE we are getting every night has different name.File format is
like
1)ISRO||'any number'.bat
2)ISBN||'any number'.bat

"any number" can be any number.

We used to have two control file before.But since INFILE
can have different name we can not use old control file.

 We have to create control file dynamically because of INFILE.

 Any idea how we can do this.

 Thanks.

          Followup:

umm, as i showed above you can use DATA= on the command line.  That will
override any infile statement in the control file.

So, just pass in the name of the datafile on the sqlldr command line.

      Rating: 4
Can load
      multiple files  May 15, 2003
Reviewer:
      Praveen  from Mumbai India
Hi Tom,

   We can load multiple files in single table using only one control file. Here
is the type of the control file that I use for daily upload and as Arun has
said, I run it daily at night and get the data loaded during the off peak hours.

load data
infile 'data_daily1.csv'
infile 'data_daily2.csv'
infile 'data_daily3.csv'
append
into table daily_upload
fields terminated by ","
optionally enclosed by '"'
trailing nullcols
(
col1,
col2,
col3,
col4,
col5
)

I upload around 150000 approx. daily and have never faced any problems.

          Followup:

since his filename changes everyday, that'll not work -- it'll not solve their
problem

      Rating: 3
What if the
      format of data is not same  September 16, 2003

Reviewer:  Pawan  from USA
Tom,
If I have a table (say T1) with 4 columns (col1,col2,col3 and col4). We use
SQLLOADER to load data into T1 from data.txt file. The data in data.txt is like
this
1,5,test
5,8,condition
8,1,real
....

The data is being loaded into col1, col2 and col3 only and col4 is blank. Now we
have another file (newfile.txt) having just 1 record - that is date. The data in
the newfile is
09/05/2003.

What is the best way to load this data into col4 of T1.

Thanks

          Followup:

there isn't one. sqlldr doesn't update, it loads

you load that row into yet another table T2 and then

update t1 set col4 = (select * from t2);

      Rating: 3
What if the
      format of data is not same  September 16, 2003

Reviewer:  Pawan  from USA
Tom,
If I have a table (say T1) with 4 columns (col1,col2,col3 and col4). We use
SQLLOADER to load data into T1 from data.txt file. The data in data.txt is like
this
1,5,test
5,8,condition
8,1,real
....

The data is being loaded into col1, col2 and col3 only and col4 is blank. Now we
have another file (newfile.txt) having just 1 record - that is date. The data in
the newfile is
09/05/2003.

What is the best way to load this data into col4 of T1.

Thanks

      Rating:
      3
Thanks!!  September 16, 2003
Reviewer:
      Pawan  from USA
Sorry for the double post.I know Oracle has External table in 9i (and maybe
other )but is there a special reason why Oracle is not enchancing SQL*loader. By
enhancing I mean - inclusing IF/THEN/ELSE condition, loading files from two
different files with different format into a single table etc. etc.
Thanks

          Followup:

because you have external tables?

SQL is extremely powerful

merge
multi-table inserts
case statements
where clauses
plsql pipelined functions

you name - sql's got it.

sqlldr is useful to generate an external table definition (so it has been
enhanced, but in a fashion to hasten its demise ;)

      Rating: 4
Thanks
      Tom  September 16, 2003
Reviewer:  Pawan
       from USA
I never stop learning !! I will need to read more about how to use
case statements
where clauses
etc in SQL*loader. Any pointers?
That was really fast response.

          Followup:

sorry -- i wasn't clear enough

the reasond sqlldr won't get those things is cause SQL has them and we have
external tables which give is "sqlldr in sql" -- which is sqlldr++

sqlldr can use case (just use sql functions in there) but thats about it.

sqlldr was enhanced, but pretty much only to let it create external table
definitions for us from legacy control files! (oh, it can direct path load with
sql functions as well)

but sqlldr doesn't have where clauses, case, etc -- SQL does and that is why we
don't need to add any of that to sqlldr, you already have it with SQL.

      Rating: 5
Thanks for the
      answer abt loading multiple files into Oracle  September 18,
      2003
Reviewer:  Zameel Arif  from Saudi Arabia

Hi Tom,
Thankx for the answer.It was of great help for me, since i am loading data to
oracle from an punching card device.I was in search for such an answer.. because
the file name i get from the punching machine will b different all the time..and
i had asked the user to combine the data to an single file for uploading...I was
wondering wht to do with tht..i tried really hard posting a ques but was not
able to...anyway thankx once again
Now i have one more doubt...regarding this
u querry "for %f in ( *.dat ) do sqlldr tkyte/tkyte data.ctl"
I tried with *.txt format also..it worked but
i want to know whether its possible to load files with different
extensions..like .dat,.txt etc
once again thank u very much..u have reduced me work a lot
regards

          Followup:

sure, it'll work

just try it.

c:\> help for

should give you more details.

      Rating: 4
Sqlldr
      Problem  January 10, 2004
Reviewer:  bipin
       from INDIA
Hi Tom
I'm working on data uploading But on my pc it is giving me error as follows
Message 2100 not found; No message file for product=RDBMS, facility=ULMessage 21
00 not found; No message file for product=RDBMS, facility=UL
How can i resolve this problem

OS => Windows 2K & Oracle 8.1.6
I have installed Oracle Developer 6 and oracle dba on my pc

          Followup:

sounds like your oracle home isn't set, cannot find message files.

use the "oracle home selector" tool buried somwhere in that innovation that is
the start menu (that comment by MS always made me laugh actually, start menu
looks a tad like the X Windows Root menu doesn't it....) to pick the oracle home
that has your sqlldr tool.

January 11, 2004 Pierre -- Thanks for the question regarding
      "Direct-path INSERTs are not propagated during a "Materialized View Fast
      Refresh"", version 8.1.7

      You Asked
Hello

The Oracle8i Concepts Guide mentions on page 10-19 that
"For Materialized Views that use the fast refresh method, a materialized view
log or direct-loader log keeps track of the changes to the master tables".

From what i read, I assumed that direct-load INSERTs were supported for fast
refresh and that the changes were kept somewhere in such a "direct-loader" log.
However, practice shows that such changes don't get propagated at all. They also
don't appear in the MLOG$_xxx table associated with the base table xxx which I
guess is fed by the internal trigger which doesn't get fired off when using the
direct-loader API. But then, where is that mysterious "direct-loader log" ?

Is there something I am missing ?

Thanks for helping me out.

Pierre

      and we said...
<quote>
Incremental loads of your detail data should be done using the SQL*Loader
direct-path option, or any bulk loader utility that uses Oracle's direct path
interface (including INSERT AS SELECT with the APPEND or PARALLEL hints). Fast
refresh after conventional DML is not supported with views with aggregates and
joins, but it is for single-table aggregate views. See Oracle8i SQL
Reference for further details.
</quote>

ops$tkyte@ORA817DEV> create table t as select * from scott.emp where rownum < 5;

Table created.

ops$tkyte@ORA817DEV> alter table t add constraint t_pk primary key(empno);

Table altered.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create materialized view log on t with (deptno), rowid
including new values;

Materialized view log created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create materialized view v
  2  refresh fast
  3  as
  4  select deptno, count(*) from t group by deptno;

Materialized view created.

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

no rows selected

ops$tkyte@ORA817DEV> insert /*+ append */ into t
  2  select *
  3    from scott.emp
  4   where empno not in (select empno from t);

10 rows created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select * from t;
select * from t
              *
ERROR at line 1:
ORA-12838: cannot read/modify an object after modifying it in parallel

ops$tkyte@ORA817DEV> commit;

Commit complete.

ops$tkyte@ORA817DEV> select * from all_sumdelta;

 TABLEOBJ# PARTITIONOBJ# D        SCN TIMESTAMP   LOWROWID           HIGHROWID
---------- ------------- - ---------- ----------- ------------------
------------------
     51308         51308 I 6.5614E+12 28-JAN-2003 AAAMhsAAHAAAk3LAAA
AAAMhsAAHAAAk3LH//

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

    DEPTNO   COUNT(*)
---------- ----------
        20          2
        30          2

ops$tkyte@ORA817DEV> exec dbms_mview.refresh( 'V' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> select * from v;

    DEPTNO   COUNT(*)
---------- ----------
        20          5
        30          6
        10          3

Review &
      Followup

      Rating:
      4
  January 28, 2003
Reviewer:  Pierre
      Six  from Brussels, Belgium
Thanks for the response. What puzzles me is that I have the simplest MV
possible, which is a mere INSERT SELECT and in that case it doesn't work. To get
the best performance in that case (direct-load interface on the insert + fast
refresh for the MV), would you then reccomend using a GROUP BY on all fields
(given that this is a unique combination) ?

          Followup:

what is the point of a non-aggregate MV on a single table?  can you tell me
that?

      Rating:
      3
  January 28, 2003
Reviewer:  Pierre
      Six
We use this this to "publish" some of our datamarts from our Data Warehouse
tables. Daily changes are done on the DWH tables and the Datamart tables are
synchronized using MV fast refresh. The datamart tables "almost" copies of the
DWH tables. The MV therefore includes some transformations on the data, such as
DECODEs and NVLs. Both reside on different instances on different machines. Does
that make sense ?

          Followup:

I would not use an MV at all -- nor copy to a DM I guess, rather a regular view
with Decodes and NVLs.  You lose nothing, gain alot.

Don't know what to tell you -- the addition of a group by could really mess it
up (speedwise).  Seems like a strange configuration

      Rating:
      3
  January 28, 2003
Reviewer:  Pierre
      Six
To cut a long story short, we have a 20 Million row table on the DWH side and
have a "near copy" in the Datamart (same number of rows but some fields are
being altered on the way). Every night, a couple hundred thousand rows get added
to the DWH table, 50K rows get updated etc ... and we don't want to do a full
refresh of the datamart table every day. DWH and DMT are on different machines.

          Followup:

ok, my point is -- datamart sounds like a waste of money -- data warehouse
sounds like the right place to keep the data.

so, now you know the restriction -- single table aggregates as no one
anticipated a single table "no aggregate" mv like that.

adding a couple hundred thousand rows should (could easily) be done in a
conventional path load -- that'll be your best solution.

      Rating:
      3
  January 29, 2003
Reviewer:
      A reader
The reason we have two tables on two machines is that

- The DWH machine does the inflow, so almost no indexes on the tables, refreshed
whenever we want, several times a day if needed. Machine can be brought down if
needed.

- The DMT machine does the querying, lots of indexes, stable "point-in-time"
view of the data (as of yesterday at midnight), some fields modified to make
them suitable for querying, high availability.

Our experience is that oncurrent heavy inflow creates a mixed workflow which
seems difficult to tune. Parallel sessions are being taken away by the inflow
etc ...

The Materialized View is used here as an extension of the snapshot mechanism,
replicating changes accross to the DMT table (with some modifications to make
the data suitable for querying).

And it works ! The MV fast refresh does the job, but the inserts into the base
table could be faster, that's all ...

Pierre

          Followup:

Thats why I would have fixed the DW, rather then try to band-aid the fix with a
data boutique.

Give me a single system any day.  Having problems with resource mgmt -- use RAC
or the resource manager.

If the DW can't keep up -- the DM with tons of extra indexes and such would flop
over -- so, by extension -- since the DM works -- the DW could have those
indexes.

      Rating:
      4
  January 29, 2003
Reviewer:
      A reader
Pierre,

What you call a DWH is probably known as ODS(Operational Data Store) on more
popular terms. And what you call a DM is probably better known as a DWH.

      Rating:
      3
Clarification  April 29, 2003
Reviewer:
      Rob  from Pittsburgh, PA
Tom:

Thanks for pointing me to this link. I think the quote below is from the data
warehousing guide. It indicates that a fast refresh is possible after a direct
load.

<quote>
Incremental loads of your detail data should be done using the SQL*Loader
direct-path option, or any bulk loader utility that uses Oracle's direct path
interface (including INSERT AS SELECT with the APPEND or PARALLEL hints). Fast
refresh after conventional DML is not supported with views with aggregates and
joins, but it is for single-table aggregate views. See Oracle8i SQL
Reference for further details.
</quote>

However this quote from the 9.2 Adv. Replication guide indicates otherwise:

<quote>
After a direct path load on a master table or master materialized view using
SQL*Loader, a fast refresh does not apply the changes that occurred during the
direct path load. Also, fast refresh does not apply changes that result from
other types of bulk load operations on masters. Examples of these operations
include some INSERT statements with an APPEND hint and some INSERT ... SELECT *
FROM statements.
</quote>

I think that a fast refresh after a direct path load is supported only for MV's
in the same instance as the Base table. If you are replicating to another
instance you have to use conventional path load for fast refresh to work. Does
this seem like the correct explanation to you.

          Followup:

it would make sense (to me anyway) that that could be true.  If you need, I can
test it out -- but I've gotta be in the office to do that....

      Rating: 5
Direct Path
      Insert and Create MV  October 10, 2003
Reviewer:
      A reader
Hi Tom,

Just want to know if Direct Path Insert (the "APPEND" hint) is supported in the
"create materialized view ... as select ..." statement.

Do you have any other suggestions about reducing resource requirements during
creating a big MV?

Thanks,

          Followup:

that is the cool thing about SQL_TRACE -- we can see what takes place.
Consider:

ops$tkyte@ORA920> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA920> create materialized view emp_mv
  2  nologging
  3  refresh complete
  4  as
  5  select * from emp;

Materialized view created.

Using that, we can see that the MV is created with:

CREATE TABLE "OPS$TKYTE"."EMP_MV"
("EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO") nologging
 AS SELECT
"EMP"."EMPNO","EMP"."ENAME","EMP"."JOB","EMP"."MGR","EMP"."HIREDATE","EMP"."SAL",
"EMP"."COMM","EMP"."DEPTNO" FROM "EMP" "EMP"

and since CTAS uses direct path semantics to process -- it is just like a direct
path load.

Further, during a refresh:

ops$tkyte@ORA920> alter session set sql_trace=true;
Session altered.

ops$tkyte@ORA920> exec dbms_mview.refresh('EMP_MV');
PL/SQL procedure successfully completed.

we can see the sql executed is:

BEGIN dbms_mview.refresh('EMP_MV'); END;

SET CONSTRAINTS ALL DEFERRED

truncate table "OPS$TKYTE"."EMP_MV" purge snapshot log

INSERT /*+ APPEND */ INTO "OPS$TKYTE"."EMP_MV"("EMPNO","ENAME","JOB","MGR",
  "HIREDATE","SAL","COMM","DEPTNO") SELECT "EMP"."EMPNO","EMP"."ENAME",
  "EMP"."JOB","EMP"."MGR","EMP"."HIREDATE","EMP"."SAL","EMP"."COMM",
  "EMP"."DEPTNO" FROM "EMP" "EMP"

ALTER SUMMARY "OPS$TKYTE"."EMP_MV" COMPILE

so, that single table refresh will be done with direct path actions as well.
(if you use a snapshot GROUP -- you'll have a delete + conventional path insert
instead -- but with a single table full refresh it is truncate + direct path
insert)

      Rating:
      5
  January 11, 2004
Reviewer:  sush
       from sj
Hi.What's the meaning of ..ALTER SUMMARY "OPS$TKYTE"."EMP_MV" COMPILE...Couple
of weeks back in our production database we found one of our mv(which was doing
a complete refresh) got struck there for a long time in that status (in alter
summary ..compile)when we looked into v$sqlarea..and it failed after 5 hours
with the following message

ORACLE error 4020 in FDPSTP
Cause:  FDPSTP failed due to ORA-04020: deadlock detected while trying to lock
object OKC.OKC_K_HEADERS_B
ORA-06512: at "APPS.XX_CM_CC_MVT_PKG", line 83
ORA-06512: at line 1

Mv Defn :
CREATE MATERIALIZED VIEW xx_cm_cc_con_loc_mv
 BUILD IMMEDIATE
 REFRESH COMPLETE ON DEMAND
  AS
SELECT /*+ parallel(okhb,4) parallel(hcsu,4) parallel(orb,4) */
   hcsu.location,
   okhb.contract_number
from okc.okc_k_headers_b okhb,
     ar.hz_cust_site_uses_all hcsu ,
     okc.okc_rule_groups_b orgb ,
     okc.okc_rules_b orb
where okhb.sts_code IN ('ACTIVE','OVERDUE','SIGNED')
  AND okhb.date_terminated IS NULL
  AND okhb.id = orgb.dnz_chr_id
  AND orgb.chr_id is not null
  AND orgb.ID = orb.rgp_id
  AND orb.jtot_object1_code = 'OKX_BILLTO'
  AND to_number (orb.object1_id1) = hcsu.site_use_id
  AND hcsu.site_use_code    = 'BILL_TO'

Explain Plan is as follows
Operation    Object Name    Rows    Bytes    Cost    Object Node    In/Out
PStart    PStop

SELECT STATEMENT Hint=CHOOSE        4 K         926228

HASH JOIN        4 K    312 K    926228                 :Q2387186006 P->S QC
(RANDOM)
HASH JOIN 267 K    13 M 910366       :Q2387186004 P->P HASH
HASH JOIN 265 K    10 M 200481   :Q2387186002 P->P HASH
TABLE ACCESS FULL OKC_K_HEADERS_B 153 K    3 M 2815  :Q2387186001 P->P HASH
TABLE ACCESS FULL OKC_RULE_GROUPS_B    542 K    8 M 197021  :Q2387186000 S->P
HASH
TABLE ACCESS FULL OKC_RULES_B    27 M 284 M 662352 :Q2387186003 P->P HASH

TABLE ACCESS FULL HZ_CUST_SITE_USES_ALL    595 K 12M 13658 :Q2387186005 P->P
HASH

My question is why is it trying to do this alter summary..compile...Why is it
trying to acquire a lock on  the tables....I did a small test in development box
by locking the table and doing the materialized view refresh.That time I didn't
have this problem..

Hope you can clarify the mystery of Materialized view Refresh and the
relationship between alter summary..compile.
Thanks in Advance

          Followup:

it validates/compiles the mv -- makes it "whole" again. invalidates cursors
against it, like compiling a real view would.  it was trying to get a quick ddl
lock in order to accomplish its goal.

      Rating: 5
to Pierre Six
      question  January 11, 2004
Reviewer:  reader

Could you use trigger which saves rowid, when change occurs on source?

January 12, 2004 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.

      Rating: 5
fastest way to
      count distinct values  January 12, 2004
Reviewer:
      A reader
Hi

I am using this to count distinct values

select /*+ index(a RL_CMF_SC_FK )*/ count(distinct SALES_CODE) from swb.CMF a;

The problem is it sometimes uses index full scan and sometimes not even I
specify index hint... how so? Isnt it enough to read the index to count the
distinct values in the index?

You might ask why I am doing this when we can get this info from dba_indexes,
since we do estimate statistics we have rough values but at this moment I need
the exact values ....

          Followup:

lose the hint, analyze the table, let the optimizer do its job.

as long as sales_code is not null
and you use the CBO
you are done.

lose the hint.

January 12, 2004 -- 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://download-west.oracle.com/docs/cd/A87860_01/doc/network.817/a76933/concepts.htm#1028475
which does not conflict with:

http://download-west.oracle.com/docs/cd/A87860_01/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://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76965/toc.htm
http://download-west.oracle.com/docs/cd/B10501_01/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.

      Rating: 4
AIX processes
      of Oracle  January 12, 2004
Reviewer:  Himnish
      Narang  from Gurgaon, India
Tom,

Just need a clarification on TIME field of the command
ps -eaf in Umix. Following is the output of the command :

  dbop 207696 499932   0 18:00:13  pts/2  0:00 grep ora_
   dbmgr 475302      1   1 22:03:36      -  9:40 ora_lgwr_BOP
   dbmgr 494562      1   0 22:03:36      -  0:00 ora_reco_BOP
   dbmgr 502564      1   0 22:03:36      -  0:36 ora_ckpt_BOP
   dbmgr 512920      1   1 22:03:36      - 18:56 ora_dbw0_BOP
   dbmgr 514628      1   1 22:03:36      -  0:03 ora_smon_BOP
   dbmgr 526904      1   0 22:03:36      -  1:23 ora_arc0_BOP
   dbmgr 527884      1   0 22:03:36      -  0:01 ora_pmon_BOP

In the TIME field for the processes ora_lgwr_BOP and ora_dbw0_BOP the values
shown are 9:40 nd 18:56 respectively. The database was brought up at 22:03:36.
Why is there this difference in times for which these processes are getting
executed?

Thanx.

Himnish

          Followup:

that shows the consumed cpu time.  dbw0 used 18 minutes, 56 seconds of
processing time.  you had a busier dbwr than you did a lgwr - they are not "a
pair", they do their own job.  that is all.

January 13, 2004 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

      Rating: 5
Controlling the
      concurrent sessions  January 12, 2004
Reviewer:
      Pushparaj Arulappan  from NJ, USA
Tom,

Is there any mechanism available in oracle to execute
two procedures concurrently but the COMMIT or ROLLBACK
of each one should be dependend on the others.
If one fails then both procedures should rollback and
commit should be issued when only both executes sucessfully.

My requirement is something like this:

create or replace procedure driver_proc
as
begin
     submit proc1; (do not commit by itself)
     submit proc2; (do not commit by itself)

     if (both are successfull) then
        issue a commit;
     else
        rollback;
     end if;
end;
/

Please advice.

Thanks
Pushparaj

          Followup:

Umm, that is the way it works.

that is the way transactions "work"

this is not sqlserver!

(if you want lots of details on this -- server concepts guide and/or my book
"Expert one on one Oracle" -- i cover this in detail)

      Rating:
      5
  January 13, 2004
Reviewer:  Dave
       from Colorado Springs
Is it possible (and I think it is) that the two concurrent child procedures
could communicate with the parent procedure (through DBMS_PIPE, I guess), and
could signal their individual completions to the parent. They then wait until
the parent signals them to commit or rollback, based its receipt of "successful
completion" messages from both children?

Not a perfect solution by any means, but a possible one.

      Rating: 3
Re: Creating
      Indexes in Parallel  January 13, 2004
Reviewer:
      Dave  from Colorado Springs
Thescenario that Doug describes, of creating/rebuilding multiple index
partitions simultaneously followinga partition exchange, is exactly the method I
have implemented myself.

I have a procedure that selects the index name and partition name of all
unusable indexes on a given table (the name of which is passed as the sole
parameter to the procedure).

The procedure submits index partition rebuild statements through DBMS_JOB, and
waits for all of them to finish.

One important feature was that when the index partition rebuild statements were
submitted through DBMS_JOB, they were ordered by partition_name, not index name.
This promoted the simultaneous rebuild of different indexes on the same table
partition, and tended to avoid the undesirable scenario of multiple table
partitions being read simultaneously.

January 13, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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

      Rating: 5
You saved me
      again!  January 12, 2004
Reviewer:  Pat
       from md
I've been seeing those clueless user-defined exception error messages, no idea
what was going on - doesn't make any sense at all. Then decided to try your
forum, and voila! The first link returned solved the problem. Just add the 32000
line size.

Even found answer to my next question: what if line size is over 32k.

Thanks so much, as always!

          Followup:

you can use a BFILE to read the file by "chunks" of 32k into a raw, then use
utl_raw.cast_to_varchar2 to convert it into a varchar2.

but plsql will have a 32k variable limit regardless -- you would have to be
processing the data yourself -- not as a big line.

January 13, 2004 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.
:(

      Rating: 3
The other
      approach?  January 12, 2004
Reviewer:
      A reader
Hi Tom,

You mentioned that you took the verbose, pedantic approach to solving the
poster's problem. What is or are the other approach/es you could have taken?
Thanks.

          Followup:

remove the inline views, the instr's get tricky --but it would be "more terse"

January 13, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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

      Rating: 5
auto refresh
      not happening  January 13, 2004
Reviewer:
      atul  from India
Hi,

Suddenly our auto refresh of a snapshot is not happening.
But manually it is working fine.
There are no errors found in alert log etc..

also in dba_jobs broken is "N".

So what might be the reason for that?

          Followup:

job_queue_processes = what? (and perhaps job_queue_interval if you are 8i or
before)

do any "jobs" work.

if you

variable n number
exec dbms_job.submit( :n, 'null;' );
commit;

does that job run?

January 14, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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.

      Rating: 4
Week
      range  January 14, 2004
Reviewer:  siraj
       from singapore
select dt, to_char( dt+1, 'iw' )
   from ( select to_date('20-dec-2002')+rownum dt
            from all_objects
                    where rownum < 45 );
I try to use the above command but result shows
29-DEC-02 01
30-DEC-02 01
31-DEC-02 01
01-JAN-03 01

31-dec-02 is show 01 week and 01-jan-03 also show 01 week
but need 31-dec-02 shows 53, bec my client wants 01-jan-2002 is 1st week and 31
dec is lask week . If 01-jan-2003 is next year so he wants 1st week start from
1st day and last week end to dec 31

          Followup:

iw is defined outside of oracle here -- it is "a standard" -- the i = iso.

January 14, 2004 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.

      Rating: 4
Another lookup
      table query...  January 14, 2004
Reviewer:  Dave
      Blake  from UK
Tom,

Wonder if you can answer this one for me.

Which is better a lookup table formed like this.
(PK)
ID      NAME
1       cat
2       dog
3       rabbit
4       horse
etc...

or on like this...

(PK)
NAME
cat
dog
rabbit
horse
etc...

Table one Advantages:
When you have to edit a NAME you only do so in one table.

Disadvantages:
When you query the database you always have to query the lookup table to find
the NAME.

Table Two Advantages:
You do not have to query the lookup table to get the NAME. (More Optimised?)

Disadvantages:
Editing a name is more difficult because all instances of that name must be
located in the database and changed.

Which of these is better in your opinion?
Does creating tables in the second example actually speed up queries at all? (I
guess this may depend on the table size.)

Thanks

Dave

          Followup:

primary keys should be immutable.

if the "name" column can change - your version #1 is right

if the "name" column cannot change ever - version #2 is right

January 14, 2004 GREEN -- Thanks for the question regarding
      "QUERY", version 8.1.6

      You Asked
HI tom,
I run a query on a table which was set up by the other people
SELECT count(*)
     FROM h_summary
        WHERE ROWNUM <=1
          AND (d_code, d_number)
        IN (SELECT d_code, d_number
            FROM f_WORK)
 the result is as follows

 COUNT(*)
---------
        1
BUT  I RUN THE FOLLOWING COMMANDS
SQL > DESC  F_WORK
D_TITLE_CODE                            NOT NULL VARCHAR2(4)
D_NUMBER                                NOT NULL NUMBER(3)

SQL >SELECT d_code, d_number  FROM f_WORK
SELECT d_code, d_number
       *
ERROR at line 1:
ORA-00904: invalid column name

It is very clear that the f_work table has different fileds.
so my question is why the first query can run successfully ?
what is the potential possibility ?
Thanks in advance
green

      and we said...
That query looks bad.

If h_summary has at least 1 row and f_work has at least 1 row, the answer will
ALWAYS be one.

It is running a correlated subquery.  d_code and d_number are columns from
h_summary -- not f_work.

This might make it more clear:

SELECT count(*)
     FROM h_summary
        WHERE ROWNUM <=1
          AND (d_code, d_number)
        IN (SELECT H_SUMMARY.d_code, H_SUMMARY.d_number
            FROM f_WORK)

See, its running the subquery with the values from the OUTER query.  And of
course (d_code,d_number) in ( select H_SUMMARY.d_code, H_SUMMARY.d_number from
f_work) is ALWAYS true as long as f_work has at least 1 row.

I think the original query is probably wrong.  If it is not wrong, it would be
more efficient as simply:

select count(*)
  from dual
  where exists ( select null from h_summary )
    and exists ( select null from f_work );

Review &
      Followup

      Rating:
      5
supper!!  November 24, 2001
Reviewer:
      Sean  from Atlanta

      Rating: 4
nested
      correlated subquery  January 14, 2004
Reviewer:
      A reader
Tom,

How do you deal with this situation?

select empno,
       sal,
       deptno
from emp outer
where sal > (select max(sal)
             from (select avg(sal)
                   from emp inner
                   where outer.deptno=inner.deptno));

I know the select max(sal) doesn't need to be there, but I have a query where I
need to do this very similar thing, and I run into the ORA-00904, which I
understand why I am getting that error, but simply cannot figure out how to
rewrite the query. You cannot reference the outer.deptno when you are two levels
deep.

So what do you do??

          Followup:

correlation names go ONE level down...

ops$tkyte@ORA10G> select *
  2    from dual d
  3   where exists ( select *
  4                    from dual d2
  5                   where d2.dummy = d.dummy )
  6  /

D
-
X

ops$tkyte@ORA10G> select *
  2    from dual d
  3   where exists
  4   ( select *
  5       from (
  6                ( select *
  7                    from dual d2
  8                   where d2.dummy = d.dummy )
  9            )
 10   )
 11  /
                 where d2.dummy = d.dummy )
                                  *
ERROR at line 8:
ORA-00904: "D"."DUMMY": invalid identifier

      Rating:
      5
Thanks!  January 15, 2004
Reviewer:
      A reader

January 14, 2004 -- 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.

      Rating: 4
getting data
      from client side flat files and saved into database  January
      14, 2004
Reviewer:  M Faisal  from Londn, UK

Tom your information is very helpful. Can you clear more. how we will do it when
flat file is in client machine and we get file name and directory through forms
and want to process that file in our database. I have come to know from this
subject we can't use
1- External tables because files are in client machine and we can only give
directory information where the server is installed.
2- UTL_FIle because the same problem as above.

We can use SQL*Loader then we have to install SQL*Loader on each client machine
what if we are using forms over the internet.
I think we can use TEXT_IO forms pl/sql package but i am not sured how good it
is.
Is it possible we can save flat file data in CLOB and save it in our database
and from there we can process it.

Cau you suggest any other method and clear my concept regarding above subject.

          Followup:

you'll use TEXT_IO, but you'll have DIY (do it yourself)

January 14, 2004 Anirban -- Thanks for the question regarding
      "Table Sizing Guidelines", version 8.0.5

      You Asked
Are there readily available guidelines to size database tables, assuming I know
the growth pattern, updates vs inserts vs deletes etc?
Im am interested particularly in deriving PCTFREE and PCTUSED.
A excel like spreadsheet would be ideal, where I input some of the
known metrics and it computes the rest.

Thanks

      and we said...
Really not possible using a spreadsheet or any sort of formula.  Oh, there are
formula's that try but due to the fact that:

o 999999999 takes three times the storage 10000000000000000000000
o indexes can be key compressed
o free space might not be used within a transaction but across transactions it
  would be
o an IOT stores things very differently then a HEAP table.
o (any one of a thousand things)

The only way I know to accurately size a table and related structures is to load
it on your system with data that is REPRESENTATIVE of the actual data you will
be using.  You don't have to load 100% of the data, anywhere from a fraction of
1% to 10% of the data (depending on the table size itself -- the bigger they are
the less you need to sample percentage wise).  Then you analyze the table and
multiply.  That'll give you your initial sizing.

As for pctfree and pctused -- they are a soft function of HOW you use the data
-- there is no spreadsheet possible for them at all, not even a function.  You
set pctfree and pctused based on how the data will be manipulated.  Some very
very generalized rules of thumb might be:

o if you load a table and never update a row and never delete any rows  the
pctfree = 0 pctused = 1.  pctfree = 0 because ROWS NEVER GROW, hence you want to
pack the block all the way up.  do not save any space for update.  pctused = 1
because you really don't care what it is in this case.  You never delete only
add. When the block gets full it will be taken off of the free list (pctfree
hit) and will never go back on (since you never delete)

o if you load a table with lots of rows at a point in time and expect that on
average the rows will DOUBLE in size and you never delete you would set pctfree
at say 55%.  Now 55% of the block will be reserved for those updates.  pctused
is not relevant since once a block is half full, we want to reserve the rest of
the space for updates so set pctused to 1 (block must be 99% empty to be a
candidate for insertion)

o if you have no idea how the rows are going to grow and shrink over time, what
effects deletes will have and so on, you would just leave then default and hope
for the best.

and so on.  Basically, you must take an understand of what pctfree and pctused
do and see how they apply to YOUR data and the way YOU will be using your data.
There are no forumlas for these.

Followup to the comment below regarding "...sometimes cause delays in projects
like ...".

Let me rephrase it this way -- if you cannot come up with a small set of data
that represents some percentage of the entire data set (representative set of
numeric data for a field, varchars that represent how the actual data will be
used and so on) at this point in time -- it does not MATTER what sizing you use,
they will all be meaningless.

If you are going to a customer to tell them to buy such and such an amount of
hardware and you do not know what the data is going to look like, the only thing
you can do is give them the WRONG answer.

If you understand the data (a prerequisite for sizing), all you need is an
Oracle instance somewhere with the appropriate block size you plan on using.
Load it up with sample data and size it.  You do not have to do it on their
hardware, you just need to do it somewhere - and if you cannot do it, you could
not size this with a formula either.  That is because the only reason you could
not do this is due to lack of knowledge of what the data looks like.

followup to comment 4 below

that is exactly the problem -- each DBA has their OWN spreadsheet with different
ways of calculating values based on THEIR installation and their PAST experience
with THEIR applications.

There is no generic solution to this.  If the DBA switched jobs to a different
company with a different set of applications -- they might as well through out
their spreadsheets (they will have to modify them greatly at the very least).
Review &
      Followup

      Rating:
      3
  March 26, 2001
Reviewer:
      A reader

      Rating:
      3
  March 28, 2001
Reviewer:  Eric
      Grancher  from CERN

      Rating: 3
Table
      Sizing  March 30, 2001
Reviewer:  Sudip Sarkar
      from PricewaterHouseCoopers  from India
BEfore loading the dat into the database then to determine the sizing sometimes
cause delays in projects like when the clients wants to order the h/w after the
sizing is done.

      Rating:
      2
  June 15, 2001
Reviewer:  reader

I believe each of DBA has his own set of formula (either spreadsheet or
programs), even though PCTUSED, PCTFREE are variables.

      Rating: 5
From DBA who
      does not use spreadsheet  June 15, 2001
Reviewer:
      Dusan  from MA
I agree that the best way for table sizing is just populating of the table with
sample data.
When using localy managed tablespaces (LMT) with uniform sizes, the whole issue
(after identifying how 'big' the table really is) is simple - take decision to
which tablespace put the table (index). To the 'small', 'medium' or 'big'
tablespace. Thanks procedure show_space and LMT strategy, I calculated very
precisely space needed for tables, indexes and tablespaces, and then rebuilt
production database (saving a lot of GB!). The only inputs were number of rows
and the real size of table or index, investigated by show_space.

      Rating: 5
From DBA who
      does not use spreadsheet  June 15, 2001
Reviewer:
      Dusan  from MA
I agree that the best way for table sizing is just populating of the table with
sample data.
When using localy managed tablespaces (LMT) with uniform sizes, the whole issue
(after identifying how 'big' the table really is) is simple - take decision to
which tablespace put the table (index). To the 'small', 'medium' or 'big'
tablespace. Thanks procedure show_space and LMT strategy, I calculated very
precisely space needed for tables, indexes and tablespaces, and then rebuilt
production database (saving a lot of GB!). The only inputs were number of rows
and the real size of table or index, investigated by show_space.

      Rating: 4
Table
      Sizing  August 30, 2001
Reviewer:  Jim
      Kirsch  from Alexandria, VA USA
Good, clear advice about the importance knowing the data before sizing.

      Rating: 5
hi
      folks  December 30, 2002
Reviewer:
      A reader
Is there any utility or program which will populate sample data, by inspecting
the tables.

Thanks

      Rating: 5
Table
      space  January 14, 2004
Reviewer:  Chandra
      Mohan  from Bangalore
I cannot get the size of a table inside Oracle. Is there any special query is
available ?

          Followup:

hmmm, look at user_segments

January 14, 2004 Sam -- Thanks for the question regarding "Why is
      it said that global indexes are suitable for OLTP apps, and local indexes
      are suitable for DSS i.e. data warehousing apps", version 8.1.7

      You Asked
Tom

Why is it said that global indexes are suitable for OLTP apps, and local indexes
are suitable for DSS i.e. data warehousing apps.

What is the reasoning behind it.

Thank you

      and we said...
do you have my book?  I go into many of these considerations.  Here are some
short extracts:

...
Data Warehousing and Global Indexes

It is my opinion that these two things are mutually exclusive. A data warehouse
implies certain things; large amounts of data coming in and going out, high
probability of a failure of a disk somewhere, and so on. Any data warehouse that
uses a 'sliding window', where old data (say anything over five years old) is
'slid' off one end of a table and new data is added to the other end, would want
to avoid the use of global indexes.
.... (the reason is that partitioning maintenance would invalidate the global
index and you do lots of partition level operations in a data warehouse, you
would have to rebuild them often)

....
OTLP and Global Indexes

An OLTP system is characterized by many small to very small read and write
transactions occurring frequently. Sliding windows of data is not something you
are concerned about in general. Fast access to the row, or couple of rows, you
need is paramount. Data integrity is vital. Availability is very important as
well.

Global indexes make sense in OLTP systems. Table data can only be partitioned by
one key ? one set of columns. You may need to access the data in many different
ways however. You might partition EMPLOYEE data by location. You still need fast
access to EMPLOYEE data however by:

o DEPARTMENT ? Departments are geographically dispersed, there is no
relationship between a department and a location.
o EMPLOYEE_ID ? While an employee ID will determine a location, you don't want
to have to search by EMPLOYEE_ID and LOCATION, hence partition elimination
cannot take place on the index partitions. Also EMPLOYEE_ID by itself must be
unique.
o JOB_TITLE
o And so on...
......

(so, you need global indexes in order to effectively do index partition
elimination to avoid having to scan N index structures)
Review &
      Followup

      Rating: 5
How much the
      local indexes will degrade OLTP performance?  November 01,
      2002
Reviewer:  Leo  from USA
We have an OLTP system (8.1.7) and some tables are over 10G.  We are planning to
migrate it to 9.2 with exp/imp and take this opportunity to partition all the
large tables.  After the migration, we can archive the historical data by moving
the partitions with old data out of the system.  As you said, for OLTP systems,
global indexes are better.  However, with global indexes, archive process will
invalidate all the global indexes.  For local indexes, the archive process will
be much easier.  I am wondering have you benchmarked how much the local indexes
will degrade the OLTP system performance.  If the effect is not significant, we
still prefer local indexes.

          Followup:

it is NOT something you can benchmark *in general*.

Partitioning is a physical design thing.  There could be OLTP apps that benefit
HUGELY from local indexes.  Others that die because of it.  Depends on the app,
depends on how you use it.

The problem is in OLTP type apps -- you generally come at the data from various
different keys over time.  You can only partition by one key really so if you
come at the data via two different keys in different apps -- one will have to
index range scan N index partitions to find its answer (the other can use
partition elimination).  It really depends.

Now, in 9i there is "maintain those global indexes for me" option to the
partition operations.  You might consider looking into that.

      Rating:
      5
  November 01, 2002
Reviewer:  Leo

Thanks.

      Rating:
      2
Clarify  January 14, 2004
Reviewer:
      A reader
*(so, you need global indexes in order to effectively do index partition
elimination to avoid having to scan N index structures) *

In the above you are stating

Have global indexes to facilitate index partition elimination. Are you
suggesting that we have a global index and also a paritioned local index on the
same table.

That means that first I have to hit the global index, then the partitioned local
index and then the table. I'm I right.

          Followup:

nope, not even close.....

you'll hit ONE index.  you hit the global and then the table.....

January 14, 2004 -- 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...

      Rating:
      5
runstats  January 14, 2004
Reviewer:
      A reader
Tom
Read the note about you being out of country - please
respond when you like (it goes without saying)

the runstats utility - is there a version that compares
more than 2 approaches?

          Followup:

not that I have but ....

http://www.amazon.com/exec/obidos/tg/detail/-/1590592174/
does......  (i tech edited that book -- they took runstats and made it "better",
should have code on apress.com)

      Rating: 5
cool-
      thanx!  January 14, 2004
Reviewer:
      A reader
Could not find it on apress download section though
may be they don't have it yet..

So this book is by our own Conor, eh? Tempted to ask your
opinion - will it give us as much value as your
chapter in "effective oracle by design"
gave us (not to put you down, Conor)?
thanx!

          Followup:

sitting next to the guy who is responsible for the downloads for this book --
says they will be there "real soon"  ;)

he does plsql in depth -- with others (chris beck, works for me contributed as
well)... if you want plsql info -- much more than I give in effective oracle.

      Rating: 5
thanx
      Tom!  January 14, 2004
Reviewer:
      A reader
May be I will be the first reviewer on amazon then?;)

      Rating: 5
Certainly a
      very good book...  January 15, 2004
Reviewer:
      Connor McDonald  from Australia
I can say without bias that its a great book :-)

Seriously though, the emphasis for the book is not syntax, but tools and
techniques for maximising the benefits from PL/SQL.  From the back cover:

<marketing mode on>

"If you’re brand new to PL/SQL, then you’ll want to take some time to get
familiar with the language before tackling this book. It’s not for the total
beginner. But once you’re up and running, we believe you’ll find our book an
invaluable guide for ensuring that the PL/SQL solutions you build are robust,
perform well, and are easy to maintain."

</marketing mode on>

Cheers
Connor

      Rating: 5
well, i ordered
      it yesterday , Conor  January 15, 2004
Reviewer:
      A reader
based on your feedbacks on asktom site and the fact that
Tom tech-edited it:) Also ordered "Scaling Oracle8i: Building Highly Scalable
OLTP System Architectures "
based on your web site recommendations...
Went through your presentation on oow from your
web site...Looks great....

      Rating: 5
runstats
        January 15, 2004
Reviewer:  A reader

do you recommend it to be shipped with the product
(assuming we can use it) - I dont see any reasons
to do that since the benchmarks are to be run on an
isolated system (which would be a dev environment.)

Also, in a connection pool env., I guess the table
run_stats would retain the data till a repository bounce
since sessions are maintained in a pool (and don't expire),
correct?

      Rating: 5
ok - no need to
      answer the second q  January 15, 2004
Reviewer:
      A reader
The stats get deleted each time you run it
per the implementation

      Rating:
      5
  January 15, 2004
Reviewer:
      A reader
Congrats Connor!!!

Good to see your book, is this your first book.

Thanks.


Listing

Recent Archives Advanced Search FAQ by Category Most Popular Hot Articles Your Questions [ Listing ] RSS Feed
Subject | Article | Last Updated (East coast time)
tune these max statements with subquery | article only | Jan 15, 2004 03:11 am
kill session and process | article only | Jan 15, 2004 03:13 am
Analytic Function with a CONNECT BY query | article only | Jan 15, 2004 09:05 am
dba_free_space | article only | Jan 15, 2004 10:30 am
execute shell script from stored procedure | article only | Jan 15, 2004 12:10 pm
Can partition key updateable ? | article only | Jan 15, 2004 12:24 pm
XML Generation/Manipulation using SQL | article only | Jan 15, 2004 01:18 pm
Undo tablespace keeps growing | article only | Jan 15, 2004 02:10 pm
Long to Varchar2 conversion.... | article only | Jan 16, 2004 12:58 am
Internals of Roles and Privs. | article only | Jan 16, 2004 01:09 am
Availability Options | article only | Jan 16, 2004 02:49 am
query | article only | Jan 16, 2004 11:07 am
Prepared statement Vs regular statement in JDBC | article only | Jan 16, 2004 11:09 am
Oracle 9i Database Concepts Guide | article only | Jan 16, 2004 11:28 am
Soundex searches | article only | Jan 16, 2004 11:37 am
Library cache latch contention | article only | Jan 17, 2004 01:12 am
anything between create procedure and create any procedure privilege? | article only | Jan 17, 2004 01:13 am
Data may be inserted into complex cviews when the statement affects only one of the tables in the join. | article only | Jan 17, 2004 01:16 am
Transaction | article only | Jan 17, 2004 01:18 am
Redo and Rollback | article only | Jan 18, 2004 12:46 pm
Integrity constraints | article only | Jan 18, 2004 12:57 pm
Stripping special characters | article only | Jan 18, 2004 01:03 pm
Database parameters | article only | Jan 18, 2004 01:18 pm
Writing more than one block - v$filestat info | article only | Jan 18, 2004 01:28 pm
How to test application? | article only | Jan 18, 2004 01:30 pm
Listener questions | article only | Jan 18, 2004 02:16 pm
Oracle across the OS platforms | article only | Jan 18, 2004 02:44 pm
before & after triggers and jre version | article only | Jan 19, 2004 09:00 am
How to re-open a closed database without shutdown DB ? | article only | Jan 19, 2004 09:01 am
Space Estimation For Schema Objects | article only | Jan 19, 2004 09:26 am
redo for inserting one at a time vs. many at a time | article only | Jan 19, 2004 09:28 am
query with like '...._....' where _ is not a wildcard | article only | Jan 19, 2004 09:31 am
Database Questions | article only | Jan 19, 2004 09:32 am
Returning CLOB from PL/SQL Function | article only | Jan 19, 2004 09:34 am
trouble justifying temp space | article only | Jan 19, 2004 09:53 am
Regd Your article in Oracle Magazine.. | article only | Jan 19, 2004 09:55 am
User Password | article only | Jan 19, 2004 11:01 am
unix login and security | article only | Jan 19, 2004 11:03 am
How can we design the big database. e.g. Tera byte Database | article only | Jan 19, 2004 03:55 pm
No Exection Generated in 10046 Event Trace | article only | Jan 20, 2004 06:57 am
lob storage | article only | Jan 20, 2004 06:58 am
Select Min And Max | article only | Jan 20, 2004 07:03 am
LogMiner | article only | Jan 20, 2004 07:09 am
Relational VS Object Oriented Database Design | article only | Jan 20, 2004 07:13 am
Oracle Internals | article only | Jan 20, 2004 07:14 am
cumulative sal | article only | Jan 20, 2004 08:16 am
Percent of Running Total | article only | Jan 20, 2004 08:17 am
Shared Pool free size | article only | Jan 20, 2004 09:11 am
Database memory set to very large | article only | Jan 20, 2004 09:21 am
Why "application server" ? | article only | Jan 20, 2004 01:55 pm
how to check a record's initialization | article only | Jan 20, 2004 02:04 pm
Cursor sharing, bind variables, histograms and data warehousing | article only | Jan 20, 2004 03:45 pm
Using Rebuild parallel option on large table index with 6 CPU's | article only | Jan 20, 2004 10:03 pm
PL/SQL questions | article only | Jan 20, 2004 10:04 pm
encrypt database username password & global variables & log4plsql | article only | Jan 21, 2004 06:24 am
Limiting the number of returned rows | article only | Jan 21, 2004 06:35 am
Host Command Problem in Forms6i (Forms 6.0.8.8.0 Ver) | article only | Jan 21, 2004 06:41 am
BIND VARIABLES&HOST VARIABLES | article only | Jan 21, 2004 09:36 am
Dynamic Dynamic SQL | article only | Jan 21, 2004 09:38 am
SDU,TDU etc | article only | Jan 21, 2004 03:10 pm
usage of procedure/function in create view/derived table | article only | Jan 21, 2004 03:24 pm
Regarding & and && | article only | Jan 21, 2004 05:59 pm
row(s) 1 - 62 of 62
January 15, 2004 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

Rating: 5
Table Join Order in From List January 14, 2004
Reviewer: A reader
Hi Tom,

I was asked by our DBA to list the bigger tables first in the FROM list and he
said although Oracle doesn't rely on the order in the list to decide the order
to join the tables, sometime it may be easy to tune the statements using ORDERED
hint if the bigger tables are listed first.

How reasonable does this sound to you?

Followup:
sounds really *wrong* ;)  on a couple of levels

a) you shouldn't be using ordered hints in practice
b) the size of the table is simply not relevant.  the size of the result set
returned by the table is relevant
c) you generally want the smaller tables to drive (generally, not always, but
generally)

consider:

big_table@ORA9IR2> /*
DOC>drop table t;
DOC>create table t ( x varchar2(30) );
DOC>insert into t values ( 'OWNER' );
DOC>commit;
DOC>analyze table t compute statistics;
DOC>*/
big_table@ORA9IR2>
big_table@ORA9IR2> select count(*) from t;

  COUNT(*)
----------
         1

big_table@ORA9IR2> select count(*) from big_table;

  COUNT(*)
----------
   1000000

big_table@ORA9IR2> set autotrace on
big_table@ORA9IR2> select t.x, big_table.created
  2    from big_table, t
  3   where t.x = big_table.owner
  4  /

X                              CREATED
------------------------------ ---------
OWNER                          12-MAY-02

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1353 Card=35701 Bytes=714020)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=1351 Card=35701
Bytes=535515)
   2    1     NESTED LOOPS (Cost=1353 Card=35701 Bytes=714020)
   3    2       TABLE ACCESS (FULL) OF 'T' (Cost=2 Card=1 Bytes=5)
   4    2       INDEX (RANGE SCAN) OF 'BIG_TABLE_OWNER_IDX' (NON-UNIQUE)
(Cost=178 Card=35701)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          8  consistent gets
          0  physical reads
          0  redo size
        438  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@ORA9IR2>
big_table@ORA9IR2> select /*+ ORDERED */ t.x, big_table.created
  2    from big_table, t
  3   where t.x = big_table.owner
  4  /

X                              CREATED
------------------------------ ---------
OWNER                          12-MAY-02

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2043 Card=35701 Bytes=714020)
   1    0   HASH JOIN (Cost=2043 Card=35701 Bytes=714020)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1379 Card=999620
Bytes=14994300)
   3    1     TABLE ACCESS (FULL) OF 'T' (Cost=2 Card=1 Bytes=5)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      14343  consistent gets
      15484  physical reads
        120  redo size
        438  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@ORA9IR2>
big_table@ORA9IR2> set autotrace off

hmmm, big table first not so good....

maybe they are thinking of the RBO -- not the CBO with the "big tables first"
(implying "small tables last")

but in any case, don't list the tables in any order other than one that pleases
you as you code it.

Rating: 5
re: Table Join Order (Continuing) January 14, 2004
Reviewer: A reader
Hi Tom,

Also please explain the table join order mechenism for ANSI joins and if the
ORDERED hint applies to ANSI join as well.

Thanks a bunch.

Followup:
the join order mechanism for ansi joins is identical to "old fashioned" joins.
the optimizer decides.

and yes, you get the same bad results as above using:

big_table@ORA9IR2> select /*+ ORDERED */ t.x, big_table.created
  2    from big_table join t on (t.x = big_table.owner)
  3  /

X                              CREATED
------------------------------ ---------
OWNER                          12-MAY-02

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2043 Card=35701 Bytes=714020)
   1    0   HASH JOIN (Cost=2043 Card=35701 Bytes=714020)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1379 Card=999620
Bytes=14994300)
   3    1     TABLE ACCESS (FULL) OF 'T' (Cost=2 Card=1 Bytes=5)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      14341  consistent gets
      15502  physical reads
          0  redo size
        438  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

January 15, 2004 -- 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.

      Rating: 4
Session Killed
      status don't go away   January 14, 2004
Reviewer:
      kumar  from Escondido
I have tested the session killed status scenarios carefully for the entire day
observation. Also tested the V$transaction table for the data in USED_UBLK
field. That was showing no records and still the staus of that user was showing
killed. When I close that connection (SQLPLUS) then only the list was disappeard
from v$session.

So this means even though it is killed the session wasn't released the resources
or the UGA+PGA allocation of that session back to the OS, Am I correct Tom?

          Followup:

the OS takes care of people who don't use memory.  it pages it out.  so the
memory allocated by that resource isn't really "relevant" here -- it wasn't real
memory unless you had so much free memory that it could afford to let it sit
idle.

but yes, the process was there -- it was waiting for the client to send a
message to it so it could gracefully say "so sorry, you've been killed, goodbye"
instead of the client just reporting "ora-3113 end of file on communication
channel" (which would tend to increase support calls by 1,000%)

January 15, 2004 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 ;)

      Rating: 5
what is wrong
      with this query  January 13, 2004
Reviewer:
      lizhuohua  from China
hi,tom look this

create table t(id number,pid number,name varchar2(10));
insert into t values(1,0,'root');
insert into t values(2,1,'child1');
insert into t values(3,2,'leaf1');
insert into t values(4,2,'leaf2');
insert into t values(5,1,'child2');
insert into t values(6,5,'leaf3');
insert into t values(7,5,'leaf4');
commit;

SQL>select * from t;

        ID        PID NAME
---------- ---------- ----------
         1          0 root
         2          1 child1
         3          2 leaf1
         4          2 leaf2
         5          1 child2
         6          5 leaf3
         7          5 leaf4

SQL>select id,name,pid,prior name pname
  2  from t
  3  start with id=1
  4  connect by prior id=pid;

        ID NAME              PID PNAME
---------- ---------- ---------- ----------
         1 root                0
         2 child1              1 root
         3 leaf1               2 child1
         4 leaf2               2 child1
         5 child2              1 root
         6 leaf3               5 child2
         7 leaf4               5 child2

SQL> select id,name,pid,prior name pname,
  2   row_number() over(partition by pid order by id) seq
  3   from t
  4   start with id=1
  5   connect by prior id=pid;

        ID NAME              PID PNAME             SEQ
---------- ---------- ---------- ---------- ----------
         1 root                0                     1
         2 child1              1                     1
         5 child2              1                     2
         3 leaf1               2                     1
         4 leaf2               2                     2
         6 leaf3               5                     1
         7 leaf4               5                     2
could you explain why the pname is null at last query?
Thanks.

          Followup:

looks like a "product issue", this works:

select a.*,
  row_number() over(partition by pid order by id) seq
from (
 select id,name,pid,prior name pname
  from t
  start with id=1
  connect by prior id=pid )a
/

      Rating:
      5
Thanks  January 14, 2004
Reviewer:
      A reader
Tom,
Thanks you for the review.I have another question about
analytic function, Why ORDER BY clause in over() affect the order of the
result?(Sorry,my English is very poor;)  )

          Followup:

it sorts stuff..

      Rating: 5
Analytic
      Function with a CONNECT BY query  January 15, 2004

Reviewer:  lizhuohua  from China
Hi,tom
  I think that Oracle processes the analytic function after CONNECT BY clause
,then there is no "prior name".
That's why the pname is null.
What do you think about it?
Thanks.
lizh.

          Followup:

but prior name should be evaulated before the analytic, so I'm still thinking
"product issue"

I've filed bug 3377810 against this.

January 15, 2004 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

      Rating:
      4
dba_free_space  January 14, 2004
Reviewer:
      reader
My understanding is that dba_free_space uses uet$ and fet$ to get info about
free space in tablespace. If so, is dba_free_space still used to obtain free
space info of locally managed tablespaces? Thanks.

          Followup:

uet$ and fet$ are used for dictionary managed -- the bitmap headers for lmts are
in the datafiles

the views are rewritten for lmts to include that fact -- just select text from
all_views where view_name = 'DBA_FREE_SPACE' to see the changes.

      Rating:
      5
dba_free_space  January 15, 2004
Reviewer:
      reader
For LMTs, still the dba_free_space can be used to get the free space info? Is
there another view for LMTs? Does one have to query the base tables for LMTs?
Thanks.

          Followup:

look up one review there -- asked and answered.

January 15, 2004 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.

      Rating: 5
A little
      precision  January 15, 2004
Reviewer:  Jean
      Boulé  from Québec,Canada
Hi,

  I would like to know what would be the intelligent way of doing it.

If I have Oracle 9i and I want to execute a cShell. Do I use JAva or do I use a
C program with dbms_pipe.

Thanks

          Followup:

i would probably use a java stored procedure

unless

i had to call this from a short lived session and i had to call this often from
short lived sessions, then I would use an external procedure (not a pipe
server).

January 15, 2004 madhusudhana rao -- Thanks for the question
      regarding "Can partition key updateable ? ", version Oracle 8.0.3

      You Asked
hi,
  Is it possible to update a partition key in the above mention
version or higher.

Regds.
Madhusudhana Rao.Puvulla

      and we said...
It is possible to update a partition key in all releases -- what
is not possible to do prior to 8i is to update the partition key
in such a fashion as to cause it to move from partition to
partition.  For example:

ops$tkyte@8.0> CREATE TABLE partitioned
  2  ( x int,
  3    y int,
  4    z DATE
  5  )
  6  PARTITION BY RANGE (z)
  7  (
  8  PARTITION part_1 VALUES LESS
           THAN(to_date('01-jan-1995','dd-mon-yyyy')),
  9  PARTITION part_2 VALUES LESS
           THAN(to_date('01-jan-1996','dd-mon-yyyy'))
 10  )
 11  /

Table created.

ops$tkyte@8.0>
ops$tkyte@8.0> insert into partitioned values
  2  ( 1, 1, to_date('01-jan-1994','dd-mon-yyyy') );

1 row created.

ops$tkyte@8.0>
ops$tkyte@8.0> insert into partitioned values
  2  ( 2, 1, to_date('01-mar-1995','dd-mon-yyyy') );

1 row created.

ops$tkyte@8.0>
ops$tkyte@8.0> commit;

Commit complete.

ops$tkyte@8.0>
ops$tkyte@8.0> update partitioned set
  2           z = to_date('01-jan-1993')
               where z = to_date('01-jan-1994')
  3  /

1 row updated.

That shows we CAN update a partition key in 8.0

ops$tkyte@8.0> rollback;

Rollback complete.

ops$tkyte@8.0>
ops$tkyte@8.0> update partitioned set
  2  z = decode( x, 1, to_date('01-mar-1995','dd-mon-yyyy'),
  3              2, to_date('01-jan-1994','dd-mon-yyyy') )
  4  /
update partitioned set
       *
ERROR at line 1:
ORA-14402: updating partition key column would cause a partition
change

But we cannot have it migrate.  Now, in Oracle8i, release 8.1
we can:

ops$tkyte@8i> CREATE TABLE partitioned
  2  ( x int,
  3    y int,
  4    z DATE
  5  )
  6  PARTITION BY RANGE (z)
  7  (
  8  PARTITION part_1 VALUES LESS
         THAN(to_date('01-jan-1995','dd-mon-yyyy')),
  9  PARTITION part_2 VALUES LESS
         THAN(to_date('01-jan-1996','dd-mon-yyyy'))
 10  )
 11  ENABLE ROW MOVEMENT
 12  /

Table created.

Enable row movement will allow us to update a partition key
and have it move from partition to partition

ops$tkyte@8i>
ops$tkyte@8i> insert into partitioned values
  2  ( 1, 1, to_date('01-jan-1994','dd-mon-yyyy') );

1 row created.

ops$tkyte@8i>
ops$tkyte@8i> insert into partitioned values
  2  ( 2, 1, to_date('01-mar-1995','dd-mon-yyyy') );

1 row created.

ops$tkyte@8i>
ops$tkyte@8i> commit;

Commit complete.

ops$tkyte@8i>
ops$tkyte@8i> select rowid, a.* from partitioned a;

ROWID                       X          Y Z
------------------ ---------- ---------- ---------
AAAWBlAADAAAIfKAAA          1          1 01-JAN-94
AAAWBmAADAAAIgKAAA          2          1 01-MAR-95

ops$tkyte@8i> update partitioned set
  2  z = decode( x, 1, to_date('01-mar-1995','dd-mon-yyyy'),
  3              2, to_date('01-jan-1994','dd-mon-yyyy') )
  4  /

2 rows updated.

ops$tkyte@8i> select rowid, a.* from partitioned a;

ROWID                       X          Y Z
------------------ ---------- ---------- ---------
AAAWBlAADAAAIfKAAB          2          1 01-JAN-94
AAAWBmAADAAAIgKAAB          1          1 01-MAR-95

ops$tkyte@8i> commit;

Commit complete.

but -- notice the side effect -- the rows ROWID changed, one
of 2 cases in Oracle8i release 8.1 whereby for the first time a
rowid of a row will change (index organized tables are the other
case -- if you update the primary key)

Review &
      Followup

      Rating:
      5
Excellent!  May 21, 2003
Reviewer:
      Prakash Bala  from Atlanta, GA, USA
Didn't know that there was such a clause in order to enable the row movement

      Rating: 5
Excellent
      Tom!!!  September 23, 2003
Reviewer:
      A reader
Learnt a new thing today and implemented it too!!!

      Rating: 5
is enable row
      movement very costy?  January 13, 2004
Reviewer:
      A reader
hi

is enable row movement very costy? large overhead?

          Followup:

no, it is an "attribute".

only when the row actually "Moves" (due to an update of a partition key that
moves partitions) would it incurr any "extra work" (it is like a delete + insert
instead of just update then)

      Rating:
      3
Indexes  January 14, 2004
Reviewer:
      John French.  from U.S.A
Could you discuss the Impact of Row Movement on Partitions on Indexes - Both
Global and Local.

          Followup:

think "delete plus insert"

that is what it is like....

the rowid changes - it is just like delete + insert.

      Rating:
      1
  January 15, 2004
Reviewer:  John
      French
I was looking for cases where the Indexes become unusable and the reasonn why
they do and which of these Indexes can be used to regenerate/rebuild new
Indexes.

          Followup:

guess you looked at the wrong page then?

ALL unusable indexes can be rebuilt (that seems obvious?)

they become unusable because you did some operation to the base table that did
not maintain the index -- partition operations, alter table moves, "big bulk
operations on the table" in general have the opportunity to do this.

      Rating: 5
Lord of
      Oracle  January 15, 2004
Reviewer:  Reader

Tom

Is there anything that you really don't know in Oracle database ? Will you
kindly lend me the Oracle part of your brain for just 30 days ?

Hats off to you again !

          Followup:

I learn something new about Oracle just about every day myself.

January 15, 2004 Sajid -- Thanks for the question regarding "XML
      Generation/Manipulation using SQL", version 8.1.7

      You Asked
Hi Tom,
After almost 3 months, i got the chance to submit a question. This is first time
i submitting. I have few queries related to XML. I would be grateful to you if
you answer with small example.

a) How to generate XML from a standard table (EMP)?
b) How to save XML generated above in a table?
c) How to display at SQL prompt the same XML output from above query?
d) How to create DTD for the just generated XML?
e) How to use it once it is done?
f) What is XPath in relation to XML?
h) How to read XML file stored in hard disk (e.g C:\sajid\xmldoc)?
i) How to save the same XML in a table?

If possible could you please answer this as well.
I have by mistake deleted few important records in our test database and
commited the changes. Now i want back those records. Could you please tell step
by step how to recover it? Again with simple example.

Thanks a million in advance for all your help.

Regards,

      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:
--
The Oracle XML Developer's Kits (XDK) is fully supported in Oracle8i 8.1.7, so
that's where you should start.  A version of the XDK is normally installed in
the 8.1.7 database, but you can get the latest copy of the FREE APIs on Oracle
TechNet (requires a free registration), here:
http://otn.oracle.com/tech/xml/content.html
 On the left hand side of
the page, there is an index titled "XML".  Click on the first link that reads
"XML Developer's Kits" and you will find links to all the downloads for the
various APIs on the various supported platforms.

You've asked a large number of questions that, in order to answer, could easily
consume a large chapter of a book or even a book on its own if the person
answering was somewhat verbose.  I'll try to give some brief examples along with
links to more information where appropriate.

----------------------------
GENERATING XML FROM ORACLE &
DISPLAYING XML FM SQL*PLUS

Generating XML from the database can be performed in a large number of ways.
We'll look at the things you asked from a SQL/PLSQL approach, with the
understanding that almost everything we discuss can be accomplished using C, C++
and Java as well.  After you've installed the XDK, use this example to generate
some XML from SCOTT.EMP:

SQL> set autoprint on
SQL> set long 100000
SQL> set linesize 100000
SQL> set longchunksize 100000
SQL> var g_clob clob
SQL> declare
  2    l_ctx  dbms_xmlquery.ctxHandle;
  3    l_clob clob;
  4  begin
  5    l_ctx := dbms_xmlquery.newContext('select * from scott.emp');
  6    dbms_lob.createtemporary(:g_clob,true,dbms_lob.session);
  7    :g_clob := dbms_xmlquery.getXml(l_ctx);
  8  end;
  9  /

PL/SQL procedure successfully completed.

G_CLOB
--------------------------------------------------------------------------------
<?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>
 . . .
</ROWSET>

SQL> set linesize 100
SQL> set longchunksize 80
SQL> set autoprint off

Two quick things here.  First, you'll notice I set my linesize, longchunksize
and long settings to 100000.  This was just to get formatting in SQL*Plus.  You
don't need this unless you want to see data in SQL*Plus.

---------------------------------
GENERATING DTDs FROM THE DATABASE

Using the above example, a quick change from DBMS_XMLQUERY.GETXML to
DBMS_XMLQUERY.GETDTD will get your DTD for the query:

CHANGE...
  7    :g_clob := dbms_xmlquery.getXml(l_ctx);
TO...
  7    :g_clob := dbms_xmlquery.getdtd(l_ctx);
RESULTS IN...

G_CLOB
--------------------------------------------------------------------------------
<!DOCTYPE ROWSET [
<!ELEMENT ROWSET (ROW)*>
<!ELEMENT ROW (EMPNO, ENAME?, JOB?, MGR?, HIREDATE?, SAL
?, COMM?, DEPTNO?)>
<!ATTLIST ROW num CDATA #REQUIRED>
<!ELEMENT EMPNO (#PCDATA)>
<!ELEMENT ENAME (#
PCDATA)>
<!ELEMENT JOB (#PCDATA)>
<!ELEMENT MGR (#PCDATA)>
<!ELEMENT HIREDATE (#PCDATA)>
<!ELEMENT S
AL (#PCDATA)>
<!ELEMENT COMM (#PCDATA)>
<!ELEMENT DEPTNO (#PCDATA)>
]>

-------------------------------------------
SAVING XML INTO ORACLE (TABLE OR OTHERWISE)

There are two ways to do this in Oracle8i 8.1.7.  You can either store native
XML in a CLOB/NCLOB/BLOB column, or you can have Oracle serialize XML into a
relational table.  Saving data into a CLOB column is simple using the above
example.  You'd simply insert the resulting CLOB value into a table.  Even
better, you could insert an empty_clob() into a table, returning the value into
a local variable.  Then just write to the local variable i.e. -

SQL> create table xmldocs(
  2    docname varchar2(50),
  3    xmldoc  clob)
  4  /

Table created.

SQL> 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  /

PL/SQL procedure successfully completed.

SQL> col docname for a20
SQL> col xmldoc  for a60
SQL> select *
  2    from xmldocs
  3  /

DOCNAME              XMLDOC
-------------------- -----------------------------------------------------------
EMP TABLE            <?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>
                      . . .
                     </ROWSET>

SQL> set echo off

For serializing this XML into a table (for example, you're getting this XML
document off the Internet and you want to store it in a relational EMP table),
you would again use the XML SQL Utility to accomplish this.  In the following
example, we use the XSU to save an XML document into a table:

SQL> create table myemp
  2  as select *
  3       from scott.emp
  4      where 1 = 0
  5  /
Table created.

SQL> select *
  2    from myemp
  3  /
no rows selected

SQL> declare
  2    l_clob  clob :=
  3  '<?xml version = "1.0"?>
  4  <ROWSET>
  5     <ROW num="1">
  6        <EMPNO>7369</EMPNO>
  7        <ENAME>SEANDILLON</ENAME>
  8        <JOB>DBDUDE</JOB>
  9        <MGR>7902</MGR>
 10        <HIREDATE>12/17/1980 0:0:0</HIREDATE>
 11        <SAL>800</SAL>
 12        <DEPTNO>20</DEPTNO>
 13     </ROW>
 14     <ROW num="2">
 15        <EMPNO>7499</EMPNO>
 16        <ENAME>TOMKYTE</ENAME>
 17        <JOB>DBDUDE</JOB>
 18        <MGR>7698</MGR>
 19        <HIREDATE>2/20/1981 0:0:0</HIREDATE>
 20        <SAL>1600</SAL>
 21        <COMM>300</COMM>
 22        <DEPTNO>30</DEPTNO>
 23     </ROW>
 24     <ROW num="3">
 25        <EMPNO>7521</EMPNO>
 26        <ENAME>MICKEYMOUS</ENAME>
 27        <JOB>RAT</JOB>
 28        <MGR>7698</MGR>
 29        <HIREDATE>2/22/1981 0:0:0</HIREDATE>
 30        <SAL>1250</SAL>
 31        <COMM>500</COMM>
 32        <DEPTNO>30</DEPTNO>
 33     </ROW>
 34  </ROWSET>';
 35
 36    l_ctx   dbms_xmlsave.ctxType;
 37    l_rows  number;
 38  begin
 39    l_ctx := dbms_xmlsave.newContext('MYEMP');
 40    l_rows := dbms_xmlsave.insertxml(l_ctx,l_clob);
 41    dbms_xmlsave.closeContext(l_ctx);
 42    dbms_output.put_line(l_rows || ' rows inserted...');
 43  end insert_xml_emps;
 44  /
3 rows inserted...
PL/SQL procedure successfully completed.

SQL> select *
  2    from myemp
  3  /

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
----------
      7369 SEANDILLON DBDUDE          7902 17-DEC-80        800
  20
      7499 TOMKYTE    DBDUDE          7698 20-FEB-81       1600        300
  30
      7521 MICKEYMOUS RAT             7698 22-FEB-81       1250        500
  30

SQL> drop table myemp
  2  /

Table dropped.

-----------------------------
HOW TO USE IT ONCE IT IS DONE

You name it.  XML has a variety of usages, but don't use XML for XMLs sake.  If
you have a valid requirement for the storage and use of XML, great.  (Such as
you are exchanging XML data with 3d parties, you need an audit trail of the data
transfered, the data you are storing is totally unstructured and does not lend
itself to relational storage, etc.).

<SOAPBOX>
There are many people in the industry that feel using XML is cool, so you should
just do it.  Personally, I think XML is a little overhyped and although useful
as a common denominator in data formats, you should have a firm grip on the
reason you MUST store/use XML before you head down that road.
</SOAPBOX>

----------------------------------------
READING XML DOCUMENTS FROM THE HARD DISK

This can be accomplished in a variety of ways.  In 8.1.7, I used Java Stored
Procedures from inside the database.  You just need to learn some of the Java
APIs for the XDK for creating XML documents, and the Java APIs for reading files
from your disk.

-------------
WHAT IS XPATH

Before I go into the Sean Dillon version of what XPath is, let me refer you to
the abundance of resources on the Internet that can answer that question in a
much more elegant, explanative COMPLETE manner :-).  Oracle has a few links that
I think you would benefit greatly from:

AN XML PRIMER:
http://download-east.oracle.com/otndoc/oracle9i/901_doc/appdev.901/a88895/appaxml.htm#621642
Oracle XML Developer's Kits Primer (don't be deterred from the "9i-esque" of the
XDK.  It's backwards compatible w/ 8.1.7):
http://download-east.oracle.com/otndoc/oracle9i/901_doc/appdev.901/a88894/toc.htm
There are a few books out there I highly recommend for IT folks looking to use
XML with Oracle.  The best yet is Steve Muench's "Building Oracle XML
Applications" which you can find here:
http://www.oreilly.com/catalog/orxmlapp/

------------------------------------------
FINDING DATA YOU DELETED THEN COMMITTED...

Well, in Oracle9i I would tell you to use Flashback Query.  This new feature of
9i allows you to reconstruct the data in your database as it existed some time
in the past.  Since you are still running on 8.1.7 :-), your only options are
recovery fm backup or using Log Miner (recommended route).  DBMS_LOGMINER is a
supplied package in the database that you can use to do things like find out
when a table was 'accidentally' dropped, perform some type of auditing on a
table after the actions have been committed, or 'undo' a transaction (your
case).  I'm not going to get into all the details of using Log Miner here, but
instead will refer you to the documentation.
http://otn.oracle.com/docs/products/oracle9i/doc_library/901_doc/appdev.901/a89852/dbms_log.htm#73729
Keep in mind, this is 9i documentation so there are going to be differences
between 9i and 8.1.7.  You should refer to the documentation that came as a part
of your 8.1.7 database.  Another GREAT place you will find information about Log
Miner is in Tom's (of Ask Tom ;-) book, Expert One-on-One Oracle.
http://www.amazon.com/exec/obidos/ASIN/1861004826
He goes into great depth in practical examples of using Log Miner.

Hope that helps!

Sean Dillon
Senior Technologist, Oracle Corporation
Author "Beginning Oracle Programming"
http://www.amazon.com/exec/obidos/ASIN/186100690X

Review &
      Followup

      Rating: 5
Was of great
      help  May 30, 2002
Reviewer:  Sajid
       from London, United Kingdom
You are simply the best. The only thing i couldnt follow is how to recover data.
Could you please explain the same for Oracle 9i?
Thanks in advance.

          Followup:

you need to read the documentation on log miner.  log miner is a tool that will
read your logs and generate sql statements that can undo an
insert/update/delete.  Read it and you'll understand (the link is right there).

if you are one who won't read docs, search this site for

log miner

I have examples there (but you'll only get part of the total functionality that
way).

As for flashback, search this site for

flashback

and you'll see yet more examples.

      Rating:
      4
  November 28, 2002
Reviewer:  Hamid

That's brilian, but what is the fastest way of pulling your data from a table in
XML format. PL/SQL is not the fastest tool.

          Followup:

why isn't it?  have you shown this?  I mean -- in 9i, the xmldb stuff is almost
100% in C, some in java (none is in plsql, there was only a very early - v8 -
plsql based limited implementation).

the dbms_xml* stuff is just a thin layer on top of C or Java for XML.  If you
are in a stored procedure and need XML output.... I sort of think the plsql api
would be the best bet.

      Rating: 4
Inserting LARGE
      XML files into a database!  January 15, 2003

Reviewer:  Edgar Chupit  from Latvia
Hello Tom and Sean,

This article is great introduction to working with XML in database.
But let's suppose following scenario, we have a third party system and we use
XML to transfer data from that app into a database. Once a month we need to
transfer very large amount of data (Let's 100Mb in XML), I was trying to parse
this file using DOM and got java.lang.OutOfMemoryError exception. Then I read
this article and wanted to test how does XML Utility works with such a large
files and also got java.lang.OutOfMemoryError exception. Maybe there is some
methods to split this data in database into smaller chunks and than parse it and
save. My question is: is it possible and how to parse and insert large XML file
into a database? Thank you.

--

SQL> create table test000 as select * from all_objects;

Table created.

SQL> begin
  2   for i in 0 .. 10 loop
  3      insert into test000 select * from all_objects;
  4   end loop;
  5  end;
  6  /

PL/SQL procedure successfully completed.

SQL> select count(*) from test000;

  COUNT(*)
----------
    245520

SQL> create table result as select * from test000 where 0 = 1;

SQL> set serveroutput on;

SQL> declare
  2      l_ctx  dbms_xmlquery.ctxHandle;
  3      l_clob clob;
  4      l_sctx   dbms_xmlsave.ctxType;
  5      l_rows  number;
  6  begin
  7      l_ctx := dbms_xmlquery.newContext('select * from test000');
  8      dbms_lob.createtemporary(l_clob,true,dbms_lob.session);
  9      l_clob := dbms_xmlquery.getXml(l_ctx);
 10      dbms_output.put_line('CLOB size = '||dbms_lob.getlength(l_clob));
 11
-------------------------------------------------------------------------------
 12      l_sctx := dbms_xmlsave.newContext('RESULT');
 13      l_rows := dbms_xmlsave.insertxml(l_sctx,l_clob);
 14      dbms_xmlsave.closeContext(l_sctx);
 15      dbms_output.put_line(l_rows || ' rows inserted...');
 16  end test0001;
 17  /
CLOB size = 114494290
declare
*
ERROR at line 1:
ORA-29532: Java call terminated by uncaught Java exception:
java.lang.OutOfMemoryError
ORA-06512: at "SYS.DBMS_XMLSAVE", line 65
ORA-06512: at line 13

          Followup:

I asked Sean Dillon to take a look at this one, here's what he had to say:
----------------------

Hi Edgar,

Yes, this is one of the issues you must contend with when dealing with large XML
files, as the DOM parser loads the entire document into memory before you can
parse it.  DOM isn't the only parser in town, however... you can always use SAX
as an alternative, which is generally what the industry recommends for larger
XML files.  Where's the cutoff?  I guess that depends on the parsers, the
complexity of the document, the environment in which you are doing the parsing,
and the resources at your disposal (hardware, mem, cpu, etc.).

While DOM loads the entire XML document into a tree structure in memory, SAX
requires only a single pass through the entire document to parse it.  This cuts
down on your flexibility with an API, but improves performance and substantially
increases the amount of data you are able to process.

For your example above, there's no way to tell the XML SQL Utility to use the
SAX parser, so for a query that returns that much data you will most likely look
into an alternative mechanism for generating your XML (SQLX, hand-generated,
etc).  Based on what you said above, however, I don't think this is necessarily
your problem.  You are trying to load large XML documents into your database
once/month (or so).  I'd encourage you to check out the SAX parser and try using
that instead of DOM.  For more information about the SAX parser, here are some
links:

http://otn.oracle.com/tech/xml/content.html
http://www.saxproject.org/
http://builder.com.com/article.jhtml?id=u00220020527adm01.htm
Steve Meunch, a senior product manager out of Oracle's Headquarters, wrote a
book entitled "Building Oracle XML Applications".  In Chapter 6, pp 227 .. 234,
he shows you a Java class named XMLDocumentSplitter that uses SAX & Java to
break down a large XML document into "bite-sized chunks" that are easier to deal
with in DOM.  Just a thought.  Here's his book:

http://www.amazon.com/exec/obidos/tg/detail/-/1565926919/qid=1042639343/
I hope that helps!

_smd_

      Rating:
      5
XML  January 15, 2003
Reviewer:
      A reader
Do I absolutely have to use java to load/install xml kit ?

 At this point we are not using the java porting of
oracle 8.1.7 (EE)/Linux73. But we do use XML all foreign/external data feed in
in XML format I have to load
the data after removing the XML tags. and generate XML to provide data to other
source after process.

1.) At this point I just see 9.x.x XDK
    will it work with 8.1.7 ?
2.) Can you please clearly tell me how can I install it ?
    (Do I have to install or is it stand alone app. ?  )
    (limitation: I can not use loadjava )

Thanks,

          Followup:

If you want the xml stuff in the db -- you'll be hard pressed NOT to use
loadjava.

      Rating:
      5
XML  January 16, 2003
Reviewer:
      A reader
Hi tom,

  I want to use xml_plsql (XDK for Pl/sql).
I want to do exactly same as Mr. Sean Dillon demonstrated above.

1.) get the xml, strip the xml tag get the data insert only
    data into database
2.) and query the database in such a way (may be using xsu,
    i don't know yet) so that I get the xml back from the
    data base and send it to the client database

 there is no web I don't know what you are saying there.
But, please tell me how can I load and
perform above tasks with out java or
though it is called xml_plsql it is completely depandent on
java and one has to know the java, use the java option to
utilize this feature of oracle other wise not

Thanks,

          Followup:

all of the xml stuff is under the covers in java.  that is what I'm saying.
once upon along time ago there was a "plsql only" one with very very limited
features -- but plsql isn't the right language to parse documents (processing
database data -- nothing better, parsing xml, umm, no).

You do not have to KNOW java -- you just have to have support for it in the
database.

      Rating:
      5
Thanks,  January 16, 2003
Reviewer:
      A reader
Hi Tom,

 Thanks for your reply.
My DBA says that if it requires java to be enabled, I should
do it outside no java in the db as db is to store and retrive the database an
enalbling java =>

1.) overhead
2.) more maintanance
3.) load

Thanks again !!

          Followup:

This is Sean...

While I understand your adminstrator's point of view, there isn't anything that
needs to be maintained here.  There is a touch of overhead when using Java in
the database because you are starting up a Java Virtual Machine, but Java has
its advantages as well.  In this case, Tom hit it on the head when he said the
PL/SQL stuff in the XML Developer's Kits loaded into the database rely on the
Java code under the covers.  That being said... however...  in Oracle9iR2, quite
a bit of the functionality in the XML Developer's Kits has been rewritten in C
and linked into the database.  Now the PL/SQL code in the 9iR2 database supplied
packages is no longer reliant on Java.  Actually, quite a bit of the Java code
that performs XML functionality in the Java supplied packages is written on top
of C libraries under the covers as well, simply for performance.

If you want to avoid loading Java into the database, you can (1) move the
functionality to the app server, (2) use Oracle9i Release 2 which includes more
features than I could possibly describe here... or (3) write it yourself.

Hope that helps!

_smd_

      Rating: 5
Thanks Sean
      !!  January 16, 2003
Reviewer:
      A reader

      Rating: 5
Too many XML
      API's  January 17, 2003
Reviewer:
      A reader  from India
Hi Tom, Sean

I am a PL/SQL programmer using 9iR2. I was going through various Oracle manuals
related to XML and that left me confused. It would be great if you could clarify
a few things -

1. What is the difference between XDK and XML DB.

2. There are a lot of APIs given in XDK and XML DB features as well. There are
different APIs which do the same job. Like for XML generation from PL/SQL we
have some 4 or 5 methods that can be used. How do we chose which one to use

3. You have mentioned that some APIs are now written in C instead of Java, but
the documentation does not mention which (Only exception is dbms_xmlgen). This
way we can avoid the JVM calls.

A general point - Oracle has been introducing some fantastic features, but its
not making it easier to learn them. AskTom is doing a great job, but I think we
need more TOMs who can help us out with this. Oracle magazine is also very good,
but we should have more database technology articles there (It is more j2ee
biased it seems)

Thanks

          Followup:

I asked Sean to take a look at this, here's what he had to say:
-----------------------------

The XDK is the XML Developer's Kit.  It is a collection of APIs, tools &
utilities for performing XML-related functionality.  It runs in the database (as
Java stored procedures & PL/SQL stored procs), in the application server (as
Java classes, Java Beans, C & C++ libraries), or even in a fat client if you're
so inclined.  As you can see above, there are numerous languages supported,
including Java, JavaBeans (not really a language right but cool nonetheless), C,
C++ and my personal favorite PL/SQL :-).  The XDK can be freely downloaded from
the Oracle Technology Network, and installed into any Oracle database provided
it is version 8.1.6 or above.  It costs nothing to use.

XML DB is the collection of XML-related functionality introduced into the Oracle
database in version 9.2.x.x.  Included in this functionality (but not limited
to...) are Java stored procedures, PL/SQL supplied procs, new operators for
generating XML called "SQLX", the XML Repository, the native XML datatype called
XMLType, and more.  This functionality is ONLY natively available in the
database.  The only way to use it from the application server or from a client
machine is through the database.  The Java stored procedures and PL/SQL code in
the database is based on C libraries, providing a theoretical improved
performance through lowered memory consumption and CPU load.

The APIs that are written in C as opposed to Java are in XML DB.  You're right,
the documentation doesn't really talk about which are which, but as a
guideline... any PL/SQL you find in the XML Developers Kits are Java-based.  The
PL/SQL found in XML DB is C-library based.  The XML DB supplied PL/SQL is
usually preceded by DBMS_<packagename>.  If you wanted to figure this out for
yourself you have only to alter your session and set SQL_TRACE to true.  Execute
the functionality in question, then end your session and TKPROF the trace file.
This will tell you if any Java was called under the covers.

I hope that helps!

_smd_
Sean Dillon

      Rating: 4
complex xml
      generation   April 27, 2003
Reviewer:
      reader
Sean Dillon,

I wanted to generate xml from database. My requirments are

1. i should not use JVM
2. less of resource consumption
3. the xml that i need to generate is huge - that caters to 10000 records from 3
tables. its a complex, nested with many tags through which way i should
generated ?
3.Does XML DB supplied PL/SQL supports complex xml generation ?
4. how do you identify XDK's procedures? - i just want to avoid it ! - it uses
my JVM
>(The XML DB supplied PL/SQL is usually preceded by DBMS_<packagename>.)

Thanks

          Followup:

you lose.

#1 precludes me from reading on.   you'll not be doing it in the database.  much
of it is in java, much of it is in C.

the DBMS_ packages -- guess what, a thin layer on top of -- java.  Just like
utl_smtp and other pieces of functionality.  sometimes C, sometimes java.

      Rating: 4
When to use
      which methode????  August 07, 2003
Reviewer:
      Vipin  from Newyork
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
inportant factor.

      Rating: 5
Too many XML
      API's   August 26, 2003
Reviewer:
      A reader

      Rating: 5
Too many XML
      API's   August 26, 2003
Reviewer:
      A reader

      Rating: 5
Getting
      sequence and updating multiple XML files  August 26, 2003

Reviewer:  A reader
Tom,
I have 2 xml files with the data shown below.  (The data in the XML file
corresponds to the data in the emp, dept table s).
Scenario I have
The deptno in the dept table is generated used a sequence number.  I want to
load the data in the 2 xml files I have to the emp/dept table, but the dept
table already has records for depts. 10,20,30 and 40.  I want to change the
deptno values from 10, 20, 30, 40 to 41, 42, 43, 44 respectively in both the XML
files and then insert the data into the emp/dept tables (The values 41, 42, 43,
44 are the values I have got using the sequence number).
How can the DEPTNO value be changed in both the XML files(IDRef!)?

--dept.xml

<?xml version="1.0"?>
<ROWSET>
 <ROW>
  <DEPTNO>10</DEPTNO>
  <DNAME>ACCOUNTING</DNAME>
 </ROW>
 <ROW>
  <DEPTNO>20</DEPTNO>
  <DNAME>RESEARCH</DNAME>
 </ROW>
 <ROW>
  <DEPTNO>30</DEPTNO>
  <DNAME>SALES</DNAME>
 </ROW>
 <ROW>
  <DEPTNO>40</DEPTNO>
  <DNAME>OPERATIONS</DNAME>
 </ROW>
</ROWSET>

--emp.xml

<?xml version="1.0"?>
<ROWSET>
 <ROW>
  <EMPNO>7369</EMPNO>
  <ENAME>SMITH</ENAME>
  <DEPTNO>20</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7499</EMPNO>
  <ENAME>ALLEN</ENAME>
  <DEPTNO>30</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7521</EMPNO>
  <ENAME>WARD</ENAME>
  <DEPTNO>30</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7566</EMPNO>
  <ENAME>JONES</ENAME>
  <DEPTNO>20</DEPTNO>
 </ROW>
 <ROW>

  <EMPNO>7654</EMPNO>
  <ENAME>MARTIN</ENAME>
  <DEPTNO>30</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7698</EMPNO>
  <ENAME>BLAKE</ENAME>
  <DEPTNO>30</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7782</EMPNO>
  <ENAME>CLARK</ENAME>
  <DEPTNO>10</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7788</EMPNO>
  <ENAME>SCOTT</ENAME>
  <DEPTNO>20</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7839</EMPNO>
  <ENAME>KING</ENAME>
  <DEPTNO>10</DEPTNO>

 </ROW>
 <ROW>
  <EMPNO>7844</EMPNO>
  <ENAME>TURNER</ENAME>
  <DEPTNO>30</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7876</EMPNO>
  <ENAME>ADAMS</ENAME>
  <DEPTNO>20</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7900</EMPNO>
  <ENAME>JAMES</ENAME>
  <DEPTNO>30</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7902</EMPNO>
  <ENAME>FORD</ENAME>
  <DEPTNO>20</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7934</EMPNO>

  <ENAME>MILLER</ENAME>
  <DEPTNO>10</DEPTNO>
 </ROW>
</ROWSET>

Thank you

          Followup:

Sean here...

This isn't a hard thing to do.  Your best bet.. honestly, is find-replace or
some sexy sed/awk script to update your XML documents ;-).  If you're bent on
using the database to do this, I would recommend you insert these into some
OTHER tables that are used for inbound XML.  After the inserts of the XML
documents (which would still have 10, 20, 30, 40 for their IDs...), I would use
a stored procedure to simply execute an INSERT INTO DEPT (deptno, dname, etc...)
SELECT DEPTNO + 1, DNAME, etc... FROM XMLDEPT.  Same thing holds true for the
XML EMP table...

We've left the XML functionality and crossed into basic application development
issues.  I guess I'd have to ask "WHY are you trying to do this?  What are you
getting at?"

      Rating: 4
Serialize to
      more than one table (Master-details relationship)  October
      27, 2003
Reviewer:  Victor Jaen  from Panama

Hello! Could you elaborate the example of serializing the XML into a table,
let's say the XML file has data which corresponds to 2 tables (repetitive tags
for a detail table), is it possible?

          Followup:

Sean here...

This is pretty easy to accomplish.  There is no automated XML utility in Oracle
to insert a single XML document into two different tables...  What you could do,
however, is create a join view on the two tables, then write an INSTEAD OF
trigger on the join view.  Insert the XML document into the join view.  The
INSTEAD OF trigger's job would be to insert rows into the appropriate tables
based on the values of the parent key found in each ROWSET of the XML document.

As an example, I have an XML document that looks like SO:

<?xml version = "1.0"?>
<ROWSET>
   <ROW num="1">
      <DEPTNO>10</DEPTNO>
      <DNAME>SALES</DNAME>
      <EMPNO>100</EMPNO>
      <ENAME>MARK JOHNSON</ENAME>
   </ROW>
   <ROW num="2">
      <DEPTNO>20</DEPTNO>
      <DNAME>TECHNOLOGY</DNAME>
      <EMPNO>200</EMPNO>
      <ENAME>TOM KYTE</ENAME>
   </ROW>
   <ROW num="3">
      <DEPTNO>20</DEPTNO>
      <DNAME>TECHNOLOGY</DNAME>
      <EMPNO>300</EMPNO>
      <ENAME>SEAN DILLON</ENAME>
   </ROW>
</ROWSET>

So you can see... the department data and the employee data co-mingled.  We want
to normalize this into two tables... so here's what I'd do:

-----------------------------
system@SLAP> create table dept (
  2    deptno   number
  3      primary key,
  4    dname    varchar2(30));
Table created.

system@SLAP> create table emp (
  2    empno    number
  3      primary key,
  4    deptno   number,
  5    ename    varchar2(30));
Table created.

system@SLAP> create view deptemp as
  2  select d.deptno, d.dname, e.empno, e.ename
  3    from dept d, emp e
  4   where d.deptno = e.empno;
View created.

system@SLAP> create or replace trigger deptemp_ioifer
  2  instead of insert on deptemp
  3  declare
  4  begin
  5    begin
  6      insert into dept (deptno, dname)
  7      values (:new.deptno, :new.dname);
  8    exception
  9      when DUP_VAL_ON_INDEX then
 10        update dept
 11           set dname = :new.dname
 12         where deptno = :new.deptno;
 13    end;
 14    --
 15    insert into emp (empno, deptno, ename)
 16    values (:new.empno, :new.deptno, :new.ename);
 17  end;
 18  /
Trigger created.

system@SLAP> declare
  2    l_clob  clob := '<?xml version = "1.0"?>
  3  <ROWSET>
  4     <ROW num="1">
  5        <DEPTNO>10</DEPTNO>
  6        <DNAME>SALES</DNAME>
  7        <EMPNO>100</EMPNO>
  8        <ENAME>MARK JOHNSON</ENAME>
  9     </ROW>
 10     <ROW num="2">
 11        <DEPTNO>20</DEPTNO>
 12        <DNAME>TECHNOLOGY</DNAME>
 13        <EMPNO>200</EMPNO>
 14        <ENAME>TOM KYTE</ENAME>
 15     </ROW>
 16     <ROW num="3">
 17        <DEPTNO>20</DEPTNO>
 18        <DNAME>TECHNOLOGY</DNAME>
 19        <EMPNO>300</EMPNO>
 20        <ENAME>SEAN DILLON</ENAME>
 21     </ROW>
 22  </ROWSET>';
 23
 24    l_ctx   dbms_xmlsave.ctxType;
 25    l_rows  number;
 26  begin
 27    l_ctx := dbms_xmlsave.newContext('DEPTEMP');
 28    l_rows := dbms_xmlsave.insertxml(l_ctx,l_clob);
 29    dbms_xmlsave.closeContext(l_ctx);
 30    dbms_output.put_line(l_rows || ' rows inserted...');
 31  end insert_xml_emps;
 32  /
PL/SQL procedure successfully completed.

system@SLAP> select * from dept;

    DEPTNO DNAME
---------- ------------------------------
        10 SALES
        20 TECHNOLOGY

system@SLAP> select * from emp;

     EMPNO     DEPTNO ENAME
---------- ---------- ------------------------------
       100         10 MARK JOHNSON
       200         20 TOM KYTE
       300         20 SEAN DILLON

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

...and there you have it.  Hope that helps!

_smd_

      Rating: 4
What about
      repetitive tags?  October 27, 2003
Reviewer:
      Victor Jaen  from Panama.
How can I handle this? One row for the department table and 3 rows for the
employee table in the same XML row.
<?xml version = "1.0"?>
<ROWSET>
   <ROW num="1">
      <DEPTNO>10</DEPTNO>
      <DNAME>SALES</DNAME>
      <EMPLOYEE>
          <EMPNO>100</EMPNO>
          <ENAME>MARK JOHNSON</ENAME>
      </EMPLOYEE>
      <EMPLOYEE>
          <EMPNO>200</EMPNO>
          <ENAME>VICTOR JAEN</ENAME>
      </EMPLOYEE>
      <EMPLOYEE>
          <EMPNO>300</EMPNO>
          <ENAME>JHON SMITH</ENAME>
      </EMPLOYEE>
   </ROW>
</ROWSET>

Thanks a lot!

          Followup:

  1* select dbms_xmlgen.getxml( 'select deptno, dname, cursor( select empno,
ename from emp where emp.deptno = dept.deptno ) employee from dept where deptno
= 10' ) from dual
scott@ORA920> /

DBMS_XMLGEN.GETXML('SELECTDEPTNO,DNAME,CURSOR(SELECTEMPNO,ENAMEFROMEMPWHEREEMP.D
--------------------------------------------------------------------------------
<?xml version="1.0"?>
<ROWSET>
 <ROW>
  <DEPTNO>10</DEPTNO>
  <DNAME>ACCOUNTING</DNAME>
  <EMPLOYEE>
   <EMPLOYEE_ROW>
    <EMPNO>7782</EMPNO>
    <ENAME>CLARK</ENAME>
   </EMPLOYEE_ROW>
   <EMPLOYEE_ROW>
    <EMPNO>7839</EMPNO>
    <ENAME>KING</ENAME>
   </EMPLOYEE_ROW>
   <EMPLOYEE_ROW>
    <EMPNO>7934</EMPNO>
    <ENAME>MILLER</ENAME>
   </EMPLOYEE_ROW>
  </EMPLOYEE>
 </ROW>
</ROWSET>

      Rating: 5
And what about
      xsl...  October 28, 2003
Reviewer:  Mirjana
       from Barcelona
Hi Tom...
Just one question:
DBMS_XMLQUERY has a procedure SETXSLT that applies XSL to an XML document. Is
there similar procedure for DBMS_XMLGEN package? How can an XSL be applied to
XML document using 9iR2 specific features (I'm trying to avoid using
DMBMS_XMLQUERY)?

Thank you...

          Followup:

what is the reasoning for avoiding dbms_xmlquery?

      Rating: 4
Asking
      again  October 28, 2003
Reviewer:  Victor
      Jaen  from Panama
I receive a XML file with data for departments and
employees tables in the same XML rowsets, how can I insert the data to those
tables using dbms_xmlsave.insertxml? any other way?
<?xml version = "1.0"?>
<ROWSET>
   <ROW num="1">
      <DEPTNO>10</DEPTNO>
      <DNAME>SALES</DNAME>
      <EMPLOYEE>
          <EMPNO>100</EMPNO>
          <ENAME>MARK JOHNSON</ENAME>
      </EMPLOYEE>
      <EMPLOYEE>
          <EMPNO>200</EMPNO>
          <ENAME>VICTOR JAEN</ENAME>
      </EMPLOYEE>
      <EMPLOYEE>
          <EMPNO>300</EMPNO>
          <ENAME>JHON SMITH</ENAME>
      </EMPLOYEE>
   </ROW>
</ROWSET>

Thanks!

          Followup:

http://otn.oracle.com/oramag/oracle/03-jul/o43xml.html
is another way.

check out
Article-ID:         <Note:227476.1>

on metalink.oracle.com for a dbms_xmlsave example

      Rating:
      4
XSL...  October 28, 2003
Reviewer:
      Mirjana  from Barcelona
Hi Tom,
I read in

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:11650482108651
that DBMS_XMLGEN "..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)..."
I'm not really trying to avioid DBMS_XMLQUERY, rather just trying to make the
same thing working
with DBMS_XMLGEN if possible (as it offers faster speeds).

Thanks...

          Followup:

well, both packages are documented in the supplied packages guide.  I don't see
anything referencing xsl in dbms_xmlgen -- it is a rather mean/lean package.

      Rating:
      4
xsl..  October 28, 2003
Reviewer:
      Mirjana  from Barcelona
Yes...I saw the supplied packages documentation also, but I thought you might
know something more about it.

Too bad ... It could be really useful having something like SETXSLT for
DBMS_XMLGEN.

Thanks anyway,

Regards.

      Rating: 3
additional
      question  January 05, 2004
Reviewer:  mark
      griffin  from Marshall Space Flight Center Huntsville Alabama

I have a xsd schema(hopefully correct terminology) that i need to:
load into my database

map xsd to series of data columns in my database(entites from xsd are completely
different in terms of my database attributes)

once mapped then pull data from my database and output into xml format

I've already read a lot of the Oracle XML documentation. However, either I
missed the example/documentation on how to perform this action or its not there.

Could you please assist?

thank you!

          Followup:

Hi Mark,

Using XML DB, it's relatively simple to register an XML Schema and have Oracle
create the storage architecture "under the covers" for XML documents you load
which validate against said schema.  Alternatively, you can use XML DB Schema
annotations to tell Oracle how to architect the storage... the table names to be
created, the column names, the data types, various parameters such as max length
of fields, etc.  It sounds like you're trying to use XML DB to load your XML
documents into an EXISTING set of tables and columns.  I.e., you have a table
where you have rows and columns already, and you want to put your XML document
into XML DB, and have it parsed and loaded into that table.  This is not
available out of the box, and XML DB is unable to use this type of storage
architecture.  At this time, the storage for XML Schema-related XML documents in
XML DB is dedicated to those documents, and must be created by the database
through the XML Schema registration process.

How to do all of this is available in the XML Database Developer's Guide, XML
DB.  Chapters 4 and 5 are where you should spend your time if this is what you
need.

Alternatively, if you'd like to maintain your own tables and have your XML
documents parsed and loaded into them... you can use the XML Developer's Kits.
This is a programmatic interface (you'll be writing code to accomplish your
task) that lets you specify an XML document and have Oracle load it into a
table.  The only catch to this is the document must be in a specific structure.
I'd first recommend you search through Ask Tom for examples, search on "XDK"
and/or "<ROWSET>".  The XDK has a PL/SQL, C, C++, Java and JavaBeans interface
so whatever your programming skills, you should be covered.

When you want to generate XML from your existing relational tables (and you're
not using XML DB), I'd recommend you use SQLX.  Again, there are plenty of
examples of how to do this throughout AskTom.  Just search on SQLX or look here:

http://asktom.oracle.com/~sdillon/rss.html
 I wrote an overview of building an RSS interface into AskTom, and there is
an example of how to generate XML from relational tables (this is me generating
the RSS from AskTom's relational tables).  Hope that helps.

_smd_

** Sean Dillon **
** Principal Technologist, Oracle Corporation **
** Author, Beginning Oracle Programming **
**
http://www.amazon.com/exec/obidos/tg/detail/-/186100690X
**

      Rating: 3
follow up to
      additional question   January 07, 2004
Reviewer:
      Mark Griffin  from Huntsville AL
Hello again,

First thing is I just submitted brand new question covering this same topic so
please ignore question. Thanks and sorry for confusion.

To follow up my previous question.

My .xsd does not contain data at all. It simply contains series of data elements
that need to be pulled from my database:
ex of .xsd:
<?xml version="1.0" encoding="UTF-8"?>
<!-- edited with XMLSPY v2004 rel. 2 U (
http://www.xmlspy.com/
 by Wang (bline express) -->
<!-- edited with XML Spy v4.4 U (
http://www.xmlspy.com/
 by Nancy Rackley (B-Line Express) -->
<xs:schema xmlns:xs="
http://www.w3.org/2001/XMLSchema
elementFormDefault="qualified"
attributeFormDefault="unqualified">
    <xs:element name="INVENTORY">
        <xs:annotation>
            <xs:documentation>XML SCHEMA FOR INVENTORY
DATABASE</xs:documentation>
        </xs:annotation>
        <xs:complexType>
            <xs:choice maxOccurs="unbounded">
                <xs:element name="TASK">
                    <xs:complexType>
                        <xs:all minOccurs="0">
                            <xs:element name="TASK_NAME">
                                <xs:simpleType>
                                    <xs:restriction base="xs:string">
                                        <xs:minLength value="1"/>
                                        <xs:maxLength value="4000"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="FUNDING" minOccurs="0">

Now i need to take this .xsd and either load it into my database or utilize it
so that I can pull data from my database. The data that I pull will be spit back
out into this same .xsd but as data. The rub is that these .xsd elements dont
directly map to my database. Logically I know what the mapping is.    The piece
that Im struggling with is how to perform the mapping programatically.

It's at this point I dont find what Im trying to accomplish in Oracle
documentation. It may be that its there but I cant find it.

Thank you for your assistance.

      Rating: 3
another
      follow-up question  January 09, 2004
Reviewer:
      Mark Griffin  from Huntsville Alabama
I guess the question I have at this point is
Oracle XML DB Foldering what I need to accomplish my goal?

          Followup:

Hi Mark, Sean here again.  I'll answer both the previous "feedback"'s in this
followup.

Yes, I understand the XSD vs XML (document formatting vs document content)
thing.  I also understand your requirements, and now it's easy to understand why
you've been struggling.

There is no automated tool for accomplishing what you're trying to accomplish.
Oracle doesn't have a tool to map an XML document into your custom-built
relational (obj-relational possibly, understood) data model.  The reason there's
not a tool for doing this is because Oracle built this capability into XML DB...
but the limitation there is that Oracle must create and manage the underlying
relational storage.  The steps involved there would be..

1.  You register your XSD with XML DB through DBMS_XMLSCHEMA.
2.  Oracle, in response to the XSD registration, creates the table(s) to store
the underlying CONTENT that is read from your XML files.
3.  You load an XML document into Oracle that references the XSD, Oracle stores
that content in the underlying storage architecture created in (2), above.
4.  You can "load" documents into the XML Repository (which is XML Foldering, as
you asked above)... or you can create XMLType columns/tables that reference the
registered XSD... which will also then utilize this underlying storage.
5.  You can then access the data as object-relational rows (by accessing the
underlying storage) or as XML documents (by using the XMLType methods or through
the XML Repository access methods).

What you're trying to do, however, is NOT THIS.  From what you've stated above,
you have an existing relational data model you'd like to load XML into.  You MAY
want to extract the XML data back out of this model as XML.  To accomplish this,
you need to PARSE your XML documents and programatically load the rows yourself.
 Oracle supports the DOM and SAX parsers, so you're covered for the two
different types of parsers.  For documentation on the parsers, check the XML
Developer's Kits (XDKs) and the Supplied Packages Reference.  You can do the
parsing in PL/SQL or Java (we support both inside Oracle).

Bottom line, no XML DB Foldering is not what you need to accomplish your goal.
You need to learn how to use the XML Developer's Kits and/or the supplied
packages (DBMS_XMLPARSER, DBMS_XMLDOM, associated Java classes if you build
using Java).  Essentially, in the code YOU WRITE, you'll parse each document and
insert the rows into your appropriate table(s).

_smd_

      Rating: 4
performace
      ?  January 12, 2004
Reviewer:  Hannibal
       from styria/austria/europe
hi tom,

i am looking at the xml-feaures of oracle with interest. I did some test but the
result
is a little bit confusing...

here are my create table statement + the way i put some simply data in it ...

create table t ( x integer, y varchar2(32));

begin
for v_int in 1 .. 25
loop
insert into t (  x, y ) values ( v_int , 'v'||to_char(v_int));
end loop;
end;

jom@NINA.US.ORACLE.COM> select * from t;

         X Y
---------- --------------------------------
         1 v1
         2 v2
         3 v3
         4 v4
         5 v5
         6 v6
         7 v7
         8 v8
         9 v9
        10 v10
        11 v11
        12 v12
        13 v13
        14 v14
        15 v15
        16 v16
        17 v17
        18 v18
        19 v19
        20 v20
        21 v21
        22 v22
        23 v23
        24 v24
        25 v25

25 rows selected.

then i run my test (you are surely familiar with :-))

jom@NINA.US.ORACLE.COM> r
  1  declare
  2  type t_xmltypes is table of sys.xmltype index by binary_integer;
  3  v_xml_res t_xmltypes;
  4  cursor cur_res
  5  is
  6  select x , y from t;
  7  type t_tab_str is table of VARCHAR2(128) index by binary_integer;
  8  v_tab_str t_tab_str;
  9  v_i integer default 0;
 10  begin
 11  runstats_pkg.rs_start;
 12  for v_int in 1 .. 100
 13  loop
 14  select xmlelement("t",xmlattributes(x as "id"),xmlforest(y)) bulk collect
into v_xml_res from t;
 15  end loop;
 16  runstats_pkg.rs_middle;
 17  for v_int in 1 .. 100
 18  loop

 19  for rec_res in cur_res
 20  loop
 21  v_i := v_i +1;
 22  v_tab_str(v_i) := '<t id="'||to_char(rec_res.x)||'"
<y>'||rec_res.y||'</y></t>';
 23  end loop;
 24  v_i := 0;
 25  end loop;
 26  runstats_pkg.rs_stop(500);
 27* end;
Run1 ran in 42 hsecs
Run2 ran in 7 hsecs
run 1 ran in 600% of the time

Name                                Run1      Run2      Diff
STAT...buffer is not pinned co       100     2,500     2,400
STAT...no work - consistent re       100     2,500     2,400
STAT...table scan blocks gotte       100     2,500     2,400
STAT...consistent gets               303     2,704     2,401
STAT...session logical reads         804     3,207     2,403
STAT...calls to get snapshot s     2,801       301    -2,500
STAT...recursive calls               102     2,702     2,600
LATCH.cache buffers chains         3,097     7,891     4,794
LATCH.library cache pin alloca     5,204         4    -5,200
LATCH.library cache pin            5,412       210    -5,202
LATCH.library cache               10,718       216   -10,502
LATCH.row cache enqueue latch     15,600         0   -15,600
LATCH.row cache objects           15,600         0   -15,600
STAT...session pga memory max     16,428         0   -16,428
STAT...session uga memory max     65,464         0   -65,464
STAT...session uga memory        196,392         0  -196,392
STAT...session pga memory        196,608         0  -196,608

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
56,670     8,936   -47,734 634.18%

PL/SQL procedure successfully completed.

adding 100 times more data

jom@NINA.US.ORACLE.COM> r
  1  begin
  2  for v_int in 26 .. 2500
  3  loop
  4  insert into t (  x, y ) values ( v_int , 'v'||to_char(v_int));
  5  end loop;
  6* end;

PL/SQL procedure successfully completed.

jom@NINA.US.ORACLE.COM> select count(1) from t;

  COUNT(1)
----------
      2500

jom@NINA.US.ORACLE.COM> declare
  2  type t_xmltypes is table of sys.xmltype index by binary_integer;
  3  v_xml_res t_xmltypes;
  4  cursor cur_res
  5  is
  6  select x , y from t;
  7  type t_tab_str is table of VARCHAR2(128) index by binary_integer;
  8  v_tab_str t_tab_str;
  9  v_i integer default 0;
 10  begin
 11  runstats_pkg.rs_start;
 12  for v_int in 1 .. 100
 13  loop
 14  select xmlelement("t",xmlattributes(x as "id"),xmlforest(y)) bulk collect
into v_xml_res from t;
 15  end loop;
 16  runstats_pkg.rs_middle;
 17  for v_int in 1 .. 100
 18  loop
 19  for rec_res in cur_res
 20  loop
 21  v_i := v_i +1;
 22  v_tab_str(v_i) := '<t id="'||to_char(rec_res.x)||'"
<y>'||rec_res.y||'</y></t>';
 23  end loop;
 24  v_i := 0;
 25  end loop;
 26  runstats_pkg.rs_stop(500);
 27  end;
 28  /
Run1 ran in 6044 hsecs
Run2 ran in 582 hsecs
run 1 ran in 1038.49% of the time

Name                                Run1      Run2      Diff
LATCH.undo global data               513         5      -508
LATCH.enqueue hash chains            758        10      -748
LATCH.shared pool                  1,040       103      -937
LATCH.checkpoint queue latch       1,054        96      -958
LATCH.SQL memory manager worka     1,350       134    -1,216
STAT...recursive cpu usage         5,842       370    -5,472
STAT...buffer is not pinned co       500   250,000   249,500
STAT...no work - consistent re       500   250,000   249,500
STAT...table scan blocks gotte       500   250,000   249,500
STAT...consistent gets               703   250,205   249,502
STAT...session logical reads       1,204   250,710   249,506
STAT...calls to get snapshot s   250,301       301  -250,000
STAT...recursive calls               102   250,202   250,100
LATCH.cache buffers chains         7,508   502,906   495,398
LATCH.library cache pin alloca   500,670         2  -500,668
LATCH.library cache pin          501,010       212  -500,798
LATCH.library cache            1,001,843       213-1,001,630
LATCH.row cache enqueue latch  1,500,758         0-1,500,758
LATCH.row cache objects        1,500,759         0-1,500,759
STAT...session uga memory max ##########         0##########
STAT...session uga memory     ##########         0##########
STAT...session pga memory max ##########    65,712##########
STAT...session pga memory     ##########    65,712##########

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
5,019,500   504,224-4,515,276 995.49%

PL/SQL procedure successfully completed.

or using dbms_xmlquery

jom@NINA.US.ORACLE.COM> declare
  2  v_xml_handle dbms_xmlquery.ctxHandle;
  3  v_clob clob;
  4  cursor cur_res
  5  is
  6  select x , y from t;
  7  type t_tab_str is table of VARCHAR2(128) index by binary_integer;
  8  v_tab_str t_tab_str;
  9  v_i integer default 0;
 10  begin
 11  runstats_pkg.rs_start;
 12  for v_int in 1 .. 100
 13  loop
 14  v_xml_handle := dbms_xmlquery.newContext('select x,y from t');
 15  dbms_lob.createtemporary(v_clob,true,dbms_lob.session);
 16  v_clob := dbms_xmlquery.getXml(v_xml_handle);
 17  end loop;
 18  runstats_pkg.rs_middle;
 19  for v_int in 1 .. 100
 20  loop
 21  for rec_res in cur_res
 22  loop
 23  v_i := v_i +1;
 24  v_tab_str(v_i) := '<t id="'||to_char(rec_res.x)||'"
<y>'||rec_res.y||'</y></t>';
 25  end loop;
 26  v_i := 0;
 27  end loop;
 28  runstats_pkg.rs_stop(500);
 29  end;
 30  /
Run1 ran in 2329 hsecs
Run2 ran in 577 hsecs
run 1 ran in 403.64% of the time

Name                                Run1      Run2      Diff
STAT...consistent gets - exami       603         5      -598
STAT...dirty buffers inspected     1,179         0    -1,179
STAT...free buffer inspected       1,179         0    -1,179
STAT...calls to get snapshot s     2,001       301    -1,700
STAT...free buffer requested       2,107        10    -2,097
LATCH.checkpoint queue latch       3,016       123    -2,893
LATCH.cache buffers lru chain      4,481        39    -4,442
STAT...consistent changes          5,889       495    -5,394
STAT...db block changes            6,380       986    -5,394
STAT...db block gets              19,782       505   -19,277
STAT...session uga memory         65,464         0   -65,464
STAT...session logical reads      46,185   250,710   204,525
STAT...buffer is not pinned co    26,200   250,000   223,800
STAT...consistent gets            26,403   250,205   223,802
STAT...recursive calls            26,003   250,202   224,199
STAT...no work - consistent re    25,600   250,000   224,400
STAT...table scan blocks gotte    25,400   250,000   224,600
LATCH.cache buffers chains       108,516   504,359   395,843
STAT...session pga memory      6,356,992  -131,072-6,488,064

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
119,617   508,510   388,893  23.52%

PL/SQL procedure successfully completed.

latches look good, but time not ...

it looks like oracle-xml-features are much slower
is there
     really so much overheader in it?
or
    am i using it in the wrong way (and yould be tuned to better just by doing
it right)?
or
    is the test i run so wrong and using runstats is not a good test in this
case?

regards

hannibal

          Followup:

the xml stuff has lots more functionality than a simple string -- the xmltype is
a complex data type with "lots of stuff"

if you have no need beyond the trivial xml snippet you have, a string will be
fine.  else, xmltype has the stuff you "need"

      Rating:
      3
okay...  January 14, 2004
Reviewer:
      Mark Griffin
Well I guess I will have to attempt to use
combination of DBMS_XMLDOM & other database packages to accomplish my goal.

I'm going to have to still muddle through the mapping though even with use of
DBMS_XMLDOM.

I have another question is there a way once you have document pulled in and
parsed via DBMS_XMLDOM to then insert data elements into some type of
object(whether table, collection...)

If I can go this route then I can manipulate the mapping via Oracle relation
logical perspective...

      Rating: 5
hello again
        January 15, 2004
Reviewer:  mark griffin
       from Huntsville Alabama
thank you both sean and tom for your patience, wisdom, and help...

i've brainstormed my scenario over and over again...

could i do this:
- take xsd and do
BEGIN
   dbms_xmlschema.registerURI(
      'cd.xsd',
      '
http://gennick.com/cd.xsd'
;
END;

- put my xsd file into HTTP/WebDAV Protocol Server

- then load xsd into structures that were created by register command

- after xsd is loaded then pull data based upon data queried from our database ?

will this work?

thank you!

          Followup:

Mark,

After you register the schema (dbms_xmlschema.registerURI), the Schema has been
loaded and the underlying storage architecture (the object/relational tables)
will have been created.

I think you're a little confused about the "put my xsd file into ... server"
comment.  You don't store your XSD (XML Schema), you store your XML.  The XML
you'll be storing references the XML Schema.  The XML is the DATA DOCUMENT, the
XSD is the DOCUMENT STRUCTURE.  The XSD is registered with the database.  The
XML is loaded into the database and stored in o/r tables).

So.... with that in mind, You can then use the HTTP/WebDAV protocol to load
**XML** documents into the database  (btw, there is no HTTP/WebDAV Protocol
Server, there is the Oracle Net Services Listener, which is now capable of
communicating over HTTP (and therefore WebDAV) and FTP connections).  Behind the
scenes, YES these XML documents are stored in the object/relational tables which
were created as a result of the registerURI call.

Once the XML has been stored in these object/relational tables, YES you can
query the object/relational tables in SQL queries, or you can pull the documents
as complete XML using HTTP/WebDAV, FTP and even SQL/PLSQL if you so desire!

Ultimately, yes it will work.  You just need to get the XSD vs XML terminology
straightened out ;-).

Good luck!

_smd_
Once the

      Rating: 3
lot of
      help  January 15, 2004
Reviewer:  mark
      griffin  from Huntsville AL
yeah depending on who you talk to you get a different defintion of terms...thank
you for your clarification between XSD and XML

it seems to me that one of the first thing(s) that people would have come up
with is a mechanism to allow fluent mapping between xsd elements...maybe im just
a dreamer though and because its the most important thing that i need :)

i just dont see how people at this point based upon xsd can effectively pull
data from their system to give to another system in a dynamic sense with the
available tools. Even if one writes custom code how much of a dynamic process
can one create? maybe this is where my lack of knowledge/ignorance comes into
play...also maybe this is where the tools have to grow up/mature.

January 15, 2004 -- 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

      Rating: 4
Automatic RBS
      Shrinking?  January 15, 2004
Reviewer:
      striker  from NC, USA
Suppose you are using Automatic Undo Management, and you have a pool of rollback
segments that are managed by the database.

Is there ever a situation where the database will automatically reduce the size
of an RBS (similar to ALTER ROLLBACK SEGMENT ... SHRINK)?

You've said in the past how it is beneficial to keep rollback segments similar
in size when managing them manually.  I wondered if the automatic approach would
do something similar.

I can imagine a situation where a large daily batch transaction could make one
segment grow much larger than the others.  Over a period of days, if the
transaction repeats, other rollback segments might get assigned to handle that
transaction and result in all rollback segments growing up to the limits of
retention period and undo tablespace.

Let's say one segment hasn't grown yet, and is handling the transaction on a
certain day--if it is out of undo tablespace, can the database automatically
shrink one or more of the other rollback segments to get the space it needs
(assuming the other rollback segments have information that is older than the
retention period)?

          Followup:

it will drop and create rbs's as needed.

need more -- creates

finished using them -- drops

if space goes out of control for whatever reason (that wild batch job) then

a) create a new small undo tablespace
b) alter system to the new one
c) after any active transactions complete in old just drop it

(that is, if shrinking the original datafiles isn't sufficient of course)

My rbs's went to 46 of them on January 5th at about 10pm.  We just shrank our
undo datafiles back down 12 hours later after the newly added ones "went away"

January 16, 2004 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,

      Rating: 3
I get error
      when I try to use your function  January 15, 2004

Reviewer:  Sonali Kelkar  from waltham, ma

When I do this I get error
create or replace function get_text( p_VIEW_NAME in varchar2 )
return varchar2
    is
        l_text user_views.text%type;
    begin
        select text into l_text
          from user_views
         where view_name = p_VIEW_NAME;

       return l_text;
   end;
   /
show errors

select view_name
      from user_views
     where get_text(view_name) like '%Company_ID%';

ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at "xyz.GET_TEXT", line 10

          Followup:

well -- a major issue with this is that varchar2 is always limited to 4,000
characters in SQL -- returning a view > 4,000 characters is going to blow up
here.

You would have to:

where do_a_like_on_view_text( view_name, '%Company_ID%' ) > 0

and code more like this:
is
   return_val number := 0;
begin
   for x in ( select text ... )
   loop
      if ( x.text like p_inputs )
      then
          return_val :=1;
      end if;
   end loop;
   return return_val;
end;

January 16, 2004 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.

      Rating: 4
"SELECT ANY
      DICTIONARY" is working through roles in 9i  January 15, 2004

Reviewer:  Raj  from San Diego, CA USA
"SELECT ANY DICTIONARY"  not working throught role for me.

Oracle version 9iR2.

I tried after connect as system
1. grant select any dictionary to role1 ;
2. grant role1 to user1 ;
3. after connecting from sqlplus as user1 tried
4. select * from v$session gives
   ora-00942 table or view does not exists.

But if I grant
1. grant select any dictionary to user1 ;
2. after connecting from sqlplus as user1 tried
3. select * from v$session (WORKS)

So it looks like as Tom said this needs to be granted individually to user and
not through role.

          Followup:

are you sure -- works for me

ops$tkyte@ORA9IR2> grant select any dictionary to role1;

Grant succeeded.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> drop user a cascade;

User dropped.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create user a identified by a;

User created.

ops$tkyte@ORA9IR2> grant create session to a;

Grant succeeded.

ops$tkyte@ORA9IR2> grant role1 to a;

Grant succeeded.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> @connect a/a
ops$tkyte@ORA9IR2> set termout off
a@ORA9IR2> REM GET afiedt.buf NOLIST
a@ORA9IR2> set termout on
a@ORA9IR2> select count(*) from v$session;

  COUNT(*)
----------
         8

      Rating: 5
Sorry It
      Works  January 16, 2004
Reviewer:  Raj
       from San Diego, CA USA
User I created yesterday to test this still does not work but after your reply.
I created three new users and all of them are working.

Thanks for the prompt reply. Right now I am working of one of the assignment
where I have to use this and I would have taken different path if I wouldn't
have got your answer.

January 16, 2004 Doug -- Thanks for the question regarding
      "Availability Options", version 8.1.7

      You Asked
Tom.. I have this bookmark to a post you had about the different options for
24x7 with Oracle:

http://www.oracle.com/oramag/oracle/00-May/index.html?o30tom.html
I wondered if it deserved some futher comments in light of Data Guard and RACs.
 Would you comment on the horizontal and verticle capabilities of RACs?  What's
the largest number of nodes running RACs that you know of?

Thanks.

      and we said...
probably the only thing I would change would be to remove:

*  You have to design your application to work with OPS. You have to understand
OPS and be aware of the potential performance bottlenecks. (Oracle8i Release
8.1.6 has a special two-node-cluster mode of OPS. If you use this mode, 100
percent of the performance issues are nonissues. Both nodes are used to read
data, but only one node is used to write data. The second node takes over for
the first node in the event of a failure and starts writing as well.)

that is not true with RAC anymore.  Data Guard is a better standby with more
features.  To have a truly available system -- it takes two things in my
opinion:

o RAC in a room -- to make sure that server system never goes down.  Scalable
and available

o data guard over the wan -- to make sure that if the room with RAC "goes away"
for whatever reason, you have upto date information in a failover site.

9iR2 makes data guard even more appealing with the introduction of a logical
standby database.

Most RAC clusters are in the 2-4-8 range.  Those are the ones I see frequently.
I know of a data warehouse running 40-50 nodes (might be more by now) on an SP2.
Review &
      Followup

      Rating: 5
Great Tom -
      thanks for the update  June 18, 2002
Reviewer:
      Doug C  from Danbury, CT USA
Thanks Tom.  I've always liked that post in oramag for an outline of the
available options.

      Rating: 5
oracle
      installation in RAC  January 15, 2004
Reviewer:
      reader
Is it a recommended practice to install oracle software on the shared disk or on
individual node? Thanks.

          Followup:

both are 'recommended' -- it depends on your environment, your wants, your
needs.  (you need a cluster filesystem, not available everywhere, to do the
shared install)

January 16, 2004 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

      Rating: 5
how to udpate
      parent and son at same time  January 15, 2004

Reviewer:  A reader
Hi

if we have this data

parent child
------ ------
a1     b1
a1     c1
a1     d1
a1     e1

How can we return the data this way?

a1
b1
c1
d1
e1

It´s because I want to update a column for both parent and child at one go... Do
we have to use a UNION to find the parent first then UNION the child?

          Followup:

umm, so what if you could return the data that way -- you say "i want to
update"....

how do you want to update?
what do you want to update?

sure -- could I return the data like that?  yes.
but -- so what?  you would not be able to update it.

      Rating: 5
for
      example  January 16, 2004
Reviewer:
      A reader
Hi

For example I want to change all employees department number of a department to
something else include the manager but who had another department number

employees have deptno 10 and manager 30

I want to change their deptno to 60 in a go

          Followup:

update emp set dept = 60
where empno = :mgr or empno in ( select empno from emp where mgr = :mgr );

no connect by needed...

January 16, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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.

      Rating:
      5
CallableStatement, returning clause,
      executeBatch  January 16, 2004
Reviewer:
      Srikanth Adiga  from Pune, India
I have the following issues with JDBC:

1. Using RETURNING INTO clause: Is CallableStatement the only way to achive this
throught JDBC. Unfortunetly, CallableStatement only appears to support batch
updates where as it does not.

2. Return value of executeBatch with CallableStatement: I am facing a pecular
issue with executeBatch. It returns 2 when the batch had 3 inserts, even when
all 3 were inserted successfully? I am running my app on a 9i box and inserted
into a remote 8i db.
Btw, I face this on one test machine and it runs fine on another?
Any ideas?

          Followup:

1) to get outputs -- you use callable statements

2) nope, cause I'm

a) not seeing a simple, small, yet 100% complete concise test case
b) not a java programmer

January 16, 2004 -- 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.

      Rating: 5
consistancy
      across versions  January 16, 2004
Reviewer:
      James Blanding  from Kingston, RI USA
I have also noticed you frequently refer to the Concepts Guide.  I took a look
at the PDF version for 8i the other day and was dismayed to find it weighing in
at 900 pages (no sense in printing it out heh!).  Anyway, I am going to start
chipping away at it, but then it occurred to me...We're going to be looking at
migrating to 9 soon (possibly even during summer break I suspect).  If I were to
read through the guide for 9i instead, would I frequently be finding things in
our current system that did not fit?  Would it be easier to read the one for 8i
and then just skim over the one for 9i, looking for differences?  I don't mind
the reading, it's just that I want to save some time for other things, too, like
your book...

Thanks!

          Followup:

I'd go for the 9i guide from the get go. 90% applicable to 8i.

January 16, 2004 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

      Rating: 3
Comparing two
      strings  January 16, 2004
Reviewer:  Kit
       from england
HI Tom.

The package posted by the other user was something I needed but as you said , it
could take time on large amounts of data.
do you have any other ideas of comparing two columns varchar and counting on the
characters that are diff.

How does the soundex internals work.

          Followup:

well, if you follow the link, i show how i did this exact thing (using pro*c)

soundex "internals" are actually documented.  google soundex, the algorithm is
out there.

January 17, 2004 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)

      Rating:
      5
  January 15, 2004
Reviewer:
      Pauline  from NY, U.S.A.
Tom,
When I use the way you provided to us to find the statements
not using BIND variable, I see something in our production database like:

SQL_TEXT_WO_CONSTANTS
--------------------------------------------------------------------------------
  COUNT(*)
----------
SELECT DISTINCT ADDRESS_TO_PTY_ID FROM PARTY_ACCOUNT_ADDRESSES PAA WHERE
PAA.ACCT_ID =@ AND PAA.PTY_ID = ( SELECT PTY_ID FROM PARTY_ACCOUNTS PTYA,
PRIMARY_ACCOUNTS PRIMA WHERE PTYA.ACCT_ID =@ AND PTYA.ACCT_ID = PRIMA.ACCT_ID
AND PTYA.PTYACCTROLE_ID = @ AND NVL(PTYA.DELETED_FLG,'#') <> '#' AND ROWNUM < @
GROUP BY PTYA.PTY_ID ) AND NVL(DELETED_FLG,'#') <> '#' AND PAA.ADDRESS_ID = @
AND PAA.ACCTADDR_USETYP_ID = @
       292

Then I try to do:
select sql_text from v$sql
where upper(sql_text) like 'SELECT DISTINCT ADDRESS_TO_PTY_ID FROM
PARTY_ACCOUNT_ADDRESSES PAA WHERE PAA.ACCT_ID%';

I see no rows selected.

If I do :

select sql_text from v$sql
where upper(sql_text) like 'SELECT DISTINCT ADDRESS_TO_PTY_ID%';

Then I see 292 rows selected.

Why that upper(sql_text) not working for the longer string?
Is there any limitation for using upper function?

Thanks.

          Followup:

you must have the white space or something wrong in your like string (maybe a
tab or newline hiding in there that you are not seeing)

like works dandy with long strings.  no problems there -- you just need the
right input.

      Rating:
      2
Views  January 16, 2004
Reviewer:
      Marcio  from br
<quote>
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.
</quote>
About a) In this context -- why do I not use views?

          Followup:

well, i thought I showed why

the instead of trigger parses the sql for each triggering statement.

the procedure does not.

      Rating: 2
Views for
      maintenance.  January 16, 2004
Reviewer:
      Marcio  from br
Sorry, I was talking about VIEWs, because I use it
to manage insted of TABLES directly -- I keep tables under my own control
and give grants on views... more or less like this:
ops$t_mp00@MRP920>
ops$t_mp00@MRP920> create sequence s;

Sequence created.

ops$t_mp00@MRP920> create table p$ ( id int primary key,
  2                    name  varchar2(10),
  3                    qty_c int default 0 );

Table created.

ops$t_mp00@MRP920>
ops$t_mp00@MRP920> create or replace trigger p$_bifer
  2  before insert on p$ for each row
  3  begin
  4          select s.nextval into :new.id from dual;
  5  end;
  6  /

Trigger created.

ops$t_mp00@MRP920>
ops$t_mp00@MRP920> create or replace view p
  2  as
  3    select id p_id, name, qty_c q from p$;

View created.

ops$t_mp00@MRP920>
ops$t_mp00@MRP920> create table c$ ( id int primary key, name varchar2(10), p_id
references p$ );

Table created.

ops$t_mp00@MRP920>
ops$t_mp00@MRP920> create or replace trigger c$_bifer
  2  before insert on c$ for each row
  3  begin
  4          select s.nextval into :new.id from dual;
  5          add_p(:new.p_id);
  6  end;
  7  /

Warning: Trigger created with compilation errors.

ops$t_mp00@MRP920>
ops$t_mp00@MRP920> create or replace view c
  2  as
  3    select id c_id, p_id, name child_name from c$
  4  /

View created.

ops$t_mp00@MRP920>
ops$t_mp00@MRP920> create or replace procedure add_p ( i in number )
  2  as
  3  begin
  4          update p
  5             set q = q + 1
  6           where p_id = i;
  7  end;
  8  /

Procedure created.

ops$t_mp00@MRP920>
ops$t_mp00@MRP920> show error
No errors.
ops$t_mp00@MRP920>
ops$t_mp00@MRP920> insert into p(name) values ( 'Marcio');

1 row created.

ops$t_mp00@MRP920>
ops$t_mp00@MRP920> insert into c(p_id, child_name) values (1, 'Alicia');

1 row created.

ops$t_mp00@MRP920> insert into c(p_id, child_name) values (1, 'Jack');

1 row created.

ops$t_mp00@MRP920> insert into c(p_id, child_name) values (1, 'Nicole');

1 row created.

ops$t_mp00@MRP920>
ops$t_mp00@MRP920> select * from p;

      P_ID NAME                Q
---------- ---------- ----------
         1 Marcio              3

1 row selected.

ops$t_mp00@MRP920> select * from c;

      C_ID       P_ID CHILD_NAME
---------- ---------- ----------
         2          1 Alicia
         3          1 Jack
         4          1 Nicole

3 rows selected.

ops$t_mp00@MRP920> select * from p$;

        ID NAME            QTY_C
---------- ---------- ----------
         1 Marcio              3

1 row selected.

I use this to maybe rename column, change order of columns, whatever... up until
now thought this is good way to manage schemas. What do you think?

thanks,

          Followup:

if you are talking about views in general -- I've said over and over "views are
good"

if you are talking about "should my answer to which you referred be changed to
say use the view instead of procedure" -- no, in this case not.

views are not 100% evil
views are not 100% good
views are a tool

and in this case (the case I was answering above) not the right choice IMO.

      Rating: 5
My mistake
        January 17, 2004
Reviewer:  Marcio
       from br
Sorry my misunderstood, when I saw your comment about views, I just forgot
context and put my follow up.

I work for that way (demonstrated above) following your advice found here on
this site (about views).

Thanks.

      Rating:
      5
  March 18, 2004
Reviewer:  TRS
       from Plano, TX USA
This was almost as good as the Thia soup they've named after you....  ;)

January 17, 2004 -- 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.

      Rating:
      3
  January 16, 2004
Reviewer:  Mark
       from KC
Are there any apps now, or planned that you know of to incorporate VSS
functionallity inside Oracle?  It's a shame we have to use a M$ database to
manage our Oracle database code.  (I'm half joking, but it would be a nice
addon).

Thanks

          Followup:

i'm not familar with VSS, don't know what it is.

for source code control, I use rcs.

January 17, 2004 Sam -- Thanks for the question regarding "Data
      may be inserted into complex cviews when the statement affects only one of
      the tables in the join.", version 8.1.7

      You Asked
"Data may be inserted into complex cviews when the statement affects only one of
the tables in the join."

Tom, can you show us how in a view , the statement can effect only one of the
tables in the join.

Does it mean that the insert can be done only into one table at a time  in the
view.

2."In complex views,  for update statements all columns changed are extracted
from  a key -preserved table . In addition , if the view is created wiht the
check option  clause, join columns and columns taken from the tables that are
referenced more than once in the view are not part of the update."

-What is a key preserved table.
-why are join columns not part of the update

3."In complex views, for delete statements, there is only one key preserved
table in the join.this table may be present more than once in the join , unless
the view has been created witht the check option."

can you explain the above...

4.why should all the columns where values are inserted must come from  a key
preserved table in case of of a complex  view.

      and we said...
searching for

key preserved

on this site, i find:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:548422757486
which explains the concept (and once you understand the concept, the rest is
pretty easy)....

1) so, here is the example of the insert:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t1 ( t1_x int primary key, t1_y
date );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t2 ( t2_x int primary key, t2_y
date );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace view v
  2  as
  3  select * from t1, t2 where t1.t1_x = t2.t2_x;

View created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into v values ( 1, sysdate, 1, sysdate
);
insert into v values ( 1, sysdate, 1, sysdate )
*
ERROR at line 1:
ORA-01776: cannot modify more than one base table through a join view

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into v (t1_x, t1_y ) values ( 1,
sysdate );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into v (t2_x, t2_y ) values ( 1,
sysdate );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>

you see -- we cannot insert into both t1 and t2 using this view WITHOUT HELP (we
can write an instead of trigger and provide the logic to insert into both
underlying tables).  We can directly insert into either of t1 or t2 as
demonstrated however.

2) See the above link for "what is a key preserved..."

As for the part about the join columns -- well, only one table at a time can be
modified via the view, and if you update the join columns -- that row would not
appear in that view anymore (you changed the join column values -- that row
doesn't exist anymore, the check condition fails).  Hence in a view with check
option the join columns cannot be modified.  You can see this via:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select column_name, updatable, insertable,
deletable
  2  from user_updatable_columns where table_name = 'V'
  3  /

COLUMN_NAME                    UPD INS DEL
------------------------------ --- --- ---
T1_X                           YES YES YES
T1_Y                           YES YES YES
T2_X                           YES YES YES
T2_Y                           YES YES YES

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace view v
  2  as
  3  select * from t1, t2 where t1.t1_x = t2.t2_x
  4  WITH CHECK OPTION;

View created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select column_name, updatable, insertable,
deletable
  2  from user_updatable_columns where table_name = 'V'
  3  /

COLUMN_NAME                    UPD INS DEL
------------------------------ --- --- ---
T1_X                           NO  NO  NO
T1_Y                           YES YES YES
T2_X                           NO  NO  NO
T2_Y                           YES YES YES

All we did was recreate the view with check option here....

3) if there are two key preserved tables in a view (our example has 2 key
preserved tables), we cannot delete from it cause Oracle doesn't know WHICH
table to delete from and it won't do both.

ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from v;
delete from v
            *
ERROR at line 1:
ORA-01752: cannot delete from view without exactly one key-preserved table

Now, if we drop the primary key constraint:

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t1 drop constraint SYS_C004852 ;

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from v;

1 row deleted.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t2;

      T2_X T2_Y
---------- ---------
         1 13-JAN-02

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t1;

no rows selected

T1 remains key preserved but T2 is no longer key preserved.  Hence we can delete
from the view and the act of deleting from the view will delete from T1.

4) hopefully, once you read about key preserved -- this will be obvious.... We
need to make sure that the row will appear at most "once" in the view itself --
otherwise the very act if modifying the row is ambigous....

Review &
      Followup

      Rating:
      4
  January 14, 2002
Reviewer:
      A reader
in this case  why we don't use insted_of trigger

          Followup:

go for it.  never said you couldn't -- only that you would HAVE to if you wanted
it to be fully insertable, updatable, deletable.

I did say:

...
you see -- we cannot insert into both t1 and t2 using this view WITHOUT HELP
(we
can write an instead of trigger and provide the logic to insert into both
underlying tables).  We can directly insert into either of t1 or t2 as
demonstrated however.
.....

      Rating: 3
Anaother
      updatable view related question  January 16, 2004

Reviewer:  Oleksandr Alesinskyy  from Germany, Bad
      Soden a. Ts.
Hi Tom,

Is there a way programmaticaly (from data dictionary or in some  other way) to
determine if a given view is updatable?
USER_UPDATABLE_COLUMNS is not very useful as "SQL Reference" stated "To learn
whether and in what ways the columns of an inherently updatable view can be
modified, query the USER_UPDATABLE_COLUMNS data dictionary view. (The
information displayed by this view is meaningful only for inherently updatable
views.)". So it is needed to know in advance is a view inherently updatable or
not. Definitely, it is possible to try update and catch an exception, but
possibly more elegant solution exists?

          Followup:

either the view is inherently updatable ( and user_updatable_columns has Y for
the columns that are)

OR

the view has an instead of trigger on it (and would be explicitly updatable).

so, look in those two -- if there is a trigger for the event you are interested
in (update, insert, delete) OR the column(s) you are interested in have Y in the
other view -- there you go.

January 17, 2004 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.

      Rating:
      5
Wonder  January 16, 2004
Reviewer:  YS
      Li  from CHINA
I know the SQLPlus will defaults to committing when exit,if type a insert
sentence then enter 'exit',the result will in the DB,
and if type a insert then end the task of SQLPlus by windows Task Manager,the
result will lost.

So I think Oracle itself will exec rollback when it knows current session is
ended,if without regard to the tools action.
-Is right?

But when I end the task of PL/SQL Developer by windows Task Manager,I found the
sentense without commit will in the DB,I think it strangeness.
So I wonder how the Oracle process deal with a ended session?

Thank you very much!

          Followup:

a dead client will roll back.  by killing the client (sqlplus), you've left it
in an unknown state.  the server will roll it back.

by exiting sqlplus gracefully, sqlplus will commit for you.

the plsql block will only commit IF the plsql block included the directive
"commit" in it.  else it will behave just like anything else.

January 18, 2004 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

      Rating:
      5
thread  January 17, 2004
Reviewer:
      Reader
Tom, what is the difference between private and public thread of redo? I have
hard time understanding this. I thought redos are specific to an instance in
RAC. How can they be public? Thanks.

          Followup:

it is like public and private rollback segments.

when you add the logfile, you can specify a thread.  if you do the instance with
that thread will use that file.  Else, they "fight" for public ones and once
acquired by an instance -- that redo will belong to that instance (it'll write
to it exclusively).  from the docs:

....
Redo Log File Storage in Real Application Clusters

In Real Application Clusters, each instance writes to its own set of online
redo log files. The redo written by an instance is called a thread of redo.
Each online redo log file is associated with a particular thread number. When an
online redo log is archived, Oracle records its thread number to identify it in
case Oracle performs recovery.

A private thread is a redo log created by using the ALTER DATABASE ADD LOGFILE
statement with the THREAD clause. A public thread is a redo log created using
the ALTER DATABASE ADD LOGFILE statement but without specifying a THREAD clause.

If the THREAD initialization parameter is specified, then the instance starting
up acquires the thread identified by that value as a private thread. If the
THREAD initialization parameter has the default value 0, then the instance
acquires a public thread. Once acquired, the acquiring instance uses the redo
thread exclusively.
..........

so, just like private and public rollback segments.  if private, you must "tell
which instance gets which" (just like with rbs).  if public, "free for all" (but
once acquired -- redo logs are used by just one instance, unlike rbs's which
could be used by all -- albeit slowly)

January 18, 2004 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.

      Rating: 5
Not clear about
      Constraint Vs Trigger [Application developers Guide (page
      4-2)]  January 17, 2004
Reviewer:  Sami
       from NJ,USA
Dear Tom,

I don't understand the following paragraph in
"Oracle 9i Application developers Guide (page 4-2)"

The following trigger does same as FOREIGN KEY constraint. Kindly enlighten me
in your way.

<Document>
To enforce this rule without integrity constraints, you can use a trigger to
query the
department table and test that each new employee’s department is valid. But this
method is ##LESS RELIABLE## than the integrity constrain, because SELECT in
Oracle uses
"consistent read" and so the query might miss uncommitted changes from other
transactions.
</Document>

create table tr_test(c1 number,c2 number);

create table tr_test_parent(c2 number primary key);

/*
The following trigger does the same as FOREIGN KEY.

*/

CREATE OR REPLACE TRIGGER TR_TEST_TR01
BEFORE INSERT OR UPDATE
ON TR_TEST
FOR EACH ROW
BEGIN
DECLARE
    A NUMBER;
BEGIN
    SELECT 1 INTO A
    FROM TR_TEST_PARENT
    WHERE C2 =:NEW.C2;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
    RAISE_APPLICATION_ERROR(-20201,'PARENT KEY NOT FOUND '||:NEW.C2);
END;
END;
/

SQL> insert into tr_test_parent values(1);
1 row created.

SQL> insert into tr_test values(1,1);
1 row created.

SQL> insert into tr_test values(1,2);
insert into tr_test values(1,2)
            *
ERROR at line 1:
ORA-20201: Parent key not found 2
ORA-06512: at "HR.TR_TEST_TR01", line 10
ORA-04088: error during execution of trigger 'HR.TR_TEST_TR01'

Thanks in advance Tom.

          Followup:

do you have any of my books?  I cover this topic in great, large, detail.  It is
THE important thing to understand in Oracle.

Basically -- you cannot really enforce cross object or cross row integrity using
triggers UNLESS you lock lots of objects (the entire object in most cases).

consider what happens when someone logs into session one and issues:

delete from tr_test_parent where c2 = 10;

and walks away for five minutes.  After 5 minutes they come back and issue
"commit"

Meanwhile -- every firing of your trigger during those 5 minutes queried
tr_test_parent -- and "saw" c2 = 10.  hmmmmmmmmm.  but c2=10 was really deleted
(and any triggers that would fire on the delete of c2=10 fired 5 minutes ago --
before any child records existed).

do NOT even consider enforcing integrity across rows or across objects using
triggers.  You'll never get it right.  See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7144179386439
and read the entire page, all of the examples.  many cases of why you cannot do
this and how tricky it can really be.

      Rating: 5
Guess I was
      just too thick in the head.  January 18, 2004

Reviewer:  Sami  from NJ,USA
Dear Tom,
I have your both books (did study long time back).

Anyway your
<example>
delete from tr_test_parent where c2 = 10;

and walks away for five minutes.  After 5 minutes they come back and issue
"commit"
</example>
has given clear understanding.

Thanks Tom

January 18, 2004 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

      Rating: 3
Please consider
      this issue...  January 17, 2004
Reviewer:
      Muhammad Mustafa  from Bombay, India
 I want to get this result. Is it possible?

   STRING

   "No pre-sales question should be answered via Technical Support, all
pre-sales suggestions should be considered invalid. Technical Support is not
authorized to make product suggestions. "

  RESULT

 "No pre-sales question should be answered chr(10)
 via Technical Support, all pre-sales     chr(10)
 suggestions should be considered invalid.chr(10)
 Technical Support is not authorised to   chr(10)
 make product suggestions."

  I want to loop through a String and replace a space with chr(10) at a specific
length of a string.

  Thanks TOM

          Followup:

search this site for

"word_wrap"

and you'll find my word wrap routine that does this.

      Rating: 5
Could the
      strip_bad function be simplified?  January 17, 2004

Reviewer:  Robert Massey  from Atlanta, GA USA

Tom,

Could you simplify the strip_bad function by only using the TRANSLATE function
like this?

create or replace package utils2 as
  function strip_bad( p_string in varchar2 )
  return varchar2;
end;
/

create or replace package body utils2 as

g_bad_chars   varchar2(256);

function strip_bad( p_string in varchar2 ) return varchar2 is
begin
  return translate( p_string, g_bad_chars, 'a');
end;

begin
  g_bad_chars := 'a';
  for i in 0..255 loop
    if ( i not between ascii('a') and ascii('z') AND
         i not between ascii('A') and ascii('Z') AND
         i not between ascii('0') and ascii('9') )
    then
      g_bad_chars := g_bad_chars || chr(i);
    end if;
  end loop;
end;
/

It seems to return the same results.

          Followup:

looks ok to me. sure

      Rating: 4
Could the
      strip_bad function be simplified?   February 16, 2004

Reviewer:  Duke Ganote  from Beautiful Warsaw,
      Indiana
Tom-- I think strip_bad could be further simplified.

GANOTEDP\dganote> ed
Wrote file afiedt.buf

  1  CREATE OR REPLACE FUNCTION strip_bad (p_string IN VARCHAR2)
  2     RETURN VARCHAR2
  3  IS
  4     good_string VARCHAR2(62)
  5        := 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ||
  6           'abcdefghijklmnopqrstuvwxyz' ||
  7           '1234567890';
  8  BEGIN
  9    RETURN
 10      TRANSLATE (p_string, 'T' ||
 11                             TRANSLATE ( p_string, '.' || good_string
 12                                                ,  '.'
 13                                       ) -- find the bad characters
 14                        ,  'T');  -- remove the bad characters
 15* END;
 16  /

Function created.

GANOTEDP\dganote> select strip_bad('0110-02-000') from dual;

STRIP_BAD('0110-02-000')
--------------------------
011002000

GANOTEDP\dganote> select strip_bad('0110.-()*"'||chr(10)||'~!@#$%^&*()') from
dual;

STRIP_BAD('0110.-()*"'||CHR(10)||'~!@#$%^&*()')
--------------------------------------------------
0110

      Rating: 4
Simplification
      & question regarding "Special Characters", version
      8.1.6  February 17, 2004
Reviewer:  Duke
      Ganote  from beautiful Warsaw, Indiana USA
Tom-- Now I see you proposed a similar nested TRANSLATE function at:

http://asktom.oracle.com/pls/ask/f?p=4950:8:2726411072436764801::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:671777767982,

January 18, 2004 Anupam -- Thanks for the question regarding
      "Database parameters", version 8.1.7.3

      You Asked
Tom,
What is the difference between the parameters NLS_CHARACTERSET and
NLS_NCHAR_CHARACTERSET in the view nls_database_parameters. Why do we have two
separate parameters when invariably the value is same in both?

      and we said...
They are not invariably the same - it depends on your needs.

This allows you to store data from two differing charactersets in your database.
 One character set in varchar2/clobs and a different one in nvarchar2 and
nclobs.

See

http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76966/toc.htm
and specifically

http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76966/ch3.htm#41673

Review
      & Followup

      Rating: 3
Q on NCHAR /
      NVARCHAR2  January 18, 2004
Reviewer:  Bobby

Hi,
You said "This allows you to store data from two differing charactersets in your
database.One character set in varchar2/clobs and a different one in nvarchar2
and nclobs."
Do u mean i can store two different languages in db. In CHAR/VARCHAR I can store
English and NCHAR/NVARCHAR I can store any other language depending on whether i
set it to UTF8 or some multi byte char set ? As per what ever i read i conclude
that NCHAR/NVARCHAR is used to store some mutli byte char set ? Am I correct ?

          Followup:

you can store words of many languages in a varchar2 simultaneously -- the
LANGUAGE is not really relevant.  If you use WE8ISO8859P1 as the character set,
you can store most all of the western european "languages" in a varchar2 --
english, french, spanish, german, etc.

Charactersets are not languages -- they are encoding schemes.  You can store
single byte data (such as .WE8ISO8859P1 defines) and multi-byte data like UTF8
encoded data in the same database using varchar2/nvarchar2's -- so, if most of
your data is single byte (western european for example) and sometimes you need
to store chinese or some other multi-byte characterset data - you could use this
feature.

read:

http://otn.oracle.com/pls/db92/db92.docindex?remark=homepage#index-GLO
the globalization guide.

January 18, 2004 Steven -- Thanks for the question regarding
      "Writing more than one block - v$filestat info", version 8.1.6

      You Asked
While looking at the v$filestat view, I noticed that most of the
datafiles have a 1:1 ratio of phyblkwrt/phywrts, but sometimes
there is a larger number of phyblkwrt compared to phywrts.  What
causes this, and is this behavior limited to a certain type of
segment - i.e. temporary, index, table etc.

      and we said...
If you look at the definition of these two columns in the Reference Manual:

PHYWRTS NUMBER Number of times DBWR is required to write

PHYBLKWRT NUMBER Number of blocks written to disk; which may be the same as
PHYWRTS ifall writes are single blocks

When PHYBLKWRT is > PHYWRTS that means we got to do some multi-block IO (thats a
GOOD thing).  We could batch up the writes and do in 1 IO what would take more
then 1 IO otherwise.

It purely depends on the nature and distribution of the data in the files, it is
not limmited to one type of file but might be observed more often on data(table)
and temporary segment -- more so then indexes (which are complex data structures
built a block at a time)
Review & Followup

      Rating:
      4
  April 21, 2001
Reviewer:  Pauline
       from U.S.A.

      Rating:
      4
  August 23, 2002
Reviewer:  John
       from Universal City, TX
The most of my datafiles have the ratio of less than 2 for the blocks/read.
According to the
document, I should set DB_FILE_MULTIBLOCK_READ_COUNT to a higher number. But the
db has
the default number 8. why the I/O so bad?
TIA

          Followup:

huh?

      Rating: 1
"huh?
      "  August 23, 2002
Reviewer:  A reader

What I asked was how do you think the i/o rating, which is less than 2 blocks
per read. and How could I do better?

          Followup:

show me how you are getting this "rating", the huh stems from the fact that I
don't really know what you are saying yet.  Be a little more elaborate in your
information.

do you actually DO operations (like full scans) that would do multi-block io?
If not, then you are doing single block IOs (index reads and table access by
rowid)

      Rating: 5
RE:
      Huh  August 23, 2002
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Force full table scans if you really want to get more blocks per read!  Index
accesses are typically single block reads.  If your systems does alot of "access
by index rowid" operations, your ratio of reads:blocks will be closer to 1:1,
would it not?  Why do you feel you need to have more blocks per read?

Unsolicted $0.02...

- Mark

      Rating:
      3
  August 23, 2002
Reviewer:  John

The infor based on what:
I had done the b/estat on the 8.1.7.3 database:

SVRMGR> Rem I/O should be spread evenly accross drives. A big difference between
SVRMGR> Rem phys_reads and phys_blks_rd implies table scans are going on.
SVRMGR> select table_space, file_name,
     2>        phys_reads reads, phys_blks_rd blks_read, phys_rd_time read_time,
     3>        phys_writes writes, phys_blks_wr blks_wrt, phys_wrt_tim
write_time,
     4>        megabytes_size megabytes,
     5>        round(decode(phys_blks_rd,0,0,phys_rd_time/phys_blks_rd),2)
avg_rt,
     6>        round(decode(phys_reads,0,0,phys_blks_rd/phys_reads),2)
"blocks/rd"
     7>  from stats$files order by table_space, file_name;
TABLE_SPACE                    FILE_NAME
READS      BLKS_READ  READ_TIME  WRITES     BLKS_WRT   WRITE_TIME MEGABYTES
AVG_RT     blocks/rd
------------------------------ ------------------------------------------------
---------- ---------- ---------- ---------- ---------- ---------- ----------
---------- ----------
CIV_EXTERNAL                   /p01/oracle/RESUMIX/civ_external.dbf
        5          5          1          3          3          0         52
   .2          1
CIV_EXTERNAL                   /p01/oracle/RESUMIX/civ_external1.dbf
       98        354         11          3          3          0        105
  .03       3.61
CIV_EXTERNAL_IDX               /p07/oracle/RESUMIX/civ_external_idx.dbf
        3          3          0          3          3          0         52
    0          1
CIV_EXTERNAL_IDX               /p07/oracle/RESUMIX/civ_external_idx1.dbf
        3          3          0          3          3          0        105
    0          1
DATA                           /p05/oracle/RESUMIX/data01.dbf
        3          3          0          3          3          0         21
    0          1
RBS01                          /p03/oracle/RESUMIX/rbs01.dbf
        7          7          1        379        379         51        262
  .14          1
RBS01                          /p03/oracle/RESUMIX/rbs02.dbf
      660        660         12      15076      15076       1905       1049
  .02          1
RBS01                          /p03/oracle/RESUMIX/rbs03.dbf
        7          7          4          7          7          2       2097
  .57          1
RESUMIX_DATA                   /p06/oracle/RESUMIX/RESUMIX_DA01.dbf
    36800      44137       2032       2000       2000        217        210
  .05        1.2
RESUMIX_DATA                   /p06/oracle/RESUMIX/RESUMIX_DA02.dbf
    68744      86837       4940       6735       6735        940        262
  .06       1.26
RESUMIX_DATA                   /p06/oracle/RESUMIX/RESUMIX_DA03.dbf
    55963      86182       3177       2391       2391        368        524
  .04       1.54
RESUMIX_DATA                   /p06/oracle/RESUMIX/RESUMIX_DA04.dbf
    53812      83904       2416       2653       2653        363        524
  .03       1.56
RESUMIX_DATA                   /p06/oracle/RESUMIX/RESUMIX_DA05.dbf
   105890     194626       7968       5071       5071        786       1049
  .04       1.84
RESUMIX_DATA                   /p06/oracle/RESUMIX/RESUMIX_DA06.dbf
    21972      43982       1204       1158       1158        117        524
  .03          2
RESUMIX_DATA                   /p06/oracle/RESUMIX/RESUMIX_DA07.dbf
    25180      48964       1666       1538       1538        166        524
  .03       1.94
RESUMIX_DATA                   /p06/oracle/RESUMIX/data_08.dbf
      177        367         84         72         72          7       1049
  .23       2.07
RESUMIX_IMAGE                  /p05/oracle/RESUMIX/RESUMIX_IM02.dbf
      633        633        160        602        602         74        419
  .25          1
RESUMIX_IMAGE                  /p05/oracle/RESUMIX/RESUMIX_IM08.dbf
      164        164         59        146        146         13       1049
  .36          1
RESUMIX_IMAGE                  /p05/oracle/RESUMIX/RESUMIX_IM09.dbf
      124        124         39         87         87          7       1049
  .31          1
RESUMIX_IMAGE                  /p05/oracle/RESUMIX/RESUMIX_IM10.dbf
       13         13          6          3          3          0       1049
  .46          1
RESUMIX_IMAGE                  /p06/oracle/RESUMIX/RESUMIX_IM01.dbf
      852        852        217        852        852        101        524
  .25          1
RESUMIX_IMAGE                  /p06/oracle/RESUMIX/RESUMIX_IM08.dbf
      338        338        134        332        332         32        839
   .4          1
RESUMIX_IMAGE                  /p07/oracle/RESUMIX/RESUMIX_IM03.dbf
     1379       1379        570       1285       1285        137       1049
  .41          1
RESUMIX_IMAGE                  /p07/oracle/RESUMIX/RESUMIX_IM04.dbf
     1631       1631        640       1593       1593        170       1049
  .39          1
RESUMIX_IMAGE                  /p07/oracle/RESUMIX/RESUMIX_IM05.dbf
     1223       1223        393       1121       1121        109       1049
  .32          1
RESUMIX_IMAGE                  /p07/oracle/RESUMIX/RESUMIX_IM06.dbf
      119        119         36        119        119         15        524
   .3          1
RESUMIX_IMAGE                  /p07/oracle/RESUMIX/RESUMIX_IM07.dbf
       36         36         10         36         36          4        524
  .28          1
RESUMIX_IMAGE                  /p07/oracle/RESUMIX/RESUMIX_IM09.dbf
       15         15          6          3          3          0       1049
   .4          1
RESUMIX_IMAGE                  /p07/oracle/RESUMIX/RESUMIX_IM10.dbf
       12         12          7          4          4          0       2097
  .58          1
RESUMIX_INDEX                  /p04/oracle/RESUMIX/RESUMIX05.dbf
    30317      60782       9116      15879      15879       2538       1049
  .15          2
RESUMIX_INDEX                  /p04/oracle/RESUMIX/RESUMIX_IN01.dbf
     5121       7667       1843       2735       2735        364        210
  .24        1.5
RESUMIX_INDEX                  /p04/oracle/RESUMIX/RESUMIX_IN02.dbf
     5674       8158       2253       3364       3364        386        210
  .28       1.44
RESUMIX_INDEX                  /p04/oracle/RESUMIX/RESUMIX_IN03.dbf
     4547       9441       1492       2596       2596        337        210
  .16       2.08
RESUMIX_INDEX                  /p04/oracle/RESUMIX/RESUMIX_IN04.dbf
     9233      14739       3480       6457       6457        870        419
  .24        1.6
RESUMIX_INDEX                  /p04/oracle/RESUMIX/RESUMIX_IN05.dbf
    32202      34792      16506      30753      30753       5905       1049
  .47       1.08
SOARS                          /p07/oracle/RESUMIX/soars.dbf
        3          3          0          3          3          0         21
    0          1
SYSTEM                         /p05/oracle/RESUMIX/system01.dbf
     1022       1483        353         21         21          1         52
  .24       1.45
SYSTEM                         /p05/oracle/RESUMIX/system02.dbf
     1855       2549        542        182        182         52        210
  .21       1.37
TEMP                           /p02/oracle/RESUMIX/temp02.dbf
      430       4180          0        724       4071          0        524
    0       9.72
TEMP                           /p02/oracle/RESUMIX/temp_01.dbf
      554       5530          0        819       5510          0        524
    0       9.98
USERS                          /p01/oracle/RESUMIX/users01.dbf
        3          3          0          3          3          0         21
    0          1

          Followup:

well, besides being "not very readable", b/estat are out of vogue.  Statspack
rules.

but you haven't answered the important question.

do you full scan.  do you full scan alot.  dropping all of your indexes will
make that "ratio" go up -- but much like a cache hit ratio, that would be
meaningless and counter productive in most cases.

Looks like TEMP is the only place we do multi-block IO meaning your OLTP
application "resumix" is using 100% index access and trying to get multiblock IO
from that would be similar to getting blood from a stone.

This blind "gotta tune these ratios" is something best left for the birds.

      Rating:
      5
Thanks.  August 23, 2002
Reviewer:
      John
I am new to site. Need to figure how the application does. So I was not able to
answer your question.

There is another performance index out from the report.txt. It is the time spent
on read blocks. For the most my datafiles, they are in single digit ms per
block( such 5ms/block). How do you think this performance index?

      Rating:
      5
v$filestat  January 18, 2004
Reviewer:
      Reader
Tom, what is the reason for v$filestat does not show info for redolog files on
disk? Is there any special reason for this? Thanks.

          Followup:

because v$filestat is all about datafiles, not redo log files.

v$log
v$logfile

tell us about logfiles and the system statistics:

redo log space requests
redo log space wait time
redo log switch interrupts

and the system events:

log switch/archive
log file sequential read
log file single write
log file parallel write
log buffer space
log file switch (checkpoint incomplete)
log file switch (archiving needed)
log file switch (clearing log file)
switch logfile command
log file switch completion
log file sync

tell us about logs....

January 18, 2004 Sikandar -- Thanks for the question regarding
      "How to test application?", version 8.1.7.4

      You Asked
Hi TOM,
We are getting a developed software in J2EE and the database it will use is
8.1.7. Now when they will deliver the application I have to check its
performance, quality and any other possible bugs as a DBA before going in the
production. So let me know as I don't have any idea of Java/J2EE. How can I test
its performance for 100 users before delivering to users?

Please let me know if still more info required.

Thanks for your time.

      and we said...
first thing, you'll need a simulator of some sort.

At the high end, Mercury Interactive sells a product "load runner".

At the low end, you find some open source thing and try to get it going --
flinging URLS at the application.

I cannot be much more specific then that really -- you'll need a simulator.
Short of having 100 people in a room (i've done that as well, it is brutally
effective but hard to accomplish) hitting the application.

After that -- the first thing -- very very very first thing to check will be --
what is the soft parse % reported by a 15 minute statspack report.  If it is not
99% or above, send it back.  they are not using binds (I'll give it at best a
50/50 chance that they ARE NOT using binds) and it'll quite literally KILL you.

Testing j2ee to scale is not any different than testing pro*c to scale, plsql to
scale, vb to scale etc.  Java is just a language (keep repeating that over and
over -- it is JUST a language).  It is nothing "new", it is just a language to
write programs in -- no different than C really.

Review &
      Followup

      Rating:
      5
  December 20, 2003
Reviewer:
      A reader
That was a good lesson!!

      Rating: 5
Unable to find
      statscre.sql?  December 22, 2003
Reviewer:
      Sikandar Hayat  from Pindi Bhattian - Pakistan
I have searched statscre.sql in my Oracle 8.1.7 db servers but it is not present
on any machine. Secondly this application has to reside in the Oracle
Application db with a separate schema so I think still statspack will give much
info.
I have used the following query and it seems they have not used any bind
variables as the queries are showing high number of that application,

select sql_text, count(*) from v$sqlarea group by sql_text;

But still I have to configure and use statspack and to get more detailed
reports.

      Rating: 5
File
      found?  December 22, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
After searching on metalink I found the following,

From: Oracle, Ken Robinson 23-Apr-01 22:13
Subject: Re : missing STATSPACK install scripts

Per bug 1516170, the names of the files changed with 8.1.7.

"The statspack file names changed between 8.1.6 and 8.1.7 to adhere to OCCS file
name standards. The 8.1.6 file names began with stats*. The 8.1.7 file names
begin with with sp*.

I have copied the excerpt below from the 8.1.7 Statspack readme (spdoc.txt):
New Name Old Name
------------ -------------
spdoc.txt statspack.doc
spcreate.sql statscre.sql
spreport.sql statsrep.sql
spauto.sql statsauto.sql
spuexp.par statsuexp.par
sppurge.sql - new file -
sptrunc.sql - new file -
spup816.sql - new file -
spdrop.sql statsdrp.sql
spcpkg.sql statspack.sql
spctab.sql statsctab.sql
spcusr.sql statscusr.sql
spdtab.sql statsdtab.sql
spdusr.sql statsdusr.sql"

Regards,
Ken Robinson
Oracle Server EE Analyst

      Rating: 5
Here is the
      output of statspack  December 23, 2003
Reviewer:
      Sikandar Hayat  from Pindi Bhattian - Pakistan
Here is 23 minutes report while the database is on Oracle Financial while that
application is deployed on the same database. At the moment the application has
only 2 to 3 screens and only 2 or 3 users are working on it. Later this
application will have more than 50 users with more than 50 screens.
Please guide me with the following details,

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:              3,765.90             15,432.60
              Logical reads:                606.58              2,485.73
              Block changes:                 24.76                101.48
             Physical reads:                 14.53                 59.56
            Physical writes:                  5.55                 22.76
                 User calls:                 34.22                140.24
                     Parses:                  4.87                 19.95
                Hard parses:                  0.09                  0.35
                      Sorts:                 21.98                 90.08
                     Logons:                  0.08                  0.33
                   Executes:                 38.66                158.43
               Transactions:                  0.24

  % Blocks changed per Read:    4.08    Recursive Call %:   73.18
 Rollback per transaction %:    5.76       Rows per Sort:    7.59

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:  100.00
            Buffer  Hit   %:   97.60    In-memory Sort %:   99.95
            Library Hit   %:   99.86        Soft Parse %:   98.25
         Execute to Parse %:   87.41         Latch Hit %:   99.98
Parse CPU to Parse Elapsd %:   48.72     % Non-Parse CPU:   99.97

          Followup:

details of what?

      Rating: 5
I
      mean  December 23, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
I mean Soft Parse %:   98.25 is ok or problmetic?

          Followup:

could be fine -- depends on how long the system is up, whether all of the sql is
parsed, and what functions you are using.  could be fine.

      Rating: 3
My old
      link...  December 23, 2003
Reviewer:  Alex
      V.  from Montreal
Once I did home-made review of several test
programs (summer 2002) :

http://pages.infinit.net/sir/test/test-1.htm
There is nothing directly about database, but it will
give you some ideas of several available load/test
programs and test strategies. Brief (somewhat obvious)
suggestions:

-- load server must be much stronger than tested PC
-- different stress SW give different results
-- watch CPU and memory load
-- timing on every layer: otherwise you do not know where is
bottleneck

Have a nice day

      Rating: 5
sqlarea has
      number...  December 23, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
I have used the following query and it seems they have not used any bind
variables as the queries are showing high numbers of queries of that
application,

select sql_text, count(*) from v$sqlarea group by sql_text;

>> Thanks Alex I will explore your site and will let you know.

          Followup:

that would NOT show "bad binds"

that would show child cursors using binds perhaps..

think about it -- if they did not use binds, queries would be:

select * from emp where empno = 1;
select * from emp where empno = 2;
...
select * from emp where empno = 1000;

if you grouped by that -- they would return a count of 1.

Now, if they used binds:

select * from emp where empno = :n;
select * from emp where empno = :n;
...

you would get a count > 1 if there were many child cursors. use
v$SQL_SHARED_CURSOR to find out "why"

      Rating: 5
Here is the
      sample statements...  December 24, 2003
Reviewer:
      Sikandar Hayat  from Pindi Bhattian - Pakistan
Here is a query I have copied from v$sqlarea and all other statements are same
and there is no use of bind variables,

SELECT RACK_ID, ROW_ID, BIN_ID, ADDT_INFO, LOCATOR_CODE, LOCATOR_ID FROM
CINV_LOCATORS WHERE STORE_ID = '1' AND LOCATOR_TYPE_LKP = 'RACK_ROW_BIN'
SELECT RACK_ID, ROW_ID, BIN_ID, ADDT_INFO, LOCATOR_CODE, LOCATOR_ID FROM
CINV_LOCATORS WHERE STORE_ID = '2' AND LOCATOR_TYPE_LKP = 'RACK_ROW_BIN'
SELECT RACK_ID, ROW_ID, BIN_ID, ADDT_INFO, LOCATOR_CODE, LOCATOR_ID FROM
CINV_LOCATORS WHERE STORE_ID = '41' AND LOCATOR_TYPE_LKP = 'RACK_ROW_BIN'
SELECT RACK_ID, ROW_ID, BIN_ID, ADDT_INFO, LOCATOR_CODE, LOCATOR_ID FROM
CINV_LOCATORS WHERE STORE_ID = '62' AND LOCATOR_TYPE_LKP = 'RACK_ROW_BIN'

----
Secondly you have mentioned that,

select * from emp where empno = :n;
select * from emp where empno = :n;

but I think it should be only one instead of 2 as both are same. So I was
expecting only,

select * from emp where empno = :n;

---
Thanks for correction as I was wrong in that group by query on v$sqlarea. Now I
am trying to understand  V$SQL_SHARED_CURSOR which seems to be very complicated.

Thanks again for your cooperation.

          Followup:

but you see -- YOUR QUERY (the select count(*) from v$sql group by sql_text)
WOULD NOT have discovered these queries!  that has been my point all along --
your approach cannot be used to find "bind variable issues"

If you have my new book "Effective Oracle By Design" I go over using
v$sql_shared_cursor to diagnose "why" you have child cursors when they occur.

      Rating: 5
I have one
      book..  December 24, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
I have only one book Expert one-on-one Oracle and reading its chap 10.
And you were right that goup by on sqlarea is not giving accurate results.

      Rating: 5
What about
      this?  January 04, 2004
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
15 minutes statspack reports results are as following,

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:  100.00
            Buffer  Hit   %:   98.45    In-memory Sort %:   99.92
            Library Hit   %:   99.73        Soft Parse %:   97.49
         Execute to Parse %:   88.79         Latch Hit %:   99.99
Parse CPU to Parse Elapsd %:   75.00     % Non-Parse CPU:   99.98

Where soft parse and ligrary hit seems to be ok but I have tried your technique
from
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1163635055580
and here are the results,

SQL_TEXT_WO_CONSTANTS
------------------------------------------------------------  COUNT(*)
----------
SELECT ITEM_ID, ITEM_CODE, ITEM_DESC, PRIMARY_UOM_CODE, IS_ACTIVE FROM
CINV_ITEMS WHERE (SEGMENT@ = '#') AND (SEGMENT@ = '#') AND (CONSUMPTION_TYPE_LKP
= '#') ORDER BY LAST_UPDATE_DATE DESC
       102

SELECT
INVOICE_TYPE,VENDOR_NAME,VENDOR_NUMBER,VENDOR_SITE_CODE,INVOICE_DATE,INVOICE_NUM,
INVOICE_CURRENCY_CODE,INVOICE_AMOUNT,GL_DATE,PAYMENT_CURRENCY_CODE,PAYMENT_CROSS_
RATE_DATE,PAYMENT_CROSS_RATE_TYPE,PAY_CURR_INVOICE_AMOUNT,PAYMENT_CROSS_RATE,FREI
GHT_AMOUNT,DESCRIPTION,USSGL_TRANSACTION_CODE,PROJECT,TASK,EXPENDITURE_TYPE,EXPEN
DITURE_ITEM_DATE,EXPENDITURE_ORGANIZATION_NAME,PA_QUANTITY,USER_RATE_TYPE,EXCHANG
E_DATE,BASE_AMOUNT,TERMS_DATE,TERMS_NAME,PAYMENT_METHOD,PAY_GROUP_LOOKUP_CODE,EXC
LUSIVE_PAYMENT_FLAG,AMOUNT_APPLICABLE_TO_DISCOUNT,INVOICE_RECEIVED_DATE,GOODS_REC
EIVED_DATE,PREPAYMENT_TYPE_LOOKUP_CODE,EARLIEST_SETTLEMENT_DATE,AUTO_TAX_CALC_FLA
G,VAT_CODE,TAX_AMOUNT,AWT_GROUP_NAME,AMOUNT_WITHHELD,PREPAID_AMOUNT,DOC_CATEGORY_
NAME,DOC_SEQUENCE_NAME,APPROVED_AMOUNT,APPROVAL_STATUS,APPROVAL_DESCRIPTION,CANCE
LLED_DATE,CANCELLED_AMOUNT,BATCH_NAME,ROW_ID,ACCTS_PAY_CODE_COMBINATION_ID,ALWAYS
_TAKE_DISC_FLAG,AMOUNT_INCLUDES_TAX_FLAG,AMOUNT_PAID,AP_TAX_ROUNDING_RULE,ATTRIBU
TE@,ATTRIBUTE@,ATTRI
       103

INSERT INTO CINV_ITEMS ( ITEM_ID, CLASS_CODE, SEGMENT@,SEGMENT@, SEGMENT@,
SEGMENT@, SEGMENT@, SEGMENT@, IS_ACTIVE, ITEM_SERIAL_NUM, ITEM_DESC,
ITEM_LONG_DESC, ITEM_CODE, BAR_CODE, IS_PURCHASABLE, IS_CASH_PURCHASABLE,
IS_SALEABLE, IS_SERVICEABLE, IS_UNIFORM, IS_TOOL, IS_MACHINE, IS_PART,
CAN_IMPORT_THROUGH_SRO, CAN_ISSUE_IN_BULK, CAN_IMPORT_UNDER_BOND,
PRIMARY_UOM_CODE, MAKE_LKP, COUNTRY_OF_ORIGIN_LKP, MODEL, ITEM_TYPE_LKP,
SALE_LIMIT, BRAND, CONSUMPTION_TYPE_LKP, CONSUMPTION_UOM_CODE, AVG_CONSUMPTION,
SYS_AVG_CONSUMPTION, TOOL_TYPE_LKP, SIZE_UOM_CODE, ITEM_SIZE, IS_STOCKABLE,
IS_CONSUMABLE, IS_REPLACEMENT_REQD, IS_QUOTA_ENABLED, IS_CCNT_ENABLED,
IS_CEP_REQD, IS_SUPL_SPECIFIC, SUPL_NUM, OEM_NUM, SHELF_LIFE_UOM_CODE,
SHELF_LIFE, HAS_SUBSTITUTES, MAT_TYPE_LKP, MAT_SPECS, PACKING_TYPE_LKP,
PACKING_UOM_CODE, STD_PACKING, PURCHASABLE_PACK_QTY, MIN_PURCHASABLE_QTY,
SHOULD_CHECK_MAT_SHORTAGE, PRE_PROCSNG_DAYS, POST_PROCSNG_DAYS,
PROCSNG_BY_AIR_DAYS, PROCSNG_DAYS, FIXED_DAYS, MIN_QTY, MAX_QT
       366

 INSERT INTO CINV_ITEM_LOCATIONS ( ITEM_LOCATION_ID, ITEM_ID, LOCATOR_ID,
CREATED_BY, CREATION_DATE, LAST_UPDATED_BY, LAST_UPDATE_DATE ) VALUES ( @ , @ ,
@ , @ , SYSDATE , @ , SYSDATE )
       367

 INSERT INTO CINV_ITEM_STOCKS ( ITEM_LOCATION_ID, QTY_IN_HAND, PLEDGED_QTY,
CREATED_BY, CREATION_DATE, LAST_UPDATED_BY, LAST_UPDATE_DATE ) VALUES ( @ ,@ ,@
, @ , SYSDATE , @ , SYSDATE )
       367

SELECT COUNT(*) NO_OF_ITEMS FROM CINV_ITEMS WHERE SEGMENT@='#' AND SEGMENT@='#'
AND SEGMENT@ ='#' AND SEGMENT@ = '#' AND SEGMENT@ ='#' AND (SEGMENT@ ='#' OR
ITEM_SERIAL_NUM = '#')
       369

SELECT PA.VALUE DUMP_DEST, P.SPID SID, PF.VALUE FILE_SIZE FROM V$SESSION S,
V$PROCESS P, V$PARAMETER PA,V$PARAMETER PF WHERE S.AUDSID = @ AND S.PADDR =
P.ADDR AND PA.NAME = '#' AND PF.NAME = '#'
       577

7 rows selected.

SQL>

These statements are not using bind variables?

          Followup:

97% is low for a transactional system -- not so good.

when you see this:

SELECT PA.VALUE DUMP_DEST, P.SPID SID, PF.VALUE FILE_SIZE FROM V$SESSION S,
V$PROCESS P, V$PARAMETER PA,V$PARAMETER PF WHERE S.AUDSID = @ AND S.PADDR =
P.ADDR AND PA.NAME = '#' AND PF.NAME = '#'

you know for pretty much sure that you are missing binds there.  They are
querying by AUDSID -- sort of a unique key there, and I'll guess that if you
look at the original sql (in that same temporary table we query this from), you
would see that.  Just take this SQL and look at the REAL sql behind it, it'll be
obvious.

As for these two:

SELECT
INVOICE_TYPE,VENDOR_NAME,VENDOR_NUMBER,VENDOR_SITE_CODE,INVOICE_DATE,INVOICE_NUM,

INVOICE_CURRENCY_CODE,INVOICE_AMOUNT,GL_DATE,PAYMENT_CURRENCY_CODE,PAYMENT_CROSS_

RATE_DATE,PAYMENT_CROSS_RATE_TYPE,PAY_CURR_INVOICE_AMOUNT,PAYMENT_CROSS_RATE,FREI

GHT_AMOUNT,DESCRIPTION,USSGL_TRANSACTION_CODE,PROJECT,TASK,EXPENDITURE_TYPE,EXPEN

DITURE_ITEM_DATE,EXPENDITURE_ORGANIZATION_NAME,PA_QUANTITY,USER_RATE_TYPE,EXCHANG

E_DATE,BASE_AMOUNT,TERMS_DATE,TERMS_NAME,PAYMENT_METHOD,PAY_GROUP_LOOKUP_CODE,EXC

LUSIVE_PAYMENT_FLAG,AMOUNT_APPLICABLE_TO_DISCOUNT,INVOICE_RECEIVED_DATE,GOODS_REC

EIVED_DATE,PREPAYMENT_TYPE_LOOKUP_CODE,EARLIEST_SETTLEMENT_DATE,AUTO_TAX_CALC_FLA

G,VAT_CODE,TAX_AMOUNT,AWT_GROUP_NAME,AMOUNT_WITHHELD,PREPAID_AMOUNT,DOC_CATEGORY_

NAME,DOC_SEQUENCE_NAME,APPROVED_AMOUNT,APPROVAL_STATUS,APPROVAL_DESCRIPTION,CANCE

LLED_DATE,CANCELLED_AMOUNT,BATCH_NAME,ROW_ID,ACCTS_PAY_CODE_COMBINATION_ID,ALWAYS

_TAKE_DISC_FLAG,AMOUNT_INCLUDES_TAX_FLAG,AMOUNT_PAID,AP_TAX_ROUNDING_RULE,ATTRIBU

TE@,ATTRIBUTE@,ATTRI
       103

INSERT INTO CINV_ITEMS ( ITEM_ID, CLASS_CODE, SEGMENT@,SEGMENT@, SEGMENT@,
SEGMENT@, SEGMENT@, SEGMENT@, IS_ACTIVE, ITEM_SERIAL_NUM, ITEM_DESC,
ITEM_LONG_DESC, ITEM_CODE, BAR_CODE, IS_PURCHASABLE, IS_CASH_PURCHASABLE,
IS_SALEABLE, IS_SERVICEABLE, IS_UNIFORM, IS_TOOL, IS_MACHINE, IS_PART,
CAN_IMPORT_THROUGH_SRO, CAN_ISSUE_IN_BULK, CAN_IMPORT_UNDER_BOND,
PRIMARY_UOM_CODE, MAKE_LKP, COUNTRY_OF_ORIGIN_LKP, MODEL, ITEM_TYPE_LKP,
SALE_LIMIT, BRAND, CONSUMPTION_TYPE_LKP, CONSUMPTION_UOM_CODE, AVG_CONSUMPTION,
SYS_AVG_CONSUMPTION, TOOL_TYPE_LKP, SIZE_UOM_CODE, ITEM_SIZE, IS_STOCKABLE,
IS_CONSUMABLE, IS_REPLACEMENT_REQD, IS_QUOTA_ENABLED, IS_CCNT_ENABLED,
IS_CEP_REQD, IS_SUPL_SPECIFIC, SUPL_NUM, OEM_NUM, SHELF_LIFE_UOM_CODE,
SHELF_LIFE, HAS_SUBSTITUTES, MAT_TYPE_LKP, MAT_SPECS, PACKING_TYPE_LKP,
PACKING_UOM_CODE, STD_PACKING, PURCHASABLE_PACK_QTY, MIN_PURCHASABLE_QTY,
SHOULD_CHECK_MAT_SHORTAGE, PRE_PROCSNG_DAYS, POST_PROCSNG_DAYS,
PROCSNG_BY_AIR_DAYS, PROCSNG_DAYS, FIXED_DAYS, MIN_QTY, MAX_QT

It is not 100% obvious -- you'll need to goto v$sqltext_with_newlines, get the
entire query and see - we don't have enough of it.  It is probable I would say
that these are not bound either.

All of the others are clearly NOT using bind variables.  I would say you have AT
LEAST 1,780 statements in your shared pool that differ by nothing more than
their inputs and the programmer didn't bind.  Probably closer to 2,100
statements.

Also, after you fix these statements, lower the threshold to 10 from 100 and fix
the rest! (where there is smoke there is fire generally, you probably have
others that just have less than 100 child cursors)

      Rating: 5
Yes there is
      smoke...  January 05, 2004
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
Here is the complete query and I am sure there is no thing like bind in this,

INSERT INTO CINV_ITEMS ( ITEM_ID, CLASS_CODE, SEGMENT1,SEGMENT2,
 SEGMENT3, SEGMENT4, SEGMENT5, SEGMENT6, IS_ACTIVE, ITEM_SERIAL_
NUM, ITEM_DESC, ITEM_LONG_DESC, ITEM_CODE, BAR_CODE, IS_PURCHASA
BLE, IS_CASH_PURCHASABLE, IS_SALEABLE, IS_SERVICEABLE, IS_UNIFOR
M, IS_TOOL, IS_MACHINE, IS_PART, CAN_IMPORT_THROUGH_SRO, CAN_ISS
UE_IN_BULK, CAN_IMPORT_UNDER_BOND, PRIMARY_UOM_CODE, MAKE_LKP, C
OUNTRY_OF_ORIGIN_LKP, MODEL, ITEM_TYPE_LKP, SALE_LIMIT, BRAND, C
ONSUMPTION_TYPE_LKP, CONSUMPTION_UOM_CODE,  AVG_CONSUMPTION, SYS
_AVG_CONSUMPTION, TOOL_TYPE_LKP, SIZE_UOM_CODE, ITEM_SIZE, IS_ST
OCKABLE, IS_CONSUMABLE, IS_REPLACEMENT_REQD, IS_QUOTA_ENABLED, I
S_CCNT_ENABLED, IS_CEP_REQD, IS_SUPL_SPECIFIC, SUPL_NUM, OEM_NUM
, SHELF_LIFE_UOM_CODE, SHELF_LIFE, HAS_SUBSTITUTES, MAT_TYPE_LKP
, MAT_SPECS, PACKING_TYPE_LKP, PACKING_UOM_CODE, STD_PACKING, PU
RCHASABLE_PACK_QTY, MIN_PURCHASABLE_QTY, SHOULD_CHECK_MAT_SHORTA
GE, PRE_PROCSNG_DAYS, POST_PROCSNG_DAYS, PROCSNG_BY_AIR_DAYS, PR
OCSNG_DAYS, FIXED_DAYS, MIN_QTY, MAX_QTY, BUFFER_STOCK, REORDER_
QTY, SYS_REORDER_QTY, PPC_BUFFER_STOCK, STD_ISSUE_QTY, MAX_ISSUE
_QTY, LIFE, IS_LABTEST_REQD, OVER_RECEIPT_QTY_TOLERANCE, SHORT_R
ECEIPT_QTY_TOLERANCE, OVER_RECEIPT_QTY_ACTION_LKP, CC_FOR_INSPEC
TION, ASSET_ACCT_RAW, INV_AP_ACCRUAL_ACCT, ACCT_SEGMENT_DIRECT,
ACCT_SEGMENT_INDIRECT, AVG_BALE_WGT, RECEIPT_CLOSE_TOLERANCE, AV
G_ITEM_COST, LOCATOR_TYPE_LKP, LIFE_UOM_CODE, REORDER_LEVEL, SUP
L_PART_NUM,CREATED_BY, CREATION_DATE, LAST_UPDATED_BY, LAST_UPDA
TE_DATE ) VALUES ( 2409,1,'1','39','50','69','419','075','Y','07
5','BELT TIMING 075-L-225','','1-39-50-69-419-075','0','Y','N','
N','N','N','N','N','N','N','N','N','NOS','LOCAL','','','INDIRECT
',0.0,'','NORMAL','DAY',0.0,0.0,'','',null,'Y','N','N','N','N','
N','N','','','',null,'N','','','BOX','NOS',1.0,1.0,1.0,'N',14,14
,0,14,14,null,null,0.0,0.0,0.0,0.0,1.0,1.0,null,'N',0.0,0.0,'NON
E','287','1111B000120600000000000000000','1111B00012080000000000
0000000','','1207',0,0,0,'RACK_ROW_BIN','',0.0,'',2, SYSDATE,2,
SYSDATE)

Also thanks for telling about v$sqltext_with_newlines.

          Followup:

nope, not a bind to be found.  the program has a bug.

      Rating: 5
How to code in
      java?  January 18, 2004
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
I have the following query,

Select sal from emp where empno = v_empno and ename = v_ename;

how we will code the above query in java so it should use bind variables?

          Followup:

use a prepared statement to prepare

"select sal from emp where empno = ? and ename = ?"

any first year jdbc programmer should know what to do based on that -- and if
they don't, send em back to school....

search for

preparedstatement pstmt

and you'll see a couple of quick and dirty examples.

      Rating: 5
You are right
      TOM...  January 18, 2004
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
I have contacted them and interesting thing which I would like to share with you
that they have no DBA at all. On asking they said our developers are testing the
code and performance of application/server. On asking for certain reports
(statspack) the response was silence.....
I don't have any idea of Java coding so was interested to know about it and
after searching preparedstatement pstmt found a lot of posts and currently
viewing all.

January 18, 2004 Venkatesh -- Thanks for the question regarding
      "Listener questions", version 9.2

      You Asked
Tom

Is there any way to find out the list of listeners running in the server.

i have the rights to access the server thru telnet. but not the listener.ora
file.... how do i know the listerners running in the server???

      and we said...
you'd have to probe each "port" -- much like a hacker.

The listener can run on ANY and EVERY port.

netstat may be of help -- but you might not be able to see enough unless you are
root. consider:

[tkyte@localhost tkyte]$ netstat -pl
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address  State
PID/Program name
tcp        0      0 *:32768                 *:*              LISTEN      -
tcp        0      0 172.16.79.1:netbios-ssn *:*              LISTEN      -
tcp        0      0 *:sunrpc                *:*              LISTEN      -
tcp        0      0 *:x11                   *:*              LISTEN      -
tcp        0      0 *:1521                  *:*              LISTEN      -
tcp        0      0 *:ftp                   *:*              LISTEN      -
tcp        0      0 *:2100                  *:*              LISTEN      -
tcp        0      0 *:ftp                   *:*              LISTEN      -
tcp        0      0 *:ssh                   *:*              LISTEN      -
tcp        0      0 *:telnet                *:*              LISTEN      -
tcp        0      0 localhost.us.oracle:ipp *:*              LISTEN      -
tcp        0      0 *:32860                 *:*              LISTEN      -
udp        0      0 *:32768                 *:*                          -
udp        0      0 localhost.us.orac:32776 *:*                          -
udp        0      0 172.16.79.1:netbios-ns  *:*                          -
udp        0      0 *:netbios-ns            *:*                          -
udp        0      0 172.16.79.1:netbios-dgm *:*                          -
udp        0      0 *:netbios-dgm           *:*                          -
udp        0      0 localhost.us.orac:32779 *:*                          -
udp        0      0 localhost.us.orac:32780 *:*                          -
udp        0      0 *:4500                  *:*                          -
udp        0      0 localhost.us.orac:29747 *:*              ....

vs the root output:

[root@localhost root]# netstat -pl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
PID/Program name
tcp        0      0 *:32768                 *:*     LISTEN      2895/rpc.statd
tcp        0      0 172.16.79.1:netbios-ssn *:*     LISTEN      3509/vmware-smbd
tcp        0      0 *:sunrpc                *:*     LISTEN      2876/portmap
tcp        0      0 *:webcache              *:*     LISTEN      7876/tnslsnr
tcp        0      0 *:x11                   *:*     LISTEN      3627/X
....

if they are not registered somewhere (/etc/services for example, again -- counts
on people setting this up), and you don't have access to root, and you don't
have access to the confirguration files -- well, you must not have a need to
know cause no ones given you that right.

January 18, 2004 Amit -- Thanks for the question regarding "Oracle
      across the OS platforms", version 8.1.7

      You Asked
I have 2 machines at different locations.One is running on Solaris 8 and other
on Windows 2000.Both machines have Oracle 8i.

Can i recover database which is running on Solaris 8 on windows 2000 machine.I
mean i want to sync oracle database which is running on Solaris with oracle
running on windows 2000.

Is it possible ?? Can i sync oracle database across the OS platforms??

      and we said...
No, not until Oracle10g are datafiles portable across operating systems.  Prior
to oracle 10g, you cannot take a file from operating system "x" and place onto
operating system "y" unless Y = X.
Review &
      Followup

      Rating: 4
There are other
      options  January 19, 2004
Reviewer:  Warren
       from Victoria, BC, Canada
However, there are a lot of "backup/recovery like" operations that you can
perform across OS's.  You can't copy your datafiles, but you can perform
inserts, updates, deletes across your DB's on different OS's.  Therefore you can
perform synchronization type operations on these databases using DML.

As well, you can perform all export/import functions across OS's (the export
file is platform neutral).

January 19, 2004 sushant -- Thanks for the question regarding
      "before & after triggers and jre version", version 8.1.5

      You Asked
   what is actual difference between before and after triggers
   is there anything substantial  which  can be acheived by a before
  trigger which cannot be done by after triggers or viceversa

  how do i know which jre version is running in my database(is there a command
to
find out)

normally if i want to delete a row from the primary key table
and a corresponding row should be deleted from the foreign key table
we use the on delete cascade option while creating the foreign key table
  but suppose if we dont use the on delete cascade option while creating the
table then how do we acheive it along with a normal delete statement
on a row from the primary key table

      and we said...
For the before/after trigger:

Well, a before trigger can prevent the work from ever taking place -- an AFTER
trigger will have allowed the work to happen.  So, if you were using a statement
trigger to do additional privilege checking -- it would be infinitely more
performant in a BEFORE trigger, thereby preventing the work from ever happening
(less wasted cycles processing a statement that will be rolled back which itself
is expensive).

A before row level trigger can modify the :new values, an after row trigger
cannot.

For the on delete cascade:

If you did not have the on delete cascade, you will have to explicitly delete
from the child tables PRIOR to deleting from the parent table.

and lastly on the jre version, this information is available in the system
properties:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace and resolve java source
named "ver"
  2  as
  3  public class util
  4  {
  5      public static void prop()
  6      {
  7          System.getProperties().list(System.out);
  8       }
  9      public static String ver()
 10      {
 11          return System.getProperty("java.version", "unknown");
 12      }
 13  }
 14  /

Java created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure jdk_props as
language java name 'util.prop()';
  2  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace function jdk_ver return
varchar2 as language java name
  2  'util.ver() return java.lang.String';
  3  /

Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2      dbms_java.grant_permission( user,
  3                                 'java.util.PropertyPermission',
  4                                 '*',
  5                                 'read,write' );
  6      commit;
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set serveroutput on size 1000000
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_java.set_output(1000000)

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec jdk_props
-- listing properties --
java.specification.name=Java Platform API Specification
java.version=1.2.1
oracle.aurora.mts.INIT=oracle.aurora.mts.rdbms.INIT
user.timezone=EST
java.specification.version=1.2
java.vm.vendor=Oracle Corporation
java.vm.specification.version=1.0
user.home=
java.naming.factory.initial=oracle.aurora.namespace.InitialContex...
os.arch=sparc
java.vendor.url=
http://www.oracle.com/java/
file.encoding.pkg=sun.io
user.region=US
java.home=/export/home/ora817/javavm/
java.class.path=
line.separator=
java.io.tmpdir=/var/tmp/
jdbc.drivers=oracle.jdbc.driver.OracleDriver
os.name=Solaris
java.vendor=Oracle Corporation
oracle.jserver.version=8.1.7
oracle.server.version=8.1.7
java.library.path=/export/home/ora817/lib
java.vm.specification.vendor=Sun Microsystems Inc.
oracle.aurora.namespace.INIT=oracle.aurora.namespace.rdbms.INIT
oracle.aurora.rdbms.SID=ora817dev
file.encoding=ISO8859_1
oracle.aurora.mts.session.INIT=oracle.aurora.mts.session.rdbms.INIT
java.specification.vendor=Sun Microsystems Inc.
oracle.aurora.vm.environment.name=rdbms
user.name=
user.language=en
java.vm.name=JServer VM
java.vm.specification.name=Java Virtual Machine Specification
java.class.version=46.0
oracle.aurora.rdbms.oracle_home=/export/home/ora817/
sun.boot.library.path=/export/home/ora817/lib:/export/home/...
sqlj.runtime=sqlj.framework.ide.aurora.rdbms.Oracl...
java.naming.factory.url.pkgs=oracle.aurora.ejb.jndi:com.sun.jndi.u...
java.protocol.handler.pkgs=oracle.aurora.rdbms.url
os.version=5.7
java.vm.version=1.2.1
java.compiler=
path.separator=:
file.separator=/
user.dir=

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select jdk_ver from dual;

JDK_VER
--------------
1.2.1

Review &
      Followup

      Rating: 5
Very
      Informative post.  September 20, 2002
Reviewer:
      Yogeeraj  from Mauritius
Hi,

Is the JRE version in the database related to the version of JRE we use in our
JDeveloper?

will there be any issues if i use latest version of JDeveloper for application
development with our Oracle 8.1.7.4 database?

Thank you for a reply.

Best Regards
Yogeeraj

          Followup:

No, the jre in the database is the jre that was in widespread use when the
database was released.

the jre in jdev is the jre that was in widespread use when jdev was released.

If you are planning on putting the java IN the database (eg: java stored
procedures), then you might find there are features available in java as seen by
jdeveloper that are not available in the jre in the database.

      Rating: 5
When should one
      use Before and After trigger  January 18, 2004

Reviewer:  Sami  from Jersey City,NJ,USA
Dear Tom,
Hope you are back to home.

I understand that AFTER trigger has some limitations.
Otherthan that if my  operation is doable in both BEFORE and AFTER trigger which
one I should prefer?

<Asktom>
o Well, a before trigger can prevent the work from ever taking place -- an AFTER
trigger will have allowed the work to happen.  So, if you were using a statement
trigger to do additional privilege checking -- it would be infinitely more
performant in a BEFORE trigger, thereby preventing the work from ever happening
(less wasted cycles processing a statement that will be rolled back which itself
is expensive).

o A before row level trigger can modify the :new values, an after row trigger
cannot.
</Asktom>

<Document>
"Oracle9 i Application Developer’s Guide - Fundamentals"

Note: AFTER row triggers are slightly more efficient than BEFORE row triggers.
With BEFORE row triggers, affected data blocks must be read (logical read, not
physical read) once for the trigger and then again for the triggering statement.
Alternatively, with AFTER row triggers, the data blocks must be read only once
for both the triggering statement and the trigger.
</Document>

Asktom is more favor of BEFORE trigger and DOCUMENT is more favor of AFTER
trigger? After reading both,I am bit confused about BEFORE and AFTER trigger.

Could you please enlighten me when should one go for BEFORE trigger and AFTER
trigger.

Thanks Tom.

          Followup:

well, a before trigger has limitations too -- let's compare a before to an after
ROW trigger:

a) before for each row -- can modify the values HOWEVER, a before for each row
trigger cannot see what value will actually be placed into the table (hence,
never use a before for each row trigger to verify values!!! it may not be seeing
the final value)

b) after for each row -- cannot modify the values HOWEVER, an after for each row
trigger sees the value of the column(s) that will actually be inserted into the
table.

Additionally -- you'll find an AFTER trigger to cause less redo to be generated.

ops$tkyte@ORA9IR2> create table t ( x )
  2  as
  3  select rownum from all_objects;

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace trigger t_trigger before update on t for
each row
  2  begin
  3          if ( :new.x is null )
  4          then
  5                  null;
  6          end if;
  7  end;
  8  /

Trigger created.

ops$tkyte@ORA9IR2> set autotrace traceonly statistics;
ops$tkyte@ORA9IR2> update t set x = x;

30614 rows updated.

Statistics
----------------------------------------------------------
         35  recursive calls
      62505  db block gets
         73  consistent gets
         47  physical reads
   13512980  redo size
        793  bytes sent via SQL*Net to client
        787  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      30614  rows processed

ops$tkyte@ORA9IR2> drop trigger t_trigger;

Trigger dropped.

ops$tkyte@ORA9IR2> create or replace trigger t_trigger AFTER update on t for
each row
  2  begin
  3          if ( :new.x is null )
  4          then
  5                  null;
  6          end if;
  7  end;
  8  /

Trigger created.

ops$tkyte@ORA9IR2> update t set x = x;

30614 rows updated.

Statistics
----------------------------------------------------------
         48  recursive calls
      31346  db block gets
         68  consistent gets
          0  physical reads
    7494192  redo size
        793  bytes sent via SQL*Net to client
        787  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      30614  rows processed

ops$tkyte@ORA9IR2> set autotrace off

many times -- i find that the statement:

Otherthan that if my  operation is doable in both BEFORE and AFTER trigger which

one I should prefer?

is actually made "by mistake"  You cannot validate (reliably) values in a
BEFORE trigger -- since they are not stable (there could be other before
triggers and triggers of the same type fire in whatever order WE feel like
firing them so -- you could have your validate happen AND THEN another trigger
comes and changes the value to something that would have failed your
validation).

Use an after trigger when you do not need to modify the values.  Use a before
trigger to modify values.  Use an after trigger to validate values....

      Rating: 5
I Salute
      You  January 18, 2004
Reviewer:  Sami
       from NJ,USA
As usual THE BEST ANSWER & CLARIFICATION with EXAMPLES.

      Rating:
      5
  January 18, 2004
Reviewer:
      A reader

      Rating: 5
Modify values
      after Validate values  January 18, 2004
Reviewer:
      A reader
Dear Tom,

How about if I want to modify :new value based on some validation?. Basically I
want perform both validation and modification like below trigger.

Kindly need your suggestion.

CREATE OR REPLACE TRIGGER  MY_ZIP_CODE_TR01
 BEFORE INSERT OR UPDATE ON  MY_ZIP_CODE
 FOR EACH ROW
 DECLARE
 INVALID_ZIP_CODE EXCEPTION;
 BEGIN

 IF MY_PKG.MY_PROC(:NEW.FROM_ZIP_CODE) = 1
    AND MY_PKG.MY_PROC(:NEW.TO_ZIP_CODE) = 1 THEN
     :NEW.FROM_ZIP_CODE := LPAD(:NEW.FROM_ZIP_CODE,10);
     :NEW.TO_ZIP_CODE := LPAD(:NEW.TO_ZIP_CODE,10);
 ELSE
   RAISE INVALID_ZIP_CODE;
 END IF;

 EXCEPTION WHEN INVALID_ZIP_CODE THEN
 RAISE_APPLICATION_ERROR(-20002, 'ZIP CODE FORMAT NOT VALID');
 END;
 /

          Followup:

as long as you do not have other triggers (in the before insert or update for
each row mode), you will be OK with this.

Why would you declare an exception, to throw it, only to catch it yourself and
then use raise_application_error?  seems to be an obscure roundabout way to just
code:

if ( f(x) = 1 and f(y) = 1 )
then
   x:=...
   y:=...
else
   raise_application_error( .... );
end if;

January 19, 2004 Lisa -- Thanks for the question regarding "How to
      re-open a closed database without shutdown DB ?", version 816

      You Asked
While in database, I did:
SQL> alter database close;

Database altered.

< change the init ora file to increase db_block_buffers from 3200 to 4000 >
SQL> alter database open;
alter database open
*
ORA-01531: a database already open by the instance
<message of 1531- Action: If you wish to open a new database on the instance,
first shut down and then restart the instance and retry the  operation >
In order to pickup the new DB_BLOCK_BUFFERS size,

1. How can I re-open the current database without shutdown and then startup the
database?

2. In a 24X7 environment, if I want to change some parameter and make it
available, do I
have to bounce the database? Is there any way not to shutdown and make the
change in init ?

Thanks.

      and we said...
1) You have to shutdown.  You can issue an alter database OPEN once per
instance.  You can use "startup force" after making the modifications.

2) Many parameters are changable on line and every release adds more (eg: in 9i,
you can resize the shared pool and buffer cache online).  If you query
v$parameter, you'll see which are modifiable online and which require a restart.
Review &
      Followup

      Rating: 3
No Sessions
      Connected.  November 06, 2001
Reviewer:  Ganesh
      Raja  from Dubai, UAE
Hi Tom,

I am using Oracle 817 on NT. when i Try to Close my Database using the alter
Database Close it says it can do it only with no sessions conected.

I checked my v$session and i get Lot of Sessions with No Programs and Type as
user Connected ...

What are these sessions and what are their uses.

Regards,
Ganesh R

          Followup:

They are the job queues.

alter system set job_queue_processes=0;

and try again.

      Rating: 5
Please
      Clarify  November 06, 2001
Reviewer:
      A reader  from Houston
It seems that alter database close then ( do maintenance work here) ,shutdown
the db and startup again is equal to use:
shutdown db then startup mount (do maintenance work here).
it is just the different option. Is my thinking right ?

          Followup:

alter database close
do work
startup force

is the same as

shutdown
startup mount
do work
alter database open

yes.

      Rating: 4
Shall we use
      "startup force" to restart the db which is in open
      state?  January 18, 2004
Reviewer:  Sami
       from saminathan@optonline.net
Dear Tom,

Thanks for your help.

The database(dev instance) is up and running.I made few changes in
init.ora(non-dynamic parameters).

Shall I use startup force to restart the instance?

Is "startup force" equivalent to
1)shutdown immediate
2)startup

Correct me if I am wrong?

          Followup:

startup force is more like shutdown abort, startup.

you can use it, but shutdown immediate/startup would be more "graceful" perhaps.

January 19, 2004 David -- Thanks for the question regarding "Space
      Estimation For Schema Objects", version N/A

      You Asked
Tom,
    While investigating the writing of a database object sizing
package I discovered 2 differences in the calculation between
Oracle 7 server and Oracle8. These are listed below. The reasons
behind these differences are either typo errors or actual
changes in the calculations. Can you enlighten me as to what the
true situation is. The documents I was using can be found at:

Oracle 7 Server

http://technet.oracle.com/doc/server73x/SAD73/apA.htm#toc201
Oracle8

http://technet.oracle.com/doc/server804/a58397/apa.htm

Error 1

Oracle 7 Server

Step 1: Calculate the Total Block Header Size
The space required by the data block header is the result of the
following formula:
Space after headers (hsize)
=
DB_BLOCK_SIZE - KCBH - UB4 - KTBBH - (INITRANS - 1) * KTBIT -
KDBH

Oracle8 Release 8.0

Step 1: Calculate the Total Block Header Size
The space required by the data block header is the result of the
following formula:
Space after headers (hsize)
=
DB_BLOCK_SIZE - KCBH - UB4 - KTBBH - ((INITRANS - 1) * KTBIT) -
KDBH

Error 2

Oracle 7 Server

Space used per row (rowspace)=MIN(UB1 * 3 + UB4 + SB2, rowsize)
+ SB2

Oracle8 Release 8.0

Space used per row (rowspace)=MAX(UB1 * 3 + UB4 + SB2, rowsize)
+ SB2

Regards,

David.

      and we said...
Well, mathematically speaking, error #1 is not an error.  The
formula are the same.  The addition of the parans on 8.0 didn't
change the outcome as

(initrans-1)*ktbit

is the same as

((initrans-1)*ktbit)

and since multiplication takes precendence over
addition/subtraction -- the end result is the same.

for #2, that was a documentation bug in the 7.x doc set.

the correct reference to the 8.0.4 doc set is:

http://download-east.oracle.com/docs/cd/A58617_01/server.804/a58397/apa.htm
(missing a dot above)

Review &
      Followup

      Rating: 4
how about in
      9i  January 18, 2004
Reviewer:  steve
       from NYC, NY
Hi Tom,

All the formulas in the qestion mentione by David
are still ture for 9i?

I can find them in 9i documentations.

Thanks!

steve

          Followup:

the formulas have never been (can never be) very accurate.

suggest you

a) create the object
b) load the object with somewhere between 0.01 and 10% of the data --
REPRESENTATIVE data
c) analyze the object(s)
d) multiply

that is the only way you'll get anything near "reality".

January 19, 2004 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 :)

      Rating: 5
DUMP the redo
      to trace files  January 19, 2004
Reviewer:
      Sami  from NJ,USA
Dear Tom
<Asktom>
"you would have to actually DUMP the redo to trace files and inspect that".
</Asktom>

How do I do that? Could you please tell me the command or way to do it?

          Followup:

the really relevant quote was:

<asktom>
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 :)
</asktom>

you can look it up on metalink or file a tar, but it is not something I do cause
it only begs the next question "ok, so i've dumped it, now tell me how to read
it".

something you can research on metalink, but not something I go into here....

January 19, 2004 Lim -- Thanks for the question regarding "query
      with like '...._....' where _ is not a wildcard", version

      You Asked
I wish to query the object name start with S_ in
DBA_OBJECTS, so I issue the SELECT statement as below,
select object_name
from dba_objects
where object_name like 'S_%';

But what I get is all the objects start with S.

Can you please tell me why this happen, and to issue the SQL
statement to get what I want.

Tks.

      and we said...
% matches 0, one or more of ANY character.
_ matches any ONE character (eg: where X like 'A_B' is the same
as where X like 'A%B' and length(x) = 3)

you can code:

select * from dba_objects where object_name like 'S\_%' escape
'\';

you get to pick the escape character and by placing it in front
of _ or %, you can make them be 'normal' characters.

Review &
      Followup

      Rating: 5
Quick follow
      up  March 12, 2002
Reviewer:  Doug
       from CT, USA
Is there anyway to specifiy alphanumeric ranges? Like [a-d]
or [a-z] to indicate an alphanumeric character?

          Followup:

Not with like, see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2200894550208
for an approach to this

      Rating: 4
escape
      character and nvarchar2   January 19, 2004

Reviewer:  Andy  from Germany
Tom, I don't understand this behaviour with escape characters and nvarchar2
columns:

mires@linux2-l2ifag> create table y(vc varchar2(100), nvc nvarchar2(100));

Tabelle wurde angelegt.

mires@linux2-l2ifag> insert into y values ('ab/c', 'xy/z');

1 Zeile wurde erstellt.

mires@linux2-l2ifag> select count(*) from y where vc like 'ab//%' escape '/';

  COUNT(*)
----------
         1

mires@linux2-l2ifag> select count(*) from y where nvc like 'xy//%' escape '/';
select count(*) from y where nvc like 'xy//%' escape '/'
                                                     *
FEHLER in Zeile 1:
ORA-01425: Escape-Zeichen muss eine Zeichenfolge der Länge 1 sein

mires@linux2-l2ifag>  select count(*) from y where nvc like 'xy//%' escape N'/';

  COUNT(*)
----------
         1

I've read in the 9i SQL reference that Oracle automatically converts my escape
character into the same data type as the column I am LIKE-ing, but why does that

mean that my escape character '/' is "lost", but N'/' isn't?

          Followup:

not knowing your environment or anything -- makes things hard.

but -- to get a nchar characterstring constant -- you use N'constant', that is
the way it works.  if you want to use the nvarchar/nchar -- you'll be using
n'x'.

same datatype -- wrong characterset is the problem in the above, '/' is not the
same as n'/'

I think you want to stick with n'  in all cases -- even on your like string.

January 19, 2004 Chacravarthi -- Thanks for the question regarding
      "Database Questions", version 8.0

      You Asked
Dear Sir,
well and wish the same from you.My questions are
1)What is the difference between Block header and Segment header?
2)Why extents do not have headers?
3)Suppose if I am a DBA,how to write a message to all sessions
WITH OUT USING  OS Capabilities?Could you please show a simple
example for that?
4)What is the size of the RAW datatype?Some say as 255 bytes and some say as
2gb.Could you please clarify on this?
Thanks a lot to the service you offer.
Bye!
Yours sincerely,
R.Chacravarthi

      and we said...
1) sort of like asking what's the difference between a person and a cell.

they are different objects.  a block is a block.  a block header has information
describing the block and what is on it.

a segment header describes a segment -- which is a collection of extents, which
are collections of blocks.  so a segment header tells us about the segment --
where it is, how space is used in it and so on.

2) because they don't need one.  they just have a starting file/block# and an
ending file/block#.  not much more to them.

3) you cannot -- this is an OS "feature", and only really works in a 100%
homogenous environment.  the database is a "server".  ANY client in the world
using ANY interface from ANY operating system can connect to it.  It cannot
broadcast to ANY thing that might be attached to it (that would be called a
virus or a worm)

4) luckily we document relevant things like this so you don't have to rely on
heresay!  You can look it and other important facts up!

see

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96524/c13datyp.htm
but a raw is between 1 and 2000, long raw/blob can go bigger

Review &
      Followup

      Rating:
      3
good  January 19, 2004
Reviewer:
      Satyaki  from Mumbai
Can I Shrink the size of a physical DB file?

          Followup:

yes.  sure....

look up alter database, there is a "datafile filename resize nnnn" option.

search for

maxshrink

on this site to see how to compute "how small" you can shrink them.

      Rating:
      5
  January 19, 2004
Reviewer:
      A reader
Satyaki, I think the question you have asked is clearly documented.

Thanks.

January 19, 2004 Brian -- Thanks for the question regarding
      "Returning CLOB from PL/SQL Function", version 8.1.7

      You Asked
I have been through the documentation and I am still not clear on something that
is critical for me to understand before deploying into a production environment.

I have created the following function - the cursor query is just an example of
creating a small resultset so I can experiment with concatenating varchar2
values and return a single clob:

create or replace function testclob return clob
as
    v_clob clob;
begin
    dbms_lob.createtemporary(v_clob,FALSE,DBMS_LOB.CALL);
    dbms_lob.open(v_clob,dbms_lob.lob_readwrite);
    for val in (select 'abc' colalias from dual union
                select 'def' colalias from dual union
                select 'ghi' colalias from dual)
    loop
        dbms_lob.writeAppend( v_clob, length(val.colalias), val.colalias );
    end loop;

    return v_clob;
end;

Because I am actually returning the clob, I am not executing the following
within the function:

    dbms_lob.close(v_clob);
    dbms_lob.freetemporary(v_clob);

I test this from sql/plus as follows:

variable c clob
exec :c := testclob
print c

I get the expected result.  However, when I execute

select * from v$temporary_lobs

it seems that with each call I make within the same sql/plus session, the count
of nocache_lobs goes up instead of remaining at 1.

Bottom line, I have a java JDBC client that will be calling a function like the
above but the session will remain open between calls.  I need to make sure I
handle scope correctly so that memory usage doesn't grow over time.

How do I return the clob and have it free up appropriately?

Please help me understand my options in this kind of scenario.

Thanks - Brian

      and we said...
From the application dev guide on LOBS:

<quote>
Note: The DBMS_LOB.CREATETEMPORARY procedure takes an
optional duration parameter. In PL/SQL, this duration parameter is
used only as a hint about the duration of the LOB data. PL/SQL
calculates the duration of the LOB data internally, taking into
account your hint. You do not have to specify the duration of the
LOB data!
</quote>

In your case, it is a good thing it does this -- since you return the LOB to the
client -- a scope of "CALL" is inappropriate for you.  The call is the call to
the function.  If PLSQL did not recognize you were returning the CLOB and
actually freed it as requested -- the caller would have nothing to print!

If your goal is to use one LOB per session you must either:

o free the LOB when you are done.  Just as you would close a file when you are
done reading it, or a cursor when you know you won't need it anymore

o use a single LOB in a package.

To use a single LOB in a package, you would code:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package testclob_pkg
  2  as
  3
  4      function testclob( p_data in varchar2 ) return clob;
  5
  6  end;
  7  /

Package created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package body testclob_pkg
  2  as
  3
  4  g_clob     clob;
  5
  6
  7  function testclob( p_data in varchar2 ) return clob
  8  as
  9  begin
 10      dbms_lob.trim( g_clob, 0 );
 11      for val in (select p_data colalias from dual union all
 12                  select ' abc' colalias from dual union all
 13                  select ' def' colalias from dual union all
 14                  select ' ghi' colalias from dual)
 15      loop
 16          dbms_lob.writeAppend( g_clob, length(val.colalias), val.colalias );
 17      end loop;
 18
 19      return g_clob;
 20  end;
 21
 22
 23  begin
 24      -- elaboration code, run once when package
 25      -- is first accessed in a sesion
 26      dbms_lob.createtemporary(g_clob,FALSE);
 27      dbms_lob.open(g_clob,dbms_lob.lob_readwrite);
 28  end;
 29  /

Package body created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from v$temporary_lobs;

no rows selected

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_clob clob;
  3  begin
  4      for i in 1 .. 10
  5      loop
  6          l_clob := testclob_pkg.testclob( 'loop ' || i  || ',' );
  7          dbms_output.put_line( dbms_lob.substr( l_clob, 250, 1 ) );
  8      end loop;
  9  end;
 10  /
loop 1, abc def ghi
loop 2, abc def ghi
loop 3, abc def ghi
loop 4, abc def ghi
loop 5, abc def ghi
loop 6, abc def ghi
loop 7, abc def ghi
loop 8, abc def ghi
loop 9, abc def ghi
loop 10, abc def ghi

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from v$temporary_lobs;

       SID CACHE_LOBS NOCACHE_LOBS
---------- ---------- ------------
        14          0            1

(don't use sqlplus variable commands to test this particular one -- sqlplus is
allocating its OWN lobs as well).

Review &
      Followup

      Rating: 5
Excellent -
      clear and educational on multiple fronts  September 24, 2001

Reviewer:  Brian White  from Rockville, MD USA

As with many questions on this forum, the answer highlights techniques that will
prove generally useful and is explained in a way that provides true education.
I've learned so much by reading Tom's new book in conjunction with reviewing the
questions answered in this forum.

      Rating: 5
Very very Good
      Example!  December 16, 2003
Reviewer:  Olga
       from Vienna
Yes, Tom is great!

Reading Tom's Books and his web-site is better than any ocp course, i bet!
That's Oracle live and no pseudo science. I spent so much money for Oracle books
where the pages were full of nothing (like a HTML-Reference in a JAVA-Book), now
I chucked them away.

Many Thanks Tom!

      Rating:
      4
CLOB  January 19, 2004
Reviewer:  Ik
       from BG
Tom,

Hope you are back from your trip?

Two questions

(1) I have almost the same situation except that my function gets called within
an SQL statement. I am using a CLOB datatype inorder to return a string > 4000.
I have followed your same methodology and iam opening the CLOB in a separate
PL/SQL block within the package.

But, my nocache_lobs count shows 20 (which is equal to the number of rows
returned by the SQL).
How do i bring the count down to 1?

(2) This question is triggered from your usage of the anonymous PL/SQL block.

I have a PL/SQL Table declared at the package level. As per design, this table
builds up as the SQL progresses through the rows and should hold data for the
duration of the SQL.

I have a separate procedure in the same package which i call (before executing
the SQL again)  inorder to flush the PL/SQL table.

For eg:-
SELECT package.function(arg1,arg2...argn) from table1..n

Before i execute the same SQL again in the same session i have to call the
procedure to flush the table.

Tom, is there any way to tell whether the package is called second time from a
different (or same) query in the same session. If so, i need to clear it.

Thank You

          Followup:

1) the caller must close the lob.

2) not following you.  since you cannot select from a PLSQL table type?  one
would need a small, yet complete, but concise example of what you mean.

      Rating: 5
Further on the
      same posting  January 19, 2004
Reviewer:  Ik
       from BG
Tom,

Further to question (1) on CLOBs - I found that by using an anonymous PL/SQL
block to call the function results in creation of only one NOCACHE_LOB. Whereas,
the same query when executed either in SQL Plus or through Oracle Reports (where
it is actually used), the count= number of times the function was called.

Is there an overhead this way? How do i minimize this.

Also, there is a performance difference when i have an anonymous PL/SQL block
within the package to do the CREATION and OPEN once for the CLOB (your way)
vs...doing it eachtime inside the function. Contrary to what i expected the
second option repeatedly came out faster.

BEGIN
        -- elaboration code, run once when package
       -- is first accessed in a session
        DBMS_LOB.CREATETEMPORARY(v_retvalue, FALSE);
        DBMS_LOB.OPEN(v_retvalue,dbms_lob.lob_readwrite);

vs

        v_retvalue       CLOB;
   BEGIN

        DBMS_LOB.CREATETEMPORARY(v_retvalue, FALSE, DBMS_LOB.CALL);
        DBMS_LOB.OPEN(v_retvalue,dbms_lob.lob_readwrite);
        DBMS_LOB.TRIM(v_retvalue,0);

Thanks.

January 19, 2004 Doug -- Thanks for the question regarding
      "trouble justifying temp space", version 9203

      You Asked
Tom - I have a "small" datawarehouse with 15GB of temp space which is perceived
as "plenty".   I think it may need more when more users start hitting it.
Question is, what's the largest amount of temp space you have seen attached on
an Oracle database, did you agree it was necessary, and what was the
justification?

      and we said...
see

http://www.tpc.org/tpch/results/tpch_result_detail.asp?id=102103103
I got bored after counting 20 or so 13,440meg tempfiles.

EG: the answer is "big"

the justification is "it won't work without it"

If you have insufficient temp space -- well, you know what happens -- end user
queries fail after running for a long time.  they are not happy, you've burned
tons of resources and there you go.

Review &
      Followup

      Rating:
      5
  January 18, 2004
Reviewer:  Dave
       from Colorado
I notice that the Full Disclosure Report shows the 9i test database using
dictionary managed tablespaces. Do you have any comments on that?

          Followup:

the tpc-c's use LMT's...

the 10g tpc-h's use LMT's...

all I can say is these benchmarks are old (been running them for a long long
time) and change is something they introduce slowly..... (they did use some
lmt's in there as well)

during the tpc-h's bear in mind -- the allocation of extents is something that
pretty much "never happens" except for TEMP and TEMP was a LMT.  The load (where
extents would be allocated) is not part of the benchmark.  LMTs provide for

o virtually eliminate your ability to fragment your tablespace
o more performant space allocation
o less thinking on your part (if you use auto-allocate like they did in 10g)

      Rating: 5
about TPC-H
      scripts  January 19, 2004
Reviewer:  Alex
       from Spb
about TPC-H scripts:
I'd like to hear the comment too
why all tables with partition created in DM Tablespaces
but other (and indexes) in LM Tablespaces
thanks
very good support

          Followup:

see above.

January 19, 2004 -- Thanks for the question regarding "Regd Your
      article in Oracle Magazine..", version 9.2.0.4

      You Asked
Tom,

I was looking at

http://asktom.oracle.com/~tkyte/asktom_articles.html
and at the bottom i saw this link

http://www.oracle.com/oramag/oracle/00-jul/index.html?o40tom.html
Back To Basics Articles about

    * What's the difference between record size and row size?
    * More Sizing Matters
    * Maximum Row Size
    * Pinning a Sequence in the SGA
    * A Closer Look at Hot Backup Mode
    * Resizing Redo Log Files

It doesnt seem to work, can you please provide the link as i tried searching at
otn.oracle.com but wasn't able to find.

Thanks.

      and we said...
http://www.oracle.com/oramag/
is the link to oracle magazine - there is a link to "read past issues"

http://otn.oracle.com/oramag/oracle/index.html
and under there we can find that -- hey, they appear to have taken away the
really old ones!!!  bummber, I never expected that.  I'll ask them about that
but I've a feeling they are lost and gone forever :(

I'll have to start archiving myself now...

Review &
      Followup

      Rating: 4
In Similar
      Vein...  January 19, 2004
Reviewer:  Johan
      Snyman  from JHB SA
Tom,

Would it be possible to make the presentation you gave during the web seminar on
17 Dec 2003 on Chapter 7 "Efficient Schemas" from "Effective Oracle by Design"
available for download ?

          Followup:

see

http://asktom.oracle.com/~tkyte/
i've made them available there.  The notes have word for word what I said online
(cause I just read them :)

      Rating: 5
Victor
      G.  January 19, 2004
Reviewer:
      A reader
Tom,

As you said the chances are dim can you please tell us something about the
difference between record size and row size?

Thanks
Victor.

          Followup:

in relation to what?  row size/record size of what? (and I've just found out
they are going to repost the year 2000 stuff for me)

      Rating:
      5
  January 19, 2004
Reviewer:
      A reader
Thanks.

January 19, 2004 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.

      Rating: 3
Thin Driver
      & NT Authentication  January 19, 2004

Reviewer:  Fred  from irvine, ca
You mentioned above that with client connections via the web, you can do:
DriverManager.getConnection ("jdbc:oracle:oci8:/@");

My followup question is two-fold:  1) Will thin drivers work with NT
authentication?  2)  I read another post where you mentioned that NT
authentication over the web cannot be done, so which is true?
http://asktom.oracle.com/pls/ask/f?p=4950:8:4693683827174539902::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:5322995057493,
Thanks in advance

Fred

          Followup:

1) you need to use the thick drivers for OS authentication (not just "nt"
authentication)

2) we recently have added support for this

http://download-west.oracle.com/docs/cd/B10464_01/manage.904/b10851/kerberos.htm#1011142
but this is not logging into the database from the browser using your
credentials -- this is you getting authenticated to the MIDDLE TIER using your
OS credentials.

January 19, 2004 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://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76956/dba.htm#1064
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76956/users.htm#682
http://download-west.oracle.com/docs/cd/A87860_01/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://download-west.oracle.com/docs/cd/A87860_01/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)

      Rating: 3
Simple Yes or
      No  January 19, 2004
Reviewer:  Fred
       from irvine, ca
So if the server has REMOTE_OS_AUTHENT=FALSE and both server and client has
SQLNET.AUTHENTICATION_SERVERS=(NTS) then the client should NOT be able to
connect, right?

          Followup:

not a simple yes or no.

the client should be able to connect locally.
maybe not remotely, but locally sure (they are not "remote" after all).

January 19, 2004 Awan -- Thanks for the question regarding "How
      can we design the big database. e.g. Tera byte Database", version 8.1.7

      You Asked
Dear Kyte:

You are really doing a great job, and the way you explain the questions, I
really love that style, because it really make the things clear.

Well my question is:

1)Some times we asked in interview, that how will you design the very big
database, e.g. of tera bytes or may be more than this. And how you will
confiugre the SGA? How many tablespaces you will make? And how many datafile
will you make? And how many rollback segments will be created etc. So how can we
answer this question.

2) And if we have some powerfull machine with multiple processors and many
physical hard disks, then the database will be designed differently or will be
changed with respect to the multiple processors.

Thanks and Regards,
Awan

      and we said...
1) and I would look at them like they were crazy.

I would ask them -- "ok, so tell me how you would configure the SGA and I'll
give you 5 examples of tbyte+ instances where your method would be totally
messed up"

"tell me how many tablespaces you would have" and I'll give you 5 example where
your method would be totally messed up

same with the datafiles

and rollback segments -- ESPECIALLY this one (well, the SGA question is a close
second).

You can only talk in generalities here.  You size the SGA based on the needs of
of the application.  It this a terabyte transactional system or a terabyte data
warehouse (we do both).  The needs would be totally different.  How much ram is
on the machine, how many users (wanna leave a bit of space for the PGA you
know).  Anything else going on here on this machine we need to be aware of?  My
first response would be "well, since this database doesn't exist yet, we should
not even for a second consider using 817 but rather 9iR2 (or whatever is the
latest production release of Oracle as you are reading this) since it has about
a gazillion features for doing big databases that 817 didn't.

How many tablespaces?  well, won't that be a function of the number of objects
-- and you are using locally managed tablespaces so the right answer might be "I
want three -- small, medium and large".  For a system like think, you won't be
micromanaging storage -- you'll be letting the hardware dudes do that at the
storage array you'll be using.  So, you don't need to segregate objects by
tablespace and GIVEN that we are using 9i, we'll have block level recovery which
means we can recover from corruptions keeping the datafile and tablespaces
online and avaiable so we don't need thousands of tablespaces.  (the answer
won't be three, it'll be some number greater then three, but that is about all I
can say).

How many datafiles?  depends on the OS doesn't it.  We'll be using the biggest
we can -- generally in the 16 to 32gig range most likely.

Rollback segments -- we, we are going to use 9i, so RBS configuration is not
done by "number of rbs's and size of each" but rather "what is the retention
period we need to specify".  and that is based solely on the length of the
longest running query so..... insufficient data to compute!

2) I've never heard of taking the number of CPU's into consideration when
designing a physical schema personally.

We might use different features (lots more PARALLEL operations for loading,
indexing, bulk updates, etc) but I've never thought about doing something in my
physical design based on the number of cpu's -- no.  (the one thing that'll
change is the number of cpu's - we add more, we change hardware -- #cpu's is not
a constant.  in fact, it can change while the system is up and running these
days!)
Review
      & Followup

      Rating:
      5
  October 20, 2002
Reviewer:  Ghulam
      Rasool
Awan asked a good question and as usual YOU replied well. I wish the people who
ask these kind of questions, read this.

      Rating: 5
Many thnaks for
      such a nice answer.  October 20, 2002
Reviewer:
      Awan
Dear Kyte:

Well it is very good answer indeed. You are smart. I am happy that you explain
the things in a very proper manner.

      Rating: 4
excellent
      advise  January 19, 2004
Reviewer:  Eric D.
      Peterson  from Bellevue, WA
A coworker and our DBA is recommend that we go from multiple tablespaces to just
two (one for tables and one for indexes).  This is part of our move from 8i to
9i.  I've been trying to explain to them the usefullness of a variety of
tablespaces with different extents sizes (your small, medium, large model). But
to no avail.  Can you provide more fodder for this discussion.  I've been
sending them various articles from here but would like some more concrete
examples on why one tablespace may not be the best idea.

I haven't heard them say but I'd guess they are thinking of one tablespace with
varying extent size.  I've seen your comments on how that can lead to fragmented
available space.  Would this lead to a regular scheduled coalessing of the
tablespace to defregment it?

Thanks much for any help you can provide.
Eric

          Followup:

I use two per application at most myself (usually just one -- both tables and
indexes go into it)

using LMTs with autoallocate extents is just too easy to skip over.  I've grown
over the years to become very fond of that storage mechanism (wrote about it in
my last book).  Even easier than small medium and large.

      Rating: 5
how i handle
      bad interview questions  January 19, 2004

Reviewer:  Ryan Gaffuri  from Tysons Corner, VA

I routinely get asked interview questions where the interviewer is looking for a
buzzword response and they clearly have not even read the documentation. The
most generic of these is 'How do you tune a query?'. They are looking for these
buzzwords: explain plan, 'cost', full table scans, and cartesian joins.

The best way I have found to answer this is to do the following:

'I'm not sure what you are looking for. So first I'll give you the generic
response, then I'll tell you how I do it and why it is better'.

The guys I talked to argued with me over ignoring 'cost', but after a little bit
they realized I knew what I was doing. Occasionally you will get the true 'know
it all' interviewing you who insists what is incorrect is correct. The vast
majority of time people will realize you know more than they do. Since the
people interviewing you have not read the documentation, that isn't saying
much...

I get that SQL Tuning question almost every time.

Tom-- The OFA recommends using 7 tablespaces. Should it be re-written? Isn't
that the official Oracle 'statement' on how to set up your tablespaces and
datafiles?

I'm assuming by two tablespaces you mean Undo, data, and then a third for system
right?

          Followup:

I'm talking at the "application level"

someone comes to me and says "hey, i have this new app we are going to put into
your database that we just wrote.  It does 'x'.  Can you give me some space"

Well, I'll create them a single tablespace, with locally managed extents, set to
autoallocate and tell them to leave off ALL storage clauses and use that single
tablespace.  At most, they might get two -- to separate tables from indexes, but
not usually.

      Rating: 5
Thanks much for
      such clarity  January 19, 2004
Reviewer:  Eric
      D. Peterson  from Bellevue, WA
Thanks that will help in our discussion.  The current discourse now is on what
are the advantages for each side.  I.e. Using three tablespaces = better
performance, better management of tables.  versus.  One tablespace = ease of
use, better managment by allowing the database to handle it, easier coding.

But what would you say are the downsides to using three tablespaces versus one?

Thanks.
Eric

          Followup:

there really are not downsides in general to either implementation.  It starts
to come down to a matter a choice.

it is easier to judge growth in the uniform sized lmt.

if you don't know the ultimate size of all objects (and hence cannot figure out
"s", "m", and "l"), autoallocate looks really good.

January 20, 2004 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.

      Rating: 3
Query on
      trace  January 19, 2004
Reviewer:  vivek
      Saxena  from UK
Tom,
During the performance testing ( via Load runner ) our client had complained
that our application is "SLOW". They are looking for 3200 records/hour and at
present we are getting only 2000/hrs. So i have traced a session in our office
and come up with following result. What i am not understood is why i am getting
parse/exec/fetch for SYS.ALL_ARGUMENTS  every time oracle tried to run procedure
from a package. Our application has packages containing many procedure with in
it.
Here is text from my trace.

=====================
PARSING IN CURSOR #1 len=165 dep=0 uid=45 oct=3 lid=45 tim=94659226 hv=106591048
ad='422ce00'
SELECT UNIQUE POSITION, DATA_TYPE, IN_OUT FROM SYS.ALL_ARGUMENTS WHERE OBJECT_ID
= 31455 AND OBJECT_NAME = 'SP_ATTENDEE_TYPE' AND DATA_LEVEL=0 ORDER BY POSITION
DESC
END OF STMT
PARSE #1:c=3,e=3,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=94659229
EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=94659229
FETCH #1:c=1,e=2,p=0,cr=15,cu=0,mis=0,r=1,dep=0,og=4,tim=94659231
=====================
PARSING IN CURSOR #39 len=50 dep=0 uid=45 oct=47 lid=45 tim=94659231
hv=883693539 ad='422be94'
BEGIN PKG_RBK_COMMON.SP_ATTENDEE_TYPE ( :V1); END;
END OF STMT
PARSE #39:c=4,e=3,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=94659234
=====================
PARSING IN CURSOR #47 len=65 dep=1 uid=42 oct=3 lid=42 tim=94659235 hv=831795434
ad='47efc1c'
SELECT ATTEND_TYPE   FROM RM_ATTENDTYPE ORDER BY ATTEND_TYPE DESC
END OF STMT
PARSE #47:c=3,e=3,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=94659237
EXEC #47:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=94659237
EXEC #39:c=4,e=5,p=0,cr=0,cu=0,mis=0,r=1,dep=0,og=4,tim=94659239
FETCH #47:c=0,e=0,p=0,cr=1,cu=0,mis=0,r=5,dep=0,og=4,tim=94659239
STAT #47 id=1 cnt=5 pid=0 pos=0 obj=31579 op='INDEX FULL SCAN DESCENDING '
XCTEND rlbk=0, rd_only=1
STAT #1 id=1 cnt=1 pid=0 pos=0 obj=0 op='SORT ORDER BY '
STAT #1 id=2 cnt=1 pid=1 pos=1 obj=0 op='SORT UNIQUE '
STAT #1 id=3 cnt=1 pid=2 pos=1 obj=0 op='FILTER '
STAT #1 id=4 cnt=2 pid=3 pos=1 obj=0 op='NESTED LOOPS '
STAT #1 id=5 cnt=2 pid=4 pos=1 obj=0 op='NESTED LOOPS '
STAT #1 id=6 cnt=2 pid=5 pos=1 obj=18 op='TABLE ACCESS BY INDEX ROWID OBJ$ '
STAT #1 id=7 cnt=2 pid=6 pos=1 obj=33 op='INDEX UNIQUE SCAN '
STAT #1 id=8 cnt=2 pid=5 pos=2 obj=22 op='TABLE ACCESS CLUSTER USER$ '
STAT #1 id=9 cnt=2 pid=8 pos=1 obj=11 op='INDEX UNIQUE SCAN '
STAT #1 id=10 cnt=2 pid=4 pos=2 obj=63 op='TABLE ACCESS BY INDEX ROWID ARGUMENT$
'
STAT #1 id=11 cnt=4 pid=10 pos=1 obj=108 op='INDEX RANGE SCAN '
STAT #1 id=13 cnt=1 pid=3 pos=3 obj=0 op='NESTED LOOPS '
STAT #1 id=14 cnt=2 pid=13 pos=1 obj=0 op='FIXED TABLE FULL X$KZSRO '
STAT #1 id=15 cnt=2 pid=13 pos=2 obj=82 op='TABLE ACCESS BY INDEX ROWID OBJAUTH$
'
STAT #1 id=16 cnt=2 pid=15 pos=1 obj=101 op='INDEX RANGE SCAN '
STAT #1 id=12 cnt=1 pid=3 pos=2 obj=0 op='FIXED TABLE FULL X$KZSPR '
=====================
PARSING IN CURSOR #1 len=69 dep=0 uid=45 oct=47 lid=45 tim=94659254
hv=1302911214 ad='56e5370'
begin dbms_utility.name_resolve(:v1,:v2,:v3,:v4,:v5,:v6,:v7,:v8);end;
END OF STMT
PARSE #1:c=2,e=2,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=94659256
EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=0,og=4,tim=94659257
=====================
PARSING IN CURSOR #1 len=168 dep=0 uid=45 oct=3 lid=45 tim=94659259
hv=4229066408 ad='4288c3c'
SELECT UNIQUE POSITION, DATA_TYPE, IN_OUT FROM SYS.ALL_ARGUMENTS WHERE OBJECT_ID
= 31451 AND OBJECT_NAME = 'SP_ATTENDEE_DETAILS' AND DATA_LEVEL=0 ORDER BY
POSITION DESC
END OF STMT
PARSE #1:c=4,e=3,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=94659260
EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=94659260
FETCH #1:c=0,e=0,p=0,cr=18,cu=0,mis=0,r=3,dep=0,og=4,tim=94659262
=====================
PARSING IN CURSOR #38 len=66 dep=0 uid=45 oct=47 lid=45 tim=94659264
hv=879131564 ad='4279c88'
BEGIN PKG_RBK_DETAILS.SP_ATTENDEE_DETAILS ( :V1,  :V2,  :V3); END;
END OF STMT
PARSE #38:c=3,e=3,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=94659265
=====================
PARSING IN CURSOR #47 len=727 dep=1 uid=42 oct=3 lid=42 tim=94659267
hv=801113033 ad='44af3e4'
SELECT
RM_ATTENDEE.ADD_COMMENTS,RM_ATTENDEE.ATTEND_TYPE,RM_ATTENDEE.AUTO_NUMBER,RM_ATTEN
DEE.BL_ID,RM_ATTENDEE.BOOKING_ID,RM_ATTENDEE.CONTACT_TEL,RM_ATTENDEE.DATE_CREATED
,RM_ATTENDEE.DATE_OF_VISIT,RM_ATTENDEE.INT_CONTACT,RM_ATTENDEE.NAME_FIRST,RM_ATTE
NDEE.NAME_LAST,RM_ATTENDEE.REQUEST_ID,RM_ATTENDEE.STATUS,RM_ATTENDEE.TIME_CREATED
,RM_ATTENDEE.TIME_EXPECTED,RM_ATTENDEE.VISITOR_PASS,RM_ATTENDEE.TIME_IN,RM_ATTEND
EE.TIME_OUT,EM.NAME_LAST CONTACTNAMELAST,EM.NAME_FIRST CONTACTNAMEFIRST   FROM
RM_ATTENDEE,EM  WHERE RM_ATTENDEE.REQUEST_ID = :b1  AND RM_ATTENDEE.BOOKING_ID =
:b2  AND RM_ATTENDEE.INT_CONTACT = EM.EM_ID (+)   ORDER BY
RM_ATTENDEE.DATE_OF_VISIT,RM_ATTENDEE.TIME_EXPECTED,RM_ATTENDEE.NAME_LAST,RM_ATTE
NDEE.NAME_FIRST
END OF STMT
PARSE #47:c=3,e=3,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=94659268
EXEC #47:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=94659270
EXEC #38:c=5,e=5,p=0,cr=0,cu=0,mis=0,r=1,dep=0,og=4,tim=94659270
FETCH #47:c=0,e=0,p=0,cr=4,cu=4,mis=0,r=1,dep=0,og=4,tim=94659271
STAT #47 id=1 cnt=1 pid=0 pos=0 obj=0 op='SORT ORDER BY '
STAT #47 id=2 cnt=1 pid=1 pos=1 obj=0 op='NESTED LOOPS OUTER '
STAT #47 id=3 cnt=2 pid=2 pos=1 obj=31580 op='TABLE ACCESS FULL RM_ATTENDEE '
STAT #47 id=4 cnt=1 pid=2 pos=2 obj=25150 op='TABLE ACCESS BY INDEX ROWID EM '
STAT #47 id=5 cnt=2 pid=4 pos=1 obj=25159 op='INDEX UNIQUE SCAN '
XCTEND rlbk=0, rd_only=1
STAT #2 id=1 cnt=1 pid=0 pos=0 obj=27191 op='TABLE ACCESS BY INDEX ROWID CDATA '
STAT #2 id=2 cnt=2 pid=1 pos=1 obj=27192 op='INDEX UNIQUE SCAN '
=====================

This happens every time a procedure is called.

Please help.

Vivek

          Followup:

probably using odbc eh?  what is the client.

      Rating: 2
query on
      trace  January 19, 2004
Reviewer:  vivek
       from uk
Its a Cold fusion MX application and uses JDBC connection using oracle type IV
driver. Oracle database version is 8.1.7.

          Followup:

what version of type iv driver.

      Rating:
      2
query  January 19, 2004
Reviewer:
      vivek  from uk
Tom,

I think its JDBC Thin Driver 8.1.7 as we have used class12.zip , to create it.

thanks

          Followup:

can you try a later version.  I vaguely recall a performance issue with that
view in the really old stuff.

it is basically describing the stored procedure.

January 20, 2004 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.
Review &
      Followup

      Rating: 3
LOB Chunk
      Size  January 19, 2004
Reviewer:  vinnie
       from Orlando
Tom,

When I query the user_lobs view & look at the chunk size I see 16k.  Is this the
value used when creating the table or the size of the block size for the
tablespace?

          Followup:

it is used when inserting lobs.  it is the LEAST amount of space that will be
allocated for any out of line lob.

it means if you have an 8k block size -- a lob will use AT LEAST 2 blocks and
will use space in multiples of 2 blocks.

it is not a block size, it is more like "an extent of a lob"

January 20, 2004 Ashwin -- Thanks for the question regarding
      "Select Min And Max", version 8.1.7.4

      You Asked
Hi Tom, Pl help me in building the SQL for ........

For each subs_no I want to fetch omr and cmr value
for mimimum omr_dt and maximum cmr_dt respectivly for each subs_no.

Table T is quite big i mean having 2million rows.

Sample Data from table T:
subs_no CMR OMR OMR_DT CMR_DT
------- ---------- ---------- --------- ---------
208236 40706 40315 16-JUN-99 30-JUN-99
208236 41106 40706 01-JUL-99 15-JUL-99
208236 41594 41106 19-JUL-99 31-JUL-99
....
....
Desired OutPut:
subs_no      cmr       omr
208236       40315     41594
.....
......

Thanks in adv.

      and we said...
select subs_no,
       to_date( substr( cmr_data, 1, 14 ), 'yyyymmddhh24miss' ) cmr_date,
       to_number( substr( cmr_data, 15 ) ) cmr,
       to_date( substr( omr_data, 1, 14 ), 'yyyymmddhh24miss' ) omr_date,
       to_number( substr( omr_data, 15 ) ) cmr
  from (
select subs_no,
       min( to_char( cmr_date, 'yyyymmddhh24miss' ) || cmr ) cmr_data,
       max( to_char( omr_date, 'yyyyhhddhh24miss' ) || omr ) omr_data
  from v
 group by subs_no
       )
/

is probably the "most efficient" way to do this.

see

http://otn.oracle.com/oramag/oracle/02-sep/o52asktom.html
"Playing with aggregation" for the "how" behind this technique (also in my book
"Effective Oracle by Design")

Review &
      Followup

      Rating: 4
Small
      correction  January 19, 2004
Reviewer:
      Venkat  from Detroit, MI USA
There is a small typo on the line -
max( to_char( omr_date, 'yyyyhhddhh24miss' ) || omr ) omr_data

It should have the format for the date as "yyyymmddhh24miss".

I was trying to solve the same using analytical functions and came up with the
following;
( I am sure several others are possible  :) )

select subs_no, cmr_date, cmr, max_dt omr_date, end_value omr
from (
select subs_no, cmr_date, max_dt, cmr, lead(omr,1,omr) over (partition by
subs_no order by subs_no, cmr_date) end_value
,row_number() over (partition by subs_no order by subs_no, cmr_date) rn
from (
select subs_no, cmr_date, omr_date, cmr, omr
, min(cmr_date)  over (partition by subs_no) min_dt
, max(omr_date) over (partition by subs_no) max_dt
, row_number() over (partition by subs_no, cmr_date order by subs_no, cmr_date)
st_rn
, row_number() over (partition by subs_no, omr_date order by subs_no, omr_date
desc) end_rn
from v
) where (cmr_date = min_dt or omr_date = max_dt) and st_rn = 1 and end_rn = 1
) where rn = 1

          Followup:

doh, yes, thanks.

benchmark that -- analytics won't outperform the simple group by in this case.
especially with two different sort orders in the analytics.

      Rating: 4
Great Answer
      TOM  January 20, 2004
Reviewer:  Ashwin
       from India
Thanks Tom.

January 20, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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

      Rating: 5
Few Log Miner
      Questions  January 19, 2004
Reviewer:  Sami
       from NJ,USA
Dear Tom,

Always thankful to YOU coz I am one of the most beneficial guy from this forum.

1)"Logical Standby" is built-up on "Log Miner". Is there any other stuff which
built based on(using) Log Miner?
How about Streams?

2)
In "10g", is there any change in the following restrictions?
The following are not supported (9i Log Miner):
– Simple and nested abstract datatypes (ADTs)
– Collections (nested tables and VARRAYs)
– Object Refs
– Index organized tables (IOTs)
– CREATE TABLE AS SELECT of a table with a clustered key

3) If we decided to implement Log Miner then it is better to avoid IOT and
Cluster schema objects during design time itself. Am I right?

Expecting your response if time permits for you.

          Followup:

1) streams......

2) these are the 10g restrictions:

Unsupported Datatypes and Table Storage Attributes LogMiner does not support
these datatypes and table storage attributes:

BFILE datatype
Simple and nested abstract datatypes (ADTs)
Collections (nested tables and VARRAYs)
Object refs
XMLTYPE datatype
Index-organized tables (IOTs) with LOB columns
Tables using table compression

3) iot's are supported now (without lobs).  yes, if log miner is something you
want to use 100% of the time, you would have to avoid non-supported
implementations.

January 20, 2004 Peter -- Thanks for the question regarding
      "Relational VS Object Oriented Database Design", version Oracle 8.1.6

      You Asked
Hi Tom:

There are two different mind set between relational vs. Object
Oriented Database Design.

Can you provide your experiences, what are the criteria (pro and
con) to use relational vs. OO database design in Oracle? Objects
in Oracle have better performance than relational table, or vice
versa.

Thanks for your time and efforts.

Peter Cheng

      and we said...
First off, in Oracle you would be considering pure relational
versus Object RELATIONAL, not OO.  We have object relational
extensions to the relational database.  We do not have an object
oriented database.

The answer to this comes down to the data you are working with.
Let's say you are building a banking application.  You are
building the application for the teller to service a person when
they walk in the door.  The Object perfect for that application
is a "person" object identified by name or account number.  That
person object has all of the attributes of a person in a bank, a
name, an address, a list of accounts with all of the account
attributes, a list of recent transactions, and so on.  A big
glob of data that represents all of the information about a
person.  That is the perfect view for the teller.  HOWEVER, at
the end of the day, there is someone else who has a totally
different view of the data -- they need to count how many
transactions happened that day.  How much money is in the bank.
They have a transaction oriented, not person oriented,
perspective of the data.  In this case, a RELATIONAL model with
possibly application specific Object relational views built on
top of it is perfect.  You need an account table, that is joined
to a transaction table and a person table.  In that fashion, the
guy who needs to count the transactions is satisfied and so is
the guy who needs the person oriented view.

That is the power of the relational model, it is able to easily
give many views of the same data.  Think of it this way with the
scott/tiger EMP, DEPT tables.  It might seem to make sense to
create an EMP object type, a nested table of EMP, a DEPT object
with an attribute of EMP nested table and then a table of DEPT
types.  Now the whole scott/tiger emp/dept example fits into 1
table with 4 rows.  Sounds great.  The problem is people will
ask questions like:

o how many emps do I have?
o KING is standing here at the front door -- is he an employee
or not?
o how much salary did I pay accountants last year?

and so on.  None of these questions are easilly answered with a
DEPT oriented view of the data.

Just some food for thought.  I've not really used the Object
relational concepts as a STORAGE mechanism, I have used them in
my programming with PLSQL lots.  I tend to stick with real
tables and use the objects in my code frequently.
Review &
      Followup

      Rating: 4
Any
      recommendations on books of database design?  August 21,
      2001
Reviewer:  A reader

      Rating: 4
Any
      recommendations on books of database design?  August 21,
      2001
Reviewer:  F  from USA

      Rating: 4
Mahendra
      Jadav  December 30, 2001
Reviewer:  Mahendra
      Jadav  from INDIA
This implies, do not to use ORDBMS as storage mechanism. Use OR views based on
plain table. Correct?

          Followup:

That is my personal choice, yes.

      Rating:
      4
Beautiful  December 31, 2001
Reviewer:
      Srimal  from Trenton, USA
Hello Mr Tom,

Your response is consise and to the point and amazing!

      Rating: 4
Object vs
      Relational - Help needed  January 19, 2004

Reviewer:  Jsri  from CT, USA
Hi Tom,

I am not sure whether I can use this context to discuss my case. I've
requirement to pull data from a database( Object model), and dump the data into
Data warehouse.

I've been searching in the Net for best methodology I can adopt to design an
interface where I can Map the Objects to a Relational model STAGE schema, to
where I can pull data first and from there to a Data warehouse schema
(Target).

Pl give your guidence about what direction I should take, and what important
things I should consider.

I would appreciate, if you could give me references to any Online articles that
might be useful for me.

Thanks,

          Followup:

what database are you pulling from.

      Rating: 3
Object vs
      Relational - Help needed   January 19, 2004

Reviewer:  Jsri  from CT, USA
Tom,

Here under giving you details:

Source Database: Oracle8i(8.1.7)
Application: Tririga (Object model DB)

Stage Database: Oracle9i (9.2.0.2)
Target Database: Oracle9i (9.2.0.2)

We're having no idea about how to analyze Source Database and Map to a
relational model schema, and then load to target Warehouse schema. Client would
like to see Real-time data in DWH, transaction volume is very low.

We're thinking about Oracle streams(Or, Informatica Power center) for Change
Data capture, but not sure how initially map Objects to RDBMS tables. If you
could give us any direction, we can quickly go from there.

Thanks in advance.

          Followup:

well, you'd have to let us know HOW you might be able to capture changes in this
tririga database (else streams is pretty much useless)

Now, most object dbs i've seen are a couple of meg/gigs -- we could just load
the entire thing in a couple of seconds -- how big is this and what are the
possibilities for tririga to tell us "whats changed"

mapping the objects -> relational tables is something you need to design, based
on your requirements, not something we can do here.

January 20, 2004 Sandy -- Thanks for the question regarding
      "Oracle Internals", version 8.1.7

      You Asked
Hi Tom,

I'm looking for some solid documentation on Oracle internals, can you please
point me some links.

Regards,
Sandy

      and we said...
Like what internals, Oracle is a big place.  99.999% of tuning and configuring
requires nothing beyond what is in the manuals (and I almost never get to that
0.001% myself)

Have you read and totally *mastered* the concepts guide?  the admin guide?  I
mean -- if I asked you to draw a picture of Oracle on a whiteboard -- showing
all of the processes, how they interact, what they do, whats in the SGA, what
all the files we use are and how they interact and what happens when a client
connects via dedicated / shared server -- that would be really easy to do?
(that is my all time favorite interview question.  Maybe 10% of the people can
actually do it)

There are lots of books out there that claim to be "internals" books, you can
look at them.  There is an internals course taught by Oracle Education as well
-- I just hope before anyone looks at them, they master -- totally master to the
point where they don't need to look at the concepts guide -- the contents of the
concepts guide.

Review &
      Followup

      Rating:
      2
  August 21, 2002
Reviewer:  Anil
       from NYC
Tom, Your comments are not at all in-appropriate.

This is just to answer the question:

The first place to go is ofcourse www.ixora.com.au, run by Steve Adams. Also get
his book. Another good source is Don Burlesons Oracle Internals Magazine.

Beyond these, you have to sweat it out yourself (by trial and error) if you are
not an Oracle insider!

          Followup:

And Caveat Emptor.

Steves site -- above reproach, I go there myself and link to it.  Excellent.
That is due to his methods more then anything else.  He, like Jonathan Lewis
(and i would like to include myself) try to prove scientifically that what we
say is accurate.  Others -- well, caveat emptor.

I was having an email discussion with the author of a series of "tuning tips"
recently (search for dbasupport on this site to see the thread that started it).
 He asked me to comment more fully on all of his tips since I was taking
exception to the ones people asked me to comment on here.  Turns out -- almost
all of them were either inaccurate, incomplete or just 100% wrong.  His comment
back to me:

"I'm really surprising that the Tuning Book was containing so many errors."

Basically, he was just saying what others had said and never bothered to
actually *test* or *prove* any of it.

I would say that you should sweat all of it out -- by trial and error.  Things
change (was is true in version 8.1.5 is not necessarily true in 8.1.7), people
mis-interpret what was intended (this I see all of the time), people mis-apply
one solution to too many problems, and so on.

So, I'll recommend Steve Adams and Jonathan Lewis's material 100%.  But, I'll
betcha the concepts guide is still something that needs to be studied here.

      Rating: 5
RE: Maybe 10%
      of the people...  August 21, 2002
Reviewer:
      Mark A. Williams  from Indianapolis, IN USA
"Maybe 10% of the people can actually do it"...

Boy, I find that surprising/perplexing...  You (That is, Tom) have lots of
experience across a broad range of individuals - why do you think that is?
Oracle is clearly a market leader, so I would think there would be a strong
knowledge base behind it.  I have found the same to be true in our experiences
with external resources (read "consultants/contractors/vendors") as well,
though.

For some seem really good ideas/techniques about using currently available
Oracle tools (some "semi-documented"), I have found hotsos, ixora, and jlcomp to
be excellent resources in addition to AskTom (and Tom's book of course).

Having said that, though, I don't think a strong knowledge of all the
*internals* is required either.  For example, say I know the exact manner by
which Oracle clones blocks in the buffer cache - now what would I do with that
knowledge?  It is kinda like the "gee whiz" info you get when flying.  "This is
the captain speaking we are currently cruising at 33,000 feet..."  what am I
going to do?  Send a recommendation to the cockpit to change altitude?
Doubtful.

Well, another $0.02...

Thanks,

Mark

          Followup:

I used to find it surprising, not anymore.

I still find it perplexing, bothersome.

The reason -- you only have to be the smartest person in the room to succeed, a
rising tide raises all ships.  The better the group of people you work with --
the better you are at what you do and how you do it.

I actually find knowledge of "internals" to be a hindrance for most people --
they hold onto one or two "facts" -- inside knowledge -- and try to apply it all
over the place.  Lack of knowledge of the basics plus knowledge of some
internals is sort of like a doctor knowing how to use a scalpel, but not
understanding human anatomy.  Keep them away from me.  I actually try to avoid
many of the "internals", finding that 80% or more of everything (tuning for
example) is done using common sense, traditional design, applying my knowledge
to the problem.  It is not tweaking the buffer cache cloning, setting "_"
parameters (which everyone just seems to really dig doing for some reason, my
databases -- no underscores beyond trace_files_public)....

      Rating: 4
where is the
      thread?  January 19, 2004
Reviewer:  Steve
       from NYC,NY
"I was having an email discussion with the author of a series of "tuning tips"
recently (search for dbasupport on this site to see the thread that started
it)......"

I search this site for dbasupport, but I can't find any thread related to it.
Could you pleae give me a link?

Thanks,

Steve

          Followup:

it was the first hit??? entitled "Issue Frequent COMMIT Statements"

January 20, 2004 sijo -- Thanks for the question regarding
      "cumulative sal", version 7.x,8.1.5

      You Asked
hello thomas,
i am sijo here
thanx for ur last answr

i want to calculate the cumulative sal from the emp tble
how do i do it?
eg
ename    sal  cum sal
smith    800   800
allan   1600   2400
ward    1250  3650

thanx ,
bye

      and we said...
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:247014750272
In Oracle8i release 2 (8.1.6), we have analytic functions to
perform this operation.  In that release, you can code:

scott@TKYTE816> SELECT ename, sal,
  2         SUM(sal) OVER
              (ORDER BY ename RANGE UNBOUNDED PRECEDING) l_csum
  3  FROM emp;

ENAME             SAL     L_CSUM
---------- ---------- ----------
ADAMS            1100       1100
ALLEN            1600       2700
BLAKE            2850       5550
CLARK            2450       8000
FORD             3000      11000
JAMES             950      11950
JONES            2975      14925
KING             5000      19925
MARTIN           1250      21175
MILLER           1300      22475
SCOTT            3000      25475
SMITH             800      26275
TURNER           1500      27775
WARD             1250      29025

14 rows selected.

To get the results.  In 7.x -- 8.1.5, we can sometimes use a
"trick".

scott@ORA734.WORLD> create or replace package my_pkg
  2  as
  3      function running_total( p_num in number )
  4      return number;
  5      pragma restrict_references(running_total,wnds,rnds);
  6
  6      pragma restrict_references(my_pkg,
  7                                 wnds,rnds,wnps,rnps);
  8
  8      g_running_total number default 0;
  9  end;
 10  /

Package created.

scott@ORA734.WORLD>
scott@ORA734.WORLD> create or replace package body my_pkg
  2  as
  3
  3      function running_total( p_num in number ) return number
  4      is
  5      begin
  6          g_running_total := g_running_total + p_num;
  7          return g_running_total;
  8      end;
  9  end;
 10  /

Package body created.

scott@ORA734.WORLD>
scott@ORA734.WORLD> begin
  2      my_pkg.g_running_total := 0;
  3  end;
  4  /

PL/SQL procedure successfully completed.

You must call this before any query that uses it is run --
it'll reset the running total for us

scott@ORA734.WORLD> select ename, sal, my_pkg.running_total(sal)
  2    from emp
  3  /

ENAME             SAL MY_PKG.RUNNING_TOTAL(SAL)
---------- ---------- -------------------------
SMITH             800                       800
ALLEN            1600                      2400
WARD             1250                      3650
JONES            2975                      6625
MARTIN           1250                      7875
BLAKE            2850                     10725
CLARK            2450                     13175
SCOTT            3000                     16175
KING             5000                     21175
TURNER           1500                     22675
ADAMS            1100                     23775
JAMES             950                     24725
FORD             3000                     27725
MILLER           1300                     29025

14 rows selected.

So, there is the running total -- however, if we apply
aggregates or order by's to the query:

scott@ORA734.WORLD> begin
  2      my_pkg.g_running_total := 0;
  3  end;
  4  /

PL/SQL procedure successfully completed.

scott@ORA734.WORLD> select ename, sal, my_pkg.running_total(sal)
  2    from emp
  3   order by ename
  4  /

ENAME             SAL MY_PKG.RUNNING_TOTAL(SAL)
---------- ---------- -------------------------
ADAMS            1100                     23775
ALLEN            1600                      2400
BLAKE            2850                     10725
CLARK            2450                     13175
FORD             3000                     27725
JAMES             950                     24725
JONES            2975                      6625
KING             5000                     21175
MARTIN           1250                      7875
MILLER           1300                     29025
SCOTT            3000                     16175
SMITH             800                       800
TURNER           1500                     22675
WARD             1250                      3650

14 rows selected.

It does not work the same.  The ORDER by in this case is
applied AFTER the SELECT (projection) is (has to be -- you could
order by my_pkg.running_total if you wanted to).  How to get
around this in 7.x - 8.0 (make sure you read and understand

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:247014750272
before using this method!)

scott@ORA734.WORLD> begin
  2      my_pkg.g_running_total := 0;
  3  end;
  4  /

PL/SQL procedure successfully completed.

scott@ORA734.WORLD> select ename, sal, my_pkg.running_total(sal)
  2    from (select ename,sum(sal) sal
  3            from emp
  4           group by ename, rowid )
  5  /

ENAME             SAL MY_PKG.RUNNING_TOTAL(SAL)
---------- ---------- -------------------------
ADAMS            1100                      1100
ALLEN            1600                      2700
BLAKE            2850                      5550
CLARK            2450                      8000
FORD             3000                     11000
JAMES             950                     11950
JONES            2975                     14925
KING             5000                     19925
MARTIN           1250                     21175
MILLER           1300                     22475
SCOTT            3000                     25475
SMITH             800                     26275
TURNER           1500                     27775
WARD             1250                     29025

14 rows selected.

scott@ORA734.WORLD>

Now, in Oracle8i, version 8.1.5 (before the cumulative SUM --
in 8.1.6 the first query is the way to go)

scott@8i> select ename, sal, my_pkg.running_total(sal)
  2    from (select ename,sal
  3            from emp
  4           order by ename )
  5  /

ENAME             SAL MY_PKG.RUNNING_TOTAL(SAL)
---------- ---------- -------------------------
ADAMS            1100                      1100
ALLEN            1600                      2700
BLAKE            2850                      5550
CLARK            2450                      8000
FORD             3000                     11000
JAMES             950                     11950
JONES            2975                     14925
KING             5000                     19925
MARTIN           1250                     21175
MILLER           1300                     22475
SCOTT            3000                     25475
SMITH             800                     26275
TURNER           1500                     27775
WARD             1250                     29025

14 rows selected.

Review &
      Followup

      Rating: 5
and with a
      "where" clause?  April 05, 2002
Reviewer:  Cesar
      Salas  from Mexico
Hi tom,

What is wrong with this query?

exec my_pkg.gtotal := 0;

select n, s, rt
from
(select ename n, sal s, my_pkg.rtotal(sal) rt from
(select ename, sal from emp order by ename))
/

N                  S        RT
---------- --------- ---------
ADAMS           1100      1100
ALLEN           1600      2700
BLAKE           2850      5550
CLARK           2450      8000
FORD            3000     11000
JAMES            950     11950
JONES           2975     14925
KING            5000     19925
MARTIN          1250     21175
MILLER          1300     22475
SCOTT           3000     25475
SMITH            800     26275
TURNER          1500     27775
WARD            1250     29025

--
-- Ok, but when I try...
--

exec my_pkg.gtotal := 0;

select n, s, rt
from
(select ename n, sal s, my_pkg.rtotal(sal) rt from
(select ename, sal from emp order by ename))
where rt < 6000
/

N                  S        RT
---------- --------- ---------
ALLEN           1600     30625
SMITH            800     31425
WARD            1250     32675

I only want fetch records while rt < :x, Is it possible?
I can't use analytic functions...

Thanks!

          Followup:

As I said:

To get the results.  In 7.x -- 8.1.5, we can sometimes use a "trick"

You cannot -- you need to use the anlytic functions.

      Rating: 5
If sal is equal
      then not cumulated  August 01, 2002
Reviewer:
      dharma  from Los Angeles, CA
The documentation says if two of the salaries were equal in subsequent rows,
then the salary will not be cumulated, is there any way to overcome that?
for example when sal 130 comes continuously its not cumulated.

   deptno empno sal cum_sal
    10       1   100  100
    10       3   120  220
    10       2   130  350
    10       5   130  350
    10       4   150  500

          Followup:

Depends on the WINDOW clause you use.  In the above example I provided, what you
say is inaccurate:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table emp as select ename, 100 sal
from scott.emp;

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> SELECT ename, sal,
  2         SUM(sal) OVER
  3                (ORDER BY ename RANGE UNBOUNDED PRECEDING) l_csum
  4    FROM emp;

ENAME             SAL     L_CSUM
---------- ---------- ----------
ADAMS             100        100
ALLEN             100        200
BLAKE             100        300
CLARK             100        400
FORD              100        500
JAMES             100        600
JONES             100        700
KING              100        800
MARTIN            100        900
MILLER            100       1000
SCOTT             100       1100
SMITH             100       1200
TURNER            100       1300
WARD              100       1400

14 rows selected.

Now, if I change that JUST A BIT, the salaries are "accumulated" but not as you
might desire them to be:

ops$tkyte@ORA817DEV.US.ORACLE.COM> SELECT ename, sal,
  2         SUM(sal) OVER
  3                (ORDER BY sal RANGE UNBOUNDED PRECEDING) l_csum
  4    FROM emp;

ENAME             SAL     L_CSUM
---------- ---------- ----------
SMITH             100       1400
ALLEN             100       1400
WARD              100       1400
JONES             100       1400
MARTIN            100       1400
BLAKE             100       1400
CLARK             100       1400
SCOTT             100       1400
KING              100       1400
TURNER            100       1400
ADAMS             100       1400
JAMES             100       1400
FORD              100       1400
MILLER            100       1400

14 rows selected.

(set theory kicking in here!!! all of the rows are logically the same, they
don't come before or after eachother hence the range unbounded preceding is the
same set every time).  Easy enough to fix:

ops$tkyte@ORA817DEV.US.ORACLE.COM> SELECT ename, sal,
  2         SUM(sal) OVER
  3                (ORDER BY sal, EMPNO RANGE UNBOUNDED PRECEDING) l_csum
  4    FROM emp;

ENAME             SAL     L_CSUM
---------- ---------- ----------
SMITH             100        100
ALLEN             100        200
WARD              100        300
JONES             100        400
MARTIN            100        500
BLAKE             100        600
CLARK             100        700
SCOTT             100        800
KING              100        900
TURNER            100       1000
ADAMS             100       1100
JAMES             100       1200
FORD              100       1300
MILLER            100       1400

14 rows selected.

Just add something to make the SAL, <something> unique (eg: rowid would do it as
well)

      Rating: 4
great
      solution  June 28, 2003
Reviewer:  jigar
       from bombay,india
brilliant answer as always

      Rating: 4
But what if the
      following.........  September 04, 2003
Reviewer:
      Neil Chapman  from England
I have the following records in a table

Account    Cycle      Val
999         1         0.11
999         2         0.18
999         3         0.27
999         4         0.35
999         5         0.52
999         6         0.61
999         7         -200
999         8         0.63
999         9         92.00
999         10        88.00
999         11        -400
999         12        0.8

And I want to produce a RUNNING TOTAL of the VAL column, but when it is a MINUS
figure, then RESET the running total to 0.

I therefore want...........

Cycle   Running Total
1        0.11
2        0.29
3        0.56
4        0.91
5        1.43
6        2.04
7        0
8        0.63
9        92.63
10       180.63
11       0
12       0.80

Can see how to do the running total, but not how to reset it to zero.

          Followup:

what we can do is this. I'll build it iteratively, first we'll need to add
something inside of account to subpartition account - to get our "breaks".

to do that i'll take the CYCLE column you have and take the max of the cycle in
a sliding window -- based on "val"  -- we'll return NULL if val is not negative
-- so this decoded cycle only has a value when val is negative.  taking the max
of that in this window gives us something to "break" on:

ops$tkyte@ORA920LAP> select account, cycle, val,
  2         max( decode(sign(val),-1,cycle,decode(cycle,1,1,null)) )
  3            over (partition by account order by cycle) max_cycle
  4    from t
  5  /

   ACCOUNT      CYCLE        VAL  MAX_CYCLE
---------- ---------- ---------- ----------
       999          1        .11          1
       999          2        .18          1
       999          3        .27          1
       999          4        .35          1
       999          5        .52          1
       999          6        .61          1
       999          7       -200          7
       999          8        .63          7
       999          9         92          7
       999         10         88          7
       999         11       -400         11
       999         12         .8         11

12 rows selected.

Now, a running total by account/max_cycle is easy:

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select account, cycle, val,
  2         sum(decode(sign(val),1,val,null))
  3            over (partition by account, max_cycle order by cycle) running
  4    from (
  5  select account, cycle, val,
  6         max( decode(sign(val),-1,cycle,decode(cycle,1,1,null)) )
  7            over (partition by account order by cycle) max_cycle
  8    from t
  9         )
 10  /

   ACCOUNT      CYCLE        VAL    RUNNING
---------- ---------- ---------- ----------
       999          1        .11        .11
       999          2        .18        .29
       999          3        .27        .56
       999          4        .35        .91
       999          5        .52       1.43
       999          6        .61       2.04
       999          7       -200
       999          8        .63        .63
       999          9         92      92.63
       999         10         88     180.63
       999         11       -400
       999         12         .8         .8

12 rows selected.

      Rating: 4
Roll your own
      ...  September 04, 2003
Reviewer:  Adrian
      Billington  from UK
I was looking at this and thought this would be a perfect use for a user-defined
aggregate function. I've added my implementation of a function I've called
"BREAKSUM" below:-

920> --
920> -- Create a TYPE to support our aggregate "BREAKSUM" function...
920> --
920> CREATE OR REPLACE TYPE typ_obj_breaksum AS OBJECT
  2  (
  3    sum    NUMBER,
  4
  5    STATIC FUNCTION ODCIAggregateInitialize (
  6                    sctx IN OUT typ_obj_breaksum
  7                    ) RETURN NUMBER,
  8
  9    MEMBER FUNCTION ODCIAggregateIterate (
 10                    self  IN OUT typ_obj_breaksum,
 11                    value IN NUMBER
 12                    ) RETURN NUMBER,
 13
 14    MEMBER FUNCTION ODCIAggregateTerminate (
 15                    self IN typ_obj_breaksum,
 16                    retval OUT NUMBER,
 17                    flags IN NUMBER
 18                    ) RETURN NUMBER,
 19
 20    MEMBER FUNCTION ODCIAggregateMerge (
 21                    self IN OUT typ_obj_breaksum,
 22                    ctx2 IN typ_obj_breaksum
 23                    ) RETURN NUMBER
 24  );
 25  /

Type created.

920>
920> CREATE OR REPLACE TYPE BODY typ_obj_breaksum IS
  2
  3  /************************** Initialisation
********************************/
  4  STATIC FUNCTION ODCIAggregateInitialize (
  5                  sctx IN OUT typ_obj_breaksum
  6                  ) RETURN NUMBER IS
  7  BEGIN
  8    sctx := typ_obj_breaksum(0);
  9    RETURN ODCIConst.Success;
 10  END;
 11
 12  /************************** Iteration ********************************/
 13  MEMBER FUNCTION ODCIAggregateIterate (
 14                  self IN OUT typ_obj_breaksum,
 15                  value IN NUMBER
 16                  ) RETURN NUMBER IS
 17  BEGIN
 18    self.sum := CASE
 19                   WHEN value > 0
 20                   THEN self.sum + value
 21                   ELSE 0
 22                END;
 23    RETURN ODCIConst.Success;
 24  END;
 25
 26  /************************** Termination ********************************/
 27  MEMBER FUNCTION ODCIAggregateTerminate (
 28                  self IN typ_obj_breaksum,
 29                  retval OUT NUMBER,
 30                  flags IN NUMBER
 31                  ) RETURN NUMBER IS
 32  BEGIN
 33    retval := self.sum;
 34    RETURN ODCIConst.Success;
 35  END;
 36
 37  /************************** Merge ********************************/
 38  MEMBER FUNCTION ODCIAggregateMerge (
 39                  self IN OUT typ_obj_breaksum,
 40                  ctx2 IN     typ_obj_breaksum
 41                  ) RETURN NUMBER IS
 42  BEGIN
 43    self.sum := self.sum + ctx2.sum;
 44    RETURN ODCIConst.Success;
 45  END;
 46
 47  END;
 48  /

Type body created.

920> sho err
No errors.
920>
920> --
920> -- BREAKSUM user-defined aggregate function...
920> --
920> CREATE OR REPLACE FUNCTION breaksum (
  2                             input NUMBER
  3                             ) RETURN NUMBER
  4     PARALLEL_ENABLE
  5     AGGREGATE USING typ_obj_breaksum;
  6  /

Function created.

920>
920> --
920> -- Create a table with the data in question...
920> --
920> CREATE TABLE accounts ( account NUMBER, cycle NUMBER, val NUMBER(5,2) );

Table created.

920>
920> ALTER SESSION SET CURSOR_SHARING = FORCE;

Session altered.

920>
920> INSERT INTO accounts VALUES ( 999, 1, 0.11 );

1 row created.

920> INSERT INTO accounts VALUES ( 999, 2, 0.18 );

1 row created.

920> INSERT INTO accounts VALUES ( 999, 3, 0.27 );

1 row created.

920> INSERT INTO accounts VALUES ( 999, 4, 0.35 );

1 row created.

920> INSERT INTO accounts VALUES ( 999, 5, 0.52 );

1 row created.

920> INSERT INTO accounts VALUES ( 999, 6, 0.61 );

1 row created.

920> INSERT INTO accounts VALUES ( 999, 7, -200 );

1 row created.

920> INSERT INTO accounts VALUES ( 999, 8, 0.63 );

1 row created.

920> INSERT INTO accounts VALUES ( 999, 9, 92.00 );

1 row created.

920> INSERT INTO accounts VALUES ( 999, 10, 88.00 );

1 row created.

920> INSERT INTO accounts VALUES ( 999, 11, -400 );

1 row created.

920> INSERT INTO accounts VALUES ( 999, 12, 0.8 );

1 row created.

920>
920> --
920> -- Now we can use our BREAKSUM function over the data...
920> --
920> SELECT account
  2  ,      cycle
  3  ,      val
  4  ,      BREAKSUM ( val ) OVER
  5            ( PARTITION BY account ORDER BY cycle ) AS running
  6  FROM   accounts;

   ACCOUNT      CYCLE        VAL    RUNNING
---------- ---------- ---------- ----------
       999          1        .11        .11
       999          2        .18        .29
       999          3        .27        .56
       999          4        .35        .91
       999          5        .52       1.43
       999          6        .61       2.04
       999          7       -200          0
       999          8        .63        .63
       999          9         92      92.63
       999         10         88     180.63
       999         11       -400          0
       999         12         .8         .8

12 rows selected.

Regards
Adrian

          Followup:

interesting solution -- neat.

but, i tested it on a scaled up scenario, the plsql call (which can be removed)
does add some amount of cpu overhead.

begin
    for a in 1 .. 998
    loop
        for c in 1..15
        loop
            insert into accounts values ( a, c, decode(mod(c,5), 0, -1, 1 ) );
        end loop;
    end loop;
end;
/
commit;

SELECT account
,      cycle
,      val
,      BREAKSUM ( val ) OVER
          ( PARTITION BY account ORDER BY cycle ) AS running
FROM   accounts

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.03       0.04          0         30          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     1000      1.71       1.70          0         38          0       14982
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1002      1.74       1.74          0         68          0       14982
********************************************************************************
select account, cycle, val,
       sum(decode(sign(val),1,val,null))
           over (partition by account, max_cycle order by cycle) running
  from (
   select account, cycle, val,
          max( decode(sign(val),-1,cycle,decode(cycle,1,1,null)) )
             over (partition by account order by cycle) max_cycle
     from accounts
  )

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     1000      0.40       0.40          0         38          0       14982
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1002      0.40       0.40          0         38          0       14982

as always -- i go back to my mantra "if you can do it in a single sql
statement"...

      Rating:
      5
  September 04, 2003
Reviewer:
      Mirjana  from Barcelona

      Rating: 4
It's a fair
      cop...  September 04, 2003
Reviewer:  Adrian
      Billington  from UK
Funny, because as you posted your followup I was just identifying the fact that
the SQL solution was quicker when running at volume.

The only reason I would possibly consider the aggregate over the SQL would be to
remove the complexity from the SQL itself if legibility was a real issue. The
SQL-only solution already has one level of in-line view nesting which could
easily turn into three or four when we scale up to a real life business-related
reporting requirement or we are not dealing in "subpartitions" that start neatly
at 1 but instead need to find different minimums for each partition. But if the
people who support the app. are comfortable reading the SQL, then of course it
makes sense to go for the quickest every time...

I hadn't considered the PL/SQL context switching when I wrote the aggregate. I
assumed because we were plugging into the extensibility framework that a
user-defined aggregate might work nearly as fast as a built-in. Alas not. Your
SQL solution has 7 built-in function calls whereas mine has one user-defined
function call, yet there's no comparison in performance.

Regards
Adrian

          Followup:

I love inline views  -- nested 3 or 4 deep sometimes.  makes the sql so easy to
read.

SQL can be commented just like code!!!

we can use LAG() easily to see if we are at "1", we just need to decode(
lag(cycle) over (...), NULL, cycle, null )   (if first cycle -- spit it out)

I think people can parse the sql and if commented -- like code -- it would be as
legible as anything else.

I always find a mixed code+sql implementation harder to read and maintain.  I
always prefer a pure SQL set based solution myself!

maybe if we stop trying to insulate people from "really hard scary stuff",
they'll begin to understand the power that is there?  and it'll become less
scary and hard.

Wait'll you see the sql MODEL clause in the next release -- now that, that is
going to be really cool and can change many things.  iteration, recursion,
dynamic subscripting -- alot like have a spreadsheet in sql.  I really hope
people wouldn't avoid using it cause "it looks strange" at first.

      Rating: 4
Bring it
      on...  September 04, 2003
Reviewer:  Adrian
      Billington  from UK
Tom

Speaking on behalf of the team I work in, absolutely not. We are very quick to
embrace new "stuff". For example, I love analytics and my colleagues and I leapt
on them as soon as they were available in 816 and now they are all over our
systems - aggregations, derivations, data marts. We are looking to make use of
all the whizz-bang stuff wherever it makes sense to do so and not because it
makes our CVs look good ;o)

Can't wait for 10g - I notice the first articles on new features are starting to
trickle through. It's a little unfair that certain non-Oracle stalwarts and
beta-partners get access to information so early that they can write books /
articles before the product is even on the market. Means we "everyday" people
down here don't get a look in before it's too late...

Regards
Adrian

          Followup:

everyday people can write books too!!  the people who write most of the books
are in fact everyday people.

Yes, the people writing about the new features get a peek at them before hand.
It is a mixed bag though, until very very very recently

10g was 10i....
the model clause was called spreadsheet...

and thousands of other changes from beta to beta :)

      Rating: 4
9i and
      10g  September 06, 2003
Reviewer:  Asim
      Naveed  from Pakistan
Hi,

Its really nice to know about MODEL Clause.

We are developing a BI front end product that will
have feature that it can support both SQL SERVER analysis
services and Oracle 9i rel. 2
(without OLAP option) at the backend.

In this product, I have made the metrics using MDX's
calculated members feature (i.e a spread sheet
within a database).

But I was unable to do that in pure SQL of ORACLE.
I have made some metrics using analytical functions, but
those are static queries not dynamic.

Can I say to my boss that if you want it in pure SQL then
wait for the MODEL clause of 10g.

Please answer the following,

1- Is MODEL clause providing the functionality something
    like Calculated members provide in Analysis Services.

2- Is it correct that 9i rel.2 didnt have any feature
   equivalent to MDX's calculated members. Note that
   we can easily just define the formula, name it
   and then store it. Then when
   we select dimensions in the query , it applies that
   formula to that dimensions, we do not have to
   explicitly put the formula in the query.

Thanks

          Followup:

1) i don't know what calculated members "provides" so cannot really comment.

2) aren't those called "views"

      Rating: 4
Cum. Sum
        September 19, 2003
Reviewer:  Jagjeet Singh
      Malhi  from INDIA
 hello Sir ..

 This query is working with 8.1.5

SQL> 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.

SQL> get a
  1  Select ename,sal,
  2    (select sum(sal) from (Select rownum r,ename,sal from
  3    (select ename,sal from emp  )) QUERY_REPEAT
  4    where query_repeat.r <= main_query.r ) cum_sal
  5  from
  6    (Select rownum r,ename,sal from
  7*   (select ename,sal from emp  )) MAIN_QUERY
SQL> /

ENAME             SAL    CUM_SAL
---------- ---------- ----------
SMITH             800        800
ALLEN            1600       2400
WARD             1250       3650
JONES            2975       6625
MARTIN           1250       7875
BLAKE            2850      10725
CLARK            2450      13175
SCOTT            3000      16175
KING             5000      21175
TURNER           1500      22675
ADAMS            1100      23775
JAMES             950      24725
FORD             3000      27725
MILLER           1300      29025

14 rows selected.

SQL> select ename,sal from emp order by sal;

ENAME             SAL
---------- ----------
SMITH             800
JAMES             950
ADAMS            1100
WARD             1250
MARTIN           1250
MILLER           1300
TURNER           1500
ALLEN            1600
CLARK            2450
BLAKE            2850
JONES            2975
SCOTT            3000
FORD             3000
KING             5000

14 rows selected.

SQL> get a
  1  Select ename,sal,
  2    (select sum(sal) from (Select rownum r,ename,sal from
  3    (select ename,sal from emp order by sal )) query_repeat
  4    where query_repeat.r <= main_query.r ) cum_sal
  5  from
  6    (Select rownum r,ename,sal from
  7*   (select ename,sal from emp order by sal )) main_query
SQL> /

ENAME             SAL    CUM_SAL
---------- ---------- ----------
SMITH             800        800
JAMES             950       1750
ADAMS            1100       2850
WARD             1250       4100
MARTIN           1250       5350
MILLER           1300       6650
TURNER           1500       8150
ALLEN            1600       9750
CLARK            2450      12200
BLAKE            2850      15050
JONES            2975      18025
SCOTT            3000      21025
FORD             3000      24025
KING             5000      29025

14 rows selected.

SQL>
SQL> select ename,sal from emp order by ename;

ENAME             SAL
---------- ----------
ADAMS            1100
ALLEN            1600
BLAKE            2850
CLARK            2450
FORD             3000
JAMES             950
JONES            2975
KING             5000
MARTIN           1250
MILLER           1300
SCOTT            3000
SMITH             800
TURNER           1500
WARD             1250

14 rows selected.

SQL> get a
  1  Select ename,sal,
  2    (select sum(sal) from (Select rownum r,ename,sal from
  3    (select ename,sal from emp order by ename )) query_repeat
  4    where query_repeat.r <= main_query.r ) cum_sal
  5  from
  6    (Select rownum r,ename,sal from
  7*   (select ename,sal from emp order by ename )) main_query
SQL> /

ENAME             SAL    CUM_SAL
---------- ---------- ----------
ADAMS            1100       1100
ALLEN            1600       2700
BLAKE            2850       5550
CLARK            2450       8000
FORD             3000      11000
JAMES             950      11950
JONES            2975      14925
KING             5000      19925
MARTIN           1250      21175
MILLER           1300      22475
SCOTT            3000      25475
SMITH             800      26275
TURNER           1500      27775
WARD             1250      29025

14 rows selected.

SQL>

          Followup:

do it for a real sized set of data :)

      Rating: 4
How about this
      in versions before or equal to 8.1.5  September 21, 2003

Reviewer:  Asim Naveed  from Pakistan
select a.empno, a.ename, a.sal, sum(b.sal)
from emp a, emp b
WHERE a.sal > b.sal
OR
(
a.sal= b.sal
and a.rowid >= b.rowid)
group by a.empno, a.ename, a.rowid,a.sal
order by a.sal, a.rowid

Just replase the a.sal column in WHERE clause and
ORDER BY clause of the above query with what ever column
you want to order by even if the column is not unique.

Tom , please comment on correctness and performance
of above query

          Followup:

this is the power of analytics, they remove the need for messy, non-intuitive,
SLOW self joins and other tricks.

that is a non-equi join of a table with itself.  think about the work involved.

but a trivial benchmark shows how bad this is:

ops$tkyte@ORA920> drop table emp;

                     Table dropped.

ops$tkyte@ORA920> create table emp
  2  as
  3  select object_id empno, object_name ename, object_id/100  sal
  4    from all_objects
  5   where rownum <= 100
  6  /

                     Table created.

ops$tkyte@ORA920> set autotrace traceonly statistics

ops$tkyte@ORA920> select a.empno, a.ename, a.sal, sum(b.sal)
  2  from emp a, emp b
  3  WHERE a.sal > b.sal
  4  OR
  5  (
  6  a.sal= b.sal
  7  and a.rowid >= b.rowid)
  8  group by a.empno, a.ename, a.rowid,a.sal
  9  order by a.sal, a.rowid
 10  /

100 rows selected.

Elapsed: 00:00:00.07

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        303  consistent gets

ops$tkyte@ORA920> select a.empno, a.ename, a.sal, sum(sal) over (order by sal,
rowid) sum_sal
  2    from emp a
  3  /

100 rows selected.

Elapsed: 00:00:00.09

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          3  consistent gets

ops$tkyte@ORA920> set autotrace off

ops$tkyte@ORA920> delete from emp;
100 rows deleted.

ops$tkyte@ORA920> insert into emp
  2  select object_id empno, object_name ename, object_id/100  sal
  3    from all_objects
  4   where rownum <= 1000
  5  /
1000 rows created.

ops$tkyte@ORA920> set autotrace traceonly statistics

ops$tkyte@ORA920> select a.empno, a.ename, a.sal, sum(b.sal)
  2  from emp a, emp b
  3  WHERE a.sal > b.sal
  4  OR
  5  (
  6  a.sal= b.sal
  7  and a.rowid >= b.rowid)
  8  group by a.empno, a.ename, a.rowid,a.sal
  9  order by a.sal, a.rowid
 10  /

1000 rows selected.

Elapsed: 00:00:01.62

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       9009  consistent gets

ops$tkyte@ORA920> select a.empno, a.ename, a.sal, sum(sal) over (order by sal,
rowid) sum_sal
  2    from emp a
  3  /

1000 rows selected.

Elapsed: 00:00:00.17

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          9  consistent gets

ops$tkyte@ORA920> set autotrace off

ops$tkyte@ORA920> delete from emp;
1000 rows deleted.

ops$tkyte@ORA920> insert into emp
  2  select object_id empno, object_name ename, object_id/100  sal
  3    from all_objects
  4   where rownum <= 10000
  5  /
10000 rows created.

ops$tkyte@ORA920> set autotrace traceonly statistics

ops$tkyte@ORA920> select a.empno, a.ename, a.sal, sum(b.sal)
  2  from emp a, emp b
  3  WHERE a.sal > b.sal
  4  OR
  5  (
  6  a.sal= b.sal
  7  and a.rowid >= b.rowid)
  8  group by a.empno, a.ename, a.rowid,a.sal
  9  order by a.sal, a.rowid
 10  /

10000 rows selected.

Elapsed: 00:03:17.97

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     580058  consistent gets

ops$tkyte@ORA920> select a.empno, a.ename, a.sal, sum(sal) over (order by sal,
rowid) sum_sal
  2    from emp a
  3  /
10000 rows selected.

Elapsed: 00:00:00.42

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         58  consistent gets

ops$tkyte@ORA920> set autotrace off

pretty compelling isn't it

      Rating: 3
Compare with
      solutions in 8,.1.5 an prior  September 21, 2003

Reviewer:  Asim Naveed  from Pakistan
You are 100 percent right, that analytics are faster,
but I propose this solution for versions 8.1.5 and earlier,
those dont have analytic functions, so can you please
compare this solution with other solutions in version
8.1.5 or earlier.

Thanks

          Followup:

my suggestion would be to do this in the client, as you are fetching rows, in
really old software like that.

the cost of doing it in SQL is exhorbinant.

      Rating: 5
Yet another try
      for versions before or equal to 8.1.5  September 22, 2003

Reviewer:  Urs  from Germany
This one is still slower than analytics but scales better than pure SQL without
analytics. Following Tom's mantra: If you can't do it in SQL, use...

SQL> CREATE OR REPLACE
  2  PACKAGE test_nocvs
  3  IS
  4     FUNCTION sum_up(
  5        pRowNumber   IN   PLS_INTEGER,
  6        pValue       IN   NUMBER)
  7        RETURN NUMBER;
  8
  9     PRAGMA RESTRICT_REFERENCES(sum_up, WNDS, RNDS);
 10  END;
 11  /

Paket wurde erstellt.

Abgelaufen: 00:00:00.60
SQL>
SQL> CREATE OR REPLACE
  2  PACKAGE BODY test_nocvs
  3  IS
  4     pckSum   NUMBER;
  5
  6     FUNCTION sum_up(
  7        pRowNumber   IN   PLS_INTEGER,
  8        pValue       IN   NUMBER)
  9        RETURN NUMBER
 10     IS
 11     BEGIN
 12        IF pRowNumber = 1 THEN
 13           pckSum := pValue;
 14        ELSE
 15           pckSum := pckSum + pValue;
 16        END IF;
 17
 18        RETURN pckSum;
 19     END;
 20  END;
 21  /

Paketrumpf wurde erstellt.

Abgelaufen: 00:00:00.60

SQL> create table emp as
  2  select object_id empno, object_name ename, object_id/100  sal
  3    from all_objects
  4  where rownum <= 10000;

Tabelle wurde angelegt.

Abgelaufen: 00:00:01.42
SQL> set autotrace traceonly statistics
SQL> select a.empno, a.ename, a.sal, sum(sal) over (order by sal,rowid) sum_sal
  2  from emp a;

3087 Zeilen ausgewõhlt.

Abgelaufen: 00:00:02.44

Statistiken
----------------------------------------------------------
          0  recursive calls
         12  db block gets
         16  consistent gets
         12  physical reads
          0  redo size
     122266  bytes sent via SQL*Net to client
      14456  bytes received via SQL*Net from client
        207  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
       3087  rows processed

SQL> select a.empno, a.ename, a.sal, test_nocvs.sum_up(rownum, a.sal) sum_sal
  2  from emp a;

3087 Zeilen ausgewõhlt.

Abgelaufen: 00:00:02.54

Statistiken
----------------------------------------------------------
          0  recursive calls
         12  db block gets
        221  consistent gets
          0  physical reads
          0  redo size
     122822  bytes sent via SQL*Net to client
      14456  bytes received via SQL*Net from client
        207  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       3087  rows processed

      Rating: 4
Is there a SUM
      - like function for characters  January 09, 2004

Reviewer:  Raj  from Bay Area, USA
I love your responses with multiple solutions.
I am trying to make use of this window functions to append (concat) the column
value for multiple rows.
ex:
create table emp (empid number, empname varchar2(1));
empid   empname
1       R
1       A
1       J
2       B
2       I
2       L
2       L

How could I use OVER aggregates to come up with -
emplid  Fullname
1       RAJ
2       BILL

Thanks !

          Followup:

OVER are analytics -- they are not aggregates at all.....

IF there is a known upper bound on the number of rows/empid -- eg:

select max(cnt) from ( select count(*) cnt from t group by empid )

is a well known, stable number -- then

ops$tkyte@ORA920PC> select deptno,
  2         max(decode(rn,1,ename||',')) ||
  3         max(decode(rn,2,ename||',')) ||
  4         max(decode(rn,3,ename||',')) ||
  5         max(decode(rn,4,ename||',')) ||
  6         max(decode(rn,5,ename||','))
  7    from (select deptno,ename,row_number() over(partition by deptno order by
  8    ename) rn from emp ) emp
  9   group by deptno
 10  /

    DEPTNO MAX(DECODE(RN,1,ENAME||','))||MAX(DECODE(RN,2,ENAME||',
---------- -------------------------------------------------------
        10 CLARK,KING,MILLER,
        20 ADAMS,FORD,JONES,SCOTT,SMITH,
        30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,

ops$tkyte@ORA920PC>

works.....

also, search this site for

stragg

      Rating:
      5
Excellente  January 09, 2004
Reviewer:
      Raj  from Bay Area, CA USA
It's magical solution I would say.
Thank you !

      Rating: 5
cumulative
      sal  January 11, 2004
Reviewer:
      A reader  from Houston
Reg. posting 1 above

>>In Oracle8i release 2 (8.1.6), we have analytic functions to perform this
>>operation.  In that release, you can code:

>>SELECT ename, sal,
>>  SUM(sal) OVER
>>    (ORDER BY ename RANGE UNBOUNDED PRECEDING) l_csum
>> FROM emp;

In the above example, I would like to know how the following can be done
if I want to show dept no. along with the employee name and then show the totals
per dept.
Like

DEPTNO    ENAME             SAL     L_CSUM
-----    ---------- ---------- ----------
10    ADAMS            1100       1100
10    ALLEN            1600       2700
10    BLAKE            2850       5550
    Dept 10 total    5550
20    CLARK            2450       8000
20    FORD             3000      11000
20    JAMES             950      11950
20    JONES            2975      14925
20    KING             5000      19925
    Dept 20 total      14375
30    MARTIN           1250      21175
30    MILLER           1300      22475
30    SCOTT            3000      25475
30    SMITH             800      26275
30    TURNER           1500      27775
30    WARD             1250      29025
    Dept 30 total      9100

-------------------
DB Version: 9.2.0.4

Thank you

          Followup:

scott@ORA9IR2> select decode( grouping(ename), 0, deptno ) deptno,
  2             decode( grouping(ename), 0, ename, 'Dept ' || deptno || ' total'
)
  3             ename,
  4             sal,
  5         sum(sal) over (partition by deptno order by sal) l_csum
  6    from emp
  7   group by grouping sets ((deptno), (deptno,ename,sal))
  8  /

    DEPTNO ENAME                       SAL     L_CSUM
---------- -------------------- ---------- ----------
        10 MILLER                     1300       1300
        10 CLARK                      2450       3750
        10 KING                       5000       8750
           Dept 10 total                         8750
        20 SMITH                       800        800
        20 ADAMS                      1100       1900
        20 JONES                      2975       4875
        20 FORD                       3000      10875
        20 SCOTT                      3000      10875
           Dept 20 total                        10875
        30 JAMES                       950        950
        30 MARTIN                     1250       3450
        30 WARD                       1250       3450
        30 TURNER                     1500       4950
        30 ALLEN                      1600       6550
        30 BLAKE                      2850       9400
           Dept 30 total                         9400

17 rows selected.

      Rating: 5
Cumulative
      sal  January 12, 2004
Reviewer:
      A reader  from Houston
Excellent answer. - Thank you

      Rating: 5
Cumulative
      sum  January 20, 2004
Reviewer:  Moiz
       from Columbus, OH USA
I want to do the following in a single query:

If I have a budget of only, say 7000, then I want to get a list of those
employees who will be paid through this budget. Our companies policy is to do
the following:
1. Sort the employees in ascending order of their salaries
2. Find the cumulative sum of the salaries
3. Put a "where" clause on this sum

I tried using the following query which is giving wrong result:

  1  select ename, sal, deptno, total from
  2  (select ename, sal, deptno,
  3  sum(sal) over (order by rowid range unbounded preceding) Total
  4  from emp)
  5  where total < 7000
  6* order by sal
SQL> /

ENAME             SAL     DEPTNO      TOTAL
---------- ---------- ---------- ----------
SMITH             800         20        800
WARD             1250         30       3650
ALLEN            1600         30       2400
JONES            2975         20       6625

This is wrong because I am missing James whose sal is 950.

          Followup:

well, you ordered by ROWID, not by salary.

sum(sal) over (order by sal, empno) total

is what you want.

January 20, 2004 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

      Rating: 5
Running Total
      truncation  January 20, 2004
Reviewer:
      M_moiz  from EU
Take the following case. If suppose I have 10,000 budget available and I want to
pay the salaries of the employees then I need to do the following:
1. Sort the employees in ascending order of salaries
2. Find the cumulative sum
3. Put a cut at 10,000 thro' "where" condition

I tried to implement this in the EMP table with the following query:

  1  select ename, sal, deptno, total from
  2  (select ename, sal, deptno,
  3  sum(sal) over (order by empno) Total
  4  from (select ename, sal, deptno, empno from emp order by sal))
  5  where total < 7000
  6* order by sal
SQL> /

ENAME             SAL     DEPTNO      TOTAL
---------- ---------- ---------- ----------
SMITH             800         20        800
WARD             1250         30       3650
ALLEN            1600         30       2400
JONES            2975         20       6625

But this is wrong as I am missing poor James whose salary is 950.

How to solve this problem. Please help.

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:305416600201#15019742126517
please ask in one place and give me a second or two to actually respond!

January 20, 2004 Vinnie -- Thanks for the question regarding
      "Shared Pool free size", version 9.2.1

      You Asked
Tom,

How can I calculate from a system that has been running for weeks, how much of
the shared pool is still free?

      and we said...
use statspack.  it'll tell you right at the top (memory used).

short of that, look at v$sgastat  -- there is an entry for free memory.
January 20, 2004 -- Thanks for the question regarding "Database
      memory set to very large", version 8.1.7

      You Asked
Hi Tom,

   I recently checked a database and found some database parameters set to very
large values:

shared_pool_size = 1G
sort_area_size = 350M
log_buffer = 250M

   I ask the DBA and he just said the server has 4G memory, so it is no harm,
and this can increase the performance (but no stat. data provided). This server
is Solaris server, and there is no other processes except the database. This
database is mainly used for GIS applications. My questions are:

1) Did you set up a database with 1G shared_pool_size before?
2) This database assumes max. conenctions 200. Should the sort_area_size set to
a value such that max_connection * sort_area_size <= total RAM? (I also ask this
DBA the same questions but he said it is not common all connections are doing
the sorting operation at the same time)
3) Is there any reasons to set log_buffer to such a large value (250M)? I always
think that log_buffer set to 10M is enough.

Thanks,
David

      and we said...
1) having a large shared pool can kill performance in a poorly implemented
system.  if they do not use binds - a big shared pool will be a killer.  1g is
about 10times what I would normally expect.

2) 350m is an upper bound -- it is not preallocated.  (hash area size defaults
to 750m!!! watch that one!)

350m seems high, but hard to say....

3) the log buffer of 250m is just wasting space.  log buffer is flushed every 3
seconds, when 1/3 full, upon commit, when there is 1meg in there -- which ever
comes first. you are never near using 250m of it.

"because i have ram, i shall set things really big" is "not smart".  size right,
size for what you do, not based on "got gobs of ram".

if they are using dedicated server, the OS would be glad to have 500/700meg back
for sorting and processes and stuff (from the shared pool/log buffer)
January 20, 2004 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://download-east.oracle.com/docs/cd/A97329_03/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)

      Rating: 5
9ias
      configuration  January 20, 2004
Reviewer:
      mo
TOm:

How do you usually set your 9ias.

Do you have a preference for either Stateless, on the one hand, or
StatelessWithPreservePackageState, on the other hand, for your
PlsqlSessionStateManagement?

Thank you

          Followup:

stateless, only stateless.

never want anyone using resources on my machine when they are not actually using
my machine.

January 20, 2004 j. -- Thanks for the question regarding "how to
      check a record's initialization ", version 9iR2

      You Asked
hi tom,

i wonder if there 's a way to check whether an arbitrary record got initialized
or not *WITHOUT* accessing specific elements:

declare
  type aRecordType is record(aColumn number);
  aRecordVariable  aRecordType;

begin
  aRecordVariable := null;  -- succeeds
  if aRecordVariable.aColumn is null then null; end if;  -- works well
  if aRecordVariable is null then null; end if;  -- fails
end;

      and we said...
a record is ALWAYS initialized.  ALWAYS

so -- it is a trick question -- a record is always initialized, either to the
defaults specified in the type or to null attributes.
Review &
      Followup

      Rating: 3
no - not a
      trick question  January 20, 2004
Reviewer:
      A reader
i was just looking for a way to GENERATE code for checking the initalization of
records without knowing their structure. can a record's structure be "examined"
with PL/SQL in order to get the information needed?

          Followup:

trick question -- cause I don't understand that at all?

how can you "check" the initialization of a record if you don't even have any
idea whats to be in the record in the first place

AND

what is the difference between an uninitialized record and an initialized one?
(that is a trick question because there is no such thing as an uninitialized
record!)

January 20, 2004 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.

      Rating: 4
How do I make
      this work  January 20, 2004
Reviewer:
      sonali  from waltham, ma
As I want to see the values returned by the select statement I am doing into but
as because there are multiple rows returned by this query I get this error.. How
do i write this query then ?

Server Output                   ON
SQLWKS> DECLARE
     2>    Category NUMBER := 1669;
     3>    Legal_type NUMBER :=  3;
     4>    Legal_locale varchar2(10):= 'en-us';
     5>    p_Legal_text varchar2 (25);
     6> BEGIN
     7>
     8>
     9>
    10>    EXECUTE IMMEDIATE 'select legal_text  from mweblegal where
legal_category =  :bind_category
    11>         And legal_type = :bind_type and legal_locale = :bind_locale'
into  p_Legal_text
    12>          using    category, legal_type, legal_locale   ;
    13>          dbms_output.put_line('legal_text '|| p_Legal_text);
    14>
    15> END;
    16>
    17>
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at line 10

          Followup:

declare
    ....
    type rc is ref cursor;
    l_cursor is rc;
begin
    open l_cursor for 'select ....' using bind1, bind2;

    loop
       fetch l_cursor into .......;
       exit when l_cursor%notfound;
       ....
    end loop;
    close l_cursor;
end;
/

January 20, 2004 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://download-west.oracle.com/docs/cd/B10501_01/server.920/a96520/tuningpe.htm#63440

      Rating: 5
super
      useful  January 20, 2004
Reviewer:  Craig
       from NW Arkansas
Tom -

I found a thread on google groups about this topic, and one of the "seemingly
smart" posters said not to build or rebuild in parallel, as it will lead to
"fragmentation of the index."  Could you refute his argument, or at least shed
some light on what he could possibly mean?  Based on your comments above,
(re)building in parallel is all kinds of good.  Is there a downside?  Surely
there is, otherwise Oracle would automatically do it, right?

http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&threadm=324BDF39.24C2%40mink.att.com&rnum=1&prev=/groups%3Fhl%3Den%26lr%3D%26ie%3DISO-8859-1%26q%3Dparallel%2Bindex%2Bbuild%2Bfragment%26meta%3Dgroup%253Dcomp.databases.oracle.*

          Followup:

use locally managed tablespaces.

the guy is worried about "extents" and having more than one.

see

http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&selm=324BDF39.24C2%40mink.att.com&rnum=10
where he explains his worry.

problem is -- i like the index in 8 extents better than one.  More extents is
good.  single extent is not desirable, not more performant, not better in any
way shape or form.

single extents being "better" is a huge huge, big, large, huge MYTH.

      Rating: 5
Man, that was
      fast!!  January 20, 2004
Reviewer:  Craig
       from NW Arkansas
Thanks bunches, Tom.  So I'm curious...why doesn't Oracle build indexes in
parallel by default, then?  I mean, you're always saying that you can come up
with a case for just as easily as a case against.  So what is a valid case
against?

ps.  Hope you enjoyed your stay overseas.  You were missed.

          Followup:

parallel is a feature of enterprise, not in standard

most people don't want to do it in parallel (don't need to, parallel = really
big).

it is an option for those that want it.

January 20, 2004 Munzer -- Thanks for the question regarding
      "PL/SQL questions", version 8.1.7

      You Asked
Tom:

1  WHen You use
for x in (Select * from table1, table2, table 3 where
table1.a=table2.a(+) and table1.a=table3.a(+)  )
LOOP
...
END LOOP

Can you use any kind of select statement. for example have a 6 table join with
all kinds of filters and bind variables.

2.  It seems that my program does not work when I do

i:=5;
For x in i+1..i+10
LOOP
..
END LOOP

Is this invalid statement where the starting and ending number is a calculation

3.  WHen you define a cursor in memory in a PL/SQL program is it possible to
change the set of records or redefine the cursor in the program again.

4.  IF you have 5 columns in a table that make a primary key, does oracle
automatically create an indesx for all those columns so that every time you
insert it is updating the index.  Would that slow things up.

Thank you,

      and we said...
1) yes.

2) its fine, you did something else wrong in the test:

ops$tkyte@ORA8I.WORLD> declare
  2          i number;
  3  begin
  4          i := 5;
  5          for x in i+1 .. i+10
  6          loop
  7                  dbms_output.put_line( x );
  8          end loop;
  9  end;
 10  /
6
7
8
9
10
11
12
13
14
15

PL/SQL procedure successfully completed.

3) does not make sense to me.  don't know what you are trying to say there.

4) Oracle will either create or use an existing index on those 5 columns.  As to
whether it would "slow things down" -- it is an UNAVOIDABLE price to pay to have
a primary key.  Its the price you pay.  There is no other way to do it.. Even if
you choose not to use these 5 columns as your primary key -- they are UNIQUE (by
your data model) and there will be an index on them in all cases.

Review &
      Followup

      Rating:
      5
Cursor  June 07, 2002
Reviewer:
      munz  from Reston, USA
Tom:

1.  I tried to use this since I do not have a primary key in EMP

  For x in (select emp.*,rownum r from emp)
  LOOP
   .....
    update emp set empid=x.lname||x.firstname where rownum=x.r
END LOOP

END;

The update did not work.  It was updating the whole table with the last value.
ANy idea why?

2.  I used ROWID instead of ROWNUM and it worked fine.

3.  IF rowid is the physical address of the record in the data file what is the
rownum?

Thank you,

          Followup:

well, this has absolutely nothing to do with the original question???

anyway, read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4061088733083
(your description of what it does is wrong as well.  it will NOT update the
entire table, in fact, only the first update will update s single row -- the
other updates do nothing -- since "where rownum = X" where X > 1 is never
true....  If you read and understand the above link, you'll know why.

rowid works cause rowid is a primary key.

rownum is a made up number assigned to a row after it is fetched from a table.

      Rating:
      5
Rownum  June 08, 2002
Reviewer:
      Monz  from Reston, USA
TOM:

Oh ok i think I understand it.

When you open the cursor yo are fetching one record a  time and every time you
fetch the rownum is 1.

I thought the when you open cursor it loads all result set in memory which
includes rownum 1..20 and then you will be accessing the rownum of each record.

Am I correct?

          Followup:

Read:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4061088733083
I think is explains this rather nicely.  rownum is assigned to rows as they
happen to be fetched during a query (or the search part of an
insert/update/delete).

      Rating: 5
Outer
      join  June 08, 2002
Reviewer:  Suresh
       from New York
Tom,
  Regarding the 1st question in this thread, could you please let me know how
many tables can be outer joined in a single SQL statement? Thanks.

          Followup:

There is no limit I am aware of.

Here is a "big one"

  1  declare
  2     l_query long;
  3     l_cnt   number;
  4  begin
  5     l_query := 'select count(*) from dual d1';
  6     for i in 2 .. 255
  7     loop
  8             l_query := l_query || ', dual d'||i;
  9     end loop;
 10     l_query := l_query || ' where 1=1 ';
 11     for i in 2 .. 255
 12     loop
 13             l_query := l_query  || ' and d1.dummy = d' || i || '.dummy(+) ';
 14     end loop;
 15     execute immediate l_query into l_cnt;
 16     dbms_output.put_line( 'and the answer is ' || l_cnt );
 17* end;
 18  /
and the answer is 1

PL/SQL procedure successfully completed.

      Rating:
      5
  June 09, 2002
Reviewer:  Suresh

Tom,
  Thanks for your response regarding the maximum number of tables that can be
outer joined in a SQL statement. The reason I asked you is that I got this
following error sometime back while trying to do more than one outer join in a
statement.

ORA-01417: a table may be outer joined to at most one other table

   So, from your anwer, I understand that there can be many tables outer joined
in a single SQL statement and from the above mentioned error, it is clear that a
table cannot be outer joined with more than one table in a statement.

  Please let me know if this is right.

          Followup:

What that is saying that if some table T is outer joined to some other table T2,
then T may not be outer joined to T3.  In the following, D1 = T, D2 = T2, D3 =
T3:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from dual d1, dual d2, dual d3
  2  where d1.dummy (+) = d2.dummy and d1.dummy(+) = d3.dummy;
where d1.dummy (+) = d2.dummy and d1.dummy(+) = d3.dummy
                   *
ERROR at line 2:
ORA-01417: a table may be outer joined to at most one other table

That is the error you are getting.  You can workaround this issue with inline
views:

  1  select *
  2    from ( select d1.dummy d1_dummy, d2.dummy d2_dummy
  3                from dual d1, dual d2
  4               where d1.dummy(+) = d2.dummy ) d_temp,
  5        dual d3
  6*  where d_temp.d1_dummy(+) = d3.dummy
ops$tkyte@ORA817DEV.US.ORACLE.COM> /

D D D
- - -
X X X

That will physically make "d_temp" exist for a moment and then outer join that
temporary set to d3.  The effect is the same (and it is allowed)

      Rating: 5
Tom, Thanks a
      lot. As always you are the best.  June 09, 2002

Reviewer:  Suresh

      Rating: 3
ANSI multiple
      outer join avoiding ORA-01417  June 11, 2002

Reviewer:  David Penington  from Melbourne Australia

This is where I like the power of the ANSI join notation. You can unambiguously
specify the behaviour while outer-joining to multiple tables. The example which
needed an inline view becomes:
select d1.dummy d1_dummy, d2.dummy, d3.dummy
from dual d3 cross join dual d2 left outer join dual d1 on
 (d1.dummy = d2.dummy AND d1.dummy = d3.dummy)
/
A more meaningful example:
select xxx.x1, yyy.empid, emp.empname
from xxx join yyy on (xxx.stateid = yyy.stateid)
left outer join emp on (emp.empid = yyy.empid and emp.empname = xxx.x1)
This outer joins two tables to one while avoiding ORA-01417: a table may be
outer joined to at most one other table

          Followup:

did not mention that as question was for 817.

      Rating:
      5
  January 07, 2004
Reviewer:
      A reader

      Rating:
      4
v$instance  January 14, 2004
Reviewer:
      mo
Tom:

Do you know why when i do :

select instance_name into v_instance_name from v$instance;

in pl/sql (package.procedure) I get an error that view/table is not found.

This is 9i database and I run the command in sqlplus and it gives me the
instance name.

          Followup:

http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html

      Rating:
      5
v$view  January 16, 2004
Reviewer:
      mo
Tom:

DBA does not how to grant a developer account access to all V$views without
using a role.  Is there a way to do it?

He says this would not work:
"grant select on v$instance to dev"

He did this workaround:

create view vinstance as select * from v$instance;
grant select on vinstance to dev; as dev:
create synonym vinstance for sys.vinstance;

It would be very tedious to do this for every view.

          Followup:

begin
    for x in ( select object_name from user_objects where object_type = 'VIEW'
and
               object_name like 'V\_$%' escape '\' )
    loop
        execute immediate 'grant select on ' || x.object_name || ' to
ops$tkyte';
    end loop;
end;
/

      Rating:
      3
Nice  January 17, 2004
Reviewer:
      Gerhard  from Dusseldorf,Germany
Dear sir,
Why do we use the word "Outer" in Outer join?What is so
special about it?

          Followup:

Hopefully this will help spell it out and make it clear.  In a nutshell --
"outer" makes it so that ALL rows from a given table (or two) are returned even
if there is no "match" in the other table.  Normally -- in a join, only rows
that have matches in both tables are returned.  an outer join preserves all of
the rows.  consider:

ops$tkyte@ORA9IR2> create table t1 ( x int );
Table created.

ops$tkyte@ORA9IR2> create table t2 ( x int );
Table created.

ops$tkyte@ORA9IR2> insert into t1 values ( 1 );
ops$tkyte@ORA9IR2> insert into t1 values ( 2 );
ops$tkyte@ORA9IR2> insert into t2 values ( 2 );
ops$tkyte@ORA9IR2> insert into t2 values ( 3 );

 so that sqlplus will show us "made up values" -- nulls supplied by the outer
join operation -- clearly...

ops$tkyte@ORA9IR2> set NULL MadeUp!

ops$tkyte@ORA9IR2> select t1.x, t2.x
  2    from t1 join t2 on ( t1.x = t2.x );

         X          X
---------- ----------
         2          2

that is the normal "join" we use all of the time.  only rows that satisfy the
join condition appear in the result set.  if a row exists in t1 (x=1) but not in
t2 -- it will not appear and vice versa

ops$tkyte@ORA9IR2> select t1.x, t2.x
  2    from t1 left outer join t2 on ( t1.x = t2.x );

         X          X
---------- ----------
         2          2
         1 MadeUp!

Now, the left outer join says "show ALL rows from the 'table on the left' and
IF it has a match in T2 -- go ahead and show those as well

ops$tkyte@ORA9IR2> select t1.x, t2.x
  2    from t1 right outer join t2 on ( t1.x = t2.x );

         X          X
---------- ----------
         2          2
MadeUp!             3

and a right join (right outer join -- the outer is not really needed but
implied in a right/left/full join) is the opposite of the left join.  It
preserves all of the rows in the righ most table (t2) and shows all of them,
making up a value in T1 when there is no match...

ops$tkyte@ORA9IR2> select t1.x, t2.x
  2    from t1 full outer join t2 on ( t1.x = t2.x );

         X          X
---------- ----------
         2          2
         1 MadeUp!
MadeUp!             3

and lastly -- a full outer join.  BOTH tables will have ALL rows contribute to
the result set -- making up rows in the other table when there is no match

      Rating:
      4
Nice  January 19, 2004
Reviewer:
      Gerhard  from Dusseldorf,Germany
Dear Sir,
Thanks a lot for your explanation.But is there any way
to avoid Outer join or Is there Any other way to replace
outer join?
Bye!

          Followup:

well -- if you want what you want (all of the rows from one of the tables), the
outer join is typically the most efficient.

But, sure, you can "remove" an outer join.  consider:

scott@ORA920PC> select dname, count(empno)
  2  from dept left outer join emp on ( emp.deptno = dept.deptno )
  3  group by dname
  4  order by 1
  5  /

DNAME          COUNT(EMPNO)
-------------- ------------
ACCOUNTING                3
OPERATIONS                0
RESEARCH                  5
SALES                     6

scott@ORA920PC>
scott@ORA920PC> select dname, (select count(*) from emp where emp.deptno =
dept.deptno)
  2    from dept
  3   order by 1
  4  /

DNAME          (SELECTCOUNT(*)FROMEMPWHEREEMP.DEPTNO=DEPT.DEPTNO)
-------------- --------------------------------------------------
ACCOUNTING                                                      3
OPERATIONS                                                      0
RESEARCH                                                        5
SALES                                                           6

scott@ORA920PC>
scott@ORA920PC>
scott@ORA920PC> select dname, count(*)
  2    from dept join emp on ( emp.deptno = dept.deptno )
  3   group by dname
  4   union all
  5  select dname, 0
  6    from dept
  7   where not exists ( select null from emp where emp.deptno = dept.deptno )
  8   order by 1
  9  /

DNAME            COUNT(*)
-------------- ----------
ACCOUNTING              3
OPERATIONS              0
RESEARCH                5
SALES                   6

scott@ORA920PC>

      Rating: 5
Thank
      u  January 19, 2004
Reviewer:  Murali
       from Bangalore, India
Thank u very much Tom...I have very much liked all your answers and dont forget
there are lots of guys out here in India who want to become like you.(including
myself)..

      Rating: 3
Java prepared
      statement to PL/SQL procedure  January 19, 2004

Reviewer:  Baqir Hussain  from Emeryville, CA USA

Tom,
The following Java code is inefficient in updating/deleting/inserting old
client_id with the new ones. The client_id will be passed on the code.

I need your help to generate an effient PL/SQL code out of this Java code and
oblige.

  PreparedStatement ps1 = connection.prepareStatement("UPDATE client_history SET
client_id = ? WHERE client_id = ?");
    PreparedStatement ps2 = connection.prepareStatement("UPDATE client_stars SET
client_id = ? WHERE client_id = ?");
    PreparedStatement ps3 = connection.prepareStatement("UPDATE customer SET
last_logged_in_client_id = ? WHERE last_logged_in_client_id = ?");
    PreparedStatement ps4 = connection.prepareStatement("UPDATE
directions_address SET client_id = ? WHERE client_id = ?");
    PreparedStatement ps5 = connection.prepareStatement("UPDATE ecrm_reply SET
client_id = ? WHERE client_id = ?");
    PreparedStatement ps6 = connection.prepareStatement("UPDATE
email_notify_link_stats SET client_id = ? WHERE client_id = ?");
    PreparedStatement ps7 = connection.prepareStatement("UPDATE email_track_log
SET client_id = ? WHERE client_id = ?");
    PreparedStatement ps8 = connection.prepareStatement("UPDATE home_search SET
client_id = ? WHERE client_id = ?");
    PreparedStatement ps9 = connection.prepareStatement("UPDATE journal SET
client_id = ? WHERE client_id = ?");
    PreparedStatement ps10 = connection.prepareStatement("UPDATE link_handler
SET client_id = ? WHERE client_id = ?");
    PreparedStatement ps11 = connection.prepareStatement("UPDATE link_stat SET
client_id = ? WHERE client_id = ?");
    PreparedStatement ps12 = connection.prepareStatement("UPDATE
loan_partner_data SET client_id = ? WHERE client_id = ?");
    PreparedStatement ps13 = connection.prepareStatement("UPDATE message SET
client_id = ? WHERE client_id = ?");
    PreparedStatement ps14 = connection.prepareStatement("UPDATE my_comparables
SET user_id = ? WHERE user_id = ?");
    PreparedStatement ps15 = connection.prepareStatement("UPDATE my_homes SET
user_id = ? WHERE user_id = ?");

    PreparedStatement ps16 = connection.prepareStatement("UPDATE outbound_links
SET client_id = ? WHERE client_id = ?");
    PreparedStatement ps17 = connection.prepareStatement("UPDATE
phoenix_idx_retention SET client_id = ? WHERE client_id = ?");
    PreparedStatement ps18 = connection.prepareStatement("UPDATE
required_contact SET client_id = ? WHERE client_id = ?");
    PreparedStatement ps19 = connection.prepareStatement("UPDATE scheduled_visit
SET client_id = ? WHERE client_id = ?");
    PreparedStatement ps20 = connection.prepareStatement("UPDATE session_log SET
client_id = ? WHERE client_id = ?");

    PreparedStatement ps21 = connection.prepareStatement("UPDATE track_end_date
SET client_id = ? WHERE client_id = ?");
    PreparedStatement ps22 = connection.prepareStatement("UPDATE
track_start_date SET client_id = ? WHERE client_id = ?");
    PreparedStatement ps23 = connection.prepareStatement("UPDATE zip_transaction
SET buyer_id = ? WHERE buyer_id = ?");
    PreparedStatement ps24 = connection.prepareStatement("UPDATE zip_transaction
SET seller_id = ? WHERE seller_id = ?");

    PreparedStatement ps25 = connection.prepareStatement("INSERT INTO sso_client
SELECT * FROM client WHERE client_id = ?");
    PreparedStatement ps26 = connection.prepareStatement("DELETE FROM client
WHERE client_id = ?");

   PreparedStatement ps1 = connection.prepareStatement("UPDATE client SET
customer_id = ? WHERE customer_id = ?");
    PreparedStatement ps2 = connection.prepareStatement("UPDATE email_address
SET customer_id = ? WHERE customer_id = ?");
    PreparedStatement ps3 = connection.prepareStatement("UPDATE telephone_number
SET customer_id = ? WHERE customer_id = ?");
    PreparedStatement ps4 = connection.prepareStatement("UPDATE
web_question_answered SET customer_id = ? WHERE customer_id = ?");
    PreparedStatement ps5 = connection.prepareStatement("UPDATE note_history SET
customer_id = ? WHERE customer_id = ?");
    PreparedStatement ps6 = connection.prepareStatement("INSERT INTO
sso_customer SELECT * FROM customer WHERE customer_id = ?");
    PreparedStatement ps7 = connection.prepareStatement("DELETE FROM customer
WHERE customer_id = ?");

          Followup:

easy:

begin
   null;
end;

that code does nothing -- just parses a bunch of statements.

But it would be a trivial stored procedure to just execute those statements as
well.  there is no logic there, just a bunch of statements to be executed.

      Rating:
      3
Help  January 19, 2004
Reviewer:
      Mohan  from India
Tom,

Is there anyway,through PL/SQL routine, i can know the sessions that got
terminated abnormally. The case with my database is that few permanent objects
are created in a session, and when the user logs off it fires a Schema level
trigger which will drop the 'temporary' permanent objects of the session using
dynamic sql. It would have been better done with the use of Temporary Tables but
the db guys before i took over didn't use that. The convention that is followed
for the objects is object_name<session_id>. Can u help me out in dropping the
objects for the session terminated abnormally.
Thanks and Regards

          Followup:

well, until you fix this for real (because doing ddl in a stored procedure is a
truly bad idea for so many reasons) - you would schedule a job that would query
dba_objects looking for objects to drop.  I guess they used
userenv('sessionid'), so you would be looking at audsid in v$session for active
sessions -- dropping anything that isn't "active"

use dbms_job to schedule this to run every "whatever unit of time you desire"

      Rating:
      5
v$view  January 20, 2004
Reviewer:
      mo
Tom:

1.  DBA says grant does not work. Any ideas why.

SQL> grant select on v$instance to nlsadmin;
grant select on v$instance to nlsadmin
                *
ERROR at line 1:
ORA-02030: can only select from fixed tables/views

2.  when you do " like 'V\_$%' escape '/'
what is this actually looking for. Is it anything that starts with V_$?

          Followup:

1) v$instance is not a view, v_$instance is

2) yes, normally _ matches ANY single character

'\_' escape '\'

looks for an underscore instead.  the escape clause lets you use what are
normally wildcards in the search string (_ and %)

      Rating:
      5
v$views  January 20, 2004
Reviewer:
      mo
Tom:

Thank you.

How do you explain this:

SQL> desc v_$instance;
ERROR:
ORA-04043: object v_$instance does not exist

However when I do:
SQL> desc v$instance
it works.

Are they both valid view names?

          Followup:

v$instance is a synonym for sys.v_$instance

that is why.  desc sys.v_$instance

January 21, 2004 Rakesh -- Thanks for the question regarding
      "encrypt database username password & global variables &
      log4plsql", version 817

      You Asked
Hi Tom

   q1) Lot of times in our code to connect to backend(oracle) we use JDBC.
Either we hardcode the username & password to connect to the database in the
java file or pick the same from a properties file.
  What i would like to know is there a way to connect to the database & send the
password in an encrypted form which will not making any sense to the naked eye.
 I read that there is a way we can do this using Oracle Wallet manager but could
not find much on the same.
Could you share your views if possible an example on the same

q2)
   I have a table(account_details) with 3 cols(deptno, account_name & status)

the status col can hold 2 values (enabled or disabled) for a particular user
(account_name)

for a particular dept in the table there are users (account_name) with status as
enabled & disabled.

Now we run a process for loading where we require to set all the enabled users
for a particular deptno to disabled & when the process completes set them back
(only the ones which were enabled status prior to start of this process) back to
enabled.

Currently i am using a global variable (package variable) a collection type
where i store all the enabled users

in the package i have 2 procedures one for disabling the users & the other for
enabling the users

i fill  the array values in the stored proc(disable_users) with the user list &
set the status to disabled for the same in the table
& then in the stored proc(enable_users) retrieve values from the array & set it
back to enabled in the tabel

I am slightly  confused what would happen to this glbal package variable (the
array i would be using to store the user list)
when the stored procs are called in a multi threaded environment

As global variables will be stored in the memory, do you think it might cause
some problems

If yes what approach should i follow to acheive the above functionality.

q3) Recently i read an article on OTN regarding the open source package
(LOG4PLSQL) similar on the lines of LOG4J.
The article though does not explain it with proper examples i.e how to use it on
our day to day plsql coding

 If you have any idea on the same it would be glad if you share your views about
the utility of this package in our day to day PLSQL coding
& how it would help us to enhance the quality of database coding

Regards
Rakesh

      and we said...
q1) passwords are transmitted encrypted already.  you need do nothing to encrypt
the password, it "happens"

q2) sounds like a data model problem.  You need a third state -- "disabled but
to be reenabled" perhaps OR another table where you save the keys of the rows to
be re-enabled.  using global variables leaves you open to "a crash" -- where you
will lose their values and be "stuck"

q3) I've never used log4plsql, however a simple google search on it turned up
the documentation on sourceforge instantly.....
Review &
      Followup

      Rating: 5
store password
      in a cryptic form  January 20, 2004
Reviewer:
      Rakesh
Hi Tom

  In a hurry to post the question , i  deviated from what i wanted to ask.
Actually my question regarding encrypt password was how do we store the username
& password in cryptic form in the application before connecting to the database.

Normally if we have the username & password in the java file it is there for any
one to see who can view the .java file
or decompile .class file & view the username & passwrod hardcoded in the .java
file.

Another way is to have teh username & password stored in a properties file
But anyone with access to the o/s where the application is running can view the
properties file

Regards
Rakesh

          Followup:

for that you would have to ask the java programmers for a "good" solution.

they will have to

a) pick the encryption class they want to use
b) figure out how the heck to do the key management (once the password is
encrypted -- what do you do with the encryption key)

January 21, 2004 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.

      Rating: 3
How do we get
      rows from 20 - 50   January 21, 2004
Reviewer:
      RB  from NJ
Is there an easy way in oracle to retrieve data from row 20 to row 50 or n to n
+ 10

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064

January 21, 2004 Ishaq -- Thanks for the question regarding "Host
      Command Problem in Forms6i (Forms 6.0.8.8.0 Ver)", version Forms Version
      6.0.8.8.0

      You Asked
The Problem is How can I run
Host() command in foms6i in Synchronous Mode.
----------------------------------------------------------------
For Example if the Following Commands were run in Forms
5.0.6.8.0 Ver
The Host Command is executed Synchronously.

Host('Dir c:\windows');
Host(Dir d:\Orawin95');
----------------------------------------------------------------
But if the Following Commands were run in Forms 6.0.8.8.0 Ver
The Host Command is executed ASynchronously.

Host('Dir c:\windows');
Host(Dir d:\Orawin95');
----------------------------------------------------------------
I have tried to call Windows applications from Oracle Forms
6.0.x using ORA_FFI.
But this too runs Asynchronously.

Thanks In Advance.

      and we said...
This appears to work:

host( 'command.com /c dir c:\*.* /s' );

Review &
      Followup

      Rating: 3
How to return
      the error message to form console  August 21, 2002

Reviewer:  Riaz Shahid  from PRAL, Islamabad,
      Pakistan
Hello Tom !

I am using Forms 6.0 and using host command to have backup from forms. For this,
i use:

host('exp
'||user||'/'||get_application_property(password)||'@'||get_application_property(c
onnect_string)||' file='||:file_name);

How i would know that backup has been successfull completed or not ?

I wanto to return error (if any) generated by runnig 'exp'. Like:

Export Terminated Successfully Without Warnings.
OR
Export Terminated Successfully With Warnings.

Regards

          Followup:

That isn't a backup, that is an export and is not a true method for backing up a
database (forget for a moment that the method you are using isn't even a
consistent export -- meaning, it might not even be IMPORTABLE given constraints
and foreign keys!)

Hopefully, you do not use this for any data of any importance....

You would redirect EXP's stdout and stderr to a text file.  You would use
text_io to read the text file and report back what you wanted to report back to
the user.

      Rating: 1
How Can i do
      that ?  August 22, 2002
Reviewer:  Riaz
      Shahid  from PRAL, Islamabad, Pakistan
Tom,

Thanks for your quick response. Please give example to how to use stderr,
text_io etc.

Regards

Riaz

          Followup:

See your windows manuals for how to redirect stdout and stderr (eg: using > in
the command.

for text_io examples, just go into forms and do "help" text_io.

They are there.  They are simple (there are things like text_io.fopen, get_line,
close, etc -- very simple text io routines)

      Rating: 3
window
      key  January 21, 2004
Reviewer:  omer
       from pakistan
Hi!
I want to use window shortcut key ALT+SHIFT(to switch in other language) in
forms(forms6) at runtime

is it possible using HOST command or any other option?

Thanks

          Followup:

try otn.oracle.com -> discussion forums.

I have not touched forms since 1995 personally.

      Rating:
      3
  January 21, 2004
Reviewer:  hina
       from pk
hi Riaz Shahid and Omer can we talk to each other at learnerxone@yahoo.com

      Rating:
      4
  January 23, 2004
Reviewer:  hina
       from pk
hi riaz shahid and omer some MEHARBAN has hacked my id so it's no use now :)

January 21, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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.

      Rating: 5
How to do this
      for integer?  January 21, 2004
Reviewer:
      Tony  from India
Hi,
   To avoid assigning null terminator for char variables, we can use the
following way.

char  job[100][20];
exec sql var job is string(20);

How to do like this for integer variables?

          Followup:

huh?  int's are not "null terminated"?

if you fetch a number into a string -- that is just like you used to_char() --
meaning that you do it the same way you did it for "job" -- no different.

if you fetch into a string, you use the same technique.

if you fetch into int's -- you don't null terminate them.

January 21, 2004 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.

      Rating: 3
Customize via
      anonymous PL/SQL blocks?  January 15, 2004

Reviewer:  j.
we have to maintain data exchange via files between our oracle databases and
customer systems.

several subsystems provide standardize interfaces to our internal systems for
that purposes. they contain data structures for data to be received/sent as well
as routines to process this data. incoming data is converted into the
standardized internal formats via xml/xslt. on the output side xml/xsl serves
for converting data into customer formats.

since these data structures reside on our internal systems that receive/send
data they are *not* generic. but due to different customer needs we are forced
to make the import routines more flexible (for export this is not a problem).
for instance customers want to be able to exclude destination tables from beeing
affected. in addition they want to configure whether rows should be imported
(inserted/updated) as a whole or piecewice (process certain columns only).

to provide that scale of flexibility we are thinking of using dynamic SQL in
conjunction with "normal" stored code: a stored PL/SQL routine implements the
steps of the overall "workflow" - processing the data in the order it has to be
done (due to target RI constraints).
every step uses its own anonymous PL/SQL block that gets intialized once per
import and executed once per source row. each PL/SQL blocks contains a
customized update and insert operation for a certain data structure. at runtime
those PL/SQL blocks get invoked via cursors from within the main routine. in
case of an update the rowid of the row to be updated is provided by the caller.
the PL/SQL blocks request the data to be processed via a static callback
function which reads one source row at a time (always the superset of
"candidate" columns). the PL/SQL block in turn (in case it is called at all)
processes the requested columns only. it handles errors so that the caller can
request codes/messages in case the execution fails.

the described solution worked well. unfortunately it took almost double the time
of the static PL/SQL solution.

question is: what do you think about this? do you see a better approach?

          Followup:

when you did the dynamic sql -- you used dbms_sql right?  to avoid a parse per
execute.

or did you use native dynamic sql which in this case would be slower?

      Rating:
      3
yes  January 15, 2004
Reviewer:  j.

(i've read all your books ;o)

          Followup:

last book (effective oracle) shows how much better static sql is than dynamic :)

you might just want to write a routine that dynamically generates the routines
once at your customer site -- resulting in static, known sql/plsql code at each
customer.

      Rating:
      3
  January 15, 2004
Reviewer:  j.

but our (internal) customers run delphi and share the same database schemas ...

          Followup:

huh?  what does delphi have to do with plsql?

anyway -- guess I'd code for each "customer" and dynamically call a procedure
per customer then.

      Rating:
      3
  January 16, 2004
Reviewer:  j

they use (thin) delphi clients to access the database. these clients call pl/sql
code residing on server side.

static code doesn't seem to be solution flexible enough for our requirements
(and preference to do as much as possible on database side :), since there are
no static rules how the data has to be transferred from source to target. in
fact the migration routines may even vary for different transfers of the exact
same input format.

          Followup:

so?  I don't understand why delphi matters.  it is just *a language*.

      Rating:
      2
  January 16, 2004
Reviewer:  j.

i just mentioned delphi because i thought you thought that there would be pl/sql
available at client side when you wrote "write a routine that dynamically
generates the routines once at your customer site -- resulting in static, known
sql/plsql code at each customer."

our current scenario consists of one "universal" static pl/sql routine at
database side. it determines the "frame" for invokation of several anonymous
pl/sql blocks (one per DML target) generated at runtime according to customers
needs for processing user specific customized DML operations (e.g. exclude or
transform certain column values during DML).

i'm afraid i didn't understand your solution at all.
could you please explain it in a little more detail?

          Followup:

guess you need a procedure per customer -- that is what I'm trying to get to --
did not know your customers were not running oracle.

why not generate (statically, via a code generator) a procedure for a customer
and update the procedure if the customer profile changes.

      Rating:
      2
  January 17, 2004
Reviewer:  j.

but then it 's all about how/where to store those generated routines. even if we
would assign one dedicated schema per customer to hold these customized database
objects we would run into problems with multiple sessions (transferring
different data) for one customer at a time.

      Rating: 5
can you create
      a procedure by executing a procedure?  January 21, 2004

Reviewer:  A reader
Can you do this?

create or replace procedure wow
is
v_sql varchar2(1000);
begin

v_sql := 'create or replace procedure wowwow
          is
          begin
          dbms_output.put_line(''Wowsers'');
          end;
          /
          ';
execute immediate (v_sql);
end;
/

After getting an ora-1031 insufficient privs on exec wow, I explicitly granted
the 'create procedure' priv to my user, and re-executed.

Now I get:

eurokous@dev> exec wow
ERROR:
ORA-24344: success with compilation error
ORA-06512: at "eurokous.WOW", line 13
ORA-06512: at line 1

It's a problem with execute immediate. I'll read up, but what do you have to
say?

Thanks much

          Followup:

"/"

is a sqlplus thingy -- it tells sqlplus "please run the buffer, i'm done typing"

it is not part of sql.

lose the "/", that is the compilation error.

not a problem with execute immediate, rather problem with the code you pass to
it.

      Rating:
      5
Thanks!  January 21, 2004
Reviewer:
      A reader

January 21, 2004 -- 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.

      Rating: 5
Don't see any
      difference even after setting (SDU=8192)(TDU=8192)  January
      21, 2004
Reviewer:  Sami  from NJ,USA
Dear Tom,
<Ref MetaLink Doc ID:  Note:1005123.6 >

Trying to tune SQL*Net.

4. Does increasing the array_size parameter provide a consistent increase   in
performance?  Should this be raised until the MTU size is met? What are   the
negative consequences?
Array_size is often the easiest way to make orders-of-magnitude gains in
performance.  There is, however, a point of diminishing returns.  In general,
aim for an arraysize that would result in about 2k of data per request to
SQL*Net if all columns were full.  You could aim for an arraysize of 4K, a
SQL*Net SDU of 4K and TCPIP packet size of 4K and maybe see some improvement.

Question on
"In general,   aim for an arraysize that would result in about 2k of data per
request to   SQL*Net if all columns were full"

We have verious types of queries (both in Java and PL/SQL)for example
Query1 row size is 50 bytes
Query2 row size is 500 bytes
Query3 row size is 2000 bytes

So, how do I set arraysize that would closely match to size 2k.

Q1) Do I have to set java.sql.Statement.setFetchSize() for each statement(based
on the row size) and arraysize in PL/SQL?

          Followup:

this is tuning at the micro level -- this is the last place to explore.

tell me

a) have you identified a performance issue
b) have you narrowed it down to tcp/ip protocol issues

I prefer to take it easier here -- I use an array size of about 100.  never
really ran into issues with that one.

you can set this for the connection (defaults to 10)

January 21, 2004 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).

      Rating: 5
Not always
      works for me  January 21, 2004
Reviewer:
      igor  from ME USA
Hi Tom,

Good technique, but sometimes doesn't work for me. Can you please take a look?
My database is 9.2.0.4. Thanks much!

create or replace type virtual_table_type as table of number
/

Type created.

create or replace function virtual_table( p_num_rows in number )
return virtual_table_type
PIPELINED -- NOTE the pipelined keyword
is
begin
  for i in 1 .. p_num_rows loop
    pipe row( i );
  end loop;
return; -- note return with no value!
end;
/

Function created.

drop table test;
create table test as select * from table(virtual_table(5));

select * from test;

COLUMN_VALUE
------------
           1
           2
           3
           4
           5

create or replace procedure del_test(n number) as
begin
  delete from test
    where column_value in (select * from table(virtual_table(n)));
end;
/

Procedure created.

exec del_test(3);

BEGIN del_test(3); END;

*
ERROR at line 1:
ORA-22905: cannot access rows from a non-nested table item
ORA-06512: at "SCOTT.DEL_TEST", line 3
ORA-06512: at line 1

          Followup:

ops$tkyte@ORA9IR2> create or replace procedure del_test(n number) as
  2  begin
  3    delete from test
  4      where column_value in (select * from table(cast( virtual_table(n) as
virtual_table_type) ));
  5  end;
  6  /

Procedure created.

ops$tkyte@ORA9IR2> exec del_test(1)

PL/SQL procedure successfully completed.

      Rating: 5
is CAST still
      needed in 9i?!  January 21, 2004
Reviewer:
      igor  from ME USA
Yes, it helps! I am just a little confused because earlier on this page you have
mentioned that CAST is not needed in 9i.

Thank you anyway!

January 21, 2004 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.

      Rating: 4
Suppress
      substitution?  January 21, 2004
Reviewer:
      Dennis  from Missouri, USA
Is there a way to suppress the substitution output?

So that instead of getting this in a spooled log:

old   9: where owner ='&&1.UTIL')
new   9: where owner ='FINUTIL')
create synonym FINutil.DEMO_TRUNCATE for FINDAILY.DEMO_TRUNCATE;

we could just get the sql statement and not have any old/new lines.

I am calling it by the following method:
@c:\quick_syn.sql FIN

I don't mind just running the spool and getting two nonsense errors, but it's
been asked to see if I could eliminate that.     I was thinking of using accept
and hide, but it wasn't working too well because it stops and wants to accept
something that is typed in.

Is there any way to accomplish this?

Thanks,
Dennis

          Followup:

set verify off

stops the substitution "showing" from happening.

      Rating:
      4
Thanks!  January 22, 2004
Reviewer:
      Dennis  from Missouri, USA
That was it.  Didn't know about the Verify set.


Listing

Recent Archives Advanced Search FAQ by Category Most Popular Hot Articles Your Questions [ Listing ] RSS Feed
Subject | Article | Last Updated (East coast time)
How to find out which process caused the maximum IO request to db server? | article only | Jan 22, 2004 06:29 am
How to get MIN and MAX of Consecutive Numbers | article only | Jan 22, 2004 06:32 am
versions | article only | Jan 22, 2004 07:36 am
Increasing Export and Import Speed? | article only | Jan 22, 2004 08:40 am
Query -- Need help to formulate | article only | Jan 22, 2004 07:34 pm
Exit From SQLPLUS | article only | Jan 22, 2004 07:56 pm
Parse, Execute And Fetch | article only | Jan 22, 2004 07:57 pm
Storing Encrypted Value in Varchar2 column | article only | Jan 22, 2004 07:59 pm
Intelligent Partitioning Table | article only | Jan 22, 2004 08:26 pm
ORA-00604 - what does it mean | article only | Jan 22, 2004 08:31 pm
implement INTERVAL functionality | article only | Jan 23, 2004 07:15 am
Prevention and Detection of database corruption | article only | Jan 23, 2004 07:27 am
Multilingual Database and UTF-8 | article only | Jan 23, 2004 07:55 am
Inserting data from comma delimited text file into a table. | article only | Jan 23, 2004 08:01 am
what is lobsegment,lobindex | article only | Jan 23, 2004 08:05 am
delete documents from operating system files | article only | Jan 23, 2004 08:08 am
copy command | article only | Jan 23, 2004 06:22 pm
Inactive sessions/Application slow | article only | Jan 23, 2004 06:29 pm
PCT FREE for Indexes | article only | Jan 23, 2004 06:31 pm
Concatenate desired columns for multiple rows based on key fields | article only | Jan 23, 2004 06:35 pm
intermedia text search | article only | Jan 23, 2004 06:36 pm
Referencing %rowtype variables without using column names | article only | Jan 23, 2004 06:37 pm
DBMS_JOB | article only | Jan 24, 2004 02:48 pm
dbverify - output | article only | Jan 24, 2004 05:12 pm
RMAN Image copies | article only | Jan 25, 2004 09:57 am
Invoker rights and Before Insert Trigger | article only | Jan 25, 2004 10:12 am
Rows into columns | article only | Jan 25, 2004 10:37 am
when we start a database there are two sizes fixed size and variable size what are real constituents of them | article only | Jan 25, 2004 06:05 pm
Performance Issue - high response time | article only | Jan 25, 2004 06:13 pm
Can using DBMS_STATS cause DB corruption or loss of data? | article only | Jan 25, 2004 06:25 pm
checkpoint | article only | Jan 26, 2004 06:21 am
chained rows | article only | Jan 26, 2004 09:05 am
Very large Database - question on max files/max filesize | article only | Jan 26, 2004 01:09 pm
Strings of Zero Length Not Equivalent To NULL | article only | Jan 26, 2004 03:35 pm
400 byte barrier create flat files | article only | Jan 26, 2004 04:48 pm
v$lock | article only | Jan 27, 2004 08:47 am
insert but discarding some unwanted/erroneous rows | article only | Jan 27, 2004 08:49 am
How do i prevent end users from connecting to the database other than my application? | article only | Jan 27, 2004 10:35 am
Querying history table | article only | Jan 27, 2004 10:55 am
Most Admirable Oracle 9i Database Feature | article only | Jan 27, 2004 01:14 pm
Table Difinition | article only | Jan 27, 2004 01:16 pm
Materialized Views & Bind variables | article only | Jan 27, 2004 01:47 pm
Shared Pool (from your book) | article only | Jan 28, 2004 08:10 am
Oracle9i: Formatting XML Tags | article only | Jan 28, 2004 08:26 am
Performance Investigation Approach when using STATSPACK | article only | Jan 28, 2004 08:45 am
why view's trigger disappear? | article only | Jan 28, 2004 08:45 am
Bulk collect with cursor variable | article only | Jan 28, 2004 08:49 am
Consistent Gets, Trace output | article only | Jan 28, 2004 08:53 am
Stripe and Mirror Everything, including redo logs? | article only | Jan 28, 2004 10:24 am
Data Compression during RMAN backup | article only | Jan 28, 2004 10:25 am
Installing Oracle 8i and Developer 2000 | article only | Jan 28, 2004 02:06 pm
Why am I getting "PLS-00201: identifier 'ROLLUP' must be declared" in 8.1.5 | article only | Jan 28, 2004 02:22 pm
J2EE and Oracle's stored procedures | article only | Jan 28, 2004 02:32 pm
Viewing fractions of a second in a date field. | article only | Jan 28, 2004 02:49 pm
row(s) 1 - 54 of 54
January 22, 2004 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.

Rating: 3
Your new book January 21, 2004
Reviewer: A reader from NJ, USA
Tom,

Are the info about STATSPACK available in your new book. If not where can I find
some very basic info about it. I am very new to using this tool. Please help

Thanks

Followup:
well, have you read the performance guide (online at otn.oracle.com)

i talk about it -- i do not go into gory detail on every wait, every statistic
(the reference guide does most of that -- metalink.oracle.com would have more
info)

It is what it is -- a fairly basic report.

January 22, 2004 -- 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

Rating: 5
Excellent January 22, 2004
Reviewer: A reader
Tom,
Are you by any chance a member of Mensa (High IQ people society).?

Followup:
nope.

January 22, 2004 -- Thanks for the question regarding "versions", version 8.1.7
You Asked
Tom

1.
Can you kindly give the approximate dates of release of different oracle
versions ( major ones) from 7.1 onwards.

As far as I know

10 g : Fall 2003
9i release 2: Early 2003
9i rel 1:End of 2001
8i(2000, 2001)for versions 8.1.5, 8.1.6, 8.1.7
8 - 1999

Please correct.

2.With which release was OEM i.e. oracle enterprise manager release. I remember
it not being part of the Oracle database software out of box, it used to be
separate installtion. Any idea about different versions of OEM untill now.

3.Is there any difference between PL/SQL table, Index by table, Associative
arrays ?

Thanks
and we said...
1)

9ir2 may 2002
9ir1 jun 2001
8ir3 aug 2000
8ir2 nov 1999
8ir1 feb 1999
806  jan 2000
805  jul 1998
804  dec 1997
803  jun 1997
734  oct 1997
733  mar 1997
732      1995
72   may 1995
716      1994
713  may 1994
7016     1992

2) OEM had various names -- you could think of sqldba -> svrmgr -> oem

oem was predominantly a version 8 thing

3) plsql tables are index by tables and a type of index by table is an
associative array (a table indexed by a string)
Review & Followup
Rating: 5
Excellent January 22, 2004
Reviewer: Ahmad Imran from pakistan
also be helpful for you
http://asktom.oracle.com/pls/ask/f?p=4950:61:3732094027051233697::::P61_ID:12213523892770

January 22, 2004 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://download-east.oracle.com/docs/cd/A81042_01/DOC/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

      Rating: 5
directio - good
      and bad  January 16, 2004
Reviewer:  Harrison
      Picot  from Haymarket "Yellow dog Powered, and Proud!" VA, USA

Tom, you say "As for raw, I found that most unix file systems mounted in
directio (or  equivalent thereof) perform about the same.", and in another place
recommend directio for logs. Solaris always reads and writes in 8k chunks which
can make direct small IO inefficient, which is why file systems cache in the
first place. For Oracle IO, Sun recommends:

http://www.sun.com/solutions/blueprints/browsesubject.html
"Solaris Volume Manager Performance With UFS File Systems
and Oracle" page 9 November 2003.

"Use an 8-Kbyte database block size in conjunction with an 8-Kbyte fragment size
for the file system. This block size ensures alignment of database blocks with
underlying storage. If blocks are not properly aligned or are too small, write
performance can suffer. Consider the example of a database using 2-Kbyte blocks
on a file system. The file system has a block size of 8 Kbytes, so 4 database
blocks will fit in one FS block. When the database issues a write on a 2-Kbyte
block, the file system must write all 8 Kbytes at once. This means that the
remaining 75% of the block will have to be read before it can be written. If you
use a 2-Kbyte database block size on file systems, you can prevent the
read-modify-write phenomenon by using the forcedirectio mount option to bypass
the file system on I/O operations."

"Caution   If you are currently using UFS file systems and are not mounting them
with the forcedirectio option, be careful to analyze your application before
enabling direct I/O. It is quite possible that some of the objects in your
database are benefiting from the UFS buffer cache. Turning on direct I/O can
increase the amount of physical I/O and reduce the transaction rate as a result
of bypassing the FS cache."

The message seems pretty clear, use an 8k database block size, or a multiple,
and forget directio for data going into the database, although logs and exports
might benefit.

Previous tests of raw volumes at Oracle showed that raw is faster for some
operations and ufs is faster for others.

It seems to me that tips like bind variables and 97% of the thousands of other
things that Tom has offered to make Oracle run faster, are worth more than
directio, unless you are bored and want to make changes to see how fast your
phone can start ringing;-)

The Sun Blueprint referenced above has a lot of good information about using
Sun's (free with the OS) volume management, which performs as well as Veritas,
at least in the tests they show. If you are working on Sun (and you are wasting
a lot of time if not), it is well worth reading.

One note for the unwary, for reasons of backwardness, Solaris newfs can make
file systems with 4k block size (in case you are tempted to make the OS match a
4k datablock size), but once you have formatted the drive, Solaris 7,8,9 can not
read it.

          Followup:

it is called benchmarking

and on my systems, under my load -- using directio eliminated all "log file sync
waits"

as with everything, evaluate it.

      Rating: 4
FAST
      EXP/IMP  January 22, 2004
Reviewer:  Prasad
       from UK
Hi Tom,

I've a scenario where I've to exp 3 BIG tables (around 1000 million in total -
non partitioned) from 1 DB & import it to another DB (Here the 3 tables are
partitioned).

What are the hints do i need to give to improve the performace of this Exp/Imp
process?

Thanks in advance,

          Followup:

transporting them (taking the datafiles) would be "the fastest"

exporting individual partitions in parallel and importing them in parallel would
be another (break the problem into many small problems)

      Rating: 3
Source not
      Partitioned  January 22, 2004
Reviewer:
      Prasad  from UK
Tom,

The source tables are not partitioned.
they are huge non partitioned tables
650 m, 250 m, 150 m each.

But the Dest tables will be partitioned.

how to go about it?
what are the hints/tips can i give for exp/imp ??

many thanks,
       Prasad

          Followup:

you can use the query= parameter with either primary key or rowid ranges to
split it up then.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:10498431232211
for a script to generate rowid ranges that splits a table in N non-overlapping
pieces that you could use.

or, just use a query= predicate that pulls out the data for "partition 1" and
then "partition 2" and so on using the definitions you will be using in the
partitioned table itself and run those in parallel.

January 22, 2004 -- Thanks for the question regarding "Query --
      Need help to formulate", version 901

      You Asked
I have 2 tables

A) graph

NodeId not null primary key number,
sal number,
ParentId null number

ParentId  references NodeId

B)Data

DataId number primary key
NodeId number

NodeId references Graph table's NodeId column

Graph table contains the hierarchy information.

I need to do the following :

Select the dataid from the data table& its associated nodeid from the graph
table along with the parentId of the nodeid i.e

Data
Dataid   Nodeid
100       333

Graph
NodeID   ParentID      SAL
222        111
333        555
555        666
666

My output should be :the dataid I pass in and the hierarchy of the nodeid it
belongs to

100        333
           555
           666

SELECT dataid ,  graph.nodeId   FROM data  RIGHT JOIN graph
  ON graph.NodeId=data.NodeId AND data.dataid IN  (333)
start with graph.nodeid in
(select graph.nodeid from graph join data on data.NodeId=graph.NodeId where
dataId in (333))
CONNECT BY graph.nodeid= prior data.nodeid

But this takes huge time as the in list increases ; is there a better way to do
this

Thanks

      and we said...
Your query doesn't appear to work at all.  as coded with 333 -- no data.  change
333 to 100 (which is a value for dataid whereas 333 is not) and it goes into an
infinite loop.

Does this result set work for you -- you'll get a single row with the entire
hierarchy in it:

ops$tkyte@ORA920.US.ORACLE.COM> select data.*,
  2        substr(
  3            (select max( to_char(level,'fm0000') ||
                                 sys_connect_by_path(nodeid,'/'))
  4               from graph
  5                  start with nodeid = data.nodeid
  6            connect by prior parentid = nodeid ), 6 ) hierarchy
  7   from data
  8  /

    DATAID     NODEID HIERARCHY
---------- ---------- ---------------
       100        333 333/555/666

ops$tkyte@ORA920.US.ORACLE.COM>

just add "where dataid in ( ..... )" to the outer query.

Review &
      Followup

      Rating:
      3
sys_connect_by_path  September 14, 2002

Reviewer:  A reader  from Bangalore
Tom,
 Can you please explain what the function, sys_connect_by_path does and its
purpose.

regards

          Followup:

it gives the path back up the tree -- it is the hierarchy back to the root.  By
keeping just the "max level" one, we get the entire hirearchy:

ops$tkyte@ORA920.US.ORACLE.COM> select lpad('*', 2*level, '*' ) || ename
empName,
  2         dname,
  3             job,
  4         sys_connect_by_path( ename, '/' ) cbp
  5    from scott.emp emp, scott.dept dept
  6   where emp.deptno = dept.deptno
  7   start with mgr is null
  8   connect by prior empno = mgr
  9   order siblings by dname
 10  /

EMPNAME              DNAME          JOB       CBP
-------------------- -------------- --------- -------------------------
**KING               ACCOUNTING     PRESIDENT /KING
****CLARK            ACCOUNTING     MANAGER   /KING/CLARK
******MILLER         ACCOUNTING     CLERK     /KING/CLARK/MILLER
****JONES            RESEARCH       MANAGER   /KING/JONES
******SCOTT          RESEARCH       ANALYST   /KING/JONES/SCOTT
********ADAMS        RESEARCH       CLERK     /KING/JONES/SCOTT/ADAMS
******FORD           RESEARCH       ANALYST   /KING/JONES/FORD
********SMITH        RESEARCH       CLERK     /KING/JONES/FORD/SMITH
****BLAKE            SALES          MANAGER   /KING/BLAKE
******ALLEN          SALES          SALESMAN  /KING/BLAKE/ALLEN
******WARD           SALES          SALESMAN  /KING/BLAKE/WARD
******JAMES          SALES          CLERK     /KING/BLAKE/JAMES
******TURNER         SALES          SALESMAN  /KING/BLAKE/TURNER
******MARTIN         SALES          SALESMAN  /KING/BLAKE/MARTIN

14 rows selected.

See, that tells us:

********ADAMS        RESEARCH       CLERK     /KING/JONES/SCOTT/ADAMS

to get to adams, we started at KING, to JONES, to SCOTT to ADAMS....

      Rating:
      4
  September 15, 2002
Reviewer:
      A reader
Tom
Thanks a lot
Please explain the usage of substr, max, level in the stmt.Why do we need it?

substr((select max( to_char(level,'fm0000') ||

          Followup:

I wanted to keep the max level only -- it has the entire hierarchy.  All of the
other levels in there (1, 2, 3, .. n-1 where n = max level) have the partial
hierarchy.

So, I just made the level a fixed with field that "sorts" nicely with to_char()
-- added the entire hirearchy after it.  We keep the "max" one (level = 3 in
this example) and then for display purposes -- we strip it off (we aren't
interested in the level, just the hierarchy for the max level)

      Rating:
      4
Query  January 22, 2004
Reviewer:
      Ratan  from IND
Hi Tom,
I am not sure that this is the correct palce to put this question. But I need
your help to solve my problem.
I Have a table with 6 columns fixed always.
SQL> desc tb_eip_dummy
 Name                          Null?    Type
 ---------------- -------- --------------------
 ST1                                VARCHAR2(10)
 ST2                                VARCHAR2(10)
 ST3                                VARCHAR2(10)
 ST4                                VARCHAR2(10)
 ST5                                VARCHAR2(10)
 ST6                                VARCHAR2(10)

Data In table:
ST1    ST2    ST3    ST4    ST5    ST6
SF1    SF12    SF13    SF14    SF15    SF16
SF1    SF12
SF1    SF12    SF13
SF1    SF12    SF13    SF14
SF1    SF12    SF13    SF14    SF15
SF1
SF2    SF21    SF22    SF23    SF24    SF25
SF2
SF3
I want to assign the levels and fill null values with 'ALL'
as follows:
ST1    ST2    ST3    ST4    ST5    ST6    LEVEL_NUM
SF1    SF12    SF13    SF14    SF15    SF16    6
SF1    SF12    ALL    ALL    ALL    ALL    2
SF1    SF12    SF13    ALL    ALL    ALL    3
SF1    SF12    SF13    SF14    ALL    ALL    4
SF1    SF12    SF13    SF14    SF15        5
SF1    ALL    ALL    ALL    ALL    ALL    1
SF2    SF21    SF22    SF23    SF24    SF25    6
SF2    ALL    ALL    ALL    ALL    ALL    1
SF3    ALL    ALL    ALL    ALL    ALL    1
Mean to say in which column the row will end level should be the number of that
column and remaining column values in that row should display with 'ALL'.
Please help me to write query.
Thanks in advace.
Ratan

          Followup:

nvl(column_name, 'ALL')

will get the all.......

case when st1 is null then 0
     when st2 is null then 1
     when ........
end

can get your 1, 2, 3, 4, ....

January 22, 2004 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.

      Rating: 4
how about stop
      instead of exit  January 22, 2004
Reviewer:
      James Blanding  from Kingston, RI USA
is there any way to get SQL*Plus to stop processing a script *without* exiting?
i admit my motivation is primarily laziness here (though it is rather annoying
to have to restart sql*plus in Windows (of course having to run sql*plus in
Windows is already annoying))...

          Followup:

not that I'm aware of, or could find.  no

      Rating:
      4
duh  January 23, 2004
Reviewer:  James
      Blanding  from Kingston, RI USA
I happened to reach chapter 2 of "Effective Oracle by Design" last night, and on
the second page, it mentions that there are two versions of SQL*Plus for Windows
platforms.  duh!  I've switched to the command line version.  This effectively
removes the problem of the window closing before I can read the error message
(and of having to perform several actions to startup a new SQL*Plus session).
Also, the command shell in Win2k (cmd.exe, NOT command.com) natively does
blockwise selection of text with the mouse, which I had previously thought to be
sqlplusw's biggest redeaming feature.  Happy day.

January 22, 2004 Nutan -- Thanks for the question regarding
      "Parse, Execute And Fetch", version 8.1.7

      You Asked
Hi Tom,
    We have two batch program in PRO*C for generating phone bills and printing
file generation of the bill, each batch prog. is taking aroung 6hrs to finish.
I have traced these session, the part of which I'm pasting below:

First Prog:
select CALNG_TEL_NO ,TRUNK_CTGRY ,sum(VALUE) ,min(CALNG_DT) ,max(CALNG_DT)  from
T_TRUNKTCKT
where ((SUBS_NO=ROUND(:b0) and CALNG_DT<=TO_DATE(:b1,'dd-mon-rr'))
   and BILL_DT is null )
group by CALNG_TEL_NO,TRUNK_CTGRY       OK

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse      235      0.00       0.00          0          0          0           0
Execute    235      0.00       0.00          0          0          0           0
Fetch      243      0.00       0.00        186        741          0           8
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      713      0.00       0.00        186        741          0           8

Second Prog:

select NVL(CMR,0) into :b0  from TOMRCMR
where (((bill_no=:b1 and bill_dt=TO_DATE(:b2,'dd/mm/yyyy'))
and   subs_no=:b3) and CMR_DT=to_date(:b4,'dd/mm/yyyy'))

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute    293      0.00       0.00          0          0          0           0
Fetch      293      0.00       0.00          0    1288321          0         293
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      587      0.00       0.00          0    1288321          0         293

What I observed here in the first trace parce, execute and fetch are having
equal values where as in the second part Parse is happen only once which is good
I suppose.  Please tell me how can I redice Parcing in Pro*C.

Thanks in advance.

      and we said...
you need to have TIMED_STATISTICS=true for sql-trace to be useful.

It could be that a query executed ONCE is taking 99.99% of the runtime and you'd
NEVER EVEN KNOW IT.

without timed stats, sql_trace is pretty much "useless"

if you are using static sql, use

hold_cursor=yes release_cursor=no maxopencursors=NN prefetch=50

where maxopencursors is something you have to figure out.  please (please please
please) read the pro*c docs so you can understand what that'll do for you!

if you are using dynamic sql -- stop closing your cursors, just parse them once,
and then bind/execute bind/execute over and over....

Also - in looking at that last query, you are using about 5,000 logical IO's per
row.  that is HUGE, lots of work to get "a row" from "a table" don't you think?

Seems to me -- that should take at most 4 lio's maybe 5....  you need to look at
the indexing
Review & Followup

      Rating: 4
I Got The
      Start  January 20, 2004
Reviewer:  Nutan
       from India
Hi Tom,
      I got the direction about how to tune the precompiler
prog. Thanks for the quick answer. I will study and implement the parameter in
my code and let you know about the batch program performance.

Thanks.

      Rating: 4
What About
      Forms?  January 22, 2004
Reviewer:  Ashwin
       from India
Hi Tom,
      I have implemented this parameters you have suggested.
(ie. hold_cursor=yes release_cursor=no prefetch=50)

 Is there any such setting in Forms to avoid hard parsing?

Thanks.

          Followup:

forms does this automagically.  just "happens"

(actually, the DB is the "hard parse avoider" -- the shared pool does that.
forms is a soft parse avoider -- caching cursors)

      Rating:
      5
  January 22, 2004
Reviewer:  Reader
       from Toronto
Hi, Tom
Is it possible that 'dd-mon-rr' mask is responsible for high parsing rate?

          Followup:

no

January 22, 2004 Saminathan -- Thanks for the question regarding
      "Storing Encrypted Value in Varchar2 column", version 8.1.7

      You Asked
Dear Tom,

After reading many threads in this forum, I understood that it is not a good
thing store encrypted value in varchar2 datatype.
For some reason our existing application is storing DESEncrypted value in
varchar2 column. We defined 2x size for varchar2 column so length is not an
issue.

Questions

1)Is there any performance degradation if we store encrypted value in VARCHAR2
instead of RAW?

2) How to convert encrypted value which is stored in varchar2 into RAW datatype?

      and we said...
1) not, it is just that if you select the varchar2 out and return it to a client
-- it might get "changed" since character set conversion would kick in.  That
would destroy the string.  You would have to perform all encrypt/decrypt in the
database. using exp/imp to move the data could likewise "destroy" the data.  It
is just "unsafe"

2) you would have to change the encrypt/decrypt procedures you have and add a
new column to the table that was raw and use UTL_RAW.CAST_TO_RAW() to convert
the string to be encrypted to RAW and UTL_RAW.CAST_TO_VARCHAR2() to convert the
decrypted raw string back into a varchar2.
Review &
      Followup

      Rating: 5
Question About
      RAW & MD5 Hash  January 22, 2004
Reviewer:
      Sami  from NJ,USA
1.When we say RAW, is it a Hexadecimal number all the time?

2) My java application expects to return HASH value(which we are going to store
it in db) as STRING. So is it okay to store HASH value in VARCHAR2 datatype
column?

I use below method for MD5 hash.
SQL> CREATE OR REPLACE FUNCTION      MD5_RAW (INPUT_VAL VARCHAR2)
  2  RETURN VARCHAR2
  3  IS
  4  SOMETHING_RAW  RAW(100) := UTL_RAW.CAST_TO_RAW(INPUT_VAL);
  5  BEGIN
  6  RETURN DBMS_OBFUSCATION_TOOLKIT.MD5( INPUT => SOMETHING_RAW );
  7  END;
  8  /

          Followup:

1) no, it is binary data.

it gets converted to hex for the pleasure of us human beings -- we can read hex.
binary just makes our terminals go nuts.

2) that'll return a HEX representation -- it is a varchar2 -- not a raw, so it
is 100% OK to save in varchar2, yes.

      Rating: 5
Absolutely
      outclass  March 18, 2004
Reviewer:  hash
       from pakistan
Tom, this expalanation  is second to none. thanks a lot

January 22, 2004 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

      Rating: 4
Range + hash
      with common prefix on hashed string  January 22, 2004

Reviewer:  Mike  from St. Louis, MO
Based on above discussion, we have data with a great profile for V9.2 range
partitioning by date + hash subpartitioning on a unique string identifier.  We
have hundreds of unique string ids so it would seem the range+hash partitioning
would distribute the data nicely into, say, 200 or so subpartitions for each
month.

Note: our string identifiers are unique but all have a common suffix.  Example:

       SPQID
  ----------
  usabakerj
  usamastersj
  canporterp
  canllensl

Please don't scold me for IDs with embedded country code.  Rather, I'm
interested to know if you think the hash distribution of strings like this will
distribute somewhat evenly of if the hash algorithm will steer all the values
into just a few sub-partitions (e.g. usa% and can%)?

I had a crazy thought to reverse the key:

  partition by range ( spq_date )
  subpartition by hash( reverse( spqid ) )

but adding the reverse() function leads to parse errors during table creation.

Thanks for any comments you may have,

-Mike

          Followup:

ops$tkyte@ORA10G> CREATE TABLE t
  2  (
  3    data       varchar2(80)
  4  )
  5  PARTITION BY hash (data) partitions 4
  6  /

Table created.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> insert into t
  2  select 'usa' || object_name from all_objects;

47079 rows created.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> 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          ( 'partition ' || x.partition_name || ' ' || l_cnt );
 14      end loop;
 15  end;
 16  /
partition SYS_P185 11856
partition SYS_P186 12062
partition SYS_P187 11664
partition SYS_P188 11497

PL/SQL procedure successfully completed.

ops$tkyte@ORA10G>

it'll spread it out

      Rating: 5
Thank
      you!  January 24, 2004
Reviewer:  Mike
       from St. Louis, MO
Great example and test.  Thank you very much and I will charge ahead with
partitioning our data.

January 22, 2004 Piotr -- Thanks for the question regarding
      "ORA-00604 - what does it mean", version 9.0.1.0.0

      You Asked
Hi,

I have the following problem:

SQL> select * from t;
select * from t
              *
ERROR at line 1:
ORA-00604: error occurred at recursive SQL level 1
ORA-04092: cannot COMMIT in a trigger
ORA-06512: at line 12
ORA-00942: table or view does not exist

And table `t' does not exist.
But this happens no matter what user I am, no matter what I do but only if there
are errors (assuming that it would normally give only the last error).

I was looking for the trigger that might do commit but there is no single one in
any schemas in our database.

What does this ORA-00604 mean?
Does it have something to do with recursive SQL queries?
Is this some kind of internal problem?
How can I get rid of it?

Thanx in advance,
Piotr

      and we said...
someone created an "after servererror" trigger and is committing in it.
consider:

ops$tkyte@ORA9I.WORLD> drop user a cascade;

User dropped.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> grant create session to a identified by a;

Grant succeeded.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> @connect a/a
ops$tkyte@ORA9I.WORLD> set termout off
a@ORA9I.WORLD> REM GET afiedt.buf NOLIST
a@ORA9I.WORLD> set termout on
a@ORA9I.WORLD> select * from t;
select * from t
              *
ERROR at line 1:
ORA-00942: table or view does not exist

a@ORA9I.WORLD>
a@ORA9I.WORLD> @connect /
a@ORA9I.WORLD> set termout off
ops$tkyte@ORA9I.WORLD> REM GET afiedt.buf NOLIST
ops$tkyte@ORA9I.WORLD> set termout on
ops$tkyte@ORA9I.WORLD> drop table log;

Table dropped.

ops$tkyte@ORA9I.WORLD> create table log( msg varchar2(255) );

Table created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> create or replace trigger error_trigger
  2  after servererror on database
  3  begin
  4     insert into log values ( 'hi' );
  5     commit;
  6  end;
  7  /

Trigger created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> @connect a/a
ops$tkyte@ORA9I.WORLD> set termout off
a@ORA9I.WORLD> REM GET afiedt.buf NOLIST
a@ORA9I.WORLD> set termout on
a@ORA9I.WORLD> select * from t;
select * from t
              *
ERROR at line 1:
ORA-00604: error occurred at recursive SQL level 1
ORA-04092: cannot COMMIT in a trigger
ORA-06512: at line 3
ORA-00942: table or view does not exist

a@ORA9I.WORLD>
a@ORA9I.WORLD> @connect /
a@ORA9I.WORLD> set termout off
ops$tkyte@ORA9I.WORLD> REM GET afiedt.buf NOLIST
ops$tkyte@ORA9I.WORLD> set termout on
ops$tkyte@ORA9I.WORLD> select owner, trigger_name
  2    from dba_triggers
  3   where trigger_type = 'AFTER EVENT';

OWNER                          TRIGGER_NAME
------------------------------ ------------------------------
SYS                            AURORA$SERVER$STARTUP
OPS$TKYTE                      ERROR_TRIGGER

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> drop trigger error_trigger;

Trigger dropped.

Use that last query to find the offending trigger and either make it an
autonomous transaction so it can commit or remove the commit.
Review &
      Followup

      Rating: 4
It just takes
      to know right things  May 24, 2002
Reviewer:
      Piotr Jarmuz  from Braunschweig, Germany
Now all is clear. I understand the flow of locus of control. Actually I knew
about system level triggers but not about `after servererror' so actually I
could not associate it with this message. I used an autonomous transaction and
now it all works well. And I also traced down the culprit who or rather what set
this trigger up.

Thank you again.

      Rating: 3
can commit
      cause problem in Logoff trigger  July 09, 2002

Reviewer:  Dilip  from NY
In before logoff trigger, I am updating flag like this:

..before logoff on database..

If <condition> THEN
      Update table
         set col = releaseflag ;
==>   COMMIT ;
End if ;
..
Can Commit in this trigger cause the database to shutdown?
Thanks!
DilipM

          Followup:

it should not.

      Rating: 5
What about
      Forms 6i  January 03, 2003
Reviewer:  Luis
      Soto  from Dominican Republic
In forms 6i every time I shutdown and restart Personal Oracle 8.1.5 in Win98Se
Forms give me this error when I try to commit my chages. I tried it in SQL*Plus
and it Works well.  If I compile the form again it runs well.  What drive me
crazy is that it happen just with one form the others works well.

Any Help Will be really apreciate

Luis Soto

      Rating: 5
superb answer
      for after servererror  May 07, 2003
Reviewer:
      jignesh  from india
u r simply gr8

      Rating: 5
ORA-00604 while
      compiling a procedure  January 22, 2004
Reviewer:
      Mariano  from Córdoba, Argentina
Hi Tom,
while trying to compile a procedure it's raising this error:
ORA-00604: error occurred al recursive sql level 1
ORA-01653: unable to extend table SYS.IDL_UB2$ by 25 in tablespace SYSTEM.
I cannot query this table/view.
how can i now if something in my code is causing the recursion (I mean a guide
in this matter as i'm aware that without taking a look in the code it's
imposible to know for sure) or it's necesary to ask my DBA to check tablespace
system in order verify extends?
Just to mention: i'm not calling the procedure inside its own code but i'm
calling another procedure with the same error.

As usual, thanks in advance.

          Followup:

contact your dba.

system is full, idb_ub2$ is a "byte code" table.  dba needs to add space

January 23, 2004 Vipin -- Thanks for the question regarding
      "implement INTERVAL functionality", version 9i

      You Asked
Hi Tom,

I have a question related to implementing functionality similar to Oracle's
method of handling JOBS.

We have a requirement in which we need to derive due dates based on a START DATE
and INTERVAL.

I wanted to give all the facility which Oracle gives, as INTERVAL field value.
So the model which I would suggest would include all types of Oracle functions
in INTERVAL fields like

If the user chooses:-

1. Every day                 - START_WITH +1
2. Every month               - ADD_MONTHS(START_WITH, 1)
3. Every year                - ADD_MONTHS(START_WITH, 12)
4. Starting of next quarter -  ADD_MONTHS(TRUNC(START_WITH, 'Q'),3)

Suppose my due date table is having the following structure:-

create table due_date_table
(
frequency varchar2(20),
start_with date,
next_date varchar2(255),
due_date date
)

Now my procedure which will derive the DUE DATE will be as follows:-

CREATE OR REPLACE PROCEDURE update_due_date
AS
v_var1 dbms_sql.varchar2s;
v_var2 dbms_sql.varchar2s;
BEGIN
SELECT next_date,
       frequency
BULK
COLLECT
INTO   v_var1,
       v_var2
FROM   Due_date_table;

FOR i IN 1..v_var1.COUNT loop
  EXECUTE IMMEDIATE ('Update Due_date_table set due_date = '||v_var1(i)||' where
frequency = '||v_var2(i));
END LOOP;
END;

Now here I know that I am violating the famous Tom's rule by doing a hard
parsing. Believe me I want to implement this with FORALL...EXECUTE
IMMEDIATE...USING, but i don't know how to because we need to SET the due_date
value USING the value in the NEXT_DATE field not AS THE VALUE in the NEXT_DATE
field.

BTW please tell me is there any better approach to give this flexibility. No
probs, if you go sarcastic because finally the answer would anyway benefit ;)

      and we said...
well, the forall would be:

forall i in 1 .. v_var1.count
   update due_date_table set due_date = v_var1(i) where frequency = v_var2(i);

don't know why you were trying to drop into dynamic sql at all there....

but actually -- in looking at the logic -- that would just update the due_date
to some random date based on the frequency (eg: if 50 rows had a frequency of
"2", all 50 would end up with the same due date derived from SOME row)... but
anyway.

why not just use the INTERVAL datatype?

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96590/adfnstyp.htm#417949

Review &
      Followup

      Rating:
      4
  January 20, 2004
Reviewer:  Vipin
       from Chennai
Oops, Tom I think I put it wrongly. The sample data in this table would be
something like this :-

frequency    start_with    next_date                                   due_date
--------------------------------------------------------------------------------
DAILY        01/20/2004    START_WITH +1
MONTHLY      01/12/2004    ADD_MONTHS(START_WITH, 1)
YEARLY       01/17/2004    ADD_MONTHS(START_WITH, 12)
QUARTERLY    01/25/2004    ADD_MONTHS(trunc(START_WITH, 'Q'),3)

Now every time the due date is reached (in a daily batch which checks whether
SYSDATE is equal to the due date and if so do the calculations for the
corresponding frequency)first we need to update the start_with field to the
current due_date value, then compute the due_date value with the formulae in the
next_date field. This calls for dynamic SQL since I need to update the due_date
with the result of the formulae stored in the next_date not with the value
stored in the next_date.

Please tell me whether I have made myself clear.

          Followup:

yes, you would have to use dynamic sql but there would be a finite number of
them possible.

      Rating:
      3
  January 22, 2004
Reviewer:  Vipin

Hi Tom,

"but there would be a finite number of them possible."

The above statement was not clear, could you please explain it, and also would
you recommend this approach for hadling this problem.

          Followup:

you would use dynamic sql

but you would be generating a small, finite set of unique sql's

so -- it is OK

January 23, 2004 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.

      Rating:
      5
  January 23, 2004
Reviewer:
      A reader
Tom,

I am in the process of testing backup and recovery using rman, i vaguely
remember you mentioning, its difficult to intentionaly corrupt a data block.

Can you please give some updation on that one as I wanna do testing of block
level recovery.

Thanks.

          Followup:

you can write a program to corrupt blocks.  you would just open an oracle
datafile, seek somewhere in the file, write junk and exit.

I'm not posting such a program -- but it is only a couple of lines of code.

      Rating: 4
On Windows
      platform simply use notepad to corrupt database
      files  January 26, 2004
Reviewer:  Lars Bo
      Vanting  from Ruemmingen, Baden-Würtemberg, germany
Hi!

On Windows platforms I simply use the NOTEPAD.EXE to open e.g. a tablespace
datafile, modify the contents and save it back. This corrupts your db file
(careful)!

Tom, I saw you at the Miracle master Class in Copenhagen - simply fabulous how
you share your knowledge with other!

Cheers
Lars Bo

January 23, 2004 Venkatesh -- Thanks for the question regarding
      "Multilingual Database and UTF-8", version 8.1.6

      You Asked
Greetings Tom !!
       I am always here when I need to get that 100% right answer and so here is
my question .
The existing Database (WE8ISO8859P1 Char Set) needs to cater to English
,Chinese,Japanese and Korean as well .
Before I opt to have a new UTF-8 instance , I need to clarify on a few points.

1. Can the data being pushed from the Browser (Lets say Korean data) be encoded
in UTF-8 using the encoding mechanisms available in Java , and stored in the
WE8ISO8859P1 Database ,is so then does it mean I have UTF-8 format data inside ?

Maybe it sounds absolutely inane , but it just occured to a few of my colleagues
(Java Guys), that this was a possibility .

2. The Java guys also claim to have seen a system using a WE8ISO8859P1 database
to store Chinese Data , the system would perfectly store , retrieve and display
data in Chinese ?
Does this prove a point or am I missing out on something very significant ,like
my basics on NLS support ?

Albeit, this issue has totally obfuscated  me , I still feel very strongly  that
having a UTF-8 database is the perfect,long term solution .

Also if I were to export WE8ISO8859P1 db data to UTF8 db , then do I have to set
the NLS_LANG=UTF8 prior to exporting the database(as opposed to the original Db
char), so as to avoid the IMP-00016 error during import ? Is it the right way ?

Please let me know your thoughts on the same .

Thanx and Regards
Venkatesh

      and we said...
1) Doesn't sound right, think about it.

WE8ISO is a single byte character set.  It has 255 characters.

Korean data requires a multi-byte character set -- each character could be 1, 2,
3 or more bytes.  It is a variable length encoding scheme.  It has more then,
way more then 255 characters.  I don't see it fitting into we8iso unless they
use RAW in which case it is just bytes, not characters at all.

2) tell them to show you this.  They should be able to set up a test.  However,
just to mess with them -- make sure to test it where the client has a different
character set then the database.

I can say this "There is a chance that if you use we8iso as the client character
set and we8iso as the database characterset -- it could possibly work since NO
character set translation would happen.  However, with Java -- i seriously doubt
it as Java is already NOT using we8iso, it has a different character set"

UTF8 would be the correct and proper approach.

Trying to fit the square peg into a round hole (abusing we8iso character set in
this case) would lead to long term "pain" and "suffering" -- it'll bite you in
the butt someday for sure.
Review & Followup

      Rating: 4
Multilingual
      data   October 07, 2002
Reviewer:  Mark
       from Singapore
Hi Tom ,
Does that mean I would still be able to store Chinese characters in a WE8ISO Db
,provided the Client and Db Char sets are the same ? Thru the java program if I
managed to encode the data in WE8ISO scheme , according to you , it should work
? Please correct me if I am wrong .

          Followup:

Not at all reliably -- no.  The data would at some point appear to corrupt
itself via some maintenance process due to character set conversion.

Chinese = more then one byte/ character
We8iso = one byte/ character

Impedence mismatch, you lose -- data corrupt.

don't do it unless you want lots of heartburn down the road.

      Rating:
      5
  December 18, 2002
Reviewer:  Sasa
       from Belgrade, Yugoslavia
Hi Tom,

We migrated our DB 8.1.7 to 9.2.In 8.1.7 we used UTF8 character set.It remains
same in 9.2.
We know that Oracle 9.2 doesn't have UTF8 but AL32UTF8.
Can we keep this UTF8 or have to change to AL32UTF8.
If we need to change, may we do it by :
alter database character set AL32UTF8
or
we must use exp/imp utility?

Regards

          Followup:

what do you mean -- utf8 is still a valid character set?

      Rating: 2
Migrating to
      9.2  December 18, 2002
Reviewer:  Sasa
       from Belgrade, Yugoslavia
After migration to 9.2 when we run this query:
select * from nls_database_parameters where parameter like '%CHARACTERSET'
we got :
Parameter               Value
------------------      ------
NLS_CHARACTERSET    UTF8
NLS_NCHAR_CHARACTERSET    UTF8

We are still confused with this values.
Any explanation?

          Followup:

why are you confused?  what are you confused about?

      Rating: 1
UTF8 in Oracle
      9.2  December 19, 2002
Reviewer:  Sasa
       from Belgrade, Yugoslavia
Hi tom,

We are confused because during creation of new db under Oracle 9.2 we have had
no opportunity to choose UTF8 as character set, only AL32UTF8 and AL16UTF16.

Thanks

          Followup:

it is in my drop down list when I use DBCA.

I just went to the "character sets" tab, selected "choose from the list" and it
is there.

Maybe you were just looking at the "use unicode" radio selection -- look down
one more item.

      Rating: 4
Multilingual
      Database with 8i client and 9i server  February 22, 2003

Reviewer:  Marie
Hi Tom,

Currently I have to work with oracle9.2.0.2.1 server as a database server, which
uses UTF8 characterset.
At the same time I have to use oracle8.1.7 client since some perl modules only
work with oracle8 client.
But I found out that I could not use oracle8.1.7 client to insert diacritics
value, the data value
is changed after inserting into the database. If I use oracle9i client, I can
insert and select then get the same value.

For example:
==============================================================
Using oracle9i client,

 SQL> create table t (tm varchar2(100));

 Table created.

 SQL> insert into t (tm) values ('êë');

 SQL> select tm from t;

 TM
 ------------------------------------------------------------
 êë

 1 row selected.

Elapsed: 00:00:00.02
=============================================================
If I use oracle8i client, I get the following:

 SQL> insert into t (tm) values ('êë');

 SQL> select tm from t;

 TM
 ------------------------------------------------------------
 ++

 ======================================================

 So is there a way I can get diacritics value work CORRECTLY in the environment
of
 Oracle8i client and Oracle9i server ?

 Thanks,

          Followup:

just set your NLS_LANG correctly -- you will find that your NLS_LANG is set
differently in 8i then in 9i.   This CLIENT setting dictates to the database
what character set it should expect from the client and the characters get
converted appropriately.

      Rating:
      5
UTF-8  February 24, 2003
Reviewer:
      A reader
Please tell us which one has to be used - either utf-8 or
some other - explicity please...

          Followup:

impossible to answer.

This is like asking "what font must be used" -- well, the one that works for you
must be used and that well could be different then the on I use.

I use we8iso, why?  I do not store multi-byte data.  would that work for you?
maybe, maybe not.

analyze your needs, read the globalization guide and decide WHICH is best for
YOU given YOUR circumstances.

      Rating: 4
Char sets can
      be brutal  February 24, 2003
Reviewer:
      A reader
If the client and db char sets are the same- you can stuff anything in, and if
the tool expects a certain char set, it'll come out "looking fine". While it may
seem dandy at first- the long term prospects are horrendous.

I worked on a migration project that turned out to be "un-migratable" because of
it. Tool one created accounts in batch, tool two via a nifty little Java program
for the CSRs. As long as you looked at the Java tool accounts with the Java tool
and the others with the older tool, all was well. These clowns built a high-end
OLTP system that was stuffing data in both Latin-1 and UTF-8.

Once you mix char sets, you can't separate things out later. Take a look at what
Oracle thinks is in the table using the SQL Dump function and you'll see that as
soon as you tell Oracle that data is Latin-1 and you want it to be UTF-8, there
will be characters that Oracle won't be able to understand. You then get to
decide which accounts aren't really important. But actually, it's even better-
there is of course overlap in the char sets, so Oracle looks at some of the data
and says, yup I can convert that, but it's actually from the other char set and
results in gibberish.

If you go this path, take the fast track and make sure the Java guys don't use
bind variables, you'll be able to redesign the system in less time that way.

There is a character set scanner tool shipped with the database, you can

      Rating:
      5
  April 23, 2003
Reviewer:
      A reader
Hi Tom,

 We are migrating from oracle 8.1.6 to oracle 9 R2. We have about 14 oracle
instance. All instances have WE8ISO88591P1
character set. Our company is expanding globally so we are thinking to use
unicode character set with oracle 9.
I have few questions on this issue.

1) What is the difference between UTF-8,UTF-16
  Is AL32UTF8 and UTF-8 is same character set or they are different?
  Is UTF-16 and AL16UTF16 is same character set or different ?

2) Which character is super set of all character set?
   If there is any, Does oracle support that character set?

3) Do we have to change our pl/sql procedure if we move to unicode database ?
The reason for this question is our developer is using ascii character for
carrage return and line feed like chr(10) and chr(13) and some other ascii
character .

4) What is impact on CLOB ?

5) What will be the size of the database? Our production DB size is currently
50GB. What it would be in unicode?

Thanks

          Followup:

you want to read

http://download-west.oracle.com/docs/cd/B10501_01/nav/docindex.htm#index-GLO
from start to finish.

see

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96529/glossary.htm#432012
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96529/glossary.htm#433170
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96529/glossary.htm#432169
basically utf8 is unicode 3.0 support, utf16 is unicode 3.1

there is no super super "top" set.

Your plsql routines may will have to change -- your data model may well have to
change.

You'll find that in utf, european characters (except ascii -- 7bit data) all
take 2 bytes.  That varchar2(80) you have in your database?  It might only hold
40 characters of eurpean data (or even less of other kinds of data).  It is 80
bytes (you can use the new 9i syntax varchar2( N char ) -- it'll allocate in
characters, not bytes).

So, you could find your 80 character description field cannot hold 80
characters.

You might find that x := a || b; fails -- with string to long in your plsql code
due to the increased size.

You might find that your string intensive routines run slower (substr(x,1,80) is
no longer byte 1 .. byte 80 -- Oracle has to look through the string to find
where characters start and stop -- it is more complex)

chr(10) and chr(13) should work find, they are simple ASCII.

On clob -- same impact as on varchar2, same issues.

Your database could balloon to 200gb, but it will be somewhere between 50 and
200.  As unicode is a VARYING WIDTH encoding scheme, it is impossible to be
precise -- it is not a fixed width scheme, so we don't know how big your strings
will get to be.

      Rating: 4
japanese
      character how to view it on sqlplus   April 23, 2003

Reviewer:  reader
tom,
i have created a database with utf8 character set.

UTF8WL>select * from nls_database_parameters where parameter like
'%CHARACTERSET';

PARAMETER                      VALUE
==============================
=========================================================
NLS_CHARACTERSET               UTF8
NLS_NCHAR_CHARACTERSET         UTF8

i created a table temp, i want to insert a japanese character and view it on
sqlplus - please let me how to do it.

i can take a japense character from my application,but when i copy to sqlplus to
insert it goes as ????. dont know why!

please through some light on this

Thanks

          Followup:

I don't know -- I cannot type japanese on my keyboard, I don't have support for
it fontwise, my telnet window doesn't do japanese.

you probably have the same problem -- your terminal emulator, set of windows
fonts, NLS_LANG -- whatever is wrong.

      Rating:
      4
Globalization  April 28, 2003
Reviewer:
      A Reader  from Corvallis, OR
I read the subject oracle doc online. However, I would like to clarify some
ponits with you.

(1) If my database char set is UTF8, does it mean that I can store chars longer
than a byte (ex chinese or japanese chars) in CHAR or VARCHAR2 datatypes even
though I have not specified ny National Char set when I created my database.

(2) Only if my client system or application is set in a character set (for
example UTF8) the clients can input foreign chars such as Chinese to store in
the database.

(3) Oracle does character set conversion NOT data conversion. My understanding
is that using globaliztion we can store chars in many languages in the database
if the database character set supports.

(4) Do I have to explicitly set National char set while creating database to use
NCHAR or NVARCHAR datatypes in my tables?

I would greatly appreciate if you could please clarify these points. Thanks so
much for your time. You are the best.

          Followup:

1) yes -- the nchar stuff just lets you have two character sets at the same
time.

UTF8 is a character set that permits you to store multi-byte data.

2) statement of fact, not a question.  the client needs to use a character set
setting that is appropriate for the data they are inserting.  They need not be
using utf8 -- we will convert using the rules ISO tells us to use.  They (the
chinese) would not use US7ASCII obviously -- but they could use a big8 or some
other relevant character set.

3) yes.

4) no.

      Rating:
      5
Thanks  April 29, 2003
Reviewer:
      A reader

      Rating: 5
UTF8 vs
      UTF-8  July 01, 2003
Reviewer:
      A reader  from Los Angeles, CA
I thought that I was beginning to get a handle on this multi-byte stuff but now
I'm confused.  (Sometimes the more you read, the more you realize how little you
know!) The 9i Global. support guide states:

UTF8
The UTF8 Oracle character set encodes characters in one, two, or three bytes. It
is for ASCII-based platforms. The UTF8 character set supports Unicode 3.0.
Although specific supplementary characters were not assigned code points in
Unicode until version 3.1, the code point range was allocated for supplementary
characters in Unicode 3.0. Supplementary characters are treated as two separate,
user-defined characters that occupy 6 bytes.

UTF-8
The 8-bit encoding of Unicode. It is a variable-width encoding. One Unicode
character can be 1 byte, 2 bytes, 3 bytes, or 4 bytes in UTF-8 encoding.
Characters from the European scripts are represented in either 1 or 2 bytes.
Characters from most Asian scripts are represented in 3 bytes. Supplementary
characters are represented in 4 bytes

UTF-8 *and* UTF8??   Even though the differences are listed above I'm not sure
that I exactly understand the distinction. Which UTF-eight characterset has been
the one discussed in this thread up until now -- UTF8 or UTF-8?

Thanks,

          Followup:

utf8 is a character set

utf-8 is an encoding scheme (like an algorithm)

(hey, we don't make up these names, we just use them -- they are made up by
others)

see

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96529/ch5.htm#1004607
utf8 is a charcterset implemented using the utf-8 ENCODING scheme

      Rating: 4
nls
      isssues  October 01, 2003
Reviewer:
      K.Venkat  from India
Dear Tom,

We have inserted japanese data using japanese key board through  our java
application into the database of character set UTF8.
When we retrieve data either through the application or backend, it is displayed
as '?????'.

Our NLS_LANG setting is AMERICAN_AMERICA.UTF8.

The output using the dump function on the column wherein we have inserted
japanese data is given below:
SQL> select dump(pt_desc,1016) from w_type;

DUMP(PT_DESC,1016)
--------------------------------------------------------------------------------
Typ=1 Len=31 CharacterSet=UTF8: e2,80,9a,c3,8d,e2,80,9a,c2,b5,e2,80,9a,c3,86,20,
e2,80,9a,c3,8d,e2,80,9a,c2,b5,e2,80,9a,c3,86

Typ=1 Len=5 CharacterSet=UTF8: 3f,3f,3f,3f,3f
Typ=1 Len=5 CharacterSet=UTF8: 4d,55,54,48,55
Typ=1 Len=16 CharacterSet=UTF8: 4d,55,54,48,55,20,4d,41,52,54,48,41,4e,44,41,4d
Typ=1 Len=14 CharacterSet=UTF8: 3f,3f,3f,3f,3f,3f,3f,3f,3f,3f,3f,3f,3f,3f
Typ=1 Len=7 CharacterSet=UTF8: 3f,3f,3f,3f,3f,3f,3f
Typ=1 Len=7 CharacterSet=UTF8: 3f,3f,3f,3f,3f,3f,3f
Typ=1 Len=5 CharacterSet=UTF8: 3f,3f,3f,3f,3f
Typ=1 Len=5 CharacterSet=UTF8: 3f,3f,3f,3f,3f

What could be the possible reason for non-display of the japanese data? Is there
any other setting we will have to look into and change?

Thanks and Regards,
Venkat

          Followup:

fonts, client not displaying it correctly.  the data looks right doesn't it (via
dump).  it is a client display issue.  when your java app retrieves it and shows
it, what does it look like then.

      Rating: 4
nls
      issues  October 01, 2003
Reviewer:  Venkat
       from India
Dear Tom,

Thanks for your immediate response.
Is there any way to query and see the data in japanese at the back end so that
we can confirm that it is certainly not a data storage problem.

Thanks and Regards,
Venkat

          Followup:

look at the dump output -- is it what you expected?  that is what is stored.

      Rating: 4
nls
      issues  October 01, 2003
Reviewer:  Venkat
       from India
Dear Tom,

In the application , the displays is '?????'.
Forgot to add this point.

Thanks and Regards,
Venkat

      Rating: 4
nls
      issues  October 01, 2003
Reviewer:  Venkat
       from India
Dear Tom,

The application team wants to see the japanese characters when they query this
data at the back end through sqlplus to confirm that the storage and display of
data is fine at the backend. What is the way to achieve this? We open a telnet
session with the server and get connected to the database.

Thanks and Regards,
Venkat

          Followup:

find a client that can show you japanes?  does your telnet client support it?

      Rating: 4
nls
      issues  October 03, 2003
Reviewer:  Venkat
       from India
Tom,

In the dump output, the character set of the database is displayed. How to
interpret and confirm that  the record  is stored in utf8? A sample output of
dump showing the particular record containing japanese data is given below:

DUMP(PT_DESC)
--------------------------------------------------------------------------------
Typ=1 Len=31: 226,128,154,195,141,226,128,154,194,181,226,128,154,195,134,32,226
,128,154,195,141,226,128,154,194,181,226,128,154,195,134

Typ=1 Len=5: 63,63,63,63,63
Typ=1 Len=5: 77,85,84,72,85
Typ=1 Len=26: 239,190,157,239,190,133,239,189,182,239,190,157,239,190,133,32,239
,189,182,239,189,189,239,189,189,32

Typ=1 Len=26: 239,190,157,239,190,133,239,189,182,239,190,157,239,190,133,32,239
,189,182,239,189,189,239,189,189,32

This is  just to confirm to the application team that the data storage is
correct in the database.

Thanks and Regards,
Venkat

          Followup:

you'd have to look up the utf8 encoding scheme and interpret the output

      Rating: 3
Look at DOC Id
      69518.1 in Metalink for a detailed answer to this
      query  October 03, 2003
Reviewer:  Sanjay
      Jha  from Toronto
UNICODE ENCODINGS
The Unicode Character Set has a few encodings, i.e. ways in which characters
are represented by binary codes. The main one is UCS-2 in which exactly 2 bytes
are used to store each character. Unicode codes of characters are conventionally

specified in this encoding and denoted as U+nnnn, where nnnn is the hex UCS-2
code of the character in range 0000-FFFF.
The second popular encoding is UTF-8 -- multibyte encoding scheme where
characters are stored in either 1, 2 or 3 bytes. Oracle RDBMS supports only
the UTF-8 encoding. The corresponding database character set is named UTF8.
MAPPING BETWEEN UCS-2 AND UTF-8
For character with UCS-2 codes in range 0x0000-0x007f (0-127), the character
code has no more than 7 significant bits and the UCS-2 and UTF-8 code points
(bit patterns) are the same, except that the UCS-2 code has 2 bytes and UTF-8
only one. If you want to use the CHR function to insert a Unicode character
in this range into an UTF8 database you simply use CHR(n) (where n is 0-127).
UTF-8 codes in this range are identical to US7ASCII.
For Unicode characters with codes from 0x0080 (128) onwards, UTF-8 uses
multibyte encoding and so you have to convert the UCS-2 code to the multibyte
code point.
The mapping between UCS-2 and UTF-8 is as follows:
Encoding bits Unicode (UCS-2) UTF-8 bit pattern UTF-8 byte size
------------- --------------- --------------------------- ---------------
7 0x0000 - 0x007f 0xxxxxxx 1
11 0x0080 - 0x07ff 110xxxxx 10xxxxxx 2
16 0x0800 - 0xffff 1110xxxx 10xxxxxx 10xxxxxx 3
As an example, from either of the above URLs, character U+00C2 is "A" with a
circumflex above. The UCS-2 code is obviously 0x00c2. To find its UTF-8 code
point:
1. 0x00c2 lies within the 11 bit range (i.e. there are from 8 to 11 significant
bits in the code) so the UTF-8 bit pattern to be used is
110xxxxx 10xxxxxx
|___| |____|
| |
A B
2. Convert 0x00c2 to its bit representation: 0000 0000 1100 0010
3. Convert 1100 0010 to the UTF-8 bit pattern:
Take the rightmost 6 bits and slot them into position B: 1000 0010
Take the remaining 5 bits (11-6) and slot them into position A: 1100 0011
Any bit positions not used are set to 0.
4. The UTF8 bit pattern is: 1100 0011 1000 0010
5. Convert this to decimal: 50050
The UTF-8 code point for UCS-2 character U+00C2 is 50050. You can use CHR(50050)

to insert "A" with circumflex above into an UTF8 database.
Similar example could be given for the Euro currency symbol, codepoint U+20AC.
20AC = 10000010101100. We have to map this to the following 3 byte pattern:
1110xxxx 10xxxxxx 10xxxxxx
|__| |____| |____|
| | |
A B C
A = 0010 , B = 000010 , C = 101100
(note that we prefixed A with '00')
So we get: 111000101000001010101100 binary, that is 14844588 decimal.
a more handy place to find a UTF8 codepoint:

http://www.macchiato.com/unicode/charts.html
(the UCS2 hex codepoint corresponds with the UTF16 hex)
create table test ( testcol VARCHAR2(10));
insert into test values (chr(50050));
(-> U+00C2 : LATIN CAPITAL LETTER A WITH CIRCUMFLEX)
insert into test values (chr(14844588));
(-> U+20AC : EURO SIGN)
If you want to insert into an Nvarchar column you need to use:
create table test ( testcol NVARCHAR2(10));
insert into test values (chr(50050 USING NCHAR_CS));
insert into test values (chr(14844588 USING NCHAR_CS));
From V9 onwards you can use also this syntax:
* the Nchar:
create table test ( testcol NVARCHAR2(10));
insert into test values (nchr(50050));
insert into test values (nchr(14844588));
* the UNISTR function to insert unicode directly:
create table test ( testcol NVARCHAR2(10));
insert into test values (UNISTR('\00c2'));
insert into test values (UNISTR('\20AC'));
the value is the UCS2 code for the symbol, you can append this like this:
insert into test values (UNISTR('\20AC\00c2'));
Oracle will do no normalisation on this type of operation, so insert of
insert into test values (UNISTR('\0041\0308'));
will insert 2 symbols the A and the ¨ and not Ä ...
6) With the "dump" command you can see what is actually stored in the database
but you get the representation for each byte back, hence if you want to check
the data in a UTF8 database you need to covert the UCS-2 to UTF8 and then to
the decimal for each byte.
For "A" with circumflex this gives 195,130
For the Euro symbol this gives 226,130,172
SQL> select dump(testcol) from test;
DUMP(TESTCOL)
--------------------------------------
Typ=1 Len=2: 195,130
Typ=1 Len=3: 226,130,172
the "Len" give the number of BYTES the row physically uses in the
database, if you store only one character, then you have of course the length of

the character (3 bytes for euro in utf8 as you can see)
other options with "dump":
SQL> select dump(testcol,16) from test;
DUMP(TESTCOL,16)
----------------------------------------
Typ=1 Len=2: c3,82
Typ=1 Len=3: e2,82,ac
50050 decimal is C382 hex and 14844588 decimal is E282AC hex
adding a '16' to the dump command shows the data in hexadecimal format.
Side remark:
How can you tell the number of bytes for a character encoded in UTF-8? (! not
UCS2 !!! )
You look at the first hex digit and follow this rule:
If it has X then the character is n byte long
----------- --------------------------------------
0 through 7 1
C or D 2
E 3
F 4
SQL> select dump(testcol,1016) from test;
DUMP(TESTCOL,1016)
-------------------------------------------------
Typ=1 Len=2 CharacterSet=AL32UTF8: c3,82
Typ=1 Len=3 CharacterSet=AL32UTF8: e2,82,ac
adding a '1016' to the dump command shows the characterset used for the data
and the data in hexadecimal format.
SQL> select dump(testcol,1010 )from test;
DUMP(TESTCOL,1010)
---------------------------------------------------
Typ=1 Len=2 CharacterSet=AL32UTF8: 195,130
Typ=1 Len=3 CharacterSet=AL32UTF8: 226,130,172
adding a '1010' to the dump command shows the characterset used for the data
and the data in decimal format.
You can also use the convert function if you know the codepoint of a character
in another characterset: (if you know the codepoint of the euro is 128 in the
WE8MSWIN1252 characterset by example)
SQL> select dump(convert(chr(128),'UTF8','WE8MSWIN1252'),16) from dual;
DUMP(CONVERT(CHR(128)
---------------------
Typ=1 Len=3: e2,82,ac
the syntax is:
select dump(convert(chr(<Decimal
value>),'<target_char_set>','<source_char_set>'),16) from dual;

      Rating: 4
multilingual
      database  November 18, 2003
Reviewer:
      Venkat  from India
Dear Tom,

The DB character set is UTF8.Like to know whether the NLS_LANG needs to be set
suitably during every insertion/querying of different language data?

For example, during insertion of japanese data, setting the value of NLS_LANG as
AMERICAN_AMERICA.JA16SJIS and settting as AMERICAN_AMERICA.UTF8  for insertion
of Hindi( one of the indian languges). Is this the way to go?

Regards,
Venkat

          Followup:

not every statement

but for every SESSION -- yes, every SESSION would want to say what NLS_LANG they
are using.

      Rating: 4
multilingual
      database  November 25, 2003
Reviewer:
      Venkat  from India
Dear Tom,

We like to store japanese,korean and english  data in our database of character
set UTF8. My colleague says that UTF8 may not support all japanese characters
and suggest going for AL32UTF8 as charater set and AL16UTF16 as national
character set. Is this correct? If so, kindly explain the rationale behind this.
If not, what should be the value for character set and national character set
and why?

Regards,
Venkat

          Followup:

check out the globalization guide.

http://otn.oracle.com/pls/db92/db92.docindex?remark=homepage#index-GLO
I don't work with enough different charactersets personally (in particular,
never worked with the languages you want to support) to answer.

      Rating: 4
US7ASCII TO
      UTF8  December 01, 2003
Reviewer:  Alvin
       from Philippines ?
We have an existing db using US7Ascii charset if we made it UTF8 will there be
performance issues ? since characters will be represented by several bytes ergo
less data fitting on the block buffer cache??

          Followup:

all of the us7ascii data will be a byte in utf8.

the performance "issues" could come from two places

a) string functions can no longer assume a byte per character. they may be more
cpu intensive

b) increased storage

I truly hesitate to call them "issues" however, they are simply the price of
playing the game here.  If you have a multi-byte requirement, they are the price
of admission

the buffer cache issue will be in my opinion "less then noise" performance wise.

      Rating: 4
support for
      multi language  January 07, 2004
Reviewer:
      Sanjiv S  from India - Hyderabad
Dear Tom,
I have installed Oracle 9i with UTF-8 characterset and nls_language as AMERICAN.
I want to use this Database for storing ENGLISH and FRENCH Data.I want to know
the following
how do i log onto sqlplus with my language as FRENCH / ENGLISH. what are the
parameters that i have to set. if i have logged in as French then i want all the
ora messages to be in french. when i alter the session and set the date format
to dd/mm/yyyy it works but when i alter the session and set the nls_language to
french it executes and on giving an errorneous select statement it returns the
ora message in English and not french. Pls. help

          Followup:

it is all ready to store both.  utf8 is perfect for that (overkill even).

the nls language just controls the language errors are shown in

do you have "french" installed? (i don't)

      Rating:
      1
  January 07, 2004
Reviewer:
      A reader
Suggest that you read the support Note:152260.1 and 179133.1

          Followup:

Hmm, an article on getting the windows code page to show a Euro symbol

And an article on getting windows fonts to show correctly...

Interesting, but of what relevance to this thread exactly........

      Rating: 4
Strange
      problem...  January 22, 2004
Reviewer:  Piotr
      Jarmuz  from Poznan, Poland
Hello Tom,

I have the following:

---------------------------------------------------------------------------------
-
create table clob_table(pk number, text clob, primary key(pk));

create or replace java source named test2 as
import java.lang.*;
import java.sql.*;
import oracle.sql.*;
import oracle.jdbc.driver.*;
import java.io.*;
public class Test2 {
    public static void save(String fn, CLOB clob) throws Exception {
        OutputStream out=clob.getAsciiOutputStream();
        InputStream z=new FileInputStream(fn);
        byte[] buffer=new byte[1024];

        int cnt;
        while ((cnt=z.read(buffer))!=-1) {
            out.write(buffer,0,cnt);
        }
        z.close();
        out.close();
    }
    public static void main(String s[]) throws Exception {
        Class.forName ("oracle.jdbc.driver.OracleDriver");
        Connection
con=DriverManager.getConnection("jdbc:oracle:thin:@bill:1521:devdb","USER","PASSW
ORD");
        con.setAutoCommit(false);
        CallableStatement cs=con.prepareCall("begin insert into clob_table
(pk,text) values (?,empty_clob()) returning text into ?; end;");
        cs.registerOutParameter(2,OracleTypes.CLOB);
        cs.setInt(1,10);
        cs.execute();
        CLOB clob=((OracleCallableStatement)cs).getCLOB(2);
        save("/var/tmp/plain.txt",clob);
        con.commit();
    }
};

/

create or replace procedure save(p_fname varchar2, p_clob clob)
as language java
name 'Test2.save(java.lang.String, oracle.sql.CLOB)';
/

declare
l_clob clob;
begin
     insert into clob_table (pk,text) values (10,empty_clob()) returning text
into l_clob;
     save('/var/tmp/plain.txt',l_clob);
end;

$> cat /var/tmp/plain.txt
über

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

Then if run the following Java program as client from windoze or unix it stores
the word `über' as it should

Offset    Byte
--------------
0         252
1         98
2         101
3         114

but if I deploy the Java program directly on the server and call the above
anonymous block it stores `?ber'
Offset    Byte
--------------
0         191
1         98
2         101
3         114

My server is:

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

Both client and server use WE8ISO8859P1

Can you explain this?

Thank you in advance

          Followup:

before we go down this path -- why would you do this rather than:

dbms_lob.loadFromFile

??  why would you even dream of writing code?

      Rating: 4
Extra
      processing needed  January 23, 2004
Reviewer:
      Piotr Jarmuz  from Poznan, Poland
Because I need to process the file before it is saved in database. This
processing is something pl/sql is not capable to do (currently, I know 10g could
do that).

          Followup:

hmm, processing plsql is not capable of doing.

such as???

but anyway -- it is most likely the fact that java is "ucs2" or whatever
character set it demands and you are seeing the effects of character set
conversion.

suggestion:

a) use dbms_lob.loadfromfile -- get into clob
b) have java process clob, not raw input file

see what happens then (but it'd be interesting to see what java can do that you
could not do in plsql)

      Rating: 4
I found a
      solution  January 23, 2004
Reviewer:  Piotr
      Jarmuz  from Poznan, Poland
I use Writer from clob.getCharacterOutputStream and explicit byte[] -> char[]
encoding. And that works albeit a little bit slower.

But still if you could explain the original problem. I'd be grateful.
Understanding "why" is sometimes more important than finding an alternate
solution.

Thank you very much.

          Followup:

that would be a character set conversion issue -- by using "byte" (equivalent to
raw) you are bypassing that.  java is a different character set and you are
seeing the defined character set conversion going on.

      Rating:
      5
Thanks!  January 23, 2004
Reviewer:
      Piotr Jarmuz  from Poznan, Poland
This "mysterious" ;) processing is zip compression. I omitted this in original
question for brevity. 9i can't do that natively but 10g can. So I opted for java
(I could do it in C as well)

We had similar issue already some time ago:

http://asktom.oracle.com/pls/ask/f?p=4950:8:11463438737389424285::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:12980645053487,
But so far I have not had to store "strange" characters so the problem didn't
manifest itself.

Well, that it is character conversion issue was clear. But why does it work on
the client (exactly the same Java code) and fails on the server???

Maybe it is different Java versions?
Client 1.4.2_01-b06
Server 1.3.1

And maybe it is because of different "drivers". On the server it is native
"already there" connection (supposedly native code) while on the client it is
"normal" remote connection (java class). Whether it is a bug I can't tell.

Thank you very much,

Regards
Piotr

      Rating: 5
Issue is OCI
      versus thin driver  January 26, 2004
Reviewer:
      Piotr Jarmuz  from Poznan, Poland
Hi again,

Just to make a final note (sorry for being persistent on that issue but I could
not sleep until I found an explanation ;))

So the little java client program above, when run (on the client's machine) with
java thin driver, does not make character conversion IMO as should be cause
client character set=server character set but with OCI driver it does character
conversion and stores junk in DB.

So now I am sure it is a bug. Cause the drivers should work at least
consistently. Changing the driver may not change the semantics of a program.

Best regards,
Piotr

      Rating: 4
Solution to
      last question  March 26, 2004
Reviewer:
      Ajay  from India
Pls see the metalink note Note:60942.1
Subject:  JDBC Drivers and NLS
It answers your query

January 23, 2004 Nag -- Thanks for the question regarding
      "Inserting data from comma delimited text file into a table.", version
      8.1.7

      You Asked
Tom

A month back, inadvertently I delted some data from production and committed.
But fortunately for me, I had saved that data in a text file, (comma
delimited)(round about 1000 rows).

I tried using the following ways:

1.Sql loader:  but I didnt have access to the server on which the database
resides. I only had access to the network server through which we connect to the
database server( the database and network servers are unix servers, where as my
client is WIN2000).

2.COPY  the insert sql against the all the rows and insert all the rows.

No.1 didnt work due to the explained reasons.

No.2 worked , but when I copied all the 1000 insert statements, and pasted at
the sqlplus prompt, it was just doing 50 to 60 inserts and breaking. Why is this
so?

So I copied 50 insert statements at a time and pasted at the sqlplus prompt ,
and committed .

to sum up I have the following questions.

1.Was that a buffer problem with the sqlplus, that it did not all me to paste
all the 1000 insert statements. If you had 1000 insert statements in a file ,
how will you execute them at the sqlplus prompt.

2.Can you tell me a real convenient method to overcome these kind of situations
using pl/sql ( from both client and server sides).

3.Can we used the sqlloader only from the database server side.

Thank you

      and we said...
number one should work -- sqlldr works just dandy over the network.

number two -- I don't know why your cut and paste buffer was getting blown out
but if you had them in a file -- why didn't you just:

SQL> @filename

that would work straight out.

PLSQL won't be too much help here.  All you really needed was sqlldr -- it would
have done it just fine.  Note that if you didn't have sqlldr and this was
important data -- why didn't you just give it to the admin on the server and ask
them to blow it in real quick?  Thats part of their job in a way -- to make sure
your business stays up and running after all.

Review &
      Followup

      Rating:
      5
  October 13, 2001
Reviewer:
      A reader
While doing SQL> @filename

We should not have the semi colon after the end of each insert statement , Iam I
right.

          Followup:

you should if you want to run it.  You either need:

insert into t values ( 1 );
insert into t values ( 2 );

or you need:

insert into t values ( 1 )
/
insert into t values ( 2 )
/

either one works.

BTW -- just had an after thought on this topic as well.  You'll love a new
feature in 9i called a flashback query.  Using that, this person would have been
able to open up a query AFTER they committed that "flashed back" to the point in
time right before they DELETED the data -- and just query it back out of the
database!  Instant recovery, no need to reload.

      Rating: 5
load_data -
      that was pl/sql solution to load data , wasnt it  October
      13, 2001
Reviewer:  Nag
'pl/sql will not be much help ', Well, I believe not, becasue ,I just found
something on your website,  you yourself  have advetised and advocated it
strongly in

http://asktom.oracle.com/pls/ask/f?p=4950:8:184310::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:464420312302,%7Binsert%7D%20and%20%7Bdelimited%7D
( The name of the  function is  load_data)

What purpose does load_data serve then.

In what context did you provide this solution(load_data) , and in what context
you said that pl/sql will not be much help ful.

          Followup:

Well, you said

"but I didnt have access to the server on which the database resides."

that sort of precludes using that utility as it relies 100% on utl_file and
utl_file can ONLY read files on the server.

So, for you -- plsql won't be much help here as I said.

Load data is only useful if you have the file on the server.

      Rating: 5
Fascinating
      feature  October 13, 2001
Reviewer:  Nag

BTW -- just had an after thought on this topic as well.  You'll love a new
feature in 9i called a flashback query.  Using that, this person would have been
able to open up a query AFTER they committed that "flashed back" to the point in
time right before they DELETED the data -- and just query it back out of the
database!  Instant recovery, no need to reload.

Tom, mindboggling ,overwhelming are the words which instatly come to my mind.

1.Can you illustrate the scenario described above, and also the sql clause which
will help flash back.

2.If oracle goes at this pace, it will be very difficult to catchup and be
uptodate. You comments please.

3.How is the flashback query managed internally. You have to answer this.

          Followup:

1) Here is an annotated demo of this feature.  You can flashback either based on
the SCN (system change number) or based on time (time is within a 5 minute
window however -- not exact.  SCN is exact)

scott@ORA9I.WORLD> @demo012
=================================================================

We'll begin by creating a table of "keep scns"

As we run through our updates, we'll remember the scn in place when
when we began/ended.  We can use this to flash back to various points
in time

scott@ORA9I.WORLD>
scott@ORA9I.WORLD>
scott@ORA9I.WORLD> create table keep_scn( msg varchar2(25), scn number );

Table created.

=================================================================

Now, we'll update and commit the EMP table manmy times in a loop.  We
remember the system change number before we start and when we are done.
this way, we can flash back to the beginning, middle or end

remember each commit in the system will up the SCN, its our "clock"

scott@ORA9I.WORLD>
scott@ORA9I.WORLD> begin
  2      insert into keep_scn values ( 'start',
dbms_flashback.get_system_change_number );
  3
  4      for i in 1 .. 100
  5      loop
  6          update SCOTT.EMP set sal = sal * 1.01;
  7          commit;
  8      end loop;
  9
 10      insert into keep_scn values ( 'stop',
dbms_flashback.get_system_change_number );
 11      commit;
 12  end;
 13  /

PL/SQL procedure successfully completed.

=================================================================

Now, lets start flashing back.  We'll start with a flash back to the
beginning first:

scott@ORA9I.WORLD>
scott@ORA9I.WORLD> variable x refcursor
scott@ORA9I.WORLD> set autoprint on
scott@ORA9I.WORLD>
scott@ORA9I.WORLD>
scott@ORA9I.WORLD> declare
  2      l_scn number;
  3  begin
  4      select scn into l_scn from keep_scn where msg = 'start';
  5      dbms_flashback.enable_at_system_change_number( l_scn );
  6      open :x for select ename, sal from SCOTT.EMP;
  7      dbms_flashback.disable;
  8  end;
  9  /

PL/SQL procedure successfully completed.

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.

=================================================================

Now we'll get the SCN that was in effect in the "middle" of update
and view the data at that point in time

scott@ORA9I.WORLD>
scott@ORA9I.WORLD> declare
  2      l_scn number;
  3  begin
  4      select trunc(avg(scn)) into l_scn from keep_scn;
  5      dbms_flashback.enable_at_system_change_number( l_scn );
  6      open :x for select ename, sal from SCOTT.EMP;
  7      dbms_flashback.disable;
  8  end;
  9  /

PL/SQL procedure successfully completed.

ENAME             SAL
---------- ----------
SMITH         1302.67
ALLEN         2605.36
WARD          2035.47
JONES         4844.33
MARTIN        2035.47
BLAKE         4640.79
CLARK         3989.45
SCOTT         4885.04
KING          8141.76
TURNER        2442.51
ADAMS         1791.19
JAMES          1546.9
FORD          4885.04
MILLER        2116.85

14 rows selected.

=================================================================

Now we'll get the SCN that was in effect at the end
and view the data at that point in time

scott@ORA9I.WORLD> declare
  2      l_scn number;
  3  begin
  4      select scn into l_scn from keep_scn where msg = 'stop';
  5      dbms_flashback.enable_at_system_change_number( l_scn );
  6      open :x for select ename, sal from SCOTT.EMP;
  7      dbms_flashback.disable;
  8  end;
  9  /

PL/SQL procedure successfully completed.

ENAME             SAL
---------- ----------
SMITH         2163.82
ALLEN         4327.72
WARD          3381.09
JONES         8046.79
MARTIN        3381.09
BLAKE         7708.73
CLARK         6626.78
SCOTT         8114.43
KING         13524.08
TURNER        4057.24
ADAMS         2975.28
JAMES         2569.53
FORD          8114.43
MILLER        3516.27

14 rows selected.

=================================================================

lastly, we'll flash back back to the start, open a cursor on the
data and then use that cursor to restore our data to that point in
time.  we'll rollback the work in effect

scott@ORA9I.WORLD>
scott@ORA9I.WORLD> declare
  2      cursor emp_cur is select empno, sal from SCOTT.emp;
  3      l_rec emp_cur%rowtype;
  4      l_scn number;
  5  begin
  6      select scn into l_scn from keep_scn where msg = 'start';
  7      dbms_flashback.enable_at_system_change_number( l_scn );
  8      open emp_cur;
  9      dbms_flashback.disable;
 10      loop
 11          fetch emp_cur into l_rec;
 12          exit when emp_cur%notfound;
 13          update SCOTT.emp set sal = l_rec.sal where empno = l_rec.empno;
 14      end loop;
 15      close emp_cur;
 16      commit;
 17  end;
 18  /

PL/SQL procedure successfully completed.

=================================================================

Just to see that it worked...

scott@ORA9I.WORLD>
scott@ORA9I.WORLD> select ename, sal from SCOTT.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.

2) Well, thats a fact of everyday life for everything.  I teach a course
internally on new 9i features -- just a bullet on each with a demo here and
there to make it real (like the one above).  Its about 170 slides and takes
eight solid hours and it only touches on new features (and doesn't get them
all).  There is alot in htere thats for sure.

3) its done the same exact way we do a consistent read -- using the UNDO we have
to store to rollback queries in the first place.  In 9i, there are UNDO
tablespaces (no need for rollback segments anymore) where by you set up an undo
retention period of N minutes and that dictates how far back you can "flash
back" to.

      Rating: 4
Flashback
      queries followup  October 17, 2001
Reviewer:
      Rich  from Ottawa, Ontario, Canada
Hi Tom,

Several places I've read mention something along the lines of "go back the
number of MINUTES you want".

Is the flashback functionality useful for reviewing data over a longer period of
time?  Eg: how many sales orders were there on the system at this time last
month.

If not, why not?

Thanks.

          Followup:

The flashback query uses the UNDO information (aka rollback data).  In 9i, you
would set up an UNDO tablespace with a retention period.  This retention period
is  specified in minutes and tells us how long to hang onto rollback for (no
need for rollback segments, just an undo tablespace).

If you have the room for a months worth of UNDO online -- you could do it (i'll
not be able to test that for you however ;)

The flashback query is a very complete solution for a "smallish" window of time,
hours of time.  You could use it in conjunction with fine grained auditing
(another new 9i feature where by we capture the SQL, the bind variables AND the
SCN when you want us to).  With that information, you can see what they saw
since you can flashback.  So, its very useful for watching someone you suspect.
It is very useful to recover data you didn't mean to drop and so on.

If I had a need to go back to last month, I would consider a tool such as
Workspace Management.  Using that, I can simply create a "branch" of my data
every month and I'll have the ability to go back to that point in time whenever
I want.  This is accomplished by versioning records.  You can use this to
automagically get an audit trail if you like (we can keep the entire change
history of a record), you can use this to create versions of your data to "what
if", you can use this to create point in time snapshots of your data.  Your
application doesn't know this is happening (totally transparent).  See

http://download-west.oracle.com/otndoc/oracle9i/901_doc/appdev.901/a88806/toc.htm
for details.  It would be more applicable to what you wanted to do and would not
require having 30 days of UNDO online.

      Rating:
      5
  October 18, 2001
Reviewer:
      A reader
excellent explanation on flashback query

      Rating: 5
RE: Flashback
      queries followup October 17, 2001  October 18, 2001

Reviewer:  Rich  from Ottawa, Ontario, Canada

Thanks for the excellent explanation and alternate solution.

      Rating: 5
concat first
      and second fields  May 22, 2002
Reviewer:
      Robert  from Scott AFB
Tom,

Can you concatenate the 1st two fields in a comma delimited infile and insert
them into the first column with sql*loader?

      Rating:
      4
  October 29, 2002
Reviewer:
      A reader
you said
"This retention period
is  specified in minutes and tells us how long to hang onto rollback for (no
need for rollback segments, just an undo tablespace)."
in the Oracle documantaion they say you specify the retention in seconds is that
correct ?

          Followup:

correct, typo on my part -- it is seconds.

      Rating:
      4
  January 22, 2004
Reviewer:
      Srinivas  from Hyd,AP,India
Hi Tom,

We have a cold Fusion front end, from where we upload a csv file, parse the file
in cf, call a stored procedure to insert data for each record(each csv file has
around 1000 records). I am not sure, if we can use sqlldr for this?  Or is this
the only way to do this?

Thanks

Srinivas

          Followup:

sqlldr works on OS files.  if you have an OS file, sqlldr would be appropriate.

if you have a variable (a big string), parse/insert is appropriate.

      Rating: 4
CSV
      Files  January 23, 2004
Reviewer:  Srinivas
       from Hyd,AP,India
Hi Tom,

Thanks for the prompt reply. But i could not understand the reply.

We are using Cold Fusion 5.0 as the front end and oracle 8i as the database.
Both are on different systems. There are other applications also running on the
systems. The cold fusion front end, which has an upload facility, from which
clients upload their data files(csv files). Right now what we are doing is,
reading the csv file, error checking is done at the front end,

the valid data(record by record) is sent as parameter to a stored procedure,
which inserts the data. In peak times, there are around 1000 users accessing the
application, and the offshot of this is response time is down, page cannot be
displayed errors. We are about to fine tune the application and identifying the
possible areas for improvement. What we felt is, to insert 1000 records, there
are 1000 calls to the database, instead at one stroke, we want to send the data
and insert it.

To achieve this, the various options considered are,
1) the correct data will be send as xml(clob) and the procedure will parse and
insert it to the tables.
2) the correct data will be send as string and the procedure will parse and
insert it into the tables.
3) the uploaded file(from cold fusion front end) will be copied from cold fusion
machine to the database system over the

network, there we would like sqlldr to insert the data into the tables. Is it
possible to invoke sqlldr from the front end.

Thanks

Srinivas

          Followup:

why not have cold fusion bulk insert it?  or cannot that API do bulk inserts?
Never programed cold fusion -- is it incapable of array inserts?

January 23, 2004 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://download-east.oracle.com/docs/cd/A81042_01/DOC/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".

      Rating: 4
Storage of
      LOBSEGMENT and LOBINDEX  January 23, 2004

Reviewer:  yazid  from France
Hi tom,
As I see the lobsegment and the lobindex are always stored in the same
tablespace.
Can i store them in diffrent tablespace : for example store lobsegment in
LOB_DATA and lonindex in LOB_INDEX.

What's better store them in the same tablespace or separate there storage.

I tried to execute this statement but both the lobsegment and the lobindex are
in the same tablespace !!
CREATE TABLE SAB_XML_PERSIST
(
  SESSION_ID   VARCHAR2(36)                     NOT NULL,
  CREATION_DT  DATE                             DEFAULT sysdate,
  UPDATE_DT    DATE                             DEFAULT sysdate,
  XML_TYPE     VARCHAR2(12)                     NOT NULL,
  XML_STREAM   CLOB
)
LOB (XML_STREAM) STORE AS SAB_LOB_DATA_XML_PERSIST
      ( TABLESPACE  EDECLIC_DATA
        STORAGE    (MAXEXTENTS       8192)
        INDEX SAB_LOB_INDEX_XML_PERSIST
          ( TABLESPACE edeclic_index
              STORAGE ( maxextents 8192 )
           )
     )
TABLESPACE EDECLIC_DATA
STORAGE    (MAXEXTENTS       8192 )

scott@test8i> r
  1  select segment_name, initial_extent, next_extent,
max_extents,TABLESPACE_NAME from user_segments
  2* where segment_name like 'SAB_%'

SEGMENT_NAME              INITIAL_EXTENT NEXT_EXTENT MAX_EXTENTS TABLESPACE_NAME
------------------------- -------------- ----------- -----------
-------------------------
SAB_XML_PERSIST                   131072      131072  2147483645 EDECLIC_DATA
SAB_LOB_INDEX_XML_PERSIST         131072      131072  2147483645 EDECLIC_DATA
SAB_LOB_DATA_XML_PERSIST          131072      131072  2147483645 EDECLIC_DATA

Why the lobindex doesn't use EDECLIC_INDEX segment ?

Thanks

          Followup:

you control the placement of the lob  segment.

you have NO control over the lob index, it is an internal structure and goes
where it goes.  the lob index is considered "part of the lob" itself.

ops$tkyte@ORA9IR2> CREATE TABLE "T"
  2  ("X" NUMBER(*,0),
  3   "Y" CLOB)
  4  TABLESPACE "T1_DATA"
  5  LOB ("Y") STORE AS (TABLESPACE "T2_DATA" )
  6  /

Table created.

ops$tkyte@ORA9IR2> select segment_name, tablespace_name from user_segments;

SEGMENT_NAME                   TABLESPACE_NAME
------------------------------ ------------------------------
T                              T1_DATA
SYS_IL0000034510C00002$$       T2_DATA
SYS_LOB0000034510C00002$$      T2_DATA

the syntax you are using is

a) deprecated
b) ignored

by design.

      Rating:
      5
  January 23, 2004
Reviewer:
      A reader

January 23, 2004 -- 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

      Rating: 5
using isopen
      function  January 22, 2004
Reviewer:  Duncan
      Mein  from England
Great advice tom!!

Tell me. Can you give me an example of using the UTL_FILE.isopen function.

I need to check if a file is open(locked) before executing the UTL_FILE.fremove
procedure.

i.e.

Before running

UTL_FILE.fremove('DATA_DIR', 'Tester.csv');

i need to check is the file tester.csv is open. If so then I need to exit before
the fremove procedure is run.

Any help would be much appreciated.

Thanks in advance

Duncan

          Followup:

how would your logic allow you to get into a situation where you would even
consider erasing a file you have open?   seems like a "bug in the algorithm" to
me.

The problem here is -- in order to see if tester.csv was opened by you you would
need to look at the plsql variable that is the "open file" handle -- but if you
knew that - well, you wouldn't probably even need to see if it was "open", you'd
know.

You cannot get here from there.  You'd have to

a) know of every variable in your code that is a file type
b) know the name of the file associated with it
c) so you could "isopen" that variable

but if you know "b", you would not need to do "c".

seems like a problem in your implementation here.

      Rating: 1
Just to
      clarify!  January 23, 2004
Reviewer:  Duncan

Thanks for your prompt response.

Just to clarify I do not want to perform check to see if I have the file open
because that as you so rightly pointed out would be a complete waste of time.

The files may be opened by multiple users who have access to the server drive
and the check i want to perform is based on the fact that one or many of the
users may have the file open when the fremove procedure is run.

In all reality this should never happen but i need to code for such an
eventuality. I realize that this is not an ideal situation however and would be
grateful of an example.

Thanks in advance again

Duncan

          Followup:

cannot do it.

but still - the logic here is "warped".

"if the file isn't open right at this second, let's erase it.  So what if
someone just decided to open the file -- erase it."

On some OS's you won't be able to erase a file others have open (eg: windows)

On other OS's it won't MATTER if you erase a file others have open (eg: unix) as
they will continue to be able to read and process that file -- when the last
user actually closes the file -- poof -- it goes away.

What you could do is

a) in your code, use DBMS_LOCK to get a shared lock named after the OS file.
You would do this in ALL CODE that opens the file

b) in the code that erases the file -- you would get an exclusive lock named
after the file -- this will block (or not, you decide) if anyone has a shared
lock

that is -- you would need to use semaphores in your code (via locks) in order to
make sure you don't erase a file other sessions have open.

January 23, 2004 -- 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://download-west.oracle.com/docs/cd/A87860_01/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

      Rating: 3
Using Net8
      listener vs direct connection to database  January 22, 2004

Reviewer:  A reader  from NJ, USA
Tom,

You mentioned in this discussion that the COPY command will go from
db1->client->db2. Assuming that I logged on directly to the UNIX machine running
db1.

1. What is the difference between
   sqlplus userid/pwd
       and
   sqlplus userid/pwd@some_connect_string

2. How will COPY work in each of the cases below? Will the two cases be same as
using db link (db1->db2) since I logged directly onto machine running the Oracle
database db1.

 a. sqlplus userid/pwd (assuming ORACLE_SID=db1 has been set)
and use COPY ...@db1 TO ...@db2 SELECT ....

 b. Connecting to db1 via net8 (listener) using
   sqlplus userid/pwd@db1 and use
   COPY ...@db1 TO ...@db2 SELECT ....

Thanks

          Followup:

1) one uses a fork/exec to get the dedicated server started.

the other asks the tnslistener to fork/exec the dedicated server.

it'll not really have an effect on the copy (beyond one might use IPC instead of
TCP but thats a protocol change)

2) you left out "where" db1, db2 and sqlplus are running. without that, one
cannot answer.

say db1 on server1.
say db2 on server2.
say db1 and db2 are right next too eachother on a really fast network.

say you dial into your system and run sqlplus scott/tiger@db1 locally.

Now, everything from db1 will be sent down the telephone to your sqlplus and
back up the telephone to db2.

that will probably be slower than a dblink between db1 and db2.

      Rating: 3
COPY
      command  January 23, 2004
Reviewer:
      A reader  from NJ, USA
Tom,

In the above question, I am sorry that I missed to mention where db1, db2 and
sqlplus are. Assuming that db1 and sqlplus are in server1 and db2 is on server2.

1. I connect to server1 directly (not using telnet) and
   A. Use sqlplus usr/pwd@db1 by asking the tnslistener to do a fork and do a
COPY to db2. What happens in this case (data flow).

   B. Use sqlplus usr/pwd and do a COPY to db2. What happens in this case (data
flow)

          Followup:

the flow is always the same.

data comes from db to sqlplus to db

database -> sqlplus client -> database

for copy..

same flow in every every case.

January 23, 2004 -- Thanks for the question regarding "Inactive
      sessions/Application slow", version 8.1.7

      You Asked
Tom,
Thank you for all the help you are doing?

We are running a EJB based application on a Oracle application server (9IAS)
against an Oracle database (8.17).  We have compliants from the application
users about the application being slow sometimes.  By the time the DBA goes to
monitor the DB instance everything looks OK.  We are unable to find out the
exactly cause of the slowness.  Every once in a while we come across inactive
and orphan sessions.  When I say Inactive session - these are the session that
show up on TOPAS (AIX based) with Inactive status with connection time over 48 +
hrs.  Orphan sessions - session shows up on TOPAS doing nothing(most of the time
with Inactive status and having a SQL associated).  Orphan sessions stay on
untill we kill them.  When we look at the SQLs behind the inactive and orpahn
sessions, they are simple SQLs that don't take much time to execute.
What might be causing the inactive and orphan sessions?  Does it help to find
out the background process associated with each of the inactive/orphan sessions?

Could missing 'close connection, close statement' be one of the problems?

Connections are made to the DB in MTS mode with connection pooling on the app
server.
How do we find out if the connections from the app server are waiting for the DB
to process the request?  How do we find out if there are enough listner and
dispatcher processes?  Do the listner and dipatcher process show up on TOPAS?
How do I find out if the instance is working properly?  ie. Is there enough
SGA/PGA/UGA?  Are there enough Oracle background processes running?

Thanks a lot in advance.

      and we said...
Using MTS with connection pooling is a lot like using a cache to cache a cache
(eg: it is overhead).

Since you can control the number of concurrent connections to the db via the
connection pool -- it seems you would want dedicated server as MTS is by
definition and design *slower* always then dedicated server.  You use MTS when
you can no longer get another connection via dedicated server cause the DB
machine is way overloaded with processes.

My question to you is: did your developers instrument the code so they generate
a log that shows you what the average response times for function X, Y or Z are?
 (doubt it, they never do until it is too later).

Have you run periodic TKPROFS (not really possible in MTS mode, need dedicated
servers to make sense of that)

Do you collect statspack information for 15 minute peak windows (eg: 11am is
busy every day so I would have a snapshot at 10:55 and 11:10 and maybe 11:30 and
11:45). It'll give you transaction rates, memory utilization, etc.

Review &
      Followup

      Rating: 3
Instrumenting
      the code  January 22, 2003
Reviewer:  Tom
       from Florida
You ask "did your developers instrument the code so they can generate a log?"
Would this instrumentation be done in PL/SQL, and if so, what are your ideas for
a best approach?

          Followup:

Yes, I do it all of the time in plsql -- for example, change the NO to YES in
the above url and just watch what happens ;)

see also

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6690299776039

I'm big into this (trace files, instrumentation, auditing).

Some people say "well, that logging code in my production code will just slow
things down".

My response is "well, you wouldn't be talking to me if your production code
wasn't slow - and yet here we are without any instrumentation and no clue as to
where the performance issue is -- now what?"

      Rating: 5
Inactive
      sessions/Application slow  January 22, 2003

Reviewer:  A reader

      Rating: 5
Now I know how
      Google works...  January 22, 2003
Reviewer:
      A reader
I always wondered where the Google engines get so accurate information.

Now I found out...

Goggle is nothing but a router to Ask Tom

;)

      Rating: 5
Now thats
      Groovy!  January 22, 2003
Reviewer:  Jim

Tom,

Pretty slick!
Do you know of examples of books, articles that
I could reference to build something similar to
your site?

Regards
Jim

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3489618933902
and ctl-f for

marvel

      Rating: 5
Very
      Interesting!  January 22, 2003
Reviewer:
      Robert  from Memphis, USA
Tom,

I've done very little web stuff.. but that NO to YES thing is very nice.
Thanks for letting us have a peek into Tom's 'mind'!

Robert.

      Rating:
      5
  January 22, 2003
Reviewer:  Jim

Tom,

Sorry my prior email should have said
I already found and read most of the discussions
re AskTom architecture, and have signed up with
Project Marvel.

I'll go checking the marvel site for more info
but I was hoping you might have suggestions
regarding info on building a site like yours

Cheers
Jim

          Followup:

it was all built with marvel??  don't know what you mean I guess.

we designed a schema
we built API's

marvel did the UI...

      Rating: 3
Best way to
      debug  January 22, 2003
Reviewer:  Tony
       from India
What is the best way to debug packages?
I hope your next book will have this topic also.

          Followup:

there is jdeveloper which has a very nice debugger (if you need that sort of
tool -- me, well, I haven't personally used a debugger since oh about
1990/1991)...

I instrument my code to generate trace files myself.

I use a tool we built called "debug" -- it is a package that lets us do tracing
in various modules of code.  it uses utl_file to write trace files on demand to
the server.

It is documented fully in "beginning Oracle programming" where we used it as a
case study.

You can get the source from www.wrox.com

      Rating: 3
Instrumenting
      he code  August 04, 2003
Reviewer:
      Subramanian  from India
Tom,
Can we instrument the code by using
sys.dbms_system.ksdwrt(1,to_char(sysdate,'hh:mi:ss'));
This creates a trace file which is useful for finding out the response time of
all the statements in a stored procedure.
Does it have any negative impact.  Is it undocumented?
Thanks in advance.
Subramanian.

          Followup:

I would not, I would use utl_file (change would to "do") for os files and
database tables for most stuff....  that is where I stick my trace information..

the problem with undocumented is -- well, it is undocumented. we have no clue as
to the inner working in x.y.z vs x.y.a and can have NO expectations either.

i know that api

i've never used it.

      Rating: 5
debug/audit
      information  August 25, 2003
Reviewer:
      A reader
Hi tom
1. Do you have some kind  of purging policy on your
   auditing table? I am imagining that while developing and
   testing we would end up inserting a lot of data.

   Even in production, how do you take care of these
   tables from becoming too large?
2. In my case, I am trying to implement a similar flag
   in URL. I use dbms_output.put_line in the plsql
   procedure and then use DbmsOutput.java to get it
   and show it through my servlet. I am encountering
   a strange issue. Sometime my entire set of messages
   is repeated as shown below:
    debug msg1
    debug msg2

    debug msg1
    debug msg2

    I debugged through and noticed that the show()
    procedure of DbmsOutput.java sometimes returns
    duplicate message. Looking at the select
    statement that you defined - do you see any bug
    off the top of your head:
   m_showStatement = 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;" );

Thanx a lot!!!

          Followup:

1) we "roll them"...

you can either do this with partitioning.... every month, add a new partition on
the end for next months data, drop/archive the oldest.

if you don't have partitioning, you can use synonyms and views.  every month,
create a new table, repoint the synonym (so the inserts go there) and have a
view that is a union all of the relevant tables you want in your audit trail.

I use both, partitioning is more effective/efficient

2) it most likely means you are maintaining a connection pool, this pool is NOT
reseting the package state, so you are getting debug from not just your session
but others who are running it as well (but not dumping the output)

      Rating: 5
Thanx
      Tom!  August 26, 2003
Reviewer:
      A reader
"2) it most likely means you are maintaining a connection pool, this pool is NOT

reseting the package state, so you are getting debug from not just your session
but others who are running it as well (but not dumping the output) "

Hmmmm..You are right! I am maintaining the
connection pool - but I am the only one who is executing
this code (since it is development - and the particular
page is new)

However, this rules out my using the DbmsOuput.java
for the purpose of "debug" flag in URL :(
I can try and use the my_dbms_output as defined
by you. However, it would be tough to convince
others to use a "non-standardad" package.

So when you convert the flag fro NO to YES
in asktom, do you get the info from a table?
In that case is not that too much data?
O I guess you just use plsql package to conditionally
generate the data - of course!:)

Any suggestions what I should do (plsql based
UI is not an option for me)

Regards

          Followup:

try debug.f maybe?

http://asktom.oracle.com/~tkyte/debugf

      Rating:
      5
my_dbms_output  August 26, 2003
Reviewer:
      A reader
I realized that my_dbms_output will also have the
same problem.

How about modifying your debug.f to populate a global
temporary table with the debug data. So for
each page.
1. In the plsql that gets invoked by the page
   I would have debug.f() that puts data in a global
   temporary table (instead of a file) (the GTT will
   have all the debug related columns such as
   Time stamp, line number etc (using the debug.f)
   functionality.
2. In the page, if the url's debug flag is open, I would
   do a select from this global temporary table. At the
   beginning of each page - I will have to delete
   the data from gtt (so that I don't see accumulated
   debug info from previous pages in the same session.

Do you see any problems with this approach, given I am
using connection pools?

Thanx!

          Followup:

do you use plsql in your applications?  if so, you might consider a call to
dbms_session.reset_package_state to avoid these issues alltogether.

else user1 will get user2's packge state, which could be a problem

      Rating: 5
thanx
      Tom!  August 26, 2003
Reviewer:
      A reader
"do you use plsql in your applications?  if so, you might consider a call to
dbms_session.reset_package_state to avoid these issues alltogether."

Yes we use plsql - all my dmls are in plsql.
However, for UI we don't use plsql.

I suppose you meant dbms_session.reset_package?
I don;t think I can use it on each url (at the beginning)
because it could have side effects? Also, I am not sure
if it is the package state that is causing the problem
since I am the only one accessing the application
and the database (and getting the duplicate debug messages)

What do you think of my GTT based solution?

Thank you so much!

          Followup:

if you are using a connection pool

and you use packages

and you don't call it - -you'll REALLY have side effects.

user 1 grabs connection A
uses a package, sets some variables
user 1 gives up connection A
user 2 gets connection A -- bummer.

      Rating:
      5
Thanx!  August 26, 2003
Reviewer:
      A reader
"if you are using a connection pool
and you use packages
and you don't call it - -you'll REALLY have side effects.
..."

Actually, I am not sure whether we are doing that or not.
I will ask the guys who are responsible for managing the
connection pool to do it.
So this has to be done just before you hand over the
connection (e.g. in the connection.getConnection() )
method, I suppose?

Assuming that that is not the problem:) (- will verify
if that is the case by asking the right folks), could
you please comment on the GTT solution. I am trying it
out as we speak..
1. I delete all the messages from the GTT at the begining
of the rendering of a url
2. I use debug.f (that also inserts into gtt now
3. I use a simple select from this GTT to display message
if the url indicates that the debug is on.

          Followup:

that'll work, sure -- it'll be just like resetting the package state :)

      Rating: 5
hi
      tom  August 26, 2003
Reviewer:
      A reader
"that'll work, sure -- it'll be just like resetting the package state "

But from the doc, it seems that resetting a package state
would free ALL package states - not just the one that
I am executing. GTT does not have this issue, I think.
Only the debug messages associated with procedures that are invoked during the
rendering of the URL will
be deleted.

btw, it seems to work fine and I don't seem to be getting
multiple debug messages anymore..

Thanx!

"DBMS_SESSION.RESET_PACKAGE;
..
This procedure deinstantiates all packages in this session: It frees **all**
package states.
Memory used for caching execution state is associated with all PL/SQL functions,
procedures, and packages that have been run in a session.
For packages, this collection of memory holds the current values of package
variables and controls the cache of cursors opened by the respective PL/SQL
programs. A call to RESET_PACKAGE frees the memory associated with each of the
previously run PL/SQL programs from the session, and, consequently, clears the
current values of any package globals and closes any cached cursors.
RESET_PACKAGE can also be used to reliably restart a failed program in a
session. If
a program containing package variables fails, then it is hard to determine which
variables need to be reinitialized. RESET_PACKAGE guarantees that all package
variables are reset to their initial values."

          Followup:

you *need* to reset the package states.  anything short of that would be what is
known as "a bug"

there is 0% chance you want user2 to have user1's package state.

it is not an issue -- it is a feature.

      Rating: 5
one doubt about
      debug.f  August 26, 2003
Reviewer:
      A reader
It seems it does not have a way to turn off
all work by flipping flag (e.g. an enable/disable switch)
I can see it has debug.clear() - but when you say debug.f()
it still goes through all the selects (they don't return
anything.. Anyways, do you think this could be a good
feature. I plan to add it but wanted to ask you since
I maybe missing something. So what I want is (pseudo code)
debug.f()
{
  if ( debug.enable) then
    do whatever
  end if
}

Thank you!!!

          Followup:

sure, sounds like a great idea.

      Rating: 5
debug.f
      enhancements  August 27, 2003
Reviewer:
      A reader
So, I already enhanced the debug.f to use temporary
table to store and retrieve debug messages alongwith
the option of putting the info in a trace file.
It seems to work well.

Now I plan to add the "enable" flag. I have doubts on
how to make this work.

In my case, as I said, I don't use plsql to generate
pages. So this is what my pseudo code from the url
looks like:
1.If debug is on in URL, I call an init to initialize
  the debug variables - this could set a package level
  variable for that session (or a temporary table) to
  YES.
2. I do my processing - some of the processing involves
  calling plsql procedures with dml statements that
  return results to be displayed in the GUI. I intersperse
  them with debug.f messages. In my case, I have enhanced
  the method to put them in a temporary table (apart from
  putting them in the file)
3. I use a util method to get the data from the temporary
  table - I have the option of get_debug_info and
  get_Debug_info_flush (the second one deletes data
  after getting them from the temporary table)
  Now if the debug url was set to NO, with the new
  enhancement of the "enable" flag in debug, I would
  not incur any penalty of selects etc being done by the
  debug.f method.

Should I use a package variable or a temporary table.
Actually, as I type this in, I am thinking of a
package variable - here I don't have the doubts of
resetting of package variables - as I would be setting
this value to ON before every URL rendering begins
IF the debug is ON.

Any thoughts ( I am using connection pooling)?

Also, I have following doubts:
1. I am still not clear on *when* to use the
   dbms_session.reset_package;
   Since I use connection pooling, I am thinking we
   should do it in the connectionPool.getConnection()
   method. However, how does this work because in
   connection pool during one transaction itself
   I can grab the connection many times.

   Say, in the same session, I am doing for a UI
   initiated process doSomethingFromUI - I do the
   following steps.
   a. grab connection - do processing 1
   b. grab connection - do processing 2

   In steps a and b, I may get different connections - does
   this imply that in 1 and two I should not be relying
   on any package state - This does not make sense -
   what am I missing or misunderstanding here!

2. should I send my enhancements to Christopher or post
   it here?

Thanx!!

          Followup:

1) you should clear it when you give the connection back for the last time OR
when you grab it for the first time.

why would you grab two different connections for a single page -- doesn't seem
to make sense -- you would lose the temp table from session1 in session2 and
vice versa.

2) you can post it here if you like.

      Rating: 5
one more
      question  August 27, 2003
Reviewer:
      A reader
To the previous questions, please add this one
3. For the debug.f file - if we don;t have the utl_file_dir
directory enabled by default in our app, how do we
deal with this. I was thinking of using "create directory"
but for each install we need a starting point (as the
absolute path would be different in different machines).
How do we solve this issue?

Thanx!

          Followup:

3) up to you -- to use utl_file, you need utl_file_dir.  or if you have 9i, you
can use a directory object which must be created.  thats about it.

      Rating: 5
thanx
      Tom!  August 27, 2003
Reviewer:
      A reader
"1) you should clear it when you give the connection back for the last time OR
when you grab it for the first time.

why would you grab two different connections for a single page -- doesn't seem
to make sense -- you would lose the temp table from session1 in session2 and
vice versa."

You are right - when I thought a little more, I realized
I don't do that. - I get it only once per rendering of the
page.
So if I have a URL - then while rendering I grabbed
the connection once. If there is a button on a url
that changes something - then again I grabbed the
connection (Second time)and rerendered the URL with the
connection (may be a different one) Is this scenario
wrong? (Every event initiated by URL that requires db
access gets a connection from the pool.)

hmm... I think I am a little confused on the the connection
thinggy versus session. Each connection I guess represents
a session (assuming dedicated server), right? There is
a one to one relationship between them, right?
Also, a global package variable - when set is set for
that session only - if some other session sets it
it gets its own copy, right?

2) you can post it here if you like.
I will
Now for the 3rd response from you
"3) up to you -- to use utl_file, you need utl_file_dir.  or if you have 9i, you

can use a directory object which must be created.  thats about it.
"

I was thinking of creating a directory. But this directory
"root" would be different in every database.
When you install the application - somewhere you would
run "CREATE DIRECTORY admin AS ’oracle/admin’;"

You don't want to hard code that path to avoid editing the
directory again and again - you want to specify a relative path. Is their any
way? I hope I am clear in my question.

Thank you!!

          Followup:

each connection represents an oracle session yes.

when you grab a connection, you are grabbing someone elses pre-existing system,
and inherit whatever they had (with most connection pools anyway)

relative path to WHAT ? relative to where??

      Rating:
      5
  August 27, 2003
Reviewer:
      A reader
"relative path to WHAT ? relative to where?? "

Basically, I want to avoid hard coding the path
because it would be different in each install and
I dont want to edit the install script to just change
the directory - so for example, I could create a path
to the Oracle home (but how do I get that in sql)?

          Followup:

i would use either of background dump dest or user dump dest as a destination.

or prompt the user for the location.

      Rating:
      5
thanx!  August 28, 2003
Reviewer:
      A reader
the idea of using bdump is a good one!

      Rating:
      5
debug.f  August 28, 2003
Reviewer:
      A reader
Hi Tom,
Can you please put the link

http://asktom.oracle.com/~tkyte/debugf
in your page
"Click here for tidbits, howtos and archived presentations "

Thanks!

          Followup:

done

      Rating: 5
here is the
      modified debug.f - please give your comments  August 28,
      2003
Reviewer:  A reader
Wish I could make things bold in my response instead
of simple text ( to highlight changes)

Features added:

F1. It has a flag to enable and disable debugging.
   The flag take on following values (int constants)
  a. DEBUG_OFF - This means that debugging code for
     debug.f() and debug.fa() would just return
     without doing anything.
  b.  DUMP_FOR_URL_ONLY - enable debugging so that you
     see the output only in the URL displayed -
     *DON'T* do any trace file generation in the trace
     file directory.
  c. DUMP_IN_TRACE_FILES_ONLY
      enable debugging so that you see the output only in
      the trace file directory - no need to display the
      output in the URL.
  d. DUMP_FOR_TRACE_FILES_AND_URL
     enable debugging so that you see the output the
     trace file as well as on the URL.

   To disable debugging execute
    debug.disable;

   To enable debugging (e.g. for trace files and URL)
   execute
     debug.enable ( DUMP_FOR_TRACE_FILES_AND_URL );

  For URL based dumping (which is not htp.p based),
  you can do the following

  1. Before URL based generation code begins check
     to see from the URL page property if debug flag
     is on.
  2. If the flag is off don't do anything
  3. If the flag is on, execute debug.enable (
         valid_debug_flag)
  4. continue processing URL generation code - this
     could involve plsql code calls that have been
     instrumented using debug.f.
  5. if debug_flag is enabled for URL display
     execute debug.get_debug_info_flush to get
     the data and flush the temporary table where
     it gets stored.
  6. execute debug.disable (this is to reset the
     package variable)

F2. If debug is not set to off then the output is
   always shown using dbms_output.put_line. This is
   useful for sql prompt based debugging which is
   what many people are used to.

Note: I added a temporary table to store messages
   to be displayed in URL.

-----------------------debug.sql starts -----------
---- also contains schema required to make it work
---------------------------------------------------
set echo off

create or replace
type Argv as
table of varchar2(4000);
/

create global temporary table debug_info
(
  message     varchar2(4000)
)
on commit delete rows;
/
create table debugTab(
  userid     varchar2(30) primary key,
  modules     varchar2(4000),
  locat         varchar2(4000),
  filename varchar2(4000)
)
/

create or replace
trigger bi_fer_debugtab
before insert on debugtab for each row
begin
  :new.modules := upper( :new.modules );
end;
/

create or replace
package debug as
--
--  version 1.0
--    clbeck - 13-OCT-98 - Initial version
--
--  PACKAGE TO DUMP DEBUG INFORMATION OF PL/SQL ROUTINE
--  TO A FILE DURING EXECUTION
--
--
--  This package allows the developer to selectively produce debug
--    information 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' );
--      debug.enable ( <a valid debug flag other than debug.DEBUG_OFF> )
--
--    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';
--
--     NOTE:
--       If debug is not set to off then the output is
--       always shown using dbms_output.put_line. This is
--       useful for sql prompt based debugging which is
--       what many people are used to.
--
--    To stop debug run:
--
--      debug.clear;
--
--    To disable debug run
--
--      debug.disable;
--
--    To get debug message outputted for a URL generation do the following:
--
--    For URL based dumping (which is not htp.p based),
--      you can do the following

--      1. Before URL based generation code begins check
--         to see from the URL page property if debug flag
--         is on.
--      2. If the flag is off don't do anything
--      3. If the flag is on, execute debug.enable (
--             valid_debug_flag)
--      4. continue processing URL generation code - this
--         could involve plsql code calls that have been
--         instrumented using debug.f.
--      5. if debug_flag is enabled for URL display
--         execute debug.get_debug_info_flush to get
--         the data and flush the temporary table where
--         it gets stored.
--      6. execute debug.disable (this is to reset the
--         package variable)
--
--  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)
--
--

  g_dir_locat constant varchar2(4000) := '/tmp';

  -- following constants determine where all the debugging messages
  -- are output. Please note that if the DEBUG_OFF is NOT used then
  -- regardless of the output option chosen, the message is output
  -- on the sqlplus screen using dbms_output.put_line.

  DEBUG_OFF constant int := 0; -- no debugging messages output to
                               -- any destination
  DUMP_FOR_URL_ONLY constant int := 1; -- only for URL based debugging
  DUMP_IN_TRACE_FILES_ONLY constant int := 2; --dump only in trace files
  DUMP_FOR_TRACE_FILES_AND_URL constant int := 3; -- both in URL and trace files

  emptyDebugArgv Argv;

  --
  --  Initializes the debuging for specified p_modules and will dump the
  --  output to the p_dir directory on the server for the user p_user.
  --
  procedure init(
    p_modules in varchar2 default 'ALL',
    p_dir     in varchar2 default g_dir_locat,
    p_file    in varchar2 default null,
    p_user    in varchar2 default user );

  procedure f(
    p_message in varchar2,
    p_arg1    in varchar2 default null,
    p_arg2    in varchar2 default null,
    p_arg3    in varchar2 default null,
    p_arg4    in varchar2 default null,
    p_arg5    in varchar2 default null,
    p_arg6    in varchar2 default null,
    p_arg7    in varchar2 default null,
    p_arg8    in varchar2 default null,
    p_arg9    in varchar2 default null,
    p_arg10   in varchar2 default null );

  procedure fa(
    p_message in varchar2,
    p_args    in Argv default emptyDebugArgv );

  procedure clear(
    p_user in varchar2 default user );

  --
  -- Returns the current status of debugging for the user p_user.
  --
  procedure status(
    p_user    in  varchar2 default user,
    p_modules out varchar2,
    p_file    out varchar2,
    p_dir     out varchar2 );

  --
  -- deletes info from the temporary table (useful for flushing the
  -- messages already seen in a URL based debugging
  --
  procedure delete_debug_info;

  --
  -- enables debugging
  -- messages already seen in a URL based debugging
  -- The p_debug_flag can take on following values (int constants)
  --   1. DEBUG_OFF - This means that debugging code for
  --      debug.f() and debug.fa() would just return
  --      without doing anything.
  --   2.  DUMP_FOR_URL_ONLY - enable debugging so that you
  --      see the output only in the URL displayed -
  --      *DON'T* do any trace file generation in the trace
  --      file directory.
  --   3. DUMP_IN_TRACE_FILES_ONLY
  --       enable debugging so that you see the output only in
  --       the trace file directory - no need to display the
  --       output in the URL.
  --   4. DUMP_FOR_TRACE_FILES_AND_URL
  --      enable debugging so that you see the output the
  --      trace file as well as on the URL.
  --
  procedure enable ( p_debug_flag int );

  -- disables debugging
  -- Used to incur minimal performance penalty for instrumenting
  -- the code with debug.f() and debug.fa()

  procedure disable;

  -- gets debug messages from the temporary table (for url based
  -- debugging  - to display the debug messages on the URL just
  -- the asktom.oracle.com does if you flip the "NO" in an article's
  -- URL to "YES")

  function get_debug_info return sys_refcursor;

  -- gets debug messages from the temporary table and deletes
  -- that info from the temporary table ( useful for url based
  -- debugging  - to display the debug messages on the URL just
  -- the asktom.oracle.com does if you flip the "NO" in an article's
  -- URL to "YES") Typically you want to see the same message again in
  -- the URL - so this method deletes after returning a cursor
  -- containing debug messages

  function get_debug_info_flush return sys_refcursor;

end debug;
/

show error

grant execute on debug to public
/

create or replace
package body debug as

  --g_enable_flag int default DEBUG_OFF;
  g_enable_flag int default DEBUG_OFF;
  g_owner varchar2(2000);
  g_name varchar2(2000);
  g_lineno number;
  g_caller_t varchar2(2000);

  g_file varchar2(2000);

  procedure init(
    p_modules in varchar2 default 'ALL',
    p_dir     in varchar2 default g_dir_locat,
    p_file    in varchar2 default null,
    p_user    in varchar2 default user ) is
  --
    r debugTab%rowtype;
  begin
    clear( p_user );
    insert into debugTab values ( p_user, p_modules, p_dir, p_file );
  end init;

  procedure clear( p_user varchar2 default user ) is
  begin
    delete from debugTab where userid = p_user;
  end clear;

  procedure status(
    p_user    in  varchar2 default user,
    p_modules out varchar2,
    p_file     out varchar2,
    p_dir    out varchar2 ) is
  begin
    select modules, locat, filename
      into p_modules, p_dir, p_file
      from debugTab
     where userid = p_user;
  exception
    when NO_DATA_FOUND then
      p_modules := null;
      p_dir := null;
      p_file := null;
  end status;

  procedure who_called_me(
    owner      out varchar2,
    name       out varchar2,
    lineno     out number,
    caller_t   out varchar2 ) is
  --
    call_stack  varchar2(4096) default dbms_utility.format_call_stack;
    n           number;
    found_stack BOOLEAN default FALSE;
    line        varchar2(255);
    cnt         number := 0;
  begin
    loop
      n := instr( call_stack, chr(10) );
      exit when ( cnt = 3 or n is NULL or n = 0 );
  --
      line := substr( call_stack, 1, n-1 );
      call_stack := substr( call_stack, n+1 );
  --
      if not found_stack then
        if line like '%handle%number%name%' then
          found_stack := TRUE;
        end if;
      else
        cnt := cnt + 1;
        -- cnt = 1 is ME
        -- cnt = 2 is MY Caller
        -- cnt = 3 is Their Caller
        if ( cnt = 3 ) then
          lineno := to_number(substr( line, 13, 6 ));
          line   := substr( line, 21 );
          if ( line like 'pr%' ) then
            n := length( 'procedure ' );
          elsif ( line like 'fun%' ) then
            n := length( 'function ' );
          elsif ( line like 'package body%' ) then
            n := length( 'package body ' );
          elsif ( line like 'pack%' ) then
            n := length( 'package ' );
          else
            n := length( 'anonymous block ' );
          end if;
          caller_t := ltrim(rtrim(upper(substr( line, 1, n-1 ))));
          line := substr( line, n );
          n := instr( line, '.' );
          owner := ltrim(rtrim(substr( line, 1, n-1 )));
          name  := ltrim(rtrim(substr( line, n+1 )));
        end if;
      end if;
    end loop;
  end who_called_me;

  function is_number( n varchar2 ) return boolean is
  begin
    if n between '0' and '9' then
      return true;
    end if;
    return false;
  end is_number;

  function parse_it(
    p_message in varchar2,
    p_args in argv default emptyDebugArgv ) return varchar2 is
  --
    l_tmp long := p_message;
    l_str long := null;
    l_idx number;

    l_numstr1 varchar2(10);
    l_numstr2 varchar2(10);

    l_tmp1 long;
    l_str1 long;

    l_num number;
    l_char long;
  begin

    for i in 1 .. p_args.count loop
      l_idx := instr( l_tmp, '%' ) ;
      exit when nvl(l_idx,0) = 0;

      l_str := l_str || substr( l_tmp, 1, l_idx-1 );
      l_tmp := substr( l_tmp, l_idx+1 );

      if substr( l_tmp, 1, 1 ) = 's' or
         substr( l_tmp, 1, 1 ) = 'd' then
        l_str := l_str || p_args(i);
        l_tmp := substr( l_tmp, 2 );
      elsif is_number( substr( l_tmp, 1, 1 ) ) or
            substr( l_tmp, 1, 1 ) = '.' then

        l_numstr1 := null;
        l_numstr2 := null;

        l_tmp1 := l_tmp;
        l_str1 := l_str;

        loop
          exit when not is_number( substr( l_tmp1, 1, 1 ) );
          l_numstr1 := l_numstr1 || substr( l_tmp1, 1, 1 );
          l_tmp1 := substr( l_tmp1, 2 );
        end loop;

        if substr( l_tmp1, 1, 1 ) = '.' then
          l_tmp1 := substr( l_tmp1, 2 );
          if is_number( substr( l_tmp1, 1, 1 ) ) then
            loop
              exit when not is_number( substr( l_tmp1, 1, 1 ) );
              l_numstr2 := l_numstr2 || substr( l_tmp1, 1, 1 );
              l_tmp1 := substr( l_tmp1, 2 );
            end loop;
          else
            l_tmp1 := '!' || l_tmp1;
          end if;
        end if;

        begin
          if substr( l_tmp1, 1, 1 ) = 's' then
            l_tmp := substr( l_tmp1, 2 );
            if l_numstr2 is null then
              l_tmp1 := p_args(i);
            else
              l_tmp1 := substr( p_args(i), 1, l_numstr2 );
            end if;
            if l_numstr1 is not null then
              l_tmp1 := lpad( l_tmp1, l_numstr1 );
            end if;
            l_str := l_str1 || l_tmp1;
          elsif substr( l_tmp1, 1, 1 ) = 'd' then
            l_tmp := substr( l_tmp1, 2 );
            if l_numstr1 is null then
              l_tmp1 := lpad( '9', 39, '9' );
            else
              l_tmp1 := lpad( '9', l_numstr1, '9' );
            end if;
            if l_numstr2 is not null then
              l_tmp1 := substr( l_tmp1, 1, l_numstr1-l_numstr2 ) || '.' ||
                        substr( l_tmp1, -l_numstr2 );
            end if;
            l_str := l_str1 || to_char( to_number( p_args(i) ), l_tmp1 );
          else
            l_str := l_str || '%';
          end if;
        exception
          when others then
            l_str := l_str1 || 'XXXXXXXXXX';
        end;

      else
        l_str := l_str || '%';
      end if;

    end loop;

    return l_str || l_tmp;

  exception
    when others then
      return l_str || l_tmp;
  end parse_it;

  procedure internal_f(
    p_message in varchar2,
    p_args    in Argv default emptyDebugArgv ) is
  --
    l_locat varchar2(4000);
    l_modules varchar2(4000);
    l_filename varchar2(4000);
    l_message long := null;
    l_final_message long;
    l_file utl_file.file_type;
    l_date varchar2(255);
  begin

    select modules, locat, filename, to_char( sysdate, 'YYMMDD HH24MISS' )
      into l_modules, l_locat, l_filename, l_date
      from debugTab
     where userid = user;

    if instr( l_modules, nvl(g_name,'BLAH') ) = 0 and
       l_modules <> 'ALL' then
      return;
    end if;

    if l_filename is not null then
      g_file := l_filename;
    end if;

    l_message := parse_it( p_message, p_args );
/*
    l_message := p_message;

    begin
      for i in 1 .. p_args.count loop
        if instr( l_message, '%s' ) = 0 then
          exit;
        else
          l_message := substr( l_message, 1, instr( l_message, '%s' )-1 ) ||
                       p_args(i) ||
                       substr( l_message, instr( l_message, '%s' )+2 );
        end if;
      end loop;
    exception
      when others then
        null;
    end;
*/

    l_message := replace( l_message, '\n', chr(10) );
    l_message := replace( l_message, '\t', chr(9) );

    if( g_enable_flag = DUMP_IN_TRACE_FILES_ONLY or
        g_enable_flag = DUMP_FOR_TRACE_FILES_AND_URL ) then
      l_file := utl_file.fopen( l_locat, g_file, 'a', 32767 );
      if not utl_file.is_open( l_file ) then
        dbms_output.put_line( 'File not opened' );
      end if;
    end if;

    if g_owner is null then
      g_owner := user;
      g_name := 'ANONYMOUS BLOCK';
    end if;

    l_final_message := l_date ||
      ' (' || lpad( g_owner || '.' || g_name, 20 ) || ',' ||
      lpad(g_lineno,4) || ') ' || l_message;

    if( g_enable_flag = DUMP_IN_TRACE_FILES_ONLY or
        g_enable_flag = DUMP_FOR_TRACE_FILES_AND_URL ) then

      utl_file.put( l_file, '' );
      utl_file.put_line( l_file, l_final_message );
      utl_file.fclose( l_file );
    end if;
    if( g_enable_flag = DUMP_FOR_URL_ONLY or
        g_enable_flag = DUMP_FOR_TRACE_FILES_AND_URL ) then
      insert into debug_info ( message) values ( l_final_message );
    end if;

    -- regardless of debug flags - always show it using the
    -- dbms_output.put_line for sqlplus based debugging.

    dbms_output.put_line ( l_final_message );
  exception
    when NO_DATA_FOUND then
      -- dbms_output.put_line( sqlerrm );
      null;
  end internal_f;

  procedure fa(
    p_message in varchar2,
    p_args    in Argv default emptyDebugArgv ) is
  begin
    if( g_enable_flag = DEBUG_OFF ) then
      return;
    end if;
    who_called_me( g_owner, g_name, g_lineno, g_caller_t );
    internal_f( p_message, p_args );
  end fa;

  procedure f(
    p_message in varchar2,
    p_arg1    in varchar2 default null,
    p_arg2    in varchar2 default null,
    p_arg3    in varchar2 default null,
    p_arg4    in varchar2 default null,
    p_arg5    in varchar2 default null,
    p_arg6    in varchar2 default null,
    p_arg7    in varchar2 default null,
    p_arg8    in varchar2 default null,
    p_arg9    in varchar2 default null,
    p_arg10   in varchar2 default null ) is
  begin
    if( g_enable_flag = DEBUG_OFF ) then
      return;
    end if;
    who_called_me( g_owner, g_name, g_lineno, g_caller_t );
    internal_f( p_message,
                argv( substr( p_arg1, 1, 4000 ),
                      substr( p_arg2, 1, 4000 ),
                      substr( p_arg3, 1, 4000 ),
                      substr( p_arg4, 1, 4000 ),
                      substr( p_arg5, 1, 4000 ),
                      substr( p_arg6, 1, 4000 ),
                      substr( p_arg7, 1, 4000 ),
                      substr( p_arg8, 1, 4000 ),
                      substr( p_arg9, 1, 4000 ),
                      substr( p_arg10, 1, 4000 ) ) );
  end f;

  procedure delete_debug_info is
  begin
    delete debug_info;
  end delete_debug_info;

  procedure enable ( p_debug_flag int) is
  begin
    if( p_debug_flag = DUMP_FOR_URL_ONLY or
        p_debug_flag = DUMP_IN_TRACE_FILES_ONLY or
        p_debug_flag = DUMP_FOR_TRACE_FILES_AND_URL ) then
      g_enable_flag := p_debug_flag;
    end if;
  end enable;

  procedure disable is
  begin
    g_enable_flag := DEBUG_OFF;
  end disable;

  function get_debug_info return sys_refcursor is
    l_cursor    sys_refcursor;
  begin
    open l_cursor for
      select message
      from debug_info;
    return l_cursor;
  end get_debug_info;

  function get_debug_info_flush return sys_refcursor is
    l_cursor    sys_refcursor;
  begin
    l_cursor := get_debug_info;
    delete_debug_info;
    return l_cursor;
  end get_debug_info_flush;

begin

  g_file := 'DEBUGF_'||userenv('SESSIONID');

end debug;
/
show errors
/

-----------------------debug.sql ends -----------

      Rating: 5
Why slow
      database becomes normal after restarting  October 10, 2003

Reviewer:  A reader
Hi Tom,

My database runs slower and slower after time goes by, however it becomes fast
if I restart it.

Is it possible that I can "reset" the database status without restarting it?

Such as:

Reset shared pool
Reset buffer cache
Reset PGA

          Followup:

maybe it was tired and needed a nap!

Seriously -- there is so little quantative information here that no one can say
anything....

no OS's
no version's
no description of what "slow" means and how fast is "fast"
no clue as to how long this takes to get like this (a day, a week, a year)
no diagnostic information like "we notice that process X grew from A bytes to B
bytes and keeps growing, the disk lights are constantly on after a while" or
anything...

You need to find the CAUSE (eg: maybe statspack taken in 15 minute windows, over
time can be used to see what waits increase over time) (eg: maybe an os tool can
detect some sort of leak, showing you -- ah hah, the system starts
swapping/paging like mad because process foo grew to X million gigabytes)

Then and only then can one say something.....

      Rating:
      2
  October 11, 2003
Reviewer:
      A reader

          Followup:

thats about the same way i felt about the above "review" myself ;)

      Rating: 5
Inactive
      sessions  October 20, 2003
Reviewer:
      A reader
Tom,
We are coming across inactive db sessions with associated SQLs.  We are not sure
why these inactive sessions are showing up.

The application is an EJB based application.
My questions are
1) Is it normal to have inactive sessions with SQLs associated(even after the
session is idle for 14 + hrs)?
2) Do such session lead to blocked sessions?

Thank you

SQL>     SELECT    TO_CHAR(SYSDATE, 'dd Mon yyyy hh24:mi:ss')     AS "System
Time"
  2         FROM dual
  3  /

System Time
--------------------
17 Oct 2003 08:42:42

SQL>     SELECT    SUBSTR(p.spid, 1, 7)                 AS process
  2            , TO_CHAR(s.logon_time, 'dd Mon yyyy hh24:mi:ss')  AS logon
  3            , TO_CHAR(s.sid, '999')                 AS sid
  4            , TO_CHAR(s.serial#, '99999')             AS serial
  5            , SUBSTR(s.status, 1, 3)                 AS act
  6            , SUBSTR(s.server, 1, 3)                 AS srv
  7            , SUBSTR(s.username, 1, 16)             AS user_name
  8            , LPAD
  9             ( TO_CHAR( FLOOR( s.last_call_et / ( 3600 * 24 ) ), 'FM999' )
 10              || ' '
 11              || TO_CHAR( MOD( FLOOR( s.last_call_et / 3600 ), 24 ), 'FM00' )
 12              || ':'
 13              || TO_CHAR( FLOOR( MOD( s.last_call_et, 3600 ) / 60 ), 'FM00' )
 14              || ':'
 15              || TO_CHAR( MOD( MOD( s.last_call_et, 3600 ), 60 ), 'FM00' )
 16             , 12
 17             )                         AS idle
 18            , s.machine                     AS computer
 19         FROM v$session s
 20            , v$process p
 21  --       , v$sesstat t
 22  --       , v$statname n
 23         WHERE  s.paddr = p.addr (+)
 24  --       AND s.sid = t.sid (+)
 25  --       AND t.statistic# = n.statistic#
 26  --       AND n.name = 'session uga memory'
 27            AND ( s.username LIKE 'MOD%'
 28           OR s.username LIKE 'MAIN_ADMIN%'
 29            )
 30         ORDER BY s.logon_time
 31  /

PROCESS LOGON                SID  SERIAL ACT SRV USER_NAME        IDLE
------- -------------------- ---- ------ --- --- ---------------- ------------
COMPUTER
----------------------------------------------------------------
10987   16 Oct 2003 10:10:49   19    252 INA NON MOD_APP_USER       0 22:14:55
citora14.COMP.com

10987   16 Oct 2003 10:21:38   46   4144 INA NON MOD_APP_USER       0 15:12:44
COMP\SYS10377

10991   16 Oct 2003 10:37:39   10   1910 INA NON MOD_APP_USER       0 21:52:16
COMP\SYS2707

10985   16 Oct 2003 11:29:45   39   4209 INA NON MOD_APP_USER       0 14:41:57
COMP\SYS10404

10991   16 Oct 2003 11:58:40   40   5767 INA NON MOD_APP_USER       0 18:19:28
COMP\SYS2668A

SQL>     SELECT    TO_CHAR(sid, '999')                 AS sid
  2            , TO_CHAR(piece, '999')                 AS line
  3            , cur_prev                     AS c
  4            , TRANSLATE(sql_text, CHR(9) || CHR(10) || CHR(13), '   ')
  5                                   AS sql
  6         FROM (
  7      SELECT    s.username                     AS username
  8            , 'c'                         AS cur_prev
  9            , s.sid                         AS sid
 10            , q.piece                      AS piece
 11            , q.sql_text                     AS sql_text
 12         FROM v$session s
 13            , v$sqltext q
 14         WHERE  s.sql_hash_value = q.hash_value
 15            AND s.sql_address = q.address
 16      UNION
 17      SELECT    s.username                     AS username
 18            , 'p'                         AS cur_prev
 19            , s.sid                         AS sid
 20            , q.piece                      AS piece
 21            , q.sql_text                     AS sql_text
 22         FROM v$session s
 23            , v$sqltext x
 24            , v$sqltext q
 25         WHERE  s.sql_hash_value = x.hash_value (+)
 26            AND s.sql_address = x.address (+)
 27            AND x.address IS NULL
 28            AND s.prev_hash_value = q.hash_value
 29            AND s.prev_sql_addr = q.address
 30           ) t
 31         WHERE ( username LIKE 'MOD%'
 32           OR username LIKE 'MAIN_ADMIN%'
 33            )
 34         ORDER BY sid, cur_prev, piece
 35  /

SID  LINE C SQL
---- ---- - ----------------------------------------------------------------
  10    0 c SELECT * FROM "APPS"."COMP_AMG_ORG_V" ORDER BY "ORG_TYP
  10    1 c E" ASC
  19    0 c update TABLE2 set TABLE2_STATUS_ID = :1, TABLE2
  19    1 c _CRITICALITY_ID = :2, DUE_BY_DATE = :3, DUE_BY_MEET
  19    2 c _ACTIVITY_ID = :4, STATE_SUBJECT_ID = :5, TABLE2_ACT
  19    3 c ION_ID = :6, OPEN_MEET_KSAT_ID = :7, CLOSE_MEET_KSAT
  19    4 c _ID = :8, OPEN_INSP_KSAT_ID = :9, CLOSE_INSP_KSAT_ID
  19    5 c  = :10, MEET_ITEM_ID = :11, CAF_ID = :12, MSI_CLAUSE
  19    6 c = :13, EQS_CLAUSE = :14, LEGACY_IND = :15, ITEM_NO = :16, DUE_BY
  19    7 c _YEVR_TYPE_ID = :17, TABLE2_MEET_ID = :18, PLAN_
  19    8 c ECCA_KSAT_ID = :19, PREVIOUS_TABLE2_ID = :20, ORIG
  19    9 c INAL_TABLE2_ID = :21 where TABLE2_ID = :22
  39    0 p SELECT rowid, "AGR"."CAF_AGR_IVER".* FROM "
  39    1 p AGR"."CAF_AGR_IVER"  Where  CAF_ID = 1
  39    2 p 12408

15 rows selected.

SQL> spool off

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

          Followup:

it is probably your connection pool.  nothing "strange" about that.  thats just
the last sql they executed.

      Rating: 5
Inactive
      sessions  October 20, 2003
Reviewer:
      A reader
Tom,
Do these inactive session lead to blocked sessions?

>>it is probably your connection pool.
Should anything be modified on the connection pool setup?

Thank you

          Followup:

not really -- it is doing what it is supposed to be doing -- pooling
connections, having the connections "already there"

      Rating: 5
Inactive
      sessions  October 20, 2003
Reviewer:
      A reader
Tom,
For how long after the SQLs have been executed will v$sqltext have these SQLs?
Is there anyway to prove that these inactive sessions with SQLs do not cause
blocking.  We are running into situations where we have blocked sessions once in
a while, and the DBA says that these inactive sessions with associated SQLs lead
to blocked sessions.

Thank you

          Followup:

could be years and years.
could be days
could be minutes

it is not really "relevant".

I never said an inactive session would not block someone -- they certainly and
surely can.  That means someone did a transction and forgot to commit (aka: a
bug in your code).

They can certainly hold locks.  The SQL -- that is meaningless -- you want to
look at v$lock and see what outstanding locks these inactive sessions may have.

      Rating: 5
Inactive
      sessions  October 20, 2003
Reviewer:
      A reader
Thank you

      Rating: 5
where are the
      comments in the code?  October 20, 2003
Reviewer:
      Joe  from PA
Tom,

The debug.f zip file that is posted on the debugf link contains sql with no
comments for the package body or anything else.  the code the user posted had
comments about how to use the package, did the reader add all the comments, or
have access to a version that contained them?

          Followup:

well, the comments are here, you can grab this version.

i honestly don't know where the verbose comments came from

      Rating: 5
To
      Joe:  October 20, 2003
Reviewer:
      A reader
Hi Joe
I *think* I got the debug.f from "Scripts" section of

http://osi.oracle.com/~tkyte/article2/index.html#Errors
I believe the latest version (published) is available
at:

"Beginning Oracle Programming" book's source code
at apress

http://support.apress.com/books.asp?bID=186100690x&s=0&Go=Select+Book
Not all comments are mine - I just added comments
for the new features I added to the debug.f.
Actually, the posting I had is an older version.
I emailed Tom a version with some enhancements
to it - he has been (understandably so) too caught up to
review it so far.

      Rating: 5
Inactive
      sessions  December 30, 2003
Reviewer:
      A reader
Reg. the Inactive sessions in the above posting
>>That means someone did a transction and forgot to commit

>>They can certainly hold locks.  The SQL -- that is meaningless -- you want to
>>look at v$lock and see what outstanding locks these inactive sessions may
have.

The inactive session is running a package having a couple of select statements.
Do we have to look at v$lock for select statements?
Will not closing a cursor/connection lead to an inactive sessions?

Thank you

          Followup:

select statements do not take locks. (unless they are for update and then they
are really update statements in disguise).

you want to look at the objects the session has locked.

an inactive session is just a session that is established and isnt currently
running any sql

      Rating: 4
Same kind of
      problem  January 06, 2004
Reviewer:  Sanjaya
      Balasuriya  from Colombo, Sri Lanka
Hi Tom,

I also have experienced such behavior.
In one of our customer sites, it take a long time to make the connection to the
database.Terribly a long time. 2,3 minutes.
But there after, the response for the applications is more or less OK.

What could be the possible reasons ?

Thanks in advance.

          Followup:

funny -- none of threads in here have to do with "taking a long time to connect"
(true, many refer to long connection times but in the context of "it was
connected for 48+ hours", not in the context of "it took 48+ hours to connect" )

Taking a long time to connect is many times due to improper DNS setup.  Use
tnsping, can you tnsping the listener fast or slow.  If you cannot tnsping it
fast, it indicates a network setup issue.

So, what is your tnsping time.

Also, whats in your sqlnet.ora on the clients. whats your version. whats your
os, etc etc etc.

      Rating: 4
Not related
      with memory ?  January 07, 2004
Reviewer:
      Sanjaya Balasuriya  from Columbo, Sri Lanka
Hi Tom,

Thanks for the guidance
And isn't this problem related with insufficient memory allocated for the
database ?
Since the memory from shared pool is used to establish a session (am I incorrect
?), can't be this happen due to not enough memory in the shared pool ?
I think so because this problem is occurred during the peak hours ?

Thanks in advance.

          Followup:

no, you'd get an "i'm so sorry, we are out of memory" error.

are you using shared server or dedicated server.

      Rating: 5
Additional
      information  January 10, 2004
Reviewer:  Sanjaya
      Balasuriya  from Colombo, Sri Lanka
Hi Tom,

Thanks for help.

And I'm using dedicated server.

          Followup:

nope, not an SGA issue  and not a shared server problem (could have been too few
shared servers)

dedicated server
slow down connecting during peak
sounds like could be overloaded machine.

      Rating: 5
Thanks a
      lot  January 10, 2004
Reviewer:  Sanjaya
      Balasuriya  from Colombo, Sri Lanka
Thanks Tom for help.

I'll check what I can do to reduce the load of the server.
The CPUs of the server are always over 90% utilized.

Thanks for the guidance.

      Rating: 5
url based
      debugging question  January 16, 2004
Reviewer:
      A reader
What happens when parameters are not passed through the
URL but are passed through POST? Any thoughts? How
do you turn on the debugging in this case?
thanx!

          Followup:

post and get are the same in mod_plsql as far as we are concerned.  what
differences do you see?  to me, well, in the mod_plsql routine I cannot really
tell the difference between the two.

      Rating: 5
thanx
      tom!  January 17, 2004
Reviewer:
      A reader
I have not tried it out - we are not using mod_plsql
though - j2ee based architecture (jsp/servlet) -
I will post my findings once I have done some experiments...

      Rating: 5
a
      question  January 23, 2004
Reviewer:
      A reader
Tom
Do you think it is a good idea to enable sql trace also
from the URL (based on client id and the new dbms_monitor/
end to end tracing facility in 10g.) This would enable us to generate trace
files for activities of one end user for rendering a particular url. Combined
with our instrumentation this could be very useful....What do you think?

          Followup:

i do it on every thing I build.

January 23, 2004 Tony -- Thanks for the question regarding "PCT
      FREE for Indexes", version 8.1.6

      You Asked
Hi Tom,
Lot has been said on this site and books about pctfree and pctused for creating
tables. What about pctfree for indexes? I'm working on Data Warehouse
application (no updates), it is easy to decide about pctfree and pctused for
data but I'm not sure, how should I decide about indexes?, and why oracle gives
default pctfree as 10 for indexes?
Your help will be highly appreciated.
Tony

      and we said...
pctfree only counts when BUILDING an index.  It reserves X% of the block for
subsequent inserts into that block (inserts into the block can arise from
inserts or UPDATES to the base table).

If you load and index and NEVER insert/update the table -- a pctfree of 0 would
be just fine.

All the pctfree does on an index is defer the splitting of an index block due to
subsequent inserts/updates AFTER the index is build.  10% is just a good
"starting point".
Review & Followup

      Rating: 3
PCTFREE for
      Indexes  January 11, 2002
Reviewer:  Tony
       from Australia
Hi Tom,
Thanks for response.
But just to be more specific:-
24 million rows (one million per month) in a monthly partitioned table, having
global and local indexes. Some are normal (B*Tree) and some are bitmap indexes.
About 40,000 rows are added (INSERTS) daily to the table, so I can't keep
PCTFREE 0. Okay, 10% is just the good starting point. But what is the algorithm
to calculate the best value, specifically when I know the distribution of data
in table-partitions-columns.
Tony

      Rating: 3
PCTFREE for
      indexes  January 11, 2002
Reviewer:  Tony
       from Australia
Hi Tom,
In addition to previous comments, while reading 'Oracle 8i Application
Developer's Guide', I found that:
--------------------
PCTFREE for Indexes
Indexes infrequently require the use of free space for updates to index data.
Therefore, the PCTFREE value for index segment data blocks is normally very low
(for example, 5 or less).
--------------------
It says 5 or less but ORACLE keeps default as 10!!!
Regards,
Tony

          Followup:

yes?  point is?  you don't like the default -- you set it.

10%, 5%, not a significant difference in the grand scheme of things.   I see no
conflict here between reality and the documentation.  10% is a fairly good rule
of thumb.  You set it when you need to or want to.

      Rating: 5
why there is no
      pctused for index?  May 03, 2002
Reviewer:
      A reader
Hi Tom

Why we cannot set pctused for index?

          Followup:

because it does not make sense (if you have my book, i go into detail on this)..

basically, a table is a "heap".  We stick data WHERE EVER.

an index is a structure, we have to stick data where it belongs.

When we take an index block off of the free list -- it is "empty".  We will then
totally fill it up until we have to split it.  It only goes back onto the free
list when it is empty again.  We cannot put a block on the freelist unless it is
empty -- the fact that the INDEX IS A STRUCTURE, prevents us from sticking just
any data on the block.  Only data that can go onto that block can go there.  It
is just "not a heap".

      Rating: 2
Some more
      details required  July 18, 2002
Reviewer:  Vinay
      Khandka  from Dearborn, MI USA
More more details with examples would have been helpful.

          Followup:

get my book then -- huge number of examples on over 1200 pages covering tons of
stuff.

      Rating:
      5
  July 18, 2002
Reviewer:  Harold
      Flores  from Managua, Nicaragua
Hello

Some oracle  instructor recently told me that an index copy the structure and
the data of the columns from the table where the index is use (???), that's why
an index will defrag and use some space from the datafiles. i dont belive that
is true, will you explain it to me.

Thanks in advanced.

Harold

          Followup:

I don't understand your statement above at all.  you'll need to be more clear.

      Rating:
      5
  July 20, 2002
Reviewer:  Harold
      Flores  from Managua, Nicaragua
It goes like this

1. I create an index  on column 1 and 2 over the table t1 (wich has 3 more
columns)
2. The index will copy the structure and the data of the columns where the index
whas created.

I do belive that the index will copy the estructure of the columns but not the
data. i hope i make myselgf clear.

Thanks.

          Followup:

Nope, still not understanding your point.

The index will copy the values of column1 and column2 and the rowid that points
to columns 1..5, sort them and store them.

I don't know what you mean by the "structure of the columns but not the data"

      Rating:
      5
  July 20, 2002
Reviewer:  Harold
      Flores  from Managua, Nicaragua
When i create an index will actualy copy all the values of the columns of the
table????

          Followup:

It will obviously copy the columns you are INDEXING -- but just those.  so, if
you have 5 columns and create an index on (c1,c2), c1 and c2 will be physically
copied into the index structure (how else could it work?)

      Rating: 5
I don't
      understand why an index block must be 100% empty before returned to free
      list.  August 13, 2002
Reviewer:  ken
       from Atlanta, GA USA
Tom,

You said:

. . . We will then totally fill it up until we have to split it.  It only goes
back onto the free list when it is empty again. . . .

I've read that second sentence in many places, but have never totally unsdrstood
the reason why it is true.  It seems like if we have an index on the ename
column of the emp table, and one of the blocks points to 100 or so rows (filling
this index-leaf block)for people with a name in the range of "SAWYER" to
"SWAZE", and then we delete about 75 of those rows from the table we would have
plenty of room to insert a value for the new employee "SMITH."  Why would we not
want to use this block for that entry?  Isn't that the "right" place for the
data to go?  Where will it go, then? A brand new block, or one that has not
reached PCTFREE?

I have re-read the B*Tree Index portion of your book and if this is addressed I
missed it.

Thanks!

          Followup:

The free list for indexes is DIFFERENT from the free list for tables.

Consider a HEAP table. You want to insert a row into it.  That row can go onto
virtually ANY block.  So, all blocks that have free space on them (sufficient
free space) are pointed to by a free list.  We goto the free list and find a
block and put the row there (maybe taking the block off of the free list if we
hit pctfree).

Now, consider an index.  You want to "insert a row into it".  That row MUST GO
IN A VERY VERY SPECIFIC LOCATION (an index is a data structure).  Hence, we find
that very very specific location in the data structure and one of two things
will be true:

a) the index block has room on it.  We use that room (pctfree is meaningless
here, we fill the block till it is totally full)

b) the index block has NO room on it, it is full.  We must split the block into
two to make room.  Hence we need a totally empty block (cannot have ANY data on
it as the data in an index has a specific place it must be!).  We goto the
freelist for the index which ONLY has totally empty blocks on it and insert that
block into the index structure at that point.

As for your question about "sawyer" to "swaze" and then the delete -- we most
CERTAINLY WOULD use that space for smith.  We don't look to the free list to put
smith in there -- smith HAS A SPECIFIC PLACE TO GO (between sawyer and swaze).
since there is space there (must be, we just freed up 75 slots right) -- smith
will go right there.

PCTFREE isn't considered in an index after the index is built.  PCTFREE is only
meaningful during an index build -- then we will reserve x% of the block for
furture updates.  After that -- we stuff those blocks as full as we can.

Read page 308 on...

      Rating: 5
You do address
      this, My apologies  August 13, 2002
Reviewer:
      ken  from Atlanta, GA USA
Sorry Tom,

I see that you do address the subject of my question in pages 309 - 311.

So "SMITH" would go into the block in question.  A block will be used if a row
comes along that will "fit" in it.

"Space is reused on index blocks as soon as a row comes along that can reuse
it."  pg 311.

So, at that time, is the block on the freelist?

          Followup:

No, the block isn't on the freelist -- we don't use pctfree for indexes AFTER
THE INDEX IS BUILT/REBUILT.

We just stuff those blocks as full as we can.

      Rating: 5
dml effects on
      an index  September 14, 2002
Reviewer:
      A reader
Hi Tom

from 8i Admin ILT Guide it states:

Effect of DML Operations on an Index
The Oracle server maintains all the indexes when DML operations are carried out
on
the table. Here is an explanation of the effect of a DML command on an index:
1.- Insert operations result in the insertion of an index entry in the
appropriate block.
2.- Deleting a row results only in a logical deletion of the index entry. The
space used
by the deleted row is not available for new entries until all the entries in the
block
are deleted.
3.- Updates to the key columns result in a logical delete and an insert to the
index. The
PCTFREE setting has no effect on the index except at the time of creation. A new
entry may be added to an index block even if it has less space than that
specified
by PCTFREE.

Point 2 it says that space is not reused unless all entries in the data block
are deleted does that mean that, for example if I have a PK in my EMP table if I
deleted a range of empno from 1 ~ 10 then I re-insert again will my new data
need a new data block for the index segmens considering that deleting 10 empnos
I have not deleted all entries in a data block?

In point 3 if I set 10 for PCTFREE I am reserving 10% of index structure (not
the data block as opposed for a table segment) for future inserts, i.e if my
index structure stores employee names and in one leaf block I stored names that
starts from A to B and that leaf block holds 100 employee names at time of
creation does that mean I have reserved space for further 10 more employee names
(10%)? Then if I need more than 10 the leaf will split. Am I correct?

          Followup:

what is an "ILT" guide.

It is obviously WRONG.  Would you like to prove it?  Just do this:

create table t ( x int );
create index t_idx on t(x);

insert into t values ( 1 );
insert into t values ( 2 );
insert into t values ( 9999999999999999 );
commit;

now, obviously 2 is between the other rows in the index right?  Ok, now run this
block:

begin
    for i in 2 .. 99999999999999
    loop
         delete from t where x = i;
         commit;
         insert into t values ( i+1 );
         commit;
    end loop;
end;
/

if that logically delete entry wasn't reused -- well, you would expect that
index to grow and grow and grow and grow during this operation right?  Well,
surprisingly (not) it does not grow at all.

The slot that was used by 2 is available to be used by ANY number between 1 and
99999999999999.  When 3 is put there and then deleted, any number between 1 and
99999999999999 can go there.

What they MIGHT mean to say is that the number -1 *might not be able to* use
that slot, and 99999999999999+1 *might not be able* to use that slot.  Any
number that falls between 1 and 99999999999999 however can.

Index space IS reused.

The INDEX block is never put back onto the FREELIST until all entries on the
block are freed (deleted, removed, whatever) but the free space on that index
block is most definitely used.  To get the LONG story with more examples, see my
book "Expert One on One Oracle" -- I cover this myth in depth.

You can test your theories about the empno 1-10 easily on your own systems --
give it a whirl.  The easiest way to bust a myth is to give a counter case!
I'll do this one for you:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table emp ( empno int constraint
emp_pk primary key, ename varchar2(30) );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into emp select rownum, username from
all_users;
46 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze index emp_pk validate structure;
Index analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select lf_rows, del_lf_rows from index_stats;

   LF_ROWS DEL_LF_ROWS
---------- -----------
        46           0

that shows there are 46 leaf row entries -- 46 index entries

ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from emp where empno <= 10;
10 rows deleted.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze index emp_pk validate structure;
Index analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select lf_rows, del_lf_rows from index_stats;

   LF_ROWS DEL_LF_ROWS
---------- -----------
        46          10

That shows there are still 46 entries HOWEVER, 10 of them happened to be
"deleted" -- not pointing to an active row in the table anymore...

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into emp select rownum,
lower(username) from all_users where rownum <= 10;

10 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze index emp_pk validate structure;

Index analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select lf_rows, del_lf_rows from index_stats;

   LF_ROWS DEL_LF_ROWS
---------- -----------
        46           0

Lo and behold, there are still only 46 leaf block entries (rows) and now there
are 0 deleted ones.  That proves that we'll reuse space.  Just to make it more
interesting, we'll do the same thing but we'll reuse the entries WITH DIFFERENT
values (not 1..10)

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from emp where empno <= 10;

10 rows deleted.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into emp select -rownum,
lower(username) from all_users where rownum <= 10;

10 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze index emp_pk validate structure;

Index analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select lf_rows, del_lf_rows from index_stats;

   LF_ROWS DEL_LF_ROWS
---------- -----------
        46           0

ops$tkyte@ORA817DEV.US.ORACLE.COM>

Now, we know the delete left us with 10 del_lf_rows, and the insert must have
reused them.

In point 3, if you set 10% free, you are saving 10% of each index block during
index creation for subsequent modifications just like a table.  Not 10% more
rows, 10% of the space.

      Rating: 5
ILT guide is
      those books  September 15, 2002
Reviewer:
      A reader
you get when you go to Oracle University, actually I got this paragraphes this
is from Oracle 8i Architecture and Administration Instructor Lead Tutorial... so
I guess it's wrong

I checked Oracle 9i DBA Fundamentals 1 ILT guide now and it states the
samething!

Time to correct them?

          Followup:

Please do -- it is a very in-accurate portrayal of what really happens.  It is a
myth.

      Rating:
      5
Reader  September 15, 2002
Reviewer:
      A reader
SQL> drop table emp;

Table dropped.

SQL> create table emp ( empno int constraint emp_pk primary key, ename
varchar2(30) );

Table created.

SQL> insert into emp select rownum, owner from dba_objects;

3260 rows created.

SQL> insert into emp select rownum+3260 , owner from dba_objects;

3260 rows created.

SQL> insert into emp select rownum+6520 , owner from dba_objects;

3260 rows created.

SQL> commit;

Commit complete.

SQL> select min(empno), max(empno) from emp;

MIN(EMPNO) MAX(EMPNO)
---------- ----------
         1       9780

SQL> analyze index emp_pk validate structure;

Index analyzed.

SQL> set pages 9999
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
-------------------- ---------- ------------
         2         24 EMP_PK
                                     9780         18      136724       8000
        17          1         170       8032           0               0
         9780                 1      152032     136894         91            1
                   3          0            0

I deleted 6519 rows , however index_stats shows 5948.
======================================================

SQL> delete from emp where rownum < 6520;

6519 rows deleted.

SQL> commit;

Commit complete.

SQL> select min(empno), max(empno) from emp;

MIN(EMPNO) MAX(EMPNO)
---------- ----------
      6520       9780

SQL> analyze index emp_pk 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
-------------------- ---------- ------------
         2         24 EMP_PK
                                     9209         18      128736       8000
        17          1         170       8032        5948           83114
         9209                 1      152032     128906         85            1
                   3          0            0

I insert only 3260 rows and index_stats shows 0 del_lf_rows and these rows are
not
=================================================================================
=
inserted in the same place where rows were deleted either
==========================================================
SQL> insert into emp select -rownum, owner from dba_objects;

3260 rows created.

SQL> commit;

Commit complete.

SQL> analyze index emp_pk 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
-------------------- ---------- ------------
         2         24 EMP_PK
                                     6521         19       94391       8000
        18          1         178       8032           0               0
         6521                 1      160032      94569         60            1
                   3          0            0

SQL> select min(empno), max(empno) from emp;

MIN(EMPNO) MAX(EMPNO)
---------- ----------
     -3260       9780

          Followup:

Use my showspace.sql script to see the blocks go onto the freelist.

When you did your delete, some of the blocks were put back onto the freelist
(hence the differing number of del_lf_rows). They were some of the last fully
emptied blocks (they are put on the freelist)

The rest went on there the next time you made us play around with the structure.

Just more evidence that index space is reused.

      Rating: 5
pctfree and
      freelists and indexes  October 07, 2002
Reviewer:
      A reader
hi

how does freelists work with index blocks?

In data blocks when the block usage reaches pctfree then it´s unlinked from
freelists and when usage falls below pctused it´s linked to freelists again.

Since index blocks we dont have pctused how is it linked to freelists and
unlinked?

          Followup:

if you clear out a block, removing all of the index entries on it via an update
or delete, it'll be placed on the freelist for the index and subsequently reused
later when a new index block is needed in the structure.

      Rating: 4
Q on PCTFree /
      PCTUsed  January 10, 2004
Reviewer:  Anil
      Pant  from India
Hi,
If I've primary or unique key index (no null values even if unique) on a column
C1. Also Im very sure this column values will never change. Then for the Index
in this case I can give PCTFree and PCTUsed both as 0. Can I do it ?

          Followup:

do they values arrive in random order or sequentially.

And remember -- pctfree only counts during INDEX CREATION time, never after
that.

And remember -- pctused is meaningless in the context of an index block.

So, pctfree = 0 if the data arrives "in order" (eg: the newly inserted data in
this index will always be GREATER THAN or LESS THAN all of the values that
existed at index creation time)

pctfree = N and N > 0 if the data arrives randomly (save some room on the index
blocks in the newly created index for data that needs to "fit there")

      Rating: 5
For data
      arriving in order...  January 10, 2004
Reviewer:
      Connor  from Oz
Even with pctfree=10, won't Oracle try to do "the right thing by us" by stuffing
the blocks full and then splitting at the 99% mark (not the 90% mark - putting
the aside the recent oak table discussion about some possible bugs in this
area).

Admittedly then if you did rebuild the index you would then take the 10%
freespace hit for nothing - but of course, no one should be rebuilding this
index anyway :-)

          Followup:

but the original index create (which is the only thing that looks at pctfree)
will leave the original blocks 90% full

if the data arrives in order, when building the index, just build it "packed"
100% full since you'll never be splitting those leaf blocks from the original
data.

      Rating: 5
Applicability
      to Index-Organized Tables  January 12, 2004

Reviewer:  Doug Wingate  from New Orleans, LA USA

I skimmed through the discussion and didn't see that anyone has mentioned
index-organized tables up to this point.  Do the points about PCTFREE and
PCTUSED apply to IOTs just as they do to ordinary B*tree indexes?

          Followup:

pctfree/pctused for iots are the same as for b*trees.

pctused = meaningless.
pctfree = used during create/rebuild

      Rating: 4
Just to
      clarify...  January 23, 2004
Reviewer:  Jeff
      W.  from Rochester, NY
Lets say I am indexing a varchar2 field and I update the indexed column value
such that it will not fit in the current block.  I assume this causes the leaf
block to split, same as it would if I tried to insert a row that would not fit
in its appropriate leaf block.

The reason I ask is we have some DB2 customers and one of them read told us a
field we have defined as a varchar2(30) field that they only populate with a max
of 10 chars is indexed in DB2 as a char(30).  I found this sort of odd that they
would allocate space not needed.

I want to verify that Oracle does not behave in this manner.

          Followup:

updates like that in the index are done sort of like a "delete + insert" --
index entries generaly MOVE blocks when modified.

Oracle stores varchar2's as varchar2's.  varying length -- not fixed.

January 23, 2004 Dheeraj -- Thanks for the question regarding
      "Concatenate desired columns for multiple rows based on key fields",
      version 8.1.7

      You Asked
Hi Tom,

I have four tables as follows -
Table - User, Responsibility, Application, Session

User has following columns along with sample data -

UserId UserName

Uid1   Tom
Uid2   Sam
Uid3   Jack

Responsibility has following columns along with sample data -

RespId RespName

Resp1   Admin
Resp2   SystemUser
Resp3   EndUser

Application has following columns along with sample data -

AppId AppName

App1   Oracle
App2   Java
App3   Ms

Session has following columns along with sample data -

SessionId UserId  RespId  AppId

Session1   Uid1   Resp1   App1
Session1   Uid1   Resp1   App2
Session1   Uid1   Resp2   App3
Session2   Uid2   Resp1   App1
Session2   Uid2   Resp2   App1
...
...
and so on.

Now my requirement is that desired result should look like -

SessionId    UserId    RespId         AppId
Session1     Uid1      Resp1, Resp2   App1, App2, App3
Session2     Uid2      Resp1, Resp2   App1
...
...
and so on.

any clues or suggestions how to go about it.

Thanks in anticipation
Dheeraj

      and we said...
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:229614022562
you have to write a function in 8i.  You can use collections as well if you
like....

scott@ORA817DEV> create or replace type TblType as table of varchar2(30)
  2  /

Type created.

scott@ORA817DEV>
scott@ORA817DEV> select deptno,
  2         cast( multiset( select distinct job
  3                           from emp
  4                          where emp.deptno = dept.deptno ) as TblType ) jobs,
  5         cast( multiset( select distinct ename
  6                           from emp
  7                          where emp.deptno = dept.deptno ) as TblType )
enames
  8    from (select distinct deptno from emp ) dept
  9  /

    DEPTNO
----------
JOBS
---------------------------------------------------------------------------------
--------------------------------------------------
ENAMES
---------------------------------------------------------------------------------
--------------------------------------------------
        10
TBLTYPE('CLERK', 'MANAGER', 'PRESIDENT', NULL)
TBLTYPE('CLARK', 'KING', 'MILLER', NULL)

        20
TBLTYPE('ANALYST', 'CLERK', 'MANAGER', NULL)
TBLTYPE('ADAMS', 'FORD', 'JONES', 'SCOTT', 'SMITH', NULL)

        30
TBLTYPE('CLERK', 'MANAGER', 'SALESMAN', NULL)
TBLTYPE('ALLEN', 'BLAKE', 'JAMES', 'MARTIN', 'TURNER', 'WARD', NULL)

as well...

Review &
      Followup

      Rating: 3
Concatenate
      desired columns for multiple rows based on key
      fields  January 23, 2004
Reviewer:  Dheeraj
       from India
Hi again,

Solution definately helped me out. One mre thing in continuation with the same.
When I use custom datatype am forced to right a function for converting it into
VARCHAR so that to access the same query results from other 4GL's like Java and
C++..
Just want to confirm is this the only way or there is some better way out which
am not aware of?
Thanks once again,
Dheeraj

          Followup:

4gls like java???  java and c++ are definitely in the 3gl camp :)

but anyway -- both java and c++ can definitely access collections.  it is fully
documented.  java and c++ can in fact SEND and RECIEVE collections.

another method would/could be:

scott@ORA817DEV> select deptno,
  2         cursor( select distinct job
  3                   from emp
  4                  where emp.deptno = dept.deptno ) jobs,
  5         cursor( select distinct ename
  6                   from emp
  7                  where emp.deptno = dept.deptno ) enames
  8    from (select distinct deptno from emp ) dept
  9  /

here each row returns two result sets in addition to the scalar data.

      Rating: 5
how
      about...  January 23, 2004
Reviewer:  Lynden
       from North Haven, CT
select sessionid
     , userid
     , stragg(distinct respid)
     , stragg(distinct appid)
 from sessions
group by session_id, userid;

Tom, would it work?

This is a great site, Tom!

          Followup:

in 9i, yup -- this was specifically for 8i

January 23, 2004 sushil -- Thanks for the question regarding
      "intermedia text search", version 8.1.6

      You Asked
Hi Tom,

I have problem with intermedia search.
I have a column which has char "#".
I know that we have to remove this non alphanumeric character
from stopwords list using ctx_ddl.set_attribute('Lexer Name','printjoins','#').

I have done this. After that when queriny like where contains(column_name ,'#')
> 0 it returns many rows.No doubt that it filters most of the records but still
there are many records which does not hold '#'.

Then as per your suggestion last time with issue regarding "," I used query like
where contains(column_name,'\#') > 0 but still the records werenot appropriate.
On using an additional clause of instr(column_name,'#') >0 I am getting
appropriate records. Can u please tell me what needs to be done.

Tks
sushil.

      and we said...
Ahh, but do you have a row with "#" as an entire word???

You see, if you run "contains( cname, 'hello' ) > 0", that finds all rows that
contain the word hello.  Do you have a row with '#' as a word?  If you do --
it'll work. If you do not -- contains isn't right for what you are doing --
instr is.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( data varchar2(50) );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 'How#Now#Red#Cow' );
1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 'How Now Red Cow' );
1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 'How # Now Red Cow' );
1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec ctx_ddl.drop_preference('my_lexer');
PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2      ctx_ddl.create_preference('my_lexer','BASIC_LEXER');
  3      ctx_ddl.set_attribute('my_lexer','printjoins','#');
  4  end;
  5  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_ind_ctx on t(data) indextype
  2  is ctxsys.context parameters('lexer my_lexer');

Index created.

no here are all of the tokens we have parsed as "words"

ops$tkyte@ORA817DEV.US.ORACLE.COM> select token_text from DR$T_IND_CTX$I;

TOKEN_TEXT
----------------------------------------------------------------
#
RED
COW
HOW
HOW#NOW#RED#COW
NOW

6 rows selected.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where contains( data, 'How' )
 > 0;

DATA
--------------------------------------------------
How Now Red Cow
How # Now Red Cow

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where contains( data, '#' ) >
0;

DATA
--------------------------------------------------
How # Now Red Cow

Review &
      Followup

      Rating:
      4
Doubt  January 14, 2002
Reviewer:
      Ak  from India
Hi,
 SQL> drop table t;

Table dropped.

SQL> create table t ( data varchar2(50) );

Table created.

SQL> insert into t values ( 'How#Now#Red#Cow' );

1 row created.

SQL> insert into t values ( 'How Now Red Cow' );

1 row created.

SQL> insert into t values ( 'How # Now Red Cow' );

1 row created.

SQL> commit;

Commit complete.

SQL> exec ctx_ddl.drop_preference('my_lexer');
BEGIN ctx_ddl.drop_preference('my_lexer'); END;

      *
ERROR at line 1:
ORA-06550: line 1, column 7:
PLS-00904: insufficient privilege to access object CTXSYS.CTX_DDL
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

SQL> conn sys/sys;
Connected.
SQL> grant dba to TEST;

Grant succeeded.

SQL> conn TEST/TEST;
Connected.
SQL>  exec ctx_ddl.drop_preference('my_lexer');
BEGIN ctx_ddl.drop_preference('my_lexer'); END;

*
ERROR at line 1:
ORA-20000: ConText error:
DRG-10700: preference does not exist: my_lexer
ORA-06512: at "CTXSYS.DRUE", line 122
ORA-06512: at "CTXSYS.CTX_DDL", line 80
ORA-06512: at line 1

SQL> begin
  2          ctx_ddl.create_preference('my_lexer','BASIC_LEXER');
  3          ctx_ddl.set_attribute('my_lexer','printjoins','#');
  4      end;
  5  /

PL/SQL procedure successfully completed.

SQL>  create index t_ind_ctx on t(data) indextype
  2      is ctxsys.context parameters('lexer my_lexer');
 create index t_ind_ctx on t(data) indextype
*
ERROR at line 1:
ORA-29855: error occurred in the execution of ODCIINDEXCREATE routine
ORA-20000: ConText error:
DRG-10503: table TEST.T cannot be indexed because it has no primary key
ORA-06512: at "CTXSYS.DRUE", line 122
ORA-06512: at "CTXSYS.TEXTINDEXMETHODS", line 34
ORA-06512: at line 1

SQL> alter table t add constraints pk_t primary key (data);

Table altered.(Why should i have primary key here ??)

SQL> create index t_ind_ctx on t(data) indextype
  2     is ctxsys.context parameters('lexer my_lexer');
create index t_ind_ctx on t(data) indextype
*
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

SQL> select index_name from user_indexes;

INDEX_NAME
------------------------------
T_IND_CTX

SQL>  select token_text from DR$T_IND_CTX$I;

no rows selected

SQL>  select * from t where contains( data, 'How' ) > 0;
 select * from t where contains( data, 'How' ) > 0
                                       *
ERROR at line 1:
ORA-20000: ConText error:
DRG-10599: column is not indexed

----You can see above,index is available..but it says not indexed..why ??

Can I have your comments on above please ??

Thanks,
  Regards
    Ak

          Followup:

umm -- obviously you need a primary key in your release and I do not.

add a primary key to the table.

      Rating: 3
Agreed but not
      fully satisfied  January 14, 2002
Reviewer:
      Sushil Vyas  from India
Tom,

What u have said is new and also understood the same.
Now in ur last query when u say contains(data,'#') you get only those rows which
has got '#' sign.What about I getting few more rows additionally which do not
have hash sign at all.
The difference here I see is that I am using more than one printjoins like

ctx_ddl.create_preference('my_lexer','BASIC_LEXER');
ctx_ddl.set_attribute('my_lexer','printjoins','#');
ctx_ddl.set_attribute('my_lexer','printjoins','_');
ctx_ddl.set_attribute('my_lexer','printjoins',',');

Is this a problem, if yes than how do we take care of search when there could be
all these nonalphanumeric characters in a field.

Tks Sushil.

          Followup:

Only the LAST printjoins is taking effect.

You want one call:

ctx_ddl.set_attribute('my_lexer','printjoins','#_,');

not three as they just overwrite each other.

      Rating: 4
why this
      error  January 16, 2002
Reviewer:  Ak
       from India
Hi tom,
  I tested again after adding primary key ,i am getting this
error again.

SQL> conn TEST/TEST;
Connected.
SQL> desc t
 Name                                                  Null?    Type
 ----------------------------------------------------- --------
--------------------------------
 DATA                                                  NOT NULL VARCHAR2(50)

SQL> drop table t;

Table dropped.

SQL>
SQL>
SQL> create table t ( data varchar2(50) primary key);

Table created.

SQL> insert into t values ( 'How#Now#Red#Cow' );

1 row created.

SQL> insert into t values ( 'How Now Red Cow' );

1 row created.

SQL> insert into t values ( 'How # Now Red Cow' );

1 row created.

SQL> exec ctx_ddl.drop_preference('my_lexer');

PL/SQL procedure successfully completed.

SQL>  begin
  2          ctx_ddl.create_preference('my_lexer','BASIC_LEXER');
  3          ctx_ddl.set_attribute('my_lexer','printjoins','#');
  4      end;
  5  /

PL/SQL procedure successfully completed.

SQL> create index t_ind_ctx on t(data) indextype
  2      is ctxsys.context parameters('lexer my_lexer');
create index t_ind_ctx on t(data) indextype
*
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

SQL> select index_name from user_indexes where index_name like 'T_IND_CT%';

INDEX_NAME
------------------------------
T_IND_CTX

SQL> select token_text from DR$T_IND_CTX$I;

no rows selected

SQL>  select * from t where contains( data, 'How' )
  2   > 0;
 select * from t where contains( data, 'How' )
                                       *
ERROR at line 1:
ORA-20000: ConText error:
DRG-10599: column is not indexed

SQL> SELECT COUNT(*) FROM T;

 COUNT(*)
---------
        3
please comment.

Ak

          Followup:

uh-huh,  so did you search this site for

ORA-28575

the error you are getting -- it'll show you how to configure this.  You just
don't have the normal setup for interMedia completed.

      Rating: 4
Help me for
      searching strings containing some special
      characters  January 23, 2004
Reviewer:
      Sanjay  from Toronto
Tom,

I have a table which contains strings having some special characters. I did the
steps as outlined above but still getting error. Please help.

I am getting problem for '&' character, please see at the extreme end.

tester@test8i> create table abc (text varchar2(4000));

Table created.

tester@test8i> insert into abc values('$60,000 to 80,000');

1 row created.

tester@test8i> insert into abc values('Assistant Controller/Comptroller');

1 row created.

tester@test8i> insert into abc values('Communication - Written');

1 row created.

tester@test8i> insert into abc values('Financial Services (Banks and Trusts)');

1 row created.

tester@test8i> set escape \
tester@test8i> insert into abc values('Financial Services (Insurance \&
Others)');

1 row created.

tester@test8i> set escape off;
tester@test8i> exec ctx_ddl.drop_preference('my_lexer');

PL/SQL procedure successfully completed.

tester@test8i> begin
  2  ctx_ddl.create_preference('my_lexer','BASIC_LEXER');
  3  ctx_ddl.set_attribute('my_lexer','printjoins','$_-#@*&^%/\()');
  4  end;
  5  /

PL/SQL procedure successfully completed.

tester@test8i> create index SEARCH_TEXT_IDX on
  2  abc(TEXT)
  3  indextype is ctxsys.context parameters('lexer my_lexer');

Index created.

tester@test8i> select * from abc where contains(text, '$60,000 to 80,000')>0;

TEXT
---------------------------------------------------------------------------------
-------------------
$60,000 to 80,000

tester@test8i> select * from abc where contains(text, 'Assistant
Controller/Comptroller')>0;

TEXT
---------------------------------------------------------------------------------
-------------------
Assistant Controller/Comptroller

tester@test8i> select * from abc where contains(text, 'Communication \-
Written')>0;

TEXT
---------------------------------------------------------------------------------
-------------------
Communication - Written

tester@test8i> select * from abc where contains(text, 'Financial Services (Banks
and Trusts)')>0;
select * from abc where contains(text, 'Financial Services (Banks and
Trusts)')>0
*
ERROR at line 1:
ORA-20000: interMedia Text error:
DRG-50900: text query parser error on line 1, column 38
DRG-50920: part of phrase not itself a phrase or equivalence

tester@test8i> select * from abc where contains(text, '{Financial Services
(Insurance & Others)}')>0;
Enter value for others:
old   1: select * from abc where contains(text, '{Financial Services (Insurance
& Others)}')>0
new   1: select * from abc where contains(text, '{Financial Services (Insurance
)}')>0

no rows selected

          Followup:

set define off

& is a sqlplus (not SQL) special character by default, just set define off in
sqlplus.

January 23, 2004 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 !

      Rating: 4
Data
      Manuplation in %ROWTYPE  January 23, 2004

Reviewer:  Jack Anderson  from Atlanta, GA

Hey Tom,
i would like to perform the following task in a procedure.

cursor c1 is select A, B, C, D from tab100;

TYPE s1 IS TABLE OF c1%ROWTYPE NOT NULL
  INDEX BY BINARY_INTEGER;

  GOOD S1;

BEGIN
    FORALL indx IN good.FIRST .. good.LAST
       UPDATE tab200
       SET col1 = ***
       col2 = 'YES', col3 = '1'
        WHERE col4 = TO_CHAR(good (indx))
      AND col5 = 2;

     COMMIT;
END;

I would like to substitute *** with C that is available in the GOOD record.
how can i do that? is it possible?
please let me know
thanks

          Followup:

you'd have to fetch into scalar arrays -- not an array of records.  the forall
arrays must be "compact" -- scalar arrays.  not array of records.

January 24, 2004 Keith -- Thanks for the question regarding
      "DBMS_JOB", version 8.1.7

      You Asked
Tom,

I have a job (dbms_job) running (Win2000 w/ 8.1.7) that executes a procedure to
update a column (on_sale_flag) value to 'false' if the offsale date
(sale_end_date) is less than the sysdate. The job is supposed to run every hour
on the hour. If I run the procedure independently, the update is immediate. When
the job runs the procedure, it takes minutes for the update to occur throwing
off the update schedule. The job is not broken and has not had any failures. Can
you tell me why it takes so long for the job to run the procedure and what I can
do to make the job run faster? We were previously using perl scripts and Win2000
AT scheduler to accomplish this task but for some reason the AT scheduler on the
server does not want to work for more than 24 hours so this unfortunately is no
longer an option. As always, thanks.

Keith

      and we said...
do you know that the job runs slower?

Look at the TOTAL_TIME, remember that time, after the next run -- look at total
time again.  The difference will be the runtime.

What is more likely happening is "slippage".  The job queues are inspected every
N seconds where you control N (job_queue_interval).  If this is set to 60
seconds for example -- then your job might run at 12:01 instead of 12:00.  If
you compute the next interval like this:

  'sysdate+1/24'

your job might next be scheduled for 1:02  (see job NEXT_DATES are computed
BEFORE the job runs -- that is why I doubt the length of the job runtime has
anything to do with this.  If the job took 5 minutes -- it would not matter, the
next_date was already computed).

When the job runs next -- it might be 1:03 -- the next date will be 2:03.  At
2:04 it gets run and so on.  Over time -- it slips.

So, how to get it as close to the hour on the hour?  Use a FIXED computation on
time -- not a sliding one.  Use:

  'trunc(sysdate,''hh'')+1/24'

then, when the job does run at 12:01 -- the NEXT_DATE it computes will be 1:00
-- NOT 1:01.  It won't slip anymore.

Also bear in mind that if you use jobs lots -- your job_queue_processes could be
coming into play as well.  If a job is running at 1pm (not your job, some other
job) and you have job_queue_processes = 1, then you will WAIT until that job is
done.  Just increment your job_queue_processes.

If you really need the job to run at exactly the hour on the hour -- schedule
the job a little BEFORE the hour.  then, have it sleep for N seconds.  Eg: say
your job_queue_interval is 60 (1 minute -- very reasonable).  You might schedule
the job to run at xx:58 instead of xx:00 using:

   'round(sysdate,''hh'')+58/24/60'

What that will do is when its just before/after the hour, it'll round the time
to the nearest hour and add 58 minutes:

  1* select sysdate, round(sysdate,'hh')+58/24/60 from dual
ops$tkyte@ORA817.US.ORACLE.COM> /

SYSDATE              ROUND(SYSDATE,'HH')+
-------------------- --------------------
24-aug-2001 18:58:16 24-aug-2001 19:58:00

Now, your job should:

   n := (round(sysdate,'hh')-sysdate)*24*60*60;
   if ( n > 0 )
   then
         dbms_lock.sleep(n);
   end if;

that way, if it is just a little before the appointed hour, it'll wait and then
run.  If it is on the hour or just after -- it'll run right away.

Review &
      Followup

      Rating:
      5
dbms_job  January 23, 2004
Reviewer:
      Phil  from UK
Hi Tom,

Just a quick bit on clarification of intervals please if you have a minute. We
want to run jobs every 15 seconds, would it be better to have a process that
somehow continually runs? EG, we have a dbms_job to run every 10 minutes or so
which checks the procedure is running and if not starts it. And the procedure
itself has the following at the bottom following which it just calls itself
(actually is this allowed in a dbms_job?) so that the 15 seconds is guaranteed
and not a function of running the dbms_job so quickly?

Thanks!

 begin
 sp body...
 n := (round(sysdate,'hh')-sysdate)*24*60*60;
    if ( n > 0 )
    then
          dbms_lock.sleep(n);
    end if;
 start sp again...
 end;

(But set for 15 seconds - sorry not sure on the n:=?? section) that waits for 15
secs? Actually, we'd have to alter that a bit as the SP may take 1-3 secs to
run.

I see this making the running of the process more controlled as the
job_queue_interval of 15 looks like it is a bit close to the max we can have? I
notice in 9i we don't actually have the interval parameter any more?

          Followup:

why/what are you doing every 15 seconds?

      Rating: 1
Job process
      killed. But still show the job running  January 24, 2004

Reviewer:  Prince Faran  from Pakistan
Respected Tom!

(Oracle9i Release 2 on Windows 2000)

I have two following jobs are running to replicate the data.

SQL> SELECT SID, r.JOB, LOG_USER, r.THIS_DATE, r.THIS_SEC
  2  FROM   DBA_JOBS_RUNNING r, DBA_JOBS j
  3  WHERE  r.JOB = j.JOB;

       SID        JOB LOG_USER    THIS_DATE THIS_SEC
---------- ---------- ----------- --------- --------
       129          6 REPADMIN    23-JAN-04 19:16:28
        51          9 REPADMIN    24-JAN-04 14:26:15

One job has been running from yesterday. But there are lot of transactions
pending in the queue. I have seen the status of this SID i.e. 129 => this
session has put the locks. Then I have killed the session. It is marked as
Killed.

SQL> EXECUTE dbms_job.run(6);

PL/SQL procedure successfully completed.

SQL> SELECT SID, r.JOB, LOG_USER, r.THIS_DATE, r.THIS_SEC
  2  FROM   DBA_JOBS_RUNNING r, DBA_JOBS j
  3  WHERE  r.JOB = j.JOB;

       SID        JOB LOG_USER    THIS_DATE THIS_SEC
---------- ---------- ----------- --------- --------
       129          6 REPADMIN    23-JAN-04 19:16:28
        51          9 REPADMIN    24-JAN-04 14:26:15

But the job transactions from this job# 6 are not being applied ...

One thing I have to resolve the issue is to restart the database. It releases
the Job Locks and then re-establish the session and it takes a minute to
propagate the transactions queued by job# 6. My questions is "How can I avoid
the restart to release the resources?"

Regards,

Faran

      Rating:
      4
dbms_job  January 24, 2004
Reviewer:
      Phil
Tom,

The job is inspecting a target table for certain status changes etc. We can not
touch this system so can't use a trigger/snapshot afraid.

The design is more or less finalised, we just need to see if we could use a
dbms_job less often to just ensure a process is running (the SP would run
continually) rather than use a dbms_job running every 15 secs. Would a SP that
runs and sleeps at the end, restarting itself be OK in your opinion?

          Followup:

why cannot you use the most effective methods?  do you want to really hurt the
system or just impact it a tiny bit?  oh well.  query like heck away....

I would not run an infinite job in the job queue.  I'd have it execute maybe 10
times or so

create procedure this_procedure
as
begin
   for i in 1 .. 10
   loop
       ... do whatever ...
       dbms_lock.sleep(15);
   end loop;
   dbms_job.submit( l_job, 'this_procedure;' );
   commit;
end;

that'll give you a chance to shutdown every now and again "nicely" if you need
and prevent any issues with long running procedures (memory buildup, whatever)

      Rating: 5
dbms_job
        January 25, 2004
Reviewer:  Phil

I totally agree with you on being a shame we can not do this in a more
integrated manner but it has been imposed on us - thanks for your advice, that
makes a lot of sense about limiting it to a number of iterations.

January 24, 2004 -- 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

      Rating: 3
How can verify
      table segment  January 24, 2004
Reviewer:
      Yousef  from Kuwait
Dear Tom:
how can I use the dbv to verify table segment, I wen to the documentation but,
there was no example on it, the say
This mode requires that you specify a segment (data or index) to be validated.
It
also requires that you log on to the database with SYSDBA privileges, because
information about the segment must be retrieved from the database.
During this mode, the segment is locked. If the specified segment is an index.
how can i specify the userid, and the segement_id parameters

          Followup:

if you want to validate a segment use:

ANALYZE TABLE validate structure <cascade>
or
analyze index validate structure

to do that.

otherwise you need to know way too many details.  support would help you run dbv
with the right inputs if you get that deep into the weeds.

but, if you really wanted:

Example:
-------

SQL> select t.ts#, s.header_file, s.header_block
  2  from v$tablespace t, dba_segments s
  3  where s.segment_name='TAB1'
  4  and t.name = s.tablespace_name;

       TS# HEADER_FILE HEADER_BLOCK
---------- ----------- ------------
         2           5        37767

% dbv userid=system/manager SEGMENT_ID=2.5.37767

January 25, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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.

      Rating: 5
RMAN COPY
      command question  January 23, 2004
Reviewer:
      SR
RMAN> copy datafile 10 to '/home/tools1.dbf' nochecksum

before I issue this command, do I need the tablespace in hot backup mode? I
thought its not needed. If you can clarify that will be great.

Doesn't this mean, even those who really do not use RMAN, can benifit from RMAN
because now they can make O/S copies without setting full tablespace (sometimes
tablespaces with 200 data files!) in hot backup mode! I mean this feature is
just too good. Please say its true, I am counting on it.

Actually, I tried to copy the file to a os pipe but somehow RMAN created a file
instead (by removing the pipe, bummer, I was going to .Z it on the fly).

Another realted question: Will this command work in logical standby? So that I
do my backup

Thanks a lot

          Followup:

rman, since it understands Oracle datafiles, does not need to place tablespaces
into hot backup mode to make hot backups.

The reason for backup mode is to include extra logging on the tablespaces in
backup mode.  This is to compensate for OS utilities that just blinding read the
datafiles without understanding them.  The problem is Oracle manages blocks at
say "8k at a time" whereas the OS might be managing them at "512bytes at a
time".  There is a good chance in that case on an active system that the OS copy
could start reading a block at time T1 -- get the first half of it -- and then
at time T2 the OS rewrites the tail of the block (dbwr checkpointed) -- and at
time T3 the OS copy gets a tail of the block that doesn't match the head.  This
is a fragmented block.  We can fix this by having extra redo generated.

RMAN is smart enough to look at the Oracle blocks and make sure this does not
happen and if it does -- it will reread the block to get it correctly.

      Rating: 5
Understood!
      Thanks  January 24, 2004
Reviewer:  SR

In summary

RMAN COPY command rocks!

Thanks!

      Rating: 5
RMAN COPY
      command question   January 24, 2004
Reviewer:
      Reader
One should remember that if COPY command is used, you have to issue 100 Copy
statements to take image copies of 100 datafiles in a tablespace. Instead, it is
more convenient to use BACKUP TABLESPACE command.

      Rating: 5
regarding
      comments from another reader  January 24, 2004

Reviewer:  A reader
I think you are missing the point. 100 copy commands, but NO extra redo log,
which is what you get in tablespace backup mode. You can very easily generate
100 commands automatically.

          Followup:

nope, you missed it.

instead of 100 commands we could do "backup database" or "backup tablespace" --
with the same "no extra logging stuff" -- you don't need to do file by file,
rman can make it much easier than that.

      Rating: 5
may be but
      ...  January 24, 2004
Reviewer:
      A reader
Tom,

I may have missed some point. But, COPY command generated "copy" is as good as
O/S copy in the sense, it is not in propritory format! Since we were talking of
COPY command and not backup command, I thought copy using RMAN is less load on
database than using o/s copy command as its doesn't need tablespace in backup
mode.

what am i missing?

Thanks for the information

          Followup:

it is as bad as the OS copy as well since it is not in the "proprietary" format!
 I'd rather have a single backupset to deal with versus a hundred tiny
bothersome files.

Use RMAN as it was designed and intended for and you'll be much happier in the
end.  It is a tool to help you automate the book-keeping you would have to do
yourself.

It is not that the files are in a proprietary format -- not any more or less
than a .dbf file is in the first place!  It isn't like you are going to feed
them to db2 or anything.  They are good for one thing and one thing only --
giving back to Oracle.

January 25, 2004 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;

      Rating: 2
Oracle Triggers
      invoking a java class or servlet or component  January 14,
      2004
Reviewer:  Ram  from USA
I am new to oracle. And want to know can Oracle triggers invoke a java class(
can be a servler, class or component). If so can you help me in this. It is
urgent

          Followup:

sure, the trigger can call any java STORED PROCEDURE, which could call a jsp or
anything

would it be "slow"
would it be "wrong"
would it be "not a good idea"
would it be "totally non-transactional" (hence having the ability to destroy
data integrity)

yes to all of the above.

      Rating: 2
Oracle Triggers
      invoking a java class or servlet or component   January 14,
      2004
Reviewer:  Ram  from USA
yes if it is a bad idea . It is Ok. My idea is to implement connection pooling
between two machines. THe client is independent from which machine the response
is coming. But in my case the session is dying. So I want to create a class and
call that class in trigger , so that session can be maintained whatever the
server may be. Is there any better idea to implement this rather than thinking
it is bad idea.
I searched in google , found like we can use loadjava .....
. It is giving tablename space  is not sufficient.
 Is there any alternative.

          Followup:

er?

no idea where you are going with this?  we sort of have connection pooling in
the db already.  guess I'd need "an example"

      Rating:
      3
Triggers  January 25, 2004
Reviewer:
      Natasha  from EGYPT
Toms,

To trap the created date & the name of the user who logged in...Which practice
is good. Please advice.

Table Available:

Create Table Xyz
( eno pls_integer,
  ename Varchar2(20),
  created_date date,
  created_user varchar2(50),
  modified_date date,
  modified_user varchar2(50)
)

Ex #1:

  To create a before insert trigger and we can track the created date & created
user.

Ex #2:

  Assinging a default values to Created_user & Created_Dates at the table level.

Please suggest for the developers.

Thanks in advance.

Natasha

          Followup:

I would use defaults whenever possible (more efficient than a trigger) and a
trigger only when I could not use something declaritive.

and when granting on this table, do not grant the ability to modify these
columns as demonstrated (else someone can override the defaults if they wanted
to spoof you)

ops$tkyte@ORA9IR2> create table t
  2  ( id int primary key,
  3    data varchar2(20),
  4    create_date date default sysdate,
  5    creator     varchar2(30) default user,
  6    update_date date,
  7    updated_by  varchar2(30)
  8  )
  9  /

Table created.

ops$tkyte@ORA9IR2> create or replace trigger t_trigger
  2  before update on t for each row
  3  begin
  4          :new.update_date := sysdate;
  5          :new.updated_by := user;
  6  end;
  7  /

Trigger created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> grant select,  update (id,data), insert(id,data) on t to
scott;

Grant succeeded.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> @connect scott/tiger
ops$tkyte@ORA9IR2> set termout off
scott@ORA9IR2> REM GET afiedt.buf NOLIST
scott@ORA9IR2> set termout on
scott@ORA9IR2> insert into ops$tkyte.t ( id, data ) values ( 1, 'hello world' );

1 row created.

scott@ORA9IR2> insert into ops$tkyte.t ( id, data, create_date ) values ( 1,
'hello world', sysdate-1 );
insert into ops$tkyte.t ( id, data, create_date ) values ( 1, 'hello world',
sysdate-1 )
                      *
ERROR at line 1:
ORA-01031: insufficient privileges

scott@ORA9IR2> update ops$tkyte.t set updated_by = 'OPS$TKYTE';
update ops$tkyte.t set updated_by = 'OPS$TKYTE'
                 *
ERROR at line 1:
ORA-01031: insufficient privileges

scott@ORA9IR2> update ops$tkyte.t set data = 'goodbye';

1 row updated.

scott@ORA9IR2> select * from ops$tkyte.t;

        ID DATA                 CREATE_DA CREATOR
UPDATE_DA UPDATED_BY
---------- -------------------- --------- ------------------------------
--------- ------------------------------
         1 goodbye              25-JAN-04 SCOTT
25-JAN-04 SCOTT

January 25, 2004 G -- Thanks for the question regarding "Rows into
      columns", version 9.0.1

      You Asked
Hi Tom,

I have a query

sql> SELECT tr_date, item_id, adult_price adult, child_price child
FROM mytable
WHERE tr_date BETWEEN '01-jan-2004' AND '31-jan-2004'
AND   tr_code LIKE 'D%'

The output is

TR_DATE            ITEM_ID    ADULT    CHILD
06/01/2004    8    1189    832.3
06/01/2004    9    1199    839.3
06/01/2004    588    1249    874.3
06/01/2004    589    1239    867.3
06/01/2004    1625    2389    1672.3
06/01/2004    2186    2439    1707.3
06/01/2004    4081    2099    1469.3
06/01/2004    4083    2149    1504.3
06/01/2004    4894    2339    2339
06/01/2004    4895    2389    2389
06/01/2004    5507    789    552.3
06/01/2004    5508    839    587.3
06/01/2004    5618    639    479
06/01/2004    6870    1269    888.3
06/01/2004    6871    1269    888.3
07/01/2004    5618    639    479
07/01/2004    5618    679    475.3
08/01/2004    5618    639    479
08/01/2004    5618    679    475.3
10/01/2004    7659    759    531.3
10/01/2004    7661    849    594.3
11/01/2004    7659    759    531.3
11/01/2004    7661    849    594.3
12/01/2004    7659    759    531.3
12/01/2004    7661    849    594.3
13/01/2004    8    959    575.4
13/01/2004    9    1199    719.4
13/01/2004    588    1249    749.4
13/01/2004    589    1009    605.4
13/01/2004    1625    2169    1301.4
13/01/2004    2186    2219    1331.4
13/01/2004    4081    1939    1163.4
13/01/2004    4083    1989    1193.4
13/01/2004    4894    2209    2209
13/01/2004    4895    2259    2259
13/01/2004    5507    679    407.4
13/01/2004    5508    729    437.4
13/01/2004    5614    739    549
13/01/2004    5618    639    479
13/01/2004    5619    589    412.3
13/01/2004    6870    1029    617.4
13/01/2004    6871    1029    617.4
13/01/2004    7659    759    531.3
13/01/2004    7661    849    594.3
.......
.......
.......
.......
.......
.......

Now I want output like this (distinct dates in vertical and distinct item_id in
horizontal) and then adult and child price in cells. The number of columns
(item_id) depend on number of distinct item_id's returned by query and number of
rows depend on number of distinct tr_date returned by query. The adult and child
depend on the adult and child price for that date and that item_id. Hence I want
the result like this

Date/Itin_id    8    8    9    9    588    588    589
          adult    child    adult    child    adult    child    adult
06/01/2004    1189    832.3    1199    839.3    1249    874.3    1239
07/01/2004    ….    ….    ….    ….    ….    ….    ….
08/01/2004    ….    ….    ….    ….    ….    ….    ….
10/01/2004    ….    ….    ….    ….    ….    ….    ….
12/01/2004    ….    ….    ….    ….    ….    ….    ….
13/01/2004    ….    ….    ….    ….    ….    ….    ….

Please advise.

Regards,

GS

      and we said...
create or replace package pivot
as
   type rc is ref cursor;
   procedure data ( p_cursor in out rc );
end;
/
create or replace package body pivot
as

procedure data( p_cursor in out rc )
is
    l_stmt long;
begin

    l_stmt := 'select tr_date';
    for x in ( select distinct item_id from t order by 1 )
    loop
        l_stmt := l_stmt ||
        ', max(decode(item_id,' || x.item_id ||
             ', adult )) adult_' || x.item_id ||
        ', max(decode(item_id,' || x.item_id ||
             ', child )) child_' || x.item_id;
    end loop;
    l_stmt := l_stmt || ' from t group by tr_date order by tr_date';

    open p_cursor for l_stmt;
end;

end;
/

variable x refcursor
set autoprint on
exec pivot.data( :x );

Review &
      Followup

      Rating:
      4
Thanks  January 27, 2004
Reviewer:
      Gaurav Srivastava  from London, UK
Many thanks Tom, this is what was required.

January 25, 2004 jasdeep -- Thanks for the question regarding
      "when we start a database there are two sizes fixed size and variable size
      what are real constituents of them", version 9.0.2

      You Asked
and then instance started
database mounted
database opened

      and we said...
From my book "expert one on one Oracle"

<quote>
SGA

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 oracle binary itself
at installation time (hence the name `fixed'). 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.

</quote>

the variable component is all of the pools -- redo log buffer, shared pool, java
pool, large pool, etc.  "variable" meaning you control it via init.ora settings.

Set processes really high -- you'll allocate more "variable" SGA memory.
Review &
      Followup

      Rating: 5
variable
      size  January 25, 2004
Reviewer:  jasdeep
       from patial,india
thanks for your reply, it has been very useful

January 25, 2004 ogundotun -- Thanks for the question regarding
      "Performance Issue - high response time", version 9.2.0

      You Asked
High Tom,

I have an oracle 9i release 9.2.0.1.0 (32 bits) database created on solaris 8 on
which I always have maximum of 50 concurrent users.The front end for the
application running against the database is ASP with VB scripts.All the tables
in the database are properly indexed and initially I was maintaining the default
values for the initialization parameters but then any time I have more than 20
users on the machine the response time is always high with 100% CPU
utilization,so I increased the shared_pool_size,buffer_cache_size but the issue
remain the same.Once the server is shutdown and restarted it will be okay for a
while.
  The application is a small one in which there are just 10 tables and the
biggest of all has 700000 records.

The system spec is as below;

 SUN FIRE V120 with IGB Processor,IGB RAM and 2(36GB) Hard disks.

Please, I how can I go about this problem.

Thanks,

-Akinwale-

      and we said...
well, i don't know how you can say "every thing is properly indexed" and so on.

You have to identify WHAT is causing your issue (frankly, it sounds like it
could be "too little hardware").

Then and only then can you apply a remedy.  Changing parameters willy nilly like
that is fruitless.  Like shooting a gun into the woods at night hoping to hit a
deer to eat for breakfast.  It won't happen.

So, use the tools like sql_trace with timed statistics.  trace your
applications.  verify they are doing the least amount of work possible (eg: if
you see a query that retrieves 5 rows from a 3 table join but does 10,000
logical IO's to get it -- you have found something to work on)

Hopefully -- I really hope (but somehow, i have a gut feeling the opposite is
true) you are using BIND VARIABLES.  If not, that is undoubtably a major
contributor to your issue (seems to be epidemic with VB programmers -- no binds,
every query is a unique query).  You can use statspack with a 15 minute snapshot
window during your busy time to find out.  If your soft parse % is not above
99%, you are missing bind variables and need to fix that immediately.

So, suggest you trace individual applications, look for low hanging fruit.

Then, maybe use statspack to get a read on how the instance as a whole is doing.
January 25, 2004 G -- Thanks for the question regarding "Can using
      DBMS_STATS cause DB corruption or loss of data?", version 8.1.7

      You Asked
Hello Tom
Thanks for taking my Q and thanks for running this great site.

Due to lot of performance issues, after making extensive analysis and testing, I
am proposing using COST/Optimizer on our system and the team has carried out
extensive testing. I used DBMS_STATS in computing schema stats after doing
research on your site on merits of it as against ANALYZE command.

On the day it was supposed to be deployed to production, our PRD/DBA has raised
the issue that he cannot use it, beacuse it is his domain to decide what to use.
Also he has mentioned that if it is not thoroughly tested (DBMS_STATS against
ANALYZE command) by DBA, it can cause I quote
"DB corruption or data loss might be our consequence"

Please clarify, if there is any document/discussion or even risk to this effect

FYI. The results of CBO versus RULE itself was debated at length but the test
results are conclusive and we have no issues on it

Again thanks for taking the Q.

      and we said...
geez, i despise DBA's like this.  the ones that say things like:

"we will use no version until it has aged for 5 years" (like software was wine
or something)

"you shall use no feature invented after version 6" (hidden message "because
doing so will show that I haven't really kept up and have no idea what it does")

"feature 'x' is deemed evil, you shall under no circumstances use it" (meaning
they used it improperly in the past -- got yelled at by someone -- and have
generalized it to be 'evil'.  VIEWS are a perfect case in point, stored
procedures another, triggers -- and so on.  Nothing is 100% good, nothing is
100% evil)

and so on.

Caution = good.

spreading FUD = bad.

DBMS_STATS, which is used by the vast majority of Oracle customers, reads data.
It does not write to the data it analyzes.  Your DBA can verify this if they
know how to use sql_trace.
Review & Followup

      Rating:
      5
caveat  January 20, 2004
Reviewer:
      reader
Loss of data, not likely.
DB corruption , possible.
I have run into database crashes
when using dbms_stats.gather_schema_stats
with 'GATHER AUTO' option under 9.0.1

Of course, it was in a test database
and problem was resolved by upgrading
before rolling out to prod.
I guess that's what Tom meant by "Caution".

          Followup:

db corruption?  that would be loss of data.  dbms_stats will not corrupt data
(and has an equal likelyhood of crashing an instance as does any command -- from
SELECT to ALTER to ANALYZE)

it is very likely that if dbms_stats caused some sort of deadlock situation --
that analyze would as well (dbms_stats sometimes just calls analyze at the end
of the day!)

      Rating: 5
"like software
      was wine or something"  January 20, 2004
Reviewer:
      Dilip Patel  from Maryland, USA
(like software was wine or something) -- Lolzzzzzzzzzzzzz. Still laughing.

Tom - Your way of explaining is excellent.

      Rating: 5
Diff bet
      COLUMNS and INDEXED COLUMNS  January 25, 2004

Reviewer:  A reader
Tom
Thanks for your response. I have a followup Q on specific to what we are doing.
The script we used haS
FOR I IN (ALL of Application Schemas)
    DBMS_STATS.GATHER_SCHEMA_STATS (
       ownname         => I.SCHEMA_NAME          ,
       estimate_percent=> NULL, --COMPUTE
       block_sample    => NULL,
       method_opt      => 'FOR ALL INDEXED COLUMNS',
       degree          => 2,
       granularity     => 'DEFAULT',
       cascade         => TRUE,
) ;
END LOOP

The one Production/DBA was using has
FOR All tables and indexes in Application Schemas,
    ANALYZE TABLE COMPUTE STATISTICS ;
    ANALYZE INDEX COMPUTE STATISTICS ;
END LOOP ;

One of the main diff I noticed (apart from the usage perspective) is that
ANALYZE with no method/opt will compute "FOR ALL COLUMNS"

Q1. Can you see any other differences as far as collected stats are concerned?
Q2. How are stats on NON-INDEXED Columns used by the optimizer?

Thanks again for taking the questions

          Followup:

hehehe.

analyze table T compute statistics -- that already got the indexes.  that gets
table, column and index stats.

They use histograms not only to decide "use index or not" but to decide "how
many rows are going to come out of this full scan"...

note the plan differences and different cards/etc in the following:

ops$tkyte@ORA9IR2> create table t
  2  as
  3  select 999 id, all_objects.* from all_objects;

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> update t set id = 1 where rownum = 1;

1 row updated.

ops$tkyte@ORA9IR2> commit;

Commit complete.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> analyze table t compute statistics
  2  for table
  3  for columns id;

Table analyzed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create table t2
  2  as
  3  select 1 id from dual
  4  union all
  5  select 999 from dual;

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> analyze table t2 compute statistics;

Table analyzed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> set autotrace traceonly explain
ops$tkyte@ORA9IR2> select * from t, t2 where t.id = 999 and t.id = t2.id;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=46 Card=29558 Bytes=3044474)
   1    0   MERGE JOIN (CARTESIAN) (Cost=46 Card=29558 Bytes=3044474)
   2    1     TABLE ACCESS (FULL) OF 'T2' (Cost=2 Card=1 Bytes=3)
   3    1     BUFFER (SORT) (Cost=44 Card=29558 Bytes=2955800)
   4    3       TABLE ACCESS (FULL) OF 'T' (Cost=44 Card=29558 Bytes=2955800)

ops$tkyte@ORA9IR2> select * from t, t2 where t.id = 1  and t.id = t2.id;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=46 Card=1 Bytes=103)
   1    0   MERGE JOIN (CARTESIAN) (Cost=46 Card=1 Bytes=103)
   2    1     TABLE ACCESS (FULL) OF 'T' (Cost=44 Card=1 Bytes=100)
   3    1     BUFFER (SORT) (Cost=2 Card=1 Bytes=3)
   4    3       TABLE ACCESS (FULL) OF 'T2' (Cost=2 Card=1 Bytes=3)

ops$tkyte@ORA9IR2> set autotrace off

January 26, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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://download-west.oracle.com/docs/cd/B10501_01/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.

      Rating: 5
alter system
      checkpoint  January 24, 2004
Reviewer:
      Reader
Tom, what is the use of the command ALTER SYSTEM CHECKPOINT as we have
incremental checkpoints these days? I was wondering when this command would be
useful? Thanks.

          Followup:

it is not really.  you can use it to force a checkpoint and wait for it to
complete.

      Rating: 5
alter system
      checkpoint  January 25, 2004
Reviewer:
      Reader
Tom, in what circumstances this command is useful for DBAs, say in 8i/9i version
of oracle? Thanks.

          Followup:

see above. one comment up.

      Rating: 5
Checkpoint not
      complete  January 26, 2004
Reviewer:  Ken
      Lau  from Hong Kong
As you say, if the checkpoint for every log file switch cannot complete on time,
all transactions will halt until the earlies checkpoint complete. If this
situation really occur, the following problem might exist:
- the I/O is slow.
- the redo log file size is small.

To fix the problem, we may:
1. increase the no. of log group.
2. increase the size of 1 (or every) log group.

So, is there simple guildline to determine weather I should use method 1 or 2?
Or there is method 3,4,5...etc?

          Followup:

you forget fix #3 -- fix your IO

to determine what to do, you look at your redo layout and  ask yourself

o are the disks going as fast as they should.  does iostat or perfmon show me
going at the right/reasonable speed there.

o are the disks suffering from contention that I can alleviate

o are the programs just committing too frequently -- like every row instead of
on transactional boundaries.  or am I doing a load with 'commits' in the middle.
 I can reduce the amount of redo the system generates by committing on
transactional boundaries, not row by row (in fact, i'll probably fix quite a few
bugs as well doing that)

o are the programs doing things row by row instead of set based.  row by row
generates lots of additional redo.  turning a row by row process into a set
operation could cut redo generation by 50% or more

o and then lastly you can decide yourself whether you want to
a) have more groups -- meaning you just extend out the period in which dbwr has
to flush the buffers

b) have larger members -- meaning dbwr might get even further behind since it
has more to checkpoint but is getting told to do it less often

c) use fast_start_mttr_target (io_target in earlier releases) to have the data
trickled out continously.

January 26, 2004 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)

      Rating: 5
Chained rows
      stats in user_tab_partitions  January 26, 2004

Reviewer:  Bogy  from Germany
Hi Tom,
I am a bit confused about information I get from view user_tab_partitions in
columns:
EmptyBlocks, AvgSpace and ChainCount.
The story is following:
I have created one partitioned table (name: my_part_table) and in one of the
partitions inserted around 500000 records with AvgRowLen 1100 on 8k block size.
I did some additional data manipulation in partition p_11 and after analyzing
table I got information that I have for partition p_11:
EmptyBlocks: 63
AvgSpace   : 1157
ChainCount : 19446

After this I did following:

CREATE TABLE t_1 as
select *
from my_part_table partition (p_11);

alter table my_part_table truncate partition p_11 drop storage update global
indexes;

Analyze....with dbms_stats package, compute statistics for partition p_11 for
all columns.

After this I have no rows in partition p_11 but statistics still show me for
this partition:
EmptyBlocks: 63
AvgSpace   : 1157
ChainCount : 19446

I do not understand how I can have now chained rows in this partition?
If I execute:
analyze table my_part_table partition (p_11) list chained rows into chained_rows

I got no chained rows.

Could you please put some light on this.

Thanks a lot!

Bogy

          Followup:

those statistics are not collected by dbms_stats as they are not used by the CBO
to develop plans.

they are only gathered by ANALYZE.

You can see this with a regular table as well:

ops$tkyte@ORA920PC> create table t ( x int, y char(2000) );

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert into t (x) select rownum from all_users;

42 rows created.

ops$tkyte@ORA920PC> commit;

Commit complete.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> update t set y = 'x';

42 rows updated.

ops$tkyte@ORA920PC> commit;

Commit complete.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select num_rows, empty_blocks, avg_space, chain_cnt from
user_tables where table_name = 'T';

  NUM_ROWS EMPTY_BLOCKS  AVG_SPACE  CHAIN_CNT
---------- ------------ ---------- ----------
        42            4       3768         39

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> truncate table t;

Table truncated.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> exec dbms_stats.gather_table_stats( user, 'T' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> select num_rows, empty_blocks, avg_space, chain_cnt from
user_tables where table_name = 'T';

  NUM_ROWS EMPTY_BLOCKS  AVG_SPACE  CHAIN_CNT
---------- ------------ ---------- ----------
         0            4       3768         39

ops$tkyte@ORA920PC> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA920PC> select num_rows, empty_blocks, avg_space, chain_cnt from
user_tables where table_name = 'T';

  NUM_ROWS EMPTY_BLOCKS  AVG_SPACE  CHAIN_CNT
---------- ------------ ---------- ----------
         0            8          0          0

January 26, 2004 Phillip -- Thanks for the question regarding
      "Very large Database - question on max files/max filesize", version 8051

      You Asked
Is there a limit to the number of datafiles Oracle can address?

Is there a limit to the size of a datafile (I could have
datafiles
in the region of 30-100 GB)

Are there any limitions to the size of RAW disk Oracle can
address?

      and we said...
From the SERVER REFERENCE manual, database limits section:

http://download-east.oracle.com/docs/cd/A58617_01/server.804/a58242/ch5.htm#576
o max datafiles / tablespace 1,022.
  max datafiles / database 65533

o max datafile size: O/S dependent, limited by maximum
O/S file size; typically 2^22 or 4M blocks.  This means 32gig
per datafile realistically.

o raw disk is just like a "file" to us.  same restrictions
apply...

Review &
      Followup

      Rating: 5
Can you have
      too many datafiles (drawback)  January 26, 2004

Reviewer:  Miho Shibata  from Canada
Hi Tom,

Is there a drawback to having too many tablespaces/datafiles?

I'm thinking of a backup solution for a data warehouse.
Once a partition is loaded, set the tablespace read-only
until it gets rolled out. Saves on Backup requirements.
This would require minimum one tablespace per partition
(i.e. a lot of tablespaces).

But this will result in a lot of datafiles. Can this
have adverse effects (e.g. CHECKPOIONT).

Thanks Tom.

Miho

          Followup:

if they are read only -- checkpointing isn't going to be an issue there.....

it is a very valid approach.  very common in fact.

      Rating: 3
Problem with a
      larger datafile  January 26, 2004
Reviewer:
      Pichai Bala  from San Jose, CA USA
Hi Tom,
   Thanks for your excellent service. Personally I know of an occassion where we
had a trouble in dropping a tablespace of 80G having only one datafile. So now I
follow the following: fan out the tablespace as a set of datafiles like 80G is
split into 9 datafiles of 9G each. This way it will help the IO also by reducing
the contention as well as helps in dropping the tablespace easily.
   What is your opinion regarding?

          Followup:

why do you believe it was the size of the file.

sorry but "we had a trouble" is so vague.... no one can respond to that.

It was more likely (guess) that

a) this was a dictionary managed tablespace
b) you had an object with thousands of extents
c) you tried to drop it including contents
d) it took a really long time

solution: don't use DMT's, use LMT's.

I doubt the size of the file had anything to do with anything.  You would have
had that issue with 9 9gig files.

OS Striping is much better than putting files here there and everywhere.

      Rating:
      4
Great  January 26, 2004
Reviewer:
      Pichai Bala  from San Jose, CA
Hi Tom,
   All your guesses are true. Also the files are OS striped.
Now we use only LMTs.

Thanks a lot

January 26, 2004 Saradha -- Thanks for the question regarding
      "Strings of Zero Length Not Equivalent To NULL", version 9.2.0

      You Asked
Tom,

As we are planning to upgrade to 9i version, I am reviewing the Oracle 9i
migration documents and in one of the topic, I read
"Strings of Zero Length Not Equivalent To NULL".

"A string of zero length ('') is not equivalent to a NULL value. According to
the ANSI SQL 1992 Transitional standard, a zero-length or empty string is not
the same as NULL. Ensure that applications do not treat empty strings and NULL
values equivalently."

I tested the following PL/SQL blocks in Oracle8i version and the
behavior looks different for CHAR variable and VARCHAR2 variable.

SQL> set serveroutput on

SQL> DECLARE
  2  col1 VARCHAR2(1);
  3  BEGIN
  4  col1 := '';
  5  DBMS_OUTPUT.PUT_LINE('Length :'||TO_CHAR(LENGTH(col1)));
  6  IF (col1 IS NULL) THEN
  7     dbms_output.put_line('Treated as NULL');
  8  END IF;
  8  IF (col1 = '') THEN
 10     dbms_output.put_line('Treated as EMPTY STRING');
 11  END IF;
 12* END;
 13  /

Length:
Treated as NULL

PL/SQL procedure successfully completed.

In the above example, col1 variable is declared as VARCHAR2 and the
empty string is treated as NULL.

SQL> DECLARE
  2  col1 CHAR(1);
  3  BEGIN
  4  col1 := '';
  5  DBMS_OUTPUT.PUT_LINE('Length :'||TO_CHAR(LENGTH(col1)));
  6  IF (col1 IS NULL) THEN
  7     dbms_output.put_line('Treated as NULL');
  8  END IF;
  8  IF (col1 = '') THEN
 10     dbms_output.put_line('Treated as EMPTY STRING');
 11  END IF;
 12* END;
 13  /

Length :1

PL/SQL procedure successfully completed.

In this example, col1 variable is declared as CHAR variable and
the empty string is neither treated as NULL nor treated as empty string. The
length of the string is 1 in this case eventhough I have assigned an empty
string to col1.

I have tested the above examples in 9i version also and the results
are same.

Can you please verify this difference as we have to modify some our PL/SQL
programs according to this.

Thanks
Saradha

      and we said...
A ZERO length varchar is treated as NULL.

'' is not treated as NULL.

'' when assigned to a char(1) becomes ' ' (char types are blank padded strings).

'' when assigned to a varchar2(1) becomes '' which is a zero length string and a
zero length string is NULL in Oracle (it is no long '')

Now, I don't know what you mean by "we'll have to modify our code due to this"
since this behaviour has been consistent for quite a while.  Consider:

tkyte@ORA716.WORLD> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle7 Server Release 7.1.6.2.0 - Production Release
PL/SQL Release 2.1.6.2.0 - Production
CORE Version 2.3.7.1.0 - Production (LSF Alpha)
TNS for SVR4: Version 2.1.6.0.0 - Production
NLSRTL Version 2.3.6.0.0 - Production

tkyte@ORA716.WORLD> set echo on
tkyte@ORA716.WORLD> @test
tkyte@ORA716.WORLD> DECLARE
  2  col1 VARCHAR2(1);
  3  BEGIN
  4  col1 := '';
  5  DBMS_OUTPUT.PUT_LINE('Length :'||TO_CHAR(LENGTH(col1)));
  6  IF (col1 IS NULL) THEN
  7     dbms_output.put_line('Treated as NULL');
  8  END IF;
  9  IF (col1 = '') THEN
 10     dbms_output.put_line('Treated as EMPTY STRING');
 11  END IF;
 12  END;
 13  /
Length :
Treated as NULL

PL/SQL procedure successfully completed.

tkyte@ORA716.WORLD>
tkyte@ORA716.WORLD> DECLARE
  2  col1 CHAR(1);
  3  BEGIN
  4  col1 := '';
  5  DBMS_OUTPUT.PUT_LINE('Length :'||TO_CHAR(LENGTH(col1)));
  6  IF (col1 IS NULL) THEN
  7     dbms_output.put_line('Treated as NULL');
  8  END IF;
  9  IF (col1 = '') THEN
 10     dbms_output.put_line('Treated as EMPTY STRING');
 11  END IF;
 12  END;
 13  /
Length :1

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating:
      5
  October 15, 2002
Reviewer:
      Saradha  from NJ, USA
Tom,

If Strings of Zero Length for VARCHAR2 variable is NULL
and Strings of Zero Length for CHAR variable is assigned with space values to
the size of the variable, then what does the following convey:

"Strings of Zero Length Not Equivalent To NULL".

How do I store an empty string or How do I check a
variable in the program if it contains an empty string.

Thanks
Saradha

          Followup:

well, as shown, a varchar2 will be NULL -- you would not be able to use a
varchar2 variable and once assigned to a char -- it'll not be an "empty" string
anymore.

      Rating: 5
Pls commnet on
      this wrt 9ir2  October 16, 2002
Reviewer:
      A reader
SQL> set null NULL

SQL> ed
Wrote file afiedt.buf

  1*  select 1 ,length('') LEN  from dual where '' is null
SQL> /

         1        LEN
---------- ----------
         1 NULL

  1*  select 1 ,length('') LEN  from dual where to_char('') is null
SQL> /

         1        LEN
---------- ----------
         1 NULL

Q)
This does mean that empty string is null and the default data type of empty
string is  varchar2 else if it where char then automatically its length would
have been one.

          Followup:

the '' is a being used as a varchar2, it is null.

      Rating:
      5
nulls  October 16, 2002
Reviewer:
      Guy  from Israel
Tom,

SQL> create table chr(x char(1));

Table created.

SQL> insert into chr values(null);

1 row created.

SQL> insert into chr values(null);

1 row created.

SQL> insert into chr values('G');

1 row created.

SQL> select * from chr where x<>'G';

no rows selected

SQL> select count(*) from chr where x is null;

  COUNT(*)
----------
         2

Why null is not different from 'G' ?

Thanks,
Guy

          Followup:

tri valued logic.

it is UNKNOWN whether null = 'G' or null <> 'G' is true.

      Rating: 4
Why different
      from SQL ?  November 06, 2002
Reviewer:
      robert  from PA
>> '' when assigned to a char(1) becomes ' ' (char types
>> are blank padded strings).

Tom why is this behavior true in PL/SQL but NOT in SQL ?
that is, why did the designers of PL/SQL take a diff , (non-ANSI ?)  approach
here ?

SQL> desc  rc_test
Name Type
---- -----------
A    CHAR(1)
B    CHAR(3)
C    VARCHAR2(3)

SQL> insert into rc_test values ('', 'A', 'A');

1 row inserted

SQL> select * from rc_test where a is null;

A B   C
- --- ---
  A   A

thanks

          Followup:

don't know

      Rating:
      3
Confused  November 06, 2002
Reviewer:
      Doug  from CT, USA
The original poster refers to some 9i migration documentation that says "Strings
of zero length are not equivalent to null", yet your explanation regarding
char's and varchars indicates the opposite behavior for varchars all the way
back to version 7.  So is the migration documentation not really referring to
anything new? or it is wrong, or what?

          Followup:

the docs are telling you want ansi says vs what we do.

      Rating: 3
zero length
      CHAR field  November 06, 2002
Reviewer:
      Gary  from Sydney, Aus
Regarding

>SQL> insert into rc_test values ('', 'A', 'A');
>1 row inserted
>SQL> select * from rc_test where a is null;
>A B   C
>- --- ---
>  A   A

In 8.1.7.4, if you do a DBMS_SQL.DESCRIBE of "select '', 'A', 'A' from dual",
you'll see the first column is a CHAR field of length 0, whereas the other two
are length 1.
I guess that it's being treated as a null CHAR(0) field at that stage, and then
gets converted to a null CHAR(1) field for the insert. Just because the user is
trying to insert it into a CHAR(1) field, doesn't mean it starts out being a
CHAR(1).

      Rating: 4
different
      effect in SQL Server  November 06, 2002
Reviewer:
      A reader
In SQL Server, null is absolutely null and empty string is absolutely emtry
string.  '' is nevel equal to null

Does it mean SQL Server and Oracle treats null and empty string differently?

          Followup:

among zillions of other differences - in a word "yes"

      Rating: 5
Can we make
      Oracle return zero length string instead of NULL  November
      14, 2002
Reviewer:  Arun Gupta  from Harrisburg, PA
      USA
Tom
Is it possible somehow to make Oracle return a zero length string instead of
NULL? We have lot of vb/asp code already written where programmers didn't check
for NULL. It seems that vb/asp doesn't handle NULL. It crashes if we assign NULL
values to variables and try to do some string operation. We have two options:
a) Modify vb/asp code (difficult, lot of code to change).
b) Make Oracle return zero length string instead of NULL (easy since only code
within packages need to be changed).

We are on Oracle 8.1.7.

Thanks...

          Followup:

there is no zero length string.  you'd have to return a 1 character length
string

      Rating: 5
there is no
      zero length string  September 11, 2003
Reviewer:
      A reader
Right. In a recent mysql to oracle intergration I found the NULL in mysql became
oracle NULL (no problme there), and the "empty string" in mysql became ASCII
code 0 in Oracle, which is a one char string (not null). ASCII code 0 represents
the "NUL" character, and can be entered using oracle's CHR() function [ chr(0)
]. The entered value can be tested with ascii() function (i.e. select * from tab
where ascii(col) = 0).

      Rating: 3
I don't see
      that as being correct.  January 26, 2004
Reviewer:
      A reader
justin@DEV> select ascii('') from dual;

        ASCII('')
=================

The "empty string" is not showing to have an ascii value of '0'.

justin@DEV> select ascii(' ') from dual;

        ASCII('')
=================
               32

justin@DEV> select ascii('
  2  ') from dual;

        ASCII('')
=================
               10

justin@DEV> select ascii(CHR(0)) from dual;

    ASCII(CHR(0))
=================
                0

          Followup:

chr(0) is not an empty string that is why.

an empty string is not chr(0)

Only in C would '\0' be the emtpy string.  SQL is not C.

      Rating:
      5
agreed  January 26, 2004
Reviewer:
      Justin  from PA
So...

Do you feel that the concept of an "empty string" and "null" are identical in
meaning when it comes to Oracle?

          Followup:

see the answer above -- depends on the use

      Rating: 4
Zero Length
      Strings in Varchar v Char  January 26, 2004

Reviewer:  Adam Musch  from Omaha, NE
No value will ever be equal to '' in Oracle, as '' in Oracle is NULL, and no
value is ever equal to NULL, as a value can only "IS" or "IS NOT" NULL.
(apologies for the pigdin English in that last bit).

A zero length string in a varchar column is null.
A zero length string in a char column is the width of the
column with spaces.

This is a portability issue with regards to Oracle vis-a-vis DB2, DB2 UDB, and
SQL Server.  A workaround for behavior consistent with those DBMS's is to use
CHAR columns for nullable columns.

Is it kinda kludgy?  Yes.
Will it result in wasted space?  Yes.
Is that a consequence of application code portability?  Yes.

An alternative would be to control DBMS access through views or triggers to
eliminate this issue, as well as a host of others, such as join and data type
conversion syntax.

      Rating: 5
Other shades
      using analytical...  January 27, 2004
Reviewer:
      Franco  from Denmark
Using analytical functions on imported data from another non-Oracle DB, probably
you must consider also this:
-----------------------------------------------------------

http://download-uk.oracle.com/docs/cd/A83908_02/NT816EE/DOC/index.htm
--  (about RANK and DENSE_RANK functions) NULLs are treated like normal values.
Also, for the purpose of rank computation, a NULL value is assumed to be equal
to another NULL value.
-----------------------------------------------------------
Tom, how to rate your responses with more then 5 *****? :-)

      Rating: 5
Sorry for the
      wrong link...  January 27, 2004
Reviewer:
      Franco  from Denmark
.... the right one:

http://download-uk.oracle.com/docs/cd/A83908_02/NT816EE/DOC/server.816/a76994/analysis.htm#13275

January 26, 2004
      Gill -- Thanks for the question regarding
      "400 byte barrier create flat files", version 8.1.7

      You Asked
I've searched the site can find nothing that seems to specifically answer my
problem.

I have a table with 500 columns of varchar2(40) (which i did not design...) plus
a few other cols. I need to output a comma delimited flat file of the 500
columns.
I can't select them all in one go as if they are populated this breaks the 4000
byte limit.
I've tried breaking the select statement down into chunks :
e.g.
set lines 21000  (or whatever max potential is)
Select col1  || ',' || col2  || ',' ||  col3 .. col49 chunk1,
       col50 || ',' || col52 || ',' ||  col53 ..col99 chunk2,
       etc
from tab

This works, but if most columns are null, i get big gaps in the output between
col49 and col50, and col99 and col100.

IS there a way in SQLPlus of outputting all 500 cols without gaps
(NB I've tried concatenating with a clob but got
ERROR at line 1:
ORA-00932: inconsistent datatypes

which I'm guessing is coz I'm only on v8 ? )

      and we said...
well, if you:

create or replace function csv( p_query in varchar2 ) return clob
authid current_user
is
    pragma autonomous_transaction;
    l_output        clob;
    l_theCursor     integer default dbms_sql.open_cursor;
    l_columnValue   varchar2(4000);
    l_status        integer;
    l_colCnt        number := 0;
    l_separator     varchar2(1);
    l_descTbl       dbms_sql.desc_tab;
    l_line          long;
begin
    dbms_lob.createtemporary( l_output, TRUE, dbms_lob.SESSION );

    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
        l_separator := '';
        l_line := '';
        for i in 1 .. l_colCnt loop
            dbms_sql.column_value( l_theCursor, i, l_columnValue );
            l_line := l_line || l_separator || l_columnValue ;
            l_separator := ',';
        end loop;
        dbms_lob.writeAppend( l_output, length(l_line)+1, l_line||chr(10) );
    end loop;
    dbms_sql.close_cursor(l_theCursor);
    return l_output;
exception
    when others then
        dbms_sql.close_cursor(l_theCursor);
        raise;
end;
/

you can then:

set linesize 32765
column csv format a32765
set long 10000000
set heading off
set embedded on
set termout off
spool x
select csv( 'select * from all_objects where rownum <= 10' ) csv from dual;
spool off
set termout on

as an exercise for the reader :) -- you can employ array fetching to speed
this up using dbms_sql

also, we are "leaking a lob" using this technique -- the temporary lob will
stick around.  To "correct" that, you could set up a package with a csv function
-- have the clob be a package variable -- return the package variable and then
have another procedure in there you can call to free the lob after you select
it.
January 27, 2004 wallacel -- Thanks for the question regarding
      "v$lock", version 8.1.6

      You Asked
Tom:

at one session i
create table test ( a int);
insert into test values (1);
commit;
update test set a=5;

then i go to another session:
alter test set a=6;

when i go to check v$lock;
i got two rows for each session.
and one id1 is object_id for test.
how about the other id1( the big one)? what is that?

      and we said...
You must be referring to the TX lock.

These are acquired in an exclusive mode when a transaction
initiates its first change and held until the transaction does a
COMMIT or ROLLBACK.  They are used as a queuing mechanism for
processes waiting for something locks by a transaction's
change to become available again (you might wait on someone
elses TX lock -- when it becomes available, the resource you
wanted is free).

id1 = undo segment number << 16 | slot

meaning, you can look at:

  1  select trunc(id1/power(2,16)) segment_id,
  2        id1 - trunc(id1/power(2,16))*power(2,16) slot
  3*   from v$lock where type = 'TX'
ops$tkyte@DEV816> /

SEGMENT_ID       SLOT
---------- ----------
         5         75
         4         45
         6         50
         4         45
         3         70

to see the rollback segment information for that transaction.

Review &
      Followup

      Rating: 5
V$lock MR or
      Media recovery  January 22, 2004
Reviewer:
      Alvin  from Philippines
select type, count(*) from v$lock group by type

TY        COUNT(*)
-- ---------------
MR              12
RT               1
ST               1
TM               4
TS               1
TT               1
TX               3
WL               1

8 rows selected.

What the MR or Media recovery imply ??

We had a new storage media installed on our server. My boss asked me how i would
move the oracle datafiles from the old storage to the new storage while
minimizing downtime.

1. What i did is take hotbackups of all the datafiles execpt the temp and rbs
datafiles to the new storage. The temp & rbs datafiles where OS copied to the
target directory when i shut down the db already.
2. Startup mount and Changed the entries on the control file via the alter
database rename file...
3 recover from thereon
4. Alter database open
It recovered properly  ( but did not restart the db).

Does it mean that i only have to restart the db so it can open on a 'clean
state' ?

          Followup:

MR locks are taken on each online datafile -- they are normal and to be
expected.  They are not "stopping" anything from happening.

you need to restart and PERFORM A FULL HOT BACKUP OF EVERYTHING (except temp)
immediately since you've rebuilt the control files and have opened with
resetlogs.

      Rating:
      5
Clarification  January 23, 2004
Reviewer:
      Alvin  from Philippines.
I didn't restart it with the resetlogs option because i did move the redologs to
the new storage too and then changed the redo entries in the control file via
'alter database rename file'.

we're using standby databases so resetlogs is the last thing on my mind.

      Rating: 3
More
      detail  January 26, 2004
Reviewer:  Oracle
      Leo  from India
Hi Tom,

There are various types of lock (MR,TS,RT,DF,KK) which we get to see at times
and dont understand what actually they mean and how they affect our DB. I
referred oracle reference manual, but there its just the decription of the lock
which is provided. Could you please shed some more light on the various types of
lock and their affects. Or you can provide us with some link on the web.

Thanks in anticipation.

          Followup:

metalink.oracle.com is the place to go.

January 27, 2004 -- 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

      Rating: 4
Regarding Code
      posted by Matthias Rogel from Kaiserslautern,
      Germany  January 26, 2004
Reviewer:  Matt
       from Australia
See:

save exceptions and so on in Oracle <= 9.0  August 28, 2003
Reviewer:  Matthias Rogel  from Kaiserslautern, Germany

Above

I'm looking for a solution similar to the one posted by the gentleman referenced
above (thanks Matthias). I need to generate a procedure to transfer data from an
external table to a heap table. I need to parameterise the table names.

As I want to bulk load arrays of data many times AND capture any exception
information for rows that fail the load the posted code looks like dbms_sql is
the way to go (I can control when the cursors get opened, parsed and closed).

The problem is that to use the exception array functionality I need to bind some
dummy values to the :num array (ie: dynamically generate "insert into t where
:num = :num"). Basically, I have the overhead of building a dummy array that is
the same size as my (parameterised) array size.

This works, but is there a 'better' way to do this? What I have is really a
workaround.

      Rating: 3
To
      Clarify...  January 27, 2004
Reviewer:  Matt
       from Australia
How do you use bulk exceptions using dbms_sql?

Is it possible to write a procedure to accept the name of source and target
tables and carry out the transfer of data in bulk?

Basically how you dynamically decide how many dbms_sql.Varchar2_Table etc table
types you need (at runtime) and how should these be defined efficiently?

          Followup:

you don't -- you fake it as demonstrated above.  You are basically doing row by
row with an exception block on each row -- but bulk binding to this row by row
thing.

better than true row by row.

Probably -- what I would do here is write a procedure to write a procedure.
Then, run generated procedure (which can use all static sql, bulk binds, the
whole suite of functionality).

January 27, 2004 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,

      Rating: 5
How can I lock
      a user account ?  January 27, 2004
Reviewer:
      A reader

          Followup:

ops$tkyte@ORA920PC> alter user scott account lock;

User altered.

ops$tkyte@ORA920PC> connect scott/tiger
ERROR:
ORA-28000: the account is locked

      Rating: 5
Thanks
      !!!  January 27, 2004
Reviewer:
      A reader

January 27, 2004 -- 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.

      Rating: 4
Similar
      Problem  January 27, 2004
Reviewer:  Michael
       from Allentown, PA
The above tips, and one you gave in another thread, have been very helpful in
meeting requirements I was given for querying audit tables. The first requirment
was to find what the data looked like for a give PK value as of a given date.
Using one of your tips, I came up with

select * from
(select nm, comm_hier_id, comm_hier_ownr_fid, ops, to_char(sys_tm, 'MM/DD/YY
"at" HH24:MI:SS') Timestamp, app_nm, os_usr from pt_comm_hier_a where sys_tm <=
to_date('22-JAN-2004 11:59:59 PM', 'DD-MON-YYYY HH:MI:SS PM')
ORDER BY sys_tm desc) where rownum = 1 and comm_hier_id = '1074787931230'
/
which yielded:
NM                  COMM_HIER_ID COMM_HIER_OWNR_FID OPS          TIMESTAMP
     APP_NM
--------------- ---------------- ------------------ ------------
-------------------- -----------------
HMustard           1074787931230      1074787929623 UPDATE       01/22/04 at
11:12:12 JDBC Thin Client

Which is the correct answer. There were 4 transactions for that pk that day, an
insert and 4 updates. The above query got the data after the last update.
Thanks!

Now to the next problem, which I have spent way too much time on already.

For simplicity, suppose I have 3 columns in an an audit table for the employees
table that I need to report on (these would be columns that could be updated).
Besides columns that will not change like employee_id, hire_date, etc., I need a
simple query using the analytic functions to show which of the updatable columns
DID change, and the old and new value. However, if only one column changed, I
want the others to show only the old value. (new value would be null).

In a simple one column example, I wrote:
select * from (select employee_id empid, department_id deptid, hire_date, ops,
sys_tm, first_value(salary) over(partition by employee_id order by sys_tm) as
first_val,
last_value(salary) over (partition by employee_id order by sys_tm) as last_val
from employee_aud )
where first_val <> last_val and ops = 'UPDATE'
order by deptid, hire_date, emp_id
/
and got the following:
 EMPID     DEPTID HIRE_DATE OPS    SYS_TM     FIRST_VAL   LAST_VAL
------ ---------- --------- ------ --------- ---------- ----------
   103         60 03-JAN-90 UPDATE 02-FEB-92       9000      10000
   105         60 25-JUN-97 UPDATE 30-JUN-99       4800       5200
   105         60 25-JUN-97 UPDATE 07-JUL-00       4800       6000
   106         60 05-FEB-98 UPDATE 07-JUL-99       4800       5000
   100         90 17-JUN-87 UPDATE 25-JUL-89      24000      27000
   101         90 21-SEP-89 UPDATE 29-DEC-98      17000      19000
   101         90 21-SEP-89 UPDATE 05-JAN-00      17000      21000
   102         90 13-JAN-93 UPDATE 10-APR-95      17000      19000
   101         95 21-SEP-89 UPDATE 01-JAN-99      17000      21000
   101         95 21-SEP-89 UPDATE 01-JAN-01      17000      23000
   108        100 17-AUG-94 UPDATE 30-JUN-96      12000      13000
   108        100 17-AUG-94 UPDATE 23-SEP-98      12000      16000

Fine.

But the problem is I want to get the old and new values for 3 columns , but only
the old value if that specific column did not change. So the changed column
would be the only one with old AND new values.

If I write:

select * from (select employee_id empid, department_id dpt#, hire_date, ops,
sys_tm, first_value(salary) over(partition by employee_id order by sys_tm) as
old_sal,
last_value(salary) over (partition by employee_id order by sys_tm) as new_sal,
first_value(last_name) over (partition by employee_id order by sys_tm) as
old_last_name,
last_value(last_name) over (partition by employee_id order by sys_tm) as
new_last_name, first_value(email) over (partition by employee_id order by
sys_tm) as old_email,
last_value(email) over (partition by employee_id order by sys_tm) as new_email
from employee_aud )
where old_sal <> new_sal
or old_last_name <> new_last_name
or old_email <> new_email
and ops = 'UPDATE'
order by dpt#, hire_date, empid

I get:

EMPID  DPT# HIRE_DATE OPS    SYS_TM       OLD_SAL    NEW_SAL OLD_LAST_NAME
NEW_LAST_NAME   OLD_EMAIL    NEW_EMAIL
----- ----- --------- ------ --------- ---------- ---------- ---------------
--------------- ------------ ------------
  103    60 03-JAN-90 UPDATE 02-FEB-92       9000      10000 Hunold
Hunold          AHUNOLD      AHUNOLD
  105    60 25-JUN-97 UPDATE 30-JUN-99       4800       5200 Austin
Austin          DAUSTIN      DAUSTIN
  105    60 25-JUN-97 UPDATE 07-JUL-00       4800       6000 Austin
Austin          DAUSTIN      DAUSTIN
  106    60 05-FEB-98 UPDATE 07-JUL-99       4800       5000 Pataballa
Pataballa       VPATABAL     VPATABAL
  100    90 17-JUN-87 UPDATE 25-JUL-89      24000      27000 King
King            SKING        SKING
  101    90 21-SEP-89 UPDATE 01-SEP-98      17000      17000 Kochhar
Marlow          NKOCHHAR     NMARLOW
  101    90 21-SEP-89 UPDATE 01-SEP-98      17000      17000 Kochhar
Marlow          NKOCHHAR     NMARLOW
  101    90 21-SEP-89 UPDATE 05-JAN-00      17000      21000 Kochhar
Marlow          NKOCHHAR     NMARLOW
  101    90 21-SEP-89 UPDATE 29-DEC-98      17000      19000 Kochhar
Marlow          NKOCHHAR     NMARLOW

So it gives the old and new values for a changed column but simply repeats the
unchanged values as both old and new if there was not update to a column. I know
this is something simple I am missing in my logic, but no matter what I try I
either get the above or no result.

          Followup:

seems like you want to

a) partition the data by empid
b) order the partition by sys_tm
c) for updates
d) use lag() to see what's changed.

ops$tkyte@ORA920PC> drop table t;

Table dropped.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create table t
  2  as
  3  select empno, hiredate, sal, ename, sysdate-1 sys_tm
  4    from emp;

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert into t
  2  select empno, hiredate, sal*1.1, ename, sysdate-1/2
  3    from emp
  4   where deptno = 10;

3 rows created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert into t
  2  select empno, hiredate, sal, initcap(ename), sysdate-1/4
  3    from emp
  4   where deptno in( 10, 20 );

8 rows created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select empno, sys_tm, hiredate,
  2         sal,
  3             decode( lag(sal) over (partition by empno order by sys_tm),
  4                     sal, to_number(null),
  5                     lag(sal) over (partition by empno order by sys_tm)
)old_sal,
  6         ename,
  7             decode( lag(ename) over (partition by empno order by sys_tm),
  8                     ename, null,
  9                     lag(ename) over (partition by empno order by
sys_tm))old_ename
 10    from t
 11   order by empno, sys_tm
 12  /

     EMPNO SYS_TM    HIREDATE         SAL    OLD_SAL ENAME      OLD_ENAME
---------- --------- --------- ---------- ---------- ---------- ----------
      7369 26-JAN-04 17-DEC-80        800            SMITH
      7369 27-JAN-04 17-DEC-80        800            Smith      SMITH
      7499 26-JAN-04 20-FEB-81       1600            ALLEN
      7521 26-JAN-04 22-FEB-81       1250            WARD
      7566 26-JAN-04 02-APR-81       2975            JONES
      7566 27-JAN-04 02-APR-81       2975            Jones      JONES
      7654 26-JAN-04 28-SEP-81       1250            MARTIN
      7698 26-JAN-04 01-MAY-81       2850            BLAKE
      7782 26-JAN-04 09-JUN-81       2450            CLARK
      7782 26-JAN-04 09-JUN-81       2695       2450 CLARK
      7782 27-JAN-04 09-JUN-81       2450       2695 Clark      CLARK
      7788 26-JAN-04 09-DEC-82       3000            SCOTT
      7788 27-JAN-04 09-DEC-82       3000            Scott      SCOTT
      7839 26-JAN-04 17-NOV-81       5000            KING
      7839 26-JAN-04 17-NOV-81       5500       5000 KING
      7839 27-JAN-04 17-NOV-81       5000       5500 King       KING
      7844 26-JAN-04 08-SEP-81       1500            TURNER
      7876 26-JAN-04 12-JAN-83       1100            ADAMS
      7876 27-JAN-04 12-JAN-83       1100            Adams      ADAMS
      7900 26-JAN-04 03-DEC-81        950            JAMES
      7902 26-JAN-04 03-DEC-81       3000            FORD
      7902 27-JAN-04 03-DEC-81       3000            Ford       FORD
      7934 26-JAN-04 23-JAN-82       1300            MILLER
      7934 26-JAN-04 23-JAN-82       1430       1300 MILLER
      7934 27-JAN-04 23-JAN-82       1300       1430 Miller     MILLER

25 rows selected.

January 27, 2004 Raja -- Thanks for the question regarding "Most
      Admirable Oracle 9i Database Feature", version 8.1.6

      You Asked
In Oracle 8i , you seem to admire the Feature "Locally Managed Tablespace" the
Most as you  metioned about it countless times in these Columns.

Like Wise in Oracle 9i , can you single out one most admirable feature from your
point of View ?

Please give your insight into those features as well.

Thank You and Regards
Raja

      and we said...
There are a couple

o undo tablespace
o flashback query
o pipelined functions
o external tables

Guess if I had to pick one, it might be external tables ( coupled with
multi-table inserts and merge of course ;)

With that, sqlldr is obsolete.  I have direct path loads (via insert /*+ append
*/ or create table as select) and can use SQL to do everything.  So much easier.

I did a benchmark where this was our preferred method of loading.  It was so
cool to do a parallel direct path load with a simple insert.

Review &
      Followup

      Rating: 3
Bug in 9.2
      external tables  June 28, 2002
Reviewer:
      Richard  from somewhere cold and wet - UK
There seems to be a bug in 9.2 where a CREATE TABLE statement to mount an
external table which works in SQL*Plus will not work when using execute
immediate in a packaged procedure.

The problem is currently the subject of a TAR and the analyst dealing thinks it
is a bug.

Oh, and it doesn't work in a standalone procedure but will from an anonymous
block....

If you need to mount external tables for a series of data files, over time for
example, the only alternative is either to spawn an external SQL*Plus routine
(which I assume will work but I haven't tried because it defeats the object) or
create the ext table as part of the database build and rename the input files to
a common name to mount them.

External tables react to changes in the underlying file - you can actually edit
the file and see the changes immediately in the table.

          Followup:

tar#?  error msg?  (my crystal ball is in the shop this week, my sixth sense is
a little off due to that)

Based on:

"Oh, and it doesn't work in a standalone procedure but will from an anonymous
block...."

I'll betcha the error is an ORA-1031.  If so, read:

http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html
and close your tar.... (and grant yourself CREATE TABLE directly...)

      Rating:
      5
benchmark  July 01, 2002
Reviewer:
      Garry Robinson  from Edinburgh, Scotland
Could you please make available details of the benchmark you carried out and the
results which proved the usefulness of the external tables feature?

          Followup:

I'll not be sharing numbers as raw numbers without background information are
useless.  Sort of like someone saying "hey, I had 100,000 waits for IO -- I
waited a total of 1000 seconds for this.  Is that bad?".  You cannot tell given
that information  -- you need a ton of supporting facts (like the duration --
was that over 5 years, 5 months, 5 days, 5 hours, 5 minutes.  Like the number of
attempted IO's -- was that 100,000 waits out of 1 trillion, billion, million....
 Like the number of concurrent users  and so on)

I'll tell you what I did and my observations.

We had dozens of fixed width files to load up.  About 150 gig of raw input data.

We had a very short period of time to do it in (normally, I would schedule 4-6
weeks for a benchmark to scope it out, set it up, tune the processes, and so
on).  We had 4 days from end to end (it was a mistake to enter into this
particular benchmark -- it was more a race to see whose team was smarter, not
which product was better.  I *hate* benchmarks that cannot be used to provide
truly useful information.  The customer was not willing to take the time
necessary to learn anything from this exercise.  In the end -- it was an utter
waste of  time and resources, but that is another discussion)

Normally, you might look at that much data and try to figure out things like,
Hmm, how many parallel direct path loaders would be optimal in order to load
this data as fast as possible?  Should I run 20 for this input set, 10 for this
other one and how many should I run concurrently?

Then, you have to sit down and write the scripts to fire off 20 loaders for that
input set, 10 for that one and so on.

What we did instead?  We enabled parallel automatic tuning (let the database
figure out the optimum degree of parallelism based on current workload and the
request itself.  We used dbms_stats to jamm in information about the flat files
(so the DB would have a clue as to the volume of data).  We then just used
CREATE TABLE AS SELECT <from external table> with the parallel and unrecoverable
options.  The DB then did all of the work for us -- doing the parallel direct
path load.  Not only that, but we were able to apply our sql functions easily to
transform the data.

We did not benchmark a finely tuned sqlldr parallel direct path load VS
"database -- do the work, load this stuff for us".  So, I don't know if it was
faster (hey, we as humans can probably out tune the software given sufficient
time) but I can say this -- the time we saved by not having to figure all of
that stuff out  on our own allowed us to finish and it was more then fast
enough.

      Rating: 5
External table
      parallel load   January 26, 2004
Reviewer:
      Lee
Tom,
How do you do that ("We used dbms_stats to jamm in information about the flat
files
(so the DB would have a clue as to the volume of data)").

I am running "create table as select from external table" with
parallel_adaptive_multi_user=true and parallel_automatic_tuning=true and
parallel_max_servers=80 and cpu_count=8. I am loading 2 BIG external tables and
Oracle give 17 parallel treds to each one and it take very long time to execute.

How can a give oracle some info about the external flat files (examle will be
very helpful)?

Thanks Tom.

          Followup:

exec dbms_stats.set_table_stats( user, 'TABLE_NAME', num_rows => nnnnn );

for example -- look up dbms_stats in the plsql supplied packages guide.

However -- it is going to be full scanning this stuff and writing it out to
database blocks -- 2 "big" (big being a rather subjective term, how big is big)
external tables might take a "long" (another rather subject term -- is 5 minutes
long -- sometimes) time.

Numbers -- need numbers (then need to understand the disk system and so on).
One needs to know what are the inputs and what speed you think this should go
and whether you have the necessary disk hardware in order to "make it so"

      Rating: 5
External table
      parallel load   January 26, 2004
Reviewer:
      Lee
You right, sorry.

I have 2 external tables,
1 is 4 flat files (Unix ls):
263536 hedis_rx_{200112}.tot
259948 hedis_rx_{200206}.tot
220844 hedis_rx_{200212}.tot
218540 hedis_rx_{200106}.tot
41 columns

the other is 1 flat file (Unix ls):
1799864 MAEMHEDC_080503.200106.TAP
61 columns

Non default oracle parameters (other then path parameters):
aq_tm_processes = 1
db_block_size = 8192
db_cache_size = 117440512
db_file_multiblock_read_count = 16
large_pool_size = 33554432
open_cursors = 300
parallel_adaptive_multi_user = true
parallel_automatic_tuning = true
pga_aggregate_target = 25165824
processes = 200
shared_pool_size = 83886080
sort_area_size = 524288

I'm working on Redhat linux with 8 cpu, db is Oracle 9i 2nd release.

I'm not really sure about the other information, is this enogh information to
work with?

I tried to execute it NOT in parallel and the one with the 4 files took around 8
minutes, the other one around 20. When I tried in parallel it took about the
same, maybe a little longer. Oracle was going crazy with the parallel threds, I
counted around 17 per load.
I did try to analyze them but it was running for more then an hour so I killed
it.

Thanks Tom.

P.S.
when I executed in parallel, the external table was defined as parallel and the
"create table as" was defind as parallel (no degree).

          Followup:

are you saying these "huge" files are just a couple hundred kbytes?  less than
2meg?

shouldn't take a serial process more than a couple of seconds to load this --
show us the process you are using.

this is no where NEAR big enough to even begin considering parallel.

      Rating: 4
External table
      parallel load   January 26, 2004
Reviewer:
      Lee
Tom,
I think I misledd you, I'm sorry, the numbers above are in KB.

The 4 files are more then 200mb each and the single file load is over 1.7 gig.

Thanks.

          Followup:

so, what is the process whereby you load the data?

create table as select?
insert /*+ APPEND */ ?

      Rating: 3
How manage data
      errors  January 26, 2004
Reviewer:  Matt
       from Australia
I was just reading about the approach that was used for your benchmark. I was
wondering about how you captured and managed errors in the data.

I need to load data from several CSV formatted files and capture any errors in
the data so that these can be fixed at the data source and re-loaded.

I am currently using external tables to validate the data types in the files and
then retreiving arrays of data from the external table and bulk loading these
into my target tables (the targets have various constraints/triggers on them)
Exceptions are then caught in bulk and loaded into an error table. This enables
me to classify bad data by error for fault resolution.

In your approach how are errors in the source data captured? My understanding of
the CTAS is that it is atomic, so that if your CTAS includes the table
constraints, then a single data error causes the whole statement to fail.

Can you add some additional detail to explain how data errors were managed in
your approach?

Best Regards

          Followup:

I use where clauses to filter bad data out (bad data that was not captured by
the external table definition that is)

      Rating: 3
How would you
      capture any error conditions.  January 26, 2004

Reviewer:  Matt  from Australia
So, for the remaining data that is in error what happens to this? Surely you
must be capturing this and then fixing the problem data and then re-loading it?

Do you just right a multi-table insert so that good data goes in one table and
the errors in another?

Best Regards.

          Followup:

Nope, the data just went in.

If it passed the bad filters with the load (that gets most of it)
AND
it satisified my predicate (that got the rest)
THEN
it loaded.  really fast.

      Rating: 3
Regarding
      previous post  January 26, 2004
Reviewer:
      Matt  from Australia
The point I was trying to make above was - How do you know what piece of the
where clause caused your data to fail to allow you to quickly fix the problem?

          Followup:

i didn't.   data that didn't load, didn't load.  you do whatever you want to
figure out why (even just trying to load it a row at a time with no filters)

      Rating: 4
External table
      parallel load   January 27, 2004
Reviewer:
      Lee
It is "create table as"

Thanks.

          Followup:

tell us about the disks here and are you in archive log mode.

Can your DISKS support reading and writing as fast as you want.
If you are in archive log mode, is lgwr being a bottleneck.

January 27, 2004 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"

      Rating: 2
ORA-31603 -
      Error  January 22, 2004
Reviewer:  Maniappan
       from India
Hi Tom,

We have a partitioned table in schema 'AP'. When we give the query to find the
DDL Script by using dbms_metadata.get_ddl
its giving the above mentioned error.

But while querying through the DBA_Objects the same table under the same schema
(which was given in the param for get_ddl function) is very much there, with the
object type 'TABLE'.

select dbms_metadata.get_ddl('TABLE','AP_LIABILITY_BALANCE','AP')
  from dual;
 ORA-31603: object "AP_LIABILITY_BALANCE" of type TABLE not found in schema "AP"
ORA-06512: at "SYS.DBMS_SYS_ERROR", line 105 ORA-06512: at "SYS.DBMS_METADATA",
line 628 ORA-06512: at "SYS.DBMS_METADATA", line 1221 ORA-06512: at line 1

select * from dba_objects where object_name = 'AP_LIABILITY_BALANCE'
AND OBJECT_TYPE='TABLE';

OWNER
------------------------------
OBJECT_NAME
--------------------------------------------------------------------------------
SUBOBJECT_NAME                  OBJECT_ID DATA_OBJECT_ID OBJECT_TYPE
------------------------------ ---------- -------------- ------------------
CREATED   LAST_DDL_ TIMESTAMP           STATUS  T G S
--------- --------- ------------------- ------- - - -
AP
AP_LIABILITY_BALANCE
                                  4387765                TABLE
27-OCT-02 11-JUL-03 2003-01-17:21:11:43 VALID   N N N

Whatz wrong with my approach?

Thanks in advance

Mani

          Followup:

DBA_OBJECTS lets you see "everything", even things you cannot query.

can you query this
do you have access directly, not via a role?

      Rating:
      2
Reply  January 27, 2004
Reviewer:
      Maniappan  from Hyderabad, India
Tom,

Yeah I can query the table. The access is not given directly, but via the role
only.

Is this the cause?. If yes, any other alternate methods available for us to get
the definition?

Thanks
Mani

          Followup:

to narrow down your issue -- grant on the table directly and see if that
corrects the issue.  If so, then we know it is:

http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html
but it is not clear -- do you have the ability to query this table (not the view
dba_objects -- do you have access to the actual table itself)

      Rating: 5
LMT and storage
      clause  January 27, 2004
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
Tom,
Since we use LMTs for all our tables, I do not want the storage clause to appear
in output of dbms_metadata.get_ddl.
Is there a way to suppress this?
Thanks

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7490088329317

January 27, 2004 Anilkumar -- Thanks for the question regarding
      "Materialized Views & Bind variables", version 8.1.6

      You Asked
Tom,

We are using 8i (8.1.6) enterprise edition as well as 9i (standard edition). I
have created materialized views for summary columns & with the session settings
for query_rewrite_enabled=true & query_rewrite_integrity=enforced, views are
being accessed nicely when query to base tables are submitted by users for any
summerised columns. Materialized views are also being accessed when a query
contains a bind variable (&xx) for summary column.

But some of the sites and some of the magazines say that

'When a query is submitted to a base table with a bind variable embeded in it
for any of summerised columns, then Oracle will not use 'Materialized View' &
perform a full table scan as SQL parser will not rewrite the query used with a
bind variable.'

'For the access of Materialized view, query with a bind variable should include
hint such as ' /*+rewrite */ and then only SQL parser will rewrite query to
access Materialized view.'

I do not find this being true in my case. Can you explain what is the meaning of
the above. How far is it true ?

Anil Deshmukh

      and we said...
they are obviously wrong.  You have shown them wrong.  You know them to be
wrong.  You need to ask them why they are wrong I guess...

simple proof that the statements are wrong:

big_table@ORA920PC> grant query rewrite to big_table;

Grant succeeded.

big_table@ORA920PC> alter session set query_rewrite_enabled=true;

Session altered.

big_table@ORA920PC> alter session set query_rewrite_integrity=enforced;

Session altered.

big_table@ORA920PC>
big_table@ORA920PC> create materialized view big_table_mv
  2  build immediate
  3  refresh on commit
  4  enable query rewrite
  5  as
  6  select status, count(*)
  7    from big_table
  8   group by status
  9  /

Materialized view created.

big_table@ORA920PC> analyze table big_table_mv compute statistics;

Table analyzed.

big_table@ORA920PC>
big_table@ORA920PC> variable x varchar2(20);
big_table@ORA920PC> exec :x := 'VALID';

PL/SQL procedure successfully completed.

big_table@ORA920PC>
big_table@ORA920PC> set autotrace on
big_table@ORA920PC> select status, count(*)
  2    from big_table
  3   where status = :x
  4   group by status;

STATUS    COUNT(*)
------- ----------
VALID      3461488

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=11)
   1    0   TABLE ACCESS (FULL) OF 'BIG_TABLE_MV' (Cost=2 Card=1 Bytes=11)

Statistics
----------------------------------------------------------
         40  recursive calls
          0  db block gets
         18  consistent gets
          0  physical reads
          0  redo size
        442  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

big_table@ORA920PC> set autotrace off

January 28, 2004 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.

      Rating: 5
when is this 4k
      chunk introduced  January 27, 2004
Reviewer:
      A reader
Hi

Which version is this 4k chunks assignment of memory introduced? In one customer
their production support has a rule, no packages bigger than 128k in size, if
bigger split in two, they say this is because they used to get lots of ORA-04031
in Oracle 7...

          Followup:

Oracle 8

January 28, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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_

      Rating: 5
And to a
      PDF  January 28, 2004
Reviewer:
      A reader
Dear Tom/ Sean,
Thanks for this wonderful explaination!
Can "Transfrom_XML_with_XSL" be modified to output a PDF file?
What would be required to do this? Perhaps a XSL-FO processor (from Apache?)
would need to be installed? How would this talk to PL/SQL then?
Any guidance is welcome.
Thanks

          Followup:

getting outside the scope here --but yes, you would sort of need a pdf
transforming piece of software.  there isn't anything in the db to generate pdf
out of the box.

January 28, 2004 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!

      Rating: 4
Understanding
      Statspack.  January 20, 2004
Reviewer:  noel
      seq  from Mumbai, India.
I have learnt immensely from this site. With regard to analyzing statspack
reports, kindly throw some light on the following extract of my statspack report
and would like to if I am on the right track.

Although the soft parse ratio is good at 99.8%, I find that the logical reads
per transaction at 50,000 seems to be very high. Is this logical I/O you always
talk about which is further reflected in SQL query part of the statspack report
in the 'gets per exec' column. Am I to start tuning queries with high 'gets per
exec' first.

Secondly I find that the rollback per transaction is 39% which again seems high.
How do I identify which queries that need tuning. Or is it that I keep tuning
those queries with high 'gets per exec' and my rollback per transaction will
reduce.

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:              3,866.26             21,026.65
              Logical reads:              9,789.83             53,242.02
              Block changes:                 18.28                 99.42
             Physical reads:              2,196.84             11,947.51
            Physical writes:                  6.44                 35.01
                 User calls:                116.91                635.84
                     Parses:                 18.54                100.84
                Hard parses:                  0.04                  0.20
                      Sorts:                  2.16                 11.73
                     Logons:                  0.08                  0.46
                   Executes:                 26.18                142.41
               Transactions:                  0.18

  % Blocks changed per Read:    0.19    Recursive Call %:   10.78
 Rollback per transaction %:   39.04       Rows per Sort:   24.98

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:   99.99       Redo NoWait %:  100.00
            Buffer  Hit   %:   77.56    In-memory Sort %:   99.86
            Library Hit   %:   99.63        Soft Parse %:   99.80
         Execute to Parse %:   29.19         Latch Hit %:   99.98
Parse CPU to Parse Elapsd %:   83.24     % Non-Parse CPU:   99.99

SQL ordered by Gets

  Buffer Gets    Executions  Gets per Exec  % Total  Hash Value
--------------- ------------ -------------- ------- ------------
      3,917,864            5      783,572.8    39.4   1676175757
 select i.branch_cd , i.series_prefix, i.serial_no, i.execution_
dt,i.settlement_type,i.settlement_no  from instruction i  where
i.instruction_no in (select id.instruction_no  from instruction_
detail id  where i.depos_cd = id.depos_cd  and i.dp_id = id.dp_i
d  and i.client_cd = id.client_cd  and i.instruction_no = id.ins

        929,943            3      309,981.0     9.3   2369995686
SELECT /*+ RULE*/ c.dep_ac_no, h.holder_nm holder_nm, a.addr1, a
.addr2, a.addr3, a.city, a.state, a.country, a.zip, a.phone1, a.
phone2, a.phone3, a.fax, a.email, c.client_cd,c.client_stat FROM
 client c, client_address a, client_holders h WHERE c.depos_cd =
 h.depos_cd AND c.dp_id = h.dp_id AND c.client_cd = h.client_cd

        767,158            3      255,719.3     7.7    921042803
SELECT /*+ RULE*/ c.dep_ac_no, h.holder_nm holder_nm, a.addr1, a
.addr2, a.addr3, a.city, a.state, a.country, a.zip, a.phone1, a.
phone2, a.phone3, a.fax, a.email, c.client_cd,c.client_stat FROM
 client c, client_address a, client_holders h WHERE c.depos_cd =
 h.depos_cd AND c.dp_id = h.dp_id AND c.client_cd = h.client_cd

Kind Regards,
Noel.

          Followup:

LIOs and parsing rates have no real correlation.

50,000 LIO's per transaction seems high, if you are a transactional system, yes.
 You would identify the high load sql (seems have that -- right there -- might
be as easy as losing the rule hint to fix them -- might be in need of looking at
indexes).

a 40% rollback per transaction means that 40% of the time -- you do work and
then undo that work.  You need some transaction level auditing (in the
application) to see which transactions are rolling back most frequently and if
they are "read/write" transactions -- you need to figure out "why"

      Rating: 5
What about
      RESOURCE_LIMIT=true  January 20, 2004
Reviewer:
      Scott Watson  from Montreal, Canada.
Tom,

Can you explain why setting the RESOURCE_LIMIT parameter to true before running
your run_until procedure failed to update the CPU used statistic more
frequently.

I was lead to believe that by setting the parameter RESOURCE_LIMIT to TRUE that
CPU usage statistics would be updated more often, and as a result I could see
which process was actually consuming CPU cycles.  However, after taking
statspack snapshots
 o before the procedure started
 o before it ended
 o and just after it ended

proved to show that all the time ended up in the final snapshot.  IE there was
no CPU time in the snapshot collected for 2.5 minutes of the 3 minute job.

Assuming I had a profile setup for my user how is the profile enforced if the
CPU time is not updated more frequently.

By the way I ran the test the first time with RESOURCE_LIMIT=FALSE and then ran
the test again after issuing an alter system set resource_limit=true.  I was
running this test on my laptop running (9.2.0.3 on W2K)

Thanks,
Scott

          Followup:

because it was not designed to do that?  the code just doesn't do that -- that
stat is not update in the system until the call completes.

you have to have a call complete to get that statistic dumped.

CPU time is not reliable with statspack when you have long running procedures.

      Rating: 3
Rollback in
      Transactions  January 28, 2004
Reviewer:  noel
       from Mumbai, India.
You were talking about transaction auditing at the application level. How should
I go about it. Could you be a little more clear.

I checked the view v$db_object_cache which showed me a lot of updates. As per
the appliction each record has to be authorised by a specific person(s). Is this
the rollback that is appearing in the report.

BTW is the entire row containing all the old values copied to the rollback
segment or only the old values of the updated column(s).

This is because the columns that are updated contain null values and are updated
only at the time of such authorisations and not at the time of the insert.

If the second option is true, I am back to square one and will have to look at
other possibilities.

Noel.

          Followup:

I mean the application itself would be instruemented (contain code) to let you
(us) know what it is doing and how long it takes to do that.

sufficient data is placed into rollback -- if you insert a row of 5meg -- the
undo generated is very very small (undo = delete rowid).  if you update 5meg of
data -- the undo is very large.  If you delete -- same thing, large.  depends -
we put in there just enough to undo it again.

I don't see two options in this so cannot really comment.

January 28, 2004 Juliet -- Thanks for the question regarding "why
      view's trigger disappear?", version 9i

      You Asked
Got information from your archives, BUT solution is not provided or there's no
solution???

Archive : "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

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"

====>>  so what should I do if i have view's with instead of triggers became
invalid?  what syntax can I use to alter the view without my trigger
disappearing?
if "create or replace" cannot be used, what syntax can i used?

      and we said...
just recompile the view.  alter view view_name compile.

or, just IGNORE it.  the next guy to query it will cause it to compile (this is
what I do personally)
Review & Followup

      Rating: 4
compile
      view  January 28, 2004
Reviewer:  Tarun Babu
       from INDIA
Hi tom,

SQL> alter view emp_view compile;

View altered.

SQL>  select ut.trigger_name, ut.table_owner, ut.table_name
  2   from user_triggers ut where trigger_name = 'TRIG_EMP_VIEW';

no rows selected

After recompiling view, the same result.
Why it is so? Is the trigger dropped after create or replace?
Your comments please.

          Followup:

doesn't happen to me.  for all I know, your trigger never existed.  See how
after the alter view -- the trigger is still there (we see the output from it!)

ops$tkyte@ORA817DEV> create or replace view my_view
  2  as
  3  select x from t;

View created.

ops$tkyte@ORA817DEV> insert into my_view values ( 'world' );

1 row created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace trigger v_trigger
  2  instead of insert on my_view
  3  begin
  4    dbms_output.put_line( 'hello ' || :new.x );
  5  end;
  6  /

Trigger created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert into my_view values ( 'world' );
hello world

1 row created.

ops$tkyte@ORA817DEV> alter view my_view compile;

View altered.

ops$tkyte@ORA817DEV> insert into my_view values ( 'there' );
hello there

1 row created.

      Rating: 2
Re-create of
      views should always Re-create it's trigger (instead of)
      ?  January 28, 2004
Reviewer:  Juliet Co
       from Philippines
Additional Question:

Does it mean if i need to re-create of views should always re-create it's
trigger (instead of) ?

          Followup:

if you want the instead of triggers -- yes, you had better.

January 28, 2004 Zsolt -- Thanks for the question regarding "Bulk
      collect with cursor variable", version 8.1.5 i

      You Asked
Hi Tom,

1.I have a cursor :

TYPE T_AltalanosKurzor IS REF CURSOR ;
Kurzor T_AltalanosKurzor ;

TYPE T_OszlopTomb IS Table OF VarChar2(80);
OszlopTomb T_OszlopTomb;

If I use this cursor with a bulk collect:

OPEN Kurzor FOR  'select '|| OszlopStr ||' from '|| TablaStr||'
Where '|| FeltetelStr ||' Order By '||RendezesStr;

FETCH Kurzor BULK COLLECT INTO OszlopTomb ;

Then I get the following error message:

Internal Server Error 500
Exception: EOracleError
Message: ORA-03113: end-of-file on communication channel

Is something wrong with it?

I can only resolve this with a
 LOOP
....
   EXIT WHEN Kurzor%NOTFOUND;
.....
 END LOOP;

2. How can I write a Pl/Sql procedure that always stays
in memory and automatically fires a session afer "x" minutes of
inactivity?

Thank you for your help

Zsolt Csillag

      and we said...
1) It is a combination of a server bug and a documenation bug.

The server bug is that the error message:

declare
*
ERROR at line 1:
ORA-01001: invalid cursor
ORA-06512: at line 11

should be returned instead of killing the session.  that is
corrected in 8.1.6

The documentation bug is that weakly typed ref cursors ( as you
are using ) are not supported with BULK COLLECT.  Unfortunately
for you, strongly typed ref cursors cannot be dynamically
opened.

So, a possible workaround for you is to use a package variable
and execute:

tkyte@ORACLE8I> create or replace package state_pkg
  2  as
  3      TYPE T_OszlopTomb IS Table OF VarChar2(80);
  4      OszlopTomb T_OszlopTomb;
  5  end;
  6  /

Package created.

tkyte@ORACLE8I>
tkyte@ORACLE8I>
tkyte@ORACLE8I> declare
  2
  3      TYPE T_AltalanosKurzor IS REF CURSOR ;
  4      Kurzor T_AltalanosKurzor ;
  5
  6  begin
  7
  8      execute immediate '
  9      begin
 10         select username
              bulk collect into state_pkg.OszlopTomb
 11           from all_users
 12          order by created;
 13      end;';
 14
 15      dbms_output.put_line( 'got ' ||
               state_pkg.OszlopTomb.count );
 16  end;
 17  /
got 16

PL/SQL procedure successfully completed.

So, instead of dynamically opening a cursor -- dynamically
execute a block that fetches into the global table.

2) see PROFILES in the admin guide.  You can set up a profile
that limits idle time.  Those sessions that exceed that idle
time will be killed.

Review &
      Followup

      Rating: 5
Invalid Cursor
      when using REF CURSOR and bulk collects  April 17, 2002

Reviewer:  Sunil  from Australia
Very Helpful as the documentation of Oracle is totally misleading...
I was stuck with this problem for days when I decided to search for Oracle
discussion groups.

Thanks a lot.

      Rating:
      4
Helped  June 28, 2002
Reviewer:
      Leonardo Graf  from Zurich, Switzerland
I spent one day trying to solve this problem, nice to see there is a workaround.

I use Oracle 8.1.6i, there the error message is correct (ORA-01001: invalid
cursor) but the documentation bug is still present.

      Rating: 5
Still a small
      problem!  October 08, 2002
Reviewer:  Rene
      A.  from Vienna, Austria, Europe
1)
The execute immediate workaround is nice, but I've a query that return an
unknown amount of data (0 - 200.000+). So I want to LIMIT the BULK each time and
work on 100 rows, and then on the next 100 rows -> how do I do that with execute
immediate?

eg:
        OPEN cur FOR select ...;

        LOOP
            FETCH cur BULK COLLECT INTO t_vc LIMIT 100;

            outer_runs := outer_runs + 1;

            FOR i IN 1 .. t_vc.COUNT
            LOOP
                DBMS_OUTPUT.put_line('[' || outer_runs || '][' || i || '] -> ['
|| t_vc(i) || ']');
            END LOOP;

            EXIT WHEN cur%NOTFOUND;
        END LOOP;
        CLOSE cur;

How do I convert this to execute immediate?

2)
" So, instead of dynamically opening a cursor -- dynamically execute a block
that fetches into the global table. "

WIll there be any problems, if this function is long running, and a other user
is executing the same function -> is he then writting to the same
package_variable or to his "own" ... ?

          Followup:

1) you won't, you'll use

scott@ORA9I.WORLD> declare
  2          type rc is ref cursor;
  3          l_cur rc;
  4
  5          type array is table of varchar2(255) index by binary_integer;
  6          l_data array;
  7  begin
  8          open l_cur for 'select ename from emp';
  9          loop
 10                  fetch l_cur BULK COLLECT into l_data limit 5;
 11
 12                  for i in 1 .. l_data.count
 13                  loop
 14                          dbms_output.put_line( l_data(i) );
 15                  end loop;
 16                  dbms_output.put_line( '--------------' );
 17                  exit when l_cur%notfound;
 18          end loop;
 19          close l_cur;
 20  end;
 21  /
SMITH
ALLEN
WARD
JONES
MARTIN
--------------
BLAKE
CLARK
SCOTT
KING
TURNER
--------------
ADAMS
JAMES
FORD
MILLER
--------------

PL/SQL procedure successfully completed.

In Oracle9i and up (bulk collect on a dynamically opened ref cursor wasn't
supported till then)

Prior to that, in 8i and before, you'll have to use DBMS_SQL to perform array
fetches.

2) every package has its own package state.  you cannot see anyone elses
variable values, they cannot see yours.

      Rating: 4
Multiple
      Columns of Bulk Fetching  January 22, 2004

Reviewer:  Sanjeev  from Bangalore, India
Tom,
Thanks for all your inputs here. I read that this works for a single column. how
about multiple oclumns?
--Example
Type table1 is a Table of Employee%RowType;
EmpTable table1;
Select * Bulk collect into emptable from employee;

--or something like this
Type employeeList is record
 ( Id employee.id%Type,
   name employee.name%Type,
   manager employee.manager%Type);
Type empt is table of employeeList;
emptable empt;
Select id, name, manager bulk collect into emptable from employee where name
like 'A%';

generates an exception PLS-00597: expression 'EMPTABLE' in the INTO list is of
wrong type

How to get around with it in 8i (Oracle8i Enterprise Edition Release 8.1.7.3.0 )
using bulk collect? The only way i figured is declaring multiple collections one
for each column and fetching individually.
do you think any other better way to sort this out?

I also noticed that 9i even supports fetch assinged by dynamic SQL

http://otn.oracle.com/sample_code/tech/pl_sql/htdocs/x/Table_Functions_Cursor_Expressions/Bulk_Fetch_From_Cursor_Variable.htm

          Followup:

in 8i, you have to bulk collect into individual "arrays"

      Rating: 4
bulk inserts
      with many cursor variables in 8.1.7.4  January 27, 2004

Reviewer:  Duncan  from uk
Tom

Bear with me on this, it does have relevance to this thread eventually!
(database is 8.1.7.4)

I have a very wide table which will be populated with data overnight via stored
procedure using several bulky sql statements. There are a few ways i can do this
but I thought that one of the best ways would be to use a separate stored
procedure for each sql statement (ie supports modularised code) then using a ref
cursor return to a calling procedure each resultset.  Then the calling procedure
could take each resultset and build up an index-by-integer table (or something
similar) in memory ready to insert into the real table.

Questions are

1) Assuming I went with this method, I can't figure out how to "glue" each
resultset together (each one contains a primary key) so that all the records
line up with the correct data ready to do the insert into the real table.  Below
is an example of two of the procedures that i want to use defined in a package
along with the calling procedure P_SBE_Build_Table.  In P_SBE_Build_Table  I
have got as far as fetching the cursor into a record type variable but am now
stuck on how to merge these two resultsets into a table record that matches the
primary keys, so I can bulk insert into my table?? :

CREATE OR REPLACE PACKAGE PKG_SBE515 AS

TYPE C_Return_Cursor IS REF CURSOR;

TYPE rec_Table1 IS RECORD (    Payment_Org_ID     SBE_515.PAYMENT_ORG_ID%TYPE,
                ST_Count     SBE_515.ST_Count%TYPE);

TYPE rec_Table1_TABLE IS TABLE OF rec_Table1  INDEX BY BINARY_INTEGER;

PROCEDURE P_SBE_Build_Table  ;

PROCEDURE P_SBETable_1 (    P_Cur IN OUT C_Return_Cursor,
                V_Start_Date     IN DATE,
                V_End_Date     IN DATE        );

PROCEDURE P_SBETable_2 (    P_Cur IN OUT C_Return_Cursor,
                V_Start_Date     IN DATE,
                V_End_Date     IN DATE        );

END PKG_SBE515;
/

CREATE OR REPLACE PACKAGE BODY PKG_SBE515 AS

PROCEDURE P_SBE_Build_Table

IS

p_cur_Tbl_1      C_Return_Cursor;
p_cur_Tbl_2      C_Return_Cursor;

V_Start_Date    DATE:=(SYSDATE - 90);
V_End_Date    DATE:=(SYSDATE - 90);

cursor l_template_Tbl_1 is SELECT Payment_Org_ID, ST_Count FROM SBE_515;
l_rec_Tbl_1  l_template_Tbl_1%rowtype;

cursor l_template_Tbl_2 is SELECT Payment_ORG_ID, OO_FULL,
                OO_PART, OMP_FULL,
                OMP_PART, TOTAL_OO,
                TOTAL_OMP,TOTAL_FULL,TOTAL_PART,TOTAL_DOM
               FROM SBE_515;

l_rec_Tbl_2  l_template_Tbl_2%rowtype;

rec_SBE_515    SBE_515%ROWTYPE;

BEGIN

P_SBETable_1 (    p_cur_Tbl_1,
        V_Start_Date,
        V_End_Date);

LOOP

    FETCH p_cur_Tbl_1 BULK COLLECT INTO l_rec_Tbl_1;

-- ***** not sure what to do here, to load into table record so that primary
keys matched

    EXIT WHEN p_cur_Tbl_1%NOTFOUND;

END LOOP;

CLOSE p_cur_Tbl_1;

P_SBETable_2 (    p_cur_Tbl_2,
        V_Start_Date,
        V_End_Date);

LOOP

    FETCH p_cur_Tbl_2 BULK COLLECT INTO l_rec_Tbl_2;

-- ***** not sure what to do here, to load into table record so that primary
keys matched

    EXIT WHEN p_cur_Tbl_1%NOTFOUND;

END LOOP;

CLOSE p_cur_Tbl_1;

END P_SBE_Build_Table;

PROCEDURE P_SBETable_1 (    P_Cur IN OUT C_Return_Cursor,
                V_Start_Date     IN DATE,
                V_End_Date     IN DATE        )
IS

BEGIN

-- Table 1 sql:

    OPEN P_Cur FOR

    SELECT     ORG_ID,
        ST_COUNT
    FROM    (
        SELECT     ORG_ID,
            NVL(SUM(THECOUNT), 0) AS ST_COUNT
        FROM    (
            SELECT
                PAYMENT_ORG_ID,
                COUNT(*) AS THECOUNT
            FROM     PAID_CLAIM
            WHERE     PAYMENT_ORG_ID IN (SELECT Org_ID FROM Organisation WHERE
Type_Id IN (5, 7, 10))
            AND     PAYMENT_PERIOD_TO >= '1/Oct/2003' AND PAYMENT_PERIOD_TO <
'1/Apr/2004'
            AND     CLAIM_TYPE='GOS1'
            GROUP BY PAYMENT_ORG_ID
            UNION ALL
            SELECT     PAYMENT_ORG_ID,
                SUM(ST_TOTAL_NUMBER) AS THECOUNT
            FROM     PAID_NHS_SIGHT_TESTS
            WHERE     PAYMENT_ORG_ID IN (SELECT Org_ID FROM Organisation WHERE
Type_Id IN (5, 7, 10))
            AND     PAYMENT_PERIOD_TO >= '1/Oct/2003' AND PAYMENT_PERIOD_TO <
'1/Apr/2004'
            GROUP BY PAYMENT_ORG_ID
            UNION ALL
            SELECT     PAYMENT_ORG_ID,
                SUM(NVL(ST_BEFORE_NUMBER, 0) + NVL(ST_AFTER_NUMBER, 0)) AS
THECOUNT
            FROM     PAID_OMPS_STATEMENTS
            WHERE     PAYMENT_ORG_ID IN (SELECT Org_ID FROM Organisation WHERE
Type_Id IN (5, 7, 10))
            AND     PAYMENT_PERIOD_TO >= '1/Oct/2003' AND PAYMENT_PERIOD_TO <
'1/Apr/2004'
            GROUP BY PAYMENT_ORG_ID
            ),
            (SELECT Org_ID FROM Organisation WHERE Type_Id IN (5, 7, 10))
        WHERE     PAYMENT_ORG_ID(+) = ORG_ID
        GROUP BY ORG_ID
        )
    ORDER BY ORG_ID;

END P_SBETable_1;

PROCEDURE P_SBETable_2 (    P_Cur IN OUT C_Return_Cursor,
                V_Start_Date     IN DATE,
                V_End_Date     IN DATE        )
IS

BEGIN

-- Table 2 sql:

    OPEN P_Cur FOR

SELECT    ORG_ID,
    OO_FULL,
    OO_PART,
    OMP_FULL,
    OMP_PART,
    (OO_FULL + OO_PART) AS TOTAL_OO,
    (OMP_FULL + OMP_PART) AS TOTAL_OMP,
    (OO_FULL + OMP_FULL) AS TOTAL_FULL,
    (OO_PART + OMP_PART) AS TOTAL_PART,
    (OO_FULL + OO_PART + OMP_FULL + OMP_PART) AS TOTAL_DOM
FROM     (
    SELECT     ORG_ID,
        SUM(OOFULL) AS OO_FULL,
        SUM(OOPART) AS OO_PART,
        SUM(OMPFULL) AS OMP_FULL,
        SUM(OMPPART) AS
        OMP_PART
    FROM     (
        SELECT     /*+ INDEX(PAID_CLAIM IDX_PCLAIM_PAYMENT_ORG_ID) */
            c.PAYMENT_ORG_ID,
            COUNT(*) AS OOFULL,
            0 AS OOPART,
            0 AS OMPFULL,
            0 AS OMPPART
        FROM     PAID_CLAIM c,
            PRACTITIONER p
        WHERE     c.PAYMENT_ORG_ID IN (SELECT Org_ID FROM Organisation WHERE
Type_Id IN (5, 7, 10))
        AND     c.PAYMENT_PERIOD_TO >= V_Start_Date AND c.PAYMENT_PERIOD_TO <
V_End_Date
        AND     CLAIM_TYPE='GOS1'
        AND     (c.PATIENT_DV_VISITED=1 OR    PATIENT_DV_VISITED=2)
        AND    c.PRACTITIONER_ID = p.PRACTITIONER_ID
        AND     p.OMP='NO'
        GROUP BY c.PAYMENT_ORG_ID
        UNION ALL
        SELECT     c.PAYMENT_ORG_ID,
            (SUM(DV_FULL_BEFORE_NUMBER) + SUM(DV_FULL_AFTER_NUMBER)) AS OOFULL,
            0 AS OOPART,
            0 AS OMPFULL,
            0 AS OMPPART
        FROM     PAID_NHS_SIGHT_TESTS c
        WHERE     c.PAYMENT_ORG_ID IN (SELECT Org_ID FROM Organisation WHERE
Type_Id IN (5, 7, 10))
        AND     c.PAYMENT_PERIOD_TO >= V_Start_Date AND c.PAYMENT_PERIOD_TO <
V_End_Date
        GROUP BY c.PAYMENT_ORG_ID
        UNION ALL
        SELECT     /*+ INDEX(PAID_CLAIM IDX_PCLAIM_PAYMENT_ORG_ID) */
            c.PAYMENT_ORG_ID,
            0 AS OOFULL,
            0 AS OOPART,
            COUNT(*) AS OMPFULL,
            0 AS OMPPART
        FROM     PAID_CLAIM c,
            PRACTITIONER p
        WHERE     c.PAYMENT_ORG_ID IN (SELECT Org_ID FROM Organisation WHERE
Type_Id IN (5, 7, 10))
        AND     c.PAYMENT_PERIOD_TO >= V_Start_Date AND c.PAYMENT_PERIOD_TO <
V_End_Date
        AND     c.CLAIM_TYPE='GOS1'
        AND     (c.PATIENT_DV_VISITED=1 OR c.PATIENT_DV_VISITED=2)
        AND     c.PRACTITIONER_ID = p.PRACTITIONER_ID
        AND     p.OMP='YES'
        GROUP BY c.PAYMENT_ORG_ID
        UNION ALL
        SELECT     c.PAYMENT_ORG_ID,
            0 AS OOFULL,
            0 AS OOPART,
            (SUM(NVL(FULL_DV_BEFORE_NUMBER, 0) + NVL(FULL_DV_AFTER_NUMBER, 0)))
AS OMPFULL,
            0 AS OMPPART
        FROM     PAID_OMPS_STATEMENTS c
        WHERE     c.PAYMENT_ORG_ID IN (SELECT Org_ID FROM Organisation WHERE
Type_Id IN (5, 7, 10))
        AND     c.PAYMENT_PERIOD_TO >= V_Start_Date AND c.PAYMENT_PERIOD_TO <
V_End_Date
        GROUP BY c.PAYMENT_ORG_ID
        UNION ALL
        SELECT     /*+ INDEX(PAID_CLAIM IDX_PCLAIM_PAYMENT_ORG_ID) */
            c.PAYMENT_ORG_ID,
            0 AS O0FULL,
            COUNT(*) AS OOPART,
            0 AS OMPFULL,
            0 AS OMPPART
        FROM     PAID_CLAIM c,
            PRACTITIONER p
        WHERE     c.PAYMENT_ORG_ID IN (SELECT Org_ID FROM Organisation WHERE
Type_Id IN (5, 7, 10))
        AND     c.PAYMENT_PERIOD_TO >= V_Start_Date AND c.PAYMENT_PERIOD_TO <
V_End_Date
        AND     c.CLAIM_TYPE='GOS1'
        AND     c.PATIENT_DV_VISITED=3
        AND     c.PRACTITIONER_ID = p.PRACTITIONER_ID
        AND     p.OMP='NO'
        GROUP BY c.PAYMENT_ORG_ID
        UNION ALL
        SELECT     c.PAYMENT_ORG_ID,
            0 AS OOFULL,
            (SUM(DV_PART_BEFORE_NUMBER) + SUM(DV_PART_AFTER_NUMBER)) AS OOPART,
            0 AS OMPFULL,
            0 AS OMPPART
        FROM     PAID_NHS_SIGHT_TESTS c
        WHERE     c.PAYMENT_ORG_ID IN (SELECT Org_ID FROM Organisation WHERE
Type_Id IN (5, 7, 10))
        AND     c.PAYMENT_PERIOD_TO >= V_Start_Date AND c.PAYMENT_PERIOD_TO <
V_End_Date
        GROUP BY c.PAYMENT_ORG_ID
        UNION ALL
        SELECT     /*+ INDEX(PAID_CLAIM IDX_PCLAIM_PAYMENT_ORG_ID) */
            c.PAYMENT_ORG_ID,
            0 AS OOFULL,
            0 AS OOPART,
            0 AS OMPFULL,
            COUNT(*) AS OMPPART
        FROM     PAID_CLAIM c,
            PRACTITIONER p
        WHERE     c.PAYMENT_ORG_ID IN (SELECT Org_ID FROM Organisation WHERE
Type_Id IN (5, 7, 10))
        AND     c.PAYMENT_PERIOD_TO >= V_Start_Date AND c.PAYMENT_PERIOD_TO <
V_End_Date
        AND     c.CLAIM_TYPE='GOS1'
        AND     c.PATIENT_DV_VISITED=3
        AND     c.PRACTITIONER_ID = p.PRACTITIONER_ID
        AND     p.OMP='YES'
        GROUP BY c.PAYMENT_ORG_ID
        UNION ALL
        SELECT     c.PAYMENT_ORG_ID,
            0 AS OOFULL,
            0 AS OOPART,
            0 AS OMPFULL,
            (SUM(NVL(PART_DV_BEFORE_NUMBER, 0) + NVL(PART_DV_AFTER_NUMBER, 0)))
AS OMPPART
        FROM     PAID_OMPS_STATEMENTS c
        WHERE     c.PAYMENT_ORG_ID IN (SELECT Org_ID FROM Organisation WHERE
Type_Id IN (5, 7, 10))
        AND     c.PAYMENT_PERIOD_TO >= V_Start_Date AND c.PAYMENT_PERIOD_TO <
V_End_Date
        GROUP BY c.PAYMENT_ORG_ID
             ),
             (SELECT ORG_ID FROM ORGANISATION WHERE ORG_ID IN (SELECT Org_ID
FROM Organisation WHERE Type_Id IN (5, 7, 10)))
    WHERE PAYMENT_ORG_ID(+) = ORG_ID
    GROUP BY ORG_ID
    )
ORDER BY ORG_ID;

END P_SBETable_2;

END PKG_SBE515;
/

2) Is this method the best way? Or would it be better to create one huge stored
procedure with all the sql resultsets unioned together before loading into the
real table?

3) Any other better more efficient way you can think of?  I am trying to avoid
too much disk I/O so am trying to build huge insert statements in memory and do
bulk inserts.

Please can you help.

Many thanks

          Followup:

when you start talking about "gluing result sets together", yuo are talking
about coding your own database engine.  You are doing joins.

It is all about inline views at that point.  the database is the thing that
knows how to glue result sets together.

You really ought to look at queries like this:

        SELECT     /*+ INDEX(PAID_CLAIM IDX_PCLAIM_PAYMENT_ORG_ID) */
            c.PAYMENT_ORG_ID,
            COUNT(*) AS OOFULL,
            0 AS OOPART,
            0 AS OMPFULL,
            0 AS OMPPART
        FROM     PAID_CLAIM c,
            PRACTITIONER p
        WHERE     c.PAYMENT_ORG_ID IN (SELECT Org_ID FROM Organisation WHERE
Type_Id IN (5, 7, 10))
        AND     c.PAYMENT_PERIOD_TO >= V_Start_Date AND c.PAYMENT_PERIOD_TO <
V_End_Date
        AND     CLAIM_TYPE='GOS1'
        AND     (c.PATIENT_DV_VISITED=1 OR    PATIENT_DV_VISITED=2)
        AND    c.PRACTITIONER_ID = p.PRACTITIONER_ID
        AND     p.OMP='NO'
        GROUP BY c.PAYMENT_ORG_ID
        UNION ALL
        SELECT     c.PAYMENT_ORG_ID,
            (SUM(DV_FULL_BEFORE_NUMBER) + SUM(DV_FULL_AFTER_NUMBER)) AS OOFULL,
            0 AS OOPART,
            0 AS OMPFULL,
            0 AS OMPPART
        FROM     PAID_NHS_SIGHT_TESTS c
        WHERE     c.PAYMENT_ORG_ID IN (SELECT Org_ID FROM Organisation WHERE
Type_Id IN (5, 7, 10))
        AND     c.PAYMENT_PERIOD_TO >= V_Start_Date AND c.PAYMENT_PERIOD_TO <
V_End_Date
        GROUP BY c.PAYMENT_ORG_ID
....

and merging into a single pass like this:

SELECT c.PAYMENT_ORG_ID,
       COUNT( case when CLAIM_TYPE='GOS1'
                        AND (c.PATIENT_DV_VISITED=1 OR    PATIENT_DV_VISITED=2)
                        AND c.PRACTITIONER_ID = p.PRACTITIONER_ID
                        AND p.OMP='NO'
                   then 1
               end ) AS OOFULL1,
       (SUM(DV_FULL_BEFORE_NUMBER) + SUM(DV_FULL_AFTER_NUMBER)) AS OOFULL2,
       0 AS OOPART,
       0 AS OMPFULL,
       0 AS OMPPART
  FROM PAID_CLAIM c,
       PRACTITIONER p
 WHERE c.PAYMENT_ORG_ID IN (SELECT Org_ID FROM Organisation WHERE Type_Id IN (5,
7, 10))
   AND c.PAYMENT_PERIOD_TO >= V_Start_Date AND c.PAYMENT_PERIOD_TO < V_End_Date
 GROUP BY c.PAYMENT_ORG_ID

A quick glance at your monster query leads me to think "single pass -- no unions
anywhere"

      Rating: 3
sorry mistake
      in above post  January 27, 2004
Reviewer:
      Duncan  from UK
Sorry I didn't mean UNION in the above question, I meant JOIN.

So my question 2) should read:

2) Is this method the best way? Or would it be better to create one huge stored
procedure with all the sql resultsets JOINED together before loading into the
real table?

          Followup:

less sql statements = better process.

fewer = better

less procedural code = best

insert as select = really good.

      Rating:
      4
thanks  January 28, 2004
Reviewer:
      Duncan  from UK
Thanks for the help on this.
However with the query that you rewrote, I think there is an error because the
second union is selecting from a different table PAID_NHS_SIGHT_TESTS:

        SELECT     /*+ INDEX(PAID_CLAIM IDX_PCLAIM_PAYMENT_ORG_ID) */
            c.PAYMENT_ORG_ID,
            COUNT(*) AS OOFULL,
            0 AS OOPART,
            0 AS OMPFULL,
            0 AS OMPPART
        FROM     PAID_CLAIM c,
            PRACTITIONER p
        WHERE     c.PAYMENT_ORG_ID IN (SELECT Org_ID FROM Organisation WHERE
Type_Id IN (5, 7, 10))
        AND     c.PAYMENT_PERIOD_TO >= V_Start_Date AND c.PAYMENT_PERIOD_TO <
V_End_Date
        AND     CLAIM_TYPE='GOS1'
        AND     (c.PATIENT_DV_VISITED=1 OR    PATIENT_DV_VISITED=2)
        AND    c.PRACTITIONER_ID = p.PRACTITIONER_ID
        AND     p.OMP='NO'
        GROUP BY c.PAYMENT_ORG_ID
        UNION ALL
        SELECT     c.PAYMENT_ORG_ID,
            (SUM(DV_FULL_BEFORE_NUMBER) + SUM(DV_FULL_AFTER_NUMBER)) AS OOFULL,
            0 AS OOPART,
            0 AS OMPFULL,
            0 AS OMPPART
        FROM     PAID_NHS_SIGHT_TESTS c   -- *** not the same table
        WHERE     c.PAYMENT_ORG_ID IN (SELECT Org_ID FROM Organisation WHERE
Type_Id IN (5, 7, 10))
        AND     c.PAYMENT_PERIOD_TO >= V_Start_Date AND c.PAYMENT_PERIOD_TO <
V_End_Date
        GROUP BY c.PAYMENT_ORG_ID
....

The join condition from the first select statement:

c.PRACTITIONER_ID = p.PRACTITIONER_ID

has been placed in the case statement is this correct?

SELECT c.PAYMENT_ORG_ID,
       COUNT( case when CLAIM_TYPE='GOS1'
                        AND (c.PATIENT_DV_VISITED=1 OR    PATIENT_DV_VISITED=2)
                        AND c.PRACTITIONER_ID = p.PRACTITIONER_ID
                        AND p.OMP='NO'
                   then 1
               end ) AS OOFULL1,
       (SUM(DV_FULL_BEFORE_NUMBER) + SUM(DV_FULL_AFTER_NUMBER)) AS OOFULL2,
       0 AS OOPART,
       0 AS OMPFULL,
       0 AS OMPPART
  FROM PAID_CLAIM c,
       PRACTITIONER p
 WHERE c.PAYMENT_ORG_ID IN (SELECT Org_ID FROM Organisation WHERE Type_Id IN (5,

7, 10))
   AND c.PAYMENT_PERIOD_TO >= V_Start_Date AND c.PAYMENT_PERIOD_TO < V_End_Date
 GROUP BY c.PAYMENT_ORG_ID

This sql does seem a lot better so please can you show how to cater for the
second sql being from a different table.

Many thanks

          Followup:

ok, never mind -- did not notice the table name changes (the correlation names
were all the same, looked like the same query repeated over and over)

January 28, 2004 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 :)

      Rating: 5
consistent gets
      and physical reads  January 28, 2004
Reviewer:
      A reader
hi

I have a query when run with autotrace on always returns this

SELECT   fill.bo_id_contact, fill.interaction_id,
          fill.interact_begin_ch,
         fill.interact_status
    FROM sysadm.ps_rit_interact_vw fill
   WHERE bo_id_contact = 1000000000000007983985
ORDER BY interact_begin DESC

TABLE ACCESS (FULL) OF 'PS_RI_INTERACTION' (Cost=590 Card=42 Bytes=1176)

       6157  consistent gets
       6115  physical reads

No matter how many time I run, I wonder why it always do physical reads when my
db cache is 200mb...? The table fits perfectly in the memory

do you know why?

          Followup:

search for "long and short"

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:255215154182
on this site.  your table is more than 2% of the buffer cache (probably about
25% of it assuming a 8k block).  we cache that differently.  for that many
blocks -- it is most likely FASTER to get from disk using multi-block IO rather
than search through the buffer cache block by block by block by block.

January 28, 2004 Clement -- Thanks for the question regarding
      "Stripe and Mirror Everything, including redo logs?", version 9.0.1

      You Asked
I have read the open world presenation on "Optimal Storage Configuration Made
Easy"
http://technet.oracle.com/deploy/availability/pdf/oow2000_sane.pdf
Based on the fact that Oracle now has OMF (Oracle Managed Files), in which we
only provide one mount point and all files are placed on that one mount.  Should
DBA be concerned with multiplexing redo logs, control files and archived logs?

The above presentation makes good points that the operating system will do a
better job at distributing I/O then the DBA ever could.  Is it now safe to stop
multiplexing Oracle files, since multiplexing will just double the I/O to the
same set of disk drives?  I know that some DBA like to multiplex as a safe guard
for human error, but there is nothing to stop a careless DBA from deleting all
of the multiplexed copies of a file.

Clement

      and we said...
YES, the DBA SHOULD BE CONCERNED.

The parameters are actually:

db_create_file_dest                  string
db_create_online_log_dest_1          string
db_create_online_log_dest_2          string
db_create_online_log_dest_3          string
db_create_online_log_dest_4          string
db_create_online_log_dest_5          string

The online log dest controls where mirrors of the redo and control files go.
You still need to pay attention to that.  None of them affect the archiving
dest, none of that has changed.

If the disks are 100% assured to never fail, you would not need multi-plexing.
Mirrors decrease but do not remove that.  I still multi-plex them no matter
what. I'm not worried about the careless dba, I'm worried about hardward
failing.

Review &
      Followup

      Rating: 4
Disk failure is
      not the only reason  June 27, 2002
Reviewer:
      A reader  from USA
I have a very high confidence in our disk array.  Mirrors, stripes and all that.
 Still we backup daily because we know that a failure will occur.  For example I
have had to recover all files on a mount point because of a file system
corruption.  This made everything on that mount point unusable.  I would hate to
think of what would happen it that file system held our redo logs and they were
not duplexed to another file system.  Broken disks are not the only point of
failure in a file system.

      Rating: 1
More questions
      on S.A.M.E.  June 27, 2002
Reviewer:  Clement
      Charbonnet  from Houston, TX USA
The OpenWorld article which comes from an Oracle employee (
http://technet.oracle.com/deploy/availability/pdf/oow2000_sane.pdf
) states that
all the disks should be striped and mirrored together.  Multiple mount points
would actually exist on the same set of disks creating one large drive with
sub-mount points.  This means that a hardware failure that would cause loss of
data would impact all the mount points and all the multiplexed copies.  Given
this methodology why would you continue to muliplex, since multiplexing will not
protect from device failures?

          Followup:

it would seem self evident to me anyway that you would use common sense and
multiplex to different, independent physical devices.  Ones that will not be
affected by the demise of the other.  Else, as you pointed out, you are wasting
your time.

Also, this paper discusses this:

<quote>
Striping the online log files across many disks does increase the probability
that a double disk failure will destroy an online log. Loss of an online log
will cause unrecoverable loss of the most recent updates. Again, the probability
of
this is very low, and a standby or remote mirror of the online log is the best
way to protect against it. If a standby is too complex or expensive to implement
and you want to reduce the very low probability of data loss, then you should
implement additional mirroring of the online logs, control file, and archive
logs. We call these three sets of files the recovery set. These are the files
that are needed to perform a full recovery from a backup.

Ideally you should create an additional mirror of all files in the recovery set
at the Oracle level. Mirroring by Oracle using file multiplexing is somewhat
more resilient to corruptions than mirroring at the storage subsystem level1.
The
additional copy of the recovery set should be separated from the main copies in
every way possible in order to reduce the incidence of correlated failures. For
example, the additional mirror should be on a separate files system, which
resides on a separate volume, which is on different disks, accessed by different
controllers, and ideally on a separate RAID device.
</quote>

So, they tell you to do just that (use different, separate devices)

so yes, I would.  Multiplex them -- standby (data guard in 9i) is a great
mutli-plexing opportunity as well.

      Rating:
      5
  August 09, 2002
Reviewer:
      A reader
Hi Tom,

In that same paper (Optimal storage configuration made easy), they suggested :
<quote>
1-Ensure that sequential access occurs in at least 1 megabyte units to achieve
high sequential bandwidth

2-Place frequently used data on the outer half of disks to provide the fastest
transfer rate.

3-Place frequently used data on the outer half of disks to minimize seek
overhead.

4-Place less frequently accessed data towards the inside half of disks.

<quote>

And here are my doubts:

a-I would really appreciate if you can explain me what should I do in order to
implement points 2,3 and 4. I cannot figure out what to do in order to create a
table in
the outer/inside half of the disk. Is there any special command to do this?

b-They suggested to set db_file_multiblock_read_count=stripe width=OS
IO=1megabyte.
  b.1-Does the OS IO mean OS block size?
  b.2-What is your oponion about this? Do you think that this strategy can be
apply to a server which will support an OLTP database with some batch process
running during the day, without problems?

Thanks in advance for any help you can give me.

          Followup:

2, 3, 4 -- almost impossible to achive, especially with volume managers and
other stuff.

Much Much Much bigger fish to fry out there.  I would skip 2, 3, and 4. (you
would use physical partitions on the disk to do this, you would partition the
disk and use the right partitions -- datafiles -- for certain tables)

b -- sounds more like DW then OLTP to me.  db_file_multiblock_read_count is data
warehouse, not oltp.

      Rating:
      5
  August 09, 2002
Reviewer:
      A reader
thanks for answering me Tom.

1-So, taking into account your answer for point b, which is the OS block size
and stripe size that you suggest for a database which will have a 8K
db_block_size. Can you help me with this point?

2-Can you tell me if I am in the right direction:
I will have a new storage with 30 disks, each of them will have 73Gigabytes and
my intention is to distribute the disk in the following way:
-Rollbacks -> raid 10
-system, online redo and control files -> 10
-archive redo -> raid 10
-temporary -> raid 0
-most used appl.data files -> raid 10
-less used appl.data files -> raid 5

3-What does exactly mean a "hot spare disk"?

Thanks for helping me with this, I really appreciate it.

          Followup:

1) see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:396975084466
for some guidance. I don't have hard numbers in that area.

2) see

http://www.acnc.com/04_01_10.html
raid 10 is good for that.

3) hot spare generally means "sitting there waiting to be used".  You have extra
disks just waiting to be plugged in or already plugged in in the event of
failure.

      Rating:
      5
  August 09, 2002
Reviewer:
      A reader
I have read many posting in your web page and other papers about stripe size and
OI size but I'm still a little confuse. Can you explain me the difference
between IO size, stripe size and OS block size? Are the OS block size and IO
size the same thing?

Thanks

          Followup:

OS block size -- how the operating system manages things at the lowest level

IO size -- how much IO the controllers can do in one fell swoop (hardware thing)

Stripe Size -- when setting up RAID, you many times "stripe" the disks -- using
more then one disk to appear as a single disk.  If your strip size was one meg
and you wrote 10 meg of data -- 1 meg goes to one disk , 1 meg to the other, 1
meg to the first and so on.

IO size and OS block size are not the same, IO size is generally MUCH larger.

      Rating: 3
OMF &
      Redo's  January 07, 2004
Reviewer:  vinnie
       from Orlando
I am considering moving to OMF's.

When looking at the REDO's I noticed that when you configure
the following as suggested:
db_create_online_log_dest_1 = '/u1/oradata/sample'
db_create_online_log_dest_2 = '/u2/oradata/sample'

When you create the dB
you will get
/u1/oradata/sample/ora_1_xxxx.log

If this is true, wouldn't you just let the init.ora
file create your control files in alternate locations?
What do you do?
/u1/oradata/sample/ora_2_xxxx.log
/u1/oradata/sample/ora_xxxx.ctl
&
/u2/oradata/sample/ora_1_nnnn.log
/u2/oradata/sample/ora_2_nnnn.log
/u2/oradata/sample/ora_nnnn.ctl

My question is, why would I want the control files on the same mount pt as my
logs?
Aren't .clt files written too often?
And .log files written sequentially?

Wouldn't I just add the entries in my init.ora file for the location of my .ctl
files?
What do you do?

          Followup:

control files are written to at least every three seconds yes.

If you have sufficient disk to spread everything out (SAME is a good idea), go
for it.  No one is saying you have to put ctl and redo on the same disk (i do
myself try to isolate redo as much as possible, it is pretty rare for the ctl
files to become a bottleneck, it can happen, but not to most systems)

      Rating: 4
OMF
      ??  January 08, 2004
Reviewer:  Vinnie
       from Orlando
I am considering moving to OMF's.

When looking at the REDO's I noticed that when you configure
the following as suggested:
db_create_online_log_dest_1 = '/u1/oradata/sample'
db_create_online_log_dest_2 = '/u2/oradata/sample'

When you create the dB
you will get
/u1/oradata/sample/ora_1_xxxx.log

If this is true, wouldn't you just let the init.ora
file create your control files in alternate locations?
What do you do?
/u1/oradata/sample/ora_2_xxxx.log
/u1/oradata/sample/ora_xxxx.ctl
&
/u2/oradata/sample/ora_1_nnnn.log
/u2/oradata/sample/ora_2_nnnn.log
/u2/oradata/sample/ora_nnnn.ctl

My question is, why would I want the control files on the same mount pt as my
logs?
Aren't .clt files written too often?
And .log files written sequentially?

Wouldn't I just add the entries in my init.ora file for the location of my .ctl
files?
What do you do?

      Rating: 5
What is a min.
      number of hard disks to start with Oracle
      installation  January 28, 2004
Reviewer:
      Olaf  from Germany
Hi Tom,

we are starting one Oracle Project and I am going to purchase the hardware. It
is a low budget project:
Oracle Standart One Edition for one reporting financial application being used
intern from 10-15 users.
The server we are going to purchase can have max. of 12 hard disks, all on one
controller.
How much disks should I purchase to start?
It should be absolute minimum to guarantee data protection and performance. What
is the best way to configure RAID?
Early I worked on big and expensive UNIX boxes and I did not think about one or
two disk more.
Now my manager says that "I think too big" and I should really find a good
reason to buy one additional disk.

What would you say? How much disks is a minimum and how to organize them in
RAID?

          Followup:

one cannot answer this -- no idea how much storage you need.

      Rating:
      1
Misunderstanding?  January 28, 2004

Reviewer:  Olaf  from Germany
Tom,

my question was not how much storage we need -we need only 2-3 GB for our data.
It was about how many disks (minimum) I have to purchase to match Oracle
recommendation to separate logfiles from data, mirror redologs (using Oracle or
HW), separate disk for archivelogs, controlfile an so on.
There are a lot of recommendation. Which are "must be" and which are "well to
have".I red one Oracle book from Oracle Press years ago and it was something
about ideal installation on 21 disks (!?)

I always worked on UNIX hosts with a lot of disks and had no such problems. Now
in my new company I must purchase HW myself and must explain to my boss why do I
need more than one disk for Oracle installation. I bought both of your books,
but not found how can I start - it is the first thing I have to do - planning
the HW. In five years I have been working with Oracle I saw a lot of system with
Oracle installed on one RAID5 or total oversized.....

          Followup:

you'd need a lot to do it "mathematically complete".

you need two to do it OK in this case -- few users, not much data.  a laptop
could do this.

you need two for mirroring logs and control files at least.

January 28, 2004 sasi -- Thanks for the question regarding "Data
      Compression during RMAN backup", version 8.1.6

      You Asked
hello tom

i wanted to know if we can compress a datafile while performing
a backup using the oracle backup utility RMAN.
we have 2G datafiles on our system, and we are interested in
knowing methods for data compression while backing up to a disk.

is data compression possible during backup or is it dependent on
th operating system ??

any information in this matter will be helpful

thanks in advance
sasi

      and we said...
In general, it is dependent on the device.  If you backup to a
tape device for example, depending on what device name you use
-- differing levels of compression would be employed.  That uses
hardware (the tape drive) to compress.

If you backup to a compressed disk device, it will work as well.

The compression RMAN has is only the ability to backup blocks
that have been written to -- so a 2 gig file with lots of never
before used space will backup to a much smaller file.
Additionally, incremental backups can cut way down on the size
of intervening backups (those between your full backups)
Review &
      Followup

      Rating: 5
What about
      backing up to regular disk  January 27, 2004

Reviewer:  Steve  from Montreal
Hi Tom,

In the case where we backup to regular disk (e.g. ATA drives as you
suggested in a prior post):

Q1: Is that possible/feasible to GZIP the file produced by RMAN?

Q2: Do you see any problems with this (it shouldn't interfere
    with the RETENTION POLICY/REPORT OBSOLETE/other functions)

Q3: Good idea? Bad idea? (Datafiles usually compress really
    well...)

Thanks

          Followup:

1) sure but....... some rman stuff would not work anymore (like validate and
check) and you would be complicating the restore process greatly.

remember this ATA stuff is cheap -- cheaping than the additional cpu you'd need
to compress a database of any size.

using compressed volumes (eg: like a windows compressed disk) would be an option
as well as long as you have sufficient "real" space

2) it could, it certainly could

3) IMO -- bad idea.  complicates things.  saves on a resource that is cheaper
than tape, burns a really expensive resource (cpu) like mad.  slows down the
entire process (probably takes longer to compress than to write it in the first
place).

      Rating: 5
Point
      taken  January 28, 2004
Reviewer:  steve
       from Mtl
Point taken! Thanks

Anyone out there have a link to an ATA drive vendor (Unix platform).
I am having trouble finding one... I've looked and looked..

Steve

          Followup:

i just googled

ata nas

and got lots and lots and lots of relevant hits.

      Rating: 3
me
      too  January 28, 2004
Reviewer:  steve
       from Mtl
Me too. But haven't found any in the $1500/Terabyte
you had mentioned (yet anyway). But I'll keep looking....

If anyone has good experience/info with such technology
on a Unix platform, I'd love to hear about it.

Thanks a lot Tom. Really appreciate the help as always.

Steve

          Followup:

http://www.electroseller.com/Merchant2/merchant.mvc?Screen=CTGY&Category_Code=hd_250
get 4 of those... (this is not an ad for that site, i've never done business
with tme -- it was just an example of a google search result)

      Rating: 4
ATA
      Disk  January 29, 2004
Reviewer:  Jeremy
      Smith  from Salt Lake City, UT
3ware makes ATA RAID cards that can accomodate up to at least 12 ATA disks in a
raid 1+0 configuration.  Those cards have great support under Linux--3ware
module right in the kernel...really spiff.  Use a 4-port one with Raid 5 on my
desktop--works great.

January 28, 2004 Dhanesh -- Thanks for the question regarding
      "Installing Oracle 8i and Developer 2000", version 8.1.5

      You Asked
Hi,

I need help in installing Oracle 8i and Developer 2000 in WinNT
4.0 machine. Please note that this is for testing purpose only.
So need to install it in the same machine.

So I need to know -
(1) Procedure to do it
(2) How to configure Developer 2000 to point to Oracle Database
(3) Any additional tips and/or tricks you can recommend

Actually I attempted in installing, the result was that
Developer 2000 (Forms) is not able to connect to the database.

Cheers
Dhanesh Shah

      and we said...
Oracle8i is release 8.1.

D2K is based on 8.0.

You can install either first/second -- they will be installed
into separate Oracle Homes. (separate directories).

You will need to use SQL*net to connect D2k to the database as
the products are 2 different versions.

You'll setup and configure the Oracle8i listener using the
configuration files in $ORACLE_HOME\network\admin (oracle home
for the oracle8i instance).  You can use the Oracle Home
Selector tool to set your default Oracle home to the database.
You'll be setting up the listener.ora.  Once you have the sqlnet
listener running, you'll be ready for the next step.

Use the Oracle Home selector now to set the default Oracle Home
to the D2k Oracle home.  You would now setup the tnsnames.ora
file in the oracle_home\net80\admin  (not network\admin,
net80\admin).  You'll have this point to the Oracle8i instance
on your machine.  You can use the tnsping tool to test this
connection.  Now you are ready to run d2k and connect to the 8i
instance.
Review
      & Followup

      Rating:
      4
d2k+oracle9i  January 28, 2004
Reviewer:
      jasdeep  from patiala,india
i have win 2000 os and
oracle 9i installed on it
when install d2k on it it works fine
but when database is shutdown and restarted
listener doesn't starts
can anybody suggest some remedy.

January 28, 2004 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

      Rating: 5
PLS-00201 on
      sys objects in PL/SQL (Oracle 9i release 2)  January 27,
      2004
Reviewer:  Mickey  from USA
Tom,

I had this function in Oracle 8.1.7.3.4 (HP Unix)..that gave no error (where
manager had dba role)...but when I created it in 9i release 2 (HP Unix)..
I get an error:
LINE/COL ERROR
-------- -----------------------------------------------------------------
0/0      PL/SQL: Compilation unit analysis terminated
1/41     PLS-00201: identifier 'DBA_OBJECTS' must be declared

if I login as Manager in sql plus..I could get to/query dba_objects just fine...

Help....

CREATE FUNCTION MANAGER.GET_DDL_TIME_FUNC (pOBJECTNAME
DBA_OBJECTS.OBJECT_NAME%TYPE,
                     pOBJECTTYPE DBA_OBJECTS.OBJECT_TYPE%TYPE := 'TABLE')
RETURN VARCHAR2 AS

VRETURN VARCHAR2(4000);

BEGIN

SELECT TO_CHAR(LAST_DDL_TIME, 'MM/DD/YYYY HH:MI PM') INTO VRETURN FROM
DBA_OBJECTS
WHERE OBJECT_NAME = pOBJECTNAME AND OBJECT_TYPE = pOBJECTTYPE;

RETURN VRETURN;

END GET_DDL_TIME_FUNC;

          Followup:

the owner had not only the dba_role but was granted select on dba_objects
directly.

go back to that 8i database and issue:

desc dba_objects
set role none;
desc dba_objects

you'll find that both work.  do that in your new database -- the first will
work, the second will not.

http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html

      Rating: 5
Invokers
      Rights....  January 28, 2004
Reviewer:
      Mickey  from USA
Tom,

I tried..
desc dba_objects
set role none;
desc dba_objects

and results were just as you said...
I have v$views, and dba views in some of my procedures..
and they all worked fine in 8i...but do not compile in 9i..
I was checking DBA_TAB_PRIVS...in 8i...and saw no explicit selects for
DBA_OBJECTS or any other v$ views...still it worked O.K. through procedures and
now failing in 9i.
Is there another view to verify explicit privileges.

Thanks.

          Followup:

In 8i -- you either

a) have been granted SELECT directly on the objects
b) have been granted the all powerful SELECT ANY TABLE priv (dba_sys_privs)
directly.

      Rating: 5
SELECT
      ANY..  January 28, 2004
Reviewer:  Mickey
       from USA
O.K. both 8i and 9i had SELECT ANY TABLE
as well as SELECT_CATALOG_ROLE.
The difference was O7_DICTIONARY_ACCESSIBILITY..
true in 8i...and false in 9i.
Now..the documentation says:
"When this parameter is not set to FALSE, the ANY privilege applies to the data
dictionary, and a malicious user with ANY privilege could access or alter data
dictionary tables."

Is it O.K. if MANAGER is the only one with DBA role...to have this parameter set
to true...because otherwise..I guess I have to give explicit access to MANAGER
for the objects I access through procedure.

Comments...

          Followup:

i would use DIRECT GRANTS.  Get direct access to the tables/views you need.

January 28, 2004 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?

      Rating: 5
Here's my
      chance - now how to proceed?  January 27, 2004

Reviewer:  Lisa  from Fairfax, VA USA
Tom,

Hello, it is Lisa again.  We have adopted a proprietary framework for building
our J2EE applications; meaning we can customize the framework to suit our needs.
 This framework currently supports direct database access via declared SQL and
through a set of strongly-typed Object Relational mapping classes.

This framework generates the classes based upon an XML description of the schema
in a strongly typed manner.  The implementation of all this is driven by a
"database policy", for example, there is an Oracle database policy which
describes Oracle-specific syntax.

I am proposing the development of a custom Oracle Stored Procedure policy under
which direct table calls will be replaced with stored procedure calls.  I am
struggling with the best way to provide the interface, and I would like your
opinion. Ultimately, I think the XML will describe the intended object model
instead of the physical database structure.  (Think Business or Value Objects
that are passed around in a Data Access Object design pattern).

Option 1:

The procedures will accept a list of parameters that map to the object
attributes.  e.g. create_person (p_fname varchar2, p_lname...)  The problem with
this is that we have "objects" that are comprised of many attributes, close to
100 in some cases.

Option 2:

The procedures will accept object types as parameters.  I have taken this
approach before with weakly-typed objects (love object types as JDBC to Oracle
bridges), but never in a strongly-typed way.  In this approach each "object"
declared in XML would get a matching Oracle type, and a stored proc to handle
CRUD by passing the custom object type in/out.

Option 3:

Complete object-oriented implementation. The objects declared in XML will get
matching Oracle objects, and these objects will have member functions/procs to
provide CRUD operations.  I have never used these object oriented features
before, but they seem appropriate for providing an O/R mapping solution.

What do you feel is a good design?  Do you have other suggestions?  This is my
chance to move SQL into the database, and I want to make a good decision.

Regards,

Lisa

          Followup:

You are probably asking the wrong person as I would be intent upon moving all
data oriented logic -- even much of that pesky "application logic" straight into
the database -- so I can use and reuse it over and over as the languages and
applications change over time.

I'll send you to devtrends.oracle.com, Cameron O'Rourke is more familar with
what you want to do in j2ee to minimize your pain.

I would personally lean towards option #1 but I might use some SIMPLE object
types (create or replace type) to hide the fact that there are 100's of
attributes and use jpublisher to create the java mapping classes. I would not be
using the object types in my create tables, just as complex datatypes in my
stored procedures.  I would be using regular old fashioned rows and columns in
the database.

      Rating: 3
More about type
      bodies  January 28, 2004
Reviewer:  Lisa
       from Fairfax, VA USA
Tom,

Perhaps I wasn't exactly clear, my question, although related to Java, is
actually a pure database question.

Instead of having the java developers declare in XML that there is a table
called user_table with columns ssn, fname, etc,I want them to completely remove
the database from their code.  Currently, the framework looks at the schema and
creates Java DAO's with methods that perform select by primary key, and CRUD.

Instead, I want them to express the object and the methods in XML.  The
framework will then generate calls to Stored Procedures to implement CRUD, and
potentially any other "methods" declared (there's the app logic).

My question relates then to the Stored Procedure implementation, and an
alternate object type implementation.

e.g.

Option 1:

Plain vanilla stored procedures.

procedure create_user (p_ssn in varchar, p_fname in...)

Option 2:

Use object types to pass the information to the Stored Procedure.

create or replace type user_type as object
(ssn           varchar2(12),
 first_name    varchar2(15),
 last_name     varchar2(25));

And a procedure such as:

procedure create_user (p_user in user_type)
is
begin
  insert into user_table
    (ssn,fname,lname)
  values
    (p_user.ssn,p_user.fname,p_user.lname);
end;
/

Option 3:

Option 3 uses just types and member functions, no packages are involved.  I am
not sure if type bodies are even intended to be used in this way, but my thought
is as follows:

create or replace type user_type as object
(ssn           varchar2(12),
 first_name    varchar2(15),
 last_name     varchar2(25),
 member procedure createUser,
 member procedure updateUser,
 member procedure deleteUser,
 member procedure getUser(p_id in varchar2)
)
/

create or replace type body user_type
as
member procedure createUser
is
begin
  insert into user_table (ssn,fname,lname)
  values (self.ssn,self.first_name,self.last_name);
end;
...

I am not talking about using object types to store data, simply as data carriers
(Option 2), or as data carriers and method implementations (Option 3).

Is is even possible to use type bodies in the manner I suggest in Option 3?

Thanks again for your time.

Regards,

Lisa

          Followup:

option 1 with some of 2 sprinkled in when it makes sense is what my answer above
was.....

yes, you can do 3 as you've shown (you'd be using some static member functions
for "non-default constructors") but it is not an approach I've done (or am fond
of)

      Rating: 4
So when are
      type bodies appropriate?  January 28, 2004

Reviewer:  Lisa  from Fairfax, VA USA
Tom,

Thanks for responding again.

Just curious, if object types and type body methods are not appropriate as an
O/R mapping solution, when are they appropriate?  (and if you are willing to
elaborate, why are you not fond of this approach?)

Again, I have only ever used object types to simplify my PL/SQL to Java
interfaces a la Option 2.  I am interested in when you would be fond of using
type bodies.

Thanks,

Lisa

          Followup:

I use them from time to time in PLSQL -- self contained in PLSQL.

From a 3gl to PLSQL -- i'd rather just call stored procedures rather then get
"cute" and message an object.  It just seems to complicate the code -- not make
it easier -- using objects across languages like that.

      Rating: 5
I don't think
      that these J2EE-Fanatics could build big apps.  March 05,
      2004
Reviewer:  Olga  from Vienna
Everytime you look in a computer magazine, you could find a new word for a new
very cool programming method/tool/environment which would be a revolution in
application development. And everything new is very cool and everything old is
something for bad old guys.

Hey, look at these old big airline reservation systems and the old banking
software where financial transactions in the whole world were managed! If those
old guys who developed these big, reliable applications would have been started
with these new "We need EJB, XSL, XML, JSF, JSP, n-Tier, J2EE, Apache here,
Eclipse there, OSS"-Blablabla they would never bring it to a production-release!

They talk about how they would save money for the company and forget about the
money consuming time they spent with sticking this and that together, deploy
these things there and configure this XML-file in that directory and upgrading
to release 0.1.4.1.3.45.3213q, and thinkling about why the hell everything has
such a bad performance... Oh! And if a problem occurs nobody knows the reason.

I don't like these "New-World"-Fanatatics and the big trouble they make. It's
like modern ugly art compared to an old renaissance painture. Nothing more than
hot air for idiots.

January 28, 2004 David -- Thanks for the question regarding
      "Viewing fractions of a second in a date field.", version 8i

      You Asked
Hi Tom,

I was wondering, is there any way to look at fractions of a second when
selecting a timestamp date field?  I would presume when one compares two such
fields, using < or >, fractions of a second are used, but can you actually
select the timestamp so as to view the fractions using an Oracle date format?

TIA

David Wison.

      and we said...
In 8i and before, the DATE type has a resolution of 1 second.  There are no
fractional seconds stored.  When using <, >, the comparision is down to the
second.

In 9i, there is a new datatype "timestamp" that supports fractional seconds.
Review &
      Followup

      Rating: 5
"Viewing
      fractions of a second in a date field.", version 8i  October
      25, 2001
Reviewer:  David Wilson  from Toronto

Short, clear and to the point.

      Rating: 5
Want to fetch
      record with the latest date   January 28, 2004

Reviewer:  Ram  from USA
This is my Query . THe remaining all coumns are same except datetimeofarchive
column. So Using the view below I want the latest datetimeofarchieve.

SELECT  distinct DA.ARCHIVEDDOCUMENT, LSN.DocTypeLifecycleStateName Status,
DA.ARCHIVEDDOCUMENTDESCRIPTION, DA.DATETIMEOFARCHIVE, DS.ColorID
FROM Tsa_DocTypes DT, Tsa_docstatus  DS, Tsa_DocArchive
DA,Tsa_DocTypeLifecycleStates LS, Tsa_DocTypeLifecycleStNames LSN
WHERE
DT.ARCHIVESTATEID=LS.DOCTYPELIFECYCLESTATEID AND
LS.DOCTYPELIFECYCLESTATENAMEID =LSN.DOCTYPELIFECYCLESTATENAMEID AND
LSN.DOCTYPELIFECYCLESTATENAMEID=8 AND
DS.STATUSID=8 ORDER BY DA.DATETIMEOFARCHIVE DESC.
THe results is
BEGINDATA
"TDP-0104-000030";"ARCHIVED";"Archive the document";"01/28/2004
15:47:10";-4144960
"TDP-0104-000030";"ARCHIVED";"Archive the document";"01/28/2004
15:47:09";-4144960
"TDP-0104-000028";"ARCHIVED";"Archive the document";"01/28/2004
15:33:41";-4144960
"EAP-0104-000148";"ARCHIVED";"Archive the document";"01/28/2004
15:30:49";-4144960
"EAP-0104-000148";"ARCHIVED";"Archive the document";"01/28/2004
15:30:48";-4144960
"EAP-1203-000049";"ARCHIVED";"Archive the document";"01/28/2004
15:26:27";-4144960
"EAP-1203-000049";"ARCHIVED";"Archive the document";"01/28/2004
15:26:26";-4144960

          Followup:

select columns...., MAX( datetimeofarchive )
  from ....
 group by columns.....


Listing

Recent Archives Advanced Search FAQ by Category Most Popular Hot Articles Your Questions [ Listing ] RSS Feed
Subject | Article | Last Updated (East coast time)
Parallel Query | article only | Jan 29, 2004 07:38 am
soft parse | article only | Jan 29, 2004 07:44 am
estimate Initial Extent size of a table | article only | Jan 29, 2004 08:14 am
PL/SQL Server Pages (PSP) | article only | Jan 29, 2004 08:27 am
dbms_debug package - reg | article only | Jan 29, 2004 01:25 pm
When-Tree-Node-Selected question. | article only | Jan 29, 2004 01:28 pm
pga_aggregate_target | article only | Jan 29, 2004 01:30 pm
Export | article only | Jan 29, 2004 01:46 pm
tkprof wrong result? | article only | Jan 29, 2004 01:58 pm
Development Environment/Methodolgy | article only | Jan 30, 2004 08:05 am
Log_checkpoint_interval and timeout | article only | Jan 30, 2004 08:10 am
How to Increase Font Size in SQL *Plus | article only | Jan 30, 2004 08:11 am
Comparing Nulls? | article only | Jan 30, 2004 08:20 am
Subtracting months frorm a current date | article only | Jan 30, 2004 08:41 am
sql_trace | article only | Jan 30, 2004 09:39 am
Your latest Oracle Magazine article | article only | Jan 30, 2004 07:28 pm
How to convert a table with long_row data into a CLOB data type table | article only | Jan 30, 2004 07:57 pm
how oracle store data from inserting row or delete? | article only | Jan 30, 2004 07:57 pm
Self-Join | article only | Jan 30, 2004 08:00 pm
Regarding On Update Cascade | article only | Jan 31, 2004 02:36 pm
email in PL/SQL | article only | Feb 01, 2004 07:31 pm
DML statement on a table | article only | Feb 02, 2004 07:39 am
Flush shared pool | article only | Feb 02, 2004 07:50 am
Time Series Data and Buffer Gets | article only | Feb 02, 2004 09:44 am
How to load a distant file | article only | Feb 02, 2004 10:47 am
Error in creating .dat with SQL | article only | Feb 02, 2004 12:34 pm
Temporary Table Questions | article only | Feb 03, 2004 07:35 am
SQL*Plus does not use SQL*Net? | article only | Feb 03, 2004 07:41 am
Usage of Role Password | article only | Feb 03, 2004 07:44 am
IN & EXISTS | article only | Feb 03, 2004 07:57 am
to remove duplicate records | article only | Feb 03, 2004 01:47 pm
Rebuilding | article only | Feb 03, 2004 01:54 pm
length limitation of CLOB in pl/sql | article only | Feb 03, 2004 03:34 pm
Snapshot too old X read only tablespaces | article only | Feb 04, 2004 07:13 am
cursor questions | article only | Feb 04, 2004 07:29 am
RMAN catalog maintenance | article only | Feb 04, 2004 07:32 am
Inserting Default Value | article only | Feb 04, 2004 09:17 am
Star Schema | article only | Feb 04, 2004 09:23 am
Functions Handling in SQL Queries. | article only | Feb 04, 2004 04:27 pm
view containing in ,or clause with outer join | article only | Feb 04, 2004 05:04 pm
does refrential integrity make the database slow | article only | Feb 04, 2004 05:17 pm
Rename a column | article only | Feb 04, 2004 05:40 pm
comparing two table data and logging the changes | article only | Feb 04, 2004 05:42 pm
row(s) 1 - 43 of 43
January 29, 2004 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"

Rating: 4
cpu utilization January 23, 2004
Reviewer: A reader
We have a Dual Xeon 2.8 Dell Windows 2000 Server running 9.2.0.4.0.

SQL> show parameter cpu_count

cpu_count                            integer     4

Here are my parallel query init.ora settings:

parallel_adaptive_multi_user         boolean     TRUE
parallel_automatic_tuning            boolean     TRUE
parallel_instance_group              string
parallel_max_servers                 integer     100
parallel_min_percent                 integer     0
parallel_min_servers                 integer     4
parallel_server                      boolean     FALSE
parallel_execution_message_size      integer     4096
parallel_server_instances            integer     1
parallel_threads_per_cpu             integer     2

On the server desktop, I open task manager, and a command line sqlplus session.

SQL> select count(*) from user_objects;

  COUNT(*)
----------
       518

Elapsed: 00:00:00.00

SQL> select count(*) from user_objects,user_objects,user_objects;

  COUNT(*)
----------
 138991832

Elapsed: 00:04:59.06

SQL> select /*+ PARALLEL (user_objects 4) */ count(*) from
user_objects,user_objects,user_objects;

  COUNT(*)
----------
 138991832

Elapsed: 00:00:18.08

So the parallel query runs much more quickly than the non-parallel query.

Now - What is disturbing me, is that when I execute these CPU intensive
operations, that my oracle.exe process shoots to 25%, and stays there, for both
the non-parallel *and* parallel executions. When I switch tabs from "Processes"
to "Performance", only one of the CPU's is showing work accomplished in the line
chart.

If I kick off an export of the schema (as an example of an operating system
command that is pretty CPU intense) I see all the CPU's showing work
accomplished. So it can't be a problem with the OS or hardware. I hope it's a
problem with how I'm doing what I'm doing or one of my settings.

Why am I not seeing other CPU's getting involved in the processing? In fact,
shouldn't I be seeing all the CPU's being utilized when I kick off a query
whether it is in parallel mode or not? In other words, I would think the
oracle.exe process should show processor utilization across all 4 processors. Am
I wrong?? :L

Followup:
perhaps -- your queries are in fact not cpu intensive but rather IO bound.

I'd guess that cartesian join ate a bit of temp, don't you?

You'd have to do something cpu intensive and I think you did something io bound
here.  In any case, 18 seconds -- not bad eh?

Rating: 5
Quad Xeon in above post January 23, 2004
Reviewer: A reader
Sorry.

Rating: 5
Multiple CPUs and utilization January 23, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
> Why am I not seeing other CPU's getting involved in the processing?

Windows is responsible for spreading the load across CPUs, not Oracle, so that
may be a consideration.

- Mark

Rating: 2
Windows is spreading the utilization across January 25, 2004
Reviewer: A reader
multiple CPU's for other processes.

But not for the oracle.exe process.

I think the issue is the threads. Of course Oracle is a multi-threaded
application, but is it properly informing the O/S that it wants to utilize all 4
CPUs? It is obviously mult-threading within one CPU (If my terminology is
correct), but I don't necessarily blame the O/S when I see other programs able
to utilize more than one CPU when executed.

The process is indeed CPU intensive. If I run it on a single CPU Windows 2000
server, the CPU goes to 100% until completion, so although I/O may be intensive,
it is still whacking the CPU hard.

I am not understanding what I am seeing, and would wonder if anyone else out
there that runs M$ 2000 on a multi-cpu machine can be of help? Does anyone have
any hard and fast answers with proof?

Thanks much!
The feedback is truly appreciated.

Followup:
oracle.exe will use many cpu's on windows.

windows is responsible for scheduling threads on more then one cpu.

tell you want -- start up a couple of job queue processes (alter system set
job_queue_processes=20) and submit to dbms_job 20 background "big queries".
watch that cpu go up.

If the process was CPU intensive -- it would take lots lots longer then 18
seconds there.

tell me -- when you run it on that single cpu going to 100%, does it run

a) faster
b) slower

than the 4 cpu with 25% utilization?  I'm not seeing anything to get panicked
over here -- windows is the guy that schedules threads.

windows model is all about threads.

Rating: 5
January 25, 2004
Reviewer: Dave from Colorado Springs
I have a data warehouse on 9.2, with all the parallel query parameters set to
automatic, and cannot get a query on a large, range-partitioned table to
parallelize.

This is with the table's degree set to default, a system that is otherwise
completely unutilized, and most importantly I have set "alter session force
parallel query" which appears to be medicine so powerful that even "select *
from dual" is parallelizing.

Would you say that it's time to open a TAR?

Followup:
nope, cause you give us not a single example to work with.

Not every query is parallelizable and queries that we don't think need be --
won't be.

What you would have to do is

a) run the query and let the optimizer do its thing
b) hint the query and let it do what you think is best

in order to show us that "hey, it should have been parallel"

Rating: 3
January 25, 2004
Reviewer: Dave from Colorado Springs
Well, it's a 30Gb data warehouse fact table that we are trying to build a
summary table on -- a full table scan is taking 11 minutes as a non-parallel
process. We have about 40 partitions on the table, 8 cpu's, RAID1+0, but the
most compelling evidence for me that something is amiss is that  even "force
parallel query" will not parallelize the query ...

select /*+ full(t) */ count(*) from my_table t;

... and that much smaller tables -- summaries of this table, in fact --
parallelize at the drop of a hat?

Does it seem anomalous that this table should not be blessed, no matter how hard
we try to force the issue?

Followup:
what is the plan for that query (and is that the query in question)

Rating: 3
parallel query January 26, 2004
Reviewer: rabeila from kar

Rating: 3
follow up to oracle.exe across multiple cpus January 28, 2004
Reviewer: Justin from PA
More information for those interested.

I figured out what the "problem" was.

I had changed what is called the "Priority Class" in Windows Task Manager from
"Normal" to "Real Time" for the oracle.exe process, and it actually increased
performance for a query I was running. At that time, I did not realize that the
performance tab of task manager switched from showing all 4 CPUs doing work, to
only 1 CPU doing the work.

So, I was relieved to figure out the answer.

I am still in a quandry though over the fact that I am being limited to 25% of
the total CPU processing power. If I kick off to of the queries listed in my
previous post, the total CPU utilization goes to 50% exactly, and stays there
for the duration of the queries. Odd that the total CPU consumption for both
settings is 25%...

Tom, what query would you use to max out a quad CPU machine? As you inferred
earlier, my particular query would have been disk intensive also. What would be
an example of a query that would be almost totally CPU intensive? Something
using a bunch of mathematical functions I would imagine?

Would you also have an idea for a way to execute a command at that os that would
be very CPU intensive? That way I could determine if the limit is an Oracle
related issue at all.

Thanks much

Followup:
I might use the sum(ln(some_number)) in parallel.  you'll find ln (natural log)
to be fairly cpu consuming (until 10g with binary_float and binary_double
anyway)

January 29, 2004 -- 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 :)

Rating: 5
Avoiding soft parse completely January 28, 2004
Reviewer: Krishnaswami from Orlando,FL
Hi Tom,
I have a question on the following code taken from your earlier reply. I run
this code in SESSION S1.
  for i in 1 .. 100
  loop
        for x in ( select * from t where zzz = :BIND )
        loop
           .....
the query "select * from t where zzz = :BIND" is parsed ONCE per session -- not
per execution.

Upon completion of the for loop, another SESSION S2 modifies the table t by
dropping one column.If I run the for loop again in session S1, I am sure soft
parse will happen as table t was changed by session S2.
What happens to the cached cursors in session S1 when session S2 fires the Alter
Table statement ?
Thanks for your reply
Krishnaswami

Followup:
it'll be a hard parse actually.

the cursors are automagically taken care of for you -- they are invalidated in
the cache.

Rating: 5
Soft Parse January 29, 2004
Reviewer: Krishnaswami from Orlando,FL
Thanks for the reply. I get it.

January 29, 2004 V Siva -- Thanks for the question regarding "estimate Initial Extent size of a table", version 8.0.4
You Asked
Hi,

I've a general question: how to determinr the size of the
initial extent for a table.

I've to create a table having some 200 columns, and approx.
record size is 2K. This table initially will have very large
number of rows around 100,000 and later the growth rate is very
less. In this case what will be the initial extent and next
extent sizes?

Thanks
Siva
and we said...
I myself prefer to use uniformly sized extents for all objects.
That is -- I do not size discrete objects "perfectly", I do not
spend time trying to figure out the initial extent should be
137.2 megabytes and the next should be 13meg with a pctincrease
of 50%.  Rather, I lump objects into classes of sizes.  Small
objects (lookup tables and such) go into a tablespace where all
objects use 64k extents.  Slightly larger objects might go into
a tablespace that uses 256 or 512k extents.  Medium sized
objects go into a tablespace with 1meg extents.  Truely large
objects go into appropriately sized tablespaces.

The goal here is to keep all extent sizes homogenous within a
tablespace -- in tablespace T1 all extents are Xmeg,
regardless of the object.  In tablespace T2, all extents are
Ymeg, regardless of the object.  The removes any possibility of
fragmentation (it just cannot happen if all extents are the same
size -- its impossible to have fragmentation).  You never have
to worry about coalescing free space or anything like that.

There is a myth that some people believe in -- it is the myth
that you should attempt to size objects such that all data fits
in the initial extent and that having more then a handful of
extents is bad.  This is quite simply not true.  It is
perfectly OK to have dozens or even low hundreds of extents for
objects.  Going overboard into the thousands, while possible,
isn't recommended.  With locally managed tablespaces in Oracle8i
release 8.1, they (tables with more then hundreds of extents)
are more feasible.

So, if I have a table with about a 2k record size and 100k
records initially we are talking about a 200meg table that grows
slowly over time.  I would need a better definition of slowly
but lets say you add 5meg of data per month (2,500 records per
month @2k per record).  Every year thats about 60meg of data.  I
might consider a 50meg extent size for this object.  Initially
it would consume 4 or 5 extents and add about 1 extent per year.
Review & Followup
Rating: 5
July 25, 2001
Reviewer: Tianhua from USA

Rating: 4
November 04, 2001
Reviewer: Mahendra Jadav from INDIA

Rating: 4
Minimum Extent January 28, 2004
Reviewer: a reader from India
Hi,

The clarification of initial and next extent was very helpful as usual.

I've a confusion (sorry if its silly one).
Biju Thomas says 'MINIMUM EXTENT controls frgmentation in the tablespace by
allocating extents of at least the size specified. The INITIAL and NEXT extent
sizes u specify should be a multiple of MINIMUM EXTENT'. He gave an example:
Create tablespace.....
Default storage(
                 Initial 256k
                 Next 256k
                 Minextents 2
                 Pctincrease 0
                 Maxextents 4096
                )
Minimum Extent 256k ;

I don't understand What is multiple of what??
Please help.

Thanks for your time.

Followup:
256 is a multiple of 256 as would be 512, 1024, and so on.

N*256 in his example, for any value of N would be a "multiple"

Rating: 3
Regarding Minimum Extent January 29, 2004
Reviewer: a reader from India
U mean the following statement
create tablespace..
default storage( initial 265
                 next 256
                 pctincrease 0)
minimum extent 512;

Is fine?? Is N=initial/next extent?

If I don't specify minimum extent it'd take 1 and work perfectly.
But what would happen if the above tablespace is created with minimum extent
512??

sorry to bother u once again.

Thanks for ur help.

Followup:
well, it is a little silly since you are saying "use 256 but at a minimum use
512"

the only way you should do this would be:

create tablespace t extent management local .......

and forget about storage clauses forever more.

Rating: 3
Regarding Minimum Extent January 29, 2004
Reviewer: A reader from India
Oops! sorry by mistaken I wrote minimum extent instead of minextent. Pls ignore
the above one. Hope u won't mind  since I'm new to ur website.

My questions:
Do u mean initial and/or next extent size is multiple of minimum extent?

Is the following statement correct?
create tablespace..
default storage( initial 265
                 next 256
                 pctincrease 0)
minimum extent 512;

Is N=initial/next extent?
If I don't specify minimum extent it'd take how much?
What would happen if the above tablespace is created
with minimum extent 512??

sorry to bother u once again.

Thanks for ur help.

Followup:
it would NOT make sense to have initial and next less than the minimum extent
size.

it would only make sense to have them be N*minimum extent size where N in ( 1,
2, 3, 4, ...... )

Rating: 4
Re: minimum extent January 29, 2004
Reviewer: reader
Thanx a lot for your help and time. I know it was a silly one. I just got
nervous while asking u.

January 29, 2004 -- Thanks for the question regarding "PL/SQL Server Pages (PSP)", version Oracle8i 8.1.6.0.0
You Asked
Where is the utility named "loadpsp" found so that I can compile
PSP files?
and we said...
It should be in $ORACLE_HOME/bin.  It is only installed with a
database (not client) install with 8.1.6 (fixed in 8.1.7).

If you have 8.1.6 on NT, you can get it from the installation CD
via:

We just extracted the loadpsp.exe and placed it in the
%ORACLE_HOME%\bin directory.  For example, if c:\orant is
ORACLE_HOME and d: is the CD drive, issue the
following 2 commands if you have the java developer kit:

 cd c:\orant\bin
 jar -xvf
d:\stage\Components\oracle.rdbms\8.1.6.0.0\1\DataFiles\exec.0.0.j
ar   loadpsp.exe

or use winzip to extract the file.
Review & Followup
Rating: 4
Would like to start using PSP January 31, 2002
Reviewer: Yogeeraj from Mauritius
Hi

could you please guide me to any good documentation which could get me started
with it.

I am running Oracle 8.1.7 and IAS release 1.

thanks in advance

best regards
yogeeraj

Followup:
http://download-west.oracle.com/docs/cd/A87860_01/doc/appdev.817/a77069/07_subs.htm#17196
...

      Rating: 5
Load
      PSP  January 29, 2004
Reviewer:  Tarun Babu
       from India
Hi tom,
    Just go to this link and view source.

http://www.oracle-base.com/articles/9i/dept_insert_req.psp

I am not able to understand how it is running (.psp) directly in the browser. Is
it possile to run a psp without loading into the database? If so how? Can we
place the .psp file in the middle tier directly to run?
I know we have to use loadpsp utility to load the .psp file which create a
stored procedure in db.
Can you please throw so lignt..

          Followup:

i don't know what you mean by "running .psp directly in the browser"

I click on a link.
browser does http to the server
server was asked to retrieve /articles/9i/dept_insert_req.psp
server did that and returned html to the browser

how is that the browser running a psp?

      Rating: 4
Load
      PSP  January 29, 2004
Reviewer:  Tarun
       from India
Tom,
    What I mean is "Accessing the the psp with .psp extension in the browser"
like a .jsp file or .php or .asp etc. I know the browser can not execute the
psp.

Can we execute a .psp file without loading it into db? or by placing it in the
middle tier?
Your help appreciated..

          Followup:

dept_insert_req.psp
package_name.procedure_name

would be one way to achieve this "magic"

running the stored procedure, retrieving the data and writing it using utl-file
to a file named dept_insert_req.psp would be another.

      Rating: 4
Load PSP
        January 29, 2004
Reviewer:  Tarun
       from India
Thanks a lot. Tom.

January 29, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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.

      Rating: 5
Debugging
      package  January 29, 2004
Reviewer:  Arun
      Mathur  from Marietta, GA
Hello Tom,

In your Expert One On One Oracle book, you mention a debug package which you and
Christopher Beck wrote. I checked the WROX site for it, but didn't get any luck.
 Do you know of any site(s) which may have it?

As always, thank you.

Arun

          Followup:

it is on the apress.com website for that book (wrox went under and got sold in
bits)

      Rating: 5
Nevermind - I
      just found it  January 29, 2004
Reviewer:  Arun
      Mathur  from Marietta,GA

January 29, 2004 Getulio -- Thanks for the question regarding
      "When-Tree-Node-Selected question.", version

      You Asked
I've created a tree and it is populating correctly during the
when-new-form-instance trigger, but my question is that when I
click the mouse on a tree node, the when-tree-node-selection
trigger fires properly and the node is highlighted. But when I
select another node, the when-tree-node-selection trigger fires
twice. Once for the node it is "leaving" and once for the node
it is "entering" (the one I clicked on). I thought this trigger
should ONLY fire for the node I just clicked, not twice.

Is this a bug, or "a feature"???? Is there anyway just to have
the trigger fire for the node selected? I've heard that there
are many bugs with the new navigator tree item. Does anyone have

a list of these bugs/features?

Any help would be greatly appreciated.

Getulio

      and we said...
the documentation for when-tree-node-selected states:

---------------------------------------------------------
When-Tree-Node-Selected Trigger
Description
Fires when a node is selected or deselected.
Definition Level form or block
---------------------------------------------------------

Perhaps you can keep a global variable "last_node_selected" and
incorporate logic such as:

if ( nvl( global.last_node_selected, 'na' ) =
:SYSTEM.TRIGGER_NODE )
then
    return;  -- do not do anything, we must be leaving this node
end if;

global.last_node_selected := :system.trigger_node;

.... rest of code here .....

That way, you'll only process the 'tree-node-selected' event
ONCE per node -- not when leaving and entering.

Review &
      Followup

      Rating: 1
avoid to fire
      the WHEN-TREE-NODE-SELECTED trigger, when leaving the
      node  July 30, 2002
Reviewer:  Gerd Droscher
       from styria/austria/europe
In my case, I must avoid to fire the trigger when the last node is leaving!

I'm using Ftree.Set_Tree_Selection(htree, :system.trigger_node ,
Ftree.SELECT_ON);
in the WHEN-TREE-NODE-EXPANDED trigger to mark the new node, if've clicked on!

In this case, the WHEN-TREE-NODE-SELECTED trigger fires also for the previous
node!

How can I avoid this?!

gerd

      Rating: 3
One more
      related question  January 29, 2004
Reviewer:
      Anil Singh  from PA
How do i check whether a node is marked selected or not without going thru the
loop?
e.g. ftree.get_tree_node_property(<htitm>,<node>,<chk Sel>) returns TRUE if node
is selected else FALSE.

          Followup:

try
http://otn.oracle.com/
-> discussion forums.

I honestly haven't used forms since 1995 myself.

January 29, 2004 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?

      Rating:
      5
pga_aggregate_target  January 29, 2004

Reviewer:  Reader
(1) If I set 1G for this parameter, is it like 1G memory is reserved for pga or
when needed oracle could get it from OS up to the limit of 1G? In otherwords, is
pga_aggregate_target considered as HWM? Thanks.

(2) What if the server does not have 1G memory for pga_aggregate_target and will
it give any error? Thanks.

          Followup:

1) it is dynamic and not preallocated.

2) only if a request to the OS for memory fails would you get an ora-4030

January 29, 2004 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

      Rating:
      5
buffer  January 28, 2004
Reviewer:
      reader
does exp use buffer cache or pga to buffer the records before it writes to the
dump file? Thanks.

          Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96652/ch01.htm#1012890
that picture shows what happens.

      Rating:
      5
buffer  January 29, 2004
Reviewer:
      Reader
Tom, the pictures says, private buffer or buffer cahe. In what situations the
exp uses private buffer? Thanks.

          Followup:

temp space for example.

      Rating: 5
Why does exp
      need temp space in private buffer? Thanks.  January 29, 2004

Reviewer:  Reader

          Followup:

sql does things directly, not with the overhead of the buffer cache.

but it (exp) uses both its own memory as well as buffer cached data to process.
record sizes, data buffers to hold fetched data, normal "processing stuff" you
would find in any program.

January 29, 2004 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.

      Rating: 5
Applying the
      trace output  January 29, 2004
Reviewer:  Arun
      Mathur  from Marietta,GA
Tom,

I placed two debug statements(using debug.f) in a function that returns a
reference cursor, one at the very beginning, and one immediately before
returning the cursor. The start and finish times from the debug logs are
identical; however, the ops guys running the C++ application which calls this
PL/SQL function (as well as some other C++ routines) say the application is
running slower than usual (3 minutes as opposed to 30 seconds).

The tkprof report shows 12 seconds CPU time to fetch (server is about 98% idle
most of the day), but 44.56 elapsed time. The wait events I see are mainly 'db
file scattered read'(1240) and 'db file sequential read'(284 of them). After
reading the Metalink articles on these two events, I played around with the
DB_FILE_MULTIBLOCK_READ_COUNT parameter, but the elapsed fetch time remained the
same.

Do you have any thoughts on what else I should investigate further?

Regards,
Arun

          Followup:

let's see the tkprof.

opening a ref cursor is instantaneous generally (no work performed really),
fetching the data is what takes time.

      Rating: 5
Here it is -
      Thanks for all your help  January 29, 2004

Reviewer:  Arun Mathur  from Marietta, GA
TKPROF: Release 9.2.0.1.0 - Production on Wed Jan 28 15:41:31 2004

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Trace file: ftdcdb_ora_15764.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: 43
********************************************************************************

alter session set events '10046 trace name context forever, level 12'

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: 43

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       24.63         24.63
********************************************************************************

BEGIN :x := return_outputs_algs('C130','5451','0307'); END;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.02       0.03          0          2          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.02       0.03          0          2          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 43

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        1.98          1.98
********************************************************************************

SELECT
    OUTPUTS.MEAS_NAME,
    OUTPUTS.TITLE,
    OUTPUTS.UNITS,
    OUTPUTS.OUTPUT_DATA_TYPE,
    OUTPUTS.ALG_NAME,
    OUTPUTS.ARG_NAME
FROM (
    -- ICD
    SELECT
        ICD_OUTPUTS.MEAS_NAME,
        SUBSTR(ICD_OUTPUTS.TITLE, INSTR(ICD_OUTPUTS.TITLE, ' ') + 1,
LENGTH(ICD_OUTPUTS.TITLE) - INSTR(ICD_OUTPUTS.TITLE, ' ')) AS TITLE,
        ICD_OUTPUTS.UNITS,
        ICD_OUTPUTS.OUTPUT_DATA_TYPE,
        ICD_ALGORITHMS.RK AS ALG_RK,
        ICD_ALGORITHMS.NAME AS ALG_NAME,
        ICD_ARGUMENTS.RK AS ARG_RK,
        ICD_ARGUMENTS.NAME AS ARG_NAME
    FROM
        TEMP_MEAS_NAME_MKDEXT,
        AIRCRAFT,
        PROJECT,
        TESTS,
        TEST_REVISIONS,
        REVISION_TYPE_LOOKUP,
        ICD_OUTPUTS,
        ICD_ALGORITHMS,
        ICD_ARGUMENTS
    WHERE
        ICD_OUTPUTS.MEAS_NAME = TEMP_MEAS_NAME_MKDEXT.MEAS_NAME AND
        AIRCRAFT.PROJECT_IDX = PROJECT.PROJECT_IDX AND
        AIRCRAFT.AIRCRAFT_IDX = TESTS.AIRCRAFT_IDX AND
        TESTS.TEST_IDX = TEST_REVISIONS.TEST_IDX AND
        TEST_REVISIONS.REVISION_TYPE_IDX =
REVISION_TYPE_LOOKUP.REVISION_TYPE_LOOKUP_PK AND
        TEST_REVISIONS.REVISION = ICD_OUTPUTS.ICD_REVISION AND
        ICD_OUTPUTS.PK = ICD_ALGORITHMS.OUTPUT_PK (+) AND
        ICD_ALGORITHMS.PK = ICD_ARGUMENTS.ALG_PK (+) AND
        (PROJECT.PROJECT = :b3) AND
        (AIRCRAFT.SERIAL_NUMBER = :b2) AND
        (TESTS.TEST = :b1) AND
        (REVISION_TYPE_LOOKUP.REVISION_TYPE = 'ICD')
    UNION
    -- FIDO
    SELECT
        FIDO_OUTPUTS.MEAS_NAME,
        SUBSTR(FIDO_OUTPUTS.TITLE, INSTR(FIDO_OUTPUTS.TITLE, ' ') + 1,
LENGTH(FIDO_OUTPUTS.TITLE) - INSTR(FIDO_OUTPUTS.TITLE, ' ')) AS TITLE,
        FIDO_OUTPUTS.UNITS,
        FIDO_OUTPUTS.OUTPUT_DATA_TYPE,
        FIDO_ALGORITHMS.RK AS ALG_RK,
        FIDO_ALGORITHMS.NAME AS ALG_NAME,
        FIDO_ARGUMENTS.RK AS ARG_RK,
        FIDO_ARGUMENTS.NAME AS ARG_NAME
    FROM
        TEMP_MEAS_NAME_MKDEXT,
        AIRCRAFT,
        PROJECT,
        TESTS,
        TEST_REVISIONS,
        REVISION_TYPE_LOOKUP,
        FIDO_OUTPUTS,
        FIDO_ALGORITHMS,
        FIDO_ARGUMENTS
    WHERE
        FIDO_OUTPUTS.MEAS_NAME = TEMP_MEAS_NAME_MKDEXT.MEAS_NAME AND
        AIRCRAFT.PROJECT_IDX = PROJECT.PROJECT_IDX AND
        AIRCRAFT.AIRCRAFT_IDX = TESTS.AIRCRAFT_IDX AND
        TESTS.TEST_IDX = TEST_REVISIONS.TEST_IDX AND
        TEST_REVISIONS.REVISION_TYPE_IDX =
REVISION_TYPE_LOOKUP.REVISION_TYPE_LOOKUP_PK AND
        TEST_REVISIONS.REVISION = FIDO_OUTPUTS.FIDO_REVISION AND
        FIDO_OUTPUTS.PK = FIDO_ALGORITHMS.OUTPUT_PK (+) AND
        FIDO_ALGORITHMS.PK = FIDO_ARGUMENTS.ALG_PK (+) AND
        (PROJECT.PROJECT = :b3) AND
        (AIRCRAFT.SERIAL_NUMBER = :b2) AND
        (TESTS.TEST = :b1) AND
        (REVISION_TYPE_LOOKUP.REVISION_TYPE = 'FIDO')
    UNION
    -- PCM
    SELECT
        ICL_OUTPUTS.MEAS_NAME,
        ICL_OUTPUTS.TITLE,
        ICL_OUTPUTS.UNITS,
        ICL_OUTPUTS.OUTPUT_DATA_TYPE,
        ICL_ALGORITHMS.RK AS ALG_RK,
        ICL_ALGORITHMS.NAME AS ALG_NAME,
        ICL_ARGUMENTS.RK AS ARG_RK,
        ICL_ARGUMENTS.NAME AS ARG_NAME
    FROM
        TEMP_MEAS_NAME_MKDEXT,
        AIRCRAFT,
        PROJECT,
        TESTS,
        TEST_REVISIONS,
        REVISION_TYPE_LOOKUP,
        ICL_OUTPUTS,
        ICL_ALGORITHMS,
        ICL_ARGUMENTS
    WHERE
        ICL_OUTPUTS.MEAS_NAME = TEMP_MEAS_NAME_MKDEXT.MEAS_NAME AND
        AIRCRAFT.PROJECT_IDX = PROJECT.PROJECT_IDX AND
        AIRCRAFT.AIRCRAFT_IDX = TESTS.AIRCRAFT_IDX AND
        TESTS.TEST_IDX = TEST_REVISIONS.TEST_IDX AND
        TEST_REVISIONS.REVISION_TYPE_IDX =
REVISION_TYPE_LOOKUP.REVISION_TYPE_LOOKUP_PK AND
        TEST_REVISIONS.REVISION = ICL_OUTPUTS.ICL_REVISION AND
        ICL_OUTPUTS.OUTPUT_PK = ICL_ALGORITHMS.OUTPUT_IDX (+) AND
        ICL_ALGORITHMS.ALGORITHM_PK = ICL_ARGUMENTS.ALGORITHM_IDX (+) AND
        (PROJECT.PROJECT = :b3) AND
        (AIRCRAFT.SERIAL_NUMBER = :b2) AND
        (TESTS.TEST = :b1) AND
        (REVISION_TYPE_LOOKUP.REVISION_TYPE = 'ICL')
    UNION
    -- HSDB
    SELECT
        HSDB_OUTPUTS.MEAS_NAME,
        HSDB_OUTPUTS.TITLE,
        HSDB_OUTPUTS.UNITS,
        HSDB_OUTPUTS.OUTPUT_DATA_TYPE,
        HSDB_ALGORITHMS.RK AS ALG_RK,
        HSDB_ALGORITHMS.NAME AS ALG_NAME,
        HSDB_ARGUMENTS.RK AS ARG_RK,
        HSDB_ARGUMENTS.NAME AS ARG_NAME
    FROM
        TEMP_MEAS_NAME_MKDEXT,
        AIRCRAFT,
        PROJECT,
        TESTS,
        TEST_REVISIONS,
        REVISION_TYPE_LOOKUP,
        HSDB_OUTPUTS,
        HSDB_ALGORITHMS,
        HSDB_ARGUMENTS
    WHERE
        AIRCRAFT.PROJECT_IDX = PROJECT.PROJECT_IDX AND
        AIRCRAFT.AIRCRAFT_IDX = TESTS.AIRCRAFT_IDX AND
        TESTS.TEST_IDX = TEST_REVISIONS.TEST_IDX AND
        TEST_REVISIONS.REVISION_TYPE_IDX =
REVISION_TYPE_LOOKUP.REVISION_TYPE_LOOKUP_PK AND
        TEST_REVISIONS.REVISION = HSDB_OUTPUTS.HSDB_REVISION AND
        TEMP_MEAS_NAME_MKDEXT.MEAS_NAME = HSDB_OUTPUTS.MEAS_NAME AND
        HSDB_OUTPUTS.HSDB_OUTPUTS_PK = HSDB_ALGORITHMS.OUTPUT_PK (+) AND
        HSDB_ALGORITHMS.HSDB_ALGORITHMS_PK = HSDB_ARGUMENTS.ALG_PK (+) AND
        (PROJECT.PROJECT = :b3) AND
        (AIRCRAFT.SERIAL_NUMBER = :b2) AND
        (TESTS.TEST = :b1) AND (REVISION_TYPE_LOOKUP.REVISION_TYPE = 'HSDB')
) OUTPUTS
ORDER BY MEAS_NAME, ALG_RK, ARG_RK

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.01       0.01          0          0          0           0
Fetch        1     12.03      44.56      25040      19792          0          14
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3     12.04      44.58      25040      19792          0          14

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 43     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
     14  SORT ORDER BY
     14   VIEW
     14    SORT UNIQUE
     14     UNION-ALL
      0      HASH JOIN
      1       TABLE ACCESS FULL OBJ#(27887)
      0       HASH JOIN
    290        TABLE ACCESS FULL OBJ#(28881)
 244212        HASH JOIN OUTER
  81264         HASH JOIN OUTER
  40632          HASH JOIN
      1           NESTED LOOPS
      3            NESTED LOOPS
      1             NESTED LOOPS
      1              TABLE ACCESS BY INDEX ROWID OBJ#(27889)
      1               INDEX RANGE SCAN OBJ#(30464) (object id 30464)
      1              TABLE ACCESS BY INDEX ROWID OBJ#(28092)
      5               INDEX RANGE SCAN OBJ#(28372) (object id 28372)
      3             TABLE ACCESS BY INDEX ROWID OBJ#(30432)
      3              INDEX RANGE SCAN OBJ#(30434) (object id 30434)
      1            TABLE ACCESS BY INDEX ROWID OBJ#(30429)
      3             INDEX UNIQUE SCAN OBJ#(30430) (object id 30430)
 361051           TABLE ACCESS FULL OBJ#(28122)
 722102          TABLE ACCESS FULL OBJ#(28125)
2182367         TABLE ACCESS FULL OBJ#(28148)
      0      HASH JOIN OUTER
      0       HASH JOIN
      0        HASH JOIN OUTER
      0         TABLE ACCESS BY INDEX ROWID OBJ#(28514)
      2          NESTED LOOPS
      1           NESTED LOOPS
      3            NESTED LOOPS
      1             NESTED LOOPS
      1              NESTED LOOPS
      1               TABLE ACCESS BY INDEX ROWID OBJ#(27889)
      1                INDEX RANGE SCAN OBJ#(30464) (object id 30464)
      1               TABLE ACCESS BY INDEX ROWID OBJ#(28092)
      5                INDEX RANGE SCAN OBJ#(28372) (object id 28372)
      1              TABLE ACCESS BY INDEX ROWID OBJ#(27887)
      1               INDEX UNIQUE SCAN OBJ#(27888) (object id 27888)
      3             TABLE ACCESS BY INDEX ROWID OBJ#(30432)
      3              INDEX RANGE SCAN OBJ#(30434) (object id 30434)
      1            TABLE ACCESS BY INDEX ROWID OBJ#(30429)
      3             INDEX UNIQUE SCAN OBJ#(30430) (object id 30430)
      0           INDEX RANGE SCAN OBJ#(29581) (object id 29581)
      0         TABLE ACCESS FULL OBJ#(28517)
      0        TABLE ACCESS FULL OBJ#(28881)
      0       TABLE ACCESS FULL OBJ#(28519)
     14      HASH JOIN
   6326       HASH JOIN OUTER
   1493        HASH JOIN OUTER
   1018         TABLE ACCESS BY INDEX ROWID OBJ#(28199)
   1020          NESTED LOOPS
      1           NESTED LOOPS
      3            NESTED LOOPS
      1             NESTED LOOPS
      1              NESTED LOOPS
      1               TABLE ACCESS BY INDEX ROWID OBJ#(27889)
      1                INDEX RANGE SCAN OBJ#(30464) (object id 30464)
      1               TABLE ACCESS BY INDEX ROWID OBJ#(28092)
      5                INDEX RANGE SCAN OBJ#(28372) (object id 28372)
      1              TABLE ACCESS BY INDEX ROWID OBJ#(27887)
      1               INDEX UNIQUE SCAN OBJ#(27888) (object id 27888)
      3             TABLE ACCESS BY INDEX ROWID OBJ#(30432)
      3              INDEX RANGE SCAN OBJ#(30434) (object id 30434)
      1            TABLE ACCESS BY INDEX ROWID OBJ#(30429)
      3             INDEX UNIQUE SCAN OBJ#(30430) (object id 30430)
   1018           INDEX RANGE SCAN OBJ#(29580) (object id 29580)
  29152         TABLE ACCESS FULL OBJ#(28201)
 113933        TABLE ACCESS FULL OBJ#(28203)
    290       TABLE ACCESS FULL OBJ#(28881)
      0      HASH JOIN
      0       NESTED LOOPS
      0        NESTED LOOPS OUTER
      0         NESTED LOOPS OUTER
      0          NESTED LOOPS
      3           NESTED LOOPS
      1            NESTED LOOPS
      1             NESTED LOOPS
      1              TABLE ACCESS BY INDEX ROWID OBJ#(27889)
      1               INDEX RANGE SCAN OBJ#(30464) (object id 30464)
      1              TABLE ACCESS BY INDEX ROWID OBJ#(27887)
      1               INDEX UNIQUE SCAN OBJ#(27888) (object id 27888)
      1             TABLE ACCESS BY INDEX ROWID OBJ#(28092)
      5              INDEX RANGE SCAN OBJ#(28372) (object id 28372)
      3            TABLE ACCESS BY INDEX ROWID OBJ#(30432)
      3             INDEX RANGE SCAN OBJ#(30434) (object id 30434)
      0           TABLE ACCESS BY INDEX ROWID OBJ#(30441)
      0            INDEX RANGE SCAN OBJ#(30443) (object id 30443)
      0          TABLE ACCESS BY INDEX ROWID OBJ#(30445)
      0           INDEX RANGE SCAN OBJ#(30447) (object id 30447)
      0         TABLE ACCESS BY INDEX ROWID OBJ#(30449)
      0          INDEX RANGE SCAN OBJ#(30451) (object id 30451)
      0        TABLE ACCESS BY INDEX ROWID OBJ#(30429)
      0         INDEX UNIQUE SCAN OBJ#(30430) (object id 30430)
      0       TABLE ACCESS FULL OBJ#(28881)

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  db file sequential read                       284        0.00          0.01
  db file scattered read                       1240        0.01          1.02
  direct path write                             335        1.58         33.30
  direct path read                              335        0.09          0.61
  SQL*Net message to client                       1        0.00          0.00
  SQL*Net message from client                     1        6.56          6.56

********************************************************************************

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.02       0.03          0          2          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      0.02       0.03          0          2          0           1

Misses in library cache during parse: 1

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       24.63         26.62
  log file sync                                   1        0.01          0.01

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      2      0.01       0.01          0          0          0           0
Fetch        2     12.03      44.56      25040      19794          0          15
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        6     12.04      44.58      25040      19794          0          15

Misses in library cache during parse: 0

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        6.56          6.56
  db file sequential read                       284        0.00          0.01
  db file scattered read                       1240        0.01          1.02
  direct path write                             335        1.58         33.30
  direct path read                              335        0.09          0.61

    4  user  SQL statements in session.
    1  internal SQL statements in session.
    5  SQL statements in session.
********************************************************************************
Trace file: ftdcdb_ora_15764.trc
Trace file compatibility: 9.00.01
Sort options: default

       1  session in tracefile.
       4  user  SQL statements in trace file.
       1  internal SQL statements in trace file.
       5  SQL statements in trace file.
       5  unique SQL statements in trace file.
    2535  lines in trace file.

          Followup:

well, that shows 44 seconds, not 3 minutes.  If it is taking 3 minutes in the
application -- the other 2minutes and 15 seconds are to be found in the app --
not in the database.

and the major wait event is:

 direct path write                             335        1.58         33.30

which is TEMP work so either

a) look at your sort area/hash area sizes or
b) pga_aggregate_target

depending on your workarea policy setting (auto or manual).  Perhaps they are
"too small" and you are using temp more then you really should be.

      Rating: 5
Very much
      appreciated  January 29, 2004
Reviewer:  Arun
      Mathur  from Marietta, GA
Thank you very much for your quick and thorough response. Can't begin to tell
you how much I've learned from you.

Regards,
Arun

January 30, 2004 Tom -- Thanks for the question regarding
      "Development Environment/Methodolgy", version 8.1.7

      You Asked
Tom:

Hope this gives you a bit of a break from the technical :-).  Can you give me a
general picture of what a development environment should look like, with regard
to permissions in the database?  Consider an organization with 2 data
architects, 10 application developers (web and otherwise), half a production
DBA, and a couple of QA people.  The DA's primary responsiblity is designing the
database.  We get somewhat involved in production issues, and laying out
strategies there for the long term, but most of the day to day DB activities
fall on the production DBA.  That works well.

Regarding the development environment, we have a few different database silos,
and each silo has 3 instances:  DEV, QA, and PROD.

I would categorize the developers as not having much experience with programming
with Oracle, and in some cases, SQL is even new to them.  They pretty much do
their best, and ask us for help as they go.  We try to review the SQL as they
go.

The main question is:  what about programmers' permissions in the DEV instance?
Should they have table create/alter privs there so they can move at their own
pace?  We (the data architects) are having trouble with this concept because the
programmers are not DB designers.  We want to own the design, and we want to be
the only ones creating/altering tables, even in DEV.  Because, if they create
something the wrong way, they will code to it, and then later when we object to
the design, they will claim they've already done 2 weeks of coding against it
and it's too expensive to change now.

I respect your opinion very much, and would appreciate your high level take on
this issue.

Thanks.

      and we said...
I concur, the data model is yours.  They should have appropriate grants against
it in order to develop the application and you should listen to their input (eg:
this query we developers have to run 1000 times an hour is impossible against
your schema -- maybe schema needs to change?).

It would be utter chaos for everyone to make up their own stuff -- you are
working on a single system -- the data is the core, the heart.  It must be built
in a coordinated fashion.

Developers should be able to create objects in their own schemas -- to test
with, to be able to come to you and say "hey, if it were like this ...." (i
think developers should have their own oracle instance on their desktop to play
with personally).  But they should be congnizant of the fact that their code
will only run against the "real schema".

If it is not too late, you might want to make a rule that there will be NO
"select, insert, update, deletes" allowed in the application itself -- that'll
make tuning, security, fixing the database logic tons easier as well.

Review &
      Followup

      Rating:
      4
QUESTION  December 12, 2002
Reviewer:
      PRS  from USA
I have a stored procedure. I have put DBMS_OUTPUT lines to print some
information for the debugging point of view in the stored procedure. The number
of lines with DBMS_OUTPUT is approximately 5% of the number of lines of source
code for a procedure. I have put this DBMS_OUTPUT for the debugging point of
view. This procedure exist in the production environment. So in case of any
failure I can execute this procedure by setting SET SERVEROUTPUT ON and get the
output by DBMS_OUTPUT(i.e. debug information) about the code.

My DBA has a problem of having a DBMS_OUTPUT lines of code in the production
environment. I asked him the reason. He says it is a policy not to have the
DBMS_OUTPUT lines. But he has no valid reason.

Is it wrong to have the DBMS_OUTPUT?

          Followup:

No, your DBA is being silly (i actually have much stronger words for what they
are being -- but this being a public forum and all)

You can justify for very good reasons why DBMS_OUTPUT should there.

Your DBA cannot justify squat.

Maybe your DBA thinks Oracle should remove the EVENT system we have for dumping
debug?  Maybe your DBA thinks the code for generating trace files should be
removed from the kernel?  Maybe your DBA wants to make it physically impossible
to track down issues and problems?

Maybe in order to "make them happy", you could global change
DBMS_OUTPUT.PUT_LINE to "MY_PKG.P" and create a package

create package my_pkg
as
   procedure p( p_str in varchar2 );
end;
/

create package body my_pkg
as

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;

end;
/

in test and in production:

create package my_pkg
as
   procedure p( p_str in varchar2 );
end;
/

create package body my_pkg
as

procedure p ( p_str in varchar2 )
is
begin
    NULL;
end;

end;
/

That way

a) you can debug in dev with lines > 255 even...
b) in production, your DBA is a happy camper
c) when you have a "bug" you make them put the right body back in, in order
   to get the debug back out.

      Rating: 3
The way we
      work  December 13, 2002
Reviewer:  Marc Blum
       from Aachen, Germany
In our projects we typically have 4 roles:

- Projectmanagemer/Designer: much experience, gathering of requierements,
holding contact to the customer, application design, data modeling, coordination
of all development activities

- Project-DBA: good or much experience, focuses on database technology, supports
the designers, involved in all design decisions, implements the data model,
coaches the developers, maintains all development databases, responsible for
versioning and deployment, is aware of all database related activities

- Developer: various levels of experience, development of application code
(PL/SQL, C++, VBA, Delphi etc.), works very task oriented

- Tester/QA: low experience, maybe good understanding of the requierements
(sorrily often students or part time workers, sigh)

hth

      Rating:
      5
DBMS_OUTPUT.PUT_LINE  December 13, 2002

Reviewer:  Arun Gupta  from Harrisburg, PA USA

Tom
I recently recommended that developers should comment out the
dbms_output.put_line from the code which goes into production. The reason was
simple. In our architecture, the COM+ components in middle tier call the stored
procedures and the dbms_output goes nowhere.

Though it is a good idea to have each developer run Oracle instance on their
desktops, I have found the following issues with this:
a) Who creates these instances and database objects and keeps these databases in
sync with the integrated development database?
b) Who manages these instances if something goes wrong? These databases are
operating without any backup, without any protection from file deletion.
c) Most often, developers will test the application with stored procedures on
their local instance and then forget to migrate the code to source control
mechanism. This can lead to application breaking down when deployed in
integrated development database. The developers come running to the DBA blaming
that the development database is not "configured properly".
d) The developers working in their own schema used schema_name.object_name in
queries. In the integrated developement environment, the schema_name doesn't
exist, the query fails and they again come running to the DBA.
e) Developers create test cases in their local database and then expect the DBA
to migrate these test cases to integrated development database. If sequences are
used as surrogate primary keys, this may not be possible.
f) How can we ensure that developers will follow the object naming convention
and change management procedures?

It can well be argued that the developers can have their own databases with the
premise that it will be used as scratchpad and doesn't come with any guarantees
or warranties, as development progresses, expectations rise and the local
databases sitting on the developer's desk becomes as important as the production
database!!

Just my experiences...

Thanks

          Followup:

so?  when they want to debug their code -- eg: they get the call "hey, code
broke, help" -- what do they do then?

why bother commenting it out?  it is a waste of time and energy.  Then you have
code in production that is not the same as code in dev and test.  The slightest
difference could cause issues.

Use my "my_pkg.p" approach -- then they could turn p into something that uses
utl_file instead of dbms_output even -- this stuff is way to precious to
comment out.  It has saved my butt a gazzilion times over -- even I have bugs in
my code.  without this sort of stuff -- i would NEVER be able to fix it.  I've
dedicated a rather large section of my next book to this topic and how it is so
short sighted to outlaw or remove it!!!

a) who said they had to be kept in sync?  The developers having their own
database is so that they can test ideas, play with concepts.  Development takes
place in development.  If they develop on their desktop -- they have to deploy
their changes to development for testing.

b) so? they are - as i said -- to test with, to play with.  It is not dev -- it
is a playground.  A total loss of these databases is not material to anything.

c) that is a problem in your PROCESS then, doesn't matter if they have a
database or not on their machine does it?  You have this problem with or without
a playground

d) why?  why do they come "running to you" for an obvious error on their
part????  not getting it.

e) why?????  I'm not getting it at all.  You are talking about migrating data
here -- not a test case.

f) BECAUSE THEY DO THEIR REAL WORK IN DEV which gets migrated to TEST which gets
rolled out to PRODUCTION.  the thing on their desktop -- lets them tune queries
without having to bend your arm to get the trace files.  Lets them use autotrace
without forcing you to give access to v$ tables.  Lets them test ideas without
asking you to grant them privs (eg: create any context, wanna test out this
context thing -- see how/if it works -- prove that it is the best AND THEN I'll
approach that DBA guy and see if I can bend his arm to grant this to me in DEV)

But to make them physically REMOVE Debug code -- boy -- you are way off base --
way way off base.  Your basic premise is sooooo wrong (my 20cents worth, my
experience).

      Rating: 5
Licence
      costs  December 13, 2002
Reviewer:  Paul
       from UK
I requested having my own local database to experiment on, but was denied this
due to licence cost :-(

Not really for this forum, but it might be worth checking with Oracle (Sales?)
to clarify the licencing issues before going down this route.

Paul

      Rating: 4
Great ideas and
      follow-up  December 13, 2002
Reviewer:  Eric
      Givler  from Harrisburg, PA
I think the idea of being able to trace your code is extremely important.  With
that in mind, though, is it an issue to run: alter <obj> compile debug; on
objects (procedures, packages, functions, etc) in your production instance?
This allows stepping the server code from the client-side tools like procedure
builder.

I've already put the utl_file tracing concept in place, and must say that it
works like a charm (the one from the Beginner Oracle book).

I'm a little confused on your "NO SELECT, INSERT, UPDATE, DELETE statements".
If you are using a Forms tool, and using base table blocks, this is going to
cause a lot of extra code to be written, especially in the case of complex
user-entered queries, etc. which forms handles automatically.  What's the point
of adding to the work if you have a LARGE# of developer written modules?  I
realize the options to base blocks on other things (stored procs, etc) has been
around for quite some time.

          Followup:

When I said "no insert/update/delete/select" statements -- I was (implicitly --
should have stated it) refering to hand generated code like VB, Java.

Yes, if using a tool like Powerbuilder or Forms -- having
select/insert/update/delete is virtually inescapable for practical reasons.

      Rating:
      5
  December 13, 2002
Reviewer:  Dave
       from Ohio
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 EE 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.

      Rating: 5
I beg to
      differ  December 14, 2002
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
======================================
But to make them physically REMOVE Debug code -- boy -- you are way off base --
way way off base.  Your basic premise is sooooo wrong
======================================
Every application has debug features. I am yet to see an application where debug
features are left turned on by default. There must be a well defined mechanism
to turn on debugging when required. How many times I have read in this forum
that every extra line of code has a performance impact associated with it.

Judging a DBA because he asked for debugging turned off (not removed) is not
justified in a forum of this standing.

As for the developers having a desktop Oracle instance and database, I have many
times read in this forum that any database used for query tuning must be as
close to production database as possible. How can this be achieved on a desktop?
Secondly, there is a real possibility of the desktop database soon becoming a
substitute for the real DEV database.

While the aim should not be to frustrate developers, a well controlled database
environment always benefits the developement process in the long run, since a
group of persons(DBA/Server Admins/Project Manager/Leads) always know what
environment settings a project is being developed under. If a developer migrates
a piece of working code from his desktop to DEV server and it breaks down, he
can spend hours trying to find what went wrong. I have seen this happen many
times. I will give real life instances personally witnessed by me.

Instance one: COM+ components. They worked very well on the desktop because the
developer had different version of dll than the server. 8 manhours were spent in
trying to figure out which dll was causing it. Result: The developers were
asked to develop and test against the server dlls only.

Instance two: Perfectly working code, tested in DEV and INTG, migrated from INTG
to Acceptance test. The transactions failed to enlist in the distributed
transaction coordinator. The difference was that we moved from Windows 2000
Advanced Server to Windows 2000 Datacenter. Oracle treats both of them as same.
I had to open a TAR with Oracle. They asked me to turn on tracing of Oracle
Service for MTS at highest level. After looking at the trace files, they
suddenly spotted that every 9th transaction was failing to enlist. They told me
to set a hidden parameter _max_transaction_branches=32. That fixed the problem.
User acceptance testing delayed by two days. Result: We moved all the databases
to Windows 2000 Datacenter.

Instance three: Developer tests the code in his schema, works fine. Forgets to
migrates to DEV, goes home (most work 6:00am to 3:00pm). The testing lead tests
the app at 4:30pm, it breaks down. Developer is called at home, he instructs the
dev lead to migrate code from local machine to DEV. Two hours lost. Result:
Developers were discouraged from developing in own schema.

All these decisions were taken by project management team. I as a DBA, had no
hand in it. However, seeing the headaches it caused, I tend to agree.

We have had our more than fair share of problems in production across multiple
projects. The one thing we have not done is use production database as debugging
platform. There are 4 pre-production platforms for this purpose. These
environments are periodically synch'ed up with production. Any code that goes
into production is kosher.

However, things vary by the size of project, technological architecture,
available resources, timelines, budgets, contractual limitations and so on.
There is nothing like a one size fits all. Thinking that forums are for sharing
a variety of experiences and opinions, I had come out with one of the approaches
that has worked for us. However, this approach is restrictive and doesn't seem
to agree with many. I apologize if I have caused any offense.

Thanks

          Followup:

dbms_output is TURNED OFF by default.  Your premise is wrong.  to make they
PHYSICALLY REMOVE IT IS WRONG.

There is overhead -- overhead something you can and should avoid.

Then there is mandatory stuff -- stuff that must be in there forever.  This
falls into this category.  You want to see some?  Just change the NO to YES in
the URL above.  I would Never in a billion years remove my debug/timing code.  I
need it CONSTANTLY.

You said (i quote)

I recently recommended that developers should comment out the
dbms_output.put_line from the code which goes into production.

you made them REMOVE IT -- it is commented out. It is not there.  They cannot
get it again without physically changing code and putting it back in and
recompiling.  You let them do that in prod when there is an issue???

I'm not judging you -- I'm saying "you are wrong to do this".  Period.

sigh -- why is everyone missing this point of the database on the desktop. You
use it to test ideas, to learn, to try things out.  You want to find out which
approach takes more latches (latches being locks, locks being serialization
devices, serialization devices being the thing that kills scalability)?  You
need a single user instance running in isolation.  It is a playground, a scratch
database.  It is not your development box, it is a playground,  It gets wiped
out -- big deal, nothing lost, just use dbca and recreate it.  See also:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6349391411093
where we discussed this playground database.

I agree a well controlled development, test, production setup is mandatory,
I think I've said that more then once here as well.  However, if the developer
wants to prove that bind variables are better then not using bind variables --
well, they need to flush the shared pool for example.  Will you let them do
that?  Oh yeah, they need to kick everyone else off during the test to make the
numbers meaningful.  Will you let them do that?  No, of course not.  So, what
can they do then?  Well, if they have that little playground instance....

And if the developer cannot migrate his code from his machine to DEV -- how pray
tell will it go from DEV to TEST magically?  Also, I never said "develop on
their desktop" -- that is what DEV is all about.  Nuff said on that topic.

Your example with the COM stuff -- that'll happen between DEV and TEST and TEST
and PROD.  Thing is -- if what you are testing is database stuff -- it won't
happen between Oracle and Oracle -- I develop on linux to deploy to solaris or
even windows all of the time.  We have the same bugs on all platforms so it
ports pretty easy.

UAT was not delayed.  UAT did what UAT was designed to do.  Catch these issues.

And with the developers not being able to have their own schema to test with --
that's a particularly bad example.  First -- the pain inflicted here was 100%
self induced -- the programmer was the entire cause.  What if they had just
written the code and not moved it into source code control or something.  Having
their own schema is not the cause here -- a person making a mistake -- yes.
Tell me -- when this same programmer who can no longer test in their own schema
AND cannot have a local database wants to compile new functionality into a
package that everyone uses -- what then?  How many hours will be wasted by:

o the programmer waiting for everyone to stop using it
o the other developers waiting for this programmer to work the kinks out -- you
know, debug this piece of code that lots of people use

All of the issues you describe happened without desktop databases -- it is all a
project management issue -- all of it policy and procedures.  I'm not saying to
develop on the desktop -- but boy, if you want to test an idea, having it there
is a nice thing.

However, we got so far off topic here -- we were talking debug code which you
are making them physically remove.  That is a bad policy period.  The Oracle
data is heavily instrumented and we can turn it on at will (hey, you know that,
Support had you do that to track down an issue!!!)

You seem to say "i want homogenous code" but you make them comment out code --
it is no longer homogenous.  Perhaps there was a side effect from calling
dbms_output that made the code work in test and dev.  Removing the call to
dbms_output introduces a bug that was not detected.  So following your methods
we must not only remove the dbms_output from prod but also from test and
therefore DEV.

True story:  Some C code.  Littered with fprintfs to a debug file (had

   if debug fprintf( .... )

all over).  Someone says "hey, code will run faster if we don't have those if's,
comment them out. don't need to test or anything, same code right..."

Well, C being C -- the physical removal of code changed some pointer locations
and exposed a nasty application related bug that wasn't exposed when the
fprint's were in the source.  Bummer, works in test -- debug shows it works --
doesn't crash, all is well in the world. But production -- doesn't work at all.
Why?  it isn't the code we developed.  It isn't the code we tested.  It is code
sans debug code.  So, not only were we handicapped in trying to figure out what
was wrong -- we had no debug we could run -- we couldn't reproduce the issue
(cause there we did have debug on).

What if the dbms_output somewhere has:

   dbms_output.put_line( 'and the answer = ' || f(x) );

well, commenting that out blindly -- just making them remove it -- could easily
materially affect the code.  What does F do?  Is it important?  Maybe they
really wanted f as a procedure, didn't care about the return value and this was
a cheap way to call it (in their mind).  Blindly removing the dbms_outputs --
well, that could change the outcome.

You haven't caused any offense, I'm still trying to convince you that leaving
that debug code in there is justified and the right thing to do.  Making them
physically remove it is a very bad idea.  If you want to outlaw a scratch
database on the desktop -- so be it.  I only see positives, no negatives.  Every
negative you came up with was pretty much policy based.  Take the COM+ dll
example.  Do you think there would have been ZERO hours of fixing if they had
the "bad dlls" from the beginning?  No -- they would have fought the DLL he%#@
for N hours on the real machine as well (been there, done that, never will I
program windows again personally).  Not a chance -- they would have spent 4
hours on the dev machine saying "why isn't this working as it should".

Anyway.

      Rating: 2
Source Control
      for PLSQL  January 29, 2004
Reviewer:  Tommy
       from Toronto, Canada
Tom,

What would you suggest for source controlling in a multi-developers environment
for PLSQL packages, procedures, functions and views editing? Seems like SQL
navigator can integrate with Microsoft Source Safe, but still we cannot prevent
developers to replace a package from script. And other tools like Toad and
SQLPlus are not aware of the control that SQL Navigator is forcing.

I have looked at others like PVCS but not in very detail. Would it have similar
problem? Plus it seems that we cannot check the history of the package being
edited.

Any good suggestion?

          Followup:

everything will have this "issue" that the developers can ignore the tools.
Everything.

      Rating: 1
Source
      Control  January 29, 2004
Reviewer:  Tommy
       from Toronto, Canada
Forget about the script problem.

If we have more than one tool (say SQLNav and Toad) for example, how would you
suggest we use for controlling? Is there a way better than check in and out a
text file into/from Source Safe, then edit by these tools, then check back in
from the text file again? I am wondering if we can allow the use of both SQLNav
and Toad, and may be some others. But if we use the SQL Nav control (ver 4),
then Toad and other tools (even SQL Nav 3) is not under control.

          Followup:

You need to use a check in/check out metaphor if you want source code control,
if you want configuration management, if you want to know whats going on

This is no different then if you were writing a C program and some wanted to use
visual C and others wanted to use borland C or whatever.  You would need to use
source code control tools.

PLSQL is just code.
Java is just code.
VB is just code.

if you want configuration management, versions, trackability -- you'll be using
source code control tools to do it.

      Rating: 4
Regarding
      debugging procedure  January 29, 2004
Reviewer:
      Matt  from Australia
I see the advantage of the replaceable package body for enabling debug.

On previous systems I have worked on I have seen this instead though using a
globally packaged variable:

procedure p ( p_str in varchar2 )
is
begin
  if not g_debug_enabled then
    return
  else
    --dubug processing here
  end if;
end;

So, there is a little more overhead to process the if, but the code is always in
production (and so it is easier to flick the switch).

Is there any good reason why this should not be used in production code? The
systems where I have seen this have typically been batch processing systems (so
the overhead is a small % of the overall run time). Would this still be
appropriate for OLTP?

          Followup:

yes -- in fact I pretty much do not put the empty body in myself -- I let the
code stay there since having it there saves so much time at the end of the day
(when you need it, you NEED it)

the "compile an empty body" is for those who would not otherwise let the code in
even.

      Rating:
      4
Gupta  January 31, 2004
Reviewer:
      A reader
I hope that you've convinced Arun to leave the debug messages alone c^)....

January 30, 2004 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 ...

      Rating:
      5
dbwr  January 17, 2004
Reviewer:
      Reader
Excellent thread and great answers from Tom.

I was asked by a person the following. I could not give a convincing answer.
Could you help? Thanks.

When dbwr writes dirty buffers every three seconds, the timeout of 3 seconds
itself should advance the checkpoint position. Why do we need to set other
parameters like log_checkpoint_interval and such. Please clarify. Thanks.

          Followup:

i would say in most all cases you do not need to set such parameters -- it
mostly takes care of itself these days.  things like fast_start_mttr_target are
"better".

ckpt writes control files every three seconds, lgwr flushes redo... but dbwr
isn't doing that to datafiles

      Rating: 5
just a small
      clarification about incremental checkpoint  January 29, 2004

Reviewer:  H.S.Anand  from New Delhi, India

I understand the difference between log_checkpoint_interval and
log_checkpoint_timeout and that
Oracle will internally calculate an optimal checkpoint positions if I set both!
Which means i need to use only one of the two!

Just one niggling doubt.
If i set neither of the two, would it mean that effectively, Incremental
checkpoints will never happen?

Please clear this one doubt!!

REgards

          Followup:

see fast_start_mttr_target....

it is almost always happening these days.

January 30, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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://download-west.oracle.com/docs/cd/A87860_01/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.

      Rating: 5
sql
      plus  January 29, 2004
Reviewer:  mo

TOm:

1. Is there anything in SQL*plus that you set to wrap full description around in
the fixed colum size.  For example, if i select :

select stock_no,description,qty from table;

stock_no  description                      qty

I want to see full description(e.g. 5 lines) wrapping within a fixed I set for
"description".

2.  Would this work if i have the query in a VIEW?

Thank you

          Followup:

column description format a40 word_wrapped

      Rating: 4
Input truncated
        January 30, 2004
Reviewer:  Tarun Babu
       from India
Hi tom,

I have written the follwoing code in a file xyz.sql

create or replace procedure empvu_process
is
    v_sal number;
begin
     select sal
     into v_sal
     from emp_vu
     where empno=7566;
     dbms_output.put_line('The salary is:'||v_sal);
end;
/
SQL> @xyz
Input truncated to 1 characters

Procedure created.

SQL> exec empvu_process
The salary is:2975

PL/SQL procedure successfully completed.

What is the meaning of "Input truncated to 1 characters". Sometimes it appears
while displaying output in our Oracle Apps reports. How to supress this message?
Thanks in Advance...

          Followup:

you are missing a "newline" on that last line.

just edit the file
goto the last line
goto the end of the last line
hit enter
save file

January 30, 2004 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.

      Rating: 4
opposite of
      null   January 30, 2004
Reviewer:  surya

Tom,

is there an opposite to null value?
what i meant was like null comparison always fails,
is there a character/value, that matches with any value?

          Followup:

it does not always fail.  In fact it never fails.  It never succeeds either.  It
is quite simply "not known whether it is true or false".

it is tri valued logic -- no "opposite" there, just three outcomes:

true
false
unknown

you use

"where a=b or (a is null and b is null)"

to include "null = null" as being "true"

January 30, 2004 Richard -- Thanks for the question regarding
      "Subtracting months frorm a current date", version 8.1.7.3

      You Asked
I am trying to subtract 6 months from the current date (SYSDATE).  I know about
the ADD_MONTH function, but I haven't been able to find documentation about a
"SUBTRACT_FUNCTION" which can do this for me.

      and we said...
add_months( sysdate, -6 )

just add "minus six" months.
Review &
      Followup

      Rating: 4
Good and quick
      response  January 30, 2004
Reviewer:
      Richard  from Toronto, Ontario, Canada
Very quick response

January 30, 2004 wallacel -- Thanks for the question regarding
      "sql_trace", version 8.1.6

      You Asked
Tom:

can i alter sql_trace on line?

alter system set sql_trace=true;
alter system set sql_trace=true
                 *
ERROR at line 1:
ORA-02095: specified initialization parameter cannot be modified

Regards,

      and we said...
Only at the session level.

ALTER SESSION SET SQL_TRACE=true;

In 8i, you can create an on-logon type trigger:

create or replace trigger login_trigger
after logon on database
begin
     execute immediate 'alter session set sql_trace=true';
end;
/

to turn it on "globally".  Make sure the owner of the trigger
has been granted "ALTER SESSION" *directly* (not via a role).
See

http://osi.oracle.com/~tkyte/Misc/RolesAndProcedures.html
for why that is.

Review &
      Followup

      Rating: 4
ORA-01031:
      insufficient privileges  November 05, 2003

Reviewer:  Robert  from CT
Tom,
I want to try out tracing & tkprof HTML DB this way....
FLOWS schema flows_010309 has been granted "alter session" & "create any
trigger"

SQL> connect flows_010309@rcdb
Enter password:
Connected.
SQL> create or replace trigger login_trigger
  2  after logon on database
  3  begin
  4       execute immediate 'alter session set sql_trace=true';
  5  end;
  6  /
after logon on database
               *
ERROR at line 2:
ORA-01031: insufficient privileges

What privileges are missing ?
Thanks

      Rating: 5
is there a
      traceonly for sql_trace?  January 30, 2004

Reviewer:  streve  from nyc, NY
Hi Tom,

In Sql-trace, how not to display query result on the screen but show all
statistics and execution plan?
just like in autotrace, we can set traceonly?

Thanks!

Steve

          Followup:

you can either:

alter session set sql_trace=true;
set termout off
run queries here
set termout on
exit

OR

set autotrace traceonly
alter session set sql_trace=true;
run queries here

to surpress SQL*Plus from printing data on the screen.  (if you use set termout
off -- make sure you are using a script -- it surpresses scripted output only)

January 30, 2004 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.............

      Rating: 5
your article on
      partition sparse outer join Oracle Magazine Jan/Feb
      2004  January 30, 2004
Reviewer:  steve

Hi tom,

we have almost the same problem, but
we don't have 10g now, How to do it in 9i?

Thanks!

Steve

          Followup:

you have to generate a "full diminesion" table -- take the time dimension and
cartesian product it with the "product" dimension to generate all "time by
product" combos.  like this (these two queries are the same in 10g, the
partitioned outer join is just more efficient)

the column:

http://otn.oracle.com/oramag/oracle/04-jan/o14asktom.html
actually demonstrates both the 9i and before with a cartesian product as well as
the 10g partitioned outer join.

January 30, 2004 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

      Rating: 4
Great
      information, but need to know...  January 29, 2004

Reviewer:  Mike  from Portland, OR
How much space is needed to run:

alter table t modify long_col CLOB?

      Rating: 4
I think I
      figured out my space question  January 30, 2004

Reviewer:  Mike  from Portland, OR
To convert a long to a clob in 9i you need at least as much space as the table
currently uses.  It looks like Oracle rewrites the entire table, right?

          Followup:

yes, it is basically rewriting it.

January 30, 2004 jeff -- Thanks for the question regarding "how
      oracle store data from inserting row or delete?", version 7.1.7

      You Asked
what order does oracle store data example.

1). insert into database the following;

    column id  name
            5  jone
            1  smith
            3  johnson

2). submit a query select id, name from tb;

report back as:   id      name

                    5  jone
                    1  smith
                    3  johnson

user request order of:
                   id      name
                    1  smith
                    3  johnson
                    5  jone

question is does oracle store data in chronological order?

      and we said...
no, rows are not stored in any sort of order.  they are stored in a "heap".
they go where ever they fit.

if you need data in some order -- YOU MUST use an order by.  no way around it.

ops$tkyte@ORA9IR2> drop table t;

Table dropped.

ops$tkyte@ORA9IR2> create table t
  2  ( x int,
  3    a char(2000),
  4    b char(2000),
  5    c char(2000),
  6    d varchar2(2000)
  7  ) tablespace &1;
old   7: ) tablespace &1
new   7: ) tablespace T1_DATA

Table created.

ops$tkyte@ORA9IR2> pause

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t (x) values (1);

1 row created.

ops$tkyte@ORA9IR2> insert into t (x,a) values (2,'x');

1 row created.

ops$tkyte@ORA9IR2> insert into t (x,a,b,c,d) values (3,'a', 'y', 'z',
rpad('x',500,'x') );

1 row created.

ops$tkyte@ORA9IR2> insert into t (x,d) values (4,rpad('x',725,'x') );

1 row created.

ops$tkyte@ORA9IR2> pause

ops$tkyte@ORA9IR2> select dbms_rowid.rowid_block_number(rowid), x from t;

DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)          X
------------------------------------ ----------
                                  10          1
                                  10          2
                                  11          3
                                  11          4

ops$tkyte@ORA9IR2> commit;

Commit complete.

ops$tkyte@ORA9IR2> delete from t where x = 2;

1 row deleted.

ops$tkyte@ORA9IR2> commit;

Commit complete.

ops$tkyte@ORA9IR2> insert into t (x) values (5);

1 row created.

ops$tkyte@ORA9IR2> commit;

Commit complete.

ops$tkyte@ORA9IR2> pause

ops$tkyte@ORA9IR2> select dbms_rowid.rowid_block_number(rowid), x from t;

DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)          X
------------------------------------ ----------
                                  10          1
                                  10          5
                                  11          3
                                  11          4

That shows data does not come out in the order of insertion.
Review &
      Followup

      Rating:
      5
Perfect  January 30, 2004
Reviewer:
      Rob  from Manitoba Canada (currently -50 dec C)
Excellent Example of Heap Tables and data storage.  Maybe you could show
examples of heap tables and different inittrans, maxtrans and PCTfree values.

          Followup:

I did -- it is in a book called "expert one on one oracle" :)

January 30, 2004 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".

      Rating: 3
self outer
      joins  January 30, 2004
Reviewer:  David
       from USA
Tom, hope I'm not sneaking one in, but I think this is the sort of thing you
like.

This is in 8.0.6.  There seems to be a problem with self outer joins.  It's not
behaving the way we would expect, and I'm wondering if there's something we're
missing.  Can you comment?  (I wish I could make this example nicely formatted
the way yours are.  And if you could help me understand how you do that, it
would be great.)

SQL> create table drop_single_table (as_of_date date, id_number number,
some_value number);

Table created.

SQL> insert into drop_single_table (as_of_date, id_number, some_value) values
('31-dec-2003', 1, 50)
;

1 row created.

SQL> insert into drop_single_table (as_of_date, id_number, some_value) values
('31-dec-2003', 2, 150
);

1 row created.

SQL>  insert into drop_single_table (as_of_date, id_number, some_value) values
('30-nov-2003', 1, 50
);

1 row created.

SQL> commit;

Commit complete.

SQL> select * from drop_single_table;

AS_OF_DAT  ID_NUMBER SOME_VALUE
--------- ---------- ----------
31-DEC-03          1             50
31-DEC-03          2            150
30-NOV-03          1             50

So we have a single table containing id numbers and value with timestamped
records loaded each month.  We want to calculate an 'average' balance based on
the previous month's ending balance and this month's ending balance, and we want
to be able to handle records that are new this month.

So we try an outer self join.

SQL> select nvl(last.as_of_date, '30-nov-2003') last_date, this.as_of_date
this_date,
  2      nvl(this.id_number, last.id_number) id_number,
  3      nvl(last.some_value,0) last_value, this.some_value this_value
  4  from drop_single_table last,
  5      drop_single_table this
  6  where last.id_number(+) = this.id_number and
  7      this.as_of_date = '31-dec-2003' and
  8      (last.as_of_date = '30-nov-2003' or last.as_of_date is null);

LAST_DATE THIS_DATE  ID_NUMBER LAST_VALUE THIS_VALUE
--------- --------- ---------- ---------- ----------
30-NOV-03 31-DEC-03          1             50             50

We miss the record that was new this month.

But if there's a seperate table for each month, the outer join works as
expected.

SQL> delete from drop_single_table where as_of_date = '30-nov-2003';

1 row deleted.

SQL> commit;

Commit complete.

SQL> create table drop_two_tables as select * from drop_single_table where 1=0;

Table created.

SQL> insert into drop_two_tables (as_of_date, id_number, some_value) values
('30-nov-2003', 1, 50);

1 row created.

SQL> commit;

Commit complete.

SQL> select * from drop_single_table;

AS_OF_DAT  ID_NUMBER SOME_VALUE
--------- ---------- ----------
31-DEC-03          1        50
31-DEC-03          2        150

SQL> select * from drop_two_tables;

AS_OF_DAT  ID_NUMBER SOME_VALUE
--------- ---------- ----------
30-NOV-03          1       50

  1    select nvl(last.as_of_date, '30-nov-2003') last_date,
  2     this.as_of_date this_date, nvl(last.id_number, this.id_number)
id_number,
  3     last.some_value last_value, this.some_value this_value
  4    from drop_two_tables last,
  5      drop_single_table this
  6    where last.id_number(+) = this.id_number and
  7    this.as_of_date = '31-dec-2003' and
  8*  (last.as_of_date = '30-nov-2003' or last.as_of_date is null)
SQL> /

LAST_DATE THIS_DATE  ID_NUMBER LAST_VALUE THIS_VALUE
--------- --------- ---------- ---------- ----------
30-NOV-03 31-DEC-03          1         50         50
30-NOV-03 31-DEC-03          2                   150

I was not aware that self outer joins return results this way.

          Followup:

it did the right thing (to see why, compare

  select last.*, this.*
  from drop_single_table last,
    drop_single_table this
  where last.id_number(+) = this.id_number and
  this.as_of_date = '31-dec-2003'
  --and(last.as_of_date = '30-nov-2003' or last.as_of_date is null)
/

to

  select last.*, this.*
  from drop_two last,
    drop_single_table this
  where last.id_number(+) = this.id_number and
  this.as_of_date = '31-dec-2003'
  -- and (last.as_of_date = '30-nov-2003' or last.as_of_date is null)
/

and it should be pretty clear) -- but this is the query you probably really want
to use:

ops$tkyte@ORA806> select nvl(last.as_of_date, '30-nov-2003') last_date,
this.as_of_date this_date,
  2      nvl(this.id_number, last.id_number) id_number,
  3      nvl(last.some_value,0) last_value, this.some_value this_value
  4  from drop_single_table last,
  5      drop_single_table this
  6  where last.id_number(+) = this.id_number
  7    and last.as_of_date(+) = add_months(this.as_of_date,-1)
  8    and this.as_of_date = to_date( '31-dec-2003' )
  9  /

LAST_DATE THIS_DATE  ID_NUMBER LAST_VALUE THIS_VALUE
--------- --------- ---------- ---------- ----------
30-NOV-03 31-DEC-03          1         50         50
30-NOV-03 31-DEC-03          2          0        150

although this emulated your two table test:

ops$tkyte@ORA806>
ops$tkyte@ORA806>   select nvl(last.as_of_date, '30-nov-2003') last_date,
  2     this.as_of_date this_date, nvl(last.id_number, this.id_number)
id_number,
  3     last.some_value last_value, this.some_value this_value
  4    from (select * from drop_single_table where  as_of_date = '30-nov-2003'
or as_of_date is null)last
  5      ,drop_single_table this
  6    where last.id_number(+) = this.id_number and
  7    this.as_of_date = '31-dec-2003'
  8  /

LAST_DATE THIS_DATE  ID_NUMBER LAST_VALUE THIS_VALUE
--------- --------- ---------- ---------- ----------
30-NOV-03 31-DEC-03          1         50         50
30-NOV-03 31-DEC-03          2                   150

      Rating: 3
with what
      records?  January 30, 2004
Reviewer:  David
       from USA
Not sure what data to use with the two queries you asked me to compare (in my
two table example, I deleted the November record from the single table), but
your SQL accomplished what I could not - a self outer join.  What escapes me is
line 7, where you have

last.as_of_date(+) = add_months(this.as_of_date,-1)

Can you explain to me what that does?

          Followup:

The data you used?  I used your data.

last.as_of_date(+) = add_months(this.as_of_date,-1)

says "i'd like the last.as_of_date from a month ago for the current
this.as_of_date"

when looking at id=1, as_of_date=dec-31, you want to find id=1, as_of_date=one
month before dec-31 -- that is what that join does.

January 31, 2004 Arun -- Thanks for the question regarding
      "Regarding On Update Cascade", version 8i

      You Asked
Dear Tom,
We know that when we delete a parent record, automatically
child record also will be deleted if we used "on delete cascade".
Is it possible to update automatically a child record when we
update parent record?
(Do we have "On Update Cascade" option? Or any other like..)

      and we said...
There is not "on update cascade" automagically.

There are ways to do it,

o deferrable constraints.  defer the foreign key check until commit, update the
parent, update the child and then commit.

o use the technique here:

http://asktom.oracle.com/~tkyte/update_cascade/index.html

Personally -- I've never found a need or use for update cascade.  I'm opposed to
it.  If your design requires it -- change your design now if you can.

Primary keys are supposed to be imutable, never changing, constant.  It is an
excessively bad practice to have to update them ever.  If there is a 0.00001%
chance you will have to update a primary key -- then it is not a primary key,
its a surrogate key and you need to find the true primary key (even if you have
to make it up via a sequence)
Review &
      Followup

      Rating: 3
Surrogate
      keys  October 07, 2002
Reviewer:  Tony
       from UK
Tom,

I agree with your advice, but query your use of terms.  A primary key that is
made up via a sequence surely IS a surrogate key.  The natural primary key may
well be the one that has a 0.00001% chance of change - e.g. due to a typing
error.  The only primary keys that could never require an update are surrogate
keys, where the key value has no meaning (like a sequence number).

          Followup:

A primary key is something that doesn't change.  If your primary key changes, it
is not a primary key -- a candidate key if you will but not a primary key.

Your right, I should have called it a "candidate key".

      Rating: 4
Thanks
      Mr.Tom  November 10, 2002
Reviewer:  Arun Kumar
      K T  from Chitradurga, Karnataka, India
Thank you very much Mr.Tom for your timely response.

      Rating: 5
update primary
      key  January 31, 2004
Reviewer:  Sean
       from NJ, USA
Hi Tom,

I have a trigger to update child record after updating primary key of parent
record.
It should not work, but it works(Oracle 902).  I don't know why.

Here is the sample:
create table t1(c1 number primary key);

create table t2(c2 number references t1(c1));

insert into t1
values(1);

insert into t2
values(1);

update t1
set c1 = 2
where c1 = 1;

update t1
*
ERROR at line 1:
ORA-02292: integrity constraint (SCOTT.SYS_C0026830) violated - child record
found

create or replace trigger cascade_update
after update of c1 on t1
for each row
begin
   update t2
   set c2 = :new.c1
   where c2 = :old.c1;
end;
/

Trigger created.

SQL> update t1
  2  set c1 = 2
  3  where c1 = 1;

1 row updated.

SQL> select * from t2;

        C2
----------
         2

Thanks so much for your help.

Sean

          Followup:

they relaxed the mutating table restrictions in 8i.

Before you go off "gung ho" on this approach -- REALLY -- consider this:

ops$tkyte@ORA9IR2> create table t1(c1 number primary key);
Table created.

ops$tkyte@ORA9IR2> create table t2(c1 number primary key, c2 number references
t1(c1));
Table created.

ops$tkyte@ORA9IR2> insert into t1 values(1);
1 row created.

ops$tkyte@ORA9IR2> insert into t1 values(2);
1 row created.

ops$tkyte@ORA9IR2> insert into t2 values(100, 1);
1 row created.

ops$tkyte@ORA9IR2> insert into t2 values(101, 2);
1 row created.

ops$tkyte@ORA9IR2> create or replace trigger cascade_update
  2  after update of c1 on t1
  3  for each row
  4  begin
  5          dbms_output.put_line( 'setting ' || :old.c1 || ' to ' || :new.c1 );
  6     update t2
  7        set c2 = :new.c1
  8      where c2 = :old.c1;
  9  end;
 10  /
Trigger created.

ops$tkyte@ORA9IR2> update t1
  2    set c1 = c1+1;
setting 1 to 2
setting 2 to 3

2 rows updated.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select * from t1;

        C1
----------
         2
         3

ops$tkyte@ORA9IR2> select * from t2;

        C1         C2
---------- ----------
       100          3
       101          3

Hmmm, they are both 3. Well, we did that to ourselves.  The database only did
what it was asked to do in this case...

Even worse perhaps:

ops$tkyte@ORA9IR2> delete from t2;
2 rows deleted.

ops$tkyte@ORA9IR2> delete from t1;
2 rows deleted.

ops$tkyte@ORA9IR2> commit;
Commit complete.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t1 values(2);

1 row created.

ops$tkyte@ORA9IR2> insert into t1 values(1);

1 row created.

ops$tkyte@ORA9IR2> insert into t2 values(100, 1);

1 row created.

ops$tkyte@ORA9IR2> insert into t2 values(101, 2);

1 row created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> update t1
  2    set c1 = c1+1;
setting 2 to 3
setting 1 to 2

2 rows updated.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select * from t1;

        C1
----------
         3
         2

ops$tkyte@ORA9IR2> select * from t2;

        C1         C2
---------- ----------
       100          2
       101          3

same exact data -- byte for byte -- same exact data, but.....

different answer.

Caveat Emptor.  Do NOT do this in a trigger.  In fact -- beware of triggers that
might depend on the order of rows being updated.

Use deferrable constraints.

Perform the updates in a stored procedure.

Always do it in a controlled, safe, predicable manner.

Better yet -- don't do it.  primary keys are supposed to be immutable.

February 01, 2004 Munzer -- Thanks for the question regarding
      "email in PL/SQL", version 8i

      You Asked
Tom:

1.  If you want to write something in PL/SQL application server that send email
automatically to an administrator requesting approval after a user create a
record, which oracle package would be using.

Would you also use an after insert trigger for it or just include it in the
insert routine of the triggering record?

Thank you,

      and we said...
search this site for

UTL_SMTP

it does that.

I myself (as documented in my book if you have it) would use DBMS_JOB to
schedule a call to a stored procedure that will send the mail right after I
commit.  The reason for doing this is twofold:

o sending mail is slow, it takes a bit of time.
o sending mail is not transactional.  if you send the email in the trigger and
then ROLLBACK the transaction -- the email won't get "unsent"

Using DBMS_JOB solves both of those issues.  scheduling the job in the trigger
is very fast (the end user is not affected) and if you rollback, the scheduled
job is "unscheduled" as well.
Review &
      Followup

      Rating: 4
More
      please  January 07, 2002
Reviewer:
      A reader
Hi, Tom,

Would you give us an example on how to schedule dbms_job
in a trigger?

Thanks

          Followup:

create trigger trig
after insert on tab for each row
declare
    l_job number;
begin
   dbms_job.submit( l_job, 'proc;' );
end;
/

In my book -- I have tons of material on this.  Additionally, the supplied
packages guide shows you how to use this documented package.

      Rating: 4
notification
      email  December 10, 2002
Reviewer:  John

Hi Tom,

I have a table as follow

Table: work_to_do
Columns: work_id, what, work_time.

I need to write a background procedure which sends a notification email whenever
the system time reach one hour before the "work time" for any rows in the table.
The table is not static, any user may add/modify/delete rows in the table.

Any solution?

Thanks in advance.

          Followup:

You will either schedule a job via dbms_job to run every N minutes looking for
notifications to send -- or you can schedule a job when they
insert/update/delete the record.

both need another column to be added -- either a "flag" saying the email was
sent or a "job" so we know what job is scheduled.

So, you could write a procedure:

procedure p
is
begin
   for x in ( select rowid rid, work_to_do.*
                from work_to_do
               where work_time <= sysdate+1/24
                 and flag = 'N'
                 for update )
   loop
       send mail...
       update work_to_do set flag = 'Y' where rowid = x.rid;
   end loop;
end;

and schedule that 5 or 10 minutes

dbms_job.submit( :n, 'p;', sysdate, 'sysdate+5/24/60' )

Or, you could use a trigger on the table to schedule a JOB to be run upon
insert, modify the job's when upon update of the time, remove the job upon
delete.  for this you would need to add a "job" column that the insert trigger
would fill in and the update/delete would use.

      Rating: 4
A dbms_job
      question  February 01, 2004
Reviewer:  Scott
       from Melbourne, Australia
Hi Tom

I'm having an interesting problem with sending email from Oracle.

I have implemented a package that sends emails along the lines of what you have
presented on this site. It uses dbms_job to send the messages in the background,
sourcing the content of the email from a table.

The interesting part is if I manually run the send procedure the email is sent
exactly as intended. If however the dbms_job queue executes the same procedure
the email shows up with content along the lines of:

sender: from@example.com
?????????????????????????????????????????????????????????????????????????????????
???????????????????????????????????????

Any ideas you have on why the procedure sends correctly from the sqlplus prompt,
but not from dbms_job would be most appreciated.

An extract of the code I'm using is:

CREATE TABLE email_message
(job_id NUMBER,
 recipient VARCHAR2(200),
 sender VARCHAR2(200),
 message CLOB);

CREATE OR REPLACE PACKAGE BODY pkg_email
   PROCEDURE prc_send
      (p_job_id IN email_message.job_id%TYPE)
   IS

      -- variable to hold the smtp server connection
      v_smtp_connection utl_smtp.connection;
      -- variable to hold the contents of the email message
      v_email_message email_message%ROWTYPE;
      -- variable to hold the smtp host name
      v_smtp_host VARCHAR2(100) DEFAULT 'SMTP_SERVER';
      -- variable to hold the smtp port
      v_smtp_port NUMBER DEFAULT 25;

   BEGIN

      SELECT *
      INTO v_email_message
      FROM email_message
      WHERE job_id = p_job_id;

      -- establish the connection to the smtp server
      v_smtp_connection := utl_smtp.open_connection(v_smtp_host, v_smtp_port);

      -- perform a handshake with the smtp server
      utl_smtp.helo(v_smtp_connection, v_smtp_host);

      -- set the 'from' address of the message
      utl_smtp.mail(v_smtp_connection, v_email_message.sender);

      -- add the recipient to the message
      utl_smtp.rcpt(v_smtp_connection,  v_email_message.recipient);

      -- open the message for writing
      utl_smtp.open_data(v_smtp_connection);

      -- populate the message with the build up message header and body
      utl_smtp.write_data(v_smtp_connection, v_email_message.message);

      -- close the message for writing
      utl_smtp.close_data(v_smtp_connection);

      -- end the connection to the smtp server
      utl_smtp.quit(v_smtp_connection);

   END prc_send;
END pkg_email;

Thanks
Scott

          Followup:

one would need to see the data being sent but it looks as if it is a character
set issue.

You might want to put a little debug in your code to see exactly what is
happening.

      Rating: 3
Follow
      up  February 01, 2004
Reviewer:  Scott
       from Melbourne, Australia
Ok, when sending manually this is what is sent:

<start of message>
MIME-Version: 1.0
Date: 01 Feb 2004 20:36:52
From: from@sample.com
To: to@sample.com
Subject: Test email subject
X-Priority: 3
Reply-To: reply-to@sample.com
Content-TYPE: multipart/alternative; boundary=D7D34C02C9684FC284D4E95CF9630699
--D7D34C02C9684FC284D4E95CF9630699
Content-Type: text/plain; charset=us-ASCII
message blah blah blah
--D7D34C02C9684FC284D4E95CF9630699
Content-Type: text/html;

<html><body><table><tr><td style="background-color: #ff0000;">hello</td><td
style="background-color: #0000ff;">hello</td></tr></table></body></html>

--D7D34C02C9684FC284D4E95CF9630699--

<end of message>

However when sending via dbms_job (executes the sample command) it sends:

<start of message>
??????›???????????????????????????????›???????????????›???????????????›??????????
???????????????›?????????????????????????????????????????????????????????›???????
????????????????????????????????????????????›????????????????????????????????›???
?????????????????????????????????????????????????????????????????
<end of message>

My database is using the AL32UTF8 character set.

Thanks again
Scott

          Followup:

Nope, I sort of want to see something like:

alter system set job_queue_processes=0;
insert into table ...;
commit;

select job, what from user_jobs;
exec dbms_job.run( NNNN );
(run that job)

versus

alter system set job_queue_processes=1;
insert into table....;
commit;
(and letting the job run itself).

You do know that all email is sent as 7bit ascii by default -- this is looking
seriously like "character set" related.  You'd have to use the "raw" interface
and make sure to include a character set in the email as per the smtp way of
doing things to send multi-byte or even 8 bit characters.

February 02, 2004 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

      Rating:
      5
insert  February 01, 2004
Reviewer:
      Reader
Tom, I have a requirement at work. I have a string which is a free form text in
a column that is varchar2(126). My requirement is to insert this column into
another table column varchar2(56). While doing so, If the text length is greater
than 56 chars I should split the string into multiple lines by providing the
line number 1, 2. If the 56th char is a space then no problem in splitting.
However, many times when this gets split at 56th char it is in the middle of the
word and if it is so, I am asked to bring the word into the second line.

For example,

xxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxxxxxxx xxxxxxxxxx (60 chars in the
free form text in varchar2(126) column).

after loading into varchar2(56) column,
1. xxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxxxxxxx (56 chars)<-- first line
2. xxxxxxxxxx <--- second line after the split by bringing the entire word into
the second line and so on.

I have to backtrack to find the space and split it. Could you please help me how
it can be done. Thanks.

          Followup:

and so -- before I give you that answer -- tell me what happens with strings
that exceed 112 characters in length (won't fit in two) or worse -- a string
like

..... hello world ...................... end of string
                ^ positition 56                      ^ position 112

Here, if we split at 56 -- it would work, but if we back up to the 'space', the
second part of the string breaks the mold again -- won't fit in 56 spaces.

      Rating:
      5
insert  February 01, 2004
Reviewer:
      Reader
..... hello world ...................... hello world .......... end of string
              ^ positition 56                    ^ position 112

Now assuming 56th char is at 'r' in the above example, then I should split the
string as shown below.

.........................................hello
                                             ^ upto 52nd char in one line
world ..................................hello
                                            ^ upto 108th char in the second line
world ...end of string
                     ^ and the rest in the third line

Thanks for your valuable time.

          Followup:

Ahh, so it is really "perhaps three lines"

Ok, ignoring bad cases like:

a bbbbb<repeated 120 times>bbbb a

(which would result in at least 4 lines).....

we can use the following:

select x,
       first_piece,
       case when len > 56
            then substr( rest, 1, offset )
            else rest
       end second_piece,
       case when len > 56
            then substr( rest, offset+1 )
            else null
       end third_piece
  from (
select x,
       first_piece,
       rest,
       decode( nvl(instr( rest, ' ', -(length(rest)-56), 1 ),0), 0, 56,
                   instr( rest, ' ', -(length(rest)-56), 1 ) ) offset ,
       length(rest) len
  from (
select x,
       case when len > 56
            then substr( x, 1, offset )
            else x
       end first_piece,
       case when len > 56
            then substr( x, offset+1 )
            else null
       end rest
  from (select x,
               decode( nvl(instr( x, ' ', -(length(x)-56), 1 ),0), 0, 56,
                           instr( x, ' ', -(length(x)-56), 1 ) ) offset ,
               length(x) len
          from t)
       )
       )

to break each row into "three pieces".  I ran this test:

create table t ( x varchar2(126) );

declare
    l_string varchar2(126);
begin
    l_string := 'hello ';
    loop
        insert into t values ( l_string );
    begin
        l_string := l_string || 'hello ';
    exception
        when others then exit;
    end;
    end loop;
    l_string := replace( l_string, ' ', '' );
    loop
        insert into t values ( l_string );
    begin
        l_string := l_string || 'hello';
    exception
        when others then exit;
    end;
    end loop;
end;
/

ops$tkyte@ORA9IR2> select length(x),
  2         first_piece,
  3         second_piece,
  4         third_piece
  5    from (
  6  select x,
  7         first_piece,
  8         case when len > 56
  9              then substr( rest, 1, offset )
 10              else rest
 11         end second_piece,
 12         case when len > 56
 13              then substr( rest, offset+1 )
 14              else null
 15         end third_piece
 16    from (
 17  select x,
 18         first_piece,
 19         rest,
 20         decode( nvl(instr( rest, ' ', -(length(rest)-56), 1 ),0), 0, 56,
 21                     instr( rest, ' ', -(length(rest)-56), 1 ) ) offset ,
 22         length(rest) len
 23    from (
 24  select x,
 25         case when len > 56
 26              then substr( x, 1, offset )
 27              else x
 28         end first_piece,
 29         case when len > 56
 30              then substr( x, offset+1 )
 31              else null
 32         end rest
 33    from (select x,
 34                 decode( nvl(instr( x, ' ', -(length(x)-56), 1 ),0), 0, 56,
 35                             instr( x, ' ', -(length(x)-56), 1 ) ) offset ,
 36                 length(x) len
 37            from t)
 38         )
 39         )
 40         )
 41    where first_piece || second_piece || third_piece != x
 42  /

no rows selected

To see that it does work -- breaks the string up and does it "right"....

You can insert this "wrapped" data using something like this:

ops$tkyte@ORA9IR2> insert into t2
  2  select decode( r, 1, first_piece, 2, second_piece, 3, third_piece )
  3    from (
  4  select x,
  5         first_piece,
  6         case when len > 56
  7              then substr( rest, 1, offset )
  8              else rest
  9         end second_piece,
 10         case when len > 56
 11              then substr( rest, offset+1 )
 12              else null
 13         end third_piece
 14    from (
 15  select x,
 16         first_piece,
 17         rest,
 18         decode( nvl(instr( rest, ' ', -(length(rest)-56), 1 ),0), 0, 56,
 19                     instr( rest, ' ', -(length(rest)-56), 1 ) ) offset ,
 20         length(rest) len
 21    from (
 22  select x,
 23         case when len > 56
 24              then substr( x, 1, offset )
 25              else x
 26         end first_piece,
 27         case when len > 56
 28              then substr( x, offset+1 )
 29              else null
 30         end rest
 31    from (select x,
 32                 decode( nvl(instr( x, ' ', -(length(x)-56), 1 ),0), 0, 56,
 33                             instr( x, ' ', -(length(x)-56), 1 ) ) offset ,
 34                 length(x) len
 35            from t)
 36         )
 37         )
 38         ) XX,
 39             (select rownum r from all_users where rownum <= 3) YY
 40   where decode( r, 1, first_piece, 2, second_piece, 3, third_piece ) is not
null
 41  /

49 rows created.

      Rating: 5
Query is
      DML?  February 02, 2004
Reviewer:  Tarun
      Babu  from India
Hi tom,
    In the previous follow up you said:
"A Query is technically DML" Can you please explain how it is?
I am thinking Query is Data retrieval. Query doesn't manipulate a table.

          Followup:

DML = data MANIPULATION language.

not data modification.  INSERT, UPDATE, DELETE, SELECT, MERGE -- they are all
DML

CREATE, ALTER, GRANT and the like are DDL.

      Rating: 5
SELECT is part
      of DML  February 02, 2004
Reviewer:  Martin
       from UK
This is an interesting discussion, and I think it's primarily based on peoples
perceptions that (as Tom correctly points out earlier), the M in DML stands for
Modification ( or some variant ) as opposed to the correct Manipulation.

I've also talked to people who believe (erroneously) that there are three levels
of "statement", DQL ( SELECT ), DML and DDL, whereas the DQL is invented
(presumably because people can't accept that SELECT manipulates data).

A good overview can be found

http://en.wikipedia.org/wiki/SQL
and specifically for

http://en.wikipedia.org/wiki/SQL#SQL_keywords
Regards

      Rating:
      5
insert  February 02, 2004
Reviewer:
      reader
THANKS MUCH TOM. very useful.

February 02, 2004 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"

      Rating: 4
License Issue -
      Please Clarify  February 02, 2004
Reviewer:
      Sunitha  from India
Hi Tom,
 You remarked:
"grab it, use it.  just don't deploy with it."

Does this mean that for an organisation providing solutions by way of building
applications for clients, the OTN license and software is sufficient to do the
development and it is only the client (who will be hosting the application), who
need to have the licensed software?(not the OTN one)?
Please correct me if I have understood wrong.

Thanks.

          Followup:

look above the "grab it" comments.  the license is available on otn as well.

      Rating: 2
New Developer
      License on OTN  March 05, 2004
Reviewer:  Dale
      Ogilvie  from New Zealand
The otn license seems to have changed a bit since your reference, it now says:

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 of a single prototype of your application;
- use the application you develop with the programs for any internal data
processing or commercial or production purposes without securing an appropriate
license from us;
- continue to develop your application after you have used it for any internal
data processing, commercial or production purpose without securing an
appropriate license from us, or an Oracle reseller;

In our development setup we have a database on a server that is shared by all
the developers in the dev team, for deploying stored procs & new tables for our
web application. Once testing is complete we then roll out our app and
associated oracle objects to production, for which we have paid up oracle
licenses. Does the dev license cover this shared server in development, or even
the scratch databases on the individual developer boxes? Obviously no commercial
work other than development is done on this dev server.

I find it hard to believe that we would have to purchase production class cpu
licenses for every development/test server, but the plain reading of the new
license would seem to suggest this, and possibly even rule out the individual
scratch databases you have mentioned on this site. I certainly would have
difficulty matching up my own scratch database with the requirement for "a
single prototype". Now, I don't know exactly how our oracle setup is licensed by
management, but I'm trying to justify going to a second development db server so
we can mirror production (rac) more closely, but if we have to fork out extra
licence fees for a two-node dev setup I'm going to have a hard road.

      Rating: 2
Oracle
      development licensing  March 10, 2004
Reviewer:
      Dale Ogilvie  from New Zealand
Here's what I think the situation is regarding the current OTN license and
development. This is just my take on the situation, talk to Oracle for the real
story as it applies to YOU.

You can only develop "a single prototype of your application" under the otn
license. I think this relegates the otn license to evaluation only. For general
development work you need to go to Oracle Personal License which gives you and
only you a carte blanche on a single machine. This is the dev sandbox license I
think.

A development database server used by a team of individuals is not covered under
the otn or the oracle personal licenses and therefore would have to be covered
under an Oracle processor or named user license.

February 02, 2004 Brij -- Thanks for the question regarding "Time
      Series Data and Buffer Gets", version 817/9

      You Asked
Hi Tom,

Wow...what a site. My conceptual/practical knowledge grows every time I visit
it!!

Now to my questions...:-)

i) From a theoretical perspective, if I understood how this works correctly, the
amount of effort and resource required by oracle to maintain read-consistency is
considerably reduced by reducing the number of commits; When a commit is
performed oracle does not write to datafile, it initially performs a 'fast
commit' by modifying he rollback header and marking the transaction as
'committed', however, the block on file remains uncommitted.

Over-committing leads to transaction headers in rollback segments which are
needed to maintain read consistency being wiped out, and hence oracle must take
further action to maintain read consistent data. In this scenario is it common
for oracle to maintain multiple versions of data in memory i.e. for each DML/DQL
operation?

How can I see what data buffer data is being held, and if multiple versions are
being held?

Would using a larder rollback segment help matters? Any other action I can
take???

ii) We store a number of 'ladder type' tables within our database which
effectively store the same data primary key, except a ladder date which is
update each day.

For example...

CREATE TABLE MARK_PRICE_LADDER (
  LADDER_DATE    DATE          NOT NULL,
  RFK_ISEC_ID    NUMBER (9)    NOT NULL,
  VALUE_SPEC_ID  NUMBER (9)    NOT NULL,
  BID_VFID       NUMBER (9),
  BID_PRICE      NUMBER (31,16),
  BID_UPD_DT     DATE,
  ASK_VFID       NUMBER (9),
  ASK_PRICE      NUMBER (31,16),
  ASK_UPD_DT     DATE,
  PRICE_CCY      VARCHAR2 (3)  NOT NULL,
  AUDIT_NOTES    VARCHAR2 (255)  NOT NULL,
  NOTES          VARCHAR2 (4000),
  UNIQUE (LADDER_DATE, RFK_ISEC_ID, VALUE_SPEC_ID, BID_UPD_DT),
  UNIQUE (VALUE_SPEC_ID, RFK_ISEC_ID, LADDER_DATE),
  PRIMARY KEY ( LADDER_DATE, RFK_ISEC_ID, VALUE_SPEC_ID ) ) ;

For each unique rfk_isec_id, value_spec_id combination, there are multiple
'ladder date' rows i.e the primary key is the same for each row.

Now, this leads us to all sorts of problems in terms of performance with high
volumes of data. I am aware of the Oracle time series cartridge and was
interested in its capabillities and/or whether it can be used in this context?

Are there any new features in 9i, or proposed features in 10(i?) which would be
of use also?

Thanks in advance,

Brij

      and we said...
i) over committing is bad -- but not for the reason you mention.

o over committing is a resource hog (the session doing the commits pays a
performance penalty)

o over committing generates more redo log

o over committing DESTROYS TRANSACTIONAL INTEGRITY.  a transaction, a logical
unit of work, is what dictates when you commit.  Nothing more -- nothing less.
A transaction defines its scope.  Performance, fear of "too big", none of those
factor into the decision to commit (or not).  The transaction is the only thing
that factors.

The condition you describe in i) is a precursor to an ora-1555 due to the
segment headers getting overwritten and some long running query not being able
to figure out what version of the block it can use anymore.

Oracle maintains multiple consistent read versions of a block in cache
regardless of how frequently or not you commit.  It is natural, normal and the
way it works.

V$BH is the view you want.

ii)

  UNIQUE (LADDER_DATE, RFK_ISEC_ID, VALUE_SPEC_ID, BID_UPD_DT),
  UNIQUE (VALUE_SPEC_ID, RFK_ISEC_ID, LADDER_DATE),
  PRIMARY KEY ( LADDER_DATE, RFK_ISEC_ID, VALUE_SPEC_ID ) ) ;

is bizzare.   Why the

  UNIQUE (VALUE_SPEC_ID, RFK_ISEC_ID, LADDER_DATE),

constraint when the primary key is already there on the same columns?  And the
other unique

  UNIQUE (LADDER_DATE, RFK_ISEC_ID, VALUE_SPEC_ID, BID_UPD_DT),

is likewise "already happening" since 3 of the columns of that one are assured
to be unique as per the primary key!

Anyway....  You would have to specify what sort of queries you execute against
this data that lead to these performance problems.  What questions do you ask of
this table.  For example, if you always want the current record give a
rfk_isec_id, value_spec_id, then I would want the primary key index to be on

rfk_isec_id, value_spec_id, ladder_date

and a query like:

select *
  from ( select *
           from mark_price_ladder
          where rfk_isec_id = :x
            and value_spec_id = :y
          order by ladder_date DESC )
 where rownum = 1;

would get that rather fast (reads the index "backwards", gets just the first
record -- would be about the same as a simple keyed read)

For example:

SQL> REM
SQL> REM drop TABLE MARK_PRICE_LADDER;
SQL> REM
SQL> REM CREATE TABLE MARK_PRICE_LADDER (
SQL> REM   LADDER_DATE    DATE          NOT NULL,
SQL> REM   RFK_ISEC_ID    NUMBER (9)    NOT NULL,
SQL> REM   VALUE_SPEC_ID  NUMBER (9)    NOT NULL,
SQL> REM   BID_VFID       NUMBER (9),
SQL> REM   BID_PRICE      NUMBER (31,16),
SQL> REM   BID_UPD_DT     DATE,
SQL> REM   ASK_VFID       NUMBER (9),
SQL> REM   ASK_PRICE      NUMBER (31,16),
SQL> REM   ASK_UPD_DT     DATE,
SQL> REM   PRICE_CCY      VARCHAR2 (3)  NOT NULL,
SQL> REM   AUDIT_NOTES    VARCHAR2 (255)  NOT NULL,
SQL> REM   NOTES          VARCHAR2 (4000)
SQL> REM )
SQL> REM /
SQL> REM create unique index mark_price_ladder_pk on
mark_price_ladder(RFK_ISEC_ID, VALUE_SPEC_ID, ladder_date ) compress 2;
SQL> REM
SQL> REM alter table mark_price_ladder
SQL> REM add constraint mark_price_ladder_pk
SQL> REM   PRIMARY KEY ( RFK_ISEC_ID, VALUE_SPEC_ID, ladder_date );
SQL> REM
SQL> REM
SQL> REM begin
SQL> REM        for i in 1 .. 1000
SQL> REM        loop
SQL> REM             insert /*+ append */ into mark_price_ladder
SQL> REM             select sysdate+rownum, 12345+i, 6789+i, 0, 324.1234,
sysdate,
SQL> REM                    23542, 43234.23432, sysdate, 'abc',
rpad('*',80,'*'),
SQL> REM                    rpad('*',80,'*')
SQL> REM               from all_objects
SQL> REM          where rownum <= 100;
SQL> REM              commit;
SQL> REM        end loop;
SQL> REM end;
SQL> REM /
SQL> REM
SQL> REM analyze table mark_price_ladder
SQL> REM compute statistics
SQL> REM for table
SQL> REM for all indexes
SQL> REM for all indexed columns
SQL> REM /
SQL>
SQL> variable x number
SQL> variable y number
SQL> variable z varchar2(25)
SQL>
SQL> alter session set nls_date_format = 'dd-mon-yyyy hh24:mi:ss';

Session altered.

SQL> column ladder_date new_val L
SQL> column audit_notes noprint
SQL> column notes       noprint
SQL>
SQL>
SQL> exec :x := 12345+1; :y := 6789+1;

PL/SQL procedure successfully completed.

SQL>
SQL> select *
  2    from ( select *
  3             from mark_price_ladder
  4                    where rfk_isec_id = :x
  5                      and value_spec_id = :y
  6                    order by ladder_date desc )
  7   where rownum = 1
  8  /

LADDER_DATE          RFK_ISEC_ID VALUE_SPEC_ID   BID_VFID  BID_PRICE BID_UPD_DT
           ASK_VFID  ASK_PRICE
-------------------- ----------- ------------- ---------- ----------
-------------------- ---------- ----------
ASK_UPD_DT           PRI
-------------------- ---
14-jan-2003 20:38:45       12346          6790          0   324.1234 06-oct-2002
20:38:45      23542 43234.2343
06-oct-2002 20:38:45 abc

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=2239)
   1    0   COUNT (STOPKEY)
   2    1     VIEW (Cost=3 Card=1 Bytes=2239)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'MARK_PRICE_LADDER' (Cost=3
Card=1 Bytes=223)
   4    3         INDEX (RANGE SCAN DESCENDING) OF 'MARK_PRICE_LADDER_PK'
(UNIQUE) (Cost=2 Card=1)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          3  consistent gets
          0  physical reads
          0  redo size
       1230  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

SQL>
SQL> exec :z := '&L';

PL/SQL procedure successfully completed.

SQL>
SQL> select *
  2    from mark_price_ladder
  3   where rfk_isec_id = :x
  4     and value_spec_id = :y
  5     and ladder_date = to_date( :z, 'dd-mon-yyyy hh24:mi:ss' )
  6  /

LADDER_DATE          RFK_ISEC_ID VALUE_SPEC_ID   BID_VFID  BID_PRICE BID_UPD_DT
           ASK_VFID  ASK_PRICE
-------------------- ----------- ------------- ---------- ----------
-------------------- ---------- ----------
ASK_UPD_DT           PRI
-------------------- ---
14-jan-2003 20:38:45       12346          6790          0   324.1234 06-oct-2002
20:38:45      23542 43234.2343
06-oct-2002 20:38:45 abc

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=223)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'MARK_PRICE_LADDER' (Cost=2 Card=1
Bytes=223)
   2    1     INDEX (UNIQUE SCAN) OF 'MARK_PRICE_LADDER_PK' (UNIQUE) (Cost=1
Card=100000)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          3  consistent gets
          0  physical reads
          0  redo size
       1230  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

SQL>
SQL>

See, the difference between "getting the most recent record" and getting a
record by primary key values is exactly the same there.

So, it really depends on the questions you are asking -- there are things like
the top-n query above, analytic functions (great, absolutely AWESOME, for
looking at windows of data which is what you have) and so on

(i did my example above in 9iR2 on linux but similar results should be found
when using the CBO on earlier releases)

Review &
      Followup

      Rating:
      4
Thanks!  October 07, 2002
Reviewer:
      Brij Malkan  from UK
Thanks Tom,

This has helped clarify things a lot.

The most common type of dql performed on the ladder tables, is to update
positions with new prices. And to get data for a number of positions over given
periods of time. Would the analytic functions you refer to, help here?

Stupid question - where can I find documentation on these?

Thanks,

Brij

          Followup:

Well, the update will be impacted a little, not much tho.

ops$tkyte@ORA920.US.ORACLE.COM> update mark_price_ladder
  2     set bid_price = 55.55
  3   where (rfk_isec_id,value_spec_id,ladder_date) =
  4         ( select :x, :y, max(ladder_date)
  5             from mark_price_ladder
  6            where rfk_isec_id = :x
  7              and value_spec_id = :y )
  8  /

1 row updated.

Execution Plan
----------------------------------------------------------
   0      UPDATE STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=27)
   1    0   UPDATE OF 'MARK_PRICE_LADDER'
   2    1     INDEX (UNIQUE SCAN) OF 'MARK_PRICE_LADDER_PK' (UNIQUE) (Cost=1
Card=1 Bytes=27)
   3    2       SORT (AGGREGATE)
   4    3         FIRST ROW (Cost=2 Card=1 Bytes=14)
   5    4           INDEX (RANGE SCAN (MIN/MAX)) OF 'MARK_PRICE_LADDER_PK'
(UNIQUE) (Cost=2 Card=100000)

Statistics
----------------------------------------------------------
          0  recursive calls
          1  db block gets
          4  consistent gets
          0  physical reads

would update the current record

The other one "getting the data for a number of posititions over given periods
of time"  -- thats a simple index range scan (and would also benefit from having
ladder_date at the END of the key)

Analytics - -well, in my book I have a pretty good chapter on them and the data
warehousing guide does as well:

http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76994/analysis.htm#11237

      Rating: 2
Read
      consistency  October 08, 2002
Reviewer:
      A reader
Thanks Tom.

Also, is it possible to disable 'read-consistency' on particular objects?

          Followup:

Not a chance.

      Rating: 4
Fast
      Commits  February 02, 2004
Reviewer:  Mike

I'm currently reading Jonathon Lewis' excellent practical 8i book (it may be a
bit out of date in terms of versions but it still seems highly relevent).

As always with Oracle books (including yours) I get a headache after about page
7 or 8, but I'm determined to understand this all if it kills me.

He (Lewis) talks about Oracle performing a "fast commit" and how this attempts
to remove the need for the old delayed block cleanout.  He brushes past it
failry quick (but then it's only on page 12!) and I'm not entirely sure I
understand.  Is the following correct??

By the commit process stamping "some" of the blocks with the SCN of the commited
change a select hitting that block knows instantly if it needs to rollback that
block rather than having to lookup the transaction table in the RBS and possibly
having to roll that back to get at the right RBS block which in turn may need
rolling back to provide the same answer.

Oracle only writes back "some" of the changed blocks with the SCN because with
the law of averages "some" of the blocks "may" well turn out to be "most" of the
effected blocks.

I guess the bit i'm most unsure of is how many items the RDBMS may have to
rollback just to get the SCN that modified the actual datablock it read at the
start and is now cleaning out.

Also in you Expert one on one book I remeber you refering to a formula Oracle
has about SCN's so that it need not look at the transaction table always to see
if it can use the block "as is".  Is this the fast commit Lweis refers too?

Thanks if you can a) understand me, b) put me right.  If the answer is in either
of your books a page number would be cool and I'll re-read that!!

Mike.

          Followup:

You might want to glance at page 177 in Expert One On One Oracle -- we are
describing the same things.

When you commit -- we try to "clean out" the blocks in the buffer cache
(formula's and thresholds and such in Expert One on One Oracle) so as to not
make the next session that hits these blocks clean them out.  For smallish OLTP
transactions -- this is probably all of the blocks.  for larger long running
transactions -- it won't be (probably).

The RDBMS may have to roll a block back many dozens/hundreds/thousands of times
to get the read consistent image.  It all depends on how active that block is,
how long running your queries are.....

February 02, 2004 Arnaud -- Thanks for the question regarding "How
      to load a distant file", version 9.0.1

      You Asked
Hy tom,

First your web site is incredible, i have always find what i was searching for.

But this time i have a problem

I'm developing a new application for my web site. This is an application for Rss
Feed. So i need to do one thing, loading the distant RSS.

My question is how can i load a distant file in my web site analyse the XML code
and build the result.

Everything must be done in PL/SQL (if possible)

Thanks a lot for your answer.

Arnaud

      and we said...
See

http://otn.oracle.com/tech/xml/index.html

you can use utl_http to retrieve rss info, XML stuff -- database is full of it.

Review &
      Followup

      Rating:
      5
  February 02, 2004
Reviewer:  Arnaud
      Bontemps  from France
Can you give me an example of parsing xml source stored in a clob?

          Followup:

take a look at:

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96620/toc.htm

February 02, 2004 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.

      Rating: 3
Why I am
      getting ONE blank line at the beginning  January 30, 2004

Reviewer:  Pawan  from USA
Tom,
I am just using SPOOL to get data to a flat file but am getting a blank line at
the beinning. My script to SPOOL is

set pages 500 lines 250 heading off feedback off
spool load_recs_in_monthly_invoice.dat
select
col1||'|'||to_date(trunc(ADJUSTMENT_DATE,'MM'))||'|'||REASON_CODE||'|'||LEGACY_ID
||'|'||PRODUCT_ID|
|'|'||LEGACY_ID||'|'||BUSINESS_UNIT_ID||'|'||
sum(PIECES_QTY)||'|'||sum(DOZENS_QTY)||'|'||sum(_CASES_QTY)||'|'||sum(FRD_AMT)||'
|'|| sum(NET_AMT)||'|'
from cso.daily_invoice WHERE ....

and the data file looks like
---------

1015245|1999-04-01|01|000010319810087338|00873300||BMP|0|0|0|0|0|

1016558|2001-05-01|15|000000319810024015|00240100||BMP|-28.009|-.131|-1.569|-158.
88|-158.88|

1019461|1998-04-01|51|000010319810080674|00806700||BMP|-1572.024|-2.752|-33.013|-
4683.3|-4683.3|
---------------------------
Why is the first line blank? What SET command should I use. Thanks

          Followup:

set embedded on

      Rating: 1
Still getting
      the first line as blanks  February 02, 2004

Reviewer:  pawan  from US
Tom,
After setting the SET EMBEDDED ON I am still getting the first line as blanks.
How do I get rid of the blank line.As you can see that the
load_recs_in_monthly_invoice.dat has 4 lines with only 3 lines of data.
Thanks
My script to SPOOL is

set pages 500 lines 250 heading off feedback off embedded on
spool load_recs_in_monthly_invoice.dat
select
col1||'|'||to_date(trunc(ADJUSTMENT_DATE,'MM'))||'|'||REASON_CODE||'|'||LEGACY_ID

||'|'||PRODUCT_ID|
|'|'||LEGACY_ID||'|'||BUSINESS_UNIT_ID||'|'||
sum(PIECES_QTY)||'|'||sum(DOZENS_QTY)||'|'||sum(_CASES_QTY)||'|'||sum(FRD_AMT)||'

|'|| sum(NET_AMT)||'|'
from cso.daily_invoice WHERE ....

and the data file looks like
---------
1
2 1015245|1999-04-01|01|000010319810087338|00873300||BMP|0|0|0|0|0|

3
1016558|2001-05-01|15|000000319810024015|00240100||BMP|-28.009|-.131|-1.569|-158.

88|-158.88|

4
1019461|1998-04-01|51|000010319810080674|00806700||BMP|-1572.024|-2.752|-33.013|-

4683.3|-4683.3|
---------------------------

          Followup:

I do not -- check again.

I mv'ed my login.sql (so I have defaults) and ran this:

set pages 500 lines 250 heading off feedback off
set trimspool on
set embedded on
spool x
select ename || '|' || empno from emp;
spool off

as a script.  results are:

SQL> @test
SMITH|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
SQL> !cat x.lst
SMITH|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

No blanks -- been doing this since version 7.0 at least -- you must have missed
something.

with word wrapping -- i see "nothing" here really.  perhaps your linesize isn't
big enough, try the small example and scale it up to see where it "stops" --
look at trimspool, linesize and others.

the first line is not blank in your example either.  I see a #1 first?

      Rating: 3
The
      number  February 02, 2004
Reviewer:  pawan
       from US
# 1 in the first line is the result of :se nu which I did to see if the first
line is blank. I will try your suggestion.

February 03, 2004 -- Thanks for the question regarding "Temporary
      Table Questions", version 8.1.7

      You Asked
Tom,
  I have a few questions abt temporary table:

  1.  Which type of temporary table is better to use in a multi-user
applications, a global temporary table with on commit preserve rows or
a global temporary table without on commit preserve rows ? Why ?  What is the
difference ?

  2.  Is it logical to create an index for a temporary table ?

  3.  Is it a good practice to truncate tables(permanent/temporary) in a
procedure ? Why?

  4.  How to do a trace on a procedure to check which statement have a bad
performance ?

  Thanks.

Rgds,
Jesse

      and we said...
1) they are equally "better".

YOU need to decide based on YOUR needs whether:

a) rows should disappear when you commit
b) rows should stay in there until you log out

regardless of what you pick, they behave identically in a multi-user
environment.  ONLY your session will ever see YOUR data.

2) yes.

3) NO. truncate is DDL.  DDL is expensive.  Truncation is something that should
be done very infrequently.  Now, I don't mean "turn your truncates into
DELETE's" -- that would be even worse.  I mean -- avoid having to truncate or
delete every row in the first place.  Use a transaction based temporary table
and upon commit, it'll empty itself.

4) wouldn't that be neat.  Well, the tools you have available are

DBMS_PROFILER
sql_trace + tkprof

If you have my book "Expert one on one Oracle" -- read chapter 10 and the
section in the appendix on dbms_profiler.  If not, search this site for
dbms_profiler and read

http://asktom.oracle.com/~tkyte/tkprof.html
Review
      & Followup

      Rating: 3
Global
      temporary table vs Truncate ?  January 31, 2004

Reviewer:  Abu Ahmadh  from Singapore
Is "global temporary table" a better solution to avoid "truncate table"?

An application is inserting and truncating from a permanent table (nologging).
Since truncate is a DDL operation, can it be replaced with GTT?  How does oracle
delete/truncate rows from GTT when it is done.  Is it equal to a normal
TRUNCATE?

          Followup:

with gtt's -- each session gets their own "segment" (it is like having your own
private, newly created partition that only you can see)

when it comes time to delete the data from the gtt (either at commit or end of
session), we just cut lose that segment.

the gtt is a template (consumes no storage when you create it)

upon the first insert into it -- a segment pops into existence.  when you commit
or close the session (depending on how you created the gtt -- on commit
preserve/delete rows) the segment disappears.

      Rating: 3
Another
      case  February 02, 2004
Reviewer:  Piotr
      Jarmuz  from Poznan, Poland
Hi,

What if I use a gtt to store complex search results?
In my system there can be more than one search in one transaction so there would
be more than one search result sets in this user's gtt.

So in this case would you delete (truncate is out of question) the old result
set before generating a new one? Or would you let the commit clean the old
result set and instead add e.g. a search identifier column to distinguish stale
and current result sets (perhaps a need for an index on that column)? In my case
the result sets rarely exceed 100. And there are rarely than few searches in a
transaction (nevertheless they do and can happen).

          Followup:

I cannot answer as I do not know your requirements.

Certainly, if the results did not need to be saved (i did not need more than one
result at a time), I would either "delete" or use an on commit delete rows (but
given you say "in ONE transaction" the on commit would be out of the question)

      Rating: 3
Temporary Table
      or Type  February 02, 2004
Reviewer:  Ray
       from Winston-Salem, North Carolina, USA
Since 8i does not recognize "order SIBLINGS by" in a query with a connect by, I
created a type...object and type... table of and a function to recursively load
a menu/submenu/application tree.
The data is returned as a result set using select * from TABLE ( cast ( data as
defined_type ) ).

    procedure RS_OPTIONS
       (iMenuID         in  varchar2
       ,oResultSet      out cursor_type) is
        pCursor             cursor_type;
    begin
        LOAD_TREE(iMenuID);
        open pCursor for
            select *
            from TABLE ( cast ( gMenuData as menu_table_type ) );
        oResultSet  := pCursor;
    end RS_OPTIONS;

The data could have just as well been stored as a temporary table.
Would a temporary table be better than the "table of type" approach?

          Followup:

answer = benchmark and evaluate ;)

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

      Rating:
      4
  February 03, 2004
Reviewer:  Ray
       from Winston-Salem, NC, USA
It's easy to overlook the obvious answer.
Thanks!

      Rating: 4
delete or
      preserve  February 16, 2004
Reviewer:
      A reader
There must be a way to determine if a temporary table is of the type that will
preserve or delete rows from information in the data dictionary.

Where do I find it?

dba_tables only specifies TEMPORARY = Y/N...
dbms_metadata will return the DDL showing preserve or commit...

Thanks for your help.

      Rating:
      5
NEVERMIND  February 16, 2004
Reviewer:
      A reader
Answer was staring me in the face.
Sorry for bothering you.

February 03, 2004 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.

      Rating: 5
TNS Going
      bad...  February 02, 2004
Reviewer:  JFC
       from Dominican Republic
I have a pool of users that connects to my Oracle DB using "Windows" since last
friday they begin to disconect in particular order, Win9X, WinMe, XP, NT, 2000.
And the TNS-12203 is present, even some of then can't telnet to the server.
This is the output of my tnsping

C:\>tnsping prueba

TNS Ping Utility for 32-bit Windows: Version 9.0.1.3.1 - Production on 02-FEB-20
04 20:08:27

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

Archivos de parßmetros utilizados:
f:\ninphas\DB\Developer\network\admin\tnsnames.ora

Adaptador TNSNAMES utilizado para resolver el alias
Attempting to contact (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)
(HOST = 200.10.100.2)(PORT = 1521))) (CONNECT_DATA = (SID = bdprueba)))
TNS-12560: TNS:error del adaptador de protocolo

Is there anything else look for like the Winsock or any other service, port or
protocol.  Any aditional test to perform.

Thanks in Advance
JFC

          Followup:

[tkyte@localhost 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: 5
I found
      it....  February 16, 2004
Reviewer:  Luis
      Soto  from Dominican Republic
Thanks a lot.  I found that it was a fault in some buggy "Windows" software with
a malicius code.  I fix my registry and it opened the ports that where closed by
the attack.

February 03, 2004 Muthukumaran -- Thanks for the question regarding
      "Usage of Role Password", version 817

      You Asked
How can i utilize password option in "create role" statement(create role
<role_name> identified by <passwd>/globally/externally;)

NIRMAL@ORANS.QTEL.COM.QA> create role nirm identified by kumar;
Role created.

NIRMAL@ORANS.QTEL.COM.QA> grant all on demo to nirm;
Grant succeeded.

NIRMAL@ORANS.QTEL.COM.QA> grant nirm to scott;
Grant succeeded.

NIRMAL@ORANS.QTEL.COM.QA>@c scott/tiger
Connected.

SCOTT@ORANS.QTEL.COM.QA> select * from nirmal.demo;

       ID THECLOB
--------- -------------------------------------------------------------------
        1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
 <html>          <head>
        2 -------------------------------------------------------------------

SCOTT@ORANS.QTEL.COM.QA>

Here user scott using the role by selecting the demo table, However in which
place the roles's password is used?...

Can you please explain.

Rgds,
Nirmal.

      and we said...
you made it a default role of scotts.  make it a non-default role, he'll have to
provide the password then

ops$tkyte@ORA8I.WORLD> create table t  ( x int );
Table created.

ops$tkyte@ORA8I.WORLD> create role toms_role identified by some_pw;
Role created.

ops$tkyte@ORA8I.WORLD> grant select on t to toms_role;
Grant succeeded.

ops$tkyte@ORA8I.WORLD> grant toms_role to scott;
Grant succeeded.

ops$tkyte@ORA8I.WORLD> alter user scott default role all except toms_role;
User altered.

ops$tkyte@ORA8I.WORLD> connect scott/tiger

scott@ORA8I.WORLD> desc ops$tkyte.t
ERROR:
ORA-04043: object ops$tkyte.t does not exist

scott@ORA8I.WORLD> set role toms_role identified by some_pw;
Role set.

scott@ORA8I.WORLD> desc ops$tkyte.t
 Name                                Null?    Type
 ----------------------------------- -------- ------------------------
 X                                            NUMBER(38)

Review &
      Followup

      Rating: 5
default
      role  July 12, 2002
Reviewer:  O user
       from LA
hi tom
why everytime I grant a role to a user it becomes a default role for that users
?
how many default roles can a user have?

SQL> create user a identified by a;

User created.

SQL> create role ra identified by a;

Role created.

SQL> grant ra to a;

Grant succeeded.

SQL> ed
Wrote file afiedt.buf

  1* select * from dba_role_privs where grantee = 'A'
SQL> /

GRANTEE                        GRANTED_ROLE                   ADM DEF
------------------------------ ------------------------------ --- ---
A                              RA                             NO  YES

          Followup:

because the default behavior is to make it a default role.

you can use the ALTER USER command to change that, to make it non-default (be
default, a role is a default role).

You control the max number of default roles via the init.ora parameter
max_enabled_roles.  You cannot have more then that many default roles.

      Rating: 5
dba_role_privs
      default_role  October 16, 2002
Reviewer:  sm
       from usa
Tom,
I am moving a database from WINDOWS to UNIX...
I built a test database first...no problems..
repeating same steps for PROD database now...
I did:
users,
roles..
grants...
structure..
data..

Strangely enough I saw many users that were granted roles with DEFAULT_ROLE as
'NO'..
As a result...although a user was granted a role..and that role had object
privilege been granted..
but when I logged in as that user...it will say that I don't have some of the
object privileges (privileges that were granted to me through a role that had
default_role as 'YES'...that's when I started looking at DBA_ROLE_PRIVS..
Question:
1. Why didn't it do some roles as default_roles
2. is the error I was getting (that I don't have updates on a table) was because
of this....

Thanks..

          Followup:

I don't know, you didn't give me a reproducible example with which to see what
you actually executed.

      Rating: 5
alter user
      scott default role all   October 16, 2002

Reviewer:  sm  from usa
doing alter user scott default role all..
took care of the issue...
but I am still interested in your answer..
and if you can shed some light on DEFAULT_ROLE...

Thanks

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

Can we set and unset roles in the procedures instead of set command at SQL
prompt.

Something like below

CREATE OR REPLACE PROCEDURE
AS
<Now Set the ROLE toms_role>
. . . . . . . . . .
do some job here
. . . . . . . . . .
<Unset the ROLE toms_role>
END ;

Regards,
Sagi

          Followup:

You do understand that

a) roles not active in a definer rights (normal) procedure
b) procedures run with the privileges of the definer (normally)

so, the above doesn't really "make sense".  When using an invoker rights routine
(rare, used for utilities like "print_table", "dump_csv", "show_space" -- and
others I have here), it will run with the privs AND roles of the invoker (person
running it).  If you have my book "Expert one on one Oracle" -- read the chapter
on this topic (invoker vs definer rights).

Consider the differences below:

ops$tkyte@ORA920.US.ORACLE.COM> create or replace procedure dr_demo( p_role in
varchar2 default null )
  2  as
  3  begin
  4          if ( p_role is not null )
  5          then
  6                  dbms_session.set_role( p_role );
  7          end if;
  8
  9          dbms_output.put_line( '---------------' );
 10          for x in ( select * from session_roles )
 11          loop
 12                  dbms_output.put_line( x.role );
 13          end loop;
 14          dbms_output.put_line( '---------------' );
 15  end;
 16  /

Procedure created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace procedure ir_demo( p_role in
varchar2 default null )
  2  authid current_user
  3  as
  4  begin
  5          if ( p_role is not null )
  6          then
  7                  dbms_session.set_role( p_role );
  8          end if;
  9
 10          dbms_output.put_line( '---------------' );
 11          for x in ( select * from session_roles )
 12          loop
 13                  dbms_output.put_line( x.role );
 14          end loop;
 15          dbms_output.put_line( '---------------' );
 16  end;
 17  /

Procedure created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> exec ir_demo
---------------
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
---------------

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> exec dr_demo
---------------
---------------

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> set role none;

Role set.

ops$tkyte@ORA920.US.ORACLE.COM> exec ir_demo
---------------
---------------

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> exec ir_demo('DBA')
---------------
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
---------------

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> exec ir_demo('ALL')
---------------
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
---------------

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> exec dr_demo
---------------
---------------

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>

but before you run off and just do everything with invokers rights which would
be a hugely bad mistake, make sure you understand the ramifications.  If you
want it spelled out -- I'll have to refer you to the book at that point -- tons
of examples, lots of hours spent putting it together.

      Rating:
      4
  October 17, 2002
Reviewer:  Sagi
       from India
Thanx Tom.

Kind Regards,
Sagi

      Rating:
      4
  February 02, 2004
Reviewer:  Vipin

Hi Tom,

I performed the following test to study the way secured appliation roles works:-

Connected as Scott/tiger did the following:-

Created a role test_app identified using test_user.verify_role_pkg.
Granted select on a table t to role test_app.
Granted test_app to test_user.
Altered test_user to have all default roles other except test_app.

Now connected as test_user/test_user

Create a package verify_role_pkg with AUTHID CURRENT_USER option.
With in this package created a procedure set_my_role which sets the role using
DBMS_SESSION.SET_ROLE procedure to test_app.

Now when I logged into test_user and try executing the following code it
worked:-

SQL> EXEC verify_role_pkg.SET_MY_ROLE;
SQL> BEGIN
SQL> FOR CF IN (SELECT * FROM T) LOOP
SQL> DBMS_OUTPUT.PUT_LINE(CF.A);
SQL> END LOOP;
SQL> END;
SQL> /
14
54
55
72
68
1
74
78
76
98
90

Now i disconnected and connected once again as test_user and executed the
following code:-

SQL> begin
SQL> verify_role_pkg.SET_MY_ROLE;
SQL> for cf in (Select a from scott.t) loop
SQL> dbms_output.put_line(cf.a);
SQL> end loop;
SQL> end;
SQL> /

And i got the following error:-

ORA-06550: line 3, column 31:
PL/SQL: ORA-00942: table or view does not exist
ORA-06550: line 3, column 11:
PL/SQL: SQL Statement ignored
ORA-06550: line 4, column 22:
PLS-00364: loop index variable 'CF' use is invalid
ORA-06550: line 4, column 1:
PL/SQL: Statement ignored

I was not able figure out why the latter failed. Coul dyou please throw some
light on this Tom.

Thanks

Vipin

          Followup:

the block of code cannot compile if the user compiling the code doesn't have
access to a table scott.t.

Hence, since it could not be compiled -- it could not execute so the
verify_role_pkg call "never happened".

PLSQL is not a scripting language interpreted from source code line by line.  It
is compiled code.

      Rating:
      4
  February 02, 2004
Reviewer:
      A reader
if this is the case, how will you ENABLE the role and access the table in a
procedure, as the procedure will never compile and fill always fail.

          Followup:

well, procedures are compiled YESTERDAY by someone having direct access to the
object.  Procedures are NEVER compiled with "role privs" enabled regardless.

roles are for runtime access only.

So, one would

a) call the procedure to enable the role
b) invoke SQL that requires the role to be enabled

You would make two calls for the anonymous block above.

call 1 = enable role for my session.

calls 2 ... N = run stuff that needs the role.

      Rating: 3
what determines
      a role te become a default role?  March 15, 2004

Reviewer:  Helma Zijlstra  from The Netherlands

one thing that has to be taken into account is the "state" of the user that is
granted the role. Look at the following example:
SQL> create user alle_rollen identified by alle_rollen
  2  default tablespace user_data
  3  temporary tablespace user_temp;

User created.

SQL> create user geen_rollen identified by geen_rollen
  2  default tablespace user_data
  3  temporary tablespace user_temp;

User created.

SQL> alter user geen_rollen default role none;

User altered.

SQL> alter user alle_rollen default role all;

User altered.

SQL> create role test;

Role created.

SQL> grant test to geen_rollen,alle_rollen;

Grant succeeded.

SQL> select * from dba_role_privs
  2  where grantee in ('ALLE_ROLLEN','GEEN_ROLLEN');

GRANTEE                        GRANTED_ROLE                   ADM DEF
------------------------------ ------------------------------ --- ---
ALLE_ROLLEN                    TEST                           NO  YES
GEEN_ROLLEN                    TEST                           NO  NO

February 03, 2004 wallacel -- Thanks for the question regarding "IN
      & EXISTS", version 8.1.6

      You Asked
Tom:

can you give me some example at which situation
IN is better than exist, and vice versa.

      and we said...
Well, the two are processed very very differently.

Select * from T1 where x in ( select y from T2 )

is typically processed as:

select *
  from t1, ( select distinct y from t2 ) t2
 where t1.x = t2.y;

The subquery is evaluated, distinct'ed, indexed (or hashed or
sorted) and then joined to the original table -- typically.

As opposed to

select * from t1 where exists ( select null from t2 where y = x
)

That is processed more like:

   for x in ( select * from t1 )
   loop
      if ( exists ( select null from t2 where y = x.x )
      then
         OUTPUT THE RECORD
      end if
   end loop

It always results in a full scan of T1 whereas the first query
can make use of an index on T1(x).

So, when is where exists appropriate and in appropriate?

Lets say the result of the subquery
    ( select y from T2 )

is "huge" and takes a long time.  But the table T1 is relatively
small and executing ( select null from t2 where y = x.x ) is
very very fast (nice index on t2(y)).  Then the exists will be
faster as the time to full scan T1 and do the index probe into
T2 could be less then the time to simply full scan T2 to build
the subquery we need to distinct on.

Lets say the result of the subquery is small -- then IN is
typicaly more appropriate.

If both the subquery and the outer table are huge -- either
might work as well as the other -- depends on the indexes and
other factors.
Review & Followup

      Rating: 5
Thanks for the
      question regarding "IN & EXISTS", version 8.1.6  March
      31, 2001
Reviewer:  Seetharaman Srinivasan  from
      Chennai , TamilNadu, India
Excellent

      Rating: 4
Thanks for the
      question regarding "IN & EXISTS", version 8.1.6   April
      11, 2001
Reviewer:  Mani Srinivasan  from CA USA

This is good stuff....

      Rating:
      5
  May 18, 2001
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating: 5
Could not Call
      Report from Form (Menu)  May 28, 2001
Reviewer:
      Mohd. Manik Miah  from Dhaka, Bangladesh
Dear Sir

I learned oracle a few months ago.
But I could not call report from Form (Menu).
Would you please help me in this regard?

Thank & best regards
Manik Miah
manikmiah@yahoo.com

      Rating: 5
Instead of T,
      why cant you use EMP AND DEPT TABLES  September 28, 2001

Reviewer:  A reader
Tom

Can you illustrate your answer by using the emp dept table.
and by
1.increasing and decreasing the number of rows in both the tables.
2.by dropping and adding indexes on both the tables

That way your explanation will be vindicated.

Can you do that please.

          Followup:

Well -- I'm not going to use EMP and DEPT as I would  have to generate tons of
EMP and DEPT data to illustrate my point (feel free to do that if you want to ;)

I'll use BIG and SMALL to make the point. I ran:

rem create table big as select * from all_objects;
rem insert /*+ append */ into big select * from big;
rem commit;
rem insert /*+ append */ into big select * from big;
rem commit;
rem insert /*+ append */ into big select * from big;
rem create index big_idx on big(object_id);
rem
rem
rem create table small as select * from all_objects where rownum < 100;
rem create index small_idx on small(object_id);
rem
rem analyze table big compute statistics
rem for table
rem for all indexes
rem for all indexed columns
rem /
rem analyze table small compute statistics
rem for table
rem for all indexes
rem for all indexed columns
rem /

so, small has 99 rows, big has 133,000+

select count(subobject_name)
  from big
 where object_id in ( select object_id from small )

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.02       0.02          0        993          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.03       0.03          0        993          0           1

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   SORT (AGGREGATE)
    792    MERGE JOIN
    100     SORT (JOIN)
    100      VIEW OF 'VW_NSO_1'
     99       SORT (UNIQUE)
    792        INDEX   GOAL: ANALYZED (FULL SCAN) OF 'SMALL_IDX'
                   (NON-UNIQUE)
    891     SORT (JOIN)
      0      TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'BIG'

versus:

select count(subobject_name)
  from big
 where exists ( select null from small where small.object_id = big.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      4.12       4.12          0     135356         15           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      4.12       4.12          0     135356         15           1

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   SORT (AGGREGATE)
    792    FILTER
 135297     TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'BIG'
 133504     INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'SMALL_IDX'
                (NON-UNIQUE)

That shows if the outer query is "big" and the inner query is "small", in is
generally more efficient then NOT EXISTS.

Now:

select count(subobject_name)
  from small
 where object_id in ( select object_id from big )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        2      0.51       0.82         50        298         22           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      0.52       0.83         50        298         22           1

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   SORT (AGGREGATE)
     99    MERGE JOIN
  16913     SORT (JOIN)
  16912      VIEW OF 'VW_NSO_1'
  16912       SORT (UNIQUE)
 135296        INDEX   GOAL: ANALYZED (FAST FULL SCAN) OF 'BIG_IDX'
                   (NON-UNIQUE)
     99     SORT (JOIN)
     99      TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'SMALL'

versus:
select count(subobject_name)
  from small
 where exists ( select null from big where small.object_id = big.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.01       0.01          0        204         12           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.01          0        204         12           1

EGATE)
     99    FILTER
    100     TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'SMALL'
     99     INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'BIG_IDX' (NON-UNIQUE)

shows that is the outer query is "small" and the inner query is "big" -- a WHERE
EXISTS can be quite efficient.

      Rating: 3
What difference
      does it make  September 30, 2001
Reviewer:
      A reader
when you remove those indexes on the small and big tables.

          Followup:

Run it and see.  All of the necessary code is there -- it is very easy to do
these sorts of tests.

The outcome is easy to hypothesize however.

The "select * from big where object_id in ( select object_id from small )" will
sort BIG once and SMALL once and join them (sort merge join) in all likelyhood.

The "select * from big where exists ( select null from small where
small.object_id = big.object_id )" will tend to FULL SCAN big once and FOR EVERY
ROW in big -- FULL SCAN small.

(after running it -- i verified it and the "rule of thumb" holds true.  BIG
outer query and SMALL inner query = IN.  SMALL outer query and BIG inner query =
WHERE EXISTS.  Remember -- thats is a RULE OF THUMB and rules of thumb always
have infinitely many exceptions to the rule.

      Rating: 5
Tom, this is
      very clever and not fair  September 30, 2001

Reviewer:  TJ  from USA
You have explained the whole of the in and exists concepts so well( no one has
ever done in like this, you must be one happy person, you believe in sharing)

and now you leave us with an open ended answer..

"Remember -- thats is a RULE OF THUMB and rules of thumb always have infinitely
many exceptions to the rule."

Kindly discuss what can be some of those possible exceptions, tell us what
factors can cause  those exceptions.

          Followup:

Well, there are infinitely many -- IO can affect the outcome of this.  The goal
of the query can effect this (eg: the WHERE EXISTS will find the first row
faster in general then the IN will -- the IN will get the LAST row (all rows)
faster then the where exists).  If your goal is the FIRST row -- exists might
totally blow away IN.  If you are a batch process (and hence getting to the LAST
row is vital) ....

and so on.  The point is:  be aware of their differences, try them both when
tuning, understand conceptually what they do and you'll be able to use them to
maximum effect.

      Rating: 5
does this
      explanation hold good even for NOT IN and NOT
      EXISTS  September 30, 2001
Reviewer:  Chenna

          Followup:

No, partially because NOT IN and NOT EXISTS are not substitutes for eachother!
They mean semantically different things and can/will return different answers.
See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:442029737684
for example

      Rating:
      5
Summary  October 01, 2001
Reviewer:
      Chenna
This is discussion of the top most class --

O.k. let me try to sum up what you are saying --

in and exists can be substitutes for each other, and we have to use one in the
place of other depending upon the rowns returned by the outer and inner query,

where as

'not in' and 'not exists' are not substitutes for each other , and they are not
related in any way. There use depends on the specific need.

o.k. to sum it up finally , can you give us examples of when the use of 'not in'
is appropriate and when 'not exists' is appropriate.

          Followup:

you got the first part right (in/exists)

the second part is too strong.  not in and not exists are not perfect
substitutes.  NOT IN is different then NOT exists but NOT EXISTS and NOT IN are
the same when the subquery you use in the NOT IN does not contain NULLS.

Both of not in and not exists can be very efficient when there are no nulls (and
not in WITH THE CBO is pretty good -- using an "anti join" -- see the
design/tuning for performance guide for details  on that).  With NULLS -- a not
in can be very in-efficient and many people substitute a not exists for it (not
realzing the ANSWER just changed!!!)

      Rating: 3
anti
      join  October 01, 2001
Reviewer:
      A reader
(and not in WITH THE CBO is pretty good -- using an "anti join" -- see the
design/tuning for performance guide for details  on that)

Please give the link to the concerned page in the manual, there is no search
feature in the document to search for 'anti join'

          Followup:

http://download-east.oracle.com/docs/cd/A81042_01/DOC/server.816/a76992/optimops.htm#36687

      Rating: 5
the mystery
      with non in and not exists  October 01, 2001

Reviewer:  A reader
"Both of not in and not exists can be very efficient when there are no nulls
(and not in WITH THE CBO is pretty good -- using an "anti join" -- see the
design/tuning for performance guide for details  on that).  With NULLS -- a not
in can be very in-efficient and many people substitute a not exists for it (not
realzing the ANSWER just changed!!!) "

1.Not in and not exists return the same performance result when there arer no
nulls.
2.Not in is especially very efficient when used with cbo and no nulls.
3.And when there are nulls what is preferred is not exists, and not not in.

I think Iam right in what I understood above.

Now

1.Why is not in in efficient when there are nulls. Can you demonstrate this.
2.And how is not exists better over not in when there are nulls.
3.What do you mean by saying
"and many people substitute a not exists for it (not realzing the ANSWER just
changed!!!) "
do you mean not exists should be substituted for  not in when there are nulls
???????

          Followup:

1a) not in can be faster then not exists (or slower).  The are different, do
different things.  A not exists is alot like an EXISTS.  A not in when the
subquery cannot contain NULLS can be alot like an IN (processed as an ANTI-JOIN
instead of a JOIN).  So, just like IN is sometimes better then EXISTS and vice
versa, NOT IT is sometimes (and sometimes not) better then NOT EXISTS

2a) thats when it works best, yes

3a) typically...

1b) Ok, time for the demo of NOT IN and NOT EXISTS (i guess I knew it was coming
eventually!)

Here we go:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table big as select * from
all_objects;
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table small as select * from
all_objects where rownum < 10;
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table big modify object_id null;
Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table small modify object_id null;
Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> update small set object_id = null where
rownum = 1;

1 row updated.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create unique index big_idx on
big(object_id);
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create unique index small_idx on
small(object_id);
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table big compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns;
Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table small compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns;
Table analyzed.

So, there is the big setup -- we have our tables.  SMALL will be used in the
subquery.  As it stands now -- lets see what happens with NOT IN and NOT EXISTS:

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from big
  2   where NOT EXISTS (select null
  3                       from small
  4                      where small.object_id = big.object_id)
  5  /

16886 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     INDEX (UNIQUE SCAN) OF 'SMALL_IDX' (UNIQUE)

Statistics
----------------------------------------------------------
        305  recursive calls
         15  db block gets
      18278  consistent gets
          0  physical reads
          0  redo size
    1961046  bytes sent via SQL*Net to client
     125305  bytes received via SQL*Net from client
       1127  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      16886  rows processed

So, for each row in BIG it did an INDEX PROBE into SMALL to see if that row
existed or not.  So far, so good...  18k consistent reads (high) but not as high
as:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from big
  2   where object_id NOT IN ( select object_id
  3                              from small )
  4  /
no 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=3)

Statistics
----------------------------------------------------------
          0  recursive calls
     202743  db block gets
      84706  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
          2  sorts (memory)
          0  sorts (disk)
          0  rows processed

Wow -- what happened here?  Well, for each row in BIG -- we FULL SCANNED small
-- yes, for each row, we full scanned small -- we full scanned small some 16,000
times (imagine if small weren't so small).  Also take care to note that this
query processed 0 rows - no data found.  The prior one got over 16k -- that is
the effect of a NULL in the subquery of a NOT IN.

So, lets get rid of that nasty (we don't have to get rid of it, we could use a
predicate to preclude it as well and we'll do that on BIG as a demonstration)

ops$tkyte@ORA817DEV.US.ORACLE.COM> update small set object_id = -1 where
object_id is null;
1 row updated.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table small modify object_id NOT NULL;
Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table small compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns;
Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> Select * from big
  2   where NOT EXISTS (select null
  3                       from small
  4                      where small.object_id = big.object_id)
  5  /

16886 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     INDEX (UNIQUE SCAN) OF 'SMALL_IDX' (UNIQUE)

Statistics
----------------------------------------------------------
         71  recursive calls
         15  db block gets
      18270  consistent gets
          0  physical reads
          0  redo size
    1961046  bytes sent via SQL*Net to client
     125305  bytes received via SQL*Net from client
       1127  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      16886  rows processed

So, that is no different... but:

ops$tkyte@ORA817DEV.US.ORACLE.COM> Select * from big b0
  2   where object_id IS NOT NULL
  3     and object_id NOT IN ( select object_id
  4                              from small )
  5  /

16886 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     INDEX (UNIQUE SCAN) OF 'SMALL_IDX' (UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
         15  db block gets
      18243  consistent gets
          0  physical reads
          0  redo size
    1961046  bytes sent via SQL*Net to client
     125305  bytes received via SQL*Net from client
       1127  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      16886  rows processed

Hey -- thats pretty good already. Its just like the NOT EXISTS.  But wait --
there is more.  Lets start anti-joining...

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set always_anti_join = MERGE
  2  /
Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> Select * from big b1
  2   where object_id is not null
  3     and object_id NOT IN ( select object_id
  4                              from small )
  5  /

16886 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=269 Card=16885 Bytes=1857350)
   1    0   MERGE JOIN (ANTI) (Cost=269 Card=16885 Bytes=1857350)
   2    1     SORT (JOIN) (Cost=267 Card=16894 Bytes=1638718)
   3    2       TABLE ACCESS (FULL) OF 'BIG' (Cost=7 Card=16894 Bytes=1638718)
   4    1     SORT (UNIQUE) (Cost=3 Card=9 Bytes=117)
   5    4       VIEW OF 'VW_NSO_1' (Cost=1 Card=9 Bytes=117)
   6    5         INDEX (FULL SCAN) OF 'SMALL_IDX' (UNIQUE) (Cost=1 Card=9
Bytes=36)

Statistics
----------------------------------------------------------
          0  recursive calls
         19  db block gets
        237  consistent gets
        236  physical reads
          0  redo size
    1863304  bytes sent via SQL*Net to client
     125305  bytes received via SQL*Net from client
       1127  SQL*Net roundtrips to/from client
          4  sorts (memory)
          1  sorts (disk)
      16886  rows processed

Now that is much much less work -- from 18k to 237 consistent gets -- not bad.
The hash anti join is similar:

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set always_anti_join = HASH
  2  /

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> Select * from big b2
  2   where object_id is not null
  3     and object_id NOT IN ( select object_id
  4                              from small )
  5  /

16886 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=145 Card=16885 Bytes=1857350)
   1    0   HASH JOIN (ANTI) (Cost=145 Card=16885 Bytes=1857350)
   2    1     TABLE ACCESS (FULL) OF 'BIG' (Cost=7 Card=16894 Bytes=1638718)
   3    1     VIEW OF 'VW_NSO_1' (Cost=1 Card=9 Bytes=117)
   4    3       INDEX (FULL SCAN) OF 'SMALL_IDX' (UNIQUE) (Cost=1 Card=9
Bytes=36)

Statistics
----------------------------------------------------------
          0  recursive calls
         15  db block gets
        237  consistent gets
         31  physical reads
          0  redo size
    2350779  bytes sent via SQL*Net to client
     125305  bytes received via SQL*Net from client
       1127  SQL*Net roundtrips to/from client
          3  sorts (memory)
          0  sorts (disk)
      16886  rows processed

2b) see 1b)

3b) see 1b) where the NOT IN returns 0 rows, but NOT EXISTS returns 16k -- they
are NOT the same when NULLs are involved.

      Rating: 5
ANTI JOIN and
      'ANY', 'SOME','ALL'  October 02, 2001
Reviewer:
      Chenna
Optimizing Anti-Joins and Semi-Joins
An anti-join returns rows from the left side of the predicate for which there is
no corresponding row on the right side of the predicate. That is, it returns
rows that fail to match (NOT IN) the subquery on the right side. For example, an
anti-join can select a list of employees who are not in a particular set of
departments:

SELECT * FROM emp
  WHERE deptno NOT IN
    (SELECT deptno FROM dept
      WHERE loc = 'HEADQUARTERS');

The optimizer uses a nested loops algorithm for NOT IN subqueries by default,
unless the initialization parameter ALWAYS_ANTI_JOIN is set to MERGE or HASH and
various required conditions are met that allow the transformation of the NOT IN
subquery into a sort-merge or hash anti-join. You can place a MERGE_AJ or
HASH_AJ hint in the NOT IN subquery to specify which algorithm the optimizer
should use.

A semi-join returns rows that match an EXISTS subquery, without duplicating rows
from the left side of the predicate when multiple rows on the right side satisfy
the criteria of the subquery. For example:

SELECT * FROM dept
  WHERE EXISTS
    (SELECT * FROM emp
      WHERE dept.ename = emp.ename
        AND emp.bonus > 5000);

In this query, only one row needs to be returned from dept even though many rows
in emp might match the subquery. If there is no index on the bonus column in
emp, then a semi-join can be used to improve query performance.

The optimizer uses a nested loops algorithm for EXISTS subqueries by default,
unless the initialization parameter ALWAYS_SEMI_JOIN is set to MERGE or HASH and
various required conditions are met. You can place a MERGE_SJ or HASH_SJ hint in
the EXISTS subquery to specify which algorithm the optimizer should use.

SELECT * FROM dept
  WHERE EXISTS
    (SELECT * FROM emp
      WHERE dept.ename = emp.ename
        AND emp.bonus > 5000);
What difference does it make when there is an index on the bonus column.

A semi join is already being executed in the above query, right ?

How will the use of MERGE_SJ or HASH_SJ hint in the EXISTS subquery improve the
performance.

what do these hints tell the optimizer.

And , finally, can you give examples of using 'ANY' ,'ALL' AND 'SOME' clauses,
as to where and when to use these clauses. We have never used these clauses.

          Followup:

ANY/SOME are alot like an in.

where x in ( select y from t )

is the same as:

where x = ANY(select y from t)

SOME is an aliase for ANY, ANY is the same as SOME.

any/some can be useful with inequalities:

scott@ORA817DEV.US.ORACLE.COM> select ename, sal, comm from emp where sal <
some( select comm from emp );

ENAME             SAL       COMM
---------- ---------- ----------
A                 800
WARD             1250        500
MARTIN           1250       1400
ADAMS            1100
JAMES             950
MILLER           1300

6 rows selected.

that finds emps such that there exists SOME commision greater then their
salary....

ALL is a cool one.  I use it to find the most frequently ocurring thing, for
example:

scott@ORA817DEV.US.ORACLE.COM> select to_char(hiredate,'mm')
  2  from emp
  3  group by to_char(hiredate,'mm')
  4  having count(*) >= ALL ( select count(*) from emp group by
to_char(hiredate,'mm') );

TO
--
12

that shows me the month with the MOST hiredates....  it says give me the month
whose count is greater than or equal to ALL other counts by month...

      Rating: 5
Tom, will the
      result be the same if ..  October 03, 2001

Reviewer:  Chenna
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set always_anti_join = MERGE
  2  /
Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> Select * from big b1
  2   where object_id is not null
  3     and object_id NOT IN ( select object_id
  4                              from small )
  5  /

16886 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=269 Card=16885 Bytes=1857350)
   1    0   MERGE JOIN (ANTI) (Cost=269 Card=16885 Bytes=1857350)
   2    1     SORT (JOIN) (Cost=267 Card=16894 Bytes=1638718)
   3    2       TABLE ACCESS (FULL) OF 'BIG' (Cost=7 Card=16894 Bytes=1638718)
   4    1     SORT (UNIQUE) (Cost=3 Card=9 Bytes=117)
   5    4       VIEW OF 'VW_NSO_1' (Cost=1 Card=9 Bytes=117)
   6    5         INDEX (FULL SCAN) OF 'SMALL_IDX' (UNIQUE) (Cost=1 Card=9
Bytes=36)

Statistics
----------------------------------------------------------
          0  recursive calls
         19  db block gets
        237  consistent gets
        236  physical reads
          0  redo size
    1863304  bytes sent via SQL*Net to client
     125305  bytes received via SQL*Net from client
       1127  SQL*Net roundtrips to/from client
          4  sorts (memory)
          1  sorts (disk)
      16886  rows processed

1.Tom, will the result be the same  if there is no index on the object_id of the
small table.

2.You are using CBO , all the way, what will be the result of all these tests
for 'in and exists' and 'not in and not exists' if we are using RBO.

3.After we analyze the tables, how should we switch to RBO if we want to.

4.Will the push_sq (push sub query ) come into picture in any way inthe above
examples. Actually when is this hint relevant.

5.out of context: Can you give us the link to the oracle error messages manual.

6.Suppose if the query's where clause has 5 sub queries, for the third sub query
in the where clause , what will be the outer query, I mean how will it resolve.

For the fifth sub query in the where clause, I guess the whole of the restof the
query will be the outer query ?

          Followup:

1) yes more or less -- the ANSWER will be the same (16,886 rows processed).  The
plan will be slightly different -- a FULL SCAN of small instead of an index full
scan.  (you have ALL of the code needed to replicate my tests -- try it out!)

2) I don't recommend the RBO with 8i -- too many features not available.  But --
hey -- you have the code, give it a whirl, try it out (but make sure you have
lots of time -- the RBO is not as smart as the CBO when it comes to this --
reread this entire article, I stress the need for the CBO to make this
efficient!)

3) use an alter session set optimizer_goal or /*+ RULE */ hint in the query.

4) push_subq affects when subqueries are evaluated (last by default, earlier if
you ask for it).  So yes, it could affect the plan here.

5) all documentation is available from
http://otn.oracle.com/,
the Oracle error messages manual
varies from release to release.  Suggest you goto otn, follow the documentation
link and goto the version you are interested in.

6) this does not make syntactic sense to me.

      Rating: 5
Train us to use
      parallel query  October 03, 2001
Reviewer:
      A reader
From the oracle magazine article

What Can Execute in Parallel?
By using parallel options, data-sensitive SQL state-
ments, database recovery, and data loads can be exe-
cuted by multiple processes simultaneously. With the
release of Oracle 8i, all the following operations can
be executed in parallel:

Table scan
PL/SQL functions called from SQL
Sort merge join
"Not in"
Nested loop join
Select distinct
Hash join
Aggregation
Group by
Cube
Union and union all
Create table as select
Order by
Rebuild index partition
Rollup
Move partition
Create index
Update
Rebuild index
Insert . . . select
Split partition
Enable constraint (the table
Delete scan is parallelized)
Star transformation

If the want to execute the above not in sub queries in parallel, actually when
should I decide to do it in paralle.

Will it suffice if I give the following.

Select * from big b1
  2   where object_id is not null
  3     and object_id NOT IN ( select/*+ PARALLEL(a1,8)*/    object_id  from
small a1)

usually I see that it is the  Sort merge joins which take the most amount of
time in a query, how can I parallelize a sort merge join.

Thank You

          Followup:

If you have to parallelize the subquery -- you are in a heap of trouble.  that
means it is really big and since you would be full scanning IT once per row in
big -- its going to be slow.  Look at the other options below in order to AVOID
having to full scan it once per row.

My advice here -- try it yourself.  Take your ideas, set up the test case, test
it out -- see what works, what doesn't.  Its the best way to really learn and
understand how this works.

      Rating: 5
I will do it
      only my own but -- --  October 04, 2001
Reviewer:
      Chenna
Select * from big b1
  2   where object_id is not null
  3     and object_id NOT IN ( select/*+ PARALLEL(a1,8)*/    object_id  from
small a1)

O.k. , let me try parallel, for myself, and report the conclusions to you for
discussion.

But before that , you need to weed out these small doubts for me ..

1.select/*+ PARALLEL(a1,8)*/    object_id  from
small a1  -- before using the parallel hint, do I need to change any init.ora
parameters,

2.I understand that 8 refers to the number of processors, how can I know the
number of processors available for my server.

3.Where you use parallel query, how does my query get divided between multiple
processors. Where are the results from each divided query kept , and then
combined , before they are displayed to the user.

4.Give us a brief insignt into the internals of how parallel query works.

And by the way, I cant see the asktom or any other images  on this page. Some
thing you might want to know about.

          Followup:

1) probably not, do a show parameter parallel and check out the settings - make
sure parallel max servers isn't 0

2) you own the machine don't you?  show parameter cpu_count will tell you as
well

3) read

http://download-east.oracle.com/docs/cd/A81042_01/DOC/server.816/a76965/c22paral.htm#365
4) see #3

as for the images, must have been a temporary "issue" - they seem alright now.

      Rating:
      4
  October 04, 2001
Reviewer:  Chenna

SQL> connect system/manager
Connected.
SQL> show parameter cpu_count

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
cpu_count                            integer 1
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>

The cpu count shows only one , but look at this
parallel_max_servers                 integer 5.

1.how can the parallel_max_servers be 5 when the cpu count is 1.
2.Does this mean that I can give a parallel hint to use 5 parallel servers.

3.When the cpu count is 1 I expect the parallel_max_servers
to be 1.

Perplexing? Need to be clarified.

          Followup:

did you read the documentation i pointed you to?

you can do parallel query on a single cpu box.  You can do a parallel 100 query
on a 20 cpu box.  You need not have parity between processes and cpu's (if you
did, unix would be a single user OS).

You can have parallel_max_servers at whatever you want, regardless of the number
of cpus on the system.  Since parallel queries typically involve tons of IO, and
IO is something you wait for -- there is plenty of opportunity for using
parallel query on a single CPU system (when one process is waiting for IO,
blocked, the other process can be munching through some data it read).

Read the docs, get a good understanding of how it really works -- then it won't
be confusing.

      Rating: 5
sub query
        October 06, 2001
Reviewer:  Jay

Tom

Lets assume that the where clause has 10 conditions, and each condition gets its
value from a sub query. IN what order do these sub queries get resolved.

select a from b,c
were b.x=c.x
and b.t=(select t from some ttablle where something)
and c.l=(select l from some xtable where something)

Now when resolving and c.l=(select l from some xtable where something)

will select a from b,c
were b.x=c.x
and b.t=(select t from some ttablle where something)

be considered as outer query, or only
select a from b,c
were b.x=c.x

be considered as outer query

          Followup:

when looking at the IN, the outer query is the result set that will be joined to
the IN result.

if b.t = (select t ... ) is processed FIRST (before c.l = ( select l...)), the
that would be the "outer query" with regards to c.1 = ( select l...).

However -- you cannot count on the order of operation of predicates!!! we can
and will change the order at will.

      Rating: 5
Good
      topic  May 17, 2002
Reviewer:  A reader

Tom and team,  Many thanks.  Love your site.

I'm running Oracle 8.1.7.3
I need help with a "NOT IN" query.
Here is what the tables look like:

Table 1:

CREATE TABLE SKU
    (ITEM_ID                        NUMBER NOT NULL,
    SKU_CODE                       VARCHAR2(10) NOT NULL,
    SKU_DESC                       VARCHAR2(255) NOT NULL
  ,
  CONSTRAINT PK_SKU
  PRIMARY KEY (ITEM_ID, SKU_CODE)
  USING INDEX)
/

CREATE TABLE SKU_SOLD_OUT
    (ITEM_ID                        NUMBER NOT NULL,
    SKU_CODE                       VARCHAR2(10) NOT NULL,
    SOLD_OUT_DATE                  DATE NOT NULL,
    DELETED                        CHAR(1) NOT NULL,
    SOLD_OUT_TEXT                  VARCHAR2(1000) NOT NULL
  ,
  CONSTRAINT PK_SKU_SOLD_OUT
  PRIMARY KEY (ITEM_ID, SKU_CODE, SOLD_OUT_DATE)
  USING INDEX)
/

With item_id, sku_code combinations having zero to many rows in sku_sold_out
but only one will be active (deleted = 'N') at a time.

I need to get a list of items that are not completely sold out.  i.e.
If an Item_id has four sku_codes and three are currently sold out, return the
item_id.
If all four sku_codes are sold out, do not return the item_id.

Here is the query I am useing now.

select distinct item_id
from sku
where item_id||'-'||sku_code not in (
    select item_id||'-'||sku_code
    from sku_sold_out
    where deleted = 'N'
     and  sold_out_date <= sysdate)

sku has about 10,000 rows
sku sold out has about 120,000 rows.

Is there a better way of doing this?  The schema is set and is production so I
cant
make any database changes other than views, procs, ect.

Thanks for the help.
Thanks

          Followup:

Well, other ways would be

select distinct item_id
  from sku
  where (item_id,sku_code) not in ( select item_id, sku_code
                                      from sku_sold_out
                                     where deleted = 'N'
                                       and  sold_out_date <= sysdate)
/

select distinct sku.item_id
  from sku, sku_sold_out
 where sku.item_id = sku_sold_out.item_id (+)
   and sku.sku_code = sku_sold_out.sku_code (+)
   and nvl(sku_sold_out.deleted,'Y') = 'Y'
   and nvl(sku_sold_out.sold_out_date,sysdate) <= sysdate
/

      Rating: 4
Index result of
      Subquery and FILTER it  May 18, 2002
Reviewer:
      Jaromir D.B. Nemec  from Vienna, Austria
Hi Tom,

in the original explanation you told us:

The subquery is evaluated, distinct'ed, >>>indexed<<<< (or hashed or sorted) and
then ....

1) Can you explain how can be the result of a subquery indexed.
temporary index is created or something like that? What you see in explain plan
in that case? - never heard of this feature.

2) Some of the explain plans you demonstrated consist of:

FILTER
    row set 1
    row set 2

I've learned that FILTER is a kind of "filter" to suppress rows that doesn't
match some condition. This is  evidently NOT (in) the case.

Can you explain (or link) what is the functionality of this two-row-ser-filter?
My experience is that this kind of FILTER operate similar to  nested loops and
is not performable in parallel - is it right?

Thanks

Jaromir

www.db-nemec.com

          Followup:

1) it is stored sorted or hashed.  The structure itself is an implicit
"indexing" of sorts.  Lets say you were doing a sort merge join -- you would see
two sorts (out to temp) and then a merge.  A hash join -- one of the sets gets
hashed.

for #2, I assume you mean something like:

ops$tkyte@ORA817DEV.US.ORACLE.COM> Select * from big b0
  2   where object_id IS NOT NULL
  3     and object_id NOT IN ( select object_id
  4                              from small )
  5  /

16886 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     INDEX (UNIQUE SCAN) OF 'SMALL_IDX' (UNIQUE)

The FILTER in this case is sort of like a nested loops join yes.  The table
access full is done -- and the "is not null" part is evaluated.  Then each row
must be filtered for the NOT IN and that is done via the index.

This could be done in parallel -- meaning the full scan can be parallelized.

      Rating: 4
Useful info
      about IN & EXISTS  July 23, 2002
Reviewer:
      Vish Inamdar  from USA
Yes, this article gives me clear idea how IN & EXISTS are different from each
other and where to apply them.

Good and simple example made it more clear.

I will be asking more question in future.

Vish

      Rating: 4
Exists, IN and
      consistent gets  August 22, 2002
Reviewer:
      Vikas Sharma  from Delhi India
Hi tom,

Can i directly relate the consistent gets of plan with the time taken by the
query executed by using IN  and another same query executed by using exists.
Actually I have query which I execute in two different ways eg.

Query with exists :
select person_id from persons where exists
 (select con_person_id  from contacts where con_person_id = person_id ) and
contact_details = 'Y'

93 rows selected ..
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=951 Card=1 Bytes=9)
   1    0   FILTER
   2    1     TABLE ACCESS (FULL) OF 'PERSONS' (Cost=951 Card=1 Bytes=
          9)

   3    1     INDEX (UNIQUE SCAN) OF 'CON_PK' (UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          8  db block gets
       6365  consistent gets
          0  physical reads
          0  redo size
       2967  bytes sent via SQL*Net to client
       1091  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
93    rows processed

Query with IN :
select person_id from persons where person_id in (select con_person_id  from
contacts ) and contact_details = 'Y';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=187 Card=1 Bytes=17)
   1    0   NESTED LOOPS (Cost=187 Card=1 Bytes=17)
   2    1     INDEX (FULL SCAN) OF 'CON_PK' (UNIQUE) (Cost=1 Card=93 B
          ytes=744)

   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'PERSONS' (Cost=2 Card=
          1 Bytes=9)

   4    3       INDEX (UNIQUE SCAN) OF 'PER_PK' (UNIQUE) (Cost=1 Card=
          1)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        543  consistent gets
          0  physical reads
          0  redo size
       2967  bytes sent via SQL*Net to client
       1091  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
93    rows processed

The persons table has 31000+ records and the contact table has 120+ record in
this case as you proved the IN is better I  am too getting the same results as
you can see the IN is fast.

I would like to know that is it always true for two same queries (executed in
the different way ) if the  consistent gets for a query are more then that query
is not optimized and will always  result a poor show, we should always go for
the query which results less consistent get.

I read in you book also the consistent gets depend upon the order of the data of
the index columns. It will be more when the data for the index column is
disordered. Does that will always effect the time taken by the query.

          Followup:

Less consistent gets is a good goal.  In generate, the less consistent gets

o the faster it'll be
o the more SCALABLE it'll be (as you add users, you can add MORE users with less
consistent gets)

Always is a very very strong term.  Almost Always i would say.

As for the last one -- that is due to a large index range scan that'll be
getting LOTS of data from the table -- not really applicable in this example.
Does it affect the time taken by the query -- probably, the more consistent
gets, the more latching (thats a lock, a serialization thing), the longer it
takes to get the data.

      Rating:
      5
  August 22, 2002
Reviewer:  Ashok,
      Bombay ,India
your style of explaining things is just amazing..!!you must be one helluva
teacher !!!

      Rating: 4
Thanks a
      Lot  August 22, 2002
Reviewer:  Vikas Sharma
       from Delhi India
Hi Tom,

Thanks lot

The way you explain the things it is great..

Regards,

Vikas Sharma

      Rating: 5
Very
      helpful  August 22, 2002
Reviewer:  Bijay
       from Fremont, CA
Tom
I wonder how you can find so much time in replying to all questions. Its amazing
a simple question can create such a big chain of Q & A.
You have been a great source of knowledge for all of us. Thank you for being
you.
Bj

      Rating: 4
why is my query
      plan using NL? where as your use SORT-MERGE  September 05,
      2002
Reviewer:  A reader
Tom,

I am trying to understand exists and not exists

 analyze table big compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns
  5  /

Table analyzed.

SQL> analyze table small compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns
  5  /

Table analyzed.

SQL> truncate table plan_table;

Table truncated.

SQL> explain plan for
  2  select count(subobject_name)
  from big
 where object_id in ( select object_id from small )
  3    4    5  /

Explained.

SQL> @$ORACLE_HOME/rdbms/admin/utlxpls.sql
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |     1 |   19 |    598 |       |       |
|  SORT AGGREGATE           |          |     1 |   19 |        |       |       |
|   NESTED LOOPS            |          |   396 |    7K|    598 |       |       |
|    VIEW                   |VW_NSO_1  |    99 |    1K|      4 |       |       |
|     SORT UNIQUE           |          |    99 |  396 |      4 |       |       |
|      INDEX FULL SCAN      |SMALL_IDX |    99 |  396 |      1 |       |       |
|    TABLE ACCESS BY INDEX R|BIG       |   113K|  665K|      6 |       |       |
|     INDEX RANGE SCAN      |BIG_IDX   |   113K|      |      2 |       |       |
--------------------------------------------------------------------------------

11 rows selected.

Thans and Regards,

          Followup:

things like sort_area_size, db_block_buffers, hash area size, database version,
etc will all affect the plans.

      Rating: 5
Update Process
      Options  September 09, 2002
Reviewer:  SK
       from NJ
Hi Tom,

I have to update my production table daily with new/update data. My update
process is like this.

Table big_table (col1 integer, col2 integer, col3 integer)
Table Small_table (c1 integer, c2 integer, c3 integer)

Small table is a external Table. and Big Table as index on Col1.

My update process is like this:

Delete from big_table where col1 in (select c1 from small_table);
insert into big_table select c1,c2,c3 from small_table.

My Big table has 32 million rows and small table has 400K rows. This process is
taking 30 minutes to do the above two processes. The requirement is to reduce
this time. How can we do it. I believe using Exists will do both full table
scans so that will not be efficient. Where as using in may use indexes (but I
doubt that it is using indexes).

1. Should I go for updating using procedure where I delete records one by one
and I will be sure that processes will use indexes. But that is not my choice
and it will defy your first rule of using single statement instead of using
procedure.

2. In one such update my big_table has more than 500 million records. and small
Table has 7 million records. I have 24GB of temp tablespace. My delete statement
is failing due to unable to allocate extents on temp tablespace. I am surprised
on this. Is Temp tablespace of 24GB too large? In the above delete statement
will it use only col1 and c1 for sort/merge. How do I estimate the temp
tablespace size for this transaction. I am running Oracle 9iR2 RAC.

Your response will be highly appreciated.

Thanks

          Followup:

why delete and insert.

Just MERGE, see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1615330671789

      Rating: 3
Follow
      up  September 09, 2002
Reviewer:  SK
       from NJ
Hi Tom,

I am using merge for some tables where I have one to one relationship between
the two table. But for many big tables I have multiple records for same key
(tables with non unique key). So will merge will take care of following
situation where for big table has 5 records for col1. and small table has 3
records for the same key. Will merge replace 5 rows in big table with 3 rows
from small table for the same key (means deleteion of two records from big
table).

I will certainly prefer merge if this is possible.

Regards

          Followup:

Then that is not a merge.

A merge would leave the same number of rows.

You'll need to do the delete+insert.  You'll want to use tkprof to see what it
is doing now and see if it can be speeded up.  Deleting 400k rows is going to
take a while if you have indexes on the table.

      Rating: 5
Oracle
      Optimizer and In & Exists  September 11, 2002

Reviewer:  Subhrajyoti Paul  from Mumbai, India

Tom,
   From the discussion above, I get the impression that In and exists clause can
be used interchangibly, in case there is a subquery inside the IN clause. But,
one would be better than the other depending on the amount data retrieved in the
inner and the outer query.
   If they can be used interchangibly, then it sounds logical that the optimizer
can substitute an in clause with an exists and vice versa, given it is provided
with proper statistics... Does this happen in practice... Or am I making some
mistakes in my understanding of the things...

   One other question,

"select *
  from t1, ( select distinct y from t2 ) t2
 where t1.x = t2.y;

The subquery is evaluated, distinct'ed, indexed (or hashed or sorted) and then
joined to the original table -- typically. "

Here you mentioned that it is distinct'ed, which means it is sorted, now once
sorted, will oracle favor the use of merge sort join, because the sort operation
is already complete...or may it choose to Indexing or hash join. How much
importance is given to the fact that one step (sort, in this case) can be
eliminated from the join operation, or is this relevant at all...

          Followup:

And in 8i, ... 9i, .... 10i, and on up -- that is true.  You'll see interesting
query rewrites with both (given that you use the cost based optimizer which I
can assure you you will be in 10i as the RBO is gone in that release)

distinct <> sort.  SORT is SORT, distinct is distinct.  DISTINCT does not imply
or infer or mean SORTed.  Never has, never will.

      Rating: 5
But how
      otherwise can u perform a distinct if u do not
      sort...  September 11, 2002
Reviewer:
      Subhrajyoti Paul  from Mumbai, India
Tom,
   I am relieved that the optimizer does the substitution...And ofcourse, I use
cost based optimizer...

   But the distinct thing.. If distincted data does not mean sorted, how
otherwise can u have distinct data if u did not sort it... Can you give me an
example...

Subhro

          Followup:

Lets say you have a partitioned table.

You used hash partitioning on the ID column.

We know that across partitions -- ID is "unique" (eg: if ID=5 is in partition 1,
ID=5 will NOT be in partitions 2, 3, 4, ....  and so on).

We use parallel query.

Each partitioned is distincted and then glued together.  ID=5 from partition 1
could come after ID=1000 from partition 2 if the PQ slave processing partition 2
was "faster" then the one doing partition 1.

For example.  There are other cases as well.  distinct, group by, etc -- do not
imply "sorted"

      Rating: 5
Thanks a lot...
      I got the point...  September 11, 2002
Reviewer:
      Subhrajyoti Paul  from Mumbai, India
Tom,
   Most of what u said in the last followup is a bouncer to me(in cricket, a
bouncer is a ball that goes above the batsman's head ;-) )...

   But, I got the point that distinct does not mean sorted...

   I just could not think of a case myself ..so the example was needed...

Thanks a lot... everytime i think i got u...u prove me wrong... cool....

      Rating: 4
not in &
      not exists  November 05, 2002
Reviewer:
      Brigitte  from Germany
Hallo Tom,

Some times ago I changed a query to "not exists", because "not in" takes some
hours.
Now I tried your interesting followup from October 01, 2001  with the test
database, but alter session
set always_anti_join = HASH/MERGE hasn't an effect. Why?
notesid is not null in all tables, it is the primary key in
testidxdokument,testidxvorgang and testidxmappe

Sql> set autotrace trace
Sql> set timing on
Sql> r
  1  select distinct mandant,notesid from testleserfeldgruppe l
  2  where not exists
  3  (select 'x' from testidxdokument
  4  where l.notesid = notesid)
  5  and not exists
  6  (select 'x' from testidxvorgang
  7  where l.notesid = notesid)
  8  and not exists
  9  (select 'x' from testidxmappe
 10  where l.notesid = notesid)
 11* order by mandant, notesid

no rows selected

Elapsed: 00:00:01.02

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=1 Bytes=35)
   1    0   SORT (ORDER BY) (Cost=6 Card=1 Bytes=35)
   2    1     SORT (UNIQUE) (Cost=5 Card=1 Bytes=35)
   3    2       FILTER
   4    3         TABLE ACCESS (FULL) OF 'TESTLESERFELDGRUPPE' (Cost=3 Card=1
Bytes=35)
   5    3         INDEX (UNIQUE SCAN) OF 'UK_TESTIDXDOKUMENT' (UNIQUE) (Cost=1
Card=1 Bytes=18)
   6    3         INDEX (UNIQUE SCAN) OF 'UK_TESTIDXVORGANG' (UNIQUE) (Cost=1
Card=1 Bytes=18)
   7    3         INDEX (UNIQUE SCAN) OF 'UK_TESTIDXMAPPE' (UNIQUE) (Cost=1
Card=1 Bytes=18)

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
       1507  consistent gets
          0  physical reads
          0  redo size
        160  bytes sent via SQL*Net to client
        251  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
          0  rows processed

Sql> r
  1  select distinct mandant,notesid from testleserfeldgruppe where notesid not
in
  2  (select notesid from testidxdokument)
  3  and notesid not in
  4  (select notesid from testidxvorgang)
  5  and notesid not in
  6  (select notesid from testidxmappe)
  7* order by mandant, notesid

no rows selected

Elapsed: 00:00:02.44

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=1 Bytes=35)
   1    0   SORT (ORDER BY) (Cost=6 Card=1 Bytes=35)
   2    1     SORT (UNIQUE) (Cost=5 Card=1 Bytes=35)
   3    2       FILTER
   4    3         TABLE ACCESS (FULL) OF 'TESTLESERFELDGRUPPE' (Cost=3Card=1
Bytes=35)
   5    3         TABLE ACCESS (FULL) OF 'TESTIDXDOKUMENT' (Cost=12 Card=303
Bytes=5454)
   6    3         TABLE ACCESS (FULL) OF 'TESTIDXVORGANG' (Cost=1 Card=9
Bytes=162)
   7    3         TABLE ACCESS (FULL) OF 'TESTIDXMAPPE' (Cost=1 Card=9
Bytes=162)

Statistics
----------------------------------------------------------
          0  recursive calls
       3304  db block gets
      32597  consistent gets
          0  physical reads
          0  redo size
        167  bytes sent via SQL*Net to client
        249  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          6  sorts (memory)
          0  sorts (disk)
          0  rows processed

Sql> alter session set always_anti_join = MERGE;

Session altered.

Sql> select distinct mandant,notesid from testleserfeldgruppe where notesid not
in
  2  (select notesid from testidxdokument)
  3  and notesid not in
  4  (select notesid from testidxvorgang)
  5  and notesid not in
  6  (select notesid from testidxmappe)
  7  order by mandant, notesid;

no rows selected

Elapsed: 00:00:01.23

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=1 Bytes=35)
   1    0   SORT (ORDER BY) (Cost=6 Card=1 Bytes=35)
   2    1     SORT (UNIQUE) (Cost=5 Card=1 Bytes=35)
   3    2       FILTER
   4    3         TABLE ACCESS (FULL) OF 'TESTLESERFELDGRUPPE' (Cost=3 Card=1
Bytes=35)
   5    3         TABLE ACCESS (FULL) OF 'TESTIDXDOKUMENT' (Cost=12 Card=303
Bytes=5454)
   6    3         TABLE ACCESS (FULL) OF 'TESTIDXVORGANG' (Cost=1 Card=9
Bytes=162)
   7    3         TABLE ACCESS (FULL) OF 'TESTIDXMAPPE' (Cost=1 Card=9
Bytes=162)

Statistics
----------------------------------------------------------
          0  recursive calls
       3304  db block gets
      32597  consistent gets
          0  physical reads
          0  redo size
        167  bytes sent via SQL*Net to client
        249  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          6  sorts (memory)
          0  sorts (disk)
          0  rows processed

Sql> alter session set always_anti_join = HASH;

Session altered.

Sql> select distinct mandant,notesid from testleserfeldgruppe where notesid not
in
  2  (select notesid from testidxdokument)
  3  and notesid not in
  4  (select notesid from testidxvorgang)
  5  and notesid not in
  6  (select notesid from testidxmappe)
  7  order by mandant, notesid;

no rows selected

Elapsed: 00:00:01.13

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=1 Bytes=35)
   1    0   SORT (ORDER BY) (Cost=6 Card=1 Bytes=35)
   2    1     SORT (UNIQUE) (Cost=5 Card=1 Bytes=35)
   3    2       FILTER
   4    3         TABLE ACCESS (FULL) OF 'TESTLESERFELDGRUPPE' (Cost=3 Card=1
Bytes=35)
   5    3         TABLE ACCESS (FULL) OF 'TESTIDXDOKUMENT' (Cost=12 Card=303
Bytes=5454)
   6    3         TABLE ACCESS (FULL) OF 'TESTIDXVORGANG' (Cost=1 Card=9
Bytes=162)
   7    3         TABLE ACCESS (FULL) OF 'TESTIDXMAPPE' (Cost=1 Card=9
Bytes=162)

Statistics
----------------------------------------------------------
          0  recursive calls
       3304  db block gets
      32597  consistent gets
          0  physical reads
          0  redo size
        167  bytes sent via SQL*Net to client
        249  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          6  sorts (memory)
          0  sorts (disk)
          0  rows processed

Regards

          Followup:

are those statistics close to right?  9 rows and such -- on such a trivial
amount of data -- all bets are off

either get real stats on there or load the tables with real data.

      Rating: 1
not in or not
      exists  November 06, 2002
Reviewer:
      Brigitte  from Germany
Hallo Tom,
here are more data.
1. Why has the alter session no effect. What's wrong? Some parameters?
2. what means: "elapsed 02:133:8032.30", I know hours:minutes:seconds
   Does it mean 2 hours + 13 minutes +...
             or 2 hours + 2 hours + 13 minutes +...

06.11.02 07:53    Anzahl der Tabellenzeilen       Seite:   1
                            dms

DMSIDXDOKUMENT               : 46220
DMSIDXMAPPE                  : 13
DMSIDXVORGANG                : 464
DMSLESERFELDGRUPPE           : 137369

Na so was> r
  1  select distinct mandant,notesid from dmsleserfeldgruppe l
  2  where not exists
  3  (select 'x' from dmsidxdokument
  4  where l.notesid = notesid)
  5  and not exists
  6  (select 'x' from dmsidxvorgang
  7  where l.notesid = notesid)
  8  and not exists
  9  (select 'x' from dmsidxmappe
 10  where l.notesid = notesid)
 11* order by mandant, notesid

no rows selected

Elapsed: 00:00:19.18

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=71 Card=25 Bytes=950)
   1    0   SORT (ORDER BY) (Cost=71 Card=25 Bytes=950)
   2    1     SORT (UNIQUE) (Cost=68 Card=25 Bytes=950)
   3    2       FILTER
   4    3         TABLE ACCESS (FULL) OF 'DMSLESERFELDGRUPPE' (Cost=65 Card=25
Bytes=950)
   5    3         INDEX (UNIQUE SCAN) OF 'UK_DMSIDXDOKUMENT' (UNIQUE) (Cost=1
Card=1 Bytes=32)
   6    3         INDEX (UNIQUE SCAN) OF 'UK_DMSIDXVORGANG' (UNIQUE) (Cost=1
Card=1 Bytes=31)
   7    3         INDEX (UNIQUE SCAN) OF 'UK_DMSIDXMAPPE' (UNIQUE)

Statistics
----------------------------------------------------------
        245  recursive calls
          7  db block gets
     149823  consistent gets
       1439  physical reads
          0  redo size
        161  bytes sent via SQL*Net to client
        246  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
         18  sorts (memory)
          0  sorts (disk)
          0  rows processed

Na so was> r
  1  select distinct mandant,notesid from dmsleserfeldgruppe where notesid not
in
  2  (select notesid from dmsidxdokument)
  3  and notesid not in
  4  (select notesid from dmsidxvorgang)
  5  and notesid not in
  6  (select notesid from dmsidxmappe)
  7* order by mandant, notesid

no rows selected

Elapsed: 02:133:8032.30

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=71 Card=25 Bytes=950)
   1    0   SORT (ORDER BY) (Cost=71 Card=25 Bytes=950)
   2    1     SORT (UNIQUE) (Cost=68 Card=25 Bytes=950)
   3    2       FILTER
   4    3         TABLE ACCESS (FULL) OF 'DMSLESERFELDGRUPPE' (Cost=65 Card=25
Bytes=950)
   5    3         TABLE ACCESS (FULL) OF 'DMSIDXDOKUMENT' (Cost=118 Card=2101
Bytes=67232)
   6    3         TABLE ACCESS (FULL) OF 'DMSIDXVORGANG' (Cost=1 Card=21
Bytes=651)
   7    3         TABLE ACCESS (FULL) OF 'DMSIDXMAPPE' (Cost=1 Card=2 Bytes=50)

Statistics
----------------------------------------------------------
          0  recursive calls
     464062  db block gets
  101189223  consistent gets
   12940155  physical reads
          0  redo size
        167  bytes sent via SQL*Net to client
        244  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          6  sorts (memory)
          0  sorts (disk)
          0  rows processed

Na so was> alter session set always_anti_join = MERGE;

Session altered.

Na so was> r
  1  select distinct mandant,notesid from dmsleserfeldgruppe where notesid not
in
  2  (select notesid from dmsidxdokument)
  3  and notesid not in
  4  (select notesid from dmsidxvorgang)
  5  and notesid not in
  6  (select notesid from dmsidxmappe)
  7* order by mandant, notesid

no rows selected

Elapsed: 02:133:8036.06

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=71 Card=25 Bytes=950)
   1    0   SORT (ORDER BY) (Cost=71 Card=25 Bytes=950)
   2    1     SORT (UNIQUE) (Cost=68 Card=25 Bytes=950)
   3    2       FILTER
   4    3         TABLE ACCESS (FULL) OF 'DMSLESERFELDGRUPPE' (Cost=65 Card=25
Bytes=950)
   5    3         TABLE ACCESS (FULL) OF 'DMSIDXDOKUMENT' (Cost=118 Card=2101
Bytes=67232)
   6    3         TABLE ACCESS (FULL) OF 'DMSIDXVORGANG' (Cost=1 Card=21
Bytes=651)
   7    3         TABLE ACCESS (FULL) OF 'DMSIDXMAPPE' (Cost=1 Card=2 Bytes=50)

Statistics
----------------------------------------------------------
          0  recursive calls
     464112  db block gets
  101219920  consistent gets
   13238978  physical reads
          0  redo size
        167  bytes sent via SQL*Net to client
        244  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          6  sorts (memory)
          0  sorts (disk)
          0  rows processed

Na so was> alter session set always_anti_join = HASH;

Session altered.

Na so was> select distinct mandant,notesid from dmsleserfeldgruppe where notesid
not in
  2  (select notesid from dmsidxdokument)
  3  and notesid not in
  4  (select notesid from dmsidxvorgang)
  5  and notesid not in
  6  (select notesid from dmsidxmappe)
  7  order by mandant, notesid;

no rows selected

Elapsed: 02:135:8144.32

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=71 Card=25 Bytes=950)
   1    0   SORT (ORDER BY) (Cost=71 Card=25 Bytes=950)
   2    1     SORT (UNIQUE) (Cost=68 Card=25 Bytes=950)
   3    2       FILTER
   4    3         TABLE ACCESS (FULL) OF 'DMSLESERFELDGRUPPE' (Cost=65 Card=25
Bytes=950)
   5    3         TABLE ACCESS (FULL) OF 'DMSIDXDOKUMENT' (Cost=118 Card=2101
Bytes=67232)
   6    3         TABLE ACCESS (FULL) OF 'DMSIDXVORGANG' (Cost=1 Card=21
Bytes=651)
   7    3         TABLE ACCESS (FULL) OF 'DMSIDXMAPPE' (Cost=1 Card=2 Bytes=50)

Statistics
----------------------------------------------------------
          0  recursive calls
     464327  db block gets
  101357656  consistent gets
   14016238  physical reads
          0  redo size
        167  bytes sent via SQL*Net to client
        244  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          6  sorts (memory)
          0  sorts (disk)
          0  rows processed

Regards

      Rating: 1
not in &
      not exists  November 08, 2002
Reviewer:
      Brigitte  from Germany
Hallo Tom,
I hope the data which I send on November 06 are enough.
I think a difference from 19 minutes to some hours is significant.
I'm very interested in your reply.

Regards

          Followup:

Brigitte -- for something so big, you'll need to do it as a question - this is
for followups, quick questions.  This is an entire question in a question.

      Rating: 5
Is the logic
      backwards for NOT IN and NOT EXISTS  November 15, 2002

Reviewer:  Doug  from CT, USA
Tom - this may be an oversimplification - however if I was to distill whether IN
or EXISTS was better I would say this after reading your reply.  An IN will
generally full table scan the subquery, and an EXISTS will generally full table
scan the outer query.  So, if my outer loop is small and my inner code is large
then an exists might be appropriate but if my outer loop is large and my inner
code is small (the match is quick).. then an IN might be appropriate.  Is that
correct?  If so.. is the situation completely reversed if using NOT in and NOT
exists?  I would guess is you were using not in.. you would have to full table
scan the OUTER query rather than sorting,hashing, the inner one.  You did cover
that NOT IN will return zero rows if it is NOT IN (null).. but I wasn't sure if
the psuedo-code for processing is backwards from the original examples if one is
using NOT IN and NOT EXISTS.

          Followup:

I encapsulate it with this:

...
So, when is where exists appropriate and in appropriate?

Lets say the result of the subquery
    ( select y from T2 )

is "huge" and takes a long time.  But the table T1 is relatively small and
executing ( select null from t2 where y = x.x ) is very very fast (nice index on

t2(y)).  Then the exists will be faster as the time to full scan T1 and do the
index probe into T2 could be less then the time to simply full scan T2 to build
the subquery we need to distinct on.

Lets say the result of the subquery is small -- then IN is typicaly more
appropriate.

If both the subquery and the outer table are huge -- either might work as well
as the other -- depends on the indexes and other factors.

.....

full scans -- no, they enter not into the picture at all IN GENERAL.  If the
subquery can use indexes, it'll use an index with IN or EXIST.

It is if the subquery is LARGE and TAKES A LONG TIME TO BUILD.... (regardless of
full scan)

Not in and not exists is even harder to characterize (pull my AOTC slides from
the home page for an interesting example -- they will be there for a while
around November of 2002 -- if you are reading this later then that, they might
not be there anymore)

      Rating: 3
How to make NOT
      EXISTS handle comparison of NULLs in same way as
      MINUS  March 13, 2003
Reviewer:  Jim Dickson
      (DBA/developer)  from London, UK
As ever, learn a lot from this site.

But one thing not addressed is how to make an antijoin deal with NULLs. The set
operators (UNION, MINUS) do this automatically.

But can it be done efficiently with NOT EXISTS or NOT IN.

e.g.
I need to compare 2 tables such that two rows are duplicates if corresponding
columns are either equal, or both contain NULL.

I cannot use MINUS since that columns being compared are a subset of the row,
but i need to return the whole row.

My understanding is that I cannot use NOT IN because it does not handle "null
comparison".

NOT EXISTS works but seems to use NESTED LOOPS => v slow
My understanding is that HASH antijoin will not work because HASH join only
works for equality tests (which fail for NULLs).

I can post example code if this helps.

          Followup:

both not exists and ESPECIALLLY not in do not do nulls like that.

not in and not exists don't even treat nulls the same in fact.  do this:

select * from emp where empno not in ( select mgr from emp );

and compare that to the "equivalent" not exists:

select * from emp where not exists ( select null from emp e2 where e2.mgr =
emp.empno );

you'll get a different answer.

You would have to tediously compare column by column by column

where (x = y or (x is null and y is null))

otherwise.

      Rating: 3
How to make NOT
      EXISTS handle comparison of NULLs in same way as
      MINUS  March 17, 2003
Reviewer:  Jim Dickson
       from London, UK
I absolutely agree but the question more about access paths than semantics.

I believe i understand the semantics surrounding nulls - which is why
MINUS/INTERSECT are such great operators.

I am prepared to write "tedious" column comparison.

Can I get Oracle to perform comparisons via a single-pass full table scan access
method (hash, sort-merge).

I have only seen Cartesian product/Nested Loops approach which obviously does
not scale with number of rows.

Is there any chance of Oracle implementing ANSI set operator extension
"CORRESPONDING BY {column-list}" ?

          Followup:

curious -- why cannot you simply use MERGE/INTERSECT?

      Rating: 3
How to make NOT
      EXISTS handle comparison of NULLs in same way as MINUS
        March 18, 2003
Reviewer:  Jim Dickson
       from London, UK
Because I am not merging into target table directly.

The sequence of processing is

1/    Read an external table
2/    Identify differences based on a subset of columns
3/    Write out differences to OS for further processing  (fuzzy logic on Name &
Address data)
4/    Read 2nd external table containing processed data and then "merge" into
target table.

Where I have a primary key, I can use MINUS for step 2.
But where there is no PK, then I am trying to get NOT EXISTS to scale.

Hope that makes sense.

BTW Is MERGE statement ANSI?

          Followup:

well, not following you by what you mean by "getting not exists to scale" -- but
give me an EXAMPLE of the syntax you would like to use and describe what would
be returned and I'll see if there isn't a way to code that...

Don't know if merge is "ansi".  Thing is NIST stop testing for ansi compliance
years ago, no one does it, the standard is so huge now that full compliance is
pretty much "not going to happen" and database indenpendence, well, I don't even
go there -- the only results I see from that over the last 15 years has been
applications that perform marginally well on a single database and blow chunks
on the rest.

      Rating: 4
How to make NOT
      EXISTS handle comparison of NULLs in same way as   March 19,
      2003
Reviewer:  Jim Dickson  from London, UK

Code posted below.

The NOT EXISTS does not include the SECONDARY column.

Ran with 100 rows, 1000 rows and 2389 rows.
On my machine

MINUS        100 rows  00.44 secs,    8  db block gets
            1000 rows  00.27 secs,    7  db block gets
            2389 rows  00.51 secs,    4  db block gets

NOT EXISTS   100 rows  00.43 secs,  208  db block gets
            1000 rows  02.90 secs, 2007  db block gets
            2389 rows  11.75 secs, 4780  db block gets

I need this to run with 10m rows.

spool not_exists.2389.out

set timing on
set echo on

drop   table t1;
drop   table t2;

create table t1 as select * from all_objects where rownum <= 2500;
create table t2 as select * from all_objects where rownum <= 2500;

analyze table t1 compute statistics;
analyze table t2 compute statistics;

--desc t1
--desc t2

insert into t1 values ('EXTRA ROW', null, null, null, null, null, null, null,
null, null, null, null, null);

select count(*) from t1;
select count(*) from t2;

set autotrace on

select count(*) from
(
  select * from t1
  minus
  select * from t2
);

select count(*) from t1 where NOT EXISTS
( select null from t2 where
  ( t1.OWNER = t2.OWNER or ( t1.OWNER is null and t2.OWNER is null ) )
  and
  ( t1.OBJECT_NAME = t2.OBJECT_NAME or ( t1.OBJECT_NAME is null and
t2.OBJECT_NAME is null ) )
  and
  ( t1.SUBOBJECT_NAME = t2.SUBOBJECT_NAME or ( t1.SUBOBJECT_NAME is null and
t2.SUBOBJECT_NAME is null ) )
  and
  ( t1.OBJECT_ID = t2.OBJECT_ID or ( t1.OBJECT_ID is null and t2.OBJECT_ID is
null ) )
  and
  ( t1.DATA_OBJECT_ID = t2.DATA_OBJECT_ID or ( t1.DATA_OBJECT_ID is null and
t2.DATA_OBJECT_ID is null ) )
  and
  ( t1.OBJECT_TYPE = t2.OBJECT_TYPE or ( t1.OBJECT_TYPE is null and
t2.OBJECT_TYPE is null ) )
  and
  ( t1.CREATED = t2.CREATED or ( t1.CREATED is null and t2.CREATED is null ) )
  and
  ( t1.LAST_DDL_TIME = t2.LAST_DDL_TIME or ( t1.LAST_DDL_TIME is null and
t2.LAST_DDL_TIME is null ) )
  and
  ( t1.TIMESTAMP = t2.TIMESTAMP or ( t1.TIMESTAMP is null and t2.TIMESTAMP is
null ) )
  and
  ( t1.STATUS = t2.STATUS or ( t1.STATUS is null and t2.STATUS is null ) )
  and
  ( t1.TEMPORARY = t2.TEMPORARY or ( t1.TEMPORARY is null and t2.TEMPORARY is
null ) )
  and
  ( t1.GENERATED = t2.GENERATED or ( t1.GENERATED is null and t2.GENERATED is
null ) )
)
;

spool off
exit

As ever, thanks.

Think the whole Oracle community is disappointed that the new book will be
delayed.

          Followup:

what I meant by:

 but
give me an EXAMPLE of the syntax you would like to use and describe what would
be returned and I'll see if there isn't a way to code that...

was -- give me a scenario where you want to use not exists instead of minus (eg:
in the above, i certainly would NOT use not exists) and I'll see what I can do
with that.

That is -- give me a real case to work with whereby minus cannot be used.

      Rating:
      4
  March 21, 2003
Reviewer:  green

Hi Tom,
Can you please explain a litte bit 'NOT IN' here? Where  can  it be used
properly?

Thanks!

          Followup:

I don't understand?

not in can be used anywhere you need "not in" -- where "record" not in "set".

      Rating: 2
How to use
      MINUS on subset of columns  March 27, 2003

Reviewer:  Jim Dickson  from London, UK
I use MINUS extensively but in certain situations I believe it is unsuitable.

e.g.

When trying to find the difference between 2 tables, where at least 1 of tables
lack a primary key,
using a subset of columns for comparison and some of those columns can be null.

The real life example is comparing a new list of suppressions against an
existing table of suppressions.
(Suppressions used in marketing to avoid target customers who do not wish to
receive offers).
The ddl is pretty much address1-10, suppression_flags1-10.
The comparison is done on address lines only but return set must be all columns.

Hope that makes sense.

The ANSI syntax i would love to use is

select * from suppressions_new
except corresponding by (address1, address2, ...., address10)
select * from suppressions_old

Suppressions_new will have no primary key.

How can i achieve this in Oracle using MINUS instead of EXCEPT.

If it cannot be done using MINUS, then how to achieve reasonable performance
using NOT EXISTS.

          Followup:

Ok, I have a big_table (4million rows) and a sample of that (400k rows).  Using
NVL() on columns that "might be nullable" and supplying an unreasonable value
(eg: chr(0) for strings, 99999999999999999999999999 for numbers,
to_date(01010001,'ddmmyyyyy') for dates) you can anti join with NOT IN like
this:

select *
  from big_table
 where (object_id, nvl(subobject_name,chr(0)) )
   NOT IN (select object_id, nvl(subobject_name,chr(0))
             from ten_percent
            where nvl(subobject_name,chr(0)) IS NOT NULL )
   and nvl(subobject_name,chr(0)) is not 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        1    114.65     248.86     118052      60214          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3    114.65     248.86     118052      60214          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 60

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  HASH JOIN ANTI (cr=60214 r=118052 w=58940 time=77317035 us)
3817191   TABLE ACCESS FULL BIG_TABLE (cr=54700 r=54649 w=0 time=14975098 us)
 381274   TABLE ACCESS FULL TEN_PERCENT (cr=5514 r=4463 w=0 time=1360329 us)

Now, that "scales" just as well as:

select *
  from big_table
 where (object_id, object_name )
   NOT IN (select object_id, object_name
             from ten_percent )

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    114.19     224.33     117115      60213          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3    114.19     224.33     117115      60213          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 60

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  HASH JOIN ANTI (cr=60213 r=117115 w=57995 time=224338032 us)
3817191   TABLE ACCESS FULL BIG_TABLE (cr=54699 r=54657 w=0 time=16561038 us)
 381274   TABLE ACCESS FULL TEN_PERCENT (cr=5514 r=4463 w=0 time=1308385 us)

without the NVL's.

      Rating: 5
Use of
      "nvl(subobject_name,chr(0)) IS NOT NULL "  March 27, 2003

Reviewer:  Logan Palanisamy  from Santa Clara, CA USA

Tom,

In your example,

select *
  from big_table
 where (object_id, nvl(subobject_name,chr(0)) )
   NOT IN (select object_id, nvl(subobject_name,chr(0))
             from ten_percent
            where nvl(subobject_name,chr(0)) IS NOT NULL )
   and nvl(subobject_name,chr(0)) is not null

1. What does chr(0) translate to? When I run "select chr(0) from dual", it
returns me an invisble character. My db character set is WE8ISO8859P15

2. What does "nvl(subobject_name,chr(0)) IS NOT NULL" do? Doesn't it evaluate to
TRUE always, since chr(0) is a not null value?

3. Also, the number of rows returned as shown in TKPROF output is zero. How
come? Shouldn't return about 3.6 millon rows?

4. I am familiar with TKPROF. But what does the name really stand for? Is it an
acronym for something?

Grateful as ever.

          Followup:

1) exactly -- it is just "\0" in C, a null string.  It is an improbable
character, something subobject_name cannot be...

2) exactly -- I'm telling the optimizer "this query returns only data that is
NOT NULL".  That permits the use of the anti-join which is what our goal was
here.

3) no, there were no rows "not in" the other set.

4) tool kit profiler is the rumorous name I've heard it refered to as.

      Rating: 3
LIKE.... OR
      LIKE.... OR  May 12, 2003
Reviewer:  Robert
       from PA
Tom,
Is there a better way (with a sub-query) to do
LIKE .... OR LIKE ... OR LIKE ... ?
e.g.:
select * from user_objects where
OBJECT_TYPE LIKE 'PROC%' OR OBJECT_TYPE  LIKE 'FUNC%' OR OBJECT_TYPE  LIKE
'VIEW%" ?

Thanks

          Followup:

that would be the most "optimal" way normally -- it could permit a normal index
on object_type to be used.

You could shorten it to

where substr( object_type, 1, 4 ) in ( 'PROC', 'FUNC', 'VIEW' )

in your case -- and if you had a function based index on substr(object_type,1,4)
it could use an index.

      Rating: 4
EXISTS and
      FTS  May 14, 2003
Reviewer:  Nitin
       from Atlanta
I tried the following.
SQL> create table outer (a number);

Table created.

SQL> create index outer_ind on outer (a);

Index created.

SQL> create table inner (b number);

Table created.

SQL> create index inner_ind on inner (b);

Index created.

SQL> select 'x' from outer where exists (select 'x' from inner where outer.a =
inner.b);

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   FILTER
   2    1     TABLE ACCESS (FULL) OF 'OUTER'
   3    1     INDEX (RANGE SCAN) OF 'INNER_IND' (NON-UNIQUE)

SQL> analyze table outer compute statistics;

Table analyzed.

SQL> analyze table inner compute statistics;

Table analyzed.

SQL> select 'x' from outer where exists (select 'x' from inner where outer.a =
inner.b);

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=13)
   1    0   FILTER
   2    1     TABLE ACCESS (FULL) OF 'OUTER' (Cost=1 Card=1 Bytes=13)
   3    1     INDEX (RANGE SCAN) OF 'INNER_IND' (NON-UNIQUE) (Cost=1 C
          ard=1 Bytes=13)

SQL> insert into outer select OBJECT_ID from user_objects;

40899 rows created.

SQL> commit;

Commit complete.

SQL> analyze table outer compute statistics;

Table analyzed.

SQL> select 'x' from outer where exists (select 'x' from inner where outer.a =
inner.b);

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=2045 Bytes=81
          80)

   1    0   FILTER
   2    1     TABLE ACCESS (FULL) OF 'OUTER' (Cost=4 Card=2045 Bytes=8
          180)

   3    1     INDEX (RANGE SCAN) OF 'INNER_IND' (NON-UNIQUE) (Cost=1 C
          ard=1 Bytes=13)

Should it not perform an index scan on the OUTER table?

Regards

Nitin

          Followup:

why would it?

every row must be inspected.
the rows are very narrow
the table is SMALLER then the index (the index has 2 columns -- the column and
the rowid, the table is just a column)

use "real" examples.

ops$tkyte@ORA920LAP> create table t1 as select * from all_objects;

Table created.

ops$tkyte@ORA920LAP> create index t1_idx on t1(object_id);

Index created.

ops$tkyte@ORA920LAP> analyze table t1 compute statistics
  2  for table for all indexes for all indexed columns;

Table analyzed.

ops$tkyte@ORA920LAP> create table t2 as select * from all_objects where 1=0;

Table created.

ops$tkyte@ORA920LAP> create index t2_idx on t2(object_id);

Index created.

ops$tkyte@ORA920LAP> analyze table t1 compute statistics
  2  for table for all indexes for all indexed columns;

Table analyzed.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> set autotrace traceonly explain
ops$tkyte@ORA920LAP> select * from t1
  2  where exists ( select null from t2 where t2.object_id = t1.object_id );

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=168 Card=82 Bytes=8938)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=2 Card=1 Bytes=96)
   2    1     NESTED LOOPS (Cost=168 Card=82 Bytes=8938)
   3    2       SORT (UNIQUE)
   4    3         TABLE ACCESS (FULL) OF 'T2' (Cost=2 Card=82 Bytes=1066)
   5    2       INDEX (RANGE SCAN) OF 'T1_IDX' (NON-UNIQUE) (Cost=1 Card=1)

ops$tkyte@ORA920LAP> set autotrace on

      Rating: 4
Still
      FTS  May 15, 2003
Reviewer:  Nitin

I replicated all the steps performed by you. And I am getting the following
execution plan.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=55 Card=3515 Bytes=2
          70655)

   1    0   FILTER
   2    1     TABLE ACCESS (FULL) OF 'T1' (Cost=55 Card=3515 Bytes=270
          655)

   3    1     INDEX (RANGE SCAN) OF 'T2_IDX' (NON-UNIQUE) (Cost=1 Card
          =1 Bytes=13)

The db is 8.1.7.4. Is is possible that there is some init.ora parameter which is
dictating the above FTS execution plan?

Thanks

          Followup:

that is fundementally the same plan I had -- so?

it needs to full scan, mine full scanned.

tell me -- what would the "straw man plan" you think would be better be exactly?

      Rating: 3
Still
      FTS  May 15, 2003
Reviewer:  Nitin

The difference is that the optimizer is choosing to perform FTS on T1 (which is
huge) and use index scan on T2 which is very small.

Regards

          Followup:

ops$tkyte@ORA817DEV> set autotrace traceonly explain
ops$tkyte@ORA817DEV> select * from t1, ( select distinct object_id from t2 ) t2
  2  where t1.object_id = t2.object_id;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=1 Bytes=109)
   1    0   NESTED LOOPS (Cost=5 Card=1 Bytes=109)
   2    1     VIEW (Cost=3 Card=1 Bytes=13)
   3    2       SORT (UNIQUE) (Cost=3 Card=1 Bytes=13)
   4    3         INDEX (FULL SCAN) OF 'T2_IDX' (NON-UNIQUE)
   5    1     TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=2 Card=26793
Bytes=2572128)
   6    5       INDEX (RANGE SCAN) OF 'T1_IDX' (NON-UNIQUE) (Cost=1 Card=26793)

in 8i seems to be the way to go -- things change, the optimizer gets smarter

      Rating: 4
What about with
      updates?  May 21, 2003
Reviewer:  Brett
       from Australia
Tom,

How does IN with a subquery affect performance of updates - is the subquery
executed once for each row (which could be really inefficient), or once for the
query. eg:

update table1
   set column_1 = 'Y'
 where row_id in (select row_id
                    from table1 t1,
                         table2 t2
                   where t1.f_key = t2.row_id
                     and t2.class_cd = 'Address'
                     and t2.created > sysdate - 30);

Is this subquery executed only once for the entire update? Or would it be better
to use the subquery to insert the data into a temporary table with a unique
constraint/primary key and then use an updatable view based on table1 and the
temporary table?

          Followup:

the subquery, since it is not a correlated subquery, would tend to be done
"once"

Now, if t2.row_id is UNIQUE, and there is a unique/primary key on it I would
code:

ops$tkyte@ORA920> update ( select column_1
  2             from t1, t2
  3                    where t1.f_key = t2.row_id
  4                      and t2.class_cd = 'Address'
  5                          and t2.created > sysdate-30 )
  6    set column_1 = 'Y'
  7  /

      Rating: 4
Are these
      same?  May 22, 2003
Reviewer:  Nitin

Are the following SQL's similar? Will they retrieve same set of data under all
circumstances?

1.

select
APRCN_ID_BI, APPRAISAL_DT, RANK_ID
from APPRECIATN_TBL WHERE
 (APRCN_ID, APRCN_ID, ACTIVITY_ID, NOTES_ID)   in  ( SELECT
APRCN_ID, APRCN_ID, ACTIVITY_ID, NOTES_ID
FROM APPRECIATN_TBL_TMP TMP WHERE
  APPRECIATN_TBL.APRCN_ID = TMP.APRCN_ID AND
  APPRECIATN_TBL.APRCN_ID = TMP.APRCN_ID AND
  APPRECIATN_TBL.ACTIVITY_ID = TMP.ACTIVITY_ID AND
  APPRECIATN_TBL.NOTES_ID = TMP.NOTES_ID AND TMP.PROCESS_INSTANCE =
  363480 AND TMP.APRCN_ID_BI = '52000' AND TMP.APPRAISAL_DT =
  TO_DATE('2003-01-19','YYYY-MM-DD') AND TMP.RANK_ID = '55053')

2.

select
APRCN_ID_BI, APPRAISAL_DT, RANK_ID
from APPRECIATN_TBL WHERE
EXISTS  ( SELECT
APRCN_ID, APRCN_ID, ACTIVITY_ID, NOTES_ID
FROM APPRECIATN_TBL_TMP TMP WHERE
  APPRECIATN_TBL.APRCN_ID = TMP.APRCN_ID AND
  APPRECIATN_TBL.APRCN_ID = TMP.APRCN_ID AND
  APPRECIATN_TBL.ACTIVITY_ID = TMP.ACTIVITY_ID AND
  APPRECIATN_TBL.NOTES_ID = TMP.NOTES_ID AND TMP.PROCESS_INSTANCE =
  363480 AND TMP.APRCN_ID_BI = '52000' AND TMP.APPRAISAL_DT =
  TO_DATE('2003-01-19','YYYY-MM-DD') AND TMP.RANK_ID = '55053')

Regards

          Followup:

they are the same, but the first is less efficient by far.  a correlated "IN",
wouldn't do that.

      Rating:
      5
query  May 22, 2003
Reviewer:  mo

Tom:

Do you know why the first query does not produce any results while the other
does:

select request_id from shipm_request where request_id not in (Select distinct
request_id from shipment);

select request_id from shipm_request a where request_id not exists (select
request_id from shipment b where a.request_id = b.request_id);

Table shipm_request(request_id) has one to many relationship with table
shipment(shipment_id).
Thanks

          Followup:

lookup upwards for this text:

No, partially because NOT IN and NOT EXISTS are not substitutes for eachother!

and read that.  it is all about NULL

      Rating: 2
Proof IN /
      EXISTS  August 29, 2003
Reviewer:  Marcio
       from br
Tom, I did test to prove to my colleague the theory of IN/EXISTS exposed here
(He just ignore it and ask to developers to use IN always -- to improve
performance).
But when I executed -- to me they are same!
take a look

ops$t_mp00@MRP9I1> drop table small;

Table dropped.

ops$t_mp00@MRP9I1> drop table big;

Table dropped.

ops$t_mp00@MRP9I1> ed teste_in_exist

ops$t_mp00@MRP9I1> create table big as select * from all_objects;

Table created.

ops$t_mp00@MRP9I1> insert /*+ append */ into big select * from big;

31317 rows created.

ops$t_mp00@MRP9I1> commit;

Commit complete.

ops$t_mp00@MRP9I1> insert /*+ append */ into big select * from big;

62634 rows created.

ops$t_mp00@MRP9I1> commit;

Commit complete.

ops$t_mp00@MRP9I1> insert /*+ append */ into big select * from big;

125268 rows created.

ops$t_mp00@MRP9I1> commit;

Commit complete.

ops$t_mp00@MRP9I1> create index big_idx on big(object_id);

Index created.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> create table small as select * from all_objects where rownum
< 100;

Table created.

ops$t_mp00@MRP9I1> create index small_idx on small(object_id);

Index created.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> analyze table big compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns
  5  /

Table analyzed.

ops$t_mp00@MRP9I1> analyze table small compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns
  5  /

Table analyzed.

ops$t_mp00@MRP9I1> exit
Disconnected from 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

c:\migracao\loads>plus /

SQL*Plus: Release 9.2.0.2.0 - Production on Fri Aug 29 14:07:08 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

ops$t_mp00@MRP9I1> @teste_in_exist
ops$t_mp00@MRP9I1> rem create table big as select * from all_objects;
ops$t_mp00@MRP9I1> rem insert /*+ append */ into big select * from big;
ops$t_mp00@MRP9I1> rem commit;
ops$t_mp00@MRP9I1> rem insert /*+ append */ into big select * from big;
ops$t_mp00@MRP9I1> rem commit;
ops$t_mp00@MRP9I1> rem insert /*+ append */ into big select * from big;
ops$t_mp00@MRP9I1> rem commit;
ops$t_mp00@MRP9I1> rem create index big_idx on big(object_id);
ops$t_mp00@MRP9I1> rem
ops$t_mp00@MRP9I1> rem
ops$t_mp00@MRP9I1> rem create table small as select * from all_objects where
rownum < 100;
ops$t_mp00@MRP9I1> rem create index small_idx on small(object_id);
ops$t_mp00@MRP9I1> rem
ops$t_mp00@MRP9I1> rem analyze table big compute statistics
ops$t_mp00@MRP9I1> rem for table
ops$t_mp00@MRP9I1> rem for all indexes
ops$t_mp00@MRP9I1> rem for all indexed columns
ops$t_mp00@MRP9I1> rem /
ops$t_mp00@MRP9I1> rem analyze table small compute statistics
ops$t_mp00@MRP9I1> rem for table
ops$t_mp00@MRP9I1> rem for all indexes
ops$t_mp00@MRP9I1> rem for all indexed columns
ops$t_mp00@MRP9I1> rem /
ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> alter session set sql_trace = true;

Session altered.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> select count(subobject_name)
  2    from big
  3   where object_id in ( select object_id from small )
  4  /

COUNT(SUBOBJECT_NAME)
---------------------
                    0

1 row selected.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> select count(subobject_name)
  2    from big
  3   where exists ( select null from small where small.object_id =
big.object_id )
  4  /

COUNT(SUBOBJECT_NAME)
---------------------
                    0

1 row selected.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> select count(subobject_name)
  2    from small
  3   where object_id in ( select object_id from big )
  4  /

COUNT(SUBOBJECT_NAME)
---------------------
                    0

1 row selected.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> select count(subobject_name)
  2    from small
  3   where exists ( select null from big where small.object_id = big.object_id
)
  4  /

COUNT(SUBOBJECT_NAME)
---------------------
                    0

1 row selected.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> alter session set sql_trace = false;

Session altered.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> set echo off
ops$t_mp00@MRP9I1> host tkprof e:\oracle\admin\mrp9i1\udump\mrp9i1_ora_1876.trc
sys=n output=tkprof.txt explain=/

TKPROF: Release 9.2.0.2.1 - Production on Fri Aug 29 14:07:57 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

ops$t_mp00@MRP9I1>

tkprof.txt
==========

select count(subobject_name)
  from big
 where object_id in ( select object_id from small )

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      7.11       7.45          0       3424          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      7.13       7.47          0       3424          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 102  (OPS$T_MP00)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=3424 r=0 w=0 time=7457761 us)
    792   NESTED LOOPS SEMI (cr=3424 r=0 w=0 time=7454167 us)
 250536    TABLE ACCESS FULL BIG (cr=3422 r=0 w=0 time=1579621 us)
    792    INDEX RANGE SCAN SMALL_IDX (cr=2 r=0 w=0 time=2667651 us)(object id
40890)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   SORT (AGGREGATE)
    792    NESTED LOOPS (SEMI)
 250536     TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'BIG'
    792     INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'SMALL_IDX'
                (NON-UNIQUE)

********************************************************************************

select count(subobject_name)
  from big
 where exists ( select null from small where small.object_id = big.object_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      6.99       6.99          0       3421          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      7.00       7.00          0       3421          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 102  (OPS$T_MP00)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=3421 r=0 w=0 time=6995659 us)
    792   NESTED LOOPS SEMI (cr=3421 r=0 w=0 time=6992099 us)
 250536    TABLE ACCESS FULL BIG (cr=3419 r=0 w=0 time=1473069 us)
    792    INDEX RANGE SCAN SMALL_IDX (cr=2 r=0 w=0 time=2557279 us)(object id
40890)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   SORT (AGGREGATE)
    792    NESTED LOOPS (SEMI)
 250536     TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'BIG'
    792     INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'SMALL_IDX'
                (NON-UNIQUE)

********************************************************************************

select count(subobject_name)
  from small
 where object_id in ( select object_id from big )

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.01       0.00          0        105          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.02       0.01          0        105          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 102  (OPS$T_MP00)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=105 r=0 w=0 time=7280 us)
     99   NESTED LOOPS SEMI (cr=105 r=0 w=0 time=6659 us)
     99    TABLE ACCESS FULL SMALL (cr=4 r=0 w=0 time=886 us)
     99    INDEX RANGE SCAN BIG_IDX (cr=101 r=0 w=0 time=3305 us)(object id
40888)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   SORT (AGGREGATE)
     99    NESTED LOOPS (SEMI)
     99     TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'SMALL'
     99     INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'BIG_IDX' (NON-UNIQUE)

********************************************************************************

select count(subobject_name)
  from small
 where exists ( select null from big where small.object_id = big.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.01       0.00          0        105          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.01          0        105          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 102  (OPS$T_MP00)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=105 r=0 w=0 time=7255 us)
     99   NESTED LOOPS SEMI (cr=105 r=0 w=0 time=6643 us)
     99    TABLE ACCESS FULL SMALL (cr=4 r=0 w=0 time=863 us)
     99    INDEX RANGE SCAN BIG_IDX (cr=101 r=0 w=0 time=3243 us)(object id
40888)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   SORT (AGGREGATE)
     99    NESTED LOOPS (SEMI)
     99     TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'SMALL'
     99     INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'BIG_IDX' (NON-UNIQUE)

To me they are same!
Any thought?

          Followup:

yes, the cbo is much much smarter then the rbo ever was, i should have caveated
that.  using the rbo -- very different.  using the cbo -- all fast, you need not
be as concerned about small things like this as much

      Rating: 5
Query
      Result  September 03, 2003
Reviewer:  AVS
       from India
Will the two queries metioned below fetch me the same result ?

Query : 1     SELECT /*+ index(ord_l2) */
       MAX(ord.data_date) data_date,
           us.person_id,
           MAX(us.level_1) ouc,
           MAX(ord.taker_user_id) css_user_id,
           MAX(ord.week_number) week_no,
           SUM(DECODE(ord.create_type,'M',
            DECODE(mv1.measure,'OSM00091',0,'OSM00096',0,ord_l2.quantity),
            ord_l2.quantity)) total_quantity,
       SUM(ord_l2.SPECIAL_PAYMENT_CHARGE) * 100 Money_value,
           mv1.measure measure,
       us.mgr_ouc
      FROM poc1  ord,
           fcih  us,
           polc1  ord_l2,
           fmv  mv1
     WHERE ord.taker_user_id = us.css_user_id
       AND ord.create_type IN ('P', 'G', 'M')
       AND ord.data_date = report_date
       AND ord.sub_zone LIKE report_sub_zone
       AND ord.order_status         != 'OL'
       AND ord_l2.input_cont_type     IN  ('O', 'R', 'S1')
       AND ord_l2.order_number = ord.order_number
       AND ord_l2.data_date    = ord.data_date
       AND ord_l2.sub_zone     = ord.sub_zone
       AND ord_l2.product_id = mv1.column_value
       AND ord_l2.action_id NOT IN ('C', 'R', 'S')
       AND ord_l2.status != 'X'
       AND mv1.source_system = 'FCH'
       AND (mv1.measure = 'OSM00100' OR mv1.measure LIKE ('OSM0009%'))
       AND mv1.column_name = 'PRODUCT_ID'
       AND EXISTS (SELECT 'x'
                     FROM polc1  ord_l,
                          fmv  mv2
                    WHERE ord_l.input_cont_type     IN  ('O', 'R', 'S1')
                AND ord_l.order_number = ord.order_number
                      AND ord_l.data_date    = ord.data_date
                      AND ord_l.sub_zone     = ord.sub_zone
                      AND ord_l.product_id = mv2.column_value
                      AND ord_l.action_id NOT IN ('C', 'R', 'S')
                      AND ord_l.status != 'X'
                      AND mv2.source_system = 'FCH'
                      AND (mv2.measure ='OSM00100' OR mv2.measure LIKE
('OSM0009%'))
                      AND mv2.column_name = 'PRODUCT_ID'
                      AND EXISTS (SELECT 'x'
                                    FROM fmv  mv3
                                   WHERE ord_l.sales_channel = mv3.column_value
                                     AND mv3.source_system = 'FCH'
                                     AND mv3.measure = mv2.measure
                                     AND mv3.column_name = 'SALES_CHANNEL'))
  GROUP BY person_id, measure, mgr_ouc;

Query 2 :    SELECT /*+ index(ord_l2) */
       MAX(ord.data_date) data_date,
           us.person_id,
           MAX(us.level_1) ouc,
           MAX(ord.taker_user_id) css_user_id,
           MAX(ord.week_number) week_no,
           SUM(DECODE(ord.create_type,'M',
            DECODE(mv1.measure,'OSM00091',0,'OSM00096',0,ord_l2.quantity),
            ord_l2.quantity)) total_quantity,
       SUM(ord_l2.SPECIAL_PAYMENT_CHARGE) * 100 Money_value,
           mv1.measure measure,
       us.mgr_ouc
      FROM poc1  ord,
           fcih  us,
           polc1  ord_l2,
           fmv  mv1
     WHERE ord.taker_user_id = us.css_user_id
       AND ord.create_type IN ('P', 'G', 'M')
       AND ord.data_date = report_date
       AND ord.sub_zone LIKE report_sub_zone
       AND ord.order_status         != 'OL'
       AND ord_l2.input_cont_type     IN  ('O', 'R', 'S1')
       AND ord_l2.order_number = ord.order_number
       AND ord_l2.data_date    = ord.data_date
       AND ord_l2.sub_zone     = ord.sub_zone
       AND ord_l2.product_id = mv1.column_value
       AND ord_l2.action_id NOT IN ('C', 'R', 'S')
       AND ord_l2.status != 'X'
       AND mv1.source_system = 'FCH'
       AND (mv1.measure = 'OSM00100' OR mv1.measure LIKE ('OSM0009%'))
       AND mv1.column_name = 'PRODUCT_ID'
       AND EXISTS (SELECT 'x'
                   FROM fmv  mv3
                   WHERE ord_l.sales_channel = mv3.column_value
                   AND mv3.source_system = 'FCH'
                   AND mv3.measure = mv1.measure
                   AND mv3.column_name = 'SALES_CHANNEL')
  GROUP BY person_id, measure, mgr_ouc;

          Followup:

i don't know, hows about you tell us what is different between them.

they are pretty big - point out the salient changes and why you made them.

      Rating: 3
Query Tuning
      (Rewriting).  September 05, 2003
Reviewer:
      AVS  from India
The Query 1 looks like

SELECT XXXXXX FROM A,B,C,D       /* PART 1 of QUERY 1 */
WHERE CONDITION1
AND   CONDITION2
AND   CONDITION3
AND   CONDITION4
AND   CONDITION5
AND   CONDITION6
AND   CONDITION7
AND   CONDITION8
AND   CONDITION9
AND   CONDITION10
AND   CONDITION11
AND   CONDITION12
AND   CONDITION13
AND   CONDITION14
AND   CONDITION15
AND   EXISTS
       (SELECT x FROM C,D        /* PART 2 of QUERY 1 */
        WHERE CONDITION1
        AND   CONDITION2
        AND   CONDITION3
        AND   CONDITION4
        AND   CONDITION5
        AND   CONDITION6
        AND   CONDITION7
        AND   CONDITION8
        AND   CONDITION9
        AND EXIST
           (SELECT 'x' FROM C,D /* PART 3 of QUERY 1 */
            WHERE CONDITION1
            AND   CONDITION2
            AND   CONDITION3
            AND   CONDITION4 )

I found that the PART 2's (FIRST EXISTS) WHERE conditions is repeatation of
PART1's condtions. Hence I want to eliminate it, which I've done in QUERY 2.

The PART1 of Query1 without EXISTS fetch me reults in 5 minutes but with SINGLE
EXISTS (QUERY 2) it runs for 4 hours .

So I wrote another yet query by creating a new table, NEWTAB

/*   CREATE TABLE NEWTAB AS
SELECT * FROM fch
WHERE SOURCE_SYSTEM = 'FCH'
and   COLUMN_NAME = 'SALES_COLUMN'  */

Query 3 :

    SELECT /*+ index(ord_l2) */
       MAX(ord.data_date) data_date,
           us.person_id,
           MAX(us.level_1) ouc,
           MAX(ord.taker_user_id) css_user_id,
           MAX(ord.week_number) week_no,
           SUM(DECODE(ord.create_type,'M',
            DECODE(mv1.measure,'OSM00091',0,'OSM00096',0,ord_l2.quantity),
            ord_l2.quantity)) total_quantity,
       SUM(ord_l2.SPECIAL_PAYMENT_CHARGE) * 100 Money_value,
           mv1.measure measure,
       us.mgr_ouc
      FROM PSTN_ORDER_C_1@qdwp ord,
           fch_user_id_hier@qdwp us,
           PSTN_ORDER_LINE_C_1@qdwp ord_l2,
           fch_measure_values@qdwp mv1, NEWTAB mv3
     WHERE ord.taker_user_id = us.css_user_id
       AND ord.create_type IN ('P', 'G', 'M')
       AND ord.data_date = report_date
       AND ord.sub_zone LIKE report_sub_zone
       AND ord.order_status         != 'OL'
       AND ord_l2.input_cont_type     IN  ('O', 'R', 'S1')
       AND ord_l2.order_number = ord.order_number
       AND ord_l2.data_date    = ord.data_date
       AND ord_l2.sub_zone     = ord.sub_zone
       AND ord_l2.product_id = mv1.column_value
       AND ord_l2.action_id NOT IN ('C', 'R', 'S')
       AND ord_l2.status != 'X'
       AND mv1.source_system = 'FCH'
       AND (mv1.measure = 'OSM00100' OR mv1.measure LIKE ('OSM0009%'))
       AND mv1.column_name = 'PRODUCT_ID'
       AND ord_l2.sales_channel = mv3.column_value
       AND mv1.measure = mv3.measure
  GROUP BY person_id, mv1.measure, mgr_ouc;

This query (QUERY 3) also takes 4 hours.

Hope you are now clear, what I want to achieve !
Please Help.

          Followup:

what do you want to achieve?

help with what?

if this is "please tune my query" -- not here, this is for followups on the
original question.

      Rating: 1
Query
      Rewriting  September 08, 2003
Reviewer:  AVS
       from India
Yes you are right I was trying to tune the query, for which I required your
help. Since it consisted of removing of EXIST clause from one of the query I
thought its continuation of the original subject(IN & EXISTS). If it was
annoying I'm sorry.

      Rating: 4
Query Execution
      takes forever using IN operator  November 07, 2003

Reviewer:  TS  from USA
Tom,

I 've a query like this:-

select id from table1
where ( contains( name, ' $?AB and $?CD and $?EF ' ) > 0)
and id in ('1234','4567','97343','0934',4576','6376''5017', '3930', '5099',
'5089', '5061', '4753', '3646', '3357', '3047', '8059','4124', '17591','14700',
'60973', '34987', '37586', '58985', '34874', '49161', '11489');

the name column which has an intermedia index  is being used during query
execution but the index on ID is not being used.
The query takes a long time to execute. I tried using
RBO, but still was slow. Eventually I tried deleting the
statistics using "analyze table1 delete statistics" and the
query was very fast. So,do I need to delete the statistics
to make it run faster(my only concern is this query might run faster after stats
delete but some other query hitting the same table might become slower),or is
there a way to rewrite the query to make it run faster?

Thanks for your help.
TS

          Followup:

well, a query with contains is going to use CBO no matter what (so, you never
used RBO, cannot be done!)

Hows about you show us

a) tkprof AND autotrace traceonly explain of the query with stats
b) tkprof AND autorace traceonly explain of the query without stats

      Rating: 4
Followup: Query
      Execution takes forever using IN operator   November 12,
      2003
Reviewer:  TS  from USA
Tom,

Here 's the tkprof output running the query,

Without Stats:-
==============
select id from table1
where ( contains( name, ' $?AB and $?CD and $?EF ' ) > 0)
and id in ('1234','4567','97343','0934',4576','6376''5017', '3930', '5099',
'5089', '5061', '4753', '3646', '3357', '3047', '8059','4124', '17591','14700',
'60973', '34987', '37586', '58985', '34874', '49161', '11489');

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=88)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'TABLE1' (Cost=1 Card=1 B
      ytes=88)

   2    1     DOMAIN INDEX OF 'NAME_TXT_IND' (Cost=1 Card=1)

from tkprof output:-
===================
EXPLAIN PLAN SET STATEMENT_ID='PLUS1583908' FOR select id from table1
where ( contains( name, ' $?AB and $?CD and $?EF ' ) > 0)
and id in ('1234','4567','97343','0934',4576','6376''5017', '3930', '5099',
'5089', '5061', '4753', '3646', '3357', '3047', '8059','4124', '17591','14700',
'60973', '34987', '37586', '58985', '34874', '49161', '11489');

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0         92          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         92          0           0

With Stats:-
==========
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=88)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'TABLE1' (Cost=1 Card=1 B
      ytes=88)

   2    1     DOMAIN INDEX OF 'NAME_TXT_IND' (Cost=1 Card=1)

Statistics
----------------------------------------------------------
       1313  recursive calls
      1  db block gets
       1715  consistent gets
    205  physical reads
       9568  redo size
       6729  bytes sent via SQL*Net to client
       4967  bytes received via SQL*Net from client
     67  SQL*Net roundtrips to/from client
     16  sorts (memory)
      0  sorts (disk)
      0  rows processed

tkprof output:-
=============
select id from table1
where ( contains( name, ' $?AB and $?CD and $?EF ' ) > 0)
and id in ('1234','4567','97343','0934',4576','6376''5017', '3930', '5099',
'5089', '5061', '4753', '3646', '3357', '3047', '8059','4124', '17591','14700',
'60973', '34987', '37586', '58985', '34874', '49161', '11489');

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         33          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0         33          0           0

Thanks,
TS

          Followup:

i want the RAW tkprof with timings -- no explain plans (they can lie).  show me
the real tkprof, making sure of course that timed statistics was ON

      Rating: 5
Thanks for the
      insight  December 30, 2003
Reviewer:  dharma
       from CA USA
HI Tom,

I have a table like this(A fictitious though)
company_id    deptno    empno    status
1        10    1    A
1        10    2    A
1        10    3    P
1        20    4    A
1        20    5    A
1        30    6    A
1        30    7    P
2        10    8    A
2        10    9    A
2        20    10    P
2        30    11    A

Primary Key on company_id,deptno,empno

To get the depts that dont have employees with status=P.

Is there anyother way other than using not exists to bring the required rows

SELECT DISTINCT deptno FROM COMP_EMP ce
 WHERE NOT EXISTS (SELECT 1 FROM COMP_EMP ces
                    WHERE ce.company_id=ces.company_id
                      AND ce.deptno=ces.deptno
                      AND ces.status='P')
since it does a sub-query on the table comp_emp and comp_emp has a million rows,
will "not exists" withstand the query.

BTW
Database is Oracle9i Enterprise Edition Release 9.2.0.4.0

          Followup:

select deptno, count( decode(status,'P',1) )
  from comp_emp
 group by deptno
having count( decode( status,'P',1 ) ) = 0;

will do it as well.  there are dozens of ways....

select deptno from comp_emp
minus
select deptno from comp_emp where status = 'P';

is another....

      Rating: 4
ORA-01415 too
      many distinct aggregate functions..  January 02, 2004

Reviewer:  Naveen  from India
Hi Tom,

I have a select statement which has a IN clause like below:

Select <column list>
from <tablename>
where column1 in(1,2,3,..)

When i am testing it by giving more and more values in the IN clause it gave the
above error. Why is there a limit for values that can be set in a IN clause. Is
the above approach recommended. Is there a way to overcome this. Kindly throw
some light on the issue.

Thank you
Nav.

          Followup:

there is a documented limited on the number of OR's and inlist items.

use a subquery (you probably aren't using binds either are you!).

create global temporary table t ( x int ) on commit delete rows;

and then INSERT the values into t and query simply

select * from table where c in ( select * from t );

      Rating: 5
Thanks
      Tom  January 02, 2004
Reviewer:  Naveen
       from India
Hi Tom,

Yes you are correct. I am not using bind variables either. I am checking the
limit of IN clause as i have to decide which approach i need to take. I'll keep
this in mind and thanks again for immediate reply.

Thank you
Nav.

          Followup:

not using bind variables will be the largest mistake of your entire career!
rethink that.

      Rating: 5
interesting
      ?  February 03, 2004
Reviewer:  Fenng
       from CNOUG China
SQL> desc a
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 X                                                  NUMBER(38)
 Y                                                  VARCHAR2(1)

SQL> desc b
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 X1                                                 NUMBER(38)
 Y1                                                 VARCHAR2(1)

SQL> select x from a
  2   where x not in (select x from b);

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   FILTER
   2    1     TABLE ACCESS (FULL) OF 'A'
   3    1     FILTER
   4    3       TABLE ACCESS (FULL) OF 'B'

SQL>

It's OK.But "x" is NOT table B's field.Would you like explain it for us ? Thanks
a lot !

          Followup:

it is expected, done by design, part of the standard.

it is simply called a correlated subquery.

consider:

select * from dept
where exists ( select null from emp where emp.deptno = dept.deptno )
                                                       ^^^^^^^^^^^

you expect that to work right?  well:

SQL> select x from a
  2   where x not in (select x from b);

is not any different than:

SQL> select x from a
  2   where x not in (select A.X from b);

      Rating: 5
Thank
      you!  February 03, 2004
Reviewer:  Fenng

Thank you very much~!

February 03, 2004 gagan -- Thanks for the question regarding "to
      remove duplicate records", version 9.0.2

      You Asked
plz explain how to remove duplicate records from a large table containing about
5 million records in a single run and with a lesser time.
i tried it with following query but it takes 10 hours of time.

delete from test1 where rowid not in (select min(rowid) from test1 group by
rc_no);

even after incraesing the rollback segs tablespace to 7gb
we are not getting desired results and while using not in clause and cursor we
generally come across this kind of problem

thanks

      and we said...
I'd generate the set of rowids to delete using analytics and then delete them..
like this:

ops$tkyte@ORA9IR2> create table t as select * from cust;

Table created.

Elapsed: 00:00:03.64
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select count(*), count(distinct cust_seg_nbr) from t;

  COUNT(*) COUNT(DISTINCTCUST_SEG_NBR)
---------- ---------------------------
   1871652                      756667

Elapsed: 00:00:05.30
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> delete from t
  2    where rowid in ( select rid
  3                       from ( select rowid rid,
  4                                     row_number() over
  5                                       (partition by cust_seg_nbr order by
rowid) rn
  6                                from t
  7                            )
  8                     where rn <> 1 )
  9  /

1114985 rows deleted.

Elapsed: 00:01:46.06
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>  select count(*), count(distinct cust_seg_nbr) from t;

  COUNT(*) COUNT(DISTINCTCUST_SEG_NBR)
---------- ---------------------------
    756667                      756667

Elapsed: 00:00:02.48

As for the RBS -- it'll get as big as it needs to be in order to process the
delete -- every index will make it "larger" and take longer as well (index
maintainence is expensive)

if you are deleting "alot of the rows" you might be better off disabling
indexes, doing the delete and rebuilding them.

OR, creating a new table that just keeps the "right records" and dropping the
old table:

ops$tkyte@ORA9IR2> create table t as select * from cust;

Table created.

Elapsed: 00:00:02.41
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select count(*), count(distinct cust_seg_nbr) from t;

  COUNT(*) COUNT(DISTINCTCUST_SEG_NBR)
---------- ---------------------------
   1871652                      756667

Elapsed: 00:00:04.60
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create table t2
  2  as
  3  select cust_seg_nbr
  4    from ( select t.*, row_number() over (partition by cust_seg_nbr order by
rowid) rn
  5             from t
  6             )
  7   where rn = 1
  8  /

Table created.

Elapsed: 00:00:10.93
ops$tkyte@ORA9IR2> drop table t;

Table dropped.

Elapsed: 00:00:00.56
ops$tkyte@ORA9IR2> rename t2 to t;

Table renamed.

Elapsed: 00:00:00.01
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select count(*), count(distinct cust_seg_nbr) from t;

  COUNT(*) COUNT(DISTINCTCUST_SEG_NBR)
---------- ---------------------------
    756667                      756667

Elapsed: 00:00:01.18
ops$tkyte@ORA9IR2>

Review &
      Followup

      Rating: 5
stats
      ...  January 30, 2004
Reviewer:  Christo
      Kutrovsky  from Ottawa, ON Canada
Very neat way of removing duplicates, however there's one little detail.

If the table has statistics, the CBO chooses a FULL TABLE SCAN the second time
too , as it always predics a selectivity of 1 (i.e. always thinks it will return
all rows).

Thus, the analytics way will run with the same performance as the group by one,
IF THE TABLE IS ANALYZED.

The work arround is to use the RULE hint:

delete /*+RULE*/ from t
where rowid in ( select rid
                   from ( select rowid rid,
                                 row_number() over
                                   (partition by cust_seg_nbr order by rowid) rn
                            from t
                        )
                 where rn <> 1 )

          Followup:

I surely did not experience that on my example....

rule or cbo -- got the same plans actually.

      Rating: 2
Follow
      up  February 02, 2004
Reviewer:  Christo
      Kutrovsky  from Ottawa, ON Canada
I did not see the analyze in your example?

I see:
- create table t as ...
- select ... from t
- delete ... from t

          Followup:

I went back and analyzed before following up.

I used the CBO in my response to you.  I did not run with tkprof the first time,
went back and did it over.

      Rating: 4
Clarification
      Please  February 03, 2004
Reviewer:  Mark
      Macke  from Tampa, FL USA
Very interesting approach to delete of duplicate rows which I would like to use
myself.  To apply to my situations I would appreciate some clarification:

1.  Should the PARTITION BY clause contain ALL of the columns used to define a
row as a duplicate?

2.  Can the ORDER BY clause be something other than rowid?

3.  Why is this better than the older method
delete from t
 where rowid <> ( select min(rowid) from t t2
                   where t2.primary_key_column1 = t.primary_key_column1
                     and t2.primary_key_column2 = t.primary_key_column2
                     ....
                     and t2.primary_key_columnN = t.primary_key_columnN );

          Followup:

1) yes.

2) yes.

3) that is a correlated subquery.  think about what it'll do -- row by row -- in
many cases.  This is more "en-mass"

      Rating:
      5
  March 18, 2004
Reviewer:  VP
       from USA

February 03, 2004 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.

      Rating: 5
Rebuilding or
      Coalesce  February 03, 2004
Reviewer:  Vivek
      Sharma  from Bombay, India
Dear Tom,

I have gained a very good knowledge from this site and have leanrt better that
Rebuilding is wastage of time. Even I have stopped rebuilding and rather
coalesce them. I have also seen a performance gain after coalescing.

But Tom, one of our Oracle Consultant visited our firm and after checking the
Database insists that the Indexes are fragmented and hence rebuild the indexes.
He says that since the Indexed Columns are frequently updated the fragmented has
increased and rebuilding can improve the performance. I have argued with his
findings that it will be just wastage of time and may degrade the performance.
The amount of redo generated next day will be significabtly higher but my
management wants to give it a try and wants me to rebuild the indexes.

What do you suggests ? Do you think that if a indexed columns are frequently
updated it can cause fragmentation issue. But can it not be resolved by
Coalescing ?

I will appreciate your early suggestions on it.

Thanks and Regards
Vivek

          Followup:

I never say always -- rebuilding CAN be beneficial in some extreme cases.  for
B*trees it is rare rare rare.  For bitmaps and text indexes -- less rare (i
rebuild my text index on asktom every now and again -- when I notice the
estimated counts getting way off)

collect statspack for a couple of days before the rebuild.  do this during peak
and make sure you see queries that would be affected by this rebuild.

then, do it again after the rebuild.

Now, compare the two and have the consultant show how much improvement they've
provided.

Be really interesting to make their fee contigent on a positive result :)

Ask them for the metrics they've used to prove the indexes are "fragmented" --
being really interesting to see their thought process as well.

February 03, 2004 -- 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.

      Rating: 5
Can I use CLOB
      as output from a stored procedure  February 03, 2004

Reviewer:  A reader
Tom,
Is it possible to return CLOB output from a stored procedure? We are on Oracle
9.2.0.4.
Thanks

          Followup:

yes,

procedure p( p_clob out clob )
....

absolutely.

February 04, 2004 Andre -- Thanks for the question regarding
      "Snapshot too old X read only tablespaces", version 816

      You Asked
Hi Tom.
If i have a query that frequently raise the error ORA 1555, then I
get the tablespace names of all the tables that the query uses.
So, I put them READONLY. Is it guaranteed that the error ORA 1555 will not be
generated ?

Another question: is it a good idea perform a
select * from ... all tables involved in the process before execute the query ?

Regards,
André.

      and we said...
No, it will not be assured.  Relevant facts for following test:

o db_block_buffers 300
o block size 8k

We'll show how a query against a table in a READ ONLY tablespace can get the
ora-1555 quite easily in an active system (which you must have).

tkyte@TKYTE816> create table t tablespace users unrecoverable as select * from
all_objects;

Table created.

tkyte@TKYTE816> create table small( x int, y char(500) ) tablespace tools;
Table created.

tkyte@TKYTE816> insert into small values ( 0, null );
1 row created.

tkyte@TKYTE816> commit;
Commit complete.

tkyte@TKYTE816> create rollback segment rbs_small
  2  tablespace tools
  3  storage ( initial 1k next 1k minextents 2 maxextents unlimited )
  4  /
Rollback segment created.

tkyte@TKYTE816> alter rollback segment rbs_small online
  2  /
Rollback segment altered.

tkyte@TKYTE816> begin
  2      commit;
  3      set transaction use rollback segment rbs_small;
  4      update t
  5         set object_type = lower(object_type);
  6      commit;
  7      execute immediate 'alter tablespace users read only';
  8
  9      for x in ( select * from t )
 10      loop
 11          for i in 1 .. 20
 12          loop
 13             set transaction use rollback segment rbs_small;
 14             update small set x = x+1, y = x;
 15             commit;
 16          end loop;
 17      end loop;
 18  end;
 19  /
begin
*
ERROR at line 1:
ORA-01555: snapshot too old: rollback segment number 10 with name "RBS_SMALL"
too small
ORA-06512: at line 9

(the above is a side effect of the delayed block cleanout)....

The solution is to have rollback segments that are sized correctly for your
entire system -- NOT just for the write components of the system.

You need to size RBS given the system -- read and write, not just write.

You should use equi-sized rollback segments (do NOT have a "big one" and then
other small ones).  the probability of the ora-1555 is in direct proportion to
the size of your SMALLEST rbs.  The largest only helps writes -- does nothing
for the reads.

It would not be a good idea to "select * from all tables" -- it would be good to
size rollback properly for the system as a whole.

In almost 15 years, I've never once hit this in a properly sized system.  I've
fixed lots of systems that hit this (by sizing RBS correctly for them).
Rollback is not something to skimp on or undersize.

Review &
      Followup

      Rating:
      3
  October 25, 2001
Reviewer:  Sagi

Hi Tom !

I agree with u in terms of Sizing the RBS properly.  But then how do we exactly
determine the exact size of the RBS. If you could explain it with an example
then it would be helpful for many of us.

Thanx in Adv.

          Followup:

You need to determine the longest running query you have.

You need to determine the length of the longest running transaction you do.

You need to ensure you have sufficient rolllback permanently (not that it can
GROW but rather that it is permanently allocated) so that you do not wrap in the
perdiod of time that is larger then the above two numbers.

Most people size for their insert/update/delete transactions.  They forget about
sizing for the read transactions.  You can monitor the v$ tables to see when and
how fast your RBS wrap.  You need to determine how much rollback you generate in
a period of time.  Say you generate X blocks of undo in 1 minutes.  You have a
long running query that takes 15 minutes.  You need to have more then 16 * X
blocks of undo configured.

In 9i, this is simplified as you may use an UNDO tablespace instead of rollback
segments.  Here you configure a retention period (how long do you want to keep
undo) and it sizes itself.

      Rating: 5
how about read
      only tablespaces since months ago?  February 03, 2004

Reviewer:  A reader
Hi

Your example is because of delayed clean out, that does not happen in
tablespaces which have been put in read only for long time huh?

          Followup:

it should not as it will be known at that point in time that the transaction
committed long ago (before your statement/transaction began) and the undo is not
needed.

February 04, 2004 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

      Rating: 5
cursor
      question  February 04, 2004
Reviewer:  Tarun
       from India
Hi tom,
      Please see the follwing code:

declare
    cursor empcur is
    select e.empno,e.sal,d.dname
    from emp e,dept d
    where e.deptno=d.deptno and d.deptno=20;

    cursor c1 is
    select empno from emp
    where empno not in (select empno
    from emp
    where deptno=20);

    type drec_type is record
        (v_empno   emp.empno%type,
         v_sal     emp.sal%type,
         v_dname   dept.dname%type);
    type demp_tab_type is table of drec_type
    index by binary_integer;

    demp   demp_tab_type;
    i  integer:=1;
begin
    for emprec in empcur loop
        demp(i).v_empno:=emprec.empno;
        i:=i+1;
    end loop;

    for i in 1..demp.count loop
           dbms_output.put_line('--'||demp(i).v_empno);
    end loop;

    for erec in c1 loop
         dbms_output.put_line(erec.empno);
    end loop;

end;
/
    In the 2nd cursor c1 instead of using subquery in the where condition, I
would like to use the values of pl/sql table demp populated in the 1st loop.
1)Is it possible?
2)Will it increase performance by using pl/sql table or
  using subquery will do that?
3) Is there any better way of writting this block?

Thanks ..in Advance

          Followup:

1) not using a plsql table type -- using a collection type -- yes.

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:110612348061
shows how

2) maybe, maybe not

if the choice is between:

a) run a query to fill a table to be used in another query

versus

b) run a single query with a subquery

I would always choose (b).  Only if the result set pulled into that table is
needed in the array for a variety of other reasons might I consider pulling data
OUT of the database only to send it BACK to the database.

3) that block should just be a query -- no code at all

select empno from emp where deptno = 20;

      Rating: 5
cursor
      questions  February 04, 2004
Reviewer:
      Tarun  from India
Thanks a lot tom. I have used the collection type. Its working fine.

February 04, 2004 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...."

Review &
      Followup

      Rating: 4
Maintenance of
      catalogs.  February 04, 2004
Reviewer:
      A reader
Hi Tom,

We are having three environments ( Live, UA and Test ) each having 10 databases.
We have implemented RMAN for one database in live. Now we are planning to
implement the same to all the databases in all the environments. As
advantageous, we are going for catalogs. Some documents says create one catalog
for one database.

In view of maintenance, which is best way to create catalogs in this kind of
environment?

          Followup:

i would have one catalog -- for all.  no reason to have parity there -- it would
make it very hard to manage.

what documents tell you to create a catalog instance per database?

<quote>
In general, Oracle Corporation advises using a catalog when you manage multiple
databases. If you have more than one database to back up, then you can create
one systemwide recovery catalog and store metadata for all the databases in this
catalog. Hence, you avoid the extra space requirements and memory overhead of
maintaining multiple databases, each with a single catalog.
</quote>

      Rating: 4
Watch out for
      DBID though  February 04, 2004
Reviewer:
      Paul  from Ottawa,ON, Canada
Depending on how these environments were created between DEV/UA/PROD you may
have trouble registering them to the RMAN catalog. If a DB was 'cloned'  using a
cold filesystem copy and a recreate of a controlfile, which I have seen done
quite often in development environments, you will end up with 2 (or more)
databases with the same DBID

connect system/xxxxx@DEV1
SQL> select dbid from v$database

      DBID
----------
 867290276

connect system/xxxxx@DEV2
SQL> select dbid from v$database

      DBID
----------
 867290276

These 2 I cannot easily put in one catalog because RMAN requires unique DBIDs.
In 9.2 there is NID.exe to help with this, in 8.1.7 you could use RMAN to
'duplicate' each DB before registering them, that will ensure all DBs have
unique DBIDs.

see Metalink 103883.1 for info on this and a link to RMAN duplicate command.

      Rating:
      4
  February 04, 2004
Reviewer:
      A reader
Hi Tom,
"what documents tell you to create a catalog instance per database? "
  Sorry its not from a Oracle documentation but it is from OCP material (Oracle
Education). As Oracle says, The purpose of catalog itself is to mangage when
ther is more than one database. I 100% agree with this. When I have seen the
statement 1 catalog to 1 database, I thought of counter checking with experts. I
will go for 1 catalog for all databases.

Thanks.

February 04, 2004 Jayasri -- Thanks for the question regarding
      "Inserting Default Value", version 8.1.6

      You Asked
Hi Tom
   I am student and learning Oracle. I had this question in one
of my interviews.

I have  to create a table with single column with default value.
Example
create table x (x number default 0);

How to insert the default value.

Regards
Jayasri

      and we said...
It is a trick question.  You cannot if the table has but one
column.  The answer is:

insert into x (x) values (0);

Inserting NULL or any other values for x.x will override the
default.  The above table with a DEFAULT is a meaningless
example -- the default will never be used in any circumstances.

Review &
      Followup

      Rating: 3
Default
      Value  February 04, 2004
Reviewer:  ARC
       from IND
Hi Tom,
Really tricky.
I have a question bit similar.
funcation:
f_add(p1,p2);
I have to create following table such that column 'c' should be always sum of
columns a and b while inserting and updating of columns a and b USING above
function f_add.

I cannot use database triggers.
Table:
t_def
a  number
b  number
c  number

Thanks in advance.
ARC

          Followup:

then you are out of luck i guess unless you always

insert into t values ( :x, :y, f_add( :x, :y ) );

and have people update it as well.  without triggers, well, it just isn't going
to happen any other way.

      Rating: 3
why not default
      used in multi-column  February 04, 2004
Reviewer:
      Sadiq  from South India
Thanks for answering any questions.

You said, one should never use default. What is wrong in using default, when
multiplle columns is available.

          Followup:

I didn't say that.

I say "in this case, the default would never be used".  very different :)

      Rating: 5
DEFAULT
      keyword  February 04, 2004
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Hi Tom,
Is the use of DEFAULT keyword new to 9i?
(I do not have earlier versions available to test).
Thanks,

SQL> create table x (x number default 0);

Table created.

SQL> insert into x values (DEFAULT) ;

1 row created.

SQL> select * from x ;

         X
----------
         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:

yes, it is a new 9i feature.  did not exist in 816...

February 04, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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"

      Rating: 3
Oracle DBA
      Guide to Data Warehousing and Star Schemas  February 04,
      2004
Reviewer:  Ferenc Palyi  from Hungary

I'have heard about this book, in the title, visited amazon.com and read reviews
saying this is the best book on star schemas ever. So I decided to buy it.

I have serious concerns that buying the book wasn't the best investment in my
life.

The book describes the four generation of star optimization.
Third is star transformation and the fourth is the introducion of bitmap join
indexes.

The book comes up with an example:
You can create a bitmap index

create bitmap index pos_day_b1 on pos_day (period_id)

or you can create a bitmap join index

create bitmap index pos_day_bj1 on pos_day (per.period_id)
from pos_day pos, period per
where pos.period_id = per.period_id

per.period_id is the primary key of the period dimension, pos.period_id is the
foreign key in the fact table.

Do you know eny reason to create a bitmap join index like that? It's structure
will be completely the same as the sturcture of the simple bitmap index, the
values in the foregin key are the same values in the primary key.

The author made some tests with this bitmap join index and got to a conclusion,
that life is better without bitmap join indexes.

I think if he would have created the join index on one of the period dimension's
higher hierarcy levels, than that would be a different story.

It's pretty funny. What do you think? Have you read the book?

          Followup:

bitmap join indexes are a way to denormalize without denormalizing.

consider the emp/dept tables..

people ask "how many people work in RESEARCH?".  "Who in SALES makes more than
5000$?"

Note that they do not ask "how many people work in deptno=10", "who in deptno=20
makes more than 5000$"

so, do you move DNAME into EMP?  No, you can create a bitmap join index.
consider:

ops$tkyte@ORA10G> create table emp as select * from scott.emp;
Table created.

ops$tkyte@ORA10G> create table dept as select * from scott.dept;
Table created.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> alter table dept add constraint dept_pk primary key(deptno);

Table altered.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> set echo off
Enter to continue

=================================================================

so, where is how to create the bitmap index -- using a FROM
and WHERE clause

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> create bitmap index emp_bm_idx
  2  on emp( d.dname )
  3  from emp e, dept d
  4  where e.deptno = d.deptno
  5  /

Index created.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> set echo off
Enter to continue

=================================================================

of course, to use a bitmap index we must use the CBO, we would need
lots of data to get it to want to use the bitmap as well -- so we fake
it out.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> exec dbms_stats.set_table_stats( user, 'EMP', numrows =>
1000000, numblks => 300000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA10G> exec dbms_stats.set_table_stats( user, 'DEPT', numrows =>
100000, numblks => 30000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G>
ops$tkyte@ORA10G> set echo off
Enter to continue

=================================================================

and now we just query...

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> set autotrace on
ops$tkyte@ORA10G> select count(*)
  2  from emp, dept
  3  where emp.deptno = dept.deptno
  4  and dept.dname = 'SALES'
  5  /

  COUNT(*)
----------
         6

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=1 Card=1 Bytes=13)
   1    0   SORT (AGGREGATE)
   2    1     BITMAP CONVERSION (COUNT) (Cost=1 Card=10000 Bytes=130000)
   3    2       BITMAP INDEX (SINGLE VALUE) OF 'EMP_BM_IDX' (INDEX (BITMAP))

Note how neither of EMP or DEPT was actually accessed for this query...

ops$tkyte@ORA10G> select emp.*
  2  from emp, dept
  3  where emp.deptno = dept.deptno
  4  and dept.dname = 'SALES'
  5  /

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
------
      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
      7698 BLAKE      MANAGER         7839 01-MAY-81       2850
30
      7844 TURNER     SALESMAN        7698 08-SEP-81       1500          0
30
      7900 JAMES      CLERK           7698 03-DEC-81        950
30

6 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=8712 Card=10000
Bytes=870000)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (TABLE) (Cost=8712 Card=10000
Bytes=870000)
   2    1     BITMAP CONVERSION (TO ROWIDS)
   3    2       BITMAP INDEX (SINGLE VALUE) OF 'EMP_BM_IDX' (INDEX (BITMAP))

and notice how DEPT is not accessed here either

bitmap join indexes could be used in some cases INSTEAD of a star transformation
-- it is not that they "help it", they are an alternative in a way.

I have not read the book, no.

      Rating: 3
datawarehousing
      link on new OTN doc site  February 04, 2004

Reviewer:  A reader
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76994/concept.htm

      Rating: 5
great
      discussion of bitmap join indexes!  February 04, 2004

Reviewer:  Alberto Dell'Era  from Milan, Italy

Liked the 'denormalize without denormalizing' hint...

February 04, 2004 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://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76965/c10datyp.htm#629
for plsql types, lo and behold -- the PLSQL manual has a section titled
"datatypes"

http://download-west.oracle.com/docs/cd/A87860_01/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.

      Rating: 3
What if
      function is complex and we are using Standard
      Edition  February 02, 2004
Reviewer:  Matt
       from Australia
Standard Edition 9.2

I have a requirement to validate the sum of the individual characters of a
number of records. I can do this in PL/SQL, but it is too slow - I need a more
efficient (SQL) solution. As I don't know the length of the string in each case
I don't think that this can be done in SQL.

I need to validate these strings as they arrive in a flat file. My validation is
carried out as part of the data load (so FBI won't work, as I have STD EDITION)

Here is an example:

 53 -> 8
 108 -> 9
 48 -> 12
 96 -> 15
 48 -> 12
 98 -> 17
 50 -> 5
 102 -> 3
 53 -> 8
 110 -> 2
sum := 91

Any ideas?

Best Regards

          Followup:

no idea what that data is supposed to mean?

what do the rows in a table look like here?  I mean -- your example shows N
number of rows we need to sum over.  is that "for real"?  what does the data you
are needing to do this to look like in the TABLE.

      Rating: 3
One
      Approach....  February 02, 2004
Reviewer:
      Matt  from Australia
This was part of a more complex problem. I think this does the job!

create type array
as table of number
/

create or replace function vtable (n in number default null)
return array
pipelined
as
begin
  for i in 1.. nvl(n,999999999)
  loop
    pipe_row(i);
  end loop;
  return;
end;

create table t (col varchar2(4))
/
alter session set cursor_sharing=force
/
insert into t(col) values ('53')
/
insert into t(col) values ('108')
/
insert into t(col) values ('48')
/
insert into t(col) values ('96')
/
insert into t(col) values ('48')
/
insert into t(col) values ('17')
/
insert into t(col) values ('50')
/
insert into t(col) values ('102')
/
insert into t(col) values ('53')
/
insert into t(col) values ('110')
/

select char_pos
       ,pad
       ,sum(substr(pad,length(pad)- row_num + 1,1)) as ch
from ( select row_number() over (partition by char_pos,alt_ascii_dbl order by
alt_ascii_dbl ) row_num
             ,char_pos
             ,lpad(alt_ascii_dbl,4,'0') as pad
        from ( select rownum char_pos
                     ,col alt_ascii_dbl
                from t )
            ,table(vtable(4))
     )
 group by char_pos, pad
/

1    0053    8
2    0108    9
3    0048    12
4    0096    15
5    0048    12
6    0017    8
7    0050    5
8    0102    3
9    0053    8
10    0110    2

      Rating: 3
More Info from
      previous post (to add context )...  February 03, 2004

Reviewer:  Matt  from Australia
I have a set of varchar2(10) string. From these I need to generate a single
digit using a defined algorithm. The algorithm carries out various operations on
each individual character that makes up the string (converts to ascii, doubles
these values, other stuff). The end result (the single digit) is used a
validation that the varchar2(10) string is correct.

I have to validate lots and lots of these (4 million+) and currently have a
PL/SQL procedure to carry this out. I have been looking at options to do this
using standard edition.

Prior to the post above, I had all but the last step (described above)
encapsulated in a single SQL statement.

I think that I now have all the info I need to carry out the entire validation
in SQL.

      Rating: 3
Function call
      in Subselect  February 04, 2004
Reviewer:
      Olga  from Vienna
Hi Tom,

I have the following query:

SELECT  t1.col1,
        t1.col2,
        t1.col3
FROM    tab1 t1,                        -- 1000 Rows
        (   SELECT  t2.col1,
                    func( t2.col1 )
            FROM    tab2
        )                               -- 10 Rows
WHERE   t1.col1 = t2.col1               -- Result = 1000 Rows

The (deterministic) Function is very complex and I tried to reduce the calling
of the function by using the subselect.
I thought: "Ok, first the inner select will be done and the the result against
the outer select". But when the select was started the function was called 1000
times and not 10 times (I tested it with a simple dbms_output in the function).

Is there a chance to reduce the calls of the function? Function-Based Indexes
were forbidden here by the DBA.

Bye,

Olga

          Followup:

Add "where rownum >= 0" to the inline view and start shopping for a new DBA
soon.  You are in real need of one.  they'd rather cut off your nose, the end
users nose and their own noses -- despite their face....  I really hate it when
someone "outlaws" a feature that may be the difference between "working" and
"not working" or "not writing code" and 'writing boatloads of code'.  I see it
with views, stored procedures, triggers, you name it.  nothing bothers me more
(well, maybe the developer that refuses to use bind variables...)

ops$tkyte@ORA10G> create table t1
  2  as
  3  select mod(rownum,10) col1, rownum col2, rownum col3
  4    from all_objects
  5   where rownum <= 1000;

Table created.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> create table t2
  2  as
  3  select mod(rownum,10) col1
  4    from all_objects
  5   where rownum <= 10;

Table created.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G>
ops$tkyte@ORA10G> exec dbms_application_info.set_client_info(0);

PL/SQL procedure successfully completed.

ops$tkyte@ORA10G> set autotrace traceonly
ops$tkyte@ORA10G> select *
  2    from t1, ( select col1, f(col1) x from t2 ) t2
  3   where t1.col1 = t2.col1
  4  /

1000 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=7 Card=1000 Bytes=52000)
   1    0   HASH JOIN (Cost=7 Card=1000 Bytes=52000)
   2    1     TABLE ACCESS (FULL) OF 'T2' (TABLE) (Cost=3 Card=10 Bytes=130)
   3    1     TABLE ACCESS (FULL) OF 'T1' (TABLE) (Cost=3 Card=1000 Bytes=39000)

Statistics
----------------------------------------------------------
         93  recursive calls
          0  db block gets
         93  consistent gets
          4  physical reads
          0  redo size
      26510  bytes sent via SQL*Net to client
       1234  bytes received via SQL*Net from client
         68  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
       1000  rows processed

ops$tkyte@ORA10G> set autotrace off
ops$tkyte@ORA10G> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
1000

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> exec dbms_application_info.set_client_info(0);

PL/SQL procedure successfully completed.

ops$tkyte@ORA10G> set autotrace traceonly
ops$tkyte@ORA10G> select *
  2    from t1, ( select col1, f(col1) x from t2 WHERE ROWNUM >= 0) t2
  3   where t1.col1 = t2.col1
  4  /

1000 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=7 Card=1000 Bytes=65000)
   1    0   HASH JOIN (Cost=7 Card=1000 Bytes=65000)
   2    1     VIEW (Cost=3 Card=10 Bytes=260)
   3    2       COUNT
   4    3         FILTER
   5    4           TABLE ACCESS (FULL) OF 'T2' (TABLE) (Cost=3 Card=10
Bytes=130)
   6    1     TABLE ACCESS (FULL) OF 'T1' (TABLE) (Cost=3 Card=1000 Bytes=39000)

Statistics
----------------------------------------------------------
         10  recursive calls
          0  db block gets
         85  consistent gets
          0  physical reads
          0  redo size
      26510  bytes sent via SQL*Net to client
       1234  bytes received via SQL*Net from client
         68  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
       1000  rows processed

ops$tkyte@ORA10G> set autotrace off
ops$tkyte@ORA10G> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
10

      Rating: 5
Wow!
        February 04, 2004
Reviewer:  Olga
       from Vienna
Many thanks for your answer, Tom! It's great!

You and your page are the best reasons for chosing Oracle over any other
Database System. Your book is also wonderful, the best Oracle book I've read.

Bye,

Olga

      Rating: 5
Would it help
      here?  April 06, 2004
Reviewer:  Gabriel
       from Montreal, Canada
Hello Tom,

In the following update:

UPDATE mytable
   SET retrycount = :1,
       outcome = :2,
       completetime = :3,
       starttime = :4,
       taskstatecode = :5,
       jobfunctionid = :6,
       repeatcount = :7,
       manualind = :8,
       dynamicgroupid = HEXTORAW (:14),
       applicationtag = :15,
       workitemid = :16,
       createtime = :17,
       taskmodelid = HEXTORAW (:18),
       stepnumber = :19,
       modelinstanceid = HEXTORAW (:20),
       milestoneinstid = HEXTORAW (:21)
 WHERE ((taskinstanceid = HEXTORAW (:22)))

Would it help if I create a function that simply returns the hextoraw and then
create an index on that function? I am not sure as the function is not on the
table column but on a value that this column is compared with. Would it help if
we would use this function everywhere we have hextoraw?

Thank you very much,

February 04, 2004 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.:-)

      Rating: 5
View with Outer
      Join  February 04, 2004
Reviewer:  Sami
       from NJ,USA
Dear Tom

My understanding is that "Step(1) = Step(2)+Step(3)"

Step(1) returns 24 records.
Step(2)+Step(3) returns 0 records.

Please enlighten me if I am wrong?

STEP (1)
========
SELECT   T1.USERSTATUSID,
      NVL(T21.DESCRIPTION,T22.DESCRIPTION) USERSTATUSDESC,
      NVL(T21.TOKENVALUE,T22.TOKENVALUE) USERSTATUSABBREV,
         T1.HSBCSERVICEID,
         T1.ACTIVEFLAG,
         T21.LANGUAGEID LANGUAGEID1,
     T22.LANGUAGEID LANGUAGEID2,
         T1.UPDATEDATE,
         T1.CREATEDATE
FROM    USERSTATUS T1,
        crmf.NLS_TOKEN_DETAILS T21,
        crmf.NLS_TOKEN_DETAILS T22
WHERE   T1.TOKENID=T21.TOKENID(+)
AND     T1.TOKENID=T22.TOKENID
AND     T22.LANGUAGEID=30
AND     T21.languageid(+)=1;

24 rows returned. <=========********

STEP (2)
========
CREATE OR REPLACE VIEW userstatus_v2 AS
SELECT   T1.USERSTATUSID,
       NVL(T21.DESCRIPTION,T22.DESCRIPTION) USERSTATUSDESC,
       NVL(T21.TOKENVALUE,T22.TOKENVALUE) USERSTATUSABBREV,
                 T1.HSBCSERVICEID,
                 T1.ACTIVEFLAG,
                 T21.LANGUAGEID LANGUAGEID1,
         T22.LANGUAGEID LANGUAGEID2,
                 T1.UPDATEDATE,
                 T1.CREATEDATE
FROM     USERSTATUS T1,
         crmf.NLS_TOKEN_DETAILS T21,
         crmf.NLS_TOKEN_DETAILS T22
WHERE    T1.TOKENID=T21.TOKENID(+)
AND      T1.TOKENID=T22.TOKENID
AND      T22.LANGUAGEID=30;

View created.

STEP (3)
========
SELECT * FROM userstatus_v2
WHERE languageid1(+)=1;
no rows returned. <==========*******

          Followup:

you need to outer join to "something"  - there is no "set" to outer join to
here.

Just like this:

ops$tkyte@ORA10G> l
  1* select * from dual where dummy(+) = 'y'
ops$tkyte@ORA10G> /

no rows selected

ops$tkyte@ORA10G> select * from dual, (select 'y' xxx from dual) where dummy(+)
= xxx;

D X
- -
  y

if you query

select * from userstatus_v2, (select 1 xxx from dual)
where languageid1(+) = xxx;

then you will get 1 row back (the xxx value) unless there are languageid1 = 1
values.

February 04, 2004 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

      Rating: 5
What about if
      business logic is in stored procs?  February 03, 2004

Reviewer:  A reader  from Ottawa, ON Canada

We have a setup where no applications have access to the database except through
stored procs. In the stored procs, referential and check constraints are
exhaustively checked with appropriate error messages returned.

Currently, we have all necessary constraints except for one that we speculate,
but haven't tested, might be hard on performance -- an RI constraint against a
20 million row table.

We are contemplated creating this constraint. Any thoughts?

          Followup:

you cannot implement

a) cross object integrity
b) cross row integrity

except with declaritive RI.

if you thing you "have", post a simple example and I'll show you were it breaks
down.

      Rating: 5
Re: What about
      if business logic is in stored procs?  February 04, 2004

Reviewer:  Salaam Yitbarek  from Ottawa, ON, Canada

Sorry, I don't know exactly what you mean by cross object and cross row RI.

Incidentally, after re-reading my post, I think I didn't make it clear that in
spite of our implementing constraints in code, we have also implemented RI at
the database level, all but for the one constraint I mentioned. So we have erred
on the side of caution, but I am wondering if the database level constraints are
necessary? And is the answer different for check constraints than for RI.

          Followup:

you cannot easily and correctly (takes lots of code and lots and lots of table
locks) enforce the CROSS OBJECT RI like the emp(deptno) -> dept(deptno) foreign
key.

You cannot enforce correctly the "cross row" integrity like that of emp(mgr) ->
emp(empno) foreign key -- or that of "at most one empno in a department can be a
manager"

if you think you can -- please post the code and we'll tell you the cases where
it falls apart :)

If you have database level constraints

AND

the code is in the stored procedure (in the database)

THEN

you have just created extra code that just slows everything down

there are cases in "client apps" outside the database where you might want to
replicate SOME of the constraints to make the end user experience "better" (eg:
as I tab out of a field - validate the value to make sure it has a chance of
being inserted).  Inside of a stored procedure however -- I would just let the
database do the work and catch any exceptions (since after all of your checking,
you still have to catch the exception since your checks do not ASSURE that the
operation will succeed!!!!!)

February 04, 2004 Norman -- Thanks for the question regarding
      "Rename a column", version

      You Asked
In Oracle 8i database, can you rename a column without
dropping
the table?

      and we said...
please use the form in the future.  we use it for tracking
purposes.

No, you cannot rename a column.

In Oracle8i, you can drop a column from a table but not rename.

The way to rename a column would be to use a view.  To minimize
the impact on applications, you would simply:

rename TableName to TableName_TBL;
create view TableName
as
select c1 New_Name_For_C1, c2, c3, .... cn from TableName_Tbl;

then grant on the view what you normally granted on the table.

In that fashion, all existing applications will see the new
column name with the old table name.

Review &
      Followup

      Rating: 2
rename a
      column  December 27, 2002
Reviewer:  Jack
       from CA, USA
Tom,
This is a nice solution. But what happens to triggers? how they will work. What
are the complications of updating this table, insertes, deletes?. pl. explain
briefly.
What is your solution if it is a production system with 5 million rows in that
table.
appreciate your help everytime.

          Followup:

triggers work as always -- on base tables.

Inserts, updates, deletes -- work as normal.

If this is a production system -- why the heck are you renaming a column?  I
would refuse that -- I would only create a new view with the new name to satisfy
whomever was not satisified with the old name.

      Rating: 5
Renaming a
      column...  December 28, 2002
Reviewer:
      Connor  from UK
I used to be of the "why the heck do want to rename column" school of
thought...Then I ran 'select count(*) from v$reserved_word' and got 800 or so
!!!

Then again, that was in 9i - and I can rename a column in 9i anyway :-)

          Followup:

well, that is misleading for most of the reserved words are OK

sys@ORA920> declare
  2      cnt number := 0;
  3  begin
  4      for x in ( select * from v$reserved_words )
  5      loop
  6          begin
  7              execute immediate 'declare ' || x.keyword || ' int; begin null;
end;';
  8          exception
  9              when others then
 10                  dbms_output.put_line( x.keyword );
 11                  cnt := cnt + 1;
 12          end;
 13      end loop;
 14      dbms_output.put_line( cnt || ' bad ones' );
 15  end;
 16  /
&
|
:
,
-
=
>
[
<
(
.
+
]
)
!
....
WHERE
WITH
106 bad ones

PL/SQL procedure successfully completed.

there is only 106 really reserved words and many of them are not words at all..

and there is always quoted identifiers ;)

      Rating:
      5
true...  December 28, 2002
Reviewer:
      Connor McDonald  from UK
...but if there is a reserved word called (say) "RESERVED", typically its not
the kinda thing you want to see littered throughout columns, triggers, plsql
code etc...Tends to confuse things ;-)

And don't me started on quoted idents.  Can't remember the name of the package,
but there is a health care one in use in Australia - you know who you are :-),
in which ALL objects are in lower case in the dictionary.  Couple of months of
that and I was very close to kicking their software down the street :-)

      Rating:
      4
Timestamp  December 30, 2002
Reviewer:
      Paul  from UK
Tom,

We've got an application (8.1.7) where there are columns named "TIMESTAMP"
(DATE) littered all over the place.  Is this going to cause problems when we
eventually go to 9i?

I would try this myself, but don't have a 9i database to play on.

          Followup:

It will give you issues in PLSQL and maybe other precompiler like environments.

      Rating: 4
use the form
      ??  December 30, 2002
Reviewer:  robert
       from PA
>> please use the form in the future.  we use it for tracking purposes.

What you mean ? you mean there is an "undocumented" way for
posting questions, an AskTom private line ....?

          Followup:

They asked a question

I asked for some followup in this case (clarification)

Instead of using the form to supply the requested information -- they emailed me
the stuff.

I was in a good mood so rather then just deleting it, I put it into the form for
them and answered the question...

Most emails that get sent to me get this little blurb in return, so it is not
the best way to get in touch for a question.  It has helped to cut way down on
the size of my inbox in the morning!

...
Please do not take this personally, this is a generic response I send to
direct questions.

Please use
http://asktom.oracle.com/
to ask me questions.  When I have time,
I accept them there.  It is not a very scalable solution for me to answer
individual questions in email.  I cannot share these answers with many
thousands of other Oracle users.

You must understand there are many thousands of people like you and there is
really only one of me (i am not a team, a group, or anything like that --
there is really just me).  Answering individual questions via email is not a
good way for me to spend my time.
........

      Rating: 4
One Tom and too
      many Oracle junkies  December 30, 2002
Reviewer:
      Robert  from PA
>> there is really only one of me (i am not a team, a group, or anything like
that -- there is really just me).

Right, you're THE man, Tom. You certainly done justice to the name of this
awesome site.
But Ive wondered why it's just you. Is it a (...shhh...) boss or Oracle Corp
thing...resource thing...?
I'd think you enjoy fielding questions and educate us Oracle junkies and there
must be people working with you who'd like to do the same and would offer to
pitch in.
Just curious, you know...

          Followup:

well, there is devtrends.oracle.com -- Cameron works for me.

I do occasionally get Sean Dillon (xml), Chris Beck (portal/webdb/ultrasearch),
Marshall Presser (RAC/HA), Kim Better (portal), Clark Colombo (spatial), and
others to answer as well (they always get the credit -- answer starts with "i
asked our resident <technlogy here> guru "name" and they said:"

      Rating: 4
Rename
      column  February 04, 2004
Reviewer:  Tarun
       from India
Dear tom,
     I just came to know that Oracle9iR2 allows renaming a column. Its fine.
But,

1) How its allowing to rename a primary key column, when there is a Referential
Integrity?

2) Will this cause any problems to other objects using that column?

SQL> create table x(x1 number primary key);

Table created.

SQL> create table y(y1 number references x);

Table created.

Inserted few rows in both tables.......

SQL> select * from x;

        X1
----------
         1
         2
         3

SQL> select * from y;

        Y1
----------
         2
         3
         3
         1

SQL> alter table x rename column x1 to x2;

Table altered.

SQL> desc x
 Name                                      Null?    Type
 ----------------------------------------- -------- -------
X2                                        NOT NULL NUMBER

Thanks.

          Followup:

1) the name of the column isn't relevant, the contents of the column (the
values) are.  it matters not if the primary key of dept is DEPTNO or FOOBAR as
long as the values 10, 20, 30 are in there....

2) nope, the name is stored normalized -- update it in one place and it cascades
down...

February 04, 2004 sachin -- Thanks for the question regarding
      "comparing two table data and logging the changes", version 8.1.6

      You Asked
in my application, i have two tables similar in structure say trans_detail and
trans_detail_tmp.
now for a trans_id i need to compare the each and every field value in the both
tables and
write to another table if any changes were found.

i have written a procedure to the above.  i declare the following cursors for
those tables
like

 cursor c_trans is select * from trans_detail where trans_id = p_trans_id;
 cursor c_trans_tmp is select * from trans_detail_tmp where trans_id =
p_trans_id;

then i compare each and every field and write to another table if they are not
same like below

  open c_trans;
  open c_trans_tmp;
  fetch c_trans into r1;
  fetch c_trans_tmp into r2;

  if r1.trans_amount != r2.trans_amount then
--     insert the change to the other table.
  end if;

so, this i have to do for all the fields.
is it possible to do the same with a single query? or any better you suggest..
any suggestions in this are appreciated.

thanks

      and we said...
This single statement:

insert into other_table
( select 'in trans_detail, not tmp', trans_detail.*
    from trans_detail where trans_id = p_trans_id
  MINUS
  select 'in trans_detail, not tmp', trans_detail_tmp.*
    from trans_detail_tmp where trans_id = p_trans_id
)
UNION ALL
( select 'in tmp, not trans_detail', trans_detail_tmp.*
    from trans_detail_tmp where trans_id = p_trans_id
  MINUS
  select 'in tmp, not trans_detail', trans_detail.*
    from trans_detail where trans_id = p_trans_id
)
will do it all for you (no code!!!) -- and it will handle NULLS properly (else
you must compare if ( a != b or ( a is null and b is not null ) or ( a is not
null and b is null ) ).

I stuck the constants "in tmp, not trans_detail" and so on in there so you could
tell which rows came from which table if you needed that.

Review &
      Followup

      Rating: 3
i need change
      in each column to written as a seperate row  December 06,
      2001
Reviewer:  sachin
thanks for the answer.
but i need to log the changes on each column as a different row into that other
table.

for ex:

select '1' a,'2' b,'3' c from dual
minus
 select '1' a,'4' b,'6' c from dual

i get the following result

A B C
- - -
1 2 3

i need that other table to contain the following rows.
column_name old_val new_val
b           2       4
c           3       6

          Followup:

You would need a full outer join in order to do that (since the row could be in
T1 and not in T2 or in T2 and not in T1).

Is trans_id the primary key?

      Rating: 4
yes trans_id is
      a primary key  December 07, 2001
Reviewer:
      sachin
can you give that sample query with that outer join.

thanks

          Followup:

Say you have the tables setup like this:

tkyte@TKYTE9I.US.ORACLE.COM> create table trans (trans_id int primary key,
  2                      c1       date,
  3                      c2       number,
  4                      c3       varchar2(10)
  5  )
  6  /

Table created.

Elapsed: 00:00:00.00
tkyte@TKYTE9I.US.ORACLE.COM>
tkyte@TKYTE9I.US.ORACLE.COM> create table trans_tmp (trans_id int primary key,
  2                      c1       date,
  3                      c2       number,
  4                      c3       varchar2(10)
  5  )
  6  /

Table created.

Elapsed: 00:00:00.03
tkyte@TKYTE9I.US.ORACLE.COM>
tkyte@TKYTE9I.US.ORACLE.COM> create table changes
  2  ( trans_id  int, cname varchar2(10), val1 varchar2(4000), val2
varchar2(4000) );

Table created.

This would capture the changes:

tkyte@TKYTE9I.US.ORACLE.COM> select *
  2    from (
  3  select t1.trans_id,
  4    decode( r, 1, 'C1', 2, 'C2', 3, 'C3' ) cname,
  5    decode( r, 1, to_char(t1.c1,'dd-mon-yyyy hh24:mi:ss'),
  6               2, to_char(t1.c2),
  7               3, t1.c3) v1,
  8    decode( r, 1, to_char(t2.c1,'dd-mon-yyyy hh24:mi:ss'),
  9               2, to_char(t2.c2),
 10               3, t2.c3) v2
 11  from trans t1,
 12       trans_tmp t2,
 13       (select rownum r from all_objects where rownum <= 3 )
 14  where t1.trans_id = :bv
 15    and t1.trans_id = t2.trans_id (+)
 16    )
 17  where v1 <> v2
 18     or (v1 is null and v2 is not null)
 19     or (v1 is not null and v2 is null)
 20  /

Where 3 in this query represents the number of columns we need to compare.  The
decodes would convert everything into character strings for comparision...

      Rating: 3
outer join
      query  December 07, 2001
Reviewer:  sachin

tom,

can you post the outer join query that you talked about in your answer please.

trans_id is a primary key.

      Rating: 5
outer join on
      the other table  December 07, 2001
Reviewer:
      sachin
tom,

i think your following query does the outer join only on table not on both. am i
right?
what about the rows that are in t2(trans_tmp), but not in t1(trans)

          Followup:

Ok, try this one on for size then:

select *
  from (
select t1.trans_id,
  decode( r, 1, 'C1', 2, 'C2', 3, 'C3' ) cname,
  decode( r, 1, to_char(t1.c1,'dd-mon-yyyy hh24:mi:ss'),
             2, to_char(t1.c2),
             3, t1.c3) v1,
  decode( r, 1, to_char(t2.c1,'dd-mon-yyyy hh24:mi:ss'),
             2, to_char(t2.c2),
             3, t2.c3) v2
from
     (select rownum rr, trans.* from trans where trans_id = :bv ) t1,
     (select rownum rr, trans_tmp.* from trans_tmp where trans_id = :bv) t2,
     (select rownum r from all_objects where rownum <= 3 ) t3,
     (select rownum rr from dual) t4
where t1.rr(+) = t4.rr
  and t2.rr(+) = t4.rr
  )
where v1 <> v2
   or (v1 is null and v2 is not null)
   or (v1 is not null and v2 is null)
/

      Rating: 3
What about when
      you just have raw data?  January 08, 2004

Reviewer:  Duncan  from UK
Tom

I have a procedure which has many input parameters representing elements of
table data.  Once these parameters are read into the procedure I validate each
element.  Once the data line has been validated I then need to check that the
record doesn't already exist in my table.

I do this by checking a few fields of demographic data, eg name, address.  If
the record doesn't exist then i insert it.

However if the record does exits i need to do an update of all the other fields
of the row with the new data fields read into the procedure.  I also need to
keep track of which fields have actually changed from their original table data.
 Obviously I can do this the long way around by checking individual fields and
comparing their table data to the new data, but I wanted to know if you have a
quicker more efficient method since my table is very wide, and checking
individual elements is tedious, especially because I have to account for NULL
values.

I was wondering about loading the data line into maybe a RECORD type and
comparing to the table data, but I understand that you cannot directly compare
RECORD types.
Would another type work, eg ORACLE OBJECT?  If so how could i get it to log
which fields have been changed?

There must be a quick less painful way to do this as I can't believe it is that
uncommon?

Many thanks for your help
(db version 8.1.7.4)

          Followup:

to check for field by field you have one and only one option.

to check field by field.

(it is rather "uncommon", I've not run into it as a requirement very often).

the "easiest" way I can think to do this is:

ops$tkyte@ORA920PC> create table t ( a int, b date, c varchar2(25) );
Table created.

that represents your table.  A is your "key", b, c the data you update

ops$tkyte@ORA920PC> insert into t values ( 1, trunc(sysdate), 'hello world' );
ops$tkyte@ORA920PC> insert into t values ( 2, null, 'hello world' );
ops$tkyte@ORA920PC> insert into t values ( 3, trunc(sysdate), null );

ops$tkyte@ORA920PC> create or replace view v
  2  as
  3  select b, 0 b_ind, c, 0 c_ind
  4    from t
  5   where 1=0;

View created.

I'm using this view as a template.  Basically, we'll have column/indicator,
column/indicator in this view.

The view is used to pick up the datatypes from the data dictionary easily and
make it so that change to the base table -- changes the view straight away (and
so we don't have to define the record in plsql)...

Instead of a view, this could be a cursor in the package

Here is your routine.  note how we use DECODE to do the compares (null safe)

ops$tkyte@ORA920PC> create or replace procedure do_iu( p_a in int, p_b in date,
p_c in varchar2 )
  2  as
  3      l_rec v%rowtype;
  4  begin
  5      select b, decode( b, p_b, 0, 1 ),
  6             c, decode( c, p_c, 0, 1 )
  7        into l_rec
  8        from t
  9       where a = p_a;
 10
 11      if ( l_rec.b_ind = 1 )
 12      then
 13          dbms_output.put_line( 'B will be changed from "' || l_rec.b || '"
to ' || p_b );
 14      end if;
 15      if ( l_rec.c_ind = 1 )
 16      then
 17          dbms_output.put_line( 'C will be changed from "' || l_rec.c || '"
to ' || p_c );
 18      end if;
 19
 20      update t
 21         set b = p_b, c = p_c
 22       where a = p_a;
 23      dbms_output.put_line( 'Updated ' || sql%rowcount );
 24  exception
 25      when no_data_found then
 26          dbms_output.put_line( 'Inserted' );
 27          insert into t (a,b,c) values ( p_a, p_b, p_c );
 28  end;
 29  /

Procedure created.

and then to test...

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> exec do_iu( 1, sysdate-1, 'hello world' );
B will be changed from "08-JAN-04" to 07-JAN-04
Updated 1

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> exec do_iu( 1, trunc(sysdate), 'goodbye' );
B will be changed from "07-JAN-04" to 08-JAN-04
C will be changed from "hello world" to goodbye
Updated 1

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> exec do_iu( 1, sysdate, 'goodbye' );
B will be changed from "08-JAN-04" to 08-JAN-04
Updated 1

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> exec do_iu( 2, sysdate, 'hello world' );
B will be changed from "" to 08-JAN-04
Updated 1

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> exec do_iu( 3, trunc(sysdate), 'hello world' );
C will be changed from "" to hello world
Updated 1

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> exec do_iu( 4, sysdate, 'hello world' );
Inserted

PL/SQL procedure successfully completed.

      Rating: 2
Read the tables
      only once!  February 03, 2004
Reviewer:  Marco
      Stefanetti  from italy
I have found this simple solution, the benefit is that i read the two tables
only once:

  1   select field,
  2          value,
  3          sum(source) source
  4   from (
  5          (
  6             select 'a' field,1 source, 20 value from dual
  7             union
  8             select 'b' field,1 source, 15 value from dual
  9             union
 10             select 'c' field,1 source, 30 value from dual
 11          )
 12        union all
 13         (
 14             select 'b' field,2 source, 15 value from dual
 15             union
 16             select 'c' field,2 source, 24 value from dual
 17             union
 18             select 'd' field,2 source, 13 value from dual
 19          )
 20       )
 21  group by field, value
 22* having sum(source) < 3

F      VALUE     SOURCE
- ---------- ----------
a         20          1
c         24          2
c         30          1
d         13          2

bye

          Followup:

huh?

you do realize that you read dual 6 times and not only that but you did a
sort/distinct on it....

And you'd read your TABLE 2 times for each column in it in general?

how'd you get "that i read the two tables only once:"?

(and not only that, but it gets "the wrong answer" to boot, what happens with
two tables:

T1

1         2
2         1

T2

1         1
2         2

Your technique would say "they are the same", but they are not.

Not sure if you meant what you typed in as the example query -- why don't you
work with real tables?

      Rating: 1
you are too
      fast on respond  February 03, 2004
Reviewer:
      Marco Stefanetti  from italy
try this :

create table a (field char,source number,value number);
insert into a values('a',1,10);
insert into a values('b',1,10);
insert into a values('c',1,10);
insert into a values('1',1, 2);
insert into a values('2',1, 1);

select * from a;

create table b (field char,source number,value number);
insert into b values('b',2,10);
insert into b values('c',2,12);
insert into b values('d',2,10);
insert into b values('1',2, 1);
insert into b values('2',2, 2);

select * from b;

select field,
       value,
       sum(source) source
     from (
           ( select * from a )
            union all
           ( select * from b )
          )
   group by field, value
  having sum(source) < 3;

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

results:

select * from a;

F     SOURCE      VALUE
- ---------- ----------
a          1         10
b          1         10
c          1         10
1          1          2
2          1          1

 select * from b;

F     SOURCE      VALUE
- ---------- ----------
b          2         10
c          2         12
d          2         10
1          2          1
2          2          2

select field,
       value,
       sum(source) source
     from (
           ( select * from a )
            union all
           ( select * from b )
          )
   group by field, value
  having sum(source) < 3;

F      VALUE     SOURCE
- ---------- ----------
1          1          2
1          2          1
2          1          1
2          2          2
a         10          1
c         10          1
c         12          2
d         10          2

Record "b 10" doesn't compare!!!!!

I read table a once
I read table b once

see you

          Followup:

that is not at all what you posted -- not at all -- see what you posted, it
would appear in your above example that you were PIVOTING fields named "a", "b",
and "c"

that is why I said "do a real example for your does not make sense as presented"

what was the purpose of the union's of duals -- they only confused the issue.  I
thought you were PIVOTING a table with 3 columns A, B and C -- that would not
work.

Now I see your general idea and say "looks good".  I would however do it like
this:

select id, count(src1), count(src2)
  from ( select a.*, 1 src1, to_number(null) src2 from big_table a
         union all
         select b.*, to_number(null) src1, 2 src2 from big_table b
       )
group by     ID, OWNER, OBJECT_NAME, SUBOBJECT_NAME, OBJECT_ID,
DATA_OBJECT_ID, OBJECT_TYPE, CREATED, LAST_DDL_TIME, TIMESTAMP,
STATUS, TEMPORARY, GENERATED, SECONDARY
having count(src1) <> count(src2)

That does effectively the same thing -- BUT works even if table A has a row
three times and table B does not have it at all (you would get sum(source) = 3
in that case)

This'll show how many rows are are in each table when they are not equal, will
tolerate duplicate rows and show the mismatches.

Thanks for the interesting new twist -- it does do less work in most cases --
the group by could be killer on really wide rows.

      Rating:
      1
ps  February 03, 2004
Reviewer:  Marco
      Stefanetti  from italy
select field,
       sum( decode(source,1,value,null) ) table_a,
       sum( decode(source,2,value,null) ) table_b
from
( select field,
       value,
       sum(source) source
     from (
           ( select * from a )
            union all
           ( select * from b )
          )
   group by field, value
  having sum(source) < 3
)
group by field;

F    TABLE_A    TABLE_B
- ---------- ----------
1          2          1
2          1          2
a         10
c         10         12
d                    10

bye

      Rating: 1
just another
      example  February 03, 2004
Reviewer:  Marco
      Stefanetti  from italy
You can compare 2 fields of 3 tables

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

create table a ( key varchar2(10), value1 number, value2 varchar2(10) );
insert into a values('A',10,'aaa');
insert into a values('B',20,'bbb');
insert into a values('C',30,'ccc');
insert into a values('D',40,'ddd');
insert into a values('E',50,'eee');

create table b ( key varchar2(10), value1 number, value2 varchar2(10) );
insert into b values('B',20,'bbb');
insert into b values('C',30,'ccc');
insert into b values('D',41,'ddd');
insert into b values('E',50,'eee');

create table c ( key varchar2(10), value1 number, value2 varchar2(10) );
insert into c values('C',30,'ccc');
insert into c values('D',40,'ddd');
insert into c values('E',50,'eeX');

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

select key,
       max( decode( source_code_tot, 1, value1, 3, value1, 5, value1, 7, value1,
null ) ) a_v1,
       max( decode( source_code_tot, 1, value2, 3, value2, 5, value2, 7, value2,
null ) ) a_v2,
       max( decode( source_code_tot, 2, value1, 3, value1, 6, value1, 7, value1,
null ) ) b_v1,
       max( decode( source_code_tot, 2, value2, 3, value2, 6, value2, 7, value2,
null ) ) b_v2,
       max( decode( source_code_tot, 4, value1, 5, value1, 6, value1, 7, value1,
null ) ) c_v1,
       max( decode( source_code_tot, 4, value2, 5, value2, 6, value2, 7, value2,
null ) ) c_v2,
       decode( source_code_tot, 7, 'OK', 'DIFF') descr
from
(
select key,
       value1,
       value2,
       sum(source_code) source_code_tot
  from (
         ( select a.*, 1 source_code from a)
         union all
         ( select b.*, 2 source_code from b)
         union all
         ( select c.*, 4 source_code from c)
       )
group by key, value1, value2
)
group by key, decode( source_code_tot, 7, 'OK', 'DIFF')
order by key;

KEY              A_V1 A_V2             B_V1 B_V2             C_V1 C_V2
DESCR
---------- ---------- ---------- ---------- ---------- ---------- ----------
--------------------------------
A                  10 aaa
DIFF
B                  20 bbb                20 bbb
DIFF
C                  30 ccc                30 ccc                30 ccc        OK
D                  40 ddd                41 ddd                40 ddd
DIFF
E                  50 eee                50 eee                50 eeX
DIFF

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

Is it strange to sum the labels 1,2,4?
not so strange, table d would have source_code=8

Useful?
yes, if one of the tables is a complex view on a remote db, and you can read it
only once

fast?
I compare 10000 rows of a remote table in 2 seconds.

let me know ...

      Rating: 3
Multiple
      records  February 04, 2004
Reviewer:  Marco
      Stefanetti  from italy
YOU:
that is not at all what you posted -- not at all -- see what you posted, it
would appear in your above example that you were PIVOTING fields named "a", "b",

and "c"

ME:
Sorry, the union select in the first of my posts was simply a table example,
but it was confusing and, sure, it is better with real tables

YOU:
select id, count(src1), count(src2)
  from ( select a.*, 1 src1, to_number(null) src2 from big_table a
         union all
         select b.*, to_number(null) src1, 2 src2 from big_table b
       )
group by     ID, OWNER, OBJECT_NAME, SUBOBJECT_NAME, OBJECT_ID,
DATA_OBJECT_ID, OBJECT_TYPE, CREATED, LAST_DDL_TIME, TIMESTAMP,
STATUS, TEMPORARY, GENERATED, SECONDARY
having count(src1) <> count(src2)

That does effectively the same thing -- BUT works even if table A has a row
three times and table B does not have it at all (you would get sum(source) = 3
in that case)

ME:
I'm not sure, you are not considering the content of the fields, only keys.

YOU:
the group by could be killer on really wide rows.

ME:
Yes, but I think that something "killer" would be necessary in any case

YOU: BUT works even if table A has a row three times

ME: I have got another idea, hope i'm right
we can use the product of primes instead of the sum of binary numbers

PROBLEM:

find differences and missing records between similar tables

TABLES:

create table a ( key varchar2(10), value1 varchar2(10) );
insert into a values('A','aaa');
insert into a values('A','aaa');
insert into a values('B','bbb');
insert into a values('B','bbb');
insert into a values('C','ccc');
insert into a values('D','ddd');
insert into a values('E','eee');

create table b ( key varchar2(10), value1 varchar2(10) );
insert into b values('B','bbb');
insert into b values('C','ccc');
insert into b values('C','ccc');
insert into b values('D','ddd');
insert into b values('E','eee');

create table c ( key varchar2(10), value1 varchar2(10) );
insert into c values('C','ccc');
insert into c values('C','ccc');
insert into c values('D','ddd');
insert into c values('E','eeX');

SELECT:

select
  key,
  decode( mod(source_code_tot,2), 0, value1, null)  a,
  decode( mod(source_code_tot,3), 0, value1, null)  b,
  decode( mod(source_code_tot,5), 0, value1, null)  c,
  source_code_tot p,
  mod(source_code_tot,30) r,
  decode(  mod(source_code_tot,30), 0, 'OK', 'DIFF')  res
from
(
  select key,
         value1,
         trunc( exp( sum( ln( source_code) ) ) ) source_code_tot
    from (
           ( select a.*, 2 source_code from a)
           union all
           ( select b.*, 3 source_code from b)
           union all
           ( select c.*, 5 source_code from c)
         )
  group by key, value1
)
;

RESULTS:

KEY   A     B     C         P     R RES
----- ----- ----- ----- ----- ----- -----
A     aaa                   4     4 DIFF
B     bbb   bbb            12    12 DIFF
C     ccc   ccc   ccc     450     0 OK
D     ddd   ddd   ddd      30     0 OK
E                 eeX       5     5 DIFF
E     eee   eee             6     6 DIFF

It seems to work, but may be inaccurate due to exp(sum(ln(x))) because of
approximations

STEPS:

1) union all : we consider all records af our tables labelling them whit a prime
number
2) group by key, value1 : we consider as different, records with different
values ( difference between records),
   at the same time we calculate the product of the labels (exp sum ln), this is
a single number that memorizes the
   provenience of the records in the group
3) decode(mod,30) : we can have a total multiple of 30 (2*3*5) if, and only if,
the record was present in the tree sources.

I'm sorry for the long posts, but it seems to me quite interesting.

          Followup:

watch out - that exp(sum(ln(x))) would be a killer cpu wise!!

I like your approach with the minor modification I have (which permits
duplicates -- I'm not assuming "keys" of any sort here at all).

The group by approach (given that you don't get sort key too long) is elegant
and efficient.

      Rating:
      4
Sorry  February 04, 2004
Reviewer:
      Marco Stefanetti  from italy
Sorry, you are rigth
considering the above tables it's enought:

select key,
       decode( ta, 0, null, value1)  a,
       decode( tb, 0, null, value1)  b,
       decode( tc, 0, null, value1)  c,
       decode( ta, 0, 'DIFF',
               decode( tb, 0, 'DIFF',
                 decode( tc, 0, 'DIFF', 'OK' ) ) ) res
from
(
  select key,
         value1,
         sum(ta) ta,
         sum(tb) tb,
         sum(tc) tc
    from (
           ( select a.*, 1 ta, 0 tb, 0 tc from a)
           union all
           ( select b.*, 0 ta, 1 tb, 0 tc from b)
           union all
           ( select c.*, 0 ta, 0 tb, 1 tc from c)
         )
  group by key, value1
)

RESULTS:

KEY        A          B          C          RES
---------- ---------- ---------- ---------- ---------------
A          aaa                              DIFF
B          bbb        bbb                   DIFF
C          ccc        ccc        ccc        OK
D          ddd        ddd        ddd        OK
E                                eeX        DIFF
E          eee        eee                   DIFF

OK, stop here, I think we have found a good solution.


Listing

Recent Archives Advanced Search FAQ by Category Most Popular Hot Articles Your Questions [ Listing ] RSS Feed
Subject | Article | Last Updated (East coast time)
SHARED_POOL_RESERVE_SIZE parameter | article only | Feb 05, 2004 07:33 am
increasing the pool size | article only | Feb 05, 2004 07:35 am
How to do bulk collect in Pro*C variables | article only | Feb 05, 2004 07:39 am
consitent read | article only | Feb 05, 2004 07:48 am
bind variables/ define confusion | article only | Feb 05, 2004 07:54 am
Retrieving Data from BLOB into a file. | article only | Feb 05, 2004 07:19 pm
Summary Tables/materialized Views/OCI | article only | Feb 05, 2004 07:29 pm
questions on views | article only | Feb 06, 2004 08:21 am
Shared Pool | article only | Feb 06, 2004 08:26 am
primary key | article only | Feb 06, 2004 08:38 am
Nested loop join in 8i vs 9i | article only | Feb 07, 2004 12:14 pm
Query on multiple union all results into one | article only | Feb 07, 2004 12:18 pm
Oracle Web Server and HTP/HTF packages | article only | Feb 07, 2004 12:33 pm
Need to Store Session Variables in PL/SQL | article only | Feb 07, 2004 01:09 pm
EXECUTE IMMEDIATE - don`t know how many input variables | article only | Feb 07, 2004 01:15 pm
Index creation with nologging and recovery | article only | Feb 07, 2004 01:24 pm
logon trigger doesnot on guys with dba role | article only | Feb 07, 2004 01:36 pm
mutating table | article only | Feb 07, 2004 01:39 pm
Owa_sylk utility | article only | Feb 07, 2004 02:06 pm
Questions on Pro*C multi-threaded programming | article only | Feb 07, 2004 02:12 pm
Outer Join - row existence decision | article only | Feb 07, 2004 02:32 pm
Developer/2000 forms(5.0) | article only | Feb 07, 2004 02:39 pm
What to do in case Performance of a production database goes slow | article only | Feb 07, 2004 02:41 pm
How to reset sequences? | article only | Feb 07, 2004 03:05 pm
Loading data using XML files | article only | Feb 07, 2004 03:06 pm
RE: [Fwd: Ask Tom Submission] Pragma Exception_Init question | article only | Feb 07, 2004 03:11 pm
Help with v$statname and v$sysstat | article only | Feb 07, 2004 03:12 pm
index_ffs | article only | Feb 07, 2004 03:58 pm
Selecting random records from a table | article only | Feb 07, 2004 04:03 pm
Default sorting order | article only | Feb 07, 2004 04:25 pm
XML Error occurs in procedure when XML data contains ampersand | article only | Feb 07, 2004 04:42 pm
trace files location | article only | Feb 08, 2004 07:25 am
updating nested tables and scalar columns in a single update | article only | Feb 08, 2004 07:28 am
Parallel Insert | article only | Feb 08, 2004 09:11 pm
Can shared pool be *too* large? | article only | Feb 09, 2004 07:19 am
Reducing LIOs | article only | Feb 09, 2004 09:43 am
Recommended way to implement Read Only information | article only | Feb 09, 2004 09:43 am
Sql*Loader problem | article only | Feb 09, 2004 09:54 am
Multiple | article only | Feb 09, 2004 10:39 am
Call a stored procedure over a database link | article only | Feb 09, 2004 12:07 pm
optimizer_index_cost_adj | article only | Feb 09, 2004 12:08 pm
Wait event: DB file sequential read | article only | Feb 09, 2004 12:12 pm
password | article only | Feb 09, 2004 04:14 pm
Determine whether update is coming from trigger | article only | Feb 09, 2004 04:23 pm
date fomat containing timezone data | article only | Feb 09, 2004 08:40 pm
Using the DBMS_STATS-package | article only | Feb 09, 2004 08:44 pm
internal - password | article only | Feb 09, 2004 08:45 pm
String Concatenation | article only | Feb 09, 2004 08:52 pm
CONVERSION DATATYPE | article only | Feb 10, 2004 06:41 am
Index on Domain Index table. | article only | Feb 10, 2004 06:48 am
all_objects vs dba_objecs, describe | article only | Feb 10, 2004 07:14 am
why so many recursive call? | article only | Feb 10, 2004 03:18 pm
function evaluation in where clause | article only | Feb 10, 2004 03:20 pm
SEQUENCES | article only | Feb 10, 2004 03:50 pm
generic trigger for auditing column level changes | article only | Feb 11, 2004 08:47 am
Provide dynamic order by for "hard" cursor | article only | Feb 11, 2004 09:03 am
storing dynamic HTML pages to files | article only | Feb 11, 2004 09:09 am
oracle developer moving to java | article only | Feb 11, 2004 09:21 am
owa_util.cellsprint | article only | Feb 11, 2004 12:31 pm
Not able to FTP from oracle server. Get the error : The filename, directory name, or volume label syntax is incorrect. | article only | Feb 11, 2004 12:42 pm
efficient use of BLOB | article only | Feb 11, 2004 02:44 pm
TNS_ADMIN | article only | Feb 11, 2004 05:19 pm
Using VARRAY Datatypes in User-Defined Aggregate Functions. | article only | Feb 11, 2004 05:27 pm
PL/SQL Wrapper | article only | Feb 11, 2004 06:50 pm
row(s) 1 - 64 of 64
February 05, 2004 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.

Rating: 5
sneh February 04, 2004
Reviewer: Snehal from USA
Hi Tom,
I have 8.1.7 on win2k.
If I hear you right than SQL's , packages and pinned packages irrespective of
their sizes are not stored in shared reserved pool. As per you they get divided
in 4K chunks and are stored in Shared pool and not in reserved shared pool.
1) Than why is that reserved shared pool ( in our case 10% of total shared pool
) required ? Should I remove it ?
2) You also mentioned that reserved pool is required for "big allocations".What
are these "big allocations" who writes them, is this internal to oracle ?
3) Does the package size play role in 4031 error.
We have some packages around 5-10MB+ size, really huge.
Is it advisable to reduce package size.

Thanks,

Followup:
the shared pool reserved component is used for "large memory allocations" --
bigger then the 4k'ish that plsql is paged in.  It was implemented to help
segregate big allocations from small and reduce the swiss cheese effect you
would get by mixing them (eg: UGA memory vs real shared pool stuff).  with the
addition of the large pool, the need for this level of 'tuning' to avoid 4031's
is no longer present.

If I had source code that was 5-10m in size, i'd be wondering "why" (that's more
code then the sum of all of the sizes of my .doc files that make up my book
Expert one on one Oracle!  thats *big*)

Rating: 2
March 31, 2004
Reviewer: A reader

February 05, 2004 Sivababu -- Thanks for the question regarding "increasing the pool size", version 8i and 9i
You Asked
Hello TOM,
       We used around 1000 MB for tablespace to storing our records. I increased
the pool size this way. Is this correct?.

java_pool_size=117440512
large_pool_size=151048576
shared_pool_size=520440512

or When it will be worthfull if we increase the buffer size.

We are inserting the 10000 records in a table and calling more stored procedures
to insert into other tables in a one process.
Is there any possiblity to fast up next time?. ie.. to keep the stored procedure
in the pool and etc..

Thanks and expecting your reply.

regards,
sivababu
and we said...
er?  massive misunderstanding here.

These are apples and oranges.  A tablespace size won't affect or drive the
*_pool_size settings!  Only what you actually do in the database will.

If you don't use java stored procedures -- the java_pool_size could be 1m.  If
you use lots of them, you'll need to size it big enough for your concurrent
workload (177m seems large, you must have lots of users and they use java stored
procedures lots)

Same with the large_pool_size -- unless you are using MTS (shared server) -- its
a tad large at 151m.

The shared pool -- I usually run with 80-100m on a large system, 250m max on a
system with TONS (literally TONS) of plsql code to be cached.

Without more details about your system -- one cannot tell you how to set them
but... I can recommend a book for you that explains how all of this works (my
book -- see
http://asktom.oracle.com/
if you are interested)
Review & Followup
Rating: 4
Thanks March 07, 2002
Reviewer: sivababu from Germany
Hello TOM,
     You are right. I didn't give more information on it. Actually our ERP
product is working for 20 to 100 users. What settings you preferred normally in
init.ora. If some customers will get the problems (slow performance ) i will
increase this pool size then it will be faster than last time. I'm very
interested to buy your book. But not this time due to my financial and due to my
salary also. It is enough to survive. That's it.

Thanks very much .

sivababu.

Followup:
you didn't say anything about the use (or not) of java or MTS -- so, the only
comment i have is that your shared pool is way large (should be 80m probably,
depending on the amount of plsql and sql you run).

you want to be DECREASING the *_pool_size parameters!!   not INCREASING them --
they all look way too large.

Rating: 3
Thanks March 07, 2002
Reviewer: sivababu from Germany
Hello TOM,
     Thanks very mcuh. We are using java stored procedure interface between
Oracle database to pervasive database. But we are not using MTS.  I thought i
can decrease 100 MB in Shared pool, 50 MB in large pool and 30 MB in Java pool.

Anyway i will keep this value in my ini.ora.
Thanks.

regards,
sivababu

Rating: 4
Hanging out March 18, 2002
Reviewer: sivababu from GERMANY
Hello TOM,
   We installed oracle 9i and it was working fine till last week.
But I got some problem now. They are not able to work now. The output of the
v$session is..

STATUS     SERVER
======== =========
ACTIVE     DEDICATED
ACTIVE     DEDICATED
ACTIVE     DEDICATED
ACTIVE     DEDICATED
ACTIVE     DEDICATED
ACTIVE     DEDICATED
INACTIVE NONE
INACTIVE NONE
ACTIVE     SHARED
INACTIVE NONE
INACTIVE NONE
INACTIVE NONE
INACTIVE NONE
INACTIVE NONE
ACTIVE     SHARED
INACTIVE NONE
INACTIVE NONE
INACTIVE NONE
INACTIVE NONE
INACTIVE NONE
INACTIVE NONE
INACTIVE NONE
INACTIVE NONE
INACTIVE NONE
INACTIVE NONE
INACTIVE NONE

But i commented the mts_dispatcher in init.Ora already. Customer worked
something in the linux yesterday. but not in the database. What is the reason?.
Please help me out. They are not able to work now. I'm checking this problem
now.
 I increased the all pool size. But no help.

Thanks.
regards,
sivababu

Followup:
The above shows a normal, lightly loaded system using a mixture of dedicated and
shared (MTS) servers.

Nothing "wrong" there.

You would really have to be more clear.  As far as I can see, everything is
"hunky dory" -- just fine.

If you commented the mts stuff out, you must not have bounced since you are
apparently using MTS (server = none|shared implies you are using MTS).

Rating: 4
More information for last question... March 18, 2002
Reviewer: sivababu from GERMANY
Hello TOM,
   This is from my alert_log file...

System parameters with non-default values:
  processes                = 150
  timed_statistics         = TRUE
  shared_pool_size         = 117440512
  large_pool_size          = 1048576
  java_pool_size           = 117440512
  resource_manager_plan    = SYSTEM_PLAN
  control_files            = /opt/oracle/OraHome1/oradata/imha/control01.ctl, /
  db_block_size            = 8192
  db_cache_size            = 67108864
  compatible               = 9.0.0
  fast_start_mttr_target   = 300
  undo_management          = AUTO
  undo_tablespace          = UNDOTBS
  remote_login_passwordfile= EXCLUSIVE
  db_domain                =
  instance_name            = IMHA
  dispatchers              = (PROTOCOL=TCP)(SER=MODOSE), (PROTOCOL=TCP)(PRE=ora
  background_dump_dest     = /opt/oracle/OraHome1/admin/imha/bdump
  user_dump_dest           = /opt/oracle/OraHome1/admin/imha/udump
  core_dump_dest           = /opt/oracle/OraHome1/admin/imha/cdump
  sort_area_size           = 524288
  db_name                  = imha
  open_cursors             = 300
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
Sat Mar 16 15:46:24 2002
starting up 1 shared server(s) ...
RECO started with pid=7
Sat Mar 16 15:46:24 2002
starting up 1 dispatcher(s) for network address '(ADDRESS=(PARTIAL=YES)(PROTOCO
starting up 1 dispatcher(s) for network address '(ADDRESS=(PARTIAL=YES)(PROTOCO
starting up 1 dispatcher(s) for network address '(ADDRESS=(PARTIAL=YES)(PROTOCO
Sat Mar 16 15:46:25 2002
ALTER DATABASE   MOUNT
Sat Mar 16 15:46:32 2002
Successful mount of redo thread 1, with mount id 1499417107.
Sat Mar 16 15:46:32 2002
Database mounted in Exclusive Mode.
Completed: ALTER DATABASE   MOUNT
Sat Mar 16 15:46:32 2002
ALTER DATABASE OPEN
Sat Mar 16 15:46:32 2002
Beginning crash recovery of 1 threads
Sat Mar 16 15:46:32 2002
Started first pass scan
Sat Mar 16 15:46:32 2002
Completed first pass scan
Sat Mar 16 15:46:32 2002
Started recovery at
 Thread 1: logseq 101, block 3, scn 0.0
Recovery of Online Redo Log: Thread 1 Group 1 Seq 101 Reading mem 0
  Mem# 0 errs 0: /opt/oracle/OraHome1/oradata/imha/redo01.log
Sat Mar 16 15:46:32 2002
Ended recovery at
 Thread 1: logseq 101, block 56, scn 0.3045660
 13 data blocks read, 13 data blocks written, 53 redo blocks read
Crash recovery completed successfully
Sat Mar 16 15:46:32 2002
Thread 1 advanced to log sequence 102
Thread 1 opened at log sequence 102
  Current log# 4 seq# 102 mem# 0: /opt/oracle/OraHome1/oradata/imha/log4.ora
Successful open of redo thread 1.
Sat Mar 16 15:46:32 2002
SMON: enabling cache recovery
Sat Mar 16 15:46:33 2002
Undo Segment 1 Onlined
Undo Segment 2 Onlined
Undo Segment 3 Onlined
Undo Segment 4 Onlined
Undo Segment 5 Onlined
Undo Segment 6 Onlined

I have doubt here. Why it is running in the dispatcher and i have seen the line
starting 1 shared server...
I set
  open_cursors             = 800. But it is showing only 300. Is it taking any
other init file?..

Few entries from the init.ora file..

###########################################
background_dump_dest=/opt/oracle/OraHome1/admin/imha/bdump
core_dump_dest=/opt/oracle/OraHome1/admin/imha/cdump
#timed_statistics=TRUE
user_dump_dest=/opt/oracle/OraHome1/admin/imha/udump

###########################################
# MTS
###########################################
#dispatchers="(PROTOCOL=TCP)(SER=MODOSE)", "(PROTOCOL=TCP)(PRE=oracle.aurora.se

###########################################
# Netzwerkregistrierung
###########################################
instance_name=IMHA

###########################################
# Pools
###########################################
java_pool_size=117440512
large_pool_size=151048576
shared_pool_size=520440512

###########################################
# Prozesse und Sessions
###########################################
processes=150

###########################################
# Cache und E/A
###########################################
db_block_size=8192
db_cache_size=67108864

###########################################
# Cursor und Library-Cache
###########################################
open_cursors=800

##########################################

Thanks in advance.

regards,
sivababu

Followup:
read
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:855380423028
you are using an spfile, pfile is ignored unless you explicitly mention it
during startup.

Rating: 4
Got the problem March 18, 2002
Reviewer: sivababu from GERMANY
Hello TOM,
 I got the problem. Thanks. It is the problem of SPFILE.

Thanks

regards,
sivababu

Rating: 5
Resizing Sga and Buffer cache July 17, 2002
Reviewer: Cct
Hi Tom
Thanks for previous helps.
We have O9.0.1.1.1 with 20-30 concurent users with heavy insert and query during
morning and in evening.
System config. is as below
OS=Winnt 4.0 SP 5
H/w=HP LH Plus P2 300,512mb ram,6 scsi HDD 7200 rpm.
Memory allocation
Shared pool=224 mb
Buffer cache=88 mb
Large pool= 30 mb
Java Pool =24 mb
Redo buffer=512k
Redo log file size=1 mb(multiplexing)
archive log=true.
Index ,Data,undo are on seperate HDD

Hit ratios during peak hours is as :-
1.Buffer hit ratio
PHYSICAL_READS    DB_BLOCK_GETS    CONSISTENT_GETS    Hit Ratio
13887039    2412872     82740589    .8369

2.Library cache
PINS    SUM(PINHITS)    SUM(PINHITS)/SUM(PINS)    CACHEMISSES
1733329    1712840     .9881                     7973

3.shared pool uses
SHARED_POOL_USED SHARED_POOL_SIZE SHARED_POOL_AVAI SHARED_POOL_PCT
83.5664      200     116.4335    41.78323

No use of Java explicitly.

Wait status.

1.SELECT name, count
  FROM x$kcbfwait, v$datafile
 WHERE indx + 1 = file#;
Result=
NAME    COUNT
F:\ORACLE\ORADATA\INDORAMA\SYSTEM01.DBF    2
E:\ORACLE\ORADATA\orcl\UNDOTBS01.DBF    25
F:\ORACLE\ORADATA\orcl\CWMLITE01.DBF    0
F:\ORACLE\ORADATA\orcl\DRSYS01.DBF    0
F:\ORACLE\ORADATA\orcl\EXAMPLE01.DBF    0
F:\ORACLE\ORADATA\orcl\INDX01.DBF    0
F:\ORACLE\ORADATA\orcl\TOOLS01.DBF    0
F:\ORACLE\ORADATA\orcl\USERS01.DBF    0
G:\ORACLE\ORADATA\orcl\MY_DATA.DBF    9768
H:\ORACLE\ORADATA\orcl\MY_IDX.DBF    71

2.redi buffer wait
SELECT
          RPAD('Redo Log space requests',25,' ')||' = '||value
FROM      v$sysstat
WHERE     name = 'redo log space requests'
Result=
RPAD('REDOLOGSPACEREQUESTS',25
Redo Log space requests   = 280

3.sysstat
STATISTIC#    NAME    CLASS    VALUE
0    logons cumulative    1    2000
1    logons current    1    60
2    opened cursors cumulative    1    107263
3    opened cursors current    1    1081
4    user commits    1    18286
5    user rollbacks    1    7579
6    user calls    1    1996829
7    recursive calls    1    747915
8    recursive cpu usage    1    80291
9    session logical reads    1    92245384
10    session stored procedure space    1    0
11    CPU used when call started    128    2093323
12    CPU used by this session    1    2093413
13    session connect time    1    1952871744915
14    process last non-idle time    128    1952871744915
15    session uga memory    1    26657120
16    session uga memory max    1    448001784
17    messages sent    128    52638
18    messages received    128    52640
19    background timeouts    128    485202
20    session pga memory    1    158408072
21    session pga memory max    1    159963328
22    enqueue timeouts    4    107
23    enqueue waits    4    41
24    enqueue deadlocks    4    0
25    enqueue requests    4    317311
26    enqueue conversions    4    17118
27    enqueue releases    4    317182
28    global lock sync gets    32    0
29    global lock async gets    32    0
30    global lock get time    32    0
31    global lock sync converts    32    0
32    global lock async converts    32    0
33    global lock convert time    32    0
34    global lock releases    32    0
35    total file opens    8    1
36    opens requiring cache replacement    8    0
37    opens of replaced files    8    0
38    gcs messages sent    32    0
39    ges messages sent    32    0
40    db block gets    8    2420680
41    consistent gets    8    89824704
42    physical reads    8    14193443
43    db block changes    8    1552845
44    consistent changes    8    23284
45    recovery blocks read    8    0
46    physical writes    8    385779
47    physical writes non checkpoint    8    365186
48    summed dirty queue length    8    155
49    DBWR checkpoint buffers written     8    62560
50    DBWR transaction table writes    8    2968
51    DBWR undo block writes    8    34621
52    DBWR revisited being-written buffer    8    0
53    DBWR make free requests    8    3176
54    DBWR free buffers found    8    2627106
55    DBWR lru scans    8    3176
56    DBWR summed scan depth    8    2669619
57    DBWR buffers scanned    8    2669619
58    DBWR checkpoints    8    236
59    DBWR cross instance writes    40    0
60    DBWR fusion writes    40    0
61    remote instance undo block writes    40    0
62    remote instance undo header writes    40    0
63    prefetch clients - keep    8    0
64    prefetch clients - recycle    8    0
65    prefetch clients - default    8    3
66    prefetch clients - 2k    8    0
67    prefetch clients - 4k    8    0
68    prefetch clients - 8k    8    0
69    prefetch clients - 16k    8    0
70    prefetch clients - 32k    8    0
71    change write time    8    5281
72    redo synch writes    8    18633
73    redo synch time    8    33866
74    exchange deadlocks    8    0
75    free buffer requested    8    14024127
76    dirty buffers inspected    8    5435
77    pinned buffers inspected    8    177
78    hot buffers moved to head of LRU    8    5759876
79    free buffer inspected    8    5616
80    commit cleanout failures: write disabled    8    0
81    commit cleanout failures: block lost    8    186
82    commit cleanout failures: cannot pin    8    0
83    commit cleanout failures: hot backup in progress    8    0
84    commit cleanout failures: buffer being written    8    53
85    commit cleanout failures: callback failure     8    7
86    commit cleanouts    8    227383
87    commit cleanouts successfully completed    8    227137
88    recovery array reads    8    0
89    recovery array read time    8    0
90    CR blocks created    8    11178
91    current blocks converted for CR    8    5
92    switch current to new buffer    8    13718
93    write clones created in foreground    8    321
94    write clones created in background    8    2
95    prefetched blocks    8    7746448
96    prefetched blocks aged out before use    8    282
97    physical reads direct    8    229911
98    physical writes direct    8    275486
99    physical reads direct (lob)    8    0
100    physical writes direct (lob)    8    0
101    cold recycle reads    8    0
102    consistent gets - examination    8    6582852
103    shared hash latch upgrades - no wait    8    2249410
104    shared hash latch upgrades - wait    8    0
105    calls to kcmgcs    128    172810
106    calls to kcmgrs    128    0
107    calls to kcmgas    128    56881
108    next scns gotten without going to GES    32    0
109    Unnecesary process cleanup for SCN batching    32    0
110    calls to get snapshot scn: kcmgss    32    948195
111    kcmgss waited for batching    32    0
112    kcmgss read scn without going to GES    32    0
113    kcmccs called get current scn    32    0
114    redo entries    2    793710
115    redo size    2    230707792
116    redo buffer allocation retries    2    244
117    redo wastage    2    8687152
118    redo writer latching time    2    18
119    redo writes    2    39209
120    redo blocks written    2    482389
121    redo write time    2    78100
122    redo log space requests    2    280
123    redo log space wait time    2    10874
124    redo log switch interrupts    2    0
125    redo ordering marks    2    0
126    global cache gets    40    0
127    global cache get time    40    0
128    global cache converts    40    0
129    global cache convert time    40    0
130    global cache cr blocks received    40    0
131    global cache cr block receive time    40    0
132    global cache current blocks received    40    0
133    global cache current block receive time    40    0
134    global cache cr blocks served    40    0
135    global cache cr block build time    40    0
136    global cache cr block flush time    40    0
137    global cache cr block send time    40    0
138    global cache current blocks served    40    0
139    global cache current block pin time    40    0
140    global cache current block flush time    40    0
141    global cache current block send time    40    0
142    global cache freelist waits    40    0
143    global cache defers    40    0
144    global cache convert timeouts    40    0
145    global cache blocks lost    40    0
146    global cache claim blocks lost    40    0
147    global cache blocks corrupt    40    0
148    global cache prepare failures    40    0
149    global cache skip prepare failures    40    0
150    total number of slots    8    0
151    instance recovery database freeze count    32    0
152    background checkpoints started    8    236
153    background checkpoints completed    8    235
154    serializable aborts    1    0
155    transaction lock foreground requests    128    0
156    transaction lock foreground wait time    128    0
157    transaction lock background gets    128    0
158    transaction lock background get time    128    0
159    transaction tables consistent reads - undo records applied    128    27
160    transaction tables consistent read rollbacks    128    1
161    data blocks consistent reads - undo records applied    128    15697
162    no work - consistent read gets    128    80804336
163    cleanouts only - consistent read gets    128    1153
164    rollbacks only - consistent read gets    128    605
165    cleanouts and rollbacks - consistent read gets    128    10591
166    rollback changes - undo records applied    128    4822
167    transaction rollbacks    128    143
168    immediate (CURRENT) block cleanout applications    128    29001
169    immediate (CR) block cleanout applications    128    11744
170    deferred (CURRENT) block cleanout applications    128    130467
171    Commit SCN cached    128    0
172    Cached Commit SCN referenced    128    0
173    table scans (short tables)    64    183246
174    table scans (long tables)    64    5900
175    table scans (rowid ranges)    64    0
176    table scans (cache partitions)    64    0
177    table scans (direct read)    64    0
178    table scan rows gotten    64    366709485
179    table scan blocks gotten    64    11257505
180    table fetch by rowid    64    257101660
181    table fetch continued row    64    810281
182    cluster key scans    64    100036
183    cluster key scan block gets    64    221962
184    rows fetched via callback    64    569209
185    leaf node splits    128    3862
186    branch node splits    128    36
187    native hash arithmetic execute    64    0
188    native hash arithmetic fail    64    0
189    index fast full scans (full)    64    5038
190    index fast full scans (rowid ranges)    64    0
191    index fast full scans (direct read)    64    0
192    session cursor cache hits    64    0
193    session cursor cache count    64    0
194    cursor authentications    128    2658
195    queries parallelized    32    0
196    DML statements parallelized    32    0
197    DDL statements parallelized    32    0
198    DFO trees parallelized    32    0
199    Parallel operations not downgraded    32    0
200    Parallel operations downgraded to serial    32    0
201    Parallel operations downgraded 75 to 99 pct    32    0
202    Parallel operations downgraded 50 to 75 pct    32    0
203    Parallel operations downgraded 25 to 50 pct    32    0
204    Parallel operations downgraded 1 to 25 pct    32    0
205    PX local messages sent    32    0
206    PX local messages recv'd    32    0
207    PX remote messages sent    32    0
208    PX remote messages recv'd    32    0
209    buffer is pinned count    72    452717563
210    buffer is not pinned count    72    74476551
211    no buffer to keep pinned count    72    2309422
212    table lookup prefetch client count    8    0
213    workarea memory allocated    64    0
214    workarea executions - optimal    64    0
215    workarea executions - onepass    64    0
216    workarea executions - multipass    64    0
217    parse time cpu    64    14776
218    parse time elapsed    64    20468
219    parse count (total)    64    247422
220    parse count (hard)    64    12121
221    parse count (failures)    64    8075
222    execute count    64    931926
223    bytes sent via SQL*Net to client    1    233538237806
224    bytes received via SQL*Net from client    1    157844962
225    SQL*Net roundtrips to/from client    1    3759911
226    bytes sent via SQL*Net to dblink    1    0
227    bytes received via SQL*Net from dblink    1    0
228    SQL*Net roundtrips to/from dblink    1    0
229    sorts (memory)    64    131059
230    sorts (disk)    64    345
231    sorts (rows)    64    1015874132

Please help to tune it further as during peak time users are experiencing slow
response.Please suggest the possible action.
Should shared pool to reduce and Bufferpool to increase?

Sorry for the large writeup.
Thanks and rgds
Oct

Followup:
your shared pool looks utterly too large.  Way too large.

Statistics are virtually useless without other information.  Suppose I tell you
that on my last car trip I had to stop at 50 red lights.  Tell me -- is that
good or bad (and no matter what you answer, I'll very successfully argue the
contrary is true).

512 meg is pretty small for a server, don't you think?

If you have my book -- read chapter 10, learn about statspack.  If you don't
have my book goto www.oracle.com/oramag and search for statspack -- couple of
articles there.

Rating: 5
server configuration July 18, 2002
Reviewer: Oct
Hi Tom,
You mentioned that 512 meg is pretty small,now we are planning to buy a ner HP
server .
What do you suggest ,what should be a fairly good server configuration for
multiuser environment with around 20 concurrent user with high insert and
reporting.
I think your recomendation will help every one in making decision for a upgrade
or replace to new H/w.
CPu?
Memory?
HDD.
...
I already have your book but not gone thr all chapters.
Thanks
Oct

Followup:
You need to look at your existing system -- figure out what you are really
using.  512 could probably do it -- but not the way you have your SGA set right
now.  512 is "small" but for 20/30 concurrent users it is doable.

Use the tools your OS provides to determine is you are making good use of
existing memory.

Rating: 4
Little clarification with respect to db_block_size September 04, 2002
Reviewer: A reader
Tom,

Just a little confusion, so clerification required. If I set db_keep_cache_size
then does it allocate memory out of db_cache_size, or memory is allocated in
addition to db_cache_size.

Followup:
it is in addition to:

ops$tkyte@ORA9I.WORLD> show sga

Total System Global Area  174359528 bytes
Fixed Size                   279528 bytes
Variable Size             134217728 bytes
Database Buffers           33554432 bytes
Redo Buffers                6307840 bytes
ops$tkyte@ORA9I.WORLD> show parameter cache

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
...
db_keep_cache_size                   big integer 0
...

ops$tkyte@ORA9I.WORLD> alter system set db_keep_cache_size=15m scope=memory;
System altered.

ops$tkyte@ORA9I.WORLD> show sga
Total System Global Area  174359528 bytes
Fixed Size                   279528 bytes
Variable Size             117440512 bytes
Database Buffers           50331648 bytes
Redo Buffers                6307840 bytes

ops$tkyte@ORA9I.WORLD> alter system set db_keep_cache_size=0 scope=memory;
System altered.

ops$tkyte@ORA9I.WORLD> show sga

Total System Global Area  174359528 bytes
Fixed Size                   279528 bytes
Variable Size             134217728 bytes
Database Buffers           33554432 bytes
Redo Buffers                6307840 bytes
ops$tkyte@ORA9I.WORLD>

See how the database buffers goes up and down as I change it (online ;)

Rating: 5
Excellent September 04, 2002
Reviewer: A reader
Excellent answer.

Rating: 4
DB_KEEP_CACHE_SIZE & DB_RECYCLE_CACHE_SIZE April 09, 2003
Reviewer: Amar from INDIA
Hi Tom,

What i wanted to know was

1. How do we decide on what the size of DB_KEEP_CACHE_SIZE &
DB_RECYCLE_CACHE_SIZE should be.

2. And after setting them how do i know if its really working. Ok i can see the
value of KEPT from  v$db_object_cache, fine but how can i see if the performance
has increased (i'm not saying that this is another fast=true :)), if i could
just see a small improvement.

Thanks and Regards

Followup:
1) have you identified that you need one.  Do you have some performance related
issues you are trying to solve and you've seen that having a keep and recycle
pool would help you?

If so -- sizing this would be relatively easy -- you know the objects you want
to keep and recycle (you'll be telling those objects to go into these pools).
sizing the keep pool will be a function of how much of those kept objects would
you like kept in cache.  sizing the recycle pool will be how much ram do I want
to give to caching those objects.  The keep and recycle pool differ only in how
they cache blocks -- you would size them according to what your goals are for
the objects you put into them....

2) statspack would be one way to measure the performance of your system.  take
15 minute snapshots every day (not every 15 minutes, one or two 15 minute
windows) during "peak".  Do this for a while before you make your changes.  Make
your changes, review the results (compare the statspack).  Do your waits go
down, did you decrease physical IO, are you doing more TPS....

Rating: 5
Thanks TOM April 09, 2003
Reviewer: A reader

Rating: 4
pool size April 29, 2003
Reviewer: juancarlosreyesp@yahoo.com from Bolivia
Hi Tom,
I had been  searching in every  where, several days and I don't find a clear
answer, I suppose it is in some way related to the pool size, but could you
please give an explanation about what are this parameters in "english" please,
because the documentation says nothing about them.
:)
OBJECT_CACHE_OPTIMAL_SIZE OBJECT_CACHE_MAX_SIZE_PERCENT

Followup:
do you use the Objects Option -- do you use the Objects API, if not ignore it.

If you do -- it relates to that.  they are actually documented:
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96536/ch1129.htm#1020397
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96536/ch1130.htm#1020454
with pointers into the OCI and Pro*C guide as they really affect those
environments -- not the server.

      Rating:
      5
  April 29, 2003
Reviewer:
      A reader
Thanks a lot

      Rating: 5
large pool and
      relevant to dedicated server option  July 10, 2003

Reviewer:  A reader
in your book, you mention why large pools are even
more important for MTS configuration. However, are large
pools relevant for dedicated mode? I presume yes. For
one, even in dedicated mode, you would still have
large plsql blocks (e.g. anonymous blocks) that
would need to be "pinned". Since pinning is not
good since 8i according to you - I presume that
this kind of allocation is done from large pool -
is that right? Point is that there are still two
types of allocation - one that needs LRU and one that
should not be recycled.

Thank you!!

          Followup:

plsql is chunked in 4k pieces and stored always in the shared pool, never in the
large pool.

plsql code is LRU managed.  the reason you don't need pinning is because you are
not mixing both LRU and Heap memory in the shared pool anymore so the
fragmentation isn't an issue as it was before the large pool was introduced with
MTS/shared server.

      Rating:
      5
thanx!  July 10, 2003
Reviewer:
      A reader
does that mean that large pool is only useful in case
of MTS? So if we find ourselves "improving" performance
by pinning some oft used plsql code - then what is the
better alternative (assuming bind variables are used)?
I presume this implies may be we need to resize (enlarge)
our shared pool size potentially?

Thank you!

          Followup:

mts, sometimes rman, parallel query

the better alternative?  just don't pin.  If you use it, it'll stay there.  If
you don't, and we need the room, it'll go away.  If things go away and come back
too often (poor library cache hit, lots of reloads) you need to think about
resizing -- but you'll have that problem with pinning by definition as well.

      Rating: 5
thanx
      tom!  July 11, 2003
Reviewer:  A reader

Till 7.x, we used to pin since shared memory was
supporting two different kinds of stuff - one LRU and other recycled ones.
In subsequent versions of ORacle (8i and later), separate memory area was
allocated for the stuff that was recycled ) - called large pool.

This implies that even in 7.x we needed to pin only in
case we were using MTS, RMAN (only that seems to mainly need the *other* kind of
memory usage). Is this statement
correct?

Thanx!

          Followup:

not 100%, in earlier releases - code was not "chunked in" in small pieces -- so
the memory in the shared pool was all different sizes.  this leads to
fragmentation in the same fashion a dictionary managed tablespace did with disk
storage.

now, code is chunked in in about 4k chunks -- so most of the memory allocations
are "reusable" by another memory allocation.

      Rating: 5
thank
      you!  July 13, 2003
Reviewer:  A reader

"not 100%, in earlier releases - code was not "chunked in" in small pieces -- so

the memory in the shared pool was all different sizes.  this leads to
fragmentation in the same fashion a dictionary managed tablespace did with disk
storage."

just to make sure I understand.
In earlier shared pool management, the "chunk" size was
not fixed (to 4k) so different kinds of entities asked
for (and required) different sizes of "chunks".
So for example, even if you have a slot of 4K free in
shared pool, because a plsql package/procedure
required to have a 5K chunk, it could not se the
4K chunk. And there were many such chunk sizes.

With 8i we now allocate chunk with just 1 size 4k. Hence
any time such a chunk is free it can be used by any
entity - hence leads to less fragmentation.

Is the aboe logic correct? If so, could you please explain
how before 8i, the chunk sizes were different?
In DMT, this is due to the fact that you could specify
nextextent etc differently per entity - what was the
corresponding reason for pre 8i shared pool management?

Thank you!!

          Followup:

If I had a bunch of "10k" pieces of code and loaded them all up -- they would
have each taken 10k of contigous memory in 7.x and before.

Now say I can age every other one out -- leaving say 100 free chunks of 10k for
1000k of free memory.

I get a request to load a 20k package -- no go, I might have 1 meg free but it
is in 100 non-contigous 10k chunks.

It is because each chunk of code used to be its own unique size that we hit this
issue -- just like DMT's get fragmented

      Rating:
      4
  July 14, 2003
Reviewer:
      A reader
Hi Tom,
 We most of the time discus on pinning the objects,query parsing and query
tuning ?? Sometimes client come and ask to review the query and asks for
suggestion to improve the performance, which is hardly possible.
Now Can we make a classification of some bad queries that most of the
people/DBA/Developer encounter?? and suggestion to improve the performance of
that type of query's.
If you have such classification or if you know such queries can you please share
with us ?? or Can we make such classification if that doesn't exist?? I will
take initiative to compile these and document it ?? You all can mail those to me
and then we can publish it some where ?? or Anyone of us may take initiative ??

Any suggestions ??

Thanks in advance,
Vijay

      Rating: 5
To Vijay - this
      site is the compilation  July 14, 2003
Reviewer:
      A reader
that you are looking for. And Tom's book "Expert one on
one" at
http://www.apress.com/book/bookDisplay.html?bID=220
is a good compilation of these issues.

But I guess Tom's favorite thing in a bad query
would be "use bind variables":) Looking just at the
query though is not sufficient - there are thousands
of other things such as indexes, table structures,
and in general proper use of the tool.

      Rating: 5
Can't find any
      sort_area_size page in this site.... so i'm posting it
      here.  July 21, 2003
Reviewer:  Alvin
       from Philippines
I read this in the performance tuning manual 9i chapter 13.

Example 13-5 Example of Creating Indexes Efficiently
A system with 512 Mb memory runs an Oracle instance with a 100 Mb SGA, and the
operating system uses 50 Mb. The memory available for sorting is 362 Mb, which
equals 512 minus 50 minus 100. If the system has four CPUs running with four
parallel slaves, then each of these will have 90 Mb available. 10 Mb is set
aside for process overhead, so SORT_AREA_SIZE should be set to 80 Mb. This can
be done either in the initialization file or for each session with the following
statement:

ALTER SESSION SET SORT_AREA_SIZE = 80000000;

Isn't 80Mb a tad excessive ? Do i need to increase the sort_area_size if i'm
generating more I/O in my temp tablespace than the others ? Because i interpret
it as sorting to disk.

Below is the IO stats of my DB. The temp tbs has twice more i/o than the user
tbs.

Tablespace IO Stats for DB: RTBE  Instance: rtbe  Snaps: 61 -62
->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)
-------------- ------- ------ ------- ------------ -------- ---------- ------
TEMP
         7,568      10    0.0     2.1        3,784        5          0    0.0
USERS
         3,711       5    6.3     1.1           67        0          0    0.0
SYSTEM
           621       1    5.6     1.1          102        0          0    0.0
TOOLS
           265       0    8.9     1.0          305        0          0    0.0
RBS
             5       0    0.0     1.0          325        0          0    0.0
INDX
            41       0   11.0     1.0          136        0          0    0.0
DRSYS
             2       0    0.0     1.0            2        0          0    0.0
          -------------------------------------------------------------

I've set my sort_area_size as

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
nls_sort                             string
sort_area_retained_size              integer 2048000
sort_area_size                       integer 1024000
sort_multiblock_read_count           integer 2

Btw, Our DB is 8i only i was just browsing through the 9i docs.

          Followup:

80m would be excessive in my opinion, yes. 10m would be a more logical choice I
find, especially in parallel, you want some reading, some writing, some reading,
some writing.  you want them to interleave.

unless the entire sort can be done in RAM using 320meg -- 80meg seems large.

      Rating: 4
Caching for
      intermittent queries   December 08, 2003
Reviewer:
      Matt  from UK
Tom,

We are experiencing large performance differences on some of our Forms - the
plans for the forms look ok. The problem seems to be caused by initiall Physical
I/O.

The form in question is a Goods receiving form, and typically it is used
sperodically during the day. (whenever a van turns up at the gatehouse).

The first time we run a query on it based on an item it takes around a minute to
execute. 2nd time, if we requery immediately it takes around 25 seconds, 3rd
time 5 seconds, and then around a second for each subsequent query.

Parse time is negligible.

Is this due to the buffer being 'cold' ?
Why do we see a variable performance time, and not just fast second query ?

I know removing PIO is not achievable - but is there anyway to remove the
initial 'big hit' ? This is Oracle Apps - the receiving form is based on a large
union ( hitting about 80 objects !!)

Any thoughts ?

          Followup:

for an OLTP application to need to take even a little more then 1 second to run
what should be a basic, simple query indicates "somethings wrong with the
design" to me.

If you need 1 minute to warm up the cache for an item -- either

a) you have the worlds slowest disk
b) you do not have a model that facilitates what you are doing.

removing PIO is achievable!  hows about you

a) instruement your code with the ability to issue alter session set
sql_trace=true at the push of a button (and =false on the next push)

b) push the button
c) run the slow thing
d) push the button again

share with us the tkprof report and we'll see if the "plans look good" or not.

      Rating:
      4
Sperodically=Sporadically !! n/t  December 08, 2003

Reviewer:  Matt  from UK
.

      Rating:
      1
Hmm  December 10, 2003
Reviewer:
      Matt  from UK
You missed a key line in that above statement. We are running Oracle APPS -
their design is quite possibly floored. I mean putting a receiving Form on top
of a view which hits around 80 tables isn't a great idea !

          Followup:

if you have a problem with apps -- please contact support.

you said things like "plan is good" (how, how can you say that???  i cannot look
at a plan and say 'plan is good' -- especially if it were on 80 tables, which i
sort of doubt, but anyway)

if you want to try and tune this, we can, however you'll have to get the tkprof
of the problem query.

else, please -- open a tar with support and file a tar against apps (something I
did not myself "code")

      Rating: 2
Plan
        December 11, 2003
Reviewer:  Matt
       from UK
You were right to have doubts I aprox'd the number of objects based on 4 unions
around 20 tables each - there are actually around 72, although I lost count at
around 46 so may be a little out.

I say the plan is good, because I think for this view it is as good as its going
to get, its driving correctly, its performing quite well for a 70(ish) join
union select.

My original question was, why do we see gradual improvement in performance, and
not fast second time ? Is there any way to stop the initial hit ?

Operation    Object Name    Rows    Bytes    Cost    Object Node    In/Out
PStart    PStop

SELECT STATEMENT Optimizer Mode=CHOOSE        5           354

  SORT ORDER BY        5      22 K    354
    VIEW    APPS.RCV_ENTER_RECEIPTS_V    5      22 K    325

      SORT UNIQUE        5      3 K    325

        UNION-ALL
          NESTED LOOPS OUTER        1      630      21

            NESTED LOOPS OUTER        1      616      19

              NESTED LOOPS OUTER        1      589      18

                NESTED LOOPS        1      575      17

                  NESTED LOOPS        1      524      14

                    NESTED LOOPS OUTER        1      472      13

                      NESTED LOOPS OUTER        1      454      12

                        NESTED LOOPS        1      433      11

                          NESTED LOOPS OUTER        1      194      7

                            NESTED LOOPS OUTER        1      177      6

                              NESTED LOOPS OUTER        1      171      5

                                NESTED LOOPS OUTER        1      147      2

                                  NESTED LOOPS OUTER        1      120      2

                                    TABLE ACCESS BY INDEX ROWID
PO.PO_LINES_ALL    1      91      2
                                      INDEX RANGE SCAN    PO.PO_LINES_N1    1
       1
                                    VIEW PUSHED PREDICATE
APPS.PO_HAZARD_CLASSES_VL    1      29

                                      NESTED LOOPS        1      29      1

                                        INDEX UNIQUE SCAN
PO.PO_HAZARD_CLASSES_U1    1      13
                                        TABLE ACCESS BY INDEX ROWID
PO.PO_HAZARD_CLASSES_TL    1      16
                                          INDEX UNIQUE SCAN
PO.PO_HAZARD_CLASSES_TL_U1    1
                                  VIEW PUSHED PREDICATE    APPS.PO_UN_NUMBERS_VL
   1      27
                                    NESTED LOOPS        1      29      1

                                      INDEX UNIQUE SCAN    PO.PO_UN_NUMBERS_B_U1
   1      13
                                      TABLE ACCESS BY INDEX ROWID
PO.PO_UN_NUMBERS_TL    1      16
                                        INDEX UNIQUE SCAN
PO.PO_UN_NUMBERS_TL_U1    1
                                TABLE ACCESS BY INDEX ROWID
INV.MTL_SYSTEM_ITEMS_B    1      24      3

                                  INDEX RANGE SCAN    INV.MTL_SYSTEM_ITEMS_B_U1
  1           2
                              VIEW PUSHED PREDICATE    APPS.PO_LINE_TYPES_VL
9      54
                                NESTED LOOPS        1      13      1

                                  TABLE ACCESS BY INDEX ROWID
PO.PO_LINE_TYPES_B    1      6      1
                                    INDEX UNIQUE SCAN    PO.PO_LINE_TYPES_B_U1
 1
                                  INDEX UNIQUE SCAN    PO.PO_LINE_TYPES_TL_U1
9      63
                            TABLE ACCESS BY INDEX ROWID
INV.MTL_UNITS_OF_MEASURE_TL    11      187      1

                              INDEX UNIQUE SCAN
INV.MTL_UNITS_OF_MEASURE_TL_U1    11

                          TABLE ACCESS BY INDEX ROWID
PO.PO_LINE_LOCATIONS_ALL    773      180 K    4

                            INDEX RANGE SCAN    PO.PO_LINE_LOCATIONS_N1    773
        2
                        TABLE ACCESS BY INDEX ROWID    PO.RCV_ROUTING_HEADERS
3      63      1
                          INDEX UNIQUE SCAN    PO.RCV_ROUTING_HEADERS_U1    3

                      TABLE ACCESS BY INDEX ROWID    HR.HR_LOCATIONS_ALL_TL    3
     54      1
                        INDEX UNIQUE SCAN    HR.HR_LOCATIONS_ALL_TL_PK    3

                    TABLE ACCESS BY INDEX ROWID    PO.PO_HEADERS_ALL    1 K
98 K    1
                      INDEX UNIQUE SCAN    PO.PO_HEADERS_U1    1 K

                  TABLE ACCESS BY INDEX ROWID    APPLSYS.FND_LOOKUP_VALUES    1
    51      3
                    INDEX RANGE SCAN    APPLSYS.FND_LOOKUP_VALUES_U1    1
   2
                TABLE ACCESS BY INDEX ROWID    GL.GL_DAILY_CONVERSION_TYPES    8
     112      1
                  INDEX UNIQUE SCAN    GL.GL_DAILY_CONVERSION_TYPES_U1    8

              TABLE ACCESS BY INDEX ROWID    PO.PO_VENDORS    3 K    98 K    1

                INDEX UNIQUE SCAN    PO.PO_VENDORS_U1    3 K

            TABLE ACCESS BY INDEX ROWID    PO.PO_RELEASES_ALL    7 K    105 K
2
              INDEX UNIQUE SCAN    PO.PO_RELEASES_U1    7 K         1

          MERGE JOIN CARTESIAN        1      750      36

            NESTED LOOPS        1      750      34

              NESTED LOOPS        1      747      33

                NESTED LOOPS OUTER        1      741      32

                  NESTED LOOPS OUTER        1      726      31

                    NESTED LOOPS OUTER        1      699      31

                      NESTED LOOPS OUTER        1      670      31

                        NESTED LOOPS OUTER        1      490      30

                          NESTED LOOPS OUTER        1      473      29

                            NESTED LOOPS OUTER        1      455      28

                              NESTED LOOPS OUTER        1      434      27

                                NESTED LOOPS OUTER        1      425      25

                                  NESTED LOOPS OUTER        1      401      23

                                    NESTED LOOPS        1      350      20

                                      NESTED LOOPS        1      279      18

                                        NESTED LOOPS        1      85      4

                                          NESTED LOOPS        1      81      4

                                            NESTED LOOPS        1      57      4

                                              NESTED LOOPS        1      29
3
                                                TABLE ACCESS BY INDEX ROWID
HR.HR_ALL_ORGANIZATION_UNITS_TL    1      24      2

                                                  INDEX RANGE SCAN
HR.HR_ALL_ORGANIZATION_UNTS_TL_N2    1           1

                                                TABLE ACCESS BY INDEX ROWID
HR.HR_ALL_ORGANIZATION_UNITS    2      10      1

                                                  INDEX UNIQUE SCAN
HR.HR_ORGANIZATION_UNITS_PK    2

                                              TABLE ACCESS BY INDEX ROWID
HR.HR_ORGANIZATION_INFORMATION    1      28      1

                                                INDEX RANGE SCAN
HR.HR_ORGANIZATION_INFORMATIO_FK2    1

                                            TABLE ACCESS BY INDEX ROWID
HR.HR_ORGANIZATION_INFORMATION    1      24

                                              INDEX RANGE SCAN
HR.HR_ORGANIZATION_INFORMATIO_FK2    1

                                          INDEX UNIQUE SCAN
INV.MTL_PARAMETERS_U1    1      4
                                        TABLE ACCESS BY INDEX ROWID
PO.RCV_SHIPMENT_LINES    15      2 K    14

                                          INDEX RANGE SCAN
PO.RCV_SHIPMENT_LINES_N1    15           2

                                      TABLE ACCESS BY INDEX ROWID
PO.RCV_SHIPMENT_HEADERS    1      71      2

                                        INDEX UNIQUE SCAN
PO.RCV_SHIPMENT_HEADERS_U1    1           1

                                    TABLE ACCESS BY INDEX ROWID
APPLSYS.FND_LOOKUP_VALUES    1      51      3

                                      INDEX RANGE SCAN
APPLSYS.FND_LOOKUP_VALUES_U1    1           2

                                  TABLE ACCESS BY INDEX ROWID
INV.MTL_SYSTEM_ITEMS_B    1      24      2

                                    INDEX UNIQUE SCAN
INV.MTL_SYSTEM_ITEMS_B_U1    1           1

                                TABLE ACCESS BY INDEX ROWID
INV.MTL_SYSTEM_ITEMS_B    1      9      2
                                  INDEX UNIQUE SCAN    INV.MTL_SYSTEM_ITEMS_B_U1
   1           1
                              TABLE ACCESS BY INDEX ROWID
PO.RCV_ROUTING_HEADERS    3      63      1

                                INDEX UNIQUE SCAN    PO.RCV_ROUTING_HEADERS_U1
 3
                            TABLE ACCESS BY INDEX ROWID
HR.HR_LOCATIONS_ALL_TL    3      54      1

                              INDEX UNIQUE SCAN    HR.HR_LOCATIONS_ALL_TL_PK
3
                          TABLE ACCESS BY INDEX ROWID
INV.MTL_UNITS_OF_MEASURE_TL    11      187      1

                            INDEX UNIQUE SCAN    INV.MTL_UNITS_OF_MEASURE_TL_U1
  11
                        TABLE ACCESS BY INDEX ROWID
PO.PO_REQUISITION_LINES_ALL    501      88 K    1

                          INDEX UNIQUE SCAN    PO.PO_REQUISITION_LINES_U1    501

                      VIEW PUSHED PREDICATE    APPS.PO_HAZARD_CLASSES_VL    1
  29
                        NESTED LOOPS        1      29      1

                          INDEX UNIQUE SCAN    PO.PO_HAZARD_CLASSES_U1    1
13
                          TABLE ACCESS BY INDEX ROWID    PO.PO_HAZARD_CLASSES_TL
   1      16
                            INDEX UNIQUE SCAN    PO.PO_HAZARD_CLASSES_TL_U1    1

                    VIEW PUSHED PREDICATE    APPS.PO_UN_NUMBERS_VL    1      27

                      NESTED LOOPS        1      29      1

                        INDEX UNIQUE SCAN    PO.PO_UN_NUMBERS_B_U1    1      13

                        TABLE ACCESS BY INDEX ROWID    PO.PO_UN_NUMBERS_TL    1
    16
                          INDEX UNIQUE SCAN    PO.PO_UN_NUMBERS_TL_U1    1

                  TABLE ACCESS BY INDEX ROWID    PO.PO_REQUISITION_HEADERS_ALL
 495      7 K    1
                    INDEX UNIQUE SCAN    PO.PO_REQUISITION_HEADERS_U1    495

                TABLE ACCESS BY INDEX ROWID    INV.MTL_SUPPLY    9 K    57 K
1
                  INDEX RANGE SCAN    INV.MTL_SUPPLY_N9    9 K

              INDEX FULL SCAN    GL.GL_SETS_OF_BOOKS_U2    1      3      1

            SORT JOIN        1           35

              TABLE ACCESS FULL    APPLSYS.FND_PRODUCT_GROUPS    1           2

          NESTED LOOPS OUTER        2      1 K    144

            NESTED LOOPS        2      1 K    142

              NESTED LOOPS OUTER        2      1 K    138

                NESTED LOOPS OUTER        2      1 K    134

                  NESTED LOOPS OUTER        2      1 K    132

                    NESTED LOOPS OUTER        2      1 K    132

                      NESTED LOOPS        2      1 K    132

                        NESTED LOOPS OUTER        2      1 K    128

                          NESTED LOOPS        2      1 K    126

                            NESTED LOOPS        10      5 K    96

                              NESTED LOOPS        10      5 K    86

                                NESTED LOOPS OUTER        10      2 K    66

                                  NESTED LOOPS OUTER        10      2 K    56

                                    NESTED LOOPS OUTER        10      2 K    46

                                      FILTER

                                        NESTED LOOPS OUTER

                                          TABLE ACCESS BY INDEX ROWID
PO.RCV_SHIPMENT_LINES    10      1 K    15

                                            INDEX RANGE SCAN
PO.RCV_SHIPMENT_LINES_N1    10           3

                                          TABLE ACCESS BY INDEX ROWID
INV.MTL_SYSTEM_ITEMS_B    1      24      3

                                            INDEX RANGE SCAN
INV.MTL_SYSTEM_ITEMS_B_U1    1           2

                                      TABLE ACCESS BY INDEX ROWID
PO.RCV_ROUTING_HEADERS    3      63      1

                                        INDEX UNIQUE SCAN
PO.RCV_ROUTING_HEADERS_U1    3
                                    TABLE ACCESS BY INDEX ROWID
HR.HR_LOCATIONS_ALL_TL    3      54      1

                                      INDEX UNIQUE SCAN
HR.HR_LOCATIONS_ALL_TL_PK    3
                                  TABLE ACCESS BY INDEX ROWID
INV.MTL_UNITS_OF_MEASURE_TL    11      187      1

                                    INDEX UNIQUE SCAN
INV.MTL_UNITS_OF_MEASURE_TL_U1    11

                                TABLE ACCESS BY INDEX ROWID
PO.PO_LINE_LOCATIONS_ALL    773      180 K    2

                                  INDEX UNIQUE SCAN    PO.PO_LINE_LOCATIONS_U1
 773           1
                              TABLE ACCESS BY INDEX ROWID    PO.PO_HEADERS_ALL
 1 K    85 K    1
                                INDEX UNIQUE SCAN    PO.PO_HEADERS_U1    1 K

                            TABLE ACCESS BY INDEX ROWID
APPLSYS.FND_LOOKUP_VALUES    1      51      3

                              INDEX RANGE SCAN    APPLSYS.FND_LOOKUP_VALUES_U1
 1           2
                          TABLE ACCESS BY INDEX ROWID
GL.GL_DAILY_CONVERSION_TYPES    8      112      1

                            INDEX UNIQUE SCAN    GL.GL_DAILY_CONVERSION_TYPES_U1
   8
                        TABLE ACCESS BY INDEX ROWID    PO.PO_LINES_ALL    4 K
212 K    2
                          INDEX UNIQUE SCAN    PO.PO_LINES_U1    4 K         1

                      VIEW PUSHED PREDICATE    APPS.PO_HAZARD_CLASSES_VL    1
  29
                        NESTED LOOPS        1      29      1

                          INDEX UNIQUE SCAN    PO.PO_HAZARD_CLASSES_U1    1
13
                          TABLE ACCESS BY INDEX ROWID    PO.PO_HAZARD_CLASSES_TL
   1      16
                            INDEX UNIQUE SCAN    PO.PO_HAZARD_CLASSES_TL_U1    1

                    VIEW PUSHED PREDICATE    APPS.PO_UN_NUMBERS_VL    1      27

                      NESTED LOOPS        1      29      1

                        INDEX UNIQUE SCAN    PO.PO_UN_NUMBERS_B_U1    1      13

                        TABLE ACCESS BY INDEX ROWID    PO.PO_UN_NUMBERS_TL    1
    16
                          INDEX UNIQUE SCAN    PO.PO_UN_NUMBERS_TL_U1    1

                  VIEW PUSHED PREDICATE    APPS.PO_LINE_TYPES_VL    9      54

                    NESTED LOOPS        1      13      1

                      TABLE ACCESS BY INDEX ROWID    PO.PO_LINE_TYPES_B    1
 6      1
                        INDEX UNIQUE SCAN    PO.PO_LINE_TYPES_B_U1    1

                      INDEX UNIQUE SCAN    PO.PO_LINE_TYPES_TL_U1    9      63

                TABLE ACCESS BY INDEX ROWID    PO.PO_RELEASES_ALL    7 K    105
K    2
                  INDEX UNIQUE SCAN    PO.PO_RELEASES_U1    7 K         1

              TABLE ACCESS BY INDEX ROWID    PO.RCV_SHIPMENT_HEADERS    25 K
1 M    2
                INDEX UNIQUE SCAN    PO.RCV_SHIPMENT_HEADERS_U1    25 K
1
            TABLE ACCESS BY INDEX ROWID    PO.PO_VENDORS    3 K    98 K    1

              INDEX UNIQUE SCAN    PO.PO_VENDORS_U1    3 K

          NESTED LOOPS        1      382      20

            NESTED LOOPS        1      359      19

              NESTED LOOPS        1      349      18

                NESTED LOOPS        1      291      17

                  FILTER

                    NESTED LOOPS OUTER

                      NESTED LOOPS        1      256      14

                        NESTED LOOPS        1      242      13

                          NESTED LOOPS        1      218      12

                            NESTED LOOPS        1      215      12

                              HASH JOIN        1      195      11

                                NESTED LOOPS OUTER        1      187      8

                                  NESTED LOOPS        1      163      8

                                    NESTED LOOPS        1      136      6

                                      NESTED LOOPS        1      100      3

                                        TABLE ACCESS BY INDEX ROWID
ONT.OE_ORDER_LINES_ALL    1      84      2

                                          INDEX RANGE SCAN
ONT.OE_ORDER_LINES_N3    1           1
                                        TABLE ACCESS BY INDEX ROWID
ONT.OE_TRANSACTION_TYPES_ALL    1      16      1

                                          INDEX UNIQUE SCAN
ONT.OE_TRANSACTION_TYPES_ALL_U1    1

                                      TABLE ACCESS BY INDEX ROWID
INV.MTL_SYSTEM_ITEMS_B    1      36      3

                                        INDEX RANGE SCAN
INV.MTL_SYSTEM_ITEMS_B_U1    1           2

                                    TABLE ACCESS BY INDEX ROWID
INV.MTL_SYSTEM_ITEMS_B    1      27      2

                                      INDEX UNIQUE SCAN
INV.MTL_SYSTEM_ITEMS_B_U1    1           1

                                  TABLE ACCESS BY INDEX ROWID
INV.MTL_CUSTOMER_ITEMS    1      24
                                    INDEX UNIQUE SCAN
INV.MTL_CUSTOMER_ITEMS_U2    1
                                TABLE ACCESS FULL
ONT.OE_SYSTEM_PARAMETERS_ALL    1      8      2

                              TABLE ACCESS BY INDEX ROWID
ONT.OE_TRANSACTION_TYPES_TL    2      40      1

                                INDEX UNIQUE SCAN
ONT.OE_TRANSACTION_TYPES_TL_U1    2

                            INDEX UNIQUE SCAN    HR.HR_ORGANIZATION_UNITS_PK
2      6
                          TABLE ACCESS BY INDEX ROWID
HR.HR_ALL_ORGANIZATION_UNITS_TL    2      48      1

                            INDEX UNIQUE SCAN
HR.HR_ALL_ORGANIZATION_UNTS_TL_PK    2

                        TABLE ACCESS BY INDEX ROWID
INV.MTL_UNITS_OF_MEASURE_TL    11      154      1

                          INDEX UNIQUE SCAN    INV.MTL_UNITS_OF_MEASURE_TL_U2
11
                      TABLE ACCESS BY INDEX ROWID    INV.MTL_UNITS_OF_MEASURE_TL
   11      154      2
                        INDEX RANGE SCAN    INV.MTL_UNITS_OF_MEASURE_TL_U2    11
          1
                  TABLE ACCESS BY INDEX ROWID    INV.MTL_UNITS_OF_MEASURE_TL
11      231      1
                    INDEX UNIQUE SCAN    INV.MTL_UNITS_OF_MEASURE_TL_U2    11

                TABLE ACCESS BY INDEX ROWID    ONT.OE_ORDER_HEADERS_ALL    2 K
 164 K    1
                  INDEX UNIQUE SCAN    ONT.OE_ORDER_HEADERS_U1    2 K

              TABLE ACCESS BY INDEX ROWID    AR.HZ_CUST_ACCOUNTS    1 K    12 K
  1
                INDEX UNIQUE SCAN    AR.HZ_CUST_ACCOUNTS_U1    1 K

            TABLE ACCESS BY INDEX ROWID    AR.HZ_PARTIES    2 K    49 K    1

              INDEX UNIQUE SCAN    AR.HZ_PARTIES_U1    2 K

          Followup:

the initial hit is "unavoidable" unless we get the query fixed (seems a tar with
support is something you really need to do here).

if the data isn't in the cache, it isn't there -- it needs to be loaded into
there.

as for why it seems to run faster on run 3 then run 2 -- we'd need to see the
tkprofs really (preferably under 9i with the 10046 trace event) and aggregate =
NO on the tkprof command line.  perhaps you can run it in sqlplus and we can see

(i count about 29 tables myself)

APPLSYS.FND_LOOKUP_VALUES
AR.HZ_CUST_ACCOUNTS
AR.HZ_PARTIES
GL.GL_DAILY_CONVERSION_TYPES
HR.HR_ALL_ORGANIZATION_UNITS
HR.HR_ALL_ORGANIZATION_UNITS_TL
HR.HR_LOCATIONS_ALL_TL
HR.HR_ORGANIZATION_INFORMATION
INV.MTL_CUSTOMER_ITEMS
INV.MTL_SUPPLY
INV.MTL_SYSTEM_ITEMS_B
INV.MTL_UNITS_OF_MEASURE_TL
ONT.OE_ORDER_HEADERS_ALL
ONT.OE_ORDER_LINES_ALL
ONT.OE_TRANSACTION_TYPES_ALL
ONT.OE_TRANSACTION_TYPES_TL
PO.PO_HAZARD_CLASSES_TL
PO.PO_HEADERS_ALL
PO.PO_LINE_LOCATIONS_ALL
PO.PO_LINES_ALL
PO.PO_LINE_TYPES_B
PO.PO_RELEASES_ALL
PO.PO_REQUISITION_HEADERS_ALL
PO.PO_REQUISITION_LINES_ALL
PO.PO_UN_NUMBERS_TL
PO.PO_VENDORS
PO.RCV_ROUTING_HEADERS
PO.RCV_SHIPMENT_HEADERS
PO.RCV_SHIPMENT_LINES

      Rating: 4
Nasty
      Stuff  December 11, 2003
Reviewer:  Matt
       from UK
OK 29 distinct tables (still quite a lot !).

I'm going to have to put this on the back burner for now, thanks for your input.

      Rating: 5
what is VIEW
      PUSHED PREDICATE?  January 03, 2004
Reviewer:
      A reader
Hi

I have seen many VIEW PUSHED PREDICATE in the plan, what is it?

          Followup:

it is the "pushing" of a predicate against a view into the view itself.

      Rating: 5
is it this?
        January 03, 2004
Reviewer:  A reader

Hi

Can this be an example?

select *
from emp x,
    (select deptno, dname
       from dept
      where deptno in (10, 20))a
where x.deptno = 10
  and x.deptno = a.deptno;

select *
from (select * from emp where deptno = 10)x,
     (select deptno, dname
        from dept
       where deptno in (10, 20))a
where x.deptno = a.deptno;

The thing is none of them shows pushed predicate in the plans :-0

          Followup:

here is one.  View merging is not possible for a view that has multiple base
tables, if it is on the outer joined side of an outer join. In this case the
view is not merged but join predicates can be pushed in.

ops$tkyte@ORA9IR2> create table emp as select * from scott.emp;

Table created.

ops$tkyte@ORA9IR2> create table dept as select * from scott.dept;

Table created.

ops$tkyte@ORA9IR2> create index emp_deptno on emp(deptno);

Index created.

ops$tkyte@ORA9IR2> alter table emp add constraint emp_pk primary key(empno);

Table altered.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> CREATE OR REPLACE VIEW emp_v1 AS
  2  SELECT e.ename, z.dname, e.empno, e.deptno
  3  FROM   dept z, emp e
  4  WHERE z.deptno = e.deptno
  5  /

View created.

ops$tkyte@ORA9IR2> delete from plan_table;

8 rows deleted.

ops$tkyte@ORA9IR2> explain plan for
  2  select /*+ first_rows */ v.ename, d.dname
  3  FROM   dept d,emp_v1 v
  4  WHERE  d.dname = 'TESTING'
  5  AND    d.deptno = v.deptno (+)
  6  /

Explained.

ops$tkyte@ORA9IR2> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
--------------------------------------------------

-----------------------------------------------------------------------------
| Id  | Operation                     |  Name       | Rows  | Bytes | Cost  |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |             |     3 |   126 |     8 |
|   1 |  NESTED LOOPS OUTER           |             |     3 |   126 |     8 |
|*  2 |   TABLE ACCESS FULL           | DEPT        |     3 |    66 |     2 |
|   3 |   VIEW PUSHED PREDICATE       | EMP_V1      |     1 |    20 |     2 |
|   4 |    TABLE ACCESS BY INDEX ROWID| EMP         |     1 |    20 |     2 |
|   5 |     NESTED LOOPS              |             |     3 |    99 |   656 |
|   6 |      TABLE ACCESS FULL        | DEPT        |   327 |  4251 |     2 |
|*  7 |      INDEX RANGE SCAN         | EMP_DEPTNO  |     1 |       |     1 |
-----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("D"."DNAME"='TESTING')
   7 - access("Z"."DEPTNO"="E"."DEPTNO")
       filter("D"."DEPTNO"="E"."DEPTNO")

Note: cpu costing is off

22 rows selected.

      Rating: 5
How to
      defragment at runtime  February 04, 2004
Reviewer:
      Wor  from USA
Hi Tom,
We sometimes have ORA 4031 issue.
What I have noticed is we have free shared memory but it fails to allocate 4K
memory.
1) Does that mean that the shared memory is fragmanted and not a single chunk
worth 4K is available ?
2) If is the case is there a way to defragment that memory without having to
restart. I am aware that flushing the shared pool does not solve the problem.
and Yes we use Bind variables whereever possible but our application produces
more ad-hoc web based dynamic queries.
Regards,
Wor

          Followup:

could mean you have free memory in the shared pool reserved area, saved for
allocations over 4k.

ad-hoc web based dynamic queries should use binds!  no reason why they cannot.
In fact, if they do not they are 100% subject to "SQL Injection" (google that
one) and are HARDER to code (try to search for something with a quote sometime
:)

If you have a huge shared pool, consider making it small -- sounds like you have
a bind variable issue from these web based queries (guessing) and you have a big
shared pool.  It gets hard to manage this stuff.  And don't forget -- when you
ASK "is there free memory" -- the ora-4031 is already "history" and at the time
the 4031 was raised, there may well not have been any free memory.

February 05, 2004 Kalpesh -- Thanks for the question regarding "How
      to do bulk collect in Pro*C variables", version 8.1.5

      You Asked
Hi Tom,
 I am writing one Pro*C code.
    In that code I want to get values from a table and want to
write them into one file.

     How can I do bulk collect using Pro*c variables so that in
one single statement I will get all the values from a table into
Pro*C variables and I can write them in a file.

My select statement is
     SELECT /*+PARALLEL(tbtick,6)*/
            NVL(symbol_id,0),
            NVL(trade_price,0),
             TO_CHAR(trade_date ,'YYYY/MM/DD'),
             TO_CHAR(ROUND(trade_date ,'MI'),'HH24:MI:SS')
      BULK COLLECT
      INTO
        p_arr_symbol_id,
        p_arr_trade_price,
        p_arr_trade_date,
        p_arr_trade_time
      FROM   tkdaily.tbtick
   where symbol_id in ( select symbol_id from tbsymbols where
symbol_type_id=1);

    I want to use my select statement only once , I don't want
to make cursor or anything.

I want  p_arr_symbol_id,
        p_arr_trade_price,
        p_arr_trade_date,
        p_arr_trade_time       into Pro*C .

How can I get them?

Thanks
Kalpesh

      and we said...
In Oracle8i, release 8.1 and up, the easiest method would be to
set the PREFETCH option on the precompiler command line or to
place that option in your C code.

Oracle will then array fetch for you magically.  You ask for a
row and it'll prefetch however many you want.

Otherwise, you can just declare your host variables as arrays
and array fetch them manually:

exec sql begin declare section;
  /* array fetch upto 100 rows at a time */
  varchar   value[100][25];
exec sql end declare section;

   EXEC SQL OPEN C;
   for( rows_fetched = 0, done = 0;
        !done;
        rows_fetched=sqlca.sqlerrd[2] )
    {
        EXEC SQL FETCH C INTO :value;
        done=(sqlca.sqlcode==1403);

        for( i = 0; i < sqlca.sqlerrd[2] - rows_fetched; i++ )
        {
            printf( "row %d is '%.*s'\n",
                     rows_fetched+i+1,
                     value[i].len, value[i].arr );
        }
    }
    EXEC SQL CLOSE C;

Review &
      Followup

      Rating: 4
Bulk collect
      into Pro*C variables  March 16, 2001
Reviewer:
      Kalpesh Patel  from Fort Lee, NJ USA
        This is really useful when someone doesn't want to use PL/SQL variables
for bulk collect.
        With this , we can do bulk collect into Pro*C variables and we can use
them into our Pro*C code for some calculation or manipulation.
   Thanks Tom, for such a useful information.

      Rating: 3
How about
      cursor with GROUP BY clause?  February 05, 2004

Reviewer:  Julien Xu  from China
My cursor in Pro*C is:

SELECT BODY, ACCT, PRPRTY, SUM(NB_MEAS)
FROM TREAD
WHERE BATCH_NB = 75
GROUP BY BODY, ACCT, PRPRTY;

I define host arrays to receive the results. But after FETCHing, I find there is
only one line in the arrays, all other cells are empty (0). Actually there
should have >300 results.
If I remove that GROUP BY and that SUM(), the results are all FETCHed as
expected. But I must need that SUM() and GROUP BY.
So why and how to fix this?

          Followup:

if you run that query in sqlplus -- what are the results (how many rows) and
show us the entire piece of PRO*C code, include things like "my version", "my
platform"

Otherwise this question fits into the "my car won't start -- why?" category ;)

February 05, 2004 Sushil -- Thanks for the question regarding
      "consitent read", version 8.0.5

      You Asked
Hi Tom,

I am facing error 1555 while trying to lots of update in a for loop
after reading values from few diff tables.

I have searched through most of your ans regarding 1555.

I have got a fair amount of understanding about this error as I
had faced a lot earlier but at present my query is bit different.
I start my pl/sql block with begin(main)
Here first I open up a cursor named c1 which is not having any dynamic variables

in its where clauses.

then I open that cursor in a for loop i.e. for x in c1 loop.
Now i insert into a table a single row, none of the tables used in cursor
definition.I make a commit hereitself.

Then i start with one more begin inside for loop to do a select statement for
fetching some values into variables and then
an insert with exception and come out of that begin

Now i go for some simple validations and then start with one more begin and
after a update (single row) and committing that update i come out of that begin.

Now i have one more update and i do a commit;
Loop ends here and then the main block also ends.

If I make one rollback segment with say initial 400K and next 400k
and minextents 3 and maxextents 500 and assign that rollback segment to this
transaction.

I can see wraps but no extends on this rollback segment.Also the
processing goes on for approx 50,000 rows and then it breaks with ORA-01555.

My Question is why does transaction break after 50,000 rows and that too when
wraps have been about 120 on that segment with no extends.
If it was to be due to rollback extent being over written then what makes it
happen after the extent being over written for 40 times and not at first
instance.

As far as processing of this transaction is concerned i have done it in my
production with more no of rollback segments and without error ORA-01555.

Please explain me the error occurance after 120 wraps and no extends and also
even after commiting at almost every update or insert.

There is no other transaction active while I am running this transaction.

Tks ,
Sushil.

      and we said...
Basically -- your rollback segments are just too darn small -- committing like
this is the cause of your ora-1555.  You are probably in this case seeing the
effects of block cleanouts.

Here is an example of a 1555 on a READONLY tablespace for example!

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:895410916429
It is the same thing you are hitting.

It just happens to happen at 120 wraps cause we hit some data at that point that
we need rollback for and its simply not there anymore.

For the prior 50k rows -- we didn't or we did and the rollback we needed was
still found in the cache.

Your rollbacks with 400k and minextents 3 are WAY too small.  My small databases
start with

initial=1m=next
minextents=25
maxextents=255

Using "set transaction use rollback segment" only makes the problem WORSE (you
seem to be doing everything possible to CAUSE this to happen!).  I hate that
command.  By using that you are causing that single rbs to wrap even more (i use
it to demonstrate HOW to get 1555's -- I never use it for anything else).

Please -- just size your RBS reasonably, use equi-sized extents - stop using
"set transaction use rollback segment"
Review &
      Followup

      Rating: 4
delayed
      cleanout  November 13, 2001
Reviewer:
      Kamaljeet  from New Castle
Tom ,
I have a doubt here.
In the following answer

http://asktom.oracle.com/pls/ask/f?p=4950:8:257295::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:275215756923,%7Bora%7D%20and%20%7B1555%7D
After stage-3 of example can some other transaction use slot number 3 of
transaction table ?
If so then what happens to the data block. Why i am asking this is if some
transactions uses changed data block then it revisits the transaction block to
make a change to data block.
But what if the transaction slot is overwritten. Then in what state the data
block is in? Changed Unchanged or what? How oracle deals with it.?

          Followup:

Yup.

The block is "changed" -- the SCN is what tells us that -- so we'll roll the
block back (which will restore the transaction slot to "pre-stage-3") and we can
roll that block back and so on -- getting the block to what it was right at the
time we started our transaction.

      Rating: 5
difference
      between these clens  February 05, 2004
Reviewer:
      A reader
Hi

In Oracle 7 I believe only delayed block cleanout existed. From 8 upwards we
have delayed block cleanouts AND delayed logging block clanouts. May you briefly
describe the difference?

Cant find anything on Metalink :-(

          Followup:

do you have my book Expert One on One Oracle?  I cover this topic (that takes a
couple of pages) in detail there.

February 05, 2004 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:)

      Rating: 3
What do you
      make of this?  February 05, 2004
Reviewer:
      Justin  from PA
justin@DEV> 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

1)
justin@DEV> var x number
justin@DEV> exec :x := 4;

PL/SQL procedure successfully completed.

justin@DEV> print x

                X
=================
                4

2)
justin@DEV> var x refcursor
justin@DEV> begin
  2  open :x for select 1111,2222 from dual;
  3  end;
  4  /

PL/SQL procedure successfully completed.

justin@DEV> print x

             1111              2222
================= =================
             1111              2222

justin@DEV> select :x from dual;
select :x from dual
*
ERROR at line 1:
ORA-03113: end-of-file on communication channel

Any ideas why the command barfs me out?
TAR?

          Followup:

well, select <closed ref cursor> from dual should give you an error and
continue, not fail like that -- but the code isn't "sensible", you cannot
"select <ref cursor> from table" where <ref cursor> is a bind input --
especially since :x is "closed, kaput"

      Rating:
      4
thanks!  February 05, 2004
Reviewer:
      A reader

February 05, 2004 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

      Rating: 4
getAsciiStream
      vs. getCharacterStream  January 29, 2004
Reviewer:
      Sanjay Jha  from Toronto
Tom,
We are facing something very strange here. We have two identical databases one
on Windows 2000 and the other on Sun OS. Both are having utf8 characterset. Our
developers coded an application using getCharacterStream class to access a CLOB
column and it worked fine on the database running on Windows 2000, but it did
not show the right data when run against the database running on SunOS, rather
showed some garbage.
When we changed to getAsciiStream class, it works fine for both the databases.
However, we are concerned for the multilingual data (like Chinese etc.) which
will be stored eventually in this column and retrieving that like Ascii will not
give us the right result ( since Chinese will use multi-byte storage for
characters unlike Ascii which uses single byte).
Could you point out where are we missing something? It is surprising that
operating system has a bearing on something within oracle database, I still do
not beleve that to be true. What is your opinion and solution if any?

Regards.

          Followup:

what was the NLS_LANG on the clients

      Rating: 4
We are running
      Weblogic as application server  January 30, 2004

Reviewer:  Sanjay Jha  from Toronto
Tom,
We are running the application through BEA weblogic server(on Windows 2000). We
just changed the database connection string for either database while keeping
the database "client" (weblogic server) constant. The results were different
though, as mentioned(i.e. garbage comes when we connect to database running on
Sun OS).  We tried setting NLS_LANG as Windows environmental parameter to utf8
and also we tried by setting nls_lang to utf8 in the weblogic startup parameter.
No difference in the outcome.
Pleaae help.

Regards.

          Followup:

should not happen -- all things constant -- same DB character sets (please
verify that -- select * from nls_database_parameters on both and verify)

      Rating: 4
NLS settings
      are identical...  January 30, 2004
Reviewer:
      Sanjay Jha  from Toronto
Tom,
I have just compared the two, they are identical.
One more piece of information. I had set nls_lang=.utf8 and then exported the
data from database on Windows(characterset utf8) and then imported into the
schema in database running on Sun OS having characterset utf8. Will it corrupt
the data for LOB columns if it is more than 4000 bytes, since it will be stored
outside the database? Does that have to do anything with the problem? However,
problem is only when we want to access it using getCharacterStream and not when
using getAsciiStream class.
I know it might be little confusing but there is definitely something to do with
character encoding which behaves differently for data being accessed from two
different databases, which are identical but running on different OS.
Any help will be highly appreciated, Tom.

Regards.

tester@TEST_Win8i> select * from v$version;

BANNER
----------------------------------------------------------------
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

tester@TEST_Win8i> 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
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         UTF8
NLS_RDBMS_VERSION              8.1.7.0.0

18 rows selected.

tester@TEST_SUN8i> select * from v$version;

BANNER
----------------------------------------------------------------
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 Solaris: Version 8.1.7.0.0 - Production
NLSRTL Version 3.4.1.0.0 - Production

tester@TEST_SUN8i> 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-rrrr hh24:mi:ss
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         UTF8
NLS_RDBMS_VERSION              8.1.7.0.0

18 rows selected.

          Followup:

it is not stored outside the database.  it is stored "out of line" in the
database.

I'll have to refer you to support as I do nothing pretty much with java --
(well, as little as humanly possible anyway).

      Rating: 4
I found the
      problem....  February 03, 2004
Reviewer:  Sanjay
      Jha  from Toronto
Tom,
Good news is that I figured out the problem and everything is working, the bad
news is that I still do not understand the reason clearly.
As I had mentioned before, that I have two identical database with utf8
character set running on Windows 2000 and Sun OS. I logged in to Windows server
and first set nls_lang=.utf8 and then exported the entire schema (the dump file
was created on the Windows platform from the databse running on Windows). Then
during the same session, I imported the data into the database running on the
Sun OS. Everything worked fine (including some data having Chinese characters)
in the database running on Sun OS, except the CLOB data, which was showing as
garbage.

Dump from database running on Sun OS:
DUMP(DBMS_LOB.SUBSTR(JOB_OFFER_DESC,10,1),1016)
--------------------------------------------------------------------------------

--------------------
Typ=1 Len=20 CharacterSet=UTF8:
c5,8c,c5,a1,c4,a0,c5,b4,c5,a9,c5,b4,c5,b5,c5,ac,c5,a1,c5,a9

Dump from database running on Windows OS:
DUMP(DBMS_LOB.SUBSTR(JOB_OFFER_DESC,10,1),1016)
--------------------------------------------------------------------------------

--------------------
Typ=1 Len=10 CharacterSet=UTF8: 4c,61,20,74,69,74,75,6c,61,69

However, I did little different this time, I ftp'd the dump file from Windows
platform to Sun box in the binary mode. Then logged on to the Sun OS and
imported into the database running on Sun OS. Everything works fine now!

Incidentally, when I tried to export the data from the database running on
Windows, while logged on to the Sun OS, I received following error:
EXP-00056: ORACLE error 3120 encountered
ORA-03120: two-task conversion routine: intege
Export terminated successfully with warnings.

As I mentioned before, I resolved the issue in trial and error fashion but not
understanding what was happening. May be you can throw some light on this.

Regards.

          Followup:

if you got an error like that last one -- that would be best for support.

do you have the "header" of the imp session from windows to unix?  I'd like to
see the character sets in use at that point.

      Rating: 5
Good english...
      from an Indian. after a long time.  February 04, 2004

Reviewer:  Zaphod B.

      Rating:
      3
  February 05, 2004
Reviewer:  Sanjay
      Jha  from Toronto
Tom,
I had opened a TAR already. But your response is much faster ;)
Here is the header of the import 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 UTF8 character set and UTF8 NCHAR character set
****************************************************************

Oracle Support is indicating that it had something to do with the buffer size
what I had set up and possible solution they are proposing is to reduce that
number in a trial and error fashion. Here is what they said:

Solution Explanation
=====================

Specify the BUFFER parameter in the Export/Import command line or in the
Export/Import parameter file. Reduce the value of BUFFER until the error is
resolved.

For example:

If BUFFER is set to 10485760 (10 MB), this will cause Export to buffer 10MB of
data before writing it to the export dump file. This would cause Import to
buffer 10MB of data before actually sending it to the database (write to
datafiles). This becomes a problem when doing Export/Import remotely. 10MB of
data will try to be sent across the network at one time. On the average a
network packet (buffer) size is 1500 bytes. So trying to send 10MB at one time
via 1500 byte packets is causing the overflow to occur.

Tom, could you explain that what is the difference in export/import being done
remotely (via SQL*Net connection) and when done locally, in terms of buffer
size? Does it have any bearing on the characterset encoding?

Regards,

Sanjay

          Followup:

i'm skeptical of that one (the buffer size)

February 05, 2004 Sankalp -- Thanks for the question regarding
      "Summary Tables/materialized Views/OCI", version 8i

      You Asked
Hi,
A)
In Oracle 8i, there is a new concept introduced - Materialized
Views.
They are also called as Summary Tables.
Well,
Summary tables(ST) are similar to Views except for the fact that
-

# ST occupy space,
# Can be rewritten by the optimizer (step 1)
# Also, they are updatable(like Snapshots)

I think I am right upto here.

my question is : are these ST similar to PLAN_TABLE?
If i drop a ST, and at the same time, the end-user or some
application queries
info from it, what will happen??

Where will it bring the data from , when the object itself is
non-existing??
I hope u followed my question.

B)

Also, can u throw some light on OCI. How is it different from
RPCs and Pro*C.
As far as OCI stuff is concerned, please do not send me URLs. I
have already
checked technet(OTN).
I want in simple language, why, where, how OCI is used.

thanks,
SANKALP.

      and we said...
are materialized views like a plan_table.  No, a plan_table is
something we store the results of an explain plan into.  A
materialized view is an "answer" we have pre-stored.  If you
drop a materialized view, while someone is querying it, you will
wait (or timeout).  Its like a real table (it is a real table).
If you drop the table while no one is using it and then someone
goes to use it -- the optimizer will see its not there and will
not rewrite the query to reference it.

OCI is a call level interface.  It has the same functionality as
Pro*C (in Oracle8i release 8.1, pro*c is now for the first time
a layer on top of OCI).  OCI is simply a C api where as pro*c is
a more declarative thing.  OCI is like "jdbc".
Review &
      Followup

      Rating: 3
Are
      Materialized views updatable  February 05, 2004

Reviewer:  Sonali  from waltham, ma
MV's area is new for me...
I wrote this view
CREATE MATERIALIZED VIEW mv_ResTimeline
BUILD IMMEDIATE
REFRESH COMPLETE ON DEMAND
    WITH ROWID
DISABLE QUERY REWRITE
AS
select Prd_ID as EfCost_PrdID, ERes_Import_ID as EfCost_ResID, ERes_ID as
EfCost_ETResID,
    (Prd_WorkDays * NVL(ERes_Hrs_Day, NVL(Group_Hrs_Day, 0))) as EfCost_HrsPrd,
    (Prd_WorkDays/NVL(ERes_Day_Year, NVL(Group_Day_Year, 0))) as
EfCost_Ratecost,
    NVL(Sum(TL_Hours), 0.00) as EfCost_HrsRptd, Eres_Currency as EfCost_Curr,
    Prd_Finish as EfPrd_Finish, sysdate as EfCost_Created, 10 as status
from   Resource,  Group,  Period, TimeLine
where TL_Res_ID =  ERes_ID
AND ERes_Source = 20
AND Group_ID = ERes_Group_ID
AND TL_Date >= Prd_Start
AND TL_Date <= Prd_Finish
and NVL(ERes_Day_Year, NVL(Group_Day_Year, 0)) > 0
group by Prd_ID, ERes_Import_ID, ERes_ID,
(Prd_WorkDays * NVL(ERes_Hrs_Day, NVL(Group_Hrs_Day, 0))),
(Prd_WorkDays/NVL(ERes_Day_Year, NVL(Group_Day_Year, 0))), Eres_Currency,
Prd_Finish
;

It works fine .. but I want to be able to update the Status column (constant)
which is initially 10..
I will have a stored procedure which will get all the information from this view
and change the status to 20 for fetched rows. But when the information for that
row is changed (EfCost_Ratecost value in the view is changed for the existing
row).. it should change the status back to 10, so the procedure can get that row
again next time its run. I am struggling to find a way to do this..
How do I do it ?
Thanks so much

          Followup:

MV's don't work that way.  You can have updatable MV's but they will roll their
value UPTO the parent object (replication)

You will not be able to do this with a MV, they are quite simply not designed to
support anything like that at all.  the only thing that should be updating them
is the refresh process itself.

February 06, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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://download-west.oracle.com/docs/cd/A87860_01/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://download-west.oracle.com/docs/cd/B10501_01/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)

      Rating:
      4
thnx  January 14, 2004
Reviewer:
      aladdin  from india
thnx sir
but if u plz could u tell me
if i can pass a parameter to a view
if i could how to be created(syntax)

          Followup:

search for

"parameterized view"

      Rating: 5
why query
      rewrite works with plain views?  January 20, 2004

Reviewer:  A reader
Hi

we have some problems with our database. The problem is when we enable query
rewrite a query against a specific view runs slow slow. I thought Query Rewrite
only works with MVs...?

How come?

          Followup:

how's about you share the before/after plans -- did you even compare them to see
what is happening?  show us the autotrace traceonly explain output....

      Rating: 5
yes here they
      are  January 20, 2004
Reviewer:
      A reader
SELECT fill.bo_id_contact, fill.role_type_id_cntct, fill.bo_id_cust,
       fill.role_type_id_cust, fill.rbtacctid, fill.rbtbillid, fill.rbtbillno,
       fill.rbtbillstatus, fill.currency_cd, fill.rbttotaldue,
       TO_CHAR (fill.rbtbilldate, 'YYYY-MM-DD'),
       TO_CHAR (fill.rbtbillduedate, 'YYYY-MM-DD')
  FROM sysadm.ps_rbt_c_bill_vw fill
 WHERE bo_id_cust = 1000000000000002483252
   AND bo_id_contact = 0
   AND rbtacctid = '4295845'

WITH QUERY REWRITE
---------------------------------------------------------------------------------
----
| Id  | Operation                         |  Name           | Rows  | Bytes |
Cost  |
---------------------------------------------------------------------------------
----
|   0 | SELECT STATEMENT                  |                 |     1 |    76 |
13 |
|   1 |  HASH JOIN                        |                 |     1 |    76 |
13 |
|   2 |   TABLE ACCESS BY INDEX ROWID     | PS_RBT_ACCOUNT  |     1 |    30 |
 3 |
|   3 |    INDEX UNIQUE SCAN              | PS_RBT_ACCOUNT  |  2454K|       |
 2 |
|   4 |   TABLE ACCESS BY INDEX ROWID     | PS_RBT_BILL     |     1 |    46 |
 8 |
|   5 |    BITMAP CONVERSION TO ROWIDS    |                 |       |       |
   |
|   6 |     BITMAP MINUS                  |                 |       |       |
   |
|   7 |      BITMAP CONVERSION FROM ROWIDS|                 |       |       |
   |
|   8 |       INDEX RANGE SCAN            | PSARBT_BILL     |    18 |       |
 3 |
|   9 |      BITMAP CONVERSION FROM ROWIDS|                 |       |       |
   |
|  10 |       SORT ORDER BY               |                 |       |       |
   |
|  11 |        INDEX RANGE SCAN           | PS3RBT_BILL     |    18 |       |
56902 |
---------------------------------------------------------------------------------
----

WITHOUT QUERY REWRITE
--------------------------------------------------------------------------------
| Id  | Operation                    |  Name           | Rows  | Bytes | Cost  |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                 |     7 |   567 |    17 |
|   1 |  NESTED LOOPS                |                 |     7 |   567 |    17 |
|*  2 |   TABLE ACCESS BY INDEX ROWID| PS_RBT_ACCOUNT  |     1 |    32 |     3 |
|*  3 |    INDEX UNIQUE SCAN         | PS_RBT_ACCOUNT  |  2397K|       |     2 |
|*  4 |   TABLE ACCESS BY INDEX ROWID| PS_RBT_BILL     |     7 |   343 |    14 |
|*  5 |    INDEX RANGE SCAN          | PS2RBT_BILL     |    11 |       |     2 |
--------------------------------------------------------------------------------

I dont get why query rewrite is firing...

          Followup:

query rewrite isn't firing (i see no rewrite taking place).

can you show us:

a) log onto sqlplus
b) set autotrace traceonly explain
c) run the query
d) alter session set query_rewrite_enabled=true
e) run the query again

let me see the plan change due solely to query_rewrite_enabled being turned on.
use CUT AND PASTE.

      Rating:
      5
here  January 21, 2004
Reviewer:
      A reader
ttd@TTD>SELECT fill.bo_id_contact, fill.role_type_id_cntct, fill.bo_id_cust,
  2         fill.role_type_id_cust, fill.rbtacctid, fill.rbtbillid,
fill.rbtbillno,
  3         fill.rbtbillstatus, fill.currency_cd, fill.rbttotaldue,
  4         TO_CHAR (fill.rbtbilldate, 'YYYY-MM-DD'),
  5         TO_CHAR (fill.rbtbillduedate, 'YYYY-MM-DD')
  6    FROM sysadm.ps_rbt_c_bill_vw fill
  7   WHERE bo_id_cust = 1000000000000002483252
  8     AND bo_id_contact = 0
  9     AND rbtacctid = '4295845' ;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=13 Card=1 Bytes=76)
   1    0   HASH JOIN (Cost=13 Card=1 Bytes=76)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'PS_RBT_ACCOUNT' (Cost=3 Card=1
Bytes=30)
   3    2       INDEX (UNIQUE SCAN) OF 'PS_RBT_ACCOUNT' (UNIQUE) (Cost=2
Card=2454650)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'PS_RBT_BILL' (Cost=8 Card=1
Bytes=46)
   5    4       BITMAP CONVERSION (TO ROWIDS)
   6    5         BITMAP MINUS
   7    6           BITMAP CONVERSION (FROM ROWIDS)
   8    7             INDEX (RANGE SCAN) OF 'PSARBT_BILL' (NON-UNIQUE) (Cost=3
Card=18)
   9    6           BITMAP CONVERSION (FROM ROWIDS)
  10    9             SORT (ORDER BY)
  11   10               INDEX (RANGE SCAN) OF 'PS3RBT_BILL' (NON-UNIQUE)
(Cost=56902 Card=18)

ttd@TTD>alter session set query_rewrite_enabled = false;

Session altered.

ttd@TTD>SELECT fill.bo_id_contact, fill.role_type_id_cntct, fill.bo_id_cust,
  2         fill.role_type_id_cust, fill.rbtacctid, fill.rbtbillid,
fill.rbtbillno,
  3         fill.rbtbillstatus, fill.currency_cd, fill.rbttotaldue,
  4         TO_CHAR (fill.rbtbilldate, 'YYYY-MM-DD'),
  5         TO_CHAR (fill.rbtbillduedate, 'YYYY-MM-DD')
  6    FROM sysadm.ps_rbt_c_bill_vw fill
  7   WHERE bo_id_cust = 1000000000000002483252
  8     AND bo_id_contact = 0
  9     AND rbtacctid = '4295845' ;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=24 Card=1 Bytes=76)
   1    0   NESTED LOOPS (Cost=24 Card=1 Bytes=76)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'PS_RBT_ACCOUNT' (Cost=3 Card=1
Bytes=30)
   3    2       INDEX (UNIQUE SCAN) OF 'PS_RBT_ACCOUNT' (UNIQUE) (Cost=2
Card=2454650)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'PS_RBT_BILL' (Cost=21 Card=1
Bytes=46)
   5    4       INDEX (RANGE SCAN) OF 'PS2RBT_BILL' (NON-UNIQUE) (Cost=2
Card=18)

          Followup:

so, do you have function based indexes.  see:

http://asktom.oracle.com/~tkyte/article1/index.html
and ctl-f for query_rewrite.  they control the use of FBI's and I'm guessing
that there is an FBI there that is used by the query with query rewrite enabled.

      Rating:
      5
FBI  January 21, 2004
Reviewer:
      A reader
Hi

yes we have function based indexes. However as you can see it´s no good, making
the query very slow... How can this be?!

          Followup:

i cannot "see" a thing.  I "see" no tkprofs.  I "see" nothing.

      Rating: 3
joining
      views  January 30, 2004
Reviewer:  Duncan
       from uk
Tom

Correct me if i am wrong but when you join a view to a view it should be the
same as joining thme if the views were in fact tables??

If that is the case then please can you explain the following results.
I have 2 views (haven't posted their code here):

12:41:28 OPT1@OP5>desc sbe_table9a
 Name                                                                    Null?
 Type
 -----------------------------------------------------------------------
-------- ------------------
 ORG_ID                                                                  NOT
NULL NUMBER(38)
 SECTION
 NUMBER
 VOUCHER_CODE
 VARCHAR2(6)
 ONE_LENS
 NUMBER
 BOTH_LENSES
 NUMBER

12:45:29 OPT1@OP5>desc sbe_table9b
 Name                                                                    Null?
 Type
 -----------------------------------------------------------------------
-------- ------------------
 ORG_ID
 NUMBER(38)
 REPAIR_CODE
 VARCHAR2(5)
 BASIC_COUNT
 NUMBER
 SMALL_COUNT
 NUMBER

Now their selects look like:

12:54:20 OPT1@OP5>select  a.*
12:54:20   2  from   sbe_table9a a;

    ORG_ID    SECTION VOUCHE   ONE_LENS BOTH_LENSES
---------- ---------- ------ ---------- -----------
       142          1 A               0           0
       142          1 B               0           0
       142          1 C               0           0
       142          1 D               0           0
       142          1 E               0           0
       142          1 F               0           0
       142          1 G               0           0
       142          1 H               0           0
       142          1 TOTAL           0           0
       142          2 Prisms          0           0
       142          2 Tints           0           0
       142          2 TOTAL           0           0
       213          1 A               8          54
       213          1 B               0          13
       213          1 C               0           0
       213          1 D               0           0
       213          1 E               0           0
       213          1 F               0           0
       213          1 G               0           0
       213          1 H               0           0
       213          1 TOTAL           8          67
       213          2 Prisms          0           0
       213          2 Tints           0           0
       213          2 TOTAL           0           0
       214          1 A              11         120
       214          1 B               0          18
       214          1 C               0           1
       214          1 D               0           1
       214          1 E               0           2
       214          1 F               0           0
       214          1 G               0           0
       214          1 H               0           0
       214          1 TOTAL          11         142
       214          2 Prisms          0           0
       214          2 Tints           0           0
       214          2 TOTAL           0           0
       220          1 A               0           0
       220          1 B               0           0
       220          1 C               0           0
       220          1 D               0           0
       220          1 E               0           0
       220          1 F               0           0
       220          1 G               0           0
       220          1 H               0           0
       220          1 TOTAL           0           0
       220          2 Prisms          0           0
       220          2 Tints           0           0
       220          2 TOTAL           0           0

48 rows selected.

Elapsed: 00:00:00.09
12:54:21 OPT1@OP5>
12:54:21 OPT1@OP5>select  b.*
12:54:21   2  from   sbe_table9b b;

    ORG_ID REPAI BASIC_COUNT SMALL_COUNT
---------- ----- ----------- -----------
       142 Front           0           0
       213 Front           2           0
       214 Front           9           0
       220 Front           0           0

4 rows selected.

If I join them i get:

12:54:22 OPT1@OP5>select  a.*,b.*
12:54:52   2  from   sbe_table9a a,
12:54:52   3   sbe_table9b b
12:54:52   4  where a.org_id = b.org_id;

    ORG_ID    SECTION VOUCHE   ONE_LENS BOTH_LENSES     ORG_ID REPAI BASIC_COUNT
SMALL_COUNT
---------- ---------- ------ ---------- ----------- ---------- ----- -----------
-----------
       142          1 A               0           0        142 Front           0
          0

1 row selected.

Elapsed: 00:00:00.05

Which is NOT what I was expecting.

However if i create the views as tables and join them i get:

12:55:51 OPT1@OP5>create table t9a as
12:55:56   2  select  a.*
12:55:56   3  from   sbe_table9a a;

Table created.

Elapsed: 00:00:00.06
12:55:56 OPT1@OP5>
12:55:56 OPT1@OP5>
12:55:57 OPT1@OP5>create table t9b as
12:55:57   2  select  b.*
12:55:57   3  from   sbe_table9b b;

Table created.

Elapsed: 00:00:00.02
12:55:57 OPT1@OP5>select  a.*,b.*
12:56:02   2  from   t9a a,
12:56:02   3   t9b b
12:56:02   4  where a.org_id = b.org_id;

    ORG_ID    SECTION VOUCHE   ONE_LENS BOTH_LENSES     ORG_ID REPAI BASIC_COUNT
SMALL_COUNT
---------- ---------- ------ ---------- ----------- ---------- ----- -----------
-----------
       142          1 A               0           0        142 Front           0
          0
       142          1 B               0           0        142 Front           0
          0
       142          1 C               0           0        142 Front           0
          0
       142          1 D               0           0        142 Front           0
          0
       142          1 E               0           0        142 Front           0
          0
       142          1 F               0           0        142 Front           0
          0
       142          1 G               0           0        142 Front           0
          0
       142          1 H               0           0        142 Front           0
          0
       142          1 TOTAL           0           0        142 Front           0
          0
       142          2 Prisms          0           0        142 Front           0
          0
       142          2 Tints           0           0        142 Front           0
          0
       142          2 TOTAL           0           0        142 Front           0
          0
       213          1 A               8          54        213 Front           2
          0
       213          1 B               0          13        213 Front           2
          0
       213          1 C               0           0        213 Front           2
          0
       213          1 D               0           0        213 Front           2
          0
       213          1 E               0           0        213 Front           2
          0
       213          1 F               0           0        213 Front           2
          0
       213          1 G               0           0        213 Front           2
          0
       213          1 H               0           0        213 Front           2
          0
       213          1 TOTAL           8          67        213 Front           2
          0
       213          2 Prisms          0           0        213 Front           2
          0
       213          2 Tints           0           0        213 Front           2
          0
       213          2 TOTAL           0           0        213 Front           2
          0
       214          1 A              11         120        214 Front           9
          0
       214          1 B               0          18        214 Front           9
          0
       214          1 C               0           1        214 Front           9
          0
       214          1 D               0           1        214 Front           9
          0
       214          1 E               0           2        214 Front           9
          0
       214          1 F               0           0        214 Front           9
          0
       214          1 G               0           0        214 Front           9
          0
       214          1 H               0           0        214 Front           9
          0
       214          1 TOTAL          11         142        214 Front           9
          0
       214          2 Prisms          0           0        214 Front           9
          0
       214          2 Tints           0           0        214 Front           9
          0
       214          2 TOTAL           0           0        214 Front           9
          0
       220          1 A               0           0        220 Front           0
          0
       220          1 B               0           0        220 Front           0
          0
       220          1 C               0           0        220 Front           0
          0
       220          1 D               0           0        220 Front           0
          0
       220          1 E               0           0        220 Front           0
          0
       220          1 F               0           0        220 Front           0
          0
       220          1 G               0           0        220 Front           0
          0
       220          1 H               0           0        220 Front           0
          0
       220          1 TOTAL           0           0        220 Front           0
          0
       220          2 Prisms          0           0        220 Front           0
          0
       220          2 Tints           0           0        220 Front           0
          0
       220          2 TOTAL           0           0        220 Front           0
          0

48 rows selected.

Which is what I expected when i joined the views.
Please tell me what is going on here???

(If necessary i can post the view code too)

          Followup:

please file a tar on this -- looks wrong, yes.

      Rating: 5
Access full in
      a view  February 05, 2004
Reviewer:  Mariano
       from Córdoba, Argentina
Tom, please read the following example:

create table t1 (t1_id number, t1_date date, t1_split number, t1_calls number,
t1_avg_talktime number, t1_avg_acw number, t1_avg_hold number, t1_goal number);
create table t2 (t2_split number, t2_splitname varchar2(100), t2_start_date
date, t2_end_date date);
create index t1_idx1 on t1 (t1_id, t1_date, t1_split);
create index t1_idx2 on t1 (t1_id, t1_date);
create index t1_idx3 on t1 (t1_id);
create or replace view v1_t1 as select
t1_id, t1_date,
t2_splitname,
extract(year from t1_date) t1year,
extract(month from t1_date) t1month,
sum(t1_calls)over(partition by t1_id, t1_date) sum_callss1,
t1_calls sum_callss2,
sum(t1_calls)over(partition by t1_id, extract(year from t1_date), extract(month
from t1_date)) sum_callss3,
sum(t1_calls)over(partition by t1_id, extract(year from t1_date), extract(month
from t1_date), t1_split) sum_callss4,
sum(t1_calls)over(partition by t1_id, extract(year from t1_date)) sum_callss5,
sum(t1_calls)over(partition by t1_id, extract(year from t1_date), t1_split)
sum_callss6,
avg(t1_avg_talktime)over(partition by t1_id, t1_date) avg_tt1,
t1_avg_talktime avg_tt2,
avg(t1_avg_talktime)over(partition by t1_id, extract(year from t1_date),
extract(month from t1_date)) avg_tt3,
avg(t1_avg_talktime)over(partition by t1_id, extract(year from t1_date),
extract(month from t1_date), t1_split) avg_tt4,
avg(t1_avg_talktime)over(partition by t1_id, extract(year from t1_date))
avg_tt5,
avg(t1_avg_talktime)over(partition by t1_id, extract(year from t1_date),
t1_split) avg_tt6,
avg(t1_avg_acw)over(partition by t1_id, t1_date) avg_acw1,
t1_avg_acw avg_acw2,
avg(t1_avg_acw)over(partition by t1_id, extract(year from t1_date),
extract(month from t1_date)) avg_acw3,
avg(t1_avg_acw)over(partition by t1_id, extract(year from t1_date),
extract(month from t1_date), t1_split) avg_acw4,
avg(t1_avg_acw)over(partition by t1_id, extract(year from t1_date)) avg_acw5,
avg(t1_avg_acw)over(partition by t1_id, extract(year from t1_date), t1_split)
avg_acw6,
avg(t1_avg_hold)over(partition by t1_id, t1_date) avg_hold1,
t1_avg_hold avg_hold2,
avg(t1_avg_hold)over(partition by t1_id, extract(year from t1_date),
extract(month from t1_date)) avg_hold3,
avg(t1_avg_hold)over(partition by t1_id, extract(year from t1_date),
extract(month from t1_date), t1_split) avg_hold4,
avg(t1_avg_hold)over(partition by t1_id, extract(year from t1_date)) avg_hold5,
avg(t1_avg_hold)over(partition by t1_id, extract(year from t1_date), t1_split)
avg_hold6
from t1, t2 where t1_split=t2_split and t1_date between t2_start_date and
nvl(t2_end_date, t1_date);

If I execute, for example the above select (without creationg the view) and
adding
"... and t1_id=1291" in the where clause, the explain plan i get is:
Description
SELECT STATEMENT, GOAL = CHOOSE
 WINDOW SORT
  WINDOW SORT
   WINDOW SORT
    NESTED LOOPS
     TABLE ACCESS FULL
     TABLE ACCESS BY INDEX ROWID
      INDEX RANGE SCAN

So far so good but as i'm intended to retrieve this same information in the web
i use view v1_t1 in order to wrapp the complexity of the query.

When i execute:
select * from v1_t1 where t1_id=1291;
The explain plan is:
SELECT STATEMENT, GOAL = CHOOSE
 VIEW
  WINDOW SORT
   WINDOW SORT
    WINDOW SORT
     MERGE JOIN
      SORT JOIN
       TABLE ACCESS FULL
      FILTER
       SORT JOIN
        TABLE ACCESS FULL

Of course my real world table has 35132 rows and it seems that the access full i
get when i use the view prevent my users from retrieving the information. i'm
working in 8.1.7 and global hints seems to no aply at all and i cannot make the
view access the index (which is the best way because the first example take less
than 10 seconds to finish and the view seems to run forever).
As always, any help will be appreciate

          Followup:

ops$tkyte@ORA817DEV> select * from v1_t1 where t1_id = 1234;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   VIEW OF 'V1_T1'
   2    1     WINDOW (SORT)
   3    2       WINDOW (SORT)
   4    3         WINDOW (SORT)
   5    4           NESTED LOOPS
   6    5             TABLE ACCESS (FULL) OF 'T2'
   7    5             TABLE ACCESS (BY INDEX ROWID) OF 'T1'
   8    7               INDEX (RANGE SCAN) OF 'T1_IDX3' (NON-UNIQUE)

ops$tkyte@ORA817DEV>  select * from v1_t1 where t1_id = 1234;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=1 Bytes=412)
   1    0   VIEW OF 'V1_T1' (Cost=6 Card=1 Bytes=412)
   2    1     WINDOW (SORT)
   3    2       WINDOW (SORT)
   4    3         WINDOW (SORT)
   5    4           NESTED LOOPS (Cost=1 Card=1 Bytes=170)
   6    5             TABLE ACCESS (FULL) OF 'T2' (Cost=1 Card=1 Bytes=83)
   7    5             TABLE ACCESS (BY INDEX ROWID) OF 'T1'
   8    7               INDEX (RANGE SCAN) OF 'T1_IDX1' (NON-UNIQUE)

does not reproduce for me using either rbo or cbo.

How's about you run the test case and cut and paste the plans straight out of
sqlplus.

(and i seriously doubt it is "preventing" your users from doing anything.  35k
rows is tiny)

February 06, 2004 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

      Rating: 5
batch job
      procedure parsing and shared pool  February 05, 2004

Reviewer:  Bogy
Hi Tom,

Could you please tell me whether I am correct or wrong.
If I submit let say 100000 dbms_jobs executing procedure proc_a with different
parameters, I will have in job_queue something like:

proc_a('param_1', 'param_2');
proc_a('param_3', 'param_4');
proc_a('param_5', 'param_6');
proc_a('param_7', 'param_8');
proc_a('param_9', 'param_10');

... and so on up to 100000....

Oracle will parse procedure "proc_a" once and execute it 100000 times, right?

So, can this process somehow make any bad influence on a shared memory pool?

I think no, since "proc_a" is parsed once and executed many times. Only what for
me is unknown is relation between job queue and shared pool if there is any.

Thanks,

Bogy

          Followup:

but the statement being parsed is:

begin proc_a( 'x', 'y' ); end;
begin proc_a( 'a', 'b' ); end;

they are each unique -- you've slammed the shared pool BIG TIME.  My approach
(as demonstrated on this site and in the book "Expert one on one Oracle") is to
use the job queues like this:

create table parameters_for_proc_a( job_id number primary key, p1 varchar2, p2
varchar2 );

create or replace procedure proc_a( P_JOB in number )
is
   l_rec parameters_for_proc_a%rowtype;
begin
   select * into l_rec from parameters_for_proc_a where job_id = P_JOB;

   ... process....
   delete from parameters_for_proc_a where job_id = p_job;
   commit;
end;
/

and to submit the job, you:

declare
    l_job  number;
begin
    dbms_job.submit( l_job, 'proc_a(JOB);' );
    insert into parameters_for_proc_a values ( l_job, param1, param2 );
    commit;
end;
/

      Rating:
      5
Rewiew  February 05, 2004
Reviewer:
      Bogy
Right, I have read about this method in your book, but what is not clear to me
is how things work with pl/sql code. With sql query we have parsing,
optimization and row-source generation. But how things work with pl/sql. So, for
block of code:

begin proc_a( 'x', 'y' ); end;

we will have hard parse but what kind of optimization and row-source generation
(for this pl/sql code) which are also the most CPU intensive operations?
There is a obvious overhead of searching SGA in phase of hard pars but can this
approach cause problems with ORA-4031 in shared_memory? If so, could you please
explain me why?
Also, if we are repeatedly calling "proc_a", this procedure should already been
in cash memory and reused? Or because we are calling this procedure repeatedly
with static parameters database have to again and again hard parse this proc
"from the beginning"?
Could you please put a little light on pl/sql procedure/package parsing process?
Thanks a lot!!!

          Followup:

don't call proc_a that way -- use binds -- period.  no if and or buts about it.

begin proc_a( ?, ? ); end;

in java/vb

begin proc_a( :x, :y ); end;

in C/pro*c and others......

February 06, 2004 -- Thanks for the question regarding "primary
      key", version 8.1.6

      You Asked
Tom:

what's the benefit for a table have a primary key?
if for performance reason, i can define it not null and
then create an index on it.
did every table should have primary key?
what's guide line?

did compare with two number is faster than compare two varchar2?
I have a table test( a varchar2(256) primary key,.......);
also i have serveal table contain "a"
if i modified it to
test(b number primary key, a varchar2(256),.....);
and assign each "a" a unique number b.
and change other table's column from "a" to "b"
do you think it will help performance?

the reason i want to do that is i think compare two number is
faster than compare two varchar2.

do you think it is a good idea?

      and we said...
The benefit of a primary key over a NOT NULL and UNIQUE index is
mostly semantics.

A primary key has meaning -- it conveys something about the
data.

A not null constraint plus a unique index does not convey that
same information.

A primary key just happens to create a not null constraint and
an index on the data (may be unique or non-unique index) in
order to enforce itself -- but a not null constraint and index
does not imply "primary key".

Not every table *needs* a primary key but if it has one, it
would be best to use "primary key".  You can only create foreign
keys to primary key or unique constraint columns.  Various tools
can make use of the fact that a primary key is there for display
purposes and such.  It just adds meaning.

As for number versus character string -- it really depends on
the length of the field.  Numbers in Oracle are stored in a
packed decimal "like" field of upto 22 bytes.  They are in fact
a varying length character string.  It take as much work to
compare them as strings in many cases until the string gets very
large.  If the strings in your case are always pretty large --
go for the number, else it really doesn't matter.
Review &
      Followup

      Rating: 3
Pk and FK
      finder script  February 05, 2004
Reviewer:
      Baqir Hussain  from Emeryville, CA USA
Tom,
I would very much appreciate if you please show me how to get Primary Key and
Foreign Key sql syntax seperately

And there are compond columns in the primary key.

1. The following script only captures one coulmn when extracting PRIMAY KEY SQL
syntax.  How can I managed to get the complete PRIMARY KEY SQL syntax. Thanks

select 'alter table '||cns.table_name||' add constraint
'||cns.CONSTRAINT_NAME||' PRIMARY KEY '||'('|
|cls.COLUMN_NAME||');'
from
all_constraints cns,
all_cons_columns cls
where
cns.CONSTRAINT_TYPE='P'
and
cns.constraint_name=cls.constraint_name
order by cns.table_name;

2. The following script will give RI SQL syntax.  Please review/edit it.  Thanks

select
'alter table '||cns.table_name||' add constraint '||cns.CONSTRAINT_NAME
||' FOREIGN KEY '||'('||cls.COLUMN_NAME||') references '
||clf.table_name||' ('||clf.COLUMN_NAME||');' from
all_cons_columns clf ,
all_constraint cns,
all_cons_columns cls
where cns.CONSTRAINT_TYPE='R'
and cns.constraint_name=cls.constraint_name
and     clf.CONSTRAINT_NAME = cns.R_CONSTRAINT_NAME
and     clf.OWNER = cns.OWNER
and     clf.POSITION = clf.POSITION
order by cns.table_name;

          Followup:

if you want DDL, use dbms_metadata.get_ddl or exp/imp with imp ....
indexfile=foo.sql.

easier -- much much easier -- then DIY.

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:399218963817
is an example of getting foreign key constraints -- you can use that technique
for pkeys as well.

February 07, 2004 Sam -- Thanks for the question regarding "Nested
      loop join in 8i vs 9i", version 9.2.0

      You Asked
Tom,

I have the following query:
select /*+ USE_NL(p po) leading(p) */ * from lookup_codes p, order_headers po
where p.lookup_type = 'PO TYPE' and
po.type_lookup_code = p.lookup_code and
po.agent_id = :b1
(the hint is added to force the plan to illustrate my question)

In 8.1.7 it generates the following plan:
PLAN
--------------------------------------------------------------------------------
  1:SELECT STATEMENT  Cost =4,1
    2:NESTED LOOPS   COST=4,1
      3:TABLE ACCESS BY INDEX ROWID LOOKUP_CODES COST=4,1
        4:INDEX RANGE SCAN LOOKUP_CODES_N1 COST=3,1
      3:TABLE ACCESS BY INDEX ROWID ORDER_HEADERS COST=,1
        4:INDEX RANGE SCAN ORDER_HEADERS_N3 COST=,1

In 9i it generates the following plan:
PLAN
--------------------------------------------------------------------------------
  1:SELECT STATEMENT  Cost =32,16
    2:TABLE ACCESS BY INDEX ROWID ORDER_HEADERS COST=28,43
      3:NESTED LOOPS   COST=32,16
        4:TABLE ACCESS BY INDEX ROWID LOOKUP_CODES COST=4,1
          5:INDEX RANGE SCAN LOOKUP_CODES_N1 COST=3,1
        4:INDEX RANGE SCAN ORDER_HEADERS_N3 COST=1,259

ORDER_HEADERS_N3 is a non-unique index on the AGENT_ID column in ORDER_HEADERS.
LOOKUP_CODES_N1 is a non-unique index on LOOKUP_TYPES column in LOOKUP_CODES

I can understand the plan in 8i.  But in 9i, I don't understand why the TABLE
ACCESS BY INDEX ROWID ORDER_HEADERS has been moved to outside the nested loop.
Since ORDER_HEADERS_N3 does not contain the TYPE_LOOKUP_CODE column, how is it
possible to resolve the join by just doing the INDEX RANGE SCAN on
ORDER_HEADERS_N3 inside the nested loop part?

Are the two plans essentially the same?  Why is the plan listed different in 8i
vs 9i?

Table ORDER_HEADERS:
ORDER_ID NUMBER NOT NULL
TYPE_LOOKUP_CODE VARCHAR2(10) NOT NULL
SUPPLIER_ID NUMBER NOT NULL
ORDER_DATE DATE NOT NULL
TOTAL_AMOUNT NUMBER
AGENT_ID NUMBER
NEED_BY_DATE DATE

Cardinality: 9053  NBLKS:  367  AVG_ROW_LEN:  244

Indexes
ORDER_HEADERS_U1(ORDER_ID) Unique
LVLS: 1   #LB: 19  #DK: 9053  LB/K: 1  DB/K: 1  CLUF: 2022

ORDER_HEADERS_N1(SUPPLIER_ID)
LVLS: 1   #LB: 32  #DK: 305  LB/K: 1  DB/K: 8  CLUF: 2486

ORDER_HEADERS_N2(NEED_BY_DATE) Non-unique
LVLS: 1   #LB: 33  #DK: 576  LB/K: 1  DB/K: 4  CLUF: 2728

ORDER_HEADERS_N3(AGENT_ID) Non-unique
LVLS: 1   #LB: 29  #DK: 65  LB/K: 1  DB/K: 13  CLUF: 903

Table LOOKUP_CODES
LOOKUP_TYPE VARCHAR2(10) NOT NULL
LOOKUP_CODES VARCHAR2(10) NOT NULL
LOOKUP_DISPLAY VARCHAR2(10) NOT NULL

Cardinality: 82430  NBLKS:  1485  AVG_ROW_LEN:  111

Indexes
LOOKUP_CODES_N1(LOOKUP_TYPE) Non-unqiue
LVLS: 1   #LB: 42  #DK: 2050  LB/K: 1  DB/K: 8  CLUF: 530

Thank you very much.

      and we said...
In 8i what it is doing it something like this (in psuedo code)

loop over the rowids in the LOOKUP_CODES_N1 index where lookup_codes = 'PO TYPE'
      goto the LOOKUP_CODES table and get that row
      loop over the rowids in the ORDER_HEADERS_N3 index where agent_id = :b1
           join the rows
           goto the ORDER_HEADERS table and get that row
           apply predicate po.type_lookup_code = p.lookup_codes
               if matched then OUTPUT ROW
      end loop
end loop

Now, in 9i, the plan is changed to this (in psuedo code)

loop over the rowids in the LOOKUP_CODES_N1 index where lookup_codes = 'PO TYPE'
      goto the LOOKUP_CODES table and get that row
      loop over the rowids in the ORDER_HEADERS_N3 index where agent_id = :b1
           join them all and output to SUBRESULT-1
      end loop

      now take SUBRESULT-1's set of rowids and access the ORDER_HEADERS table
      in order to get the blocks from the table in a more optimal fashion.
      process the predicate (po.type_lookup_code=p.lookup_codes).  If matched
      then OUTPUT ROW
end loop

It is deferring the table access by rowid which is needed to evaluate the
predicate po.type_lookup_code=p.lookup_codes, until AFTER it has done the
cartesian product in effect -- in the hopes that it can more efficiently access
the base table ORDER_HEADERS in "batch" after doing the join (you say they are
joined by type lookup code to lookup codes, I'm saying that is just a predicate
really -- given the plan we have) -- rather then rowid by rowid by rowid

You can use this script to reproduce these findings:

drop table ORDER_HEADERS;
create table ORDER_HEADERS (
ORDER_ID NUMBER NOT NULL,
TYPE_LOOKUP_CODE VARCHAR2(10) NOT NULL,
SUPPLIER_ID NUMBER NOT NULL,
ORDER_DATE DATE NOT NULL,
TOTAL_AMOUNT NUMBER,
AGENT_ID NUMBER,
NEED_BY_DATE DATE
)
/

exec dbms_stats.set_table_stats( user, 'ORDER_HEADERS', numrows => 9053, numblks
=> 397,  avgrlen => 244 );

alter table order_headers add constraint ORDER_HEADERS_U1 primary key(order_id);
create index order_headers_n1 on order_headers(supplier_id);
create index order_headers_n2 on order_headers(need_by_date);
create index order_headers_n3 on order_headers(agent_id);

drop Table LOOKUP_CODES;
create Table LOOKUP_CODES (
LOOKUP_TYPE VARCHAR2(10) NOT NULL,
LOOKUP_CODES VARCHAR2(10) NOT NULL,
LOOKUP_DISPLAY VARCHAR2(10) NOT NULL
)
/
exec dbms_stats.set_table_stats( user, 'LOOKUP_CODES', numrows => 82430, numblks
=> 1485,  avgrlen => 111 );

create index lookup_codes_n1 on lookup_codes(lookup_type)
/

variable b1 number

set autotrace traceonly explain

select /*+ USE_NL(p po) leading(p) */ *
  from lookup_codes p, order_headers po
 where p.lookup_type = 'PO TYPE'
   and po.type_lookup_code = p.lookup_codes
   and po.agent_id = :b1
/
set autotrace off

Review &
      Followup

      Rating: 4
Batching
      rowids  January 03, 2003
Reviewer:  Sam To
       from San Mateo, CA
Tom,

You listed this step in your response:
"loop over the rowids in the ORDER_HEADERS_N3 index where agent_id = :b1
   join them all and output to SUBRESULT-1
end loop"

So is it true that in this case the CBO decides that the savings by batching the
rowid fetches on ORDER_HEADERS more than offset the cost of the cartesian
product?   I always thought cartesian product are quite bad, except in the case
when one of the row sources has very few rows.

          Followup:

You were already doing a cartesian product with a filter.

All of the rows in the orders table that matched that index were joined to every
row in the lookup that matched that other index -- AND THEN the predicate would
be resolved.

Like full scans -- just keep repeating

"cartesian joins are not evil", "cartesian joins are not evil"......

if they were, we would never do them.  This is not a true cartesian product in
the traditional sense but in fact every row in lookup that is PO TYPE is matched
to every row in orders where agent_id = :b1 regardless of version!  The "when
the predicate is evaluated" is done in a different place, that is all.

      Rating: 5
It's clear
      now  January 03, 2003
Reviewer:  Sam To
       from San Mateo, CA
Tom,

Thanks for the followup answer.  It is clear to me now.

If I add an extra join to the query, the table access on ORDER_HEADERS is now
pushed back into the nested loop, which is what I expect:

select /*+ USE_NL(p po s) leading(p) */ * from lookup_codes p, order_headers po,
suppliers s
where p.lookup_type = 'PO TYPE' and
po.type_lookup_code = p.lookup_code and
po.agent_id = :b1 and
s.supplier_id = po.supplier_id

1:SELECT STATEMENT  Cost =28,6
 2:NESTED LOOPS   COST=28,6
  3:NESTED LOOPS   COST=19,9
   4:TABLE ACCESS BY INDEX ROWID LOOKUP_CODES COST=4,1
    5:INDEX RANGE SCAN LOOKUP_CODES_N1 COST=3,1
   4:TABLE ACCESS BY INDEX ROWID ORDER_HEADERS COST=15,1
    5:INDEX RANGE SCAN ORDER_HEADERS_N3 COST=1,145
  3:TABLE ACCESS BY INDEX ROWID SUPPLIERS COST=1,248
   4:INDEX UNIQUE SCAN SUPPIERS_U1 COST=,1

Originally I was concerned about the cartesian product creating a large row
source for the subsequent join steps.  But the plan above indicates this will
not happen.

          Followup:

don't be concerned -- it is the job of the optimizer to be concerned.  Let is do
its job.  This  (the moving of the table access by rowid ) is an OPTIMIZATION,
not a decrease in performance option!

      Rating:
      4
clarification  January 03, 2003
Reviewer:
      Stu Charlton  from Toronto ON Canada
Perhaps I'm misreading the EXPLAIN PLAN or misunderstand the algorithm, but I'd
like a clarification on this new algorithm.

In this article:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6643159615303
Connor refers to this new nested loops algorithm in a comment, and says "Then it
sorts the rowids so that it only has to visit each table block once."

I also notice that in the EXPLAIN PLAN above, the TABLE ACCESS BY INDEX ROWID
ORDER_HEADERS occurs outside of the "NESTED LOOPS" operation (which seems to
resonate with what Connor says above).

If this is the case, wouldn't the pseudo-code be more like:

loop over the rowids in the LOOKUP_CODES_N1 index where lookup_codes = 'PO TYPE'
      goto the LOOKUP_CODES table and get that row
      loop over the rowids in the ORDER_HEADERS_N3 index where agent_id = :b1
           join them all and output to SUBRESULT-1
      end loop
end loop
(^^^ the nested loops end here)

Now take SUBRESULT-1's set of rowids
 - sort to remove duplicate rowids
 - access the ORDER_HEADERS table
 - process the predicate (po.type_lookup_code=p.lookup_codes).
 - If matched then OUTPUT ROW

If SUBRESULT-1 is predicate-matched within the nested loop as you've written
above, then the whole point of this optimization doesn't seem to click.

My understanding of this optimized algorithm is:  The "cartesian" product with
the index is cheaper than accessing table blocks repeatedly inside a nested loop
(because index blocks are fewer, denser, etc)... so defer the table access until
after you've cartesian joined potential rowid's together through the indices.
Am I wrong?

Cheers
Stu

          Followup:

Not the way NL joins are processed -- NL joins stream data back ASAP.

If it where a sort/merge or a hash join, you would be correct.  with NL, nope.

It always did a "cartesian join", in both 8i and 9i.  It found a row in LOOKUP,
it found all rows in ORDERS -- it joined every row it found in orders with that
lookup row, evaluated that predicate and then either kicked a row out or
rejected it.

It they went on to the next row in lookup and did the same thing all over again.

In 9i, it just postpones the one step -- it gets all of the ORDERS rows for
agent_id = :b1, joins them to that single lookup row -- sorts the rowids and
accesses the table as efficiently as possible -- does the predicate -- outputs.

A nested loop feeds the output part of the plan ASAP.  Else you would have to
wait till the last row was finished to get the first row (and you don't)

      Rating: 5
got
      it  January 03, 2003
Reviewer:  Stu Charlton
       from Toronto ON Canada
Thanks for your time Tom, I understand now.

      Rating: 4
Nested Loop
      Verses Merge join  February 21, 2003
Reviewer:
      reader  from USA
In what scenario one is better than the other. Can you please give some
conceptual details about nested loops and merge joins or point to a resource.

Thanks.

          Followup:

You know -- if you use the CBO, it'll tell you when one is preferred over the
other! that is exactly the job of an optimizer.

Think of nested loops like this:

for x in ( select * from T1 )
loop
     for y in ( selectd * from t2 where t2.joins = t1.joins )
     loop
            output records
     end loop
end loop

whereas a sort merge is like:

sort t1 by joins into temp1
sort t2 by joins into temp2

read a rec from temp1;
read a rec from temp2;
loop
   while (temp1_rec < temp2_rec) read temp1
   while (temp1_rec > temp2_rec) read temp2
   if ( temp1_rec = temp2_rec ) output record
   exit when end of file on temp1 or temp2
end;

the nested loops joins can be very poor performers for tons of rows -- whereas
the sort merge excels at them (lots less index lookups)

if you want the first rows fastest however -- nested loops is the way to go.

      Rating: 5
excellent
      explanation of prefetch  February 24, 2003

Reviewer:  Mikito Harakiri

      Rating:
      5
  September 08, 2003
Reviewer:
      Pauline  from NY,U.S.A.
Tom,

We just upgrade one oracle8i database to oracle9i database by using dbua
tool.When we run the query to check if database has locks, it takes very long
tile.
But this query returns very soon( 3-5 seconds ) in all our other oracle8i
database. To test it, we also run it on one 9i database which is created by dbca
tool. It takes more than 1 minutes.

The query statement is:

SELECT /*+ CHOOSE */ s.username,
s.osuser,
s.serial# "Serial#",
lk.block, -- block = 0 non blocking, block = 1 blocking others
S.PROGRAM "Program",
s.sql_address "address",
s.sql_hash_value "Sql hash",
--lk.sid,
DECODE(lk.TYPE,   'MR', 'Media Recovery',   'RT', 'Redo Thread',   'UN', 'User
Name',   'TX', 'Transaction',
'TM', 'DML',   'UL', 'PL/SQL User Lock',   'DX', 'Distributed Xaction',   'CF',
'Control File',
'IS', 'Instance State',   'FS', 'File Set',   'IR', 'Instance Recovery',   'ST',
'Disk Space Transaction',
'TS', 'Temp Segment',   'IV', 'Library Cache Invalidation',   'LS', 'Log Start
or Switch',   'RW', 'Row Wait',
'SQ', 'Sequence Number',   'TE', 'Extend Table',  'TT', 'Temp Table',   lk.TYPE)
lock_type,
DECODE(lk.lmode,   0, 'None',    1, 'Null',    2, 'Row-S (SS)',   3, 'Row-X
(SX)',   4, 'Share',     5, 'S/Row-X (SSX)',
6, 'Exclusive',   TO_CHAR(lk.lmode)) mode_held,
DECODE(request,   0, 'None',   1, 'Null',   2, 'Row-S (SS)',    3, 'Row-X (SX)',
   4, 'Share',     5, 'S/Row-X (SSX)',
6, 'Exclusive',   TO_CHAR(lk.request)) mode_requested,
TO_CHAR(lk.id1) lock_id1,
TO_CHAR(lk.id2) lock_id2,  -- will show the job number corresponding in dba_jobs
s.USERNAME  "DB User",
s.sid,
OWNER||'.'||OBJECT_NAME "Object"
FROM v$lock lk,
     v$session s,
     DBA_OBJECTS ao
WHERE lk.lmode  > 1
     AND s.username is not null
     AND lk.sid    = s.sid
     AND ao.OBJECT_ID(+) = lk.id1
     ORDER BY 1, "Object";

We compared the execution plan for Oracle9i database and Oracle8i database, they
are very different for this query:

Oracle9i database execution plan:

Query Plan
--------------------------------------------------------------------------------
1.106 SELECT STATEMENT
  2.1 SORT ORDER BY
    3.1 HASH JOIN OUTER
      4.1 NESTED LOOPS
        5.1 MERGE JOIN CARTESIAN
          6.1 NESTED LOOPS
            7.1 FIXED TABLE FULL X$KSUSE
            7.2 FIXED TABLE FIXED INDEX X$KSUSE (ind:1)
          6.2 BUFFER SORT
            7.1 FIXED TABLE FULL X$KSQRS
        5.2 VIEW  GV$_LOCK

Query Plan
--------------------------------------------------------------------------------
          6.1 UNION-ALL
            7.1 VIEW  GV$_LOCK1
              8.1 UNION-ALL
                9.1 FIXED TABLE FULL X$KDNSSF
                9.2 FIXED TABLE FULL X$KSQEQ
            7.2 FIXED TABLE FULL X$KTADM
            7.3 FIXED TABLE FULL X$KTCXB
      4.2 VIEW  DBA_OBJECTS
        5.1 UNION-ALL
          6.1 FILTER
            7.1 NESTED LOOPS

Query Plan
--------------------------------------------------------------------------------
              8.1 TABLE ACCESS FULL OBJ$
              8.2 TABLE ACCESS CLUSTER USER$
                9.1 INDEX UNIQUE SCAN I_USER# NON-UNIQUE
            7.2 TABLE ACCESS BY INDEX ROWID IND$
              8.1 INDEX UNIQUE SCAN I_IND1 UNIQUE
          6.2 HASH JOIN
            7.1 TABLE ACCESS FULL LINK$
            7.2 TABLE ACCESS FULL USER$

30 rows selected.

Oracle8i database:

Query Plan
--------------------------------------------------------------------------------
1.86 SELECT STATEMENT
  2.1 SORT ORDER BY
    3.1 HASH JOIN
      4.1 FIXED TABLE FULL X$KSUSE
      4.2 HASH JOIN OUTER
        5.1 VIEW  V$LOCK
          6.1 NESTED LOOPS
            7.1 HASH JOIN
              8.1 VIEW  GV$_LOCK
                9.1 UNION-ALL
                  10.1 VIEW  GV$_LOCK1

Query Plan
--------------------------------------------------------------------------------
                    11.1 UNION-ALL
                      12.1 FIXED TABLE FULL X$KDNSSF
                      12.2 FIXED TABLE FULL X$KSQEQ
                  10.2 FIXED TABLE FULL X$KTADM
                  10.3 FIXED TABLE FULL X$KTCXB
              8.2 FIXED TABLE FULL X$KSUSE
            7.2 FIXED TABLE FIXED INDEX #1 X$KSQRS
        5.2 VIEW  DBA_OBJECTS
          6.1 UNION-ALL
            7.1 FILTER
              8.1 NESTED LOOPS

Query Plan
--------------------------------------------------------------------------------
                9.1 TABLE ACCESS FULL OBJ$
                9.2 TABLE ACCESS CLUSTER USER$
                  10.1 INDEX UNIQUE SCAN I_USER# NON-UNIQUE
              8.2 TABLE ACCESS BY INDEX ROWID IND$
                9.1 INDEX UNIQUE SCAN I_IND1 UNIQUE
            7.2 NESTED LOOPS
              8.1 TABLE ACCESS FULL LINK$
              8.2 TABLE ACCESS CLUSTER USER$
                9.1 INDEX UNIQUE SCAN I_USER# NON-UNIQUE

31 rows selected.

The time for returing the result from the query in Oracle8i and 9i database
also very different:
Oracle8i databases only need 3 - 5 seconds to return the result.
Oracle9i database (created by dbca tool) returns the result by 49 seconds.
Oracle9i database (upgraded by dbua tool) returns the result by 37 minutes.
I see MERGE JOIN CATTESIAN in 9i database execution plan. Is it the reason
taking so long to return the result? How can we fix/avoid such problem so that
once we upgrade production database to 9i, we won't have similar problem.

Thanks for your help.

          Followup:

try

SELECT /*+ CHOOSE */ s.username,
s.osuser,
s.serial# "Serial#",
lk.block, -- block = 0 non blocking, block = 1 blocking others
S.PROGRAM "Program",
s.sql_address "address",
s.sql_hash_value "Sql hash",
--lk.sid,
DECODE(lk.TYPE,   'MR', 'Media Recovery',   'RT', 'Redo Thread',   'UN', 'User
Name',   'TX', 'Transaction',
'TM', 'DML',   'UL', 'PL/SQL User Lock',   'DX', 'Distributed Xaction',   'CF',
'Control File',
'IS', 'Instance State',   'FS', 'File Set',   'IR', 'Instance Recovery',   'ST',

'Disk Space Transaction',
'TS', 'Temp Segment',   'IV', 'Library Cache Invalidation',   'LS', 'Log Start
or Switch',   'RW', 'Row Wait',
'SQ', 'Sequence Number',   'TE', 'Extend Table',  'TT', 'Temp Table',   lk.TYPE)

lock_type,
DECODE(lk.lmode,   0, 'None',    1, 'Null',    2, 'Row-S (SS)',   3, 'Row-X
(SX)',   4, 'Share',     5, 'S/Row-X (SSX)',
6, 'Exclusive',   TO_CHAR(lk.lmode)) mode_held,
DECODE(request,   0, 'None',   1, 'Null',   2, 'Row-S (SS)',    3, 'Row-X (SX)',

   4, 'Share',     5, 'S/Row-X (SSX)',
6, 'Exclusive',   TO_CHAR(lk.request)) mode_requested,
TO_CHAR(lk.id1) lock_id1,
TO_CHAR(lk.id2) lock_id2,  -- will show the job number corresponding in dba_jobs
s.USERNAME  "DB User",
s.sid,
(select OWNER||'.'||OBJECT_NAME from dba_objects where object_id = lk.id1)
"Object"
FROM v$lock lk,
     v$session s
WHERE lk.lmode  > 1
     AND s.username is not null
     AND lk.sid    = s.sid
     ORDER BY 1, "Object";

      Rating:
      4
  September 10, 2003
Reviewer:
      Pauline  from NY,USA
Tom,
Your query running faster on our 8i database. But in our oracle 9i database it
takes 57 minutes. I think this 9i database might have something wrong with data
dictionaries.
When I login using Connect "/ as sysdba" against this 9i database, I get
incorrect results. For example:
<dbstg1:stg1:/appl/oracle/SQLTXPLAIN> sqlplus /nolog

SQL*Plus: Release 9.2.0.1.0 - Production on Wed Sep 10 17:24:08 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

SQL> conn / as sysdba
Connected.
SQL> SELECT USERENV('SESSIONID') FROM DUAL;

USERENV('SESSIONID')
--------------------
                   0

SQL> SELECT s.audsid, p.spid, p.addr
  2    FROM v$parameter u,
       v$session   s,
  3    4         v$process   p
 WHERE u.name   = 'user_dump_dest'
  5    6     AND s.audsid = USERENV('SESSIONID')
   AND p.addr   = s.paddr;  7

    AUDSID SPID         ADDR
---------- ------------ ----------------
         0 25188        000000039537E638
         0 4868         000000039538A568
         0 4985         0000000395389148
         0 25228        00000003953827A0
         0 25226        0000000395382298
         0 25224        0000000395381D90
         0 25222        0000000395381888
         0 25220        0000000395380970
         0 25200        0000000395380468
         0 25198        000000039537FF60
         0 25196        000000039537FA58

    AUDSID SPID         ADDR
---------- ------------ ----------------
         0 25190        000000039537EB40
         0 25194        000000039537F550
         0 25192        000000039537F048

14 rows selected.

The second query returns multiple rows and it will cause
other query depending on this query to get error of
ORA-01422: exact fetch returns more than requested number of rows

But when I login as user with dba role:
SQL> conn /
Connected.
SQL> SQL> SELECT USERENV('SESSIONID') FROM DUAL;

USERENV('SESSIONID')
--------------------
             3978577

SQL> SELECT s.audsid, p.spid, p.addr
  2    FROM v$parameter u,
       v$session   s,
  3    4         v$process   p
 WHERE u.name   = 'user_dump_dest'
  5    6     AND s.audsid = USERENV('SESSIONID')
  7     AND p.addr   = s.paddr;

    AUDSID SPID         ADDR
---------- ------------ ----------------
   3978577 9273         0000000395389650

Can you tell me which result is correct? Suppose should return one row or
multiple rows?

          Followup:

backgrounds have a sessionid of 0 -- both are correct.

      Rating:
      5
  September 11, 2003
Reviewer:
      Pauline
Thanks for the answer to give the key point.

      Rating:
      5
  September 11, 2003
Reviewer:
      A reader
Tom,
I got some information that I was told this is the BUG:2243239 Connect "/ as
sysdba" causes sessionid to be 0.  It is fixed on oracle10g. I tried to search
bug list
from metalink but could not find. Can you tell me is it
true or not.

Tnanks.

Pauline

          Followup:

use

(select sid from v$mystat where rownum=1)

don't use sessionid, i never do, it is zero for internal/sysdba for a while.

select * from v$session where sid = (select sid from v$mystats where rownum=1)

      Rating:
      5
  September 12, 2003
Reviewer:
      Pauline  from NY, USA
Thanks for good advice. I will do as you suggested.

      Rating: 5
list fully
      scaned tables  September 17, 2003
Reviewer:
      Gabriel  from Montreal, Canada
Your answers are very interesting, as usual. We recently migrated from 8i to 9i.
We experience many more full table scans. Can you please tell me if there is a
query that will show us all the tables that are fully scanned?

Thank you very much,

          Followup:

they should pop to the top of your top sql reports if they are problems
(statspack)

you can use v$segment_statistics to get a count of logical io's against the
segments as well -- looking for "big" ones.

      Rating: 5
Article on
      nested loop joins.  December 01, 2003
Reviewer:
      Sai.  from San Jose, CA
Hi Tom,

I remember you talking about "how nested loop joins can sometimes kill
performance with lots of LIO". I couldn't find that link. Could you please help
me find that link.

Thanks,
 Sai.

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6749454952894

      Rating: 4
Your query
      running faster on our 8i database. But ...  January 03, 2004

Reviewer:  Swetlana  from Ukraine
Hi Tom.
We have like problem. Explain please what happen and what do? Query is:
 select s.username,s.sid,l.type,l.id1,l.id2,l.lmode,
        l.request,p.spid pid,s.serial#
 from v$lock l,v$session s,v$process p
 where s.sid=l.sid and p.addr=s.paddr
       and s.username is not null
 order by id1,s.sid,request;

Thanks.

          Followup:

huh?

what "like" problem?

      Rating: 4
We have like
      problem  January 06, 2004
Reviewer:
      Swetlana  from Ukraine
We just upgrade one oracle8i database to oracle9i database.
When we run the query to check if database has locks, it takes very long tile.
Query is :

 select s.username,s.sid,l.type,l.id1,l.id2,l.lmode,
        l.request,p.spid pid,s.serial#
 from v$lock l,v$session s,v$process p
 where s.sid=l.sid and p.addr=s.paddr
       and s.username is not null
 order by id1,s.sid,request;
What happen ?
Thanks.

          Followup:

select x.username, x.sid, l.type, l.id1, l.id2, l.lmode, l.request, x.pid,
x.serial#
  from ( select s.username, s.sid, p.spid pid, s.serial#
           from v$session s, v$process p
          where s.paddr = p.addr(+)
            and rownum > 0
            and s.username is not null ) x,
       v$lock l
 where x.sid = l.sid
 order by l.id1, x.sid, l.request
/

      Rating:
      4
  January 08, 2004
Reviewer:
      Swetlana  from Ukraine
Thanks for the answer.

      Rating: 5
Is there any
      Problem ???  January 27, 2004
Reviewer:  Riaz
      Shahid  from PRAL, Lahore, Pakistan
  1* select * from v$session where sid = (select sid from v$mystats where
rownum=1)
SQL> /
select * from v$session where sid = (select sid from v$mystats where rownum=1)
                                                     *
ERROR at line 1:
ORA-00942: table or view does not exist

          Followup:

sorry, typo on my part.  got it right on the line above.

singular -- v$mystat

      Rating: 5
why this query
      runs so fast  February 05, 2004
Reviewer:
      A reader
Hi

I have a query which I expected it to run very slow but to my surprise it runs
instantaneously

SELECT b.setid, b.inst_prod_id
  FROM sysadm.ps_rf_inst_prod a, sysadm.ps_pa_arbor_elemen b
 WHERE a.parent_inst_prodid = ' '
   AND a.product_id = :b1
   AND b.pa_comp_inst_id = 0
   AND a.inst_prod_id = b.inst_prod_id
   AND a.setid = b.setid

---------------------------------------------------------------------------------
--------------------------
| Id  | Operation                           |  Name               | Rows  |
Bytes | Cost  | Pstart| Pstop |
---------------------------------------------------------------------------------
--------------------------
|   0 | SELECT STATEMENT                    |                     |     1 |
67 |   642 |       |       |
|   1 |  NESTED LOOPS                       |                     |     1 |
67 |   642 |       |       |
|*  2 |   TABLE ACCESS BY GLOBAL INDEX ROWID| PS_RF_INST_PROD     |     1 |
45 |   640 | ROWID | ROW L |
|*  3 |    INDEX RANGE SCAN                 | PSBRF_INST_PROD     |   880 |
 |     5 |       |       |
|*  4 |   TABLE ACCESS BY GLOBAL INDEX ROWID| PS_PA_ARBOR_ELEMEN  |     1 |
22 |     2 | ROWID | ROW L |
|*  5 |    INDEX UNIQUE SCAN                | PS_PA_ARBOR_ELEMEN  |     2 |
 |     1 |       |       |
---------------------------------------------------------------------------------
--------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("A"."PRODUCT_ID"=:Z)
   3 - access("A"."PARENT_INST_PRODID"=:Z)
   4 - filter("B"."PA_COMP_INST_ID"=0)
   5 - access("A"."SETID"="B"."SETID" AND "A"."INST_PROD_ID"="B"."INST_PROD_ID")

select count(*)
  2  from sysadm.ps_rf_inst_prod
  3  where parent_inst_prodid = ' ';

  COUNT(*)
----------
   5684073

I thought this scan

INDEX UNIQUE SCAN                | PS_PA_ARBOR_ELEMEN

has to happen 5684073 times and would be slow... but it´s fast do you see how?!

          Followup:

i cannot read the wrapped stuff there. and i'm not sure that query is the one
explained (the filters in the explain do not map to it anyway)

but basically it looks like this:

nested loop
   TABLE ACCESS BY GLOBAL INDEX ROWID| PS_RF_INST_PROD
      INDEX RANGE SCAN                 | PSBRF_INST_PROD
   TABLE ACCESS BY GLOBAL INDEX ROWID| PS_PA_ARBOR_ELEMEN
      INDEX UNIQUE SCAN                | PS_PA_ARBOR_ELEMEN

and with the filters it is doing:

a) read the index psbrf_inst_prod to find parent_inst_prodid = :z in table A.
We expect to get about 880 hits for this.

b) then goto the table ps_rf_inst_prod and apply the predicate product_id = :z.
We expect 1 row to meet that criteria.

c) then do the index range scan to pick up the single row from the other table
and join.

So, it should be very fast if the assumptions about the cardinality are in fact
correct.

      Rating: 3
cardinality is
      wrong!  February 05, 2004
Reviewer:
      A reader
sql>var b0 varchar2(30)
sql>var b1 varchar2(15)
sql>exec :b0:=' '

PL/SQL procedure successfully completed.
sql>exec :b1:='20'

PL/SQL procedure successfully completed.

sql>set autotrace trace exp
sql>r
  1  SELECT b.setid, b.inst_prod_id
  2    FROM sysadm.ps_rf_inst_prod a, sysadm.ps_pa_arbor_elemen b
  3   WHERE a.parent_inst_prodid = :b0
  4     AND a.product_id = :b1
  5     AND b.pa_comp_inst_id = 0
  6     AND a.inst_prod_id = b.inst_prod_id
  7*    AND a.setid = b.setid

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=642 Card=1 Bytes=67)
   1    0   NESTED LOOPS (Cost=642 Card=1 Bytes=67)
   2    1     TABLE ACCESS (BY GLOBAL INDEX ROWID) OF 'PS_RF_INST_PROD'
(Cost=640 Card=1 Bytes=45)
   3    2       INDEX (RANGE SCAN) OF 'PSBRF_INST_PROD' (NON-UNIQUE) (Cost=5
Card=880)
   4    1     TABLE ACCESS (BY GLOBAL INDEX ROWID) OF 'PS_PA_ARBOR_ELEMEN'
(Cost=2 Card=1 Bytes=22)
   5    4       INDEX (UNIQUE SCAN) OF 'PS_PA_ARBOR_ELEMEN' (UNIQUE) (Cost=1
Card=2)

explain plan for
SELECT b.setid, b.inst_prod_id
  FROM sysadm.ps_rf_inst_prod a, sysadm.ps_pa_arbor_elemen b
 WHERE a.parent_inst_prodid = :b0
   AND a.product_id = :b1
   AND b.pa_comp_inst_id = 0
   AND a.inst_prod_id = b.inst_prod_id
   AND a.setid = b.setid;

---------------------------------------------------------------------------------
--------------------------
| Id  | Operation                           |  Name               | Rows  |
Bytes | Cost  | Pstart| Pstop |
---------------------------------------------------------------------------------
--------------------------
|   0 | SELECT STATEMENT                    |                     |     1 |
67 |   642 |       |       |
|   1 |  NESTED LOOPS                       |                     |     1 |
67 |   642 |       |       |
|*  2 |   TABLE ACCESS BY GLOBAL INDEX ROWID| PS_RF_INST_PROD     |     1 |
45 |   640 | ROWID | ROW L |
|*  3 |    INDEX RANGE SCAN                 | PSBRF_INST_PROD     |   880 |
 |     5 |       |       |
|*  4 |   TABLE ACCESS BY GLOBAL INDEX ROWID| PS_PA_ARBOR_ELEMEN  |     1 |
22 |     2 | ROWID | ROW L |
|*  5 |    INDEX UNIQUE SCAN                | PS_PA_ARBOR_ELEMEN  |     2 |
 |     1 |       |       |
---------------------------------------------------------------------------------
--------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("A"."PRODUCT_ID"=:Z)
   3 - access("A"."PARENT_INST_PRODID"=:Z)
   4 - filter("B"."PA_COMP_INST_ID"=0)
   5 - access("A"."SETID"="B"."SETID" AND "A"."INST_PROD_ID"="B"."INST_PROD_ID")

that 880 is wrong, it should be over 5000000!!!

The column has histograms by the way, otherwise the query is damn slow

          Followup:

if it were over 5,000,000 for the predicate, the query would not return fast.

show us the TKPROF with the actual row counts, then we can comment.d

      Rating: 5
what
      rownum>0 means?  February 05, 2004
Reviewer:
      Andrew  from Calgary Alberta Canada
Tom,

in the answer to "we have like problem" (she meant "we have similar problem")
you use inline view to speed up the output. In this subquery you use rownum>0.
Could you please explain what's the point of "and rownum>0"

Isn't that any query if it returns row will always have 0<rownum for every row?

So it's an obsolete line, IMHO. Please correct me if I am mistaken.

Thank you

          Followup:

well, it obviously isn't "obsolete" :)   it works after all...

it causes the inline view to be "evaluated once and stored in temp" to avoid
view merging.

if you have my book "Effective Oracle By Design" -- i cover this in some more
detail/depth.

      Rating: 4
rownum >
      0  February 06, 2004
Reviewer:
      A reader
is to avoid view merging

      Rating:
      5
TKPROF  February 06, 2004
Reviewer:
      A reader
Hi here is the result

SELECT b.setid, b.inst_prod_id
  FROM sysadm.ps_rf_inst_prod a, sysadm.ps_pa_arbor_elemen b
 WHERE a.parent_inst_prodid = :b0
   AND a.product_id = :b1
   AND b.pa_comp_inst_id = 0
   AND a.inst_prod_id = b.inst_prod_id
   AND a.setid = b.setid

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          4          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.01       0.00          0          4          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 123

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  TABLE ACCESS BY GLOBAL INDEX ROWID OBJ#(48103) PARTITION: ROW LOCATION
ROW LOCATION (cr=4 r=0 w=0 time=238 us)
      1   NESTED LOOPS  (cr=4 r=0 w=0 time=228 us)
      0    TABLE ACCESS BY GLOBAL INDEX ROWID OBJ#(47711) PARTITION: ROW
LOCATION ROW LOCATION (cr=4 r=0 w=0 time=219 us)
      0     INDEX RANGE SCAN OBJ#(55911) (cr=4 r=0 w=0 time=215 us)(object id
55911)
      0    INDEX RANGE SCAN OBJ#(55791) (cr=0 r=0 w=0 time=0 us)(object id
55791)

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       36.17         36.17

select object_id, object_name from dba_objects where object_id in (55911, 55791)

 OBJECT_ID OBJECT_NAME
---------- --------------------
     55791 PS_PA_ARBOR_ELEMEN
     55911 PS2RF_INST_PROD

select index_name, column_name
from dba_ind_columns
where index_name in ('PSBRF_INST_PROD', 'PS_PA_ARBOR_ELEMEN', 'PS2RF_INST_PROD')

INDEX_NAME                     COLUMN_NAME
------------------------------ --------------------
PS_PA_ARBOR_ELEMEN             SETID
PS_PA_ARBOR_ELEMEN             INST_PROD_ID
PS2RF_INST_PROD                PRODUCT_ID
PS2RF_INST_PROD                SETID
PS2RF_INST_PROD                INST_PROD_ID
PSBRF_INST_PROD                PARENT_INST_PRODID

I can see from TKPROF that the plan is totally different from that shown in
autotrace or explain plan for... (and may be this is why it´s so fast). Why is
this, does this mean we should stop using set autotrace and explain plan from
now on?

It´s Oracle 9.2.0.4

          Followup:

explain plan for
SELECT b.setid, b.inst_prod_id
  FROM sysadm.ps_rf_inst_prod a, sysadm.ps_pa_arbor_elemen b
 WHERE a.parent_inst_prodid = :b0
   AND a.product_id = :b1
   AND b.pa_comp_inst_id = 0
   AND a.inst_prod_id = b.inst_prod_id
   AND a.setid = b.setid;

if :b0 / :b1 are to be numbers or dates -- use to_number/to_date -- else they
are assumed to be strings and that can through off the explain plan.  that is my
guess.  be careful with binds in explain plans -- use EXPLICIT
to_numbers/to_dates on them!

autotrace/explain plan show you want "might happen".

only tkprof shows you what "really did happen"

February 07, 2004 -- 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

      Rating: 3
Just a related
      suggestion  February 06, 2004
Reviewer:  Asim
      Naveed
Sir Tom,

Why didnt you use CASE expressions in SQL instead of
using the combination of SIGN and DECODE for checking
the BETWEEN condition.

e.g

select case when 3 between 1 and 3 then 'a' else 'b' end from dual

Is it just because the user is using the older version of
oracle or any other reason?

Thanks

          Followup:

question was asked against a version 806 database.

case did not exist back then....

February 07, 2004 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...

      Rating: 5
Question for
      Tyler  January 14, 2004
Reviewer:  Igor
       from France
>>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...

Is it possible to find anywhere how to set up some simple working example, if I
have 8.1.7 (and packages are installed there ) ?
[ I mean with no iAS ]

Thanks

      Rating: 2
No more
      supermen... :-(  February 06, 2004
Reviewer:
      Igor  from France

          Followup:

umm, well, apache and mod_plsql come with 8ir3 and the setup is, well, described
above.  just configure apache (if you cannot find that on the web, well....),
configure a DAD (documented) and fire it up

      Rating:
      4
  March 09, 2004
Reviewer:  Igor
       from France
Actually, setup is simple.
I find documentation a bit confusing.
In database you have all you need to do it, but docs are not clear in that.
Let's say for htp I found somewhere in database docs "if you want to know more
do desc htp" or something like this.
You schould consult also ias docs for different things and there you are never
sure if it still counts for database only...

February 07, 2004 Asha -- Thanks for the question regarding "Need
      to Store Session Variables in PL/SQL", version 8.1.7/Portal

      You Asked
Hi Tom,
       Thanks for taking my question and your great idea ofd knoledge
sharing .
        I'm using Portal version 3.0.6.6.5 with Oracle 8.1.7 .
I created couple of repots by coding Pl Sql webtoolkit in WebDB 2.0.
Now I need to create an application with 9 TABS .The first TAB is the default
page in which user makes all selections and each of the following TABS is a
report which will use the selections made in the First TAB as parameters . The
user should be able to come back to the
first page any time and the selections he made before should be
in that page. If you have any examples of this sort either stored procedures or
PSP I'd really appriciate that .I apologize for this long question , I know your
time is so premium but I wanted to
explain it in detail as much as I could.
Thanks in advance,
Asha

      and we said...
The way I do this is to set up a session id.  I either use a cookie to pass this
session id from page to page or I use a hidden field (either approach works
fine).

This session id is primary key into a "state" table.  This state table it just a
database table that you use to store the attributes of a users sessions (your
info from "tab1" for example.

You would create a package like this:

create package state
as
    g_data   state_table%rowtype;

    procedure get_state( p_sess_id in number );
    procedure save_state( p_sess_id in number );
end;
/

create package body state
as

procedure save_state( p_sess_id in number )
is
begin
   update state_table set  c1 = g_data.c1, ... cn = g_data.cn
     where sess_id = p_sess_id;

   if ( sql%rowcount = 0 )
   then
            insert into state_table (sess_id, c1, c2, ... )
            values ( p_sess_id, g_data.c1, ... g_data.cn );
   end if;
end;

procedure get_state( p_sess_id in number )
is
begin
   select * into g_data from state_table where sess_id = p_sess_id;
exception
    when no_data_found then null;
end;

end;
/

Now just call get_state/save_state whenever you need and use the values in
state.g_data.

before you say "but thats IO to the database" -- I'll say -- thats what
databases do best.  It is what I do, even on this application you are using
right here...
Review & Followup

      Rating: 4
Straight and
      simple  March 01, 2002
Reviewer:
      A reader
Hi Tom,
       Thanks a lot for your info .
I'll try that out.

Thanks,
Asha

      Rating:
      4
state_table  February 06, 2004
Reviewer:
      Susan  from Watertown, MA
Tom,

as part of your process do you routinely delete data from your state_table?
Thanks.

          Followup:

yes, we have a regular job to clean up "uncleaned up" sessions.

February 07, 2004 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.

      Rating: 3
help regarding
      ora-01744  February 06, 2004
Reviewer:  Pritesh
      Salia  from Pune - India
Hi Tom,
       Please see the attached EditPlus file with two functions.

Both are similar Functions USING Execute Immediate - AND also "INTO" clause IN
their dynamic SQL formation....
BUT the CALL TO FIRST Function error outs ........ WITH message .....
"ORA-01744 inappropriate INTO
 Cause: The INTO clause may not be used in a subquery.
 Action: Check the syntax, place the INTO clause in the top-level query, and
retry the statement. "

If we COMMENT the INTO clause OF FIRST FUNCTION AND THEN call it - It works Fine
!!!
I want TO know why doesnt it WORK WITH INTO clause - WHILE the second Function
works Fine WITH INTO Clause.
There IS no such difference IN both the Functions.

All the tables used in the Func are eithier Pub/Pvt Syn or View. Do u need any
rights/privs for Execute immediate?
Kindly let me know what can/needs to be done .
Do let me know if you need any more clarification.

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

Just in case if u r NOT able to open the File - here is the what it contains

/* First Function FN_GET_CHASSIS_QUANTITY */
FUNCTION FN_GET_CHASSIS_QUANTITY ( p_contract_number         IN
DW_KS_SVC_HEADERS.CONTRACT_NUMBER%TYPE
                                    ,p_svc_header_id            IN
DW_KS_SVC_HEADERS.SVC_HEADER_ID%TYPE
                                      ,p_contract_status         IN
DW_KS_SVC_HEADERS.CONTRACT_STATUS%TYPE
                                   ,p_install_loc             IN
DW_KS_INSTALL_BASE.INSTALL_LOC%TYPE
                                   ,p_item_list                IN LONG
                                      ,p_expd_parts_flag        IN VARCHAR2
                                   ,p_termd_parts_flag        IN VARCHAR2
                                   ,p_zero_item_flag        IN VARCHAR2
                                   ,p_cat_a_flag            IN VARCHAR2
                                ) RETURN DW_KS_SVC_DETAILS.QUANTITY%TYPE
AS
l_chassis_quantity DW_KS_SVC_DETAILS.QUANTITY%TYPE;
l_boolean           BOOLEAN := FALSE;
l_con_query       VARCHAR2(6000);
l_con_select      VARCHAR2(200);
l_con_tables_2      VARCHAR2(400);
l_con_conds_2      VARCHAR2(5000);
l_con_tables_4      VARCHAR2(400);
l_con_conds_4      VARCHAR2(5000);
l_con_conds_list_4  VARCHAR2(5000);
p_svc_line_conds  VARCHAR2(200);
p_cp_status_conds VARCHAR2(1000);
l_cat_a_cond         VARCHAR2(100);
l_det_prod_join      VARCHAR2(100);
l_zero_item_cond  VARCHAR2(100);
l_position           NUMBER;

BEGIN
         DBMS_OUTPUT.PUT_LINE('In CAT A SUM list ');
         l_boolean := Pk_Ks_Bi_Csd_Report.FN_VALID_STATUSES( p_contract_status
                                                             ,p_expd_parts_flag
                                                          ,p_termd_parts_flag
                                                          ,p_svc_line_conds
                                                          ,p_cp_status_conds);
         DBMS_OUTPUT.PUT_LINE('after valid status');
       l_con_select      := 'SELECT SUM(SDTLS.QUANTITY) ' ||
                              'INTO   :l_chassis_quantity ';
       l_con_tables_2 := 'FROM   DW_KS_SVC_HEADERS SHDRS ' ||
                            '        ,DW_KS_SVC_DETAILS SDTLS ' ||
                            '        ,CDW_PRODUCTS       PRODS  ';
       l_con_conds_2  := 'WHERE  shdrs.SVC_HEADER_ID        = ''' ||
p_svc_header_id   || ''' ' ||
                              'AND      shdrs.CONTRACT_NUMBER      = ''' ||
p_contract_number || ''' ' ||
                            'AND     shdrs.CONTRACT_STATUS      = ''' ||
p_contract_status || ''' ' ||
                         'AND      shdrs.SVC_HEADER_ID        =
sdtls.SVC_HEADER_ID   ' ||
                            'AND    shdrs.CONTRACT_NUMBER           =
sdtls.CONTRACT_NUMBER ' ||
                          p_cp_status_conds    || ' ' ||
                            'AND     PRODS.PRODUCT_ID             =
SDTLS.PRODUCT_ID ' ||
                         'AND    PRODS.CSM_ITEM_TYPE        IN (''CHASSIS'') ';
       l_con_tables_4 := '        ,DW_KS_INSTALL_BASE INBS ';

       l_con_conds_4  := 'AND     to_number(SDTLS.INSTALL_INSTANCE_ID)    =
INBS.INSTANCE_ID ' ||
                       'AND       SUBSTR(inbs.INSTANCE_STATUS,1) IN
(''Latest-INSTALLED'',''?DMY'') '         ||
--                         'AND    substr(inbs.instance_status,1)       =
''Latest-INSTALLED'' ' ||
                         'AND    INBS.INSTALL_LOC            = '''  ||
p_install_loc || ''' ';

       l_con_conds_list_4  := 'AND     to_number(SDTLS.INSTALL_INSTANCE_ID)    =
INBS.INSTANCE_ID ' ||
                                    'AND   SUBSTR(inbs.INSTANCE_STATUS,1)
 IN (''Latest-INSTALLED'',''?DMY'') '  ||
--                         'AND    substr(inbs.instance_status,1)       =
''Latest-INSTALLED'' ' ||
                         'AND    INBS.INSTALL_LOC            IN ('  ||
p_install_loc || ')  ';

       l_cat_a_cond      := ' AND prods.PRODUCT_CATEGORY          = ''A'' ';
       l_det_prod_join:= ' AND sdtls.product_id                =
prods.product_id ';

       l_zero_item_cond  := ' AND (sdtls.maint_list_price <> 0  OR
sdtls.maint_net_price <> 0 ) ';

       IF p_item_list IS NOT NULL THEN
                IF p_item_list <> 'ALL' THEN
             l_con_conds_2 := l_con_conds_2 ||
                                'AND    PRODS.PRODUCT_ID        IN (' ||
p_item_list || ') ';
          END IF;
       END IF;

       IF p_zero_item_flag = 'N' THEN
             l_con_conds_2 := l_con_conds_2 ||  l_zero_item_cond;
       END IF;

          IF p_cat_a_flag = 'Y' THEN
             l_con_conds_2 := l_con_conds_2 || l_cat_a_cond;
       END IF;

       IF p_install_loc IS NULL THEN
               l_con_query := l_con_select  || l_con_tables_2 || l_con_conds_2;
       ELSE
               BEGIN
                 SELECT INSTR(p_install_loc,'''', 1) INTO l_position FROM DUAL;
               EXCEPTION
                        WHEN NO_DATA_FOUND THEN
                        l_position := 0;
               END;
            IF l_position = 0 THEN
                  l_con_query := l_con_select  || l_con_tables_2 ||
l_con_tables_4 ||
                             l_con_conds_2 || l_con_conds_4;

            ELSE
                  l_con_query := l_con_select  || l_con_tables_2 ||
l_con_tables_4 ||
                             l_con_conds_2 || l_con_conds_list_4;
            END IF;
       END IF;
       BEGIN
--                execute immediate 'insert into cib_temp values (:id,
:sys_date, :text)' using 111112,sysdate,l_con_query;
                DBMS_OUTPUT.PUT_LINE('after insert');
             COMMIT;
       END;
       BEGIN
                EXECUTE IMMEDIATE l_con_query INTO l_chassis_quantity;
                   DBMS_OUTPUT.PUT_LINE('after execute immediate');
       EXCEPTION
             WHEN NO_DATA_FOUND THEN
                   l_chassis_quantity := 0;
                 RETURN l_chassis_quantity;
       END;

          RETURN l_chassis_quantity;

END FN_GET_CHASSIS_QUANTITY;
/* First Function FN_GET_CHASSIS_QUANTITY */

/* Second Function  */
FUNCTION FN_GET_CON_EARLIEST_DATE ( p_contract_number         IN
DW_KS_SVC_HEADERS.CONTRACT_NUMBER%TYPE
                                       ,p_svc_header_id        IN
DW_KS_SVC_HEADERS.SVC_HEADER_ID%TYPE
                                       ,p_contract_status         IN
DW_KS_SVC_HEADERS.CONTRACT_STATUS%TYPE
                                       ,p_expd_parts_flag        IN VARCHAR2
                                    ,p_termd_parts_flag        IN VARCHAR2
                                  )  RETURN DW_KS_SVC_DETAILS.END_DATE%TYPE
AS
l_min_end_date DW_KS_SVC_DETAILS.END_DATE%TYPE := NULL;
l_boolean BOOLEAN := FALSE;
l_con_query VARCHAR2(1000);
p_svc_line_conds VARCHAR2(200);
p_cp_status_conds VARCHAR2(1000);
BEGIN

         DBMS_OUTPUT.PUT_LINE('In con earliest date ');
         l_boolean := Pk_Ks_Bi_Csd_Report.FN_VALID_STATUSES( p_contract_status
                                                             ,p_expd_parts_flag
                                                          ,p_termd_parts_flag
                                                          ,p_svc_line_conds
                                                          ,p_cp_status_conds);
       DBMS_OUTPUT.PUT_LINE('after valid status');
       l_con_query  := 'SELECT MIN(sdtls.END_DATE) ' ||
                         'INTO   :l_min_end_date '       ||
                       'FROM   dw_ks_svc_headers shdrs, ' ||
                       '       dw_ks_svc_details sdtls '    ||
                       'WHERE  shdrs.SVC_HEADER_ID       = ''' ||
p_svc_header_id      || ''' ' ||
                       'AND    shdrs.CONTRACT_NUMBER  = ''' || p_contract_number
|| ''' ' ||
                       'AND    shdrs.CONTRACT_STATUS  = ''' || p_contract_status
|| ''' ' ||
                       'AND    shdrs.SVC_HEADER_ID      = sdtls.SVC_HEADER_ID '
    ||
                       'AND       shdrs.CONTRACT_NUMBER  = sdtls.CONTRACT_NUMBER
'  ||
                        p_cp_status_conds  || ' ' ;
--                       'GROUP BY shdrs.CONTRACT_NUMBER ';
          DBMS_OUTPUT.PUT_LINE('before insert');
       BEGIN
--                execute immediate 'insert into cib_temp values (:id,
:sys_date, :text)' using 111112,sysdate,l_con_query;
                DBMS_OUTPUT.PUT_LINE('after insert');
             COMMIT;
       END;
       BEGIN
                EXECUTE IMMEDIATE l_con_query INTO l_min_end_date;
                   DBMS_OUTPUT.PUT_LINE('after execute immediate');
       EXCEPTION
             WHEN NO_DATA_FOUND THEN
                   l_min_end_date := NULL;
                 RETURN l_min_end_date;
       END;

          RETURN l_min_end_date;
END FN_GET_CON_EARLIEST_DATE;
/* Second Function  */

/* Call to First procedure */
DECLARE
A  DW_KS_SVC_DETAILS.QUANTITY%TYPE;
BEGIN
a := Pk_Ks_Bi_Csd_Report.FN_GET_CHASSIS_QUANTITY ( '1000520' ,
                               '47441033' ,
                               'ACTIVE'  ,
                                NULL ,
                               'ALL' ,
                               'N' ,
                               'N' ,
                               'N' ,
                               'N'
                               );
DBMS_OUTPUT.PUT_LINE('The output : '||a);
END;
/* Call to First procedure */

/* Call to Second procedure */
DECLARE
A  DW_KS_SVC_DETAILS.END_DATE%TYPE;
BEGIN
    a := Pk_Ks_Bi_Csd_Report.FN_GET_CON_EARLIEST_DATE  ('1000520' ,
                                                        '47441033' ,
                                                        'ACTIVE'  ,
                                                        'N' ,
                                                        'N'
                                                        ) ;
DBMS_OUTPUT.PUT_LINE('The output : '||a);
END;
/* Call to Second procedure */

          Followup:

the only correct way to use 'into' with execute immediate is like this:

execute immediate 'select dummy from dual' INTO plsql_variable;

INTO is not "part of sql selects".  Your syntax is wrong.  get the "into :bind"
out of the dynamically generated queries.

February 07, 2004 Prince -- Thanks for the question regarding
      "Index creation with nologging and recovery", version 8.1.7.4

      You Asked
I take the backup os the tablespace TSIX at T0.

I create an index with NOlogging at time T1 on TSIX.

Alter the index for logging at T2

Say at T3, I have a failure and I want to recover TSIX

Assume, I did not take the TSIX backup after the index creation.

Now, Can I be able to recover the TSIX as of T3? Can I drop the index, while
doing the recovery?

In summary, how do I recover TSIX to Point T3, from the backup which was taken
at T0?

Thanks,

      and we said...
You cannot recover the index in TSIX as your backup predates its existence and
you have no redo log with which to recreate it.

You'll restore TSIX, recover it -- but the index will be invalid, you'll drop
and recreate the index.

Review &
      Followup

      Rating: 5
Thanks
      Tom!  August 31, 2002
Reviewer:  Prince.

My main quetion was, Can I recover the TSIX or would it error out when it comes
to the point of recovering the index and fails to recover the TSIX further.

Anyway, your answer suggests that I can recover the TSIX but not the index,
which is absoluttlely fine.

          Followup:

That is correct.

      Rating: 4
faster when
      creating index with nologging?  February 06, 2004

Reviewer:  A reader
Hi Tom,

   just want to know if create index with nologging on noarchive log database,
no redo generation? 0r in noarchive log database, nologging and logging when
building index are the same (in terms of redo generation)? If yes, nologging
option in creating index should only be
"valid" in archivelog database? Am I right?

David

          Followup:

easy to see....

ops$tkyte@ORA9IR2> select log_mode from v$database;

LOG_MODE
------------
NOARCHIVELOG

ops$tkyte@ORA9IR2> set verify off
ops$tkyte@ORA9IR2> column value new_val V

ops$tkyte@ORA9IR2> select a.name, b.value from v$statname a, v$mystat b
  2  where a.statistic# = b.statistic# and a.name = 'redo size'
  3  /

NAME                                VALUE
------------------------------ ----------
redo size                         3619828

ops$tkyte@ORA9IR2> create table t NOLOGGING as select * from all_objects;

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select a.name, b.value, b.value-&V diff from v$statname a,
v$mystat b
  2  where a.statistic# = b.statistic# and a.name = 'redo size'
  3  /

NAME                                VALUE       DIFF
------------------------------ ---------- ----------
redo size                         3690384      70556

ops$tkyte@ORA9IR2> drop table t;

Table dropped.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select a.name, b.value from v$statname a, v$mystat b
  2  where a.statistic# = b.statistic# and a.name = 'redo size'
  3  /

NAME                                VALUE
------------------------------ ----------
redo size                         3724888

ops$tkyte@ORA9IR2> create table t as select * from all_objects;

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select a.name, b.value, b.value-&V diff from v$statname a,
v$mystat b
  2  where a.statistic# = b.statistic# and a.name = 'redo size'
  3  /

NAME                                VALUE       DIFF
------------------------------ ---------- ----------
redo size                         3795492      70604

so, for create table -- no difference.... but....

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create index t_idx on t(owner,object_type,object_name);

Index created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select a.name, b.value, b.value-&V diff from v$statname a,
v$mystat b
  2  where a.statistic# = b.statistic# and a.name = 'redo size'
  3  /

NAME                                VALUE       DIFF
------------------------------ ---------- ----------
redo size                         5519780    1724288

ops$tkyte@ORA9IR2> create index t_idx2 on t(object_name,object_type,owner)
NOLOGGING;

Index created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select a.name, b.value, b.value-&V diff from v$statname a,
v$mystat b
  2  where a.statistic# = b.statistic# and a.name = 'redo size'
  3  /

NAME                                VALUE       DIFF
------------------------------ ---------- ----------
redo size                         5559596      39816

for create index, big difference....

February 07, 2004 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)

      Rating: 5
restrict logon
      trigger  February 06, 2004
Reviewer:  Tom
      fan  from big apple
Hi Tom,

I was searching your site for examples of coding restrict login triggers on
various conditions. This was a very useful thread.

I have question regarding suppressing the error stack coming out of the trigger.
It is often very confusing to the end users. I am trying to see if there is any
other way of coding this, without showing the error stack to the users. I read
about setting the application context in 9i / using resource consumption groups
etc. Is it possible, instead of failing the trigger block, to let the user
connect briefly and then disconnect / disable without allowing him to do
anything else, using a null context or a resource group with no resources etc?.
I don't know if it is a valid idea. Is it possible and if yes what is your
openion. Can you please show in an example how to do it.

Thank you soomuch.

          Followup:

ORA-00604: error occurred at recursive SQL level 1
ORA-20002: 'Maximum connection exceeded'
ORA-06512: at line 19

is confusing?  to someone that can read?

You get to tell them anything you want in the error text.  if you have a custom
app -- it can catch this error and do whatever it likes (present a pretty error
message).

No, you cannot allow them to connect briefly -- to what end?  so they can get
"your session was killed" in an error stack instead of something meaningful that
you provide them with??

February 07, 2004 Arnaud -- Thanks for the question regarding
      "mutating table", version 8.1.7

      You Asked
hello,

i've got a table MRC and a trigger on it (AFTER INSERT)

thus, after an insert in the table MRC, this trigger has to determine if a new
line must be inserted into an other table PLAN : for that, it does compare the
:new values with the MOST RECENT enregistrement of MRC

but i got a mutating table error
i understand the problem but how can i get over ??

thanks
Arnaud

      and we said...
My personal opinion -- when I hit a mutating table error, I've got a serious
fatal flaw in my logic.

Have you considered the multi-user implications in your logic?  Two people
inserting at the same time (about the same time).  What happens then???  Neither
will see eachothers work, neither will block -- both will think "ah hah, I am
first"...

anyway, you can do too much work in triggers, this may well be that time --
there is nothing wrong with doing things in a more straightforward fashion (eg:
using a stored procedure to implement your transaction)

but if you persist, you can use the technique:

http://asktom.oracle.com/~tkyte/Mutate/index.html
to avoid the mutating table constraint -- but I would avoid the situation that
gets me there in the first place.  The logic is a whole lot more understandable
that way (and maintainable and testable and everything)
Review &
      Followup

      Rating:
      5
Ora-4091  May 05, 2003
Reviewer:
      A reader
We create trigger in the test server (8i) its working without error, and when we
created at life (8) we get the following error:
ORA-04091: table XXXX is mutating, trigger/function may not see it.

          Followup:

they relaxed some of the constraining rules between 8.0 and 8.1 -- things are in
general upwards (develop in 8.0 and goto 8.1) compatible but not backwards.

      Rating: 4
Why I can't get
      the 4091 error when insert?  January 05, 2004

Reviewer:  Li ys  from CHINA
I only want to prove the mutating table by this triggers:
CREATE TABLE r_Module
(
    Bureauno    NUMBER(3),
    Moduleno    NUMBER(3),
    primary key
    (
        Bureauno,
        Moduleno
    )
);

CREATE OR REPLACE TRIGGER LimitTest
    BEFORE INSERT OR UPDATE ON r_Module
    FOR EACH ROW
DECLARE
    v_MaxModuleNum CONSTANT NUMBER := 5;
    v_CurModuleNum NUMBER;
BEGIN
    SELECT COUNT(*) INTO v_CurModuleNum FROM r_Module WHERE Bureauno =
:new.Bureauno;
    IF v_CurModuleNum + 1 > v_MaxModuleNum THEN
        RAISE_APPLICATION_ERROR(-20000,'Too many Module in the
Bureauno:'||:new.Bureauno);
    END IF;
END;
/

When I insert some record into r_module table,I can't get the 4091 error,but
when I update one, the error come.
I wonder if the insert can't lead the mutating table error?
Thank you very much!

By the way,The link (Oracle Server Application Developers Guide ) in the:

http://asktom.oracle.com/~tkyte/Mutate/index.html
may be invalidation.

          Followup:

thanks -- fixed the link.

Anyway, the mutating table issue won't crop up during a single row insert.

if you "insert into t (...) values (....)" the server recognizes that "hey,
there is just one row -- it is 'safe', the table isn't being subjected to a
multi-row modification.  the trigger won't see an unstable set of rows"

if you turn that insert into t ( ... ) values ( ... ) into:

  insert into t ( .... ) select .... from dual;

you'll get the mutating table issue -- for even though you are just inserting a
single row, the database no longer knows that.

      Rating: 4
I get the new
      information:  January 05, 2004
Reviewer:  Li
      ys  from CHINA
If I use 'Insert into table SELECT ...',The 4091 error will happen,if only
insert one record,It does not happen,but I want to know why?

      Rating: 5
backup data
      using trigger  January 26, 2004
Reviewer:  Sean
       from NJ, USA
Hi Tom,

I want to move the records of parent and child table to archive tables before
delete.  I created the trigger to do that.  But I was not able to do that
because of the mutating table.  (We use 817)

The reason that we wanted to use trigger instead of calling stored procedure to
move data is that we have several places in application to delete this record.
If we don't use trigger, we need to add calling stored procedure code in all
those places of application.

I understand the concept of mutating table, but just but don't know how to
implement it in this case.

Here is the sample:

create table t1(c1 number primary key, c2 number);

create table t2(c1 number , c2 number,
    constraint t2_fk foreign key(c1) references t1(c1));

create table t1_arch(c1 number, c2 number);

create table t2_arch(c1 number, c2 number);

insert into t1(c1, c2)
values(1, 10);

insert into t1(c1, c2)
values(2, 20);

insert into t2(c1, c2)
values(1, 100);

insert into t2(c1, c2)
values(2, 200);

create or replace trigger tri1
before delete on t1
for each row
begin
    insert into t1_arch(c1, c2)
    select c1, c2 from t1
        where c1 = :old.c1;

    insert into t2_arch(c1, c2)
    select c1, c2 from t2
    where c1 = :old.c1;
end;
/

SQL> delete from t1
  2  where c1= 1;
delete from t1
            *
ERROR at line 1:
ORA-04091: table SCOTT.T1 is mutating, trigger/function may not see it
ORA-06512: at "SCOTT.TRI1", line 2
ORA-04088: error during execution of trigger 'SCOTT.TRI1'

Thanks so much for your help.

Sean

          Followup:

why would a delete on T1 query T2????

you would have had a row trigger on T2 that captured the T2 records and a row
trigger on T1 to capture them.

Think about it -- when you goto delete from T1 there had better not be
anything LEFT in t2 that points to it anymore!!!!  If there was, you just
found a bug (t2 would be devoid of child records by the time you get to t1 to
delete, your insert as select from t2 could not possibly find any matching
records!!!!!)

      Rating: 5
Back data
      before deleting  January 26, 2004
Reviewer:
      Sean  from NJ, USA
Hi Tom,

Sorry for the confusion.  Actually, I just wanted to archive t1 data before
delet it.

Here is the sample:

create table t1(c1 number primary key, c2 number);

create table t1_arch(c1 number, c2 number);

insert into t1(c1, c2)
values(1, 10);

insert into t1(c1, c2)
values(2, 20);

create or replace trigger tri1
before delete on t1
for each row
begin
    insert into t1_arch(c1, c2)
    select c1, c2 from t1
        where c1 = :old.c1;

end;
/

SQL> delete from t1
  2    where c1= 1;
delete from t1
            *
ERROR at line 1:
ORA-04091: table SCOTT.T1 is mutating, trigger/function
ORA-06512: at "SCOTT.TRI1", line 2
ORA-04088: error during execution of trigger 'SCOTT.TRI1

Thanks so much for your help.

Sean

          Followup:

insert into t1_arch( c1, c2 ) values ( :old.c1, :old.c2 );

you don't want to "select it", you already have it.

      Rating: 5
mutate
      table  February 06, 2004
Reviewer:
      A reader
Hi Tom,

I want to update the record after insert by using trigger, but I got
mutating table error.  Here is the example.

create table t1(c1 number, c2 varchar2(20));

create or replace trigger trg1
after insert on t1
for each row
begin
    -- Get c2 values from other table, then update herre.
    update t1
    set c2 = 'B'
    where c1 = :new.c1;

end;
/

insert into t1(c1)
values(1);

ERROR at line 1:
ORA-04091: table SCOTT.T1 is mutating, trigger/function may not see it
ORA-06512: at "SCOTT.TRG1", line 3
ORA-04088: error during execution of trigger 'SCOTT.TRG1'

Thanks so much for your help.

Sean

          Followup:

BEFORE insert on t1
for each row
begin
   :new.c2 := 'whatever you want to set it to';
end;

you don't want to UPDATE the row, you just want to set values.  It is very
natural just to use the assignment operation -- no?  (forget sqlserver's way of
doing everything!  it is much easier this way...)

      Rating: 5
mutate
      table  February 06, 2004
Reviewer:  Sean
       from NJ, USA
Hi Tom,

I want to update the record after insert by using trigger, but I got
mutating table error.  (I post it again because I forgot to put my personal
info.  Sorry for that)

Here is the example.

create table t1(c1 number, c2 varchar2(20));

create or replace trigger trg1
after insert on t1
for each row
begin
    -- Get c2 values from other table, then update herre.
    update t1
    set c2 = 'B'
    where c1 = :new.c1;

end;
/

insert into t1(c1)
values(1);

ERROR at line 1:
ORA-04091: table SCOTT.T1 is mutating, trigger/function may not see it
ORA-06512: at "SCOTT.TRG1", line 3
ORA-04088: error during execution of trigger 'SCOTT.TRG1'

Thanks so much for your help.

Sean

February 07, 2004 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)

      Rating: 4
Can not get
      output recognized as csv  February 05, 2004

Reviewer:  robert  from ct
Tom, I am using webdb's wwv_sys_sylk..but I can not get the output to be
recognized as csv, can not auto-launch Excel either...in stead I get a prompt to
Save or select an application to open the file. (output is recognized as
"mypkg.theprocedure")

I'd like the link to function like HTMLDB's whose output is recognized as i.e
"employee.csv"

this is portion of webdb.wwv_sys_sylk program that generates the mime-header....

thanks

PROCEDURE print_heading
(   grid        IN VARCHAR2,
    col_heading IN VARCHAR2,
    allignments IN vc_arr,
    headings    IN vc_arr
)
IS
    l_align      VARCHAR2(1);
    l_heading    VARCHAR2(1000);
BEGIN
    owa_util.showpage;
    owa_util.mime_header('application/excel');
    htp.p('ID;ORACLE');

          Followup:

what mime type does your browser show and what browser are you using and does
your browser have excel registered to deal with mime types of
"application/excel"

      Rating:
      4
owa_util.mime_header for Excel  February 06, 2004

Reviewer:  robert  from CT
Searched on Metalink...this does the trick

owa_util.mime_header('application/vnd.ms-excel');

thanks

          Followup:

that just means you did not have application/excel configured in your browser is
all.

      Rating: 4
been using the
      same packge for over 2 years  February 07, 2004

Reviewer:  steve  from china
i've been using this exact package since way back ,and had very little problem
with it.
It's currently producing spreadsheet reports automatically ,Even tied it into
javamail via a clob, so that they can be emailed automatically to managers &
directors.

i just cannot believe the level of some these requests and how little work some
people seem willing to do.

February 07, 2004 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.

      Rating:
      3
Thread  February 06, 2004
Reviewer:
      alex  from UK
Tom

I have following code sort off

main() {

fetch from XYZ;

insert into ABC from the fetched records of XYZ;

thread(1);
thread(2);
thread(3);

COMMIT; --commit transactions

}

thread(x int) {

insert records into tab1 for x;

}

The above program inserts the records into ABC but not in Tab1. Is it because it
is saperate thread and after successful completion threads data is rollbacked. I
have tried putting COMMIT in tread function as well but it did not work.

Can I request you to suggest on this please.

Thanks

          Followup:

no clue for you since there are two ways to do threaded programming with Oracle.

way 1: all threads share the same connection and use mutexes to protect from
more than one thread using a connection at a time.

way 2: all threads have their own connection/context.  Each thread (including
the "main mother of all threads thread") have their own connect their own
context.  A commit in one thread affects nothing in the other threads.

One would have to see an entire, well coded example to comment -- but I've a
feeling that it would be too large for here.

February 07, 2004 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

      Rating: 5
scalar subquery
      disable parallel exec?  February 06, 2004

Reviewer:  steve  from NY
Hi Tom,

Based on the example in your book (Effective oracle by design p507), I modified
a outer join query using scalar
subquery, and found that the new query is not executed in parallel, the outer
join query was executed in parallel.
Is scalar subquery will disable the parallel execution?

thanks!

steve

          Followup:

it can, you might be able to use an inline view to avoid that -- applying the
scalar subquery after the PQ runs:

ops$tkyte@ORA9IR2> select /*+ parallel(emp 5) */ emp.* from emp;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=327 Bytes=28449)
   1    0   TABLE ACCESS* (FULL) OF 'EMP' (Cost=1 Card=327 Bytes=28449)  :Q31000

   1 PARALLEL_TO_SERIAL            SELECT /*+ NO_EXPAND ROWID(A1) */ A1."EMPNO"
                                   ,A1."ENAME",A1."JOB",A1."MGR",A1."HIREDATE",
                                   A1."SAL",A1."COMM",A1."DEPTNO" FROM "EMP" PX
                                   _GRANULE(0, BLOCK_RANGE, DYNAMIC)  A1

ops$tkyte@ORA9IR2> select /*+ parallel(emp 5) */ emp.*, (select count(*) from
dual) cnt from emp;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=327 Bytes=28449)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'DUAL' (Cost=11 Card=8168)
   3    0   TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=327 Bytes=28449)

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select x.*, (select count(*) from dual)
  2    from (
  3  select /*+ parallel(emp 5) NO_MERGE */ emp.* from emp
  4         ) x
  5  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=327 Bytes=28449)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'DUAL' (Cost=11 Card=8168)
   3    0   VIEW* (Cost=1 Card=327 Bytes=28449)  :Q32000
   4    3     TABLE ACCESS* (FULL) OF 'EMP' (Cost=1 Card=327 Bytes=28449)
:Q32000

   3 PARALLEL_TO_SERIAL            SELECT /*+ NO_MERGE  */ C0,C1,C2,C3,C4,C5,C6
                                   ,C7 FROM (SELECT /*+ NO_EXPAND ROWID(A1) */
                                   A1."EMPNO" C0,A1."ENAME" C1,A1."JOB" C2,A1."
                                   MGR" C3,A1."HIREDATE" C4,A1."SAL" C5,A1."COM
                                   M" C6,A1."DEPTNO" C7 FROM "EMP" PX_GRANULE(0
                                   , BLOCK_RANGE, DYNAMIC)  A1) A0

   4 PARALLEL_COMBINED_WITH_PARENT

But note -- the scalar subquery typically works best when the result set is
going to be small but the outer joined to object is largeish -- instead of
processing big set -- we do a couple of scalar subqueries.  So, I'm not sure
that this technique applies so much when you are using PQ.

February 07, 2004 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.

      Rating: 3
default menu in
      oracle forms  February 07, 2004
Reviewer:
      Anurag  from INDIA
Dear Tom,

Please help in getting solution to following problems:

1.) How in Forms6.0 I can maintain both - default toolbar menu as well as
customised dropdown menu.
2.) In an application I want to display command window for open & save files
like we have in window O.S.

Thanks in adv.

          Followup:

try otn.oracle.com -> discussion forums.

i haven't touched forms since march, 1995.

February 07, 2004 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.

      Rating: 5
can we use
      arraysize to tune SQL  February 07, 2004
Reviewer:
      A reader
Hi

Read some of your replies above about set arraysize and reducing consistent
gets. I wonder, can this use to improve batch processes?

For example if I have a query using 10 table-join which returns 2 million of
rows and spooled to text file will it run faster if I use a larger arraysize?

Would it work for Materialized View refresh??

          Followup:

arraysize = MANDATORY FEATURE for batch!

arraysize is a programming tool, it'll not affect MV refreshes at all.

February 07, 2004 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

      Rating:
      2
  February 07, 2004
Reviewer:
      Prince  from Pakistan
Hi Tom!

I have two databases prod and test. test dababase has been populated by
importing one month old dump of prod. Now here is a requirement. we want to see
the next values of sequences of test database at least equal to prod database's
sequence next values. Can you guide plz... how can it be done?

Thanks as always :)

          Followup:

you'd have to goto prod, grab the sequence name and last_number from
dba_sequences.

Then you can use the technique above in test to reset to zero, change the
increment to last_number and select nextval (hence making the sequence "be at"
last_number, then change the increment to 1 again and it'll be done.

something like this (on test -- table t would be created as select from
user_sequences on the other machine and moved over)

ops$tkyte@ORA9IR2> create table t ( s_name varchar2(30), last_number number );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create sequence s1 increment by 212321;

Sequence created.

ops$tkyte@ORA9IR2> create sequence s2 increment by 2352;

Sequence created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select s1.nextval, s2.nextval from dual;

   NEXTVAL    NEXTVAL
---------- ----------
         1          1

ops$tkyte@ORA9IR2> select s1.nextval, s2.nextval from dual;

   NEXTVAL    NEXTVAL
---------- ----------
    212322       2353

ops$tkyte@ORA9IR2> alter sequence s1 increment by 1;

Sequence altered.

ops$tkyte@ORA9IR2> alter sequence s2 increment by 1;

Sequence altered.

ops$tkyte@ORA9IR2> select s1.nextval, s2.nextval from dual;

   NEXTVAL    NEXTVAL
---------- ----------
    212323       2354

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t values ( 'S1', 523323 );

1 row created.

ops$tkyte@ORA9IR2> insert into t values ( 'S2',  10023 );

1 row created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace procedure reset_sequences
  2  as
  3          l_dummy number;
  4  begin
  5          for x in ( select * from t )
  6          loop
  7                  execute immediate 'select ' || x.s_name || '.nextval from
dual' into l_dummy;
  8
  9                  execute immediate 'alter sequence ' || x.s_name ||
 10                                    ' increment by -' || (l_dummy-1);
 11
 12                  execute immediate 'select ' || x.s_name || '.nextval from
dual' into l_dummy;
 13
 14                  execute immediate 'alter sequence ' || x.s_name ||
 15                                    ' increment by ' || x.last_number;
 16
 17                  execute immediate 'select ' || x.s_name || '.nextval from
dual' into l_dummy;
 18
 19                  dbms_output.put_line( 'last selected value from ' ||
x.s_name || ' was ' || l_dummy );
 20          end loop;
 21  end;
 22  /

Procedure created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec reset_sequences;
last selected value from S1 was 523324
last selected value from S2 was 10024

PL/SQL procedure successfully completed.

February 07, 2004 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

      Rating: 5
loading xml
      into multiple tables with obj relational view  January 14,
      2004
Reviewer:  reader  from PA
Tom/Sean,

The docs describe the capability to load an xml document into multiple
relational tables using an object relational view over the top of the relational
tables.

There are other approaches described as well, but can you show a simple example
of this, or point me to an example from documentation where this might be
accomplished.

          Followup:

Hi, Sean here...

There are a couple ways to do this.  First and foremost, I'd look to Oracle XML
DB in 9iR2 and beyond (10g, eventually) to do this for you.  XML DB will use an
XML Schema (mandatory) and will create an object-relational table (or multiple
object-relational tables, depending on how you tell Oracle to store the data) to
store the scalar values within the XML documents you load into the database.
This is a GREAT way to do it, and I'd say Oracle's #1 recommendation for
"breaking down" XML into tables.  (See pointers to Oracle doc and samples about
these concepts, below)

Pro's:
- Oracle manages all the tables, the shredding of the XML upon load, the
reconstitution of the XML when you select the entire document.
- No programming necessary.  You don't have to learn the DOM or SAX parser to
read your document manually and load values into tables.
- You can load/query the documents via the standard channels such as SQL,
PL/SQL, Java, C, etc.  You can also use FTP, WebDAV and HTTP through the new
Open Internet Protocol support in Oracle's Net Services layer.
- You can control the object/relational table structure through XML Schema
annotations (See pointers to documentation, below)
Con's:
- You can't use your own tables.  Oracle MUST create and maintain them "behind
the scenes".
- You MUST use an XML Schema for Oracle to do the shredding.

Another way of accomplishing this is by using the Oracle XML Developer's Kits,
specifically the XML SQL Utility.  Using this approach, you provide the XML in a
particular format, and Oracle will automatically insert the XML into a
object/relational table for you.  There are examples and a full explanation of
this approach in another AskTom question, here:

http://asktom.oracle.com/pls/asktom_admin/f?p=4950:8:::::F4950_P8_DISPLAYID:4980337843276
Pro's:
- Custom object/relational tables
- Very little programming
- Oracle does the loading for you automatically (and can generate the same
documents using the same facility, the XML SQL Utility)
Con's:
- XML Schemas and DTDs have nothing at all to do with this, so although it's not
a deviation from the standards... if you have data based on some XSD or DTD...
you will most likely have to transform the XML into Oracle's format using XSLT
(again, see the AskTom bookmark, above)
- There is no notion of XML storage with this approach.  You will have to use a
programming layer to generate XML if you want to expose your data as XML
documents later.
- There is no way to use the XML Repository with this approach (see below for
pointers to documentation about the XML Repository)

Lastly, the third approach is the most programming centric.  It would consist of
you parsing each XML document received into the database programmatically, and
then determining in your code where to insert the data.  You would be using the
XML Developer's Kits or the supplied packages with the database (DBMS_XML*)...
available languages are basically PL/SQL, Java or C (there are also some
JavaBeans created to automate repetitive tasks...).
Pro's:
- Greatest degree of flexibility
- Can code in any business logic into the loading process as you see fit
Con's:
- This approach requires a thorough understanding of the XML parser APIs.
- You're writing lots of code
- Oracle does nothing for you automatically
- No availability to the XML Repository

Given those approaches and your skillset/desires, it should be relatively easy
to determine your desired direction.  Instead of me writing drawn out examples,
I'll point you to documentation, OTN sites and/or AskTom questions that show
these concepts for you:

DOC REFERENCES:
XML DB Storage Architecture:
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96620/xdb01int.htm#1047568
XML DB Structured Mapping and Annotations:
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96620/xdb01int.htm#1047568
XML DB Repository:
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96620/whatsnew.htm#973015
XML Developer's Kits:
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96621/toc.htm
SQL XML Utility:
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96621/adx08xsu.htm#1000433
PL/SQL DOM Parser (see DBMS_XMLDOM):
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96612/toc.htm
Java Supplied Packages:
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96609/arjavpart2xdk.htm#435787
There are lots of examples based on the approach you take.  Make sure you search
through AskTom for XML and see those examples, there have been MANY posted.

Hope that helps!

_smd_

      Rating: 4
Problem
      creating table based on xml schema  February 07, 2004

Reviewer:  Raghu  from India
Hi Tom,
I have register a XML schema succefully in this way.
/*register the schema*/
BEGIN
 DBMS_XMLSCHEMA.REGISTERSCHEMA
   ('
http://www.w3.org/2001/XMLSchema/XMLResultMep2000.xsd'
   , BFILENAME('DIR_TEMP', 'XMLResultMep2000.xsd')
   ,TRUE         /*local schema*/
   ,TRUE         /*generate sql object types*/
   ,FALSE        /*do no generate java bean*/
   ,TRUE         /*generate tables*/
   ,TRUE        /*generate error if schema is invalid*/
   ,USER);       /*owner of the schema*/
END;
/

Now i try to create a table based on the schema using this .
CREATE TABLE result
(
   id  NUMBER,
   data  SYS.XMLType
  )
XMLTYPE COLUMN data
STORE AS OBJECT RELATIONAL
XMLSCHEMA "
http://www.w3.org/2001/XMLSchema/XMLResultMep2000.xsd
ELEMENT "MEPOUTPUT"
/

and i get this error.

CREATE TABLE result
*
ERROR at line 1:
ORA-00600: internal error code, arguments: [qmtcolcb_nomatch], [VarSequData],
[VarSequData], [], [], [], [], []

.But at the same time i can create the sample PurchaseOrder table based on
PurchaseOrder schema provided in Oracle documentation.

Can you please let me know the problem.

Background Information:
-----------------------
This schema is generated from DTD using XMLSPY 5.0. I decided to convert the DTD
to XML Schema based on suggestion provided by Sean as mentioned above. The DTD
is also given above as i have already asked question before.

Here is the XML schema .

<?xml version="1.0"?>
<xsd:schema xmlns:xsd="
">http://www.w3.org/2001/XMLSchema">
    <xsd:element name="VarSequData">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="VarSequData.Column" minOccurs="0"
axOccurs="unbounded"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="WaitPhase">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="Beginphase" minOccurs="0"/>
                <xsd:element ref="WaitVar" minOccurs="0" maxOccurs="unbounded"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="STDUPSequenceData.Column">
        <xsd:complexType>
            <xsd:simpleContent>
                <xsd:extension base="xsd:string">
                    <xsd:attribute name="Unit" type="xsd:string"
use="optional"/>
                    <xsd:attribute name="View" type="xsd:string"
use="optional"/>
                    <xsd:attribute name="Format" type="xsd:string"
use="optional"/>
                    <xsd:attribute name="DataType"
type="STDUPSequenceData.ColumnDataTypetype" use="optional" default="double"/>
                    <xsd:attribute name="id" type="xsd:string" use="required"/>
                    <xsd:attribute name="ViewValue" type="xsd:string"
use="optional"/>
                </xsd:extension>
            </xsd:simpleContent>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="General">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="General.Column" maxOccurs="unbounded"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="Beginphase">
        <xsd:complexType>
            <xsd:simpleContent>
                <xsd:extension base="xsd:string">
                    <xsd:attribute name="TimeBase" type="BeginphaseTimeBasetype"
use="optional"/>
                </xsd:extension>
            </xsd:simpleContent>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="STDUPSequenceData">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="STDUPSequenceData.Column" minOccurs="0"
maxOccurs="unbounded"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="VarSet">
        <xsd:complexType>
            <xsd:choice>
                <xsd:sequence>
                    <xsd:element ref="SetValue"/>
                    <xsd:element ref="SetState" minOccurs="0"/>
                    <xsd:element ref="SetTime" minOccurs="0"/>
                </xsd:sequence>
                <xsd:sequence>
                    <xsd:element ref="ExtSetFile"/>
                    <xsd:element ref="SetState" minOccurs="0"/>
                    <xsd:element ref="SetTime" minOccurs="0"/>
                </xsd:sequence>
                <xsd:sequence>
                    <xsd:element ref="VarName"/>
                    <xsd:element ref="SetValue"/>
                    <xsd:element ref="SetState" minOccurs="0"/>
                    <xsd:element ref="SetTime" minOccurs="0"/>
                </xsd:sequence>
            </xsd:choice>
            <xsd:attribute name="Typ" type="VarSetTyptype" use="optional"
default="STDARD"/>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="ValueTransform">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="FourPoint"/>
            </xsd:sequence>
            <xsd:attribute name="Typ" type="ValueTransformTyptype"
use="required"/>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="General.Column">
        <xsd:complexType>
            <xsd:simpleContent>
                <xsd:extension base="xsd:string">
                    <xsd:attribute name="id" type="xsd:string" use="required"/>
                </xsd:extension>
            </xsd:simpleContent>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="UsedData">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="UsedInpDatum" maxOccurs="unbounded"/>
            </xsd:sequence>
            <xsd:attribute name="UsedDataType" type="UsedDataUsedDataTypetype"
use="required"/>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="Sequence">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="Name"/>
                <xsd:element ref="ViewName" minOccurs="0"/>
                <xsd:choice minOccurs="0" maxOccurs="unbounded">
                    <xsd:element ref="MeasPoint" minOccurs="0"/>
                    <xsd:element ref="STDUP" minOccurs="0"/>
                </xsd:choice>
            </xsd:sequence>
            <xsd:attribute name="id" type="xsd:ID" use="optional"/>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="STDUP">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="Name" minOccurs="0"/>
                <xsd:element ref="ViewName" minOccurs="0"/>
                <xsd:element ref="STDUPSequenceData" minOccurs="0"/>
                <xsd:element ref="STDUPMeasData"/>
            </xsd:sequence>
            <xsd:attribute name="id" type="xsd:ID" use="required"/>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="MeasPointSteps">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="MeasPointStep" minOccurs="0"
maxOccurs="unbounded"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="StepGeneralMeas">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="StepGeneralMeas.Column" minOccurs="0"
maxOccurs="unbounded"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="VarMeasValues">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="VarMeasValue" minOccurs="0"
maxOccurs="unbounded"/>
            </xsd:sequence>
            <xsd:attribute name="MeasQuality"
type="VarMeasValuesMeasQualitytype" use="optional"/>
            <xsd:attribute name="MeasTyp" type="VarMeasValuesMeasTyptype"
use="optional"/>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="VarMeasValue">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="VarMeas" maxOccurs="unbounded"/>
            </xsd:sequence>
            <xsd:attribute name="Step" type="xsd:string" use="optional"/>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="UsedInpDatum">
        <xsd:complexType>
            <xsd:simpleContent>
                <xsd:extension base="xsd:string">
                    <xsd:attribute name="Typ" type="UsedInpDatumTyptype"
use="required"/>
                    <xsd:attribute name="LastTimeStamp" type="xsd:string"
use="optional"/>
                    <xsd:attribute name="Version" type="xsd:string"
use="optional"/>
                </xsd:extension>
            </xsd:simpleContent>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="UsedDevices">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="Device" minOccurs="0" maxOccurs="unbounded"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="ExtMeasFile">
        <xsd:complexType>
            <xsd:simpleContent>
                <xsd:extension base="xsd:string">
                    <xsd:attribute name="Typ" type="ExtMeasFileTyptype"
use="required"/>
                </xsd:extension>
            </xsd:simpleContent>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="KonfigFile">
        <xsd:complexType>
            <xsd:simpleContent>
                <xsd:extension base="xsd:string">
                    <xsd:attribute name="Typ" type="KonfigFileTyptype"
use="required"/>
                    <xsd:attribute name="Version" type="xsd:string"
use="optional"/>
                </xsd:extension>
            </xsd:simpleContent>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="MEPInputData">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="UsedDatas"/>
                <xsd:element ref="UsedDevices"/>
            </xsd:sequence>
            <xsd:attribute name="Model" type="xsd:string" use="optional"/>
            <xsd:attribute name="Version" type="xsd:string" use="optional"/>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="STDUPMeasData">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="STDUPMeasData.Column" minOccurs="0"
maxOccurs="unbounded"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="StepGeneral">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="StepGeneral.Column" minOccurs="0"
maxOccurs="unbounded"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="SetPhase">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="Beginphase" minOccurs="0"/>
                <xsd:element ref="SetVar" minOccurs="0" maxOccurs="unbounded"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="MeasPoint">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="Name" minOccurs="0"/>
                <xsd:element ref="ViewName" minOccurs="0"/>
                <xsd:element ref="MeasPointSteps" minOccurs="0"
maxOccurs="unbounded"/>
            </xsd:sequence>
            <xsd:attribute name="Typ" type="MeasPointTyptype" use="optional"/>
            <xsd:attribute name="id" type="xsd:ID" use="required"/>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="Device">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="ViewName" minOccurs="0"/>
                <xsd:element ref="Konfiguration" minOccurs="0"/>
            </xsd:sequence>
            <xsd:attribute name="DeviceID" type="xsd:ID" use="required"/>
            <xsd:attribute name="Version" type="xsd:string" use="optional"/>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="SetVar">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="Name"/>
                <xsd:element ref="ViewName" minOccurs="0"/>
                <xsd:element ref="VarDescription" minOccurs="0"/>
                <xsd:element ref="VarDevice" minOccurs="0"/>
                <xsd:element ref="VarDeviceReadBack" minOccurs="0"/>
                <xsd:element ref="DisplayFormat" minOccurs="0"/>
                <xsd:element ref="ValueTransform" minOccurs="0"/>
                <xsd:element ref="VarSequData" minOccurs="0"/>
                <xsd:element ref="VarSetValues"/>
            </xsd:sequence>
            <xsd:attribute name="Unit" type="xsd:string" use="optional"/>
            <xsd:attribute name="DataType" type="SetVarDataTypetype"
use="optional" default="double"/>
            <xsd:attribute name="id" type="xsd:string" use="required"/>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="MEPOutput">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="XMLInputOrder" minOccurs="0"/>
                <xsd:element ref="General" minOccurs="0"/>
                <xsd:element ref="MEPInputData" minOccurs="0"/>
                <xsd:element ref="MEPOutputData" minOccurs="0"/>
            </xsd:sequence>
            <xsd:attribute name="Version" type="xsd:string" use="optional"/>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="Konfiguration">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="KonfigFile" minOccurs="0"
maxOccurs="unbounded"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="UsedDatas">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="UsedData" minOccurs="0"
maxOccurs="unbounded"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="MEPOutputData">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="Sequences" minOccurs="0"/>
            </xsd:sequence>
            <xsd:attribute name="Model" type="xsd:string" use="optional"/>
            <xsd:attribute name="Version" type="xsd:string" use="optional"/>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="MeasPointStep">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="Name" minOccurs="0"/>
                <xsd:element ref="ViewName" minOccurs="0"/>
                <xsd:element ref="StatusSequence" minOccurs="0"/>
                <xsd:element ref="StepGeneral" minOccurs="0"/>
                <xsd:element ref="StepGeneralMeas" minOccurs="0"/>
                <xsd:element ref="SetPhase" minOccurs="0"/>
                <xsd:element ref="WaitPhase" minOccurs="0"/>
                <xsd:element ref="MeasPhase" minOccurs="0"/>
            </xsd:sequence>
            <xsd:attribute name="Nr" type="xsd:string" use="optional"/>
            <xsd:attribute name="id" type="xsd:string" use="required"/>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="VarSetValues">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="VarSetValue" minOccurs="0"
maxOccurs="unbounded"/>
            </xsd:sequence>
            <xsd:attribute name="MeasQuality" type="VarSetValuesMeasQualitytype"
use="optional"/>
            <xsd:attribute name="SetTyp" type="VarSetValuesSetTyptype"
use="optional"/>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="WaitVar">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="Name"/>
                <xsd:element ref="ViewName" minOccurs="0"/>
                <xsd:element ref="VarDescription" minOccurs="0"/>
                <xsd:element ref="VarDevice" minOccurs="0"/>
                <xsd:element ref="DisplayFormat" minOccurs="0"/>
                <xsd:element ref="ValueTransform" minOccurs="0"/>
                <xsd:element ref="VarSequData" minOccurs="0"/>
                <xsd:element ref="VarMeasValues"/>
            </xsd:sequence>
            <xsd:attribute name="Unit" type="xsd:string" use="optional"/>
            <xsd:attribute name="DataType" type="WaitVarDataTypetype"
use="optional" default="double"/>
            <xsd:attribute name="id" type="xsd:string" use="required"/>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="ExtSetFile">
        <xsd:complexType>
            <xsd:simpleContent>
                <xsd:extension base="xsd:string">
                    <xsd:attribute name="Typ" type="ExtSetFileTyptype"
use="optional"/>
                </xsd:extension>
            </xsd:simpleContent>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="STDUPMeasData.Column">
        <xsd:complexType>
            <xsd:simpleContent>
                <xsd:extension base="xsd:string">
                    <xsd:attribute name="Unit" type="xsd:string"
use="optional"/>
                    <xsd:attribute name="View" type="xsd:string"
use="optional"/>
                    <xsd:attribute name="Format" type="xsd:string"
use="optional"/>
                    <xsd:attribute name="DataType"
type="STDUPMeasData.ColumnDataTypetype" use="optional" default="double"/>
                    <xsd:attribute name="id" type="xsd:string" use="required"/>
                    <xsd:attribute name="ViewValue" type="xsd:string"
use="optional"/>
                </xsd:extension>
            </xsd:simpleContent>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="StepGeneralMeas.Column">
        <xsd:complexType>
            <xsd:simpleContent>
                <xsd:extension base="xsd:string">
                    <xsd:attribute name="Unit" type="xsd:string"
use="optional"/>
                    <xsd:attribute name="View" type="xsd:string"
use="optional"/>
                    <xsd:attribute name="Format" type="xsd:string"
use="optional"/>
                    <xsd:attribute name="DataType"
type="StepGeneralMeas.ColumnDataTypetype" use="optional" default="double"/>
                    <xsd:attribute name="id" type="xsd:string" use="required"/>
                    <xsd:attribute name="ViewValue" type="xsd:string"
use="optional"/>
                </xsd:extension>
            </xsd:simpleContent>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="StepGeneral.Column">
        <xsd:complexType>
            <xsd:simpleContent>
                <xsd:extension base="xsd:string">
                    <xsd:attribute name="Unit" type="xsd:string"
use="optional"/>
                    <xsd:attribute name="View" type="xsd:string"
use="optional"/>
                    <xsd:attribute name="Format" type="xsd:string"
use="optional"/>
                    <xsd:attribute name="DataType"
type="StepGeneral.ColumnDataTypetype" use="optional" default="double"/>
                    <xsd:attribute name="id" type="xsd:string" use="required"/>
                    <xsd:attribute name="ViewValue" type="xsd:string"
use="optional"/>
                </xsd:extension>
            </xsd:simpleContent>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="FourPoint">
        <xsd:complexType>
            <xsd:simpleContent>
                <xsd:extension base="xsd:string">
                    <xsd:attribute name="Y0" type="xsd:string" use="required"/>
                    <xsd:attribute name="X0" type="xsd:string" use="required"/>
                    <xsd:attribute name="Y1" type="xsd:string" use="required"/>
                    <xsd:attribute name="X1" type="xsd:string" use="required"/>
                </xsd:extension>
            </xsd:simpleContent>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="XMLInputOrder">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="OrderID"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="MeasVar">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="Name"/>
                <xsd:element ref="ViewName" minOccurs="0"/>
                <xsd:element ref="VarDescription" minOccurs="0"/>
                <xsd:element ref="VarDevice" minOccurs="0"/>
                <xsd:element ref="DisplayFormat" minOccurs="0"/>
                <xsd:element ref="ValueTransform" minOccurs="0"/>
                <xsd:element ref="VarSequData" minOccurs="0"/>
                <xsd:element ref="VarMeasValues"/>
            </xsd:sequence>
            <xsd:attribute name="Unit" type="xsd:string" use="optional"/>
            <xsd:attribute name="DataType" type="MeasVarDataTypetype"
use="optional" default="double"/>
            <xsd:attribute name="id" type="xsd:string" use="required"/>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="MeasPhase">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="Beginphase" minOccurs="0"/>
                <xsd:element ref="MeasVar" minOccurs="0" maxOccurs="unbounded"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="Sequences">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="Sequence" minOccurs="0"
maxOccurs="unbounded"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="VarSetValue">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="VarSet"/>
                <xsd:element ref="VarMeas" minOccurs="0" maxOccurs="unbounded"/>
            </xsd:sequence>
            <xsd:attribute name="Step" type="xsd:string" use="optional"/>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="VarSequData.Column">
        <xsd:complexType>
            <xsd:simpleContent>
                <xsd:extension base="xsd:string">
                    <xsd:attribute name="Unit" type="xsd:string"
use="optional"/>
                    <xsd:attribute name="View" type="xsd:string"
use="optional"/>
                    <xsd:attribute name="Format" type="xsd:string"
use="optional"/>
                    <xsd:attribute name="DataType"
type="VarSequData.ColumnDataTypetype" use="optional" default="double"/>
                    <xsd:attribute name="id" type="xsd:string" use="required"/>
                    <xsd:attribute name="ViewValue" type="xsd:string"
use="optional"/>
                </xsd:extension>
            </xsd:simpleContent>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="VarMeas">
        <xsd:complexType>
            <xsd:choice minOccurs="0" maxOccurs="unbounded">
                <xsd:sequence>
                    <xsd:element ref="MeasValue"/>
                    <xsd:element ref="MeasState" minOccurs="0"/>
                    <xsd:element ref="MeasTime" minOccurs="0"/>
                </xsd:sequence>
                <xsd:sequence>
                    <xsd:element ref="ExtMeasFile"/>
                    <xsd:element ref="MeasState" minOccurs="0"/>
                    <xsd:element ref="MeasTime" minOccurs="0"/>
 **** MESSAGE TRUNCATED ****

          Followup:

for internal errors like that, please -- please -- open a tar with support.
they equate to "bug"

      Rating: 3
Bug with Oracle
      XMLDB  February 09, 2004
Reviewer:  Raghu

Thanks Tom,

you are right. It's bug with oracle XMLDB.
If an Element Name inside the xmlschema contains dot then you get such error .
I removed all the dots from the element name from the schema and i was able to
register the schema and create table based on the schema.
Another option incase you do not want to remove the dots is to use annotations
for the element name using xdb:SQLName. and the value provided with xdb:SQLName
should not contain dots.

February 07, 2004 Rajendran -- Thanks for the question regarding
      "RE: [Fwd: Ask Tom Submission] Pragma Exception_Init question", version

      You Asked
Hi
I was looking at user defined exceptions and assigning error
code/numbers to exception using PRAGMA EXCEPTION_INIT. for example,

DECLARE
invalidSKU EXCEPTION;
EXCEPTION_INIT (invalidSKU, -20001);
BEGIN
...
END;

Now is there a way to associate an error message with an user defined
error code, in addition to the defining exception ? The idea being that
I should be able to capture the error message using SQLERRM. Also I will
be able define various user exceptions,
an valid error code and an a generic
error message, all at one place maybe one package.

Thanks,
raj.

      and we said...
You are sort of mixing 2 concepts up here.

One is the ability to define a user defined exception (invalidSKU above) and the
other is to take an existing Oracle error code (eg: -20001 in your example) and
map that to some user defined exception.

We'll be able to achieve your goal (define exceptions and have known error
messages/codes associated with them) easily though.  You can do this in one
package as well.

First, lets look at what you did above.  Here is a full example:

SQL> declare
  2     invalidSKU EXCEPTION;
  3     pragma EXCEPTION_INIT (invalidSKU, -20001);
  4  BEGIN
  5          raise_application_error( -20001, 'Some Error Message' );
  6  exception
  7          when invalidSKU then
  8                  dbms_output.put_line( 'We caught the invalid SKU exception'
);
  9                  dbms_output.put_line( 'sqlerrm = ' || sqlerrm );
 10                  dbms_output.put_line( 'sqlcode = ' || sqlcode );
 11  end;
 12  /
We caught the invalid SKU exception
sqlerrm = ORA-20001: Some Error Message
sqlcode = -20001

PL/SQL procedure successfully completed.

You made it so that when the SQL error "-20001" is raised, PL/SQL will convert
that into the named exception invalidSKU.  The error message that accompanied
the -20001 (generated by the raise_application_error) is there in the SQLERRM
builtin function.  While we can make it so that when invalidSKU is raised -- the
sqlcode is set to -20001 -- we have no control over the associated error text
(it'll be NULL).

What pragma exception_init is designed for is to convert an Oracle Error code
into a named exception to make programing easier.  Instead of having a big 'when
others' like:

when others then
   if ( sqlcode = some_value ) then
       ...
   elsif ( sqlcode = some_other_value ) then
       ...
   elsif ....
   ....
   end if;

the pragma allows you to have:

when Some_Error then
when Some_Other_Error then
....

Now, how can you define a package that has a bunch of user defined exceptions
with corresponding error codes and messages and how would another programmer
'raise' such an exception?  It might look like this:

SQL> create or replace package exception_pkg
  2  as
  3          invalid_sku  constant number default -20001;
  4          invalid_sku_exception   exception;
  5          pragma exception_init( invalid_sku_exception, -20001 );
  6
  7          another_error constant number default -20002;
  8          another_error_exception exception;
  9          pragma exception_init( another_error_exception, -20002 );
 10
 11          procedure throw( p_exception in number, p_extra_msg in varchar2
default NULL );
 12
 13  end;
 14  /

Package created.

This is my package spec for all of my exceptions.  Here I will assign a numeric
code in the range of -20000 to -20999 (these are the valid sqlcodes you may
control, all others belong to Oracle itself) to each named exception.  I also
define an EXCEPTION for each code and then use pragma exception_init to map the
named exception to the error code.  In the above example, I have defined 2
exceptions.  I have directed PL/SQL to map the error code -20001 to the named
exception "invalid_sku_exception".

This spec also defines a "throw" procedure.  This is what the developers will
call to cause an exception to be raised.  This procedure will take one of the
constants as input and will call the internal routine "raise_application_error"
to raise an exception with a specific error code and message.  Inside the
package body, we will see how we get error messages (text) mapped to specific
error codes.  Additionally, we will see that the parameter p_extra_msg that can
be supplied to the throw procedure will be appended to the error message as
well.

Now, the body of exception_pkg.  It might look like this:

SQL> create or replace package body exception_pkg
  2  as
  3
  4  -- We will create an array of error messages.  Since the PLSQL table
  5  -- type is a sparse array, we can put into it indices like -20001,
  6  -- -20002 and so on and leave the rest of the table blank.  The
  7  -- 'elaboration' code at the bottom of this package will be executed
  8  -- once per session when the package is first referenced.    This
elaboration
  9  -- code will initialize the plsql table of error messages for us
 10
 11  type myarray is table of varchar2(255) index by binary_integer;
 12  err_msgs  myArray;
 13
 14
 15  -- throw is what is used to raise user defined exceptions, those
 16  -- defined in the exception_pkg specification.
 17  --
 18  -- p_exception should be one of the constants found in the specification
 19  -- p_extra_msg is optional.  if supplied, it will be appended to the error
message
 20  --
 21  -- this procedure uses another routine, who_called_me (supplied
separately).
 22  -- who_called_me gets the callers name and line number.  this information
 23  -- is put into the error message as well so we can figure out who threw
 24  -- the exception in the first place easily from the error message.
 25
 26  procedure throw( p_exception in number, p_extra_msg in varchar2 default
NULL )
 27  is
 28       l_owner    varchar2(30);
 29       l_name        varchar2(30);
 30       l_type        varchar2(30);
 31       l_line        number;
 32       l_exception number;
 33  begin
 34       who_called_me( l_owner, l_name, l_line, l_type );
 35       raise_application_error( p_exception      ,
 36                   'Exception at ' || l_type || ' ' ||
 37                    l_owner || '.' || l_name || '(' || l_line || ') ' ||
 38                    err_msgs(p_exception       ) || ' ' || p_extra_msg );
 39  exception
 40       -- we will get this when we have an invalid exception code, one
 41       -- that was not set in the err_msgs array below.  The plsql table
 42       -- access will raise the NO-DATA-FOUND exception.  We'll catch it,
 43       -- verify the exception code is in the valid range for
raise_application_error
 44       -- (if not, set to -20000) and then raise the exception with the
message
 45       -- "unknown error"
 46
 47       when NO_DATA_FOUND then
 48           if ( p_exception between -20000 and -20999 ) then
 49           l_exception := p_exception;
 50           else
 51           l_exception := -20000;
 52           end if;
 53
 54           raise_application_error( l_exception     ,
 55                   'Exception at ' || l_type || ' ' ||
 56                    l_owner || '.' || l_name || '(' || l_line || ') ' ||
 57                    '**UNKNOWN ERROR**' || ' ' || p_extra_msg );
 58
 59  end;
 60
 61  begin
 62       -- Our elaboration code.  For each constant in the package spec we
should
 63       -- have an entry in this plsql table.
 64       -- this code is run once per session when the package is first
'touched'
 65
 66       err_msgs( invalid_sku ) := 'Invalid SKU Code';
 67       err_msgs( another_error ) := 'Another Error Message...';
 68  end;
 69  /

Package body created.

The procedure who_called_me is attached (as well as the source code for this
package).  You could just embed who_called_me right into this package body but
since I use it in so many places, I prefer to just let it standalone.

Now, a developer will throw and catch exceptions like this:

SQL> create or replace procedure demo1
  2  as
  3  begin
  4       exception_pkg.throw( exception_pkg.invalid_sku, '<extra text from
runtime>' );
  5  exception
  6       when exception_pkg.invalid_sku_exception then
  7           dbms_output.put_line( 'Caught the error' );
  8           dbms_output.put_line( 'sqlerrm = ' || sqlerrm );
  9           dbms_output.put_line( 'sqlcode = ' || sqlcode );
 10  end;
 11  /

Procedure created.

They will use the exception_pkg.throw procedure to raise an exception (instead
of using RAISE or raise_application_error directly).  They can also pass in
extra text, for example with the invalid SKU error -- they might pass in the SKU
that was in fact invalid for the end user to see.

This routine also shows how they would catch these errors.  They would use the
named exceptions in the exception_pkg as I have above.  Specifically:

  6       when exception_pkg.invalid_sku_exception then

is how they would refer to the exception.  When we run this procedure in SQLPLUS
we can see that we achieved the desired results:

SQL> exec demo1
Caught the error
sqlerrm = ORA-20001: Exception at PROCEDURE TKYTE.DEMO1(4) Invalid SKU Code
<extra text from runtime>
sqlcode = -20001

PL/SQL procedure successfully completed.

We

o Caught the error with line 6
o See the error message "Invalid SKU Code".  It has some runtime info appended
and at the front it has the actual name and line number of the procedure that
threw the exception.
o it has the error code we defined for this error....

The following demonstrates what happens when this package is used with an
undefined, out of range error code:

SQL> create or replace procedure demo2
  2  as
  3  begin
  4       exception_pkg.throw( 123, '<This is an unknown error>' );
  5  exception
  6       when Others then
  7           dbms_output.put_line( 'Caught the error in OTHERS since this error
was not mapped' );
  8           dbms_output.put_line( 'sqlerrm = ' || sqlerrm );
  9           dbms_output.put_line( 'sqlcode = ' || sqlcode );
 10  end;
 11  /

Procedure created.

SQL> exec demo2
Caught the error in OTHERS since this error was not mapped
sqlerrm = ORA-20000: Exception at PROCEDURE TKYTE.DEMO2(4) **UNKNOWN ERROR**
<This is an unknown error>
sqlcode = -20000

PL/SQL procedure successfully completed.

Since "123" is not a valid error code -- this raises the OTHERS exception
(cannot be mapped to a named exception) and the error message reflects this

Review &
      Followup

      Rating: 4
Good
      Stuff  February 14, 2002
Reviewer:  roland
      Daane  from Netherlands
This is really interesting stuff.

I'm using this method of work by inderting the data for the exceptions into an
Oracle table and from that creating the error package.

I'm really interested in the source for the who_called_me procedure!

          Followup:

See

http://osi.oracle.com/~tkyte/who_called_me/index.html
for that.

      Rating:
      5
  August 20, 2003
Reviewer:
      A reader

      Rating: 5
Excellent
      Tom  January 26, 2004
Reviewer:  Sami

      Rating: 2
Who am i --
      Packages  January 31, 2004
Reviewer:  Marcio
       from Brazil
Tom, I still don't get how to work with packages and who_called_me, look this:

ops$t_mp00@MRP817> create or replace package pkg
  2  is
  3     procedure p;
  4     procedure p1;
  5  end;
  6  /

Package created.

ops$t_mp00@MRP817> create or replace package body pkg
  2  is
  3     procedure p
  4     as
  5     begin
  6             p1;
  7     end;
  8     procedure p1
  9     as
 10      l_owner        varchar2(30);
 11      l_name      varchar2(30);
 12      l_lineno    number;
 13      l_type      varchar2(30);
 14     begin
 15       who_called_me( l_owner, l_name, l_lineno, l_type );
 16       dbms_output.put_line( l_owner || '.' || l_name);
 17     end;
 18  end;
 19  /

Package body created.

ops$t_mp00@MRP817> exec pkg.p
OPS$T_MP00.PKG

How to log it into log_error, just package name? I mean the real name of
procedure inside package can I see? Or any other suggestion for this kind of
instrumentation am trying implement on?

          Followup:

you get the schema object name (the package name) and the LINENUMBER.

there could be 500 procedure "p1's" in that package -- the package name and line
number are what you get here. and that is all you can get.  we do not track
things down to the procedure/function name inside of a package.

      Rating: 5
another
      light+  February 05, 2004
Reviewer:  Marcio
       from Brazil
Suppose you have an entire batch system, setting up by parameters -- this system
catch information from tables over dblink and populates some another tables and
them write flat files as long as day range of parameter.

1) What would be a effective handling exception on this context?

2) Is it a good idea have a trigger before insert on log_error table to send up
e-mails with body error caught to warning operators people that something is
going wrong?

3) Is the exception_pkg code public?

thank you,
Marcio

          Followup:

1) don't know, insufficient data, the programmers should "naturally know" as
writing PLSQL code is no different then writing C, Java, VB -- just easier :)

This is a programming decision.  I can only tell you the wrong ways to avoid --
such as "using when others" and returning error codes instead of propagating the
exceptions out.

2) see #1.  I would find it rather annoying myself but it is up to you.

3) everything on this site is for you to cut and paste, copy and use.

      Rating: 4
about question
      #2  February 07, 2004
Reviewer:  Marcio
       from Brazil
2) But, how will the operator know if something was going wrong? I'm thinking
mail him just where system goes down, not for any exception.

          Followup:

there are excellent system management tools on the market from hundreds of
vendors (including Oracle with OEM) that will do that.

February 07, 2004 Mandar -- Thanks for the question regarding "Help
      with v$statname and v$sysstat", version 8.1.6

      You Asked
Tom,

  Can you please provide info on how can I find the
  full table scan and index table scan activities
  in the database using v$statname and v$sysstat?

  Do I need to set TIMED_STATISTICS=TRUE before running
  queries against v$sysstat?

  Also, I look at v$sqlarea for most offensive sqls, is
  there a better way to find the most offensive SQLs?

Thanks in advance,

 -Mandar.

      and we said...
  1* select * from v$sysstat where lower(name) like '%table%'
ops$tkyte@ORA8I.WORLD> /

STATISTIC# NAME                                CLASS      VALUE
---------- ------------------------------ ---------- ----------
        48 DBWR transaction table writes           8        614
       144 transaction tables consistent         128          0
           reads - undo records applied

       145 transaction tables consistent         128          0
           read rollbacks

       158 table scans (short tables)             64    1676475
       159 table scans (long tables)              64       3942
       160 table scans (rowid ranges)             64          0
       161 table scans (cache partitions)         64       7153
       162 table scans (direct read)              64          0
       163 table scan rows gotten                 64 1512068438
       164 table scan blocks gotten               64   24283078
       165 table fetch by rowid                   64   22813564
       166 table fetch continued row              64     309293

  1* select * from v$sysstat where lower(name) like '%full%'
ops$tkyte@ORA8I.WORLD> /

STATISTIC# NAME                                CLASS      VALUE
---------- ------------------------------ ---------- ----------
        76 commit cleanouts successfully           8     268320
           completed

       174 index fast full scans (full)           64      10981
       175 index fast full scans (rowid r         64          0
           anges)

       176 index fast full scans (direct          64          0
           read)

will get that.   You do not need timed_stats however, i would recommend you just
leave it on, it is so useful.

I use statspack to find the "top offenders" as far as SQL goes.  Goto
www.oracle.com/oramag and search for statspack for a couple of articles on this
utility.
Review
      & Followup

      Rating: 5
Full Table
      Scans  July 10, 2003
Reviewer:  Jaideep
       from Grand Rapids, MI , USA
Wonderful answer. I was not looking for anything more than this !

      Rating:
      2
Cool  February 07, 2004
Reviewer:
      Khalid  from Yemen
I want to now what is the sql statements that do a full table scan in the
database

          Followup:

if you have 9i, easy.  just look at v$sql_plan

if you do not, then, start thinking about all of the other things you might be
missing in the older releases :)

February 07, 2004 play -- Thanks for the question regarding
      "index_ffs", version 8.1.7

      You Asked
Tom

I have observed that in a few queries of mine, I am retrieving columns which are
part of the composite primary key, or the whole primary key.

then explain shows that this is doing a index scan and a table scan by rowid.

Now if I force the optimizer to use index_ffs, will it be more economical
performance wise.

my optimizer right now is rbo, I will get the tables analyzed to do the above.

Want your suggestions, and examples.

Further, when I do a select empno from emp where rownum<2, it will return only
one row to be

1.Will it do a full table scan
2.Will it make use of the index on empno
3.Will it return the row after it reads the first two rows, or does it read the
whole table.
3.If for my key value there are multiple rows, and I want to return only one,
what is the way apart from where rownum<2

Thanks

      and we said...
Well, as with everything in life, it depends.

An INDEX_FFS (index fast full scan) will read the ENTIRE index structure.  If
you have a 2 gig index on a 200 gig table and you are only retrieving 20 rows
from it -- reading the index as a data structure and NOT full scanning it will
be faster.

an index ffs reads the entire index structure as if it were a table.  You would
only want to use it when you are getting MOST of the rows -- not just a few.

As for the rownum <2, the plan will say "full scan emp" but it will include a
"count stopkey".  Meaning it will start full scanning emp but quit the moment
the rownum is exceeded.

It MIGHT use the index on emp, but maybe not ( with rownum < 2, it'll be easier
to full scan the table rather then process an index structure).

It will not read the whole table.

as for "if for my key value there are >1 rows", you would have to give me some
method to "identify" the row you want.  If you don't care which row you get,
rownum = 1 is as good as anything else.
Review &
      Followup

      Rating: 5
Index_FFS and
      Full Table  February 07, 2004
Reviewer:
      A reader
Hi Tom,

Do mean that Index_FFS can be slower than Full Table Scan? Why not faster given
that Index_FFS reads less columns?

Could you give a solid example?

Thanks.

          Followup:

that is not what I said at all.

(but yes, it can be true for an index can in fact be larger than the table
itself :)

In generaly, an INDEX_FFS will be faster than a TABLE FULL SCAN since in general
the indexes are smaller than the tables they are on.

However, my point above was that an INDEX FULL SCAN or INDEX RANGE SCAN might be
better (even though it uses single block IO) for certain classes of queries.

      Rating: 5
Index
      FFS  February 07, 2004
Reviewer:
      A reader
Hi Tom,

I have a query which joins a table multiple times. And I noticed that INDEX_FFS
is being used on that table for all the joins. Will the index actually be
scanned only once or multiple times?

          Followup:

i don't know.  I cannot see your query plan from over here.  My eyes are pretty
good but they can read a screen more than a couple of feet away typically....

:)

(that means, the answer is

a) yes
b) no
c) maybe
d) all of the above <<<====

)

February 07, 2004 Mark -- Thanks for the question regarding
      "Selecting random records from a table", version 9.0

      You Asked
I would like to select out n random records from a table using a query that has
other criteria e.g.

select to_char(time_start,'yyyy-mm-dd hh24:mi:ss'),a.*
from sitesession2 a
where
fptoken in ('7Y','7Z') and
time_start>=to_date('2004-01-28','yyyy-mm-dd') and
time_start<to_date('2004-01-29','yyyy-mm-dd') and
test_condition_key between 0 and 24

      and we said...
What I usually do for that is:

ops$tkyte@ORA9IR2> l
  1  select empno,ename
  2  from ( select * from emp where deptno = 20 order by dbms_random.random)
  3* where rownum <= 3
ops$tkyte@ORA9IR2> set echo on
ops$tkyte@ORA9IR2> @test
ops$tkyte@ORA9IR2> select empno,ename
  2  from ( select * from emp where deptno = 20 order by dbms_random.random)
  3  where rownum <= 3
  4  /

     EMPNO ENAME
---------- ----------
      7788 SCOTT
      7876 ADAMS
      7369 SMITH

ops$tkyte@ORA9IR2> /

     EMPNO ENAME
---------- ----------
      7369 SMITH
      7876 ADAMS
      7566 JONES

ops$tkyte@ORA9IR2> /

     EMPNO ENAME
---------- ----------
      7902 FORD
      7369 SMITH
      7876 ADAMS

ops$tkyte@ORA9IR2> /

     EMPNO ENAME
---------- ----------
      7876 ADAMS
      7788 SCOTT
      7902 FORD

works reasonably well if the query itself returns a reasonably small number of
records in the first place.  else the cost of dbms_random.random and ordering by
it on lots and lots of records gets to be "high".

February 07, 2004 sudarsanam -- Thanks for the question regarding
      "Default sorting order", version 8.1.7

      You Asked
Hi Tom,

My sincere thanks for the great service you are doing for the DBA world.

I have a table with 10 million records.  I inserted the same in one shot.  The
table is more of a look up type ie. it never undergoes any Update / Deletes

Will a simple select return the rows in the order i inserted during the
following cases.

1 -  Mine is the only session which inserted the records and no other users are
connected and  Data has been inserted in one Big initial Extent.

2.   Other Sessions are active and i did the insert

Colud you pl. provide an example if your answer is that the sort order is never
guarenteed or the rows returned by ORACLE is in ther order it finds.

Thanks and Regards
swamy

      and we said...
1) nope.

2) nope.

never -- never -- never is the order of rows returned assured to be in any sort
of order.  NEVER.

and if you are putting 10,000,000 records into a single big extent -- well,
thats not going to be the wisest thing you've done either!

this is against 9ir2 (cause it is easiest there):

ops$tkyte@ORA9IR2> create table t
  2  ( x int,
  3    a char(2000),
  4    b char(2000),
  5    c char(2000),
  6    d varchar2(2000)
  7  ) tablespace tools;

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t
  2  select rownum, 'x', 'y', case when mod(rownum,10) = 5 then 'z' end, case
when mod(rownum,10) in ( 4,5) then 'x' end
  3  from all_users
  4  /

44 rows created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select x from t;

         X
----------
         5
         1
         2
         3
         4
        11
        12
        13
         6
         7
         8
         9
        10
....

definitely not in any sort of order.

I don't really care if you show over and over that "it appears to work", you
cannot as a professional developer rely on it working -- there is no such thing
as "order of insertion" in a relational database -- never.
Review &
      Followup

      Rating: 4
Default Sorting
      Order  February 08, 2004
Reviewer:
      sudarsanam  from CT, Stamford - USA
Thanks a lot TOM.  Your answer is to the point and clear.

Regards
swamy

February 07, 2004 Bob -- Thanks for the question regarding "XML
      Error occurs in procedure when XML data contains ampersand", version
      9.2.0.3

      You Asked
Tom,
we've been getting an error in the parser for the xmlParser.parseclob method. Is
there something we need to do to the data we are looking at in case it contains
an ampersand? The data element is something like

<FirmName>Jones & Smith</FirmName>

exec cdr.parse_xml3;
error occurredORA-20100: Error occurred while parsing: Missing entity
'AMP'.<br>

and the procedure is started like this(just the beginning)

CREATE OR REPLACE PROCEDURE cdr.PARSE_XML3 as
begin
DECLARE
  v_parser  xmlParser.Parser;
  v_doc     xmldom.DOMDocument;
  v_nl      xmldom.DOMNodeList;
  v_n       xmldom.DOMNode;

l_xml_clob clob;
rows number;

  TYPE tab_type IS TABLE OF cdr.sp_ticket2%ROWTYPE;
  t_tab  tab_type := tab_type();

BEGIN

select sp_xml_tkt_clob
  into l_xml_clob
from cdr.sp_xml_tkt
     where sp_xml_tkt_status = 'Q' ;

 dbms_output.put_line('read sp record');

  -- Create a parser.
  v_parser := xmlParser.newParser;
 dbms_output.put_line('create new parser');

  -- Parse the document and create a new DOM document. Here is where it chokes.

  xmlParser.parseclob(v_parser, l_xml_clob);
  v_doc := xmlParser.getDocument(v_parser);
.............

Do we have to declare something else or set something up, or does the data have
to not contain the ampersand? I am not that familiar with the XML standard but
am learning.

Bob

      and we said...
that is not valid XML.  XML cannot contain & and other characters in it.

Who ever is generating that "XML" isn't generating XML.

see

http://www.itworld.com/nl/xml_prac/07042002/

February 08, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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.

      Rating: 5
how can you get
      user_dump_dest directory value from database?  January 27,
      2004
Reviewer:  A reader
SQL> select name, value from v$parameter
  2  where name = 'user_dump_dest';

user_dump_dest
?/rdbms/log

I understand the "?" perhaps stands for Oracle home. How
can I get the absolute path of this parameter
from sql?

Thanx!

          Followup:

You cannot -- it is known in the server processes itself.  You have to be on
"the server" and looking at the environment to get that information.

      Rating:
      5
Thanx!  January 28, 2004
Reviewer:
      A reader

      Rating: 3
alter session
      set sql_trace = false; doesn't release the trace file..
        February 06, 2004
Reviewer:  Justin
       from PA
Tom,

Could you give a list of steps you perform to issue several benchmarks of some
code using sql_trace and tkprof in the same session?

For example: I am running a packaged function -

var x clob

BEGIN
:x := test_pkg.VMGETVENDORLIST;
END;
/

where VMGETVENDORLIST is returning XML from the DB.

When I issue sql_trace=true and run that command, I get the trace file I want,
and I tkprof it, and check out the report. *BUT*, if I issue sql_trace=false,
and try to delete the trace file for a second run (so that the run results don't
"stack up" in the trace file) Windose doesn't let me delete the file because it
sees it as still being open. I have even allowed some time to pass thinking that
it would be released, but it is not. It isn't until I terminate my connection
that the trace file "goes away" on it's own, and I reconnect and do the steps
over to "start from scratch" so my trace file is clean.

So.. . to restate the question, do you delete your trace file for a second run
of a benchmark, or do you let the results "stack up" in the trace file or ...?

Thanks much

          Followup:

you have to exit the session and start a new one.  we keep the trace file open
for that session.

use separate sessions.  I always exit sqlplus to get the trace file closed (else
the STAT records with really important data might not be there!)

so, i

set sql trace on
do something
exit sqlplus, the application, whatever
analyze the results.

      Rating:
      5
v$session.username  February 07, 2004

Reviewer:  A reader
In what situations will v$session.username will
appear in the trace file

          Followup:

i don't have an exhaustive list.

      Rating:
      4
v$session.username  February 07, 2004

Reviewer:  A reader
Can you give me just one situation

          Followup:

a dump for a deadlock has usernames in it.

...
    ----------------------------------------
    SO: 0x532b8850, type: 4, owner: 0x5328ff88, flag: INIT/-/-/0x00
    (session) trans: 0x53afecd0, creator: 0x5328ff88, flag: (10100041) USR/-
BSY/-/-/-/-/-
              DID: 0001-000B-00000050, short-term DID: 0000-0000-00000000
              txn branch: (nil)
              oct: 2, prv: 0, sql: 0x57da9188, psql: 0x57da9188, user:
76/OPS$TKYTE
    O/S info: use
.....

      Rating:
      5
Thanks  February 08, 2004
Reviewer:
      A reader

February 08, 2004 Ambarish -- Thanks for the question regarding
      "updating nested tables and scalar columns in a single update", version
      8.1.5

      You Asked
Hi Tom,

Thanks for your solution.

How do i update a nested column and a scalar column in a single
update ?

Consider a nested table trynested(a int primary key, b varchar2
(10), c myarray)

where myarray is table of varchar(2).

lets say for a = 1, i have b = 'test' and c =
myarray('1','2','3')

Is it possible to update b to 'test1' and the third element of c
which is '3' to '4' ? I could do this in a two piece update with
transaction semantics. Can i do this in a singular update ?

Thanks
Chitnis

      and we said...
First of all and VERY importantly -- there is simply no such
thing as the "third element" of a nested table.  A varray, yes,
a nested table -- no.  You have a nested table element with the
value '3', you would like that to be '4'.

You would have to update the entire collection, you would not be
able to modify an individual "row" within the collection.

You can definitely do this in two updates with "transaction
semantics".

Here are two ways.  One straightforward, the other infinitely
more obscure:

tkyte@TKYTE816> create type myArray
  2  as table of varchar2(2)
  3  /

Type created.

tkyte@TKYTE816>
tkyte@TKYTE816> create table t
  2  ( a int, b varchar2(10), c myArray )
  3  nested table c store as c_tab
  4  /

Table created.

tkyte@TKYTE816>
tkyte@TKYTE816> insert into t
  2  values
  3  ( 1, 'test', myArray( '1','2','3') );

1 row created.

tkyte@TKYTE816> commit;

Commit complete.

tkyte@TKYTE816> select * from t;

         A B          C
---------- ---------- -------------------------
         1 test       MYARRAY('1', '2', '3')

tkyte@TKYTE816> update t
  2     set b = 'test1',
  3         c = myArray( '1', '2', '4' )
  4   where a = 1;

1 row updated.

tkyte@TKYTE816> select * from t;

         A B          C
---------- ---------- -------------------------
         1 test1      MYARRAY('1', '2', '4')

tkyte@TKYTE816> rollback;
Rollback complete.

tkyte@TKYTE816>
tkyte@TKYTE816> update t
  2     set b = 'test1',
  3         c =
  4         ( select cast
  5                 ( multiset
  6                    ( select decode(column_value,
  7                                    '3','4',
  8                                     column_value)
  9                        from TABLE( select t2.c
 10                                      from t t2
 11                                     where a=t.a )
 12                      ) as myArray
 13                  ) from dual
 14         )
 15       where a = 1
 16  /

1 row updated.

tkyte@TKYTE816>
tkyte@TKYTE816> select * from t;

         A B          C
---------- ---------- -------------------------
         1 test1      MYARRAY('1', '2', '4')

tkyte@TKYTE816>

Review &
      Followup

      Rating: 4
It was of
      course   April 01, 2003
Reviewer:
      A reader
thanks - very useful hint - but i shall stick to update the table with new
values of the array!

      Rating:
      5
  February 07, 2004
Reviewer:  Zlatko
      Sirotic  from Croatia
1. Varray of scalar values

CREATE TYPE array_of_scalars_t AS ARRAY (3) OF NUMBER (2)
/
CREATE TABLE table1 (
   id     NUMBER (4)         NOT NULL PRIMARY KEY,
   array1 array_of_scalars_t NOT NULL)
/
INSERT INTO table1 VALUES (10, array_of_scalars_t (11, 12, 13));
INSERT INTO table1 VALUES (20, array_of_scalars_t (21, 22, 23));
INSERT INTO table1 VALUES (30, array_of_scalars_t (31, 32, 33));
INSERT INTO table1 VALUES (40, array_of_scalars_t (41, 42, 43));
INSERT INTO table1 VALUES (50, array_of_scalars_t (51, 52, 53));

-- We want to do as followed:
/*
UPDATE table1
   SET array1 (2) = 99 -- NOT SUPPORTED!
 WHERE id > 30;
*/

UPDATE table1 outer_table
   SET array1 =
       CAST (MULTISET (SELECT DECODE (ROWNUM, 2, 99, COLUMN_VALUE)
                         FROM TABLE (SELECT array1
                                       FROM table1
                                      WHERE ROWID = outer_table.ROWID)
                      )
             AS array_of_scalars_t
            )
 WHERE id > 30
/

2. Varray of objects

CREATE TYPE pair_t AS OBJECT (
   a NUMBER (2),
   b NUMBER (2))
/
CREATE TYPE array_of_pairs_t AS ARRAY (3) OF pair_t
/
CREATE TABLE table2 (
   id     NUMBER (4)       NOT NULL PRIMARY KEY,
   array2 array_of_pairs_t NOT NULL)
/
INSERT INTO table2 VALUES
  (10, array_of_pairs_t (pair_t (11, 12), pair_t (13, 14), pair_t (15, 16)));
INSERT INTO table2 VALUES
  (20, array_of_pairs_t (pair_t (21, 22), pair_t (23, 24), pair_t (25, 26)));
INSERT INTO table2 VALUES
  (30, array_of_pairs_t (pair_t (31, 32), pair_t (33, 34), pair_t (35, 36)));
INSERT INTO table2 VALUES
  (40, array_of_pairs_t (pair_t (41, 42), pair_t (43, 44), pair_t (45, 46)));
INSERT INTO table2 VALUES
  (50, array_of_pairs_t (pair_t (51, 52), pair_t (53, 54), pair_t (55, 56)));

UPDATE table2 outer_table
   SET array2 =
       CAST (MULTISET (SELECT DECODE (ROWNUM, 2,  pair_t (98, 99),
                                      VALUE (temp_tab))
                         FROM TABLE (SELECT array2
                                       FROM table2
                                      WHERE ROWID = outer_table.ROWID) temp_tab
                      )
             AS array_of_pairs_t
            )
 WHERE id > 30
/

Best regards,
Zlatko Sirotic

          Followup:

umm, how would an update be easier than:

  array(2) := 55;

???  sorry, updating of in memory collections just "doesn't happen" like that.

      Rating:
      5
  February 08, 2004
Reviewer:  Zlatko
      Sirotic  from Croatia
I need explanation for:

"sorry, updating of in memory collections just "doesn't happen" like that"

Thanks,
Zlatko Sirotic

          Followup:

-- We want to do as followed:
/*
UPDATE table1
   SET array1 (2) = 99 -- NOT SUPPORTED!
 WHERE id > 30;
*/

updating collections just doesn't happen like that.

you have to select it out.
assign to it
and put it back

February 08, 2004 Chandra -- Thanks for the question regarding
      "Parallel Insert", version 8.1.7.1

      You Asked
Hi Tom
Thank you for the serivice.
I have a large transaction table to be loaded( 60 Millions rows) in a data
warehousing environment which is taking around 19 hours in a regular PL/SQL
inserting one record at a time and commiting for every 20000 records. From
research I found that we can do the same thing using "bulk loading" option which
are
1. Serailly
2. Parallelly
I started using the following methods.
_____________________________________________________________________
CREATE OR REPLACE PROCEDURE WVDBA.bulk_load_serial AS
BEGIN
  INSERT /*+ APPEND */
    INTO wvdba.gift_detail_temp (
         POST_SEQ_NBR,
         EMPNO,
         ENAME)
  SELECT
         A.PST_POST_SEQ_NR,
         B.EMPNO,
         C.ENAME
 FROM  STAGE.DONOR_INC_DTL A,
       STAGE.POSTING_HIST  B,
       wvdba.ACCT_DONOR_XREF C
 WHERE A.PST_POST_SEQ_NR = B.POST_SEQ_NR (+)
   AND A.INC_DONOR_ACCT  = B.DONOR_ACCT (+)
   AND A.INC_DONOR_ACCT  = C.PRAISE_ACCT_NUM
   AND A.INC_AMT != 0;
  COMMIT;
END;
______________________________________________________________________
This process is taking around 3 hours which is good.
IS there anything I should worry about like
   1. Size of the rollback segements
   2. TEMP tablespace size etc.,
I also understand that We can use PARALLEL insert instead of SERAIL
  INSERT .
Do I need to partition the target table so that the engine would
   write into serveral areas on disk and make use of Parallel querry
   option?

Please let me know on these topics. Please bear with me if I am more elaborative
or less elaborative
Thanks,
Chandra

      and we said...
read:

http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76965/c21dlins.htm#365
to understand the direct load insert and parallel direct load insert.

and

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1669403535933
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2417875549841
for advice on what to do with those indexes if you have any before you do this
insert into...

Review &
      Followup

      Rating: 5
parallel
      processes vs parallel threads   February 07, 2004

Reviewer:  steve  from NY
hi tom,

We have a unix machine with 12 CPUs and 3 partitioned tables, say a, b, and c
with 12 partitions.

we have 2 ways to do inserting. one is the  following

  insert /*+ append */ into c
       select /*+ parallel(a,12), parallel(b,12) */ ...
        from a,b
       where a.col = b.col
        and ...

   let oracle parallel server to execute in parallel.

and other way is to spawn 12 sqlplus processes and each one to run the following
sql

   alter sesssion disable parallel ;
   insert into c(partition i)
       select ...
        from a(partition i) , b (partition i)
       where a.col = b.col
        and ...

    i=1,2,...12

Our consultant said that 2nd way is better than the 1st way
because 2nd way uses parallel process and 1st way just use parallel threads, and
12 processes will win more cpu resource than one oracle process with 12 threads.

Is above statement right? I'd to hear your option.
and do you have any benchmark for this?

thanks!

steve

          Followup:

he he he.  thats good.

On unix -- both method A and method B would use PROCESSES.

On Windows -- both method A and method B would use THREADS.

we do not mix threads/processes....

I have a question -- what use is C there?  just seems to be the result of
partionwise joins?  If C is useful - -then what use are A and B?

      Rating: 5
followup for
      parallel processes vs parallel threads   February 08, 2004

Reviewer:  A reader  from NYC
thank you for your answer!

In unix enviroment, the two methods should be same in your opinoin. no one is
better than other. right?

to answer your question, the table C is aggregate table.

thanks!
steve

          Followup:

No, one is perhaps easier cause it requires a single sql statement.  the other
requires some amount of work on your part.

February 09, 2004 Beth -- Thanks for the question regarding "Can
      shared pool be *too* large?", version 8.1.6

      You Asked
Is there a downside to having a very large shared pool?

We have a box with 4GB of memory almost completely devoted to 3 Oracle
databases.  We have quite a small shared_pool_size (100MB for one db and smaller
for the rest).

Is there a downside to allocating 1GB or so to the shared_pool in each database
for instance, since the box not being used for other applications?

      and we said...
YES.  definitely.

Especially if you don't make use of bind variables.
The shared pool works best when right sized to hold what you do.  A common
"myth" (conventional wisdom) is that if you are getting ora-4031's due to not
using bind variables you must immediately increase the size of the shared pool

What that does is this:

o delays the problem a little while.
o makes the problem even WORSE when it does happen.

The larger the shared pool, the harder to manage and page the stuff in and out.
When not using bind variables, you litterally flood the shared pool with
statements that will never be reused --sending us into a frenzy of "load and age
and flush".  The bigger the shared pool -- the longer it takes to do this -- the
more you wait (the system seems to sloooowwww  way down) and then eventually, it
falls apart.

Take that same system, decrease the shared pool way down -- and there you go,
it'll managed itself in a timely fashion.

I've seen sites that didn't (refused) use bind variables in OLTP.  They actually
had a guy who would sit there and watch a quest screen all day waiting for their
2gig (yes 2gig) shared pool to get 50% utilized.  He would then alter system
flush shared pool.  That was his job.

I begged them to test a 75m shared pool -- when they finally did, guess what
happened.... (rhetorical question, you should be able to figure it out by now)

The real fix for them -- use BINDS, re-assign the guy who was paid to flush the
shared pool (heck, they didn't even script that...)

If you ask me, 100mb is NOT small, it's about right.
Review &
      Followup

      Rating:
      5
  April 05, 2002
Reviewer:  MaxU
       from Munich
Tom, thank you for clear answer!

Tom, should one use bind variables even though he/she has constants, for
example:

'select * from bla_table where level=1 and name=:nm'

if application uses such code and it's hardcoded, should we use bind variable
for column "level" too?

Thank you!

          Followup:

Nope, you use bind variables on things that CHANGE from run to run of the query.

If you have my book -- I actually cover this topic in "why I don't like
cursor_sharing = force".  It over binds -- sometimes you DON'T want to bind
everything.

Only bind things that change from execution to execution.

      Rating: 5
Thanks
      !!!  April 05, 2002
Reviewer:  MaxU
       from Munich

      Rating: 4
Does this apply
      to MTS?  May 07, 2003
Reviewer:  Fred
       from irvine, ca
Does your recommendation about reducing size apply to MTS?

      Rating:
      4
  September 17, 2003
Reviewer:  Jean
      Zhou  from Columbus, OH USA
I forward this page to developement managers so that they will understand the
importance of using bind variables.

      Rating: 5
Avoiding Shared
      Pool  February 02, 2004
Reviewer:  Wor
       from USA
Hi Tom,
In my application we have alot of queries form dynamically which take space in
shared pool. Yes we have SQL's with bind variables and stored procedures too.
But considering the quantity of change and urgent need to handle dynamic SQL's,
Is there any command that when executed does not put the SQL into shared memory.

e.g. Set Command 1=>execute SQL=>Undo command 1.
which will not put the SQL in the shared pool.
And even if we have some command how will this affect the performance of the
system ?
Thanks in advance,

          Followup:

Nope, the sql is shared in the shared pool.

Just because you build queries dynamically doesn't mean the cannot use binds and
hence would probably be reused.

      Rating: 5
What do you say
      about this?  February 04, 2004
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
Tom,
Please see the recommendation from Oracle support about a 7445 error that we
have been getting:

<quote>
The next thing I looked at was your non-default values listed during startup of
the database:

shared_pool_size = 100663296
job_queue_processes = 5

Now since you are running a 64-bit version of Oracle, the minimum shared pool is
64MB. Then you need to add about 10MB for each of the job queues, which leaves
you short of memory.

The error you see is most likely caused by a misconfigured database.

The minimum shared_pool_size for a single user database with the job queues you
have is at least 114MB. Then you have to add about 1 to 1.5MB for each
concurrent connection. Even more so depending on the types of operations the
database is performing. I would imagine a shared pool in the area of 256MB would
be more accurate for your system.
</quote>

My questions are:
a) We take 8 statspack snapshots a day and generate 4 reports, 15 minutes apart
during peak database activity periods. The shared pool advisory has never
indicated that we need to increase shared pool. In light of this, is the above
observation correct?
b) Why do we need to allocate 1MB to 1.5MB memory in shared pool per concurrent
connection? We are running in dedicated server mode.
c) Why the job queues take up 10MB per queue? The job queues are active only
during off hours. Do the job queues use memory from shared pool even when not
active?

We have 100MB shared pool and the system is running fine. Won't these
recommendations cause an over allocated shared pool?

Thanks

          Followup:

I fail to see how "job queue processes" has anything to do with "sizing the
shared pool" -- not directly anyhow -- definitely not as a "formula".

I think they need to rethink this -- a 7445 is due to a "program failing" (eg:
segmentation fault).  While too small of a shared pool could cause this (and
100m is pretty huge to me), it is still something that needs to be *fixed*.

Only if you are using shared server would you add 1 to 1.5 meg per concurrent
connection -- but, the funny thing is you better NOT add it to the shared pool
-- it would go into to the large pool and you'd watch your average UGA usage to
get the real number (versus the 1 to 1.5 meg rule of thumb)

soooooo -- i would not agree with this advice (which quite honestly is taken out
of context -- so, don't get me wrong -- I'd have to see the entire thing in
context before being 100%).

      Rating:
      5
  February 04, 2004
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
Tom,
Thanks for your advise. We were suspecting the 7445 due to bug 2475995. We were
just trying to get a confirmation from support whether the trace files indicated
as much or not. We had done our due diligence efforts to reproduce the problem
and checked that the workaround suggested by Oracle worked fine, use the
no_merge hint for inline view. However, since this is a production system, we
did not want to apply the one off patch without getting confirmation from
support. After analyzing the RDA and trace files, the advise we got was:

<quote>
I have been reviewing this issue on behalf of the owning analyst.

I started with a look at your alert.log and found these errors:

Tue Feb 3 12:24:38 2004
Errors in file /export/home/oracle/CCMP/trace/user/ccmp_ora_5081.trc:
ORA-07445: exception encountered: core dump [0000000100F8E3FC] [SIGSEGV]
[Address not mapped to object] [0x000000000] [] []

The next thing I looked at was your non-default values listed during startup of
the database:

shared_pool_size = 100663296
job_queue_processes = 5

Now since you are running a 64-bit version of Oracle, the minimum shared pool is
64MB. Then you need to add about 10MB for each of the job queues, which leaves
you short of memory.

The error you see is most likely caused by a misconfigured database.

The minimum shared_pool_size for a single user database with the job queues you
have is at least 114MB. Then you have to add about 1 to 1.5MB for each
concurrent connection. Even more so depending on the types of operations the
database is performing. I would imagine a shared pool in the area of 256MB would
be more accurate for your system. You should start using STATSPACK to gather db
statistics that will enable you to monitor the memory usage and to size the
database appropriatly.

pga_aggregate_target = 188743680
workarea_size_policy = auto

This is also a source of concern. your pga_aggregate_target is very low. It
should normally be set to about 50-60% of your total amount of physical memory
on the server. Since you have about 20GB of RAM on the box, I would at least
recommend setting it to 2-3GB.

The last issue I see is that your redolog files are way too small. They are
sometimes switched every minute. Oracle recommends sizing them so they only
switch every 15-30 minute during PEAK load.

If you still are seeing the ora-7445 errors after reconfiguring the database,
please update the tar with the errors and uplaod a new alert.log and the
tracefile, else the tar will be closed automatically after 2 weeks.
</quote>

Our statspack reports do not indicate any need to increase the PGA aggregate
target or the shared pool. Improperly sized redo log files can hardly cause
7445. The shared pool size calculations and recommendations looked factually
incorrect so I decided to check with you.

Thanks...

          Followup:

I disagree with their analysis, yes.

      Rating: 2
How do we
      explain the recommended SHARED_POOL for Oracle
      Apps  February 05, 2004
Reviewer:  Hemant K
      Chitale  from Singapore
How do we explain the very large recommended
SHARED_POOL_SIZEs for Oracle Apps --eg in Note 216205.1
on MetaLink -- sizes of 600M to 800M for upto 500 users,
with 600M being the minimum.

          Followup:

they have sized it, they have tested it, they have designed for it.  it is a
requirement of that packaged application.

apps uses a TON of plsql - lots of it - huge amounts of it.

      Rating:
      5
APP  February 05, 2004
Reviewer:
      A reader
Becase Oracle Applications is full of PL/SQL codes!

      Rating: 5
can db cache be
      too big?  February 06, 2004
Reviewer:
      A reader
Hi

Recently I visited a shop, they have Oracle 8.1.7 running on Sun Solaris 8 and a
database of 10GB. My surprise is their db block buffer was set to 4GB (The
server has 8GB memory), the guy who sized it said because he was taught in
Oracle performance tuning courses that more memory == good == improved hit
ratio...

My question is can a data cache be too BIG?

          Followup:

answer = yes.

show them this:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6749454952894
and ask them if the 98.9% cache hit ratio would be something to be proud of.

I massively prefered my 50% cache hit ratio.

A cache hit ratio by itself is meaningless.  The best way to improve performance
- identify your top SQL's (by IO/CPU amounts) and look at them as ask yourself
"are they doing as little work as possible"

      Rating: 5
side effect of
      very very big database cache?  February 08, 2004

Reviewer:  A reader
Are there any side effects of huge data cache? Shared pool you mentioned that
too big will make things hardered to manage how about data cache?

Will my application run slower if my data cache is very big?

          Followup:

yes, it can.  it can actually take longer during some operations (like a full
scan) to figure out what needs to be read and what is already read -- then to
just "read it all".  the bigger the cache -- the longer it takes.

bigger = harder to manage, more stuff to manage.  lots of stuff you might not be
using in the cache to scan over time and time and time and time again.

depends on how you use the data.

      Rating: 4
db cache
      size  February 08, 2004
Reviewer:
      A reader
Hi Tom,

If I have 4G RAM on one machine which is dedicated to oracle database.  What is
your recommended db_cache_size?

thanks,

          Followup:

somewhere between 50meg and 3.75gig probably.

depends on what you are doing -- 100%.

February 09, 2004 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.

      Rating: 5
cache
      table  February 08, 2004
Reviewer:  reader

What is the effect of cache clause in creating tables? Is it going to cache the
blocks permanently in the buffer cache until the instance is bounced. Is it a
way to improve performance in oltp? Thanks.

          Followup:

No, it does not cache the blocks permanently.  It only changes the manner in
which they are treated in a full scan when the table is "large"

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:255215154182
you'd be better off using the KEEP and RECYCLE pools if you want this degree of
control

      Rating: 3
OCP
      Tests  February 09, 2004
Reviewer:  noel seq
       from Mumbai, India
This is for Ryan Gaffuri.

Tom never meant that Hit ratios at all times are totally meaningless. He means
that HIT ratios in *most* cases  are totally meaningless especially in cases
where the LIO's are too high or shared pool too large or where bind variables
are not used for frequently repeated queries.

First Tune your system, then look at the hit ratios. Ratios are meaningful only
in a tuned environment.

Don't expect your db to be 100% tuned immediately after putting it into
production. Its the DBA's job to tune it and then as a follow up look at the
ratios where even a slight change means that somewhere something needs tuning.

Noel.

          Followup:

Let me clarify further.

A hit ratio, in isolation -- all by itself, is pretty much meaningless.

You need *more* information.   Ratios are not even really relevant all by
themselves in a "tuned" environment.  They are simply indicators that -- coupled
with knowledge -- can be used to identify places to look.

example:

soft parse ratio = 80%.  Ok, is that good or bad?

You say bad?  I say "oh wait, it is a data warehouse after all, they parse
queries that will run for seconds or minutes and need to use advanced features
such as star transformation.  if they used binds, well, it wouldn't work right"

You say good? I say "oh wait, it is a high end transactional system.  Anything
less than 99% for them is 'performance nightmare city'.  They have a bug in the
developed code that must be fixed really soon"

You see -- you need ratio PLUS knowledge and maybe a slew of other numbers in
order to come to conclusion.

      Rating:
      5
ratios  February 09, 2004
Reviewer:
      Ryan Gaffuri  from Tysons Corner, VA
How often do you look at ratios when tuning a database? I have seen you write
about the parse/execute ratio. Do you even look at the cache hit ratio? In the
oraperf statspack report it says to ignore this ratio. I've seen carrie milsap
post that the only thing the cache hit ratio tells him is if it is too high,
that there is alot of bad sql.

what about the other ratios? I never use them personally. Should I use them in
any form of context? You don't really even mention them in either of your books.

My understanding was that you can get more precise information by looking at the
wait events rather than looking at ratios.

          Followup:

I use the soft parse % and ask "what kind of system do you have".

I predominantly use wait events for tuning applications...

A high cache hit ratio very often means

a) applications are totally untuned, performing massive LIO's that they need not
be
b) applications are partially tuned
c) applications are totally tuned

that is - a high cache hit by itself means nothing.

February 09, 2004 Richard -- Thanks for the question regarding
      "Recommended way to implement Read Only information", version 9.0.1

      You Asked
Tom,
You perform a wonderful service. Please keep up the great work!

We have several tables partitioned by time (fiscal year). What do you recommend
as the best approach for having historical data be read only and current data be
read/write? Basically, we want to be able to append for the current fiscal year,
and lock the prior fiscal years so that they can not be modified. My current
thought is to have the historical data be in a separate table space than the
current data. Does this approach make sense? Are there any new partition
features supporting read only or read/write (as far as I can tell there is not)?
I'm using Oracle 9.0.1 on Windows 2000.
Thanks!

      and we said...
Well, if I was using 9iR2 i would do this:

at the end of a "window", I would "create table NEW_HISTORY_DATA compressed
tablespace XXX as select * from existing_table partition(partition_name)"   --
that'll compress by a factor of maybe 3 to 1 or even 12 to 1.

alter table existing_table exchange partition partition name with this new table
(remove the existing partition and replace it with a nice compressed partition)

alter tablespace XXX READ ONLY;

In 9iR1 -- i would just put each partition into a separate tablespace and make
that tablespace READ ONLY when I wanted to freeze it.

Review &
      Followup

      Rating: 5
Implementing
      Read Only  July 26, 2002
Reviewer:  Richard
      Bulan  from Cleveland, OH USA
Excellent response. Thank you!

      Rating: 5
Use of
      data_segment_compression  August 26, 2002

Reviewer:  Cachito  from Bolivia
Hi Tom, I agree excellent answer.

First
The compressed option in
create table NEW_HISTORY_DATA compressed
As I understand reading the documentation is
good for any table used for storing and that is not going to be updated or
deleted.

I´m right?

Second, I asked it before, sorry for asking again, but there is a way to put the
tablespaces for java, xml as readonly, the reason is because the time of backing
up for this database is critical and this must be a full offline backup.
So if I can´t reduce the size ( excluding that tablespaces from backup ) I
should have to delete them.

Thanks

          Followup:

It is best when there will be no more subsequent updates or deletes on the
PARTITION of the table or the table itself (so, no -- it is not the "table" but
rather the partition in this case...)

If you let me know what a tablespace for "java" is, or what an XML tablespace
is, I'd be glad to answer.

I disagree that it must be a full offline backup.  It should NEVER be an offline
backup in a production system since by defintion you MUST be in archivelog mode
if you value your data and if you don't, you aren't in production so backup time
is meaningless.

Do online backup.  Shutting down your database is criminal.  Don't do it.

      Rating: 5
Recommended way
      to implement Read Only information  August 27, 2002

Reviewer:  Ram  from INDIA
Tom,

Now I am facing  the same problem .,  But  I am using Oracle 8.1.7  standard
edition  on
Windows 2000. Can  you please give me the solution How should I approach .

Thanks

          Followup:

You cannot as you have neither table compression nor partitioning. (nor do you
really give enough information about what you want to do actually.  You do have
read only tablespaces, you can put triggers on tables to prevent modifications,
you do have read only views, you do have GRANTS)

      Rating: 5
Is it suitable
      for snapshot table?  August 27, 2002
Reviewer:
      chao  from sh
hi, tom:
  For a snapshot site, is it suitable to use segment compression? We refresh the
snapshot every 1 hour, and the segment itself is not big(tables' size is about
50M total),and the refresh itself is quite fast. The application consumes a lot
of cpu,and have a lot of buffer_gets, from statspack,23K/second in one day's
time. And i want to reduce the load on the server.On seeing your idear, i wonder
if segment compression in snapshot segment will reduce the buffer_gets of the
application? After the data is read into memory, will it restore its original
size?
  Thanks for your advice.

          Followup:

It may reduce the buffer gets -- the data is not decompressed in the buffer
cache, it is decompressed upon access - so look for slightly higher CPU usage.

      Rating: 4
Using table
      compression  October 01, 2002
Reviewer:  Ofir
      Manor  from Israel
Just wanted to add one thing. From my experience, when you finish to insert rows
into the current partition and start to insert rows into a new partition, and
you want to recreate the old partition (compressed or not), it is much better to
use an order by clause:
create table NEW_HISTORY_DATA compressed
tablespace XXX as select * from existing_table partition(partition_name)
ORDER BY A,B,C
That way the data is physicly clustered by some meaningful fields, and that can
improve the compression (if the leading fields are common and non unique).
Also, it should improve some of your queries performance significantly (for
example, if you partition by month maybe order each closed partition by
customer_id so all his records will be contained in one or two physical blocks
instead of tens of blocks). You should decide the physical order of the rows in
the rebuilt partition - it is unlikely that the optimal ordering is "just the
order it was inserted over the last month".
You should also consider using "nologging" option of CTAS to save time.

      Rating:
      4
Compression  February 09, 2004
Reviewer:
      Ik  from BG
Tom,

Scenario :
Our database at offshore contains a fraction of the actual database. This is a
datawarehouse and we develop reports against it. We get new data (export/import
process) very rarely.

Problem :
We have a space issue and most of the tables are huge and are non-partitioned
tables. I have you our scenario to indicate that it will be mainly read-only
operations that goes against the database.

Questions are
(1) Can i compress non-partitioned tables?

(2) If so, what is the procedure? Is it using ALTER TABLE MOVE? But, that is for
partitons, right?

Your thoughts are greatly appreciated.

Thanks

          Followup:

1) yes
2) alter table move works for real tables as well.

February 09, 2004 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.

      Rating: 5
I want to
      increase the commit in sqlloader   February 09, 2004

Reviewer:  A reader
Tom,
I searced this site - i am still missing it ....

My requirement is - i have 300000 records in a dat file, i want to load it
faster...

it looks like its commiting for every 64 rows..am i right here ?- if not please
make it clear to me...

so if i increase the rows per commit to 1000 - i think i can make it faster...

I want to increase the commit in sqlloader -- it says
 logical commit point reached - 64
 logical commit point reached - 128 like this ....

i want to commit for every 1000 ...how to do? .. is it possible...

Thanks for your time and consideration ...

          Followup:

hmmm.  seems simple if you either

a) type sqlldr at the command line and look at the parameters.
b) refer to the parameters in the documentation.

consider:

$ sqlldr

SQL*Loader: Release 9.2.0.4.0 - Production on Mon Feb 9 07:31:34 2004

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Usage: SQLLDR keyword=value [,keyword=value,...]

Valid Keywords:

    userid -- ORACLE username/password
   control -- Control file name
       log -- Log file name
       bad -- Bad file name
      data -- Data file name
   discard -- Discard file name
discardmax -- Number of discards to allow          (Default all)
      skip -- Number of logical records to skip    (Default 0)
      load -- Number of logical records to load    (Default all)
    errors -- Number of errors to allow            (Default 50)
      rows -- Number of rows in conventional path bind array or between direct
path data saves
               (Default: Conventional path 64, Direct path all)
  bindsize -- Size of conventional path bind array in bytes  (Default 256000)
    silent -- Suppress messages during run
(header,feedback,errors,discards,partitions)
    direct -- use direct path                      (Default FALSE)

      Rating: 5
Conventional
      path 64 rows. .. how to increase?  February 09, 2004

Reviewer:  A reader
Thanks tom,

but cant we increase that ..?

Please let me know,

      Rating: 5
oh - got it
      ...  February 09, 2004
Reviewer:
      A reader
Thanks tom, ...
i missed your above two points in hurry... I am trying here ... Thanks you so
much

      Rating: 5
Are there not 2
      dates   February 09, 2004
Reviewer:  Umesh
      Kasturi  from Bangalore India
Tom
Thanks for the answer above
"
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:
"

My question is that column 4,5 are both dates
Also I did not understand that " stuffed constant "

Thanks in advance

          Followup:

Look at the input data:

c1: Fac.SQLL|
c2: 1|
c3: DESCRIPCIÓN SQL|
c4: 31-DEC-02|
c5: 2002/12/01 00:00:00|
c6: 1000|
c7: 0|
c8: 0|
c9: 5|
....

Now, look at the control file:

(
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,

the part in bold is the "stuffed constant", they stuffed a constant in there.

Using this control file

INVOICE_NUMBER         -> C1
FIXED_ASSESTS          -> stuffed a constant into it
DESCRIPTION            -> C2
DATE_PLACED_IN_SERVICE -> C3

but C3 is "description sql" -- that is wrong, they want to SKIP the second
column -- but they did not.

So, the control file:

( c1,
  foo filler,
  c3,
  c4,
  c5,
  c6,

does that -- FOO filler "skips" C2 (they didn't want to load that) and lets the
columns match up with the input data.

February 09, 2004 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.

      Rating: 5
SET AUTOCOMMIT
      N  February 05, 2004
Reviewer:  LB
       from New Jersey
Tom,
I cannot find the answer to this on your site.
If I do SET AUTOCOMMIT 1000 will that commit after every 1000 rows within my
statement?
I want a way to do an insert statement that will commit after every X number of
records.
Thanks for your help!

          Followup:

that will never happen (thank goodness) in Oracle.  That would be the equivalent
of a "transactional nightmare"

      Rating:
      5
Why?  February 09, 2004
Reviewer:
      A reader
Why would that create a transactional nightmare?

Why is there no way to commit within a statement and only after the statement
completes?  (e.g. DELETE FROM t and assume that t has 50,000 rows - I want to
commit within that every 1000.)

          Followup:

statement is partway -- some of the way -- finished.

it has done some, but not all of the rows.

system crash......

now what?  how far were you? how do you restart? what do you do?  just erase
your database and start over??

You SIZE your system for the WORK you do.  You configure sufficient UNDO space
for the work you do.  You use transactions correctly.

      Rating: 5
Makes
      sense  February 09, 2004
Reviewer:
      A reader

February 09, 2004 Pierre -- Thanks for the question regarding "Call
      a stored procedure over a database link", version 7.3.2.3

      You Asked
!!!!
!!2000-07-05 THIS HAS BEEN UPDATED AS PER REQUEST, SEE BELOW!!
!!!!
!!!!
!!2000-06-30 THIS HAS BEEN UPDATED AS PER REQUEST, SEE BELOW!!
!!!!
We have developed a stored procedure in one of our machines. How
can I run this procedure from another machine (or other database
instance).

I am familar with database links and synonyms. I have set up a
database link accessing the machine as the correct user (owning
the procedure). I can run "select * from table1@machine1" and
that works OK. I try to do a smilar thing with my procedure
"execute proc1@machine1". No luck, it does not like the '@'
sign. I try to create a synonym for the procedure but still no
luck.

Can I run a stored procedure on a foreign machine?
How? What is the syntax?
Do I need to make it "public"?

Thanks!

Best regards
Pierre Oberg

Update, 2000-06-30.
These are the errors that I get:
SQL> execute put_ai@as69('TESTAIC1',33)
begin put_ai@as69('TESTAIC1',33); end;

*
ERROR at line 1:
ORA-02074: cannot ROLLBACK in a distributed transaction
ORA-06512: at "GTADMIN.GT", line 245
ORA-06512: at "GTADMIN.GT", line 1393
ORA-06512: at "GTADMIN.GTAI", line 254
ORA-06512: at "OPS$OCSMGR.PUT_AI", line 7
ORA-02041: client database did not begin a transaction
ORA-02041: client database did not begin a transaction
ORA-06512: at line 1

The put_ai is a procedure that I have written and it looks like:
CREATE OR REPLACE PROCEDURE PUT_AI (tag VARCHAR2,value NUMBER)
   IS
BEGIN

DECLARE
  status GT.AccessStatus;

BEGIN
  IF gtAI.put_VALUE(tag,value,status) != GT.SUCCESS THEN
    DBMS_OUTPUT.PUT_LINE('Function failed');
  ELSE
    DBMS_OUTPUT.PUT_LINE('Access status code = ' ||
status.codes);
  END IF;
END;
END PUT_AI;
/

commit;

The gtAI is part of our "operating environment". It is itself a
stored procedure and part of a package.

So, it looks like the problem is in in its implementation. Any
ideas how to get around this?
Is the gtAI our problem?
From the error messages above, can you draw any conclusions?

Thanks!

/Pierre Oberg

Update, 2000-07-05.

Hello Tom,

a little bit of background first:
Our Historian system where this Oracle database resides, sits on
top of what is called a DCS (Distibuted Control System).
Features in this system allow us to read and write values to and
from real objects in this DCS.

This gtAI.put_VALUE mentioned above, works fine when you are in
the local node.

I have tried to look into this and yes, it this package GT is
calling both ROLLBACK and COMMIT. It is actually inserting
values into a table from where the data is being put into our
objects. After the insert,a commit is issued. I'd say that this
commit is needed.

So, how come we have a probelm with commit and rollback when we
are calling this proceudre from a external node?

Please let me know if you need info.
Please also let me know if there is anything that can be done to
fix this (based on that we need commit and rollback in our base
package). Is there anything we can do?

Best regards
Pierre Oberg

      and we said...
In a distribute transaction -- one in which "DATABASE A" calls
"DATABASE B", only "DATABASE A" may commit -- the reason --
"database B" has no mechanism for co-ordinating with "database
a" on the commit.  We need to do a 2 phase commit to ensure that
when the transaction commits any work performed on DATABASE A is
committed *as well as* any work on database b.

The problem becomes more complex in general when database a
calls both database b and database c.  Here, if database b
(which has NO IDEA that database c is even involved) attempted
to commit -- it would not be able to coordinate with database a
and having no knowledge of the existence of database c in the
transaction -- could not coordinate it either.

This is the reason will do no allow for ANY node other then the
"parent" node of the transaction to issue a commit or rollback
in a distributed transaction.  The other nodes simply do not
have control over the other possible nodes in the transaction.

It is never *needed* that you commit within the stored procedure
(in fact, when I program -- i explicitly outlaw the use of
commit or rollback in a procedure/function unless you are using
autonomous transactions (new in 8i -- see

http://osi.oracle.com/~tkyte/autonomous/index.html
for a discussion)

It should always be left to the CALLER as to whether they need
to commit or rollback.  The problem with procedures committing
is that at some point in the future, you will need to call 2 or
3 of these procedures as a single transaction (new
functionality).  All (or none) should succeed (or fail).  If one
or more them "commits" or rollsback for you -- you cannot
achieve this.  I hate commits in procedures -- they totally mess
everything up and its very hard to program correct transactions
once you start putting them in there.

The only thing I can suggest to you now is to add a new optional
parameter to the existing routine -- a commit flag (make it be
something like:
....
  p_do_not_commit boolean in default FALSE );
....

You will then seek out all commit and rollback statements in the
affected routine and rewrite them as:

   if ( NOT p_do_not_commit ) then
       commit;
   end if;

When you call this routine you will pass:

   ....
   p_do_not_commit => TRUE );

as the last parameter.  Since you are adding a DEFAULTED
(optional) parameter at the end of the parameter list, there is
no chance of affecting any existing code.  You must control the
transaction from database a. It is the only way if you want to
call this remote procedure syncronously (you can run it
asyncronously using dbms_job@the_remote_site)...
Review &
      Followup

      Rating: 2
Handling Errors
      in a distributed Transaction  September 30, 2002

Reviewer:  Ivan  from USA
I can think of one case where DATABASE B needs to do a commit when it is being
called from DATABASE A.  The case being to log errors.  How can you log errors
in DATABASE B if DATABASE A has the only way to commit?

My scenerio is I have a remote loader who updates data in our main server.
However a log file needs to be populated for all changes that happen to the
database.  However is an error occurs than the errors are never logged because
it will never all DATABASE B to commit the logging.

Before we had distributed transactions I was able to use PRAGMA
AUTONOMOUS_TRANSACTION and commit the error.  Now I am unable in 8.1.7.2.1.

Your thoughts please...

          Followup:

UTL_FILE would be an opportunity there.

If an error occurs, the changes NEVER occurred and hence, why would you want to
commit log messages that never happened??

That the autonomous transaction *sometimes* and in very specific cases worked in
816 was a bug -- it was a dangerous situation.

      Rating: 2
Writing to a
      table within the database  October 10, 2002

Reviewer:  ivan  from USA
Tom,

This is a follow up to my previous post in this subject.

I wasn't very clear on what I was writing to and the purpose.  I am calling a
package from another Oracle server and trying to commit into a table
(distributed transaction).  The purpose is to log errors into an error table so
that I can see where the loader is not following the "rules". Whether it is a
loader trying to update a column that they do not have rights to or when a
column changes (history). I thought UTL_FILE just writes to a text file. I tried
to use DBMS_PIPE but I still get a "ORA-00164: autonomous transaction disallowed
within distributed transaction" error.

How can I do this?

          Followup:

Sorry, i thought I was clear -- you cannot, not in a distributed transaction.

UTL_FILE was my attempt to give you a workable solution, one that would let you
capture the errors and move forward.

      Rating: 2
Autonomous
      transaction disallowed - why?  October 10, 2003

Reviewer:  Ranga Chakravarthi  from Orlando, FL USA

Tom,

"That the autonomous transaction *sometimes* and in very
specific cases worked in
816 was a bug -- it was a dangerous situation."
Could you please explain why it is dangerous?
Why can't I do an autonmous transaction over a distributed
transaction? Why is it disallowed?
TIA

          Followup:

in 8i, it was NOT supported.

in 9i it is now supported.

      Rating: 1
Reason for not
      allowing an autonmous transaction over distributed  October
      13, 2003
Reviewer:  Ranga Chakravarthi  from Orlando,
      FL USA
Tom,

Could you comment on why it was not supported and what
the reason was for disallowing it?

Thanks

          Followup:

it wasn't supported -- thats all that can be said.

the code didn't exist.
therefore, it didn't work.

      Rating: 5
About
      -02041  January 09, 2004
Reviewer:  Mariano
       from Córdoba, Argentina
Hi, Tom.
I've read this page and its links (similar in fact to expert 1-on-1 chapter) but
i cannot make sense about a situation i have with my databases.
i'm working in an 8.1.7.0.0, using it for a website with ASP and ODBC connection
(The ora client in the web IIS server -not the same machine for the database
server- is 9.2.0.4)
I'm using dbms_obfuscation_toolkit to encript/desencript passwords for the
website but, cause this package is not working in my 8i, i send the password to
be desencripted to a remote 9.2.0.4.0 database.
Since i changed the webserver oraclient from 7.3 to actual 9.2, when i try to
execute the procedure i get ora-02041 error over and over again (this is not
happening from sqlplus). in the remote database i just execute
dbms_obfuscation_toolkit.des3decrypt and this action is wrapped in a procedure
in my 8i with some data processing actions after using dbms_obfuscation_toolkit.

Can you suggest an action in order to correct this situation?
Regards

          Followup:

ahh -- lets fixed the dbms_obfuscation thing.

It is a byte order issue.  See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:791026226790#8473587738442
(doesn't affect the latest releases -- all platforms use the same byte ordering
now)

You have an 8 byte key right now -- say the key was:

12345678

the key to unlock your data is:

43218765

swap the bytes -- in your decrypt just use the key:

  substr(key,4,1)||substr(key,3,1)||substr(key,2,1)||substr(key,1,1)||
  substr(key,8,1)||substr(key,7,1)||substr(key,6,1)||substr(key,5,1)

So, use the 8i stuff and just "fix the key" -- have your decrypt routine accept
the key "as normal" but apply this tranformation to it.

      Rating: 5
Please don't
      answer my question  January 09, 2004
Reviewer:
      Mariano  from Córdoba, Argentina
now it's ok. it was all about dealing with MTS in the webserver.

regards.-

      Rating: 3
synonym for
      remote function doesn't work  February 05, 2004

Reviewer:  James Su  from Toronto CA
SQL> create or replace function ff return number is begin return 200; end;
  2  /

Function created.

SQL> conn XXXX/XXXX@dev32
Connected.

SQL> create database link dbl_rep connect to XXXXXXX identified by XXXXXXXX
using 'dev2';

Database link created.

SQL> select * from dual@dbl_rep;

D
-
X

SQL> select ff@dbl_rep from dual;

        FF
----------
       200

SQL> create or replace synonym sy_test for ff@dbl_rep;

Synonym created.

SQL> select sy_test from dual;
select sy_test from dual
       *
ERROR at line 1:
ORA-00904: "SY_TEST": invalid identifier

          Followup:

look on the other page where you asked *the same thing*.

      Rating: 4
another
      question about remote function  February 06, 2004

Reviewer:  James Su  from Toronto CA
Sorry for posting my question twice. It seemed that something was wrong with
this site yesterday. After I posted my question, I could never found it again.
I have a new question about remote function:

select sf_myfunction(colname)
  from mytable@somelink
where ......
group by sf_myfunction(colname);

sf_myfunction is a local function and mytable is a remote table.
This query runs slowly. Can I improve the performance by moving sf_myfunction to
@somelink that links to the same server where mytable located?

I was trying to do that yesterday and I failed to access to a synonym for remote
function. Something is wrong with my Oracle9i for linux. So I am planning today
to create a view on that server, and create a synonym for that view on the local
server.

          Followup:

it takes a minute (or 15) for new things to be indexed.  i update the text index
on a schedule.

it probably would go faster to have a view at the remote site HOWEVER, I would
recommend you look at my_function and say "do i really need a function or can i
do this in sql" as that would be infinitely "faster" than calling plsql from
sql.

      Rating: 4
sub sql vs
      function  February 09, 2004
Reviewer:  James
      Su  from toronto CA
I can use a sub query instead of a function, but that will make the whole sql
too complex. And the function is called more than once, in several sql
statements. It's a hierarchical query using an argument as part of the "start
with" clause, and I don't know how to implement it as a view other than a
function. Another reason to use a function is that I can capture all the
exceptions in the function and return a default value, and I don't know how to
do that in a sub query. How much slower it could be to use a function instead of
a subquery ?

Can you tell me which of the following is better? (suppose table B and table A
has a one-to-many relationship and table A has a foreign key constraint on
column b_id )

select A.*,B.colname
 from table_a A,table_b B
where A.b_id = B.id;

select A.*,
       (select B.colname
          from table_b B
         where B.id=A.b_id
        )
  from table_a A;

Thank you so much for your help.

          Followup:

"views" - use a view to hide the "complex sql".  it could well be the difference
between

a) the query returning in a reasonable amount of time
b) the query never seeming to return at all

the two queries are not "the same"

select A.*,B.colname
 from table_a A,table_b B
where A.b_id(+) = B.id;

and

select A.*,
       (select B.colname
          from table_b B
         where B.id=A.b_id
        )
  from table_a A;

are if A.B_ID is nullable (if A.B_ID is not null -- then your two queries are
the same).....

the first approach is the standard one.

      Rating: 4
a view based on
      hierarchical query   February 09, 2004
Reviewer:
      James Su  from toronto CA
hi Tom,
I thought of using a view to hide the complex sql. But, as I mentioned above,the
function is a hierarchical query using an argument as part of the "start with"
clause:

.......
   SELECT CATL_TITLE
     into v_sport
     FROM CATEGORY,CATEGORTITLES
    WHERE CTGR_PARENT=0 AND CTGR_ID = CATL_CTGR_ID
    START WITH CTGR_ID=p_ctgr_id
  CONNECT BY PRIOR CTGR_PARENT=CTGR_ID;

    return v_sport;
.......

you see I use an argument "p_ctgr_id" in "start with".
How can I define a view to do that? Thank you.

          Followup:

application contexts can be used for that  - but if this query is going "into
plsql code" (assuming that based on p_ctgr_id) what do you care if

a) the query is nastly complex
b) the query is simple

???

You can just use a parameterized cursor and "hide the nastiness" in a package
spec somewhere (instead of a view)

create or replace package my_nasty_query
as
   cursor c( p_ctgr_id in number ) is
   select ..........;
end;

and then others would

   for x in my_nasty_query.c( some_input )
   loop
       .....

      Rating: 4
hiding a
      complex sub sql  February 09, 2004
Reviewer:
      James Su  from toronto
I'm using this subquery in a view:

CREATE OR REPLACE VIEW VW_TRANSACTION AS
select  TRANSACTION.*
       ,VW_ACCOUNT.*
       ,( SELECT CATL_TITLE
            FROM CATEGORY,CATEGORTITLES
           WHERE CTGR_PARENT=0 AND CTGR_ID = CATL_CTGR_ID
           START WITH CTGR_ID=EVENTS.CTGR_ID
         CONNECT BY PRIOR CTGR_PARENT=CTGR_ID
        )                      as CTGRTITLE
  from  TRANSACTION
       ,VW_ACCOUNT
       ,EVENTS
       ,LINE
 where  TRANSACTION.ACC_ID = VW_ACCOUNT.ACC_ID
        and TRANSACTION.LINE_ID = LINE.LINE_ID
        and LINE.LINE_EVENT_ID = EVENTS.EVENT_ID ;

and I access to this view in my plsql:

   select .......
     from VW_TRANSACTION
    where .......
    group by CTGRTITLE;
             ^^^^^^^^^

I use this subquery in quite a few views, and I don't like to write it
everytime. Is there any way to hide it?(other than a function)

          Followup:

no

February 09, 2004 -- 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

      Rating: 5
Hints Vs Index
      Paramateres  February 09, 2004
Reviewer:
      Mike  from USA
Tom,

We are moving to 9i from 8i...and also moving from rule to cost based.
With just analyzing tables and having optimizer changed to "CHOOSE" we are
seeing a lot of performance gains in 9i database..in comparison to 8i (rule
based)...I mean some of the stuff that used to drag...is flying now...
On the other hand some queries are dragging a bit ..they are not picking indexes
that they were using while in 8i..

Question: Is it a good idea to play with these index optimizing parameters
(which will effect the whole database) or just start adding hints to the queries
that are slow...to use particular indexes

Thanks.

          Followup:

I prefer things that give the optimizer information such as

o optimizer_index_cost_adj
o optimizer_index_caching
o good hints, like FIRST_ROWS <<--- might be all you need.

February 09, 2004 sudhakar -- Thanks for the question regarding
      "Wait event: DB file sequential read", version 9.2.0.1.0

      You Asked
I am using a database link to connect from one 9i database to another 9i
database.

I am frequently getting "db file sequential read" wait event at the linked
server. Due to that all my queries are having a slow response.

I tried to find the table that is causing the sequential read, it has 560,780
rows in it. Unfortunately, I am not able to make use of any primary key on it.

I have created a seperate tablespace for this table alone with 16k block size.
Still it is very slow. I have 1GB of RAM, Oracle SGA 400MB.

How should I make all my queries faster? Can you give me any suggestions on
this.

      and we said...
buy faster disks?

stripe the data across multiple disks?

db file sequential read is due to INDEXED reads by the way -- it is single block
IO, it is not the result of a full scan.  So.... you may well be looking at the
wrong place.  the p1, p2, p3 info in v$session_wait can be used to determine the
actual object being waited on.  A sequential read is usually a single-block
read, although it is possible to see sequential reads for more than one block
(See P3). This wait may also be seen for reads from datafile headers (P2=1
indicates a file header read) .

Block reads are fairly inevitable so the aim should be to minimise un-necessary
IO. This is best achieved by good application design and efficient execution
plans. Changes to execution plans can yield orders of magnitude changes in
performance. Tweaking at system level usually only achieves percentage gains.
The following points may help:

    * Check for SQL using unselective index scans

    * A larger buffer cache can (not will, "might") help

    * A less obvious issue which can affect the IO rates is how well data is
clustered physically. Eg: Assume that you frequently fetch rows from a table
where a column is between two values via an index scan. If there are 100 rows in
each index block then the two extremes are:
         1. Each of the table rows is in a different physical block (100 blocks
need to be read for each index block)
         2. The table rows are all located in the few adjacent blocks (a handful
of blocks need to be read for each index block)
      Pre-sorting or re-organising data can help to tackle this in severe
situations.

    * See if partitioning can be used to reduce the amount of data you need to
look at.

Review &
      Followup

      Rating: 4
WAIT on db file
      sequential read  January 22, 2004
Reviewer:
      Vinnie  from ORlando
Tom,

Related to the snippet below:

PARSING IN CURSOR #9 len=784 dep=0 uid=36 oct=2 lid=40 tim=2014330226848
hv=3624461969 ad='bba33118'
INSERT INTO JSITORLA_EQUIP_GROUP_0088
(TYPE,DEFENSIVE_POSITION,COMMAND_ORG,UIC_O,SYMBOL_CODE_O,INITIALIZATION_FO_ID,IR_
EXPOSURE,MOTION_
RANGE,HEALTH_STATUS,CONTAMINATION_CHEMICAL,MOUNT_STATUS,CONTAMINATION_NUCLEAR,TIM
ELINE_ID,MOBILITY_STATUS,VISUAL_EXPOSURE,ORG_NAME,ELAP
SED_SIM_TIME,MINEFIELD_VULNERABLE,MOVE_SEGMENTS_0017,FORMATION,SERVICE_TYPE,PAREN
T_ORG,CREW_COUNT,LOCATION_TYPE,SPACING,ACTIVE_SENSOR_M
ODE,FOLLOW_ROLE,CONTAMINATION_BIOLOGICAL,ROW_SEQUENCE_ID,PARENT_ID,ID_U,HOST_ID_O
,NUMBER_OF_PLATFORMS,DISPLAY_LEVEL,ENTITY_TYPE,COMMS_S
TATUS,FACTION_ID,TYPE_NAME,GND_COMBAT_POWER,ECHELON,AIR_COMBAT_POWER) VALUES (
:1,:2,:3,:4,:5,:6,:7,:8,:9,:10,:11,:12,:13,:14,:15,:16,:
17,:18,empty_blob(),:19,:20,:21,:22,:23,:24,:25,:26,:27,:28,:29,:30,:31,:32,:33,:
34,:35,:36,:37,:38,:39,:40)
END OF STMT
PARSE #9:c=10000,e=4932,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=0,tim=2014330226832
BINDS #9:
 bind 0: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac5a5b0 bln=4000 avl=02 flg=05
   value="cp"
 bind 1: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=24
offset=0
   bfp=ffffffff7ac708b0 bln=22 avl=02 flg=05
   value=100
 bind 2: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac595d0 bln=4000 avl=27 flg=05
   value="PELOWER_ORG.LAND.UNIT.14106"
 bind 3: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac585f0 bln=4000 avl=06 flg=05
   value="WG0HD0"
 bind 4: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac57610 bln=4000 avl=15 flg=05
   value="SFGPUUM---AE--G"
 bind 5: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac56630 bln=4000 avl=47 flg=05
   value="PELOWER_INITIALIZATION.LAND.EQUIP_GROUP_P.19102"
 bind 6: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=24
offset=0
   bfp=ffffffff7ac70870 bln=22 avl=02 flg=05
   value=100
 bind 7: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac55650 bln=4000 avl=10 flg=05
   value="not_moving"
 bind 8: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=24
offset=0
   bfp=ffffffff7ac726a8 bln=22 avl=02 flg=05
   value=100
 bind 9: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac54670 bln=4000 avl=01 flg=05
   value="F"
 bind 10: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac53690 bln=4000 avl=08 flg=05
   value="no_mount"
 bind 11: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac526b0 bln=4000 avl=01 flg=05
   value="F"
 bind 12: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=72
offset=0
   bfp=ffffffff7ac5e5a8 bln=22 avl=02 flg=05
   value=33
 bind 13: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=0
offset=24
   bfp=ffffffff7ac5e5c0 bln=22 avl=02 flg=01
   value=100
 bind 14: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=0
offset=48
   bfp=ffffffff7ac5e5d8 bln=22 avl=02 flg=01
   value=65
 bind 15: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac516d0 bln=4000 avl=09 flg=05
   value="HQ/D310MI"
 bind 16: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=24
offset=0
   bfp=ffffffff7ac5e578 bln=22 avl=03 flg=05
   value=7007
 bind 17: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac506f0 bln=4000 avl=01 flg=05
   value="T"
 bind 18: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac7f060 bln=4000 avl=37 flg=05
   value="PELOWER_ABSTRACT.LAND_FORMATION.20786"
 bind 19: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac7e080 bln=4000 avl=07 flg=05
   value="us_army"
 bind 20: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac7d0a0 bln=4000 avl=27 flg=05
   value="PELOWER_ORG.LAND.UNIT.14106"
 bind 21: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=24
offset=0
   bfp=ffffffff7ac5e548 bln=22 avl=02 flg=05
   value=6
 bind 22: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac7c0c0 bln=4000 avl=06 flg=05
   value="normal"
 bind 23: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=24
offset=0
   bfp=ffffffff7ac5e518 bln=22 avl=02 flg=05
   value=5
 bind 24: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac7b0e0 bln=4000 avl=18 flg=05
   value="active_sensing_off"
 bind 25: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac7a100 bln=4000 avl=10 flg=05
   value="uninvolved"
 bind 26: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac79120 bln=4000 avl=01 flg=05
   value="F"
 bind 27: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=24
offset=0
   bfp=ffffffff7ac5e4e8 bln=22 avl=04 flg=05
   value=385494
 bind 28: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac78140 bln=4000 avl=04 flg=05
   value="NULL"
 bind 29: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac77160 bln=4000 avl=34 flg=05
   value="PELOWER_ORG.LAND.EQUIP_GROUP.15333"
 bind 30: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac76180 bln=4000 avl=01 flg=05
   value="0"
 bind 31: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=48
offset=0
   bfp=ffffffff7ac5e4a0 bln=22 avl=02 flg=05
   value=4
 bind 32: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=0
offset=24
   bfp=ffffffff7ac5e4b8 bln=22 avl=01 flg=01
   value=0
 bind 33: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac751a0 bln=4000 avl=24 flg=05
   value="34398A000_1_1_CO CMD GRP"
 bind 34: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=24
offset=0
   bfp=ffffffff7ac5e470 bln=22 avl=02 flg=05
   value=100
 bind 35: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac741c0 bln=4000 avl=21 flg=05
   value="PELOWER_FACTION.20796"
 bind 36: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac731e0 bln=4000 avl=04 flg=05
   value="NULL"
 bind 37: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=24
offset=0
   bfp=ffffffff7ac5e440 bln=22 avl=02 flg=05
   value=61
 bind 38: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac8f060 bln=4000 avl=07 flg=05
   value="section"
 bind 39: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=24
offset=0
   bfp=ffffffff7ac5e410 bln=22 avl=02 flg=05
   value=22
WAIT #9: nam='db file sequential read' ela= 171 p1=17 p2=111721 p3=1
WAIT #9: nam='db file sequential read' ela= 85 p1=18 p2=55014 p3=1
WAIT #9: nam='db file sequential read' ela= 81 p1=18 p2=110338 p3=1
WAIT #9: nam='db file sequential read' ela= 7405 p1=18 p2=86570 p3=1
WAIT #9: nam='db file sequential read' ela= 83 p1=18 p2=113262 p3=1
WAIT #9: nam='db file sequential read' ela= 10407 p1=18 p2=86574 p3=1
WAIT #9: nam='db file sequential read' ela= 83 p1=18 p2=123340 p3=1
WAIT #9: nam='db file sequential read' ela= 12581 p1=18 p2=86578 p3=1
WAIT #9: nam='db file sequential read' ela= 78 p1=18 p2=113389 p3=1
WAIT #9: nam='db file sequential read' ela= 81 p1=18 p2=86582 p3=1
WAIT #9: nam='db file sequential read' ela= 84 p1=18 p2=123402 p3=1
WAIT #9: nam='db file sequential read' ela= 3937 p1=18 p2=86586 p3=1
WAIT #9: nam='db file sequential read' ela= 81 p1=18 p2=114481 p3=1
WAIT #9: nam='db file sequential read' ela= 607 p1=18 p2=86590 p3=1
WAIT #9: nam='db file sequential read' ela= 78 p1=18 p2=113074 p3=1
WAIT #9: nam='db file sequential read' ela= 8835 p1=18 p2=86594 p3=1
WAIT #9: nam='db file sequential read' ela= 78 p1=18 p2=112505 p3=1
WAIT #9: nam='db file sequential read' ela= 7834 p1=18 p2=103326 p3=1
WAIT #9: nam='db file sequential read' ela= 83 p1=18 p2=115211 p3=1
WAIT #9: nam='db file sequential read' ela= 5111 p1=18 p2=742 p3=1
WAIT #9: nam='db file sequential read' ela= 9352 p1=18 p2=1070 p3=1
WAIT #9: nam='db file sequential read' ela= 160 p1=18 p2=1062 p3=1
WAIT #9: nam='db file sequential read' ela= 156 p1=18 p2=738 p3=1
WAIT #9: nam='db file sequential read' ela= 8868 p1=18 p2=614 p3=1
WAIT #9: nam='db file sequential read' ela= 6671 p1=18 p2=630 p3=1
WAIT #9: nam='db file sequential read' ela= 157 p1=18 p2=666 p3=1
EXEC #9:c=30000,e=104859,p=26,cr=1,cu=26,mis=0,r=1,dep=0,og=4,tim=2014330332010
WAIT #9: nam='SQL*Net message to client' ela= 3 p1=1413697536 p2=1 p3=0
WAIT #9: nam='SQL*Net message from client' ela= 969 p1=1413697536 p2=1 p3=0
=====================
=====================
PARSING IN CURSOR #15 len=784 dep=0 uid=36 oct=2 lid=40 tim=2014330488814
hv=3624461969 ad='bba33118'
INSERT INTO JSITORLA_EQUIP_GROUP_0088
(TYPE,DEFENSIVE_POSITION,COMMAND_ORG,UIC_O,SYMBOL_CODE_O,INITIALIZATION_FO_ID,IR_
EXPOSURE,MOTION_
RANGE,HEALTH_STATUS,CONTAMINATION_CHEMICAL,MOUNT_STATUS,CONTAMINATION_NUCLEAR,TIM
ELINE_ID,MOBILITY_STATUS,VISUAL_EXPOSURE,ORG_NAME,ELAP
SED_SIM_TIME,MINEFIELD_VULNERABLE,MOVE_SEGMENTS_0017,FORMATION,SERVICE_TYPE,PAREN
T_ORG,CREW_COUNT,LOCATION_TYPE,SPACING,ACTIVE_SENSOR_M
ODE,FOLLOW_ROLE,CONTAMINATION_BIOLOGICAL,ROW_SEQUENCE_ID,PARENT_ID,ID_U,HOST_ID_O
,NUMBER_OF_PLATFORMS,DISPLAY_LEVEL,ENTITY_TYPE,COMMS_S
TATUS,FACTION_ID,TYPE_NAME,GND_COMBAT_POWER,ECHELON,AIR_COMBAT_POWER) VALUES (
:1,:2,:3,:4,:5,:6,:7,:8,:9,:10,:11,:12,:13,:14,:15,:16,:
17,:18,empty_blob(),:19,:20,:21,:22,:23,:24,:25,:26,:27,:28,:29,:30,:31,:32,:33,:
34,:35,:36,:37,:38,:39,:40)
END OF STMT
PARSE #15:c=0,e=257,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=2014330488800
BINDS #15:
 bind 0: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac5a8c8 bln=4000 avl=18 flg=05
   value="electronic_warfare"
 bind 1: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=24
offset=0
   bfp=ffffffff7ac5fb90 bln=22 avl=02 flg=05
   value=100
 bind 2: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac598e8 bln=4000 avl=27 flg=05
   value="PELOWER_ORG.LAND.UNIT.13761"
 bind 3: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac58908 bln=4000 avl=06 flg=05
   value="WSHA19"
 bind 4: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac57928 bln=4000 avl=15 flg=05
   value="SHGPUH-----C--G"
 bind 5: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac56948 bln=4000 avl=47 flg=05
   value="PELOWER_INITIALIZATION.LAND.EQUIP_GROUP_P.18158"
 bind 6: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=24
offset=0
   bfp=ffffffff7ac5fb60 bln=22 avl=02 flg=05
   value=100
 bind 7: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac55968 bln=4000 avl=10 flg=05
   value="not_moving"
 bind 8: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=24
offset=0
   bfp=ffffffff7ac5fb30 bln=22 avl=02 flg=05
   value=100
 bind 9: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac54988 bln=4000 avl=01 flg=05
   value="F"
 bind 10: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac539a8 bln=4000 avl=08 flg=05
   value="no_mount"
 bind 11: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac529c8 bln=4000 avl=01 flg=05
   value="F"
 bind 12: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=72
offset=0
   bfp=ffffffff7ac5fad0 bln=22 avl=02 flg=05
   value=33
 bind 13: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=0
offset=24
   bfp=ffffffff7ac5fae8 bln=22 avl=02 flg=01
   value=100
 bind 14: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=0
offset=48
   bfp=ffffffff7ac5fb00 bln=22 avl=02 flg=01
   value=65
 bind 15: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac519e8 bln=4000 avl=35 flg=05
   value="10TM/R INTER CEPT & DF/44 SIG RECON"
 bind 16: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=24
offset=0
   bfp=ffffffff7ac5faa0 bln=22 avl=03 flg=05
   value=7007
 bind 17: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac50a08 bln=4000 avl=01 flg=05
   value="T"
 bind 18: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac8f060 bln=4000 avl=37 flg=05
   value="PELOWER_ABSTRACT.LAND_FORMATION.20786"
 bind 19: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac8e080 bln=4000 avl=07 flg=05
   value="us_army"
 bind 20: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac8d0a0 bln=4000 avl=27 flg=05
   value="PELOWER_ORG.LAND.UNIT.13761"
 bind 21: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=24
offset=0
   bfp=ffffffff7ac5fa70 bln=22 avl=02 flg=05
   value=4
 bind 22: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac8c0c0 bln=4000 avl=06 flg=05
   value="normal"
 bind 23: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=24
offset=0
   bfp=ffffffff7ac5fa40 bln=22 avl=02 flg=05
   value=30
 bind 24: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac8b0e0 bln=4000 avl=18 flg=05
   value="active_sensing_off"
 bind 25: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac8a100 bln=4000 avl=10 flg=05
   value="uninvolved"
 bind 26: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac89120 bln=4000 avl=01 flg=05
   value="F"
 bind 27: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=24
offset=0
   bfp=ffffffff7ac5fa10 bln=22 avl=04 flg=05
   value=385502
 bind 28: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac88140 bln=4000 avl=04 flg=05
   value="NULL"
 bind 29: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac87160 bln=4000 avl=34 flg=05
   value="PELOWER_ORG.LAND.EQUIP_GROUP.14666"
 bind 30: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac86180 bln=4000 avl=01 flg=05
   value="0"
 bind 31: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=48
offset=0
   bfp=ffffffff7ac5f9c8 bln=22 avl=02 flg=05
   value=1
 bind 32: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=0
offset=24
   bfp=ffffffff7ac5f9e0 bln=22 avl=01 flg=01
   value=0
 bind 33: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac851a0 bln=4000 avl=49 flg=05
   value="MI002A_1_MI002A-02_RADIO INTERCEPT AND DF PLATOON"
 bind 34: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=24
offset=0
   bfp=ffffffff7ac5f998 bln=22 avl=02 flg=05
   value=100
 bind 35: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac841c0 bln=4000 avl=21 flg=05
   value="PELOWER_FACTION.20797"
 bind 36: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac831e0 bln=4000 avl=04 flg=05
   value="NULL"
 bind 37: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=24
offset=0
   bfp=ffffffff7ac5f950 bln=22 avl=02 flg=05
   value=5
 bind 38: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10
size=4000 offset=0
   bfp=ffffffff7ac82200 bln=4000 avl=07 flg=05
   value="platoon"
 bind 39: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=24
offset=0
   bfp=ffffffff7ac509b0 bln=22 avl=02 flg=05
   value=20
WAIT #15: nam='db file sequential read' ela= 101 p1=18 p2=123336 p3=1
WAIT #15: nam='db file sequential read' ela= 84 p1=18 p2=113388 p3=1
EXEC #15:c=0,e=15571,p=2,cr=1,cu=26,mis=0,r=1,dep=0,og=4,tim=2014330504682
WAIT #15: nam='SQL*Net message to client' ela= 2 p1=1413697536 p2=1 p3=0
WAIT #15: nam='SQL*Net message from client' ela= 921 p1=1413697536 p2=1 p3=0
=====================

If you notice the first insert waited over 8 seconds
on db file sequentil read & the second only very little.
File 18 is the index as you probably guessed.
Why would I wait on the  db file sequentil read so long for the first insert &
hardly nothing for the very next insert?

          Followup:

physical io on your system was really really slow.  do you have a hot disk, slow
disks, or a really busy busy system.

(the 2cnd did very little pio and perhaps the thing causing the slow IO was done
when it happened)

      Rating: 4
PIO & db
      file sequential read  January 23, 2004
Reviewer:
      Vinnie  from Orlando
The only thing that was running was the application loading the data.  We have
re-run the same test many times & always see the bulk of the PIO on the first
insert like shown in the previous message.

If you look at the first insert:
Just a few questions on the WAIT for the indexes.
1.
WAIT #9: nam='db file sequential read' ela= 85 p1=18 p2=55014 p3=1
I checked this block & it is in fact the PK for the table.
I also checked the Buffer Cache & this segment is already there.
From the DOC:
db file sequential read: the user process is reading buffers into the SGA buffer
cache & is waiting for a physical I/O call to return.
Why would I re-load the index from the datafile if it is already in the BC?

2.
WAIT #9: nam='db file sequential read' ela= 81 p1=18 p2=110338 p3=1
WAIT #9: nam='db file sequential read' ela= 7405 p1=18 p2=86570 p3=1
...
...
all the other WAIT events are on PK's for other tables. Why would these indexes
get loaded into the Buffer Cache for an insert into a table not related to them?

Thanks

          Followup:

1) the block in the buffer cache wasn't good enough (multi-versioning means that
many versions of a block can be in the cache, the one you needed wasn't -- we
had to read it)

2) foreign keys i would guess.  you haven't given us a testcase to play with so
we can only guess.

      Rating: 3
long insert on
      8.1.7.4  February 09, 2004
Reviewer:  dl
       from uk
Tom

Using tkprof i've identified an insert statement that is taking a long time to
run. Looking at the raw trace file it is doing a lot of waits for db sequential
file read, the following is an extract from the trace file:

=====================
PARSING IN CURSOR #3 len=1412 dep=1 uid=33 oct=2 lid=33 tim=234202020 hv=4174812
185 ad='c54f7960'
INSERT INTO MORTALITY_DATA_SET SELECT LMD.NHS_NUMBER,LMD.ANONYMISED_ID,LMD.DECEA
SED_FIRST_FORENAME,LMD.DECEASED_SECOND_FORENAME,LMD.DECEASED_THIRD_FORENAME,LMD.
DECEASED_SURNAME,LMD.DECEASED_ALIASES,LMD.DECEASED_MAIDEN_NAME,LMD.DATE_OF_BIRTH
,LMD.DATE_OF_DEATH,LMD.PLACE_OF_DEATH,LMD.DECEASED_USUAL_ADDRESS,LMD.DEC_USUAL_A
DDRESS_POSTCODE,LMD.CAUSE_OF_DEATH_LINE_1,LMD.CAUSE_OF_DEATH_LINE_2,LMD.CAUSE_OF
_DEATH_LINE_3,LMD.CAUSE_OF_DEATH_LINE_4,LMD.CAUSE_OF_DEATH_LINE_5,LMD.CAUSE_OF_D
EATH_LINE_6,LMD.CAUSE_OF_DEATH_LINE_7,LMD.CAUSE_OF_DEATH_LINE_8,LMD.UNDERLYING_C
AUSE_OF_DEATH,LMD.STHA_OF_RESIDENCE,LMD.PCT_OF_RESIDENCE,LMD.DATE_OF_REGISTRATIO
N,LMD.SEX,LMD.CAUSE_OF_DEATH_ICD_CODE_1,LMD.CAUSE_OF_DEATH_ICD_CODE_2,LMD.CAUSE_
OF_DEATH_ICD_CODE_3,LMD.CAUSE_OF_DEATH_ICD_CODE_4,LMD.CAUSE_OF_DEATH_ICD_CODE_5,
LMD.CAUSE_OF_DEATH_ICD_CODE_6,LMD.CAUSE_OF_DEATH_ICD_CODE_7,LMD.CAUSE_OF_DEATH_I
CD_CODE_8,LMD.CERTIFYING_GP,LMD.PLACE_OF_DEATH_CODE,LMD.GP_CODE,LMD.GP_NAME,LMD.
GP_PRACTICE_CODE, NULL  GP_PRACTICE_TEXT,LMD.PCT_CODE,DECODE(ST.STHA_CODE, NULL
,LMD.HA_CODE,ST.STHA_CODE),SYSDATE,:b1,MBR.BATCH_RECORD_ID   FROM LINKED_MORTALI
TY_DATA2 LMD,MORT_BATCH_RECORDS MBR,STHA_PCT ST  WHERE LMD.NHS_NUMBER = MBR.NHS_
NO  AND LMD.PCT_CODE = ST.PCT_CODE (+)    AND MBR.BATCH_ID = :b1  AND MBR.STAGE_
ID = 3  AND YEAR = GREATEST(TRUNC(NVL(DATE_OF_DEATH,TO_DATE('01-apr-2002','dd-mo
n-yyyy')),'q'),TO_DATE('01-apr-2002','dd-mon-yyyy'))
END OF STMT
PARSE #3:c=1,e=2,p=0,cr=4,cu=0,mis=1,r=0,dep=1,og=0,tim=234202020
BINDS #3:
 bind 0: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=7000000001 size=
48 offset=0
   bfp=80000001000e4e80 bln=22 avl=02 flg=05
   value=13
 bind 1: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=7000000001 size=
0 offset=24
   bfp=80000001000e4e98 bln=22 avl=02 flg=01
   value=13
WAIT #3: nam='file open' ela= 0 p1=0 p2=0 p3=0
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=35310 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=35324 p3=1
WAIT #3: nam='db file scattered read' ela= 1 p1=19 p2=35334 p3=2
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=35374 p3=1
WAIT #3: nam='db file sequential read' ela= 3 p1=19 p2=35389 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=35394 p3=1
WAIT #3: nam='db file scattered read' ela= 1 p1=19 p2=35419 p3=2
WAIT #3: nam='db file scattered read' ela= 1 p1=19 p2=35439 p3=3
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=35454 p3=1
WAIT #3: nam='db file scattered read' ela= 0 p1=19 p2=35459 p3=2
WAIT #3: nam='db file scattered read' ela= 0 p1=19 WAIT #3: nam='db file
sequential read' ela= 0 p1=19 p2=36049 p3=1
WAIT #3: nam='db file scattered read' ela= 1 p1=19 p2=36059 p3=3
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=36079 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=36084 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=36094 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=36099 p3=1
WAIT #3: nam='db file scattered read' ela= 0 p1=19 p2=36104 p3=2
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=36109 p3=1
WAIT #3: nam='db file scattered read' ela= 0 p1=19 p2=36129 p3=2
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=36134 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=36149 p3=1
WAIT #3: nam='db file scattered read' ela= 1 p1=19 p2=36154 p3=3
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=36159 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=36194 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=36204 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=36219 p3=1
WAIT #3: nam='db file scattered read' ela= 1 p1=19 p2=36234 p3=2
WAIT #3: nam='db file scattered read' ela= 1 p1=19 p2=36249 p3=3
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=36259 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=36269 p3=1
WAIT #3: nam='db file scattered read' ela= 2 p1=19 p2=36314 p3=4
WAIT #3: nam='db file sequential read' ela= 2 p1=19 p2=36319 p3=1
WAIT #3: nam='db file scattered read' ela= 1 p1=19 p2=36329 p3=2
WAIT #3: nam='db file sequential read' ela= 3 p1=19 p2=36354 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=36384 p3=1
WAIT #3: nam='db file scattered read' ela= 2 p1=19 p2=36389 p3=2
WAIT #3: nam='db file sequential read' ela= 2 p1=19 p2=36394 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=36404 p3=1
WAIT #3: nam='db file sequential read' ela= 2 p1=19 p2=36424 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=36454 p3=1
WAIT #3: nam='db file scattered read' ela= 0 p1=19 p2=36459 p3=2
WAIT #3: nam='db file scattered read' ela= 2 p1=19 p2=36464 p3=2
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=36479 p3=1
WAIT #3: nam='db file scattered read' ela= 1 p1=19 p2=36494 p3=2
WAIT #3: nam='db file scattered read' ela= 1 p1=19 p2=36514 p3=2
WAIT #3: nam='db file sequential read' ela= 2 p1=19 p2=36564 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=36574 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=36579 p3=1
WAIT #3: nam='db file scattered read' ela= 1 p1=19 p2=36614 p3=2
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=36634 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=36644 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=36669 p3=1
WAIT #3: nam='db file scattered read' ela= 0 p1=19 p2=36679 p3=5
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=36704 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=36714 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=36719 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=36724 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=36729 p3=1
WAIT #3: nam='db file scattered read' ela= 0 p1=19 p2=36744 p3=3
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=36764 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=36774 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=36794 p3=1
WAIT #3: nam='db file scattered read' ela= 0 p1=19 p2=36804 p3=2
WAIT #3: nam='db file scattered read' ela= 1 p1=19 p2=36809 p3=2
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=36824 p3=1
WAIT #3: nam='db file sequential read' ela= 2 p1=19 p2=36849 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=36854 p3=1
WAIT #3: nam='db file scattered read' ela= 0 p1=19 p2=36864 p3=3
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=36869 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=36909 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=36934 p3=1
WAIT #3: nam='db file sequential read' ela= 2 p1=19 p2=36939 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=36969 p3=1
WAIT #3: nam='db file sequential read' ela= 3 p1=19 p2=36974 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=36984 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=37354 p3=2
WAIT #3: nam='db file scattered read' ela= 0 p1=19 p2=37359 p3=2
WAIT #3: nam='db file sequential read' ela= 3 p1=19 p2=37364 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=37369 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=37379 p3=1
=====================
PARSING IN CURSOR #4 len=36 dep=2 uid=0 oct=3 lid=0 tim=234202148 hv=1705880752
ad='c5fe81f8'
select file# from file$ where ts#=:1
END OF STMT
PARSE #4:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=2,og=0,tim=234202148
BINDS #4:
 bind 0: dty=2 mxl=22(22) mal=00 scl=00 pre=00 oacflg=08 oacfl2=8000000100000001
 size=24 offset=0
   bfp=80000001000c9ab8 bln=22 avl=02 flg=05
   value=3
EXEC #4:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=234202148
FETCH #4:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=2,og=4,tim=234202148
FETCH #4:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=2,og=4,tim=234202148
FETCH #4:c=0,e=0,p=0,cr=1,cu=0,mis=0,r=0,dep=2,og=4,tim=234202148
STAT #4 id=1 cnt=2 pid=0 pos=0 obj=17 op='TABLE ACCESS BY INDEX ROWID FILE$ '
STAT #4 id=2 cnt=3 pid=1 pos=1 obj=39 op='INDEX RANGE SCAN '
WAIT #3: nam='file open' ela= 0 p1=0 p2=0 p3=0
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=47970 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=47996 p3=13
WAIT #3: nam='db file scattered read' ela= 1 p1=19 p2=37454 p3=2
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=37459 p3=1
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48022 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48048 p3=13
WAIT #3: nam='db file sequential read' ela= 2 p1=19 p2=37484 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=38064 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=38084 p3=1
WAIT #3: nam='db file sequential read' ela= 3 p1=19 p2=38099 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=38119 p3=1
WAIT #3: nam='db file scattered read' ela= 1 p1=19 p2=38129 p3=2
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=38144 p3=1
WAIT #3: nam='db file scattered read' ela= 1 p1=19 p2=38149 p3=2
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=38154 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=38159 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=38169 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=38174 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=38184 p3=1
WAIT #3: nam='db file scattered read' ela= 0 p1=19 p2=38239 p3=2
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=38249 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=38264 p3=1
WAIT #3: nam='db file scattered read' ela= 1 p1=19 p2=38289 p3=3
WAIT #3: nam='db file scattered read' ela= 1 p1=19 p2=38314 p3=2
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=38324 p3=1
WAIT #3: nam='db file scattered read' ela= 1 p1=19 p2=38334 p3=2
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=38339 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=38349 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=38354 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=38369 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=38379 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 WAIT #3: nam='db file
sequential read' ela= 1 p1=19 p2=38919 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=38924 p3=1
WAIT #3: nam='db file scattered read' ela= 2 p1=19 p2=38934 p3=2
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=38949 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=38954 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=38964 p3=1
WAIT #3: nam='db file sequential read' ela= 3 p1=19 p2=38979 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=38984 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=19 p2=38989 p3=1
 WAIT #3: nam='db file scattered read' ela= 0 p1=19 p2=103094 p3=2
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48970 p3=13

WAIT #3: nam='db file scattered read' ela= 0 p1=19 p2=103094 p3=2
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48970 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48738 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48829 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48764 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48751 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48816 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48842 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48803 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48790 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48777 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48610 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48623 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48636 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48649 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48675 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48701 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48482 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48508 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48534 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48560 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48586 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48688 p3=13

WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48586 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48688 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48662 p3=13
WAIT #3: nam='direct path read' ela= 0 p1=4 p2=50441 p3=13
WAIT #3: nam='direct path read' ela= 0 p1=4 p2=50480 p3=13
WAIT #3: nam='direct path read' ela= 0 p1=4 p2=50736 p3=13
WAIT #3: nam='direct path read' ela= 0 p1=4 p2=47894 p3=13
WAIT #3: nam='direct path read' ela= 0 p1=4 p2=49775 p3=13
WAIT #3: nam='direct path read' ela= 0 p1=4 p2=49634 p3=13
WAIT #3: nam='direct path read' ela= 0 p1=4 p2=48675 p3=13
=====================
PARSING IN CURSOR #4 len=36 dep=2 uid=0 oct=3 lid=0 tim=234203293 hv=1705880752
WAIT #3: nam='direct path read' ela= 0 p1=4 p2=50428 p3=13
WAIT #3: nam='direct path read' ela= 0 p1=4 p2=50441 p3=13
WAIT #3: nam='direct path read' ela= 0 p1=4 p2=50480 p3=13
WAIT #3: nam='direct path read' ela= 0 p1=4 p2=50736 p3=13
WAIT #3: nam='direct path read' ela= 0 p1=4 p2=47894 p3=13
WAIT #3: nam='direct path read' ela= 0 p1=4 p2=49775 p3=13
WAIT #3: nam='direct path read' ela= 0 p1=4 p2=49634 p3=13
WAIT #3: nam='direct path read' ela= 0 p1=4 p2=48675 p3=13
=====================

The tkprof report for this insert is:

INSERT INTO MORTALITY_DATA_SET SELECT LMD.NHS_NUMBER,LMD.ANONYMISED_ID,
  LMD.DECEASED_FIRST_FORENAME,LMD.DECEASED_SECOND_FORENAME,
  LMD.DECEASED_THIRD_FORENAME,LMD.DECEASED_SURNAME,LMD.DECEASED_ALIASES,
  LMD.DECEASED_MAIDEN_NAME,LMD.DATE_OF_BIRTH,LMD.DATE_OF_DEATH,
  LMD.PLACE_OF_DEATH,LMD.DECEASED_USUAL_ADDRESS,
  LMD.DEC_USUAL_ADDRESS_POSTCODE,LMD.CAUSE_OF_DEATH_LINE_1,
  LMD.CAUSE_OF_DEATH_LINE_2,LMD.CAUSE_OF_DEATH_LINE_3,
  LMD.CAUSE_OF_DEATH_LINE_4,LMD.CAUSE_OF_DEATH_LINE_5,
  LMD.CAUSE_OF_DEATH_LINE_6,LMD.CAUSE_OF_DEATH_LINE_7,
  LMD.CAUSE_OF_DEATH_LINE_8,LMD.UNDERLYING_CAUSE_OF_DEATH,
  LMD.STHA_OF_RESIDENCE,LMD.PCT_OF_RESIDENCE,LMD.DATE_OF_REGISTRATION,LMD.SEX,
  LMD.CAUSE_OF_DEATH_ICD_CODE_1,LMD.CAUSE_OF_DEATH_ICD_CODE_2,
  LMD.CAUSE_OF_DEATH_ICD_CODE_3,LMD.CAUSE_OF_DEATH_ICD_CODE_4,
  LMD.CAUSE_OF_DEATH_ICD_CODE_5,LMD.CAUSE_OF_DEATH_ICD_CODE_6,
  LMD.CAUSE_OF_DEATH_ICD_CODE_7,LMD.CAUSE_OF_DEATH_ICD_CODE_8,
  LMD.CERTIFYING_GP,LMD.PLACE_OF_DEATH_CODE,LMD.GP_CODE,LMD.GP_NAME,
  LMD.GP_PRACTICE_CODE, NULL  GP_PRACTICE_TEXT,LMD.PCT_CODE,
  DECODE(ST.STHA_CODE, NULL ,LMD.HA_CODE,ST.STHA_CODE),SYSDATE,:b1,
  MBR.BATCH_RECORD_ID   FROM LINKED_MORTALITY_DATA2 LMD,MORT_BATCH_RECORDS
  MBR,STHA_PCT ST  WHERE LMD.NHS_NUMBER = MBR.NHS_NO  AND LMD.PCT_CODE =
  ST.PCT_CODE (+)    AND MBR.BATCH_ID = :b1  AND MBR.STAGE_ID = 3  AND YEAR =
  GREATEST(TRUNC(NVL(DATE_OF_DEATH,TO_DATE('01-apr-2002','dd-mon-yyyy')),'q'),
  TO_DATE('01-apr-2002','dd-mon-yyyy'))

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.02          0          0          0           0
Execute      1    302.05     995.24    3128000     100579     606040       43147
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2    302.06     995.26    3128000     100579     606040       43147

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 33     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
  43148  HASH JOIN OUTER
  43147   HASH JOIN
 822834    VIEW LINKED_MORTALITY_DATA2
 822834     HASH JOIN
6433183      HASH JOIN
 230194       TABLE ACCESS FULL ASCT_GP_LIST_STAGE_4
 861932       INDEX FAST FULL SCAN (object id 4895)
 913559      TABLE ACCESS FULL ONS_DATA_SET
  45272    INDEX RANGE SCAN (object id 4879)
    302   TABLE ACCESS FULL STHA_PCT

********************************************************************************

It takes about 15 minutes to run.

I know that without the data you can't see the whole picture but if you've got
any clues as to what might be going on here? Why is it waiting so much, do i
need to hint it to use a different execution path??
I have indexed the relevant foreign keys etc.. but maybe i am missing an index
somewhere or am using the wrong type of index (if it helps the stha_pct table is
very small compared to the other tables which are similar in size).

This query does select about 50000 rows and joins a couple of fairly big tables
(about 1 million rows from mort_batch_records and 6 million rows from
linked_mortality_data2) to a small table (stha_pct 300 rows) but i would've
thought it should be quicker that this.

Any ideas would be appreciated.

Many thanks

          Followup:

the waits are for physical IO - both read and write.  Looks like temp.  what is
your hash/sort area size (or pga_aggregate_target) set to there?

      Rating:
      3
Thanks  February 09, 2004
Reviewer:
      dl  from uk
the hash_area_size=2097152
and the pga_aggregate_target parameter has not been set in init.ora.

Can you also explain how you knew it was TEMP that maybe causing this problem
please ?

Thanks

          Followup:

WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48586 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48688 p3=13
WAIT #3: nam='direct path write' ela= 0 p1=4 p2=48662 p3=13
WAIT #3: nam='direct path read' ela= 0 p1=4 p2=50441 p3=13
WAIT #3: nam='direct path read' ela= 0 p1=4 p2=50480 p3=13
WAIT #3: nam='direct path read' ela= 0 p1=4 p2=50736 p3=13

direct path reads and writes -> temp

set the hash area "bigger" (say 20/200 meg) for this session.

      Rating: 5
big
      hash_area_size  February 09, 2004
Reviewer:
      A reader
Hi

I am not sure, but once I tested with different hash_area_size setting for one
of our dwh query and didnt notice much difference in response time between 64MB
and 128MB... Is there a limit or something similar like a threshold?

      Rating: 3
Tuning direct
      path reads  March 07, 2004
Reviewer:  Matt
       from Australia
I have direct path reads taking up about 25% of my execution time - see below.

create table grms_mig_meter_reading_engy (
  mirn constraint mre_mirn_nn not null,
    constraint mre_mirn_len_ck check (length(mirn) = 10),
  mirn_chksum constraint mre_mirn_chksum_nn not null,
  reading_type constraint mre_reading_type_nn not null,
    constraint mre_reading_type_ck check (reading_type in ('A','D','E','S')),
  start_date constraint mre_start_date_nn not null,
  end_date constraint mre_end_date_nn not null,
    constraint mre_end_date_ck check (end_date >= start_date),
  energy_value constraint mre_energy_value_nn not null,
    constraint mre_energy_value_ck check (trunc(energy_value)=energy_value and
energy_value>=0),
  constraint mig_meter_reading_engy_pk primary key (mirn,start_date)
) nologging
as
select mirn
      ,mirn_chksum
      ,reading_type
      ,start_date
      ,end_date
      ,energy_value
  from ( select mirn
               ,mirn_chksum
               ,reading_type
               ,start_date
               ,end_date
               ,energy_value
               ,max(record_classification) over (partition by mirn)
classification
               ,row_number() over (partition by mirn,start_date order by
end_date desc NULLS last ) rn
           from ( select mirn
                        ,mirn_chksum
                        ,reading_type
                        ,start_date
                        ,end_date
                        ,energy_value
                        ,case when start_date <
to_date('01/02/2002','DD/MM/YYYY') then 'Start Date Before Historical Start
Date'
                              when end_date >
trunc(to_date('26/02/2004','DD/MM/YYYY')) then 'End Date Greater than Extract
Date' -- extract date
                              when max_ed >
trunc(to_date('26/02/2004','DD/MM/YYYY')) then 'Max End Date Greater than
Extract Date' -- extract date
                              when end_date < next_sd then 'Data Set has Gap'
                              when end_date != next_sd then 'Data Set has
Overlap'
                               end record_classification
                    from ( select mirn
                                 ,mirn_chksum
                                 ,reading_type
                                 ,start_date
                                 ,end_date
                                 ,energy_value
--                               ,lag(start_date) over ( partition by id order
by start_date) last_sd
--                               ,lag(end_date) over ( partition by id order by
end_date) last_ed
                                 ,lead(start_date) over ( partition by mirn
order by start_date) next_sd
--                               ,lead(end_date) over ( partition by id order by
end_date) next_ed
                                 ,max(end_date) over (partition by mirn ) max_ed
                             from extern_meter_reading_engy
                            where 1=1
                              and length(mirn) = 10
                              and mirn is not null
                              and mirn_chksum is not null
                              and reading_type in ('A','D','E','S')
                              and reading_type is not null
                              and start_date is not null
                              and end_date is not null
                              and end_date >= start_date
                              and energy_value is not null
                              and trunc(energy_value)=energy_value
                              and energy_value>=0
                         )
               )
        ) where 1=1
            and classification is null
            and rn=1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute      1    104.28     159.67      72962        519       5634     2659501
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        1    104.28     159.67      72962        519       5634     2659501

Misses in library cache during parse: 0
Misses in library cache during execute: 1
Optimizer goal: CHOOSE
Parsing user id: 228

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  direct path read                            51470        0.05         40.61
  direct path write                               6        0.00          0.00
  SQL*Net message to client                       1        0.00          0.00
  SQL*Net message from client                     1        0.01          0.01
********************************************************************************

I have  workarea_size_policy=auto and increased pga_aggregate_target to 2GB.

Using the following query

select a.sid
      ,c.serial#
      ,c.username
      ,c.osuser
      ,b.name
      ,a.value
      ,b.class
  from v$sesstat a
      ,v$statname b
      ,v$session c
where 1=1
  and a.statistic# = b.statistic#
  and a.sid = c.sid
  and a.sid = 35
  and b.name like '%sort%'
order by value desc

Whatever I try I still see some sorts to disk

I can see that the max pga size gets up to approx 120MB (ie: well below the
pga_aggregate value), and three disk sorts.

Any idea how I can get rid of these waits??

          Followup:

for this batch type operation, you might want to

alter session to set the workarea policy to manual
alter session to set hash area size/sort area size to a large number

workarea (auto memory tuning) works best in an ad-hoc environment.  for
recurring batch jobs, no reason not to set some of these things "as part of the
job"

February 09, 2004 -- 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://download-east.oracle.com/docs/cd/A81042_01/DOC/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://download-west.oracle.com/docs/cd/B10501_01/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.

      Rating: 5
internal
      password  February 09, 2004
Reviewer:
      Andrew  from Calgary, AB, Canada
how to change the password of "internal"?

smart.prod@XX001269.SYS> alter user internal identified by xxx;
alter user internal identified by xxx
           *
ERROR at line 1:
ORA-01918: user 'INTERNAL' does not exist

This is Oracle 8.1.7

          Followup:

internal doesn't have a password really... internal isn't a user.

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:221413645021

You should be using "as sysdba" so you can

connect your_Name/your_password AS SYSDBA

after being granted SYSDBA.

      Rating: 4
How to know the
      current password  February 09, 2004
Reviewer:
      A reader
Assume I am a DBA . I want to know the current password of a DB user. How to get
this info.Is there any DB V$ view for this ? I know how to change the password
using Alter user command but how to know the current one.
Thanx Sir.

          Followup:

you cannot.  we do not store the passwords.  in the dba_users view there is a
HASH (not an encrypted password).

what you look for just does not exist.

February 09, 2004 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://download-west.oracle.com/docs/cd/A91202_01/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.

      Rating: 5
Since OTN moved
      the docs...  February 09, 2004
Reviewer:  Kevin
      Engel  from Reston, VA
Since OTN moved

http://download-west.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a88876/adg14evt.htm#1000872
and "cool tricks" are always worth looking at, can I assume that the link might
be to chapter 14 of one of the application developer's guide?

As an aside, would it be possible [that is, without significant manual effort]
to "future proof" your links to oradocs by including a text reference to the
book/chapter?

          Followup:

here is the new link:

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96590/adg14evt.htm#999369
problem is that is actually chapter 16! and the chapters move too.

February 09, 2004 -- 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://download-west.oracle.com/docs/cd/B10501_01/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://download-west.oracle.com/docs/cd/B10501_01/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.

      Rating: 3
Date format
      picture ends before converting   January 13, 2004

Reviewer:  Jamil  from Saudi
Dear Sir
   I am getting this message when I run my report

REP-0613: value does not match mask DD/MM/YYYY
ORA-01830: Date format picture ends before converting entries input string

Please tell me how can I solve this problem

Best regards

Jamil

          Followup:

make sure your date format picture does not end before converting the string...

it means your input string -- is not in the format dd/mm/yyyy

ops$tkyte@ORA9IR2> select to_date( '01/01/2004 12', 'dd/mm/yyyy' ) from dual;
select to_date( '01/01/2004 12', 'dd/mm/yyyy' ) from dual
                *
ERROR at line 1:
ORA-01830: date format picture ends before converting entire input string

(EG: you have bad data)

      Rating: 4
time zone
      question  February 09, 2004
Reviewer:  James
      Su  from toronto CA
hi Tom,

I am using Oracle 9i, and I have some date type columns (not timestamp type)
storing GMT datetimes. My users log on from different time zones, how can I
provide a view for them to see these columns displayed as their local time(also
date type,not timestamp)? Daylight savings should be taken in account. I am
thinking of adding a time zone column to my user table and assign it to a
package value when a user logs on. What kind of data should I store in this time
zone column?

Thank you!

          Followup:

check out

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5011677391274#8218209147129
(just a tad UP in this page in fact :)

You can use that to do the conversions.

      Rating: 4
solution in
      9i  February 11, 2004
Reviewer:  James Su
       from toronto
CREATE OR REPLACE FUNCTION to_localtime(
       p_date in date,      -- GMT datetime
       p_tzr  in varchar2   -- timezone name
       ) return date        -- local datetime
AS
  v_localts TIMESTAMP WITH TIME ZONE;
BEGIN
   v_localts:= FROM_TZ(CAST(p_date AS TIMESTAMP),'Greenwich') AT TIME ZONE
p_tzr;

   return to_date(to_char(v_localts,'YYYY-MM-DD HH24:MI:SS'),'YYYY-MM-DD
HH24:MI:SS');
END to_local;
/

hi Tom,

the package is great, but table timedate_locales$ does not
contain a complete list timezone names as V$TIMEZONE_NAMES.TZNAME. since I am
using 9i, I wrote a function for conversion.

February 09, 2004 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://download-east.oracle.com/docs/cd/A81042_01/DOC/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.

      Rating:
      5
gather_table_stats & gather stale
      option  February 06, 2004
Reviewer:  Craig
       from NW Arkansas
Does the gather stale option work only with gather_schema_stats, or could you
gather stale for a single table using gather_table_stats?

All the examples and the docs seem to imply gather stale works only with
gather_schema_stats.

          Followup:

gather_schema|database_stats actually.

If you want to use gather_table_stats only on stale tables ( presumuably because
you want different method_opts or something) you would use gather_schema_stats
with LIST STALE to get a list of stale objects that you could then use
gather_table_stats on.

      Rating: 4
Results of a
      Trace of Gather_Table_Stats for Global Statistics  February
      09, 2004
Reviewer:  Doug Wingate  from New Orleans,
      LA
I've skimmed through this thread and, although I think my question is germaine
to the topic, I didn't find that it had already been discussed.  If I'm
mistaken, please just tell me where I can read your prior response.  Thanks.

In the course of my work today, I had occasion to execute this call to
Gather_Table_Stats to refresh the global statistics on a partitioned data
warehouse fact table.  I confess that the database is at level 8.1.7.3.  (We're
working on upgrading to level 9.2.)

EXECUTE DBMS_STATS.GATHER_TABLE_STATS (-
   ownname          => 'WAREHOUSE',-
   tabname          => 'CHARGES',-
   partname         => NULL,-
   estimate_percent => 3,-
   block_sample     => TRUE,-
   method_opt       => 'FOR ALL INDEXED COLUMNS SIZE 75',-
   degree           => 4,-
   granularity      => 'GLOBAL',-
   cascade          => TRUE,-
   stattab          => NULL,-
   statid           => NULL,-
   statown          => NULL);

I traced the execution of the procedure and looked at the trace file to see what
I could learn about how Gather_Table_Stats does its work of gathering global
statistics.  I found this ANALYZE statement buried deep in the trace file.

     analyze table "WAREHOUSE"."CHARGES"
       ESTIMATE statistics sample 3 percent
       FOR TABLE
       FOR ALL INDEXES
       FOR ALL INDEXED COLUMNS
       SIZE 75

Setting aside, if possible, the matter of the index statistics also gathered
here, are 'global statistics' really merely the same as the statistics one can
gather with 'ANALYZE TABLE...ESTIMATE STATISTICS...FOR TABLE;'?  I mean, if you
or someone else wishes to say that Gather_Table_Stats guides and simplifies the
writing of ANALYZE statements by correctly writing ANALYZE statements on users'
behalf, in response to simple parameters that allow the user to clearly express
his intention, rather than leaving the user to flounder and perhaps write the
statement incorrectly, I agree.  But is there any more to it than that even in
the case of global statistics?

Thanks.

          Followup:

In 8i, dbms_stats many times dropped down to "analyze" (serial)

In 9i, it does so infinitely less often.

So, you are just seeing the artifacts of an implementation over time.  In 8i,
anything with the size > 1 wasn't parallelized (done with analyze).  Later, when
done with sql, it could be.

      Rating: 5
Very
      Helpful  February 10, 2004
Reviewer:  Doug
      Wingate  from New Orleans
Thanks.  It hadn't occurred to me until now that to the extent
Gather_Table_Stats uses SELECT statements to gather statistics, its execution
plans can be optimized just like any other SELECT statement's plan can be
optimized.

February 09, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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.

      Rating: 4
internal on
      Windows  February 09, 2004
Reviewer:  Andrew
       from canada
Tom, thank you for the link.

The problem is that I just inherited the 8.1.6 running on windowz. And
"internal" has default password that I want to change (I use it from svrmgrl,
connect internal/pwd@prod and then statrup/shutdown). Do I understand correctly
that internal's password is stored in pwd* file? I searched the folder and yes
in ../database folder there is PWDprod.ora file. So, how should I change it?
they talk about different versions, but those are ver.7. What about 8.1.6 What
other passwords are there in the pwd.ora file? A bit messy... Can you clarify
this, please?

          Followup:

orapwd is used to recreate the password file with whatever password you want to
use

HOWEVER

you should just start using AS SYSDBA.  then you can connect u/p@remote as
sysdba instead.

February 09, 2004 Peter -- Thanks for the question regarding
      "String Concatenation", version 8.1.6

      You Asked
Hi Tom,

Is there anyway that I can just use SQL to do string
concatenation on a VARCHAR2 field much the same way
that a SUM() function is used on a NUMBER field.

For example,

   SELECT SUM(revenue), department
   FROM   sales_table
   GROUP  BY department;

Here revenue is a NUMBER field.
I'd like to do the same thing when the column is
a VARCHAR2 field. But instead of SUM, I'd like to
do string concatenation (||).

Can this be done without using PL/SQL?

THanks

- peter

      and we said...
Not in 8i, in 9i yes -- it is very very doable.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:229614022562
for 8i methods (not aggregates)

in 9i,

scott@ORA9I.WORLD> create or replace type string_agg_type as object
  2  (
  3     total varchar2(4000),
  4
  5     static function
  6          ODCIAggregateInitialize(sctx IN OUT string_agg_type )
  7          return number,
  8
  9     member function
 10          ODCIAggregateIterate(self IN OUT string_agg_type ,
 11                               value IN varchar2 )
 12          return number,
 13
 14     member function
 15          ODCIAggregateTerminate(self IN string_agg_type,
 16                                 returnValue OUT  varchar2,
 17                                 flags IN number)
 18          return number,
 19
 20     member function
 21          ODCIAggregateMerge(self IN OUT string_agg_type,
 22                             ctx2 IN string_agg_type)
 23          return number
 24  );
 25  /

Type created.

scott@ORA9I.WORLD>
scott@ORA9I.WORLD> create or replace type body string_agg_type
  2  is
  3
  4  static function ODCIAggregateInitialize(sctx IN OUT string_agg_type)
  5  return number
  6  is
  7  begin
  8      sctx := string_agg_type( null );
  9      return ODCIConst.Success;
 10  end;
 11
 12  member function ODCIAggregateIterate(self IN OUT string_agg_type,
 13                                       value IN varchar2 )
 14  return number
 15  is
 16  begin
 17      self.total := self.total || ',' || value;
 18      return ODCIConst.Success;
 19  end;
 20
 21  member function ODCIAggregateTerminate(self IN string_agg_type,
 22                                         returnValue OUT varchar2,
 23                                         flags IN number)
 24  return number
 25  is
 26  begin
 27      returnValue := ltrim(self.total,',');
 28      return ODCIConst.Success;
 29  end;
 30
 31  member function ODCIAggregateMerge(self IN OUT string_agg_type,
 32                                     ctx2 IN string_agg_type)
 33  return number
 34  is
 35  begin
 36      self.total := self.total || ctx2.total;
 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 stragg(input varchar2 )
  3  RETURN varchar2
  4  PARALLEL_ENABLE AGGREGATE USING string_agg_type;
  5  /

Function created.

scott@ORA9I.WORLD>
scott@ORA9I.WORLD> select deptno, stragg(ename)
  2    from emp
  3   group by deptno
  4  /

    DEPTNO
----------
STRAGG(ENAME)
---------------------------------------------------------------------------------
--------------------------------------------------
        10
CLARK,KING,MILLER

        20
SMITH,FORD,ADAMS,SCOTT,JONES

        30
ALLEN,BLAKE,MARTIN,TURNER,JAMES,WARD

would be the approach.
Review & Followup

      Rating: 5
Wowsers
      ...  December 11, 2001
Reviewer:  Mike
      Wilson  from Los Angeles, CA
I just spent a few hours wrapping this logic up in a VB function for my db
hitting Oracle 8i:
a) 9i seems to have a few hundred things I need.  This would have completely
simplified my logic (an put it into the database layer, security, speed, etc
...).
b) Oh, yeah, Tom rocks also ...

      Rating:
      5
  December 12, 2001
Reviewer:
      A reader

      Rating: 5
And in case if
      you want to pivot it back...  December 13, 2001

Reviewer:  Mikito harakiri
Given

create table COMMAGGR
(
        NUM           NUMBER,
        AGGR           VARCHAR2(2000)
);

insert into COMMAGGR values (1,'1071,12,11103,123404');
insert into COMMAGGR values (2,'15,11116,1,,118');
insert into COMMAGGR values (3,'6');
commit;

we want to parse it back.

1. Using 9i pipelined function to generate a "pseudotable" -- it is much faster
than just pulling integers from the table that is "big enough":

CREATE or replace FUNCTION UNSAFE
  RETURN  IntSet PIPELINED IS
  i INTEGER;
BEGIN
    i := 0;
    loop
       PIPE ROW(i);
       i:=i+1;
    end loop;
END;
/

1a (Optional). Now we can generate a sequence of 1M integers like this:

select rownum from TABLE(UNSAFE) where rownum < 1000000;

Be aware about stop condition: if you remove the predicate, you'll hung up the
client process. Note that just adding a parameter to UNSAFE function is inferior
to "stop count" predicate -- the code is much more self-documenting in the
latter case.

2. The query:

select NUM, substr( -- format
             substr( aggr,
                    instr(aggr, ',', 1, i)+1,
                    instr(aggr, ',', 1, i+1)
                    -instr(aggr, ',', 1, i)-1 )
            ,1,20)  -- /format
from ( select rownum i from TABLE(UNSAFE) where rownum < 1000 ),
     ( select NUM, ','||aggr||',' AGGR from COMMAGGR )
where instr(aggr, ',', 1, i)>0
and instr(aggr, ',', 1, i+1)>0

Alternative solution:

2. Enumerate all integer pairs:

    select t-(xplusy+1)*xplusy/2 y,
        xplusy-t+(xplusy+1)*xplusy/2 x from (
            select FLOOR((SQRT(8*(rownum-1)+1)-1)/2) xplusy,
                   rownum-1 t
                   from TABLE(UNSAFE) where rownum <1000
    )

3. The query:

select NUM, substr(aggr, beg, len)
from (
    select t-(xplusy+1)*xplusy/2+1 beg,
        xplusy-t+(xplusy+1)*xplusy/2+1 len from (
            select FLOOR((SQRT(8*(rownum-1)+1)-1)/2) xplusy,
                   rownum-1 t
                   from TABLE(UNSAFE) where rownum <1000
    )
) interval, COMMAGGR
where ( beg=1 or substr(aggr,beg-1,1)=',' )
                       -- a parsed string
                       --usually begins with comma
and ( substr(aggr,beg+len,1)=','      -- it
or beg-1+len = length(aggr) )         -- usually ends with comma as well

and instr(substr(aggr, beg, len), ',')=0    -- it has no comma in the middle
;

Also, Tom, how can I easily cut and paste your code without line numbers?

          Followup:

I cut and paste all of my examples from SQLPlus.  It cuts way down on typos.  I
manually reuse my own code in here by using an editor...

I'll not be changing the format -- its too nice having the
code/response/code/response.  Just posting the script and then the response is a
hassle and not as nice.

I use vi, I cut and paste the examples into vi and then a simple:

:1,$s/.*COM>/xxxx/
:1,$s/.....//

gets rid of all of the line numbers and sqlplus prompts.  I just have to delete
" created", "s inserted." and so on after that...

      Rating: 5
missed
      definition  December 13, 2001
Reviewer:  Mikito
      harakiri
0.

CREATE TYPE IntSet AS TABLE OF Integer;
/

      Rating: 4
Another Oracle
      8i workaround  June 17, 2002
Reviewer:  msc
       from Estonia
Here is another 8i workaround which is of course not perfect :-) but suitable
for certain cases.

1. create package which collects strings into pl/sql table for each department.

create or replace package stragg as
  function result(i in integer) return varchar2;
  function collect(i in integer, s in varchar2) return integer;
end;
/

create or replace package body stragg as
  type TStringTable is table of long index by binary_integer;
  tab TStringTable;
  iNullIndex binary_integer := -1;
  bCanEmpty boolean := False;

  function result(i in integer) return varchar2 is
    itmp binary_integer := nvl(i,iNullIndex);
  begin
    bCanEmpty := True;
    if tab.exists(itmp) then
      return tab(itmp);
    else
      return NULL;
    end if;
  end;

  function collect(i in integer, s in varchar2) return integer is
    itmp binary_integer := nvl(i,iNullIndex);
    stmp long := substr(s,1,4000);
  begin
    if bCanEmpty then -- after result(..) we can clean up here
      tab.delete;
      bCanEmpty := False;
    end if;
    if tab.exists(itmp) then
      tab(itmp) := substr( tab(itmp)|| '.' || s, 1, 4000);
    else
      tab(itmp) := stmp;
    end if;
    return 1;
  end;

end;
/

2. use in SQL:

SQL> col names for a50
SQL> select deptno, stragg.result(deptno) as NAMES
  2  from emp
  3  where stragg.collect( deptno, ename) = 1
  4  group by deptno
  5  /

    DEPTNO NAMES
---------- --------------------------------------------------
        10 CLARK.KING.MILLER
        20 SMITH.JONES.SCOTT.ADAMS.FORD
        30 ALLEN.WARD.MARTIN.BLAKE.TURNER.JAMES

SQL>

----
This solutions has one (afaik) pitfall -- one must ensure that stragg.collect is
executed.

Following query does *not* work as expected because Oracle optimizes collect()
function out:

SQL> select deptno, stragg.result(deptno) as NAMES
  2  from (select deptno, stragg.collect( deptno, ename) as COL from EMP)
  3  group by deptno;

    DEPTNO NAMES
---------- --------------------------------------------------
        10
        20
        30

SQL>

(btw if this query happens to show right results then they are from *previous*
execution)

This subquey can be tricked to work with where clause:

SQL> select deptno, stragg.result(deptno) as NAMES
  2  from (select deptno, stragg.collect( deptno, ename) as COL from EMP)
  3  where COL = 1
  4  group by deptno;

    DEPTNO NAMES
---------- --------------------------------------------------
        10 CLARK.KING.MILLER
        20 SMITH.JONES.SCOTT.ADAMS.FORD
        30 ALLEN.WARD.MARTIN.BLAKE.TURNER.JAMES

SQL>

      Rating: 5
Can you use a
      dynamic separator with the 9i solution?  September 30, 2002

Reviewer:  Daryl  from Texas
Is it possible to use the 9i user aggregate solution with a dynamically
specified string to use between the values? In the example, you used comma
(","). Is there a way to specify colons (":") or conjunctions ("or ")  without
using separate types and function for all the different possibilities?

          Followup:

You would have to sort of "prime the pump" on this one.

You could:

dbms_application_info.set_client_info( 'delim=|' )

before running the query and we can use that.  The changes would be:

create or replace type string_agg_type as object
(
   total varchar2(4000),
   delim varchar2(1),
.....

create or replace type body string_agg_type
is

static function ODCIAggregateInitialize(sctx IN OUT string_agg_type)
return number
is
    l_delim  varchar2(1) default ',';
begin
    for x in ( select sys_context('userenv','client_info') client_info from dual
)
    loop
        if ( x.client_info like 'delim=%' )
        then
            l_delim := substr( x.client_info, 7, 1 );
        end if;
    end loop;
    sctx := string_agg_type( null, l_delim );
    return ODCIConst.Success;
end;

....

and then:

ops$tkyte@ORA9I.WORLD> column enames format a25
ops$tkyte@ORA9I.WORLD> select deptno, stragg(ename) enames
  2    from emp
  3   group by deptno
  4  /

    DEPTNO ENAMES
---------- -------------------------
        10 CLARK|KING|MILLER
        20 SMITH|FORD|ADAMS|SCOTT|JO
           NES

        30 ALLEN|BLAKE|MARTIN|TURNER
           |JAMES|WARD

3 rows selected.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> exec dbms_application_info.set_client_info( 'delim=|' )

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> select deptno, stragg(ename) enames
  2    from emp
  3   group by deptno
  4  /

    DEPTNO ENAMES
---------- -------------------------
        10 CLARK|KING|MILLER
        20 SMITH|FORD|ADAMS|SCOTT|JO
           NES

        30 ALLEN|BLAKE|MARTIN|TURNER
           |JAMES|WARD

3 rows selected.

      Rating: 5
Exactly what I
      was looking for  January 07, 2003
Reviewer:
      John
Thanks Tom!!  This is exactly what I was looking for.

I did have one problem.  The type compiled fine but the function did not.    My
version is  8.1.7.3.4 on Windows.

SQL> CREATE or replace
  2  FUNCTION stragg(input varchar2 )
  3  RETURN varchar2
  4  PARALLEL_ENABLE AGGREGATE USING string_agg_type;
  5  /

Warning: Function created with compilation errors.

SQL> show errors
Errors for FUNCTION STRAGG:

LINE/COL ERROR
-------- -----------------------------------------------------------------
3/17     PLS-00103: Encountered the symbol "AGGREGATE" when expecting one
         of the following:
         ; is authid deterministic parallel_enable as

My guess is that it may be a parameter that I do not have set, but I have not
been able to fine it anywhere.

          Followup:

Not in 8i, in 9i yes -- it is very very doable.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:229614022562
for 8i methods (not aggregates)

in 9i,
....

in 9i.....

      Rating: 4
Never
      Mind  January 07, 2003
Reviewer:  John

I read you solution wrong.  I don't 9i so I will have to find another way.

Thanks.

      Rating: 5
Linux
      vi  January 18, 2003
Reviewer:  Jerry
       from Washington, DC USA
"
I use vi, I cut and paste the examples into vi and then a simple:
:1,$s/.*COM>/xxxx/
:1,$s/.....//
"

I suspect that you're a regex purist, but knowing that you use Linux, the Linux
vi has the ability to do a columnar block delete.

You just hit CTRL-V, and "paint" the area you want to delete with the cursor.

Now, if I can just get the mouse to work with it, vi would be a GUI editor...:)

          Followup:

well, there is gvim

try

SQL> define editor="gvim -f"

      Rating: 5
Need an order
      by statement...  February 05, 2003
Reviewer:
      Damian Smith  from UK
This is almost exactly what I want, except that I want to concatenate strings
according to a third column called seq_num.  The order of concatenation doesn't
appear to be predictable.

My source table has deptno, seq_num, ename.  I have tried an in-line view with
an order by clause but it doesn't seem to work:

scott@ORA9I.WORLD> select deptno, stragg(ename)
  2    from (
  3    select deptno, seq_num, ename
  4    from emp
  5    order by deptno, seq_num)
  3   group by deptno
  4  /

Many thanks,
Damian

          Followup:

Just write a custom function instead of an aggregate.  Aggregates are "SET"
things and "sets" really don't have any concept of order.

select deptno, f(deptno) from dept;

where f(deptno) runs your query against EMP with an order by and does what you
want....

      Rating: 3
Using only SQL
      (9i only though : ( )  February 21, 2003
Reviewer:
      Alan Patil  from Reading, UK
You can do this with 9i just using SQL without any PLSQL

Query to generate a list of values from a child table (in CSV format) for every
primary key value in a parent table.

i.e.

if parent table has

id
1
2
3

and the child table has

id     value_id
1      a
1      b
1      c
2      d
3      e
3      f

this query will output

id    list of value_ids
1     a,b,c
2     d
3     e,f

The only issue I can think of is what happens if the list is longer than the
varchar2 limit of 4000 bytes.

Obviously this only works with 9i due to the SYS_CONNECT_BY_PATH : (.

-- get the longest csv list of values for each id
select
id,
SUBSTR(MAX(list) KEEP (DENSE_RANK FIRST ORDER BY (lev) desc),2) as lov
from
(
        -- create a tree and use sys_connect_by_path to create a csv list of
values
        select id , SYS_CONNECT_BY_PATH(value_id, ',') list, level lev
        from
        (
                -- create a result set with a linked list column for every id so
that we can create a hierarchical tree with the next query
                select r.id, i.value_id, rownum prow,
                LAG(rownum, 1) OVER (PARTITION BY i.id ORDER BY value_id) as
connect_id
                from parent_table r, child_table i
                where r.id = i.id
        ) r
        START WITH connect_id is null
        CONNECT BY PRIOR prow=connect_id
)
group by id
order by id

          Followup:

...
The only issue I can think of is what happens if the list is longer than the
varchar2 limit of 4000 bytes.
.....

use a clob then -- it works, i've done that.

don't understand the spurious reference to sys_connect_by_path???  stragg seems
a bit easier?

      Rating: 3
A straight sql
      solution  February 21, 2003
Reviewer:  Jack
      Mulhollan  from Little Rock, AR USA
If the max number of values within each group is finite, known, and relatively
small, then straight sql will do.  For example, my hypothetical company has a
maximum number of five employees per department:

SQL> ---------------------
SQL> -- View sample table.
SQL> ---------------------
SQL>
SQL> select dept,
  2          emp_name
  3  from temp_01_tb
  4  order by 1,2
  5  ;

   DEPT  EMP_NAME
-------  --------------------
      1  Bob
      1  David
      1  Sally
      1  Sam
      2  Jane
      2  Jennifer
      2  Joe
SQL>
SQL> -------------------------
SQL> -- Perform string concat.
SQL> -------------------------
SQL>
SQL> select dept,
  2          max(decode(my_seq,1,emp_name)) || ' ' ||
  3          max(decode(my_seq,2,emp_name)) || ' ' ||
  4          max(decode(my_seq,3,emp_name)) || ' ' ||
  5          max(decode(my_seq,4,emp_name)) || ' ' ||
  6          max(decode(my_seq,5,emp_name)) emp_list
  7  from (
  8        select dept,
  9           emp_name,
 10           row_number() over
 11             (partition by dept order by emp_name) my_seq
 12        from temp_01_tb
 13        )
 14  group by dept
 15  order by 1
 16  ;

   DEPT  EMP_LIST
-------  --------------------------------------------
      1  Bob David Sally Sam
      2  Jane Jennifer Joe

          Followup:

Yes, that works as well -- very similar to the example I have in expert one on
one Oracle to demonstrate pivots.

but it is funny -- once you install stragg -- it is "straight sql" as well.

      Rating: 4
analytic sql
      solutions are disgusting  February 21, 2003

Reviewer:  Mikito Harakiri
db1>with ordemp as (select * from emp order by deptno),
  2       seqemp as (select rownum rn, ordemp.* from ordemp),
  3       startfinish as (select min(rn) low, max(rn) high, deptno from seqemp
ee group by deptno),
  4    paths as (
  5    select sys_connect_by_path(ename,',') concat, deptno, rn
  6    from seqemp o
  7    connect by rn = prior rn + 1 and deptno = prior deptno
  8    start with rn in (select low from startfinish s where s.deptno =
o.deptno)
  9  ) select concat, deptno from paths p where rn in (select high from
startfinish s where s.deptno = p.deptno);

CONCAT
---------------------------------------------------------------------------------
----------------------------------
    DEPTNO
----------
,CLARK,KING,MILLER
        10

,SMITH,ADAMS,FORD,SCOTT,JONES
        20

,ALLEN,BLAKE,MARTIN,JAMES,TURNER,WARD
        30

db1>

          Followup:

You are amusing sometimes with your analysis and comments you know ;)

Analytic functions ROCK AND ROLL -- coolest thing to happen to SQL since the
keyword select.  I find AT LEAST 2 or 3 answers a day involve (gasp) analytics.

This will not be the first time I take one of your SQL only solutions and show
has dastardly slow, hard to read, unfathomable in the real world they actually
are.

I find queries that are

o really fast
o readable
o easy to maintain, develop

to be slightly less digusting then queries that

o are not any of the above ;)

Now, I cannot see how ANY index could be used with your psuedo procedural
approach (it is so procedural as to be scary -- looks almost like a SAS program
except it is using SELECT instead of proc print; to show the end result).

You sort emp by deptno (all of the rows).
You then assign a rownumber to those ordered rows...
you then find the mininum and maximum rownums by deptno and then procede to
query...

So, lets benchmark shall we?  Well use all three approaches.  Then, you tell us
which is "disgusting"

So, I'll start with:

drop table emp;

create table emp
as
select object_id empno, substr(object_name,1,10) ename, object_type job,
data_object_id mgr,
       created hiredate, object_id sal, object_id comm, mod(rownum,500) deptno
  from all_objects
/

Just to generate some data.  TKPROF says:

select deptno,
         max(decode(my_seq, 1,ename)) || ' ' ||
         max(decode(my_seq, 2,ename)) || ' ' ||
         max(decode(my_seq, 3,ename)) || ' ' ||
         ...... 4..67 snipped
         max(decode(my_seq,68,ename)) || ' ' ||
         max(decode(my_seq,69,ename)) emp_list
 from (
       select deptno,
          ename,
          row_number() over
            (partition by deptno order by ename) my_seq
       from emp
       )
 group by deptno
 order by 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       35      1.12       1.13          0        237          0         500
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       37      1.13       1.14          0        237          0         500

Rows     Row Source Operation
-------  ---------------------------------------------------
    500  SORT GROUP BY
  32498   VIEW
  32498    WINDOW SORT
  32498     TABLE ACCESS FULL OBJ#(42644)

so, analytics, while limited in this case as you have to sort of know the MAX
number of elements, perform quite outstanding here -- don't they (especially as
we move on)

********************************************************************************
select deptno, stragg(ename) from emp group by deptno order by 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       35      4.70       4.93          0        240          0         500
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       37      4.70       4.94          0        240          0         500

Rows     Row Source Operation
-------  ---------------------------------------------------
    500  SORT GROUP BY
  32498   TABLE ACCESS FULL EMP

by far the simpliest solution but a tad more cpu intensive on this set... but
wait:

with ordemp as (select * from emp order by deptno),
     seqemp as (select rownum rn, ordemp.* from ordemp),
     startfinish as (select min(rn) low, max(rn) high, deptno from seqemp ee
group by deptno),
 paths as (
  select sys_connect_by_path(ename,',') concat, deptno, rn
  from seqemp o
  connect by rn = prior rn + 1 and deptno = prior deptno
  start with rn in (select low from startfinish s where s.deptno = o.deptno))
select concat, deptno
  from paths p
 where rn in (select high from startfinish s where s.deptno = p.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          6           0
Fetch       35     13.97      15.69          2     259435          1         500
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       37     13.97      15.70          2     259435          7         500

Rows     Row Source Operation
-------  ---------------------------------------------------
    500
    500  FILTER
  32498   VIEW
  32498   CONNECT BY WITHOUT FILTERING
  32498    COUNT
  32498    VIEW
  32498     TABLE ACCESS FULL SYS_TEMP_4254950912
    500    VIEW
16124250      TABLE ACCESS FULL SYS_TEMP_4254950913
    500   VIEW
16124250       TABLE ACCESS FULL SYS_TEMP_4254950913

that is a query I wouldn't like to see on my system -- slightly too many IO's
for my taste and it really chomps up the CPU doesn't it?

So, here is what I would say if you need to do this:

o have a known finite number of elements?  row_number() is looking pretty good
o have an unknown or just looking for the easiest query and you find the result
sets you are going against perform more then well enought?  stragg
o your psuedo procedural approach?  I don't think so.....

But -- you are free to keep ignoring the existence of language features that can
greatly increase the performance of your system all you want....

      Rating:
      5
  April 07, 2003
Reviewer:  Arun
      Mathur  from Marietta,GA
Tom,

This is a great site. Would you mind explaining how the PARALLEL_ENABLE option
works?

Regards,
Arun

          Followup:

It permits the user defined aggregate to be called from Parallel query.

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96540/statements_59a.htm#2075083

      Rating:
      5
sys_connect_by_path  July 30, 2003

Reviewer:  A reader
Tom Can you please explain the post by Alan Patil  where he uses
sys_connect_by_path ?

          Followup:

which part needs explaining?  sys_connect_by_path is documented in the 9i doc
set if that is it?

      Rating: 4
analytical sql
      is not intuitive  July 31, 2003
Reviewer:
      mikito harakiri
Why

select deptno,
   ename,
   sum(1) over
     (partition by deptno order by deptno) my_seq
from emp

returns 3,3,3,5,5,5,5,5,6,6,6,6,6,6

while

select deptno,
   ename,
   sum(1) over
     (partition by deptno order by empno) my_seq
from emp

returns

1,2,3,1,2,3,4,5,1,2,3,4,5,6

?

          Followup:

because you added "order by"

the order by makes things "the same".  you order by deptno -- well, isn't
everything with the same deptno the same? (rhetorical question -- YES)

when you order by empno -- they are NOT the same (empno happens to be unique) so
the sum is a pure cumulative total.

using sum(1) is "not intuitive" -- i believe you truly meant to use the correct
and proper "count(*)" syntax.  here is an example:

scott@ORA920LAP> select deptno, ename,
  2         count(*) over (partition by deptno order by deptno) cnt1,
  3         count(*) over (partition by deptno order by deptno, ROWID) cnt2,
  4         count(*) over (partition by deptno order by empno) cnt3,
  5         count(*) over (partition by deptno order by job) cnt4
  6    from emp
  7  /

    DEPTNO ENAME            CNT1       CNT2       CNT3       CNT4
---------- ---------- ---------- ---------- ---------- ----------
        10 CLARK               3          1          1          2
        10 KING                3          2          2          3
        10 MILLER              3          3          3          1

 see with order by deptno -- cnt1 -- all of the rows are "the same".  with
order by deptno, rowid -- they are "all different" -- rowid makes them
different.

order by empno - same thing since empno just happens to be unique.

order by job, boring in this case -- job was "unique"

        20 SMITH               5          1          1          4
        20 JONES               5          2          2          5
        20 SCOTT               5          3          3          2
        20 ADAMS               5          4          4          4
        20 FORD                5          5          5          2

oh, now cnt 4, order by job, is interesting.  apparently ford and scott have
the same job, adams and smith have the same job, and jones is unique.  since the
jobs were the same -- the rows in the window defined by the order by are the
SAME

        30 ALLEN               6          1          1          6
        30 WARD                6          2          2          6
        30 MARTIN              6          3          3          6
        30 BLAKE               6          4          4          2
        30 TURNER              6          5          5          6
        30 JAMES               6          6          6          1

14 rows selected.

sorry you don't find it "intuitive" -- but it is the way it has to work.

      Rating: 5
FUNCTION stragg
      in package  August 14, 2003
Reviewer:  fan
       from PA
>>scott@ORA9I.WORLD> CREATE or replace
  2  FUNCTION stragg(input varchar2 )
  3  RETURN varchar2
  4  PARALLEL_ENABLE AGGREGATE USING string_agg_type;
  5  /

Tom, how do I properly put this function in a package ?
Thanks

          Followup:

don't believe you can, it is just a 'definition', no code really.

      Rating: 3
are analytical
      queries powerful?  August 22, 2003
Reviewer:
      Mikito Harakiri
I'm still evaluating analytical functions:-)

Here is an exersise. Given
table commissions
        RN ENAME            COMM
---------- ---------- ----------
         1 ADAMS               0
         2 ALLEN             300
         3 BLAKE               0
         4 CLARK               0
         5 FORD                0
         6 JAMES               0
         7 JONES               0
         8 KING                0
         9 MARTIN           1400
        10 MILLER              0
        11 SCOTT               0
        12 SMITH               0
        13 TURNER              0
        14 WARD              500

The output should be

     begin        end       COMM
---------- ---------- ----------
         1          1          0
         2          2        300
         3          8          0
         9          9       1400
        10         13          0
        14         14        500

Clearly, commission should span continuous ranges only, but how would I specify
that as a window condition?

I have no problems with the other SQL constructs: they scale. An subquery can be
used at any place where expression is needed, which gives enormous
expressiveness. On the other hand, window syntax looks like has been invented to
solve one particular problem, and there little opportunity to use it outside
this very specific context. You can refute this ramble with just one query, of
course:-)

          Followup:

"clearly" "clearly what????  continous ranges of what?  I've no idea what your
basic question is -- why should the output be that?  what is the question you
are trying to answer -- and more importantly where is the subquery query you
thing is "better"

i give up.  Mikito -- do what you will.  Like what you like.

I'll write fast queries that perform and scale well.  I'll be using analytics
out the whazoo to do it.  I'll drop down to relatively inefficient subqueries
(in general, not always) when I have to.

      Rating: 5
how bout
      ODCITABLE ?  November 19, 2003
Reviewer:
      ant  from ny
Tom,
 Stragg, using the ODCI, is very cool. I came across the ODCITABLE interface and
thought it was interesting.
I noticed that stragg outperforms a "regular" plsql function that does pretty
much the same operation
and was wondering if a function using ODCITABLE interface would outperform a
pipelined function.
I did a search for ODCITABLEFETCH on metalink and it returned no results. I got
all of 4 hits ( all to the oracle doc ) from google. Can a function using
ODCITABLE be used to "stream" result sets like pipelined functions ?

The ODCI doc doesn't have a complete example using just pl/sql and the ODCI
interface ( the examples use OCI and JAVA ).
Can you provide an example that uses the ODCITABLE interface and pl/sql ( no OCI
or JAVA calls ) ?

thanks,
 ant

      Rating: 4
An exception
      with sys views?  November 20, 2003
Reviewer:
      Alex Taylor  from Canberra, Australia
All good, however it doesn't work on my 9.2.0.4 database for the this view:
select ADDRESS,stragg(sql_text)
from v$sqltext
where address = '0000000383E4FCA8'
group by ADDRESS;

The aggregate value includes only the last sql_text value of 6 values. Is there
something funny with sys view?

          Followup:

don't know what you mean by "only the last sql_text value of 6 values"

give us the "example", show the unaggregated data and the aggregated data and
point out what is "wrong"

      Rating: 4
Analytic
      Functions are VERY powerful - get used to it.  November 20,
      2003
Reviewer:  Glen Morris  from Canberra, Australia

I tried out the 'challenge' from Mikito above and used Analytic Functions. This
may not be the most elegant method but since the requirement wasn't stated
clearly I'll forgive myself. I've assumed the rown wasn't actually in the source
table. If it turns out to be then the query is simpler and just need to replace
all occurences of 'row_number() over (order by ename)' with 'rown'.

SQL> create table zz_comm
  2  (ename varchar2(15),comm number);

Table created.

SQL>
SQL> insert into zz_comm values ('ADAMS',0);

1 row created.

SQL> insert into zz_comm values ('ALLEN',300);

1 row created.

SQL> insert into zz_comm values ('BLAKE',0);

1 row created.

SQL> insert into zz_comm values ('CLARK',0);

1 row created.

SQL> insert into zz_comm values ('FORD',0);

1 row created.

SQL> insert into zz_comm values ('JAMES',0);

1 row created.

SQL> insert into zz_comm values ('JONES',0);

1 row created.

SQL> insert into zz_comm values ('KING',0);

1 row created.

SQL> insert into zz_comm values ('MARTIN',1400);

1 row created.

SQL> insert into zz_comm values ('MILLER',0);

1 row created.

SQL> insert into zz_comm values ('SCOTT',0);

1 row created.

SQL> insert into zz_comm values ('SMITH',0);

1 row created.

SQL> insert into zz_comm values ('TURNER',0);

1 row created.

SQL> insert into zz_comm values ('WARD',500);

1 row created.

SQL> commit;

Commit complete.

SQL>
SQL> select start_range
  2        ,nvl(lead(start_range,1) over (order by rown) -1,start_range)
end_range
  3        ,comm
  4  from (select row_number() over (order by ename) rown
  5              ,comm
  6              ,case when comm <> nvl(lag(comm,1) over (order by ename),-1)
  7                         then row_number() over (order by ename) else null
end start_range
  8          from zz_comm
  9       )
 10  where start_range is not null;

START_RANGE  END_RANGE       COMM
----------- ---------- ----------
          1          1          0
          2          2        300
          3          8          0
          9          9       1400
         10         13          0
         14         14        500

6 rows selected.

SQL> spool off

      Rating: 4
Awsome STRAGG,
      how to explicitly order the result?  January 09, 2004

Reviewer:  Antonio  from Houston, TX
Excellent, STRAGG simplified most everything.

Two questions though:

1.  How to specify the order of the data returned by

select stragg(text_message) as "long_message" from messages;

long_message
-------------------------------------
message3,messag2,message4,message1

The table MESSAGES has columns  ID, text_message, usage.
How can I have the results ordered by the ID column (or other column) to get
something like

select stragg(text_message, "ordered by ID or group by ID or where something)
from messages

long_message
------------------------------------
message1,message2,message3,message4

2.  What is the best resource(s) for me to read to get up to speed on your
example to "understand" what the ODCIAggregate* are and to use/apply on my own?

          Followup:

1) you cannot -- that is one of the "issues" with aggregates.  they work on
unordered sets.  Most likely the reason there is no such thing as "stragg" in
the "real database" since the results are somewhat indepterminate as far as
order goes.

ROW_NUMBER() is deterministic however since you assign the numbers after
ordering.

2) the data cartridge developers guide and application developers guide, both
available on otn.oracle.com

      Rating: 3
Handy function
      for sorting elements in a string  January 12, 2004

Reviewer:  Gary  from Sydney, Aus
In a pinch, the following may be useful. It splits a string by a delimiter,
stores the elements in an associative array, then re-extracts them in a sorted
order, described in the PL/SQL User's Guide as :

"For associative arrays with VARCHAR2 keys, these methods (NEXT and PRIOR)
return the appropriate key value; ordering is based on the binary values of the
characters in the string, unless the NLS_COMP initialization parameter is set to
ANSI, in which case the ordering is based on the locale-specific sort order
specified by the NLS_SORT initialization parameter."

Since Associative arrays are a 9i feature, this cannot be ported to 8i or
earlier (unless you implement your own sort code)

create or replace function sort_string
  (p_string in varchar2, p_delim in varchar2,
  p_dup in varchar2 default 'I') return varchar2 is
  type t_sort_tab is table of number(4) index by varchar2(4000);
  t_sort t_sort_tab;
  v_start number := 1;
  v_end number := 0;
  v_ext varchar2(4000);
  v_out varchar2(4000);
begin
  while v_end < length(p_string) loop
    v_end := instr(p_string,p_delim,v_start);
    if v_end = 0 then
      v_end := length(p_string)+1;
    end if;
    v_ext := substr(p_string,v_start,v_end-v_start);
    if t_sort.exists(v_ext) THEN
      t_sort(v_ext) := t_sort(v_ext) + 1;
    else
      t_sort(v_ext) := 1;
    end if;
    v_start := v_end + length(p_delim);
  end loop;
  v_ext := t_sort.first;
  WHILE v_ext is not null loop
    IF p_dup = 'I' THEN
      -- Option A - Ignore duplicates (treat as single occurrence)
      v_out := v_out||v_ext||p_delim;
    ELSIF p_dup = 'N' THEN
      -- Option B - Add number of entries after value in brackates
      v_end := t_sort(v_ext);
      v_out := v_out||v_ext||'('||to_char(v_end)||')'||p_delim;
    ELSIF p_dup = 'D' THEN
      -- Option C - Repeat duplicated entries
      v_end := t_sort(v_ext);
      FOR i in 1..v_end loop
        v_out := v_out||v_ext||p_delim;
      END LOOP;
    END IF;
    v_ext := t_sort.next(v_ext);
  END LOOP;
  v_out := substr(v_out,1,instr(v_out,p_delim,-1)-1);
  return v_out;
end;

      Rating: 5
Sorting the
      results  January 13, 2004
Reviewer:  Noam
      Tene  from McLean, VA
This is exactly what I have been trying to do for a while.

My problem was that (as Tom says) aggregates are by definition unsorted.  It
gets even worse because with parallel execution the order in which
ODCIAggregateMerge happens is unspecified and may even be non-deterministic.   I
am not sure the results are even reproduceable.

In cases where the results is going to be grouped by the value returned by
stragg, it is often desireable to treat
"CLARK,KING,MILLER" and "KING,CLARK,MILLER"
as the same combination and to do that it would be useful of the result were
lexicographically sorted.

The sort_string function from Gary was the missing piece.
I wonder if there is some way to create the associative array in
ODCIAggregateInitialize and generate the sorted result in
ODCIAggregateTerminate.

That would make the solution much more elegant.

      Rating: 4
using sragg in
      Crystal report  February 06, 2004
Reviewer:
      A reader
I use view with stragg function in Crystal report.
And I had to work with this field, but Crystal report prompted: Can't use blob
in formula.
I wonder why blob if return value varchar2?

          Followup:

I supposed you would have to call Crystal Reports and ask them why?

      Rating: 4
Comment to
      Glenn's solution  February 09, 2004
Reviewer:
      Mikito Harakiri
Yes, lag analytic function is critical. Or we can join with c1.rn=c2.rn-1
predicate. Analytical functions are for join-challenged folks!

More significant objection, however, is that analytic functions frequently work
together with "group by". And in my example I intuitively expect "group by"
accociated with those range buckets. Instead, you filtered out the rows with
some predicate.

          Followup:

Analytical functions are for those who crave, desire or just sometimes NEED
speed.

      Rating: 5
Instant
      Gratification  February 20, 2004
Reviewer:
      Kevin Meade  from Connecticut USA
Thanks once more Tom.  Had a guy call me minutes ago who wanted "a way in 9i to
concatinate strings as an aggregate function, can this be done?".

Natrually being a very experienced IM guy I did what any experienced
developer/dba/architect worth his salt would do; I stole from somebody else who
is better at it than I am.

Damn if I don't look good.  If you could have heard him: "wow, I have never seen
anything like this before!, this is amazing!, where do you get this stuff?".

I plan on spending some time with him to make sure he verses himself with the
technology before he does anything real with it.  Can't have people using code
they don't understand.

Did I tell you this is now my favorite site on the internet?

Kevin

February 10, 2004 -- 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"

      Rating: 4
CLOB to
      LONG??  February 10, 2004
Reviewer:  Anirudh
       from New Delhi, India
Hi Tom,
I am stcuk up with a requirement wherein I need to populate a LONG column at
remote site with the data in our CLOB column. 8i doesn't support conversion of
LOBs to LONGs, I guess.
I am getting a datatype mismatch when doing an INSERT...SELECT..
do I need to create a function which would return me LONG and in that function I
would be taking my CLOB data and putting it to a LONG variable piece by piece...
Please share your viewpoint on this.
Anirudh

          Followup:

nothing will support lobs to longs.  longs to lobs -- sure.  never the other
direction.

if the clob is ALWAYS LESS THAN 32k in size, you can use plsql..

if the clob exceeds 32k -- you will HAVE to write a 3gl program in C, Java,
whatever to do this.

February 10, 2004 Saminathan -- Thanks for the question regarding
      "Index on Domain Index table.", version 9.2.0.4

      You Asked
Dear Tom,

Version 9.2.0.4

[1]
create index emp_domain_ind on employees(resume) indextype is ctxsys.context;

This will create

DR$EMP_DOMAIN_IND$I
DR$EMP_DOMAIN_IND$K
DR$EMP_DOMAIN_IND$N
DR$EMP_DOMAIN_IND$R

Also this one created DR$EMP_DOMAIN_IND$X index like below

[2]create index DR$EMP_DOMAIN_IND$X on
DR$EMP_DOMAIN_IND$I(TOKEN_TEXT,TOKEN_TYPE,TOKEN_FIRST,TOKEN_LAST,TOKEN_COUNT);

[Q1]
Is the behavior same in 8.1.7.3? Or do we have to create (step [2])manually? coz
my co-worker provided script to create this step[2] but I don't have 8.1.7.3 db
available for testing

[Q2]
Is it better practice to keep separate tablespace(separate from data and
ordinary index) for domain indexes? What is your recommendation?

      and we said...
q1) you should never touch the domain index tables, you would not be adding your
own index.

if the create index statement did it, good -- OK
if the create index statement did not, good -- it wasn't supposed to

it is not anything you would have to do yourself.

q2) nope, i put applications in a single tablespace and use raid striping to
evenly distribute IO.  I use tablespaces for what they were designed for -- as
an administrative tool.  By putting small/medium apps in a single tablespace --
you make it easy to admin them individually.  Only if the app got pretty "large"
(many gigabytes) would I break it out -- and then by size, not by type.

February 10, 2004 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

      Rating: 4
More on
      DBMS_DESCRIBE  February 10, 2004
Reviewer:
      A reader  from UK
How can this package be used in Stored procs to describe some objects?

          Followup:

as it gives the same output as ALL_ARGUMENTS and I find many times SQL to be
easier then a bunch of plsql tables -- i would suggest you use that documented
view.

short of that, we document all supplied packages in a supplied packages guide:

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96612/d_descrb.htm#998100

February 10, 2004 -- 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.

      Rating: 5
tkprof and
      stored procedure  February 06, 2004
Reviewer:
      Igor  from France
Hi

I hope it's not too much out of topic.

In one tkprof there are:

BEGIN mps_insert_cdr_data(v_msg_id=>:v_msg_id,v_msg_id2=>:v_msg_id2,
  v_msg_id_serv=>:v_msg_id_serv,v_split_row_num=>:v_split_row_num,
  v_ext_tracking_id=>:v_ext_tracking_id,v_trans_id=>:v_trans_id,v_element_id=
  >:v_element_id,v_type_id_usg=>:v_type_id_usg,v_orig_type_id_usg=
  >:v_orig_type_id_usg,v_rate_class=>:v_rate_class,v_bill_class=
  >:v_bill_class,v_provider_id=>:v_provider_id,v_jurisdiction=
  >:v_jurisdiction,v_rate_currency_code=>:v_rate_currency_code,v_account_no=
  >:v_account_no,v_subscr_no=>:v_subscr_no,v_subscr_no_resets=
  >:v_subscr_no_resets,v_external_id=>:v_external_id,v_external_id_type=
  >:v_external_id_type,v_point_origin=>:v_point_origin,v_point_id_origin=
  >:v_point_id_origin,v_country_code_origin=>:v_country_code_origin,
  v_country_dial_code_origin=>:v_country_dial_code_origin,
  v_point_tax_code_origin=>:v_point_tax_code_origin,
  v_point_tax_code_type_origin=>:v_point_tax_code_type_origin,
  v_cell_id_origin=>:v_cell_id_origin,v_point_target=>:v_point_target,
  v_point_id_target=>:v_point_id_target,v_country_code_target=
  >:v_country_code_target,v_point_tax_code_target=>:v_point_tax_code_target,
  v_point_tax_code_type_target=>:v_point_tax_code_type_target,v_base_amt=
  >:v_base_amt,v_rate_dt=>:v_rate_dt,v_trans_dt=>:v_trans_dt,v_second_dt=
  >:v_second_dt,v_timezone=>:v_timezone,v_primary_units=>:v_primary_units,
  v_second_units=>:v_second_units,v_third_units=>:v_third_units,
  v_units_currency_code=>:v_units_currency_code,v_billing_units_type=
  >:v_billing_units_type,v_ccard_id=>:v_ccard_id,v_ccard_id_serv=
  >:v_ccard_id_serv,v_federal_tax=>:v_federal_tax,v_state_tax=>:v_state_tax,
  v_county_tax=>:v_county_tax,v_city_tax=>:v_city_tax,v_other_tax=
  >:v_other_tax,v_annotation=>:v_annotation,v_customer_tag=>:v_customer_tag,
  v_rated_units=>:v_rated_units,v_total_amt=>:v_total_amt,v_foreign_amount=
  >:v_foreign_amount,v_rate_period=>:v_rate_period,v_no_bill=>:v_no_bill,
  v_comp_status=>:v_comp_status,v_cdr_status=>:v_cdr_status,v_bill_ref_no=
  >:v_bill_ref_no,v_bill_ref_resets=>:v_bill_ref_resets,v_file_id=>:v_file_id,
  v_file_id_serv=>:v_file_id_serv,v_rev_rcv_cost_ctr=>:v_rev_rcv_cost_ctr,
  v_arch_flag=>:v_arch_flag,v_unrounded_amount=>:v_unrounded_amount,
  v_corridor_plan_id=>:v_corridor_plan_id,v_access_region_origin=
  >:v_access_region_origin,v_access_region_target=>:v_access_region_target,
  v_open_item_id=>:v_open_item_id,v_num_records=>:v_num_records,
  v_amount_reduction=>:v_amount_reduction,v_amount_reduction_id=
  >:v_amount_reduction_id,v_seqnum_rate_usage=>:v_seqnum_rate_usage,
  v_seqnum_rate_usage_overrides=>:v_seqnum_rate_usage_overrides,v_raw_units=
  >:v_raw_units,v_raw_units_rounded=>:v_raw_units_rounded,v_aggr_usage_id=
  >:v_aggr_usage_id); END;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse      354      0.19       0.29          0          0          0           0
Execute    708     34.19      35.78          0          0          0       45236
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1062     34.38      36.07          0          0          0       45236

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: 31  (ARBOR)
********************************************************************************

INSERT INTO CDR_DATA ( MSG_ID,MSG_ID2,MSG_ID_SERV,SPLIT_ROW_NUM,
  EXT_TRACKING_ID,TRANS_ID,ELEMENT_ID,TYPE_ID_USG,ORIG_TYPE_ID_USG,RATE_CLASS,
  BILL_CLASS,PROVIDER_ID,JURISDICTION,RATE_CURRENCY_CODE,ACCOUNT_NO,SUBSCR_NO,
  SUBSCR_NO_RESETS,EXTERNAL_ID,EXTERNAL_ID_TYPE,POINT_ORIGIN,POINT_ID_ORIGIN,
  COUNTRY_CODE_ORIGIN,COUNTRY_DIAL_CODE_ORIGIN,POINT_TAX_CODE_ORIGIN,
  POINT_TAX_CODE_TYPE_ORIGIN,CELL_ID_ORIGIN,POINT_TARGET,POINT_ID_TARGET,
  COUNTRY_CODE_TARGET,POINT_TAX_CODE_TARGET,POINT_TAX_CODE_TYPE_TARGET,
  BASE_AMT,RATE_DT,TRANS_DT,SECOND_DT,TIMEZONE,PRIMARY_UNITS,SECOND_UNITS,
  THIRD_UNITS,UNITS_CURRENCY_CODE,BILLING_UNITS_TYPE,CCARD_ID,CCARD_ID_SERV,
  FEDERAL_TAX,STATE_TAX,COUNTY_TAX,CITY_TAX,OTHER_TAX,ANNOTATION,CUSTOMER_TAG,
  RATED_UNITS,AMOUNT,FOREIGN_AMOUNT,RATE_PERIOD,NO_BILL,COMP_STATUS,
  CDR_STATUS,BILL_REF_NO,BILL_REF_RESETS,FILE_ID,FILE_ID_SERV,
  REV_RCV_COST_CTR,ARCH_FLAG,UNROUNDED_AMOUNT,CORRIDOR_PLAN_ID,
  AMOUNT_REDUCTION,AMOUNT_REDUCTION_ID,ACCESS_REGION_ORIGIN,
  ACCESS_REGION_TARGET,OPEN_ITEM_ID,NUM_RECORDS,SEQNUM_RATE_USAGE,
  SEQNUM_RATE_USAGE_OVERRIDES,RAW_UNITS,RAW_UNITS_ROUNDED,AGGR_USAGE_ID )
VALUES
 ( :b1,:b2,:b3,:b4,:b5,:b6,:b7,:b8,:b9,:b10,:b11,:b12,:b13,:b14,:b15,:b16,
  :b17,:b18,:b19,:b20,:b21,:b22,:b23,:b24,:b25,:b26,:b27,:b28,:b29,:b30,:b31,
  :b32,:b33,:b34,:b35,:b36,:b37,:b38,:b39,:b40,:b41,:b42,:b43,:b44,:b45,:b46,
  :b47,:b48,:b49,:b50,:b51,:b52,:b53,:b54,:b55,:b56,:b57,:b58,:b59,:b60,:b61,
  :b62,:b63,:b64,:b65,:b66,:b67,:b68,:b69,:b70,:b71,:b72,:b73,:b74,:b75,:b76
  )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute  45236     83.24    1039.81      48757       7029     710660       45236
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    45236     83.24    1039.81      48757       7029     710660       45236

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: 31  (ARBOR)   (recursive depth: 1)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  INSERT STATEMENT   GOAL: RULE

Could you comment:

1) Number of rows [for stored procedure] (45236) is multiply of executions ( 708
) => that probably comes from  passing parameters in some sort of array (like
bulk in PLSQL) on some level. I suppose that application does that batch things
in OCI level (it use OCI) ?

2) Total time of INSERT is much less than complete procedure [ and yes, disk is
saturated, and RULE should change but that's another story  ]

          Followup:

1) probably passing plsql table types -- a simple "describe" one the procedure
would confirm that.

2) it is much "more" actually.  rule vs cost isn't going to affect an INSERT ..
VALUES statement AT ALL.

Would would materially benefit this is if the stored procedure doing the insert
used FORALL bulk inserts instead of row by row inserts.

      Rating: 2
No batch inside
      procedure  February 09, 2004
Reviewer:  Igor
       from France
1) Actually, that doesn't hold. Procedure has as parameters just numbers,
varchar2, etc.
Inside procedure it does a simple INSERT ... VALUES(...) with input arguments.
I'm unable to see any bulk processing in trace file...
So, I thought that that might be some bulk thing that does not use pl/sql bulk
way, but on some other layer...

2) sorry for unlucky expression. Yes, I just wanted to avoid this "bypass"
comments...

          Followup:

well, it is also clear from the tkprof that your tracing was done AFTER the
application started using this procedure (the insert was not parsed) -- hence
the tkprof does not cover the entire execution of this routine.

they could be array binding in the client.  if they are -- performance would be
improved by sending PLSQL tables to the stored procedure and having the
procedure use FORALL to insert.

      Rating:
      3
  February 10, 2004
Reviewer:  Igor
       from France
Hi, again

Good idea, but...
Instance restarted with SQL_trace=true.
Launched again but no sign of array processing traced...

          Followup:

they could be array binding from the client as stated above.  it was just an
aside that exact number matches cannot be entirely expected on that trace since
it was started AFTER the application started.

they are most likely array binding on the outside, it would behoove them to pass
arrays and let you array bind with forall on the "inside"

why don't you ask them?

      Rating:
      4
  February 13, 2004
Reviewer:  Igor
       from France
3rd party tool. Getting noise with info.
Thank you

February 10, 2004 Scott -- Thanks for the question regarding
      "function evaluation in where clause", version 8.1.7

      You Asked
G'day Tom.

I have a question relating to when and why functions would be evaluated if
present in the where clause of a query.

Consider
create function foo return number;
begin
  null;
end;

This function will return an exception when called, so it should highlight when
it is being evaluated in the following queries:
SELECT * FROM DUAL WHERE 1=2 AND FOO = NULL;

Here, I would not expect foo to be called, since 1 does not equal 2, and the
second expression in the AND should not need to be evaluated.
But it is called, however if I vary the order of the where clause:
SELECT * FROM DUAL where  FOO = NULL and 1=2

the statement executes successfully.
The same phenomena occurs with
SELECT * FROM DUAL WHERE 1=2 OR FOO = NULL;

Is the order of evaluation something that can be predicted, or can you further
explain Oracle's "thought" processes in these cases?

Because at a glance, a developer reading the code from left to right may
consider it differently...

Another point of interest is the evaluation of this:
SELECT * FROM DUAL WHERE NULL = FOO;
If NULL and an operator such as =  or != is encountered, it doesn't even attempt
the other side of the expression.

Thanks Tom.

      and we said...
the order of evaluation is something that cannot only NOT BE predicted -- but
could change from execution to execution (eg: the order one day need not be the
same tomorrow).

SQL, by its very "non procedural" definition neither asserts, nor documents any
"order of predicate evaluation"

do not even try to use inline views/whatever to "trick it" as we can merge
inline views into the larger sql statement -- so:

select *
  from ( select * from dual where 1=2 )
 where foo IS null;

does not imply 1=2 first, foo IS null (never say = null, that is never true, not
is it ever false, it is simply "unknown") second.

If you have zero divide issues, or need "short circut evaluation", you can use
decode and CASE

   select decode( denom, 0, to_number(null), numer/denom )

is "safe" (in a where clause or select list) but:

   select numer/denom
     from t
    where denom != 0;

or

   where denum != and numer/denom > 5

are both "not so safe".  A simple upgrade, change in the access path, a gather
stats -- etc, is free and able to totally change the order of operation.
Review &
      Followup

      Rating:
      5
Hints  February 10, 2004
Reviewer:
      Connor  from Perth
There's always the ORDERED_PREDICATES hint...although thats probably not the
option you'd want to be considering as an extensive part of your applications

hth
Connor

          Followup:

http://www.google.com/search?q=define%3A+hint&sourceid=mozilla-search&start=0&start=0&ie=utf-8&oe=utf-8
hints = not a good way to do this IMO.  A hint is just that -- a hint ;)  I
would not use it to assure some predicable order of operation.

February 10, 2004 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.

      Rating: 4
Help
      Please  February 10, 2004
Reviewer:
      A reader
Hi, Tom,
We are currently having NUMBER sequences as our order number for all type of
products(perfect now). Now, things are going to be changed: for some type of
products, it will be number, others are alphanumberic. And the thing is that it
still could be changed in the future, but we need to keep the production DBs up
and running no matter how it changes.
What we plan to do is: change the orderNumber type to VARCHAR2(80) from NUMBER;
create a table including product types, categories, orderID, orderIDType, ...
and reference this table based on the product type and categories whenever we
want to create the new order(and generate new orderNumbers either numeric or
aplphanumeric). Problem: the alphanumeric orderNumber is fixed length, say only
4 digits like A001, A002..A999 and then B001...., one of the methods is create a
"format" based on the type of products " 11ADK23N09" which means 1->1st
position, 1->length, A->alphanumeric, DK->from D to K; 2->2nd position,
3->length, N->number, 09-> from 0 to 9, hope you understand the meaning(I think
so). But do you think is it awkward? Do you have some good methods in your
crystal ball to handle that while still keep the system dynamic for the future
changes?

Thanks a Millions,

          Followup:

you are basically saying "our primary key will be some string"

so, fine -- use a varchar2 and put into it anything you want. how you generate
that string (using whatever algorithm you want) is pretty much up to you.

(me -- i don't like it, the number 515432431 is just fine.  if you are trying to
encode MEANING into the key - you really are saying "we have a composite key"
and should not be putting it into a single field like that)

February 11, 2004 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.

      Rating: 5
A new
      wrinkle  January 13, 2004
Reviewer:  Michael
       from Allentown, PA
Thanks for letting me know about the analytics, somehow I missed those. I need
an opinion regarding the audit triggers I am working on. There are two choices.
Both triggers collect all of the new column values for inserts, and all of the
old column values for deletes. The problem is how to record updates. In one
trigger, I could record all of the old values for columns that did not change,
only showing a new value in the column that changed. (P.S. I am not allowed have
"old" and "new" column values in the same row, and I cannot audit to a single
table). So in those rows, it would not be obvious which column changed, that
could only be pulled out through code. In the other approach, whenever an update
is done, only the primary key value and the new value of the column that changed
are recorded (of course there are timestamp and userid columns in the audit
table) but all of the other columns are left null. So consider the following
data in the audit table:

EMP# FNAM LNAM HIREDATE  JOBID SALY OPR UPDUSR UPDDAT
135  KI   GEE  12-DEC-99 STCLK 2400 INS LDORAN 12-DEC-99
135  KI   GEE  12-DEC-99 STSPV 2600 UPD LDORAN 10-MAR-01
135  KI   GEE  12-DEC-99 STSPV 2800 UPD LDORAN 12-APR-02
135  KI   POPP 12-DEC-99 STSPV 2800 UPD SBAIDA 15-JUN-02
135  KI   POPP 12-DEC-99 STMGR 3200 UPD LDORAN 01-JAN-03

or in the second kind of trigger:

EMP# FNAM LNAM HIREDATE  JOBID SALY OPR UPDUSR UPDDAT
135  KI   GEE  12-DEC-99 STCLK 2400 INS LDORAN 12-DEC-99
135                      STSPV 2600 UPD LDORAN 10-MAR-01
135                            2800 UPD LDORAN 12-APR-02
135       POPP                      UPD SBAIDA 15-JUN-02
135                      STMGR 3200 UPD LDORAN 01-JAN-03

Which is the better of the two sets of audit data in terms of meeting two
requirements for pulling data from audit tables:
1. Rebuild an entire history of changes for the entire history of the table, or
a range of time, e.g. July
2. Show what the data for a specific EMP looked like on a given date. NOTE: We
cannot compare the audit table to the production table data.

Any advice/help will be greatly appreciated.

          Followup:

if you have to do that -- you might just consider:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4529781014729
instead of writing tons of code yourself.  you can use dbms_wm goto date to see
what the data looked like at any point in time.

      Rating: 3
WM not
      feasible  January 14, 2004
Reviewer:
      Michael  from Allentown, PA
First, thanks for responding quickly, I did see your home page notice that you
are very busy right now...

We have a number of "alternate keys" in the schema which have unique constraints
on them. There will be additional unique indexes built on some columns as time
goes by (still in development stages, but production rollout in June of first
modules). PM wants audit strategy in cement now, or I will be in cement instead.
We will not be going to 10g soon. They will not even let me patch up from
9.2.0.1 to 9.2.0.4...afraid something will break (I told them this was very
unlikely).

I oversimplified previous data example. We currently have about 280 tables, and
will eventually have over 1000. It is a web based front end with connection
pooling, so the application only has one user, which is the schema owner (not my
decision!). However, the developers have written functionality into the front
end that captures the real user id of the person who logged in. When that person
changes anything in a table, if it is an insert, there are two column in every
production table, cr_usr_id and cr_usr_dt, that get populated. If someone
deletes a row or changes data, there are columns upd_usr_id and upd_usr_dt that
get populated. According to the developers, this is the only way they could
capture the real id of the user inserting or updating a table. In addition, I
have been working on triggers that fire whenever any inserts, updates, or
deletes are made to the production tables. These triggers populate audit tables
that are identical to the production tables, except that there are additional
columns:
OPS (operation) SYS_TM, APP_NM, OS_USR, TERM
These columns will be populated by the triggers for transactions that occur both
inside the application or outside of it, as in SQL*PLUS, TOAD, etc.

So I am stuck in a box, basically my only choice is to structure the triggers so
that they populate the audit tables in one of the two ways shown earlier. Since
I am new to analytics, and do not have a lot of time to experiment, which of
those audit tables would be the easiest to build code for if I needed to extract
change reports for a period of time or as of a given date?

Right now, we are leaning heavily toward the first example, recording all column
values in the updated row, even though the changed column (new value) would not
be obvious. The other approach, just recording the new value of the column that
changed, was due to storage concerns, along with the idea that it *might* be
easier to pull out audit reports for data changes.

Once again, thanks for your comments.

      Rating: 5
what other
      sql*plus settings are you using?  January 14, 2004

Reviewer:  reader  from TX
Tom,

sqln and sqlprompt text is appearing in my spoooled file, as well as the
original prompt commands and prompt output from your original trigger generation
script.

I think I need to include:
set define off
set sqln off
set sqlprompt ''

but when I do my sql*plus spool file looks like this:

prompt create or replace trigger aud#&1
create or replace trigger aud#&1
prompt after update on &1
after update on &1
prompt for each row
for each row
prompt begin
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;;
end;
prompt /
/

spool off

how do I make this look like the output you generate?

          Followup:

set serveroutput on
set feedback off
set verify off
set embedded on
set heading off
spool tmp.sql

      Rating: 4
What about the
      4 'who' columns?  January 14, 2004
Reviewer:
      Robert  from Memphis, USA.
Tom,

Knowing that...
1) You are very busy.
2) This question involves a slightly different level of auditing

Here goes...
What do you think of a shop standard that insists that every table put into
production, no matter how transitory the data is, MUST contain the 4 'who'
columns (user_inserting, date_inserted, user_updating, date_updated), which are
populated by a database trigger?

Can you please share the philosophy and/or reasoning behind your answer?

Thanks,

Robert

          Followup:

i can see the user|date_inserting......

but the user|date_updated -- that does not make sense......

consider:

connect bob
insert row
connect mary
update row set x = 55
connect sue
update row set y = 66

hmmm -- who updated x = 55??  thats sort of lost -- so, what point "last
updated"?

      Rating: 4
Good
      One  January 14, 2004
Reviewer:  Abhijit
       from Mumbai
Hi Tom,
Is there any way of knowing the name of the column that is getting changed in
the trigger? I am using Oracle 8.1.7

I have a table MAIN_TBL with 165 columns. Another table TRAC_TBL contains
COLUMN_NAME varchar(30) and AUDIT varchar2(1). The columns that need to be
tracked are marked by the user in the TRAC_TBL by setting the value of the AUDIT
column = "Y". However the user also desires an interface where she can change
this audit value to "N" at any point of time. How do I keep track without
resorting to 165 if conditions?

Warm Regards,
Abhijit

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4529781014729
sometimes -- it is just easier/faster to "do them all".  seems it would be much
easier for the end user to analyze as well if they had the row history -- just
just deltas (which makes it virtually impossible to analyze in retrospect).

short of that -- you'll be doing 165 if's.

      Rating: 4
User|Date
      Updated 'Who' columns  January 15, 2004
Reviewer:
      Robert  from Memphis, USA
Tom,

The purpose of the User|Date Updated columns is simply to see if the record has
been touched... and who was the last to touch it.

It is not as complete as full colummn level auditing, but it is simple to
implement and use.

This information has helped me many times in debugging a tangled problem, and
reconstructing a sequence of events...And it is much easier to implement and use
than full column level auditing....sort of the 80/20 rule... 20% of the effort
for 80% of the results.

Will you please comment on my reasoning?

Thanks,

Robert.

          Followup:

maybe v$logminer contains more than enough information for your debugging
purposes without adding any additional runtime overhead then?

but even so -- the runtime overhead of 165 if's and all of that procedural code
may be "huge"....

(how is full column auditing "harder" -- it is a single INSERT -- versus
hundreds of lines of procedural code to check each and every column......)

      Rating: 5
must be
      something misconfigured in my sql*plus  January 15, 2004

Reviewer:  reader  from TX
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> set serveroutput on
SQL> set feedback off
SQL> set verify off
SQL> set embedded on
SQL> set heading off
SQL> spool tmp.sql
SQL> prompt create or replace trigger aud#&1
Enter value for 1: prompt after update on &1
create or replace trigger aud#prompt after update on &1
SQL> prompt for each row
for each row
SQL> prompt begin
begin
SQL>
SQL> select '    audit_pkg.check_val( ''&1'', ''' || column_name ||
  2            ''', ' || ':new.' || column_name || ', :old.' ||
  3               column_name || ');'
  4  from user_tab_columns where table_name = upper('&1')
  5  /
Enter value for 1: prompt end;;
Enter value for 1: prompt /
SQL>
SQL> spool off
SQL> @tmp dept
SP2-0734: unknown command beginning "SQL> promp..." - rest of line ignored.
SP2-0734: unknown command beginning "Enter valu..." - rest of line ignored.

SQL> @tmp dept
SP2-0734: unknown command beginning "SQL> promp..." - rest of line ignored.
SP2-0734: unknown command beginning "Enter valu..." - rest of line ignored.
 16  /
SQL> prompt for each row
*
ERROR at line 2:
ORA-04079: invalid trigger specification

SQL> l
  1  create or replace trigger aud#prompt after update on &1
  2  SQL> prompt for each row
  3  for each row
  4  SQL> prompt begin
  5  begin
  6  SQL>
  7  SQL> select '    audit_pkg.check_val( ''&1'', ''' || column_name ||
  8    2            ''', ' || ':new.' || column_name || ', :old.' ||
  9    3               column_name || ');'
 10    4  from user_tab_columns where table_name = upper('&1')
 11    5  /
 12  Enter value for 1: prompt end;;
 13  Enter value for 1: prompt /
 14  SQL>
 15* SQL> spool off
SQL>

--

          Followup:

did you just type that in or are you running a script -- it is meant to be run
as a script (eg: @audit_pkg_create_script DEPT)

      Rating: 5
disregard prior
      problem  January 15, 2004
Reviewer:  reader
       from TX
Tom,

Sorry, i am a little slow today.  disregard my previous problem.  I get it.

      Rating: 5
How to compare
      spatial data  February 10, 2004
Reviewer:
      Irina  from Australia
Hi Tom,

Thanks a lot for your site. I found it the most useful.

This discussion helped me to create my audit package. It works fine for all data
types except for the spatial data columns.

I wonder if it is possible to compare spatial data in the same manner as other
data types. I need something like:

if  (l_new_geom <> l_old_geom or
l_new_geom is null and l_old_geom is not null) or
(l_new_geom is not null and l_old_geom is null))

Cheers

Irina

          Followup:

since the mdsys type does not have a map or order method, you'll have to compare
them attribute by attribute if you want to compare them at all.

February 11, 2004 Per -- Thanks for the question regarding "Provide
      dynamic order by for "hard" cursor", version 8.1.7

      You Asked
I need guidance as to the order by clause for a hard refcursor
Unless I hardcode the order by clause the order by gets ignored when fetching
from the cursor.
Can I achieve dynamic ordering on ver 8.1.7 or any higher version ?
I can use parameters in all other places I've tried except the order by

sample code

   open p_cursor for
    select
     e.code
    , e.name
    from events e
    where e.sys_value = c_rv
    order by e.name asc;  -- ok
    order by l_sortitem ; -- gets ignored l_sortitem := ' e.code asc'
    order by decode( l_sort_by, '12',' e.name asc', ' e.code asc'); -- gets
ignored

Thanks for any suggestions

      and we said...
the order by decode DOES NOT GET IGNORED.

select * from t order by ' $.%@@#$ @!#';

that is what you are doing in effect -- ordering by a constant. so-- not
ignored, just not what you wanted to do.

Two methods:

a)  order by decode( l_sort_by, '12', e.name, to_char( code, '0000000000009' ) )

that is, return a character string to sort by.  if sortby = 12, return ename to
sort.  else return code (which I assume is a number perhaps) in a fixed width
field so it sorts.  if you had a date, you would use to_char( dt,
'yyyymmddhh24miss' ).

if you have numbers that go negative, you'll need to find a format that "sorts"
right.

b) use dynamic sql or an if then opened cursor:

is
  type rc is ref cursor;
  l_cursor rc;
begin
  if l_sort_by = '12'
  then
     open l_cursor for select ..... order by e.aname asc;
  else
     open l_cursor for select ..... order by e.code asc;
  end if;
  loop
      fetch l_cursor into ....;
      exit when l_cursor%notfound;

OR

begin
    if (l_sort_by = '12') then
       l_query := 'select ..... order by e.name asc';
     .....

     open l_cursor for L_QUERY;
     ....
Review & Followup

      Rating: 5
Thanks
        February 10, 2004
Reviewer:  Per Elmgreen
       from Sydney
Great - I'm getting the sorting I wanted - had overlooked the fixed width issue

      Rating: 4
Translate
      statement from a) to b)  February 10, 2004

Reviewer:  A reader
I cannot set asc/desc dynamically using a)

Would anyone have a utility function to convert a statement from from a to b ie
to build up string l_query  and but in all the ||, ' and ''' in the right places

          Followup:

sure you can.

order by
decode( l_asc_desc, 'ASC',
   decode( l_sort_by, '12', e.name, to_char( code, '0000000000009' ) ) ),
decode( l_asc_desc, 'DESC',
   decode( l_sort_by, '12', e.name, to_char( code, '0000000000009' ) ) ) DESC

if you use asc, and l_sort_by was 12, that would be like:

  order by e.name, NULL desc

if you use DESC, and l_sort_by was 12, that would be like

  order by NULL, e.name desc

how in the world could someone have a utility to convert your unique queries?
AND if there was -- it would mean that you already figured out how to do this in
a)??

      Rating:
      5
Gotcha  February 11, 2004
Reviewer:
      A reader
Very useful approach - nesting of decode

February 11, 2004 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://download-east.oracle.com/docs/cd/F49540_01/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.

      Rating: 5
This is what
      exactly I was looking for...  February 09, 2004

Reviewer:  Sami  from NJ,USA
Dear Tom,

This is what exactly I was looking for.

We used to generate reports using mod_plsql. Now user wants to get the reports
in email. so I can dump the result page into flatfile (using UTl_FILE  package)
and email it to them using cron job.

      Rating: 5
like to use
      CLOB datatype instead of UTL_FILE?  February 10, 2004

Reviewer:  Sami  from NJ,USA
Dear Tom,

I hope you like this question coz you always recommend to store things inside
the DB.

Due to security reason we do not have UTL_FILE parameter setup in our shop.
Instead of "dump_page (into file system), is it possible to store the html
output of "pretend_this_is_a_webdb_proc" into table CLOB datatype.

So that using Java program I can read it from client machine and dump into a
file system.

Will it be better alternative for UTL_FILE? If so kindly throw some light how to
capture the HTML output and store in in CLOB data type.

Your help is always appreciated.

          Followup:

sure, you can write it to a clob.

but why not just have a java program
a) run the procedure
b) get the results (just like dump_page does)
c) write the file?

and be done with it?

to store as a clob, change utl_file to dbms_lob, insert an empty_blob() into a
table and return it into a local variable of clob type, use writeAppend or
something instead of utl_file.put to write to it.

      Rating: 5
Need your
      opinion  February 10, 2004
Reviewer:  Sami

Dear Tom,

I wrote dump_clob to store the html output into CLOB column. Is there any better
way to do the same?

PROCEDURE dump_clob
AS
l_thePage       htp.htbuf_arr;
l_lines         NUMBER DEFAULT 99999999;
g_clob             CLOB;

BEGIN
owa.get_page( l_thePage, l_lines );
FOR i IN 1 .. l_lines
LOOP
IF (i=1) THEN
INSERT INTO DUMP_HTML VALUES (1, SYSDATE,EMPTY_CLOB() ) returning html_page INTO
g_clob;
dbms_lob.WRITE( g_clob,LENGTH(l_thePage(i)), 1,l_thePage(i));
END IF;
dbms_lob.writeappend( g_clob, LENGTH(l_thePage(i)), l_thePage(i));
END LOOP;
END dump_clob;

          Followup:

well, you could

   if ( i=1 )
   then
     insert into dump_html vlaues ( 1, l_thePage(i) ) returning html_page into
...
   else
     writeappend...
   end if

you see to print the first line twice as well...

ops$tkyte@ORA9IR2> create table dump_html( id number, dt date, html_page clob );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace  PROCEDURE dump_clob
  2  AS
  3  l_thePage       htp.htbuf_arr;
  4  l_lines         NUMBER DEFAULT 99999999;
  5  g_clob             CLOB;
  6
  7  BEGIN
  8     owa.get_page( l_thePage, l_lines );
  9     FOR i IN 1 .. l_lines
 10     LOOP
 11        IF (i=1) THEN
 12          INSERT INTO DUMP_HTML VALUES (1, SYSDATE,l_thePage(i) )
 13                  returning html_page INTO g_clob;
 14        else
 15              dbms_lob.writeappend( g_clob, LENGTH(l_thePage(i)),
l_thePage(i));
 16            end if;
 17     END LOOP;
 18  END dump_clob;
 19  /

Procedure created.

ops$tkyte@ORA9IR2>

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec htp.p( 'hello' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec htp.p( 'world' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec dump_clob;

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> select * from dump_html;

        ID DT        HTML_PAGE
---------- ---------
--------------------------------------------------------------------------------
         1 10-FEB-04 hello
                     world

      Rating: 5
Thanks
      Tom.  February 10, 2004
Reviewer:
      A reader
Silly logical mistake.

      Rating: 5
After package
      modification...  February 10, 2004
Reviewer:
      Sami
Dear Tom,

After Modifying the package I executed modified package 3 times.

1 st time
==========
I am getting "ORA-04068: existing state of packages has been discarded"

This is OKAY, Understandable.

2nd time
==============
I am getting "ORA-06502: PL/SQL: numeric or value error"

I DON"T KNOW WHY?

3rd Time
================

Running fine.

Nothing got modified between step (1) and Step (3).

Could you please enlighten me why Step(2) is giving ORA-06502 and step(3) is
NOT.

SQL> @1
begin
*
ERROR at line 1:
ORA-04068: existing state of packages has been discarded
ORA-04061: existing state of package body "MYUSER.MY_REPORTS" has been
invalidated
ORA-04065: not executed, altered or dropped package body "MYUSER.MY_REPORTS"
ORA-06508: PL/SQL: could not find program unit being called
ORA-06512: at line 2

SQL> @1
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 "SYS.HTP", line 40
ORA-06512: at "MYUSER.MY_REPORTS", line 257
ORA-06512: at line 2

SQL> @1

PL/SQL procedure successfully completed.

SQL> l
  1  begin
  2  olpr_reports.bib_mqcnt;
  3  olpr_reports.dump_clob('olpr_reports.bib_mqcnt');
  4* end;
SQL>

      Rating: 5
ORA-06502 with
      owa package  February 10, 2004
Reviewer:  Mark
      A. Williams  from Indianapolis, IN USA
The issue is that in the owa package header (around line 8 or so) the variable
'num_cgi_vars' is declared but not given an initial value.  One option is
presented in note #202017.1 on MetaLink.  The bug is #1821493 on MetaLink as
well.  Changing the declaration to 'num_cgi_vars number := 0;' will resolve the
issue, but I don't normally recommend changing things in the 'SYS' schema.

HTH,

Mark

      Rating: 5
Thanks a lot
      Mark A. Williams   February 10, 2004
Reviewer:
      Sami

      Rating: 5
You're
      Welcome  February 10, 2004
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
You're welcome - I ran across the same problem while doing some research on
mod_plsql a few months ago.

- Mark

      Rating: 3
Thanks for the
      info - one more question  February 10, 2004

Reviewer:  Paul  from South Africa
Tom,
after i use the owa.get_page, i'm not able to see the HTML output in the
browser. Is there a mechanism to save the html to a file and also display output
in the browser.

for example

create or replace procedure test
as
 l_thePage       htp.htbuf_arr;
 l_output        utl_file.file_type;
 l_lines         number default 99999999;
begin
for i in 1..100
loop
 htp.p('hello') ;
 htp.br ;
end loop;
owa.get_page( l_thePage, l_lines );
dump_page( '/tmp', 'page.html' );
end ;

Now, due to the owa.get_page i do not see anything
when invoking this proc. thru the browser.
I tried htp.p(l_thePage(i)) but broken up HTML tags cause problems. Please
suggest another way.

Thanks
Paul

          Followup:

you cannot have your cake and eat it too.

once YOU get the page -- others cannot get the page.

Use htp.prn instead of p.  prn will not inject the newlines.

February 11, 2004 kit -- Thanks for the question regarding "oracle
      developer moving to java", version 8.1.7

      You Asked
Hi, tom
been to your site many times and seen you often recommned other books.
being a predominant oracle person, it appears its vital to pick up some java
knowledge.

however, there is always a time constraint and most of my time goes in learning
new oracle ideas. I have been doing oracle for 15 years and even now i call my
self an expert. So I would like to learn java but in terms of getting by with
oralce.

I noticed 1 book you recommended recently, 9i pl/java programming book but it
seems to cover lots of the java in the db which i gather is being replaced by it
being in the application server

do you know of such easy understanding books.

there are so many oracle java books,it be nice your thoughts on them

thanks
kit

      and we said...
No, it doesn't really cover the J2EE java in the database (and thats what is
being de-emphasized)

If you check out the table of contents:

http://www.amazon.com/exec/obidos/tg/stores/detail/-/books/1861006020/contents/ref=pm_dp_ln_b_2/002-4751878-3050444
I think you'll find it is a gentle introduction into Java from a PLSQL/Oracle
developers perspective.  Showing bits and pieces where Java is truly useful in
and out of the database (using java to EXTEND the database).

It concentrates on Java + DATABASE stuff (hey, I helped do the technical
editing, wouldn't have it any other way).

I've read other java books (and I was a C and C++ programmer before this...) and
they just all make my head spin.  I think you'll find this one to be a "gentle"
introduction.  It is ideally suited, perhaps even uniquely suited, to meet your
goal of "So I would like to learn java but in terms of getting by with oralce."

But -- as anyone is free to do -- I rekon there'll be some other suggestions
popping up under this answer anytime now...

Review &
      Followup

      Rating: 3
Thanks for the
      question regarding "oracle developer moving to java", version
      8.1.7  September 04, 2002
Reviewer:  kit
       from england
thanks

      Rating:
      4
JAVA/JSP/STRUTS/TOMCAT/Servlets  February 10, 2004

Reviewer:  kit  from england
HI,
I followed your recommendation and have gone though your previous
recommendation. I'd like to step it up a bit now and need a new book that covers
the title subjects.

Asa fellow Oracle person. Do you feel I'm going down the wrong path in should I
just stick to learn oracle inside outI mean there is enough to learn there.

As a developer should I update my DBA skills.

Please do recommend the DBA/JAVA/JSP/STRUTS/TOMCAT/Servlets Books that you have
found easy to understand

Will you be doing a 9i/10g book soon ?

          Followup:

I read a java book once (way back when).  that is when I discovered -- hey, this
miracle thing that will save the world is "gasp" -- just a programming language.
 Sooo, not being the guy who does the gui -- and java not being the perfect
language for writing code inside the server -- I haven't really followed the
java/jsp/struts/tomcat/servlet thingy (all gui to me) too much.  Haven't read
any.

I learned all of my database stuff from the documentation and experimentation --
never really read too far outside the supplied stuff. (well, not for a long time
anyway).

I did a 9i book -- released in August 2003.  See the home page.

      Rating: 4
Effective
      oracle by design  February 10, 2004
Reviewer:
      kit  from england
Hi,
thanks for your input. if  any of your readers have any recommendations then
pleas add on.

I have bought the oracle by design book and I know it covers some 9i features
but wondering if a new book will come out that details all the features

Or do you think whats in the book is enough for 9i

          Followup:

well, if you just want a list of new features -- there is always the new
features guide.  then, there is exploration (experimentation) to really
understand and learn the new feature.

      Rating: 3
Java-Oracle
      advices  February 10, 2004
Reviewer:  Alex
       from Montreal
hello,

I would recommend the following:

JDeveloper - tutorials, wizards, code samples from OTN,
It includes Struts helpers, JSP editor and convinient
database tools. Of course, wizards will somewhat shields
you from inside understanding, but you can always
dig/rewrite generated code...

Here, you will find Tomcat and Struts (originals)

http://jakarta.apache.org/
9iAS (oops, sorry, 10gAS  :-)  ) for J2EE or standalone
Tomcat - simple JSP server. In both there is a lot
of JSP and some J2EE examples.

Thinking in Java - Just great openning to OO-Java world.

http://www.mindview.net/Books/TIJ/
There are 100s other java books, Tom's
suggestopn looks good according to table of contents

Hibernate and TopLink (Oracle's) - 2 great O/R mappers.
Since EJB is going down in popularity
(complex/not flexible) O/R mappers are important.

You may need JDBC stuff - see Oracle docs and samples
and a lot of on-line tutorials. (ask me if you
need help to find)

Alex V.

P.S. Tom, thanks for Montreal presentation
this morning :-))

      Rating: 5
Java book
      suggestions  February 10, 2004
Reviewer:  Ryan
      Gaffuri  from Tysons Corner, VA
I'd recommend staying away from jdeveloper or any IDE at first. Just learn Java.
Start with the basics. Go to www.textpad.com That is wordpad that gives you a
nice java editor.  You need to install the java SDK from the sun.com then
install this(or you might have a problem). It will give you formatting, colors,
and a list you can use to switch classes.

The java book that alot of people like is actually free. www.bruceeckel.com
"Thinking in Java". His design pattern book is very good.

When you go to sun.com and check out the javadocs you will be in for a rude
awakening. They are very terse compared to Oracle's robust documentation. No one
book will teach you everything you need to know, but this is a good start.

There is also a java tutorial(that is terse) on sun.com.

      Rating:
      3
  February 11, 2004
Reviewer:
      A reader
Hi Tom,

"No, it doesn't really cover the J2EE java in the database (and thats what is
being de-emphasized)"

Not sure what you mean? Is Java in the database (Java Stored Procedures etc.)
being de-emphasized? I have the book you provided the link to, but it has quite
a few examples of doing stuff using Java Stored Procedures, such as FTP, email,
compressing files etc. Am I missing something here?

          Followup:

no, j2ee in the database is being de-emphasized.  j2ee apps belong in the middle
tier, not the database.

j2ee is a huge huge architecture.

java, that is just a language - like plsql, c, vb, etc.

      Rating: 3
oracle
      developer moving to java  February 12, 2004

Reviewer:  kit  from england
HI thanks for all your input

I'll have look at the tomcat site

I' have a firm understanding of the JAVA but now i need exmaples of integrating
with JSP and that using JDBC calling stored procs

February 11, 2004 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"

      Rating: 4
Got a issue
      with cellsprint  February 10, 2004
Reviewer:
      Robert  from CT
declare
  l_bol BOOLEAN ;
  l_out NUMBER;
begin
owa_util.cellsprint( p_theQuery => 'select rownum r from (select * from
all_objects where rownum < 61
                                    orderr by object_id) a',
                   p_max_rows => 10,
                   p_skip_rec => 50,
                   p_more_data => l_bol,
               p_reccnt    => l_out );
    htp.showpage;
    dbms_output.put_line(owa_util.ite(l_bol,'MORE','NO MORE') || '  fetched = '
|| l_out);
end;

...........
<TR>
<TD>60</TD>
</TR>
MORE  fetched = 10

PL/SQL procedure successfully completed

SQL>

Tom, as the above indicates..
When the table/query result record counts is wholy divisible by p_max_rows,
p_more_data still returns TRUE when the LAST set is fetched....is it meant to do
this by design ? sort of Google-like as you've always preached....
(and I thought I wrote a dandy wrapper to cellsprint until the right arrow gif
is not grayed out :)

Thanks

          Followup:

cellsprint stops at the last row -- it does not "over fetch" by one.  It would
have to "over fetch" by one.

since it is shipped as source, you are free to "read out", "modify to your
desire" and "reuse the concept".

If you are worried about "copyright" -- I can give you the original version
which I called cells_from_query :)

      Rating: 4
modified
      cellsprint need your blessing  February 11, 2004

Reviewer:  robert  from CT
Hi, Tom, thanks for the clarification, as you suggested I've
made a version of cellsprint & modified to do an overfetch indicated by comments
below...
Can you pls take a look and see if there is any pitfall with  the code, thanks

FUNCTION cellsprint_fn(p_thecursor      IN INTEGER,
                        p_max_rows       IN NUMBER DEFAULT 100,
                        p_format_numbers IN VARCHAR2 DEFAULT NULL,
                        p_skip_rec       IN NUMBER DEFAULT 0,
                        p_reccnt         OUT NUMBER) RETURN BOOLEAN IS
  columnvalue VARCHAR2(2000);
  colcnt      NUMBER DEFAULT 0;
  tmpcursor   NUMBER DEFAULT p_thecursor;
  recix       NUMBER DEFAULT 0;
  reccnt      NUMBER DEFAULT 0;
 BEGIN

  colcnt := bind_outputs(p_thecursor);
  WHILE (reccnt < p_max_rows + 1) --< modified (+1)
  LOOP
    EXIT WHEN(dbms_sql.fetch_rows(p_thecursor) <= 0);
    recix := recix + 1;
    IF (recix > p_skip_rec) THEN
        reccnt := reccnt + 1;
        ---
        IF reccnt <= p_max_rows THEN --< and added this
           htp.tablerowopen;
           htp.p('<!--' || reccnt || '-->');
           FOR i IN 1 .. colcnt
           LOOP
             dbms_sql.column_value(p_thecursor, i, columnvalue);
             format_cell(columnvalue, p_format_numbers);
           END LOOP;
           htp.tablerowclose;
        END IF;
        -----
    END IF;
  END LOOP;
  dbms_sql.close_cursor(tmpcursor);

  ---- Added this --------------------------------|
  IF NOT (reccnt >= p_max_rows + 1) THEN        --|
     p_reccnt := reccnt;                        --|
  ELSE                                          --|
     p_reccnt := reccnt - 1;                    --|
  END IF;                                       --|
  ------------------------------------------------|

  RETURN(reccnt >= p_max_rows + 1); --< modified  (+1)
 EXCEPTION
  ....snip...
 END cellsprint_fn;

          Followup:

I might have just added a call to dbms_sql.fetch_rows after the loop and see if
there is yet another row to go.  Might be "easier" but if you have something
that works -- go for it.

February 11, 2004 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.

      Rating:
      4
my_ftp.get  February 11, 2004
Reviewer:
      Faisal  from LONDON
Tom i created my_ftp java procedure as you mentioned in your code its created
successfully. but when i run the procedure it give me following error.

ERROR at line 1:
ORA-29532: Java call terminated by uncaught Java exception:
java.lang.StringIndexOutOfBoundsException: String index out of range: -1
ORA-06512: at "PSDS.FTP_GET", line 0
ORA-06512: at line 8

here is the block code:

set serveroutput on size 1000000
exec dbms_java.set_output(1000000)
declare
  file_data  blob;
  file_name  varchar2(255) := '\ftp-java.txt';
begin
  insert into lob_test(id, filename, title, data)
  values(2, file_name, 'ftp test', empty_blob)
  returning data into file_data;
  ftp_get('myftpsite.co.uk',21,'username','pswd',
    file_name, file_data, '', 0);
end;
/

please can you help me.

          Followup:

code seems to be looking for '/'

you seem to be sending '\'

try '/' or update the code to look for 'windows' stuff.

      Rating: 4
fTP
        February 16, 2004
Reviewer:  M Faisal
       from London
Thanks TOM its work. Thanks for help.

      Rating: 5
For those
      interested...  March 11, 2004
Reviewer:
      Kashif  from Reston, VA
Hi all,

Metalink has a pretty good article (it's a 3-part article) on FTP'ing from
PL/SQL using the sun.net.ftp.FtpClient class. The details:

Note:159034.1:
http://metalink.oracle.com/metalink/plsql/showdoc?db=NOT&id=159034.1
Note:159061.1:
http://metalink.oracle.com/metalink/plsql/showdoc?db=NOT&id=159061.1
Note:159062.1:
http://metalink.oracle.com/metalink/plsql/showdoc?db=NOT&id=159062.1
Hope it helps...

Kashif

February 11, 2004 Sudip -- Thanks for the question regarding
      "efficient use of BLOB ", version 8.1.7

      You Asked
 Hi All,
One of my tables has a BLOB column, in which I will store Excel Files. Now this
particular Column will be updated very frequenty. I have taken the precaution of
putting the BLOB column on a separate tablespace. All my tablespaces are LMT. I
have increased FREELISTS on this table to 10.
What should be the optimal value of PCTUSED, PCTFREE parameter?

Regards
Sudip

      and we said...
pctused and pctfree do not have any meaning with regards to BLOBS.  A blob
allocates space in a series of CHUNKS (you controlled the chunk size when you
created it).  If you for example, set the chunksize to 32k and you have an 8k
block -- then a blob will always allocate blocks for itself in multiples of 4
blocks.  If your blob is 32k + 100 bytes for example, it will consume 8 blocks
(4 for the first 32k chunk, 4 for the remaining 100 bytes).  The 3 empty blocks
at the end of the blob are dedicated to that blob -- they are not on a freelist
anywhere.

Remember also that there is block overhead so my example of 32k + 100 bytes is
too simplistic.  32k of blob data will not fit on 4-8k blocks, that simple 32k
blob will consume 2 x 4-8k blocks due to block overhead.

When a blob "shrinks" -- it will give up free space in blocks of CHUNK size
bytes.  eg: in that 32k + 100 byte example, if you later trim the blob back to
16k -- it'll give up the one 32k chunk back for reuse.
Review &
      Followup

      Rating:
      5
  March 18, 2002
Reviewer:  Sudip
      Sarkar  from India
Thanks Tom, It is indeed a very useful information

      Rating: 5
LOB CHUNK size
      and index clause  August 20, 2002
Reviewer:
      vinci  from USA
Hi Tom,
I had two questions.

1.If i have to follow a Uniform, locally managed tablaspace strategy for an LOB
column in a table(LOB in a seperate tablespace than the table data), then the
uniform extent size shud be equal to the CHUNK size?
My datablock size is 4k (Solaris, Oracle 8.1.7.4).
We normally use standard extent sizes for tablespaces based on what size the
tables are going to be. For example if a table size is between 96k and 128m then
all tables like that go into one tablespace which will have a NEXT or UNFIROM
extent size of 128k ....and so on.
But in case of a LOB inside a table...should NEXT or UNIFORM be equal to CHUNK
size for that LOB tablespace?(which is different from teh general convention
that we follow). What is your advice on this?
Even if i continue with the same 128kextent size allocation for teh LOB
tableapsce also ... does CHUNK size matter?(say CHUNK size is 4k). What wud be
ur recommendation.

2.Oracle documentiation says not to mention index clause for LOB tablespace as
of oracle 8i. Even if i use it, oracle will ignore it.
But can i just specify the index name atlaest(for maintenance/standards
following)? Will it have any effect on how oracle handles the LOB and its index?

Thanks

          Followup:

1) no, that would be not a good idea.  You want a couple of chunks in an extent
-- the rule would be "make the extent size a MULTIPLE of the chunk size".

An extent/ chunk would be a little over zealous, you want N chunks / extent.
You get to pick N -- it depends on the ultimate sum of the sizes of the lobs
you'll be storing -- again, shooting for 1000 or less extents

2) you cannot control it (technically).  The name should have no effect.  The
following is not documented but appears to work (but could stop working
unpredicably!)

ops$tkyte@ORA920.US.ORACLE.COM> CREATE TABLE T (X NUMBER, Y CLOB)
  2  LOB (Y) STORE AS
  3  ( INDEX foo )
  4  /

Table created.

ops$tkyte@ORA920.US.ORACLE.COM> CREATE TABLE T2 (X NUMBER, Y CLOB)
  2  /

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select index_name, index_type, table_name from
user_indexes;

INDEX_NAME                     INDEX_TYPE                  TABLE_NAME
------------------------------ ---------------------------
------------------------------
FOO                            LOB                         T
SYS_IL0000030677C00002$$       LOB                         T2

ops$tkyte@ORA920.US.ORACLE.COM>

Use at your own risk for that reason.  It might be best to just leave it named
by the system.  It is not like you can alter it or do anything to it.

      Rating:
      5
  August 20, 2002
Reviewer:  vinci

Thanks Tom...

      Rating: 5
Any BLOB
      limitations  February 11, 2004
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
Tom,
We want to use BLOB type to store image data in the database. From a DBA
perspective do you see any issues with backup, export/import or any thing else
that comes to your mind. We are on 9iR2.
Thanks

          Followup:

Nope.

backup -- it is just data, in the database.  not much different from a "date"

export/import -- not with blobs (although you'll find imp does it a row at at
time - could be a bit "slow")

you'll have to make decisions about

o log / nolog
o cache / nocache
o chunksize

February 11, 2004 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

      Rating:
      3
tnsnames  February 06, 2004
Reviewer:
      alex  from UK
Tom

We are using the oracle foms 6.8.8.0 on NT4 with oracle 8.1.7

I have tns entry in my tnsnames.ora file without COMMUNITY and it connects well
through Oracle forms /SQL PLUS to Database.

But on the other PC same oracle forms version 6.0.8.8.0 and same tnsnames file
without COMMUNITY, but this does not work unless you but the COMMUNITY with
x.world

Can you advise me what could be the reason and how can find what exact tnsnames
entry should be.

Thanks

          Followup:

did you compare the sqlnet.ora files, which might have a default domain set on
one but not the other

      Rating:
      3
Review  February 11, 2004
Reviewer:
      alex  from uk
I have checked, entry is commented in both the PC in sqlnet.ora file.

anyway follow is the tnsnames.ora's entry on PC

connstr1 =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = HOST1)(PORT = 1527))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = DB1)
    )
  )

and on other pc
connstr2 =
  (DESCRIPTION =
    (ADDRESS_LIST =
        (ADDRESS =
          (COMMUNITY = tcp.world)
          (PROTOCOL = TCP)
          (Host = HOST1)
          (Port = 1527)
        )
    )
    (CONNECT_DATA = (SID = DB1)
    )
  )

          Followup:

<quote>
I have tns entry in my tnsnames.ora file without COMMUNITY and it connects well
through Oracle forms /SQL PLUS to Database.

But on the other PC same oracle forms version 6.0.8.8.0 and same tnsnames file
without COMMUNITY, but this does not work unless you but the COMMUNITY with
x.world
</quote>

hard to say which is the right one since you say both are without community -
but it would be safe to say that in your environment the right one to use would
be, well, the one that works?

February 11, 2004 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://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96590/adg10pck.htm#36206
and

http://download-west.oracle.com/docs/cd/B10501_01/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

      Rating: 4
is there a way
      to do this in 8i  February 11, 2004
Reviewer:
      Diana  from USA
is there a way that oracle calculates a median in version 8i?

          Followup:

see the nasty query up one or two from here?  that'll do it.

although you can use row_number() over () and count(*) over () as well.

consider:

scott@ORA9IR2> select deptno,
  2         count(*),
  3         percentile_cont(0.5) within group (order by sal) med
  4    from emp
  5   group by deptno
  6  /

    DEPTNO   COUNT(*)        MED
---------- ---------- ----------
        10          3       2450
        20          5       2975
        30          6       1375

scott@ORA9IR2>

this is what we want -- starting with:

scott@ORA9IR2>
scott@ORA9IR2> select deptno,
  2         sal,
  3         cnt,
  4         rn
  5    from (
  6  select deptno,
  7         sal,
  8         count(*) over (partition by deptno) cnt,
  9         row_number() over(partition by deptno order by sal) rn
 10    from emp
 11         )
 12   where (mod(cnt,2) = 1 and rn = ceil(cnt/2) )
 13      or (mod(cnt,2) = 0 and rn in ( cnt/2, cnt/2+1 ) )
 14  /

    DEPTNO        SAL        CNT         RN
---------- ---------- ---------- ----------
        10       2450          3          2
        20       2975          5          3
        30       1250          6          3
        30       1500          6          4

and then:

scott@ORA9IR2>
scott@ORA9IR2> select deptno,
  2         avg(sal),
  3         cnt
  4    from (
  5  select deptno,
  6         sal,
  7         count(*) over (partition by deptno) cnt,
  8         row_number() over(partition by deptno order by sal) rn
  9    from emp
 10         )
 11   where (mod(cnt,2) = 1 and rn = ceil(cnt/2) )
 12      or (mod(cnt,2) = 0 and rn in ( cnt/2, cnt/2+1 ) )
 13   group by deptno, cnt
 14  /

    DEPTNO   AVG(SAL)        CNT
---------- ---------- ----------
        10       2450          3
        20       2975          5
        30       1375          6

scott@ORA9IR2>

February 11, 2004 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

      Rating:
      4
ORA-01489  February 11, 2004
Reviewer:
      robert  from ct
ORA-01489: result of string concatenation is too long

Tom, I ran the script "getcode", but got the above error,
what can I do about it ?

thanks

          Followup:

that means you have a line of code that is thousands of characters long!

select decode( type||'-'||to_char(line,'fm99999'),
               'PACKAGE BODY-1', '/'||chr(10),
                null) ||
       decode(line,1,'create or replace ', '' ) ||
       text text

that is the only concat -- you have code that is over 3900 characters on a
single LINE!

sorry, my script won't work for you. never anticipated that.


Listing

Recent Archives Advanced Search FAQ by Category Most Popular Hot Articles Your Questions [ Listing ] RSS Feed
Subject | Article | Last Updated (East coast time)
How to get the ORA-01652 error code in Unix | article only | Feb 12, 2004 09:14 am
Data differencing between two Oracle Schemas | article only | Feb 12, 2004 12:03 pm
DROP USER | article only | Feb 12, 2004 12:37 pm
Why a Merge Join Cartesian? | article only | Feb 13, 2004 09:47 am
Business Logic - PL/SQL Vs Java - Reg | article only | Feb 13, 2004 10:05 am
different states of integrity constraints | article only | Feb 13, 2004 10:11 am
contention for data blocks. | article only | Feb 13, 2004 10:21 am
Password File | article only | Feb 13, 2004 10:24 am
Data Warehousing database & a regular Database | article only | Feb 13, 2004 10:35 am
Format the Number for display | article only | Feb 13, 2004 12:10 pm
Enable Novalidate does not work for PK? | article only | Feb 13, 2004 01:25 pm
Freelists and Initrans | article only | Feb 13, 2004 03:02 pm
reading files in a directory -- how to get a list of available files. | article only | Feb 13, 2004 03:55 pm
CPU Time | article only | Feb 14, 2004 04:54 pm
Disable Default Sorting | article only | Feb 14, 2004 04:57 pm
sqlcxt undefined | article only | Feb 14, 2004 05:30 pm
Oracle Session Management in Web Applications | article only | Feb 14, 2004 07:51 pm
Transactions and Isolation Level Confusion | article only | Feb 15, 2004 11:45 am
16 bit or 32 bit or 64 bit | article only | Feb 15, 2004 11:49 am
redo log buffer | article only | Feb 15, 2004 11:50 am
scalar query | article only | Feb 15, 2004 12:27 pm
How to use checkbox on web form so only checked records are updated. | article only | Feb 15, 2004 12:48 pm
Comparison between Oracle and Others | article only | Feb 16, 2004 07:08 am
Any ideas on how to make SELECT faster | article only | Feb 16, 2004 07:43 am
Transforming row to columns | article only | Feb 16, 2004 07:45 am
Type 2 Dimension | article only | Feb 16, 2004 09:12 am
Q: nologging option | article only | Feb 16, 2004 11:12 am
difference between soft parse and hard parse | article only | Feb 16, 2004 12:43 pm
What´s your oppinion about the DBA job in the future | article only | Feb 16, 2004 01:12 pm
Oracle dbms output | article only | Feb 16, 2004 01:14 pm
Caching a table in Memory | article only | Feb 16, 2004 01:57 pm
cannot connect to oracle 92 database | article only | Feb 17, 2004 08:22 am
DBMS_SQL : using bind variables in insert stmt | article only | Feb 17, 2004 10:04 am
dbms_alert_info | article only | Feb 17, 2004 10:09 am
Extending Rollback segments | article only | Feb 17, 2004 10:48 am
local index on OLTP partitioned table | article only | Feb 17, 2004 07:00 pm
Using Portal With Multiple Database Instances And Other Questions | article only | Feb 17, 2004 07:07 pm
Need to load a file that is written in matrix format | article only | Feb 18, 2004 07:45 am
Bind Variables | article only | Feb 18, 2004 08:38 pm
Oracle Fail Safe and Dataguard | article only | Feb 18, 2004 08:54 pm
about the RR format | article only | Feb 18, 2004 09:05 pm
REBUILDING INDEXES: clustering_factor | article only | Feb 18, 2004 09:09 pm
Transactions per second | article only | Feb 18, 2004 09:14 pm
Deleting duplicate records without using rowid and rownum | article only | Feb 18, 2004 09:15 pm
DDL Trigger | article only | Feb 18, 2004 09:18 pm
remove user who have not logged in for 3 months.. | article only | Feb 18, 2004 09:29 pm
HTTP request | article only | Feb 18, 2004 09:30 pm
Mutating Table | article only | Feb 18, 2004 09:39 pm
Join a view with UNION | article only | Feb 18, 2004 09:50 pm
row(s) 1 - 49 of 49
February 12, 2004 thierry -- Thanks for the question regarding "How to get the ORA-01652 error code in Unix", version Oracle 8i
You Asked
Hi Tom,

In order to pass an Oracle error to Unix, I use the "whenerror..." statement.
This works fine for some kind of errors (like division by zero, invalid SP name,
...), BUT NOT FOR ALL.
In particular, when the error ORA-01652 (segments too small) is raised in
Oracle, it is not passed to Unix.

Is there a way to raise this error at the Unix level ?

Thanks,
Thierry.
and we said...
It is working however the status returned to Unix is limited to a single 1
number (in the range of 0..255).

If you run a shell script like this:

#!/bin/csh -v

sqlplus -s / <<EOF
whenever sqlerror exit SQL.SQLCODE
declare
    e exception;
    pragma exception_init( e, -1652 );
begin
    raise e;
end;
/
EOF

echo $status from SQLPlus

sqlplus -s / <<EOF
whenever sqlerror exit SQL.SQLCODE
declare
    e exception;
    pragma exception_init( e, -254 );
begin
    raise e;
end;
/
EOF

echo $status from SQLPlus

sqlplus -s / <<EOF
whenever sqlerror exit SQL.SQLCODE
declare
    e exception;
    pragma exception_init( e, -255 );
begin
    raise e;
end;
/
EOF

echo $status from SQLPlus

sqlplus -s / <<EOF
whenever sqlerror exit SQL.SQLCODE
declare
    e exception;
    pragma exception_init( e, -256 );
begin
    raise e;
end;
/
EOF

echo $status from SQLPlus

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

 you will recieve

$ test.csh
declare
*
ERROR at line 1:
ORA-01652: unable to extend temp segment by  in tablespace
ORA-06512: at line 5

116 from SQLPlus
declare
*
ERROR at line 1:
ORA-00254: error in archive control string ''
ORA-06512: at line 5

254 from SQLPlus
declare
*
ERROR at line 1:
ORA-00255: error archiving log  of thread , sequence #
ORA-06512: at line 5

255 from SQLPlus
declare
*
ERROR at line 1:
ORA-00256: cannot translate archive destination string
ORA-06512: at line 5

0 from SQLPlus

There is nothing we can do about this -- you might use GREP to GREP the ORA-
messages out and just use:

whenever sqlerror exit 1

so you can detect an error.
Review & Followup
Rating: 5
September 04, 2002
Reviewer: A reader
Tom,

We regularly use "grep ORA- > err.log" on the log files to trap the error
messages. Do you know how we can trap the preceding 2 lines of the line that has
ORA- error message also in the error log.

Eg:

line1
line2
line3
line4 ORA-xxxx
line5

I want to trap

line2
line3
line4 ORA-xxxx

in the error log. Is it possible? Thanks.

Followup:
write an awk script would be one way.

Rating: 3
Oracle error to Unix February 09, 2004
Reviewer: Philip from MI USA
I am trying to call a procedure from a unix script through sqlplus and I would
like to stop the processing of unix script if any errors occur in the procedure.
I am not able to pass the error code from my procedure to Unix. I use WHENEVER
SQLERROR EXIT SQL.SQLCODE before executing the procedure and tried to check the
status(echo $status), but it shows null.

Could you help me to find out what's wrong?

Followup:
might you be using sh or some other scripting environment?  (i used csh, it'll
vary)

for example:

[tkyte@tkyte-pc tkyte]$ cat t
#!/bin/sh

sqlplus / <<EOF
whenever sqlerror exit sql.sqlcode
begin
  raise program_error;
end;
/
exit
EOF
echo 'Status = ' $?
[tkyte@tkyte-pc tkyte]$ sh ./t

SQL*Plus: Release 9.2.0.4.0 - Production on Mon Feb 9 10:25:57 2004

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> ops$tkyte@ORA920PC>   2    3    4  begin
*
ERROR at line 1:
ORA-06501: PL/SQL: program error
ORA-06512: at line 2

Disconnected from 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
Status =  101
[tkyte@tkyte-pc tkyte]$

would be the sh way of doing it.  refer to your "man" page for your shell.

Also -- remember, exit codes are in the range 0..128 or 255 -- best to use

whenever sqlerror exit 1

(as noted above) instead of the sqlcode.  run this script to see why :)

#!/bin/sh

sqlplus / <<EOF
whenever sqlerror exit sql.sqlcode
begin
  raise_application_error( -20223, 'foo' );
end;
/
exit
EOF
echo 'Status = ' $?

sqlplus / <<EOF
whenever sqlerror exit sql.sqlcode
begin
  raise_application_error( -20224, 'foo' );
end;
/
exit
EOF
echo 'Status = ' $?

sqlplus / <<EOF
whenever sqlerror exit sql.sqlcode
begin
  raise_application_error( -20225, 'foo' );
end;
/
exit
EOF
echo 'Status = ' $?

Rating: 4
very useful February 09, 2004
Reviewer: Andrew from ca, usa

Rating: 2
February 10, 2004
Reviewer: philip from MI USA
It seems very useful and a working solution, but some how it is not working for
me. I am using sh(Bourne shell) and the only difference I can see is I am
calling a database procedure through sqlplus instead of commands direct.

I have WHENEVER SQLERROR EXIT 1 before I call the procedure, but when I do echo
$? after sqlplus I always get a value of 0 even if there is an error in the
procedure.

Followup:
how about "man sh" on your system to see what it says would be "right" for you.

or how about "you post your example" like I did mine and we can take a look
see..

or how about you run my example "as is" to see if in fact it seems to work....

Rating: 2
February 11, 2004
Reviewer: philip from MI, USA
I tried your example but it also give me a value 0 for the status. It seems
nothing gets executed between the begin and end. I tried to run the script with
some mispelled commands, it didn't give me an error. I tried to execute some
valid SELECT statements, but I didn't see any results.

I think it just connect to Oracle and disconnect without executing any
commands(or at lease I don't see any rsponse from Oracle). But the interesting
thing is I am able to execute a database procedure from the script. No idea
what's wrong and where?

Followup:
your shell must be a totally "non standard shell" then.

this is really "basic" stuff -- works on solaris, works on linux, should work on
pretty much any "standard" unix like OS.

If it is not working -- remove sqlplus from the equation all together and show
us something that does work on your system.  The problem is *not* sqlplus here
at all -- but rather that we don't know how to write "sh" on your system
(whatever it is)

Rating: 1
February 11, 2004
Reviewer: philip from MI,USA
My Unix is HP-UX miladms B.11.11 U 9000/800 3255374999. The default shell is
called POSIX which is similar to Korn shell and I don't have any problem in
executing any unix script on this shell.

As I said in my previous response I am even able to run Oracle procedures by
calling sqlplus from the shell script. So there is nothing wrong with the
standard execution  capability of the shell. The problem is sqlplus not
returning the correct exit codes to Unix. It always return 0 no matter you have
error in your procedure or not. It seems I need to setup something in the
sqlplus environment to return the status properly? Following is the snippet of
the script.

sqlplus -s test_721_d/tecsys@fnok_dms >>$LOGFILE 2>&1 <<-EOF
        set feed off veri off head off
        set serveroutput on
        whenever sqlerror exit 1
        exec gl_export_hyperion('$EXPFILE','$EXPDIR')
        set serveroutput off
        set feed on veri on head on
        exit
        EOF
echo Status $?

Followup:
do this,

compile this C program:

void main()
{
    exit(55);
}

call it test.c and just

$ make test

then:

#!/bin/sh

./test
echo status = $?

and show us what that does.  remove sqlplus from the equation here.

Rating: 2
Watch your indentation February 11, 2004
Reviewer: Alfonso from Hartford, CT USA
Indentation matters in Philip's code section.
This sample returns "Status = 255" (using ksh)

sqlplus / <<-EOF
whenever sqlerror exit sql.sqlcode
begin
   raise_application_error( -20223, 'foo' );
end;
/
exit
EOF
echo 'Status = ' $?

But this sample returns "Status = 0" (note indented lines):
sqlplus / <<-EOF
    whenever sqlerror exit sql.sqlcode
    begin
       raise_application_error( -20223, 'foo' );
    end;
    /
    exit
EOF
echo 'Status = ' $?

Try Tom's examples verbatim and you shouldn't have a problem.
Regards.

Followup:
the "/" not being in position 1 would be the cause.

there is no code being run.

Rating: 2
February 12, 2004
Reviewer: Philip from MI,USA
First let me answer to Tom.

I ran your C program and it gave me a status of 55 which I think what you are
expecting.

To Alfonso

I ran both of your samples and both gave me a status of 255 and I don't think
indentation matters. When you call a procedure from the script you don't need to
use / at the end, Right? Please look at the code I listed in my previous
response and let me know if anything wrong with that.

Followup:
I don't have a shelled called "posix", but in ksh, that EOF needs to be like
this:

[tkyte@localhost tkyte]$ cat ./test.sh
#!/bin/ksh
export LOGFILE=foo.lst

sqlplus -s />>$LOGFILE 2>&1 <<-EOF
        set feed off veri off head off
        set serveroutput on
        whenever sqlerror exit 1
        exec raise program_error;
        set serveroutput off
        set feed on veri on head on
        exit
EOF

echo Status $?
[tkyte@localhost tkyte]$ ./test.sh
Status 1
[tkyte@localhost tkyte]$

"works for me"

don't know what else to say except try the SH or KSH -- using code provided
above, as is -- verbaitim.

indentation does matter -- alot.

Rating: 5
Unsolicited $ 0.02 February 12, 2004
Reviewer: Madhu from Cincinnati,OH USA
Here is another approach we use to pass the exact Oracle error code from SQLPLUS
to unix . We use HP-UX/ Korn Shell

RCODE=`sqlplus -s /  << EOF

    SET    SHOWMODE   off
    SET    ECHO       off
    SET    FEEDBACK   off
    SET    VERIFY     off
    SET    RECSEP     off
    SET    PAGES      0
    SET    TRIMSPOOL  on
    SET    LINESIZE   2000
   VARIABLE RCODE NUMBER ;

    WHENEVER SQLERROR EXIT SQL.SQLCODE ROLLBACK;
    DECLARE
    BEGIN
       :RCODE := rso_gen_email_info('${EMAIL_FILE}');
    END;
/

    print  :RCODE

    exit :RCODE

EOF`

echo $RCODE

This will print the exact Oracle return code . As Tom mentioned in previous
post, indentation matters a lot .

Thanks,
Madhu

Rating: 4
February 12, 2004
Reviewer: Philip from MI,USA
Sorry guys, I wasn't capturing the error in my procedure correctly. I was under
the impression that oracle will return the error even if I didn't
raise_application_error for oracle defined exceptions. I was just using
dbms_ouput to capture the error.

There was nothing wrong with the shell or the useage of the script, all because
of the procedure. I think you will excuse since I am still learning pl-sql
programming.

Thanks any way for the great help you all guys provided in this regard.

February 12, 2004 Srinivas -- Thanks for the question regarding "Data differencing between two Oracle Schemas", version 9.x.x
You Asked
Tom,

Thanks for your valuable suggestions and comments.

Even though data differencing and synchronization is a standard requirement,
there are not any standard tools to do that task.

I am talking about windiff like tool to find differences between two oracle
schemas.

I tried my level best to find one such tool/utility but could not find till now.
I could only find schema differencing and synchronization tools. In the last
project we had to develop one such internal tool (but is very expensive to
develop such tools and maintain them for small companies).

If you know any such tool to do data differencing and synchronization, please
let us know.

thanks,
Srinivas
and we said...
is it a standard task?

I do not believe so, in fact, I've never had a requirement for it in my
experience.

But, there are literally hundreds of tools.  Oracle Designer is one such tool.
Toad has capabilities in this area as well.  OEM (enterprise manager) change
mgmt packs do this.

But, again, I've never actually seen the need in a well run shop for such a
tool!  If you use configuration mgmt techniques from the get go, you'll KNOW
what the differences are at a glance, for this information would all be
continously catalogued and recorded.

I'll publish this one and we'll start collecting the tool names that do this
(others will add them I'm sure).

The list begins with

a) designer
b) OEM
c) toad (i think)
Review & Followup
Rating: 2
Thanks for your reply December 29, 2003
Reviewer: Srinivas from Virginia, USA
Yes, I agree that there are many tools out there for finding differences between
two schemas in terms of DB objects but not the data itself.

After doing some more extensive search could find some tools to do the data
differencing and synchoronization, but not getting dev tools those can be
embedded into our J2EE based system.

Followup:
now I really know it is NOT a standard task.  (and not really at all what you
described)

we have tools for performing replication.

You can use MERGE to merge changes from table 1 into table 2.

but this is by no means "a standard" task.  But even so, the simple merge
command is you "windiff".  windiff tells you how to make file 1 be file 2 or
file 2 be file 1.  thats what merge does.

Rating: 3
(A-B) U (B-A) December 29, 2003
Reviewer: Sami from NJ,USA
For example,

Schema-1
========
Tab-1
  c1
  c2

Tab-2
  col1
  col2

Schema-2
========
Tab1
  c1
  c2

Tab2
  col1
  col2

I think, Srinivas is looking for something like below for the entire schema
objects.

select * from (
select * from tab-1
minus
select * from tab-2
Union
select * from tab-2
minus
select * from tab-1
) a order by primary_key;

Rating: 3
Also check out... December 30, 2003
Reviewer: A reader
the following link... might be of help.
http://asktom.oracle.com/pls/ask/f?p=4950:8:4674014385171550577::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:941629680330,

      Rating: 2
Again this is
      to comapare the structure.  January 08, 2004

Reviewer:  A reader
The original poster is looking for some kind of script/tools to compare DATA
between tables in TWO schemas. NOT data structure/definition comparison.

          Followup:

interestingly....

the method to find the differences between two structures outlined in the link
you take exceptoin to -- just happens to show exactly

HOW TO FIND the differences between the DATA between table in TWO schemas

cause, the method I used to report on differences says "hey, find the diffs
between user_tab_columns and find the schema differences.

Not much different from "hey find the diffs between these two tables"

      Rating: 2
Found some
      tools   January 08, 2004
Reviewer:  Reader
       from NJ, USA
1) for SQL Server

http://www.dbbalance.com/home.htm
2) for Oracle

http://www.dkgas.com/oradbdiff.htm

      Rating: 3
How to
      implement Version management of RDBMS data?  January 08,
      2004
Reviewer:  Mike  from NY, USA
One of my customer has this requirement.

They start working with a base dataset (can be called version 1.0). They keep
doing modifications to the data (it is for analysis purpose, no transactions).
After some time, they base line again (like label in visual source safe, it is
version 2.0) and continue working.

They need a tool to compare the data between Version 1.0 and Version 2.0 and it
will help them in their analysis.

Storing all versions in database itself could be one option. But as the data
size is huge, also they have hundred of datasets (with different versions), it
may not be practical to store the data in database itself.

We have version management tools for text files but not for database. Can some
one suggest me alternatives?

          Followup:

<quote>
They keep
doing modifications to the data (it is for analysis purpose, no transactions).
<quote>

that contradicts itself

a) they keep doing modifications to the data
b) no transactions

A and B are not possible simultaneously.

Databases can get quite large.  This ain't dbase.  People buy databases to do
exactly what you describe.

it would NOT be practical to store the data anywhere OTHER than in the database.

      Rating: 4
Re:How to
      implement Version management of RDBMS data?   January 08,
      2004
Reviewer:  A reader
Just try Change Manager which is part of OEM and third party software like BMC
(
http://www.bmc.com/

      Rating: 3
Re:How to
      implement Version management of RDBMS data?  January 08,
      2004
Reviewer:  A reader
Mike,

Perhaps they're looking for a data warehouse that stores history of data, but
they just don't know it yet... It seems in line with what you've described in
your post.

      Rating: 2
How to
      implement Version management of RDBMS data?  January 08,
      2004
Reviewer:  Mike  from NY, USA
Tom,

Thanks for reply. What I meant by "NO transaction" is that it is not a
transaction oriented system and users modify changes to their own datasets. Each
dataset is stored in a separate Oracle user.

Clearcase, VSS, CVS type of configuration management tools store the base
version and keep storing changes as delta.

Is there any such feature available in RDBMS world is my question.

          Followup:

you may be interested in this:

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96628/toc.htm

      Rating: 4
How to
      implement Version management of RDBMS data?  January 09,
      2004
Reviewer:  Mike  from NY, USA
This it is. I got a infrastructure to work on version management in our product.
I will experiment more on this and will give an update in a week.

Thanks Tom for the great info.

      Rating: 5
Very
      Nice  February 10, 2004
Reviewer:  Gabriel
       from Montreal, Canada
Hello,

I was able to configure change manager scheduled compares between a database and
a baseline using the command line interface and scripts. But then do I have to
login every morning to check the reports? Is there a way to configure the
sending of emails in case there are differences, or if not, just the emailing of
the reports? UNIX or NT solutions are welcomed.

Thank you very much,

P.S. I would have liked to come to your conference in Montreal, but I couldn't.
Hope to see you again soon in Montreal.

          Followup:

if you have a script (shell script I assume) doing this -- sending email from it
should be easy?  just mail user@domain < report.txt?  or am i missing something?

      Rating: 5
No
      report.txt  February 11, 2004
Reviewer:
      Gabriel  from Montreal, Canada
Hello,

As far as I know, I can't generate text file reports from the command line. The
only way I know to look at the reports is to connect in the repositiry schema
and inspect them through the Change Manager GUI. These are the commands to
genereate the reports that I'm using:

ocm login -idOEM_REPOSITORY
ocm compare MG1CET_BCUN

Is there an option to specify a file to write he report to? Then there would be
no problem. But otherwise the info is in oem_repository tables with very
unmeaningfull names in BLOB format. In this case, do you have a script that
would read this info and display it in a meaningfull way?

Thank you very much,

          Followup:

not that I'm aware of -- I do not use that particular feature myself (so have no
real practical experience with it).

      Rating: 3
Also check
      out...   February 11, 2004
Reviewer:
      Philippe  from Paris
Try oracle Tool free software on

http://www.orafaq.net/links/Products/

      Rating: 2
Does it do
      compares  February 11, 2004
Reviewer:
      Gabriel  from Montreal, Canada
Hello,

Does this OracleTool perform scheduled comapres between a database and a
baseline?

Thank you,

      Rating: 4
Does it do
      compares   February 11, 2004
Reviewer:
      Philippe  from Paris
No, there is no scheduling function today,

but you can compare two schemas as often as you want, selecting objects you want
to compare.

Maybe for futur version,it may be a good idea post it on the site

      Rating: 1
Then I can't
      use it  February 11, 2004
Reviewer:  Gabriel
       from Montreal, Canada
Hello,

I need scheduled compares that will email me the results, nothing less, nothing
more. TOAD has this functionality but it has bugs. I am currently looking for
alternatives. I would love to get the Oracle Change Manager to do this somehow
as I believe any third party tools are invariably inferior to using Oracle
native products.

Thank you for your response anyway.
So Tom, is there a way of doing this with Change Manager?

Thank you again,

          Followup:

not that i know of (i'd be interested in the "environment" you work in where by
you have to be notified of a schema change.  Seems that perhaps "i want to audit
things and send myself an audit trail report" or "i need better CM on my
systems" would be better then "hey, found differences, now, figure out why").

I honestly know of no one else with this need -- I'm curious about the
circumstances of having such a 'need'.

      Rating: 5
i want to audit
      things and send myself an audit trail report  February 12,
      2004
Reviewer:  Gabriel  from Montreal, Canada

Hello,

On this system, with 5 database,2 testing, validation, integration, production,
everybody has all the psswords and this cannot be changed as they are all hard
coded in all 25 3rd party tools. Sometimes, people end up running scripts in the
wrong environment. Cannot change passwords, cannot change grants, cannot
restrict access to the machine cannot etc.
We basically cannot guard against mistakes but we would like to know when they
happen. In the beggining we tried to use TOAD which has this specific feature
(automatic compare against a baseline and emailing of results ;if they
implemented it means that there are other people need it) but it has bugs. We
then tried to use Oracle Change Manager which seems to implement the same
functionality. But now, after I read a bit about auditing this seems like a good
idea. In the beggining I chose change manager because at a glance it has a
smaller performance impact.
To conclude, from what I understand, you recommed auditing instead of using
change manager? Do you have any scripts that get the info from the auditing
views in a nice format?

Thank you very much,

          Followup:

Maybe DDL triggers (capture alters, creates and the like -- audit them yourself)

or audit DDL changes and have them emailed to you on a recurring basis.

It would be a way of letting you know that a change had been made.  You could
even write a query to make sure that if a change is audited as having happened
on database 1 - you verify that it was made also on databases 2...5 and send
yourself an email if not.

I have no scripts to do this, no.

      Rating: 2
Compare
      /AUdit  February 12, 2004
Reviewer:  johan
      graden  from sweden
Well, I have a little homebrewed tool that does portions of this. I have found
occasions where such comparos have been useful.... Email me and I'll send you
the tool - you can take a look and suggest improvements. If I'm in a good mode I
may implement the suggestions :)

      Rating: 3
Compare
      /AUdit  February 12, 2004
Reviewer:  Johan
      Graden  from sweden
use johan<at>graden.com. (replace <at> with @!)

February 12, 2004 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;
/

      Rating: 3
Problems with
      drop user command  February 12, 2004
Reviewer:
      Jelena  from Germany
Hi Tom,
we tried to drop the user once, it took too long, so we closed the window :)
The problem is that user (or whatever is left from him) is now in some corrupted
state, and we can't drop it any more.
so we always do 'drop user something cascade'
is there any drop user ... FORCE option?
first time, as we said, we just interrupted the process, and now when we try to
drop it again, we always get the error 'can not drop user that is currently
connected...'
And we can see that there is no session of that user!
Also interesting is that in enterprise manager, we can see that user in the user
list, but not in table list (by owners)... which means he has no objects(?).
Also it is not possible to connect as that user again.
So is there any way to force drop user or fix the user so we can drop him?
Thanks,
Jelena

          Followup:

you might have closed the window, but that doesn't kill a session.

anyway -- not sure what your situation is here -- please contact support.

February 13, 2004 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.

      Rating: 5
Notify when
      Merge Join Cartesian is used in the database  February 12,
      2004
Reviewer:  A reader
Hi Tom,

Is it possible to have OEM to monitor any Merge Join Cartesian being used by the
optimizer and have it notify me?

Thanks.

          Followup:

you'd have to script it yourself.  if you have 9i, tis easy (v$sql_plan is
sitting right there).  if you have 8i, you'll be wanting to see when you can
upgrade.

February 13, 2004 -- 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)

      Rating: 3
Business
      Logic  February 13, 2004
Reviewer:  Aakki
       from Florida USA
Hi Tom,
Sorry to get my answer this way, but desperately need answer from the expert of
database.

I am hard-core PL/SQL/ Oracle fella, I want to design an extension to existing
system which is forms driven. This extension is developing an automated system
to dispose records at load time itself. By doing this automation we will atleast
decrease work-load of 30% where users need to go and take decisions thru front
end (user intervension).

What I want to do here is
1. Load data record file everyday morning. (which I am doing for existing
system)
2. Check if a particular reason code exists.
3. Do certain action like
    a) Insert debit record if 6 digit auth present else
       pass Represent decision record to Issuer
    b) Send email
    c) create letter to Issuer.

That is as simple as I can explain.

Been PL/SQL guy.. I dont want to write my rules in procedure which would need a
change every time the business rule get added or removed. eg. on certain reason
code - also create a history record etc.

I want this very generic.

I thought of business rule matrix table, but my idea did not perfectly got drawn
on paper. (black/white)

I thought I will have table saying

group decription-ReasonCode-amount-action
eg. Auth        -23,26,29- less then 10$- debit if 6digit auth present else
represent.

Finally thought I will have one more column name call_proc and I will call a
procedure for that action.

But then I need to change the procedure every time old rules need one more
action item to be added.

Hope I explained the designing problem.

Please try to help me in this automation process and help a programmer who does
not want to hard code business rule in programs.

Regards,
Aakki

          Followup:

remember this

"generic = slow, hard to debug, things just seem to happen by accident"
"specific = fast, easy to debug, things happen explicitly for a reason"

I've never seen how putting a "rule that is pretty much like procedural code in
a table that has to be parsed and dynamically interpreted" is easier then
putting "a rule that is compiled code into the code"

Sorry, I'm not a big fan of your approach.  Making things "table driven" --
sure.  Using constraints on tables and ALTERS to change these constraints over
time -- sure.

But inventing my own language -- no, not really.

February 13, 2004 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

      Rating: 5
Changing
      datatype without dropping constraints  February 13, 2004

Reviewer:  Pinaki Bhattacharya  from New Delhi, India

Hi Tom,

Thanks for all you have done for the Oracle community.

I have two tables...P_TYPE  and AC_PROJECT

P_TYPE(table)

p_type_code varchar2(2) Primary Key --column
......
......

AC_TYPE(table)

ac_type_code varchar2(2) Primary Key --column
p_type_code varchar2(2) Foreign Key relation to p_type_code in P_TYPE table.

Now I want to change the datatype of the p_type_code to char(2) in both the
tables
P_TYPE and AC_TYPE without dropping the constraint.

Will disabling the constraint allow me to change the datatype of the columns?

Thanks

Pinaki

          Followup:

you won't be doing that.  (char's stink anyhow, you'd be going the wrong
direction).

You'll need to drop the constraint in order to do the alter which could rewrite
both tables (to pad out the p_type_code).

Maybe your best solution is just:

alter table p add constraint ( check (length(p_type_code) = 2) );

that you can add right now, it'll have the same effect (varchar2 and char use
the SAME storage methods, but a char is just blank padded -- if you make sure
the field is 2 characters -- you have exactly the same bytes on disk).

February 13, 2004 Sateesh Kumar -- Thanks for the question
      regarding "contention for data blocks.", version 8.1.5

      You Asked
Hi Tom,

pri>select * from v$waitstat where class='data block';

CLASS                  COUNT      TIME
------------------ --------- ---------
data block             43619         0

It is very difficult to find out what causes block contention. I
increased the db_block_buffers, But still the contention is
there. Can u tell me what else can i do to reduce this
contention. It is not a bottleneck in our database. But i want
to zero them out ideally speaking.

Thanks
Sateesh

      and we said...
Well, these are contention for the SAME blocks -- would not
matter how many you cache -- these are people bumping into
eachother for the same exact things.

also, 43k is not a very large number if your database has been
up for a while.

Freelists are the answer to this (and one of the most overlooked
storage settings).

You'll want to increase the freelists settings on the affect
indexes and tables.  You'll look into v$session_wait and look
for buffer busy waits and look at p1, p2, and p3 from that
table.  You can then

SELECT * FROM v$datafile WHERE file# = p1

to find the affected file.  If only 1 object is in that file --
you are done, you know which one to fix.  Otherwise, you might
have to:

SELECT  segment_name ,  segment_type , owner , tablespace_name
  FROM  sys.dba_extents
 WHERE  file_id = p1
   AND  p2 BETWEEN block_id AND block_id + blocks -1

to identify the object.  In 8.1.5 and before as you have, the
only way to change the freelists is via a rebuild of the object.
 In 8.1.6 and up, you can alter the object to set the freelists
higher.

Review &
      Followup

      Rating:
      5
  May 09, 2001
Reviewer:  Anand
      Singh  from Delhi-India

      Rating: 4
When no entry
      in v$session_wait   July 18, 2002
Reviewer:
      Prince
Tom,

When we couldn't get the p1,p2, p3 from v$session_wait (say at this moment there
is no wait), can x$bh.tch or anything else we used to find the highly
active/contended tables?

I believe I have a couple of tables which are very active and I would like to
find whether they really have the contention problem. How do I determine it?
(Oracle version 8.1.7.4)

From v$waitstat, I know there are contentions.

Thanks,

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:865627341240
we can find it by file.

      Rating: 4
What if the
      datafile has lot of objects?  July 18, 2002

Reviewer:  Prince.
Thanks Tom for the pointer!

I already read the doc you pointed. But, how do I identify the particular object
if the datafile has many many objects. I have coouple of datafiles in the TS and
are having almost the same number of I/O.

          Followup:

you would start segmenting your objects into different tablespaces then.  We can
get you to the file.  If you have many objects using that file -- you'll need to
move them about to figure it out.

      Rating: 4
Very
      Interesting! But...  July 19, 2002
Reviewer:
      Robert  from Tennessee, USA
Tom,

I just checked our prod database and found these data block wait stats seemingly
very high. For one of our largest tables (120M rows) we had 14,368,500 of these
waits.
Our database has been up for 5 days.
I am going to show this to our head DBA.

Is there a downside to having too many freelists? ... can one shoot oneself in
the foot by setting this too high?

Thanks,

Robert

          Followup:

You may find you use slightly more space with multiple freelists.  A process
(connection) will only use space from ONE freelist -- so if it runs out of space
in its freelist, it'll grab a new extent, before reusing the space on the other
freelists.

In general -- in a high volume situation with lots of contention for a single
freelist -- this is OK and not an issue (since you are by definition consuming
the freelist space rapidly as it is!)

      Rating:
      5
  July 07, 2003
Reviewer:
      A reader

      Rating:
      4
Freelists  February 10, 2004
Reviewer:
      vinnie  from Orlando
Tom,

I have a tablespace created as such:

create tablespace x
datafile 'x'
size 50m resuse autoextend on next 10m maxsize unlimited
extent management local;

Can I still use the freelist clause for tables?

          Followup:

yup

      Rating:
      3
Datafile  February 13, 2004
Reviewer:
      Gururaj Kulkarni  from Bangalore India
Tom,

When I queried my database (tiny one), I got something like this:

SQL> select * from v$waitstat where class='data block';

CLASS                   COUNT       TIME
------------------ ---------- ----------
data block                  2          1

Could you tell me what is the significance of the time here?

Another thing is, I heard somebody saying having more datafiles in a tablespace
would improve the performance of select query. Is it true ?

Thanks
-Gururaj

          Followup:

means you spent virtually no time (1/100th of a second) waiting, twice.

it is true that

adding more datafiles

a) can improve performance
b) destroy performance
c) do nothing at all to your performance

C is the most likely response for most situations.  A is most likely if you have
no raid implemented and have widely unbalanced IO.  B is most likely if you add
too many files (and we spend most of our time opening and closing them for you)

February 13, 2004 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

      Rating: 5
password
      file  February 12, 2004
Reviewer:  atul
       from India
Hi,

We are having a database running with ownership of oracle as

oracle  66568  1   0   Feb 09  -  0:03 ora_smon_test_154

I don't have oracle password,but through password file i can login as "svrmgrl"
or as "sysdba".

When i logged in through my unix-id "t1test" and connected as svrmgrl and
started the database it shows as follows:

t1test  66568  1   0   Feb 09  -  0:03 ora_smon_test_154

If you see its showing my id instead of "oracle".
Can i give this database to users to use,what ill effects are there to start
with other id?

Thank,
ATUL

          Followup:

probably means your install is wrong.  what are the permissions on the oracle
binary?  it should look like this:

$ ls -l $ORACLE_HOME/bin/oracle
-rwsr-s--x    1 ora9ir2  ora9ir2  51500205 Nov 25 10:55 /home/ora9ir2/bin/oracle

if you do not see the 's' in there, your install is wrong, you did not run
root.sh after installing.

      Rating: 5
password
      file  February 13, 2004
Reviewer:  atul
       from India
Hi,
setting is as follows:

-rwsr-s--x   1 oracle   dba  37836717 Dec 02 2002  oracle

You can see 's' there.

One thing is that this base has been upgraded from 8.0.6 to 8.1.7..

Thank,
ATUL

          Followup:

"base".  hmmm. whats that.  anyway.

if the setuid bit is on
AND
it is the actual binary you are using (eg: you are not confused over oracle
homes)
AND
the userid isn't showing as oracle
THEN
something is fishy in your system.

regardless of who I log in as, when you have a setuid program as the oracle
binary is, it'll be running "as the owner"

tell me, what does ipcs -a show you, who is the owner of the shared memory
segments associated with the SGA.  If that is not Oracle -- you are "getting
confused" somewhere for the s bit would ensure that Oracle was the owner.

February 13, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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://download-west.oracle.com/docs/cd/B10501_01/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.

      Rating: 5
Indexes and
      FTS  February 13, 2004
Reviewer:  Arun Gupta
       from Harrisburg, PA USA
Tom,
Is it normal to NOT have indexes on fact tables in a star schema? Is it normal
to do a full table scan of a  13 million row fact table and hash join with a
dimension table?
Thanks

          Followup:

not really -- and indexes are NOT the only game in town

star schemas can bitmap index the foreign keys to enable star transformations.

you use bitmap join indexes sometimes to denormalize the structures without
denormalizing them.

many fact tables are partitioned (similar to the concept behind an index, reduce
the data that needs be scanned via partition elimination)

MV's are used to pre-aggregate/join things.

February 13, 2004 -- 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://download-west.oracle.com/docs/cd/A87860_01/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.

      Rating: 4
How to display
      numbers in fixed significant numbers  February 13, 2004

Reviewer:  A reader
Tom,

Is there a way to display the numbers with only three significant numbers?  Here
is the situation:

In the table:
col1                Desired display
======             ==================
105125    --->       105000
105687    --->       106000
.011223   --->       .0112
.0012367  --->       .00124

Thanks,

          Followup:

that is a really strange request. but, we can do:

ops$tkyte@ORA9IR2> select x, to_number( to_char( x, '9.99EEEE' ) ) from t
  2  /

         X TO_NUMBER(TO_CHAR(X,'9.99EEEE'))
---------- --------------------------------
    105125                           105000
    105687                           106000
   .011223                            .0112
  .0012367                           .00124

      Rating: 5
You are
      amazing, thank you so much!  February 13, 2004

Reviewer:  A reader

      Rating: 5
It is
      amazing  February 13, 2004
Reviewer:
      Shailandra  from NJ
Tom,

This was amazing. Can you give the logic behind the above answer.

          Followup:

the to_char converted the number into a string, preserving only 3 digits using
scientific notation (just run the to_char by itself to see what it does)

the to_number took this string and converted back into a number, there was only
three digits left so thats all we get.

February 13, 2004 Yong -- Thanks for the question regarding "Enable
      Novalidate does not work for PK?", version 8.1.5

      You Asked
I know NOVALIDATE works for foreign keys. But it doesn't seem to
work for primary keys:

SQL> create table q (a number, constraint pk_q primary key (a));

Table created.

SQL> alter table q disable constraint pk_q;

Table altered.

SQL> insert into q values (1);

1 row created.

SQL> insert into q values (1);

1 row created.

SQL> alter table q modify constraint pk_q enable novalidate;
alter table q modify constraint pk_q enable novalidate
*
ERROR at line 1:
ORA-02437: cannot validate (INDIGO2.PK_Q) - primary key violated

SQL> alter table q enable novalidate primary key;
alter table q enable novalidate primary key
*
ERROR at line 1:
ORA-02437: cannot validate (INDIGO2.PK_Q) - primary key violated

      and we said...
You can do this with a deferrable constraint.  These are
enforced with non-unique indexes.  They are not the default (so
by default -- unique index).

It would look like:

ops$tkyte@8i> create table t
  2  ( x int,
  3    constraint t_pk primary key (x)
  4    deferrable initially immediate );

Table created.

ops$tkyte@8i>
ops$tkyte@8i> alter table t disable constraint t_pk;

Table altered.

ops$tkyte@8i>
ops$tkyte@8i> insert into t values ( 1 );

1 row created.

ops$tkyte@8i> insert into t values ( 1);

1 row created.

ops$tkyte@8i>
ops$tkyte@8i> alter table t modify constraint t_pk enable
novalidate
  2  /

Table altered.

ops$tkyte@8i>
ops$tkyte@8i> insert into t values ( 1);
insert into t values ( 1)
            *
ERROR at line 1:
ORA-00001: unique constraint (OPS$TKYTE.T_PK) violated

ops$tkyte@8i>
Review & Followup

      Rating: 4
...or
      pre-create the index on pk as non-unique  October 30, 2001

Reviewer:  Nj?l A. Ekern  from Norway
Very appliciable that Oracle creates non-unique indexes for deferrable unique
constraints.

Another way to do this is to pre-create a non-unique index on the primary key.
The creation of the primary key will not create an index automatically if a
similar index (unique or non-unique) already exists on the pk-columns:

SQL> create table q (a number);

Table created.

-- Now, create a non-unique index:

SQL> create index pk_q on q(a);

Index created.

SQL> alter table q add(constraint pk_q primary key (a));

Table altered.

SQL> alter table q disable constraint pk_q;

Table altered.

SQL> insert into q values (1);

1 row created.

SQL> insert into q values (1);

1 row created.

SQL> alter table q modify constraint pk_q enable novalidate;

Table altered.

SQL> insert into q values (1);
insert into q values (1)
*
ERROR at line 1:
ORA-00001: unique constraint (xxx.PK_Q) violated

SQL> insert into q values (2);

1 row created.

SQL> commit;

Commit complete.

SQL> select * from q;
         1
         1
         2

3 rows selected.

      Rating:
      5
  January 06, 2004
Reviewer:  Puneet
      Mathur  from India
Excellent search was excatly what i wanted

      Rating: 4
Primary Key and
      Non-Unique Index  February 13, 2004
Reviewer:
      Reviewer  from Japan
Suppose a primary key is defined as Deferrable and Initially immediate. Then a
non-unique index is created for the primary key.

Then how the uniqueness of the Primary key column is checked. I have read that
Primary key is implemented by using a Unique Index.

          Followup:

You read something that is not correct.

Since we implemented deferrable constraints, all we need is an index -- unique
or otherwise.

"how" -- Oracle does it.  It uses an index, but it does it.  Index need not be
unique.  It just "works"

February 13, 2004 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"

      Rating: 4
How does
      freelist help block contention ??  February 13, 2004

Reviewer:  amit poddar  from New Haven, CT USA

Hi,
You mentioned in response to the first question that block contention can be
reduced by increasing the freelist.
My question is how ??
since the free list is stored in the segment header, it makes sense that
increasing free list would help segment header contenetion but how would it help
block contention ?

          Followup:

getting new blocks for inserted data -- if you have more than one freelist, you
can have more than one person at a time getting a block for an insert - else,
you wait for one another (momentarily, not for the entire transaction, but it
would be a wait for a data block)

      Rating:
      3
clarification  February 13, 2004
Reviewer:
      amit poddar  from New Haven, CT USA
Hi,

What do you mean by you would wait for one onother ??

Once the process has got the block from the free list it would release the lock
on segment header block so that next process can look for a free block.

Hence 2nd process would register only a segment header wait not a data block
wait .

I know I am wrong somewhere could you please point where ??

Moreover suppose a the free list has 3 block 1-2-3 .
Now process 1 gets block 2 for insert a releases the lock on segment header.
Would process 2 also get block 2 since block 2 would be still on the free list
(it  would be removed from free list only when process 1's insert completes and
if the free space hase gone below pctfree)

          Followup:

It is not a segment header wait, it is a block wait.

if you have expert one on one Oracle -- my first book -- i have many simulations
in there that walk through this.

February 13, 2004 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.

      Rating: 4
How does one
      display the value of a directory created using "create directory ... as
      ..."?  February 13, 2004
Reviewer:  ht
       from California
Tom,
select * from dba_objects where object_type='DIRECTORY' will display the
directories a user has created.  How does one display the actual directory value
(TMPDIR=/tmp)?
Thanks,
ht

          Followup:

DBA_DIRECTORIES

      Rating:
      5
  February 13, 2004
Reviewer:
      A reader

      Rating: 5
How do I read
      files from a certain directory with PL/SQL, without  April
      02, 2004
Reviewer:  Adrian  from Belgium
Hi,

First time that I've used your site, found this review via a google search.
Solved my problem both elegently and imaginatively,

wonderfull

many thanks

February 14, 2004 Mohammed Irfan -- Thanks for the question
      regarding "CPU Time", version 7.3.4

      You Asked
Hi Tom,

Can you please explain us to make us understand the concepts, the result we get
when tkprof is executed. For example call  count

cpu  (how the time is calculated)
elapsed
disk
query
current
rows
library cache misses
Execution Plan (The different results we get on execution).
Its a bit tough to understand from manuals. I hope you will provide with
example.

Also what is and CBO & RBO and how is one better than the other. Any examples
will do..

Thanks a lot...

      and we said...
Do you have my book?  I go into GREAT details on tkprof, statspack and the other
tools like dbms_profiler available to you.

You can read:

http://download-east.oracle.com/docs/cd/A81042_01/DOC/server.816/a76992/ch14_str.htm#1018
its from the 816 doc set but applies 100% to 7.3

With regards to the optimizer -- read the Oracle7 Tuning guide, it goes into
detail on the CBO vs RBO.  In Oracle8i and up -- CBO is the *only* one to use.
In 7.3.4, I would use CBO when using parallel query or bitmapped indexes or
something that required it.
Review &
      Followup

      Rating: 5
SQL
      Profiler  February 13, 2004
Reviewer:
      A reader
Hi Tom,

Is there any tool I can use to monitor all sql statements executing in the
current instance? Just like the MS SQL Profiler.

Thanks,

          Followup:

the v$ tables (see the Oracle Reference guide) shows you all of the current AND
past activity.  Many hundreds of tools -- from OEM (Oracle Enterprise Manager)
to Quest to whatever -- can automate the querying of this information (but good
old sqlplus command line lets you have at it all)

alter session set sql_trace=true;

lets you trace individual sessions, see:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/toc.htm

to info on sql_trace, tkprof and statspack as well.

February 14, 2004 Shefali -- Thanks for the question regarding
      "Disable Default Sorting", version 8.1.7

      You Asked
Hi Tom,

I have the following query:

create table semp (emp_id int, emp_name varchar2(50));

insert into semp values (1, 'ABC');
insert into semp values (2, 'XYZ');
insert into semp values (3, 'LMN');
insert into semp values (4, 'PQR');

select * from semp;

    EMP_ID EMP_NAME
---------- -----------
         1 ABC
         2 XYZ
         3 LMN
         4 PQR

select emp_name from semp where emp_id in (3,1,4,2);
EMP_NAME
-----------
ABC
XYZ
LMN
PQR

The above query returns a result set based on the timestamp the records were
inserted into the table.

The result set I want is based on what is specified in th IN Clause
i.e.

EMP_NAME
---------
LMN
ABC
PQR
XYZ

How can this be achieved, Basically I need to disable the default sort method of
Oracle.

Thanks,
Shefali

      and we said...
There is no such THING as a "default sort".  The data is *not* coming out in the
order of insertion.  That is a common myth -- but it is not so.

In order to get data out in a specific order -- you MUST use an order by.  Else
the data comes out in any order it wishes.

You can:

select *
  from emp
 where emp_id in ( 3, 1, 4, 2 )
 order by decode(emp_id, 3, 1, 1, 2, 4, 3, 2, 4 );

Review &
      Followup

      Rating:
      5
  April 23, 2002
Reviewer:  reader

excellent solution..!!!

      Rating: 4
URGENT -
      sorting the results when the sql can't be changed  February
      12, 2004
Reviewer:  M.Saeed
Hi Tom:

How can I change the sort order of a result set without changing the sql
statement.

We have a case where we have discovered a bug in our application and the source
of the problem is the order in which the results are sent back from the d/b. We
can't change the code that generates the sql.

Will creating a view with an order by clause help ? I'm assuming the view will
have to be the same name as the tablename for the current code to work (and that
can't be, which means that I have to rename the table), but then:

How will the dml work against the table/view?

What other implications do I need to be concernd with ?

This is a quick fix until we can patch the application with the correct sql..

Thanks for your help (hope you are not too busy to answer this).

          Followup:

there is a slim, very very slim chance that a view can be used -- it is not
assured, it is not promised -- you still have a bug.

I would suggest that the only way, ONLY WAY to fix this is to develop a patch
for your code.

DML will be not 100% supported with the order by.

you can TRY,if this is a simple, single table query, to do this (rename the
table, create the view as the name of the table)

ops$tkyte@ORA9IR2> create table emp
  2  as
  3  select * from scott.emp;

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace view emp_view
  2  as
  3  select * from emp order by empno;

View created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create index emp_idx on emp(ename);

Index created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select empno, ename
  2    from emp_view
  3   where ename like 'A%'
  4  /

     EMPNO ENAME
---------- ----------
      7499 ALLEN
      7876 ADAMS

you'll want to explain plan that to verify it is sorting as the last step!

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> update emp_view set ename = lower(ename);

14 rows updated.

ops$tkyte@ORA9IR2> insert into emp_view select * from scott.emp;
insert into emp_view select * from scott.emp
            *
ERROR at line 1:
ORA-01732: data manipulation operation not legal on this view

inserts won't happpen

ops$tkyte@ORA9IR2> delete from emp_view;

14 rows deleted.

      Rating: 4
Thanks....a
      lot, I guess patch is the only fool proof way.  February 12,
      2004
Reviewer:  M.Saeed

      Rating: 4
Just a wild
      thought....  February 13, 2004
Reviewer:
      M.Saeed
Hi Tom:

I tried playing with Materialized View to see if it would give me sorted
results.

It seems to work with the data currently present in the table, but I can't get
the MV to refresh after DML to the table.

Anything I'm overlooking.

Here is the example from the SCOTT schema:

Notice that I do get results sorted by deptno in desc order as per my MV query.
But fails after the newly inserted records.

SQL> CREATE TABLE MS_1 AS SELECT * FROM DEPT;

Table created.

SQL> SELECT * FROM MS_1;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 ACCOUNTING     NEW YORK
        20 RESEARCH       DALLAS
        30 SALES          CHICAGO
        40 OPERATIONS     BOSTON

SQL>
SQL>
SQL> ALTER TABLE SCOTT.MS_1
  2    ADD CONSTRAINT pk_ms_1 PRIMARY KEY (DEPTNO)
  3  /

Table altered.

SQL> create materialized view log on ms_1 nocache with primary key including new
values;

Materialized view log created.

SQL>
SQL> create materialized view mv_ms_1 build immediate refresh fast on commit
with primary key enable
query rewrite as select * from ms_1 order by deptno desc;

Materialized view created.

SQL> set autotrace on;
SQL> show parameter query

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
query_rewrite_enabled                string      TRUE
query_rewrite_integrity              string      enforced
SQL>

SQL> select * from ms_1;

    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 'MS_1'

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          4  consistent gets
          0  physical reads
          0  redo size
        600  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)
          4  rows processed

SQL> analyze table ms_1 compute statistics for table for all indexes for all
indexed columns;

Table analyzed.

SQL> select * from ms_1;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        40 OPERATIONS     BOSTON
        30 SALES          CHICAGO
        20 RESEARCH       DALLAS
        10 ACCOUNTING     NEW YORK

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=327 Bytes=981
          0)

   1    0   TABLE ACCESS (FULL) OF 'MV_MS_1' (Cost=2 Card=327 Bytes=98
          10)

Statistics
----------------------------------------------------------
        113  recursive calls
          0  db block gets
         25  consistent gets
          1  physical reads
          0  redo size
        600  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)
          4  rows processed

SQL> insert into ms_1 values (11,'Cust_Supp','Zanzibar');

1 row created.

Execution Plan
----------------------------------------------------------
   0      INSERT STATEMENT Optimizer=CHOOSE (Cost=2 Card=4 Bytes=76)

Statistics
----------------------------------------------------------
         12  recursive calls
         40  db block gets
         24  consistent gets
          0  physical reads
       2004  redo size
        621  bytes sent via SQL*Net to client
        551  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL> commit;

Commit complete.

SQL> select * from ms_1;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        40 OPERATIONS     BOSTON
        30 SALES          CHICAGO
        20 RESEARCH       DALLAS
        10 ACCOUNTING     NEW YORK
        11 Cust_Supp      Zanzibar

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=327 Bytes=981
          0)

   1    0   TABLE ACCESS (FULL) OF 'MV_MS_1' (Cost=2 Card=327 Bytes=98
          10)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          8  consistent gets
          0  physical reads
          0  redo size
        627  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)
          5  rows processed

SQL> select deptno from ms_1;

    DEPTNO
----------
        10
        11
        20
        30
        40

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=4 Bytes=8)
   1    0   INDEX (FULL SCAN) OF 'PK_MS_1' (UNIQUE) (Cost=1 Card=4 Byt
          es=8)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          2  consistent gets
          0  physical reads
          0  redo size
        436  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)
          5  rows processed

SQL> select deptno,loc from ms_1;

    DEPTNO LOC
---------- -------------
        40 BOSTON
        30 CHICAGO
        20 DALLAS
        10 NEW YORK
        11 Zanzibar

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=327 Bytes=686
          7)

   1    0   TABLE ACCESS (FULL) OF 'MV_MS_1' (Cost=2 Card=327 Bytes=68
          67)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          8  consistent gets
          0  physical reads
          0  redo size
        527  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)
          5  rows processed

SQL>

Would this even work if the table was not very dynamic and/or with several
thousand records only.

Thanks for your assistance.

M.Saeed

          Followup:

there is exactly ONE way and only ONE way to fix this bug in the code.

that is -- fix the bug in the code.

don't even go down this path with MV's

I'm curious now.  Why would it be soooooo hard to patch your application?  I
mean you've been staring at this issue for more than a day now right?  Seems it
would take about 5 minutes to "fix" this problem and less time to deploy the
patch.

You CANNOT get ordered data from the database without an ORDER BY in any sort of
reliable fashion.

      Rating: 4
Totally
      agreed....but  February 13, 2004
Reviewer:
      M.Saeed
Trivial as it may sound, adding an order by clause in the application, the s/w
has to undergo regression testing and the question of version dependancy etc...

basically it's a can of worms....

I was just experimenting with what ever could be done purely from the d/b
perpective, while the developpers figured out how they were going to release
this patch from which baseline of the s/w.

I totally agree with you. The fix is very trivial but the implications on the
development/release/QA is great.

Thanks

M.Saeed

          Followup:

the software is BROKEN.  the software doesn't WORK.  Hmmmm.  funny, you are
protecting something that doesn't work in the first place.

(with the view above, you could use an INSTEAD OF trigger to insert - however,
it would be very shaky -- not reliable -- as to the order by.

SEEMS SILLY THAT A CHANGE IN THE CODE that would definitely fix the bug would
have to go through that process

BUT A CHANGE TO THE DATABASE would not.... hmmm, something is broken in this
process methinks)

      Rating:
      4
  February 13, 2004
Reviewer:
      A reader
I have view as
select item_id, location, lot_number
from t
order by location.
But if lot_number is not null
i have to order by lot_number.
Can I create such view?

          Followup:

huh?

do you want to order by lot_number within location?  if so, just

order by location, lot_number

otherwise, not sure "what you mean" -- do you mean "if lot_number is not null,
then order by it, else order by location"  that would be

order by decode( lot_number, null, location, lot_number )

(i hate views with order bys.... the application should be doing that.... but
anyway)

      Rating:
      4
  February 13, 2004
Reviewer:
      A reader
Sorry, I wasn't spesific.
I have view:create view locations_id_v
as
select inventory_id, location_id,room,partition,shelf,Expiration_date,lot_number
from (select inventory_id, location_id,room,partition,shelf,
row_number() over (partition by inventory_id order by location_id) rn
from locations)
where rn <= 3
Inside each group sorted by location_id,
if Expiration_date or lot_number aren't null
I need to sort by Expiration_date?

Thank you.

          Followup:

just add the order by -- with expiration_date at the end of the list of columns
to order by.

      Rating:
      5
  February 13, 2004
Reviewer:
      A reader
You mean this
select inventory_id, location_id,room,partition,shelf,Expiration_date,lot_number
from (select inventory_id, location_id,room,partition,shelf,
row_number() over (partition by inventory_id order by location_id) rn
from locations
order by Expiration_date)
where rn <= 3
But I might be missing oldest expiration dates.
For Ex.
inventory_id   location_id   Expiration_date
  100             101            1/1/03
  100             102            1/2/03
  100             103            1/3/03
  100             104            1/1/02
  100             105            1/2/02
 1/1/2002 and 1/2/2002 aren't in this view.

thank you.

          Followup:

does not compute.

you assign row_number() by inventory_id after sorting by location_id.

tell me -- how do you WANT to assign row_number() ??

by location id?
by expiration_date?
by what?

you tell me - and I'll tell you.  seems you want to sort by expiration date
perhaps?  it really isn't very clear at all here.

      Rating:
      5
  February 13, 2004
Reviewer:
      A reader
inventory_id   location_id   Expiration_date
  100             101            1/1/03
  100             102            1/2/03
  100             103            1/3/03
  100             104            1/1/02
  100             105            1/2/02
  101             110
  102             122
  102             111
  102              99
  102              66
  102             133
I need:
 inventory_id   location_id   Expiration_date
   100             104            1/1/02
   100             105            1/2/02
   100             101            1/1/03
   101             110
   102             66
   102             99
   102             111

thank you.

          Followup:

yeah, so you still have not told me what it is you want.

tell me -- how do you WANT to assign row_number() ??

by location id?
by expiration_date?
by what?

row_number() over (partition by inventory_id order by expiration_date,
location_id) rn

seems to be the RN you want.  it'll sort by expiration_date first (nulls go
last) and then by location_id.  You will pick off the three OLDEST locations or,
if the expiration date is NULL, the three lowest location_ids per inventory_id

      Rating: 4
Fixing the
      code  February 20, 2004
Reviewer:  M.Saeed

Tom:

Totally agreed, again.

I was just trying to explore any way including changing the code to be able to
get sorted results. If we found a working solution then you are right, it would
have gone through some testing also before being implemented.

I expected that implementing a d/b level solution would be much quicker than
fixing the code and making a new release/patch.

Thanks anyway for your invaluble input and the time to respond to my urgent
plea.

M.Saeed.

February 14, 2004 subhash -- Thanks for the question regarding
      "sqlcxt undefined", version

      You Asked
Sir, I have typical problem.
I am writing proc code first time,
I have written simple code to connect database and display
emp_no
and name.

When i have issued the follwoing command

$proc sample1.pc

It has created a sample1.c file and
when i issued command

$cc sample1.c
It has given me error
Undefined                       first referenced
symbol                             in file
sqlcxt                              sam1.o
ld: fatal: Symbol referencing errors. No output written to a.out

Actually sir i don't known what all the settings i have to do in

my .profile
or any thing else

Can you pls mail me the details at
bhandari@irs.co.id

Thanks

Subhash Bhandari

      and we said...
You need to link in the Pro*C Libraries.

See
http://govt.oracle.com/~tkyte/proc_makefile/
 for an example, generic makefile for Pro*C.

Review &
      Followup

      Rating: 5
help about
      linking a library  February 14, 2004
Reviewer:
      Fernando Sánchez  from Madrid, Spain
By using the method you recommended above I've been able to precompile an
compile several .pc files and making an exutable one for that having a main
function.

Also, by watching how the above process was made I could make an executable file
from a .c file with main function that used a .pc file I precompiled (by
compiling and linking both .c files together).

But I haven't been able to make an executable from a .c file with a main
function that makes calls to a library (a .a file) I precompiled and compiled
alone (without the one with the main function).

Thanks for any advice.

          Followup:

just add your libraries to the link line:

$(TARGET): $(SOURCE) $(SOURCE:.pc=.c) $(SOURCE:.pc=.o)
    $(CC) $(LDFLAGS) -t -o $(TARGET) \
        $(SOURCE:.pc=.o) -L$(ORACLE_HOME)/lib $(PROLDLIBS) -l testing

                                               ....

that'd add "libtesting.a" to the link.

February 14, 2004 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.

      Rating: 5
jdbc and
      database sessions  February 14, 2004
Reviewer:
      A reader
Hi

Let's say I have a repot generation tool programmed using JSP, Servlets, Thin
JDBC, connection pool etc. If I run a report which takes 10 minutes and my PC
hangs before the repot finishes what happens to the database session? Does the
report keep on running? Is there a way to kill/contol database session if this
situation happens? Imagine my uses arent patient and keep on running reports
thinking their previous executions hanged my database will be flooded!

          Followup:

who cares about your PC here, the code is running on the server -- the
application server.

Imagine if you will a DIFFERENT architecture whereby you do not run things from
a browser that take 10 minutes.  I (being a patient person normally) would not
have the tolerance for anything taking more than a couple of SECONDS in a web
page.

Sounds like you need to

a) submit a request for a report to run
b) email them a link to the report when it is done or
c) have a page they can goto to see the reports they've submitted and that are
ready for review.

In this fashion, you can

1) control the number of concurrently executing reports easiy
2) facilitate the sharing (caching) of reports -- if two people request the same
report you may well already have it
3) use off the shelf offerings that already do this sort of stuff (eg: Oracle
Reports is just such a tool)

So, your PC has nothing to do with the equation here.

The application server is running the report, it doesn't "hang".

Asking people to wait 10 minutes for a page will never fly in the real world
(many browsers would time out for example, very fragile).

You want to look for an alternative solution that makes it so the user doesn't
have to wait.

      Rating: 5
what if
      application server fails?  February 14, 2004

Reviewer:  A reader
What happens if the application server fails? Do the database sessions stop as
well if there are long queries running?

          Followup:

depends, did your DBA set up DCD (dead connection detection) or not....

if not, the database might not realize the client is gone for a while....

February 15, 2004 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'.
:-)

      Rating: 3
first
      transaction in Oracle  February 15, 2004
Reviewer:
      vkoul  from Lacey, WA USA
Sorry Tom, I was waiting for putting a question, but weeks after weeks I was not
able to, so I am putting this question.

On Database startup what is the first transaction Oracle performs (I was asked
to answer this question in an interview)

          Followup:

trick question, one that is not answerable by the interviewers themselves unless
they wrote the Oracle database itself (doubtful, no?)

I'd ask them, hmm, you mean transcations are happening when I do a startup
nomount?  Amazing -  i did not know they could do that without any sort of redo
logs, datafiles, whatever.

maybe they meant, "what does oracle sort of do upon startup", that would be a
fair question -- one that most experienced DBA's should be able to answer in
general.  Things like "well, it use the init file to find the control files.  it
would use the control files to find the data/online redo log files.  It would
then see if crash recovery needed to be done.  If so, it would use the redo to
replay the transactions that were not yet recorded to the datafiles.  It would
then use the UNDO information to rollback transactions that were in flight when
the db crashed and then ....blah blah blah...."

      Rating: 4
First
      Transaction In Oracle  February 17, 2004
Reviewer:
      VKOUL  from Lacey, WA USA
Thanks Tom, You are the best ...

February 15, 2004 Tim -- Thanks for the question regarding "16 bit
      or 32 bit or 64 bit", version 8.1.5

      You Asked
Tom:

I want to know what do you mean by "Oracle 8.1.6 for solaris 64 bit"? I have
seen so many places mentined 16 bit Oracle or 32 bit Oracle or 64 bit Oracle.
Now I found Oracle 8.1.6 for solaris 64 bit and I hoping definately it should be
for 32 bit and 16 bit solaris also. Same thing for NT 64 bit or 32 bit or 16
bit.

Basically what I want to know weather Oracle itself in differ from 64 bit to 32
bit or 32 bit to 16 bit.
or
It's operating system specific, I mean Oracle for 64 bit solaris or Oracle for
32 bit/16 bit solaris (also for NT OS).

Thanks

Tim

      and we said...
There is no such thing as 16 bit solaris.

Oracle (the database) operates much the same regardless of the "bits" on the
system.  We are very portable that way.

A 64bit OS would allow us to address much more memory then a 32bit one.  On a
32bit system (a system where pointers are 32bits) we can address 4gig or less of
RAM.  That limits the size of an SGA.  On a 64bit system -- we can address an
incredibly large SGA.

16, 32, 64 all speak to the native word size of the underlying hardware.  We
have versions of Oracle for 32 bit platforms and 64 bit platforms.  Windows used
to be a 16 bit application (DOS) and the tools that ran on it were 16 bit. We
windows 3.x came out -- things could be "win32" or 16 bit -- hence the large
number of "16 bit" or "32 bit" tools you saw.

Review &
      Followup

      Rating: 4
16 bit or 32
      bit or 64 bit  May 21, 2001
Reviewer:
      Sanjeev
Good One!!!

      Rating: 4
How would i
      know if the Oracle db is 32 or 64 bit ?  February 15, 2004

Reviewer:  Alvin  from Philippines
How would i know if the Oracle db is 32 or 64 bit ? i ran sqlplus in our solaris
server and it says.

SQL*Plus: Release 8.1.7.0.0 - Production on Sat Feb 14 21:28:44 2004

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

My Unix SA says it could be the sqlplus is 32 bit but oracle db itself can be 64
bit. Where do i check ?

          Followup:

if the OS is 64 bit only (eg: 9i's on AIX, HP) the db is definitely 64bit.

if the OS is either 32 or 64 bit (eg: solaris for example), the banner would say
"64bit"

Actually -- the person that needs to know, the only one it affects, the DBA
would already know this.  They installed it after all.

To the end user, to the developer, it is not "visible", it isn't something they
need to see really.  They cannot tell the difference between 64 and 32 bitness
in the database.  It changes none of their code, it changes nothing they can
"see"

      Rating: 5
question
      clarification  February 15, 2004
Reviewer:
      Alvin  from Philippines
As posted above.

1. How/Where do i find out whether or not our Oracle DB is in 32 or 64 bit on a
Oracle 8.1.7.0.0 db ?

I was applying the 8174 (32bit) patch on our developmental database to determine
how much time a database patch consumes. On the OEM installer GUI i hit an error
the partial snapshot of the logs are...

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 = ioracle
        undoMakeFileName =
        installArguments = ORACLE_HOME=/export/spare/app/oracle/product/8.1.7,
        logFile = /export/spare/app/oracle/product/8.1.7/install/make.log
        undoTarget =

Exception thrown from action: make
Exception Name: MakefileException
Exception String: Error in invoking target ioracle of makefile
/export/spare/app/oracle/product/8.1.7/rdbms/lib/ins_rdbms.mk
Exception Severity: 1
*** Cancel Dialog:  ***
User selected: Stop installation of all products.

Error in invoking target ioracle of makefile
/export/spare/app/oracle/product/8.1.7/rdbms/lib/ins_rdbms.mk/export/spare/app/or
acle/oraInventory/logs->

2. Say that my UNIX SA is right that the oracle software installed was a 64bit
one. He's suggesting that i install the 32 bit over the existing installation.
I'm a little doubtful with his suggestion an am seeking for your advice. Can i
just install the 32bit over the 64 bit one or will there be (any) complications
ahead ?

          Followup:

Please contact support for installation/configuration questions like this --
that is what they are really good at.  without the right information, one cannot
make the right suggestions.  I don't even know what OS you are on (and no, just
providing that won't let me answer either).

Please -- use support for something like this.

February 15, 2004 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

      Rating:
      5
Switching  February 13, 2004
Reviewer:
      Invisible  from UK
Since it appears to be impossible to post a question directly, I'll try here.
(Been waiting 2 weeks now - still, there's only one Tom!)

We have a database used for some app we didn't develop. Database was set up
before I joined the company. According to the DB person who set it up, in Oracle
online redo log files always have to be 1MB in size. (I now know this is false.)

I read somewhere that you should aim to have 1 log switch every 15 mins
roughtly. (Obviously it varies.) Do you agree with this?

To begin with, we had about 1 log switch per month. Then it went up to 1 per
week. Then it went up to as much as 4 per day.

It's still holding at about 4 per day - except that, now and then, I see little
"bursts" of activity. Sometimes it appears that up to 5 log switches have
occurred in less than 1 second!

Is that even physically possible? (We only have 4 online redo logs - ARCHIVELOG
mode of course. ;-)

Speaking to app users (I don't even know what the app is supposed to do!), it
appears they gradually fill up the database, but when a "project" is completed,
it gets taken out of the database and put into our archives on CD.

I *imagine* this "taking it out of the database" is producing large amounts of
redo fairly fast. If the DB went down half way through, Oracle would have to
"undelete" all this stuff to get to a consistent state...

Questions:
1. Should I make the online redo logs larger?
2. How much larger?
3. Do I have to switch off the database to do this?
4. How (if at all) will this affect database recovery? (Recommendation from my
Oracle book is to take a cold backup any time you do something major to the
database.)

Also have questions about hot vs. cold backups, but I'll try to find a more
appropreate thread to tack those onto...

          Followup:

15 is as good as 30 is as good as 60 is as good as 5.  pick a number, any
number.

yes, 5 is possible in a short period of time, even with 4 -- depends on what we
have to checkpoint (could be you updated a single block ALOT generating gobs of
redo but very very little work for dbwr)

1) cannot answer that.  are you suffering from "checkpoint not complete cannot
allocate new log".?
2) see #1
3) no
4) you won't need to backup since you'll just be adding larger logs or more logs
and you never never never backup online redo !

      Rating:
      5
Thanks  February 13, 2004
Reviewer:
      Invisible  from UK
Well, that's what I call a fast responce...

We're not having any particular issues that I know about, other than that (my
magic book says) reducing the number of log switches might make things go
faster. (Not having tried it, I can't really comment.)

What is there to checkpoint? Well, since the peak activity is (presumably) one
big delete, I would imagine it's accessing each block only once... (I won't
pretend to have a deep understanding of how Oracle works at the block level.)

Since the problem (if it is one!) is rapid log switches (rather than actually
running out of online redo), I presume the thing to do is make the logs bigger
rather than allocate more of them...

No need to backup? Yup, I like that idea... It's just I'm told certain events
are hard to recover through. (e.g., reset logs is supposedly a *very* hard one.
Not something I'm hoping to have to do, like, EVER.)

Just curious... the archived logs are sometimes smaller than the online ones.
Does the archiver actually "know" how much real data is in there? I had assumed
it just does an OS file copy...

          Followup:

if the archives are smaller, that is usually due to

a) someone doing a switch logfile command
b) using the archive lag init.ora parameter to force log switches on a recurring
basis (9i)

      Rating: 4
"redo log
      buffer", version 7.3.4  February 13, 2004

Reviewer:  VKOUL  from Lacey, WA USA
But I have seen the archived log files of different sizes without someone doing
a switch logfile command

Could it be checkpointing

          Followup:

log switches cause checkpoints.

checkpoints do not cause log switches.

      Rating:
      3
Nice  February 15, 2004
Reviewer:
      Carl  from London,UK
Dear Tom,
What does the wait event "log file sync" deal with?
Please do reply.
Thanks.

          Followup:

it is the wait event a client process gets when they issue "commit" and must
wait for LGWR to finish the commit event.

Generally occurs with people committing in a tight for loop, in the mistaken
thought that comitting frequently "is a good thing (tm)".  It is a "bad" thing.

February 15, 2004 -- Thanks for the question regarding "scalar
      query", version 8.1.7

      You Asked
Tom,

I was reading your example in oracle magazine long time back where in order to
select max(wt) from some table

you used

     select wt from
      (select wt
       from t1
       where pet_id = :b1
        order by pet_id desc,
                  setup_dte desc,
                  wt desc)
       where rownum = 1;

Now i want to use the same logic as

select pet_id, (select wt from
      (select wt
       from t1
       where t1.pet_id = pet_id
        order by pet_id desc,
                  setup_dte desc,
                  wt desc)
       where rownum = 1)
from t2
where <condition>

This kind of scalar query is not recognized by oracle. Is there an easy way to
do this in a single query?

Thanks,

      and we said...
select pet_id, max_wt
  from (
select t2.*, max(wt) over (partition by pet_id) max_wt
  from t2
       )
where condition....

or just

select pet_id, (select max(wt) from t2 a where a.pet_id = t2.pet_id) max_wt
  from t2
 where condition......

If you expect lots of rows back, #1 is probably best.  If you expect very very
few rows back, #2.
February 15,
      2004 Richard -- Thanks for the question
      regarding "How to use checkbox on web form so only checked records are
      updated.", version 9.2.0

      You Asked
Hi Tom,

I'm trying to learn how to use the checkbox on a web form.
What I wan't the package to do is update the record if the checkbox box was
checked.
The user may click on 1 or more checkboxes. Such as checkbox 2, 5, 7,
etc...these
records will be updated.

If the user does not click on a checkbox
then no records will be updated when the formsubmit is clicked.

I can not get the owa_util.ident_arr array(s) to coordinate. For example, if I
click on the second checkbox, I update the wrong record...

Thanks,
Richard

--here is the code using emp table.

CREATE OR REPLACE package demoUpdate_Checkbox6
as

PROCEDURE sample2_form_emp ;

procedure do_something( p_old_cols  in owa_util.ident_arr,
                        p_old_ename in owa_util.ident_arr,
                        p_new_ename in owa_util.ident_arr,
                        p_old_job   in owa_util.ident_arr,
                        p_new_job   in owa_util.ident_arr);
end;
/
CREATE OR REPLACE package body demoUpdate_Checkbox6
as

PROCEDURE sample2_form_emp
IS

BEGIN
   htp.htmlopen;
   htp.bodyopen;
   htp.paragraph;
   htp.formopen('demoUpdate_Checkbox6.do_something');

htp.tableOpen;
htp.tableRowOpen;                   -- <TR>
htp.tableHeader ('Check Emp no to Update');
htp.tableHeader ('Emp name');
htp.tableHeader ('Job');
htp.tableRowClose;                  -- </TR>

-- do we need to initialize the arrays??
   htp.formHidden( 'p_old_cols',  'x');
   htp.formHidden( 'p_old_ename', 'x');
   htp.formHidden( 'p_new_ename', 'x');
   htp.formHidden( 'p_old_job',   'x');
   htp.formHidden( 'p_new_job',   'x');

-- Is this te correct way to get the records I want?
for x in ( select '</td><td><input type="checkbox"
value="'||to_char(empno)||'"name=p_old_cols>'||to_char(empno) empno,
                  htf.escape_sc(ename) ename,
                  htf.escape_sc(job)   job
             from emp
            order by empno asc ) loop

    htp.p(to_char(x.empno)) ;

   htp.p( '</td><td>' );
   htp.formHidden( 'p_old_ename', x.ename);
   htp.formText(   'p_new_ename', cvalue => x.ename );

   htp.p( '</td><td>' );
   htp.formHidden( 'p_old_job', x.job);
   htp.formText(   'p_new_job', cvalue => x.job );

   htp.p( '</td></tr>' );

end loop;

htp.formsubmit(NULL,'Go');
htp.formclose;
htp.bodyclose;
htp.htmlclose;

END;

procedure do_something( p_old_cols  in owa_util.ident_arr,
                        p_old_ename in owa_util.ident_arr,
                        p_new_ename in owa_util.ident_arr,
                        p_old_job   in owa_util.ident_arr,
                        p_new_job   in owa_util.ident_arr)
is

begin

 for col_counter in 2 .. p_old_cols.count LOOP

-- debug ...
   htp.bold('emp old# ' ||p_old_cols(col_counter)) ;
      htp.br;
   htp.bold('ename old ' ||p_old_ename(col_counter)) ;
      htp.br;
   htp.bold('enane new ' ||p_new_ename(col_counter)) ;
      htp.br;
   htp.bold('job old ' ||p_old_job(col_counter)) ;
      htp.br;
   htp.bold('job new ' ||p_new_job(col_counter)) ;
   htp.br;

        update emp
           set ename = upper(p_new_ename(col_counter)),
                 job = upper(p_new_job(col_counter))
         where empno = to_number(p_old_cols(col_counter)) ;

   htp.bold( 'update to emp ' || p_old_cols(col_counter)||' Name
'||p_new_ename(col_counter) );
   if ( sql%rowcount = 0 ) then
      htp.bold( ' failed, someone else changed it'||p_new_ename(col_counter) );
   else
      htp.bold( ' succeeded...' );
   end if;
      htp.paragraph ;

END LOOP;

EXCEPTION
   WHEN no_data_found THEN
      htp.paragraph;
      htp.line;
      htp.p('No data found...') ;
      htp.bodyclose;
      htp.htmlclose;

end;

end;
/

      and we said...
that is the way checkboxes work.  It is not an OWA issue -- it is the manner in
which the web itself "works"

it is the manner in which HTTP encodes the data.  The unchecked checkboxes are
"not sent", there is no data for them.  They quite simply "do not exist"

This is why you don't see many checkboxes on the web in general.

I prefer to use a drop down list with two elements myself.  Sort of a "yes",
"no" toggle.  In that fashion, each "row" in the form would have a Y or N value.

Alternatively, you could have the checkboxes be "subscripts", like this:

....
L_CNT := 1;
for x in ( select '</td><td><input type="checkbox"
value="'||L_CNT||'"name=p_old_cols>'||to_char(empno) empno,
                  htf.escape_sc(ename) ename,
                  htf.escape_sc(job)   job
             from emp
            order by empno asc ) loop

    htp.p(to_char(x.empno)) ;

   htp.p( '</td><td>' );
   htp.formHidden( 'p_old_ename', x.ename);
   htp.formText(   'p_new_ename', cvalue => x.ename );

   htp.p( '</td><td>' );
   htp.formHidden( 'p_old_job', x.job);
   htp.formText(   'p_new_job', cvalue => x.job );

   htp.p( '</td></tr>' );

end loop;

Now, in your  update routine, your array from the check box would be subscripts
into the other array.  That is, if someone clicked on rows 1, 3, 5 and 7 -- you
would get a checkbox array with the values 1, 3, 5 and 7 in it -- with which you
can subscript the p_old_ename and p_new_ename array.

Actually -- truth be told -- I would hate an interface that didn't "recognize"
that I changed something.  Why should I have to type over someones ENAME and
click a little box to tell you "please update this row".  I should only have
to type over the ENAME field -- that should be sufficient data for you (the
programmer) to figure out what I the end user want done -- update this guys
ename!

Review &
      Followup

      Rating: 4
How to use
      checkbox on web form so only checked records are updated.", version
      9.2.0  February 23, 2004
Reviewer:  Leonard
      Anukam  from Finland
Hei Tom can you shade more light on this issue or demonstrate what you mean on
the later part of your answer

February 16, 2004 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.

      Rating: 5
MS SQL
      Server...sending a boy to do a man's job  January 20, 2004

Reviewer:  Bob  from Tulsa, OK
I am an Oracle DBA working in a shop that just put a mission critical app on SQL
Server, and I can see first-hand the weaknesses of this DBMS.

This thread is over 3 years old but not much has changed in the MS product.  All
of the locking and read-consistency problems are still there from 3 years ago.
Another issue that Tom did not mention is that SQL Server uses its transaction
logs for rollback information, so doing a transaction log backups is very
resource intensive.  Our tran log backups failed on this very busy DB.  By the
time we could restart the tran log backups the tran log was 65GB.  It took 1.2
hours to back up the log, and while it was backing up all other DB activity
crawled.  The users were very unhappy.

If this happened in Oracle, the tran logs wouldn't be that big to begin with,
and nobody would feel the pinch from backing up 65GB of tran logs because it's
just a series of simple file copies, unlike the MS world.

There are other architectural drawbacks that have real-world significance.  Even
MS says "We don't scale up...we scale out".  And it is true.  The DB and the
platform it runs on runs out of gas in a hurry.  So to get more performance, you
must partition your DB, and your application must be aware of the partitioning.
Oracle has a lot more room to grow and can do it without dinking with the app.

Yes, SQL Server may cost less, but you get what you pay for.

          Followup:

"not much has changed"

that is an understatement -- you see, MS has a habit of naming their products
after dates.

It is not called SQL Server 2000 for nothing (sitting here in 2004....)
Yukons coming, sometime.

(and if you compare Standard Edition to SS2000 and or named users you cannot
really get away with the "may cost less" cause that just really isn't true
either and you have to compare SE to SS2000 if you want to compare apples to
something that resembles another fruit)

      Rating:
      5
Incredible  January 20, 2004
Reviewer:
      Jim  from MA
Tom, you are an absolute god!

      Rating: 4
Please hold the
      flame war but ...  January 20, 2004
Reviewer:
      Mike Wilson  from Los Angeles, CA
... in a recent evaluation of PostgreSQL I have to say I was very impressed.
Uses multi-versioned concurrency, non-blocking locks, has hot backup, triggers
can be written in a PL/SQL like language, or Java, or Python.  With the
exception of some of Oracle's deeper features such as analytics and RAC it held
it's own on features we regularly use.

I don't want to start a flame war here but from a stability and standard
features review I found that there seems to be little holding our company back
from looking seriously at replacing our Oracle installation with PostgreSQL.
This would, of course, be a long winding road but I was curious to hear from the
user community regarding PostgreSQL vs. Oracle because to my eye they compare
favoribly, except in price.  I also have to admit that I'm an Oracle shareholder
and finding a free product that has many of the same features as Oracle was
disheartening.  And just for the record we also evaluated mySQL but it never
made it out of the gate with it's lack of stored procedures.

      Rating: 4
Comments on
      Postgresql  January 21, 2004
Reviewer:  Andy

Follow-up to Mike's comment above: I've been working on a product that has
interfaces to MSSQL, Oracle or Postgresql as the back-end. I'm impressed with
PGS's robustness but I've also noticed the following:

1. The fulltext search capabilites in PGS are way off the standard found in
either MSSQL and (especially) Oracle. The capability is an add-on module, for
starters, so there is not the same "stability" (i.e. it's upgraded more
regularly than with other DBs), and although stem searches/custom dictionaries
are now possible with tsearch2, there is still a LOT of daylight between that
and Oracle Text (can't seem to search for non-alphabetic characters, requires
supplementary columns + triggers instead of utilising just an index etc.).

2. The OLEDB provider for Postgresql presents its fair share of challenges: e.g.
finding the right balance between server-side (much quicker but with strange
behaviour at times esp. when adding rows) and client-side cursors (very slow).
There also seems to be very little documentation.

3. The "perceived" amount of DB administration needed. Database product X may
indeed have weak/missing features, but it may also be the case that the
marketing plug of "hey - it's easy/cheap" means that there is not the same DBA
experience on site as it's not deemed to be so necessary. So bad press might
arise simply because the features that ARE there are under-utilised - because
the marketing encouraged people to overlook that need. From what I've seen,
sites running MSSQL or PGS are less likely to have the necessary DBA experience
at hand, so there's an argument to be made that paying a bit more means you're
more likely to hire/already have the DBA skills you're going to need anyway.

      Rating: 3
Is data
      modeling for DB2 same as for Oracle  February 16, 2004

Reviewer:  Bala  from Chennai
Hi
I am an oracle resource and have 6 years exp in Oracle including data modeling ,
perf tuning etc.
Is it required that knoe DB2 , so as to design a data model for a DB2 database??
I presume if it is up to logical database design , I dont have to worry whether
it is oracle or db2.
But if it is Physical design , then I need to be specific.Is it true or
for even the logical DB design , I need to be specific as to whether it is
oracle or db2??
Answers from anyone else also welcome

          Followup:

physical design implies you know all of the feature functions of the underlying
database.

In Oracle that means you known and understand when to use IOT's, b*tree
clusters, bitmap indexes, bitmap join indexes, hash clusters, single table hash
clusters, partitioning and so on.

In DB2, you have lots less to choose from, but you better know what you do and
do not have (if you want to succeed)

      Rating: 5
LOGICAL Dump...
      Its Granularity and Independence. This can be a very good
      index......  March 05, 2004
Reviewer:
      Shouvik  from India
A thread like this is a tonic for us, Oracle pros.

I have been working with Oracle since I was out of grad school in 1997. Recently
I had to work with Sybase and SQL Server. There is one glaring hole in both. I
donot know why people do not mention it.

Oracle's Logical Object Dump (Export, Import) is unparalleled. The only issue
with it was VLDB scalability. But then 10g has removed it.

(1) Sybase - Cannot export selected tables from a database (synonymous to Oracle
Schema). Stored Proc DDLs can be exported in a clumsy was by building each DDL.
If you ogical backup you must take a physical backup or dump.

(2) SQL Server - Was elated to find "Export" option. But .. oh my god .. it
exports to XL, CSV, etc. Stored Procs, Triggers, Views, etc needs to be dumped
by generating DDLs.

      Rating: 4
Oracle vs
      others  March 12, 2004
Reviewer:  Walter
      Koller  from Austria, Europe
Hi

I've worked with SQL Server, Oracle and Teradata as an end-user for a while, and
had a first insight in DBA's work (but far away of beeing a pro).

The first reply of Tom was the best in this thread, you can't compare the DB in
a simple list it depends on what you and you're users want.

If you have billions of rows and a DSS platform (or lets say a DWH) TERADATA
might be the right choise. It might be true the biggest DBs are on Oracle with
several DBAs maintain all the indices, partitions, clusters and the physical
storage of the datafiles and table spaces, monitoring queries and so on...
Of course a few of these things has to be done in Teradata as well, but much
less of consideration because the DB know what to do because it was designed for
this but only for this (it does not make sense to try to run a heavy transaction
application on this DB). AND nevertheless the end-users will thank you for the
abilities you gave them; a high ANSI standard SQL with nice additions where
Oracle just came up with a nice try in O9.

The point that Oracle is OS/HW independent ist absolutly correct, but this
should not be THE reason to choose, because: will you run a highly available
application where several thousands of revenue will flow threw, on a PC bought
at the store next door with a OS for free downloaded from the internet ??
hopefully not !
OK, you can use Oracle on nearly any OS (Win/Unix/...) but most of the time you
will buy a more or the less proprietary HW (if it is HP,Compaq,Sun,NCR,...).

Also the fact a software exists since beginning of computerization means nothing
except experience (hopefully) and maybe overhead because of backward
compatibility.
But this does not mean that it will exist in the next 10 years as well and also
not that others won't.

I did not want to prefer a specific DB or put another down, but wanted to say
some words to other postings where I thought they have been not complete.

Again, it makes no sense to compare databases in general using simples list.
Just consider everything (also the maintainance and it's costs ! afterwards) but
keep focus what you want to achive, maybe it is Oracle (thousands of DBAs can't
be wrong, can they ?)

cheers

      Rating: 3
Generic
      replication  April 05, 2004
Reviewer:  John
      Snider  from Victoria BC Canada
This is a response to an old response buried way back in this thread about
replication packages being generic.

I wrote a generic replication package that uses a procedure to generate the
replication code.  I just give it the tablename, schema, replication group, etc.
 It is not specific to an application or schema.  It does have some limitations
such as requiring a numeric primary key but we always use a numeric surrogate
key so no problem.  We have been using it for almost 5 years.  We may throw it
out and use standby databases for disaster recovery (but hey we got our money's
worth).  When I worked for Oracle Services we spent most of our time managing
marketing hype versus customer realities.  The reality being that Oracle
replication is not a silver bullet.

February 16, 2004 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.

      Rating: 5
Another Query
        February 16, 2004
Reviewer:  phil

Hi Tom,
I am indeed been very cheeky by slipping my question in here, but I have an
under performing (as in I would like to see better performance )  sql statement
that when you have a moment may like to comment on - I am hoping indexes may be
my saviour, the tkprof is below ...

the logicaltransaction table ( partitioned by value_date_time monthly) contains
but 20000 rows the transactiontype contains but 10. The index
logicaltransaction_ix3 is a local index on VALUE_DATE_TIME, ACCOUNT_FK,
ISOCURRENCY_FK

do you have any pointers ?

SELECT txn.rowid,txn.pk,transaction_status_date_time,final,amount,statement_fk,
                prev_transactionstatustype_fk,transactionstatustype_fk
         from logicaltransaction txn,  transactionstatustype
        where value_date_time = :b7
        and account_fk = :b6
        and (abs(original_amount - :b5)<:b9
          or abs(original_amount - :b5)<abs(original_amount*:b8/100))
        and txn.transactionstatustype_fk = transactionstatustype.pk
        and ( txn.servicing_institution_ref is null
          or :b4 is null
          or txn.servicing_institution_ref =  :b4 )
        and (:b3 = transaction_reference
          or :b3 = related_ref
          or :b3 = account_owner_ref
          or :b2 = transaction_reference
          or :b2 = related_ref
          or :b2 = account_owner_ref
      or :b1  = transaction_reference
          or :b1 = related_ref
          or :b1 = account_owner_ref
           )
        and is_unmatchable != 1
        and rownum = 1
        order by pk

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.20       0.20          0        658          0           0
Execute   4000      8.41       8.78          0         53          0           0
Fetch     4000     26.30      25.23          0     682281          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     8001     34.91      34.22          0     682992          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 101     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  SORT ORDER BY
      0   COUNT STOPKEY
      0    NESTED LOOPS
      0     PARTITION RANGE SINGLE PARTITION: KEY KEY
      0      TABLE ACCESS BY LOCAL INDEX ROWID LOGICALTRANSACTION PARTITION: KEY
KEY
6477839       INDEX RANGE SCAN LOGICALTRANSACTION_IX3 PARTITION: KEY KEY (object
id 36100)
      0     TABLE ACCESS BY INDEX ROWID TRANSACTIONSTATUSTYPE
      0      INDEX UNIQUE SCAN TRANSACTIONSTATUSTYPE_PK (object id 36007)

          Followup:

that query runs in 0.006575 cpu seconds.

how much "faster" do you envision?

problem is -- if you do anything over and over and over - it'll add up.  If you
want this to go "faster", bulk it up -- roll it into what query is causing it to
be invoked over and over and over and over and over (that is -- look to your
ALGORITHM here, get rid of the row by row processing you must be doing, think
"sets")

      Rating: 5
thank you
      !!  February 17, 2004
Reviewer:  phil

February 16, 2004 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.

      Rating: 5
dynamic query
      against statspack  January 30, 2004
Reviewer:
      A reader
Hi

I have following query

   select executions, hash_value
  from STATS$SQL_SUMMARY
 where hash_value in (3822005349, 3255899958, 635556189)
   and snap_id between 200 and 222

I would like to know if there is a way in SQL to make this query run against
several snap_id. Right now I have to repeat the query for each

and snap_id between 200 and 222

is it possible?

          Followup:

don't understand what you mean.

(it is right now running against several snap_ids -- 200, 201, 202, .... 222.
so, not sure what you mean)

      Rating: 5
dynamic query
      against statspack   January 30, 2004
Reviewer:
      A reader
sorry I was not clear

right now I do this

select executions, hash_value
  from STATS$SQL_SUMMARY
 where hash_value in (3822005349, 3255899958, 635556189)
   and snap_id between 201 and 202

select executions, hash_value
  from STATS$SQL_SUMMARY
 where hash_value in (3822005349, 3255899958, 635556189)
   and snap_id between 202 and 203

select executions, hash_value
  from STATS$SQL_SUMMARY
 where hash_value in (3822005349, 3255899958, 635556189)
   and snap_id between 203 and 204

and so on until

select executions, hash_value
  from STATS$SQL_SUMMARY
 where hash_value in (3822005349, 3255899958, 635556189)
   and snap_id between 221 and 222

I have to run many times the same query with different snap_id range, I was
wondering if I can run all these SQLs in a go in SQL or I need to use PL/SQL?

          Followup:

tell me what you do/did with the output of these queries and I'll tell you how
to write it as a single query.  But I'd need to know what you were doing with it
sort of.

But, in a nutshell -- it'll involve LAG() and/or LEAD()  (hint hint)

      Rating: 5
The
      output  February 03, 2004
Reviewer:
      A reader
Hi

Sorry for the delay..

I tried this query

select hash_value, snap_id, lead(snap_id) over (order by snap_ID) leadsnap,
cpu_time
from STATS$SQL_SUMMARY
where snap_id between 383 and 385
  and hash_value = 4133308760

HASH_VALUE    SNAP_ID   LEADSNAP   CPU_TIME
---------- ---------- ---------- ----------
4133308760        383        384  887070000
4133308760        384        385  887210000
4133308760        385             887310000

What I want is this

select min(snap_id) || ', ' || max(snap_id) snap_range, (max(cpu_time) -
min(cpu_time))/1000000 cpu_used
  from STATS$SQL_SUMMARY
 where hash_value = 4133308760
   and snap_id between 383 and 384
union all
select min(snap_id) || ', ' || max(snap_id) snap_range, (max(cpu_time) -
min(cpu_time))/1000000 cpu_used
  from STATS$SQL_SUMMARY
 where hash_value = 4133308760
   and snap_id between 384 and 385

SNAP_RANGE       CPU_USED
-------------- ----------
383, 384              .14
384, 385               .1

possible :-?

          Followup:

ops$tkyte@ORA10G> select *
  2    from (
  3  select hash_value,
  4         snap_id  || ',' ||
  5         lead(snap_id) over (order by snap_ID) snapid,
  6         (lead(cpu_time) over (order by snap_ID) - cpu_time)/1000000 cpu
  7  from t
  8  where snap_id between 383 and 385
  9    and hash_value = 4133308760
 10        )
 11   where snapid not like '%,'
 12  /

HASH_VALUE SNAPID            CPU
---------- ---------- ----------
4133308760 383,384           .14
4133308760 384,385            .1

      Rating: 4
this works
      but  February 03, 2004
Reviewer:
      A reader
Hi again

I rewrote the query not it looks like this

select snap_id || ', ' || leadsnap snap_range, (cpu_time - cpu_used)/1000000
cpu_time
from
(select hash_value, snap_id, lead(snap_id) over (order by snap_ID) leadsnap,
cpu_time, lead(cpu_time) over (order by snap_id) cpu_used
from STATS$SQL_SUMMARY
where snap_id between 383 and 387
  and hash_value = 4133308760)

SNAP_RANGE       CPU_TIME
-------------- ----------
383, 384             -.14
384, 385              -.1
385, 386             -.55
386, 387             -.51
387,

It works, how do you get rid of last row though?

          Followup:

inline view and filter with a where clause.

      Rating: 5
convert row to
      ?  February 16, 2004
Reviewer:
      A reader
Hi

I am writing a procedure to analyza tables, basically I have a table with these
two columns

table_name
column_name

data looks like this

table_name      column_name
-----------     -------------
emp             deptno
emp             mgr

What I am trying to do is get this output

deptno, mgr

This is because I need to analyze histograms for certain columns for certain
tables.... Of course the number of rows can vary depending on number of columns
required to gather histograms

Cheers

          Followup:

search this site for stragg

February 16, 2004 Pradhan -- Thanks for the question regarding
      "Type 2 Dimension", version 8.1.7

      You Asked
Hello Tom,

We have data warehouse. On daily basis we are planning to receive one file 3Gb
size and approximate 3 millions records. The problem the file from source
contains same record daily, some of the accounts can be added and some of the
accounts are changed. Before loading the file into the target table we have to
check three conditions. Compare the record by record with data in target table,
if the record is same, just ignore do not do anything,2) if the record has
changed, load the updated record and at the same time load the old record in
history table 3) insert the new records into target.

As source system is not able to send only changed and new records, it is very
painful at our end to do checking of the existing records and filter out and
then second part changed records will be inserted in the target table and the
old will be inserted in history table and new records will go in target.

Right in testing phase its taking 1.5 hours for 7000 record, so we are not when
the actual file 3 million records will come daily, how we will handle.

Any ideas, suggestions, along WITH EXAMPLE OR PROCEDURE will be greatly
appreciated.

Thanking you in advance.

Pradhan

      and we said...
this should take a couple of minutes at best....

Load the 3 million records into a "scratch table". Index it, etc.

first thing is to "move" into history, well that is:

insert into history
(
select *
  from existing_table E, scratch_table S
 where <join condition>
   and ((E.f1 <> S.f1 or (E.f1 is null and S.f1 is not null)
                      or (E.f1 is not null and S.f1 is null))
        or
        (E.f2 <> S.f2 or (E.f2 is null and S.f2 is not null)
                      or (E.f2 is not null and S.f2 is null))
        or
        .......
)

Then, I would

a) drop table existing_table
b) rename table scratch_table to existing_table

Done.  Sounds like you get a complete refresh right -- so rather then "update
existing", just "replace the entire thing".

The easiest way to speed up something is to not do it at all.  So, rather then
try to update and "merge", just "replace it all".

If you need to capture DELETES as well -- just make the above JOIN an OUTER
join.
Review
      & Followup

      Rating: 3
Slowly changing
      dimensions type 2  February 15, 2004
Reviewer:
      A reader
Pradhan,

You are metion "Type 2 Dimension", do you mean by that slowly changing
dimensions type 2 (SCD 2)?

A short description of SCD2:
Using Type 2, a new record with the new attributes is added to the dimension
table. Historical fact table rows continue to reference the old dimension key
with the old roll-up attribute; going forward, the fact table rows will
reference the new surrogate key with the new roll-up thereby perfectly
partitioning history (from
www.rkimball.com/html/designtips/2000/designtip15.html)

So with SCD 2 you would keep the entire history of your dimension in the
dimension. Fact data from August 2000 will for ever point to dimension
attributes which were valid in August 2000 and fact data from May 2003 will for
ever point to dimension attributes valid in May 2003. It's possible that that
attribute didn't change between August 2000 and May 2003 so it would be the same
dimension record but in case something changed it would be two different
dimension records.

So if you are using SCD 2 why would you move your old row to a history table?
When a query is executed against your mart, how should the DB know when it has
to look in the history table and when in the actuall dimension?

Regards
Maurice

      Rating:
      3
alternative  February 16, 2004
Reviewer:
      Ferenc Palyi  from Hungary
(E.f1 <> S.f1 or (E.f1 is null and S.f1 is not null)
                      or (E.f1 is not null and S.f1 is null))

An alternative for the above statement:

decode(e.f1, s.f1, 0, 1) = 1

          Followup:

you'll find the original to be marginally faster -- i prefer the verbose
approach here.

      Rating: 4
But if you
      can't replace the table  February 16, 2004

Reviewer:  Peter  from UK
If you use materialized views and have logging over the dimension table you may
not be able to replace the dimension table without triggering a large amount of
MV changes.
You could consider using a set based check on the loaded table to ignore
identical rows. As you state Oracle 8.1.7 you will not have access to MERGE
(shame, as that alone is well worth the move to 9.2) so you will need to process
the updates and inserts separately.
Steps to follow:
1) load the data file to a STAGE area
2) insert into a CHANGES table all the rows that are NOT identical to those in
the DIMENSION table (use something like Select * from STAGE MINUS select * from
DIMENSION).
3) update all rows that exist in both the DIMENSION and CHANGES tables
4) insert all the rows from the CHANGES table that do not exist in the
DIMENSION.

For simplicity I have put the updates first then the inserts - you may find that
for efficiency you should go insert then update. But if you had Oracle 9 then
use Merge as it 1)set based 2)cool

For one of 8i data warehouses we attempt to update all the DIMENSION records we
have a changes value for and use exception handling to process the inserts when
the we get a missing dimension value. Our code to this is row by row using a
cursor but performs quite well

          Followup:

they made no mention whatsoever of other stuff.

If they come up with other constraints, I'd be glad to address them...

      Rating: 5
Type 2
      Dimension  February 17, 2004
Reviewer:
      A reader
Thanking you very much.

February 16, 2004 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://download-east.oracle.com/docs/cd/F49540_01/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://download-west.oracle.com/docs/cd/A87860_01/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.

      Rating: 5
global
      temporary table, archivelog mode, direct path
      insert  February 16, 2004
Reviewer:  Dusan
      Valasek  from Czech republic
Hi Tom,

I consider using "alter table exchange partition with table", where table for
exchange would by global temporary table. To populate temporary fast, I would
use insert /*+ append*/ approach, then rebuild indexes and finally exchange with
proper partition. Do I need to do backup immediately after this operation in
archivelog mode?

thanks,

Dushan

          Followup:

it'll not be happening.  you won't be using a GTT for such an operation.
(doesn't really make sense, the data "disappears" and the data would be in a
"tempfile", not a "datafile" in most all 'well configured' setups)

You'll be using a "real" table.
If you use NOLOGGING operations you'll want to backup the affected datafiles,
yes.

insert /*+ append */ by itself is "redo safe" (generates redo)

you would have to alter the table to be NOLOGGING to bypass redo generation (and
even then, make sure the database supports no logging, the dba can disable that
feature)

February 16, 2004 -- 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

      Rating:
      5
Clarification  January 16, 2004
Reviewer:
      Richard  from Ireland
You said in your first response in this thread :

"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.
........ "

Say for example,

SQL> connect user1/test
SQL> select ename from scott.emp where empno = :x ;

Now let's say that this query undergoes PARSING and the SQL is in the shared
pool for future use.

SQL> connect scott/tiger
SQL> revoke select on emp from user1;
SQL> connect user1/test
SQL> select ename from scott.emp where empno = :x

So, now, when does Oracle check that the USER1 has no privileges to select
SCOTT's EMP table ?

Can I say that every SQL statement has to undergo symantic Analysis everytime
irrespective of whether the SQL is available in shared pool or not ?

          Followup:

it'll check when user1 goes to execute it again.

yes, semantic checking must happen during parse.

      Rating:
      5
Clarification  January 16, 2004
Reviewer:
      Richard  from Ireland
Tom

Please ignore my previous query. You have answered it already in the subsequent
follow ups. So I understand that every SQL (whether it is in S.Pool or not) has
to undergo SYNTAX and Semantic check every time. Certainly it makes sense but I
was wondering when Oracle would do it if the SQL statement is already available
in the S.Pool.

Thanks

      Rating: 5
parsing
      question  January 17, 2004
Reviewer:  SR

Tom,

I was hoping if you could shed some light on following. In our production
following is observed:

select * from v$statname where name like '%parse%'
STATISTIC# NAME CLASS
177 parse time cpu 64
178 parse time elapsed 64
179 parse count (total) 64
180 parse count (hard) 64

select a.sid, a.program, b.value total_parse, c.value soft_parse,
(b.value-c.value) hard_parse,
d.value cpu_time, e.value elapsed_time
from v$session a, v$sesstat b, v$sesstat c, v$sesstat d, v$sesstat e
where a.sid = b.sid and a.sid = c.sid and b.statistic# = 179 and c.statistic# =
180
and d.statistic# = 177 and e.statistic# = 178 and a.sid = d.sid and a.sid =
e.sid
order by b.value desc

SID PROGRAM                TOTAL_PARSE  SOFT_PARSE HARD_PARSE CPU_TIME
ELAPSED_TIME

13  oracle@GXPROD (QMN0)   5642612      6988       5635624    12926     77639

I hope the text lines up. But why is so much hard parse going on and can I
improve this situation?

I really appreciate your help here.

Thanks

          Followup:

there are an infinite number of reasons for "why" -- but the most common:

a) the developers of the application did not use bind variables and hence every
time they run a query it is a brand new query that has never been seen before

followed by

b) you just started your system and the cache is not warmed up

at the very end of the list, very very end is

c) your shared pool is tool small.

i would guess "a" if asked to guess with so little information.

      Rating: 5
Tom, thanks for
      the feedback. Some more question  January 18, 2004

Reviewer:  SR
The QMN high parse lead me to look further. This is 8.1.7.4 database. I don't
know how neatly following output will look. If it was allowed in html table then
it would

I hope it is readble. Almost all the SQL's I grep'ed with '$' in sql_text and
sorted by parse_calls were some of the heavy hitters. Something looks really bad
in these numbers. I remember looking thru some trace files and saw very similar
thing as well. I have listed the trace file output (5 minutes trace) and the
v$sql rows.

shared pool is 200mb
aq_tm_processes is 1

Following is sample from the trace file I generated by tracing application
server

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       20      0.06       0.07          0          0          0           0
Execute     66      0.06       0.02          0          0          0           0
Fetch      200      0.04       0.28         98        561          0         134
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      286      0.16       0.37         98        561          0         134

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 /*+ rule */ bucket_cnt, row_cnt, cache_cnt, null_cnt, timestamp#,
  sample_size, minimum, maximum, distcnt, lowval, hival, density, col#,
  spare1, spare2, avgcln
from
 hist_head$ where obj#=:1 and intcol#=:2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       12      0.01       0.01          0          0          0           0
Execute    146      0.05       0.05          0          0          0           0
Fetch      146      0.00       0.17         31        458          0         146
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      304      0.06       0.23         31        458          0         146

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: SYS   (recursive depth: 1)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: HINT: RULE
      0   TABLE ACCESS (BY INDEX ROWID) OF 'HIST_HEAD$'
      0    INDEX (RANGE SCAN) OF 'I_HH_OBJ#_INTCOL#' (NON-UNIQUE)

********************************************************************************

select intcol#,nvl(pos#,0),col#
from
 ccol$ where con#=:1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       17      0.00       0.00          0          0          0           0
Execute    752      0.09       0.14          0          0          0           0
Fetch     1619      0.05       0.09          7       3238          0         867
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2388      0.14       0.23          7       3238          0         867

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   TABLE ACCESS (BY INDEX ROWID) OF 'CCOL$'
      0    INDEX (RANGE SCAN) OF 'I_CCOL2' (UNIQUE)

********************************************************************************

select type#,blocks,extents,minexts,maxexts,extsize,extpct,user#,iniexts,
  NVL(lists,65535),NVL(groups,65535),cachehint,hwmincr, NVL(spare1,0)
from
 seg$ where ts#=:1 and file#=:2 and block#=:3

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       21      0.02       0.00          0          0          0           0
Execute     21      0.00       0.01          0          0          0           0
Fetch       21      0.01       0.18         23         63          0          21
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       63      0.03       0.19         23         63          0          21

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS CLUSTER SEG$
      1   INDEX UNIQUE SCAN (object id 9)

select obj#,type#,ctime,mtime,stime,status,dataobj#,flags,oid$, spare1
from
 obj$ where owner#=:1 and name=:2 and namespace=:3 and(remoteowner=:4 or
  remoteowner is null and :4 is null)and(linkname=:5 or linkname is null and
  :5 is null)and(subname=:6 or subname is null and :6 is null)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       18      0.02       0.01          0          0          0           0
Execute     40      0.04       0.03          0          0          0           0
Fetch       40      0.03       0.13         37        107          0          26
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       98      0.09       0.17         37        107          0          26

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   TABLE ACCESS (BY INDEX ROWID) OF 'OBJ$'
      0    INDEX (RANGE SCAN) OF 'I_OBJ2' (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       21      0.09       0.09          0          0          0           0
Execute     70      0.03       0.03          0          0          0           0
Fetch      958      0.06       0.02          5        270          0         888
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1049      0.18       0.14          5        270          0         888

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 node,owner,name
from
 syn$ where obj#=:1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       10      0.06       0.04          0          0          0           0
Execute     10      0.00       0.00          0          0          0           0
Fetch       10      0.01       0.09         17         30          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       30      0.07       0.13         17         30          0          10

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   TABLE ACCESS (BY INDEX ROWID) OF 'SYN$'
      0    INDEX (UNIQUE SCAN) OF 'I_SYN1' (UNIQUE)

Following is one of a kind (all above don't show rows as the server was still
active when I stopped trace)

select tc.type#,tc.intcol#,tc.position#,c.type#, c.length,c.scale,
  c.precision#,c.charsetid,c.charsetform
from
 triggercol$ tc,col$ c ,trigger$ tr where tc.obj#=:1 and c.obj#=:2 and
  tc.intcol#=c.intcol# and tr.obj# = tc.obj# and (bitand(tr.property,32) !=
  32 or bitand(tc.type#,20) = 20) union select type#,intcol#,position#,69,0,0,
  0,0,0 from triggercol$ where obj#=:3 and intcol#=1001 union select tc.type#,
  tc.intcol#,tc.position#,121,0,0,0,0,0 from triggercol$ tc,trigger$ tr where
  tr.obj# = tc.obj# and bitand(tr.property,32) = 32 and tc.obj# = :4 and
  bitand(tc.type#,20) != 20

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       23      0.01       0.03          0          0          0           0
Execute     23      0.01       0.01          0          0          0           0
Fetch      254      0.05       0.08         17       2209          0         231
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      300      0.07       0.12         17       2209          0         231

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      6  SORT UNIQUE
      6   UNION-ALL
      6    NESTED LOOPS
      7     NESTED LOOPS
     34      TABLE ACCESS CLUSTER COL$
      2       INDEX UNIQUE SCAN (object id 3)
     39      INDEX RANGE SCAN (object id 122)
      6     TABLE ACCESS BY INDEX ROWID TRIGGER$
     12      INDEX UNIQUE SCAN (object id 120)
      0    INDEX RANGE SCAN (object id 122)
      0    NESTED LOOPS
      7     INDEX RANGE SCAN (object id 122)
      0     TABLE ACCESS BY INDEX ROWID TRIGGER$
     12      INDEX UNIQUE SCAN (object id 120)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      6   SORT (UNIQUE)
      6    UNION-ALL
      6     NESTED LOOPS
      7      NESTED LOOPS
     34       TABLE ACCESS (CLUSTER) OF 'COL$'
      2        INDEX (UNIQUE SCAN) OF 'I_OBJ#' (NON-UNIQUE)
     39       INDEX (RANGE SCAN) OF 'I_TRIGGERCOL2' (NON-UNIQUE)
      6      TABLE ACCESS (BY INDEX ROWID) OF 'TRIGGER$'
     12       INDEX (UNIQUE SCAN) OF 'I_TRIGGER2' (UNIQUE)
      0     INDEX (RANGE SCAN) OF 'I_TRIGGERCOL2' (NON-UNIQUE)
      0     NESTED LOOPS
      7      INDEX (RANGE SCAN) OF 'I_TRIGGERCOL2' (NON-UNIQUE)
      0      TABLE ACCESS (BY INDEX ROWID) OF 'TRIGGER$'
     12       INDEX (UNIQUE SCAN) OF 'I_TRIGGER2' (UNIQUE)

select text
from
 view$ where rowid=:1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       14      0.00       0.04          0         13          0           0
Execute     14      0.00       0.00          0          0          0           0
Fetch       14      0.00       0.02          3         28          0          14
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       42      0.00       0.06          3         41          0          14

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 USER ROWID VIEW$

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   TABLE ACCESS (BY USER ROWID) OF 'VIEW$'

There are so many of these I just don't understand why they are getting
reparsed. Isn't oracle running it for its own sake, like internal stuff?

I am really puzzled.

SQL_TEXT    LOADED_VERSIONS    OPEN_VERSIONS    USERS_OPENING    EXECUTIONS
LOADS    INVALIDATIONS    PARSE_CALLS    DISK_READS    BUFFER_GETS
ROWS_PROCESSED    COMMAND_TYPE    OPTIMIZER_MODE
insert into sys.aud$( sessionid,entryid,statement,timestamp#,
userid,userhost,terminal,action#,returncode,
obj$creator,obj$name,auth$privileges,auth$grantee,
new$owner,new$name,ses$actions,ses$tid,logoff$pread,
logoff$lwrite,logoff$dead,comment$text,spare1,spare2,priv$used)
values(:1,:2,:3,SYSDATE,:4,:5,:6,:7,:8,:9, :10,:11,:12,:13,:14,:15,:16,:17,
:18,:19,:20,:21,:22,:23)    1    1    965    3294139    3    0    3288922
54569    23560335    3294121    2    CHOOSE
update sys.aud$ set action#=:2, returncode=:3, logoff$time=sysdate,
logoff$pread=:4, logoff$lread=:5, logoff$lwrite=:6, logoff$dead=:7 where
sessionid=:1 and entryid=1 and action#=100    1    0    0    3280637    1    0
 3280637    51910    22976944    3280635    6    CHOOSE
select privilege#,level from sysauth$ connect by grantee#=prior privilege# and
privilege#>0 start with (grantee#=:1 or grantee#=1) and privilege#>0    1    0
 0    3313241    2    0    3086704    912    67950067    14743279    3    CHOOSE
select text from view$ where rowid=:1    1    0    0    2293306    1    0
2293306    70702    4587224    2293306    3    CHOOSE
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 locked    1    1    1    142206332    4    0
 1296258    38465    1422072265    142206332    3    CHOOSE
select t.schema, t.name, t.flags, q.name from system.aq$_queue_tables t,
sys.aq$_queue_table_affinities aft,      system.aq$_queues q where
aft.table_objno = t.objno and aft.owner_instance = :1 and        q.table_objno =
t.objno and q.usage = 0 and       bitand(t.flags, 4+16+32+64+128+256) = 0 for
update of t.name, aft.table_objno skip locked    1    1    1    1296258    4
0    1296258    6234762    819408698    142206332    3    CHOOSE
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#    1    0
0    389865    2    0    359685    222811    2024534    3288041    3    CHOOSE
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#    1    0    0    374791    1    0    346728    155006    762998    0
 3    CHOOSE
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#
1    0    0    626841    9    0    330999    118565    2238396    8758210    3
 CHOOSE
select 1 from aq$_schedules    1    0    0    935059    4    0    300215    160
  1201272    0    3    CHOOSE
select 1 from sys.aq$_queue_table_affinities    1    0    0    935059    4    0
  300215    36    1501251    300215    3    CHOOSE
select
con#,type#,condlength,intcols,robj#,rcon#,match#,refact,nvl(enabled,0),rowid,cols
,nvl(defer,0),mtime,nvl(spare1,0) from cdef$ where obj#=:1    1    0    0
537761    6    0    284193    36829    6421503    4393795    3    CHOOSE
update seq$ set
increment$=:2,minvalue=:3,maxvalue=:4,cycle#=:5,order$=:6,cache=:7,highwater=:8,a
udit$=:9 where obj#=:1    1    0    0    275064    3    0    275061    12408
1101019    275061    6    CHOOSE
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.t
runccnt,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    1    0    0
473428    8    0    263898    536397    3624643    894903    3    CHOOSE
select con#,obj#,rcon#,enabled,nvl(defer,0) from cdef$ where robj#=:1    1    0
  0    445245    6    0    246829    127807    2796453    951065    3    CHOOSE
select intcol#,nvl(pos#,0),col# from ccol$ where con#=:1    1    0    0
4393690    6    0    245310    97287    19116995    5164884    3    CHOOSE
select pos#,intcol#,col#,spare1 from icol$ where obj#=:1    1    0    0
671549    3    0    187091    37354    3977817    1317372    3    CHOOSE
select order#,columns,types from access$ where d_obj#=:1    1    0    0
178297    2    0    178297    111980    1048617    345872    3    CHOOSE
select
ts#,file#,block#,nvl(bobj#,0),nvl(tab#,0),intcols,nvl(clucols,0),audit$,flags,pct
free$,pctused$,initrans,maxtrans,rowcnt,blkcnt,empcnt,avgspc,chncnt,avgrln,analyz
etime,
samplesize,cols,property,nvl(degree,1),nvl(instances,1),avgspc_flb,flbcnt,kernelc
ols,nvl(trigflag, 0),nvl(spare1,0),nvl(spare2,0),spare4,nvl(spare3,0) from tab$
where obj#=:1    1    0    0    194303    7    1    120622    117555    692870
 194296    3    CHOOSE
update sys.job$ set failures=0, this_date=null, flag=:1, last_date=:2,
next_date=:3, total=total+(sysdate-nvl(this_date,sysdate)) where job=:4    1
0    0    24544    2    0    117026    130    184285    23032    6    CHOOSE
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#    1    0    0    178297    6    4    109876    96065    1087876
309934    3    CHOOSE
select condition from cdef$ where rowid=:1    1    0    0    153913    6    0
85163    2361    184988    92494    3    CHOOSE
select job from sys.job$  where next_date < sysdate  and (field1 = :1 or (field1
= 0 and 'Y' = :2)) order by next_date, job    1    0    0    124880    18    0
 70911    3797    95738    36733    3    CHOOSE

Thanks for your help

          Followup:

if it were in an html table, it would be word wrapped and virtually (no, make
that totally) unreadable.  totally unreadable.  courier with real line breaks is
the only real font in this world -- for code and such anyway.

I've no idea what I'm to be looking for here -- none.  what looks "bad" to
you???

These queries obviously use binds -- you can see them.  the $ are part of the
OBJECT NAME.  You should not be looking at SYS sql right now (over which you
have very very very little control).  Look to your sql.

      Rating: 2
Tom, my point
      is why so much parsing is happening  January 19, 2004

Reviewer:  SR
For example, first SQL
PARSE_CALLS=3288922,
EXECUTIONS=3294139
Is this normal?

SQL_TEXT    LOADED_VERSIONS    OPEN_VERSIONS    USERS_OPENING    EXECUTIONS
LOADS    INVALIDATIONS    PARSE_CALLS    DISK_READS    BUFFER_GETS
ROWS_PROCESSED    COMMAND_TYPE    OPTIMIZER_MODE
insert into sys.aud$( sessionid,entryid,statement,timestamp#,
userid,userhost,terminal,action#,returncode,
obj$creator,obj$name,auth$privileges,auth$grantee,
new$owner,new$name,ses$actions,ses$tid,logoff$pread,
logoff$lwrite,logoff$dead,comment$text,spare1,spare2,priv$used)
values(:1,:2,:3,SYSDATE,:4,:5,:6,:7,:8,:9, :10,:11,:12,:13,:14,:15,:16,:17,
:18,:19,:20,:21,:22,:23)    1    1    965    3294139    3    0    3288922
54569    23560335    3294121    2    CHOOSE

update sys.aud$ set action#=:2, returncode=:3, logoff$time=sysdate,
logoff$pread=:4, logoff$lread=:5, logoff$lwrite=:6, logoff$dead=:7 where
sessionid=:1 and entryid=1 and action#=100    1    0    0    3280637    1    0
3280637    51910    22976944    3280635    6    CHOOSE

select privilege#,level from sysauth$ connect by grantee#=prior privilege# and
privilege#>0 start with (grantee#=:1 or grantee#=1) and privilege#>0    1    0

 0    3313241    2    0    3086704    912    67950067    14743279    3    CHOOSE

select text from view$ where rowid=:1    1    0    0    2293306    1    0
2293306    70702    4587224    2293306    3    CHOOSE

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 locked    1    1    1    142206332    4    0

 1296258    38465    1422072265    142206332    3    CHOOSE

Thanks for your help

          Followup:

recursive SQL is (has to be) parsed during each execution.  so yes, in this
case, this is normal.

      Rating: 4
Perfectly
      Clear.....  January 19, 2004
Reviewer:
      Denise  from Virginia, USA
thanks Tom...

I also wondered about the differences of hard parse vs
soft parse.

Your explanation made it perfectly clear and explains
why sometimes when I would kick off a query for the first
time it would take a while to process...then any subsequent
calls with that same query would process almost immeditately. Great
explanation(as usual!!).

I noticed you mentioned a book you collaborated on
noted in this thread that is dated January -- 2002...
being released March/April(I assume year 2002).

What is the name of the book and where can I get a copy
of it?

          Followup:

http://www.amazon.com/exec/obidos/search-handle-url/index=books&field-author=Thomas%20Kyte/103-3213568-4455846
it was beginning oracle.

      Rating:
      5
Excellent  January 19, 2004
Reviewer:
      Sikandar Hayat  from Pindi Bhattian - Pakistan
Nice explanation of soft vs hard......

Interesting think while visiting Amazon site is all your books have price with
decimal .99$.....

      Rating: 3
Soft parsed vs
      No parse  February 04, 2004
Reviewer:
      A reader  from NJ, USA
Tom,

In one of your followup, you mentioned the following :

"
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"

1. My understanding after reading through this discussion is that when you use
PL/SQL (not dynamic sql like execute immediate, but static sql), it will
automatically take care of parsing only once per session and we do not need to
do anything special about it.
2. Using static PL/SQL avoids doing soft parses for each execution, but does a
parsing (soft or hard) once during the session. Is it what some books term as
'No parse'?
3. If you use any other language other than PL/SQL,
my understanding is that a soft parse take place once for each execution and
cannot be avoided. If it is not correct, pls. explain how to avoid soft parses.
When you mention "do not parse inside a loop" which parse do you mean ( soft or
hard). eg. if you have a language like Java and if you code something like

parse
loop
   execute
   fetch
end loop
close

My understanding is it is parsed once per session (hard/soft) and parsed (soft)
for every execution since any language other than PL/SQL does not do caching
automatically. Is that correct or this will also be parsed (hard/soft) once per
session and executed many times(since parse is outside the loop). Pls. explain

5. Consider

Case A
======
parse
loop
   execute
   fetch
end loop
close

Case B
======
loop
   parse
   execute
   fetch
end loop
close

Case C
======
loop
   parse
   execute
   fetch
   close
end loop

My understanding is that
a. Soft/hard parsed once and executed many times
b. Soft/Hard parsed once and parsed once for each execution
c. Hard parsed every execution since closing within the loop. If not pls clarify

          Followup:

1) correct (for static sql, not so for dynamic -- that is up to you again)

2) yes.

3) incorrect.  a parse takes place each time the coder parses (prepares) the
query.  if the coder does this more than once per session -- well, they are the
one doing that.  Would you compile a subroutine before calling it each time?
probably not -- so why compile SQL each time?

a parse is a parse - hard or soft - they both should be avoided.

Java, VB, C, all languages can parse once, execute many.  Here is a simple
example:

class select
{
static PreparedStatement pstmt = null;

public static void process_data( Connection conn, String inputs )
throws Exception
{
int    empno;
String ename;
String job;
int    mgr;
String hiredate;
int    sal;
int    comm;
int    deptno;

    if ( pstmt == null )
    {
        pstmt = conn.prepareStatement
        ("select * "+
           "from emp " +
          "where ename like ? " );
    }

    pstmt.setString( 1, inputs );
    ResultSet rset = pstmt.executeQuery();

    while( rset.next() )
    {
        empno    = rset.getInt(1);
        ename    = rset.getString(2);         job      = rset.getString(3);
        mgr      = rset.getInt(4);
        hiredate = rset.getString(5);
        sal      = rset.getInt(6);
        comm     = rset.getInt(7);
        deptno   = rset.getInt(8);
        System.out.println( ename );
        // process( empno, ename, job, mgr, hiredate, sal, comm, deptno );
    }
    rset.close();
}

No matter how many times you invoke that subroutine -- pstmt will only have been
parsed the first time around...

      Rating: 4
Hard Parse vs
      Soft Parse  February 05, 2004
Reviewer:
      A reader  from NJ, USA
Tom,

My understanding with regard to Hard/Soft parse in a nutshell is as follows :

1. Soft parse = Syntax + Semantic check + Lookup in the library cache for
finding if a similar statement exists

This is less expensive than hard parse and takes less CPU time and cycle, but
involves obtaining a latch on :

a. Row cache/Data dictionary cache(for semantic check)
b. Library cache (to determine if a similar statement already exists)
Both of the above steps are a serialization mechanism that prevents scalability

2. Hard Parse = Syntax + Semantic check + lookup in the library cache and if not
found, allocating memory in shared pool and generating execution plans and
performing row source optimization.
This is more expensive than soft parse and consumes  more CPU time and cycle and
also involves obtaining latches on
  row cache (for semantic check)
  library cache (for determining if a parsed version exists)
  shared pool (for allocating shared pool area to store the new execution plan)

All of the above steps are a serialization mechanism that prevents scalability

3. No parse (is the ideal way to design an application and code so it provides
scalability and does not consume any CPU time/cycles and does not involve latch
contention)

Is my understanding about them true. If not pls. help me

          Followup:

that is pretty much it, yup.

to shorten further:

hard parse bad,
soft parse less bad,
softer soft parse (via session_cached_cursors) less bad still,
no parse good.

      Rating: 5
no
      parse?  February 05, 2004
Reviewer:  reader

From above, how do you achieve step 3? You got to do either hard or soft or as
Tom says softer softer parse (session cached cursors) and there is nothing
called no parse. is there?

          Followup:

means you parse as LITTLE AS POSSIBLE..

parse once, execute the statment over and over and over.

not like most programmers -- parse/execute/close, parse/execute/close and so
on..

      Rating: 3
Hard parse vs.
      soft parse  February 05, 2004
Reviewer:
      A reader  from NJ, USA
Tom,

My understanding with regard to Hard/Soft parse in a nutshell is as follows :

1. Soft parse = Syntax + Semantic check + Lookup in the library cache for
finding if a similar statement exists

This is less expensive than hard parse and takes less CPU time and cycle, but
involves obtaining a latch on :

a. Row cache/Data dictionary cache(for semantic check)
b. Library cache (to determine if a similar statement already exists)
Both of the above steps are a serialization mechanism that prevents scalability

2. Hard Parse = Syntax + Semantic check + lookup in the library cache and if not

found, allocating memory in shared pool and generating execution plans and
performing row source optimization.
This is more expensive than soft parse and consumes  more CPU time and cycle and

also involves obtaining latches on
  row cache (for semantic check)
  library cache (for determining if a parsed version exists)
  shared pool (for allocating shared pool area to store the new execution plan)

All of the above steps are a serialization mechanism that prevents scalability

3. No parse (is the ideal way to design an application and code so it provides
scalability and does not consume any CPU time/cycles and does not involve latch
contention)

Is my understanding about them true. If not pls. help me

Thanks

      Rating: 3
Small
      clarification  February 13, 2004
Reviewer:
      A reader  from NJ, USA
Tom,

I read the article you had mentioned about cursor sharing (Oracle white paper).
I am getting clear about the soft/hard parse issues after reading this
discussion. However, I still have the following doubt and would be useful if you
clarify it for me.

Say for eg. I have the following piece of application code in PL/SQL :

declare
  cursor c is select ... from ... where ...
  i number;
begin
  for i in 1..10
  loop
     open c;
     fetch c into ....
     close c;
  end loop;
end;

I am aware that it is a bad programming practice. However, for my understanding,
pls clarify :
1. In the above case, will a hard or a soft parse take place because the cursor
is opened/fetched/closed in a loop or will PL/SQL cache the cursor and will
parse only once (The above article says that for PL/SQL an explicit cursor with
static SQL will be cached and not parsed).This is the thing that confuses me
when you say "Keep the cursor open and do not close it"

2. If i have the same logic as above in other prog. languages other than PL/SQL
(such as VB, Java, C etc), what will happen

Thanks much

          Followup:

that is not a bad programming practice necessarily -- if the inputs into the
query in the loop change that is.  It should be a select into, but same
difference.

In plsql -- close doesn't really close :)  PLSQL is awesome for performance,
scaling and doing tons of work for you.

As most there would be 1 hard parse (if the query was never parsed before)
OR 1 soft parse.  As PLSQL goes out of its way to cache cursors
transparently for us.

In java, vb, C, etc -- you the programmer must code this better -- with a
prepared statement that you prepare once and reuse over and over and over

This is why PLSQL rocks, this is why PLSQL rolls -- it is all about doing SQL
right.  In all other languages you have to

o convert data types
o cache your own cursors
o do all of the work.

      Rating: 5
same effect
      when using dbms_sql package ?  February 14, 2004

Reviewer:  pasko  from Hamburg
Hi Tom,

Thanks for your comments above,

Should we put the usage of
'DBMS_SQL Package' and 'EXECUTE IMMEDIATE'
in the same brackets with :  java, vb, C, etc  ,
although they are infact plsql ?

Thanks in advance.

Best Regards.

          Followup:

dynamic sql in plsql means -- you are doing it yourself.

with dbms_sql, you can prepare once, bind/execute over and over and then close.

with execute immediate, you cannot.  Hence execute immediate is not appropriate
in the case where you execute the same statement more then a handful of times.

      Rating: 5
dbms_sql vs
      native  February 16, 2004
Reviewer:  James
      Blanding  from Kingston, RI USA
The Application Developer's Guide for 8i compares the advantages of DBMS_SQL and
native Dynamic SQL.  Your last comment seems to relate to the following tidbit
from Chapter 8 of that guide:

 Reuse of SQL Statements

"The PARSE procedure in the DBMS_SQL package parses a SQL statement once. After
the initial parsing, the statement can be used multiple times with different
sets of bind arguments.

In contrast, native dynamic SQL prepares a SQL statement for execution each time
the statement is used. Statement preparation typically involves parsing,
optimization, and plan generation. Preparing a statement each time it is used
incurs a small performance penalty. However, Oracle's shared cursor mechanism
minimizes the cost, and the performance penalty is typically trivial when
compared to the performance benefits of native dynamic SQL."

So the reuse of a given SQL statement by the DBMS_SQL package is like
"parse/execute once, fetch many times", right?  Whereas native dynamic SQL using
bind variables is more like "hard parse once, soft parse/fetch many times"?  Or
am I getting things confused here (seems likely, with the latter anyway...)?

Also, which model does a properly written (i.e. utilizes bind variables) ODBC
application follow?

I am trying to convince a particular software vendor that one of their programs
needs to be rewritten to use bind variables.  It produces obscene quantities of
SELECT statements that are identical save for a few literal values.  I gathered
a sample of these statements via SQL_TRACE last week and tried to quantify the
difference in performance between running the statements as-is and running one
statement with bind variables.  To do this, I put all of the literal values into
a table, and then looped over them with a cursor.  For each row from the table,
I used EXECUTE IMMEDIATE to select from the actual data table.  In one case, I
just used a single form of the statement with bind variables and the USING
syntax, and in the other case, I worked the values from the cursor into a string
variable so that the statement would be different each time.  I'm sure there are
some reasons why this is not completely accurate (like some overhead from the
surrounding PL/SQL code, especially the string concatenation); I was really only
trying to get some ballpark numbers.  But after reading your last post, I'm
wondering if using DBMS_SQL would have been more appropriate.  I guess I could
just try it myself heh...

          Followup:

dbms_sql lets you:

parse
loop
   bind
   execute
   <fetch if query>
end loop
close

NDS makes you

loop
   parse
   bind
   execute
   <fetch>
   close
end loop

The "best way" - the right way -- for more then a handful of iterations is the
first loop -- dbms_sql will quickly blow away NDS if you are doing
dozens/hundreds of iterations.

The best way in ODBC, Proc, VB, Java, whatever is the first way.

Comparing

a) literal sql that is parsed each time
to
b) bound sql that is parsed each time is "fair"

it shows what impact not binding has.  To go further, you can show them the
difference between b and

c) bound sql that is parsed ONCE per session

to show the further gain they can achieve.

A quick fix for them MIGHT be cursor_sharing=force

search for that on this site, read about it.

February 16, 2004 Sergio -- Thanks for the question regarding
      "What´s your oppinion about the DBA job in the future", version 9.2.0.4

      You Asked
Hi Tom,

First, i would like to thank you for your site in web. I learn a lot of things
with the doubts of my coleages registered in this site. I learn too how to
explain to the developers using examples (like you).

Well, i would like to know what do you think about the future of the DBA
profession. I read somenthing about the new Oracle 10g version and see that many
admininstration tasks now become very easy. Do you think that de DBA job will
become so simple that, as others Database products (like SQL Server), don´t need
a DBA professional ?

Thanks for the answer (sorry for my english...) !

      and we said...
I get this question all of the time.

Let me ask you 4 questions -- then you tell me what the future of the DBA is:

question 1:  Have you been told you will be managing fewer applications running
against your databases?  that is, is your company retiring applications --
desupporting them and not replacing them with something else?

OR

are you finding that every month brings yet another application in addition to
the ones your already had to manage?

question 2: Have you been told to give back all of the disk you no longer need
since your databases are getting smaller and smaller?

OR

have you been ordering more disk since they are growing and growing?

question 3: Have you been dropping users and not creating new ones?  That is,
has the base of users dependent on your databases been GROWING?

OR

has it been shrinking?

question 4: The big question.  When was the last time someone said to you "hey,
its ok if the database is down frequently and for long periods.  performance
isn't really important either. "

The answers are typically

- i'm getting more applications to manage
- against larger and larger databases
- for more users
- and if it goes down for any period of time or performance tanks, they yell
  at me really really loud

with another caveat added

- and they won't increase our staff, we have to do it with the same dba's we
  had last year and the year before

So, how do you do that?  Well, one answer is "you work more hours" but many of
us are already sort of working lots so thats not good.  Another answer is "the
database automates the mundane tasks that a jr DBA should have been able to do
and gives us the tools to be more efficient"

Tuning bad sql.  Many times that can be done with a tweak here or there.  the DB
can do that.

Designing an efficient Schema.  That cannot be done by software, requires smart
people.

Finding objects that are mostly 'white space' below the high water mark and
fixing them.  That can be done by a monkey (so the database does it)

Figuring out that if you take this manual 50 step process and turn it into 5 sql
statments it runs in 30 seconds instead of 5 hours.  That cannot be automated
(requires smart people)

I'm not worried about the future of DBA's who are willing to really administer
databases and their applications.

I'd be worried about DBA's who want to manage the way we did with version 6 of
the database.

(and that last comment about sqlserver cracks me up -- have you ever asked the
guy who manages the sqlserver database if they consider themselves a DBA
professional?

if they say NO -- ask them how many times they've reloaded their databases,
cause they are the ones that don't know about real backup, recovery, fixing,
troubleshooting.  The databases they manage are not worth anything)
Review &
      Followup

      Rating:
      5
  November 30, 2003
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
Tom,
What you say about databases growing in size, more database intensive
application being deployed, the DBA staff not growing in proportion to the
number/size of databases is right, but the impression being conveyed by 10g is
that the database will itself take care of growth, tuning etc.

The question here is that if database can tune itself, take care of growth by
itself, installation, patching, backup and recovery can be done by untrained
staff, what is the need for a DBA?

Though my personal opinion is that no matter what the capabilities of the
software and no offense to Oracle, there are always things which a trained DBA
can do which are important, the managers who are in the decision making seat do
not always see things in the right perspective. It is easy to get carried away
by the sales pitch.

Thanks...

      Rating: 4
less DBA
      jobs  December 01, 2003
Reviewer:  Ryan
       from Tysons Corner, VA
If you look at the jobs ads there are far, far,far fewer DBA and Oracle
Developer jobs available. There is a specific draught in developer jobs. Most
positions are low paying temp work.

Salaries have dropped radically. At the same time I see java and .Net positions
at a relatively healthy level(though you cant be completely incompetent to get
hired any more).  The last few projects I have been on have been object oriented
applications with Oracle backends.

Employers seem to want skilled Object Oriented developers who know a little bit
about Oracle. Then want 1 or a few DBAs and few if any oracle developers.
Companies are not interested in oracle developers who can also do object
oriented programming.

I think that the people who will stay employed in the future will do one of two
things:

1. Be both a developer and a DBA. This is hard to do, but the number of jobs
available is small enough that employers can demand both. This increases
efficiency of the position. So DBAs will be responsible for keeping the database
up, etc... and handling the bulk of the complex PL/SQL, plus helping object
oriented developers with Oracle. Most DBAs are pretty weak in PL/SQL and
development. Many will have to change or they will see themselves out of work or
in lower paying positions.

2. Be both an object oriented and Oracle developer. This is easier to break into
if you are coming from the OO world, since few if any employers will give oracle
developers the opportunity to do object oriented programming.

Does anyone else have an opintion on this?

      Rating: 5
hehe... You can
      always be a COBOL programmer.  December 01, 2003

Reviewer:  Kevin  from Connecticut USA
Hi, I have been an oracle DBA for uh 20 years maybe?  Its true that the
traditional DBA function has changed a bit but I didn't like the old DBA job
that much anyway.  And I have notice also that job posting seems to what so much
crap out of somebody that you can't possibly find someone who could "fit the
profile".  But it doesn't worry me.  I am glad to have expanded my horizons to
include Modeling and App Development; it has given me a big picture perspective
which in the end makes for better data systems.  In the old days, the typical
life span of an Oracle developer/DBA was 3-5 years.  After that they got bored
and wanted to do something else (I seem to remember a study done in the
early/mid 90's that said this).  Guess I was just too thick in the head or too
slow to figure that out for myself that I should be changing jobs so  just doing
it.  Anyway, as an "enhanced DBA" I may not make as much as Larry or Bill, but I
make more than most people I know, easily into 6 figures, and I have never been
out of work a day in my life.  My problem ain't finding work, its avoiding it.
Just keep current and remember your granddad's advice, find something to do in
life for which you have some passion; if you don't like your job/profession then
you got the wrong one so get another one, if you do, don't worry cause you'll be
good at it and have plenty of work.  Also remember this, the last study on
installed code bases I saw continues to say: traditional and homegrown business
software still makes up the lions share of applications and for true business
software, COBOL still has more lines of code out there than any other language
(not C, not C++, not JAVA not .NET, not VB, not any of that new crap) so you can
always learn COBOL and do maintenance work.  Y2K didn't change this.

Well its one man's opinion anyway.

      Rating: 5
developer
      support  December 01, 2003
Reviewer:  Stu
      Charlton  from Toronto ON Canada
I see the need for "developer support" DBAs to be crucial.  Most teams don't
staff for database experience and suffer for it.  The idea of a DBA to help the
team use the database effectively is something Tom has advocated for some time
and I think is absolutely necessary.

The issue is that this is not really a mainstream idea, it has to be sold, so
this isn't a "cookie cutter career" solution yet.  But it is an option I've been
recommending to talented support technicians and operational DBAs looking to
break into something more rewarding (vs. the well-trodden paths of Cisco
certification).

      Rating: 5
It's Developer
      DBAs and not "developer support" DBAs we use.  December 02,
      2003
Reviewer:  Peter  from UK
I work for an outsourcing company and manage several DWH systems for my
customers.
In my development group (we develop as well as support) we specifically employ
"Development DBA"s. These guys understand development life cycles and how
developers think, they are also highly skilled at tuning - for if the warehouse
does not fly nobody loves you. System DBAs don't always have the right aptitude
to cut it as a developer.

It took a lot of effort to set this group up as the 'services' group
traditionally controlled all DBA activity across all supported databases (OLAP &
OLTP), but frankly having a part share of DBA does not work if your core task is
to build databases. But it is worth it

      Rating: 4
DBA
      Developers  December 03, 2003
Reviewer:  Dan
      Clamage  from Pittsburgh, PA (home of the once-great Pittsburgh
      Steelers)
I've seen more and more operational DBAs learning PL/SQL and having a huge
input/impact into new systems as they're being designed and developed. If the
Development team doesn't get a DBA onto their new project early on, they usually
suffer for it (unless they have a Tech Lead who is also a DBA).
If you outsource your DBA function overseas, you're missing 80% of the value of
having DBAs on hand.

      Rating: 5
DBA
      developers  December 05, 2003
Reviewer:  PJ
       from UK
Dan

We are both on the same page here.

Although we provide outsourcing to our customers, all our DBAs are based at my
location (none overseas). However, the system DBAs are very busy and flit from
database to database.

Having dedicated development DBAs works well, especially with DW developments.
Having easy access to the system DBAs is a plus - afterall these guys will be
running the database when the developers have moved on to the next big project.
Easy access = good knowledge transfer.

      Rating: 4
sql
      query  December 16, 2003
Reviewer:  srinivas
       from india
sir
how to select
                   *
                 * * *
                * * * * *

using an sql query

          Followup:

i don't even know what that "is"

      Rating:
      5
  December 16, 2003
Reviewer:
      A reader
and what has that do to with this thread ??

      Rating: 5
To
      Srinivas  December 16, 2003
Reviewer:
      A reader
select '    *'||chr(10)||'  * * *'||chr(10)||'* * * * *' from dual;

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.4.0 - 64bit Production
With the Partitioning option
JServer Release 9.2.0.4.0 - Production

'*'||CHR(10)||'***'||CH
-----------------------
    *
  * * *
* * * * *

SQL>

Hope this is what you were looking for, though this is not the right thread for
a SQL question.

      Rating: 1
Do you want to
      draw the Christmas tree???  December 16, 2003

Reviewer:  msc
Something like this?

SQL> set heading off
SQL> set feedback off
SQL> set pagesize 2000
SQL>
SQL> select decode
  2         ( sign(floor(maxwidth/2)-rownum)
  3         , 1, lpad( ' ', floor(maxwidth/2)-(rownum-1))
  4           || rpad( '*', 2*(rownum-1)+1, ' *')
  5         , lpad( '* * *', floor(maxwidth/2)+3))
  6  from all_objects
  7     , (select 40 as maxwidth from dual)
  8  where rownum < floor(maxwidth/2) +5;

                    *
                   * *
                  * * *
                 * * * *
                * * * * *
               * * * * * *
              * * * * * * *
             * * * * * * * *
            * * * * * * * * *
           * * * * * * * * * *
          * * * * * * * * * * *
         * * * * * * * * * * * *
        * * * * * * * * * * * * *
       * * * * * * * * * * * * * *
      * * * * * * * * * * * * * * *
     * * * * * * * * * * * * * * * *
    * * * * * * * * * * * * * * * * *
   * * * * * * * * * * * * * * * * * *
  * * * * * * * * * * * * * * * * * * *
                  * * *
                  * * *
                  * * *
                  * * *
                  * * *
SQL>
SQL>

      Rating: 3
DBA's of the
      Future.......  December 17, 2003
Reviewer:
      Denise  from Virginia,USA
From my own personal experience, and to expand a little on what Kevin said, I
believe versatility and diversity of skills will be paramount to roles of future
DBA's.

Kevin mentions COBOL..ah yes...I remember those years way back when the IT field
was called 'Data Processing' and some of us were programming RPG,COBOL,PASCAL
and,how can one forget,BASICA and GWBASIC(with line numbers,GOSUB and GOTO
statements).

IT Technology has drastically changed since those years of programming
procedural code.IBM was the big guns in those days with their System 34,36 and
38's dominating the industry market share.

Through the years I worked as a computer operator,programmer,and in more recent
times,database and application developer(SQLServer 2000) after returning to
school to upgrade my programming and IT skills. It was in school where I 'met'
Oracle and found my calling. My goal was to become a PL/SQL Programmer...however
upon landing my current job after graduation...my role drastically changed to
that of a full-fledged DBA(of which I knew nothing about 20 months ago). My
responsibilities are 90% DBA and 10% Programming and I learn something new
everyday and absolutely love it!.

Being a one person Oracle 'team', that also performs extensive Access and Excel
VBA Programming,my Visual Basic Programming(SQLServer project) comes in real
handy.

What I'm getting at is that versatility of skills and experience will be key
factors in decisions made by employers when they hire for future DBA positions.

I know Tom mentions in his book...DBA's make bad developers and vice-versa...I
have to disagree from my standpoint. I am able to bring to my role that part of
me that understands application development concepts adding to my newly acquired
knowledge of database administration and blend both skills effectively and
harmoniously. 25+ years and the learning never stops!!

anyway..for what it's worth that's my 2 cents.

          Followup:

I don't say that do i?  I started as "developer dude" and did the dba thing for
a while.

I do both.

I actually propose the "database developer" (sort of a triumvirate).  DBA,
Coders and people that build the database part.

They could be a single person
Or three
Or thirty

But right now, what I see is

DBA's on the left of a wall.
Coders on the right of a wall.
No intermingling of the left and right.

I want to bridge that gap and I think you are basically saying the same thing.

      Rating: 3
mea
      culpa..  December 18, 2003
Reviewer:  Denise
       from Virginia, USA
ok Tom...

here's your quote from page 50:

"A good developer is usually a very bad DBA, and vice versa".

True..the key word "usually" infers "not always" but "oftentimes".

Then you go on to state:
"They are two different skillsets,two different mindsets
and two different personalities in my opinion".

Taking yourself as a prime example...doing both DBA
and Developer stuff...perhaps the skillsets,mindsets and
personalities are not that different.

Things are perceived as 'different' when they seem
foreign and not understood...i.e. your reference to the
database as a 'black box' that exists in a vacuum.

For me..I believe my analytical and logical skills enabled
me to quickly train and learn DBA skills.
And I believe this would be true for most developers wanting
to learn and understand DBA stuff...and vice-versa.

so yes...I am on your bandwagon that the DBA-Developer
relationship must and should become interactive and integrated...a point and
message clearly emphasized and presented in your book.

;~)

      Rating: 3
If you can
      create value, you will have a job....  December 22, 2003

Reviewer:  Jens  from Norway
I think its all about creating value for your company. So:
If spending 80 percent of your time tweaking I/O and tuning creates enough value
for your company, then they'll still keep you on the payroll.
However, maybe you would create even more value by spending only 30% of your
time doing that, and use the freed up 50% time getting familiar with your
companies business processes to find "bottlenecks" there.
Or maybe by staying up to do date on new technology, so you can assist
developers and project managers utilising the right tool/technology for the job,
let alone recommend *against* going for the latest "hyp-o-magic
<put-your-3-letter-abbrevation-her>" which may be very cool, but totally
unsuitable compared the good old stuff you know so well..

But then, doing that, maybe you'll no longer be a DBA? Hmmm.. difficult this....

      Rating: 4
DBA = Superset
      of Developer  December 24, 2003
Reviewer:
      GovindanK  from CA,USA
>DBA's on the left of a wall.
>Coders on the right of a wall.
>No intermingling of the left and right.

>I want to bridge that gap and I think you are basically saying the same thing.

As a general expectation a DBA should be able to substitute a Developer, atleast
for a short period of time or till some other developer is selected to fill the
gap. After all , in the moment of trouble, the developer looks forward to
assistance from DBAs .. Correct???. Sorry if i this hurts the
feelings/sentiments of any developer.

Also, i like the statement by Tom.
>I'd be worried about DBA's who want to manage the way we >did with version 6 of
the database.

Well said. Bothers me too.

      Rating: 5
Great Exchange
      on "Future of DBA"  January 03, 2004
Reviewer:
      William Summerhill  from Charlotte, NC - USA
Very insightful (and inciteful) thoughts on this topic.  I've also wondered
about the future of the DBA going forward as Oracle continues to push the
envelope with respect to automating DBA tasks.  Great exchange, and I'm glad
that this thread was highlighted on the OTN home page, otherwise, I would have
never read it!

      Rating: 3
Future of
      DBA  January 05, 2004
Reviewer:  Graham
       from UK
The discussion in this thread seems to be largely about how DBAs can become more
valuable by expanding their skills into development. However, there is another
way in which DBAs can become much more valuable to an expanding company and that
is by moving into the often neglected realm of data architecture.
A data architect's remit is usually to investigate and decide what hardware and
software needs to be deployed to cater for growth and changes to the business,
to research and then to decide how new and emerging technologies (e.g. SAN, 10g,
other database technologies, etc.) can best be deployed, and to plan data
migration from legacy systems to new systems.
With the skills experienced DBAs will have gleaned from working day-to-day with
databases, developers, end-users, and management, they are in an almost uniquely
good position to add value to their companies - and CVs - by moving into
architecture.

      Rating: 3
DBA is
      incomplete without developer  January 06, 2004

Reviewer:  Engr. M. A. Rashid  from BANGLADESH

You can not realize and understand all problems in a system
without developer knowledge.
Learn developer and then try to digest all of dba. Then you can be king of DBA.

          Followup:

unless of course you just buy software from 3rd parties ;)

      Rating: 5
The Great
      Oracle DBA  January 08, 2004
Reviewer:
      a-reader  from New Delhi, INdia
Reading through this thread, I just coulnt resist contributing to it.
I have been associated with the Oracle Software thru realeases 8,8i
(Rel1,Rel2,Rel3) and 9i (rel1 and rel2), since 1999. Initially as an instructor,
then as a software developer and finally Now as a Full fledged DBA.

In these years what I have see tnat here, where I come from, DBA is the last leg
of the whole show!

I mean most applicatoins are designed (still in the old fashioned way) and
implemented and finally the DBA is pressed into service only when some
bottleneck shows up!

That is sad!

So even if you know both PL/SQL as well as DBA skills, be sure that one or the
other will renmain untapped!
And as far as the job scene is concerned, I feel still companies have not become
so broad-minded. The still type-cast the person as "ok he knows how to tune a
database, but he is a developer, so I wont let him touch my SYSTEM schema"!

And if you try to position yourself as a person who can offer the best of both
worlds, then you end up getting nowhere!!

Because, even though outwardly the companies desire to have a person who is good
at both, when it comes to hiring, company talks of having only ONE core skillset
and sticking to it.

Lot of growing up is needed on the companies front.

As for the applications themselves, I have see big names in software, not
actually using the vast user account management features provided in the Oracle
Database!
They invariably create one "super" schema, which has one table called "app_user"
or some stuff like that which has all users names and passwords!

But then coming back to the original thread of discussion, offerring a
combo-pack of both developer and dba makes sense only in a mature market!

      Rating: 3
I Can Do 3D,
      DBA,Developer,Designer, "You name it"  January 16, 2004

Reviewer:  N. POGI (Oracle 9i OCP/DBA)  from TORONTO,
      CANADA
This is what we get in terms of Future DBA's ... etc. People esplly from
developing countries like India,Pak,bangla and the rest tend to be a Jack of all
trades in order to get employed and in order for them to survive. So what's the
outcome?
Millions went to US,CANADA accepting low salary. I cannot blame them for being
wary about the future since specialty is not their focus but survival.

      Rating: 5
DBA
      Jobs  February 09, 2004
Reviewer:  Dan-O
       from Chicago - USA
What do you need to do to get your resume noticed?  I have 5 years of experience
with RAC/OPS, backup, recovery, tunning, physical design etc.  Still I"ve been
sending my resume and nobody is replying.  What am I missing?

      Rating: 5
DBA Jobs
        February 09, 2004
Reviewer:  Dan-O
       from Chicago, usa
I also have developing skills pl/sql; stored procedures; triggers; sql loader.

      Rating: 5
Application DBA
      Role  February 16, 2004
Reviewer:  Preet
      Singh  from Mississauga, Canada
What is the role of an Application DBA? How does it differ from that of a
Production DBA. What is the next role into which an Application DBA can move?

          Followup:

Guess you would have to define your context.  Normally, I think of an
application dba as one that runs SAP, Peoplesoft, Oracle apps -- they are
definitely "in production"

so, you must have some other term/meaning.

      Rating: 3
Application
      DBA, Product DBA, and Data Architect  February 16, 2004

Reviewer:  Duke Ganote  from beautiful Warsaw,
      Indiana
Probably the best distinction between application DBA, production DBA, and data
architect is drawn by Craig Mullins in part 4 of his article "What is a DBA?"

http://www.dbazine.com/dba_4.shtml

      Rating: 4
Appication DBA
      Role  February 17, 2004
Reviewer:  Preet
      Singh  from Mississauga, Ontario, Canada
Thanks Tom and Duke for your quick responses. Tom, I refer to the Ask Tom site
almost everyday for looking up answers to various Oracle problems in my day to
day work.

The reason for my question was that I had been working as an Oracle
programmer/analyst until last year and now I am gradually moving into an
application dba role by taking on more and more tasks of this position.

The link provided by Duke is a closer description of my job role as an
applicaton dba.

February 16, 2004 Santhi -- Thanks for the question regarding
      "Oracle dbms output", version 8.1.7

      You Asked
Hi Tom,

I am trying to display output via dbms_output. I have to display 8 blank spaces
at the start of the line. I am doing the following. Variables values come from a
procedure. As I am lpading v_check_num the dbms output does not display the
blank spaces in the beggining of the line. (the line should contain 8 blank
spaces, 10 digit number, 11 digit amount, 4 blank spaces and T at the end) What
should I do? Please advice.

dbms_output.put_line('        '||lpad(v_check_num,10,0)||
                          lpad(v_check_amt,11,0)||'      '||'T');

Thanks for your help.

Santhi. S.

      and we said...
format WRAPPED is what you are looking for

ops$tkyte@ORA920PC> set serveroutput on
ops$tkyte@ORA920PC> exec dbms_output.put_line('    where are the spaces');
where are the spaces

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> set serveroutput on format WRAPPED
ops$tkyte@ORA920PC> exec dbms_output.put_line('    where are the spaces');
    where are the spaces

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating: 5
oracle dbms
      output -- Tom's Response  February 16, 2004

Reviewer:  Santhi  from Houston, USA
Thank you very much Tom for your quick response.

This will help users like me who is trying to bring data in a specific format.

I greatly appriciate your help.

February 16, 2004 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.

      Rating: 3
short table
      threshold ?  February 16, 2004
Reviewer:
      Christo Kutrovsky  from Ottawa, ON Canada
Tom,

You mention the "short table trheshold" what is it ? According to Oracle
documentations it should be 5 database blocks.
However I have a table that uses 935 blocks, and is 37 000 rows, and when I full
scan it the "table scans (short tables)" statistic is incremented.

What are your observations?

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:255215154182
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/hintsref.htm#15719

      Rating:
      5
Thanks  February 16, 2004
Reviewer:
      Christo Kutrovsky  from Ottawa, ON Canada
Thanks.

February 17, 2004 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)

      Rating: 4
Problem
      connecting 806 client to 9204 db  February 17, 2004

Reviewer:  raj  from Kuala Lumpur, Malaysia

Hi tom, i have just recently upgraded my 9i db to 9204. And i have also applied
patch 10 for my developer components. I am still not able to connect to the 9i
db via my 806 client. The 806 sqlplus just closes without any error message. How
do i connect to 9204 db via sqlplus 806 version ?

          Followup:

contact support.

      Rating: 2
Connecting from
      806 sqlplus to 9204 db  February 20, 2004

Reviewer:  Raj  from Kuala Lumpur, Malaysia

I came to find out that certain 9204 db charactersets are not support by 806
sqlplus clients. Therefore i had to drop the existing database and recreate a db
with a compatible characterset and that solved that issue.

February 17, 2004 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

      Rating: 5
can this be
      dynamic /binding?  February 11, 2004
Reviewer:
      phil  from UK
Hi Tom,

this is an extract of a procedure I have inherited. Because of  the SQLWhere in
the execute imediate I end up doing an awful lot of hard parses.

an example of where_clause is .. AMOUNT >=(-499970.6) AND AMOUNT < (499900.73)
....

can you help in any way .. can I get this into 1 sql statement , or some smart
dynamic sql that binds using the above techniques??

create or replace Procedure updatetransactionalerts (
   naccount_fk   IN      NUMBER,
   nmessage_pk   IN      NUMBER,
   nlogicaltransaction_fk IN pls_integer,
   nStatus IN NUMBER,
   nType IN NUMBER,
   nAmount IN NUMBER)
   IS

   SQLText varchar2(2000);
   SQLWhere varchar2(2000);
   colName varchar2(30);
   AlertBatchNo number(15);
   newpk number(15);
   Seq number(15);
   sAlertType varchar(50);
   rowcount PLS_INTEGER :=0;

BEGIN
  DECLARE CURSOR AlertListC IS

                   select a.pk,a.rtnuser_fk,a.alerttype_fk,
                  (case when a.where_clause is not null then 'WHERE ' ||
a.where_clause end) where_clause ,
                 a.description, c.name
                    from alert a, alert_account_xref b  ,alerttype c
                 where a.pk = b.alert_fk
                 and  a.alerttype_fk = c.pk
                 and  a.alerttype_fk = 1
                 and  a.active=1
                 and (a.valid_From is Null or a.valid_From<=SYSDATE)
                 and (a.valid_To is Null or a.valid_To>=SYSDATE)
                 and (a.Last_Trigger_Time is null or a.Last_Trigger_Time
<=(SYSDATE-(10/(24*60))) )
                 and b.account_fk = naccount_fk ;

    BEGIN

        -- get the next alertbatch number
        SELECT SEQ_ALERT_BATCH.NextVal INTO alertbatchno FROM Dual;

        FOR Alert IN AlertListC LOOP

            SQLWhere :=
replace(replace(replace(Alert.where_clause,'STATUS',nStatus),'AMOUNT',nAmount),'T
YPE',nType);
           -- insert into the log
           EXECUTE IMMEDIATE 'INSERT INTO AlertLog(PK, alerttype_fk, Alert_FK,
Trigger_Time, Account_FK,
                                   Message_Instance_FK,
Alert_Batch_no,rtnUser_fk)
                             SELECT SEQ_ALERTLOG.NextVal,:2,:3,:4,:5,:6,:7,:8
FROM DUAL '|| SQLWhere
                             using alert.alerttype_fk, alert.PK, sysdate,
naccount_fk, nmessage_pk, AlertBatchNo,
                             alert.rtnuser_fk;

       END LOOP;

  END;

END UPDATETRANSACTIONALERTS;

as always
many many thanks in advance

          Followup:

You'll either

a) have to change your data model and store "where clauses" and "inputs into
where clauses" -- eg: store:

AMOUNT >=sys_context('myctx','amt1') AND AMOUNT < sys_context('myctx','amt2')

and storing in a normalized table the string amt1 and the value for amt1.  Then,
use dbms_session.set_context to set these values

b) blow off binds all together and rely on cursor sharing -- like this:

ops$tkyte@ORA9IR2> create table t ( x int );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> begin
  2          for i in 1 .. 10
  3          loop
  4                  execute immediate '
  5                  insert /* TAG */ into t ( x )
  6                  select :1
  7                    from dual
  8                   where ' || i || ' = ' || i
  9                  using i;
 10          end loop;
 11  end;
 12  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select sql_text from v$sql where sql_text like '% /* TAG */
%';

SQL_TEXT
---------------------------------------------------------------------------------
--------------------------------------------------
   insert /* TAG */ into t ( x )   select :1     from dual    where 4 = 4
   insert /* TAG */ into t ( x )   select :1     from dual    where 7 = 7
   insert /* TAG */ into t ( x )   select :1     from dual    where 2 = 2
select sql_text from v$sql where sql_text like '% /* TAG */ %'
begin  for i in 1 .. 10  loop   execute immediate '   insert /* TAG */ into t (
x )   select :1     from dual    where ' || i || '
= ' || i   using i;  end loop; end;

   insert /* TAG */ into t ( x )   select :1     from dual    where 8 = 8
   insert /* TAG */ into t ( x )   select :1     from dual    where 5 = 5
   insert /* TAG */ into t ( x )   select :1     from dual    where 3 = 3
   insert /* TAG */ into t ( x )   select :1     from dual    where 10 = 10
   insert /* TAG */ into t ( x )   select :1     from dual    where 1 = 1
   insert /* TAG */ into t ( x )   select :1     from dual    where 9 = 9
   insert /* TAG */ into t ( x )   select :1     from dual    where 6 = 6

12 rows selected.

that shows your problem and this is the alternative:

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> begin
  2          execute immediate 'alter session set cursor_sharing=force';
  3          for i in 1 .. 10
  4          loop
  5                  execute immediate '
  6                  insert /* TAG2 */ into t ( x )
  7                  select ' || i || '
  8                    from dual
  9                   where ' || i || ' = ' || i;
 10          end loop;
 11          execute immediate 'alter session set cursor_sharing=exact';
 12  end;
 13  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select sql_text from v$sql where sql_text like '% /* TAG2 */
%';

SQL_TEXT
---------------------------------------------------------------------------------
--------------------------------------------------
select sql_text from v$sql where sql_text like '% /* TAG2 */ %'
   insert /* TAG2 */ into t ( x )   select :"SYS_B_0"     from dual    where
:"SYS_B_1" = :"SYS_B_2"
begin  execute immediate 'alter session set cursor_sharing=force';  for i in 1
.. 10  loop   execute immediate '   insert /* TAG2 *
/ into t ( x )   select ' || i || '     from dual    where ' || i || ' = ' || i;
 end loop;  execute immediate 'alter session set c
ursor_sharing=exact'; end;

just using cursor sharing won't fix you for if you bind a single value into
the statement (as you are) cursor sharing won't touch the other literals.  You
would have to stop binding all together.

      Rating:
      5
  February 11, 2004
Reviewer:  Phil

sorry, could you expand option 1 with a simple example.

thanks

          Followup:

ops$tkyte@ORA9IR2> create table emp as select * from scott.emp;

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create table t ( x int, y int );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create table wc ( id int primary key, where_clause
varchar2(2000) );

Table created.

ops$tkyte@ORA9IR2> create table wc_dtl ( id references wc, bv_name varchar2(30),
bv_val varchar2(2000), primary
  2  key(id,bv_name) );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into wc values
  2  ( 1, ' sal between to_number(sys_context( ''myctx'', ''lo_sal'' )) and
to_number(sys_context( ''myctx'', ''hi_sal'' )) ' );

1 row created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into wc_dtl values ( 1, 'lo_sal', 100 );

1 row created.

ops$tkyte@ORA9IR2> insert into wc_dtl values ( 1, 'hi_sal', 1000 );

1 row created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> commit;

Commit complete.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace context myctx using myproc
  2  /

Context created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace procedure myproc( p_name in varchar2, p_val
in varchar2 )
  2  as
  3  begin
  4          dbms_session.set_context( 'myctx', p_name, p_val );
  5  end;
  6  /

Procedure created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2          l_stmt long;
  3  begin
  4          l_stmt := 'insert into t select empno, sal from emp where hiredate
>= :x ';
  5
  6          for x in ( select * from wc where id = 1 )
  7          loop
  8                  l_stmt := l_stmt || ' and ' || x.where_clause;
  9          end loop;
 10
 11          for x in ( select * from wc_dtl where id = 1 )
 12          loop
 13                  myProc( x.bv_name, x.bv_val );
 14          end loop;
 15
 16          execute immediate l_stmt using to_date( '01-jan-1980' );
 17  end;
 18  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select * from t;

         X          Y
---------- ----------
      7369        800
      7900        950

      Rating: 5
So good ....
        February 13, 2004
Reviewer:  Phil

Thank you ....

out of interest what if my where clause that is stored was a an  sal IN( 1,2,3)
rather than the between can I use a similar technique ?

          Followup:

of course.

ops$tkyte@ORA9IR2> insert into wc values
  2  ( 1, ' sal in ( :x, :y, :z )' );
1 row created.

ops$tkyte@ORA9IR2> insert into wc_dtl values ( 1, 'x', 1 );

ops$tkyte@ORA9IR2> insert into wc_dtl values ( 1, 'y', 2 );

ops$tkyte@ORA9IR2> insert into wc_dtl values ( 1, 'z', 3 );

      Rating: 4
Error with the
      IN technique  February 17, 2004
Reviewer:
      Phil
using the technique you mentioned above for an IN, and using the sample code you
provided previous ... I get an error. The only difference the hiredate bind

any ideas ?

n_sys@N> declare
  2              l_stmt long;
  3      begin
  4              l_stmt := 'insert into t select empno, sal from emp where hired
ate >= :a ';
  5
  6              for x in ( select * from wc where id = 1 )
  7              loop
  8                      l_stmt := l_stmt || ' and ' || x.where_clause;
  9              end loop;
 10
 11             for x in ( select * from wc_dtl where id = 1)
 12             loop
 13                     myProc( x.bv_name, x.bv_val );
 14             end loop;
 15
 16             execute immediate l_stmt using to_date( '01-jan-1980' );
 17     end;
 18  /
declare
*
ERROR at line 1:
ORA-01008: not all variables bound
ORA-06512: at line 16

thanks

          Followup:

show the entire example please. including what is in wc_dtl for you.

      Rating:
      4
  February 17, 2004
Reviewer:  Phil

Can I use the same technique as setting the context here would not work ?

          Followup:

i don't understand that?

      Rating: 4
data
      tables  February 17, 2004
Reviewer:  phil

n_sys@N> select * from wc_dtl;

        ID BV_NAME
---------- ------------------------------
BV_VAL
-----------------------------------------------------
--------------------
         1 x
1

         1 y
2

         1 z
3

         2 lo_sal
10

         2 hi_sal
1000

n_sys@NOSTRODB> select * from wc;

        ID
----------
WHERE_CLAUSE
--------------------------------------------------------------------------------
---------------------------------------------------
         1
 sal in ( :x , :y, :z )

         2
 sal between to_number(sys_context( 'myctx', 'lo_sal' )) and to_number(sys_conte
xt( 'myctx', 'hi_sal' ))

I am trying so hard to bind this particular inheritance but having trouble. I am
probably missing the obvious and feel quite dumb

thanks

          Followup:

you have binds in the id=1, not sys_context values.  you would have needed to
bind x, y, z.

binds do not belong in the wc table.
sys_context calls do.

February 17, 2004 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.

      Rating: 4
Deadlock on
      dbms_alert_info  February 11, 2004
Reviewer:
      Dilip  from PA,USA
Tom,
If this is not right place, I will put my question when you are taking new
questions.

We are getting deadlock on dbms_alert_info

We need to refresh static data for an application when data is inserted/updated.
 Inserts/Updates takes place via single session only.

Here is what we have..

There are insert/update triggers on couple of tables.

CREATE OR REPLACE TRIGGER TRG_T1_REFRESH
AFTER INSERT OR UPDATE ON T1
BEGIN
  DBMS_ALERT.SIGNAL('T1_ALERT','T1');
END;
/

CREATE OR REPLACE TRIGGER TRG_T2_REFRESH
AFTER INSERT OR UPDATE ON T2
BEGIN
  DBMS_ALERT.SIGNAL('T2_ALERT','T2');
END;
/

There is a DBMS_JOB which register calls refresh_static_data procedure.

declare
   l_job number;
begin
   dbms_job.submit( l_job,
                   'begin
                refresh_static_data;
             end;',
                   sysdate,
                   'sysdate' );
   commit;
end;
/

Here is the refresh_static_data procedure

CREATE OR REPLACE PROCEDURE refresh_cst_static_data
AS

  v_http_data     VARCHAR2(2000);
  v_name        VARCHAR2(60);
  v_message        VARCHAR2(200);
  v_status        NUMBER;
  v_static_url    VARCHAR2(200) := 'some_url_for_refresh';
  v_refresh_url    VARCHAR2(250);

BEGIN

  DBMS_ALERT.REGISTER('T1_ALERT');
  DBMS_ALERT.REGISTER('T2_ALERT');

  LOOP

      DBMS_ALERT.WAITANY(v_name, v_message, v_status, 60);

      IF v_status = 0 THEN

        v_refresh_url := v_static_url||v_message;

        begin
            v_http_data := utl_http.request(v_refresh_url);
        exception when others then
            null;
        end;

      ELSE
        exit;
    END IF;

  END LOOP;

  DBMS_ALERT.REMOVEALL();

END;
/

From time to time, we get deadlock error.

When we see trace file, it has following information.

...
...
DEADLOCK DETECTED
Current SQL statement for this session:
DELETE FROM DBMS_ALERT_INFO WHERE SID = :b1
----- PL/SQL Call Stack -----
  object      line  object
  handle    number  name
8dc57e28       468  package body SYS.DBMS_ALERT
8dc4e93c        64  procedure USZONE_MGR.REFRESH_CST_STATIC_DATA
8dc60d38         2  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:
....
....

Is this deadlock caused by transactions on DBMS_ALERT_INFO?
Not sure where to start looking to troubleshoot this one.

Thanks for you help,

Regards

Dilip

          Followup:

what is line 64 of

8dc4e93c        64  procedure USZONE_MGR.REFRESH_CST_STATIC_DATA

?  not clear from the example which line that could be.

      Rating:
      4
  February 11, 2004
Reviewer:  Dilip
       from PA, USA
Line 64 in the procedure is,

  DBMS_ALERT.REMOVEALL();

          Followup:

I believe this is a true deadlock.

if one session "signals", the removeall will block.  I believe the signalling
session then tries to lock a resource the blocked one has.  Deadlock in the
conventional sense.

do this:

---------------- test1.sql ----------------------
set echo on

drop table t;
create table t ( x int );
insert into t values ( 1 );

variable nm varchar2(10)
variable msg varchar2(20)
variable stat number

exec dbms_alert.register( 'a' );
prompt goto other session, run test2 and come back and hit enter
pause
update t set x = x;
exec dbms_alert.removeall
commit;

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

---------------------- test2.sql -----------------------------

set echo on

exec dbms_alert.signal( 'a', 'msg' )
prompt go back to other session and hit enter
pause
update t set x = x;
commit;

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

run those two scripts and you'll have a small example of what I mean.

Look at the two real processes (you seem to have given a small snippet) and see
if they do not go after the same resources.  Perhaps you are missing a "commit"
in the job before the removeall?

      Rating:
      4
  February 16, 2004
Reviewer:  Dilip

Thank you Tom for your response.

I understand the example given by you and tried on my end resulting in deadlock.

But the original proc (which is executed by DBMS_JOB), does not lock any other
resources (atleast I can not find it/understand it if it does).  It registers
for alerts, waits for them and if it finds any alert, makes call to
utl_http.request function. If timeout happens in waiting for alert, then it
calls dbms_alert.removeall.

Now I understand that the call to dbms_alert.removeall gets blocked by
signalling session (if no commit or rollback in signalling session is issued
before waitany times out) but there are no resources locked by the job issuing
dbms_alert.removeall.

I am not able to reproduce this particular situation in test database but this
deadlock happened again in production system.

For the time being, I am planning to increase time out period on
dbms_alert.waitany to dbms_alert.maxwait so the job will just sit there, waiting
for any alert.

Dilip

          Followup:

what does this running of a url do exactly then?

are we looking at the real code?  how big is the real code.

      Rating:
      4
  February 16, 2004
Reviewer:  Dilip
       from PA, USA
Tom,

I have put the actual code here.

1. Trigger

CREATE OR REPLACE TRIGGER TRG_SEGMENTATION_DATA_REFRESH
AFTER INSERT OR UPDATE ON SEGMENTATION
BEGIN
  DBMS_ALERT.SIGNAL('SEGMENTATION_ALERT','SEGMENTATION');
END;
/

CREATE OR REPLACE TRIGGER TRG_SERVICE_CLASS_DATA_REFRESH
AFTER INSERT OR UPDATE ON SERVICE_CLASS
BEGIN
  DBMS_ALERT.SIGNAL('SERVICE_CLASS_ALERT','SERVICE_CLASS');
END;
/

-- There are 26 more tables with similar trigger code

2. Procedure

CREATE OR REPLACE PROCEDURE refresh_cst_static_data
AS

  v_http_data     VARCHAR2(2000);
  v_name        VARCHAR2(60);
  v_message        VARCHAR2(200);
  v_status        NUMBER;
  v_static_url    VARCHAR2(200) := '
http://cst.zoneld.net/ecare/reloadStaticData/';
  v_refresh_url    VARCHAR2(250);

BEGIN

  DBMS_ALERT.REGISTER('SEGMENTATION_ALERT');
  DBMS_ALERT.REGISTER('SERVICE_CLASS_ALERT');

  --This proc registers for 26 other alerts in the same way.  I have removed
those statements.

  LOOP

      DBMS_ALERT.WAITANY(v_name, v_message, v_status, 60);

      IF v_status = 0 THEN

        v_refresh_url := v_static_url||v_message;

        begin
            v_http_data := utl_http.request(v_refresh_url);
        exception when others then
            null;
        end;

      ELSE
        exit;
    END IF;

  END LOOP;

  DBMS_ALERT.REMOVEALL();

END;
/

3. This is the script for creating the job

declare
   l_job number;
begin
   dbms_job.submit( l_job,
                   'begin
                refresh_cst_static_data;
             end;',
                   sysdate,
                   'sysdate' );
   commit;
end;
/

The URL points to a sevlet (on 9ias) which does the refresh of static data.

Servlet is selecting data for the table specified in the URL.

          Followup:

ahh, got it.

When you call removeall -- it is removing the alerts in order of registration.
Do this:

IN SESSION 1:

declare
    v_name        VARCHAR2(60);
    v_message        VARCHAR2(200);
    v_status        NUMBER;

begin
  dbms_alert.register( 'A' );
  dbms_alert.register( 'B' );
  DBMS_ALERT.WAITANY(v_name, v_message, v_status,1);
end;
/

IN SESSION 2:

exec dbms_alert.signal( 'B', 'x' );

IN SESSION 1:

exec dbms_alert.removeall();

<that'll block -- but it'll have locked "alert A" in the removal and will be
stuck on removing "alert B">

IN SESSION 2:

exec dbms_alert.signal( 'A', 'x' );

<that'll block -- and the other session #1 will get:

ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
ORA-06512: at "SYS.DBMS_ALERT", line 464
ORA-06512: at line 1

They are locking each others resources here.

Also, since ALERTS are totally "blocking city" sort of things (eg: only one
session at a time can fire an alert of a given name!!!!) you have serialized all
access to these 26 tables (surprised you do not have deadlocks all over the
place actually).

A suggestion, create a table:

create table job_parameters( job_id number primary key, url varchar2(255) );
create index job_parameters_idx on job_parameters(url);

and use a trigger like this:

CREATE OR REPLACE TRIGGER TRG_SEGMENTATION_DATA_REFRESH
AFTER INSERT OR UPDATE ON SEGMENTATION
DECLARE
  l_job number;
BEGIN
  dbms_job.submit( l_job, 'run_url(JOB);' );
  insert into job_parameters(job_id,url) values
  (job_id,'http ://cst.zoneld.net/ecare/reloadStaticData/' );
END;
/

and code:

create or replace procedure run_url( p_job_id in number )
as
   l_rec job_parameters%rowtype;
   l_http_data long;
begin
   select * into l_rec from job_parameters where job_id = p_job_id;
   delete from job_parameters where URL = l_rec.url;
   commit;
   l_http_data := utl_http.request(l_rec.url);
exception
   when no_data_found then NULL; -- just skip it, we didn't need to run again
end;
/

In that fashion -- N number of concurrent sessions can request that URL to be
run at the same time.

The job will run -- pick up the URL, get rid of any duplicate URL's queued up,
then run the URL.  The other jobs will just run and exit -- until something
appears in the job parameters table again.

      Rating:
      5
  February 17, 2004
Reviewer:  Dilip
       from PA, USA
Tom,
Thank you so much.  Now I understand why we get deadlock in our particular
situation.

I will implement your suggested solution.  One question though, this solution
will generate job id everytime.  Is there any upper limit to job ids generated
by oracle?

Regards,

Dilip

          Followup:

999,999,999

but it'll cycle so it is rather "infinite"

February 17, 2004 T.S.V.S. -- Thanks for the question regarding
      "Extending Rollback segments", version 8.1.5

      You Asked
Hai Tom,

I am porting the data from old structures to New structures. Some times I can
prot more than 1,00,000 records also. At that time it is giving the following
error :
 ora-1562:failed to extend rollback segment no.2
 ora-01628:max#extents (249) reached for rollback segment block.

I know how to increase max# extents and size of extents for tables. But rollback
segements how I can increase?

Thanking You,
Kishore

      and we said...
You can chang the maxextents via:

alter rollback segment rbs2 storage ( maxextents N );

if you are going to change next, you could:

alter rollback segment rbs2 storage ( next 524288 );

but I would recommend DROPPING and recreating them so they have uniformly sized
extents.
Review
      & Followup

      Rating:
      2
Strange  February 16, 2004
Reviewer:
      Invisible  from UK
I just did this:

SQL> declare
  2    n integer;
  3  begin
  4    for n in 0..65535 loop
  5      insert into cipher_rc6_sbox (input, output) values (n, (2*n+1)*n);
  6    end loop;
  7    commit;
  8  end;
  9  /
declare
*
ERROR at line 1:
ORA-01562: failed to extend rollback segment number 1
ORA-01628: max # extents (121) reached for rollback segment ROLLBACKSEG1
ORA-6512: at line 5

Erm... how much rollback do you NEED? :-| I wouldn't have thought this tiny
thing would need very much...

It's only a test database, so I haven't allocated much rollback. (And I am the
*only* person using this DB.)

I was under the impression that using AUTOEXTEND means that the thing... well...
automatically extends to be whatever size it needs to be! Clearly I was
catastrophically mistaken... :-S

Anyway, the rollback segments:

Create Tablespace RollbackData
DataFile: '...'
Size 20M  AutoExtend ON;

Create Public Rollback Segment RollbackSeg1  Tablespace RollbackData  Storage
(Optimal 4M);

[same for RollbackSeg2..4]

I have no idea what Oracle's problem is. I suspect this means I fundamentally
don't understand how this stuff works.

This is not good.

[Guess who operates the company's Oracle databases?]

PS. Is there a Better Way(tm) to create the data I'm trying to create?

          Followup:

might not be your transaction that is causing this.

were there other transactions happening here?  look in v$transaction (perhaps
you left a window open last week sometime.  we use RBS space in a circular
fashion -- we cannot reuse RBS data until the transactions that were using it
committed.  It only takes a tiny -- let longer lived -- transaction to cause
this.  Just means you tried to wrap back around in the RBS and couldn't -- but
could not grow it either).

If you have my book "expert one on one Oracle" -- i describe how RBS is used.

The concepts guide (a MUST MUST read for any Oracle professional) covers it as
well.

The ADMIN guide would be a second "must read" for you followed by the backup and
recovery guide (at least 2 times).

You do not say what version of Oracle you are using either -- in 9i, you should
use AUM (automatic undo mgmt) instead of manually creating your own RBS's.

Autoextend lets a file grow as segments extend.

Problem here is you said "max extents is 121 for this RBS", so -- didn't matter
that the file could grow, you did not let the RBS grow.  You also did not set up
an INITIAL, NEXT (should probably be 1m and 1m in many generic cases to start
with).

But, yes, your approach generates THE MOST undo possible.  Consider the
following 4 approaches:

ops$tkyte@ORA920PC> create table cipher_rc6_sbox ( input number, output number
);

Table created.

Elapsed: 00:00:00.04
ops$tkyte@ORA920PC> create index t1_idx on cipher_rc6_sbox(input);

Index created.

Elapsed: 00:00:00.05
ops$tkyte@ORA920PC> create index t2_idx on cipher_rc6_sbox(output);

Index created.

Elapsed: 00:00:00.05
ops$tkyte@ORA920PC> create index t3_idx on cipher_rc6_sbox(input,output);

Index created.

Elapsed: 00:00:00.04
ops$tkyte@ORA920PC> create index t4_idx on cipher_rc6_sbox(output,input);

Index created.

Elapsed: 00:00:00.02

Just assuming you are loading into heavily indexed tables....

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> declare
  2    n integer;
  3  begin
  4    for n in 0..65535 loop
  5      insert into cipher_rc6_sbox (input, output) values (n, (2*n+1)*n);
  6    end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:47.64
ops$tkyte@ORA920PC> select used_ublk*8/1024 "RBS used in MEG" from
v$transaction;

RBS used in MEG
---------------
     27.3984375

Elapsed: 00:00:00.05

now, same table -- no indexes:
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> drop table cipher_rc6_sbox;

Table dropped.

Elapsed: 00:00:00.62
ops$tkyte@ORA920PC> create table cipher_rc6_sbox ( input number, output number
);

Table created.

Elapsed: 00:00:00.07
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> declare
  2    n integer;
  3  begin
  4    for n in 0..65535 loop
  5      insert into cipher_rc6_sbox (input, output) values (n, (2*n+1)*n);
  6    end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:18.35
ops$tkyte@ORA920PC> select used_ublk*8/1024 "RBS used in MEG" from
v$transaction;

RBS used in MEG
---------------
       4.421875

Elapsed: 00:00:00.03
ops$tkyte@ORA920PC>

Now, same table -- BULK load:

ops$tkyte@ORA920PC> drop table cipher_rc6_sbox;

Table dropped.

Elapsed: 00:00:00.20
ops$tkyte@ORA920PC> create table cipher_rc6_sbox ( input number, output number
);

Table created.

Elapsed: 00:00:00.05
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert into cipher_rc6_sbox
  2  select n, (2*n+1)*n
  3    from ( select rownum-1 n
  4             from all_objects, all_objects
  5                    where rownum <= 65536 );

65536 rows created.

Elapsed: 00:00:06.97
ops$tkyte@ORA920PC> select used_ublk*8/1024 "RBS used in MEG" from
v$transaction;

RBS used in MEG
---------------
         .15625

Elapsed: 00:00:00.00

that is quite the difference!  but wait, it gets better:

ops$tkyte@ORA920PC> create table cipher_rc6_sbox ( input number, output number
);

Table created.

Elapsed: 00:00:00.05
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert /*+ APPEND */ into cipher_rc6_sbox
  2  select n, (2*n+1)*n
  3    from ( select rownum-1 n
  4             from all_objects, all_objects
  5                    where rownum <= 65536 );

65536 rows created.

Elapsed: 00:00:03.72
ops$tkyte@ORA920PC> select used_ublk*8/1024 "RBS used in MEG" from
v$transaction;

RBS used in MEG
---------------
       .0078125

Elapsed: 00:00:00.00

      Rating:
      5
Illuminating...  February 16, 2004

Reviewer:  Invisible  from UK
Well, it's not another transaction; I only just switched on the machine with the
DB on it (which isn't networked).

I generally stay away from the Oracle manuals, simply because they are SO
difficult to use. (For example, there are several hundred pages describing in
minute the exact syntax diagrams... but would be useful if it said what these
things actually DO. :-S)

Anyways, I'm using Oracle 8i. (8.1.6 IIRC...)

<quote>
Autoextend lets a file grow as segments extend.
</quote>

I get it...

<quote>
Problem here is you said "max extents is 121 for this RBS", so -- didn't matter
that the file could grow, you did not let the RBS grow.  You also did not set up

an INITIAL, NEXT (should probably be 1m and 1m in many generic cases to start
with).
</quote>

I don't remember saying that... is it a default if not explicitly specified or
something?

Anyway, I'll go have a look at the syntax diagrams again.

Thanks for you time.

          Followup:

you just described the concepts guide!

it is a default,

select * from dba_segments where segment_name = 'YOUR ROLLBACK SEGMENT NAMES';

will show you. could be 40k/40k meaning about 4.7m max with 121 extents.

Neither the concepts nor the admin nor the backup guide are full of train tracks
-- just the SQL reference (one guide, one guide out of all of them!) has that.

I've learned most everything I know about Oracle from those documents you avoid.
 They are not nearly anywhere near what you describe.  One document with wire
diagrams.  The rest -- well, not so.

      Rating: 4
clarification
      of amount of undo generated  February 16, 2004

Reviewer:  amit poddar  from New Haven, CT USA

Hi,
In your examples above you should how oracle generates different amount of undos
in different scenarios.

Inserting in table without indexes would generate less undo. That makes sense
But could you please clarify why bulk insert generates less undo than than the
row by row plsql insert

          Followup:

because each insert statement has some amount of "header/trailer" overhead
stuffed on the undo record generated -- each statement is just that, a statement
with fixed overhead.

by doing in bulk, we generate 1/65536th of that overhead.  Doing things in bulk
is always a "good thing (tm)"

I always say "do it in a single statement if you can".

      Rating: 5
about append
      hint  February 16, 2004
Reviewer:  amit
      poddar  from New Haven, CT USA
Hi,
I missed this in previous question

What about append hint
it seems to generate much less undo almost negiligible
Could you shed some light on why ??

          Followup:

it is its very nature.

append writes above the high water mark for the table.
the only undo necessary is therefore the undo to undo the "get me space"
request.  The actual insert generates no real undo.  In order to rollback that
insert -- all we need do is "un-get your space" and poof -- data is gone.

If you have indexes, they will generate UNDO regardless but even that is
minimized with append as the indexes are maintained in BULK at the end of the
insert -- minimizing the work done to the index itself.

      Rating: 5
Once
      again...  February 17, 2004
Reviewer:
      Invisible  from UK
Well, I can never find anything in the Oracle docs. I can find masses of highly
technical writing which seems to assume you already know it all, and I can find
introductory material which assumes you bearly know what a computer is, but I
can find almost none of the material I want - the bit in the middle! It's really
very difficult indeed to find any useful info at all - just finding out the
exact command to cause a log switch took me ages. (Is it ALTER DATABASE
something? No, wait, maybe it's ALTER SYSTEM... or is it ALTER LOG...)

If it makes any odds, my lecturers at university had the same trouble. But
anyway, moaning about it isn't going to make the documentation magically get
better, so let's do something more positive with this thread...

<quote>
But, yes, your approach generates THE MOST undo possible.
</quote>

Thought so!

I figured more frequent commits might change that (it's not like there are real
"transactions" here, I'm just loading data into a table, and I'm the only
database user). But you seem to recommend infrequent commits. (Certainly the
extra checkpoints would [presumably] slow things down.)

I don't have ANY indexes; I figured it would be faster to build those AFTER I
fill up the table (if I choose to do them at all).

<quote>
ops$tkyte@ORA920PC> insert into cipher_rc6_sbox
  2  select n, (2*n+1)*n
  3    from ( select rownum-1 n
  4             from all_objects, all_objects
  5                    where rownum <= 65536 );

65536 rows created.
</quote>

Huh?!

Well, clearly a single SQL statement is better than my loops approach - but I
couldn't think of any other way to do it.

I have no idea how that example works! :-/

<quote>
ops$tkyte@ORA920PC> insert /*+ APPEND */ into cipher_rc6_sbox
  2  select n, (2*n+1)*n
  3    from ( select rownum-1 n
  4             from all_objects, all_objects
  5                    where rownum <= 65536 );

65536 rows created.
</quote>

Ditto.

(Besides, isn't /* */ just a comment?)

Again, thanks for your time.

          Followup:

sorry if this sounds overbearing, that is not my intention.  I'm just on a
crusade to get people to *read the stuff*.

also, one should keep in mind that this is a continual learning process. I learn
something new about Oracle every single day -- and I've been using it for 16
years.

question for you tho

why would you want to know how to switch logs?  before knowing what logs are,
do, etc?  I mean -- switching logs manually just isn't "something normally done"
-- ever?  I would hope it would be something you would think about after reading
something like the admin guide (as it is sort of an admin task)

but that aside, lets follow my line of thinking here.

a) I want to switch logs (why, no idea, just do)
b) log mgmt is an admin task.
c) admin task are in the admin guide
d) admin guide has chapter 7 "managing the online redo log"
e) i goto that chapter and the first page has list of topics
f) read list and see "forcing log switches"

hmmm.  Perhaps you are just only using the sql ---->>> REFERENCE <<<---- (would
hope academics at a school would understand the use of a reference manual vs the
others -- well, actually, I would hope a lecturer at a university to which I'm
paying big bucks would have long ago -- i don't know -- sort of MASTERED the
topic they are lecturing on?)

The sql reference -- very dry, gives you syntax and prereqs.

Admin Guide -- well, does what it says, tells you how to admin, discusses in a
conversational tone the topics.  Take that log switch topic -- here is what it
had to say:

<quote>
Forcing Log Switches

A log switch occurs when LGWR stops writing to one online redo log group and
starts writing to another. By default, a log switch occurs automatically when
the current online redo log file group fills.

You can force a log switch to make the currently active group inactive and
available for online redo log maintenance operations. For example, you want to
drop the currently active group, but are not able to do so until the group is
inactive. You may also wish to force a log switch if the currently active group
needs to be archived at a specific time before the members of the group are
completely filled. This option is useful in configurations with large online
redo log files that take a long time to fill.

To force a log switch, you must have the ALTER SYSTEM privilege. Use the ALTER
SYSTEM statement with the SWITCH LOGFILE clause.

The following statement forces a log switch:

ALTER SYSTEM SWITCH LOGFILE;
</quote>

You see -- I violently disagree that the documentation "needs to get better".

I think it just needs to be *read*, or at least *searched*.

Let's take that example again.  I want to find out how to switch logs.  Ok, I
goto

http://otn.oracle.com/pls/db92/db92.homepage
(which is the online docs, freely available to anyone on the planet).  I plug in

log switch

in the "enter word or phrase" dialog and hit search....

I get "99 topics in 19 books".  the admin guide had 12 matching topics and is
listed first (it does not have the most hits, it had the most relevant ones).  I
click on Admin Guide and get a VTOC (virtual table of contents...) for my
search:

Administrator's Guide (12 matching topics)

About Oracle Background Processes
 Dropping Log Groups
 Dropping Online Redo Log Members
 Factors Affecting the Setting of ARCHIVE_LAG_TARGET
 Forcing Log Switches
 Log Switches and Log Sequence Numbers
 Managing the Online Redo Log
 Responding to Online Redo Log Failure
 Running a Database in ARCHIVELOG Mode
 Running a Database in NOARCHIVELOG Mode
 Setting the ARCHIVE_LAG_TARGET Initialization Parameter
 Setting the Size of Online Redo Log Members

Ok, so -- if you ignore the reference (except to get explicit help on a specific
command -- using the reference would be like using a spanish dictionary to try
and phrase a question in spanish when you only speak english.  not very useful.
I'd rather has a PHRASE book at that point.  But, in order to find out what each
word in a phrase meant, I'd want that dictionary no?)  and use the other guides,
you'd find it is actually "pretty darn good"

In my last book -- I even set up a roadmap of required reading:

                  Concepts Guide (all)
                         |
                         v
                  New Features Guide (all)
                         |
 dev track +------------------------------+  dba track
           |                              |
    app. dev. guide                 backup/recovery concepts
           |                              |
    pl/sql guide                    rman guide
           |                              |
    performance planning            admin guide
           +------------------------------+
                        |
                   performance tuning
                   guide and reference

And I wouldn't expect what I did above with the INSERT as SELECT to be
"documented" as it is one of those things that "if you know sql, you should just
'know'".  It is "just sql".

You wanted 64k rows.  Well, we can generate as many rows as we want by selecting
from some tables.

select null from all_objects, all_objects

just generates a result set that is the cartesian product of all_objects with
all_objects.  The first row in all_objects is joined to every row in
all_objects, then the second row and so on.  So, at the end of the day, we get
cardinality(all_objects) * cardinality(all_objects) rows out of that.  Given
that all_objects has 10's of thousands of rows in 8i and up -- that is "lots of
rows".  We needed 64k so we just:

select null from all_objects, all_objects where rownum <= 65536

to stop it (rownum being a builtin very important psuedo column in Oracle -- if
you use the above link to search for rownum, you can read about it if you don't
know what it is).

Now, you needed "an i looping number from 0..on", well, we just

select rownum-1 n from all_objects, all_objects where rownum <= 65536

and we get that.  Then, we just apply your formula.

/* */ is a comment

/*+ .... */ is a HINT

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/hintsref.htm#7834
/*+  APPEND */ specifically is a direct path insert, used in bulk loads for high
speed loading.  You'll have to see:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96524/c21dlins.htm#11358
to fully appreciate it

      Rating:
      5
Oops  February 17, 2004
Reviewer:
      Invisible  from UK
<quote>
I figured more frequent commits might change that (it's not like there are real
"transactions" here, I'm just loading data into a table, and I'm the only
database user). But you seem to recommend infrequent commits. (Certainly the
extra checkpoints would [presumably] slow things down.)
</quote>

Erm... what on Earth am I chatting about?!

It's LOG SWITCHES that cause a checkpoint, not commits...

Feeling stupid now. :-/

          Followup:

well, frequent commits as well as single row inserts do generate gobs more redo
-- so actually there is a correlation between the two!!!

commit more often = more redo generated = more frequent log switching = more
checkpoint activity.

      Rating: 5
You'll love
      that books....  February 17, 2004
Reviewer:
      Franco  from Denmark
.... I can't force you to read Oracle manuals, but I can suggest Tom books, then
you'll feel the need to read the related Oracle manuals! Anyway, /* */ =
Oracle9i SQL Reference==>Chapter 2 'Basic Elements of Oracle SQL' pag. 2-92
;-)

      Rating:
      5
Orange  February 17, 2004
Reviewer:
      Invisible  from UK
<quote>
sorry if this sounds overbearing, that is not my intention.  I'm just on a
crusade to get people to *read the stuff*.
</quote>

...in other words, READ THE MANUAL ;-)

I know, a lot of people don't - and there can be loads of useful information in
manuals.

I did actually TRY to read Oracle's various manuals, but in the end I just gave
up. It was vastly too hard to find the information I wanted. The documentation
just seems to be a HUGE mass of pageso, and I really struggled to work out where
to look for various things.

...so I went out and bought a book about Oracle. Suddenly everythnig made much
more sense. (For example, as a result our database is now *regularly backed up*,
which is always a Good Thing[tm]...)

<quote>
also, one should keep in mind that this is a continual learning process. I learn
something new about Oracle every single day -- and I've been using it for 16
years.
</quote>

Surely this is true of just about any nontrivial area of knowledge! ;-)

<quote>
question for you tho

why would you want to know how to switch logs?  before knowing what logs are,
do, etc?  I mean -- switching logs manually just isn't "something normally done"
-- ever?  I would hope it would be something you would think about after reading
something like the admin guide (as it is sort of an admin task)
</quote>

Oh, totally - if I hadn't already worked out what a redo log is, I wouldn't be
trying to do a log switch. The fact is, I was working on a test database, and I
wanted to change the redo log size. To do that, create new logs and drop the old
ones. But you can't do that while one of them is active...

As I say, I had no idea where to look in the documentation to find the
command... I eventually found it (I now have the printout for ALTER SYSTEM and
ALTER DATABASE on my desk, since these are the two most used commands ever!)

<quote>
a) I want to switch logs (why, no idea, just do)
b) log mgmt is an admin task.
c) admin task are in the admin guide
d) admin guide has chapter 7 "managing the online redo log"
e) i goto that chapter and the first page has list of topics
f) read list and see "forcing log switches"
</quote>

I got lost at B... I wouldn't know what to classify the task under! :-S

C... I didn't realise there even WAS an "admin guide"... I'll have to see if I
can find that... wonder what else is in it?

D, E, F - C kinda makes these moot.

<quote>
hmmm.  Perhaps you are just only using the sql ---->>> REFERENCE <<<---- (would
hope academics at a school would understand the use of a reference manual vs the
others -- well, actually, I would hope a lecturer at a university to which I'm
paying big bucks would have long ago -- i don't know -- sort of MASTERED the
topic they are lecturing on?)
</quote>

Well *I* was only reading the SQL Reference (and it wasn't easy to find -
there's SQL, there's SQL* Plus, there's PL/SQL [no idea what the difference is
or which one I need to read about])

/* Dear me - too many nested comments! :-S */

When at Uni, I didn't have access to *any* documentation at all. Just had to
take very good notes. ;-)

As for the lecturers... well yes, you would have thought your money would buy
you something. To be fair, they knew just about everything there is to know
about writing SQL queries and designing schemas. Just not about the exact
implementation of who Oracle does other stuff. (Concurrency, backups, etc.)
Wasn't really part of the course anyway...

...

Mmm... now that quote from the Admin Guide sounds a lot more helpful than
anything I managed to find before! I'll have to see if it's on my CD...

<quote>
You see -- I violently disagree that the documentation "needs to get better".
</quote>

I couldn't tell. ;-)

No, seriously... what good is having all these great explainations if I can't
find them? They may as well not exist if I don't know where they are. Fact is,
all the documentation I could find was terse or just confusing. [Yeah, I know -
you're going to tell me I'm looking in the wrong places.]

<quote>
Let's take that example again.  I want to find out how to switch logs.  Ok, I
goto

http://otn.oracle.com/pls/db92/db92.homepage
(which is the online docs, freely available to anyone on the planet).
</quote>

Well, it keeps asking me for a username and password. (Just like the discussion
forums did. When I tried to apply for said account, the website demanded all
sorts of details I'm not willing to give out... Presumably this is a similar
thing.)

But hey, the documentation is all on my CD - somewhere.

<quote>
In my last book -- I even set up a roadmap of required reading:
</quote>

Yeah - I saw a preview of Chapter 1 online somewhere. (Well, can't remember
exactly WHICH of your books, but one of them.) The whole chapter was in fact
rather good if I may say so.

<quote>
You wanted 64k rows.  Well, we can generate as many rows as we want by selecting
from some tables.
</quote>

Didn't know there was a table called ALL_OBJECTS... Given that there is, and
that it's big, this now makes more sense. (There's still that RowNum part though
- if I could figure out which manual to look in, it's probably there somewhere.
Another one of those Oracle mysteries - like that DUAL thing... [I'll save that
for another thread])

<quote>
/* */ is a comment

/*+ .... */ is a HINT
</quote>

OK... (I think!)

</quote>

http://d/
/*+  APPEND */ specifically is a direct path insert, used in bulk loads for high
speed loading.  You'll have to see:

http://d/
to fully appreciate it
</quote>

Also asks me for passwords... lol. But again, I suppose it's in the documents
somewhere.

Anyway, I've hopefully solved the present problem. I don't really want to get
into a big argument about how good Oracle's documentation is or isn't. I shall
go forth and make a second attempt at reading it, and see if I can find anything
useful. (Like why my test database throws strange and obscure errors at me,
etc.) If I have any more specific questions, I'll come back and ask.

Thanks for your time.

          Followup:

if you are not willing to make up fake information but supply a real email (at a
yahoo account) to get access to the best material around -- well, sorry -- not
much I can do.

I'm signed up at IBM and MS -- do you think they "really know me"?

once you do that, rownum will be revealed.

get an otn account, you aren't into Oracle until you have one. Make up stuff (i
use barney fife, bfife@mayberry.rfd, policeman -- alot myself)

February 17, 2004 -- 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!

      Rating: 3
Global
      Index  February 17, 2004
Reviewer:  alex
       from UK
Hello Tom

Can I askyou what is the diffeence between global and local index and when to
use them.

Which one is better to use.

Thanks

          Followup:

if you have access to my book "effective oracle by design", I cover that with
examples in there (as well as expert one on one Oracle)

the concepts guide does too.

In a nutshell

a local index is an index that is equi-partitioned with the table itself.  Say
the table is hash partitioned by the ID (unique key) column into 8 hash
partitions.  That means there will will 8 hash partitions for the local index
and all of the data in partition-1 of the INDEX will point only to data in
partition-1 of the table and so on (equi-partitioned, 1 to 1)

a global index is an index that is NOT equi-partitioned.  taking same example of
the 8 way table -- a globally partitioned index can have a totally different
number of partitions and the index entries in index partition-1 can point to ANY
of the table partitions.

February 17, 2004 Elie -- Thanks for the question regarding "Using
      Portal With Multiple Database Instances And Other Questions", version
9.0.2

      You Asked
Hi, Tom.

Thanks so much for a great site.  It keeps getting more and more informative.

Here is my situation.

I have Oracle Portal (9.0.2.2.22) and two Oracle instances (iasdb and
infdb), both at 9.0.1.4, all installed onto a single Win2000 server.  They are
part of a "Web Farm". This is strictly a development configuration. In fact, I
am very new to Portal, and so, am just feeling my way through, reading the docs,
looking at the Portal online forum, etc. I have created several Portal pages and
associated portlets, dynamic pages, etc. These are working fine against a set of
Oracle tables that reside in an Oracle 8.1.6 instance on a Linux server. My
portal pages access these tables via synonyms in which dblinks have been
embedded. As I said, all of this is working fine.  Here then are my questions.

1) All of the tables currently being accesed belong to one agency. However,
there are 4 other agencies that each want to use the same portlets, pages,
forms, reports, etc. that were created for the first agency.  Each of these
other 4 agencies hace EXACTLY the same table names, table structure, data
columns, etc. They are, in fact, clones of one another. All that is different
between them is, of course, the actual data and the users accessing this data
and, also, of course, these tables reside on different Oracle instances (all
8.1.6 and above). When I move my Portal to a production environment, how do I
configure Portal to handle all 5 agencies?  Do I need to install 9iAS and Portal
on 5 different (production ready) machines?  May I have 9iAS and Portal
installed on a single machine and still have this single install service
requests from all 5 agencies (via dblinks)? How do you recommend I go about
doing this?

2) When moving to production, may I export my current 9.0.2.2.22 Portal contents
to a newer release [say, 9.0.2.6 or, perhaps, 9.0.4 (10g - although I think
Portal 10g is not available yet for Windows)]? I read somewhere that, if I
create a transport set (via the Portal Administration interface), I CANNOT
simply import this transport set into a newer Portal release. Is this true?
Do I first need to upgrade my current 9.0.2.2.22 Portal to a newer release, and
then create a transport for eventual import into a production machine on which
the newer release has already been installed?

3) Finally, how does Portal handle application contexts?  Specifically, should I
be creating FGAC policies for the tables in each of my 5 agencies so as to
control which data specific users may query or perform DML against?  Or, does
Portal create such application context for me?  Can you direct me to any docs
about this?

Thanks in advance.

Elie

      and we said...
I asked Kim Better, our resident portal expert, to take a look at this and she
says:

I will take question #1 and #3 and answer at the same time. You have 5 agencies
that access data in databases with the exact same structure; Instead of having 5
different copies of this data in 5 different databases, you could put all of
this data in 1 schema, in 1 database, and take advantage of Fine Grained Access
Control (or Virtual Private Database as some call it), which uses Row Level
Security.

You seem to be versed in security features, but in a nutshell for others, the
Fine Grained Access Control is the ability to dynamically attach, at runtime, a
predicate (where clause) to any and all queries issued against a database table
or view.  This is the gives you the ability to procedurally modify the query at
runtime. You may evaluate who is running the query, where they are running the
query from, when they are running the query and develop a predicate given those
circumstances. With the use of Application Contexts, your may securely add
additional information to the environment (such as an application role the user
may have) and access this in your procedure or predicate as well.

As an example of Fine Grained Access Control, you might have a security policy
that determines what rows different groups of people may see. Your security
policy will develop a predicate based on who is logged in and what group they
are in. Fine Grained Access Control allows the query "select * from emp" when
submitted by different users to be evaluated as shown on

http://asktom.oracle.com/~tkyte/article2/index.html

So, you could also retrieve relevant variables from an application context.
Using Fine Grained Access Control, each user would log in as themselves in
Portal; The users wouldn’t access the database through a common user.

Portal can take advatnage of Application Contexts. Portal does not create the
Application Context for you. To use the feature, you would create the
Application Context, and a security policy. Look at the following doc:

http://asktom.oracle.com/~tkyte/article2/index.html

This article will show you in detail how to implement Fine Grained Access
Controls and secure application contexts in relation to Portal.

Portal Export/Import utilities will only work between portal installations that
are at the same revision level. For example, Portal Import/Export will copy
content successfully between two 9.0.2.x instances of Portal, but not between a
9.0.2 portal instance and a 9.0.4 portal instance. So no, you cannot create a
Transport Set in 9.0.2.2.x and import it into a newer revision  (9.0.2.6 or
9.0.4). Once you import objects into your Target Portal, you can then proceed to
upgrade the Portal on that Target instance to 9.0.2.6 or 9.0.4 (10g).

That said, Portal Export/Import WILL work between two portal instances that are
of different "minor" revision levels. For example, Portal Export/Import will
work between a 9.0.2.2.14 portal instance to a 9.0.2.2.22 portal instance, and
vice versa.

You are correct – Oracle Application Server 10g has not been released for
Windows yet. Sorry – we always release on Solaris first (and now Linux). I
suspect the Windows release will be in the next month or two.

Here are two documents you might find useful on the subject of Import/Export:

        * Exporting and Importing Portal Content

http://portalcenter.oracle.com/pls/ops/docs/FOLDER/COMMUNITY/DOCTEAM/DOCINSTCONF/9026_CG_IMEX.PDF
        * FAQ for Portal 9.0.2 Import/Export

http://portalstudio.oracle.com/pls/ops/docs/FOLDER/COMMUNITY/OTN_CONTENT/MAINPAGE/DEPLOY_PERFORM/EXPORT_IMPORT_FAQ_1121.HTM

Follow the path below to get to the Upgrade information on PortalCenter:

    *
      Go to PortalCenter.Oracle.com
    *
      Click on Product Information in the left side Navigation Bar
    *
      Click on the Upgrade Link
    *
      Scroll down to Upgrade Targets
    *
      For more information on your upgrade follow the links below to the target
version you are upgrading to.

Review &
      Followup

      Rating: 4
Very nice to
      know new features  February 17, 2004
Reviewer:
      Karthik  from Charlotte,USA

      Rating: 5
Using Portal
      With Multiple Databases And Other Questions  February 17,
      2004
Reviewer:  Elie Grunhaus  from Rochester, NY USA

Hi, Kim and Tom.

Thanks so much for your quick response.

Kim, in your response you said:

"You have 5 agencies that access data in databases with the exact same
structure; Instead of having 5 different copies of this data in 5 different
databases, you could put all of
this data in 1 schema, in 1 database, and take advantage of Fine Grained Access
Control (or Virtual Private Database as some call it), which uses Row Level
Security."

If I understand you, I should clone all of the portlets, dynamic pages, URLs,
Forms, and Reports I've created for my one agency (which are all assigned to a
single Portal Data Provider) and somehow (and I'm not sure HOW!) copy these to
four other Portal data providers, each of which would access the same named
tables located on the associated four remote database instances (via dblinks
embedded within synonyms).  Is this correct?  If this is correct, then the
synonyms I would be using would necessarily be different from one agency to
another.  In other words, I cannot create within the same schema two synonyms
called MYTABLE.  I would have to create two synonyms MYTABLE1 (for agency 1) and
MYTABLE2 (for agency 2).  This then means that I would have to go into all of my
cloned portlets, dynamic pages, Forms, etc and manually change the synonym
names.  Am I understanding you correctly?  Wouldn't I have quite a maintenance
problem on my hands every time a page needs to be changed?  In line with this,
is there someway to automatically copy all of the components (portlets, dynamic
pages, forms, etc.) from one data provider to another, perhaps, via the PDK?

In effect, what I am trying to achieve is this.  Please imagine a portlet
homepage on which users with the proper authorization would see 5 tabs.
Clicking on one of these tabs would allow the user access to that agency's data
via my created portlets, forms, reports, etc.  On the other hand, users at any
one of these agencies, when logging into the Portal, would be presented with
only their own agency tab and would not be allowed to access data from any other
agency.  In a nutshell, this is the final product that I am trying to achieve.
By the way, these agencies will be accessing the Portal via mobile (wireless)
lap tops.  Kim, am I interpreting your response correctly or not?  I would
really appreciate your thoughts.  Thanks so much in advance.

Elie

          Followup:

actually, she is saying "having 5 databases is a bad idea" and I concurr.

I'll ask her if there is a way to have a portlet query a different data object
depending on who is logged in (but my gut says "probably not" -- we'd have to
write a stored procedure to execute dynamic sql i think, or query a view that is
actualy built on a stored procedure so we can return the "right stuff"

      Rating: 5
Using Portal
      With Multiple Databases And Other Questions  February 17,
      2004
Reviewer:  Elie Grunhaus  from Rochester, NY USA

Hi Tom.

Thanks for your followup thoughts.  I understand that Kim felt that using 5
databases is something I could (and should) avoid doing.  And I'm glad she feels
this way, too.  It makes my development efforts easier.  It's just that I wanted
to check my understanding, namely, that to implement the type of Portal
application that I outlined at the end of my previous response, Kim recommends
that I create 5 sets of private synonyms, with each set pointing via dblinks to
the tables residing in an agency's remote database.  And that I would, in fact,
need to clone (I suppose using Portal's export/import interface) all of my
existing Portal pages (with their associated components and data providers) and
then assign each of these cloned sets of pages to 5 different Page Groups, one
for each agency.  If these thoughts are correct, then I wanted to hear Kim speak
to the maintenance issues surrounding this scenario.  Perhaps, Kim knows of a
way to (somehow) have changes to a given page in one agency's Page Group be
"inherited" by the same named page in the other agency Page Groups.  Perhaps
this sort of change maintenance is not really possible.  Actually, though, your
suggestion about using dynamic sql to automatically access tables on the desired
agency database is an intriguing idea.  Perhaps I could write a portlet that
acts something like an "agency broker", so to speak, that will query a table
residing on the Portal instance that has been populated with all users from all
of the 5 agencies, specifically each user's agency dblink.  When a user logs
onto the Portal (via SSO), perhaps I could set up an application context (using
FGAC's dbms_rls package) in which this user's associated dblink retrieved from
this table would be set up as this user's context.  Then my "agency broker"
portlet could retrieve this user's dblink from this application context, and
then pass this dblink to each and every Portal page component (forms, reports,
dynamic pages, etc.).  My components, in turn, would use dynamic sql to access
the correct agency data via this passed-in dblink.  I know this is all
hypothetical and probably far fetched.  Still ... I would be most interested in
Kim's (and yours) thoughts.  Thank you, again, in advance.

Elie

February 18, 2004 Amit -- Thanks for the question regarding "Need
      to load a file that is written in matrix format", version 9.2.0.3

      You Asked
Hi Tom,
 I require to load a semi colon delimited file that looks like this:

 F100;F101;F102;F103;F103b
 GB09;0000712061;0;2;NULL
 GB09;11003930;0;3;NULL

 Now I am trying to load it into a table that will hold values in this order:

 F100 GB09
 F101 0000712061
 F102 0
 F103 2
 F103b NULL
 F100 GB09
 F101 11003930
 F102 0
 F103 3
 F103b NULL

I have not been able to understand, how to load this data using SQL Loader or
Utl_file package. I am unable to read the file character by character where I
can make use of the delimiter.
 Can you help me on this ?

 Thanks in Advance
  Amit

      and we said...
you will not be able to directly -- but, we can easily write a control file that
can load this sort of data.  What you would need to do is write a control file
based on the header record -- then it is easy.

control file:

LOAD DATA
infile data.dat
replace
INTO TABLE T FIELDS TERMINATED BY ";" ( a  constant 'F100', b  )
INTO TABLE T FIELDS TERMINATED BY ";" ( a  constant 'F101', b  )
INTO TABLE T FIELDS TERMINATED BY ";" ( a  constant 'F102', b  )
INTO TABLE T FIELDS TERMINATED BY ";" ( a  constant 'F103', b  )
INTO TABLE T FIELDS TERMINATED BY ";" ( a  constant 'F103b', b  )

command line:

$ sqlldr / t skip=1

SQL*Loader: Release 9.2.0.4.0 - Production on Mon Feb 16 14:53:37 2004

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Commit point reached - logical record count 2

and resulting data:

ops$tkyte@ORA920PC> select * from t;

A          B
---------- ----------
F100       GB09
F100       GB09
F101       0000712061
F101       11003930
F102       0
F102       0
F103       2
F103       3
F103b      NULL
F103b      NULL

10 rows selected.

At worst -- you'd have to write a script that got the first line of the .dat
file, to generate the ctl file you need to load with.
Review &
      Followup

      Rating: 3
Thanks Tom. But
      How can I load the data?   February 17, 2004

Reviewer:  Nazmul Hasan  from B'lore, India.

In a .dat file, generated from a punch machine, the data are like the fillowing
format-
  "005:919043:0402123:134403:ALLEN:11"
  "005:9085:0402123:134408:M.SCOTT:12"
  "003:999:0402124:045258:Tom Kyte:12"
  "003:77:0402124:045351:Blank !!:14"
  "003:9:0402124:045455:Blank !!:12"
  ...
Each field is separeted by ':' and each record is enclosed with double quote( "
" ).
I have to load the data is following table -
 Time_office(mach_no,card_no,w_date,w_time,w_name,w_shift)
 Q1: How I can I load data enclosed by double quote (" ")?
 Every day the machine generate a .dat file in a default location. The file name
is like 'PDNL0212'. The first 4 char 'PDNL' is fixed, the nest 4 char '0213' for
date(MMDD). Every day I have to find the latest .dat file to load the data.
 Q2: Is it possible, find the letest file (max date )programmatically and load
the data by SQLLoader or PL/SQL?
I use 8.1.7.
 Regards..
- Naz

          Followup:

q1)

LOAD DATA
INTO TABLE t
REPLACE
FIELDS TERMINATED BY ':'
(
mach_no  "substr(:mach_no,2)",
card_no,
w_date,
w_time,
w_name,
w_shift "rtrim(:w_shift,chr(34))"
)

will load the data up.

q2) your host operating system, which is what you'll be using to run SQLLDR,
should be able to find the latest filename easily.  eg: on unix, I would run
simply:

$ sqlldr / t data=`ls -tr PDNL*.dat | tail -1`

SQL*Loader: Release 9.2.0.4.0 - Production on Tue Feb 17 08:15:59 2004

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Commit point reached - logical record count 5

but that'll be something you have to figure out on whatever OS you have.

      Rating: 3
Solution to
      Loading data file containing fields in matrix
      format  February 17, 2004
Reviewer:  Amit
      Saha  from United Kingdom
Thanks Tom.
 It provided me with a new perspective to using the SQL Loader.

      Rating:
      5
  February 17, 2004
Reviewer:
      A reader

      Rating: 4
How to convert
      matrix into recoreds  February 17, 2004
Reviewer:
      Ketan Popat  from INDIA
Hi Tom,

Can you help me in loading matrix like

  1/4  0.23 - 0.29 ct.  $100/CT.       1/9/04
 P/CT  VVS   VS  SI1  SI2  SI3   I1   I2   I3
  D-F 23.0 17.0 14.0 11.5 10.0  8.1  6.7  4.1
  G-H 18.0 14.0 12.5 10.7  9.5  7.8  6.5  3.7
  I-J 13.0 11.5 10.5  9.7  8.7  7.0  5.5  3.3
  K-L 10.1  9.1  8.6  8.1  7.6  6.6  4.9  2.9
  M-N  8.1  7.6  7.1  6.1  5.6  4.9  4.0  2.6

the resulting record should be like this:
--
('0.23','0.29','D','VVS',23.0)
('0.23','0.29','E','VVS',23.0)
('0.23','0.29','F','VVS',23.0)
--
('0.23','0.29','D','VS',17.0)
('0.23','0.29','E','VS',17.0)
('0.23','0.29','F','VS',17.0)

Thanks in adv.

          Followup:

not really, looks like "code needed" to me.

February 18, 2004 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....

      Rating: 5
how to reset an
      execution plan   January 31, 2004
Reviewer:
      A reader
Hi

Recently we had some problem with our database, basically it was because one
query used a very bad plan because of bind peeking, I suspect when it was first
hard parsed it deduced an adequate plan for that moment but since it's cached
afterwards all the queries afterwards were using that plan (a partition full
scan). Normally this query should do index range scan. FYI we have
cursor_sharing = FORCE

I realized that something had to be done so I flushed the shared pool and
performance came back as good as before.

My question is, is there a way to flush a query execution plan instead of
flushing the whole shared pool?

          Followup:

in your case - sounds like you don't want binds on this query -- sounds like you
want a plan per query here.

but, you've got a system where the developers bound NOTHING, so you are using
cursor_sharing=force as a crutch.

sigh.

perhaps you want to alter your session and set cursor sharing=exact before doing
this particular query -- or, if you can -- use the cursor sharing hint to go
exact for this query.

      Rating: 4
Ur
      Admirer  February 18, 2004
Reviewer:  Sagi

In  reply you said

INSERT /* STATIC SQL */ into t values ( :b1 )

Parses once and executes 100000

Whereas when using EXECUTE IMMEDIATE

insert /* DYNAMIC SQL */ into t values ( :i )

Its Parsed 100000 and Exuted 100000  times. But i am not able to understand why
so many soft parses too.

If you see the statement both look the same except the text /* changes here */
and both are using bind variable.

I would appreciate your explanation.

Regards,
Sagi

          Followup:

if you use

alter session set sql_trace=true;

begin
   for i in 1 .. 10000
   loop
      insert /* static */ into t values ( i );
      execute immediate 'insert /* dynamic */ into t values ( :x )' using i;
   end loop;
end;
/

you will see what I mean.  static sql in plsql is "cached for you as part of the
language".

dynamic sql -- cannot be.  if you use NDS (native dynamic sql) as above -- it
will

o open
o parse
o bind
o execute
o close

each time.  with dbms_sql, you can move the open/parse to outside the loop,
bind/execute inside the loop and close after...

      Rating:
      4
  February 19, 2004
Reviewer:
      A reader
Thanx.

Ur awesome.

Regards,

February 18, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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.

      Rating: 5
clarifications
      ....  February 18, 2004
Reviewer:  reader

Tom,

(1) Is switchover concept new in 9i dataguard?
(2) Creating standby redologs new feature in 9i? If so how was it in 8i standby
configuration?

Thanks.

          Followup:

1) yes
2) yes

in 8i, there was just the archive log transfer (fill a log on prod, ship to
standby, apply)

February 18, 2004 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

      Rating:
      5
  February 18, 2004
Reviewer:
      A reader
Hi All

 Even if i use DD-MON-YYYY the date is stored as '12-JAN-0004'

but if i use DD-MON-RRRR it stores properly '12-JAN-2004'

Why is this behavior in YYYY

By the way what would you advice us to use (YYYY or RRRR)

Regards

          Followup:

if you use dd-mon-yyyy as an INPUT FORMAT and you get 12-jan-0004 -- that means
you are giving the database 12-jan-04, that is the year "4".  you would have to
give the database 12-jan-2004.

my point is - you and your end users cannot give anything "4" and expect "the
right answer".  the year is 2004, not 4.

February 18, 2004 James -- Thanks for the question regarding
      "REBUILDING INDEXES: clustering_factor", version 8.0.3.0.0

      You Asked
After computing statistics on an index, the "clustering_factor"
is available when querying DBA_INDEXES.  A book I have
states: "If the clustering factor is greater than the number of
blocks in the index, then the index is out of balance due to a
large volume of insert and delete operations."  (High
Performance Oracle8 Tuning, Burleson, p. 316, if you have it.)

First, can you give a better explanation of what the
clustering_factor is?  Then, which blocks do you think they
mean: index leaf blocks or index physical blocks?

I have another (same subject).  The book goes on to say "If the
clustering factor is more than 50 percent of the number of rows
in the table that it is indexing, you should consider
(rebuilding) the index."  Here, are they talking about num_rows
from DBA_INDEXES or the actual (counted) number of rows in the
table?

One more.  It says "If the value for the clustering factor
approaches the number of blocks in the base table, then the
index is said to be clustered.  If the clustering factor is
greater than the number of blocks in the base table and
approaches the number of rows in the base table, then the index
is unclustered."  OK: so which one is good and when do we
consider rebuilding?

Thanks for helping.

      and we said...
From the Reference manual:

<quote>
CLUSTERING_FACTOR:

Indicates the amount of order of the rows in the table based
on the values of the index.

o 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.

o 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>

I do not believe the clustering factor they talk of is the
clustering_factor in the dba-indexes but rather some metric they
have made up.  Not having the book referred to -- I cannot
confirm this.

If the clustering factor they are talking of is the
clustering_factor from dba_indexes, then I do not see how they
can come to the conclusion that the "index is out of balance"
from that.  It is a factor that looks at the index entries on
blocks and looks at the rowids they point to.  It is telling us
how close together the data in the table these index entries
point to are (how sorted the table is with respect to this
index).

Let's say you have a really small block size and only 3 index
entries fit on a block.  On a given block -- you have the values
1, 2, and 3.  The value 1 points to a block in the first extent
of the TABLE.  The value 2 points to a block in the second
extent of the TABLE.  The value 3 points to a block in the third
extent of the table.  The clustering factor, if all other blocks
are like this block, would be near the number of rows in the
table -- since none of the index entries on this block point to
rows that are physically close to eachother in the table.

If they go on to say -- rebuild the index based on this value --
something is wrong in their thinking if you ask me.  No matter
how many times I rebuild that index -- the values 1, 2 and 3 are
always going to end up in the same index block and they will
still point to blocks that are very far away from eachother.
The clustering factor will not be materially affected by an
index rebuild (it might get marginally smaller as the index is
compacted -- it might get worse as well since more index entries
might fit into a block after the rebuild).  The
clustering_factor is a measure of how physically ordered the
table is with respect to this index!  Not how out of balance an
index is.

Here is an example.  I have a table EMP with 21,000+ rows in it.
 I built this table via:

create table emp
as
select object_id empno, owner||'.'||object_name ename,
       object_type city, mod(object_id,100) deptno,
       status division
  from all_objects
/

create unique index emp1 on emp(empno);
create index emp2 on emp(ename);
create index emp3 on emp(deptno);

Now, lets look at the indexes:

ops$tkyte@DEV816> analyze index emp1 compute statistics;
ops$tkyte@DEV816> analyze index emp2 compute statistics;
ops$tkyte@DEV816> analyze index emp3 compute statistics;

ops$tkyte@DEV816> select index_name, clustering_factor, num_rows

  2  from user_indexes where index_name like 'EMP_';

Index
Name                           CLUSTERING_FACTOR   NUM_ROWS
------------------------------ ----------------- ----------
EMP1                                       19632      21442
EMP2                                         186      21442
EMP3                                       12000      21442

ops$tkyte@DEV816> analyze table emp compute statistics;
ops$tkyte@DEV816> select blocks from user_tables where
                                      table_name = 'EMP';

    BLOCKS
----------
       171

No matter what index I rebuild, or how many ways I rebuild it,
the results of the above will be the same.  What I can "glean"
from the information above is that:

o the data in the TABLE emp appears to be sorted physically by
the columns in the index EMP2 since the clustering factor of
EMP2 is very near the number of blocks.  EMP2 is the index on
OWNER||'.'||OBJECT_NAME from ALL_OBJECTS.  This makes sense if
you run the all_objects view, data tends to come out sorted by
owner, object_name due to the way the view is processed.

o the data in the TABLE emp is very randomly organized with
respect to EMP1 -- the index on empno.  The object_ids are
coming out of that table very randomly.  So, the TABLE data is
not sorted by empno (object_id).

o the data in the TABLE emp is sort of organized physically in
order by DEPTNO.  Its sort of in the middle.

ops$tkyte@DEV816> alter index emp2 rebuild;
ops$tkyte@DEV816> analyze index emp2 compute statistics;
ops$tkyte@DEV816> select index_name, clustering_factor, num_rows
from
  2  user_indexes where index_name like 'EMP_';

Index
Name                           CLUSTERING_FACTOR   NUM_ROWS
------------------------------ ----------------- ----------
EMP1                                       19632      21442
EMP2                                         186      21442
EMP3                                       12000      21442
ops$tkyte@DEV816>

The numbers will always come out the same.

The clustering_factor can only be used to guess if using the
index to read the table lots will cause the table to be read
almost in order or not.  For example:

select * from emp order by ename;

if I use the index emp2 to process that -- the data in EMP
pointed to index entries in the first index leaf block I process
will mostly be on the same block(s).  I will do a physical read
of the table and then many logical IO's of that block before
moving onto the next block.  It will be fairly efficient to
range scan large ranges with TABLE ACCESS BY ROWIDS using emp2.

On the other hand, if I have:

select * from emp order by object_id;

The index EMP1 is not very good for reading that table.  The
first entry in the index will point to block 1, the second to
block 100, the third to block 500 and so on -- to process the N
rows on the first index leaf block, I'll have to do N physical
IO's to get the blocks from disk probably.  Not only that but
they will probably get flushed from the cache since I won't need
them again right away and I'll have to re-read them once again
later.  It will be horribly inefficient to range scan LARGE
ranges with TABLE ACCESS BY ROWIDS using EMP1.

I myself do not really believe in the rule of "rebuild your
indexes".  I like the alter index rebuild command to MOVE
indexes, but I do not in practice rebulid indexes.
Review &
      Followup

      Rating: 4
Is fair to say
      the smaller of clustering factor is the better
      index??  August 31, 2001
Reviewer:  Mike
       from GA, USA

          Followup:

Well, assuming that you have more rows than blocks (eg: rowsize < blocksize) --
MAYBE.  If you have less rows then blocks (rowsize > blocksize) -- not so.

This factor relates to how efficient this index is for "large range scans".  If
the number of rows to be returned via an index range is small -- the cluster
factor does not come into play.  If the number of rows to be returned via an
index range scan is deemed LARGE -- the cluster factor will come into play.  The
optimizer might start choosing a full scan over an index range scan for an index
that does not have a good cluster factor (due to the fact that the resulting
random IO to many single blocks here and there would be slower then just reading
the table in the first place).

So, the cluster factor is used to determine  whether an index is a good
candidate for LARGE range scans.  For normal index usage (get me my small set of
rows) it doesn't come into play as much.

      Rating: 5
Which view can
      show the rowsize?  September 01, 2001
Reviewer:
      Mike  from GA, USA

      Rating: 4
rowsize is
      AVG_ROW_LEN in DBA_TABLES?  September 01, 2001

Reviewer:  Mike  from GA, USA

          Followup:

Thats the average row length in bytes, yes.  Only valid after an ANALYZE.

      Rating: 4
Very
      interesting  October 20, 2001
Reviewer:  Doug
      Cowles  from CT, USA
I've alwasy wondered how indexes can be rebuilt and get larger clustering
factors.

      Rating: 4
REBUILDING
      INDEXES: clustering_factor  February 06, 2002

Reviewer:  Christophe Tsobgny  from France

Hello

As it is almost impossible to ask tom a question being
in Europe and having a different time zone,
i did a search on indexes and improving query
response time.
The article was very useful and helped confirm
my understanding of the clustering factor.
But will it really improve query response time
if one has a good clustering factor ?
i have one query that has a good execution plan
but the response time is always bad.

kind regards

          Followup:

Well, its not a factor of being in Europe or India or anywhere really (i've run
the stats -- I've a pretty even distribution of questions from everywhere).  Its
more related to the fact that about 4,000 distinct people come here everyday and
on a good day I'll take as many as 40 questions....

If you are interested in examples showing the differences with the clustering
factor -- i have some in my book.  However -- i would start more simply with
tkprof and sql_trace and see what the query is doing, see what the waits (if any
are).  Chapter 10 of my book would take you through that in detail,

http://asktom.oracle.com/~tkyte/tkprof.html
would get you started as well

      Rating: 4
Mark of a
      genius  February 06, 2002
Reviewer:  Pawan
       from USA
It was very confusing for me to understand the clustering factor but Tom made it
so simple.

Thanks

      Rating: 4
Better for data
      warehousing than OLTP  February 06, 2002
Reviewer:
      Dave
Here's my two-cents.

I always think of reducing the clustering factor by physical row ordering as a
quick-and-dirty partitioning feature, or at least a complement to partitioning.
Here's a practical example ...

Say you are loading up a large fact table in a data warehouse with 10million
rows per daily load. It would be common practice to use rolling partitions to
provide one partition per day, with local indexing. If users commonly access the
data according to a particular column -- let's say it is by zip code -- then
physically ordering your daily data load by zip code gives a low clustering
factor on the zip_code index and, as Tom says,  encourages large index range
scans. It would probably be impractical to partition by day and then by zip
code, but you get some of the same perfomance enhancement as you might do
through partition elimination. A major benefit is that you can go further and
choose, for example, four attributes that users commonly access the data by, and
physically order the rows by each (ie. insert into ... select ... order by
attribute_1, attribute_2, attribute_3, attribute_4;).

I've reduced the number of blocks read by common queries by a factor of 25 by
doing this, and reduced device I/O measured at the O/S level by a factor of 8.
Not to be sniffed at.

Of course you get into a case of diminishing returns, but it's more true in DW
than in OLTP that you insert once, select many times, and an extra bit of work
in data load can go a long way towards getting response time down.

      Rating: 5
this looks the
      other way round  February 18, 2004
Reviewer:
      A reader
Hi I have a query in two servers, one fast and one slow. The funny thing is both
have same configuration (same init.ora settings). In fact the slow database is a
copy of the fast one made last week! The only difference I see is clustering
factor in the indexes.

SLOW DATABASE:

OWNER        TABLE_NAME        INDEX_NAME         CLUSTERING_FACTOR   NUM_ROWS
------------ ----------------- ------------------ ----------------- ----------
SYSADM       PSWORKLIST        PS0PSWORKLIST                   2358      75593
SYSADM       PSWORKLIST        PSAPSWORKLIST                   2218      75593
SYSADM       PSWORKLIST        PSBPSWORKLIST                  17453      75593
SYSADM       PSWORKLIST        PS_PSWORKLIST                   2218      75600

FAST DATABASE
OWNER        TABLE_NAME                     INDEX_NAME
CLUSTERING_FACTOR   NUM_ROWS
------------ ------------------------------ ------------------------------
----------------- ----------
SYSADM       PSWORKLIST                     PS0PSWORKLIST
       2358      75593
SYSADM       PSWORKLIST                     PSAPSWORKLIST
       2218      75593
SYSADM       PSWORKLIST                     PSBPSWORKLIST
      17453      75593
SYSADM       PSWORKLIST                     PS_PSWORKLIST
       2218      75600

The thing is PSBPSWORKLIST in the slow database shows a low clustering factor so
this index should be used and otherway round in the fast database. But in
reality Oracle  full scans PSWORKLIST in the slow database and index range scan
in the fast database!!! Why so?!

          Followup:

umm, query - autotrace explain only output and tkprofs? (and settings of
optimizer_*)

but it does sound "normal", cluster factor near number of blocks = range scan
'good looking'.  cluster factor near number of rows = range scan 'not
appealing', bulky operations (full scans, fast full scans, etc) more appealing.

February 18, 2004 Gabe -- Thanks for the question regarding
      "Transactions per second", version 8.0.5.1.0

      You Asked
Hi Tom,

Is there a quick way to calculate the number of tranactions per
second that my database has processed since it was started?

Thanks,

Gabe Zauss

      and we said...
No, not really -- and besides, I'm sure it has sat idle at some
point, that would drag down the averages.

You can see how many commits have happened and divide -- but
that is most certainly not "TPS" in any meaningful way.

select value from v$sysstat where name = 'user commits';
Review &
      Followup

      Rating: 4
Transactions
      Per Second  February 17, 2004
Reviewer:
      Mark  from AZ
Hi Tom,

Searching Metalink found this document 502252.995.

"To calculate response time I gather from v$system_event so called "non-idle"
wait events and also "CPU used when call started" from v$sysstat. This is the
base for calculation of response time using the following formula: response_time
= CPU_time + wait_time.
In next step I divide the response time by number of executions ("execute
count") or number of transactions ("user rollbacks + user commits"). All this
values I display on one graph where the response time is displayed with both
components."

Could the prescribed approch from above be used as a rough estimate to calculate
transactions per second? The reason we are trying to find out the number of TPS
is to calculate the size of the undo tablespace.

Thanks,

Mark

          Followup:

statspack gives you TPS right at the top of the page.  transactions per second.

      Rating:
      3
  February 18, 2004
Reviewer:
      A reader
Why don't use v$undostat to estimate the undo size directly?

          Followup:

yup, in 9i there is even an undo advisor you can use.

February 18, 2004 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.

      Rating:
      3
Nice  February 18, 2004
Reviewer:
      Carl  from TX,USA
Dear sir,
Is there any alternate way to select duplicate rows from a
table other than the following
 sql>select empno,ename from emp group by empno,ename
 having count(*) > 1;
Do you have any other trick?
Please do reply.
Bye!

          Followup:

lots of them.

select *
  from (select a.*, count(*) over (partition by <keys you want unique>) cnt
          from some_table a
       )
where cnt > 1;

is another way for example.  it gets you the entire row -- not just the key.

February 18, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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

      Rating: 2
Re:DDL
      Trigger  February 18, 2004
Reviewer:  Joe
      Bayer  from USA, PA
I am using 9.2.0.4.  I found that in most cases, sql like "Alter table blabla
drop parttion...." will not show up in v$open_curson, so I rewrite the trigger a
little bit.

create or replace trigger ddl_monitor_trigger
    after create or alter or drop on schema
    declare
        l_sysevent varchar2(25);
        l_status   varchar2(25);
        l_obj_owner varchar2(25);
        l_obj_name  varchar2(25);
        l_obj_type  varchar2(25);
        l_text varchar(1000);
      l_sid varchar(10);
    begin
       select sql_text into l_text from v$session vs,v$sql vsql
       where vs.audsid = (select SYS_CONTEXT('USERENV','SESSIONID') from dual)
       and vs.SQL_ADDRESS  = vsql.address
       and vs.SQL_HASH_VALUE = vsql.hash_value;

        select ora_sysevent into l_sysevent from dual;
        select ora_dict_obj_owner into l_obj_owner from dual;
        select ora_dict_obj_name into l_obj_name from dual;
        select ora_dict_obj_type  into l_obj_type from dual;
        select status into l_status from user_objects where object_name =
'MY_PKG'
      and object_type='PACKAGE' ;
        if (l_status = 'INVALID' )
        then
                    insert into system.my_table (insert_time,command,obj_owner,
 obj_name,obj_type,sql_text)
                    values (sysdate,ora_sysevent,
            l_obj_owner,l_obj_name,l_obj_type,l_text);
       end if;
   end;

Everything works fine except the sql_text, which did not catch the "alter table
drop partition",  which is the quilty sql during my test, it catch the sql from
this trigger.  Which is,
"select sql_text into l_text from v$session vs,v$sql vsql
       where vs.audsid = (select SYS_CONTEXT('USERENV','SESSIONID') from dual)
       and vs.SQL_ADDRESS  = vsql.address
       and vs.SQL_HASH_VALUE = vsql.hash_value;"

Could you give more help?

Thanks

          Followup:

why don't you use the builtin event function that gives you the sql text that
fired the trigger?

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96590/adg14evt.htm#1005274
since you have 9ir2?

February 18, 2004 Carine -- Thanks for the question regarding
      "remove user who have not logged in for 3 months..", version

      You Asked
I work on Oracle 7.3.4 and want to clean up users who
haven't logged on for more than 3 months. How to...?
Thanks very much in advance.

      and we said...
Well, we will have to wait 3 months to drop the first user
however -- the process you follow is:

o enable AUDITING in your database.  You do this by setting
"audit_trail=true" in your init.ora and restarting

o directing the system to audit connections, it might look like
this:

SVRMGR> audit session by access;
Statement processed.

Then, to monitor logins, you can:

SVRMGR> select username, to_char( timestamp, 'dd-mon-yyyy
hh24:mi:ss' ), priv_used from dba_audit_trail;

USERNAME        TO_CHAR(TIMESTAMP,'DD-MON-YYYYHH24:MI:SS')
PRIV_USED
--------------- --------------------------------------------
--------------------
SCOTT           16-feb-2000 08:30:09
CREATE SESSION
1 row selected.

beware -- watch the size of your sys.aud$ table!!! you might
have to copy it to another place and delete from it periodically
to avoid having your system tablespace grow and grow over time.
SYS is not audited (so if system fills up and no one can log in
due to system being full and no room for audit entries, SYS can
still log in and clean up).

Review &
      Followup

      Rating: 4
Can I
      selectively delete from sys.aud$ table ???  February 17,
      2004
Reviewer:  Alan Rosenthal  from San Diego,
      California
Hi,

I want to do the samething.  However, my aud$ table is growing very fast and I
only care about who logs in - not how many times they log in.  Therefore, I
would like to purge this table.

Is it safe, sane and kosher to delete all records for each userid except for the
one with the max sessionid (I just want to keep one record for each user and max
sessionid seemed like a good enough way to pick the survivors):

     -- create a list of what I want to keep.
     create table aud_list as
          select userid, max( sessionid ) sessionid
          from sys.aud$
          group by userid ;

     -- Delete everything not in the list.
     delete from sys.aud$
     where sessionid not in
           select sessionid
             from aud_index ;

     -- get rid of list.
     drop table aud_list ;

Thanks,

Alan

          Followup:

what version do you have?

      Rating:
      4
Version  February 18, 2004
Reviewer:
      alan rosenthal  from San Diego, CA
Hi,

Sorry I forgot to include the versions.  One DB is 9.2.0.4 and the other one is
8.1.7.3

          Followup:

use a logon trigger instead.  just update a row by user name and if sql%rowcount
= 0, insert it.

(eg: don't use auditing in this particular case)

      Rating: 4
Another similar
      article  February 18, 2004
Reviewer:  eric
      givler  from harrisburg, pa
That's exactly what we ended up with, as alluded to in:

http://asktom.oracle.com/pls/ask/f?p=4950:61:3076181060304310743::::P61_ID:1830072957439

February 18, 2004 KK -- Thanks for the question regarding "HTTP
      request", version 8.1.6

      You Asked
Can I post a XML document to a web server via the
http request from Oracle 8i?

      and we said...
The existing UTL_HTTP package in the database does not support
POST, only GET.  So, if you are using UTL_HTTP, no, you cannot
POST the xml, you can only put it in the query string (eg:
utl_http.request( 'http;//server/url?xml_doc=<tag>xml</tag>' )).

Java in the database (if you use a java stored procedure) can do
this easily with the http classes in java.

8.1.6 supports a package UTL_TCP which allows PLSQL to do
"sockets" as well.  You could open a connect to a webserver on
port 80, write out the POST request:

POST /url/.../.../...  HTTP/1.0
Content-Length: xxxx

<tag>xml document of xxxx bytes goes here</tag>

as well.
Review
      & Followup

      Rating:
      4
  October 31, 2002
Reviewer:  Ramon de
      Diego

      Rating:
      5
  February 18, 2004
Reviewer:
      A reader
Hi,
 How can I do http post in oracle 9i??

Thanks

          Followup:

read the supplied packages guide (available on otn).  it'll tell you how the api
works.

February 18, 2004 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

      Rating: 4
I think I know
      what but I sure don't know why  February 12, 2004

Reviewer:  Dave  from Richmond, VA USA
I don't think this is a "mutating table" issue but I'm not sure how else to
address it. I've boiled it down to the following test case but can give you more
details if you need them. We are running 8.1.7. I start by creating the
following table and trigger.

create table t (text_col varchar2(10) not null);

CREATE OR REPLACE TRIGGER ti
BEFORE INSERT
ON t
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
BEGIN
  :new.text_col := substr(:new.text_col, 2);
END;

Then I execute each of the following inserts.

insert into t select 'XXX' from all_tables where rownum <= 5;

insert into t select rownum||rownum from all_tables where rownum <=5;

The first one fails and the second one does not. I can make the first one not
fail by increasing the number of X's or decreasing the 5 to 2. It seems that in
the trigger, if I send 'XXX' it eats them up until there are none and then
fusses but if I send rownum||rownum, it picks off the first and leaves the rest.
What am I missing?

          Followup:

cannot reproduce, what are we looking for here?

ops$tkyte@ORA817DEV> create table t (text_col varchar2(10) not null);

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> CREATE OR REPLACE TRIGGER ti
  2  BEFORE INSERT
  3  ON t
  4  REFERENCING NEW AS NEW OLD AS OLD
  5  FOR EACH ROW
  6  BEGIN
  7    :new.text_col := substr(:new.text_col, 2);
  8  END;
  9  /

Trigger created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert into t select 'XXX' from all_tables where rownum <=
5;

5 rows created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert into t select rownum||rownum from all_tables where
rownum <=5;

5 rows created.

ops$tkyte@ORA817DEV> select * from t;

TEXT_COL
----------
XX
XX
XX
XX
XX
1
2
3
4
5

10 rows selected.

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

      Rating:
      3
Hmmmmm  February 13, 2004
Reviewer:
      Dave  from Richmond, VA USA
Odd. Here is what I get from the inserts (sorry I didn't send this earlier) and
why I'm confused. I expected the same results you got.

SQL>  select * from v$version;

BANNER
----------------------------------------------------------------
Oracle8i Enterprise Edition Release 8.1.7.4.0 - 64bit Production
PL/SQL Release 8.1.7.4.0 - Production
CORE    8.1.7.0.0       Production
TNS for HPUX: Version 8.1.7.4.0 - Production
NLSRTL Version 3.4.1.0.0 - Production

SQL> insert into t select rownum||rownum from all_tables where rownum <=5;

5 rows created.

SQL> select * from t;

TEXT_COL
------------------------------
2
3
4
5
1

SQL> insert into t select 'XXX' from all_tables where rownum <= 5;
insert into t select 'XXX' from all_tables where rownum <= 5
            *
ERROR at line 1:
ORA-01400: cannot insert NULL into ("DHANZLIK"."T"."TEXT_COL")

SQL>  insert into t select 'XXX' from all_tables where rownum <= 2;

2 rows created.

SQL> select * from t;

TEXT_COL
------------------------------
2
3
4
5
XX
X
1

7 rows selected.

SQL>

          Followup:

show the entire script, like I did.

from create table, to create trigger et. al.

otherwise, I'll assume there is something out there we cannot see (eg: another
trigger)

      Rating: 3
My
      apologies  February 13, 2004
Reviewer:  Dave
       from Richmond, VA USA
Here is the entire script. I had to drop the table first.

SQL> drop table t;

Table dropped.

SQL> select * from v$version;

BANNER
----------------------------------------------------------
Oracle8i Enterprise Edition Release 8.1.7.4.0 - 64bit Prod
PL/SQL Release 8.1.7.4.0 - Production
CORE    8.1.7.0.0       Production
TNS for HPUX: Version 8.1.7.4.0 - Production
NLSRTL Version 3.4.1.0.0 - Production

SQL>  create table t (text_col varchar2(10) not null);

Table created.

SQL>  CREATE OR REPLACE TRIGGER ti
  2  BEFORE INSERT
  3  ON t
  4  REFERENCING NEW AS NEW OLD AS OLD
  5  FOR EACH ROW
  6  BEGIN
  7    :new.text_col := substr(:new.text_col, 2);
  8  END;
  9  /

Trigger created.

SQL> insert into t select rownum||rownum from all_tables w

5 rows created.

SQL> insert into t select 'XXX' from all_tables where rown
insert into t select 'XXX' from all_tables where rownum <=
            *
ERROR at line 1:
ORA-01400: cannot insert NULL into ("DHANZLIK"."T"."TEXT_C

SQL>
SQL>  insert into t select 'XXX' from all_tables where row

2 rows created.

SQL> select * from t
  2  /

TEXT_COL
----------
1
2
3
4
5
XX
X

7 rows selected.

SQL>

Thanks for any insight you might have in this.

          Followup:

hmm, something really utterly 'strange' is going on there.

the XX and X should NOT BE THERE since the insert into statement failed.

turn on SQL trace please

alter session set sql_trace=true

and do the insert into t select 'XXX' .....

and then exit sqlplus

and post the resulting TRACE file.

      Rating: 2
Constant vs row
      dependent value  February 13, 2004
Reviewer:
      Alfonso  from Hartford, CT USA
'XXX' is a constant, rownum is not.
For the 1st. row it starts as 'XXX' and substr( :new, 2 ) sets it to 'XX' first
time through.  2nd time through, the value, now at 'XX' getsd substring'ed to
'X'.  If you had more than <=2 rows, your string would become a null and fail
the column's NOT NULL constraint.

          Followup:

they are selecting 'xxx' from all_tables where rownum <= 5;

there is "no second time through".

look at the logic there -- it selects 'XXX' from 5 rows.  'XXX' is processed by
the trigger ONCE.  One time.

there is something else going on there.

there is no "xx" => "x" => null

something else is afoot on their system.

      Rating:
      2
  February 13, 2004
Reviewer:  Dave
       from Richmond, VA USA
Boy I am just screwing up all over. It looks like the last time I pasted, some
of the lines got cut off. The reason the 'X' and 'XX' got in the table was
because I ran the insert again but changed the where clause to rownum <= 2. That
doesn't generate the error and (thank you Alfonso) I can see that it is cutting
off my constant. I don't understand why and why it doesn't for you, Tom. I'll
try the trace thing and get back to you.

Thanks.

          Followup:

It doesn't work that way for anyone in the world EXCEPT for you!  There is
something else afoot here in your database.

another thing -- can you stick into there:

dbms_output.put_line( 'before rowid = ' || :new.rowid || ' text = ' ||
:new.text_col );

your substr....

dbms_output.put_line( 'after rowid = ' || :new.rowid || ' text = ' ||
:new.text_col );

and set serveroutput on, then run your insert and if the dbms_output.putlines
don't appear -- please just

SQL> exec null;

that'll dump them out.  lets get a little debug there.

      Rating: 2
Funny constant
      behavior  February 13, 2004
Reviewer:
      Alfonso  from Hartford, CT USA
In my case I get the same result as Tom. Haven't looked further into it, but it
could be some setting in your database that makes the trigger treat the constant
value almost as a "global" rather than local to the row processed. Or perhaps as
a stmt trigger instead of row trigger, somehow.

      Rating: 3
The debug and
      trace file  February 17, 2004
Reviewer:  Dave
       from Richmond, VA USA
Here is what I executed followed by the abridged trace file (the whole file
would not fit). I modified the insert statement and re-ran it with "where rownum
<=2" and the output seems to reflect that it is shortening the 'XXX' string as
each record is inserted. I should have put an exec null; right aftert the error
occured but didn't so the output generated by the successful two rows is
preceded by the output (6 lines) generated by the errored insert.

SQL> alter session set sql_trace=true
  2  /

Session altered.

SQL> drop table t;

Table dropped.

SQL> create table t (text_col varchar2(10) not null);

Table created.

SQL> CREATE OR REPLACE TRIGGER ti
  2  BEFORE INSERT
  3  ON t
  4  REFERENCING NEW AS NEW OLD AS OLD
  5  FOR EACH ROW
  6  BEGIN
  7    dbms_output.put_line( 'before rowid = ' || :new.rowid || ' text = ' ||
:new.text_col );
  8    :new.text_col := substr(:new.text_col, 2);
  9      dbms_output.put_line( 'after rowid = ' || :new.rowid || ' text = ' ||
:new.text_col );
 10  END;
 11  /

Trigger created.

SQL> set serveroutput on;
SQL> insert into t select rownum||rownum from all_tables where rownum <=5;
before rowid = AAAAAAAAAAAAAAAAAA text = 11
after rowid = AAAAAAAAAAAAAAAAAA text = 1
before rowid = AAAAAAAAAAAAAAAAAA text = 22
after rowid = AAAAAAAAAAAAAAAAAA text = 2
before rowid = AAAAAAAAAAAAAAAAAA text = 33
after rowid = AAAAAAAAAAAAAAAAAA text = 3
before rowid = AAAAAAAAAAAAAAAAAA text = 44
after rowid = AAAAAAAAAAAAAAAAAA text = 4
before rowid = AAAAAAAAAAAAAAAAAA text = 55
after rowid = AAAAAAAAAAAAAAAAAA text = 5

5 rows created.

SQL> insert into t select 'XXX' from all_tables where rownum <= 5;
insert into t select 'XXX' from all_tables where rownum <= 5
            *
ERROR at line 1:
ORA-01400: cannot insert NULL into ("DHANZLIK"."T"."TEXT_COL")

SQL>
SQL> insert into t select 'XXX' from all_tables where rownum <= 2;
before rowid = AAAAAAAAAAAAAAAAAA text = XXX
after rowid = AAAAAAAAAAAAAAAAAA text = XX
before rowid = AAAAAAAAAAAAAAAAAA text = XX
after rowid = AAAAAAAAAAAAAAAAAA text = X
before rowid = AAAAAAAAAAAAAAAAAA text = X
after rowid = AAAAAAAAAAAAAAAAAA text =
before rowid = AAAAAAAAAAAAAAAAAA text = XXX
after rowid = AAAAAAAAAAAAAAAAAA text = XX
before rowid = AAAAAAAAAAAAAAAAAA text = XX
after rowid = AAAAAAAAAAAAAAAAAA text = X

2 rows created.

SQL> select * from t;

TEXT_COL
----------
1
2
3
4
5
XX
X

7 rows selected.

SQL>

I've abbreviated the corresponding trace file to show only the top (up to the
alter session) and the bottom (beginning with the "insert into t select
rownum||rownum ...":

/u02/db1/oracle/admin/D01/bdump/s000_129_d01.trc
Oracle8i Enterprise Edition Release 8.1.7.4.0 - 64bit Production
With the Partitioning option
JServer Release 8.1.7.4.0 - 64bit Production
ORACLE_HOME = /u02/db1/oracle/product/8.1.7
System name:    HP-UX
Node name:    hp10
Release:    B.11.00
Version:    U
Machine:    9000/800
Instance name: D01
Redo thread mounted by this instance: 1
Oracle process number: 9
Unix process pid: 129, image: oracle@hp10 (S000)

*** 2004-02-17 09:54:25.350
*** SESSION ID:(19.213) 2004-02-17 09:54:25.338
APPNAME mod='SQL*Plus' mh=3669949024 act='' ah=4029777240
=====================
PARSING IN CURSOR #1 len=33 dep=0 uid=376 oct=42 lid=376 tim=328784135
hv=3732290820 ad='ddd6fb0'
alter session set sql_trace=true
END OF STMT
.
.
<Abridged here>
.
.
=====================#
PARSING IN CURSOR #1 len=78 dep=0 uid=376 oct=2 lid=376 tim=328790998
hv=103470991 ad='db83c90'
insert into t select rownum||rownum from all_tables where rownum <=:"SYS_B_0"
END OF STMT
PARSE #1:c=7,e=8,p=4,cr=232,cu=0,mis=1,r=0,dep=0,og=0,tim=328790998
EXEC #1:c=4,e=15,p=80,cr=1611,cu=16,mis=0,r=5,dep=0,og=4,tim=328791013
=====================
PARSING IN CURSOR #3 len=52 dep=0 uid=376 oct=47 lid=376 tim=328791015
hv=1697159799 ad='db55ba0'
BEGIN DBMS_OUTPUT.GET_LINES(:LINES, :NUMLINES); END;
END OF STMT
PARSE #3:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=0,tim=328791015
EXEC #3:c=1,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=0,og=4,tim=328791015
STAT #1 id=1 cnt=6 pid=0 pos=0 obj=0 op='COUNT STOPKEY '
STAT #1 id=2 cnt=5 pid=1 pos=1 obj=0 op='FILTER '
STAT #1 id=3 cnt=5 pid=2 pos=1 obj=0 op='NESTED LOOPS '
STAT #1 id=4 cnt=5 pid=3 pos=1 obj=0 op='NESTED LOOPS '
STAT #1 id=5 cnt=5 pid=4 pos=1 obj=0 op='NESTED LOOPS OUTER '
STAT #1 id=6 cnt=5 pid=5 pos=1 obj=0 op='NESTED LOOPS OUTER '
STAT #1 id=7 cnt=5 pid=6 pos=1 obj=0 op='NESTED LOOPS OUTER '
STAT #1 id=8 cnt=5 pid=7 pos=1 obj=0 op='NESTED LOOPS OUTER '
STAT #1 id=9 cnt=5 pid=8 pos=1 obj=0 op='NESTED LOOPS '
STAT #1 id=10 cnt=947 pid=9 pos=1 obj=6057 op='TABLE ACCESS FULL OBJ$ '
STAT #1 id=11 cnt=951 pid=9 pos=2 obj=6047 op='TABLE ACCESS CLUSTER TAB$ '
STAT #1 id=12 cnt=1359 pid=11 pos=1 obj=6046 op='INDEX UNIQUE SCAN '
STAT #1 id=13 cnt=5 pid=8 pos=2 obj=6057 op='TABLE ACCESS BY INDEX ROWID OBJ$ '
STAT #1 id=14 cnt=9 pid=13 pos=1 obj=6069 op='INDEX UNIQUE SCAN '
STAT #1 id=15 cnt=2 pid=7 pos=2 obj=6069 op='INDEX UNIQUE SCAN '
STAT #1 id=16 cnt=5 pid=6 pos=2 obj=6060 op='TABLE ACCESS CLUSTER USER$ '
STAT #1 id=17 cnt=9 pid=16 pos=1 obj=6050 op='INDEX UNIQUE SCAN '
STAT #1 id=18 cnt=5 pid=5 pos=2 obj=6053 op='TABLE ACCESS CLUSTER SEG$ '
STAT #1 id=19 cnt=9 pid=18 pos=1 obj=6041 op='INDEX UNIQUE SCAN '
STAT #1 id=20 cnt=9 pid=4 pos=2 obj=6055 op='TABLE ACCESS CLUSTER TS$ '
STAT #1 id=21 cnt=9 pid=20 pos=1 obj=6043 op='INDEX UNIQUE SCAN '
STAT #1 id=22 cnt=9 pid=3 pos=2 obj=6060 op='TABLE ACCESS CLUSTER USER$ '
STAT #1 id=23 cnt=9 pid=22 pos=1 obj=6050 op='INDEX UNIQUE SCAN '
STAT #1 id=27 cnt=1 pid=2 pos=3 obj=0 op='FIXED TABLE FULL X$KZSPR '
STAT #1 id=24 cnt=5 pid=2 pos=2 obj=0 op='NESTED LOOPS '
STAT #1 id=25 cnt=85 pid=24 pos=1 obj=0 op='FIXED TABLE FULL X$KZSRO '
STAT #1 id=26 cnt=80 pid=24 pos=2 obj=6256 op='INDEX RANGE SCAN '
=====================
PARSING IN CURSOR #3 len=37 dep=1 uid=0 oct=3 lid=0 tim=328791893 hv=1966425544
ad='ef92eb8'
select text from view$ where rowid=:1
END OF STMT
PARSE #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=328791893
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=328791893
FETCH #3:c=0,e=0,p=0,cr=3,cu=0,mis=0,r=1,dep=1,og=4,tim=328791893
STAT #3 id=1 cnt=1 pid=0 pos=0 obj=6092 op='TABLE ACCESS BY USER ROWID VIEW$ '
=====================
PARSING IN CURSOR #1 len=75 dep=0 uid=376 oct=2 lid=376 tim=328791894
hv=1137854665 ad='e05d290'
insert into t select :"SYS_B_0" from all_tables where rownum <= :"SYS_B_1"
END OF STMT
PARSE #1:c=1,e=1,p=0,cr=3,cu=0,mis=1,r=0,dep=0,og=0,tim=328791894
EXEC #1:c=2,e=2,p=0,cr=1512,cu=6,mis=0,r=0,dep=0,og=4,tim=328791896
ERROR #1:err=1400 tim=328791896
STAT #1 id=1 cnt=3 pid=0 pos=0 obj=0 op='COUNT STOPKEY '
STAT #1 id=2 cnt=3 pid=1 pos=1 obj=0 op='FILTER '
STAT #1 id=3 cnt=3 pid=2 pos=1 obj=0 op='NESTED LOOPS '
STAT #1 id=4 cnt=3 pid=3 pos=1 obj=0 op='NESTED LOOPS '
STAT #1 id=5 cnt=3 pid=4 pos=1 obj=0 op='NESTED LOOPS OUTER '
STAT #1 id=6 cnt=3 pid=5 pos=1 obj=0 op='NESTED LOOPS OUTER '
STAT #1 id=7 cnt=3 pid=6 pos=1 obj=0 op='NESTED LOOPS OUTER '
STAT #1 id=8 cnt=3 pid=7 pos=1 obj=0 op='NESTED LOOPS OUTER '
STAT #1 id=9 cnt=3 pid=8 pos=1 obj=0 op='NESTED LOOPS '
STAT #1 id=10 cnt=945 pid=9 pos=1 obj=6057 op='TABLE ACCESS FULL OBJ$ '
STAT #1 id=11 cnt=947 pid=9 pos=2 obj=6047 op='TABLE ACCESS CLUSTER TAB$ '
STAT #1 id=12 cnt=1355 pid=11 pos=1 obj=6046 op='INDEX UNIQUE SCAN '
STAT #1 id=13 cnt=3 pid=8 pos=2 obj=6057 op='TABLE ACCESS BY INDEX ROWID OBJ$ '
STAT #1 id=14 cnt=5 pid=13 pos=1 obj=6069 op='INDEX UNIQUE SCAN '
STAT #1 id=15 cnt=0 pid=7 pos=2 obj=6069 op='INDEX UNIQUE SCAN '
STAT #1 id=16 cnt=3 pid=6 pos=2 obj=6060 op='TABLE ACCESS CLUSTER USER$ '
STAT #1 id=17 cnt=5 pid=16 pos=1 obj=6050 op='INDEX UNIQUE SCAN '
STAT #1 id=18 cnt=3 pid=5 pos=2 obj=6053 op='TABLE ACCESS CLUSTER SEG$ '
STAT #1 id=19 cnt=5 pid=18 pos=1 obj=6041 op='INDEX UNIQUE SCAN '
STAT #1 id=20 cnt=5 pid=4 pos=2 obj=6055 op='TABLE ACCESS CLUSTER TS$ '
STAT #1 id=21 cnt=5 pid=20 pos=1 obj=6043 op='INDEX UNIQUE SCAN '
STAT #1 id=22 cnt=5 pid=3 pos=2 obj=6060 op='TABLE ACCESS CLUSTER USER$ '
STAT #1 id=23 cnt=5 pid=22 pos=1 obj=6050 op='INDEX UNIQUE SCAN '
STAT #1 id=27 cnt=1 pid=2 pos=3 obj=0 op='FIXED TABLE FULL X$KZSPR '
STAT #1 id=24 cnt=3 pid=2 pos=2 obj=0 op='NESTED LOOPS '
STAT #1 id=25 cnt=51 pid=24 pos=1 obj=0 op='FIXED TABLE FULL X$KZSRO '
STAT #1 id=26 cnt=48 pid=24 pos=2 obj=6256 op='INDEX RANGE SCAN '
*** 2004-02-17 09:55:53.390
*** SESSION ID:(19.213) 2004-02-17 09:55:53.390
=====================
PARSING IN CURSOR #1 len=75 dep=0 uid=376 oct=2 lid=376 tim=328792939
hv=1137854665 ad='e05d290'
insert into t select :"SYS_B_0" from all_tables where rownum <= :"SYS_B_1"
END OF STMT
PARSE #1:c=1,e=1,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=328792939
EXEC #1:c=1,e=11,p=0,cr=1259,cu=6,mis=0,r=2,dep=0,og=4,tim=328792950
=====================
PARSING IN CURSOR #3 len=52 dep=0 uid=376 oct=47 lid=376 tim=328792950
hv=1697159799 ad='db55ba0'
BEGIN DBMS_OUTPUT.GET_LINES(:LINES, :NUMLINES); END;
END OF STMT
PARSE #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=328792950
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=0,og=4,tim=328792950
STAT #1 id=1 cnt=3 pid=0 pos=0 obj=0 op='COUNT STOPKEY '
STAT #1 id=2 cnt=2 pid=1 pos=1 obj=0 op='FILTER '
STAT #1 id=3 cnt=2 pid=2 pos=1 obj=0 op='NESTED LOOPS '
STAT #1 id=4 cnt=2 pid=3 pos=1 obj=0 op='NESTED LOOPS '
STAT #1 id=5 cnt=2 pid=4 pos=1 obj=0 op='NESTED LOOPS OUTER '
STAT #1 id=6 cnt=2 pid=5 pos=1 obj=0 op='NESTED LOOPS OUTER '
STAT #1 id=7 cnt=2 pid=6 pos=1 obj=0 op='NESTED LOOPS OUTER '
STAT #1 id=8 cnt=2 pid=7 pos=1 obj=0 op='NESTED LOOPS OUTER '
STAT #1 id=9 cnt=2 pid=8 pos=1 obj=0 op='NESTED LOOPS '
STAT #1 id=10 cnt=753 pid=9 pos=1 obj=6057 op='TABLE ACCESS FULL OBJ$ '
STAT #1 id=11 cnt=754 pid=9 pos=2 obj=6047 op='TABLE ACCESS CLUSTER TAB$ '
STAT #1 id=12 cnt=1150 pid=11 pos=1 obj=6046 op='INDEX UNIQUE SCAN '
STAT #1 id=13 cnt=2 pid=8 pos=2 obj=6057 op='TABLE ACCESS BY INDEX ROWID OBJ$ '
STAT #1 id=14 cnt=3 pid=13 pos=1 obj=6069 op='INDEX UNIQUE SCAN '
STAT #1 id=15 cnt=0 pid=7 pos=2 obj=6069 op='INDEX UNIQUE SCAN '
STAT #1 id=16 cnt=2 pid=6 pos=2 obj=6060 op='TABLE ACCESS CLUSTER USER$ '
STAT #1 id=17 cnt=3 pid=16 pos=1 obj=6050 op='INDEX UNIQUE SCAN '
STAT #1 id=18 cnt=2 pid=5 pos=2 obj=6053 op='TABLE ACCESS CLUSTER SEG$ '
STAT #1 id=19 cnt=3 pid=18 pos=1 obj=6041 op='INDEX UNIQUE SCAN '
STAT #1 id=20 cnt=3 pid=4 pos=2 obj=6055 op='TABLE ACCESS CLUSTER TS$ '
STAT #1 id=21 cnt=3 pid=20 pos=1 obj=6043 op='INDEX UNIQUE SCAN '
STAT #1 id=22 cnt=3 pid=3 pos=2 obj=6060 op='TABLE ACCESS CLUSTER USER$ '
STAT #1 id=23 cnt=3 pid=22 pos=1 obj=6050 op='INDEX UNIQUE SCAN '
STAT #1 id=27 cnt=1 pid=2 pos=3 obj=0 op='FIXED TABLE FULL X$KZSPR '
STAT #1 id=24 cnt=2 pid=2 pos=2 obj=0 op='NESTED LOOPS '
STAT #1 id=25 cnt=34 pid=24 pos=1 obj=0 op='FIXED TABLE FULL X$KZSRO '
STAT #1 id=26 cnt=32 pid=24 pos=2 obj=6256 op='INDEX RANGE SCAN '
=====================
PARSING IN CURSOR #1 len=16 dep=0 uid=376 oct=3 lid=376 tim=328793526
hv=2959870057 ad='e930a50'
select * from t
END OF STMT
PARSE #1:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=4,tim=328793526
EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=328793526
FETCH #1:c=0,e=0,p=0,cr=1,cu=4,mis=0,r=1,dep=0,og=4,tim=328793526
FETCH #1:c=0,e=0,p=0,cr=1,cu=0,mis=0,r=6,dep=0,og=4,tim=328793526
*** 2004-02-17 09:57:04.038
XCTEND rlbk=0, rd_only=0
STAT #1 id=1 cnt=7 pid=0 pos=0 obj=86374 op='TABLE ACCESS FULL T '

          Followup:

yippee (sort of).

Ok, I reproduced.

It is cursor_sharing=force...

(trace files reveal all.... thats how I see you are shared server and using
cursor_sharing...)

scott@ORA817DEV> drop table t;
Table dropped.

scott@ORA817DEV> create table t (text_col varchar2(10) not null);
Table created.

scott@ORA817DEV> CREATE OR REPLACE TRIGGER ti
  2  BEFORE INSERT
  3  ON t
  4  REFERENCING NEW AS NEW OLD AS OLD
  5  FOR EACH ROW
  6  BEGIN
  7    :new.text_col := substr(:new.text_col, 2);
  8  END;
  9  /
Trigger created.

scott@ORA817DEV> alter session set cursor_sharing=force;
Session altered.

scott@ORA817DEV> insert into t select rownum||rownum from all_tables where
rownum <= 5;

5 rows created.

scott@ORA817DEV> insert into t select 'XXX' from all_tables where rownum <= 5;
insert into t select 'XXX' from all_tables where rownum <= 5
            *
ERROR at line 1:
ORA-01400: cannot insert NULL into ("SCOTT"."T"."TEXT_COL")

thats your error...

scott@ORA817DEV> select * from t;

TEXT_COL
----------
1
2
3
4
5

scott@ORA817DEV> alter session set cursor_sharing=exact;
Session altered.

that is your workarounds

scott@ORA817DEV> insert into t  select rownum||rownum from all_tables where
rownum <= 5;

5 rows created.

scott@ORA817DEV> insert into t  select 'XXX' from all_tables where rownum <= 5;

5 rows created.

scott@ORA817DEV> select * from t;

TEXT_COL
----------
1
2
3
4
5
1
2
3
4
5
XX
XX
XX
XX
XX

15 rows selected.

but in any case, we now have the testcase, root cause and workarounds -- .

I filed Bug No:     3449923 against this, please contact support and you can
reference that number.

      Rating: 5
Insert into
      table  February 17, 2004
Reviewer:  Malavika
       from Sri Lanka
what I want to know is
How to insert into a running variable
eg:-
for lvcount 0..11 loop
Insert into table1 numb'||Lvcount||'
values(Lvcount);
end loop;

          Followup:

not sure what you mean as that is "logically" the same as:

insert into table1 values ( lvcount )

the numb || lvcount is a "correlation name", not really relevant. so, not sure
what you are trying to do...

      Rating: 5
Thanks
      Tom  February 18, 2004
Reviewer:  Dave
       from Richmond, VA USA
I altered my session setting cursor_sharing to exact and it works as expected
now. I really appreciate the time you took to look at this and the timeliness of
your responses.

          Followup:

they found out it is not just cursor sharing, it affects anything with binds in
this fashion.  surprising it has not been hit before actually.

      Rating: 4
Mutating
      Trigger  February 18, 2004
Reviewer:  M S
      Reddy  from USA.
Hi Tom ,
 I need help on a mutating table error.
 My trigger updates table B when ever something is updated  or inserted into
table A.The update in the trigger is a correlated query that also uses columns
from B to update.
Iam also pasting the query .please help.

Thanks,
M S Reddy.

create or replace trigger t_p_a
before insert or update of p_a on e_j_p
for each row
begin
update eiio f
   set
(f.t_p,f.b_d ) =
 (
  select case when tmp.c = 0 then NVL(:new.p_a,0)
          else ( tmp.s_j + NVL(:new.p_a,0) )
         end as tot_pay,
         case when tmp.c = 0 then NVL(:new.p_a,0)
          else ( NVL(f.o_t_a,0)- NVL(f.d_a,0) - ( tmp.s_j+ NVL(:new.p_a,0) ) )
         end as b_d
   from ( select sum(NVL(j.p_a,0)) s_j
                ,count(j.ion) c
            from e_j_p j
           where j.ion = :new.ion ) tmp
 )
where f.ss = 'C'
and f.ion = :new.ion;
end;
/

          Followup:

you cannot read e-j-p in that trigger (and you should say "thanks" for that).
if you could, a multi-row update would give ambigous -- and ever changing
results.

see

http://asktom.oracle.com/~tkyte/Mutate/index.html
for how to postpone the read to the AFTER trigger.

February 18, 2004 Jaromir -- Thanks for the question regarding
      "Join a view with UNION", version 8.1

      You Asked
Hi Tom!

A very simple question on the topic of view with union and join.

We have a UNION ALL view with more branches to relatively large tables.

The question is: can I perform an equi join on this view and a simple table and
expect that it will be performed as nested loops with index range access on all
tables.

Of course all relevant tables do have index on the join key!

Simple something like that:

SELECT *
FROM
  (select * from t1
   UNION ALL
   select * from t2
  ) x,
  t3 y
WHERE x.a = y.a

all tables t1 to t3 have index on column a
the aim is to get an index range (and not full scan) on t1 and t2 (t3 is no
problem)

Thanks

J.Nemec

      and we said...
Ultimately, the optimizer will make the decision but if you have statistics on
the table and it appears that T3 should drive the query -- yes, it will.  Here I
made t1 and t2 much larger then t3 and it does:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t1 ( a int, b char(2000) );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t2 ( a int, b char(2000) );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t3 ( a int, b char(2000) );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t1_idx on t1(a);
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t2_idx on t2(a);
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t3_idx on t3(a);
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_stats.set_table_stats( user, 'T1',
numRows => 10000000, numBlks => 5000000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_stats.set_table_stats( user, 'T2',
numRows => 10000000, numBlks => 5000000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_stats.set_table_stats( user, 'T3',
numRows => 10000, numBlks => 5000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace on explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> SELECT *
  2  FROM
  3    (select * from t1
  4     UNION ALL
  5     select * from t2
  6    ) x,
  7    t3 y
  8  WHERE x.a = y.a
  9  /

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=20144 Card=2000000000
   1    0   NESTED LOOPS (Cost=20144 Card=2000000000 Bytes=2230000000000)
   2    1     TABLE ACCESS (FULL) OF 'T3' (Cost=144 Card=10000 Bytes=1000000)
   3    1     VIEW
   4    3       UNION-ALL (PARTITION)
   5    4         TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=2 Card=100000
   6    5           INDEX (RANGE SCAN) OF 'T1_IDX' (NON-UNIQUE) (Cost=1
   7    4         TABLE ACCESS (BY INDEX ROWID) OF 'T2' (Cost=2 Card=100000
   8    7           INDEX (RANGE SCAN) OF 'T2_IDX' (NON-UNIQUE) (Cost=1

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off

Review &
      Followup

      Rating: 3
union-all
      (partition) ?  February 18, 2004
Reviewer:
      Andrew Markiewicz  from Madison, WI
Tom,
What is the significance of the "UNION-ALL (PARTITION)" in an explain plan as
opposed to just  "UNION-ALL"?
It appears it happens when the union operation is in a view.

E.G.

  1          select * from t11
  2          union all
  3*         select * from t12
AMA 12791(9519)jixe-> /
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=2 Bytes=4030)
   1    0   UNION-ALL
   2    1     TABLE ACCESS (FULL) OF 'T11' (Cost=1 Card=1 Bytes=2015)
   3    1     TABLE ACCESS (FULL) OF 'T12' (Cost=1 Card=1 Bytes=2015)

AMA 12791(9519)jixe-> ed
Wrote file /home/andrew/afiedt_from_loginsql.buf

  1  select *
  2    from (
  3          select * from t11
  4          union all
  5          select * from t12
  6*        )
AMA 12791(9519)jixe-> /
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=1015)
   1    0   VIEW (Cost=1 Card=1 Bytes=1015)
   2    1     UNION-ALL (PARTITION)
   3    2       TABLE ACCESS (FULL) OF 'T11' (Cost=1 Card=1 Bytes=2015)
   4    2       TABLE ACCESS (FULL) OF 'T12' (Cost=1 Card=1 Bytes=2015)

Thanks.

          Followup:

just the "semantic" difference between the two.

the second one is "like" a partitioned table, the first is just a union all of
two tables.  the other is like a query from a 'partitioned' table.

ultimately, no real difference to you.


Listing

Recent

Archives

Advanced Search

FAQ by Category

Most Popular

Hot Articles

Your Questions

[ Listing ]

RSS Feed

Subject

Article

Last Updated (East coast time)

Creating hierarchial tree over a view

article only

Feb 19, 2004 07:26 am

Printing a report to file in .pdf format

article only

Feb 19, 2004 08:24 am

running a report in d2k forms 5.0 from the menu

article only

Feb 19, 2004 08:25 am

Standby database

article only

Feb 19, 2004 08:50 am

RMAN and archivelogs

article only

Feb 19, 2004 10:54 am

Indexes on foreign keys

article only

Feb 19, 2004 10:58 am

exception handling in batch processing

article only

Feb 19, 2004 11:07 am

To determine SGA size for unix box

article only

Feb 19, 2004 11:08 am

Sending HTML using UTL_SMTP

article only

Feb 19, 2004 11:19 am

PLS-123 error

article only

Feb 19, 2004 11:37 am

SQL questions involving combinations

article only

Feb 19, 2004 12:38 pm

Reading deadlock trace files

article only

Feb 19, 2004 02:36 pm

negative number format

article only

Feb 19, 2004 03:34 pm

Pro*C and UTL_SMTP

article only

Feb 19, 2004 03:38 pm

Trigger on Blob-Fields

article only

Feb 19, 2004 07:19 pm

Using Savepoints

article only

Feb 19, 2004 07:23 pm

Real Application Clusters

article only

Feb 19, 2004 07:31 pm

IN (vs) EXISTS and NOT IN (vs) NOT EXISTS

article only

Feb 20, 2004 07:22 am

Returning clob to varchar from a stored procedure

article only

Feb 20, 2004 07:34 am

Cuncurrent execution of procedures

article only

Feb 20, 2004 09:38 am

Difference between Sort Merge and Hash Joins

article only

Feb 20, 2004 09:39 am

How to pass an array to a stored procedure

article only

Feb 20, 2004 09:55 am

Converting Dictionary Managed to LMT tablespace

article only

Feb 20, 2004 09:58 am

Missing Sequence Number..

article only

Feb 20, 2004 12:56 pm

Start and end date of a quater

article only

Feb 20, 2004 01:17 pm

dbms_job.submit

article only

Feb 20, 2004 01:22 pm

ORA-01025: UPI parameter out of range

article only

Feb 20, 2004 02:59 pm

Best way to copy From Global TEMP Table to an empty Standard Table

article only

Feb 20, 2004 03:26 pm

Analytic Functions

article only

Feb 20, 2004 03:31 pm

RMAN Error

article only

Feb 21, 2004 10:51 am

retrive only numbers.

article only

Feb 21, 2004 11:11 am

How to flush the db buffer cache

article only

Feb 21, 2004 11:15 am

Develop Environment

article only

Feb 21, 2004 11:17 am

Exclusions from Hot backup.

article only

Feb 21, 2004 01:31 pm

reducing reorganisation time of large indexes and tables

article only

Feb 21, 2004 04:19 pm

Dynamic Sql : where to put it in Java or Stored Procedure

article only

Feb 22, 2004 08:54 am

How to use varray type in where_clause of select statements?

article only

Feb 22, 2004 10:10 am

Generating fixed format report

article only

Feb 22, 2004 11:17 am

Using a Recordset in a Loop Statement

article only

Feb 22, 2004 12:44 pm

Using XML...

article only

Feb 23, 2004 07:38 am

dropping and adding partitions periodically

article only

Feb 23, 2004 07:41 am

Limit Depth of Hierarchical Query

article only

Feb 23, 2004 07:47 am

Is it possible to encrypt PL/SQL procedure?

article only

Feb 23, 2004 07:52 am

Java Development

article only

Feb 23, 2004 08:08 am

owa_opt_lock.checksum question

article only

Feb 23, 2004 09:11 am

SQL Model Clause

article only

Feb 23, 2004 09:17 am

Oracle over satellite link

article only

Feb 23, 2004 09:27 am

Open cursors exceeded

article only

Feb 23, 2004 10:03 am

opinion

article only

Feb 23, 2004 10:35 am

UPDATE using COLLECTION

article only

Feb 23, 2004 10:49 am

Partition - Modifying the Range Values - 14400 Error

article only

Feb 23, 2004 04:31 pm

session_cached_cursor

article only

Feb 23, 2004 04:34 pm

In which order does Oracle parse the SQL statement?

article only

Feb 23, 2004 05:05 pm

Migration

article only

Feb 23, 2004 05:08 pm

using explain plan

article only

Feb 23, 2004 05:09 pm

Materialized View Refresh

article only

Feb 24, 2004 06:27 am

Recovery Manager Catalog - different versions of Database

article only

Feb 24, 2004 06:31 am

Index not used.

article only

Feb 24, 2004 06:38 am

join orders related to outer join

article only

Feb 24, 2004 06:45 am

How to use ' From clasue query ' for a data block as a datasource.

article only

Feb 24, 2004 06:48 am

uninitialized collections

article only

Feb 24, 2004 06:50 am

Tuning SQL Statements Using Explain Plan

article only

Feb 24, 2004 08:21 am

PL/SQL execeptions

article only

Feb 24, 2004 08:22 am

Update statement and Null-values -- update a table from another table

article only

Feb 24, 2004 09:08 am

how to select and insert a long type column

article only

Feb 24, 2004 12:44 pm

reading data from .dbf file into oracle 8i -- HOW TO LOAD DBASE data

article only

Feb 24, 2004 12:55 pm

SGA size

article only

Feb 24, 2004 12:57 pm

Partition

article only

Feb 24, 2004 01:05 pm

pl/sql for native execution

article only

Feb 24, 2004 02:04 pm

flashback using timestamp

article only

Feb 25, 2004 07:39 am

Import ignore=y

article only

Feb 25, 2004 07:41 am

Data types - performance considerations - PCTUSed Doubts - Reg

article only

Feb 25, 2004 07:45 am

latch contention

article only

Feb 25, 2004 08:16 am

Triggers vs Packages !

article only

Feb 25, 2004 08:29 am

Orakill utility

article only

Feb 25, 2004 08:40 am

Replication from SQL Server

article only

Feb 25, 2004 09:22 am

Adding Time part in Date Field

article only

Feb 25, 2004 09:23 am

Procedures vs Triggers

article only

Feb 25, 2004 11:02 am

DBMS_STATS and MONITORING

article only

Feb 25, 2004 11:22 am

Rebuilding Indexes

article only

Feb 25, 2004 11:33 am

list to table function with string > 4000 chars (CLOB)

article only

Feb 25, 2004 11:44 am

unexplained writes in tablespaces with indexes

article only

Feb 25, 2004 12:21 pm

rownum question

article only

Feb 25, 2004 12:41 pm

Difference between constraints and Indexes

article only

Feb 25, 2004 05:23 pm

Autmatic Undo Management

article only

Feb 25, 2004 05:27 pm

V$session_wait

article only

Feb 25, 2004 07:04 pm

sql loader questions

article only

Feb 25, 2004 07:23 pm

Refresh on Materialized View

article only

Feb 25, 2004 07:25 pm

Oracle standby database hardware/software requirements

article only

Feb 25, 2004 07:33 pm

Data Guard

article only

Feb 25, 2004 07:43 pm

row(s) 1 - 90 of 90

February 19, 2004 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.
Rating: 3
Join makes hierarchical query slow February 05, 2004
Reviewer: AKS from PA, USA
Hi TOM,
I wrote a hierarchical query on joining the two tables that makes it very-very
slow.
If i execute the join query that is much faster than hierarchical query.
My question is how the CONNECT BY works over a JOIN?
Here is my tables-

SQL> select count(1) from T1;
 COUNT(1)
---------
  2405037
SQL> select count(1) from T2;
 COUNT(1)
---------
   123070

select t1.col1 ,t2.col2
from t1 ,t2
where t1.id = t2.id
connect by prior t2.id = t2.id_parent
start with t2.id = 380054;
It goes very slow. How to improve it?
Followup:
umm, you cannot in any way shape or form compare an apple to a toaster oven and
that is what you are trying to do here.

2.4 million records
vs
123k

You give no indication of how long "long" is or how fast you think it "should
be".

counting 2.4 million records via an index lookup like you are doing could be
"slow", sure.

you might consider:

select (select col1 from t1 where t1.id = t2.id), t2.col2
  from t2
connect by prior id = id_parent
start with id = 380054;

an index on t2(id_parent,id,col2) and one on t2(id) would benefit this query.
Rating: 4
CONNECT BY thread February 18, 2004
Reviewer: Bob from Boston
Tom, as usual your examples and illumination of how Oracle works is dead on.
However, there seems to be a dollop of backlash with those who don't quite get
it. This may make it difficult for them to learn and does make it more difficult
for me to enjoy the thread.

D'ya think you could lighten up a bit on the newbies?
Followup:
where was I harsh?  and who was the newbie i was harsh on in this thread?
(serious question -- i've reread this and don't see it)
February 19, 2004 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
Rating: 5
automating the process of generating PDF's February 18, 2004
Reviewer: A reader from Houston
Hi Tom,
I have a situation where a report is generating a pdf file and I need to store
the pdf file as a blob in the DB.  The process of generating the PDF file and
storing the PDF file needs to be automated through PL/SQL.

This is what I was planning on doing.

1) Create a bat file (windows) and run the bat file from within PL/SQL
---run.bat start-----
RWRUN60.EXE REPORT=SummaryReport.RDF P_Parameter1=123
USERID=report_user/report_pwd@abc.xyz.com server=Rep60_XYZ  BATCH=YES
DESFORMAT=PDF DESTYPE=FILE DESNAME=123.PDF
---run.bat end-----
2) Read the stored PDF file and write it to the blob

My questions
1) I would like to know if there are any PL/SQL packages for generating byte
streams and storing the byte streams as a blob in the database(instead of
generating the PDF file, storing it on the DB server and then saving it as a
blob).
2) Your suggestions on how this can be done in a better way.

Thank you
Followup:
1) nope

2) you could use dbms_lob.loadfromfile to do step #2 instead of a custom program
Rating: 3
Generating PDF's February 19, 2004
Reviewer: Ben from Quebec
I need to do the same thing for a clinical research project database - at
specified intervals, I need to generate a letter that would be sent to the
patients, and store that document in the database.

My approach was to create a procedure to be executed weekly through the dbms_job
package. Among other things, that PL/SQL procedure executes a host call to
launch a Perl script, which generates the letter in PDF format, using the
PDF::API2 library, and loads the letter up in the database.
Rating: 3
PDF via FOP March 17, 2004
Reviewer: Olga from Vienna
I use XML DB and the Apache FOP Framework for generating PDF. It works very
well!

1. Convert the Query Output into a XML Document (with PL/SQL-XDB-Functions)

2. Use a (in DB or file system stored) XSL-Stylesheet to generate a
XSL-FO-Document in the database (with PL/SQL-XDB-Functions)

3. Call a servlet via UTL_HTTP. The servlet reads the XSL-FO, generates the pdf
and and stores it in a BLOB in the database.

It works very well!

I tried to import the FOP-libraries into the database, so that I could use
JavaStoredProcedures instead of the servlet. But after importing 7000(!) Classes
I got some errors I couldn't handle. Perhaps anybody has done it before and
could give me a solution?
Rating: 3
TO: A reader from Houston March 17, 2004
Reviewer: Olga from Vienna
I want to do the same in this way:

1. Set up the report-server that you could request a report via an url in the
browser

2. Get the response from a http-reports-request with UTL_HTTP

3. Write the UTL_HTTP-Output into a BLOB.

Bye,

Olga
February 19, 2004 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
Rating: 4
Problems with Report Builder Dec. 2k February 18, 2004
Reviewer: Ramón Alejandro Berrones Cruz from Queretaro, Mexico
I have a report and in the screen preview all the information is fine; however
when I print it some letters disappear on the left side of the fiel. I don't
know How to correct this. I tried with the properties that allows to fields
expand them in an horizontal and vertical way. But it doesn't work. How can I
get WYSWYG ("What you see What you get") in a print form?... Could be possible
that it is a Report Builder bug and How I can correc this?... I need help...

Thank you very much
Followup:
please contact support for something like this.
Rating: 5
Problems with Report Builder- Developer 2k February 23, 2004
Reviewer: Ramón Alejandro Berrones Cruz from Queretaro, México
Thank you very much for your response!

However it is difficult to understand that a Report that was made it in Report
builder has problems of software support, because it is a common task to make
reports in Databases. Did you ever hear about this kind of troubles specifically
on Report Builder? or maybe I'm creating the reports in a wrong way.

Best Regards
February 19, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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.
Rating: 4
Patching up Standby DB's February 18, 2004
Reviewer: Alvin from Philippines
Is it possible for me to apply patch 8174  my standby db (residing on solaris
5.8) ? Because i cannot 'open' the db for it will not revert to standby db
anymore.

Or on the standby machine i can..

1. Close the standby db
2. create another db with the same SID this will be my "dummy"
3. Open the Dummy db and apply the patch.
4. close the dummy db
5. reopen the standby db and synch it with the main db.
Followup:
please contact support for the proper steps to upgrade -- but in a nutshell
you'll

o update the SOFTWARE on both systems
o startup the standby in managed recovery mode again
o THEN migrate the production system.  the data dictionary/etc changes will flow
over to the standby via the redo stream...

you do not need to run catalog.sql etc on the standby -- running that stuff on
production will "carry over".

But - don't do anything until you get the steps from support for 8i.
February 19, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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
Rating: 5
More on the arc redo log files January 31, 2004
Reviewer: steve from Mtl
Hi Tom,

I am confused with how the archived redo log files
are managed when doing RMAN backups

Assume the following
--------------------

1. database has 25 tablespaces/datafiles
2. all datafiles are constantly changing
3. 50 redo log files are generated per day (50*200Mb)
4. Backup redundancy of 2
5. Backups done as follows:

  Sun              --> FULL
  Mon - Wed - Fri  --> incremental

I can visualize what used to happen in traditional hot
backups. If we did a full every Sunday, we would have
something like:

   On Sunday:         copy 25 data files           D1
   During the week:   Keep 7*50 arc redo lg files  R1

   On next Sunday:    copy 25 data files           D2
   During the week:   Keep 7*50 arc redo lg files  R2

   On next Sunday:    copy 25 data files           D3
   Delete A1 and R1
   During the week:   Keep 7*50 arc redo lg files  R3

   On next Sunday:    copy 25 data files           D4
   Delete A2 and R2
   During the week:   Keep 7*50 arc redo lg files  R4

With RMAN, I don't understand how to manage
the archived redo log files:

  Full on Sunday  --> creates backup set B1.1

  incr on Mon     --> creates backup set B1.2

  incr on Wed     --> creates backup set B1.2

  incr on Fri     --> creates backup set B1.3

  Full on Sunday  --> creates backup set B2.1

  incr on Mon     --> creates backup set B2.2

  incr on Wed     --> creates backup set B2.2

  incr on Fri     --> creates backup set B2.3

  Full on Sunday  --> creates backup set B3.1

Q1: At this point (right after B3.1), woudl B1.1
    to B1.4 all be considered obsolete?

Q2: (and here is where I am having the difficulty)
    What do I do with the 350 arc redo log files
    that are generated during the week?

    q. How do I handle the arc redo log files? Does RMAN
       backup all 350 arc redo log files generated during
       the week?

    q. Does RMAN copy all them from the arc destination
       to the backup destination?

    q. Who/how/when are they copied/deleted?

    Help!

I could really use some clarification on the 'lifecycle'
and 'movement' of the arc redo log files.

Note: This will also help me properly dimension my
      disk based (ATA drive) backup solution!  ;)

Steve
Followup:
well -- i don't see how you can sleep at night with only one full backup?

I need at least 3, if not many more, to be comfortable.

You want to keep the archives all of the way back to your oldest backup -- in
your case, everything from sunday on.

That way -- you can restore sunday and be OK.

you can restore sun apply mon incremental and still be OK.

and so on.  If wed's incremental is on a bad tape -- no worries, you haven't cut
off any archives you need.

So, look at your OLDEST backup, keep all archives back to that point in time at
least.

(and keep more than one backup of course!)
Rating: 4
January 31, 2004
Reviewer: steve from Mtl
I knew you were gonna comment on the "keeping only one backup
set". I just wanted to make the example shorter.

But Tom, I still don't know how to handle the archived
redo log files. Would it be through RMAN or separately?

Steve
Followup:
you could use rman to back them up -- that would be easiest (they would be known
to rman) or you can back them up yourself.  I would back them up via rman to
keep everything "together"
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96566/rcmconc1.htm#465607

Rating: 5
Thanks January 31, 2004
Reviewer: steve from Mtl
Thanks Tom
Rating: 5
Something I though about February 03, 2004
Reviewer: steve from Mtl
Hi again,

I was thinking about something...

Suppose:
========

 1.  We will never want to roll back the database (i.e.
     recovery window is 0).

  2. We want backups merely to protect against
     media failure.

  3. We are backing up to local disks (i.e. fast)

  4. Assume incrementals take as long or longer
     than full backups.

Since we should to keep 3 sets of backups and since
incrementals are as long to take as full backups (smaller but longer),
do you see a use to doing incrementals? Why not just
perform a FULL wherever we were performing an incremental.

In the end, we will actually need less storage space.
I don't see the need for the incrementals in this situation.

I'd love to hear your comments on this.

Steve
Followup:
if you got the disk -- the fulls are what you want -- since you want 3 fulls
regardless (and you want them on tape! or at least "elsewhere").

the incrementals in 9i and before are purely for "saving space" pretty much.

In 10g -- this all changes with the ability for rman to catch up your backups
and for the incremental to read ONLY changed blocks instead the entire
database...
Rating: 5
Great! Thanks Tom. February 03, 2004
Reviewer: steve from Mtl
Great! Thanks Tom.

Neat about incrementals catching up the full in 10G.

Not sure how we will maintain 3 copies in the 10G scenario,
but I guess we'll cross that bridge when we get there...

Thanks again

Steve
Rating: 4
rman and archivelogs timing - 0racle 9.2 February 19, 2004
Reviewer: BAQIR HUSSAIN from Emeryville, CA USA
Tom,
rman was setup first time and ran the following to backup data files and archive
logs.  I started archiving datafile at 9pm and done by 10:30pm.  When the time
to backup archive logs, the rman asks for the very first archive log generated a
few days back.  My questions are:

1.  Why rman is asking for the very old archive logs?
2.  Since the rman backup was done on the datafiles at 9pm,therefore, rman
should only backup those archive logs that were generated before and during the
backup started for the roll forward.
3. How can I direct RMAN to backup only those archive logs that are generated
before and during the rman backup run?
4. I feel like I am not supplying the right parameters to achieve.  Please show
me how can I do it?

Thanks

3> run {
4>  allocate channel c1 type disk;
5>  allocate channel c2 type disk;
6>
7> backup
8>   incremental level 0
9>   tag sun_bkup_level_0
10>   format '/mnt/dump/db_file_bkup/df_%u'
11> database;
12>
13> sql 'alter system archive log current';
14>
15> backup
16>   format '/mnt/dump/db_file_bkup/al_%u'
17>   archivelog all
18>   delete all input;
19>  }
Followup:
it wanted what was in the control file but it had no record of having.  it is
asking to backup that which has not been backed up.

...
should only backup those archive logs that were generated before and during the
backup started for the roll forward.....

"before", it got them, it got them all ;)

see
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96566/rcmquick.htm#440801
and specifically:
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96566/rcmbackp.htm#441460
February 19, 2004 Gerard -- Thanks for the question regarding "Indexes on foreign keys", version 8.0.5.2.1 (64bit)
You Asked
I have read several books which have repeatedly mentioned
creating indexes on foreign keys. I know one advantage is that
it eliminates table-level locks and, I have seen the benefit
since I have encountered a similar problem. However, I would
like to know if you would recommend doing this for ALL foreign
key constraints or, for only those which create the locking
problem? How badly would it impact the database performance
considering an OLTP system?
and we said...
See
http://osi.oracle.com/~tkyte/unindex/index.html
for a script to detect unindexed foreign keys and pointers into
the documentation as to the above referred to locking issue
(which is real -- table locks can arise if you delete a parent
record or update the primary key of a parent record and have
unindexed foreign keys).

In addition to the table lock issue that might hit you, an
unindexed foreign key is bad in the following cases as well:

o When you have an on delete cascade and have not indexed the
child table. For example EMP is child of DEPT. Delete deptno =
10 should cascade to EMP. If deptno in emp is not indexed, you
will get a full table scan of EMP. This full scan is probably
undesirable and if you delete many rows from the parent table,
the child table will be scanned once for each parent row
deleted.

o When you query from the PARENT to the CHILD.  Consider the
EMP, DEPT example again.  It is very common to query the EMP
table in the context of a deptno.  If you frequently query:

    select * from dept, emp
     where emp.deptno = dept.deptno and dept.deptno = :X;

to generate a report or something, you'll find not having the
index in place will slow down the queries.

So, when do you NOT need to index a foriegn key.  In general
when the following conditions are met:

o you do NOT delete from the parent table. (especially with
delete cascade -- it is a double whammy)

o you do NOT update the parent tables unique/primary key value.

o you do NOT join from the PARENT to the CHILD (like DEPT->EMP).

If you satisfy all three above, feel free to skip the index, it
is not needed.  If you do any of the above, be aware of the
consequences.

As for the effect of an index on a foriegn key index on an OLTP
system -- it depends.  If you frequently update the foreign key
(in my experience, you do not update the fkey frequently, if at
all, in many applications), the overhead is during the insert
and might not be noticed.  If you update it frequently, it might
be worse.  Its like any other index -- you just have more
reasons (the above list) to consider adding that index then
normal....
Review & Followup

Rating: 5
March 28, 2001
Reviewer: Helena Markova from Bratislava, Slovakia
Rating: 5
Indexes on foreign keys May 09, 2001
Reviewer: Thasin Sardar from Lansing, MI USA
Rating: 5
Great info! May 25, 2001
Reviewer: A reader
Rating: 5
July 12, 2001
Reviewer: A reader
Rating: 4
July 12, 2001
Reviewer: A reader
Rating: 5
July 17, 2001
Reviewer: Martin Gamperl from Graz, AUSTRIA
Rating: 4
August 09, 2001
Reviewer: shibu from bangalore ,India
very informative
Rating: 4
October 25, 2001
Reviewer: an from DE
excellent!
Rating: 5
And now.... A moment for the newbies! January 25, 2002
Reviewer: Paul from Carmel, IN
Tom,

   If we want to index our forgien key columns, what is the best index to build
against them? I mean does the index need to be seperate and special on that
peticular column to be effective? Or is it better to index the column as part of
a larger indexing stragey? Or should I go back to selling shoes? :)
Followup:
Any index that contains the foreign key on the leading edge will do -- eg:

create table emp ( empno int primary key, ...  deptno references DEPT );

create index on dept(deptno,empno);  -- this is OK
create index on dept(empno,deptno);  -- this will not prevent the fkey lock
create index on dept(deptno);  -- this is OK
Rating: 3
Reader January 26, 2002
Reviewer: Reader
Tom,

In your script
http://osi.oracle.com/~tkyte/unindex/index.html,
why each decode returns 2 rows

SQL> set feedback on
SQL> /

DECODE(COLUMN_POSITION,1,','||SU
--------------------------------
, COL1
null

2 rows selected.

SQL> l
  1  select decode(column_position,1,', '||substr(column_name,1,30),NULL) from
  2* dba_ind_columns where  index_name = 'IDX'
SQL>
Followup:
You are not running the query as I have it.  The query you have above is part of
an inline view that I AGGREGATE over to get rid of the multiple rows.

Revisit that URL
http://osi.oracle.com/~tkyte/unindex/index.html
and run the query as I have it coded.
Rating: 5
Another twist - what if the child tables are empty? May 01, 2002
Reviewer: Doug from CT, USA
Tom.. due to an app upgrade, we have some new child tables that aren't even
used.  They have unindexed foreign keys but the tables are empty. Do all these
rules still apply?
Followup:
Yes, they still apply.

BUT -- do you care if that empty child table gets a share lock on it to prevent
updates (doubtful, if the child table is empty)...

So, they apply -- but do you care?
Rating: 3
Lock on child Table May 22, 2002
Reviewer: kiro from Bulgaria
Hi Tom
I have tried to reproduce lock on child table on 9.0.1 but I could not. Is it
fix or not. See example.
test> create table b (id int primary key);

Table created.

test> create table b1 (id references b);

Table created.

test>  insert into b values(1);

1 row created.

test> insert into b values(2);

1 row created.

test> commit;

Commit complete.

test> update b set id = 10 where id=1;

1 row updated.

 --- Here I suppose that table b1 is locked

 From another session I tried to insert into child table b1 and I did it.

test1> insert into b1 values(2);

1 row created.

test1> commit;

Commit complete.

test1> select * from b1;

        ID
----------
         2

Again frist session

test> commit;

Commit complete.

test> select * from b;

        ID
----------
        10
         2

Please comment this.
Thank you.
Followup:
set echo on

drop table t1 cascade constraints;
drop table t2 cascade constraints;

create table t1 ( x int primary key );
create table t2 ( y references t1 );

insert into t1 values ( 1 );
insert into t1 values ( 2 );
commit;

set echo off
PROMPT goto another window, execute:
PROMPT insert into t2 values ( 1 );
PROMPT then come back here and hit enter
set echo on
pause

set echo off
PROMPT the following update will BLOCK as it
PROMPT tries to lock the child (cannot because
PROMPT of the other session).
update t1 set x = 3 where x = 2;

will show it.

The update/delete of the parent will still attemp to lock the child but only for
an instant.  If the child has outstanding transactions against it, the
update/delete will have to wait.  Other transactions will become blocked by the
update/delete itself (potentially).  Hence the problem still exists, just in a
lesser form.

The child table lock is taken for the duration of the parent update/delete NOT
the entire transaction in 9i.  Lessens the problem but does not remove it.
Rating: 4
Thanks. May 23, 2002
Reviewer: kiro from Bulgaria
Rating: 5
Delete Cascade Locking August 16, 2002
Reviewer: Mark from Huston,TX
Tom,
If I declare delete cascade, does it lock the child tables or only the child
records.In another words when I delete the master record what kind of lock is
applied to the child tables? row level or table level? because I have lot of
inserts going on at the same time and I don't want to lock the Child or master
table.I have indexes on all the foreign key columns.

Thanks,
Mark
Followup:
If you have indexes on the fkeys, this question is MOOT for you -- the locking
at the table level happens ONLY on unindexed foreign keys.

Since the server cannot know beforehand if there are any child records or not --
the lack of an index on the fkey will cause the child table to be locked upon
delete of a parent record regardless of whether a row exists or not in the
child.

This really isn't to do with "on delete cascade" -- it is to do with

a) having a foreign key
b) not having an index on that fkey
c) updating the parents primary key or deleting from parent.
Rating: 5
How the index prevent table-level locking? November 06, 2002
Reviewer: Praveen from India
Hi Tom,

   You said that an index on  foriegn key prevents the table-level locking of
the child table when the parent record is deleted or primary key of a parent
record is modified.

Q. How does the existance of FK-index prevent table-level locking of child
table?

Thanks and regards

Praveen
Followup:
follow the links -- on the unindex link is a link to that pesky server concepts
manual where so much of the internals of Oracle that are relevant to you and me
are explained in nice detail.  In fact, the entire "how this works when you have
an index vs not having an index" is right there!
Rating: 4
Why not hard wire this into Oracle January 13, 2003
Reviewer: Bala from Kuala Lumpur
If there are 99 reasons for indexing the foreign key why not hard wire this into
oracle?
Followup:
because of the last reason not to.
Rating: 5
Do I need to index? February 13, 2003
Reviewer: Sri
Dear Tom,

  Please see the following scripts: -

Create Table BranchMaster
(BranchCode Varchar2(10) Primary Key,
 BranchName Varchar2(100) Unique Not Null
)
/
Create Table ItemMaster
(ItemCode Varchar2(10) Primary Key,
 ItemName Varchar2(100) Unique Not Null
)
/
Create Table ShippingMaster
(ShippingCode Varchar2(10) Primary Key,
 ShippingDate Date,
 BranchCode Varchar2(10) References BranchMaster(BranchCode),
 ShipDate Date
)
/
Create Table ShippingDetail
(ShippingCode Varchar2(10) References ShippingMaster(ShippingCode),
 SlNo Number(2),
 ItemCode Varchar2(10) References ItemMaster(ItemCode),
 Quantity Number(12,2),
 Primary Key (ShippingCode, SlNo)
)
/

  Here, as you can see, BranchMaster and ItemMaster are not restrictly Master
tables.  They are sort of Code tables to store code in transactions instead of
long descriptions.  As you can see the BranchCode in the ShippingMaster and
ItemCode in the ShippingDetail are NOT indexed.

  Now, I have an application that uses the above tables.  The daily transactions
are mainly on the ShippingMaster and ShippingDetail tables.  As and when the
transactions are happening, I tried to create another table: -

Create Table BillingMaster
(BillCode Varchar2(10) Primary Key,
 BillDate Date,
 BranchCode Varchar2(10) References BranchMaster(BranchCode)
)
/

  I could not create this and Oracle gives an error message after some time.

Create Table BillingMaster
*
ERROR at line 1:
ORA-04021: timeout occurred while waiting to lock object GFL.BRANCHMASTER

  1. Why is this happening.  I am just refering the table.

  2. I queried the v$access table and it shows that all the users of my
application (Shipping screen) are having an entry in the table with Object
BranchMaster.  Is this abnormal and can I relate the error to this?  Otherwise,
what might be the problem?

  3. With the above tables in place, should I index the columns referring the
Code tables (BranchMaster & ItemMaster).  I will NOT be modifying or deleting
any records in these Code tables.  I might add a new record once in a while.

  I am using Oracle 9.0.1.1.1.  Kindly advice.  Thanks a lot.
Followup:
1) you need to have the parent for a moment in order to put the constraint in
place.  You can simulate this issue easily:

ops$tkyte@ORA817DEV> create table p ( x int primary key );

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert into p values ( 1 );

1 row created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> declare
  2          pragma autonomous_transaction;
  3  begin
  4          execute immediate 'create table c2 ( x references p )';
  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> commit;

Commit complete.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> declare
  2          pragma autonomous_transaction;
  3  begin
  4          execute immediate 'create table c2 ( x references p )';
  5  end;
  6  /

PL/SQL procedure successfully completed.

2) it is normal - they are in fact accessing it after all.

3) if you DELETE from the parent tables OR update the parent tables primary keys
-- yes.  else, it is upto you.
Rating: 5
Doubts regarding v$access February 14, 2003
Reviewer: Sri
Dear Tom,

  This is in continuation with my just previous question.

  Regarding v$access, the oracle 9i Database Reference says "This view displays
objects in the database that are currently locked and the sessions that are
accessing them".

  1. What type of lock will it put on the object?
  2. If it is a table, would it lock only the row that the user is accessing or
the whole table?
  3. When I run a report with a select statement, why should Oracle put a lock,
I am only reading it and it is not Select... For Update.
  5. With this lock in place, will I be able to delete a row from that table?
  4. What are the implications of this locking in a multi-user environment?

  Kindly Comment.  Thank you.
Followup:
1) it is a lock that prevents ddl -- you cannot have someone physically changing
that which you are currently actively using.  like you cannot compile a
procedure I am running.

2) it is the DEFINITION of the table, not the rows therein.

3) it is the DEFINITION of the table that is being protected -- not the rows
therein.

4) hey, I got a freebie!

5) yes.

4) bummer, it came back.  the implications of locking in a multi-user
environment. well, very simply:

o without locking -- you would not be able to have multi users
o with locking -- you can

so i guess its a good thing
Rating: 5
on delete cascade and unique keys March 31, 2003
Reviewer: Madhuri Bondre from India
i have a parent table with unique constraint (contains few null columns) this
unique key is refered by another table
(child table)
when i delete the parent key with few null columns
it does not delete the child table althogh relationship is on delete cascade.

is it a feature ??

if i want to delete the child rows for maching null columns also how can i do it
?

thanks in advance
Followup:
feature.  In fact, you probably didn't realize that you can put anything in the
child table you want when you use a composite column fkey constraint and one of
the columns is NULL.

The documentation is a little hazy on this:
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76965/c24integ.htm#2251
it describes the three cases ANSI allows us to persue -- however, it never
actually says "and this is the one we do do".  We do "match none".  consider:

ops$tkyte@ORA920> create table t1
  2  ( x int, y int,
  3   constraint t1_unq unique(x,y) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t2
  2  ( x int, y int,
  3    constraint t2_fk_t1 foreign key(x,y) references t1(x,y) ON DELETE CASCADE
);

Table created.

ops$tkyte@ORA920> insert into t1 values ( 1, null );
ops$tkyte@ORA920> insert into t1 values ( 1, 1 );

so, the parent table has two rows -- ONLY ONE of which can possibly be a
parent key in a parent child relationship.

Now, this child record is NOT associated with the parent at all
ops$tkyte@ORA920> insert into t2 values ( 1, null );
1 row created.

As evidenced by this insert, (2,null) goes right in

ops$tkyte@ORA920> insert into t2 values ( 2, null );
1 row created.

but note that there is in fact a foreign key

ops$tkyte@ORA920> insert into t2 values ( 2, 2 );
insert into t2 values ( 2, 2 )
*
ERROR at line 1:
ORA-02291: integrity constraint (OPS$TKYTE.T2_FK_T1) violated - parent key not
found

ops$tkyte@ORA920> insert into t2 values ( 1, 1 );
1 row created.

NULLS can really mess you up in a case like this (and many others)... You
might consider re-evaluating your use of null in a composite foreign key
relationship like this.
Rating: 5
What's behind the .......and many others... March 31, 2003
Reviewer: pasko from HH,Germany
Hi Tom,

Thanks for Great Demo showing the danger of having  Nullable Composite foreign
keys...

1.What are other side effects do you refer to ...

2.I remember to have read from some Book that nullable foreign keys should in
general be avoided , regardless of wether they are composite or not ...
--you have already demonstraated  to us about the Composite ones...what are the
Dangers of using nullable  simple foreign keys ....

3.Do we have same issues when we have simple Nullable foreign keys in a single
Table  referencing two separate tables ?

For instance :

create table parent1 ( id1 number primary key ,... ) ;
create table parent2 ( id2 number primary key ,... ) ;

create table  ref_table (
              idr  number primary key ,
           ref_p1  number ... references parent1( id1 )  ,
           ref_p2  number ... references parent2( id2 )  ,
           ---other columns here
) ;

Thanks in advance.
Followup:
1) queries gone "wrong" -- they are done right but give the "apparently wrong"
answer to the end user:

select * from t where x != 5;

they want everything not equal to 5 -- well, that should be:

where x != 5 or x is null;

queries going slower then then should,

select * from t where x NOT IN ( select nullable_column from t2 )

will run much slower (under CBO -- under RBO both run slow) then

select * from t where x not in ( select NOT_nullable_column from t2 )

2) nullable single column fkeys are perfectly OK.  When the fkey is null -- it
points to nothing.  If it has a value, it must point to something.

3) no, they are separate keys all together.
Rating: 5
Thanks Tom....and Response was really quick ..:)- March 31, 2003
Reviewer: pasko
Rating: 4
Functional unique index as foreign key April 16, 2003
Reviewer: Madhuri Bondre from India
Can I use functional unique index as foreign key reference for another table in
8i ?

Since i want to avoid reference of null columns to child tables. If I use docode
in unique index, will it help me?

will it be feasible ?

if yes then please guide.

thanks in advance
Followup:
No, you need a unique constraint/primary key.

I do not understand what is meant by:

Since i want to avoid reference of null columns to child tables...

Please elaborate on your concern (perhaps there is nothing to be concerned
about).   concern by example works best
Rating: 4
docode=decode April 16, 2003
Reviewer: madhuri bondre from india
i am sorry

docode = decode
Rating: 4
unique functional index with example April 17, 2003
Reviewer: Madhuri Bondre from India
here with showing with an example as you said

I have following tables

1. sales_order_item

CREATE TABLE sales_order_item
(
   so_no       number  (  6) not null,
   item_cd     varchar2(  6) not null,
   qty         number  (  6) not null,
   rate        number  (11,3) not null,
    .
    .
    .
    .
CONSTRAINT pky_sales_order_item
   PRIMARY KEY (so_no,
                item_cd)
);

2. production_plan_item

CREATE TABLE production_plan_item
(
plan_no     number   (  6)    not null,
item_cd     VARCHAR2 (  6)    not null,
so_no       number   (  6),
qty         number   (  6)     not null,
 .
 .
 .
 .
CONSTRAINT unq_production_plan_item
           UNIQUE  (plan_no,
                    item_cd,
                    so_no)
                    ),
CONSTRAINT fky_prod_plan_item__so_item
           FOREIGN KEY (so_no,
                        item_cd)
           REFERENCES sales_order_item
                       (so_no,
                        item_cd)
);

3. prodction_plan_item_batch

CREATE TABLE production_plan_item_batch
(
plan_no      number   (  6)    not null,
item_cd      VARCHAR2 (  6)    not null,
so_no        number   (  6),
batch        number   (   4)   not null,
qty         number    (  6)     not null,
CONSTRAINT unq_production_plan_item_batch
           UNIQUE  (plan_no,
                    item_cd,
                    so_no,
                    batch),
CONSTRAINT fky_prod_plan_item_bt__prod_plan_item
           FOREIGN KEY (plan_no,
                        item_cd,
                        so_no)
           REFERENCES   prod_plan_item
                        (plan_no,
                        item_cd,
                        so_no)
           ON DELETE CASCADE
);

Now, my problem is

1. on delete cascade will not work, with respect to
fky_prod_plan_item_dt__prod_plan_item
   when so_no is null
2. as per user requirement even null record is also going to appear only once.
   means avoiding the constraint unq_production_plan_item on
production_plan_item
   and creating new unique functional index as

CREATE UNIQUE INDEX unq_production_plan_item
ON production_plan_item
                (plan_no,
                 item_cd,
                 nvl(so_no,0));

how can I replace the following constraint using the unique functional index

CONSTRAINT fky_prod_plan_item_bt__prod_plan_item
           FOREIGN KEY (plan_no,
                        item_cd,
                        so_no)
           REFERENCES   prod_plan_item
                        (plan_no,
                        item_cd,
                        so_no)
           ON DELETE CASCADE

how to use nvl(so_no,0) in above constraint to use unique index
unq_production_plan_item ?

alter table white_item_bin
add constraint fky_white_item_bin
foreign key (item_cd,
             item_srno,
             warehouse_cd,
             nvl(so_yr,0),
             nvl(so_no,0))
references white_item_bin
          (item_cd,
           item_srno,
           warehouse_cd,
           nvl(so_yr,0),
           nvl(so_no,0)
           ON DELETE CASCADE);

when I try this, it shows error as

ERROR at line 7:
ORA-00907: missing right parenthesis

is there any way out to do this ?

I want to use unique functional index instead of unique constraint.
Followup:
1) it is not that it doesn't work -- it is that when there is NULL in a foreign
key -- it does not get related to ANY parent key.  It is an orphan from birth.
There is no key it can be related to in parent since NULL = NULL is not true and
NULL != NULL is not true.

2) you should default that column to 0 -- it obviously does not support missing
values (nulls).  This is an issue with the data model more then anything.
Rating: 5
One more related question April 17, 2003
Reviewer: Alla from Boston, MA USA
Tom;

What do we do when we have a table that is resolving a many-to-many relationship
and have a composite primary key from two tables (in terms of indexing foreign
keys)?

Example:

emp (emp_no PK, emp_name)
project (project_id PK, project_name)

Employee can work on multiple projects:
emp_project (emp_no, project_id, no_of_hours).

PK in emp_project would be (emp_no, project_id).

If I understand you correctly, PK will take care of the index for one of the
foreign keys (emp_no), since the first column there is emp_no and I'll have to
index only project_id to take care of the second foreign key

Please let me know what you think

Thanks in advance

Alla
Followup:
association objects, as this table is known as, generally need to "go both ways"

you would have an index on emp_no, project_id and an index on prject_id, emp_no.

might be time to look at an IOT:

ops$tkyte@ORA920> create table emp_project
  2  ( empno int, projectid int, primary key(empno,projectid) )
  3  organization index
  4  /

Table created.

ops$tkyte@ORA920> create unique index emp_project_idx on
emp_project(projectid,empno)
  2  /

Index created.

that'll just have two indexes and NO tables.  Normally -- you want the entire
associate object's row in the index to avoid table access by rowid steps in
plans -- since you only use this row to traverse from object to object.
Rating: 4
More about IOT April 17, 2003
Reviewer: Alla from Boston, MA USA
Thanks for you reply, Tom!

One more question though - how can I use an IOT when I need to keep some more
information in this table as well - no of hours an employee worked on the
project, not to mention audit fields, like create_ts, update_ts etc?

Thanks

Alla
Followup:
that all works.  If the data is small enough, you'll keep it right in the index
structure, if not, you'll keep it in an overflow segment.

If you have my book "Expert one on one Oracle" -- I cover this in lots more
detail -- but it is all available in the Oracle docs as well (overflow segments,
controlling what is stored in the index, what gets spilled over and so on)
Rating: 4
unique index and unique constraint April 18, 2003
Reviewer: Madhuri Bondre from India
hi tom,

Thanks for you reply.

as you said make so_no default 0, won't solve my problem

since it has fky reference with so_item. and there is no record in so_item with
0 no.

is there any difference between unique constraint and unique index ?

if I can use unique index to create fky reference, then why can't I use unique
functional index to create fky reference
as asked in example ?

does it compare value of the column or value of function with parent table ?

if I want to compare with value of the function how can I do that ?

am I doing something wrong ?

please help me.
Followup:
then -- you have even BIGGER problem....

You need a parent record there for that one. You quite simple DO NOT HAVE ONE.
Period....

A unique constraint is a CONSTRAINT.
A unique index is an INDEX.

This is like asking "is there any difference between a house and a car?"  They
are totally different beasts, nothing in common really.  Just like Cars have
seats, windows, carpeting --  houses normally have seats, windows, carpeting,
but they are totally different.

I think your model is wrong - you have partial fkeys with no parents.  You need
to have all NON-NULL columns in your fkeys for it to be a parent child
relationship.  The second you let NULLS in there -- no go.
Rating: 4
ora-00600 April 19, 2003
Reviewer: Madhuri Bondre from India
hi tom,

thanks for your reply.

Sorry to bother you again in this thread.

with reference to above example

I have created an unique functional index on parent table. converting null
columns to not null.

ex.
CREATE UNIQUE INDEX unq_production_plan_item
ON production_plan_item
                (plan_no,
                 item_cd,
                 nvl(so_no,0));

which has converted internally nvl(so_no,0) to sys_nc00051$

then I used that column to create fky reference for child table

as

alter table .... add
CONSTRAINT fky_prod_plan_item_bt__prod_plan_item
           FOREIGN KEY (plan_no,
                        item_cd,
                        so_no)
           REFERENCES   prod_plan_item
                        (plan_no,
                        item_cd,
                        sys_nc00051$)
           ON DELETE CASCADE;

it gives me following error.

    ERROR at line 1:
ORA-00600: internal error code, arguments: [kkdlgvx2], [], [], [], [], [], [],

and in ora-00600 it tells me to call customer support.

I have to use "functional unique index" to create foreign key reference, since I
can't use neither "unique constraint" nor "unique index" of parent table for the
above task.

I think, I have cleared my requirement.

pl.help me.

once again sorry to bother you.
Followup:
a FBI does not convert columns to NOT NULL.

SYS_NC00051$ does not exist, as the famous movies says "ignore that man behind
the curtain".

Don't bother calling support -- they'll just say "well, don't do that".

In order to accomplish what you want to accomplish -- I'll say it again -- so_no
would have to be NOT NULL for real

I hear your requirement
I've told you "isn't going to happen"

don't know what else to say....  so_no APPARENTLY should not be NULLABLE in that
parent table -- you've allowed it to be so.  Therefore you cannot get there from
here.
Rating: 3
thanks April 21, 2003
Reviewer: Madhuri Bondre from India
Thanks for your reply
Rating: 5
Question about script to detect unindexed foreign keys May 13, 2003
Reviewer: Arun Mathur from Marietta, GA
Tom,

I'm reading Chapter 3 from your book "Expert One-On-One Oracle", and am getting
lots of valuable information.  I tried running the script you provided, but got
the following error:

nvl2(cname3,','||cname3,null) || nvl2(cname4,','||cname4,null) ||
      *
ERROR at line 3:
ORA-00979: not a GROUP BY expression

I'm almost certain that it's something on my end, especially since your SQL
session is provided as an example, but I can't figure out where I went wrong.
Below is the script I took from p.110:

select table_name,constraint_name,
cname1||nvl2(cname2,','||cname2,null) ||
nvl2(cname3,','||cname3,null) ||
nvl2(cname4,','||cname4,null) ||
nvl2(cname5,','||cname5,null) ||
nvl2(cname6,','||cname6,null) ||
nvl2(cname7,','||cname7,null) ||
nvl2(cname8,','||cname8,null)
columns
from (select b.table_name,
        b.constraint_name,
        max(decode(position,1,column_name,null)) cname1,
        max(decode(position,2,column_name,null)) cname2,
        max(decode(position,3,column_name,null)) cname3,
        max(decode(position,4,column_name,null)) cname4,
        max(decode(position,5,column_name,null)) cname5,
        max(decode(position,6,column_name,null)) cname6,
        max(decode(position,7,column_name,null)) cname7,
        max(decode(position,8,column_name,null)) cname8,
        count(*) col_cnt
    from (select substr(table_name,1,30) table_name,
        substr(constraint_name,1,30) constraint_name,
        substr(column_name,1,30) column_name,
        position
        from user_cons_columns ) a,
        user_constraints b
        where a.constraint_name = b.constraint_name and
        b.constraint_type='R'
        group by b.table_name,b.constraint_name
    ) cons
    where col_cnt > ALL
        (select count(*)
        from user_ind_columns i
        where i.table_name = cons.table_name
        and i.column_name in (cname1,cname2,cname3,cname4,
                    cname5,cname6,cname7,cname8)
        and i.column_position <= cons.col_cnt
        group by i.index_name
        )
/

Thanks in advance, as always.
Followup:
stick:

            where a.constraint_name = b.constraint_name
              and b.constraint_type = 'R'
              and rownum > 0
            group by b.table_name, b.constraint_name
         ) cons

in there and let me know if that doesn't fix it.
Rating: 5
group by error in 9i May 13, 2003
Reviewer: John Hurley from Clevelahd, OH USA
The error noted below (by someone)

nvl2(cname3,','||cname3,null) || nvl2(cname4,','||cname4,null) ||
      *
ERROR at line 3:
ORA-00979: not a GROUP BY expression

Appears to start in oracle 9.0.1 and 9.2.0.1 but is fixed (thankfully) after
applying the patchset to 9.2.0.2.3
Rating: 5
The additional clause you provided fixed the script May 14, 2003
Reviewer: Arun Mathur from Marietta, GA
Thanks for your help. Also, before reading John Hurley's response that mentions
a bug, I downloaded your unindex script and used it without any problems. John,
thanks for your response as well.

Regards,
Arun
Rating: 5
Re: Lock on child Table July 02, 2003
Reviewer: A reader from Houston
Tom,
We are trying to find out why a certain deadlock is occuring in an
application(EJB based application) time and again on the same table.  I would
appreciate your input on the following

For deadlocks due to unindexed FK's
a) the SQL's behind the blocker and blockee are update/delete's(or
deletes/updates), no inserts? Yes/No - If No can you please explain
Reason: Even with the child table locked, we can still do an insert - right!!
b) deadlock don't effect insert statements(wrt unindexed FK's)?
Parent table P has 2 col's as PK's(col1, col2), these 2 col's are going to the
child table as fk's, an index is present on the child table for the FK.  ie.
index present as Index1 = (col1, col2).
c) Do I need to have an aditional index in the child table Index2 = (col2, col1)
for the deadlock not to occur? Yes/No

Thank you
Followup:
if (you UPDATE a parent primary key OR you DELETE a row in the parent table)
   AND (child tables fkey is not indexed)
then
   child table becomes locked -- no other session can insert/update/delete in
   the child table
end if;

(slightly modified in 9i but still an issue)

Index your foreign keys if you EVER

o update parents primary key
o delete from parent

the order of columns is not relevant, they need be on the leading edge is all.
That is, if the fkey is on C1, C2 an index on :

c1,c2,c3
c2,c1,c3

is OK, but

c3,c1,c2
c3,c2,c1

is not.
Rating: 5
Re: Lock on child Table July 02, 2003
Reviewer: A reader from Houston
Thank you

This helps a lot

>>child table becomes locked -- no other session can >>INSERT/update/delete in
>>   the child table
Rating: 5
Re: Lock on child Table July 02, 2003
Reviewer: A reader from Houston
Tom,

For a deadlock that is occuring, our DBA provided the following information.
The table wts has a composite fk and is indexed.  We are not sure why the
deadlock is occuring.  We are planning on requesting the trace file information
on the oracle database to find out the time the deadlock has occured.  Is there
any other information we get from the DB that can help us detect why the
deadlock is occuring?

Thank you

WAITING_SESSION
--------------------------------------------------------------------------------
LOCK_TYPE                  MODE_REQUESTED MODE_HELD      LOCK_ID1
-------------------------- -------------- -------------- ----------------------
LOCK_ID2
----------------------
110
None

   92
Transaction                Share          Exclusive      2359306
2008

PROCESS LOGON                SID  SERIAL ACT SRV USER_NAME        UGA
------- -------------------- ---- ------ --- --- ---------------- ---------
COMPUTER
----------------------------------------------------------------
55582   01 Jul 2003 09:51:47   92   3653 ACT DED GEN_USER        340096
houston.aabb.com
48166   01 Jul 2003 09:58:23  110   6118 INA DED GEN_USER        274688
houston.aabb.com

       SID SQL
---------- ----------------------------------------------------------------
        92 insert into WTS (P_ID, CT_
        92 ID, W_ID) VALUES (:1, :2, :3)
Followup:
not really knowing what I'm looking at (looks like the output of utllockt --
sesson 110 is blocking sesson 92, session 92 is attempting to insert into wts.
perhaps wts has a unique constraint and session 110 already inserted a row with
that value into wts and session 92 is blocked for that reason
Rating: 5
Re: Lock on child Table July 02, 2003
Reviewer: A reader from Houston
That meaans we have not got into a deadlock situation - right?  this is just one
session blocking the other - Is that correct?
Followup:
you were not deadlocked at that point, no.

it'll be highly unlikely to actually "observe" a deadlock using v$ tables as
they very quickly "un deadlock" themselves.
Rating: 5
Re: Lock on child Table July 02, 2003
Reviewer: A reader from Houston
The only wat to trace a deadlock is through the oracle trace files.  Is that
correct?
Followup:
yes
Rating: 5
Re: Lock on child Table July 02, 2003
Reviewer: A reader from Houston
The only way to trace a deadlock is through the oracle trace files.  Is that
correct?
>>they very quickly "un deadlock" themselves.
How does this happen.  We will have to manually kill one of the dead locked
sessions? won't we?
Followup:
one of the two sessions gets an ORA-60 "deadlock detected". it is never
necessary to kill a deadlock, they are self resolving.
Rating: 5
Re: Lock on child Table July 02, 2003
Reviewer: A reader from Houston
Tom,
1) Are all deadlocks self resolving?
Deadlocks with error messages ORA-60 and ORA-04020?

2) what are the reasons for the sessions to get blocked?  Missing commits?, Will
unIndexed FKs get us into blocking sessions?

3) From your earlier posting
>>you were not deadlocked at that point, no.
If the seesions 92 and 110 were deadlocked, what would be different in the
output below?

WAITING_SESSION
--------------------------------------------------------------------------------
LOCK_TYPE                  MODE_REQUESTED MODE_HELD      LOCK_ID1
-------------------------- -------------- -------------- ----------------------
LOCK_ID2
----------------------
110
None

   92
Transaction                Share          Exclusive      2359306
2008

PROCESS LOGON                SID  SERIAL ACT SRV USER_NAME        UGA
------- -------------------- ---- ------ --- --- ---------------- ---------
COMPUTER
----------------------------------------------------------------
55582   01 Jul 2003 09:51:47   92   3653 ACT DED GEN_USER        340096
houston.aabb.com
48166   01 Jul 2003 09:58:23  110   6118 INA DED GEN_USER        274688
houston.aabb.com

       SID SQL
---------- ----------------------------------------------------------------
        92 insert into WTS (P_ID, CT_
        92 ID, W_ID) VALUES (:1, :2, :3)

Thank you for your timely help
Followup:
1) yes.

2) there is no such thing as a "missing commit" -- if you have a missing commit,
you have a totally non-functional application.

unindex fks get you into block situations easily.

3) there would be no output actually - the deadlock is self resolving.  It is an
event that "happens" and is corrected.

you will find a trace file on the server.
Rating: 5
Re: Lock on child Table July 02, 2003
Reviewer: A reader from Houston
Tom,
I would really appreciate it if you can point me to the documentation that
mentions that deadlocks are self resolving.  I would like to give that as a
reference to our DBA.

Thank you again
Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96524/c21cnsis.htm#6037
right there in that handy dandy server concepts guide!
Rating: 5
Re: Lock on child Table July 02, 2003
Reviewer: A reader from Houston
Thank you
Rating: 4
PARENT to the CHILD & Driving Table July 03, 2003
Reviewer: Robert from PA
Quote >>
When you query from the PARENT to the CHILD.  Consider the EMP, DEPT
example again.  It is very common to query the EMP table in the context of a
deptno.  If you frequently query:

    select * from dept, emp
     where emp.deptno = dept.deptno and dept.deptno = :X;
<< End

Tom, you made the same point on this one in your book.

But I need a re-fresher on this. I'm not clear on exactly what is a "PARENT to
the CHILD  sql". (confused by the notion of "driving table")
--- is it because of the ORDER of the tables "from dept, emp" in your example ?
or the order in the WHERE clause ?

Please clarify the idea of Parent->Child, Child->Parent in light of the notion
of "driving table"

In the sample you gave above,
What's a Child->Parent query with EMP & DEPT ?
Followup:
The parent table is pretty clear -- it is the table WITHOUT the fkey.

dept has primary key deptno
emp has fkey deptno -> dept(deptno)

dept is the parent
emp is the child.

Here, the query predicate is phrased against DEPT - the optimizer will most
likely  choose DEPT as the driving table in this query under "normal"
circumstances (eg: dept has more then 4 measely rows!)  the query plan will be:

index access dept
  then index access emp

the DEPT table is the "driving" table in the query.

parent/child -- doesn't have anything to do with it -- in the query:

    select * from dept, emp
     where emp.deptno = dept.deptno and emp.ename = 'KING'

EMP would most likely be the driving table in the query plan.
Rating: 5
July 11, 2003
Reviewer: A reader
Rating: 5
cascade deletes on parent record July 16, 2003
Reviewer: Menon from ca
if i have a fk cascade delete relation (with appropriate
indexes) and if my parent record refers to multiple
child tables with lots of records - when I delete
the parent record and it is deleting child records -
does the insert, update and selects (without for update
clause) on the child table get affected. I think that Oracle just acquires a TM
lock for the delete transaction
to be over. This means that we cant issue DDL on the
parent and associated child tables but this in no way
blocks other inserts that may be happenning on
the child tables for example. Is this correct?

Thank you!
Menon:)
Followup:
if you have indexed the foreign keys in the child tables -- then simple row
level locking of the affected rows are all you need to be concerned about --
concurrent dml can take place.
Rating: 5
thanx! July 16, 2003
Reviewer: A reader from ca
that confirms what I was thinking (after having
read your book)

Regards
Menon:)
Rating: 5
please review this ..... August 25, 2003
Reviewer: Reader
Tom, in the emp table, If I have a composite primary key on empno and deptno
columns, will this composite pk index take care of locking issue with regard to
updating deptno in the dept table? I am using emp.deptno in the composite pk as
well as it refers to dept.deptno column as a referential integrity. Thanks.
Followup:
well, it would not make sense to have such a key (empno is the primary key) but
-- no, it would not be a good "fkey index"

deptno would have to be on the leading edge of the index -- the primary key
would have to be (DEPTNO, EMPNO) and the index would have to be in that specific
order.

since you probably need EMPNO on the leading edge for querying -- you need
another index on deptno if you ever

o update DEPT.DEPTNO
o delete from DEPT

in order to avoid locking contention on the child (EMP) table.
Rating: 4
good August 26, 2003
Reviewer: nitin from india
good note
Rating: 4
very good August 28, 2003
Reviewer: A reader from Paradise, P.R.China
Rating: 3
Lock on child Table May 22, 2002 August 28, 2003
Reviewer: A reader
Lock on child Table  May 22, 2002
Reviewer:  kiro  from Bulgaria

Hi Tom
I have tried to reproduce lock on child table on 9.0.1 but I could not. Is it
fix or not. See example.
test> create table b (id int primary key);

Table created.

test> create table b1 (id references b);

Table created.

test>  insert into b values(1);

1 row created.

test> insert into b values(2);

1 row created.

test> commit;

Commit complete.

test> update b set id = 10 where id=1;

1 row updated.

 --- Here I suppose that table b1 is locked

 From another session I tried to insert into child table b1 and I did it.

test1> insert into b1 values(2);

1 row created.

test1> commit;

Commit complete.

test1> select * from b1;

        ID
----------
         2

Again frist session

test> commit;

Commit complete.

test> select * from b;

        ID
----------
        10
         2

Please comment this.
Thank you.

----------------
I call same SQL in my system (817+win2000server), but I cannot insert b1
anymore, I really don't understand the explanation by TOM.
Followup:
things change.  in 9i, the lock is tell there -- it is just taken for a shorter
duration.

to see it:

-----------------------------------------------------------
drop table c;
drop table p;

set echo on

create table p ( x int primary key );
create table c ( x references p );

insert into p values ( 1 );
insert into p values ( 2 );
insert into c values ( 2 );
commit;

insert into c values ( 2 );

set echo off
prompt in another session execute:
prompt delete from p where x = 1;;
prompt then come back here and hit enter, that session will block
pause

prompt now, in another session execute
prompt insert into c values ( 2);;
prompt that too will block.
pause

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

in 9i, the full table lock on C is taken by an update or delete to P when the
fkey is unindexed just long enough for P to check out C and then released.  It
does not have to wait for a commit on P to release the lock.  that is the
enhancement from 8i to 9i
Rating: 3
ORA-04021 - timeout occurred while waiting to lock object December 31, 2003
Reviewer: Kiran Shah from India
Tom,
I hope I am putting my question in proper thread...
Because while searching this site for 'timeout occurred while waiting to lock
object' I am getting this page...

Now, about our problem...We are using Oracle 9i DB and when we use ALTER command
we get following error :
ORA-04021 - timeout occurred while waiting to lock object abc.table1.

This error occurs for every table. However when we shutdown the database and
again re-start it and immediately do this ALTER command, then this works fine.

Pl. give guideline to overcome this problem.
Followup:
what kind of alters -- give us a cut and paste of the full error stack and tell
us about the object and whats going on in the database at that time.
Rating: 3
Sorry for not giving full info... January 01, 2004
Reviewer: Kiran Shah from India
Tom,

We are using ALTER TABLE TABLE1 DROP COLUMN COLUMN1
command in SQL Plus.

At this juncture it seems to just hang. After about 10 minutes it gives
following message. Right now we are developing our application. So no user is
actually connected with database. And this particular table is also not being
used any where while this command is given. Following is cut & paste of SQL Plus
window...

  1  alter table randpregister
  2* drop column detailsofrandp
SQL> /
alter table randpregister
*
ERROR at line 1:
ORA-04021: timeout occurred while waiting to lock object CCIS.RANDPREGISTER
Followup:
ok, show us the nicely formatted output of:

alter table randpregister drop column detailsofrandp;

select *
  from v$access
 where owner = 'CCIS' and object = 'RANDPREGISTER' and type = 'TABLE';

select *
  from v$db_object_cache
 where owner = 'CCIS' and name = 'RANDPREGISTER' and type = 'TABLE';

and while the alter is blocked -- run this query and see what it says:

select /*+ ordered */ w1.sid  waiting_session,
    h1.sid  holding_session,
    w.kgllktype lock_or_pin,
        w.kgllkhdl address,
    decode(h.kgllkmod,  0, 'None', 1, 'Null', 2, 'Share', 3, 'Exclusive',
       'Unknown') mode_held,
    decode(w.kgllkreq,  0, 'None', 1, 'Null', 2, 'Share', 3, 'Exclusive',
       'Unknown') mode_requested
  from dba_kgllock w, dba_kgllock h, v$session w1, v$session h1
 where
  (((h.kgllkmod != 0) and (h.kgllkmod != 1)
     and ((h.kgllkreq = 0) or (h.kgllkreq = 1)))
   and
     (((w.kgllkmod = 0) or (w.kgllkmod= 1))
     and ((w.kgllkreq != 0) and (w.kgllkreq != 1))))
  and  w.kgllktype     =  h.kgllktype
  and  w.kgllkhdl     =  h.kgllkhdl
  and  w.kgllkuse     =   w1.saddr
  and  h.kgllkuse     =   h1.saddr
/

and check out support note <Note:169139.1> on metalink.oracle.com
Rating: 3
Query output.... January 03, 2004
Reviewer: Kiran Shah from India
Tom,
We run "alter table randpregister drop column detailsofrandp" in one Sqlplus
session and simultaneously open another session of pl/sql and run three queries.

Following is the result we got while running 3 queries given by you.

SQL> select *
  2    from v$access
  3   where owner = 'CCIS' and object = 'RANDPREGISTER' and type = 'TABLE';

no rows selected

SQL>
SQL> select substr(owner,1,5) owner,substr(name,1,15) name,substr(db_link,1,10)
db_link,
  2  namespace,type,sharable_mem,loads,executions,locks,pins,kept
  3  from v$db_object_cache
  4  where owner = 'CCIS' and name = 'RANDPREGISTER' and type = 'TABLE' ;

Result of above query : (Edited...)
OWNER : CCIS
NAME  : RANDPREGISTER
DB_LINK  :
NAMESPACE : TABLE/PROCEDURE
TYPE : TABLE
SHARABLE_MEM  : 19814
LOADS : 1
EXECUTIONS : 0
LOCKS  : 1
PINS : 2
KEPT : NO

SQL>
SQL> select /*+ ordered */ w1.sid  waiting_session,
  2      h1.sid  holding_session,
  3      w.kgllktype lock_or_pin,
  4          w.kgllkhdl address,
  5      decode(h.kgllkmod,  0, 'None', 1, 'Null', 2, 'Share', 3, 'Exclusive',
  6         'Unknown') mode_held,
  7      decode(w.kgllkreq,  0, 'None', 1, 'Null', 2, 'Share', 3, 'Exclusive',
  8         'Unknown') mode_requested
  9    from dba_kgllock w, dba_kgllock h, v$session w1, v$session h1
 10   where
 11    (((h.kgllkmod != 0) and (h.kgllkmod != 1)
 12       and ((h.kgllkreq = 0) or (h.kgllkreq = 1)))
 13     and
 14       (((w.kgllkmod = 0) or (w.kgllkmod= 1))
 15       and ((w.kgllkreq != 0) and (w.kgllkreq != 1))))
 16    and  w.kgllktype     =  h.kgllktype
 17    and  w.kgllkhdl     =  h.kgllkhdl
 18    and  w.kgllkuse     =   w1.saddr
 19    and  h.kgllkuse     =   h1.saddr
 20  /

WAITING_SESSION HOLDING_SESSION LOCK ADDRESS  MODE_HELD MODE_REQU

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

             11               8 Pin  70A97624 Share     Exclusive

             11               8 Pin  70A97624 Share     Exclusive

SQL>
SQL>
SQL> spool off

I hope this is as per your requirement.

Thanks for your support...
Followup:
so, now all you have to do is look at sid=8 (or whatever sid pops up next time)
and see what they are doing.

They are the one holding the pin on the object that is preventing you from
modifying it right now.

There IS a session using that object, contrary to what you believe and there is
the "evidence".
Rating: 4
o.k. January 05, 2004
Reviewer: Kiran Shah from India
ok. Tom, now onwards I will look which session is holding particular object.
This is really new thing, I learnt now. Thanks for your guidelines...
Rating: 5
Timeout time February 06, 2004
Reviewer: Andres from Estonia
Kiran Shah wrote above:

/---/
After about 10 minutes it gives
following message.
/---/
  1  alter table randpregister
  2* drop column detailsofrandp
SQL> /
alter table randpregister
*
ERROR at line 1:
ORA-04021: timeout occurred while waiting to lock object CCIS.RANDPREGISTER

---------

can this "about 10 minutes" be set to something shorter, eg 10 seconds? There is
a system parameter DISTRIBUTED_LOCK_TIMEOUT, but this is for distributed row
locking, right? Can't find a parameter as LOCK_TIMEOUT, for example.. (9.2.0.4)
Followup:
no, it cannot.
Rating: 5
deadlock and FKs February 07, 2004
Reviewer: A reader
Hi

if we dont index FKs and update parent tables can this cause deadlocks?

How can I reproduce this?
Followup:
if you update a parent tables primary key OR you delete from parent
then
   if you have 8i and before
      a full table lock is taken and held on the child table
      for the duration of the transaction
   else if you have 9i and above
      a full table lock is taken on the child table
      for the duration of the statement that does the update/delete (and
      for as long as it blocks)
   end if;
end

armed with that knowledge -- setting up a testcase is easily done.  for 9i, all
you need is some shared resource that two sessions might go after, and then one
session that updates a parent primary key(or deletes) and another session that
will update a totally unrelated child record.  If both try to get the shared
resource -- bam, deadlock.

drop table c;
drop table p;
drop table some_other_shared_resource;
set echo on

create table p ( x int primary key );
create table c ( a int primary key, x references p );
create table some_other_shared_resource (  x int );

insert into some_other_shared_resource values ( 1 );

insert into p values ( 1 );
insert into p values ( 2 );
insert into c values ( 1, 1 );
insert into c values ( 2, 1 );
insert into c values ( 3, 2 );
insert into c values ( 4, 2 );
commit;

prompt in another session update c set a = a where a = 3
pause

update some_other_shared_resource set x = 1;
prompt this will block, when it does go back to the other session
prompt and update some_other_shared_resource set x = 1;
update p set x = x where x=1;
Rating: 5
last example does not work? February 09, 2004
Reviewer: A reader
Hi

trying your last example using emp, dept and x tables

session 1

update dept set deptno = 10 where deptno = 10;

1 row updated.

session 2
update x set a = 1 where a = 1;

1 row updated.

session 1

update x set a = 1 where a = 1;

blocked.........

session 2

update emp set deptno = 20 where deptno = 10;

3 rows updated.

I thought the last update would give deadlock?
Followup:
i did not study your example (since it is incomplete -- no table creates)

how's about you use my example -- which definitely has primary/foreigng keys and
no index on the fkeys (things emp/dept could well be lacking/having) and let us
know....
Rating: 4
sorry February 09, 2004
Reviewer: A reader
Hi

I am sorry, it works, just that I must update Child first then Parent
Rating: 2
Sql Query Timeout? February 17, 2004
Reviewer: Amar Desai from India
Hi Tom,

We are using Oracle 8i on Windows 2000/XP. Is it possible to restrict query, If
execution of that query is going to take xx seconds/minutes? By setting any
Server para or any other utility.

Some database have query timeout, So If query doesn't execute/fetch any records
for say 300 seconds, Server will throw timeout error. Is this possible to
implement in Oracle?

Please reply.

Thanks & Regards,

Amar Desai
Followup:
In 9i we can do this proactively (it'll estimate how long the query is to run
for and based on that run it -- or not)

In all releases (10g and before) we have resource profiles which reactively
cancel long running queries.

So yes, we have this.  See
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76956/users.htm#1148
(the admin guide, a great place to start to discover things like this...)
Rating: 4
Query timeout February 19, 2004
Reviewer: Amar from India
Thanks Tom. I found that Parse/Execution/fetch can limit using Resource Limit
Parameters (LIMIT CPU_PER_CALL) of User Profiles.

If user execeeds limit of CPU time. Error appears "ORA-02393 exceeded call limit
on CPU usage"

But how can we do this proactively in 9i? Could you please explain us?

Thanks & Regards,

Amar
Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96521/part5.htm#435958
ops$tkyte@ORA920PC> @test
=================================================================

Begin by creating the resource plan.  It limits statements to
1 second in duraction for members of the plan...

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> 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.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> 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

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> drop user low_priority cascade;
drop user low_priority cascade
          *
ERROR at line 1:
ORA-01918: user 'LOW_PRIORITY' does not exist

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create user low_priority identified by x;

User created.

ops$tkyte@ORA920PC> grant connect, resource to low_priority;

Grant succeeded.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> 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@ORA920PC>
ops$tkyte@ORA920PC> set echo off
Enter to continue

=================================================================

Now, we need to make the plan active

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> alter system set resource_manager_plan = est_max_time scope
= memory;

System altered.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> connect low_priority/x
Connected.
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> 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@ORA920PC>
low_priority@ORA920PC> create table t as select * from all_objects;

Table created.

low_priority@ORA920PC>
low_priority@ORA920PC> analyze table t compute statistics;

Table analyzed.

low_priority@ORA920PC>
low_priority@ORA920PC> select count(*) from t;

  COUNT(*)
----------
     24673

low_priority@ORA920PC>
low_priority@ORA920PC> select count(*) from t,t;
select count(*) from t,t
                     *
ERROR at line 1:
ORA-07455: estimated execution time (15614 secs), exceeds limit (1 secs)

low_priority@ORA920PC>
low_priority@ORA920PC> set echo off
Enter to continue

=================================================================

Lastly, we'll put things back the way they were

low_priority@ORA920PC>
low_priority@ORA920PC> connect demo/demo
Connected.
low_priority@ORA920PC> set echo off
demo@ORA920PC>
demo@ORA920PC> alter system set resource_manager_plan = '' scope = memory;

System altered.
demo@ORA920PC>
demo@ORA920PC> begin
  2          dbms_resource_manager.create_pending_area;
  3      dbms_resource_manager.delete_plan( 'est_max_time' );
  4          dbms_resource_manager.delete_consumer_group( 'demo' );
  5          dbms_resource_manager.validate_pending_area;
  6          dbms_resource_manager.submit_pending_area;
  7  end;
  8  /

PL/SQL procedure successfully completed.

demo@ORA920PC>
demo@ORA920PC> drop user low_priority cascade;

User dropped.
February 19, 2004 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.
Rating: 4
Where can I get list of all exceptions??? February 19, 2004
Reviewer: A reader
Followup:

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96624/07_errs.htm#784
February 19, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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)
Rating: 4
db_cache_size for data warehouse env. February 19, 2004
Reviewer: A reader
Hi Tom,
Recently, we installed oracle 9ir2 with data warehouse configuration on AIX box.
The default setting was 16MB for db_cache_size. I can change it, but my question
is, initially I am loading a huge amount of data (ranging from 2 GB to 10GB),
which I loaded, and created indexes. Now I need to create an ASCII file through
the ETL tool. I wrote a sql query, which I can use in ETL tool to create files,
but the query is taking a huge amount of time. What workable size of initial
parameters should I start with, as I have a time constraint to get the results
ASAP – without wasting time on trying various values. The  box has 8GB of memory
and 3 CPUs.
Thanks
Followup:
alot bigger than 16meg certainly.

I cannot answer the question 100% -- but 512m to 1gig would be "a good starting
point" perhaps.
Rating: 4
Thanks a lot! February 19, 2004
Reviewer: A reader
February 19, 2004 -- 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.
Rating: 5
work immediately after compilation February 05, 2004
Reviewer: August Groh from Munich, Germany
I start my search for an example of using HTML in the utl_smtp package at
METALINK. No success

Remembering "ask Tom" I found this article.

After 15 minutes I was able to  send mails in HTML format.

Many thanks for this article
Rating: 5
February 12, 2004
Reviewer: Richard from Washington
Hi Tom,

How are you. Thank you very much for your previous help. This is urgent. I got
the error numeric or value error, I think the problem is that html content
(using plsql variable) exceeds 32k in production, please advise what is the
workaround in order to send html content email (greater than 32k).  I am using
this html send functionality.

Thank you very much for help.

Richard
Followup:
you'll have to change p_text from a simple varchar2 type into a plsql table type
and send an "array" of text to transmit (changing the send mail routine to
iterate over the array of strings and utl-smtp each one) -- or pass a CLOB
instead of a varchar2 and dbms_lob.substr through it all and have utl-smtp send
each chunk.
Rating: 5
The follow-up of over 32k html content. February 12, 2004
Reviewer: Richard from WASHINGTON
Hi Tom,
Thank you very much for your quick response. This is the follow-up.
We only concern about html content over 32k (ignore text).  Please refer to the
original html_email codes on the site.

I am thinking

1.  l_temp  varchar2(32767);  -- No need to change.
2.   Instead of doing
dbms_lob.write(l_body_html,length(p_html),l_offset,p_html);
We should change to
dbms_lob.copy(l_body_html, p_lob_html,dbms_lob.getlength(p_lob_html), l_offset,
1);

3.   dbms_lob.substr() had been done by the original program, because it sends
1900 chunk by chunk.

I try to confirm that Item 2 is the only thing I need to do to send html content
over 32k.

Please confirm and advise.

Thank you very much for help.

Richard
Followup:
that is not the only change - you need to change p_html to a clob everywhere and
change whatever else might need changing (sort of a simple change in all
probability).  that looks "right" but I haven't tested it (me, i would be
sending an email with a LINK to a big page cause I despise getting big emails
personally!  32k exceeds my threshold for pain.)
Rating: 5
February 13, 2004
Reviewer: Richard from Washington
Hi Tom,

Thank you very much for help.  The issue gets resolved (I have to change
multiple places outside html_email to push string here and there into a clob and
then pass into html_email.

Thank you very much and have great weekend.

Richard
Rating: 5
HTML Email February 13, 2004
Reviewer: Ruben
Tom, what is the meaning of value 'a1b2c3d4e3f2g1' for
the variable l_boundary  varchar2(255)?
Because when I set to other value such as '5555555555', the procedure
funcionality works fine. Why?

Thanks a lot.
Followup:
it is some unique string that will not appear in the email itself.  It is a
"boundary", a demarcation string, something to look for to say "at the end"
Rating: 4
Swedish characters February 18, 2004
Reviewer: rd from sweden
Hi TOM,

I use your HTML_MAIL code.
I replaced "charset=us-ascii" with charset=iso-8859-1'.
Still I fail to get swedish characters in the mail.

Pls advise.

TIA,
Rd
Followup:
utl_smtp sends 7bit ascii by default

you'll have to use the "raw" interface.  check out the supplied packages guide
(available on otn if you don't have one).
Rating: 5
clob to raw !! February 19, 2004
Reviewer: rd from Sweden
TOM,

Thanks for the response.
Now my problem( could be dumb one) is can i use the same  code with clob
converted into raw(pls suggest if there is anyway at all) and then use
UTL_SMTP.WRITE_RAW_DATA?

I am pretty new to RAW/CLOB data.

TIA,
Rd
Followup:
utl_raw.cast_to_raw( dbms_lob.substr( clob data ) );

sure.
February 19, 2004 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...

:)
Rating: 5
What about a PL/SQL block in an Execute Immediate in 8.1.7 February 19, 2004
Reviewer: Phil Stanworth from England
My Package is a generic data copy engine and works by generating a dynamic
pl/sql block to validate and copy the data.  It builds the dynamic block in
several VARCHAR2(32767) items, and runs by using EXECUTE IMMEDIATE on a
concatenation of them.  When I run it for a table having 376 columns, it fails
with PLS-00123.  However, if I capture the generated block to a file and compile
it as a Procedure, it compiles OK and show a Parsed_Size of 249367 (Source_Size
is 302472 & Code_Size is 256636).  My question is: Why does it behave
differently when it is a dynamic block from when it is a procedure?  Are the
limits known to be different in these two cases?
Followup:
see support note  <Note:62603.1> on metalink.

I would suggest you use dbms_sql to parse code over 32k in size.  You can pass a
plsql table type -- with each "array element" being "a line of code".

It'll be lots safer than concatenating big strings.
February 19, 2004 Dan -- Thanks for the question regarding "SQL questions involving combinations", version 8.1.7.1
You Asked
Dear Tom...

I have two questions that are somewhat related to each other in concept:

1) Consider the following table which represents a state history per each person
identified by PERSON_ID:

SQL> CREATE TABLE status_history
  2         (person_id     NUMBER,
  3          status_code   VARCHAR2(1),
  4          status_date   DATE)
  5  /

Table created.

SQL> INSERT INTO status_history
  2  SELECT 1, 'A', TO_DATE('01-JAN-2002', 'DD-MON-YYYY') FROM DUAL
  3  UNION SELECT 1, 'B', TO_DATE('02-JAN-2002', 'DD-MON-YYYY') FROM DUAL
  4  UNION SELECT 1, 'C', TO_DATE('03-JAN-2002', 'DD-MON-YYYY') FROM DUAL
  5  UNION SELECT 2, 'A', TO_DATE('03-JAN-2002', 'DD-MON-YYYY') FROM DUAL
  6  UNION SELECT 2, 'C', TO_DATE('05-JAN-2002', 'DD-MON-YYYY') FROM DUAL
  7  UNION SELECT 2, 'B', TO_DATE('06-JAN-2002', 'DD-MON-YYYY') FROM DUAL
  8  UNION SELECT 3, 'B', TO_DATE('02-JAN-2002', 'DD-MON-YYYY') FROM DUAL
  9  UNION SELECT 3, 'A', TO_DATE('07-JAN-2002', 'DD-MON-YYYY') FROM DUAL
 10  UNION SELECT 3, 'C', TO_DATE('08-JAN-2002', 'DD-MON-YYYY') FROM DUAL
 11  UNION SELECT 4, 'B', TO_DATE('01-JAN-2002', 'DD-MON-YYYY') FROM DUAL
 12  UNION SELECT 4, 'A', TO_DATE('05-JAN-2002', 'DD-MON-YYYY') FROM DUAL
 13  UNION SELECT 4, 'C', TO_DATE('08-JAN-2002', 'DD-MON-YYYY') FROM DUAL
 13  /

12 rows created.

SQL> SELECT * FROM status_history
  2  ORDER BY person_id, status_date
  3  /

PERSON_ID S STATUS_DA
--------- - ---------
        1 A 01-JAN-02
        1 B 02-JAN-02
        1 C 03-JAN-02
        2 A 03-JAN-02
        2 C 05-JAN-02
        2 B 06-JAN-02
        3 B 02-JAN-02
        3 A 07-JAN-02
        3 C 08-JAN-02
        4 B 01-JAN-02
        4 A 05-JAN-02
        4 C 08-JAN-02

12 rows selected.

My question is: how does one determine all possible sets of state changes? In
other words, the desirable result is something like the following:

COMBO   STATUS_CODE
-----   -----------
    1             A
    1             B
    1             C

    2             A
    2             C
    2             B

    3             B
    3             A
    3             C

2) Consider the following table which holds attributes for things:

SQL> CREATE TABLE thing_attributes
  2         (thing_id         NUMBER,
  3          attribute_code   VARCHAR2(1))
  4  /

Table created.

SQL> INSERT INTO thing_attributes
  2  SELECT 1, 'A' FROM DUAL
  3  UNION SELECT 1, 'B' FROM DUAL
  4  UNION SELECT 2, 'B' FROM DUAL
  5  UNION SELECT 2, 'C' FROM DUAL
  6  UNION SELECT 2, 'D' FROM DUAL
  7  UNION SELECT 3, 'A' FROM DUAL
  8  UNION SELECT 3, 'D' FROM DUAL
  9  UNION SELECT 4, 'D' FROM DUAL
 10  /

8 rows created.

SQL> SELECT * FROM thing_attributes
  2  /

 THING_ID A
--------- -
        1 A
        1 B
        2 B
        2 C
        2 D
        3 A
        3 D
        4 D

8 rows selected.

Each thing can have at least one and no more than four of the attributes 'A',
'B', 'C', or 'D'.

Now consider the following table which contain things belonging to persons:

SQL> CREATE TABLE belongings
  2         (person_id   NUMBER,
  3          thing_id    NUMBER)
  4  /

Table created.

SQL> INSERT INTO belongings
  2  SELECT 100, 1 FROM DUAL
  3  UNION SELECT 100, 2 FROM DUAL
  4  UNION SELECT 100, 3 FROM DUAL
  5  UNION SELECT 100, 4 FROM DUAL
  6  UNION SELECT 200, 1 FROM DUAL
  7  UNION SELECT 200, 2 FROM DUAL
  8  UNION SELECT 200, 4 FROM DUAL
  9  /

7 rows created.

SQL> SELECT * FROM belongings
  2  /

PERSON_ID  THING_ID
--------- ---------
      100         1
      100         2
      100         3
      100         4
      200         1
      200         2
      200         4

7 rows selected.

Now the question is: how does one determine all the possible combinations of
attributes that a person's collection of "things" can have, choosing only one at
a time? In other words, for person 200, the ideal answer would be something like
this:

COMBO THING_ID ATTRIBUTE_CODE
----- -------- --------------
    1        1              A
    1        2              B
    1        4              D

    2        1              B
    2        2              B
    2        4              D

    3        1              A
    3        2              C
    3        4              D

    4        1              B
    4        2              C
    4        4              D

    5        1              A
    5        2              D
    5        4              D

    6        1              B
    6        2              D
    6        4              D

To clarify, since there is only one attribute for thing 4, and there are three
for thing 2 and two for thing 1, the total number of combinations should be 6.

Can either of these thing be accomplished using pure Oracle SQL, or does this
sort of thing require PL/SQL or Java?

Many thanks in advance,

dan kefford
and we said...
1) analytics can be VERY useful here:

ops$tkyte@ORA920.US.ORACLE.COM> select distinct rtrim(state_change,'-')
  2    from (
  3  select person_id,
  4         max(decode(rn,1,status_code,null)) ||'-'||
  5         max(decode(rn,2,status_code,null)) ||'-'||
  6         max(decode(rn,3,status_code,null)) ||'-'||
  7         max(decode(rn,4,status_code,null)) ||'-'||
  8         max(decode(rn,5,status_code,null)) ||'-'||
  9         max(decode(rn,6,status_code,null)) ||'-'||
 10         max(decode(rn,7,status_code,null)) ||'-'||
 11         max(decode(rn,8,status_code,null))  state_change
 12    from ( select person_id, status_code,
 13                  row_number() over ( partition by person_id
 14                                      order by status_date ) rn
 15             from status_history
 16         )
 17   group by person_id
 18         )
 19  /

RTRIM(STATE_CHA
---------------
A-B-C
A-C-B
B-A-C

ops$tkyte@ORA920.US.ORACLE.COM>

for example (doesn't make sense to pull along person_id, we could -- but it
isn't meaningful at all)

2) that will, i believe (it hurts my head to think of doing this in SQL),
require a little bit of procedural processing.

this'll be one of those rare cases where you want to say "forget SQL for a
minute, lets just program it"
Review & Followup

Rating: 4
excellent September 22, 2002
Reviewer: A reader
excellent codes.
possible problem is: if times of status change is over 8, you have to modify
this sql.
Followup:
Yes, i neglected to point that out -- the number of status changes must be
finite and known.
Rating: 5
Always something usefull September 22, 2002
Reviewer: Chuck Jolley from Oklahoma City, OK USA
row_number() !?

I guess we now have a full set:
row_number()
dense_rank()
and rank()

Very helpfull!
Thanks,
Chuck
Rating: 4
analytics make my head hurt :( February 19, 2004
Reviewer: pete
Tom,

Hope that you can help with this - it is sort of similar to the first post, but
I am having real difficulty trying to ammend it for my requirements!

I have a table like this:
create table mgroup (id number, mod varchar2(8), value number);

with dome data like this:
insert into mgroup values (1, 'A', 15);
insert into mgroup values (1, 'B', 15);
insert into mgroup values (1, 'C', 15);
insert into mgroup values (1, 'D', 15);

What I need to do is get a listing of all the possible combinations of "mod"
within a particular id which have a combined sum of "value" equal to a given
number. (note: the value column could vary and would not always be the same for
each row within an id)

eg for the examples above, if the number was 15 then the results would be:
A
B
C
D

if the number was 45 the results would be:
ABC
ABD
ACD
BCD

if the number was 60 then the results would be:
ABCD

thanks,
Pete
Followup:
don't think you can do this in sql (sort of like their 2cnd part of the
question).

well, you can -- but boy would it be the most expensive query in the world
(involves this cartesian semi-join thing to get every combo).
Rating: 4
How about this... February 19, 2004
Reviewer: Padders from UK
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> CREATE TABLE mgroup (
  2    id NUMBER, mod VARCHAR2 (8), value NUMBER);

Table created.

SQL> INSERT INTO mgroup VALUES (1, 'A', 15);

1 row created.

SQL> INSERT INTO mgroup VALUES (1, 'B', 15);

1 row created.

SQL> INSERT INTO mgroup VALUES (1, 'C', 15);

1 row created.

SQL> INSERT INTO mgroup VALUES (1, 'D', 15);

1 row created.

SQL> COLUMN id FORMAT 99
SQL> COLUMN combo FORMAT A5
SQL> SELECT   a.id, REPLACE (a.combo, '#') combo
  2  FROM    (SELECT id, SYS_CONNECT_BY_PATH (mod, '#') || '#' combo
  3           FROM   mgroup
  4           CONNECT BY PRIOR id = id AND mod > PRIOR mod) a, mgroup b
  5  WHERE    b.id = a.id AND INSTR (a.combo, '#' || b.mod || '#') > 0
  6  GROUP BY a.id, a.combo
  7  HAVING   SUM (b.value) = 15;

 ID COMBO
--- -----
  1 A
  1 B
  1 C
  1 D

SQL> SELECT   a.id, REPLACE (a.combo, '#') combo
  2  FROM    (SELECT id, SYS_CONNECT_BY_PATH (mod, '#') || '#' combo
  3           FROM   mgroup
  4           CONNECT BY PRIOR id = id AND mod > PRIOR mod) a, mgroup b
  5  WHERE    b.id = a.id AND INSTR (a.combo, '#' || b.mod || '#') > 0
  6  GROUP BY a.id, a.combo
  7  HAVING   SUM (b.value) = 30;

 ID COMBO
--- -----
  1 AB
  1 AC
  1 AD
  1 BC
  1 BD
  1 CD

6 rows selected.

SQL> SELECT   a.id, REPLACE (a.combo, '#') combo
  2  FROM    (SELECT id, SYS_CONNECT_BY_PATH (mod, '#') || '#' combo
  3           FROM   mgroup
  4           CONNECT BY PRIOR id = id AND mod > PRIOR mod) a, mgroup b
  5  WHERE    b.id = a.id AND INSTR (a.combo, '#' || b.mod || '#') > 0
  6  GROUP BY a.id, a.combo
  7  HAVING   SUM (b.value) = 45;

 ID COMBO
--- -----
  1 ABC
  1 ABD
  1 ACD
  1 BCD

SQL> SELECT   a.id, REPLACE (a.combo, '#') combo
  2  FROM    (SELECT id, SYS_CONNECT_BY_PATH (mod, '#') || '#' combo
  3           FROM   mgroup
  4           CONNECT BY PRIOR id = id AND mod > PRIOR mod) a, mgroup b
  5  WHERE    b.id = a.id AND INSTR (a.combo, '#' || b.mod || '#') > 0
  6  GROUP BY a.id, a.combo
  7  HAVING   SUM (b.value) = 60;

 ID COMBO
--- -----
  1 ABCD

SQL>
Followup:
yeah, thats that nasty cartesian semi join happening.

very nifty solution though, elegant.  don't know if I would have thought of
connect by in that context - very neat.
Rating: 5
fantastic February 20, 2004
Reviewer: pete
Padders, Tom - this is exactly what I was afterm, extra team points all round -
cheers!
Rating: 5
Thank you Padders and Tom! March 04, 2004
Reviewer: Dan Kefford from New Haven, CT
O.K... retrofitting Padders' approach to the first problem that I originally
proposed, I arrived at:

SQL> column status_change_path format a30
SQL> WITH all_paths AS
  2  (
  3  SELECT  iv1.person_id,
  4          LTRIM(SYS_CONNECT_BY_PATH (iv1.status_code, '->'), '->')
status_change_path,
  5          level path_length
  6  FROM
  7  (
  8  SELECT  h1.person_id,
  9          h1.status_code,
 10          h1.status_date status_date,
 11          LAG(h1.status_date) OVER (PARTITION BY h1.person_id ORDER BY
h1.status_date) prev_status_date
 12  FROM    status_history h1
 13  ) iv1
 14  START WITH iv1.prev_status_date IS NULL
 15  CONNECT BY iv1.person_id        = PRIOR iv1.person_id
 16  AND        iv1.prev_status_date = PRIOR iv1.status_date
 17  )
 18  SELECT  iv3.status_change_path,
 19          COUNT(*)
 20  FROM
 21  (
 22  SELECT  iv2.person_id,
 23          iv2.status_change_path
 24  FROM    all_paths iv2
 25  WHERE  iv2.path_length =
 26         (SELECT  MAX(iv3.path_length)
 27          FROM    all_paths iv3
 28          WHERE   iv3.person_id = iv2.person_id)
 29  ) iv3
 30 GROUP BY iv3.status_change_path
 31 /

STATUS_CHANGE_PATH             COUNT(*)
------------------------------ --------
A->B->C                               1
A->C->B                               1
B->A->C                               2

This query has the advantage that I do not need to presume the maximum number of
state changes, as Tom's original answer does (8). Very cool solution, Padders.

I _do_ realize that the plan for this query is pretty expensive, but the version
of this query that I would consider for production purposes would not be run
very frequently.

However, I do have some follow-up questions.

1) I noticed when building this query, that SYS_CONNECT_BY_PATH appears to
generate all possible paths from the points specified by the START WITH clause,
e.g. A, A->C, A->C->B. I needed to obtain the full/longest path per each person
id, e.g., A->C->B. I accomplished this by MAX'ing out the path length, using
LEVEL in the inline view, GROUPing by each person_id. Is there a better way to
do this? Or am I misunderstanding how SYS_CONNECT_BY_PATH behaves?

2) This, of course, could also have been done using a PL/SQL function to return
the path for each person_id. Is there a real advantage to use
SYS_CONNECT_BY_PATH over such a PL/SQL function? What is SYS_CONNECT_BY_PATH
typically used for?

Thanks again, Tom, for this forum, and a great opportunity to exchange ideas.
Rating: 4
Or maybe... March 08, 2004
Reviewer: Padders from UK
Dan perhaps I misunderstand your requirements but I suspect a user-defined
string aggregate is more appropriate (not to mention efficient) in your case.
For information on 'stragg' function used below try a search right here.

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> DROP TABLE status_history;

Table dropped.

SQL> CREATE TABLE status_history (
  2    person_id NUMBER,
  3    status_code VARCHAR2(1),
  4    status_date DATE);

Table created.

SQL> INSERT INTO status_history
  2    SELECT 1, 'A', TO_DATE ('01-JAN-2002', 'DD-MON-YYYY')
  3    FROM   DUAL
  4    UNION ALL
  5    SELECT 1, 'B', TO_DATE ('02-JAN-2002', 'DD-MON-YYYY')
  6    FROM   DUAL
  7    UNION ALL
  8    SELECT 1, 'C', TO_DATE ('03-JAN-2002', 'DD-MON-YYYY')
  9    FROM   DUAL
 10    UNION ALL
 11    SELECT 2, 'A', TO_DATE ('03-JAN-2002', 'DD-MON-YYYY')
 12    FROM   DUAL
 13    UNION ALL
 14    SELECT 2, 'C', TO_DATE ('05-JAN-2002', 'DD-MON-YYYY')
 15    FROM   DUAL
 16    UNION ALL
 17    SELECT 2, 'B', TO_DATE ('06-JAN-2002', 'DD-MON-YYYY')
 18    FROM   DUAL
 19    UNION ALL
 20    SELECT 3, 'B', TO_DATE ('02-JAN-2002', 'DD-MON-YYYY')
 21    FROM   DUAL
 22    UNION ALL
 23    SELECT 3, 'A', TO_DATE ('07-JAN-2002', 'DD-MON-YYYY')
 24    FROM   DUAL
 25    UNION ALL
 26    SELECT 3, 'C', TO_DATE ('08-JAN-2002', 'DD-MON-YYYY')
 27    FROM   DUAL
 28    UNION ALL
 29    SELECT 4, 'B', TO_DATE ('01-JAN-2002', 'DD-MON-YYYY')
 30    FROM   DUAL
 31    UNION ALL
 32    SELECT 4, 'A', TO_DATE ('05-JAN-2002', 'DD-MON-YYYY')
 33    FROM   DUAL
 34    UNION ALL
 35    SELECT 4, 'C', TO_DATE ('08-JAN-2002', 'DD-MON-YYYY')
 36    FROM   DUAL;

12 rows created.

SQL> COMMIT;

Commit complete.

SQL> COLUMN combo FORMAT A20

SQL> SELECT combo, COUNT (*)
  2  FROM  (SELECT person_id, REPLACE (
  3                  stragg (status_code), ',', '->') combo
  4         FROM  (SELECT person_id, status_code
  5                FROM   status_history
  6                ORDER BY status_date)
  7         GROUP BY person_id)
  8  GROUP BY combo;

COMBO                  COUNT(*)
-------------------- ----------
A->B->C                       1
A->C->B                       1
B->A->C                       2

SQL>
Rating: 5
Thanks, Padders March 09, 2004
Reviewer: Dan Kefford from New Haven, CT
Thanks, Padders. I'll look into it.
February 19, 2004 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
Rating: 3
find the cause of dead lock February 19, 2004
Reviewer: Linda from USA
This is very helpful info, but i have a senerio which I am not able to idenifty
which row are locked in my case:

 DEADLOCK DETECTED
 Current SQL statement for this session:
 DELETE FROM INSTRUCTIONS WHERE INSTRUCTION_ID = :b1
 ----- PL/SQL Call Stack -----
   object      line  object
   handle    number  name
 3f2497af8       891  package body MASTER.CG$INSTRUCTIONS
 3f52c65d0        30  procedure MASTER.DEL_INSTRUCTIONS_PR
 3f52ca450      2811  package body MASTER.CG$ORDERS
 3f445f838        29  procedure MASTER.DEL_ORDERS_PR
 3f447f9d8       110  package body MASTER.ORDERS_PG
 3f447f9d8      2100  package body MASTER.ORDERS_PG
 3f447bdb8       186  MASTER.ORDERS_TR
 3f447f9d8      2133  package body MASTER.ORDERS_PG
 3c6fd9060         1  anonymous block
 3f4e4ccf8       207  procedure MASTER.DELETE_PR
 3f256fab8         1  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:

When i look at the cursor session:

Cursor 347 (419799398): CURBOUND  curiob: 41624dbd0
 curflg: c5 curpar: 0 curusr: 20 curses 380542900
 cursor name: DELETE FROM INSTRUCTIONS WHERE INSTRUCTION_ID = :b1
 child pin: 3c14a1560, child lock: 383154b00, parent lock: 3c26e1f50
 xscflg: 101424, parent handle: 3f248c098, xscfl2: 1121800
  nxt: 3.0x00000368  nxt: 2.0x00000560  nxt: 1.0x000006e8
Cursor frame allocation dump:
frm: -------- Comment --------  Size  Seg Off
 bind 0: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=1 size=2
offset=0
   No bind buffers allocated

There is no binding varible being signed when the trace file is generated, how
would i identify which row is locked?
I am not sure when this is a unique index lock, there is a unique index is
created on INSTRUCTIONS.INSTRUCTION_ID  in this case....
Followup:
you need to look at what the resources here were -- forget about the row for a
moment, look at what the other session had -- what they wanted, what you had and
what you wanted -- that is the cause.

we know you were both going after the same row -- it doesn't matter if it was
"id=55" or "id=50320"
February 19, 2004 Robert -- Thanks for the question regarding "negative number format", version 8.1.7
You Asked
tom,
Is there a format to output negative number in parenteses (accounting style)?

I am looking for a more "elegant" way of doing this:
l_celldata := owa_util.ite(i.amount < 0, '(' || to_char(abs(i.amount),
'999,999,999,990.00') ||')', to_char(i.amount, '999,999,999,990.00'));

thanks
and we said...
ops$tkyte@ORA920PC> select x, to_char( x, '999,999,990.00PR' ) from t;

         X TO_CHAR(X,'999,9
---------- ----------------
  -1234623   <1,234,623.00>
   1234623    1,234,623.00

ops$tkyte@ORA920PC> select x, translate( to_char( x, '999,999,990.00PR' ), '<>',
'()' ) from t;

         X TRANSLATE(TO_CHA
---------- ----------------
  -1234623   (1,234,623.00)
   1234623    1,234,623.00
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96540/sql_elements4a.htm#34692
February 19, 2004 -- Thanks for the question regarding "Pro*C and UTL_SMTP", version 9.2.0
You Asked
Hi Tom,

We are trying to use UTL_SMTP to send e-mails from Pro*C.  Initially, we had a
single function that performed all of the utl_smtp subprograms (open_connection,
helo, mail, rcpt, open_data, write_data, close_data, quit) and returned a
success or failure flag.  This was working fine, until we started to try and
send a large number of e-mails in a short amount of time.  We started seeing the
error "ORA-00018: maximum number of sessions exceeded".  Our DBA believed this
was caused by a time delay when closing the virtual circuits used to establish
the smtp connections.  So, we decided to create a procedure to do the
open_connection and helo, a function to send messages within that connection,
and then a procedure to do the quit.  This way, we would only use 1 smtp
connection to send several e-mails.  We actually figured out how to do this in
Pro*C (just passing the private_sd (pls_integer) part of the utl_smtp.connection
variable back and forth).  The problem now is that we are seeing "ORA-29278:
SMTP transient error: 421 Service not available" when trying to send messages or
doing the quit.  We believe the smtp connection is timing out.  Do you have any
ideas on how to handle this any better?  Thanks.
and we said...
well, rather then try to debug proc code and such -- given that I cannot see how
you would get the ora 18 from a single pro*c program with a single connection...

what I do is put the email I want to send into a table and schedule a plsql
routine to email it.

eg:

create table mail_queue( job_id primary key, to varchar2, from varchar2, body
clob );

and then have a small procedure "send_mail" that takes p_job_id in number as
input.  It'll read the record from the above table, mail it and then delete it
(actaully, I update the record to keep a log of what mail was sent to whom and
when it was transmitted)

and then to send a mail:

   dbms_job.submit( :job, 'send_mail( JOB );' );
   insert into mail_queue values ( :job, 'tkyte@oracle', 'tkyte@oracle', 'hello
world' );
   commit;

and that is it.  We control how many mails get sent at the same time using
job_queue_processes.
Review & Followup

Rating: 5
February 20, 2004
Reviewer: amol from bangalore
elegant as always..
February 19, 2004 Naether -- Thanks for the question regarding "Trigger on Blob-Fields ", version 8.0.5
You Asked
Hallo Tom,

I 'd like to use a trigger in Oracle as follows :

   1. there is a Blob-Field in the triggered table
   2. in case of Updating the Blob-Field by any user-action
      the fiered Update-trigger makes a copy of the Blob to
      an other Blob in an other table

I use this kind of trigger-action with "normal" Fields for
a long time. Now I need that feature for the blobs too -
but it seems it is very difficult.

I could not found terms as: new.Blobfieldname,
:old.Blobfieldname ...

I could not give a Blobfield in the trigger-observed FieldList
...

How can I make it ?

Greetings from Dresden, Andreas Näther
and we said...
Well, this is a sticky issue (i think we can solve it though).
The problem is two fold:

o Triggers are not supported on LOBs. However, you can use a LOB
in the body of a trigger as follows:

- in regular triggers old values may be read but not written and
new values may not be read nor written.
- in 'instead of triggers', the:old and:new values can be read
but not written.

o dbms_lob.write (and other dbms_lob calls) do not fire a
trigger -- people can and will change the value of a blob and
you will not be able to detect it with a trigger.  In order to
update a LOB, I might never issue an UPDATE.

Now, if you want to copy the blob to another table -- we can use
snapshots to copy the data.  This can be accomplished via basic
replication.  See the Oracle8 server replication guide.
Review & Followup

Rating: 4
Still True in 9iR2? February 19, 2004
Reviewer: Michael from Allentown, PA
I am a DBA for a large development team, and just received some new ddl that has
a table with 2 blob columns. The problem is, they also sent ddl for audit
triggers for all the tables. I am assuming that there is still a restriction on
this. Is your advise above still the only way around it?
Followup:
the audit "triggers" (custom code i expect) will not capture all updates to
blobs.

it would be easy to see that "this is so" with a small test.
February 19, 2004 Ranjan -- Thanks for the question regarding "Using Savepoints", version 8.1.x
You Asked
In a distributed transaction can it calls an api which uses 'SAVEPOINTS'.
Procedure p1 over database link needs to calls procedure p2
Procedure p2 in turns calls a series of api and some of the api uses
'SAVEPOINTS' . So I get the error which says that can not set savepoint in a
distributed transactions. So is there any way I can accomplish this task. I
tried calling p2 and making p2 an autonomous transaction but it didn't help. So
is there any other way?

Thanks
Ranjan
and we said...
No, you cannot do TRANSACTION control statements in a remotely executed
procedure.

What if procedure p1 calls p2.  P2 issues a savepoint.  You return to P1, P1
calls P3 on another machine.  P3 finishes and P1 calles P2 again.  P2 now does a
"rollback to savepoint".  Well, P2 has no clue about P3 -- cannot do anything
about it.

ALL transaction control statment, ALL of them, must originate from the original
initiating database.  It is the ONLY database that understands all of the
participants in the global transaction.
Review & Followup

Rating: 5
when do you use save points? February 18, 2004
Reviewer: A reader
Tom
Can you list out some common scenarios where save points
are useful? (I have not had a scenario where I needed
to use it so far.) I know that they get implicitly used
by Oracle but when do you think we should use them?

thanx for a great site!:)
Followup:
begin
    insert into log_table ( .... ) values
    ( .... 'starting process at ' || systimestamp );

    savepoint foo;
    begin
       p1;
       p2;
       p3;
    exception
       when others then
         rollback to foo;
         insert into log_table ( .... ) values
         ( .... 'failed with ' || sqlerrm || ' at ' || systimestamp );
         commit;
         RAISE;
    end;
    insert into log_table ( .... ) values
    ( ...., 'finished successfully at ' || systimestamp );
    commit;
end;

is sort of a "made up example" -- here, you have an insert that should "stick"
(the first one).  The three procedures should either all suceed or all fail.
So, we savepoint before the procedures -- if any fail for any reason -- we undo
their work -- log the reason and commit (and then re-raise the exception) --
else we just do the insert of success and done.

Personally, I cannot remember ever using savepoint "for real".  The implicit
ones Oracle does for us are generally more than sufficient.
Rating: 5
thanx! February 19, 2004
Reviewer: A reader
Even in your example. you could use autonomous
transactions, correct?

So in general, looks like savepoints are of not much use..
Followup:
there was no "pragma autonomous_transaction" there.

i did not use them.
Rating: 5
what i meant was February 19, 2004
Reviewer: A reader
that you could use them thus avoiding savepoints.
Followup:
it would not be the same.

with an atrans -- the first insert would be "committed".  If the system crashed
-- it would still be commited.  You would have "an infinite" transaction (no
"closing" record).

I'm not a fan of atrans very much.
Rating: 4
A use for savepoints February 20, 2004
Reviewer: eric givler from harrisburg, pa
We had a use for them.

Scenario: Our whitewater boating module allows either 30 or 60 people in a time
slot.  There are 8 slots each day.  The original designers thought that each
slot should be represented by a row.

So:
7:30am (60 people) = 60 rows * 200 day season = whoa!
8:30am (60 people) = 60 rows ...

Anyway, they were having myriads of problems overbooking these slots as there
were blocking updates which were later overwritten.  If a customer was changing
their party size, it would "deallocate" rows by setting a used flag.  This would
leave gaps.

The new code to "grab" the slots went like this:

savepoint sp1;
for slot_rec in (select * from boat_availability where site_id=p_site and
site_date=p_request_date order by slot_number ) loop
    begin
        attempt select for update on this row
        i := i + 1;
        slots_tab(i) := slot_rec.rowid;
         exception
           null;
    end;
end loop;

if slots_grabbed < slots_needed then
   -- couldn't get enough slots for this site
   rollback to savepoint sp1;
else
   for i in 1..slots_grabbed loop
       update boat_availability
          set slot_used='Y'
        where rowid=slots_tab(i);
   end loop;
end if;
February 19, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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://download-west.oracle.com/docs/cd/B10501_01/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://download-west.oracle.com/docs/cd/A91202_01/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://download-west.oracle.com/docs/cd/B10501_01/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://download-west.oracle.com/docs/cd/B10501_01/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.
Rating: 5
resource January 14, 2004
Reviewer: Reader
<quote>The Global Cache and Global Enqueue Services maintain information about
each resource within a cluster.<quote>

Does oracle save the Global Cache and Global Enqueue resource information in the
disk? If so, in which tablespace (system?) datafile? Thanks.
Followup:
no, does not.
Rating: 4
resource info in RAC January 15, 2004
Reviewer: reader
If it does not save the block status info in the disk, if one instance fails,
how does the other instance recontruct the resources managed by the failed
instance? Thanks for your valuable time.
Have a great time abroad.
Followup:
another instance takes over for it.
Rating: 4
instance owned redo files January 15, 2004
Reviewer: Jianhui from DC
Tom,
In RAC environment, when a single instance crashes, since it's own redo file set
is not in the shared storage (like datafiles and control files), how could other
instances take over the redo data of the crashed instance, then how could
failover happen without data loss?

Another question is if the interconnect has problem, each node will think other
nodes are dead and try to complete to take over the database, what mechanism
ensures that the first instance gets the right? I have heard of the voting disk
thing, but not so sure, could you explain?

Thanks as always
Followup:
they are in a shared environment -- it is that they are not "shared" -- each
instance has their own copy, but they are in an environment where anyother
instance can in fact access them.

if the interconnect dies, the entire cluster is pretty much going to go with it.
 the entire thing will be dead.
Rating: 4
redo log files should be in the shared disk January 15, 2004
Reviewer: reader
Rating: 5
Global_dbname January 15, 2004
Reviewer: Reader
Tom,

(1) is it true that global_dbname should not be set in the listener.ora in order
for srvctl to manage the instances/database in RAC environment?
(2) Also, for oem to discover nodes, the SID_NAME must be explicitly set in the
listener.ora file? If so, does it disable the dynamic registration of instances
with the listener?
Thanks.
Followup:
1) depends on how you want to do it.  it can be set, it need not be set.  from
the docs:

....
Note: In Real Application Clusters environments, the GLOBAL_ DBNAME parameter
disables connect-time failover or transparent application failover (TAF), Oracle
Corporation strongly recommends against adding this parameter to your
listener.ora file.
..........

2) I'm not an OEM setup "wizard" but i believe if the intelligent agent is
setup, it matters not what is in the listener.ora file.  perhaps someone else
reading this could confirm (but it would be an easy question for an iTar)
Rating: 5
Datafile timestamp is diff between OPS nodes January 18, 2004
Reviewer: Sami from NJ,USA
Dear Tom,
Thanks for your marvelous support and help.

I have an OPS(2 node) environment where datafiles are stored in raw disk. I
check datafiles TIMESTAMP from both node and they are totally different?

Just curious to know why is it like this?

#############
NODE-1
#############

$ cd /dev/vx/rdsk/dbdg3

server01 user1 PROD11 /dev/vx/rdsk/dbdg3
$ ls -ltr
total 0
crw-r--r--   1 oracle   dba      159,92003 Dec 16 13:16 NonCrm_i12.dbf
crw-r--r--   1 oracle   dba      159,92002 Dec 16 13:16 NonCrm_i11.dbf
crw-r--r--   1 oracle   dba      159,92001 Dec 16 13:16 Crm_i12.dbf
crw-r--r--   1 oracle   dba      159,92000 Dec 16 13:16 Crm_i11.dbf

server01 user1 PROD11 /dev/vx/rdsk/dbdg3
$

#############
NODE-2
#############
$ cd /dev/vx/rdsk/dbdg3
server02 user1 PROD12 /dev/vx/rdsk/dbdg3

$ ls -ltr
total 0
crw-r--r--   1 oracle   dba      159,92003 Jul  5  2002 NonCrm_i12.dbf
crw-r--r--   1 oracle   dba      159,92002 Jul  5  2002 NonCrm_i11.dbf
crw-r--r--   1 oracle   dba      159,92001 Jul  5  2002 Crm_i12.dbf
crw-r--r--   1 oracle   dba      159,92000 Jul  5  2002 Crm_i11.dbf

server02 user1 PROD12 /dev/vx/rdsk/dbdg3
$
Followup:
those are links right.

not knowing your OS or the nuances of your file system -- i'd say "those look
like the dates the links were created".
Rating: 5
high availability January 19, 2004
Reviewer: SR
Tom,

In RAC environment or even regular setup, I find one thing little bit surprising
the way oracle has done it. I mean, if you lose a multiplexed redo member,
database stays up, it just warns you. Really classy. Now, I got four
controlfiles, one of them gives error, the whole thing falls down. What's with
that? Considering RAC, this SPOF is even worst because you will bring down all
nodes.

So my curiosity is if log writer is taking a chance on remaining member, why
can't same be said about control file? Its not even that you are losing any
data. You can recreate controlfile completely as well. So where's the catch? I
would like to see failsafe controlfile setup. We had a situation once when
Veritas checkpoint held the controlfile hostage a bit too long, panicked the
perfectly fine database and aborted the instance.

Obviously, if performance vs safety is a concern & customer wants to do it this
way: Do you think it will be reasoanble to allow oracle based mirroring of
system tablespace? You can do online recovery. If system is down you are again
stuck. I know same can be said about UNDO, but before going too far -
realistically if we have 8 node RAC cluster, adding a bit of extra safety on
these SPOF points wouldn't cost that much over long run - especially if oracle
internally does it..

Thanks
Followup:
controlfiles are key to our operation -- a failed write on the control file is
considered "down database", something really bad has happened.  You are losing
data -- the control files tell us about the state of our system -- where
everything is, what point in time they are, everything.
Rating: 5
Tom, my main concern January 20, 2004
Reviewer: SR
1. RAC - All nodes will go down if one controlfile write/read fails? How can
that be justifiable

Now a follow up on that. What is the point of multiple controlfiles then? Please
explain to me what are we truly gaining from multiple controlfiles? (I do
understand disk failure, controller problem and etc etc reasons). But, suppose
each and every time if you were to lose controlfile - as long as you have backup
controlfile created (for just argument sake) each structural change and each
hour - you won't really lose any data, would you?
So, if what are we buying with extra controlfiles? convenience - so that you
will copy good controlfile to failed location?

I am just not understanding, how LGWR being so much more busy, dealing with so
much critical data, can take a hit on log file but not same with the
controlfile.

I apologize for pushing this issue but, if my employer is going to RAC and it
still has one point of failure (cluster volume manager being one already) I
wan't to understand more about it.

Thanks
Followup:
you don't have to use a cluster volume manager.

the control files are the eyes of the database -- without them we are blind.  we
don't even know what files constitute the thing.
Rating: 4
controlfiles January 21, 2004
Reviewer: Reader
My understanding is that if you loose one controlfile during the database
operaion, oracle would function with the remaining mirrored controlfiles.
Otherwise, what is the purpose of multiplexing the controlfiles? Only if the
write operation failed for all of the controlfiles, the system would crash.
Thanks.
Followup:
they are the eyes.  they are important.  a failed write shuts down.
Rating: 5
controlfiles January 21, 2004
Reviewer: Reader
<quote>a failed write shuts down<quote>

Tom, do you mean failed write on one controlfile (even though Oracle wrote
successfully on the remaining controlfiles) would shutdown the instance? Thanks.
Followup:
don't do this at home but:

$ echo x > /home/ora9ir2/oradata/ora9ir2/control03.ctl
$ tail -f alert*

***
Corrupt block relative dba: 0x00000001 (file 0, block 1)
Completely zero block found during control file header read
Wed Jan 21 17:58:51 2004
Errors in file /home/ora9ir2/admin/ora9ir2/bdump/ora9ir2_ckpt_16456.trc:
ORA-00202: controlfile: '/home/ora9ir2/oradata/ora9ir2/control01.ctl'
Wed Jan 21 17:58:52 2004
Errors in file /home/ora9ir2/admin/ora9ir2/bdump/ora9ir2_ckpt_16456.trc:
ORA-00227: corrupt block detected in controlfile: (block 1, # blocks 1)
ORA-00202: controlfile: '/home/ora9ir2/oradata/ora9ir2/control01.ctl'
CKPT: terminating instance due to error 227
Instance terminated by CKPT, pid = 16456
Rating: 5
further question on control file mirror January 21, 2004
Reviewer: SR
Tom,

Let me as you would you wish for a feature where database won't abort when at
least one controlfile is functioning properly and others not? I understand what
happens now. But thats not Oracle always is about, is it? All those new features
of 9i make you want to believe Oracle must have some such plan in the makings.

In your earlier comments you mention don't use cluster volume manager. I don't
understand that. We have Veritas infrastructre. We will use CVM, CFS, ODM and
RAC. You cannot drop CVM - thats the heart. Am I missing something?

May be in early version of oracle error in one of redo member may have caused db
to crash also, did it? I don't know. If so we have come long way. I hope with
10g fault tolerant control file would be very welcome addition.

Thank you
Followup:
you need the clustering software to cluster the OS's in 9i (not in 10g and
above) but you don't need to cluster the filesystems if you don't want to.  Raw
will do.
Rating: 5
How does RAC recover resource GCD info? February 05, 2004
Reviewer: reader
Tom, it is not clear from reading the doc how the surviving instance knows about
the resources mastered by the failed instance as the resource info is not stored
in the database. Global Cache Directory resides in the memory and once the
instance crashes there goes the resource info as well. It is not documented
clearly how it is done. Is it a proprietory info for oracle or DBAs do not need
to know in depth to manage RAC? I know you must be knowing all of these and can
you share? Thanks for your time.
Followup:
just internal stuff, we don't need to know really.  we only need to know "it
works as advertised"
Rating: 5
February 06, 2004
Reviewer: A reader
Tom,

Lets say if i issue the same command as yours to datafile, will it corrupt once
block or entire datafile.

$ echo x > /home/ora9ir2/oradata/ora9ir2/users01.dbf

Thanks.
Followup:
maybe, maybe not.

You see, if an Oracle process has users01.dbf open -- it'll will be "open" and
the echo will just unlink (remove the directory entry) of the file and create a
new file.  the Oracle process will still "see" the old file -- until it closes
it and opens it again.

So, it might take a long time for this to "appear" to happen and when it does,
the file will just be "x" (1 byte) long.

it would corrupt the "entire file" effectively.
Rating: 5
CFS vs Raw February 19, 2004
Reviewer: reader
Tom, what is the difference between CFS and raw partitions in RAC? Which one is
prefered? Thanks.
Followup:
cfs gives you "a file system".  it makes people "feel good" because raw is
perceived as "scary" and "hard".

CFS adds a tiny bit of overhead -- single digit percents -- but if you feel more
comfortable with a file system over raw -- it'll be what you want.
February 20, 2004 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!!
Rating: 5
rewriting "not in" February 19, 2004
Reviewer: A reader
I have seen a "not in" query written as:

select a.key
from t1 a,
     t2 b
where a.key = b.key(+) and
      b.key is null
/

What do you think of this?
Followup:
it is the "poor mans hash anti-join", a good idea in the olden days of the RBO,
not so good in the days of a smarter CBO.

start with:

ops$tkyte@ORA920PC> create table t1 as select * from all_objects where rownum <=
5000;

Table created.

ops$tkyte@ORA920PC> create table t2 as select * from all_objects where rownum <=
4950;

Table created.

ops$tkyte@ORA920PC> create index t2_idx on t2(object_id);

Index created.

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      8.63       8.53     129066     197295          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      8.64       8.53     129066     197295          0           1

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.08       0.13         83      10075          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.09       0.13         83      10075          0           1

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.08       0.11         72       5087          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.09       0.11         72       5087          0           1

so, it looks "good" right?  better than the alternatives in RBO, yes.  but,
analyze and

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.04          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.06       0.08          0         91          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.06       0.12          0         91          0           1

hmmm, rbo not so good -- "user written hash anti join under rbo" not so
good...

the cbo does OK though with the user written one:

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.08       0.10          0         91          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.08       0.11          0         91          0           1

but I find the not in to be infinitely easier to code and alot more
"informational" (clear,easy to understand)
Rating: 5
Union in NOT EXISTS February 19, 2004
Reviewer: Ishaque Hussain from Fords, NJ USA
Hi Tom, I don't have any statistics to provide you but could you look at query A
and query B and advise which would be more efficient to write.

Query A)

select *
from contr_prod_price cpp,
where cpp.contr in (
    select contr
    from contr
    where co = 'E')
and cpp.prod_cd = 'PX'
and not exists
    (select 'x'
     from contr_prod_prc_low_lvl_ind cpli
     where cpp.contr       = cpli.contr
     and   cpp.contr_frmla = cpli.contr_frmla
     and   cpp.contr_lvl   = cpli.contr_lvl
     union
     select 'x'
     from contr_prod_prc_low_lvl_dir cpld
     where cpp.contr       = cpld.contr
     and   cpp.contr_frmla = cpld.contr_frmla
     and   cpp.contr_lvl   = cpld.contr_lvl )

Query B)

select *
from contr_prod_price cpp,
where cpp.contr in (
    select contr
    from contr
    where co = 'E')
and cpp.prod_cd = 'PX'
and not exists
    (select 'x'
     from contr_prod_prc_low_lvl_ind cpli
     where cpp.contr       = cpli.contr
     and   cpp.contr_frmla = cpli.contr_frmla
     and   cpp.contr_lvl   = cpli.contr_lvl )
union
select *
from contr_prod_price cpp
where cpp.contr in (
     select contr
     from contr
     where co = 'E')
and  cpp.prod_cd = 'PX'
and  not exists
       (select 'x'
     from contr_prod_prc_low_lvl_dir cpld
     where cpp.contr       = cpld.contr
     and   cpp.contr_frmla = cpld.contr_frmla
     and   cpp.contr_lvl   = cpld.contr_lvl )

Thanks
Followup:
well, given that they do not return the same results "in general" (the 2cnd will
do a distinct on the result set)....

and that I might really consider a NOT IN....

I would probably go with the first, but I would benchmark them (since I had
both)
February 20, 2004 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
Rating: 4
excellent February 20, 2004
Reviewer: vj from in
Hi,

The below is the version of oracle we are using

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

We have an SP that return (out param )the CLOB datatype to the calling
program.The calling program is an JSP and we use oracle thin driver. My size of
CLOB is around 32KB and sometimes little more than 32KB. If we send below 30kb
it is working, but if it is more than 30 KB it is not
working. Any other alternate as we built the programs around the same logic.

Rgds
Followup:
you'd really have to provide a teeny tiny code snippet for you can use clobs of
unlimited size in java/jdbc (well, upto the current 4gig limit in 9i).  You
would interface to them as "streams" -- perhaps you are using "strings"?

small
concise
yet complete

is the best test case.  it won't need a jsp to reproduce your issue -- a tiny
"main" with a tiny bit of code.
February 20, 2004 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.
Rating: 5
When others then null February 20, 2004
Reviewer: Tony from Reading, UK
<quote>
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.
</quote>

In the application that I am supporting at the moment I see this kind of thing
all the time.
There are lots of procedures that contain

WHEN OTHERS THEN
   NULL;

I think this is a really BAD practice and a lazy way to code exceptions but I
need to go to the developers and try to explain why this is not good.
Can you give my some feedback as to why this is not a good way to code
exception?
I can then build a strong argument to take to the development team and argue my
point.
If you disagree with me, can you tell me why?
Followup:
it is not "lazy" (heck, they had to type extra code).  It is just "horrible".

Ask them -- Ok, so when your insert as select fails due to ora-1555, or your
update fails or <whatever fails> -- how does the invoker understand this, find
out what happened, get an error message, not blindly ignore the fact that the
error occurred?

Do they even understand what it means to transactional consistency?  That is
totally changes the atomicity of statement invocations?  consider a
procedure/function like this:

create procedure p1
as
begin
    update t set ....;
exception
    when others then
         null;
end;

create procedure p2
as
begin
    update t2 set ...;
    p1;
end;

ask them -- so, what happens when p1 fails?  the update of t2 will persist, and
the procedure p2 is sort of "clueless" that "something bad (tm)" has happened.

if they chop out the exception block -- what'll happen is

o the invoker of p2 will understand "error happened" and have access to the
information regarding the error

o all work by both P1 and P2 will be undone, the database will be in a known,
consistent, stable state.

when others then null;  is a bug in 99.99999% of the cases (the only place it is
OK is something like:

    begin
       number_var := str_var;
       return TRUE;
    exception
       when others then return FALSE;
    end;

that'd be a function to test if a string contained a number in it.)
Rating: 3
when whenother then null is bad... February 20, 2004
Reviewer: A reader
It is bad because the developers are coding...

When ever any, ANY, *ANY* error occurs in this block of code, ignore it and move
on...

How can you EVER want to code that??
Rating: 5
It's Java! February 20, 2004
Reviewer: Invisible from UK
"If EXCEPTION do NOTHING."

Hmmm, sounds like a Java programmer's trick to me! ;-)

[In Java, you have to catch every exception that could possibly occur and
specify what to do with it - or specify that the function might produce that
error - so everyone gets so hacked off with it they just ignore all exceptions.

Sounds like people are just carrying it straight over to Oracle, even though
Oracle is a tad more intelligent about much thigns!]

Just my opinion... [can't even BEGIN to imagine how the data such applications
work on can stay consistent!]
February 20, 2004 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
Rating: 3
hash_area_size February 20, 2004
Reviewer: A reader
Hi Tom,
If I am using pga_aggregate_target in 9ir2, and execution plan showing hash
joins, Do I need to tune hash_area_size?
Thanks
Followup:
well, it depends on the workarea_size_policy init.ora setting.  when set to AUTO
then No -- hash_area_size would only be used by a shared server connection in 9i
when using AUTO workareas (and in 10g, even shared servers won't use it.)

if using manual, then yes, hash_area_size is the setting the server will use to
hash.
February 20, 2004 Prem -- Thanks for the question regarding "How to pass an array to a stored procedure", version
You Asked
I want to know if I can have one of the parameter in
stored procedure as an array.
Ex : I have a procedure
procedure employee_report
(emp_no      number
,emp_dept   varchar2
,emp_salary number
,emp_title  varchar2)

Instead can I define an object/array emp_property of structure
(emp_no      number
,emp_dept   varchar2
,emp_salary number
,emp_title  varchar2)
and use it a parameter in stored proc like
procedure employee_report(emp_prop emp_property ).
If this is possible than can I pass multiple records in this
procedure.
and we said...
Sure, you just need to declare new types.  You can have types
that are tables of a RECORD or types that are tables of SCALARS.
 I'll show both below.  See the plsql guide for how to declare
record types -- you can do them based on an existing table
structure, a cursor structure or by explicity defining your own
record type.

Note that if you are clling emp_report from a 3gl such as Pro*C,
OCI, VB, etc (eg: anything OTHER then plsql) you will need to
use tables of scalars -- NOT a table of records as tables of
records cannot be bound to by a 3gl.  Also, I suggest only use
charArray's if you are going to be calling this from a 3gl -- if
you made a table of NUMBER or a table of DATE -- we would expect
you to send the INTERNAL format of a number (22 byte field in an
obscure format) or a date (7 byte, equally obscure format).
Best to send strings and let the conversions take place.

ops$tkyte@8i> create or replace package demo_pkg
  2  as
  3      type empArray is table of emp%rowtype index by
binary_integer;
  4
  5      type charArray is table of varchar2(255) index by
binary_integer;
  6
  7
  8      procedure emp_report( p_inputs in empArray );
  9
 10      procedure emp_report( p_empno  in charArray,
 11                            p_deptno in charArray,
 12                            p_sal    in charArray,
 13                            p_job    in charArray );
 14
 15  end;
 16  /

Package created.

ops$tkyte@8i>
ops$tkyte@8i>
ops$tkyte@8i>
ops$tkyte@8i> create or replace package body demo_pkg
  2  as
  3      procedure emp_report( p_inputs in empArray )
  4      is
  5      begin
  6          for i in 1 .. p_inputs.count loop
  7              dbms_output.put_line( 'Empno = ' ||
p_inputs(i).empno ||
  8                                    ' Deptno = ' ||
p_inputs(i).deptno );
  9          end loop;
 10      end;
 11
 12
 13      procedure emp_report( p_empno  in charArray,
 14                            p_deptno in charArray,
 15                            p_sal    in charArray,
 16                            p_job    in charArray )
 17      is
 18      begin
 19          for i in 1 .. p_empno.count loop
 20              dbms_output.put_line( 'Empno = ' || p_empno(i)
||
 21                                    ' Deptno = ' ||
p_deptno(i) );
 22          end loop;
 23      end;
 24
 25
 26
 27  end;
 28  /

Package body created.

ops$tkyte@8i>
ops$tkyte@8i> set serveroutput on
ops$tkyte@8i> declare
  2      my_data   demo_pkg.empArray;
  3  begin
  4      my_data(1).empno := 1234;
  5      my_data(1).deptno := 10;
  6
  7      my_data(2).empno := 4567;
  8      my_data(2).deptno := 20;
  9
 10      demo_pkg.emp_report( my_data );
 11  end;
 12  /
Empno = 1234 Deptno = 10
Empno = 4567 Deptno = 20

PL/SQL procedure successfully completed.

ops$tkyte@8i>
ops$tkyte@8i> declare
  2      my_empnos   demo_pkg.charArray;
  3      my_deptno   demo_pkg.charArray;
  4      empty       demo_pkg.charArray;
  5  begin
  6      my_empnos(1) := 1234;
  7      my_deptno(1) := 10;
  8
  9      my_empnos(2) := 4567;
 10      my_deptno(2) := 20;
 11
 12      demo_pkg.emp_report( my_empnos, my_deptno, empty, empty
);
 13  end;
 14  /
Empno = 1234 Deptno = 10
Empno = 4567 Deptno = 20

PL/SQL procedure successfully completed.
Review & Followup

Rating: 5
March 23, 2001
Reviewer: Helena Markova from Bratislava, Slovakia
Rating: 5
October 17, 2001
Reviewer: A reader
3      type empArray is table of emp%rowtype index by
binary_integer;

Can pl/sql tables contain more than one column.

can I select all the rows in emp into emp array, and then insert emparray into
another table
Followup:
The example was of a PLSQL table type that is of more then one column.  empArray
is a table of records -- each record is the shape of a row in the emp table.

See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1583402705463
for howto bulk collect into a SQL Table type (not a plsql type) and insert using
it.
Rating: 4
Pass array to stored proc from VB? February 06, 2002
Reviewer: Sasa Petkovic from Belgrade , Yugoslavia
Your examaple is very usefull and it works excellent in PL/SQL, but the problem
occurs when trying to call the sam procedure from VB. Parameter of a command
object is defined as adArray + adVarChar, and assigning a VB array of strings to
it goes fine, but .Execute crashes with "Type mismatch" error. Same thing
happens when using either ODBC or Oracle OLEDB drivers. Is this feature
currently supported, and if it is - then what is the lowest version of drivers
that support it?
Followup:
I don't know much about VB personally.  I know we can do this with OO4O (Oracle
Objects for OLE).  Sorry I cannot provide an example as I've never touched VB
personally.
Rating: 5
Great responses March 07, 2002
Reviewer: Teodora Jevtic from Livonia, MI USA
Tom,

Do you know if there are such limitations in ODBC that can prevent from calling
this kind of stored procedures - i.e in Crystal Reports. Some of the reports are
to complex to be written in one SQL statement.
I am thinking if the call to this kind of stored procedure is not possible(which
seems to be the case) to make a call to stored procedure (prior running the
report - from the GUI application) which will store the resultset into a Oracle
temporary table - and the report will just pull the data from it (or use
permanent table with a unqiue identifier for each calling session).
Do you have any other ideas? Thanks in advance.
Followup:
I've no idea what limits crystal reports might impose, sorry -- i've never used
that tool.

You can use global temporary tables for this purpose handily.  Very very little
redo log generated during insertion and automatic cleanup on commit or session
termination.
Rating: 5
RE: Great responses March 07, 2002
Reviewer: Kim from Boise, ID USA
As far as crystal reports go...
I suggest you look at the following (you have to set up a couple things in
Crystal Reports as well):
http://www.seagatesoftware.com/docs/
and search for
SCR_Oracle_Stored_Procedures.pdf
down where it says or search for the filename below.
Rating: 5
but how to delete the element June 28, 2002
Reviewer: Raj from Peoria, IL
Tom
Nice example.  Could you please tell me how to remove an element from the array.
 Once removed is the row empty or deleted completely?

Thanks for your help
Followup:
see
http://download-west.oracle.com/docs/cd/A87860_01/doc/appdev.817/a77069/04_colls.htm#14165
you'll use the .DELETE method.

The row is deleted from the collection.  It'll still "be there", it just won't
"exist" anymore.  Read the above link for all of the details
Rating: 5
June 28, 2002
Reviewer: Alex from Chicago, IL
I would like to comment on calling procedures from VB apps. If VB works the same
way as ASP, then you cannot use PL/SQL tables of rows - ASP (and possibly VB)
doesn't recognize it, so you must declare each field that will be used as a
separate PL/SQL table and pass it as a parameter.
Rating: 5
June 28, 2002
Reviewer: Robert from Memphis, TN USA
Rating: 5
Thanks June 28, 2002
Reviewer: Raj from Peoria, IL
Thanks Tom for your reply.
Rating: 4
How to pass arrya of number from oci to PL/sql November 12, 2002
Reviewer: sam
Dear Tom

This article is very useful to PL/SQL, but when i write a oci(C++) project, it
said "Type mismatch".

Can you give me a example in C++ of oci?
Followup:
Nope -- cuase only PLSQL can see PLSQL types-

You need to use OBJECT TYPES

create or replace type myObject as object .....
create or repalce type myArray as table of ....

See the OCI guides for that -- they have examples and PLSQL can use the object
types as freely as its own types.
Rating: 5
About OCI Object for PL/SQL Parameter November 12, 2002
Reviewer: sam
Tom

I have searched OCI Progammer Guide, But i cann't find and OCI number Array for
PL/SQL,

The following link is OCI Programmer Guide, page 154 is the example of number
array, is it for sql, not for pl/sql?
http://docs.oracle.com/cd_a87860/doc/appdev.817/a76975.pdf
How to pass number array to pl/sql?

thanks again
Followup:
it is an array bind
see
http://download-west.oracle.com/docs/cd/A87860_01/doc/appdev.817/a76975/oci05bnd.htm#420907
Rating: 5
continued for above November 12, 2002
Reviewer: sam
The following is my package code:

package PKG_SCOTT2
    as
        type NumberArray is table of Number index by binary_integer;
        type CharArray is table of varchar2(255) index by binary_integer;

       procedure Dept_Insert2( p_DeptNo  in NumberArray,
                             p_DName in CharArray,
                             p_Loc   in CharArray );

   end;

package body PKG_SCOTT2
    as
        procedure Dept_Insert( p_DeptNo  in NumberArray,
                                p_DName in CharArray,
                                p_Loc   in CharArray )
           is
        begin
            for i in 1 .. p_DeptNo.count loop
                insert into dept
                 values(p_DeptNo(i), p_DName(i), p_Loc(i));
            end loop;
       end;
   end;
Rating: 5
pass array to procedure February 20, 2004
Reviewer: A reader
Hi

Is it possible to pass arrays this way?

exec proc_a(array);

proc_a needs to process this way

proc_a (p_array chararray)
as
begin
for i in p_array.first .. array.last loop
dbms_output.put_line(p_array(i));
end loop;
end;
/
Followup:
yes

exec declare array; begin array(1) := 'x'; proc_a(array); end;
February 20, 2004 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.
Rating: 5
clarification .... February 19, 2004
Reviewer: reader
<quote>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 <quote>

Tom, if there is no bitmap on the migrated one, how does the extent allocation
and deallocation is managed? I am puzzled. Thanks.
Followup:
it's a hack (and not really entirely too important to us -- it "works" but it
doesn't give you the nice attributes of LMTS -- equi sized extents or nice
system allocated extents)
Rating: 5
A question February 20, 2004
Reviewer: Invisible from UK
Here seems like as good a place as any to ask this...

Is converting DMT to LMT *always* a good idea?

It strikes me that Oracle does what it does for, like, A REASON. If Oracle
always does task X using method Y, it's because method Y is always the best
method. If Oracle lets YOU choose between method Y or method Z, it's because one
isn't _always_ better than the other.

"If indexes are so cool, why can you turn them off?"

Because they can make it slower as well as faster.

"Why doesn't Oracle partition my tables automatically?"

Because that can make it slower as well as faster.

"Which kind of partitioning is best?"

It depends.

"Which access path is best?"

It depends.

etc.

It takes extra effort for the folks at Oracle to program two different ways of
doing something. If Oracle gives you a choice, it's usually because although one
may appear "better" than the other, there is at least one important situation
where the reverse is true.

Of course... Oracle isn't some theoretical model. It's a real-world product,
used by actual people. So it has to contend with unfortunate things like...
backwards compatibility. *shudders*

So my question is... LMT seems (from what I can gather) to be "better" than DMT.
Now, is that ALWAYS the case? (i.e., is DMT for backwards compatibility only?)
Or is there some situation where DMT is actually better than LMT?

Also, if I change one to the other, will the apps using the database care?

Thanks for your time.
Followup:
moving DMT stored data into an LMT if you were already going to reorg is always
a "good idea (tm)".

if the DMT data is doing just fine, you were not going to reorg it in the first
place, i'd just "leave it be"

LMTs are an evolution.

DMTs are provided for backwards compatibility.

With 9ir2 and above, if you create a database with system as LMT, you do not
even have a choice anymore (so by default, in 9ir2 and above, you do not have a
choice).

Apps using the database should almost always be totally unaware anything
changed.  Only if they themselves perform some DDL during an upgrade or patch
might they become aware of the change.
February 20, 2004 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.
Rating: 3
sort of reserving numbers.... February 20, 2004
Reviewer: Mike from England
Would it be possible to reserve numbers in a sequence???

My problem is that we are developing an "off line" version of our app, in this
off line version users will be able to insert records into a table that will be
syncronised up, the PK of this table is taken from a sequence...

I was thinking of doing some cheeky manipulation of the "increment by" of the
sequence along the lines of...

when about to go off line...

1) alter sequence my_seq increment by 1000;

2) select my_seq.nextval into my_min from dual;

3) select my_seq.nextval into a_dummy from dual;

4) alter sequence my_seq increment by 1;

5) create  sequence my_local_seq start with my_min increment by 1 maxvalue
my_min + 999;

If the my_local_seq runs out of values then an ORA-08004 is what we want.

This seems a little messy though, any better ideas?  Oh this is on 9i R2 and
we'll be using Oracle Replication for the sync'ing if that helps.

Cheers,

Mike
Followup:
that would not work in a multi-user environment.

imagine what happens when some other session(s) touches the sequence at time
1.1, 2.1, 3.1....

A preferred method would be

a) decide how many offline systems you will have (say 1000)
b) multiply that by 10 (so 10,000) just to be "safe"
c) on the production site, "create sequence my_seq start with 1 increment by
10000"

d) on each of the remotes - "create sequence my_seq start with 2 increment by
10000", start with 3, start with 4 and so on (you could have a table you
maintain that has the numbers 2, ... 10000 and upon "install" of the application
onto a remote site you "select * from t for update", fetch the first row, delete
it and commit.  then create the sequence using that number you just got.
Rating: 4
cunning... February 20, 2004
Reviewer: A reader
I hadn't thought of that very cunning...

I thought that aside from the locking of the sequence while I alter it I would
be "safe" from users coming in at 1.1, 2.1, 3.1... Is that what you mean??

say the sequence is currently at 1
1.0 I alter the sequence to increment by 1000
1.1 they get a value that is now 1001
2.0 I get 2001 and call it my minimum - the nextval is now 3001
2.1 they get 3001 - this is fine, I won't use it, in fact I
3.0 get the next value in case no one else has to ensure the sequence "bumps
along" (to 4001 in this case)
3.1 they get 5001
4.0 I change the sequence back down to increment by 1
4.1 They get 5002.

It doesn't seem to matter to me how many people "jump in" between my X.0 steps,
I can be safe in the knowledge that no one can use the value I get in step 2.0 +
1000 (or what ever I changed the increment by too.

That said I think I'll go with your solution, the number of remote sites will be
around 50... ;)
Followup:
you cannot "lock" a sequence.  right after you altered it (step 1) anyone could
and would be able to select it again.

You are right -- since you only use min_val, you would be "safe" but -- think of
the invalidations you would be doing (i'm dead against DDL in a real system)

create sequence my_seq;

                                 alter session set sql_trace=true;
select my_seq.nextval from dual insql;
select my_seq.nextval from dual insql;
alter sequence my_seq increment by 1000;
select my_seq.nextval from dual;
alter sequence my_seq increment by 1;
select my_seq.nextval from dual insql;
select my_seq.nextval from dual insql;

tkprof with aggregate=no will show:

select my_seq.nextval from dual insql

Misses in library cache during parse: 1
********************************************************************************
select my_seq.nextval from dual insql

Misses in library cache during parse: 0
********************************************************************************
select my_seq.nextval from dual insql

Misses in library cache during parse: 1   <<<==== hard parse
********************************************************************************
select my_seq.nextval from dual insql

Misses in library cache during parse: 0
February 20, 2004 -- Thanks for the question regarding "Start and end date of a quater", version 8.1.7
You Asked
Hi Tom,

Is there a way to return the start date , end date and the quarter in a single
select statement.

eg. If sysdate is 02-03-2003

START_DATE END_DATE    YYYY_Q
---------- ----------  ---------
01-01-2003 03-31-2003  2003 - 1

I have a process that must be run every quarter but on the Last day.
This process does some processing by selecting data between the date ranges. I
can not format the where in the select as it call a properitory code with needs
the date range.

I am using dbms_job which calls a function that return a boolean
(if <funct> then <do_process> .....), and a out parameter of start and end date,
the input is defaulted to sysdate. The date ranges are assumed in the function
based on the Q value returned.

I though maybe there is a easier way to do it with a single select.

Thanks in advance.
and we said...
If you run this on the last day -- sure, just replace DT with SYSDATE and select
the three columns I do from DUAL (instead of all_objects which I'm just using to
generate data)

ops$tkyte@ORA920>
ops$tkyte@ORA920> select trunc(add_months(dt,-2),'mm') start_date,
  2         dt end_date,
  3             to_char( dt, 'yyyy - q' ) qtr
  4    from (
  5  select add_months( '31-dec-2002', rownum*3) dt
  6    from all_objects
  7   where rownum <= 10
  8         )
  9  /

START_DAT END_DATE  QTR
--------- --------- --------
01-JAN-03 31-MAR-03 2003 - 1
01-APR-03 30-JUN-03 2003 - 2
01-JUL-03 30-SEP-03 2003 - 3
01-OCT-03 31-DEC-03 2003 - 4
01-JAN-04 31-MAR-04 2004 - 1
01-APR-04 30-JUN-04 2004 - 2
01-JUL-04 30-SEP-04 2004 - 3
01-OCT-04 31-DEC-04 2004 - 4
01-JAN-05 31-MAR-05 2005 - 1
01-APR-05 30-JUN-05 2005 - 2

10 rows selected.
Review & Followup

Rating: 4
good February 04, 2003
Reviewer: A reader
dint know sql cud be used like this
Rating: 5
Changed query to run by sysdate February 04, 2003
Reviewer: Saru from Cincinnati, OH USA
Thanks Tom,

I changes the query, to use sysdate, and to select only on current year.

select trunc(add_months(dt,-2),'mm') start_date
       ,dt end_date
       ,to_char( dt, 'yyyy - q' ) qtr
 from (select add_months( trunc(sysdate,'Y') -1 , rownum*3) dt
        from all_objects
      where rownum <= 4)
 where sysdate between trunc(add_months(dt,-2),'mm') and dt;

I still think there is a better way to do it by using dual.

Excellent trick to take the last day of the year as the base date.
Rating: 5
Thanks February 04, 2003
Reviewer: Saru from Cincinnati, OH USA
I got it... :)

select trunc(add_months(add_months(trunc(sysdate,'Y') -1
,to_number(to_char(sysdate,'Q')) * 3),-2),'MM') start_date
      ,add_months(trunc(sysdate,'Y') -1 ,to_number(to_char(sysdate,'Q')) * 3)
end_date
      ,to_char(sysdate,'YYYY - q') qtr from dual
/
Rating: 5
Slightly simpler version February 04, 2003
Reviewer: Andrew from London, UK
Before reading this I didn't know trunc() did different levels of truncation -
incredibly useful information just when I needed it!

This version avoids all the to_char/to_number conversions (apart from the qtr
string):

SELECT trunc(SYSDATE) AS "SYSDATE",
       trunc(SYSDATE, 'q') AS "Q_START",
       add_months(trunc(SYSDATE, 'q'), 3) - 1 AS "Q_END",
       to_char(SYSDATE, 'yyyy - q' ) AS "QTR"
FROM dual
Rating: 4
Date Scaling September 02, 2003
Reviewer: Fred from irvine, ca
I have a similar situation.  Let's say I have a simple table like this:

SEQUENCE_NUMBER    EFFECTIVE_DATE
6    20021223
6    20021020
6    20021017
6    20021011
...
...

And I want the output of a query to be like:

Start_Date      End_Date
12/23/02        00/00/00
10/20/02        12/22/02
10/17/02        10/19/02
10/11/02        10/16/02
...
...

Where the End_Date column of the very first row will always be 00/00/00 and the
subsequent ones are 1 day subtracted from the Start_Date column of the previous
row.

Now I am able to do the scaling (subtract 1 day) part, but I haven't figured out
how to make the first row always 00/00/00.

Could you offer some insight?
Followup:
ops$tkyte@ORA920> select to_char( to_date( edate,'yyyymmdd'), 'mm/dd/yy' ),
  2         nvl( to_char( to_date( lag(edate-1) over (order by edate
desc),'yyyymmdd'), 'mm/dd/yy'), '00/00/00' )
  3    from t
  4   order by edate desc ;

TO_CHAR( NVL(TO_C
-------- --------
12/23/02 00/00/00
10/20/02 12/22/02
10/17/02 10/19/02
10/11/02 10/16/02
Rating: 4
Never mind September 02, 2003
Reviewer: fred from irvine, ca
I got help.

SQL> select * from t_edate;

SEQUENCE_NUMBER EFFECTIVE
--------------- ---------
              6 23-DEC-02
              6 20-OCT-02
              6 17-OCT-02
              6 11-OCT-02
              8 10-DEC-02
              8 30-DEC-02

6 rows selected.

  1  select
  2    b.sequence_number, to_char(b.effective_date,'MM/DD/YY'),
nvl(to_char(a.effective_date-1,'MM/DD/YY'),'00/00/00')
  3  from
  4    (select sequence_number,effective_date, rownum rnum from
  5     (select * from t_edate order by sequence_number, effective_date desc))
a,
  6    (select sequence_number,effective_date,rownum-1 rnum from
  7     (select * from t_edate order by sequence_number, effective_date desc)) b
  8  where
  9    b.rnum=a.rnum (+) and
 10    b.sequence_number=a.sequence_number (+)
 11* order by 1 desc, 2 desc
SQL> /

SEQUENCE_NUMBER TO_CHAR( NVL(TO_C
--------------- -------- --------
              8 12/30/02 00/00/00
              8 12/10/02 12/29/02
              6 12/23/02 00/00/00
              6 10/20/02 12/22/02
              6 10/17/02 10/19/02
              6 10/11/02 10/16/02

6 rows selected.
Followup:
see above -- much easier with analytics!!!
Rating: 5
So Simple, Yet Effective September 03, 2003
Reviewer: fred from irvine, ca
Now I will sit in the corner with the good ol' dunce cap on my head.  I must
overcome... ughhh.

Thanks.

Fred
Rating: 5
Generate one month date with or without date January 09, 2004
Reviewer: Sean from NJ, USA
Hi Tom,

create table t1(c1 date, c2 varchar2(20));

insert into t1
values(sysdate-5, 'A');

insert into t1
values(sysdate, 'B');

I would like to use a query to generate whole month with or without data.
someting like:

01-JAN-04
02-JAN-04
03-JAN-04
04-JAN-04     A
05-JAN-04
06-JAN-04
07-JAN-04
08-JAN-04
09-JAN-04     B
.
.
.
31-JAN-04

Thanks so much for your help.

Sean
Followup:
ops$tkyte@ORA9IR2> /*
DOC>drop table t;
DOC>
DOC>create table t ( c1 date, c2 varchar2(1) );
DOC>
DOC>insert into t values ( sysdate-5, 'a' );
DOC>insert into t values ( sysdate, 'b' );
DOC>*/
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select x.dt, t.c2
  2    from (
  3  select first+rownum-1 dt
  4    from ( select trunc(last_day(max(c1))) last, trunc(min(c1),'mm') first
from t ),
  5         all_objects
  6   where rownum <= last-first+1
  7         ) x,
  8             t
  9   where x.dt = trunc(t.c1(+))
 10  /

DT        C
--------- -
01-JAN-04
02-JAN-04
03-JAN-04
04-JAN-04
05-JAN-04 a
06-JAN-04
07-JAN-04
08-JAN-04
09-JAN-04
10-JAN-04 b
11-JAN-04
12-JAN-04
13-JAN-04
14-JAN-04
15-JAN-04
16-JAN-04
17-JAN-04
18-JAN-04
19-JAN-04
20-JAN-04
21-JAN-04
22-JAN-04
23-JAN-04
24-JAN-04
25-JAN-04
26-JAN-04
27-JAN-04
28-JAN-04
29-JAN-04
30-JAN-04
31-JAN-04

31 rows selected.

Of course, you need not use the ( select trunc(last_day(max(c1))) last,
trunc(min(c1),'mm') first from t ) if you KNOW the start/stop dates.  You would
just make the query be:
ops$tkyte@ORA9IR2> variable f varchar2(25);
ops$tkyte@ORA9IR2> variable s varchar2(25);
ops$tkyte@ORA9IR2> exec :f := '01-jan-2004'; :s := '31-jan-2004';

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select x.dt, t.c2
  2    from (
  3  select to_date(:f,'dd-mon-yyyy')+rownum-1 dt
  4    from all_objects
  5   where rownum <= to_date(:s,'dd-mon-yyyy')-to_date(:f,'dd-mon-yyyy')+1
  6         ) x,
  7             t
  8   where x.dt = trunc(t.c1(+))
  9  /

and you might even:
ops$tkyte@ORA9IR2> select x.dt, t.c2
  2    from (
  3  select to_date(:f,'dd-mon-yyyy')+rownum-1 dt
  4    from all_objects
  5   where rownum <= to_date(:s,'dd-mon-yyyy')-to_date(:f,'dd-mon-yyyy')+1
  6         ) x,
  7         (select *
  8            from t
  9           where c1 between to_date(:f,'dd-mon-yyyy') and
to_date(:s,'dd-mon-yyyy')
 10         ) t
 11   where x.dt = trunc(t.c1(+))
 12  /
Rating: 3
Similar problem... February 20, 2004
Reviewer: Neeti from TX
Hi Tom,

  I have a similar problem. I have a report which accepts
  a parameter in the form of YYYYQ and the requirement is
  this parameter to be transformed into last day of that
  quarter to use in a select statement.e.g. if I pass 19981
  then this should be transformed into 03311998(last day of
  first quarter)

  This I use currently...

select last_day(to_date (substr( '19981', 1,4) ||
       decode(substr('19981', 5,1),'1', '03', '2','06', '3'
                     ,'09', '4', '12'), 'YYYYMM'))
   from dual;

 Is there a way I can improve this SQL..?

  Thanks,
  Vimal
Followup:
select last_day(to_date( substr(:x,1,4) || (mod(:x,10)*3), 'yyyymm')) from dual;

is "shorter"
February 20, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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.
Rating: 2
pass two parameters to a procedure that is invoked from a job February 20, 2004
Reviewer: Ram from USA
I am trying to run this stored procedure. Tsa_ArchiveDoc( message IN Varchar2
,DocID IN Number ) is the procedure  I am trying to run from a job.
The job is
CREATE OR REPLACE procedure schedule_archivejob(DOCID IN number, name in
varchar2)
as
  a number ;
  b number ;
  doccomment varchar2(64);
begin
doccomment := 'Auto Archive';
dbms_job.submit(a, 'Tsa_ArchiveDoc("'||doccomment||'",'||DOCID||');',sysdate +
b/24 ,null);
dbms_output.put_line(doccomment);
end;
I am getting this error
'Auto Archive must be declared .
How to do this
Followup:
well, that string would translate into:

tsa_archiveDoc( "Auto Archive", 55 );

"Auto Archive" would have to be "an identifier" since you have it in quotes.
But-- in order to not SLAM your shared pool with unique jobs, I would strongly
(really strongly) encourage you to do the following:

create table archiveDoc_parms( job_id number primary key,
                               name varchar2(64),
                               docid );

and then, schedule the job like this:

...
begin
    dbms_job.submit( l_job, 'tsa_archiveDoc( JOB );', sysdate+b/24 );
    insert into archiveDoc_parms( doccoment, docid );
end;

and code the procedure as:

create procedure tsa_archiveDoc( p_job in number )
is
   l_rec archiveDoc_parms%rowtype;
begin
   select * into l_red from archiveDoc_parms where job_id = p_job;
   ...... process .....
   delete from archiveDoc_parms where job_id = p_job;
   commit;
end;
/

In that way, you'll be bind variable "friendly" and not have to worry about how
to properly construct a character string literal with quotes and all....
February 20, 2004 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.
Rating: 5
Bulk Colloct over dblink 9i ->8i February 20, 2004
Reviewer: Kim Piper from Baltimore, MD
Tom -

I see here that bulk collect is not supported over a db link in 8i.  We are
pulling data from an 8i (8.1.7.4.0)into a 9i instance(9.2.0.3.0).  Is utilizing
bulk collect across a dblink also not supported in 9i as well?  If it is not
supported then ignore everything below.  If it is supported then is my
driving_site hint what is causing the problem I illustrated below?

Here is my scenario:

Initially, I was trying to employ bulk collect and it worked, but after I added
the driving site hint (the performance of the individual query was much faster
and the explain plain looked better in sql plus) I "broke" my procedure.  I now
receive two errors:

ORA-00932: inconsistent datatypes
ORA-02063: preceding line from FIN2BCL  (dblink)

If I utilize a cursor for loop and the driving_site hint I receive no errors.
If I use a bulk collect then I get the errors.  Is the reason I am receiving the
above errors because by using the driving_site hint I am forcing the query to
"start" in the 8i database where bulk collect across a dblink is not supported?
In the explain plan it looks like my 9i table has become the remote table which
would then support my thinking.  Below is test case, output, and explain plan I
am referring to:

SQL> desc xxconvert.xxcus_exp_type_map
 Name                         Null?    Type
 ---------------------------- -------- -----------------
 INSTANCE                     NOT NULL VARCHAR2(20)
 BUSINESS_UNIT                NOT NULL VARCHAR2(20)
 SRC_EXPENDITURE_TYPE         NOT NULL VARCHAR2(30)
 DST_EXPENDITURE_TYPE         NOT NULL VARCHAR2(30)

declare
 C_CUTOFF_DATE_D CONSTANT DATE := TO_DATE('31-DEC-2003','dd-mon-yyyy');
    C_CONV_EXP_TYPE_S    CONSTANT    VARCHAR2(30)    := '(CONV) Summary';
   CURSOR cur_c
   IS
   SELECT  /*+ driving_site (ei) */
       ei.task_id
      ,ei.cost_distributed_flag
      ,ei.org_id
      ,DECODE ( SIGN(TRUNC(ei.expenditure_item_date) - C_CUTOFF_DATE_D)
            ,1,DECODE(ei.system_linkage_function,'BTC', 'BTC',NULL)
            ,NULL)                 as system_linkage_function
      ,DECODE ( SIGN(TRUNC(ei.expenditure_item_date) - C_CUTOFF_DATE_D)
            ,1, DECODE( et.expenditure_category,'Labor', 'Y', 'N')
            ,'N')                  as labor_flag
      ,DECODE ( SIGN(TRUNC(ei.expenditure_item_date) - C_CUTOFF_DATE_D)
            ,1,m.dst_expenditure_type
            ,C_CONV_EXP_TYPE_S)        as expenditure_type
      ,SUM(ei.quantity)    qty
      ,SUM (ei.burden_cost)    burden_cost
    FROM
    xxcus_exp_type_map m
    ,pa_expenditure_types@fin2bcl        et
    ,pa_expenditure_items_all@fin2bcl    ei
    WHERE ei.task_id =71900
    AND et.expenditure_type = ei.expenditure_type
    AND m.instance = 'EBIS'
    AND m.business_unit = 'CEG/BGE'
    AND m.src_expenditure_type = et.expenditure_type
    GROUP BY
     ei.task_id
    ,ei.cost_distributed_flag
    ,ei.org_id
      ,DECODE ( SIGN(TRUNC(ei.expenditure_item_date) - C_CUTOFF_DATE_D)
            ,1,DECODE(ei.system_linkage_function,'BTC', 'BTC',NULL)
            ,NULL)
    ,DECODE ( SIGN(TRUNC(ei.expenditure_item_date) - C_CUTOFF_DATE_D)
            ,1, DECODE( et.expenditure_category,'Labor', 'Y', 'N')
            ,'N')
    ,DECODE ( SIGN(TRUNC(ei.expenditure_item_date) - C_CUTOFF_DATE_D)
            ,1,m.dst_expenditure_type
            ,C_CONV_EXP_TYPE_S);

    type exp_rec IS RECORD
    (
     task_id            NUMBER
    ,cost_distributed_flag
pa_expenditure_items_all.cost_distributed_flag%TYPE
    ,org_id                NUMBER
    ,system_linkage_function
pa_expenditure_items_all.system_linkage_function%TYPE
    ,labor_flag            VARCHAR2(1)
    ,expenditure_type        pa_expenditure_types.expenditure_type%TYPE
    ,qty                pa_expenditure_items_all.quantity%TYPE
    ,burden_cost            pa_expenditure_items_all.burden_cost%TYPE);
    TYPE exp_sumTYPE IS TABLE OF exp_rec INDEX BY BINARY_INTEGER;
    l_exp_sum                exp_sumTYPE;
begin

    FOR l_rec IN cur_c LOOP
    dbms_output.put_line('inside
loop...expenditure_type::'||l_rec.expenditure_type);
    END LOOP;

    OPEN cur_c;
    LOOP
    FETCH cur_c BULK COLLECT INTO l_exp_sum LIMIT 100;
    FOR i in 1..l_exp_sum.COUNT LOOP
        dbms_output.put_line('bulk
collect...expenditure_type::'||l_exp_sum(i).expenditure_type);
    end loop;
    exit when cur_c%notfound;
    end loop;
    close cur_c;
exception
    when others then
    dbms_output.put_line('unexpected error::'||SQLERRM);
end;
/

output with driving site hint:

inside loop...expenditure_type::Fringe Benefits-Exempt
inside loop...expenditure_type::(CONV) Summary
inside loop...expenditure_type::Cont Services-Other
inside loop...expenditure_type::Material-Non Stock
inside loop...expenditure_type::Other Employee Costs
inside loop...expenditure_type::Travel and Ent-Other
inside loop...expenditure_type::Labor Exempt-Straight Time
unexpected error::ORA-00932: inconsistent datatypes
ORA-02063: preceding line
from FIN2BCL

PL/SQL procedure successfully completed.

output without driving_site hint:

inside loop...expenditure_type::(CONV) Summary
inside loop...expenditure_type::Material-Non Stock
inside loop...expenditure_type::Cont Services-Other
inside loop...expenditure_type::Other Employee Costs
inside loop...expenditure_type::Travel and Ent-Other
inside loop...expenditure_type::Labor Exempt-Straight Time
inside loop...expenditure_type::Fringe Benefits-Exempt
bulk collect...expenditure_type::(CONV) Summary
bulk collect...expenditure_type::Material-Non Stock
bulk collect...expenditure_type::Cont Services-Other
bulk collect...expenditure_type::Other Employee Costs
bulk collect...expenditure_type::Travel and Ent-Other
bulk collect...expenditure_type::Labor Exempt-Straight Time
bulk collect...expenditure_type::Fringe Benefits-Exempt

PL/SQL procedure successfully completed.

With driving_site hint:
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT (REMOTE) Optimizer=CHOOSE (Cost=936 Card=26
           Bytes=3380)

   1    0   SORT (GROUP BY) (Cost=936 Card=26 Bytes=3380)
   2    1     NESTED LOOPS (Cost=932 Card=35 Bytes=4550)
   3    2       NESTED LOOPS (Cost=3 Card=1 Bytes=87)
   4    3         REMOTE* (Cost=2 Card=1 Bytes=58)                     !
   5    3         TABLE ACCESS (BY INDEX ROWID) OF 'PA_EXPENDITURE_TYP BISY.GE.
          ES' (Cost=1 Card=81 Bytes=2349)                              BGE.COM

   6    5           INDEX (UNIQUE SCAN) OF 'PA_EXPENDITURE_TYPES_U1' ( BISY.GE.
          UNIQUE)                                                      BGE.COM

   7    2       TABLE ACCESS (BY INDEX ROWID) OF 'PA_EXPENDITURE_ITEMS BISY.GE.
          _ALL' (Cost=929 Card=2840 Bytes=122120)                      BGE.COM

   8    7         INDEX (RANGE SCAN) OF 'PA_EXPENDITURE_ITEMS_N17' (NO BISY.GE.
          N-UNIQUE) (Cost=13 Card=2840)                                BGE.COM

   4 SERIAL_FROM_REMOTE            SELECT "INSTANCE","BUSINESS_UNIT","SRC_EXPEN
                                   DITURE_TYPE","DST_EXPENDITURE_TYPE"

Thank you for providing such a valuable resource to the Oracle community I have
learned so much in the last few months since first visiting your site.

Regards,
Kim
Followup:
bulk collect was not supported over a dblink in 8i.

If your goal is to copy the data -- i would suggest just

insert into ...
select .......

(no code, just sql).  You could use a GTT if you need to "process" the data
(insert into gtt select ..., process gtt locally) in the meantime.
Rating: 5
GTT? February 20, 2004
Reviewer: Kim Piper from Baltimore, MD
GTT = Global Temporary Table?

Direct insert is unfortunately out, but a global temporary table is possible.

So is Bulk Collect from 9i to 9i supported?

Thanks,
Kim
Followup:
yes, bulk collects over dblinks are in 9i.
Rating: 5
FYI - PL/SQL BUG - 3475973 March 15, 2004
Reviewer: Kim Piper from Baltimore, MD
Tom,
After removing the bulk collect I continued getting errors so I logged a TAR.
As an easy work around we decided to just copy the two tables I needed to the 9i
database while Oracle works on the issue.  Apparently there is a bug when you
try to use the driving_site hint in a cursor pulling data from 8i into a 9i db.
The error shows up the second time through.
9i=9.2.0.3.0
8i=8.1.7.4.0
Just thought I would pass it on.
Regards,
Kim
February 20, 2004 Tom -- Thanks for the question regarding "Best way to copy From Global TEMP Table to an empty Standard Table", version 9.2.0
You Asked
Q1. I have a Global Temp (GT) (ON commit Preserve Rows).
    I do lots of processsing (mostly inserts, some updates)
    After processing and committing, I trucate a Permanent Table (P)   and want
to copy  GT ->P, say a million rows.
 What is the quickest way of achieving that
   - Drop P, CTAS P as select from GT
   - Some other ?

Q2. Curiosity - there does not seem a compelling reason why TEMP tables should
not persist across sessions. In which case I would not need to do the above
operation (i.e GT -P). That is, I want to exploit the minimal redo generation
aspect of GT but want more persistance. Is that a reasonable demand from Oracle?

thanks
Tom
and we said...
q1) insert /*+ APPEND */ into P select * from GT;

see
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96524/c21dlins.htm#11358
for detalis on that -- it is known as a direct path insert

q2) No, we give you the tools to do minimal redo generation on a permanent table
via non-logged bulk operations.

You do understand that even a GTT will generate GOBS of redo when you use UPDATE
and DELETE on it (generates MINIMAL with INSERT).

So, against a permanent table you can do many of the things you are asking with
bulk operations (CTAS -- doesn't have to be logged, INSERT /*+ APPEND */ -- does
not have to be logged, truncate -- no log)

But just remember that with GTTs and permanent tables -- UPDATES and DELETES --
whole lotta logging going on ...

ops$tkyte@ORA920.US.ORACLE.COM> create global temporary table t1
  2  on commit preserve rows
  3  as
  4  select * from all_objects
  5  where 1=0;

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create table t2
  2  as
  3  select * from all_objects
  4  where 1=0;

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set autotrace on statistics
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> insert into t1 select * from all_objects;

29303 rows created.

Statistics
----------------------------------------------------------
         97  recursive calls
      31377  db block gets
     138393  consistent gets
          0  physical reads
    3767208  redo size
        789  bytes sent via SQL*Net to client
        809  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      29303  rows processed

ops$tkyte@ORA920.US.ORACLE.COM> insert /*+ APPEND */ into t2 select * from all_o
                      bjects;

29303 rows created.

Statistics
----------------------------------------------------------
        387  recursive calls
        483  db block gets
     138450  consistent gets
          0  physical reads
    3390208  redo size
        773  bytes sent via SQL*Net to client
        823  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      29303  rows processed

ops$tkyte@ORA920.US.ORACLE.COM> commit;

Commit complete.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> update t1 set object_id = object_id+1;

29303 rows updated.

Statistics
----------------------------------------------------------
          0  recursive calls
      30079  db block gets
        799  consistent gets
          0  physical reads
    4472720  redo size
        789  bytes sent via SQL*Net to client
        806  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      29303  rows processed

ops$tkyte@ORA920.US.ORACLE.COM> update t2 set object_id = object_id+1;

29303 rows updated.

Statistics
----------------------------------------------------------
          0  recursive calls
      29986  db block gets
        407  consistent gets
        400  physical reads
    7171100  redo size
        789  bytes sent via SQL*Net to client
        806  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      29303  rows processed

ops$tkyte@ORA920.US.ORACLE.COM> commit;

Commit complete.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> delete from t1;

29303 rows deleted.

Statistics
----------------------------------------------------------
        128  recursive calls
      31547  db block gets
        863  consistent gets
          0  physical reads
    8449052  redo size
        790  bytes sent via SQL*Net to client
        783  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      29303  rows processed

ops$tkyte@ORA920.US.ORACLE.COM> delete from t2;

29303 rows deleted.

Statistics
----------------------------------------------------------
         56  recursive calls
      32713  db block gets
        810  consistent gets
        221  physical reads
   10464456  redo size
        790  bytes sent via SQL*Net to client
        783  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      29303  rows processed

ops$tkyte@ORA920.US.ORACLE.COM> commit;

Commit complete.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set autotrace off

so no, I would say "not a reasonable 'demand'" as we already supply it for the
most part with non-logged operations
Review & Followup

Rating: 4
Direct Path Load into GTT ? October 19, 2002
Reviewer: Robert C from PA
>>insert /*+ APPEND */ into P select * from GT;

Tom , does it do any good to do a "Insert /*+ APPEND */" into a GTT ?

thanks
Followup:
yes it can.

What happens (if you have my book "Expert one on one Oracle" i go into things
about temp tables...) is that

a) no REDO is generated for the GTT
b) however UNDO is generated for the GTT
c) and REDO for the UNDO *must* be generated

since an insert /*+ append */ can skip UNDO generation for the table (but not
index) segment -- it'll generate that much less REDO.

ops$tkyte@ORA920.US.ORACLE.COM> create global temporary table gtt on commit
preserve rows
  2  as
  3  select * from all_objects where 1=0;

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set autotrace traceonly statistics;
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> insert into gtt select * from all_objects;

29317 rows created.

Statistics
----------------------------------------------------------
         25  recursive calls
      31307  db block gets
     138355  consistent gets
          0  physical reads
    3765980  redo size
        782  bytes sent via SQL*Net to client
        810  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      29317  rows processed

ops$tkyte@ORA920.US.ORACLE.COM> commit;

Commit complete.

ops$tkyte@ORA920.US.ORACLE.COM> insert /*+ APPEND */ into gtt select * from
all_objects;

29317 rows created.

Statistics
----------------------------------------------------------
          7  recursive calls
          8  db block gets
     138276  consistent gets
          0  physical reads
        224  redo size
        768  bytes sent via SQL*Net to client
        824  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      29317  rows processed
Rating: 5
Space above HWM usage... October 21, 2002
Reviewer: pasko from HH,Germany
HI Tom,
As usual, thanks for a Great Discussion above!

I have this doubt:
I know that :

with : "insert /*+ APPEND */ into some_table
        select * from some_other_table "   ,

we use space only above the HWM  and less REDO generated.

But does the same hold true for insert without the APPEND HINT:

       "insert    into some_table
        select * from some_other_table "   ?

What are other major differences between the two ?

Thanks in advance.
Followup:
it'll generate LESS UNDO (which leads to less REDO) but in archive log mode,
it'll generate gobs of REDO as well unless the table is nologging.

without the append hint, it'll work as normal -- all UNDO and REDO is fully
generated and there is no way to reduce the amount.

If you click on that first link above -- you'll learn lots about what this
direct path insert does.
Rating: 4
October 21, 2002
Reviewer: Sagi from India
Hi !

Tom, Your solutions are the BEST. But for this question the user was :

1) Creating/Has a Global Temporary Table (GTT)
2) Does some operations on GTT.
3) Truncates the table P (Permanent Table)
4) CTAS or any other Solution which he asked...

You told that he can use

INSERT /*+ APPEND */ INTO P
AS SELECT * FROM GTT

But I was thinking he can as well do this, which is more faster:

1) Anyway doing TRUNCATE TABLE P
2) Now DROP TABLE P
3) RENAME GTT_TBL TO P
4) Create the TEMPORARY TABLE agin.

This is much more faster than the Direct Load. What do you say?

Regards,
Sagi
Followup:
renaming a gtt would be sort of useless.

a) no one else can ever see the data in it
b) the data would disappear right after you logged out.

so, while it might be "fast", it would not be "useful"
Rating: 4
October 21, 2002
Reviewer: Sagi from India
Hi Tom,

To see whether a table is GLOBAL TEMPORARY TABLE (GTT) or not we can execute the
below query:

SELECT * from USER_OBJECTS
where temporary='Y'

But i wanted to know if the GTT was a Session Based or Transaction Based i.e. ON
COMMIT PRESERVER ROWS or ON COMMIT DELETE ROWS.

Which Datadictionary (DD) table or columns in DD tells us this info?

Thanx in advance.

Regards,
Sagi
Followup:
select duration from user_tables;
Rating: 2
Why the Redo? October 21, 2002
Reviewer: A reader
Ok, so I give up.  How come GTTs need redo?  In the event of a server failure
both the transaction and session will end, so what value is redo?
Followup:
GTTs don't

UNDO does

GTTs generate undo (they have to -- you can rollback, rollback to savepoint and
have statements that fail halfway thru -- we roll those back as well).  In order
to rollback, we need UNDO.

For an INSERT, minimal undo.
For an UPDATE/DELETE -- lots more.

if all you do is INSERT and SELECT from GTT's -- little redo.  If you UPDATE
DELETE them -- correspondinly more.
Rating: 3
October 22, 2002
Reviewer: Sagi from India
Hi Tom,

I have one silly doubt. You said

"an insert /*+ append */ can skip UNDO generation"

q1) It can skip or it skips. If you say "can skik" can you give us example.
Because i was under assumption that always it skips.

q2) Why does REDO log get generated using "INSERT /*+ APPEND */. Because anyway
we it does not generate UNDO, and recovery is not possible, So why?

Thank you in Advance.

Regards,
Sagi
Followup:
q1) insert /*+ append */ into table_with_indexes select ....

that'll skip UNDO on the table
it cannot skip UNDO on the indexes

q2) see #1
Rating: 4
GTT vs. permanent? February 18, 2004
Reviewer: Ilya from New York
Hi Tom,

This is a very useful discussion, but I still have questions on global temporary
tables:

If a stored procedure is accessed in single-user mode (that is, it's a job that
runs on the server and not user-initiated), does it make sense to use Global
Temporary Tables or use Permanent tables with /*+append*/ hint? From what I read
above, there should be no difference in performance?

Also, is there any disadvantage to using global temporary table as opposed to a
permanent one in that situation? My DBA is insisting (rather vehemently) that I
change all global temporary tables into permanent, because they take up
temporary space. What is a reasonable size for temporary space in general? Right
now I have global temporary tables in all stored procedures (that need them),
and use permanet tables only for data I want to preserve.

Last one -- I prefer to have 'on commit preserve rows' so that when I debug a
stored procedure I can verify the data after it finished. Another reason is I
don't want to rely on where I commit inside procedure and rather truncate the
table myself (I truncate it at the start). What is the penalty in this case --
versus changing it to 'on commit delete rows.'

thanks,
ilya
Followup:
if you are doing something that requires a scratch table for the duration of the
transaction/session -- a GTT is appropriate, regardless of single user,
multi-user.

so what if "temporary stuff" takes up "temporary space"?   what the HECK is
wrong with that?

temp is temp.  They would rather you use real space that needs to be backed up,
allocated?  I mean -- you either have the space in tempfiles or you have the
space in datafiles.  At the end of the day, you have the space someone - no?

Your dba would have to give some scientific reason for wanting this in real
datafiles in order for me to comment -- I cannot even dream of a reason I'd want
temporary stuff in permanent datafiles?

there is no "penalty" -- if you want the data to persist for the session, you
don't have a choice.
Rating: 4
GTT and temp space February 19, 2004
Reviewer: A reader
Hi Tom,

thanks so much for a fast response.

The main reason I'm given is that if my stored procedure using a GTT hangs, it
will screw up the whole system, while if it uses permanent tables, only my
schema will be affected.  Is it possible to somehow isolate the temp space used
by my stored procedure from the rest?

Also, it seems the dba on the client side is reluctant to allocate even 1 GB for
temp space (I think they want to keep it at 300 Mb). I don't really understand
why (I'm hearing that the hard disk space used for database is very expensive).
So, presumably by using permanent tables there'll be more space for stored
procedure?

thanks,
ilya
Followup:
1gig of temp
or
1gig of permanent.

hmm, sounds like "6 one way", "1/2 dozen the other" doesn't it.

(that and you've already spent more of your companies money talking about this
issue then the disk would cost, really).

You have to have "X bytes of storage".

You can either "always have X bytes of storage" permanently allocated or in
temp.  I don't get it.
Rating: 4
Redo In GTTs February 20, 2004
Reviewer: VKOUL from Lacey, WA USA
Hi Tom

Pls. see the output below :

*************************************************
First Session (Same USER)
*************************************************
SQL> ed
Wrote file afiedt.buf

  1  create global temporary table gtt on commit
  2  preserve rows
  3  as
  4* select * from all_objects where 1=0
SQL>
SQL>
SQL>
SQL> /

Table created.

SQL> set autotrace traceonly statistics;
SQL>
SQL>
SQL> insert into gtt select * from all_objects;

24582 rows created.

Statistics
----------------------------------------------------------
         18  recursive calls
       1916  db block gets
      95778  consistent gets
        845  physical reads
     130208  redo size
        444  bytes sent via SQL*Net to client
        320  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      24582  rows processed

SQL> disconnect
Disconnected from 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>
*************************************************
Second Session (Same USER)
*************************************************
SQL> create global temporary table gtt on commit
  2  preserve rows
  3  as
  4  select * from all_objects where 1=0;

Table created.

SQL> set autotrace traceonly statistics;
SQL>
SQL> insert /*+ APPEND */ into gtt select * from
  2  all_objects;

24582 rows created.

Statistics
----------------------------------------------------------
         20  recursive calls
         18  db block gets
      95448  consistent gets
          0  physical reads
        184  redo size
        419  bytes sent via SQL*Net to client
        334  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          3  sorts (memory)
          0  sorts (disk)
      24582  rows processed

SQL> disc
Disconnected from 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>
*************************************************

Apart from REDO, why there is a huge difference between PHYSICAL READS, DB BLOCK
GETS between the two queries ?
Followup:
physical reads -- easy, the first query did them, the second benefited from the
first one doing them for it. (it cached the blocks)

the db block gets are due to the way APPEND works vs conventional SQL.

In order to insert into a block using conventional SQL, the block is retrieved
from the freelist in "current mode" -- we can only modify the "current version"
of the block.

In order to insert using APPEND, we just do direct writes to "new'ed" blocks, we
don't have to get them in current mode, they are "blank" to begin with.
Rating: 4
February 22, 2004
Reviewer: VKOUL from Lacey, WA USA
Thanks a lot
February 20, 2004 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.
Rating: 4
can I use analytical function in having clause February 06, 2004
Reviewer: A reader
I need order_id which were packed different people.
Let's say:
ID    packed_by
123     james
123     james
122     james
122     james
122     jeff
140     jeff
140     jeff
128     jeff
128     james
I need orders with id 122 and 128.
Can I use select something like this:
select id, packed_by
from t1,t1
where t1.ID=t2.ID
having count(*) over (partition by id) <>
       count(*) over (partition by packed_by)
Followup:
select id, packed_by
  from t
 where id in (select id from t group by id having count(distinct packed_by)>1)
/

or

select id, packed_by, cnt
  from ( select id, packed_by, count(distinct packed_by) over (partition by
id)cnt
           from t )
 where cnt > 1;
Rating: 3
Tom... February 20, 2004
Reviewer: Denise from Virginia,USA
please bear with me...I know I've been a real pest recently.
I'm working on several different projects..the primary
one being getting the required tables loaded into the 9i
db so I can start doing analysis reports using Analytic
Functions.

I mentioned in a previous thread about this matrix report.

I found a pl/sql block that another programmer was developing to run this report
3 years ago.It never got completed however I'm looking at it from the standpoint
as a flowchart.

I've read somewhere that Analytic Functions can now be
used with PL/SQL in 9i as long as you compile the cursor
SELECT as a view and select from the VIEW instead of
the actual TABLE.

I'm reviewing Chapter 12 of your book.

Perhaps given the power of AF with SQL maybe just scrap
PL/SQL altogether.

what is your take on this?

thanks...and I promise to stay away from your website(
for the rest of the day!)
Followup:
In 8i, you had to "hide" analytics with dynamic sql or a view.

In 9i, just "code away", you don't have to hide them anymore.

analytics work great in plsql in 9i -- no issues.
February 21, 2004 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.
Rating: 5
trial recovery February 20, 2004
Reviewer: Reader
Tom, is trial recovery command an RMAN command or SQL command? When I tried at
RMAN prompt I recieve the following error. However, after restoring the
tablespace using RMAN, and if I issue 'recover tablespace users test' command
from SQL prompt, no problem and it does work. I was wondering if trial recovery
command should be issued at SQL prompt? Thanks.

RMAN> recover tablespace users test;

RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-00558: error encountered while parsing input commands
RMAN-01005: syntax error: found "test": expecting one of: "archivelog, check,
comma, delete, from, high, noredo, ;, until"
RMAN-01007: at line 2 column 26 file: standard input
Followup:
that is the "user managed backup and recovery" command, yes
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96572/troublereco.htm#25755
February 21, 2004 Severino -- Thanks for the question regarding "retrive only numbers.", version 8.1.7
You Asked
Hi Tom,

Here is what I want: I have table with a field varchar2
and I want to query everything from the table where the varchar2 field has a
valid number.
i.e:

table has the following data:

name    seq
ab      10
cd      1-
ef      1a
gh      12
ij      1.0

the result should be
ab  10
gh  12

Everything that does not have a number format I reject.

This table could become to have 10000 rows, not much more.
But I will use the query I ask you very often to do updates in that table.

Thanks,
and we said...
well, if I wanted indexed access, I'd be using a function based index
http://asktom.oracle.com/~tkyte/article1/index.html
like this:

ops$tkyte@ORA920PC> create or replace
  2  function is_number( p_string in varchar2 )
  3    return number
  4  deterministic
  5  as
  6          l_num number;
  7  begin
  8          l_num := p_string;
  9          return 1;
 10  exception
 11          when others then return null;
 12  end;
 13  /

Function created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create table t
  2  as
  3  select decode( mod(rownum,50), 0, to_char(object_id), object_name ) data,
all_objects.*
  4    from all_objects
  5   where rownum <= 10000
  6  /

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert into t
  2  select 1.1, all_objects.* from all_objects where rownum = 1;

1 row created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert into t
  2  select -1.1, all_objects.* from all_objects where rownum = 1;

1 row created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert into t
  2  select '   -1.1   ', all_objects.* from all_objects where rownum = 1;

1 row created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create index t_idx on t(is_number(data));

Index created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> analyze table t compute statistics
  2  for table
  3  for all indexes;

Table analyzed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> alter session set query_rewrite_enabled=true;

Session altered.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> set autotrace traceonly

ops$tkyte@ORA920PC> select * from t where is_number(data) = 1;

203 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=39 Card=100 Bytes=11700)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=39 Card=100 Bytes=11700)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1 Card=40)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        185  consistent gets
          8  physical reads
          0  redo size
      18461  bytes sent via SQL*Net to client
        642  bytes received via SQL*Net from client
         15  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        203  rows processed

But if a full scan was OK, this would probably suffice:

ops$tkyte@ORA920PC> select *
  2    from t
  3   where decode( replace( translate(trim( data ),'0123456789','00000000'),
'0', '' ),
  4                 null, 1,
  5                             '.', 1,
  6                             '-.', 1 ) = 1;

203 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=40 Card=100 Bytes=11700)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=40 Card=100 Bytes=11700)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        216  consistent gets
          8  physical reads
          0  redo size
      18461  bytes sent via SQL*Net to client
        642  bytes received via SQL*Net from client
         15  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        203  rows processed
Review & Followup

Rating: 5
what if the string is "." or "-." ? February 20, 2004
Reviewer: SP from Manchester, UK
Tom,
Very nice way of extracting Only NUMBERS through a single SQL but what would
happen if the string itself is "." or "-." ??

Please see the following output:

SQL> ed
Wrote file afiedt.buf

  1  SELECT DECODE(
  2                REPLACE(
  3                         TRANSLATE(TRIM( '-.' ),'0123456789',  '00000000'),
  4                         '0', '' ),
  5                NULL, 1,
  6                '.', 1,
  7                '-.', 1 ) NUM_COL
  8* FROM DUAL
SQL> /

  NUM_COL
---------
        1

SQL> ED
Wrote file afiedt.buf

  1  SELECT DECODE(
  2                REPLACE(
  3                         TRANSLATE(TRIM( '.' ),'0123456789',  '00000000'),
  4                         '0', '' ),
  5                NULL, 1,
  6                '.', 1,
  7                '-.', 1 ) NUM_COL
  8* FROM DUAL
SQL> /

  NUM_COL
---------
        1

Or am I doing something wrong here ?

Thanks,
SP
Followup:
nope, you are not -- i did :)

modify to add

 decode( str, '.', 0, '-', 0, ( decode( replace ...., ..... ) )
Rating: 3
decode will return null if the string is "-10", any negative number February 20, 2004
Reviewer: R Perumal from NJ, USA
SQL> l
  1  select decode('&1',
  2           '.',0,
  3                '-',0,
  4                '-.',0,
  5
decode(replace(translate(trim('&1'),'0123456789','00000000'),'0', ''),
  6                null, 1,
  7                '.', 1,
  8*               '-.', 1 ) ) NUM_COL from dual
SQL> /
Enter value for 1: -10
old   1: select decode('&1',
new   1: select decode('-10',
Enter value for 1: -10
old   5:
decode(replace(translate(trim('&1'),'0123456789','00000000'),'0', ''),
new   5:
decode(replace(translate(trim('-10'),'0123456789','00000000'),'0', ''),

   NUM_COL
----------
Followup:
right, simply forgot to add

'-', 1,

to the decode list.
Rating: 3
February 21, 2004
Reviewer: Nopparat V. from Thailand
Can we use
 translate(string,'A0123456789','A')
instead of
 replace(translate(string,'0123456789','0000000000'),'0','')

Can this make it faster (even little)?

and how about this string '12-2004' case?
Followup:
if you want this 100%, use the "is_number", else you are going to end up writing
your own parser in case, decode, whatever.

give it a go -- we all have sqlplus, you should be able to sort of figure out
what fly's (or not)
Rating: 3
February 21, 2004
Reviewer: Nopparat V. from Thailand
I try to consider all other cases i.e. '123e4', '123e+4'.
Then the code will be:

select
  case
    when :x in ('+','-','.')
     then null
    when :x like '%e' or :x like '%e+' or :x like '%e-'
     then null
    when :x like '%e%+%' and :x not like '%e+%'
     then null
    when :x like '%e%-%' and :x not like '%e-%'
     then null
    else
     decode(translate(decode(substr(:x,1,1)
                             ,'+', substr(:x,2)
                             ,'-', substr(:x,2)
                             ,:x)
                      ,'a0123456789','a')
           ,'',1,'.',1,'e',1,'e+',1,'e-',1
           ,'.e',1,'.e+',1,'.e-',1,null)
  end is_num
from dual ;

This should works smarter but more complex. So I question about its performance
compare to the stored function you used previously. Which one should be faster?
What is your idea?
Followup:
pure sql will almost certainly be faster than calling plsql from sql however,
the function based index would make this a one time penalty (upon insert) so
since you insert once and query it many times over -- the plsql approach may
well "blow it away"

so -- yes, no and maybe are all valid answers depending on what you end up doing
with the data.
February 21, 2004 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 !
Rating: 5
To place all of the table blocks in buffer cache January 30, 2004
Reviewer: Reader
Tom, does select count(*) from table_name place all of the table blocks in the
buffer cache? Thanks.
Followup:
yes
no
maybe
sometimes

it depends.....

count(*) might not even hit the table (hope not!)

search for

"long and short"

on this site -- to read about the long and short of tables and how the cacheing
of a full scan works.
Rating: 3
OK January 31, 2004
Reviewer: R.Chacravarthi from Chennai,India
Dear sir,
 Regarding flushing buffer cache,I think in oracle 9i you can use
 sql>Alter session set events immediate trace name'..'
  I forgot the command but you can do
But in oracle 10g simple
 sql> alter system flush buffer cache;
Hope this helps
Bye!
Rating: 3
Nod from Cary Millsap February 21, 2004
Reviewer: R.Chacravarthi from Chennai,India
Dear sir,
Nice to meet you.I said we can flush the cache the with the
command specified over there.But you never responded.I mailed Millsap and he has
said that it is correct.But I was
very disturbed when you didn't put a followup.
My mail to him:
Dear Sir,
Well and wish the same from you.Doesn't the command
 sql>alter session set events 'immediate trace name
       flush cache';
Flushes the buffer cache in oracle 8i or above?Do you have any other way
for this?please do reply.
Bye!

Cary's reply to my mail as follows:

I don't have the resources at hand to check right now, but I believe
this is correct. In 10g, there's a more direct way to do it.

Cary Millsap
Hotsos Enterprises, Ltd.
Followup:
why where you disturbed?

I saw no reason to follow up, you provided information -- there you go.  why did
I need to followup with anything?

You said "here is a command you can use to do this.  In 10g it gets easier.
hope this helps".  what more need be said?

I guess I could have said:

but, as i said above, if you are using "cooked files" without directio as 99.99%
of the people out there are, flushing the buffer cache is quite simply "a really
silly, misleading thing to do -- you cannot make any judgements -- other than
leap to wrong conclusions -- based on it"

so, flush away, but beware, you are mostly just "wasting your time"
February 21, 2004 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.
Rating: 4
Advice January 16, 2004
Reviewer: Marcio from br
Tom looking forward for your advice about:
I have to code this:

table t
class   description   value
A0      AAAA          10
A0      AXXX          12
A1      DSDD          10
A1      DDDD          20
...

Have to, for this sample, generate 2 flat files grouped by class (A0 and A1) and
so on...
I didn't benchmark because I thought you've already seen thing like that about
thousands times before.
I'm thinking to code function to write a flat file and query to call the
function. But it is sql calling plsql, I don't think this is good idea. So,
guess to function would receive a query and write result to file.

I'm little confuse. Any advice?
thks,
Followup:
is this "2 rows -- one for A0 and A1 -- that have to be written to two separate
files"

or "there will be two files -- one with all of the A0 records and another with
all of the A1 records"
Rating: 4
flat file January 16, 2004
Reviewer: Marcio from br
it'll be --> "there will be two files -- one with all of the A0 records and
another with
all of the A1 records" , and so on, A3, A4, A5 and eachother with its rows...
Followup:
it'll be time to write client code -- in a 3gl probably.  plsql can do this with
utl_file, but if you have thousands of records, C or something faster for doing
write IO might be called for.

I don't see a query writing a file

I see a routine running a query with an order by to write a file.
Rating: 1
Server process January 16, 2004
Reviewer: Marcio from br
But it is a server process, and must to write in plsql.
Anyway, my choice either plsql-function to write a flat file receiving query
parameter (group by class) or I HAVE NO IDEA! :|
Followup:
a server process can be written in ANY LANGUAGE INVENTED BY HUMANS so far.

your pick.  if you want to do it in plsql -- go for it.

You'll have to design, implement (write code for) a simple "dump to file" using
utl_file.  everytime the field value you want to break on changes -- you'll
close and open a file.  It is an algorithm as old as dirt here (we used to call
it control break processing in the days of cobol).

see
http://asktom.oracle.com/~tkyte/flat/index.html
for a pointer to a plsql routine to get started.  you'll have to modify it to
close/open a file as the field changes.
Rating: 4
Ultimate fight February 19, 2004
Reviewer: Marcio from Brazil
Hi Tom,
Glad to come here to say that -- few performance result I've got with sqls set
and some pl/sql with bind variable:

- 5 hours batch process TO 36 secs
- 19 minutes screen oltp TO 7 hsecs

and so on...

I've been doing a great job with Oracle, due to the fact that they put me
together with java team which insist to use hibernate java. Have you heard about
it?
http://www.hibernate.org/hib_docs/reference/html/architecture.html#architecture-s1
What would be a best colaboration between us? I mean, what should be into
database and what should be write in java hibernate (dunno what)? Could you
gimme samples/arguments to justify your choice.

thank you so much!
Followup:
the problem is -- coders take the "it is all about the application" approach,
which in my experience is 100% wrong -- "it is all about the data".

Applications come
Applications go
Applications disappear and no one cares

As long as the data is there.

Application programming paradigms come
and they go...
and they change....
and they have so many to choose from.....

It is not about the application and "object oriented" and this that and the
other thing.

For most people, it is getting the data they want, when they want it, in the
form they need it, quickly.

Hibernate looks pretty "scary" to me.  especiall the "full cream" picture.
Rating: 5
Why hibernate looks scary to you? February 19, 2004
Reviewer: Marcio from Brazil
Followup:
did you see the picture.  when I see things like

"transaction handling outside the database"

and the desire to abstract the database to a bit bucket.... that scares me.

it also reminds me of 3270 green screen applications with security in the UI at
the panel level and data access so tied up in the UI that it was near impossible
to rip the pieces apart when the "paradigm" changed.
Rating: 5
A "pearl" from Hibernate docs... February 20, 2004
Reviewer: Franco from Denmark
".......Deleting persistent objects
Session.delete() will remove an object's state from the database. Of course,
your application might still hold a reference to it. So it's best to think of
delete() as making a persistent instance transient.
.......
You may now delete objects in any order you like, without risk of foreign key
constraint violations. Of course, it is still possible to violate a NOT NULL
constraint on a foreign key column by deleting objects in the wrong order....."
And so on... funny, how to make complex and very very dangerous a simple, safe
(at least in Oracle) 'delete' :-))
Followup:
thats the problem with many middle tier java object caches too -- some of them
actually delete the "in memory version", update their logs and later get around
to telling the database to delete the thing.  You can imagine the havoc this
wreaks on a system were other things actually want to use the database as well.
Rating: 5
Delete February 20, 2004
Reviewer: Tony Andrews from London UK
> You can imagine the havoc this wreaks on a system were other things actually
want to use the database as well.

Unfortunately it seems a lot of people can't!
Rating: 5
This is a 'good way'... February 20, 2004
Reviewer: Franco from Denmark
... to serialize not only the transactions but also the applications...
Rating: 4
RE: Franco February 20, 2004
Reviewer: Marcio from Brazil
Sorry, I don't know nothing about java, why would serialize?
Rating: 3
Serialize February 21, 2004
Reviewer: Marcio from Brazil
Tom, could hibernate serialize my application? Why? Any url to learn more?

/thanks
Followup:
I don't know, I don't use it.
February 21, 2004 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.
Rating: 5
Rebuild parts of LOCAL indexes January 19, 2004
Reviewer: steve from Montreal
Hi again,

I have 2 questions in what follows highlighted by

QUESTION 1:    and
QUESTION 2:

In regards to the topic above (Backing up fact table)
we have decided to see if we can exclude indexes from
the backup solution (for the huge partitioned fact tables
only. These fact tables have only LOCAL indexes).
We think that, with proper documentation, we can rebuild
in an acceptable abount of time.

So I'm trying to run a simulation of how we would
recover from a hosed index. If we lose an index datafile
that affects only one tablespace (possibly 31 partitions),
I want to make sure we can recover that tablespace
without rebuilding the whole index.

Example:
========

SQL> create table t (i int, j int) partition by range (i) (
     partition part_t1 VALUES LESS THAN (10) tablespace t1_data,
     partition part_t2 VALUES LESS THAN (20) tablespace t2_data,
     partition part_t3 VALUES LESS THAN (30) tablespace t3_data);

SQL> insert into t values (3,3);

1 row created.

SQL> insert into t values (13,13);

1 row created.

SQL> insert into t values (23,23);

1 row created.

SQL> create index t_i on t(i) local (
     partition part_t1 tablespace t1_index,
     partition part_t2 tablespace t2_index,
     partition part_t3 tablespace t3_index);

Index created.

Now, suppose we lose the t2_index tablespace:

$ mv t2_index   t2_index_hide

We can't drop the index partition:
----------------------------------

SQL> alter index t_i drop partition part_t2;
alter index t_i drop partition part_t2
            *
ERROR at line 1:
ORA-14076: DROP/SPLIT PARTITION may not be applied to a LOCAL index partition

We can't drop the tablespace
----------------------------

SQL>  drop tablespace t2_index INCLUDING CONTENTS ;
 drop tablespace t2_index INCLUDING CONTENTS
*
ERROR at line 1:
ORA-14405: partitioned index contains partitions in a different tablespace

This is what I came up with:
----------------------------

SQL>  select index_name,status ,tablespace_name  from user_ind_partitions where
INDEX_NAME='T_I';

INDEX_NAME                     STATUS   TABLESPACE_NAME
------------------------------ -------- ------------------------------
T_I                            USABLE   T1_INDEX
T_I                            USABLE   T2_INDEX
T_I                            USABLE   T3_INDEX

SQL> alter index t_i modify  partition part_t2 unusable;

Index altered.

SQL> alter index t_i rebuild  partition part_t2 tablespace cd_index
  2  /

Index altered.

SQL>  drop tablespace t2_index INCLUDING CONTENTS ;

Tablespace dropped.

SQL> create tablespace t2_index datafile '/index1/t2_index' size 50M;

Tablespace created.

SQL>  alter index t_i rebuild  partition part_t2 tablespace t2_index;

Index altered.

QUESTION 1:
==========

  Would the following be the fastest way to rebuild?

     1. set index unusable
     2. rebuild on different tablespace (parallel nologging)
     3. drop original tablespace
     4. re-create original tablespace
     5. rebuild on original tablespace (parallel nologging)

  There are 2 rebuilds occurring so I'm wondering if
  there's not a way to skip one of the rebuilds, I tried
  to move the LOCAL index for step 5 but I got the error:

  SQL> alter index t_i move partition part_t3 tablespace t2_index;

  alter index t_i move partition part_t3 tablespace t2_index
                  *
  ERROR at line 1:
  ORA-02243: invalid ALTER INDEX or ALTER MATERIALIZED VIEW option

QUSETION 2
==========

While testing this, I saw something strange. The index file
was gone, but life went on:

SQL> select count(*) from t partition (part_t2);

  COUNT(*)
----------
      8192

SQL> commit;

Commit complete.

SQL> !/bin/mv /index1/t2_index /index1/t2_index_hide

SQL> insert into t select * from  t partition (part_t2);

8192 rows created.

I guess it did not fail here because everything wsa cached in
memory?

SQL> commit;

Commit complete.

I thought an analyze (what follows) would have caused it to fail,
but again I guess everything was cached in memory?

SQL>  analyze table t compute statistics for table for all indexes
      for all indexed columns;

Table analyzed.

I thought a checkpoint would have flagged the problem but
that didn't either:

SQL> alter system checkpoint;

System altered.

But the analyze after the checkpoint did:

SQL> analyze table t compute statistics for table for all indexes for all
indexed columns;
analyze table t compute statistics for table for all indexes for all indexed
columns
*
ERROR at line 1:
ORA-00376: file 308 cannot be read at this time
ORA-01110: data file 308: '/index1/t2_index'

QUESTION 2:
===========

  I was surprised that the checkpoint didn't return an
  error. Can you give me some insights on what was happening
  (i.e. why analyze didn't trip up, why checkpoint didn't trip
   up, etc)

Tom: I know you hear it all the time, but thank you
     so very much for all your help you have given.
     I mean that sincerely.

Steve
Followup:
before we go into this -- why would you do this?  I mean -- recovery is
something you want to be easy, to be fast, to just "go".  you are making it
hard.

If you don't want to back these puppies up -- just use a tablespace per index
(at most).  it'll be easier.

moving datafiles isn't a "good test".  When a process opens a file -- you can
move that file in another session without that process losing access to it.
You've only moved the definition of the file.  You have to take the disk away to
simulate a lost file like that.
Rating: 5
Reason behind this complicate strategy January 19, 2004
Reviewer: steve from Mtl
Hi Tom,

First, the easy part. I redid the test with 'rm' instead
of 'mv'. This time the analyze failed:

SQL> analyze table t compute statistics for table for all indexes
      for all indexed columns;
analyze table t compute statistics for table for all indexes
*
ERROR at line 1:
ORA-01116: error in opening database file 308
ORA-01110: data file 308: '/index1/t2_index'
ORA-27041: unable to open file
SVR4 Error: 2: No such file or directory
Additional information: 3

Thanks for that one.

Next, the tough question. I know I am making things harder in an
area where simplicity is key. But let me put things into perspective.

We currently have about 8 installations. Each installation
is in the 2-3 terabyte range. We generate about two to
three hundred archived redo log files per day. None are
running in archive log mode and there are no backups
(cringe),

Now, when we start talking backup solution with the
customer and start talking cost (we've got to put
this data somewhere), well...., you get the picture.

I know I know I know we and they are asking for
trouble and it's just a matter of time before we
lose data. But that's where we are right now.

So, rather than going with no backup solution, we are
trying to develop the most cost effective solution. Since
roughly 20% of our data is indexes, that's significant.
Because we are in a situation where rebuilding the indexes
is relatively easy, we are analyzing the feasability
of not backing up the indexes.

Also, changing the index/tablespace assignment is not very
simple at this point; then again, neither is this backup strategy ;)

That being said, does the approach described above sound
reasonable? Is there a way to avoid the 2 rebuilds?

Thanks Tom,

Steve
Followup:
rm will have the same issues, you just got "lucky".  You can erase a file and
processes will still be able to write to it until they close it themselves.

did you know you can buy a terabyte of ATA drives for about 1500$ USD?  cost
less then this conversation probably will at the end of the day.

If the customers are unsophisticated enough to

a) run a terabyte plus
b) without having any plans for backups
c) on a transactional system

you MUST make this trivial, simple, bullet proof.  being penny wise, pound
foolish is only going to cause massive problems here.

They have partitioning, they have big, they deserve better and it's not nearly
as costly as you believe for backup -- even to JBOD (just a bunch of disk).

but sigh, here is a set of steps you can undertake, i used OMF (oracle managed
files) so your steps will be a little different if you do not (the AS NEW in the
alter database CREATE DATAFILE for example would be different)

ops$tkyte@ORA9IR2> create tablespace t1_data datafile size 1m;
Tablespace created.

ops$tkyte@ORA9IR2> create tablespace t2_data datafile size 1m;
Tablespace created.

ops$tkyte@ORA9IR2> create tablespace t3_data datafile size 1m;
Tablespace created.

ops$tkyte@ORA9IR2> create tablespace t1_index datafile size 1m;
Tablespace created.

ops$tkyte@ORA9IR2> create tablespace t2_index datafile size 1m;
Tablespace created.

ops$tkyte@ORA9IR2> create tablespace t3_index datafile size 1m;
Tablespace created.

ops$tkyte@ORA9IR2> create table t (i int, j int) partition by range (i) (
  2       partition part_t1 VALUES LESS THAN (10) tablespace t1_data,
  3       partition part_t2 VALUES LESS THAN (20) tablespace t2_data,
  4       partition part_t3 VALUES LESS THAN (30) tablespace t3_data);
Table created.

ops$tkyte@ORA9IR2> create index t_i on t(i) local (
  2       partition part_t1 tablespace t1_index,
  3       partition part_t2 tablespace t2_index,
  4       partition part_t3 tablespace t3_index);
Index created.

ops$tkyte@ORA9IR2> insert into t ( i, j )
  2  select rownum, rownum from all_users where rownum < 30;
29 rows created.

ops$tkyte@ORA9IR2> column file_name new_val F

ops$tkyte@ORA9IR2> select file_name from dba_data_files where tablespace_name =
'T2_INDEX';

FILE_NAME
------------------------------
/home/ora9ir2/oradata/ora9ir2/
o1_mf_t2_index_00rfj4w0_.dbf

ops$tkyte@ORA9IR2> !ls -l &F

ops$tkyte@ORA9IR2> alter database datafile '&F' offline drop;
old   1: alter database datafile '&F' offline drop
new   1: alter database datafile
'/home/ora9ir2/oradata/ora9ir2/o1_mf_t2_index_00rfj4w0_.dbf' offline drop
Database altered.

ops$tkyte@ORA9IR2> !msu -u ora9ir2 rm &F
ops$tkyte@ORA9IR2> !ls -l &F

ops$tkyte@ORA9IR2> analyze index t_i validate structure;
analyze index t_i validate structure
*
ERROR at line 1:
ORA-00376: file 21 cannot be read at this time
ORA-01110: data file 21:
'/home/ora9ir2/oradata/ora9ir2/o1_mf_t2_index_00rfj4w0_.dbf'

ops$tkyte@ORA9IR2> alter database create datafile '&F' as new;
old   1: alter database create datafile '&F' as new
new   1: alter database create datafile
'/home/ora9ir2/oradata/ora9ir2/o1_mf_t2_index_00rfj4w0_.dbf' as new
Database altered.

ops$tkyte@ORA9IR2> select file_name from dba_data_files where tablespace_name =
'T2_INDEX';

FILE_NAME
------------------------------
/home/ora9ir2/oradata/ora9ir2/
o1_mf_t2_index_00rfj6g3_.dbf

ops$tkyte@ORA9IR2> alter database recover datafile '&F';
old   1: alter database recover datafile '&F'
new   1: alter database recover datafile
'/home/ora9ir2/oradata/ora9ir2/o1_mf_t2_index_00rfj6g3_.dbf'
Database altered.

ops$tkyte@ORA9IR2> alter tablespace t2_index online;
Tablespace altered.

ops$tkyte@ORA9IR2> alter index t_i rebuild partition part_t2 compute statistics;
Index altered.

ops$tkyte@ORA9IR2> analyze index t_i validate structure;
Index analyzed.
Rating: 5
Thanks! January 19, 2004
Reviewer: steve from Steve
Thanks a lot Tom. Really cool.

Put some ice on you forehead - it'll help (I heard
you banging you head on your desk with this one - not
for technical reasons of course, but for trying to drive
your point across).

Rest assured, your point has been taken.

I learnt a lot from this exercise and will no longer
discuss complicated backup solutions.

Now on the market for cheap disks...

Steve
Rating: 4
Thanks Tom January 19, 2004
Reviewer: Murali from Bangalore, India
Tom,
Thank you for your everlasting replies.
I had one doubt.
1) How do i disable my stored procedure and later enable it
Can I do it without dropping it? Which table or view gives me the status of a
stored procedure or Function (whether enabled or disabled)?
I am asking this because once i happened to re-create a function (using create
or replace) and at the same time some other transaction was also calling that
function. So it got locked and used all the resources. What my DBA told me was
that "he has, at present, disabled the stored procedure. Once everything is
fine, I would re-enable it. " In the mean-time, I did a "create or replace" of
the same function and the function was created. But still the DBA told me that
the function has to be enabled to make it accessible in the front end. What does
he mean by "Enabling". All these days i have been thinking that by recreating a
function, I would be re-enabling it? what does the DBA mean in this context?
Please explain.
Followup:
huh?

what do you mean by "disable a stored procedure"

you cannot disable a stored procedure

Say you have a procedure P.

Someone is running P.

You compile a new version of P -- it will block -- but also everyone who tries
to run p will block as well (waiting for p to be compiled)

when the first guy finished running P, p will recompile (that will auto-unblock)
and then everyone waiting to run p will unblock.

You will not consume "all resources", you will just be put to sleep for a while,
get blocked.

Either you misunderstood the DBA or the DBA is making up terms.  No idea what
they did when they said "i disabled your procedure"
Rating: 3
Thanks January 22, 2004
Reviewer: Murali from Bangalore, INDIA
Tom..Thanks for ur reply.
I would like to know which table to access to check the status of any stored
procedure or Function?

Regards,
Murali
Followup:
user_objects
dba_objects
all_objects

look at the status column
Rating: 3
Reg logs January 29, 2004
Reviewer: Murali from Rockville, Maryland
Hello Tom,
Thanks for the reply.
I have one more query.
We have a table called logs which has 2 columns, filename and filedate. There
are about 16 million records in this table. I wanted to get a optimal query for:
i) Getting the filename and count(filename) and the filedate from this table
where the filename has been repeated in intervals of 24 hrs time, i.e., if
filename today is "sssss" then the same file should be repeated tomorrow also
(i.e. tomorrow - today <= 24 hrs).
Please get back me ASAP because i am stuck on this for a long time.

Thanks and regards,
Murali
Followup:
don't understand.  need a simple example of inputs and outputs.
Rating: 3
Reg Logs January 29, 2004
Reviewer: Murali from MD
i need the following output:
filename count(filename) filedate

XXXX     1                10-jan-2004
YYYY     2                11-jan-2004

I want the result such that where count(filename) >1...
I think u can understand this..this was what was told to me also...

We have a table called logs which has 2 columns, filename and filedate. There
are about 16 million records in this table. I wanted to get a optimal query for:
i) Getting the filename and count(filename) and the filedate from this table
where the filename has been repeated in intervals of 24 hrs time, i.e., if
filename today is "sssss" then the same file should be repeated tomorrow also
(i.e. tomorrow - today <= 24 hrs).
Please get back me ASAP because i am stuck on this for a long time.
Followup:
sorry -- you've lost me totally.

this sentence in particular:

filename today is "sssss" then the same file should be repeated tomorrow also
(i.e. tomorrow - today <= 24 hrs).

it not making anysense.  that you understand is good -- that i understand
because you understand does not however naturally follow.

so, if you have "filename" at 10-jan-2004 23:50:00 and "filename" at 11-jan-2004
00:50:00 -- when then? are they to be aggregated together or what and if so,
what would the filedate be then -- the 10th or the 11th and what if it keeps
going like that so you really have dates of 10, 11, 12, 13, 14, ...... and so
on.
Rating: 3
Reg Logs January 29, 2004
Reviewer: Murali from MD
Hello Tom,

No..it is not like that.

Ok...here is how the query must be:
give me the count(filename) where tomorrow-today<=24 hrs and the filename is
repeating. (i.e. get me all results where today's filename = tomorrow's filename
and tomorrow-today<=24 hrs)..
got it...
I am really sorry for confusing you a lot with this...but this is how i can
explain you the situation..

Thanks and regards,
Murali
Followup:
so, what is "today" and what is tomorrow.

Like I said -- give me a small, concise, yet complete example with a couple of
rows of input data.  show boundary value conditions.  explain how the inputs
shown logically make the output you will also show.

"an example" is called for here.
Rating: 3
Reg Logs January 30, 2004
Reviewer: Murali from MD
Hello Tom,
Ok here goes the sample...

filename            filedate
_____________________________________
SBI21983.PDF        28-NOV-01
SBI21986.PDF        28-NOV-01
SBI21987.PDF        28-NOV-01

Here the filename is not being repeated on the same date.
I want to get all records (count of records) along with the filename and date
where the filename has been repeated for the same date.(remember i told u abt
today and tomorrow..this is what it is..).
There are about 16 million records in this particular table and i want an
optimised query to get me such a result.
Please help me out in this regard ASAP. I need to give a definitive answer to
the client by end of today.

Thanks and regards,
Murali
Followup:
select filename, filedate, count(*)
  from t
 group by filename, filedate
having count(*) > 1;
Rating: 4
Reg logs January 30, 2004
Reviewer: Murali from MD
Thank u Tom...i had tried this out...but it hung..was taking a very long time to
come out..and finally gave a "Could not extend Temp tablespace by 256" error....
any other optimised way of doing this?

anyway I will try this out again...

Thanks,
Murali
Followup:
if you have an index on filename, filedate and one of filename, filedate is
defined as NOT NULL, this'll work fine using that index (it'll group by nosort)

even with 16million rows though, this should NOT take too much temp -- you most
likely have insufficient temp configured.  also look at your sort area/pga
aggregate target.  perhaps they are too small as well.

correctly configured, this is a minute long query

ops$tkyte@ORA9I> select count(*) from t;

  COUNT(*)
----------
  13455885

select filename, filedate, count(*)
  from t
 group by filename, filedate
having count(*) > 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    11820     55.35      56.12      76433      66641         18      177273
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    11822     55.35      56.12      76433      66641         18      177273

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 51012

Rows     Row Source Operation
-------  ---------------------------------------------------
 177273  FILTER  (cr=66641 r=76433 w=9911 time=55772842 us)
 214050   SORT GROUP BY (cr=66641 r=76433 w=9911 time=55454964 us)
13455885    TABLE ACCESS FULL OBJ#(68533) (cr=66641 r=66522 w=0 time=13606971
us)
Rating: 4
Reg Logs February 03, 2004
Reviewer: Murali from MD
Thank u Tom...Actually I had not created an index on the filedate. i created it
and it is quicker now with the same plan as u have shown.
Thanks for that.

And one more thing.
I need to get the and the count(filename) filedate, and filename which have been
viewed more than once in a gap of 24 hours. How do i query that. I tried using
an inline view, but it did not work. Could you suggest me something on this.

I want it like this:

Filename   Filedate     count
____________________________
AAAAA      28-Nov-2002   2 or more

Could you please help me out on this.

Regards,
Murali
Followup:
my query does that already?  shows the filename, the filedate and the count???
Rating: 3
Reg Logs February 03, 2004
Reviewer: Murali from MD
Hello Tom,
But it does not show the files which have been repeated on the same day, right?

Regards
Murali
Followup:
that is exactly what it does?

select filename, filedate, count(*)
  from t
 group by filename, filedate
having count(*) > 1;

gets all filename/filedates such that there are MORE THAN one of them in the
table (duplicates).

that is exactly what it does.
Rating: 4
Reg Logs February 04, 2004
Reviewer: Murali from MD
Thank you Tom for that clarification.

I have 2 more questions.
1) I am loading a table (using sql loader). There are around 17 million rows in
the file (approx.).
I want to know whether I can specify the tablespace name when loading this
table. I think by default it goes to USERS tablespace. This is because, the
loader fails with SQL-Loader-605 error (space problem) after loading 12 million
rows.
I am using Oracle 9.1.2 sqlldr version.

2) I have a 870 MB file (corresponding to about 17 million records). I want to
load only the last 3 million rows into a table. Is it possible to do that using
sql loader?

Thanks,
Murali
Followup:
1) the table must exist before you run sqlldr -- you specify the tablespace in
the create table -- hence, no sqlldr cannot specify the tablespace but then
again, sqlldr isn't creating the table so it doesn't really "make sense"

2) sqlldr ..... skip=14000000

(you'd sort of need to know EXACTLY how many records to skip)
Rating: 4
Reg Logs February 04, 2004
Reviewer: Murali from MD
Thank u Tom
Rating: 5
related Group By ..Having ....Clause verification... February 05, 2004
Reviewer: pasko from Hamburg,Germany
Hi Tom,
thanks for a Great Support you are providing to us .

:
I have written a Query which answers the following :

"
get distinct expired phone_id's which are older than some specified number of
Days from current_date , by comparing a Date column ( my_timestamp ) from
my_table ;
a phone_id could be duplicated , but the query should return only ONE phone_id
in a group whereby all the Phone_id's have expired "

--i hope i have explained that correctly.

My initial try is :

select  phone_id
from    my_table main
where
my_timestamp < sysdate - 5
--Get phone_ids older than 5 Days
group by phone_id
having ( count(*) =
         select count(*)
         from   my_table subQ
         where  subQ.phone_id = main.phone_id
        )

is this Query correct ?
Any other ways of writing this query for fast Response Times?

Thanks in advance.

Best Regards.
Followup:
why not just

select distinct phone_id from t where my_timestamp < sysdate - 5;

?
Rating: 5
How about Duplicate Rows .. February 05, 2004
Reviewer: pasko from Hamburg, Germany
Hi Tom ,
Thanks for a prompt Response..

if i have data like this:

phone_id      my_timestamp
---------------------------
1             sysdate
1             sysdate - 11
1             sysdate - 12

2             sysdate - 15
2             sysdate - 200

Then my Query should give back only Phone_id  2 ,
in which all of the my_timestamp values are older than 5 days from now.

Phone_id = 1 does should not be returned in this case because it has one row
with my_timestamp = sysdate ,which is not older than 5 Days from now.

I hope you understand what i mean..

Thanks.
Followup:
select phone_id
  from t
 group by phone_id
having count(*) = count( case when my_timestamp < sysdate-5 then 1 end );

would be one way....
Rating: 5
backup data of large volume February 06, 2004
Reviewer: Mahabal RAi from India
i feel that it may little more eloborate then people will more benift as well
your database will grow tramoundously and more over it will be usefull to
everybody. I once again request to fire more question to asktom.oracle.com but
asktom replies that he can accept only 10 question that too when he will be
free. It is better organise to accept more questions so that people will get
benift. This is my review.
Followup:
Well, "asktom" really is -- ask Tom, a single person.

It isn't "ask tom and some group of people" ;)
Rating: 5
How to represent this Query without using a SQL Minus Operator February 21, 2004
Reviewer: pasko from Hamburg,Germany
Hi Tom,
Your Answer above is Great !

Now i need to extend my phone_ids Question above a little bit.

I also have another Table : say table t2 , which has some phone_ids ; the only
difference to the Table t above is that , the phone_ids in this Table are not
Duplicated , but some of the phone_ids may be present in table t1.

So ,i need to combine the Results of the Tables: t1 and t2 ,
and add another condition that : the Union Query should not output Rows with
phone_ids which are in EXPIRED and in either of the two Tables but not in both
Tables.
--hope you get me there..

"
i.e Output the row if and only if the phone_id is expired in   either of the
tables but do not output the row if phone_id is expired in one Table but has not
expired in the other Table. "

thinking in sets :
( I guess i might call this Exclusive-OR in set Theory ? )
i would like the Result set:

 Expired phone_ids in
( t1 Union t2 )
 minus
( t1  intersection  t2 )

If i am correct in my in-sets-thinking , then i would be able to write that
query in Oracle using union/minus(intersect Operators.

But i need also to write the Query for other Databases , for example MS-SQL
Server ,which do not have the Minus Operator;

i heard that we can also represent the Minus with the not exists Operator:

I tried this one :

--your query
select phone_id
from   t1
where
not exists( select 1 from t2 where t2.phone_id =t1.phone_id )
group by phone_id
having count(*) = count( case when my_timestamp < sysdate-5 then 1 end )
union
select phone_id
from t2
where
my_timestamp < sysdate-5
and not exists (
select 1 from t1 where t1.phone_id = t2.phone_id )

1. Is my thinking above to answer the Query Ok ?
2. Do you have a simplified Query to get the desired Result set ?

Thanks in advance.

Best Regards.
Followup:
"the Union Query should not output Rows with
phone_ids which are in EXPIRED and in either of the two Tables but not in both
Tables."

i.e Output the row if and only if the phone_id is expired in   either of the
tables but do not output the row if phone_id is expired in one Table but has not

expired in the other Table.

seem to conflict?

first one seems to say:
do not output if (expired in T1 or T2 )(in either of the tables)
and NOT ( expired T1 and T2 )

second seems to say:

output if (expired in T1 or T2) and
NOT (( expired T1 and not expired T2 ) or (expired T2 and not expired T1 ))

sorry -- guess I'm just getting confused on this one. can you state in a more
"rule based way" -- maybe a truth table would help.
Rating: 5
February 22, 2004
Reviewer: pasko from HH,Germany
Hi Tom ,
Thanks for your prompt Response and sorry for the Confusion..

if i have data like this: some of the data taken from the previous Example for
Table  t1 , whose Conditions should also hold true :
i.e
(
Table t1 should give back only those Phone_IDs, in which all  of their
my_timestamp values are older than 5 days from now, eg. phone_id 2 ;
Phone_id = 1 should not be returned in this case because it has one row with
my_timestamp = sysdate ,which is not older than 5 Days from now
)

Table  t1 :
---------------------------
phone_id      my_timestamp
---------------------------
1             sysdate        -->not all( t1 ) expired but t2
1             sysdate - 11   -->not all( t1 ) expired but t2
1             sysdate - 12   -->not all( t1 ) expired but t2
2             sysdate - 15   --*all( t1 ) expired and  T2
2             sysdate - 200  --*all( t1 ) expired and  T2
4             sysdate - 200  --expired t1 and NOT IN t2
5             sysdate        --Not expired t1 but expired t2
6             sysdate - 200  --expired     t1 but not in  t2

Table  t2 :
---------------------------
phone_id      my_timestamp
---------------------------
1             sysdate - 200   -->expired t2 not all( t1 )
2             sysdate - 200   --*expired t2 and all( t1 )
3             sysdate - 200   --expired t2 and NOT IN t1
5             sysdate - 200   --expired in t2 but Not in t1
6             sysdate         --Not expired t2  expired t1

from above sample Data , taking sysdate - 5 as Maximum ageing Period:
i would like to output  phone_ids:  2, 3 , 4
February 21, 2004 Helfried -- Thanks for the question regarding "reducing reorganisation time of large indexes and tables", version 8.0.5
You Asked
What I did is the following (it is possible to open the database
in exclusive mode for a short time once a week):

* put temporary-, data- and index-tablespace on different
devices
* starting the database with an optimized parameterfile (larger
SGA)in exclusive mode
* using drop index / create index and export /truncate table /
import (export/import to/from an other device by another
controller)

What else can I do to reduce total amount of reorganization-time?
and we said...
to reduce the reorg time of a table -- stop doing it. Seriously.
You shouldn't have to do it.

If you are concerned with chained/migrated rows (only reason I
could think of for wanting to do this) -- analyze the table and
list the chained rows.  Create a temp table with this rows,
delete them from the "real" table and then insert them back.
They are no longer chained/migrated.  You can do this online.

For the indexes, in the rare cases where you do want to rebuild
them -- rebuild them, don't drop and recreate.  By rebuilding,
we don't have to sort and have lots less data to read through
(indexes are smaller then their base tables).

In 8i, you can do the index rebuild ONLINE meaning all DML can
take place against the table while you are rebuilding (no
downtime).

In 8i, you can also "alter table T move ..." to "rebuild it"
(but again, I've never ever rebuilt a table -- "dechained" it
maybe, rebuild it -- never.  I like the alter table move for the
case where you are totally reorganizing a database as it leaves
constraints and all in place and unaffected -- you only need to
rebuild the indexes after you move the table)

Partitioning and parallel processing can help here as well.
Partition the really large indexes and rebuild the partitions in
parallel.
Review & Followup

Rating: 5
dbms_redefinition February 20, 2004
Reviewer: reader
If I used the above package to move the table online, do I still need to rebuild
the index? Or it does keep the index maintained as well.
Followup:
if you "alter table t move", you must rebuild all indexes (and it is not truly
an online operation - no modifications during the move)
Rating: 5
clarification please .... February 21, 2004
Reviewer: reader
If I used dbms_redefinition package to reorg a table, No need to rebuild the
associated indexes? thanks.
Followup:
well, yes, no need to "rebuild" for the simple reason that you are (in 9i)
responsible for BUILDING the indexes on the newly redefined table! (trick answer
or trick answer, depends on how you want to look at it)

the steps to redefine in 9i are roughly:

a) create scratch (new) table to redefine into
b) start redefine process, copies data from old to new
c) index new
d) constrain new
e) add triggers , etc to new
f) grant on new
g) finish redefine

In 10g, much easier, you

a) create scratch (new) table to redefine into
b) start redefine process, copies data from old to new
c) ask dbms_redef to copy all of the stuff you did in c,d,e,f above
d) finish redefine

so yes, you do not need to rebuild anything

but

you need to create them in the first place!
Rating: 5
dbms_redefinition February 21, 2004
Reviewer: reader
Thanks for the clarification. In 9i, would you recommend creating index on the
new "interim" table just before running finish procedure and not immediately
after start redef proc? Does it matter? Thanks again for your time.
Followup:
you'd want to do everything (index, constrain, grant, triggers, everything)
after the start redef and before the finish redef -- else the index would
"disappear" for your users and that would obviate the "online nature".

"immediately" and "just before" don't have a context here -- the steps are:

a) start

sometime in between index, grant, constrain, etc...

b) finish

it is both "immediately after" starting and "just before" finishing no matter
how you look at it.
February 22, 2004 -- 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"
Rating: 5
use bind variables and DAO February 13, 2004
Reviewer: A reader
Hi

Is there a way to use PrepareStatement with Java DAO? We have a customer who
generates SQL dynamically using DAO and the parsing contention is enormous. They
do know how to use bind variables please help!
Followup:
java dao?  that is a design pattern really -- not an API.

Yes, since the Prepared statement is a procedural thing, and you can definitely
bind with it -- they most certainly can bind.  It is very "natural" actually.

DAO just means they wrote a class to hide a query.  Their class would just
"bind"
Rating: 3
February 14, 2004
Reviewer: Alex from Montreal
There are strong reasons to place SQL in
Java in the following situations:

-- Object-Oriented aproach
-- work with individual objects (records) in oposite to
multi record operations
-- extensive use of code generators speed up development
-- usage of O/R mappers like Hibernate, TopLink

PreparedStatement are [must be] used in all DAO generator and ORMaper places
where author understand jdbc above a
beginner level.

It is interesting, that Oracle's jdbc driver somehow
can reuse even Statement objects. My wild guess was
that driver was able to des-integrate Statement into
sql with bind variable and bind value extracted
from constant in SQL. The result was considerable
speed increase executing second and consequitive
Statements (that have similar sql with different X
in WHERE COL1 = X clause)

And one more thing - single Statement is faster (!)
than single PreparedStatement. PreparedStatement
will win only having reasonable
number of repetative usage (say 50) within
life-time of Oracle's soft-parced buffer
(say 1-10 minutes).
Followup:
ref cursors, put all sql into plsql anyway.  much easier to tune, reuse,
control.

Prepared statements are faster in ALL CASES, EVERY CASE (I've proven this I
don't know how many times -- I've even put it into writing in my last book).

Prepared statements are faster, cheaper and the ONLY correct way to implement
(and the number 0,1,2,3,4 and 5th reason I promote the storing of all SQL
outside of java -- cause the java guys will kill us every time with this sort of
stuff)

Prepared statements are faster with the 1st execution,
with the 50th
with the 5,000th

Over statements.  If you have access to my Book Effective Oracle By Design, the
entire test case, benchmark, and repeatable proof is in there.

I wish statements did not even exist.   They "stink" to put it lightly.

(you probably read that wildly inaccurate O'Reilly book with that famously
INCORRECT advice about statements, ugh, wish that book never existed -- it kills
us every time)
Rating: 5
not really: ) February 14, 2004
Reviewer: A reader
"Prepared statements are faster, cheaper and the ONLY correct way to implement "

I agree with every thing you say. But I think if you want
to put yor Java statements in pl/sql, you have got to
use CallableStatement ( or OracleCallbleStatement.):-)

When you compare PreparedStatement and CallableStatement,
the only time you should use PreparedStatement in my
opinion is when you want to improve performance during
a bulk insert operations that gets input from a data
source existing outside the database. Using prepared
statement you can speed up the loader by taking advantage
of update batching (standard or Oracle update batching)
Rest of the time, one should encapsulate code in pl/sql
and use CallalbleStatement.

Btw, Tom, thanx for a wonderful site, and two awesome books!
Followup:
Correct -- I meant " when compared to a statement, Prepared statements are
faster, cheaper and the ONLY correct way to implement ...."
Rating: 4
February 17, 2004
Reviewer: Alex from Montreal
Tom,

Yes, I have looked in pages 275-279 with
Statement vs. PreparedStatement benchmark.

I totally agree with advice to use
PS (or CS) all the time. Single statement timing is
no really relevant to real high-load systems where
PS is a king. There is even technique to cache
PreparedStatement java objects for each connection in
connection pool to minimize object creation

(to be very pedantic, single Statement is still faster,
timing PS creation : PS java object is a bit bigger than Statement but also
there is ADDITIONAL round trip during
prepareStatement call).

I have related, but more general question here:
pre-parsed SQL that sitting in Oracle buffer (is it SGA?),
is there any identification to user or session?
In another words, if one user create SQL with
bind variables, will EVERYBODY, from ANY client
benefit from this?

(I know it defenitely works from multiple java
Connections, but they are from the same
java-connectioon-pool under the same
Oracle user)

Have a nice day
Followup:
(single statement timing isn't even relevant to tiny systems, they become
medium/big later)

single statment IS NEVER EVER NEVER NOT EVEN ONCE faster.  NEVER, NOT EVEN ONCE.
(i showed that didn't I on those very very pages! One PS -- and the class be
there.  You forget to time the "statement initialization" that happened too.
any program that does more then 1) logon 2) logoff benefits IMMEDIATELY and
FULLY from prepared statements, period.)

we call it "shared sql", it is in the "shared pool", in the "shared global area
(SGA)".  We share it with all.  So, first guy to hard parse a sql statement did
the work for everyone else - regardless of identity.
Rating: 4
February 18, 2004
Reviewer: Alex from Montreal
Thanks
Rating: 5
Some benchmarks February 21, 2004
Reviewer: Menon
Tom
I have read your Statement Vs. PreparedStatement
benchmark (in your effective oracle by design book.)

I am not sure if it is a conclusive proof though.
(Btw, I totally agree that PreparedStatement (or even
better CallableStatement) are the way to go.)

Since your argument was that the comparison was
flawed because of PreparedStatement not getting loaded
(for inserting one record), I made sure both the classes
are loaded before I ran my compare test.) Here is the benchmark - followed by
the results Let me know if I missed anything...
-------
/*
* This program compares Statement and PreparedStatement in terms
* of elapsed time. It explains a myth that states that for single
* statement executions, Statement is faster than
* PreparedStatment.
*
* You need to have following table created in the schema you
* run this program in (we use scott/tiger in this program)
*
*  create table t1 ( x number,  y varchar2(20))
*
* IMPORTANT: You need to modify the connection information
* to point to your own database for this example to work.
*/

// importing standard JDBC classes - standard JDBC classes are under
// java.sql class hierarchy

import java.sql.SQLException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement; // for accessing constants only

// importing Oracle specific JDBC classes

import oracle.jdbc.pool.OracleDataSource;

class StatementVsPreparedStatement
{
  // To ensure that PreparedStatement class is loaded
  // before becnhmarking starts
  static Class class1 = PreparedStatement.class;

  // To ensure that Statement class is loaded
  // before becnhmarking starts
  static Class class2 = Statement.class;

  public static void main(String args[])
  {
    _checkUsage( args );
    Connection connection = null;
    Statement stmt = null;
    PreparedStatement pstmt = null;

    try
    {
      connection = _getConnection();
      _benchmarkStatement( connection );
      _benchmarkPreparedStatement( connection );
    }
    catch (Exception e)
    {
      // handle the exception properly - in this case, we just
      // print a message and stack trace and exit.
      System.out.println ("SQL Exception caught ! Rolling back and exiting .."
);
      System.out.println ("error message: " + e.getMessage() );
      try { connection.rollback(); } catch (SQLException ignore) {}

      e.printStackTrace();
      System.exit (1);
    }
    finally
    {
      // close the result set, the stmt and connection.
      // ignore any exceptions since we are in the
      // finally clause.
      try
      {
        if( connection != null )
          connection.close();
      }
      catch ( SQLException ignored ) { }
    }
  }

  private static void _checkUsage (String[] args)
  {
    int argc = args.length;
    if( argc != 1)
    {
      System.out.println( "usage: java StatementVsPreparedStatement <number of
records>");
      System.out.flush();
      System.exit(1);
    }

    s_numberOfRecords = Integer.parseInt( args[0] );
  }

  private static Connection _getConnection ()
    throws SQLException
  {
    String user = "benchmark"; // modify to your db user
    String password = "benchmark"; // modify to your db user
                               // password
    String host = "usunrat24.us.oracle.com"; // modify to
                                             // your db host
    String port = "1521"; // modify to your db listener port
    String dbService = "ora10g"; // modify to your db service
                                 // name
    String thinDriverPrefix = "jdbc:oracle:thin";
    String thinConnectURL = thinDriverPrefix + ":"
      + user + "/" + password + "@" + host + ":" +
      port + ":" + dbService;

    System.out.println("DB connect url: " + thinConnectURL);
    System.out.print("Establishing conn to the database...");
    System.out.flush();

    // in real world, you may want to read this from file or
    // keyboard.
    // Open an OracleDataSource
    OracleDataSource ods = new OracleDataSource();
    ods.setURL(thinConnectURL );
    // get the connection
    Connection conn = ods.getConnection();
    conn.setAutoCommit( false );
    System.out.println( "Connected..." );

    return conn;
  }

  private static void _benchmarkStatement( Connection connection )
    throws SQLException
  {
    long startTime = System.currentTimeMillis();
    Statement stmt = connection.createStatement();
    try
    {
      for ( int i=0; i < s_numberOfRecords; i++ )
      {
        String stmtStr = "insert into t1(x, y) values (" +
          i + ", data" + i + ")";
        //System.out.println ( stmtStr );
        stmt.executeUpdate ( "insert into t1(x, y) values (" +
          i + ", 'data" + i + "')" );
      }
    }
    finally
    {
      try
      {
        stmt.close();
      }
      catch (Exception e)
      {
      }
    }
    long endTime = System.currentTimeMillis();
    connection.commit();
    System.out.println ( "Statement for inserting " + s_numberOfRecords
      + " record(s) took " + (endTime - startTime) + " milliseconds" );
  }

  private static void _benchmarkPreparedStatement( Connection connection )
    throws SQLException
  {
    long startTime = System.currentTimeMillis();

    PreparedStatement  pstmt = connection.prepareStatement(
        "insert into t1( x, y ) values ( ?, ?)");
    try
    {
      for ( int i=0; i < s_numberOfRecords; i++ )
      {
        pstmt.setInt(1, i );
        pstmt.setString(2, "data" + i  );
        pstmt.executeUpdate();
      }
    }
    finally
    {
      try
      {
        pstmt.close();
      }
      catch (Exception e)
      {
      }
    }
    long endTime = System.currentTimeMillis();
    connection.commit();
    System.out.println ( "PreparedStatement for inserting " + s_numberOfRecords
      + " record(s) took " + (endTime - startTime) + " milliseconds" );

  }

  private static int s_numberOfRecords = 0;

}

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

And the results are:
----
Script started on Sat 21 Feb 2004 02:44:56 PM PST
usunrat24 ora10g 1> java StatementVsPreparedStatement 1
DB connect url:
jdbc:oracle:thin:benchmark/benchmark@usunrat24.us.oracle.com:1521:ora10g
Establishing conn to the database...Connected...
Statement for inserting 1 record(s) took 312 milliseconds
PreparedStatement for inserting 1 record(s) took 417 milliseconds
usunrat24 ora10g 2> java StatementVsPreparedStatement 110
DB connect url:
jdbc:oracle:thin:benchmark/benchmark@usunrat24.us.oracle.com:1521:ora10g
Establishing conn to the database...Connected...
Statement for inserting 10 record(s) took 352 milliseconds
PreparedStatement for inserting 10 record(s) took 440 milliseconds
usunrat24 ora10g 3> java StatementVsPreparedStatement 20
DB connect url:
jdbc:oracle:thin:benchmark/benchmark@usunrat24.us.oracle.com:1521:ora10g
Establishing conn to the database...Connected...
Statement for inserting 20 record(s) took 402 milliseconds
PreparedStatement for inserting 20 record(s) took 456 milliseconds
usunrat24 ora10g 4> java StatementVsPreparedStatement 30
DB connect url:
jdbc:oracle:thin:benchmark/benchmark@usunrat24.us.oracle.com:1521:ora10g
Establishing conn to the database...Connected...
Statement for inserting 30 record(s) took 466 milliseconds
PreparedStatement for inserting 30 record(s) took 469 milliseconds
usunrat24 ora10g 5> exit
script done on Sat 21 Feb 2004 02:45:38 PM PST
--

As you can see it took around 30 inserts for the two
to start showing same time (in millisecs).
----
# of records    Statement       PreparedStatement
                (millisecs)     (millisecs)
1                312            417
10               352            440
20               402            456
30               466            469

----

I definitely agree that we should always use
PreparedStatement (or CallableStatement) to use
bind variables. However, it does seem to be true
that for inserting fewer records Statement seems
to be outperforming PreparedStatement by a small
margin.

Please keep up the awesome work - I will see you
in the hotsos symposium!
Followup:
Just use a single prepared statement (use it, warm up -- just like you never
start a benchmark cold, you run for 15 minutes, benchmark for 30, ramp down).

Use them (prepared, statement)
then benchmark them
Rating: 5
Thanx Tom! February 22, 2004
Reviewer: Menon
That implies that class loading has not much
to do with the perceived performance difference.
(I realize this is more of an academic debate for
obvious reasons but debating just for completeness
sake:))

Btw, your java test code does not appear on the code
download on asktom as well as on the oracle press...
Rating: 5
February 22, 2004
Reviewer: Dave from Colorado Springs
In the seemingly eternal debate over sql-in-database-or-app, here's a reference
that I thought is illuminating.
http://www.cio.com/archive/021504/grand.html
See ...

"Since user screen code is contained in the database rather than in the
client/server tier, a code change made in the database is immediately available
citywide."

Can there be anyone whois still unconvinced of the benefits, i wonder
February 22, 2004 minnie -- Thanks for the question regarding "How to use varray type in where_clause of select statements?", version Oracle 8i 8.1.5
You Asked
Hi,Tom
If I have a table including a column which is a varry type,

such as
create table myarray as varray(5) of number(10);
create table mytest(col1 number,col2 myarray)
insert into table mytest(1,myarray(1,2,3,4,5));

I try to use the values of varray type column as condition in
where_clause like
"select * from mytest where col2=myarray(1,2,3,4,5);",
but I failed . How I can do
and we said...
From the PLSQL reference:

<quote>
Comparing Whole Collections

Nested tables and varrays can be atomically null, so they can be
tested for nullity, as the following example shows:

DECLARE
  TYPE Staff IS TABLE OF Employee;
  members Staff;
BEGIN
...
   IF members IS NULL THEN ... -- condition yields TRUE;
END;

However, collections cannot be compared for equality or
inequality. For instance, the following IF condition is
illegal:

DECLARE
  TYPE Clientele IS TABLE OF Customer;
  group1 Clientele := Clientele(...);
  group2 Clientele := Clientele(...);
BEGIN
...
  IF group1 = group2 THEN -- causes compilation error
...
  END IF;
END;

This restriction also applies to implicit comparisons. For
example, collections cannot appear in a DISTINCT, GROUP BY, or
ORDER BY list.

</quote>

So, you need to use an OBJECT type to do this and, much like you
would in Java or C++, you must create an "operator" to do the
comparision -- Just as in Java you have to use:

     stringVariable.isEqual( "some other string" )

we must use

     objectCol.isEqual( some other object instance )

Here is a small example:

ops$tkyte@8i> create type myarray as varray(5) of number(10);
  2  /
Type created.

ops$tkyte@8i> create type myObject as object
  2  ( array myArray,
  3    member function isEqual( p_other in myArray default null)

                       return number
  4  )
  5  /

Type created.

ops$tkyte@8i>
ops$tkyte@8i> create type body myObject
  2  as
  3      member function isEqual( p_other in myArray )
                return number
  4      is
  5      begin
  6          if ( self.array.count <> p_other.count )
  7          then
  8              return 0;
  9          end if;
 10
 11          for i in 1 .. self.array.count
 12          loop
 13              if ( self.array(i) <> p_other(i) )
 14              then
 15                  return 0;
 16              end if;
 17          end loop;
 18          return 1;
 19      end;
 20  end;
 21  /

Type body created.

ops$tkyte@8i> create table mytest(col1 number,col2 myObject)
  2  /

Table created.

ops$tkyte@8i> insert into mytest
              values ( 1, myObject( myarray(1,2,3,4,5) ) )
  2  /

1 row created.

ops$tkyte@8i> select t.col2.array Array from mytest T
  2  /

ARRAY
-------------------------
MYARRAY(1, 2, 3, 4, 5)

ops$tkyte@8i> select t.col1, t.col2.array array
  2    from mytest t
  3   where t.col2 = myobject( myarray(1,2,3,4,5) )
  4  /

no rows selected

No data found, object1 <> object2...

ops$tkyte@8i>
ops$tkyte@8i> select t.col1, t.col2.array array
  2    from mytest T
  3   where t.col2.isEqual( myArray(1,2,3,4,5) ) = 1
  4  /

      COL1 ARRAY
---------- -------------------------
         1 MYARRAY(1, 2, 3, 4, 5)

But out compare works OK
Review & Followup

Rating: 5
Looping Through Collection Elements (PRIOR and NEXT Methods) February 22, 2004
Reviewer: A reader
Hi

How can we compare current array element and previous?

something like

if l_array(i) = l_array(i-1) thn blah blah

is this possible?
Followup:
you just wrote the code, you got it.
Rating: 5
why this does not work? February 22, 2004
Reviewer: A reader
declare
   type array is table of varchar2(30);
   las array := array('TEST', 'LALA');
begin
   for i in 1..las.count loop
      dbms_output.put_line(las(i));
      dbms_output.put_line(las(i-1));
   end loop;
end;
/

TEST
declare
*
ERROR at line 1:
ORA-06532: Subscript outside of limit
ORA-06512: at line 7
Followup:
there is no (0) element.  you sort of have to start at "2" if you want to
compare to the prior one in all cases -- there is nothing prior to "1"
February 22, 2004 Abhijit -- Thanks for the question regarding "Generating fixed format report", version 8.1.7
You Asked
Tom,
I am frequent visitor to your forum. Your inputs and solutions are invaluable.

I am working in a data warehousing environment using Oracle DB 8.1.7 Simplified
version of my fact table (ods_facts) looks like  -

studio_id                  int not null references ods_studios(studio_id),
distribution_entity_id     int not null references
ods_distribution_entities(distribution_entity_id),
period_id                  int not null references ods_periods(period_id),
project_id                 int null references ods_projects(project_id),
kpi_id                     int not null references ods_kpis(kpi_id),
value_in_usd               number

The first six _id fields are foreign key references to appropriate dimension
tables. The dimension tables for studio, distribution_entity, period, project &
kpi store the attributes of the respective dimensions.

project_id is a non-mandatory field in fact table. So we do an outer-join with
dimension table ods_projects to retrieve project related attribute for a
specific fact record.

ods_kpis is the dimension for key performance indicators (KPI) and is a master
list of all KPIs. For a specific period (e.g. period_id = 6) records in fact
table may not entries corresponding to all key performance indicators. Example -

ODS_KPIS
---------
KPI_ID        KPI_NAME
1        All Other Revenue
2               OEM Revenue
3               License Revenue
4               Gross Revenue

If we look into fact table for period_id = 6 we may get kpi_id values only 1 & 4
for various  combinations of other dimensional values
(studio_id,distribution_entity_id, project_id)

SELECT  b.studio_name,
        e.distribution_entity_name,
        d.project_name,
        c.kpi_name,
        sum(value_at_usd)
FROM    ods_facts a,
        ods_studios b,
        ods_kpis c,
        ods_projects d,
        ods-distribution_entities e
WHERE   a.project_id = d.project_id(+)
and     a.studio_id = b.studio_id
and     a.distribution_entity_id = e.distribution_entities
and     a.kpi_id = c.kpi_id
and     a.period_id = 6
group by b.studio_name,
        e.distribution_entity_name,
        d.project_name,
        c.kpi_name

This result of this query will not have any lines for KPI #2 & KPI #3

My end users want to generate a summary report which contains all KPI names for
combinations of studio, distribution entity & project present in fact table.

(_name fields come from appropriate dimension tables)

studio_name    dist name    project_name    kpi_name    total value at usd
------------------------------------------------------------------
A        B        null        ALL OTHER REVENUE        100
A        B        123456        OEM REVENUE            0
A        B        null        LIC REVENUE            0
A        B        null        GROSS REVENUE            150
P        B        345678        ALL OTHER REVENUE        230
P        B        345729        OEM REVENUE            0 etc etc

I can't do an outer join between ods_kpis and ods_facts because I have an outer
join between ods_facts and ods_projects. I guess in Oracle 9.2 the lines missing
in fact table (e.g. line #2, #3, #6) can be "artificially" created using
pipelined TABLE function accepting a cursor for the above select statment.

My fact table is very large even for a specific period (contains more than 1
million rows for each period). It partitioned by period_id.

Will you please suggest how I can get it done in 8.1.7 ?
and we said...
This is actually a partitioned outer join! (new in 10g
http://otn.oracle.com/oramag/oracle/04-jan/o14asktom.html
) since you have to make up studio_name, dist_name as well..

I think the easiest thing is to create a set:

studio_name X dist_name X (projects PLUS "null" project) X KPIs.

we'll outer join that to a selection of facts.

Here is an example.  S = studio, D = distribution, P = project, K = KPI and F =
fact table:

create table s ( id number primary key, name varchar2(5) );
create table d ( id number primary key, name varchar2(5) );
create table p ( id number primary key, name varchar2(5) );
create table k ( id number primary key, name varchar2(5) );

create table f ( sid not null constraint sfk references s,
                 did not null constraint dfk references d,
                 pid     null constraint pfk references p,
                 kid not null constraint kfk references k,
                 period_id number,
                 value_in_usd number );

Now, we create:

insert into s select rownum, 's'||rownum from all_users where rownum <= 10;
insert into d select rownum, 'd'||rownum from all_users where rownum <= 7;
insert into p select rownum, 'p'||rownum from all_users where rownum <= 6;
insert into k select rownum, 'k'||rownum from all_users where rownum <= 4;

so we expect:

10 * 7 * (6+1) * 4 = 1960 rows back for any report (every "contains all KPI
names for combinations of studio, distribution entity & project")

Well, that could be:

select sname,
       dname,
       pname,
       kname,
       nvl(value_in_usd,0) value_in_usd
  from
  ( select s.name sname, s.id sid,
           d.name dname, d.id did,
           p.name pname, p.id pid,
           k.name kname, k.id kid
      from s,
           d,
           (select * from p
             UNION ALL
            select -1, '(null)' from dual) p,
           k
  ) X,
  ( select sid, did, nvl(pid,-1) pid, kid, sum(value_in_usd) value_in_usd
      from f
     where period_id = 6
     group by sid, did, nvl(pid,-1), kid
  ) Y
 where x.sid = y.sid(+)
   and x.did = y.did(+)
   and x.pid = y.pid(+)
   and x.kid = y.kid(+)
/

See how we "dummied up" a missing project and we can now outer join to fact
nicely.  You could even add:

 where x.sid = y.sid(+)
   and x.did = y.did(+)
   and x.pid = y.pid(+)
   and x.kid = y.kid(+)
   and (x.pid <> -1 or y.value_in_usd is not null)
/

to the mix -- if you didn't want NULL projects that had no mates in the fact
table to be present (in my example that takes the result set from 1960 rows to
1690 rows for example -- by removing the null projects we didn't really card
about)
Review & Followup

Rating: 5
Awesome February 22, 2004
Reviewer: A reader
February 22, 2004 fernando -- Thanks for the question regarding "Using a Recordset in a Loop Statement ", version 5.0.6.22.0
You Asked
Hi Tom,

    I need to translate this sentence from Visual Basic to
Oracle Forms 5.0.6.22.0.

For Each fld In recset.Fields
             ...
Next fld

Where
    recset.Fields: it's a  collection that contains all stored
Field objects of a Recordset object.
    fld: it's a Field of the collection.

I've seen a similar sentence with a record from a cursor,

FOR record_name IN
 {  cursor_name [(cursor_parameter_name[,
      cursor_parameter_name]...)]
  | (select_statement)}
LOOP
   seq_of_statements
END LOOP [label_name];

 but I'm working with a recordset from a MapObjects OCX form
ESRI. Can it be done?

    Thank you very much.
and we said...
This functionality is available in the EXEC_SQL builtin package
in forms (DBMS_SQL on the server, use EXEC_SQL when in forms,
DBMS_SQL when in a stored procedure).

It allows you to iterate over rows by fetching them and to
iterate over columns using a positional index (eg: you can ask
for the i'th column of a given row).

Here is an example.  If I had a form with a block T and a text
item X (and X was sufficiently large), this would take all of
the rows in EMP and put a comma separate list of values into the
field :t.x

declare
  connection_id EXEC_SQL.CONNTYPE
                     default exec_sql.default_connection;
  cursorID      EXEC_SQL.CURSTYPE;

  nIgn        number;
  columnValue varchar2(4000);
  ncols       number;
  sep         varchar2(1);
  colName     varchar2(40);
  colLen      number;
  coltype     number;
  tmp         varchar2(20000);
BEGIN
  IF exec_sql.is_connected = FALSE THEN
    Message('No primary connection. Please connect.');
    RETURN;
  END IF;

  cursorID := EXEC_SQL.OPEN_CURSOR;
  EXEC_SQL.PARSE(cursorID, 'select * from emp', exec_sql.V7);
  nIgn := EXEC_SQL.EXECUTE(cursorID);

  for i in 1 .. 1000 loop
  begin
        exec_sql.describe_column( connection_id, cursorId, i,
                                    colName, colLen, colType );
        exec_sql.define_column(cursorID, i, columnValue,
                                  4000 );
    exception
          when exec_sql.invalid_column_number then
              ncols := i-1;
              exit;
     end;
  end loop;

  while (exec_sql.fetch_rows(cursorID) > 0)
  loop
        sep := '';
        for i in 1 .. ncols loop
             EXEC_SQL.COLUMN_VALUE(cursorID, i, columnValue);
             tmp := tmp || sep || columnValue;
         sep := ',';
        end loop;
        tmp := tmp || chr(13) || chr(10);
  END LOOP;

  EXEC_SQL.CLOSE_CURSOR(cursorID);
  EXEC_SQL.CLOSE_CONNECTION;
  :t.x := tmp;

END;
Review & Followup

Rating: 5
why this exits oth loops? February 22, 2004
Reviewer: A reader
Hi

I have this pl/sql code

declare
xx number := 1;
begin
for i in (select 1 from dual union all select 2 from dual)
loop
  for x in (select username from user_tab_partitions)
  loop
    exit when xx > 1;
    dbms_output.put_line(x.table_name);
    xx := xx + 1;
  end loop;
end loop;
end;
/

I thought exit only exits the inner loop but it doesnt, it exits both... I even
tried using labels but it does not work neither

What I want is exit nly the inner loop but proceed processing the outer loop...
How can I achive this?
Followup:
ops$tkyte@ORA9IR2> declare
  2     xx number := 1;
  3  begin
  4     for i in (select 1 from dual
  5               union all
  6               select 2 from dual)
  7     loop
  8         dbms_output.put_line( 'top of loop 1' );
  9         for x in (select * from user_tab_partitions)
 10         loop
 11             dbms_output.put_line( 'top of loop 2' );
 12             exit when xx > 1;
 13             dbms_output.put_line(x.table_name);
 14             xx := xx + 1;
 15             dbms_output.put_line( 'bottom of loop 2' );
 16         end loop;
 17         dbms_output.put_line( 'bottom of loop 1' );
 18     end loop;
 19  end;
 20  /
top of loop 1
top of loop 2
MYEMP
bottom of loop 2
top of loop 2         <<<=======
bottom of loop 1      <<<=======
top of loop 1         <<<=======
top of loop 2
bottom of loop 1

PL/SQL procedure successfully completed.

It does just exit the inner loop.   that shows we went from the top of loop2 to
the bottom of loop 1 and back to the top again...
February 23, 2004 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!
Rating: 5
dynamic SQL with XML January 27, 2004
Reviewer: vll from USA
Hi, Tom!
Am I doing something wrong?

With direct SQL:
select xmlelement("RESULTS") into sc_xml.result from dual;
everything is fine (sc_xml.result is a package variable). When I'm trying to do
it with dynamic SQL - i'm getting error:
strSQL := 'select xmlelement("RESULTS") into sc_xml.result from dual';
execute immediate strSQL;

error is:ORA-00905: missing keyword.

Is there any way to build XML queries dynamically?
Thank you very-very much!!!
Followup:
INTO :variable

is a plsql thing.  INTO is not sql.

strSQL := 'select xmlelement("RESULTS") from dual';
execute immediate strSQL into sc_xml.result;
Rating: 4
Using XMLSequence February 19, 2004
Reviewer: Raghu
I have registered a xml schema in the database.
then i created a table based on the xml schema.
then i  inserted the xml data into the table using the file from XML repository.
Here are the scripts.

CREATE TABLE TabL_FileContent
(
   id             NUMBER,
   SequenceData   XMLType,
   FileContent    BLOB
  )
XMLTYPE COLUMN SequenceData
STORE AS OBJECT RELATIONAL
XMLSCHEMA "
http://www.w3.org/2001/XMLSchema/XMLAblDaten.xsd
ELEMENT "MEPSequenceData";

INSERT INTO TabL_FileContent VALUES
( 1
  ,XMLTYPE(XDBURITYPE('/public/labor/ftp_path/xmlabldaten.xml').GetClob())
  , EMPTY_BLOB()
)
/

When i issue this query
SELECT EXTRACT(SequenceData,
'//VarSequData/VarSequDataRow/VarSequDataColumn/text()').GetClobVal()
  FROM TabL_FileContent
WHERE ROWNUM = 1
/

i get the results in this way.
EXTRACT(SEQUENCEDATA,'//VARSEQUDATA/VARSEQUDATAROW/VARSEQUDATACOLUMN/TEXT()').GE
--------------------------------------------------------------------------------
10.52.5000101010010.52.50001010.52.5000101010010.52.50001010.52.5000101010010.52
.50001010.52.50001010.52.5000101010010.52.50001010.52.50001010.52.50001010.52.50
001010.52.50001010.52.5000101010010.52.500010

I want the results to be in record level one after another.So based on the above
example i tried to use XMLSequence but without success.

Here is the query for which i do not get any result.

select extractvalue(value(d), '//VarSequData/VarSequDataRow/VarSequDataColumn')
   from tabl_filecontent x,
         table( xmlsequence( extract(x.sequencedata,
'//VarSequData/VarSequDataRow/VarSequDataColumn') ) ) d
where rownum = 1
/

i just get blank. no result.
EXTRACTVALUE(VALUE(D),'//VARSEQUDATA/VARSEQUDATAROW/VARSEQUDATACOLUMN')
---------------------------------------------------------------------------------
-------------------
I am just attaching a part of the xml file becuae the entire file cannot be
pasted here.

<?xml version="1.0" encoding="ISO-8859-1"?>
<MEPSequenceData
xmlns="
http://www.w3.org/2001/XMLSchema/XMLAblDaten.xsd
xmlns:xdb="
http://xmlns.oracle.com/xdb
xmlns:xsi="
http://www.w3.org/2001/XMLSchema-instance
xsi:schemaLocation="
http://www.w3.org/2001/XMLSchema/XMLAblDaten.xsd

http://www.w3.org/2001/XMLSchema/XMLAblDaten.xsd
Name="C
R_SimpleSequencedaten" DataType="Sequencedata" Model="CR" VersionTemplate="1.0"
VersionEditor="1.0" LastTimeStamp="20030219132346" LastUpdate="19.02.2003
13.23.46">
    <Allgemein>
        <AllgemeinRow TimeStamp="20030219132346">
            <AllgemeinColumn id="Name"
DataType="text">CR_Bedatung_Einschwingen</AllgemeinColumn>
            <AllgemeinColumn id="Version" DataType="text">0.1</AllgemeinColumn>
            <AllgemeinColumn id="Bemerkung" DataType="text">geaendert von
Benutzer: demo</AllgemeinColumn>
            <AllgemeinColumn id="letztes Update" DataType="text">19.02.2003
13.23.46</AllgemeinColumn>
        </AllgemeinRow>
    </Allgemein>
    <!-- Teilablaeufe -->
    <TA_Sequences>
        <Sequence id="TA_01">
            <Name>Partsequence PartOne</Name>
            <ViewName>PartOne auf Englisch?</ViewName>
            <MeasPoint id="TA_01_LPREV_00" Typ="Revolution">
                <Name>Emi Revolution</Name>
                <ViewName>Emi Revolution...</ViewName>
                <PrgIdent>LPREV_01</PrgIdent>
                <MeasPointSteps>
                    <MeasPointStep id="S_001" Nr="1">
                        <ViewName>Emi Revolution Schritt 1</ViewName>
                        <StepGeneral>
                            <StepGeneralRow TimeStamp="20030219132346">
                                <!--- Diese Daten sind von MeasPoint zu
Lastpuntk verschieden
                                und sind zu jedem Zeitpunkt erweiterbar -->
                                <StepGeneralColumn id="Lastpunktanzeige"
DataType="integer">1</StepGeneralColumn>
                                <StepGeneralColumn id="EMI_Huebe"
DataType="integer">10</StepGeneralColumn>
                                <StepGeneralColumn id="Messwertaufnahme"
DataType="double">1</StepGeneralColumn>
                                <StepGeneralColumn id="Wartezeit"
DataType="double">0</StepGeneralColumn>
                            </StepGeneralRow>
                        </StepGeneral>
                        <SetPhase>
                            <SetVar id="p_Rail(ASAP)" Unit="bar"
DataType="double">
                                <ViewName>p_Rail(ASAP)</ViewName>
                                <VarDescription>Hier kann ein beliebiger Text
fuer die Beschreibung der Var stehen</VarDescription>
                                <VarDevice>Emi2</VarDevice>
                                <!-- dargestellt im Format -->
                                <DisplayFormat>%8.2f</DisplayFormat>
                                <!-- 4 Punkte Umrechnungsformel -->
                                <VarSequData>
                                    <VarSequDataRow TimeStamp="20030219132346">
                                        <VarSequDataColumn id="Sollwert" Unit=""
DataType="double">10.5</VarSequDataColumn>
                                        <VarSequDataColumn id="Toleranz" Unit=""
DataType="double">2.5</VarSequDataColumn>
                                        <VarSequDataColumn id="Beurteilung"
Unit="" DataType="integer">0</VarSequDataColumn>
                                        <VarSequDataColumn id="Bewertung"
Unit="" DataType="integer">0</VarSequDataColumn>
                                        <VarSequDataColumn id="Vorverarbeitung"
Unit="" DataType="integer">0</VarSequDataColumn>
                                        <VarSequDataColumn
id="VorverarbeitungParameter" Unit="" DataType="integer">10</VarSequDataColumn>
                                        <VarSequDataColumn id="Stepweite"
Unit="" DataType="integer">10</VarSequDataColumn>
                                        <VarSequDataColumn id="Stepanzahl"
Unit="" DataType="integer">100</VarSequDataColumn>
                                    </VarSequDataRow>
                                </VarSequData>
                            </SetVar>
                        </SetPhase>
                        <MeasPhase>
                            <MeasVar id="Q_quer(Emi2)" Unit="bar"
DataType="double">
                                <ViewName>Q_quer(Emi2)</ViewName>
                                <VarDescription>Hier kann ein beliebiger Text
fuer die Beschreibung der Var stehen</VarDescription>
                                <VarDevice>Emi2</VarDevice>
                                <VarSequData>
                                    <VarSequDataRow TimeStamp="20030219132346">
                                        <VarSequDataColumn id="Sollwert" Unit=""
DataType="double">10.5</VarSequDataColumn>
                                        <VarSequDataColumn id="Toleranz" Unit=""
DataType="double">2.5</VarSequDataColumn>
                                        <VarSequDataColumn id="Beurteilung"
Unit="" DataType="integer">0</VarSequDataColumn>
                                        <VarSequDataColumn id="Bewertung"
Unit="" DataType="integer">0</VarSequDataColumn>
                                        <VarSequDataColumn id="Vorverarbeitung"
Unit="" DataType="integer">0</VarSequDataColumn>
                                        <VarSequDataColumn
id="VorverarbeitungParameter" Unit="" DataType="integer">10</VarSequDataColumn>
                                    </VarSequDataRow>
                                </VarSequData>

Can you kindly help me out with this problem.
Rating: 1
Should i place this question in new thread ? February 23, 2004
Reviewer: Raghu
Hi Tom,

Do i need to put the above question in a new thread.
I have posted it here becuase in the above discussions XMLSequence is being
discussed.

Please let me know.
Followup:
I do not see all reviews -- and I cannot answer all of them either.

I asked someone else to look at this, xml is not my area of expertise, if they
get time, they will.
Rating: 1
Problem is solved. February 25, 2004
Reviewer: Raghu
i am posting this so that others can benefit from this.

In such situations you can provide namespace along with the xpath to EXTRACT and
EXTRACTVALUE operators.

Here is the query.
SELECT  EXTRACTVALUE(VALUE(x), '/StepGeneralColumn[@DataType="KonfigFile"]',
'xmlns="
http://www.w3.org/2001/XMLSchema/XMLAblDaten.xsd
  FROM TabL_FileContent
, TABLE(XMLSEQUENCE(EXTRACT(SequenceData, '/MEPSequenceData//StepGeneralColumn',
'xmlns="
http://www.w3.org/2001/XMLSchema/XMLAblDaten.xsd
)) x
WHERE FileID = 1
/
Rating: 4
Setting the dateformat using DBMS_XMLGEN March 09, 2004
Reviewer: Nolan Tucker from Harrisburg, PA
Hi Tom and Sean,
This thread has lots of useful information.  Thank you.

We're using dbms_xmlgen and passing it a resultset from a stored procedure.  I'd
like date fields generated in 'YYYY-MM-DD HH24:MM:SS' format.  I don't see a
procedure like
dbms_xmlgen.usenullattributeindicator that will do this.  Can this be influenced
by setting the session nls_date_format?  If not how do I format?  We're running
Oracle9i Enterprise Edition Release 9.2.0.4.0.

This is a simplified version of what we're doing:
declare
   test_cursor         HDP_LocationDetail_PAK.LocationDetailCur;
   resultset         test_cursor%rowtype;
   resultset_xml    XMLTYPE;
   resultsetrows     integer;
   resultsetsize     integer;
   l_ctx          dbms_xmlgen.ctxHandle;
begin
   HDP_LocationDetail_PRC(test_cursor,'Valexia','101815',TO_DATE('2003-05-15',
'YYYY-MM-DD'), TO_DATE('2003-06-30', 'YYYY-MM-DD');
   l_ctx := dbms_xmlgen.newContext(test_cursor);
   dbms_xmlgen.usenullattributeindicator(l_ctx,TRUE);
   resultset_xml := dbms_xmlgen.getxml(l_ctx);
   resultsetrows := dbms_xmlgen.getNumRowsProcessed(l_ctx);

   dbms_output.put_line(''rowsprocessed=''||resultsetrows);

   UPDATE vrp_ReportRuns SET enddate=SYSDATE,
rowsreturned=resultsetrows,reportresultset=resultset_xml,
                                    statuscode=0,statusmessage=''Success'',
                        nextrundate=NULL, failurecount=0;

   dbms_xmlgen.closeContext(l_ctx);

   commit;
end;

Thank you for your time.
Nolan
Rating: 4
Okay - yes it works. March 18, 2004
Reviewer: Nolan Tucker from Harrisburg, PA
I altered my session at the beginning of the procedure with
DBMS_SESSION.SET_NLS('NLS_DATE_FORMAT','''YYYY-MM-DD HH24:MI:SS''');
and found the XML datetype nodes are stored as 'YYYY-MM-DD HH24:MI:SS'.
February 23, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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.
Rating: 5
Still applicable in 9.2 and 10? February 09, 2004
Reviewer: Jim Nasby from Austin, TX
Is this still the best method to use for doing a 'rolling window' in 9.2? How
about 10?
Followup:
sure, 100%.  there is no "rolling window" command as yet (doubt there will be -
sort of "implementation dependent")
Rating: 5
Simpler query to find partitions to drop February 23, 2004
Reviewer: Jim Nasby from Austin, TX
Tom, would the following query work for finding what partitions to drop? It
seems much simpler than what's show in your first response, at least to me. :)

SELECT *
    FROM (
        SELECT partition_name
            FROM user_tab_partitions p
            WHERE table_name = 'PARTITION'
                AND partition_name <> 'THE_REST'
            ORDER BY partition_name ASC
    )
    WHERE rownum > 30
/

It seems this would also run faster than the other version, though I don't have
any data to back that up yet.
Followup:
run that and you'll find it never ever returns any rows.

Yes, there are probably an infinite number of ways to run any given query.  I
could do it with rownum (although not the way you do.   where rownum > 30 is
NEVER  true -- in order for rownum to be greater than 30, it would have had to
of been > 1, >2 and so on -- but it never was)..... I could do it with
analytics, i could do it dozens of ways.
February 23, 2004 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.
Rating: 3
Nice February 23, 2004
Reviewer: F.Maer from CT,USA
Hi Tom,
Which of the either queries is better?
 sql>select max(sal) from (select sal from emp
                      where rownum <= 5);
  or
 sql>select sal from (select sal from emp where rownum <= 5
                        order by sal desc) where rownum = 1;
 Do you have other ways to put this query in a far better
way? Please do reply.
Bye!
Followup:
both are wrong.  both return random data.  neither is a good query.

both get 5 random rows more or less from emp, then return the max sal from this
set of 5.

so, both are meaningless, unpredicable.

ops$tkyte@ORA920PC> select max(sal) from ( select sal from emp1 where rownum <=
5 );

  MAX(SAL)
----------
      5000

ops$tkyte@ORA920PC> select sal from ( select sal from emp2 where rownum <= 5
order by sal desc) where rownum=1;

       SAL
----------
      1250

ops$tkyte@ORA920PC> select * from emp1 minus select * from emp2;

no rows selected

ops$tkyte@ORA920PC> select * from emp2 minus select * from emp1;

no rows selected

ops$tkyte@ORA920PC>

See, same data in both, different answers....
Rating: 3
Good to see February 23, 2004
Reviewer: Siva from Bangalore,India
Dear tom,
I need a query that puts employees hierarchically sorted
 departmentwise.I mean
 dept Hierarchy
 ----  --------
 Accounts King <<-- Head
           Jones -- Subordinate
 Marketing ..
 Projects  ..
Is that possible using sql?
Thanks in advance.
Followup:
search for

"order siblings by"

on this site.
February 23, 2004 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://download-east.oracle.com/docs/cd/A81042_01/DOC/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.
Rating: 3
got error when wrapping types February 23, 2004
Reviewer: oleg from hungary
in our app. We use plsql in types extensivly. But when wrapping it, we got
compilation errors. is there any known problem with wrapping types? Any
workaround? any suggestion?
Followup:
not that I am aware of personally.  if you have a test case, contact support.
February 23, 2004 Marcio -- Thanks for the question regarding "Java Development ", version 920
You Asked
In your opinion, which would be top 10 danger flaw from java developers?  What
would be the worst things done by java programmers which you have seen before?

Thanks,
and we said...
Well, I won't pick on java developers -- how's about "people writing database
applications" in general?

Not going to promise "10" exactly but here goes:

o lack of interest in understanding the tool they are using.  What I mean by
this is the developer that refuses to understand what transactions are/do/mean.
why they are relevant.  how concurrency controls in the database works.  This is
generally the developer who has bought 15 books on "programming language X", has
taken days/weeks of offsite education on "programming language X", probably has
a comp sci degree (can program linked list with the best of them) but when it
comes to understanding a pivital -- some would say THE pivital -- piece of
software -- nope, they won't stand for it.  These developers are downright
dangerous as they will introduce transactional bugs (eg: bad data) into your
systems left and right and consume orders of magnitude more resources than
necessary.  Their argument "databases should be easier".  My response "they are
easier than your programming language you are so caught up on and -- they will
be around much longer, unless of course you are using Cobol which is one of few
languages longer in the tooth than this database thing"

That is a big one.

o bind variables -- of course. Even when told "lack of binds are bad for a)
performance of a single user, b) scalability for multi-users and are c) a
security hole the size of Alaska (alaska is a really big place)" they will
refuse to use them.  Lack of bind variables in most every application is a bug
in the developed code.  Nothing more, nothing less.  They are a bug.

o doing everything procedurally, refusing to thing "sets".  slow by slow is
the rule of the day.  The argument I get is "your sql query is too hard for me
to understand".  My response "it took me 15 minutes to understand what your code
was even trying to do -- wish there was a comment or two or maybe some
documentation.  Tell you want, I'll comment my query to explain how it works,
you comment your code -- then lets see which is 'too hard to understand'"

o thinking "database independence" is a positve thing to be achieved at all
costs (and by costs I mean their salary and the salaries of their coworkers --
it has real true monetary costs) and that it must be done at the source code
level outside the database.  The only way to achieve this is to replicate the
functionality of a database in their code (like SAP and PSoft do).  This is due
to the inherit and unchangeable differences between the databases concurrency
and locking models -- in addition to subtle differences between sql dialects.
The only truly successful database independent code I've seen is that which does
everything via stored procedures -- with each stored procedure developed to the
wire for the database it will live in.  Every database in use today can have
procedures that

a) are transactions
b) return result sets
c) accept inputs
d) provide outputs

there you go, define your transactions and implement them in the best way
possible in each database.  Use the features of each database as much as
possible (to reduce the amount of code you write) and you'll find you can
implement much faster on 2 or 3 databases with this approach then you will with
"well, lets develop on DB1 and hope we didn't do anything that we cannot in Db2
and Db3 and if we did, we'll come back to db1 and update the code again.  We'll
spend hours coming up with cool workarounds and so on.  It'll be fun"

That is probably not what you were expecting -- it is not as simple as a bullet
list of

o not using binds
o using not in instead of not exists (which is a myth, don't worry about that)
o improper indentation of code
o etc.....

It is more fundemental.  It is the approach they take to developing database
applications (note: i did not say applications, "database applications" -- big
difference to me between say MS Word -- an application and HR -- a database
application)

You see, word for better or worse will be around for a long time to come
probably.  It is an environment unto itself. (but hey, as open standards come
along and more stuff can open your documents......)

HR applications -- come and go -- but the data, well that data stays literally
forever.  You never delete or update in HR, you only insert.  The data is key,
the application is just lipstick more or less to make the data prettier.

Database application developers need to have a different mindset from "just some
nice gui application like a browser, word processor, etc".  They need to realize
the data is the point, not the application.

see
http://otn.oracle.com/oramag/oracle/04-jan/o14dev_trends.html
Cameron wraps it up nicely.
Review & Followup

Rating: 5
This is 100% what I expected February 23, 2004
Reviewer: Marcio from Brazil
Thank you!
February 23, 2004 Ronald -- Thanks for the question regarding "owa_opt_lock.checksum question", version 8.0.6.2.0
You Asked
Tom, I want to use package owa_opt_lock.checksum to avoid lost updated in my web
application. I noticed that updates to a column named last_update_date are not
visible in the checksum number. Am I missing something? Here is a small test
case (script and output):

*** script ***

set serveroutput on size 10000

select * from v$version;

create table t1 as select sysdate last_update_date from dual
/

select to_char(last_update_date,'DD-MON-YYYY HH24:MI:SS') last_update_date
from t1
/

declare
 l_rowid rowid;
begin
 select rowid into l_rowid from t1;
 dbms_output.put_line('checksum: '||owa_opt_lock.checksum('APPS','T1',l_rowid));
end;
/

update t1 set last_update_date = sysdate-(1/24)
/
commit
/

select to_char(last_update_date,'DD-MON-YYYY HH24:MI:SS') last_update_date
from t1
/

declare
 l_rowid rowid;
begin
 select rowid into l_rowid from t1;
 dbms_output.put_line('checksum: '||owa_opt_lock.checksum('APPS','T1',l_rowid));
end;
/

drop table t1
/

*** output ***

BANNER
----------------------------------------------------------------
Oracle8 Enterprise Edition Release 8.0.6.2.0 - Production
PL/SQL Release 8.0.6.2.0 - Production
CORE Version 4.0.6.0.0 - Production
TNS for Solaris: Version 8.0.6.2.0 - Production
NLSRTL Version 3.3.3.0.0 - Production

Table created.

LAST_UPDATE_DATE
--------------------
23-FEB-2004 02:02:22

checksum: 1636

PL/SQL procedure successfully completed.

1 row updated.

Commit complete.

LAST_UPDATE_DATE
--------------------
23-FEB-2004 01:02:23

checksum: 1636

PL/SQL procedure successfully completed.

Table dropped.
and we said...
the way optlock works is to use the default NLS settings.  by default, a date
does not include the time.

sooo, you can either

a) use checksum with buffer and select out the columns yourself (this is what I
normally do, since you have in fact already queried the data typically).  This
buffer would have

   c1 || to_char(c2,'yyyymmddhh24miss') || to_number(c3,'000000000009.999') ....

and so on (eg: each field nicely "strung" out)

b) issue alter session to set the default date mask to something else:

ops$tkyte@ORA920PC> create table t1 as select sysdate last_update_date from dual
  2  /

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select to_char(last_update_date,'DD-MON-YYYY HH24:MI:SS')
last_update_date from t1
  2  /

LAST_UPDATE_DATE
--------------------
23-FEB-2004 09:11:08

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> declare
  2   l_rowid rowid;
  3  begin
  4   select rowid into l_rowid from t1;
  5   execute immediate 'alter session set nls_date_format =
''yyyymmddhh24miss'' ';
  6   dbms_output.put_line('checksum: '||owa_opt_lock.checksum(user,
'T1',l_rowid));
  7   execute immediate 'alter session set nls_date_format = ''dd-MON-rr'' ';
  8  end;
  9  /
checksum: 22124

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> update t1 set last_update_date = sysdate-(1/24)
  2  /

1 row updated.

ops$tkyte@ORA920PC> commit
  2  /

Commit complete.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select to_char(last_update_date,'DD-MON-YYYY HH24:MI:SS')
last_update_date
  2  from t1
  3  /

LAST_UPDATE_DATE
--------------------
23-FEB-2004 08:11:08

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> declare
  2   l_rowid rowid;
  3  begin
  4   select rowid into l_rowid from t1;
  5   execute immediate 'alter session set nls_date_format =
''yyyymmddhh24miss'' ';
  6   dbms_output.put_line('checksum:
'||owa_opt_lock.checksum(user,'T1',l_rowid));
  7   execute immediate 'alter session set nls_date_format = ''dd-MON-rr'' ';
  8  end;
  9  /
checksum: 22123

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> drop table t1
  2  /

Table dropped.
Review & Followup

Rating: 5
February 23, 2004
Reviewer: Ronald from Amsterdam, the Netherlands
Thanks Tom, I knew I was forgetting something!
February 23, 2004 Chacravarthi -- Thanks for the question regarding "SQL Model Clause", version 9.0.1
You Asked
Dear Sir,
Have you started using Oracle 10g?If so,Could you please explain
"SQL Model clause" with a simple example?Expecting your reply
Yours sincerely,
R.Chacravarthi
and we said...
that is sort of like asking one to summarize "war and peace" in a sentence.
cannot do it justice.

see
http://otn.oracle.com/products/bi/pdf/10gr1_twp_bi_dw_sqlmodel.pdf
Review & Followup

Rating: 5
Book-a-munute February 23, 2004
Reviewer: Mikito Harakiri

http://rinkworks.com/bookaminute/classics.shtml
They should open tech section
February 23, 2004 guy -- Thanks for the question regarding "Oracle over satellite link", version 8.1.7
You Asked
Hi,
I have a client server setup which works good over LAN.
I have a sattelite connection (vsat) with enough bantwidth (128 Kbps) which
works very good.
Once I'm using the client behind the satellite link (which provides delay of 0.8
seconds) the connection become really slow.
I have measured the real throuput and the Oracle only uses 12 Kbps while  other
applications (internet browsing, Ftp, etc.)use all the bandwidth.
Are there any parameters to setup in Oracle to fit it better to work over a
satellite?
and we said...
I too use a satellite at home (wish I had 0.8 turn around times!  Over my
wireless LAN to the NAT bridge across the VPN up 22,000 miles to the satellite
down 22,000 miles to someplace in california, to Oracle's network and back -
about 1.5 to 2.0 seconds turn around time.

You will find that simple protocols like http, ftp, etc work dandy.  They send a
request and get a big stream of data sent back as a response.  One message over
-- and bamm - you have it all.

Chattier protocols like telnet, SQL*net -- not so well.  they send a tiny bit,
wait for response, send a tiny bit, wait for a response, and so on.  Consider:

ops$tkyte@ORA920PC> set autotrace on
ops$tkyte@ORA920PC> 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
          3  consistent gets
          0  physical reads
          0  redo size
        375  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

for you that would be a MINIMUM of 1.6 seconds.

scott@ORA920PC> set arraysize 1
scott@ORA920PC> set autotrace traceonly statistics;
scott@ORA920PC> select * from emp;

14 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         14  consistent gets
          0  physical reads
          0  redo size
       2033  bytes sent via SQL*Net to client
        445  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         14  rows processed

a minimum of 6.4 seconds -- and so on

If that were a file to be ftped -- that would take no time -- 0.8 seconds to
send "get" request and get stream of response.  ftp is not chatty back and
forth.  the conversation is "get me this -- ok here you go".  With sqlnet it is
"get me some -- here you go -- get me some more -- here you go -- get me some
more  -- here you go" and so on.  with each -- taking a turnaround.  You never
get a measurable burst (unless you set arraysize 100000 select * from BIG_TABLE,
then you would -- but your client server app probably does not do that)

So, what is the fix?  It is really pretty simple -- you have to "amazon.com" or
"yahoo.com" or "google.com" or "asktom.oracle.com" your application.  Asktom is
an Oracle application -- I know it responds well over a high speed, high latency
network cause that is where I spend most of my time on it (even on a low speed,
high latency network it works just fine, spend lots of time on that as well).

You place a webserver or app server near the database.  You use your browser to
request the app server to chat with the database over the LAN.  the app server
returns you a page.

You cannot do client server over a satellite.
Review & Followup

Rating: 5
February 23, 2004
Reviewer: A reader
Rating: 5
thanks March 24, 2004
Reviewer: A reader
February 23, 2004 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
Rating: 4
clarification of v$open_cursor and v$sysstat's "opened cursors current" January 15, 2004
Reviewer: Joseph Sat from VA, USA
SELECT b.SID, UPPER(a.NAME), b.VALUE
    FROM v$statname a, v$sesstat b, v$session c
    WHERE a.statistic# = b.statistic#
    AND c.SID = b.SID
    AND LOWER(a.NAME) LIKE '%' || LOWER('CURSOR')||'%'
    AND b.SID=20
UNION
SELECT SID,  'v$open_cursor opened cursor', COUNT(*)
FROM v$open_cursor
WHERE SID=20
GROUP BY SID
ORDER BY SID

SID,UPPER(A.NAME),             VALUE
20 ,CURSOR AUTHENTICATIONS,     292
20 ,OPENED CURSORS CUMULATIVE,  2247
20 ,OPENED CURSORS CURRENT,     477
20 ,SESSION CURSOR CACHE COUNT, 0
20 ,SESSION CURSOR CACHE HITS,  0
20 ,v$open_cursor COUNT(*),     16

After reading many comments on cursors, I get confused.

- Could you please clarify once and for all what is the difference between
'OPENED CURSORS CURRENT' and COUNT(*) from v$open_cursor?  What is the meaning
in this case of 477 and 16?

- And which one do we use to diagnose problem when the application has MAXIMUM
CURSOR EXCEEDED exception?

Thanks so much
Followup:
search for

"opened cursors current"  v$open_cursor

on this site
Rating: 5
Oralce9i client February 20, 2004
Reviewer: Pushparaj Arulappan from NJ, USA
Tom,

We currently have Oracle9.2.0.3 installed on HP-UX.

The following query opens different number of cursors (in v$open_cursor) based
on the platform.

select deptno,
       dname,
       cursor (select ename from emp where deptno=b.deptno)
       as "Employee"
from dept b
/

This query has only two rows (opens only two cursors) in v$open_cursor if I run
this query by logging on to the HP server and running from the sqlplus.

HP-UX:SQL> SELECT * FROM V$OPEN_CURSOR WHERE SID=64;
/
SADDR                   SID USER_NAME                      ADDRESS
HASH_VALUE
---------------- ---------- ------------------------------ ----------------
----------
SQL_TEXT
------------------------------------------------------------
C000000037237C98         64 SCOTT                        C0000000478A4C28
660474943
select deptno,dname,cursor (select ename from emp where dept

C000000037237C98         64 SCOTT                        C000000047422958
4190467353
SELECT "A2"."ENAME" "ENAME" FROM "EMP" "A2" WHERE "A2"."DEPT

If I run the same query from WINDOWS environment connecting
to the same database through SQLPLUS with the Oracle9i client it opens up as
many number connections as there in DEPT table.

SADDR                   SID USER_NAME                      ADDRESS
---------------- ---------- ------------------------------ ----------------
HASH_VALUE SQL_TEXT
---------- ------------------------------------------------------------
C000000037236778         60 SCOTT                        C0000000478A4C28
 660474943 select deptno,dname,cursor (select ename from emp where dept

C000000037236778         60 SCOTT                        C000000047422958
4190467353 SELECT "A2"."ENAME" "ENAME" FROM "EMP" "A2" WHERE "A2"."DEPT

C000000037236778         60 SCOTT                        C000000047422958
4190467353 SELECT "A2"."ENAME" "ENAME" FROM "EMP" "A2" WHERE "A2"."DEPT

C000000037236778         60 SCOTT                        C000000047422958
4190467353 SELECT "A2"."ENAME" "ENAME" FROM "EMP" "A2" WHERE "A2"."DEPT

C000000037236778         60 SCOTT                        C000000047422958
4190467353 SELECT "A2"."ENAME" "ENAME" FROM "EMP" "A2" WHERE "A2"."DEPT

Is there a problem with Oracle9i client or is any setting
that we are missing in our environment.

Thanks
Pushparaj
Followup:
It is not a problem -- when I run it, i actually see "no open cursors" -- until
i turn on session cached cursors (init.ora setting/session setting) then I see
some.

they are not really truly open, just sort of hanging out.
Rating: 4
February 20, 2004
Reviewer: Pushparaj Arulappan from NJ, USA
Tom,

Thanks for your immediate reply.

The following is the setting for cursors in our environment.

show parameter cursor

NAME                                 TYPE        VALUE
------------------------------------ ----------- -------
cursor_sharing                       string      EXACT
cursor_space_for_time                boolean     FALSE
open_cursors                         integer     500
session_cached_cursors               integer     0

One of our application query is failing with "maximum open cursors exceeded"
which has some CURSOR statements in the SELECT statement and retrieves many
rows.

The same SQL without any change works fine if I log on to
the unix box and run it from the sqlplus.

But if I run the exact same query without any change
by connecting to the exact same database from windows
environment through SQLPLUS then it fails with "maximum open cursors exceeded".

While it is running, I could see the rows in V$OPEN_CURSOR
keep increasing and when it reaches the 500 count it fails.

But this is not the case if I run it from the SERVER itself.

I can make this query fail by simply putting 500 rows in
DEPT table.

sql> select deptno,loc,cursor(select ename from emp
where deptno = b.deptno) from emp
/

Please advice.

Thanks
Pushparaj
Followup:
what version of plus do you have one windows and what "plus" are you using
(sqlplusw or sqlplus)
Rating: 4
9i client February 20, 2004
Reviewer: Pushparaj Arulappan from NJ, USA
Tom,
I have mistyped in my previous posing.
sql> select deptno,loc,cursor(select ename from emp
where deptno = b.deptno) from emp
/
It should be

sql> select deptno,loc,cursor(select ename from emp
where deptno = b.deptno)
from dept b
/

The dept table has 550 rows and the emp table has 20 rows
and this query fails from windows sqlplus but not if i run
it from the server itself.

Thanks
Pushparaj
Rating: 4
SQL*Plus: Release 9.2.0.1.0 February 23, 2004
Reviewer: Pushparaj Arulappan from NJ, USA
Tom,

We are using sqlplusw and please the see the version of it.

SQL*Plus: Release 9.2.0.1.0 - Production on Mon Feb 23 09:25:08 2004

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 option
JServer Release 9.2.0.3.0 - Production

SQL> select deptno, cursor(select 'testing' from dual
) as "maxopen" from dept
/
...
...
       692 CURSOR STATEMENT : 2

CURSOR STATEMENT : 2

'TESTIN
-------
testing

ERROR:
ORA-00604: error occurred at recursive SQL level 1
ORA-01000: maximum open cursors exceeded

497 rows selected.

Thanks
Pushparaj
Followup:
try sqlplus.exe.

if that does not reproduce, then you have found a product issue with
sqlplusw.exe -- it is not closing the cursors properly.
February 23, 2004 ron -- Thanks for the question regarding "opinion", version 8.1.7
You Asked
tom
sorry for the late reply. i have rephrased it.i came up with something.need your
expert opinion on this
Does the two queries below give the same results??

this query is taking for ever..

update table1 set
insur_no=12061909
where
insur_type='abcd'
and insur_no=11000001
and sku_linenum in (select sku_linenum from table2
where sku_status is null)

this one executed in 2 minutes..

update table1 T set
T.insur_no=12061909
where exists (select 'x' from table2 Y
where T.sku_linenum=Y.sku_linenum
and Y.sku_status is NULL
and T.insur_type='abcd'
and T.insur_no=11000001)

i couldnt compare the results as the first one did not finish

thanks
and we said...
pushing the t.insur_type='abcd' and t.insur_no=11000001 into the exists is very
strange.

but yes, they are equivalent (and if you used the CBO, you would not have to
perform this sort of rewrite)

See how the RBO is sort of "rule driven" and follows a path based on the query
syntax.  The CBO is not and comes up with effectively the same plan for both:

ops$tkyte@ORA920PC> create table table1 ( insur_no number, insur_type
varchar2(5), sku_linenum number );

Table created.

ops$tkyte@ORA920PC> create table table2 ( sku_linenum number, sku_status
varchar2(5) );

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create index table2_idx on table2(sku_linenum);

Index created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> set autotrace traceonly explain
ops$tkyte@ORA920PC> update table1 set
  2  insur_no=12061909
  3  where
  4  insur_type='abcd'
  5  and insur_no=11000001
  6  and sku_linenum in (select sku_linenum from table2
  7  where sku_status is null)
  8  /

0 rows updated.

Execution Plan
----------------------------------------------------------
   0      UPDATE STATEMENT Optimizer=CHOOSE
   1    0   UPDATE OF 'TABLE1'
   2    1     MERGE JOIN
   3    2       SORT (JOIN)
   4    3         TABLE ACCESS (FULL) OF 'TABLE1'
   5    2       SORT (JOIN)
   6    5         VIEW OF 'VW_NSO_1'
   7    6           SORT (UNIQUE)
   8    7             TABLE ACCESS (FULL) OF 'TABLE2'

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> update table1 T set
  2  T.insur_no=12061909
  3  where exists (select 'x' from table2 Y
  4  where T.sku_linenum=Y.sku_linenum
  5  and Y.sku_status is NULL
  6  and T.insur_type='abcd'
  7  and T.insur_no=11000001)
  8  /

0 rows updated.

Execution Plan
----------------------------------------------------------
   0      UPDATE STATEMENT Optimizer=CHOOSE
   1    0   UPDATE OF 'TABLE1'
   2    1     FILTER
   3    2       TABLE ACCESS (FULL) OF 'TABLE1'
   4    2       FILTER
   5    4         TABLE ACCESS (BY INDEX ROWID) OF 'TABLE2'
   6    5           INDEX (RANGE SCAN) OF 'TABLE2_IDX' (NON-UNIQUE)

ops$tkyte@ORA920PC> set autotrace off
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> exec dbms_stats.set_table_stats( user, 'TABLE1', numrows =>
1000000, numblks => 100000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> exec dbms_stats.set_table_stats( user, 'TABLE2', numrows =>
1000000, numblks => 100000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> set autotrace traceonly explain
ops$tkyte@ORA920PC> update table1 set
  2  insur_no=12061909
  3  where
  4  insur_type='abcd'
  5  and insur_no=11000001
  6  and sku_linenum in (select sku_linenum from table2
  7  where sku_status is null)
  8  /

0 rows updated.

Execution Plan
----------------------------------------------------------
   0      UPDATE STATEMENT Optimizer=CHOOSE (Cost=15379 Card=100 Bytes=4700)
   1    0   UPDATE OF 'TABLE1'
   2    1     NESTED LOOPS (SEMI) (Cost=15379 Card=100 Bytes=4700)
   3    2       TABLE ACCESS (FULL) OF 'TABLE1' (Cost=15179 Card=100 Bytes=3000)
   4    2       TABLE ACCESS (BY INDEX ROWID) OF 'TABLE2' (Cost=2 Card=50000
Bytes=850000)
   5    4         INDEX (RANGE SCAN) OF 'TABLE2_IDX' (NON-UNIQUE) (Cost=1
Card=20)

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> update table1 T set
  2  T.insur_no=12061909
  3  where exists (select 'x' from table2 Y
  4  where T.sku_linenum=Y.sku_linenum
  5  and Y.sku_status is NULL
  6  and T.insur_type='abcd'
  7  and T.insur_no=11000001)
  8  /

0 rows updated.

Execution Plan
----------------------------------------------------------
   0      UPDATE STATEMENT Optimizer=CHOOSE (Cost=115179 Card=50000
Bytes=1500000)
   1    0   UPDATE OF 'TABLE1'
   2    1     FILTER
   3    2       TABLE ACCESS (FULL) OF 'TABLE1' (Cost=15179 Card=50000
Bytes=1500000)
   4    2       FILTER
   5    4         TABLE ACCESS (BY INDEX ROWID) OF 'TABLE2' (Cost=2 Card=500
Bytes=8500)
   6    5           INDEX (RANGE SCAN) OF 'TABLE2_IDX' (NON-UNIQUE) (Cost=1
Card=4000)

ops$tkyte@ORA920PC> set autotrace off
ops$tkyte@ORA920PC>
Review & Followup

Rating: 5
opinion February 23, 2004
Reviewer: A reader
tom. thanks for your reply.i guess this should be fine....silly question ..if i
replace select 'x'..with select Y.sku_linenum  in the subquery ...it'll still
produce the same results right??

update table1 T set
T.insur_no=12061909
where exists (select 'x' from table2 Y
where T.sku_linenum=Y.sku_linenum
and Y.sku_status is NULL)
and T.insur_type='abcd'
and T.insur_no=11000001
Followup:
you should use

update table1 T
   set T.insur_no=12061909
 where exists (select NULL
                 from table2 Y
                where T.sku_linenum=Y.sku_linenum
                  and Y.sku_status is NULL)
   and T.insur_type='abcd'
   and T.insur_no=11000001
/

the select list in a where exists is "meaningless".  you are not using it
anywhere.  NULL shows you "know this fact".  Anything else is just misleading.
February 23, 2004 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)
Rating: 4
February 23, 2004
Reviewer: malcolm
Tom, this thread has been very useful me. Thanks.

I like the way that Oracle can automagically detect whether the result of a
merge will be ambiguous, and throws a ORA-30926 if it's not.

But why can't it do a similar thing with updates of views? What I mean is that
it would be nice to tell Oracle that a view is key-preserved, even if it's not
provable to the server.

Something like this...

update /*+is_key_preserved_hint*/
   (select t1.y t1_y, t2.y t2_y
      from t1, t2
     where t1.x = t2.x)
   set t1_y = t2_y

... and Oracle tries the update, but it fails if the view really isn't key
preserved.

Then we wouldn't need to use the merge workaround.
Followup:
there is a hint, hint is undocumented, therefore I don't talk about bypass_ujvc
Rating: 5
bypass updatable join view check? February 23, 2004
Reviewer: malcolm
I notice this hint is used for MV refreshes, so it must be reasonably safe.

Of course I won't be using it for any production code, but it will be useful for
ad-hoc updates.

Since the functionality is there, it would be nice if Oracle fully supported it.
Followup:
MERGE does it.

Especially in 10g when we can skip the INSERT portion of the MERGE all together.
February 23, 2004 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>
Rating: 3
Importing nonpartitioned table to partitioned table February 23, 2004
Reviewer: Denise from Virginia,USA
Good day Tom...
it's been a somewhat frustrating weekend trying to get
the gift.dmp export from 8.1.7 imported to the 9i db.

I created the partitioned gift table on the 9i db.
As you recommended the LIST partition was the best option
for the intended purpose.
PARTITION BY LIST (usercode1)
        (partition p_acga VALUES('ACGA') tablespace aclj_data,
         partition p_aclj VALUES('ACLJ') tablespace aclj_data)

  1  select table_name,partition_name,tablespace_name
  2* from user_tab_partitions
SQL> /

TABLE_NAME                     PARTITION_NAME                 TABLESPACE_NAME
------------------------------ ------------------------------
-----------------------
GIFT                           P_ACGA                         ACLJ_DATA
GIFT                           P_ACLJ                         ACLJ_DATA

I tried every import statement under the sun:

imp user/pwd tables=gift file=gift.dmp ignore=y, rows=y
that generated an ora-14400 "inserted partition key does
not map to any partition"

I then tried:

imp user/pwd tables=(gift:p_acga,gift:p_aclj) file=gift.dmp
ignore=y rows=y grants=n indexes=n constraints=n

that generated am imp-00055 error "warning partition or
subpartition not found in export file".

I've tried several other scenarios using samples and examples I research and
can't get this darned .dmp file imported.I keep getting either one of the above
import errors.

I've read that if a target table is partitioned,partition
level import rejects any rows that fall above the highest
partition of the target table. Use the MAXVALUE clause to
set this value to unlimited?...not to sure what this means.

I made sure there were no NULL values in the usercode1
column before exporting.

any hints,help much appreciated.
Followup:
well, all that means is the data you imported has usercodes other than the ones
you set up for.  Consider:

ops$tkyte@ORA920PC> drop table t;
Table dropped.

ops$tkyte@ORA920PC> create table t
  2  ( usercode1 varchar2(5),
  3    data      varchar2(10)
  4  )
  5  PARTITION BY LIST (usercode1)
  6      (partition p_acga VALUES('ACGA') ,
  7           partition p_aclj VALUES('ACLJ')  )
  8  /
Table created.

ops$tkyte@ORA920PC> !imp userid=/ full=y ignore=y

Import: Release 9.2.0.4.0 - Production on Mon Feb 23 10:45:57 2004
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

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"
IMP-00058: ORACLE error 14400 encountered
ORA-14400: inserted partition key does not map to any partition
IMP-00028: partial import of previous table rolled back: 2 rows rolled back
Import terminated successfully with warnings.

that is your error, it is because:

ops$tkyte@ORA817DEV> select * from t;

USERC DATA
----- ----------
ACGA  hello
ACLJ  world
XXXX  ugh

the source table has usercodes we did not anticipate - and your data must also
have the same issue.  to get this imported:

ops$tkyte@ORA920PC> drop table t;
Table dropped.

ops$tkyte@ORA920PC> create table t
  2  ( usercode1 varchar2(5),
  3    data      varchar2(10)
  4  )
  5  PARTITION BY LIST (usercode1)
  6      (partition p_acga VALUES('ACGA') ,
  7           partition p_aclj VALUES('ACLJ') ,
  8           partition the_rest values ( DEFAULT ) )
  9  /
Table created.

ops$tkyte@ORA920PC> !imp userid=/ full=y ignore=y

Import: Release 9.2.0.4.0 - Production on Mon Feb 23 10:45:58 2004
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

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"          3 rows imported
Import terminated successfully without warnings.

and there you go!  And then you can:

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select * from t partition(the_rest);

USERC DATA
----- ----------
XXXX  ugh

to see what the bad rows are
Rating: 4
thanks Tom.... February 23, 2004
Reviewer: Denise from Virginia,USA
Connected to:
Oracle8i Release 8.1.7.0.0 - Production
JServer Release 8.1.7.0.0 - Production

SQL> select count(*) from gift where usercode1 not in('ACGA','ACLJ');

  COUNT(*)
----------
         0

SQL>

I had also made sure of the values(sorry for not indicating that)....I believe
there is a CHECK constraint on that column since it is crucial that the proper
client be identified during an insert.

I noticed with your import commands you used full=y.

I'll review and run your test sample on the 9i machine.

as ever...thanks soo much for your patience and help.
Followup:
I used full=y cause I had only one table in there.

You have *bad* data -- I'm 100% on that.  Use the default and you'll see.

  1  select count(*),
  2         count( case when usercode1 not in('ACGA','ACLJ') then 1 end) x,
  3         count( case when usercode1 in('ACGA','ACLJ') then 1 end) y
  4*   from t
ops$tkyte@ORA9IR2> /

  COUNT(*)          X          Y
---------- ---------- ----------
         3          0          2

Hmmm, 3 rows -- none are "not in", but only 2 are "in"....

(beware the NULL)
Rating: 4
Beware the nulls February 23, 2004
Reviewer: Mike from Cleveland, OH USA
If some of your rows have null values, they could cause the results you are
seeing here.
Rating: 4
thanks Tom and Mike... February 23, 2004
Reviewer: Denise from Virginia,USA
I did test for nulls and did test to see if any other
value existed other than ACGA and ACLJ in usercode1.

I imported the gift.dmp file into a regular nonpartitioned
table just so I could perform data validation in the
event data got corrupted during the export...although
the export generated no errors and I fail to see how
that could have happened.

I'll do what Tom says...then I am going to do another
export just incase something is wrong with the current
export file just to make sure the column I need as a
partition key has the correct values.

thanks again...will let you know when I finally get this
figured out.
Rating: 5
Tom.....as always....you are RIGHT! February 24, 2004
Reviewer: Denise from Virginia,USA
  1  select count(*),
  2        count( case when usercode1 not in('ACGA','ACLJ') then 1 end) x,
  3        count( case when usercode1 in('ACGA','ACLJ') then 1 end) y,
  4        count(case when usercode1 is NULL then 1 end) z
  5*   from gift
SQL> /

  COUNT(*)          X          Y          Z
---------- ---------- ---------- ----------
   8059479          0    4232800    3826679

SQL>

I ran your sql statement...added another clause for the
NULLS...and 'lo and behold' the NULLS popped up.

Why didn't they show when I ran the original sql
statement: select count(*) from gift where usercode1 IS NULL;???

anyhow...there it is in black and white.

thanks a quintillion..love the case statement to count
the rows..will add that to my repetoire of sql scripts.

have a great day!
;~D
Rating: 5
Never Mind.... February 24, 2004
Reviewer: Denise from Virginia,USA
I answered my own question...used the wrong operator...
spent 4 days on this because of that...let this be a lesson
for others.

SQL> select count(*) from gift where usercode1 IS NULL;

  COUNT(*)
----------
   3827277

SQL> ed
Wrote file afiedt.buf

  1* select count(*) from gift where usercode1=NULL
SQL> /

  COUNT(*)
----------
         0
February 23, 2004 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
Rating: 3
cursor opened in for cur_name in (select * .....) close automatically when an exception occurs February 23, 2004
Reviewer: Ram from FL,USA
I did not declare any explicit cursor . So I cannot use close cursor when an
exception occurs.
Her eis my code
begin
 for cur_name in (select id, name from peoples)
  if (cur_name.id =2 )
    select deptname into name from department where peopleid =2;
 exception:
   when NO_DATA_FOUND then
     dbms_output.put_line(sqlerrm);
  end if;
 end loop
end;

if the deptname Query does not return anything and raises exception, does the
cursor opened in for loop closes automatically.
One more question , does the for loop open cursor each time it traverses thru
the loop, or it uses the same cursor.
Followup:
implicit cursors are always taken care of for you as they go out of scope.

PLSQL is awesome (if you have my book effective Oracle by design -- i go into
this in more detail).  It'll parse a query once per session and keep is cached
for you.  You'll find that your SQL is parsed ONCE (tkprof will prove that to
you) unless you become seriously starved for cursor slots (then it'll really
close ones you are not using to avoid the "max open cursors" error you'd get
otherwise)
February 23, 2004 Paul -- Thanks for the question regarding "In which order does Oracle parse the SQL statement?", version 8.1.7
You Asked
Hi!

Does Oracle parse the SQL statement from the start or begining of the
statements?
E.g.
A table containing all people in France, 2 columns (City and Sex).

Which is the best approach - placing the largest sort value first or last?

SELECT * FROM huge_table
WHERE city = 'Lyon'
AND sex = 'M'
/

or

SELECT * FROM huge_table
WHERE sex = 'M'
AND city = 'Lyon'
/

Thank you
Paul
and we said...
It does't matter.

the optimizer doesn't care.  The rbo used to, but the rbo is sort of brain dead.
 it would do it bottom up (predicate), right to left (table).  indexes would
change that (it would use an index on city regardless of where in the predicate
it appeared)

My advice -- don't think about it, use the CBO.
Review & Followup

Rating: 3
CBO? February 23, 2004
Reviewer: Murat from Turkiye
Hi,

What is CBO? How is it used?

Thanks,
Murat.
Followup:
CBO = Cost Based Optimizer

You'll want to read the Concepts Guide and the Performance Guide.  Both are
available on otn.oracle.com under documentation.
February 23, 2004 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://download-east.oracle.com/docs/cd/A58617_01/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://download-west.oracle.com/docs/cd/B10501_01/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://download-west.oracle.com/docs/cd/B10501_01/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://download-west.oracle.com/docs/cd/A87860_01/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)
Rating: 3
Migration across platform on OLTP system - Oracle9i R2 January 19, 2004
Reviewer: Baqir Hussain from Emeyville, CA USA
I would like to use materialized views to migrate data from SOLARIS to LINUX and
you suggested as well.

Before kill user connection from the Solaris box, I would like most of the data
to be replicated ahead of time and then migrate a few hours of the rest of the
data across.

I would appreciate if you please show me how to achieve it.
Thanks
Followup:
i gave you the abc's above?

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: 3
migration across platform January 19, 2004
Reviewer: Baqir Hussain from Emeryville, CA USA
Thanks for the prompt response. I would appreciate if you please either point me
to any existing example or show with a very simple example.  Thanks
Followup:
abc, 123, ... here you go...

concept

a) create empty tables in new database....
b) put constraints etc on them (deferrable please, important for mview groups!)
c) refresh them (fill them up)
d) put mv logs on remote tables
e) keep fast refreshing after that (could switch d/c above to avoid possible
double complete refresh)
f) when ready to cut over -- kill access to old db, do a last refresh -- drop
materialized views and there you go.

ops$tkyte@ORA9IR2> create table emp as select * from emp@ora9ir1 where 1=0;

Table created.

ops$tkyte@ORA9IR2> create table dept as select * from dept@ora9ir1 where 1=0;

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> alter table emp add constraint emp_pk
  2  primary key ( empno ) deferrable;

Table altered.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> alter table dept add constraint dept_pk
  2  primary key ( deptno ) deferrable;

Table altered.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> alter table emp add constraint emp_fk_dept
  2  foreign key (deptno ) references dept(deptno) deferrable;

Table altered.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create materialized view emp
  2  on prebuilt table
  3  refresh complete
  4  as
  5  select * from emp@ora9ir1;

Materialized view created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create materialized view dept
  2  on prebuilt table
  3  refresh complete
  4  as
  5  select * from dept@ora9ir1;

Materialized view created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> begin
  2     dbms_refresh.make(
  3        name             =>'group1',
  4        list             =>'emp,dept',
  5        next_date        =>sysdate,
  6        interval         =>'sysdate+1/24',
  7        implicit_destroy =>true);
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec dbms_refresh.refresh( 'GROUP1' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select mview_name, last_refresh_type from user_mviews;

MVIEW_NAME                     LAST_REF
------------------------------ --------
DEPT                           COMPLETE
EMP                            COMPLETE

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> @connect scott/tiger@ora9ir1.localdomain
ops$tkyte@ORA9IR2> set termout off
scott@ORA9IR1> REM GET afiedt.buf NOLIST
scott@ORA9IR1> set termout on
scott@ORA9IR1> create materialized view log on emp;

Materialized view log created.

scott@ORA9IR1> create materialized view log on dept;

Materialized view log created.

scott@ORA9IR1>
scott@ORA9IR1> @connect /
scott@ORA9IR1> set termout off
ops$tkyte@ORA9IR2> REM GET afiedt.buf NOLIST
ops$tkyte@ORA9IR2> set termout on
ops$tkyte@ORA9IR2> alter materialized view emp refresh fast;

Materialized view altered.

ops$tkyte@ORA9IR2> alter materialized view dept refresh fast;

Materialized view altered.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> @connect scott/tiger@ora9ir1.localdomain
ops$tkyte@ORA9IR2> set termout off
scott@ORA9IR1> REM GET afiedt.buf NOLIST
scott@ORA9IR1> set termout on
scott@ORA9IR1> update emp set ename = initcap(ename) where rownum = 1;

1 row updated.

scott@ORA9IR1> update dept set dname = initcap(dname) where rownum = 1;

1 row updated.

scott@ORA9IR1> commit;

Commit complete.

scott@ORA9IR1>
scott@ORA9IR1> @connect /
scott@ORA9IR1> set termout off
ops$tkyte@ORA9IR2> REM GET afiedt.buf NOLIST
ops$tkyte@ORA9IR2> set termout on
ops$tkyte@ORA9IR2> exec dbms_refresh.refresh( 'GROUP1' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> select mview_name, last_refresh_type from user_mviews;

MVIEW_NAME                     LAST_REF
------------------------------ --------
DEPT                           FAST
EMP                            FAST

ops$tkyte@ORA9IR2> drop materialized view emp;

Materialized view dropped.

ops$tkyte@ORA9IR2> drop materialized view dept;

Materialized view dropped.

ops$tkyte@ORA9IR2> select count(*) from emp;

  COUNT(*)
----------
        14

ops$tkyte@ORA9IR2> select count(*) from dept;

  COUNT(*)
----------
         4
Rating: 4
migration across platform January 19, 2004
Reviewer: Baqir Hussain from Emeryville, CA USA
I figured out ,based on your suggestion, how to set up mv in target and source
database.  Thanks
Rating: 5
migration across platforms January 27, 2004
Reviewer: Baqir Hussain from Emeryville, CA USA
Tom,
Thanks for the example.  Without this example it was impossible to figure out
how to migrate across platforms. Thanks again.

I have few questions regarding data move:

1. What is deferrable constraint for in mv replication?
2. How the refresh groups should be created?  Should I create multiple fresh
groups with (parent + child ) tables ONLY and/or some other way?
3. Should I create all indexes after the cut over and then run dbms_stat and/or
any other way?

I would very much appreciate your help in this regard.
Thanks again
Followup:
1) the way snapshots are refreshed is "table at a time", not transaction by
transaction.  hence RI and other contraints may be invalid during the refresh
but will be consistent by the commit.  so, we defer constraints until commit.

2) parent+child

3) you tell me -- that is your call.
Rating: 5
data migration across platform January 28, 2004
Reviewer: Baqir Hussain from Emeryville, CA USA
Tom,
Thanks again.  I would prefer creating indexes after the cut over and than run
dbms_stats on each table.

1. Should I change deferrable constraint to default (not deferrable) after the
cut over for regular OLTP operation?
Please advise.
Thanks
Followup:
1) you cannot.  you would have drop and recreate them.  the major difference
will be that a unique/primary key that is deferrable will use a NON-UNIQUE
index, instead of a unique index for enforcement.
Rating: 5
data migration across platform January 29, 2004
Reviewer: Baqir Hussain from Emeryville, CA USA
Tom,
Thanks for the prompt reply.  Unique index enforement is the requirement for our
application.  During the data migration this enforment is going to break.
And during migration we might get duplicate records due to "deferrable" .
After the cut over, should
1. I look for the duplicate records in each table and then delete it.
2. Drop old contraint and then enfore new constraint (unique index).

3. Or there is any other way that we can keep the uniqueness during migration.

Thanks
Followup:
no, you won't get duplicates.

deferrable means "wait until commit to verify"

it does not mean "just let junk in and ignore it"

when the snapshot refreshes -- everything is OK -- everything is unique,
everything is consistent.  no duplicates.
Rating: 5
migration across plat form January 31, 2004
Reviewer: Baqir Hussain from Emeryville, CA USA
Tom,
Thanks for providing valuable suggestions.
I understand deferrable constraint , in read-only mv's replication, keep the
database away from conflict.

1. In multi-master replication (master-slave) what is best way to avoid
conflict?
2.  Data type LONG cannot be replicated.  I have a couple of table with LONG.  A
week before the cut over I will export and import these tables into the new
database.  After the cut over, a week of the remaining data, needs to be
imported back to the new database.  I can take an export by (sysdate - 7)using
query option. Is it possible to import this data on the existing table into the
new database or please advise some other method to achieve it.
Thanks
Followup:
1) multi-master is peer to peer (symetric).  master-slave is materialized views.
 Hmm?

In multi-master, replication is implemented by replaying transactions exactly as
they happened -- so, you do not need deferrable constraints.

In master/slave, replication is done to the snapshot tables one table at a time,
hence you need deferrable constraints so the tables can be inconsistent for a
brief moment of time during the refresh (no one sees these inconsistencies of
course since no one sees updates until they are committed and the constraints
are verified upon commit)

2) longs will have to be done manually via whatever process you dream up.  If
they were clobs -- it'd be done with the materialized views for you.
Rating: 5
migration across platform February 04, 2004
Reviewer: Baqir Hussain from Emeryville, CA USA
If I run the following at command line
exec dbms_refresh.refresh ('BDR.GROUP1');
I will not get the prompt back until it completes "refresh complete" on the
tables defined in the group.
After the completion of this procedure , I change mv's to "refresh fast" at
command line.
I tried using cron job as follows:

exec dbms_refresh.refresh ('BDR.GROUP1');
alter materialized view client refresh fast;
alter materialized view home_search refresh fast;

but it oracle stucks at
update sys.snap$ set auto_fast = :1
where vname = :3 and sowner = :2 and instsite = :4

On the other hand, it works fine at command line.

I would like to have it done automatically (with some sort of script).

Please advise.
Thanks
Followup:
Oracle should not be getting "stuck" there.

create a job using dbms_job

declare
   l_job number;
begin
  dbms_job.submit( l_job,
  'begin
       dbms_refresh.refresh( ''BDR.GROUP1'' );
       execute immediate ''alter ....'';
       execute immediate ''alter ....'';
   end;'
  );
  commit;
end;
/

that'll "background" it -- assuming job_queue_processes > 0 (init.ora parameter)
Rating: 5
Can we migrate directly from 8i to 10g? February 23, 2004
Reviewer: A Reader from USA
Hi Tom,
Since Oracle will desupport 8i, we need to migrate to a newer version.  Since
10g is already on the market, can we migrate directly from 8i to 10g (I could
not find related 10g document).  This is very important for us to make our
decision.  Can you please tell us if it's possible or good idea to skip 9i?
Technically, is it much more difficult to do?  PLease tell us the pros and cons
of doing that.
Thank you for your help.
Followup:
8iR3 (817), 9iR1 (90), and 9iR2 (9.2) can be upgraded directly to 10gR1 (10.1)
from their terminal releases (eg: 8.1.7.4 is typically the terminal release of
8iR3)

If you don't have the "first version willies" -- going from 8i -> 10g involves
less work than 8i -> 9i -> 10g will at the end of the day.

pros of going to 10g, one less upgrade, test, fix cycle.

cons, you'll have lots of first version people out there ready to jump all over
you everytime something appears to be wrong (even tho you'll hit issues going to
9i as well in all probability -- things you find in test, fix or workaround
before production)
February 23, 2004 Ramesh -- Thanks for the question regarding "using explain plan", version
You Asked
How should I use the EXPLAIN Plan for tuning of my SQL
Statements .Kindly advice me on the sequence of steps to be
followed . Some live examples could also be very helpful.

With Regards.
Ramesh.S
and we said...
If you are looking for the syntax of explain plan -- i will
simply refer you to the SQL Language Manual, it is documented
there.

I myself find it much easier to use autotrace in sqlplus.
Consider this example:

scott@dev8i> set autotrace on
scott@dev8i> select count(*) from emp, dept where emp.deptno =
dept.deptno;

  COUNT(*)
----------
        14

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=1
Bytes=26)
   1    0   SORT (AGGREGATE)
   2    1     MERGE JOIN (Cost=6 Card=11 Bytes=286)
   3    2       SORT (JOIN) (Cost=3 Card=14 Bytes=182)
   4    3         TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=14
Bytes=182)
   5    2       SORT (JOIN) (Cost=3 Card=82 Bytes=1066)
   6    5         TABLE ACCESS (FULL) OF 'DEPT' (Cost=1 Card=82
Bytes=1066)

Statistics
----------------------------------------------------------
        475  recursive calls
         27  db block gets
         81  consistent gets
          8  physical reads
          0  redo size
       1083  bytes sent via SQL*Net to client
        697  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          9  sorts (memory)
          0  sorts (disk)
          1  rows processed

(to interpret the explain plan output -- see the server tuning
manual).  the above shows me that we did 2 full scans and merged
the emp/dept tables together, then did an aggregate (the count)
and finished.  It took 81 logical reads (consistent gets) and
hit the disk for 8 blocks and so on.

to get a quick start on setting up autotrace, see
http://osi.oracle.com/~tkyte/article1/autotrace.html
TKPROF and SQL_TRACE is another great tool.  Consider:

scott@dev8i> alter session set sql_trace=true;
Session altered.

scott@dev8i> alter system set timed_statistics=true;
System altered.

scott@dev8i> select count(*) from dept,emp where dept.deptno =
emp.deptno;

  COUNT(*)
----------
        14

$ ls -t $ORACLE_HOME/admin/$ORACLE_SID/udump/*_ora_*.trc | head
-1
/export/home/oracle8i/admin/ora8idev/udump/ora8idev_ora_13400.trc

$ tkprof
/export/home/oracle8i/admin/ora8idev/udump/ora8idev_ora_13400.trc
 tmp.txt explain=scott/tiger

TKPROF: Release 8.1.5.0.0 - Production on Sun Feb 27 09:19:20
2000

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

$ vi tmp.txt

and in the report we'll find:

...
select count(*)
from
 dept,emp where dept.deptno = emp.deptno

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          2
 24           1
------- ------  -------- ---------- ---------- ----------
----------  ----------
total        4      0.01       0.01          0          2
 24           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 34  (SCOTT)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
     14   MERGE JOIN
     15    SORT JOIN
     14     TABLE ACCESS FULL EMP
     14    SORT JOIN
      4     TABLE ACCESS FULL DEPT
....

which is even more informative then the autotrace.  it not only
shows the plan but execution times, number of rows flowing
through each step of the plan and so on.

see the server tuning manual for more info on sql_trace and
timed_statistics....
Review & Followup

Rating: 5
autotrace December 05, 2002
Reviewer: mo
Tom:

when i do this in GUI sqlplus I get:

SQL> set autotrace on;
SP2-0613: Unable to verify PLAN_TABLE format or existence
SP2-0611: Error enabling EXPLAIN report

do i have to install anything in database
Followup:
to get a quick start on setting up autotrace, see
http://osi.oracle.com/~tkyte/article1/autotrace.html
that tells you what you need to do.
Rating: 4
You set sys=no ? December 05, 2002
Reviewer: Robert from PA
$ tkprof /export/home/oracle8i/admin/ora8idev/udump/ora8idev_ora_13400.trc
tmp.txt explain=scott/tiger

Tom, do you set sys=no or do you find the sys stuff any useful ?
Followup:
depends, it can be useful -- sometimes it is just in the way.

Consider this recent issue that might have been missed with sys=no:

...

select c.name, u.name
from
 con$ c, cdef$ cd, user$ u  where c.con# = cd.con# and cd.enabled = :1 and
  c.owner# = u.user#

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse   275635    128.33     356.95          0          0          0           0
Execute 275635    130.44     300.40          0          0          0           0
Fetch   275635    128.62     600.91          0    2756350          0      275635
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   826905    387.39    1258.26          0    2756350          0      275635

does this means that 275635 soft parses??? and can you tell me y this is
happening?

tnx and more power.

Cheers.

Followup:

This is recursive SQL -- sql that is done on behalf of your SQL.

In this case, it looks like the recursive SQL we use to retrieve a constraint
name upon unique key violation (ora-00001).  Consider:

ops$tkyte@ORA920.US.ORACLE.COM> create table t ( x int primary key );
Table created.

ops$tkyte@ORA920.US.ORACLE.COM> insert into t values ( 1 );
1 row created.

ops$tkyte@ORA920.US.ORACLE.COM> alter session set sql_trace=true;
Session altered.

ops$tkyte@ORA920.US.ORACLE.COM> insert into t values ( 1 );
insert into t values ( 1 )
*
ERROR at line 1:
ORA-00001: unique constraint (OPS$TKYTE.SYS_C004571) violated

ops$tkyte@ORA920.US.ORACLE.COM> insert into t values ( 1 );
insert into t values ( 1 )
*
ERROR at line 1:
ORA-00001: unique constraint (OPS$TKYTE.SYS_C004571) violated

and the tkprof has:

select c.name, u.name
from
 con$ c, cdef$ cd, user$ u  where c.con# = cd.con# and cd.enabled = :1 and
  c.owner# = u.user#

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         16          0           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        6      0.00       0.00          0         16          0           2

So, one approach to decreasing could be changing the logic.  Suppose you are
doing a "merge" -- you have logic like this:

   for x in ( set of records )
   loop
        begin
              insert into table
        exception
              when dup_val_on_index then update ...
        end
   end loop;

turn it into:

   for x in ( set of records )
   loop
       update ...
       if ( sql%rowcount = 0 )
       then
            insert ....
       end if;
   end loop

or avoid the constraint by doing something like:

   insert into t
   select :pk, :bv1, :bv2, :bv3 from dual
    where not exists (select 1 from T where pk = :pk );

   if ( sql%rowcount = 0 )
   then
      duplication record logic here or just raise dup_val_on_index
   end if;

In short -- it is the result of 275,635 unique key violations in one program
execution.
Rating: 3
Error enabling Explain Plan May 13, 2003
Reviewer: Aamir Abbas from Karachi, Pakistan
I've installed 9i Enterprise in Typical mode -- and I assume that Typical will
install almost complete package).

I selected to create General Purpose database with pre-defined set of
objects....

When I use the AUTOTRACE options for query optimization, I get the following
errors.

SQL> set autotrace on explain
SP2-0613: Unable to verify PLAN_TABLE format or existence
SP2-0611: Error enabling EXPLAIN report

SQL> set autotrace traceonly
SP2-0613: Unable to verify PLAN_TABLE format or existence
SP2-0611: Error enabling EXPLAIN report
SP2-0618: Cannot find the Session Identifier.  Check PLUSTRACE role is enabled
SP2-0611: Error enabling STATISTICS report

How can I enable this TRACE, EXPLAIN PLAN, STATISTICS etc in my newly built
database?

Also kindly guide.. if I want to rebuild my database with predefined set of
objects.... Lets say if I get my database inconsistent or corruprt.. or for any
other reason... I want to rebuilt the GENERAL PURPOSE database with predefined
set of objects.. How can I do so?

Thanks for your help.
Followup:
see
http://asktom.oracle.com/~tkyte/article1/autotrace.html
trace and statistics are always "enabled".  explain plan needs the plan_table,
?/rdbms/admin/utlxplan.sql

you would delete and create a new database -- use DBCA, it'll do that for you
Rating: 3
Explain Plan + DBCA May 14, 2003
Reviewer: Aamir Abbas from Karachi, Pakistan
Your link worked. Thanks. But sorry for being still a bit asky. :)

<quote>
trace and statistics are always "enabled".  explain plan needs the plan_table,
?/rdbms/admin/utlxplan.sql
</quote>

I've just installed 9i, and it's not "enabled by default". Also I've 8.1.6 on
other server, and it also says the same thing:

SQL> set autotrace on
SP2-0613: Unable to verify PLAN_TABLE format or existence
SP2-0611: Error enabling EXPLAIN report
....

DBCA
====
<quote>
you would delete and create a new database -- use DBCA, it'll do that for you
</quote>

I found no files with this name on my disk. See...

D:\>dbca
'dbca' is not recognized as an internal or external command,
operable program or batch file.

D:\>path
PATH=d:\progra~1\jdk13\bin;D:\Oracle\OraDev6i\bin;D:\Oracle\Ora816\bin;D:\Progra
m Files\Oracle\jre\1.1.7\bin;D:\WINNT\system32;D:\WINNT;D:\WINNT\System32\Wbem;D
:\Program Files\Common Files\Network Associates\VirusScan Engine\4.0.xx\;E:\Orac
le\Ora816\orb\bin;E:\Oracle\OraDev\BIN;D:\Oracle\OraDev\BIN;D:\Oracle\Ora816\orb
\bin;D:\Oracle\OraDev6i\jdk\bin;D:\Program Files\Rational\common;D:\Program File
s\Rational\jintegra\bin;D:\Program Files\Rational\XDE\Addins\CompareMerge;

D:\>dir dbca*.* /a/s/w
 Volume in drive D has no label.
 Volume Serial Number is 0CAD-CA0B

 Directory of D:\Oracle\Ora816\assistants

[dbca]
               0 File(s)              0 bytes

 Directory of D:\Oracle\Ora816\classes\oracle\sysman\ocmhelp

dbcapturewiz32x32.gif
               1 File(s)            391 bytes

     Total Files Listed:
               1 File(s)            391 bytes
               1 Dir(s)     297,930,752 bytes free

What's DBCA? Is it an executable or a .sql? Where to find it? Can you briefly
hint the procedure of creating new database with predefined options/schemas?

Thanks a lot for your patience. :)
Rating: 3
location of tkprof May 27, 2003
Reviewer: sgs_ocp from India
Hello Tom,
I could not find the tkprof executable in our installation

win2000 / oracle 9.0.1

Do i have to select something while installation?
As we have gone for default installation.
Please guide.
Followup:
goto metalink and get the patch.  They forgot tkprof.exe on windoze early
releases.
Rating: 5
got sp2 error when set autotrace on in scott account May 27, 2003
Reviewer: David from Dallas, TX
Tom,

In your original example you used scott account. I tried to use scott as well
but I got the error as showing below.

PLUSTRACE role is granted to Scott and PLAN_TABLE is created by Scott via
utlxplan.sql.

By the way, set autotrace on in my dba account worked.

Enter user-name: scott
Enter password:

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> drop table plan_table;

Table dropped.

SQL> @/app/oracle/product/817-64/rdbms/admin/utlxplan.sql

Table created.

SQL> set autotrace on
SP2-0618: Cannot find the Session Identifier.  Check PLUSTRACE role is enabled
SP2-0611: Error enabling STATISTICS report
Followup:
don't think the plustrace role is correct then.

It found the plan table.
It did not find the v$ tables that plustrace gives you access to.

so plan table was OK.
v$ not

something wrong with your plustrace role. consider this example:

ops$tkyte@ORA817DEV> drop user a cascade;
User dropped.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> grant connect, plustrace to a identified by a;

Grant succeeded.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select table_name, privilege from dba_tab_privs where
grantee='PUBLIC'
  2  and table_name in ( 'V_$SESSION', 'V_$SESSTAT', 'V_$STATNAME' );

no rows selected

shows the v$ stuff we need access to isn't granted to public on my system or
anything..

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select table_name, privilege from dba_tab_privs where
grantee='PLUSTRACE' and table_name like 'V%';

TABLE_NAME                     PRIVILEGE
------------------------------ ----------------------------------------
V_$SESSION                     SELECT
V_$SESSTAT                     SELECT
V_$STATNAME                    SELECT

that is what plustrace gives access to

ops$tkyte@ORA817DEV> select * from dba_sys_privs where grantee='PUBLIC';

no rows selected

and no funky "select any table" privs on public or anything like that

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> select * from session_roles;

ROLE
------------------------------
PLUSTRACE
CONNECT

only those two roles are on...

a@ORA817DEV> set autotrace on
a@ORA817DEV> 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

a@ORA817DEV> set autotrace off

and that is what it takes to get autotrace working
Rating: 5
AUTOTRACE ON EXPLAIN September 10, 2003
Reviewer: A reader
If set autotrace on explain and tried running a query using USER_ data
dictionary tables.  I kept getting the error

Execution Plan
----------------------------------------------------------
ERROR:
ORA-01031: insufficient privileges

I get an explain plan if I query any of the application tables.  So, is there a
restriction on generating an explain plan when dealing with data dictionary
tables?
Followup:
you need to be able to see the base tables upon which the view is constructed to
explain it

you don't have that access, so you cannot see the plans.
Rating: 3
UTLXPLAN.SQL October 27, 2003
Reviewer: Kunjan Mehta from Bangalore, India
Hi, I tried to locate the UTLXPLAN.SQL file . as per documentation you
suggested, (it is located under $ORACLE_HOME/rdbms/admin on Unix.) The OS for me
is windows and not Unix. Still, i checked in $ORACLE_HOME/rdbms/admin but did
not find the file.
(Q 1) would it be under some other name in windows?

I searced for UTLXPLAN.sql in entire ORACLE_HOME (which is directory orahome1 in
our root directory) but didnot find such file.

i also tried to check the distribution media (2 CDs) from which we had installed
Oracle9iDS (we have installed entire 9iDS) but search didn't yield any positive
results here also. Probably it might be compressed in any of the jar file.
(Q 2) Can you please guide me as to where can i find the script of UTLXPLAN.SQL?

(Q 3) or if we missed some component while installing 9iDS, what component
should be install which will install such utility SQL script files?
Followup:
utlxplan.sql is in rdbms/admin on windows and on all ports.

show us a cut and paste of you being in the oracle_home/rdbms/admin directory
and typing:

dir utlx*.*
Rating: 3
dir utlx*.* October 28, 2003
Reviewer: Kunjan Mehta from Bangalore, India
giving this command gives following result.

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

C:\ORAHOME1\RDBMS\ADMIN>dir utlx*.*
 Volume in drive C has no label.
 Volume Serial Number is 78A5-ED9A

 Directory of C:\ORAHOME1\RDBMS\ADMIN

File Not Found

C:\ORAHOME1\RDBMS\ADMIN>

------------------
As i had written in my query, i have already searched C:\orahome1\rdbms\admin.
(also checked the DBS, BIN etc .directory before giving a full scan of directory
c:\orahome1 for file utlxplan.sql, u*.sql and *.sql, but i did not find
utlxplan.sql in entire directory tree orahome1.

If I get any URL from where i can download this script, that also would do good.

Rgds...
Followup:
you do not have a database install there

there would be lots of utlx*.* files in there.

so, you have none of the utl scripts.  find a database install.

Here is a plan table of 9iR2

create table PLAN_TABLE (
    statement_id    varchar2(30),
    timestamp       date,
    remarks         varchar2(80),
    operation       varchar2(30),
    options         varchar2(255),
    object_node     varchar2(128),
    object_owner    varchar2(30),
    object_name     varchar2(30),
    object_instance numeric,
    object_type     varchar2(30),
    optimizer       varchar2(255),
    search_columns  number,
    id      numeric,
    parent_id   numeric,
    position    numeric,
    cost        numeric,
    cardinality numeric,
    bytes       numeric,
    other_tag       varchar2(255),
    partition_start varchar2(255),
        partition_stop  varchar2(255),
        partition_id    numeric,
    other       long,
    distribution    varchar2(30),
    cpu_cost    numeric,
    io_cost     numeric,
    temp_space  numeric,
        access_predicates varchar2(4000),
        filter_predicates varchar2(4000));
Rating: 5
Kunjan try this: October 28, 2003
Reviewer: David from Burnaby, BC CAN
You are not interpreting oracle home correctly.  You want to enter dir
%ORACLE_HOME%\rdbms\admin\utlx*.* on a windows system.  If you don't find
anything, you don't have oracle installed!
Rating: 5
Hate it when I post without testing October 28, 2003
Reviewer: David from Burnaby, BC CAN
For %oracle_home% to translate properly, you need to be in sqlplus.  To run
utlxplan.sql on a windows sytem simply enter @%oracle_home%\rdbms\admin\utlxplan
at the sqlplus prompt (make sure you are logged in with an account with correct
privileges).
Rating: 5
utlxplan.sql October 30, 2003
Reviewer: Kunjan Mehta from Bangalore, India
Hi Tom,

Thank you very much for the script.

----
you do not have a database install there
----
looking to your answer..... I was looking at a wrong place. I was trying to
locate the script on the client machine, in rdbms/admin directory with intention
to run it from client machine through SQL Plus - SYS account. That's my mistake.
I checked on the server and found UTLXPLAN.sql and all other scripts.

I am using Oracle 9i Enterprise Release 9.0.1. Is it same as 9iR1? also i run
script given by you (from my machine through sys as sysdba) and it worked well.
I also checked by explaining plan for a couple of scripts. But the script you
gave (9iR2) is slightly different than the script i found on our database server
(your script have 2 more fields - access_predicates varchar2(4000) &
filter_predicates varchar2(4000)). should i rerun the script i found on the
server (i.e. for the version it's intended for) or i can continue using script
given by you. Any advantage/disadvantage/mismatch?

Thanks a lot once again.

Regards

----------------------------
Hi David,
If you read my query, you would be knowing that i'm not interpreting oracle home
incorrectly. on my machine the oracle home is 'C:\Orahome1'. Still... I tried
running the command given by you.
---
SQL*Plus: Release 9.0.1.3.0 - Production on Wed Oct 29 09:20:51 2003

(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.1.1 - Production

SQL> connect sys as sysdba
Enter password: *****************
Connected.
SQL> @%oracle_home%\rdbms\admin\utlxplan
SP2-0310: unable to open file "C:\OraHome1\rdbms\admin\utlxplan.sql"
SQL>
---
The problem was not in the oracle home. it was the machine, on which i was
locating the script.
anyway, thanks for the reply.
Followup:
i gave you 9iR2's plan table.

you are running 9iR1 -- different plan table.  use the one from your install
Rating: 3
exec plan generation hangs November 15, 2003
Reviewer: j.
hi tom,

i've to analyze a performance issue within an existing application developed
some years ago by some consultants.

there are several views, building up different layers, one on top of another.
these views provide a client interface and are therefore forced by hints to get
first rows fast. furthermore nested loops and ordered are enforced many times
and outer joins are frequently used. so in general these views are at least fast
enough as long as they are used the *exact* way they are designed for, using
certain predicates ...

but since we have now the additional need to use such views in "all rows mode"
(or to join them) we are facing really bad performance in most cases.

i generated an execution plan for one of those uppermost (top level) views using
the predicates it was designed for. the plan contained nearly 100 nested loops
(almost only index unique scans). in order to measure the execution time for one
of my all rows use cases without having a chance to get trace files from server
side i've "wrapped" the view statement like this:

select count(*) from (select RowNum, a.* from <slow view> a where <usual view
parameters>)

the intention of rownum was to "materialize" the result set (all rows hint
didn't kick in), and count(*) should minimize the generated data to be
transferred to the client side to zero out network overhead.

unfortunately this last statement didn't terminate at all, and in addition i
can't get an execution plan for it anymore although i set optimizer max
permutations to 5 (!).

so, i've now three questions at a time: is my plan to measure "net" execution
time appropriate at all, and what could have caused the statement to hang after
applying rownum and count? how can one find out the reason in such cases?

can you please help?
Followup:
well, the query probably did not "hang", it was just taking a long time.  if you
have 9i, the plan for the executing query will be in v$sql_plan, you can query
it from there.

set autotrace traceonly explain -- does that work with this?

getting the TRACE FILE is going to be very very relevant.

tuning a query with 100+ nested loops joins is going to be a "tad difficult"
Rating: 3
traceonly was *THE* answer November 15, 2003
Reviewer: j
the "only" problem was to get statistics generated. the execution plan looks
very simlar to the previous (although i haven't inspected each and every of
those 100 nested loops so far) - except the costs computed.

what could be the impact of count and rownum? it takes some time to loop through
the result set of <slow view> in pl/sql, but at least this loop reaches its end.
do you have any ideas why pure sql doesn't work as well?
Followup:
you are waiting for the very very very last row to be returned.

you have queries that have been hinted to death to get the first row returned.

meaning, the last row could be days or weeks in coming.  See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6749454952894#6760861174154
for a classic case.....

1 hour using nested loops
10 seconds using hash joins

your views are not at all appropriate for what you are trying to use them for.
Suggestion:

a) search for getaview on this site
b) suck their views out of the database
c) rename them all (put xx_ on the front)
d) delete the hints
e) query using the xx_ views with ALL_ROWS optimization and see what happens
then
Rating: 5
9i R2 explain plan January 03, 2004
Reviewer: A reader
Hi

I am using dbms_xplan to see the execution plans. There is something which make
someone wonder, sometimes it shows me COST %CPU in COST column but most of time
not. How so? I dont gather system statistics at all
Followup:
optimizer_index_cost_adj can "accidently" turn it on

ops$tkyte@ORA9IR2> create table t ( x int primary key );
Table created.

ops$tkyte@ORA9IR2> insert into t select rownum from all_users;
44 rows created.

ops$tkyte@ORA9IR2> analyze table t compute statistics;
Table analyzed.

ops$tkyte@ORA9IR2> delete from plan_table;
2 rows deleted.

ops$tkyte@ORA9IR2> explain plan for select * from t where x = 55;
Explained.

ops$tkyte@ORA9IR2> select * from table( dbms_xplan.display );

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
--------------------------------------------------

---------------------------------------------------------------------
| Id  | Operation            |  Name        | Rows  | Bytes | Cost  |
---------------------------------------------------------------------
|   0 | SELECT STATEMENT     |              |     1 |     2 |     1 |
|*  1 |  INDEX UNIQUE SCAN   | SYS_C004027  |     1 |     2 |       |
---------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("T"."X"=55)

Note: cpu costing is off

14 rows selected.

ops$tkyte@ORA9IR2> alter session set optimizer_index_cost_adj = 50;
Session altered.

ops$tkyte@ORA9IR2> delete from plan_table;
2 rows deleted.

ops$tkyte@ORA9IR2> explain plan for select * from t where x = 55;
Explained.

ops$tkyte@ORA9IR2> select * from table( dbms_xplan.display );

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
--------------------------------------------------

--------------------------------------------------------------------------
| Id  | Operation            |  Name        | Rows  | Bytes | Cost (%CPU)|
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |              |     1 |     2 |     2  (50)|
|*  1 |  INDEX UNIQUE SCAN   | SYS_C004027  |     1 |     2 |            |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("T"."X"=55)

12 rows selected.
Rating: 5
oh I see January 03, 2004
Reviewer: A reader
No wonder, I was playing with these optimizer parameters... Btw what is a good
value for optimizer_index_cost_adj and optimizer_index_caching for OLTP? 20/80,
30/70?
Followup:
yes, those are both "valid", one it just "stronger than the other"
Rating: 4
A quick one February 04, 2004
Reviewer: OracleO from India
Hi Tom,

I guess this might be out of context but should be a quick one for you to
answer.

The output after setting autotrace on gets wraaped to next line. is there anyway
for it not to wrap.

   7    6               INDEX (RANGE SCAN) OF 'INDX_FK_PERMISSION_PART <== wraps
and goes to next line
          Y' (NON-UNIQUE) (Cost=3 Card=1)

   8    3         TABLE ACCESS (BY INDEX ROWID) OF 'PROCESS' (Cost=1 C
          ard=1064 Bytes=45752)

Thanks,
Followup:
column plan_plus_exp format a100

(if you have my book Effective Oracle by Design -- chapter 2 has all of the
settings you can set for autotrace)
Rating: 5
Explain Plan on Stored procedure February 23, 2004
Reviewer: Wor from USA
Hi Tom,
I have oracle 8.1.7 windows 2000.
How can I get the explain plan of stored procedure.
I know to generate the explain plan for SQL's but I am not able to generate the
Plan for stored procedures.

The reason:
If I extract the SQL from procedure with harcoded IN Clause
I am able to generate the explain plan.
but in stored procedure if I have 2 where conditions with  syntax e.g. select
..where ..IN (SELECT * FROM THE (SELECT CAST(..) FROM DUAL))) and ...IN (SELECT
* FROM THE (SELECT CAST(..) FROM DUAL)))

The stored procedure executes for couple of minutes and then gives the same
results if I harcode the IN clause.

My guess is when I use 2 SELECT CAST syntax to handle IN clause the procedure
ignores all the indexes, thats why I want to generate the explain plan for the
stored procedure and not extract the SQL from procedure to generate explain
plan.

Thanks in advance
Wor
Followup:
alter session set sql_trace=true; and use tkprof.
February 24, 2004 Richer -- Thanks for the question regarding "Materialized View Refresh", version 9.2.0
You Asked
Hi Tom,

I was at your conference in MTL in february.  It was a pleasure to finally meet
you.

My question is regarding the refresh of MVs.  We are using Discoverer with a 9i
Database in Archivelog mode.   The problem is when we need to refresh our
Materialized Views, a lot of redo logs are generated.  It is somewhow strange to
me since I though the refresh was done with an append hint.  The Materialized
views have been created with the NOLOGGING option.

CREATE MATERIALIZED VIEW ....
AS SELECT /* APPEND */...

We are using the DBMS_MVIEWS.REFRESH procedure:

exec DBMS_MVIEW.REFRESH('TBGADM.TBGADM_MV_44','C','',FALSE,FALSE,0,0,0,TRUE);

This refresh generated around 679 MB of REDO (the MV weights around 200MB).  How
can we accomplish this refresh using less ARCHIVE LOGS?

Can you shed some light on this?

Rich
and we said...
If you do incremental refreshes, the MV would be refreshed using conventional
SQL.

If this is a single table, complete refresh, it will use:

truncate
insert /*+ append */

which DOES NOT negate redo, redo will be generated -- append bypasses UNDO on
the table data only (and hence minimizes redo, but does not bypass it).

Only if it was a NOLOGGING object would the append bypass redo and then only for
the table -- but not any indexes on the table (if this MV has indexes, the will
always be logged regardless, they have to be)

If this table is part of a refresh group, it'll be done with a DELETE (redo)
plus insert (redo).

But here is what you should be seeing if this is a single table (not in a
refresh group) and how you can mimimize the redo generated and what'll happen if
you have indexes on this:

ops$tkyte@ORA920PC> select count(*) from big_table;

  COUNT(*)
----------
   1000000

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> drop materialized view big_table_mv;

Materialized view dropped.

ops$tkyte@ORA920PC> create materialized view big_table_mv as select * from
big_table;

Materialized view created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> column value new_val V
ops$tkyte@ORA920PC> set verify off
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> 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                       153976776

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> exec dbms_mview.refresh( 'BIG_TABLE_MV', 'C' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select a.name, b.value, to_char( b.value-&V,
'999,999,999,999' ) diff
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and a.name = 'redo size'
  5  /

NAME                                VALUE DIFF
------------------------------ ---------- ----------------
redo size                       330108116      176,131,340

so, in archivelog mode, about 176m of redo generated for that -- no indexes or
anything.  we can drastically reduce that:

ops$tkyte@ORA920PC> alter table big_table_mv nologging;

Table altered.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> exec dbms_mview.refresh( 'BIG_TABLE_MV', 'C' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select a.name, b.value, to_char( b.value-&V,
'999,999,999,999' ) diff
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and a.name = 'redo size'
  5  /

NAME                                VALUE DIFF
------------------------------ ---------- ----------------
redo size                       387304376       57,196,260

using nologging on the MV table -- but -- if you have indexes:

ops$tkyte@ORA920PC> create index big_table_mv_idx on
big_table_mv(owner,object_type,object_name);

Index created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> 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                       444214684

ops$tkyte@ORA920PC> exec dbms_mview.refresh( 'BIG_TABLE_MV', 'C' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select a.name, b.value, to_char( b.value-&V,
'999,999,999,999' ) diff
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and a.name = 'redo size'
  5  /

NAME                                VALUE DIFF
------------------------------ ---------- ----------------
redo size                       709972648      265,757,964

and the only way to reduce that would be to use a custom job to refresh the MV
and the job would:

a) disable indexes
b) call refresh
c) rebuild indexes with no logging

But honestly, 6/700meg of redo isn't but a drop in the proverbial bucket (eg: it
really isn't "that much" in the grand scheme of things) -- but those are the
things you can do about it.

Bear in mind, you need to backup any files with unrecoverable changes right
after doing them!
Review & Followup

Rating: 5
MV view February 20, 2004
Reviewer: Jegan from Chennai
Tuning this query...
I need you suggestion and your great input in tuning this query.

Question on tuning this query...

Count of records per table.
---------------------------
CCRT_CALENDAR_DIM - 7304
PORTFOLIOS_DIM - 17
ACCOUNT_MONTHLY_FACT - 39691552 PARTITIONED BY MONTH (has 38 partitions)

This create mv script (attached below) takes 2 hrs 30 minutes and I am doin a
COMPLETE REFRESH since I am using ANALYTICAL FUNCTIONS, I cannot use FAST
REFRESH option.

Can you guys throw me some light? Guide me wht are all the parameters that I
should look into.
Wht type of indexes will do the trick, How to re-write the sql in a much
betterway.

This is test data only. The actual size of ACCOUNT_MONTHLY_FACT is 520 millions
rows.

I got 70 more mv to tune. (I got to do this before March 3)

I need your guidance. How to start and proceed from here...
My guts says I am in trouble. I need you guys to help me.

CREATE MATERIALIZED VIEW MV_CREDIT_MEASURES AS
SELECT
B.PORTFOLIO_GROUP_ID,
A.RISK_LEVEL_ID,
A.SUB_PORTFOLIO_ID,
A.SECURITIZATION_IND,
A.BRAND_ID,
(CASE WHEN GROUPING_ID
(B.PORTFOLIO_GROUP_ID,
A.RISK_LEVEL_ID,
A.SUB_PORTFOLIO_ID,
A.SECURITIZATION_IND,
A.BRAND_ID,
TRIM(D.CAL_QTR_STR),
TRIM(C.CAL_MONTH_STR),
A.MONTHS_ON_BOOK) = 0 THEN
TRIM(D.CAL_QTR_STR)
WHEN GROUPING_ID
(B.PORTFOLIO_GROUP_ID,
A.RISK_LEVEL_ID,
A.SUB_PORTFOLIO_ID,
A.SECURITIZATION_IND,
A.BRAND_ID,
TRIM(D.CAL_YEAR_STR),
TRIM(C.CAL_MONTH_STR),
A.MONTHS_ON_BOOK) = 0 THEN
TRIM(D.CAL_YEAR_STR)
ELSE
TRIM(D.CAL_MONTH_STR)
END) VINTAGE_PERIOD,
TRIM(C.CAL_MONTH_STR) REPORT_PERIOD,
A.MONTHS_ON_BOOK,
SUM(INT_AND_FEES) INT_AND_FEES,
SUM(OPENING_BAL) OPENING_BAL,
SUM(CLOSING_BAL) CLOSING_BAL,
SUM(NUM_ACCOUNTS_OPEN) NUM_ACCOUNTS_OPEN,
SUM(NUM_ACCOUNTS_VOLUNTARY_ATTR) NUM_ACCOUNTS_VOLUNTARY_ATTR,
SUM(NUM_ACCOUNTS_INVOLUNTARY_ATTR) NUM_ACCOUNTS_INVOLUNTARY_ATTR,
SUM(PRINCIPAL_CHGOFF) PRINCIPAL_CHGOFF,
SUM(GROSS_CHGOFF) GROSS_CHGOFF,
SUM(NET_CHGOFF) NET_CHGOFF,
SUM(TWO_PLUS_CLOSING_BAL) TWO_PLUS_CLOSING_BAL,
SUM(THREE_PLUS_CLOSING_BAL) THREE_PLUS_CLOSING_BAL,
SUM(FOUR_PLUS_CLOSING_BAL) FOUR_PLUS_CLOSING_BAL
FROM (SELECT
A.RISK_LEVEL_ID,
A.SUB_PORTFOLIO_ID,
A.PORTFOLIO_ID,
A.SECURITIZATION_IND,
A.BRAND_ID,
A.MONTHS_ON_BOOK,
A.VINTAGE_DATE,
A.MONTH_END_DATE,
(CASE WHEN A.DELINQUENCY_STATUS_ID <> 10 THEN
NVL(A.PURCHASE_FIN_CHG, 0) + NVL(A.CASH_ADV_FIN_CHG,0) + NVL(A.ANNUAL_FEE, 0) +
NVL(A.FRONT_END_FEE ,0) + NVL(A.INSURANCE_FEE, 0) + NVL(A.INTERCHANGE_FEE, 0) +
NVL(A.LATE_FEE, 0) + NVL(A.OTHER_FEE, 0) + NVL(A.OVERLIMIT_FEE, 0)
ELSE 0
END) INT_AND_FEES,
LAG(CASE WHEN A.DELINQUENCY_STATUS_ID <> 10 THEN NVL(A.MTD_CLOSING_BAL,0) ELSE 0
END)
OVER (PARTITION BY
A.ACCOUNT_ID
ORDER BY
A.MONTH_END_DATE)
OPENING_BAL,
(CASE WHEN A.DELINQUENCY_STATUS_ID <> 10 THEN
NVL(A.MTD_CLOSING_BAL, 0)
ELSE 0
END) CLOSING_BAL,
(CASE WHEN A.DELINQUENCY_STATUS_ID <> 10 THEN
1
ELSE 0
END) NUM_ACCOUNTS_OPEN,
(CASE WHEN A.ACCOUNT_STATUS_ID = 1 THEN
1
ELSE 0
END) NUM_ACCOUNTS_VOLUNTARY_ATTR,
(CASE WHEN A.ACCOUNT_STATUS_ID = 2 THEN
1
ELSE 0
END) NUM_ACCOUNTS_INVOLUNTARY_ATTR,
(CASE WHEN A.DELINQUENCY_STATUS_ID = 10 THEN
NVL(A.PURCHASE_CHGOFF, 0) + NVL(A.CASH_ADV_CHGOFF, 0)
ELSE 0
END) PRINCIPAL_CHGOFF,
(CASE WHEN A.DELINQUENCY_STATUS_ID = 10 THEN
NVL(A.TOTAL_CHGOFF, 0)
ELSE 0
END) GROSS_CHGOFF,
(CASE WHEN A.DELINQUENCY_STATUS_ID = 10 THEN
NVL(A.TOTAL_CHGOFF, 0) - NVL(A.TOTAL_RECOVERY_AMT, 0) -
(NVL(A.PURCHASE_FIN_CHG_CHGOFF, 0) + NVL(A.CASH_ADV_FIN_CHG_CHGOFF, 0) +
NVL(A.MISC_CHGOFF, 0))
ELSE 0
END) NET_CHGOFF,
(CASE WHEN A.DELINQUENCY_STATUS_ID NOT IN (0, 1, 10) THEN
NVL(A.MTD_CLOSING_BAL, 0)
ELSE 0
END) TWO_PLUS_CLOSING_BAL,
(CASE WHEN A.DELINQUENCY_STATUS_ID NOT IN (0, 1, 2, 10) THEN
NVL(A.MTD_CLOSING_BAL, 0)
ELSE 0
END) THREE_PLUS_CLOSING_BAL,
(CASE WHEN A.DELINQUENCY_STATUS_ID NOT IN (0, 1, 2, 3, 10) THEN
NVL(A.MTD_CLOSING_BAL, 0)
ELSE 0
END) FOUR_PLUS_CLOSING_BAL
FROM ACCOUNT_MONTHLY_FACT A) A,
PORTFOLIOS_DIM B,
CCRT_CALENDAR_DIM C,
CCRT_CALENDAR_DIM D
WHERE A.PORTFOLIO_ID = B.PORTFOLIO_ID
AND A.MONTH_END_DATE = C.CAL_DAY_DT
AND A.VINTAGE_DATE = D.CAL_DAY_DT
GROUP BY GROUPING SETS (
(B.PORTFOLIO_GROUP_ID,
A.RISK_LEVEL_ID,
A.SUB_PORTFOLIO_ID,
A.SECURITIZATION_IND,
A.BRAND_ID,
TRIM(D.CAL_MONTH_STR),
TRIM(C.CAL_MONTH_STR),
A.MONTHS_ON_BOOK),
(B.PORTFOLIO_GROUP_ID,
A.RISK_LEVEL_ID,
A.SUB_PORTFOLIO_ID,
A.SECURITIZATION_IND,
A.BRAND_ID,
TRIM(D.CAL_QTR_STR),
TRIM(C.CAL_MONTH_STR),
A.MONTHS_ON_BOOK),
(B.PORTFOLIO_GROUP_ID,
A.RISK_LEVEL_ID,
A.SUB_PORTFOLIO_ID,
A.SECURITIZATION_IND,
A.BRAND_ID,
TRIM(D.CAL_YEAR_STR),
TRIM(C.CAL_MONTH_STR),
A.MONTHS_ON_BOOK)
);
Rating: 3
Does something else matter redo? February 22, 2004
Reviewer: steven from BeiJing . China
but it seems that the redo after nologging table is still mass

SQL> select count(*) from t;

  COUNT(*)
----------
     28992

SQL> create materialized view tmv as select * from t;

Materialized view created.

SQL> column value new_val V
SQL> set verify off
SQL> select a.name,b.value from v$statname a ,v$mystat b where a.statistic#=b.st
atistic# and a.name='redo size';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
redo size                                                            590228

SQL> exec dbms_mview.refresh( 'TMV', 'C' );

PL/SQL procedure successfully completed.

SQL> select a.name,b.value,TO_char(b.value-&v,'999,999,999,999') diff from v$sta
tname a ,v$mystat b where a.statistic#=b.statistic# and a.name='redo size';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
DIFF
--------------------------------------------------------------------------------

redo size                                                           2366496
       1,776,268

SQL> alter table tmv nologging;

Table altered.

SQL> exec dbms_mview.refresh( 'TMV', 'C' );

PL/SQL procedure successfully completed.

SQL> select a.name,b.value,TO_char(b.value-&v,'999,999,999,999') diff from v$sta
tname a ,v$mystat b where a.statistic#=b.statistic# and a.name='redo size';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
DIFF
--------------------------------------------------------------------------------

redo size                                                           4148956
       1,782,460

SQL> alter table tmv nologging;

table altered

SQL> exec dbms_mview.refresh( 'TMV', 'C' );

PL/SQL procedure successfully completed.

SQL> select a.name,b.value,TO_char(b.value-&v,'999,999,999,999') diff from v$sta
tname a ,v$mystat b where a.statistic#=b.statistic# and a.name='redo size';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
DIFF
--------------------------------------------------------------------------------

redo size                                                           5899420
       1,750,464
Followup:
1.7 meg "massive"???

try with a big result set like I did please.  there is some overhead that is
just going to be there.  show us what happens when you do a non-trivial workload
like I did (and use column name format a15, etc to make the output "fit", its
lots easier to read that way)
Rating: 2
nologging still does not effect February 23, 2004
Reviewer: steven from BeiJing. China
Thank you very much for reply and sorry for obscurity format.

i load more data and repeat again.  but it still could see redo increased
corresponding with amount of data;confused about it.do I make something wrong?
sorry for disturb again.

SQL> select count(*) from big_table;

  COUNT(*)
----------
    463872
SQL> create materialized view big_table_mv as select * from
  2  big_table;

Materialized view created.

SQL> col name format a15
SQL> column value new_val V
SQL> set verify off
SQL> select a.name,b.value from v$statname a,v$mystat b where a.statistic#=b.sta
tistic# and a.name='redo size';

NAME                 VALUE
--------------- ----------
redo size          8375876

SQL> exec dbms_mview.refresh( 'BIG_TABLE_MV', 'C' );

PL/SQL procedure successfully completed.
SQL> col diff format a20

SQL> select a.name,b.value,to_char(b.value-&V,'999,999,999,999') diff from v$sta
tname a,v$mystat b where a.statistic#=b.statistic# and a.name='redo size';

NAME                 VALUE DIFF
--------------- ---------- --------------------
redo size         36881836  28,505,960

 SQL> alter table big_table_mv nologging;

Table altered.

SQL> exec dbms_mview.refresh( 'BIG_TABLE_MV', 'C' );

PL/SQL procedure successfully completed.

SQL> select a.name,b.value,to_char(b.value-&V,'999,999,999,999') diff from v$sta
tname a,v$mystat b where a.statistic#=b.statistic# and a.name='redo size';

NAME                 VALUE DIFF
--------------- ---------- --------------------
redo size         65400332       28,518,496

LOAD MORE ....

SQL> select count(*)from big_table;

  COUNT(*)
----------
    927744

SQL> drop materialized view big_table_mv;

Materialized view dropped.

SQL> create materialized view big_table_mv as select * from
  2  big_table;

Materialized view created.

SQL>
SQL> col name format a15
SQL> column value new_val V
SQL> set verify off
SQL> select a.name,b.value from v$statname a,v$mystat b where a.statistic#=b.sta
tistic# and a.name='redo size';

NAME                 VALUE
--------------- ----------
redo size        320433004

SQL> exec dbms_mview.refresh( 'BIG_TABLE_MV', 'C' );

PL/SQL procedure successfully completed.

SQL>
SQL> select a.name,b.value,to_char(b.value-&V,'999,999,999,999') diff from v$sta
tname a,v$mystat b where a.statistic#=b.statistic# and a.name='redo size';

NAME                 VALUE DIFF
--------------- ---------- --------------------
redo size        377704252       57,271,248

SQL> alter table big_table_mv nologging;

Table altered.

SQL> exec dbms_mview.refresh( 'BIG_TABLE_MV', 'C' );

PL/SQL procedure successfully completed.

SQL>
SQL> select a.name,b.value,to_char(b.value-&V,'999,999,999,999') diff from v$sta
tname a,v$mystat b where a.statistic#=b.statistic# and a.name='redo size';

NAME                 VALUE DIFF
--------------- ---------- --------------------
redo size        434843812       57,139,560

does workload is trivial?...
Followup:
select force_logging from v$database;

what is the output of that.
Rating: 2
it's not for standby February 23, 2004
Reviewer: steven from BeiJing China
SQL> col force_logging format a20
SQL> select force_logging from v$database;

FORCE_LOGGING
--------------------
NO

 does some init parameter matter this??
Followup:
use sql_trace=true and verify that the refresh is being done via TRUNCATE +
INSERT /*+ APPEND */

what version are we looking at here on what platform
Rating: 3
feel embarrass February 25, 2004
Reviewer: steven
after desc big_table,i discover that there is pk on it.THe Index make mass redo
when /*+ append */. sorry for disturb you  because of my carelessness.

SQL> select count(*)from big_table;

  COUNT(*)
----------
    927744

SQL> select index_name,table_name from user_indexes where table_name='BIG_TABLE_
MV';

INDEX_NAME                     TABLE_NAME
------------------------------ ------------------------
SYS_C008606                    BIG_TABLE_MV

SQL> alter table  BIG_TABLE_MV drop primary key;

SQL> select index_name,table_name from user_indexes where
table_name='BIG_TABLE_MV';

no rows selected

SQL> alter table  BIG_TABLE_MV nologging;

table altered

SQL> column value new_val V
SQL> set verify off
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'
  5  /

NAME            VALUE
-----------------------
redo size       16512

SQL> exec dbms_mview.refresh( 'BIG_TABLE_MV', 'C' );

SQL>  select a.name, b.value, to_char( b.value-&V,
  2  '999,999,999,999' ) diff
  3   from v$statname a, v$mystat b
  4   where a.statistic# = b.statistic#
  5   and a.name = 'redo size'
  6  /

NAME             VALUE        DIFF
-----------------------------------

redo size        326452       309,940

You are right!Thank you for patience;
I'll never make stupid again
February 24, 2004 balasubramanian -- Thanks for the question regarding "Recovery Manager Catalog - different versions of Database", version 8.0.6/8.1.5
You Asked
Dear Tom,
 Sorry for asking again about RMAN.

 I have 2 questions:

1.Can we create a catalog for a 8i version database in another
oracle 8.0.x or 7.3.x database ? i-e I have a 8.1.5 database and
7.3.2 database. I want to backup 8.1.5 database using RMAN. Can
I use Oracle 7.3 database to create recovery catalog for oracle
8i? (Is there any restrictions about oracle versions ?)

2.Suppose my datafile is of size 2 GB. Can I break this 2GB file
in the backup pieces as (1GB+1GB)and store using RMAN in the
backup form? (Since oracle8i supports exporting into multiple
files, is it possible to break-up big file into small pieces
using RMAN ?
and we said...
RMAN's recovery catalog should be in the release of the database
that RMAN ships with.  Since RMAN did not exist with 7.3 -- you
would not be creating an RMAN catalog in 7.3.  If you are using
the 8.0 RMAN, it would be in an 8.0 database.  If you are using
the 8i RMAN, it would be in an 8.1 instance.  This is the
supported configuration.  Part of recovery manager is a DBMS_*
package and this package is stored only in the appropriate
versioned database.

for #2, not that I am aware of.  It does image copies.  You can
use incremental backups which will result in smaller backup sets
and files but eventually you need do a full.
Review & Followup

Rating: 5
catalog February 18, 2004
Reviewer: reader
(1) I was under the impression that if i created a catalog in 8i then, i could
manage version 8 databases using 8i catalog. Is it possible? Along the same
line, If i created a catalog in 9i database can i store rman information for
prior 9i databases in 9i catalog? Thanks.

(2) Can I use 9i RMAN executable to backup 8i and 8 databases? Thanks.
Rating: 3
catalog February 23, 2004
Reviewer: David
I wanted to ask questions in line with the ones asked above. Could you answer
those questions? Thanks.
Followup:
rman is backwards compatible, yes.
February 24, 2004 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.
Rating: 5
Unused Indexes in oracle 8.1.7 February 23, 2004
Reviewer: Wor from USA
Hi Tom,
I am using oracle 8.1.7 , windows 2000
Over the time we have added alot of indexes and I guess that some of the indexes
may not be getting used at all.

Is there a way in ver 8.1.7 to identify the unused indexes, so that I can get
rid of them.

Wor
Followup:
do you have my book "expert one on one Oracle".  in there i suggest a method
using query plan stability.
Rating: 4
Index usage throgh db link February 23, 2004
Reviewer: Lakshminarasimhan R from Dubai
Tom

how to ensure whether index is used if a query contains a table from remote
database.

I hv tried to traceout explain plan but not giving any clue

QL> SELECT TRAVEL_MONTH,PROCESSING_MONTH,AGENCY,CHECK_DIGIT,
 2
ONLINE_ORG,ONLINE_DEST,ORIGIN,DEST,COMPARTMENT,ONLINE_CARRIER,PAX_COUNT,EK_PRESEN
T,
 3
ONLINE_CLASS,POS,CRS_CODE,DIRECT_FLAG,sp.RUN_INDICATOR,sp.OLD_CUR_NEW,sp.data_typ
e,
 4
sp.host_al_code,sp.primary_seq_id,sp.secondary_seq_id,sp.fin_id,sp.info_cat,c.MID
T_DATE_REQ
 5       FROM syn_mkt_share_aln_source src,
 6       TDC_SESSION_PARAMETERS sp,
 7       v_midt_req_period c
 8       WHERE    src.processing_month >=
DECODE(sp.RUN_INDICATOR,'N',src.processing_month,sp.DATA_DATE_FROM)
 9       AND src.processing_month <=
DECODE(sp.RUN_INDICATOR,'N',src.processing_month,sp.DATA_DATE_TO)
10       AND sp.info_cat = 'MKT_SHARE_ALN';

xecution Plan
---------------------------------------------------------
  0      SELECT STATEMENT Optimizer=RULE
  1    0   NESTED LOOPS
  2    1     NESTED LOOPS
  3    2       TABLE ACCESS (FULL) OF 'DUAL'
  4    2       TABLE ACCESS (FULL) OF 'TDC_SESSION_PARAMETERS'
  5    1     REMOTE*                                                  INST_INS
                                                                      IGHT_ADH
                                                                      _LINK

  5 SERIAL_FROM_REMOTE            SELECT "TRAVEL_MONTH","PROCESSING_MONTH","AG
                                  ENCY","CHECK_DIGIT","ONLINE_ORG","ONLINE_DES
                                  T","ORIGIN","DEST","COMPARTMENT","ONLINE_CAR
                                  RIER","PAX_COUNT","EK_PRESENT","ONLINE_CLASS
                                  ","POS","CRS_CODE","DIRECT_FLAG" FROM "MIDT_
                                  MAIN_YTD_TDC" "SRC" WHERE "PROCESSING_MONTH"
                                  <=DECODE(:1,'N',"PROCESSING_MONTH",:2) AND "
                                  PROCESSING_MONTH">=DECODE(:3,'N',"PROCESSING
                                  _MONTH",:4)

Here, table Midt_main_ytd_tdc lying in Remote db and accessing thr db link
(synonym).

can you throw some light on this.
Followup:
touch the database link (select * from dual@remote)

log directly into remote from another session and use dbms_system to
set_sql_trace_in_session for that new session you just created.

run your query and exit.

then use tkprof.

(or cut and paste that query above into sqlplus on remote and use set autotrace
traceonly explain).

Looking at the query -- I would say pretty close to 0% chance that an index is
involved.  Look at the predicate:

WHERE "PROCESSING_MONTH"
                                  <=DECODE(:1,'N',"PROCESSING_MONTH",:2) AND "
                                  PROCESSING_MONTH">=DECODE(:3,'N',"PROCESSING
                                  _MONTH",:4)

In this case, I would advise (since that sp table looks just to be a parameter
table) that you use a ref cursor and procedural logic to decide on "query a" or
"query b" and avoid using the function on the database column as that pretty
much "precludes indexes"
February 24, 2004 guoliang -- Thanks for the question regarding "join orders related to outer join", version 8.1.6
You Asked
In oracle reference Oracle8i Concepts page 24-8, there is a paragraph:

For join statements with outer join conditions, the table with the outer join
operator must come after the other table in the condition in the join order. The
optimizer does not consider join orders that violate this rule.

I was puzzled by this. I tested the stmts:

SELECT /*+ordered*/ E.ENAME
  ,D.DNAME
  ,B.COMM
FROM EMP E
  ,BONUS B
  ,DEPT D
WHERE E.ENAME = B.ENAME(+)
  AND E.DEPTNO = D.DEPTNO

the explain plan said "( EMP hash TO BONUS ) hast TO DEPT"

SELECT E.ENAME
  ,D.DNAME
  ,B.COMM
FROM EMP E
  ,BONUS B
  ,DEPT D
WHERE E.ENAME = B.ENAME(+)
  AND E.DEPTNO = D.DEPTNO

the explain plan said "( DEPT hash TO EMP ) hash TO BONUS"

It seemed the optimizer does consider join orders even the stmt violate the
above rule. Where am I wrong? Could you give a example to explain the above rule?
and we said...
No, it did not necessarily consider join order here, it is just the difference
between the RBO and CBO and the way it evaluated that query.

In other words, it was a coincidence.

Just using CBO gets the same table results as your ordered:

tkyte@TKYTE816> select /*+ first_rows */ E.ENAME
  2    ,D.DNAME
  3    ,B.COMM
  4  FROM EMP E
  5    ,BONUS B
  6    ,DEPT D
  7  WHERE E.ENAME = B.ENAME(+)
  8    AND E.DEPTNO = D.DEPTNO
  9  /

14 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=85 Card=67 Byt
   1    0   NESTED LOOPS (Cost=85 Card=67 Bytes=4154)
   2    1     HASH JOIN (OUTER) (Cost=3 Card=82 Bytes=3280)
   3    2       TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=82 Bytes=1640)
   4    2       TABLE ACCESS (FULL) OF 'BONUS' (Cost=1 Card=82 Bytes=1640)
   5    1     TABLE ACCESS (BY INDEX ROWID) OF 'DEPT' (Cost=1 Card=82 Bytes
   6    5       INDEX (UNIQUE SCAN) OF 'DEPT_PK' (UNIQUE)

  1  select /*+ all_rows */ E.ENAME
  2    ,D.DNAME
  3    ,B.COMM
  4  FROM EMP E
  5    ,BONUS B
  6    ,DEPT D
  7  WHERE E.ENAME = B.ENAME(+)
  8*   AND E.DEPTNO = D.DEPTNO
tkyte@TKYTE816> /

14 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: ALL_ROWS (Cost=5 Card=67 Bytes=4154)
   1    0   HASH JOIN (Cost=5 Card=67 Bytes=4154)
   2    1     HASH JOIN (OUTER) (Cost=3 Card=82 Bytes=3280)
   3    2       TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=82 Bytes=1640)
   4    2       TABLE ACCESS (FULL) OF 'BONUS' (Cost=1 Card=82 Bytes=1640)
   5    1     TABLE ACCESS (FULL) OF 'DEPT' (Cost=1 Card=82 Bytes=1804)

followup to comment one below

The quote from the manual is:

"the table with the outer join operator must come after the other table in the
condition in the join order"

that is true and was observed in your examples.  BONUS is always joined to FROM
EMP (after EMP).  The optimizer did what it could to satisfy you without
breaking the results.  Consider:

scott@ORA8I.WORLD> SELECT /*+ ORDERED */ E.ENAME
  2    ,D.DNAME
  3    ,B.COMM
  4  FROM
  5     BONUS B ,
  6     EMP E,
  7     DEPT D
  8  WHERE E.ENAME = B.ENAME(+)
  9    AND E.DEPTNO = D.DEPTNO
 10  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=11 Card=55 Bytes=3795)
   1    0   MERGE JOIN (Cost=11 Card=55 Bytes=3795)
   2    1     SORT (JOIN) (Cost=8 Card=67 Bytes=3149)
   3    2       MERGE JOIN (OUTER) (Cost=6 Card=67 Bytes=3149)
   4    3         SORT (JOIN) (Cost=3 Card=82 Bytes=2214)
   5    4           TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=82 Bytes=2214)
   6    3         SORT (JOIN) (Cost=3 Card=82 Bytes=1640)
   7    6           TABLE ACCESS (FULL) OF 'BONUS' (Cost=1 Card=82 Bytes=1640)
   8    1     SORT (JOIN) (Cost=3 Card=82 Bytes=1804)
   9    8       TABLE ACCESS (FULL) OF 'DEPT' (Cost=1 Card=82 Bytes=1804)

scott@ORA8I.WORLD> SELECT /*+ ORDERED */ E.ENAME
  2    ,D.DNAME
  3    ,B.COMM
  4  FROM
  5     EMP E,
  6     BONUS B ,
  7     DEPT D
  8  WHERE E.ENAME = B.ENAME(+)
  9    AND E.DEPTNO = D.DEPTNO
 10  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=11 Card=55 Bytes=3795)
   1    0   MERGE JOIN (Cost=11 Card=55 Bytes=3795)
   2    1     SORT (JOIN) (Cost=8 Card=67 Bytes=3149)
   3    2       MERGE JOIN (OUTER) (Cost=6 Card=67 Bytes=3149)
   4    3         SORT (JOIN) (Cost=3 Card=82 Bytes=2214)
   5    4           TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=82 Bytes=2214)
   6    3         SORT (JOIN) (Cost=3 Card=82 Bytes=1640)
   7    6           TABLE ACCESS (FULL) OF 'BONUS' (Cost=1 Card=82 Bytes=1640)
   8    1     SORT (JOIN) (Cost=3 Card=82 Bytes=1804)
   9    8       TABLE ACCESS (FULL) OF 'DEPT' (Cost=1 Card=82 Bytes=1804)

In both cases, EMP is done and than BONUS is joined to.  It did never go from
BONUS to EMP -- always EMP to BONUS.  The ordered hint, when it could not be
obeyed as the answer would be wrong, was ignored.
Review & Followup

Rating: 3
I am still puzzled June 07, 2001
Reviewer: lgliang from China
Thanks for your quick answer. and I am sorry I am still confused. I have
analyzed these 3 tables, and the two stmts above I tested all used CBO
optimizer, so I can not understand why you said 'it is just the difference
between the RBO and CBO and the way it evaluated that query.'. I assume you
haven't anaylze tables,haven't you? And my real question is whether we should
pay attention to the order of tables related to outer join in there where
clause, as oracle reference document said.
Rating: 5
writing query January 02, 2004
Reviewer: A reader
Hi

I have a question if we work with HR demo schema and I want to retrieve the
employee´s current job and job position he has been I use this query

select a.employee_id, a.first_name, a.last_name,
       b.start_date,
       c.job_title,
       d.department_name
  from employees a,
       job_history b,
       jobs c,
       departments d
 where a.employee_id = b.employee_id(+)
   and b.job_id = c.job_id(+)
   and a.department_id = d.department_id
   and a.employee_id between 100 and 120;

however I only get the old job title, how can we get the current job title as
well?
Followup:
one way would be to add a column:

select ...., (select job_title from jobs where job_id = a.job_id) current_title,
..

another would be to join yet again to jobs

 where a.job_id = join_to_jobs_again.job_id

and add join_to_jobs_again.job_title to the select list
Rating: 5
join again with jobs? January 02, 2004
Reviewer: A reader
Hi

do you mean add another jobs table in from clause? I tried the following but it
returns 4 rows instead of 6!

this returns 4 rows
select a.employee_id, a.first_name,
       b.start_date,
       c.job_title,
       e.job_title
  from employees a,
       job_history b,
       jobs c,
       jobs e
 where a.employee_id = b.employee_id(+)
   and b.job_id = c.job_id(+)
   and a.job_id = e.job_id(+)
   and a.employee_id between 100 and 102;

whereas following query returns 6 rows which is correct!

select a.employee_id, a.first_name, a.last_name, NULL curr_job_id,
       to_char(b.start_date) start_date, b.job_id hist_job_id, c.job_title
  from hr.employees a,
       hr.job_history b,
       hr.jobs c
 where a.employee_id = b.employee_id
   and b.job_id = c.job_id
   and a.employee_id between 100 and 102
union
select a.employee_id, a.first_name, a.last_name, a.job_id curr_job_id,
       'Currently', NULL, b.job_title
  from hr.employees a,
       hr.jobs b
 where a.job_id = b.job_id
   and a.employee_id between 100 and 102;
Followup:
mine includes the current job title in each and every record -- i said to add
join_to_jobs_again.job_title to the select list -- that is the "current one"

hr@ORA9IR2> l
  1  select a.employee_id, a.first_name,
  2         b.start_date,
  3         c.job_title,
  4         e.job_title CURRENT_TITLE
  5    from employees a,
  6         job_history b,
  7         jobs c,
  8         jobs e
  9   where a.employee_id = b.employee_id(+)
 10     and b.job_id = c.job_id(+)
 11     and a.job_id = e.job_id
 12     and a.employee_id between 100 and 102
 13*  order by a.employee_id, b.start_date
hr@ORA9IR2> /

EMPLOYEE_ID FIRST_NA START_DAT JOB_TITLE            CURRENT_TITLE
----------- -------- --------- -------------------- --------------------
        100 Steven                                  President
        101 Neena    21-SEP-89 Public Accountant    Administration Vice
                                                    President

        101 Neena    28-OCT-93 Accounting Manager   Administration Vice
                                                    President

        102 Lex      13-JAN-93 Programmer           Administration Vice
                                                    President

if you want to "make up" a row for the employee, you would lose the outer joins
and use a union all.

you didn't say you wanted "6" records -- you just asked for the current job
title.  I got you that as a column in each of the existing 4 records
Rating: 5
ahh! I am very sorry January 02, 2004
Reviewer: A reader
Sorry I mixed up, I wanted this result

hr@LNX920>select a.employee_id, a.first_name, NULL curr_job_title,
  2         to_char(b.start_date) start_date, c.job_title
  3    from hr.employees a,
  4         hr.job_history b,
  5         hr.jobs c
  6   where a.employee_id = b.employee_id
  7     and b.job_id = c.job_id
  8     and a.employee_id between 100 and 102
  9  union
 10  select a.employee_id, a.first_name, b.job_title,
 11         'Currently', b.job_title
 12    from hr.employees a,
 13         hr.jobs b
 14   where a.job_id = b.job_id
 15     and a.employee_id between 100 and 102;

EMPLOYEE_ID FIRST_NAME           CURR_JOB_TITLE                      START_DATE
      JOB_TITLE
----------- -------------------- -----------------------------------
----------------- -----------------------------------
        100 Steven               President                           Currently
      President
        101 Neena                Administration Vice President       Currently
      Administration Vice President
        101 Neena                                                    19890921
00:00:00 Public Accountant
        101 Neena                                                    19931028
00:00:00 Accounting Manager
        102 Lex                  Administration Vice President       Currently
      Administration Vice President
        102 Lex                                                      19930113
00:00:00 Programmer

6 rows selected.

I used UNION but I am not sure if this can be done in more efficient way. Do you
know?

Thank you a lot
Followup:
UNION ALL

is more efficient.
Rating: 3
OK February 24, 2004
Reviewer: siva from Bangalore,India
Dear Sir,
Any simple example for left join and anti join?Please do
reply.
Thanks in advance.
Followup:
left join, right join -- just flip flop the table order

from dept left join emp on .......

from emp right join dept on ......

are the same.

anti join is "not in"

select * from dept where deptno NOT IN ( select deptno from emp )

that'll typically do an anti-join, which you can do manually (don't, it isn't as
effective) like this:

from deptno left join emp on ( emp.deptno = dept.deptno )
where emp.deptno is null;
February 24, 2004 dileep -- Thanks for the question regarding "How to use ' From clasue query ' for a data block as a datasource. ", version forms (6.0)
You Asked
I want to use 'From Clause Query' as a datasource for a data
block.
Please illustrate with code.
and we said...
Here is a support note on this subject:

PURPOSE
-------

The purpose of this note is to demonstrate the use of dynamic
From Clause Query with Forms variables.

SCOPE & APPLICATION
-------------------

This article is intended for all Developers using Oracle Forms
6.0.

Using From Clause Query dynamically with Forms variables.
---------------------------------------------------------

In Forms Builder build a block manually.  In this example we use
a block that contains 4 items.  Name the items EMPNO, ENAME, JOB
and DEPTNO.  We can call the items whatever we want, but
remember to use alias in the select statement with the same name
as the items in the block.  We will come back to this later in
the article.

Let us call BLOCK1 the block we have just created.  In the
Property Palette of BLOCK1 the following properties must be set:

   Database Data Block        =         YES
   Query Allowed              =         YES
   Query Data Source Type     =         FROM clause query
   Query Data Source Name     =         SELECT '1','2','3','4'
FROM DUAL

The select statement in Query Data Source Name is necessary,
otherwise we get a FRM-41380 error message.  It looks like Forms
is expecting to having something different from null in the
Query Data Source Name-property.  The number of items
in BLOCK1 should match the number of columns in the select
statement, i.e. if we had a block with 10 items then the select
statement should look like this:

SELECT '1',.....'10' FROM DUAL.

Next we check that the items in BLOCK1 match the properties of
the columns in the select statement we are interested in
executing.  This means that the items in the block should have
the same type and length of the data from the database.

Now we build another block.  This time a control block to
provide a dynamical variable.  We call this block BLOCK3.  In
BLOCK3 we create an item: x_deptno.

We create a When-New-Block-Instance trigger in BLOCK1.  The code
is as follows:

DECLARE
 q varchar2(1000);
BEGIN
 q := '(SELECT empno, ename, job, deptno FROM emp WHERE deptno =
'
      ||:BLOCK3.x_deptno||')';
 CLEAR_BLOCK;
 SET_BLOCK_PROPERTY('BLOCK1', QUERY_DATA_SOURCE_NAME, q);
 EXECUTE_QUERY;
END;

Note the select statement is in parenthesis.
The value of BLOCK3.x_deptno is assigned dynamically and
compared to deptno in the select statement.  Note that the
select statement in the code above could have been:
'(SELECT empno AS e_num, ename AS e_name, job AS e_pos, deptno
AS e_dno FROM emp
...........
Then the items in BLOCK1 should be named like this:
E_NUM, E_NAME, E_POS and E_DNO.
Review & Followup

Rating: 3
I have a little different case, you may have the ideal to fix it. March 23, 2002
Reviewer: A reader from Atlanta
Form6.0.8, oracle8.1.6

I am relatively new to the forms.
The automatic query refresh is not working for my application, tested on
client/server and the
Web.

In order to refreshing, I have to disconnect the form application from the
database and
reconnect it to.
I used the SQLPLUS to verify the user does not need reconnect inorder to view
the data
updated.

Settings in LOVs

automatic refresh =yes
filter before display = no
automatic display=no
automatic select =no
automatic skip =no
automatic position =no

settings in the record groups

record group fetch size =20
record group type =query
record group query =SELECT distinct(GL_CODE_COMBINATIONS.SEGMENT18) FROM
GL.GL_CODE_COMBINATIONS order by 1

I have created a small form for just test the auto-refresh, and it works by the
same settings. The problem for me is I could not find out what else other than
the settings of LOVs and the Record Groups .
Rating: 3
from clause query February 21, 2004
Reviewer: omer from Pakistan
Hi!
I am using from claue query as a datablock source property.
my query is
select empno ,to_char(name) name from emp
When i try to update any record error come
frm-40654 record has been updated by another user. requery to see change
there is no user except me.
i think the problem is of using To_CHAR function but
I have to use to_char function
is there any other option like procedure or else
plz help
regards
Followup:
why would you do that?
Rating: 2
from clause February 24, 2004
Reviewer: omer from Pakistan
Hi!
I am using to_char function because
name is nvarchar2 column
desc emp
----------
empno number
name  nvarchar2(4000)
there is no nvarchar2 datatype property in form. (forms 6)
i am using char datatype for name column in form.
if i do not use to_char function then name column remain empty and only empno is
retreived.
regards
Followup:
you'll get this because the data in your form does not match the data in the
database (you turned the nvarchar2 into a varchar2, they are different)

You'll have to program your own "on-lock" trigger -- but I would suggest you
first goto otn.oracle.com -> discussion forums and get into the developer
discussion forum and ask them for advice -- I haven't touched forms since 1995
Rating: 5
Couple of things... February 24, 2004
Reviewer: Connor from Aus
Whilst playing with this on an app (6i/9i) at work, we found that

a) its wise to wrap the SQL in parentheses - otherwise updateable blocks have
errors in some places

b) enforce primary key had some dramas - can't remember the specifics

Other than that, ran very nicely indeed.
February 24, 2004 -- 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.-
Rating: 4
Same Problem February 04, 2004
Reviewer: A reader
Hi, Tom,

Here is what I did(under SQLPLUS in 9i, DB: 9i):
create or replace type rec_1 as object(item  VARCHAR2(80),
                                       orno  NUMBER)
/
create or replace type tab_1 as table of rec_1
/
And then in PL/SQL:
DECLARE
  tb tab_1 := tab_1(NULL,NULL);
  ln_cnt   := 0;
BEGIN
  tb.EXTEND(1, 1);
  tb(ln_cnt+1).item:= 'book';
  tb(ln_cnt+1).orno := 1000;
END;
/
After execute in the run time, I got the error:
"ORA-06530: Reference to uninitialized composite".
I think I alreay initialized it, why still problem? WOrkaround?

THanks a milion,
Followup:
Your problem is that you allocated the "table" tb to have 2 slots, you then
added more slots to them -- but the OBJECTS were atomically NULL (not
initialized)

ops$tkyte@ORA10G> DECLARE
  2    tb tab_1 := tab_1(NULL,NULL);
  3    ln_cnt   number := 0;
  4  BEGIN
  5    tb(1) := rec_1( 'book', 1000 );
  6    tb(2) := rec_1( 'book', 1000 );
  7  END;
  8  /

PL/SQL procedure successfully completed.

will work -- you set tb(1) and tb(2) -- not tb(1).item.  After you set tb(1) to
an object (not null) you can reference tb(1).item -- but not until you do that.
Rating: 4
ORA-06533 February 16, 2004
Reviewer: A reader from UK
Hi Tom,

 Do we need to initialise nested table when doing bulk collect as well???

 I am trying following code. Oracle version 9.2

CREATE OR REPLACE PROCEDURE P_MERGE_INCREMENTAL_FILES (IN_TABLE_NAME IN
VARCHAR2)
AS
  EXT_TABLE_NAME VARCHAR2(30);
  CP_TABLE_NAME  VARCHAR2(30);
  MERGE_STMNT_STR VARCHAR2(4000) DEFAULT 'MERGE INTO '||CP_TABLE_NAME||' USING
'||EXT_TABLE_NAME||' ON (';
  CONDITION_STR   VARCHAR2(1000);
  SET_STR         VARCHAR2(4000) DEFAULT ') WHEN MATCHED THEN UPDATE SET ';

  TYPE COLUMN_TABLE IS TABLE OF ALL_CONS_COLUMNS.COLUMN_NAME%TYPE;
  CONDITION_COLS_TAB COLUMN_TABLE;
  COLS_TAB COLUMN_TABLE;

  COUNT_1 INTEGER;
  COUNT_2 INTEGER;
  I       INTEGER;

  /*This cursor holds all primary key columns*/
  CURSOR CONS_COLS_CURSOR IS
  SELECT ACC.COLUMN_NAME
  FROM ALL_CONS_COLUMNS ACC, ALL_CONSTRAINTS AC
  WHERE
       AC.CONSTRAINT_TYPE = 'P'
  AND ACC.TABLE_NAME = CP_TABLE_NAME
  AND ACC.TABLE_NAME = AC.TABLE_NAME
  AND ACC.CONSTRAINT_NAME = AC.CONSTRAINT_NAME;

  /*This cursor holds all other columns*/
  CURSOR NON_CON_COLS_CURSOR IS
  SELECT UYPGBSD_COLUMN_NAME
  FROM CP_UYPGBSD
  WHERE UYPGBSD_TABLE_NAME = LTRIM(RTRIM(REPLACE(EXT_TABLE_NAME, 'DELTA_', '
')))
  MINUS
  SELECT ACC.COLUMN_NAME
  FROM ALL_CONS_COLUMNS ACC, ALL_CONSTRAINTS AC
  WHERE
      AC.CONSTRAINT_TYPE = 'P'
  AND ACC.TABLE_NAME = CP_TABLE_NAME
  AND ACC.TABLE_NAME = AC.TABLE_NAME
  AND ACC.CONSTRAINT_NAME = AC.CONSTRAINT_NAME;

BEGIN

  EXT_TABLE_NAME := UPPER(LTRIM(RTRIM(IN_TABLE_NAME)));
  CP_TABLE_NAME  := REPLACE(EXT_TABLE_NAME,'DELTA', 'CP');

/* Following cursor will load key column names in an array*/
  OPEN  CONS_COLS_CURSOR;
  FETCH CONS_COLS_CURSOR BULK COLLECT INTO CONDITION_COLS_TAB;
  CLOSE CONS_COLS_CURSOR;

/* Following cursor will load non-key column names in an array*/
  OPEN  NON_CON_COLS_CURSOR;
  FETCH NON_CON_COLS_CURSOR BULK COLLECT INTO COLS_TAB;
  CLOSE NON_CON_COLS_CURSOR;

/*Setting join conditions*/
  IF CONDITION_COLS_TAB.COUNT > 0
  THEN
  IF CONDITION_COLS_TAB.FIRST < 2
  THEN CONDITION_STR := CP_TABLE_NAME||'.'||CONDITION_COLS_TAB(1)||' =
'||EXT_TABLE_NAME||'.'||CONDITION_COLS_TAB(1);
  ELSE
  FOR I IN 2..CONDITION_COLS_TAB.LAST
  LOOP
  CONDITION_STR := ' AND '||CP_TABLE_NAME||'.'||CONDITION_COLS_TAB(I)||' =
'||EXT_TABLE_NAME||'.'||CONDITION_COLS_TAB(I);
  END LOOP;
  END IF;
  END IF;

/*Setting Update statement*/
  IF COLS_TAB.COUNT > 0
  THEN
  FOR I IN COLS_TAB.FIRST..COLS_TAB.LAST
  LOOP
  SET_STR := SET_STR||CP_TABLE_NAME||'.'||CONDITION_COLS_TAB(I)||' =
'||EXT_TABLE_NAME||'.'||CONDITION_COLS_TAB(I)||', ';
  END LOOP;
  I := LENGTH(SET_STR) - 2;
  SET_STR := SUBSTR(SET_STR,1,I);
  END IF;

/*Concatinating parts of merge statement to be executed*/
  MERGE_STMNT_STR := MERGE_STMNT_STR||CONDITION_STR||SET_STR||' WHEN NOT MATCHED
THEN INSERT INTO '||CP_TABLE_NAME||' SELECT *, SYSDATE FROM '||EXT_TABLE_NAME;

  DBMS_OUTPUT.PUT_LINE( MERGE_STMNT_STR );

  EXECUTE IMMEDIATE MERGE_STMNT_STR;

  END;
/

Thanks.
Followup:
did you get an error?  (meaning, running the code would answer your question?)

No, you do not need to initialize it as you are assigning to it an entire array,
you are not doing element by element assignments.
Rating: 3
Am getting Ora-06533 February 16, 2004
Reviewer: A reader from UK
I am getting this error when trying to run above code. I have previously used
table types in procedures ... and they are working fine. Not really
understanding why this is failing.

Thanks.

Regards,
Su
Followup:
well, i cannot run it -- missing some tables -- so, hard to say...........
Rating: 4
autogenerated collections February 24, 2004
Reviewer: Alexander from Ukraine
suppose we have:
1) create or replace type some_typ as object (
   some_atr number
   )not final;
2) create table some_tbl of some_typ;
3) create or replace coll_typ as table of number;
4) create or replace type some_typ2 of some_typ (
      other_atr coll_typ
   );
after all these steps Oracle generates NESTED TABLE with terrible name
"SYSNT1BXSYbRfdyLgMKjA+gAs0g==". Everything works correct, but I can't neither
rename nor make an index on this table.
I tryed to re-create some_typ2 without any attributes and then alter it with
INVALIDATE option and then

ALTER TABLE some_tbl UPGRADE
  NESTED TABLE other_atr store as other_atr_nt;

but "some_tbl" is of "some_typ" and "other_atr" can't be seen. So I get
ORA-00904 error.

Are there any way to create this table with readable name?
Followup:
you can make an index -- use "SYSNT1BXSYbRfdyLgMKjA+gAs0g==" with the " as the
idenfifier.
February 24, 2004 Vinod -- Thanks for the question regarding "Tuning SQL Statements Using Explain Plan", version 8i
You Asked
Hi Tom , I am new in tuning sql statements. Can u give a methodology of tuning
the sql statements and to predict the output of explain plan.
When i use explain plan for a particular query, i am able to understand what the
result means. can u pls clarify it for me in detail.

Another doubt is that while using Powerbuilder 7.0 with Oracle8i rel 8.1.5 , PB
gets hanged while going to the datawindow SQL painter . This happens only when
OPTIMIZER_MODE = COST. This problem is ridiculous for our development team and
our team needs clarification from u.

Thanks
Vinod
and we said...
As for "a methodolody to predict the output of explain plan" -- I'm not sure at
all what you are looking for.  Do you mean "how do I interpret it"?  If so
please see:
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/ex_plan.htm#16972
In fact, the entire document:
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/toc.htm
which is the server tuning guide, will be invaluable to you in learning this.
It covers all of the access plans and such so you'll know what a SORT MERGE JOIN
versus NESTED LOOPS means.

As for the second problem -- it sounds like a bad query plan is being generated
for the given query.   PB isn't really hung in all probability but the query is
taking a very long time to complete.  You say you've set the optimizer_mode =
cost, but values for optimizer_mode are

o RULE
o CHOOSE (uses CBO if statistics are present, RBO otherwise)
o FIRST_ROWS (find a plan to get the first row the fastest using
              the CBO)
o ALL_ROWS (find a plan to get the last row the fastest using
            the CBO)

So, I'll assume you've set the optimizer_mode to FIRST_ROWS or ALL_ROWS.  My
question back to you would be -- have you analyzed the tables or have you just
set the optimizer_mode.  If you have not analyzed the tables recently -- with
their current set of data, then the plans generated by the optimizer can be
quite bad indeed.

I would suggest as a way to see what is really happening -- to read the server
tuning manual and find all about SQL_TRACE, TIMED_STATISTICS, and TKPROF.
Those three things are the most powerful application tuning tools out there.
They will show you the SQL your application is submitting to the database, the
plans used to run it, how many rows flowed through each step of the plan, how
many rows were returned to the client, how many fetches took place to get those
rows, how much CPU it took and how much wall clock time it took (plus lots more
information).
Review & Followup

Rating: 5
Tuning in development, test, production April 23, 2002
Reviewer: Schesser
Tom, I'm a regular visitor toyour website, and have quite a bit on tuning on
your forum.

I was in a meeting today, and was surprised to hear from a lot of developers
that they are under the opinion that a sql tuned in development will work the
same way in test and in production. This is in a case where the dev and test
database is refreshed everyday.

I disagree with them, even though the database is refreshed every day.

My point of view is that  even though everything is the same datawise in d1 , t1
and p1, the OPTIMIZER will not necessarily decide on the same execution path in
all the 3 instances.

My standpoint is based ont he facts

1.The hardware is not the same for the 3 instances.
2.More number of users access the production database. If the number of users in
development is 100, then it is 1000 in production.
3.When contention for objects increases(which is the case in production) the
OPTIMIZER will for sure take different  decesions as far as execution path is
concerned.

Can you give your valuable expert opinion on the above.
1.Is my contention right? If so can you give me some more factors which make the
up the difference in performance between devolopment and production.
2.How can I benchmark my standpoint and show it on paper that tuning in
development not necessarily meants the sql will do fine in Production.
Followup:
If you use RBO -- the plans will probably never change.  It depends -- most
likely they will be the same (but create your indexes in a different order and
we might use different indexes)

If you use CBO -- the plans can and will change.  They can change from day to
day.
Rating: 4
Query plan chaging question? April 28, 2002
Reviewer: William from Australia
Tom,

Would the query plan change if the table/index statistics
are not re-gathered or any initialisation parameters changed?
Followup:
The plans should stay constant if all other things are constant (the software is
deterministic, given the same inputs, it'll come to the same conclusion)
Rating: 4
April 29, 2002
Reviewer: Mike
without the stats, and set hint with FIRST_ROWS. The explain still shown the
cost=something, which the costs come? I assume Oracle will use rule, if no
stats:

SQL>  select /*+ first_rows */ count(*) from tt where object_id=1000;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=1 Card=1 B
          ytes=13)

   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'TT_IO_IDX' (NON-UNIQUE) (Cost=1 C
          ard=20 Bytes=260)
Followup:
Your assumption is wrong.  Oracle makes up statistics when you demand the use of
the CBO.  It does this based on cached data dictionary information (how many
extents, whats the high water mark and so on)

Watch the cost/card go up and down (notice that a flush must take place, else
the cached data dictionary info is used)

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 traceonly explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ first_rows */ * from t t1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=1 Card=82
Bytes=10496)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=1 Card=82 Bytes=10496)

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select * from
all_objects;

22907 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;

22907 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;

22907 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;

Commit complete.

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>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ first_rows */ * from t t2;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=28 Card=77188
Bytes=9880064)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=28 Card=77188 Bytes=9880064)

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from t;

68721 rows deleted.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter system flush shared_pool
  2  /

System altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ first_rows */ * from t t3;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=28 Card=77188
Bytes=9880064)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=28 Card=77188 Bytes=9880064)

ops$tkyte@ORA817DEV.US.ORACLE.COM> truncate table t;

Table truncated.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter system flush shared_pool;

System altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ first_rows */ * from t t4;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=1 Card=82
Bytes=10496)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=1 Card=82 Bytes=10496)
Rating: 4
August 08, 2002
Reviewer: Rajiv from NJ, USA
Hi Tom,

I learned a lot from your site. The way you explain things with examples is
superb. Thanks for all your help.

I would like to ask you about tuning complex select statements with several
joins and outer joins (the ones geneated by OLAP tools etc). Explain plan would
show the access path and SQL trace the wait statistics. That will help us to see
if the access path is optimum or if there are any inordinate waits. But I feel
sometimes in case of complex statements, a total re-write or even design changes
would be necessary to achieve better response time. What should be the
principles of re-writing an SQL statement effectively? Inorder to illustrate
what I am trying to ask, let me post the predicate of a long SQL I am trying to
tune:

----------------------
where  m.material = ms.mat_sales
  and  ms.matl_grp_2 = g.matl_grp_2
  and  ms."/BIC/ZCPRDSTAT" = ps."BIC_ZCPRDSTAT"
  and  ms.salesorg = '1010'
  and  ms.distr_chan = '01'
  and  ms.mat_sales = pz.material (+)
  and  ms.plant = pz.plant (+)
  and  pz.comp_code (+) = '101'
  and  pz.val_class (+) = '7920'
  and  pz.fiscvarnt (+) = 'K4'
  and  pz.fiscper (+) = mercator.fisc_per
  and  m.division = d.division
  and  m."/BIC/ZCLANGUAE" = le."BIC_ZCLANGUAE"
  and  m."/BIC/ZCLANGUAG" = lg."BIC_ZCLANGUAG"
  and  m.matl_group = mg.matl_group
  and  m."/BIC/ZCDESIGN" = mt."BIC_ZCDESIGN"
  and  m."/BIC/ZCPRODCAT" = mp."BIC_ZCPRODCAT"
  and  m."/BIC/ZCPRDCODE" = pc."BIC_ZCPRDCODE"
  and  m."/BIC/ZCRELEASE" = mr."BIC_ZCRELEASE"
  and  m."/BIC/ZCSERIES"  = s."BIC_ZCSERIES"
  and  m."/BIC/ZCSUBCAT1" = sc1."BIC_ZCSUBCAT1"
  and  m."/BIC/ZCSUBCAT2" = sc2."BIC_ZCSUBCAT2"
  and  m."/BIC/ZCSUBCAT3" = sc3."BIC_ZCSUBCAT3"
  and  m.material = mu.material (+)
  and  mu.mat_unit (+) = 'CS'
  and  m."/BIC/A_ISBN" || '.1' = w.wbs_elemt (+)

And part of the Explain plan on a select count(*) using this query as an inline
view is:

   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=21521004692346200000
          0 Card=1 Bytes=282)

   1    0   SORT (AGGREGATE)
   2    1     HASH JOIN (OUTER) (Cost=215210046923462000000 Card=57211
          36614938280000000000 Bytes=1613360525412600000000000000)

   3    2       NESTED LOOPS (Cost=5559474405851140 Card=7711673875745
          780000000 Bytes=2066728598699870000000000)

   4    3         HASH JOIN (Cost=5559474405851140 Card=77116738757457
          80000000 Bytes=2028170229321140000000000)

   5    4           VIEW OF 'BIC_TZCSUBCAT3_BO_VW' (Cost=8 Card=732 By
          tes=4392)

   6    5             SORT (UNIQUE) (Cost=8 Card=732 Bytes=13000)
   7    6               UNION-ALL
   8    7                 TABLE ACCESS (FULL) OF '/BIC/TZCSUBCAT3' (Co
          st=1 Card=650 Bytes=13000)

----------

Except one or two, the tables are'nt that big. Also, eventhough we have indexes
on most of the join columns, many of them are not being used. We have many such
queries and we will have more data in Production. What can we do to to speed up
such queries. I hope I am clear in what I am asking. Wating for your reply.

Rajiv.
Followup:
Are those cardinality counts right?

Indexes do not imply, mean, infer or suggest "faster".  In this query, it
appears most of the rows in most of the tables would be accessed --hashing is
better for that then slower index reads.

Is that the full plan?

(bummer on those table and column names -- cannot believe someone would do that
do you, slashes and all...)
Rating: 5
August 08, 2002
Reviewer: A reader
Ya, those CARD numbers were scary. It was not the full plan. Let me post both
the query and the plan here.

About the slashes on the table names: This is SAP Business Warehouse. This is
the way they name the tables and columns. It was hard for me too to believe when
I saw it first.

We have a number of small views (union of 1 or 2 tables with dual) used in the
query. (the ones with _VW are views).

Here's the query and plan:

SQL> set autot traceonly explain
SQL>
SQL>
SQL> ed
Wrote file afiedt.buf

  1  select
  2    m.material, m."/BIC/A_ISBN",
  3    m."/BIC/ZKAGERAFR", m."/BIC/ZKAGERATO",
  4    m."/BIC/ZCAUDIENC", m."/BIC/ZCARTIST", m."/BIC/ZCDISPOSA",
  5    m.EANUPC, m.DIVISION, m."/BIC/ZKEXTENT",
  6    m."/BIC/ZKGRARAFR", m."/BIC/ZKGRARATO",
  7    m."/BIC/ZCLANGUAE", m."/BIC/ZCLANGUAG",
  8    m."/BIC/ZCTXTGRUN", m."/BIC/ZCTXTGRU1", m."/BIC/ZCTXTGRU2",
  9    m.MATL_TYPE, m.MATL_GROUP, m."/BIC/ZCDESIGN", m."/BIC/ZCOUTPRDT",
 10    m."/BIC/ZCPRODCAT", ms.PROD_HIER, m."/BIC/ZCPRDCODE", m."/BIC/ZCPUBLDAT",
 11    m."/BIC/ZCRELEASE", m."/BIC/ZC1SHPDAT",
 12    m."/BIC/ZCSUBCAT1", m."/BIC/ZCSUBCAT2", m."/BIC/ZCSUBCAT3",
 13    m.LENGHT, m."/BIC/ZCSUBTIT1", m."/BIC/ZCSUBTIT2", m."/BIC/ZCSUBTIT3",
 14    m.HEIGHT, m.WIDTH, m. "/BIC/ZKVOLNUM", m.NET_WEIGHT,
 15    m."/BIC/ZCSERIES", ms.matl_grp_1, ms.matl_grp_2,
 16    ms."/BIC/ZCONSALED", ms."/BIC/ZCPRDSTAT", ms.PLANT,
 17    d.DIVISION_DESC, le.BIC_ZCLANGUAE_DESC, lg.BIC_ZCLANGUAG_DESC,
 18    mg.MATL_GROUP_DESC, mt.BIC_ZCDESIGN_DESC,
 19    mp.BIC_ZCPRODCAT_DESC, pc.BIC_ZCPRDCODE_DESC, mr.BIC_ZCRELEASE_DESC,
 20    g.MATL_GRP_2_DESC, s.BIC_ZCSERIES_DESC,
 21    ps.BIC_ZCPRDSTAT_DESC, sc1.BIC_ZCSUBCAT1_DESC, sc2.BIC_ZCSUBCAT2_DESC,
 22    sc3.BIC_ZCSUBCAT3_DESC,
 23    mu.NUMERATOR, w.PS_RESPNO, m."/BIC/ZCSERIES2", m."/BIC/ZCSERIES3",
 24    pz.PLANT, pz.PRICE_MAT, m."/BIC/ZKPRICECA"
 25  from  sapr3."/BI0/PMATERIAL" m,
 26    sapr3."/BI0/PMAT_SALES" ms,
 27    BOADMIN.BI0_TDIVISION_BO_VW d,
 28    BOADMIN.BIC_TZCLANGUAE_BO_VW le,
 29    BOADMIN.BIC_TZCLANGUAG_BO_VW lg,
 30    BOADMIN.BI0_TMATL_GROUP_BO_VW mg,
 31    BOADMIN.BIC_TZCDESIGN_BO_VW mt,
 32    BOADMIN.BIC_TZCPRODCAT_BO_VW mp,
 33    BOADMIN.BIC_TZCPRDCODE_BO_VW pc,
 34    BOADMIN.BIC_TZCRELEASE_BO_VW mr,
 35    BOADMIN.BI0_TMATL_GRP_2_BO_VW g,
 36    BOADMIN.BIC_TZCSERIES_BO_VW s,
 37    BOADMIN.BIC_TZCPRDSTAT_BO_VW ps,
 38    BOADMIN.BIC_TZCSUBCAT1_BO_VW sc1,
 39    BOADMIN.BIC_TZCSUBCAT2_BO_VW sc2,
 40    BOADMIN.BIC_TZCSUBCAT3_BO_VW sc3,
 41    sapr3."/BI0/PMAT_UNIT" mu,
 42    sapr3."/BI0/PWBS_ELEMT" w,
 43    sapr3."/BIC/AP1OSCIMV00" pz
 44  where  m.material = ms.mat_sales
 45    and  ms.matl_grp_2 = g.matl_grp_2
 46    and  ms."/BIC/ZCPRDSTAT" = ps."BIC_ZCPRDSTAT"
 47    and  ms.salesorg = '1010'
 48    and  ms.distr_chan = '01'
 49    and  ms.mat_sales = pz.material (+)
 50    and  ms.plant = pz.plant (+)
 51    and  pz.comp_code (+) = '101'
 52    and  pz.val_class (+) = '7920'
 53    and  pz.fiscvarnt (+) = 'K4'
 54    and  pz.fiscper (+) = mercator.fisc_per
 55    and  m.division = d.division
 56    and  m."/BIC/ZCLANGUAE" = le."BIC_ZCLANGUAE"
 57    and  m."/BIC/ZCLANGUAG" = lg."BIC_ZCLANGUAG"
 58    and  m.matl_group = mg.matl_group
 59    and  m."/BIC/ZCDESIGN" = mt."BIC_ZCDESIGN"
 60    and  m."/BIC/ZCPRODCAT" = mp."BIC_ZCPRODCAT"
 61    and  m."/BIC/ZCPRDCODE" = pc."BIC_ZCPRDCODE"
 62    and  m."/BIC/ZCRELEASE" = mr."BIC_ZCRELEASE"
 63    and  m."/BIC/ZCSERIES"  = s."BIC_ZCSERIES"
 64    and  m."/BIC/ZCSUBCAT1" = sc1."BIC_ZCSUBCAT1"
 65    and  m."/BIC/ZCSUBCAT2" = sc2."BIC_ZCSUBCAT2"
 66    and  m."/BIC/ZCSUBCAT3" = sc3."BIC_ZCSUBCAT3"
 67    and  m.material = mu.material (+)
 68    and  mu.mat_unit (+) = 'CS'
 69*   and  m."/BIC/A_ISBN" || '.1' = w.wbs_elemt (+)
 70  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=74990665314992100000
          0 Card=5721136614938280000000000 Bytes=530349364204779000000
          0000000)

   1    0   HASH JOIN (OUTER) (Cost=749906653149921000000 Card=5721136
          614938280000000000 Bytes=5303493642047790000000000000)

   2    1     HASH JOIN (Cost=33776078474649500 Card=77116738757457800
          00000 Bytes=6994488205301420000000000)

   3    2       VIEW OF 'BIC_TZCSUBCAT3_BO_VW' (Cost=8 Card=732 Bytes=
          13176)

   4    3         SORT (UNIQUE) (Cost=8 Card=732 Bytes=13000)
   5    4           UNION-ALL
   6    5             TABLE ACCESS (FULL) OF '/BIC/TZCSUBCAT3' (Cost=1
           Card=650 Bytes=13000)

   7    5             TABLE ACCESS (FULL) OF 'DUAL' (Cost=1 Card=82)
   8    2       HASH JOIN (Cost=24056545335293200 Card=105350736007456
          00000 Bytes=9365680431062840000000)

   9    8         VIEW OF 'BIC_TZCSUBCAT2_BO_VW' (Cost=8 Card=732 Byte
          s=13176)

  10    9           SORT (UNIQUE) (Cost=8 Card=732 Bytes=13000)
  11   10             UNION-ALL
  12   11               TABLE ACCESS (FULL) OF '/BIC/TZCSUBCAT2' (Cost
          =1 Card=650 Bytes=13000)

  13   11               TABLE ACCESS (FULL) OF 'DUAL' (Cost=1 Card=82)
  14    8         HASH JOIN (Cost=24043532550830800 Card=1439217705019
          8900 Bytes=12535586210723300000)

  15   14           VIEW OF 'BIC_TZCSERIES_BO_VW' (Cost=11 Card=1074 B
          ytes=31146)

  16   15             SORT (UNIQUE) (Cost=11 Card=1074 Bytes=30752)
  17   16               UNION-ALL
  18   17                 TABLE ACCESS (FULL) OF '/BIC/TZCSERIES' (Cos
          t=1 Card=992 Bytes=30752)

  19   17                 TABLE ACCESS (FULL) OF 'DUAL' (Cost=1 Card=8
          2)

  20   14           HASH JOIN (Cost=24043509114311200 Card=13400537290
          688 Bytes=11283252398759300)

  21   20             VIEW OF 'BIC_TZCPRODCAT_BO_VW' (Cost=6 Card=152
          Bytes=2280)

  22   21               SORT (UNIQUE) (Cost=6 Card=152 Bytes=1190)
  23   22                 UNION-ALL
  24   23                   TABLE ACCESS (FULL) OF '/BIC/TZCPRODCAT' (
          Cost=1 Card=70 Bytes=1190)

  25   23                   TABLE ACCESS (FULL) OF 'DUAL' (Cost=1 Card
          =82)

  26   20             HASH JOIN (Cost=24043509089064700 Card=881614295
          44 Bytes=72909502232888)

  27   26               TABLE ACCESS (FULL) OF '/BI0/TMATL_GRP_2' (Cos
          t=1 Card=237 Bytes=6399)

  28   26               HASH JOIN (OUTER) (Cost=24043509040196500 Card
          =88161429544 Bytes=70529143635200)

  29   28                 NESTED LOOPS (OUTER) (Cost=24043435323438600
           Card=744931389402 Bytes=575831964007746)

  30   29                   HASH JOIN (Cost=24043435323438600 Card=168
          706101620750000 Bytes=122311923675044000000)

  31   30                     VIEW OF 'BIC_TZCPRDSTAT_BO_VW' (Cost=6 C
          ard=90 Bytes=1350)

  32   31                       SORT (UNIQUE) (Cost=6 Card=90 Bytes=13
          6)

  33   32                         UNION-ALL
  34   33                           TABLE ACCESS (FULL) OF '/BIC/TZCPR
          DSTAT' (Cost=1 Card=8 Bytes=136)

  35   33                           TABLE ACCESS (FULL) OF 'DUAL' (Cos
          t=1 Card=82)

  36   30                     HASH JOIN (Cost=24040201730831000 Card=1
          3121585681613900 Bytes=9316325833945870000)

  37   36                       TABLE ACCESS (FULL) OF '/BI0/PMAT_SALE
          S' (Cost=481 Card=56618 Bytes=3906642)

  38   36                       HASH JOIN (Cost=13709136541766 Card=19
          2498514928967000 Bytes=123391548069468000000)

  39   38                         VIEW OF 'BIC_TZCDESIGN_BO_VW' (Cost=
          6 Card=215 Bytes=3440)

  40   39                           SORT (UNIQUE) (Cost=6 Card=215 Byt
          es=2128)

  41   40                             UNION-ALL
  42   41                               TABLE ACCESS (FULL) OF '/BIC/T
          ZCDESIGN' (Cost=1 Card=133 Bytes=2128)

  43   41                               TABLE ACCESS (FULL) OF 'DUAL'
          (Cost=1 Card=82)

  44   38                         HASH JOIN (Cost=1250511513346 Card=5
          7301883513739100 Bytes=35813677196087000000)

  45   44                           VIEW OF 'BIC_TZCLANGUAG_BO_VW' (Co
          st=6 Card=137 Bytes=2192)

  46   45                             SORT (UNIQUE) (Cost=6 Card=137 B
          ytes=880)

  47   46                               UNION-ALL
  48   47                                 TABLE ACCESS (FULL) OF '/BIC
          /TZCLANGUAG' (Cost=1 Card=55 Bytes=880)

  49   47                                 TABLE ACCESS (FULL) OF 'DUAL
          ' (Cost=1 Card=82)

  50   44                           HASH JOIN (Cost=9345853174 Card=58
          55666928411300 Bytes=3566101159402480000)

  51   50                             VIEW OF 'BIC_TZCSUBCAT1_BO_VW' (
          Cost=6 Card=130 Bytes=2340)

  52   51                               SORT (UNIQUE) (Cost=6 Card=130
           Bytes=960)

  53   52                                 UNION-ALL
  54   53                                   TABLE ACCESS (FULL) OF '/B
          IC/TZCSUBCAT1' (Cost=1 Card=48 Bytes=960)

  55   53                                   TABLE ACCESS (FULL) OF 'DU
          AL' (Cost=1 Card=82)

  56   50                             HASH JOIN (Cost=75162061 Card=45
          043591757010 Bytes=26620762728392900)

  57   56                               VIEW OF 'BIC_TZCLANGUAE_BO_VW'
           (Cost=6 Card=119 Bytes=2856)

  58   57                                 SORT (UNIQUE) (Cost=6 Card=1
          19 Bytes=814)

  59   58                                   UNION-ALL
  60   59                                     TABLE ACCESS (FULL) OF '
          /BIC/TZCLANGUAE' (Cost=1 Card=37 Bytes=814)

  61   59                                     TABLE ACCESS (FULL) OF '
          DUAL' (Cost=1 Card=82)

  62   56                               HASH JOIN (Cost=357783 Card=37
          8517577790 Bytes=214619466606930)

  63   62                                 VIEW OF 'BIC_TZCRELEASE_BO_V
          W' (Cost=6 Card=102 Bytes=1632)

  64   63                                   SORT (UNIQUE) (Cost=6 Card
          =102 Bytes=360)

  65   64                                     UNION-ALL
  66   65                                       TABLE ACCESS (FULL) OF
           '/BIC/TZCRELEASE' (Cost=1 Card=20 Bytes=360)

  67   65                                       TABLE ACCESS (FULL) OF
           'DUAL' (Cost=1 Card=82)

  68   62                                 HASH JOIN (Cost=11746 Card=3
          710956645 Bytes=2044737111395)

  69   68                                   VIEW OF 'BIC_TZCPRDCODE_BO
          _VW' (Cost=6 Card=101 Bytes=1414)

  70   69                                     SORT (UNIQUE) (Cost=6 Ca
          rd=101 Bytes=304)

  71   70                                       UNION-ALL
  72   71                                         TABLE ACCESS (FULL)
          OF '/BIC/TZCPRDCODE' (Cost=1 Card=19 Bytes=304)

  73   71                                         TABLE ACCESS (FULL)
          OF 'DUAL' (Cost=1 Card=82)

  74   68                                   HASH JOIN (Cost=4855 Card=
          36742145 Bytes=19730531865)

  75   74                                     VIEW OF 'BI0_TMATL_GROUP
          _BO_VW' (Cost=6 Card=94 Bytes=1692)

  76   75                                       SORT (UNIQUE) (Cost=6
          Card=94 Bytes=228)

  77   76                                         UNION-ALL
  78   77                                           TABLE ACCESS (FULL
          ) OF '/BI0/TMATL_GROUP' (Cost=1 Card=12 Bytes=228)

  79   77                                           TABLE ACCESS (FULL
          ) OF 'DUAL' (Cost=1 Card=82)

  80   74                                     HASH JOIN (Cost=3645 Car
          d=6644856 Bytes=3448680264)

  81   80                                       VIEW OF 'BI0_TDIVISION
          _BO_VW' (Cost=6 Card=88 Bytes=1320)

  82   81                                         SORT (UNIQUE) (Cost=
          6 Card=88 Bytes=102)

  83   82                                           UNION-ALL
  84   83                                             TABLE ACCESS (FU
          LL) OF '/BI0/TDIVISION' (Cost=1 Card=6 Bytes=102)

  85   83                                             TABLE ACCESS (FU
          LL) OF 'DUAL' (Cost=1 Card=82)

  86   80                                       TABLE ACCESS (FULL) OF
           '/BI0/PMATERIAL' (Cost=3621 Card=830607 Bytes=418625928)

  87   29                   TABLE ACCESS (BY INDEX ROWID) OF '/BIC/AP1
          OSCIMV00'

  88   87                     INDEX (RANGE SCAN) OF '/BIC/AP1OSCIMV00~
          0' (UNIQUE)

  89   28                 TABLE ACCESS (FULL) OF '/BI0/PMAT_UNIT' (Cos
          t=234 Card=98301 Bytes=2654127)

  90    1     TABLE ACCESS (FULL) OF '/BI0/PWBS_ELEMT' (Cost=101 Card=
          74188 Bytes=1483760)

Thanks for your time.

Rajiv.
Followup:
Well, I do believe that a cost of Seven Hundred Forty-Nine quintillion Nine
Hundred Six quadrillion Six Hundred Fifty-Three trillion One Hundred Forty-Nine
billion Nine Hundred Twenty-One million is the largest cost I've ever seen!

The plan looks appropriate however, given the predicate -- you don't have many
predicates other then joins in there meaning the full table scans are
appropriate.

You might want to look at upping the hash_area_size and utilizing parallel query
to help with these objects.
Rating: 4
August 08, 2002
Reviewer: A reader
Oh! I didn't know I was working on the costliest query in the whole world!!

Thanks for the suggestion. I was alerady trying to use parallel excution on this
query. I will increase the hash area too.

I am also trying to re-write the query by creating a table/mv replacing those
views: they are views on lookup tables where the data does not change. Do you
think it will help?

Also would you suggest any other re-writing tips: like replace the outer join
with subqueries or anything like that? Thanks for your help.

Rgds,
Rajiv.
Followup:
You might try selecting a select, instead of:

  1  select
....
 23    mu.NUMERATOR, w.PS_RESPNO, m."/BIC/ZCSERIES2", m."/BIC/ZCSERIES3",
....
 69*   and  m."/BIC/A_ISBN" || '.1' = w.wbs_elemt (+)
 70  /

You can

  1  select
....
 23    mu.NUMERATOR,
      (select w.PS_RESPNO
           from sapr3."/BI0/PWBS_ELEMT" w
          where w.wbs_elemt = m."/BIC/A_ISBN" ),
      m."/BIC/ZCSERIES2", m."/BIC/ZCSERIES3",
....
 69*
 70  /

and remove W from the "from" list in the major query.  You might try that for
some of the other outer-joined to things as well.

Additionally -- determine IF in fact you TRULY NEED the outer joins in all cases
-- I've found many times they are there simply "in case".  outer joins can be
very expensive.

Doing that select of a select with the SC1, SC2, SC3 tables might be dramatic as
well -- may find that better then a join join join join (then again, might not
be, give it a try)
Rating: 5
August 10, 2002
Reviewer: A reader
Tom, I tried with the subquery, but it didnt make much difference. The query
runs for about half hour still.

I loaded all the tables in another system and tried to run the query there.
Amazingly, it was done in less than 3mins. The plan looks mostly similar, but
the cost and card numbers are far less. I compared the buffers and other init
parameters of the two systems. While they are different, they were not
excessively different. Also the opt* init parameters were same. On both the
systems the tables involved were of same size and the stats were current. The
first (slow) system was a 12 CPU RS/6000 AIX 4.3. There are 2 other instances
running on this box. The second system was a 24 CPU RS/6000 AIX 5 with only one
database running.

If you are interested in moving this thread, I can post the details of
comparison. Else, I would like to have your colsing comments on why we had such
excessive costs on the first system. Can it be attributed to the smaller CPU
strength alone? How can I find more info on what is going on?

Thanks for your time and valuable suggestions. Appreciate it a lot.

Rgds,
Rajiv.
Followup:
Are the STATS the same (up to date) in both boxes.
Rating: 3
Explain Plan document. January 16, 2003
Reviewer: Kashif from Houston, TX
Hi Tom,

The Explain Plan document you provided the link to (to the original poster) does
not adequately explain how the Explain Plan is actually supposed to be
interpreted. For example, I understand that an explain is supposed to be read
innermost to outermost, but that info is not mentioned in the document. I also
tried looking for this info in your book but was unable to get much information.
Do you have any other documents/links which might explain how to interpret the
Explain Plan more thoroughly? Thanks.

Kashif
Followup:
Ok, tell me if this helps.  It is an excerpt from my forth coming book:

1.3.3 Reading an Explain Plan

This is a frequently asked question - how exactly do you read an explain plan.
Here I will present my 'simple' approach to reading the plan.  I do suggest
however that a quick read of the chapter in the Oracle Performance Guide - the
chapter on the explain plan command - would be very useful as well.

For all of the details on reading an explain plan, please refer to the Oracle
Performance Guide. There are complete details on how to read the query plan and
interpret the results.

We'll take a look at a query plan resulting from a query against the SCOTT/TIGER
tables (note, I add primary keys to the EMP and DEPT tables - hence, they are
indexed):

scott@ORA920> delete from plan_table;
7 rows deleted.

scott@ORA920> explain plan for
  2  select ename, dname, grade
  3    from emp, dept, salgrade
  4   where emp.deptno = dept.deptno
  5     and emp.sal between salgrade.losal and salgrade.hisal
  6  /
Explained.

scott@ORA920> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------
| Id  | Operation                    |Name     |Rows|Bytes|Cost |
-----------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |    |     |     |
|   1 |  NESTED LOOPS                |         |    |     |     |
|   2 |   NESTED LOOPS               |         |    |     |     |
|   3 |    TABLE ACCESS FULL         | SALGRADE|    |     |     |
|*  4 |    TABLE ACCESS FULL         | EMP     |    |     |     |
|   5 |   TABLE ACCESS BY INDEX ROWID| DEPT    |    |     |     |
|*  6 |    INDEX UNIQUE SCAN         | DEPT_PK |    |     |     |
-----------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   4 - filter("EMP"."SAL"<="SALGRADE"."HISAL" AND
              "EMP"."SAL">="SALGRADE"."LOSAL")
   6 - access("EMP"."DEPTNO"="DEPT"."DEPTNO")

Note: rule based optimization

21 rows selected.

Now - what happens first?  How does that plan actually get evaluated?  First
I'll show you the psuedo code for how the plan is evaluated and then we'll
discuss how I arrived at this conclusion:

For salgrade in (select * from salgrade)
Loop
   For emp in ( select * from emp )
   Loop
      If ( emp.sal between salgrade.losal and salgrade.hisal )
      Then
          Select * into dept_rec
            From dept
           Where dept.deptno = emp.deptno;

          OUTPUT RECORD with fields from salgrade,emp,dept
      End if;
   End loop;
End loop;

The way I read the plan was to turn it into a graph of sorts - an evaluation
tree.  In order to do that, we need to understand something about access paths.

For detailed information on the access paths available to Oracle, please see the
Oracle Performance and Tuning Guide.

In order to build the tree - we can start at the top, with step 1.  That will be
our "root node" in the tree.  Next, we need to find the things that "feed" this
root node - that will be steps 2 and 5 - as you can see - 2 and 5 are at the
same level of indentation - they "feed" into step 1.  Further, we can see that
steps 3 and 4 feed step 2 and that step 6 feeds step 5.  Putting that together
iteratively - we would draw:

                                1
                               / \
                              2   5
                             / \   \
                            3   4   6

And then just read the tree.  In order to get 1 we need 2 and 5 - 2 is "first".
In order to get 2, we need 3 and 4.  3 is "first".  That is how I arrived at the
psuedo code for:

For salgrade in (select * from salgrade)
Loop
   For emp in ( select * from emp )
   Loop

Full scan SALGRADE is step 3, full scan EMP is step 4 and step 2 is a nested
loop - which is roughly equivalent to two "for loops".  Once we get step 2 going
like that - we can look at step 5.  Step 5 runs step 6 first - step 6 is the
index scan step.  We are taking the output of step 2 here and using that to
"feed" this part of the query plan.  So, the output from step 2 is used to
perform an index scan - that index scan output is used to TABLE ACCESS BY ROWID
the DEPT table and that result is the output of step 1 - our result set.

Now, to make this "interesting", we will run an equivalent query - but we'll mix
up the order of the tables in the from clause this time.  Since I am using the
rule based optimizer - this will affect the generated query plan (and is just
one reason why you DON'T want to use the rule based optimizer! We'll cover more
reasons in a later section).  We'll use the same logic to build its query plan
tree and evaluate how it processed the query:

scott@ORA920> delete from plan_table;
7 rows deleted.

scott@ORA920> explain plan for
  2  select ename, dname, grade
  3    from salgrade, dept, emp
  4   where emp.deptno = dept.deptno
  5     and emp.sal between salgrade.losal and salgrade.hisal
  6  /
Explained.

scott@ORA920> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
-------------------------------------------------------------------
| Id  | Operation                     |  Name       | Rows  | Bytes
-------------------------------------------------------------------
|   0 | SELECT STATEMENT              |             |       |
|   1 |  NESTED LOOPS                 |             |       |
|   2 |   NESTED LOOPS                |             |       |
|   3 |    TABLE ACCESS FULL          | EMP         |       |
|   4 |    TABLE ACCESS BY INDEX ROWID| DEPT        |       |
|*  5 |     INDEX UNIQUE SCAN         | DEPT_PK     |       |
|*  6 |   TABLE ACCESS FULL           | SALGRADE    |       |
-------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   5 - access("EMP"."DEPTNO"="DEPT"."DEPTNO")
   6 - filter("EMP"."SAL"<="SALGRADE"."HISAL" AND
              "EMP"."SAL">="SALGRADE"."LOSAL")
Note: rule based optimization

21 rows selected.

Here we see that steps 2 and 6 feed 1, steps 3 and 4 feed 2, and step 5 feeds 4.
 Drawing the tree:
                                1
                               / \
                              2   6
                             / \
                            3   4
                                 \
                                 5

So, the psuedo code logic here is - starting with steps 3 and 4:

For emp in ( select * from emp )
Loop
   -- using the index
   Select * from dept where dept.deptno = emp.deptno

   For salgrade in (select * from salgrade )
   Loop
      If ( emp.sal between salgrade.losal and salgrade.hisal )
      Then
         OUTPUT RECORD;
      End if;
   End loop
End loop;

And that is it - if you draw the graphical tree like that and then read it
bottom up, left to right, you'll get a good understanding of the "flow" of the
data.
Rating: 5
Fabulous! January 17, 2003
Reviewer: Kashif from Houston, TX
Hi Tom,

Thanks for that excellent easy-to-follow explanation. I understand the plan much
better now. I'm just not quite getting my particular plan:

batchcode@QGF1> get ins2cpi.sql
  1  insert into hmc.cpi_lnum (lnum, clnum, Loan_Type, Loan_amount,
  2                            RESEND_FLAG, PRVS_LOAD, Error_flag)
  3  (
  4  select /*+ ORDERED PUSH_SUBQ INDEX (dt pk_dates) */
  5         dt.lnum, tr.clnum, pr.s_ltype, pr.ola , 'F', 1, 'N'
  6    from chk_screen c,
  7         hmc_dates hmc,
  8         dates dt,
  9         hmv_current_status hmv,
 10         tracking tr,
 11         product pr
 12   where c.lnum = hmc.lnum (+)
 13     and c.lnum = dt.lnum
 14     and c.lnum = hmv.lnum
 15     and c.lnum = tr.lnum
 16     and c.lnum = pr.lnum
 17     and dt.lnfundat >= to_date ( '10111999', 'MMDDYYYY' )
 18     and dt.lnfundat <= to_date ( '12182002', 'MMDDYYYY' )
 19     and hmc.send_to_cpi is null
 20     and c.s_checkstatus = 'CS_CMPT'
 21     and hmv.status_code||'' >= 700
 22     and hmv.status_code||'' <= 2300
 23     and length (tr.clnum) >= 10
 24     and not exists (select null
 25                       from wireinfo w
 26                      where w.lnum = c.lnum)
 27* )
batchcode@QGF1> get queryplan.txt
        ID Query_Plan
---------- -------------------------------------------------------
         0 INSERT STATEMENT   Cost = 71
         1   NESTED LOOPS
         2     NESTED LOOPS
         3       NESTED LOOPS
         4         NESTED LOOPS
         5           FILTER
         6             NESTED LOOPS OUTER
         7               TABLE ACCESS FULL CHK_SCREEN
         8                 INDEX RANGE SCAN PK_WIREINFO
         9               TABLE ACCESS BY INDEX ROWID HMC_DATES
        10                 INDEX UNIQUE SCAN HMC_DATES
        11           TABLE ACCESS BY INDEX ROWID DATES
        12             INDEX UNIQUE SCAN PK_DATES
        13         TABLE ACCESS BY INDEX ROWID HMV_CURRENT_STATUS
        14           INDEX UNIQUE SCAN PK_HMV_CURRENT_STATUS
        15       TABLE ACCESS BY INDEX ROWID TRACKING
        16         INDEX UNIQUE SCAN PK_TRACKING
        17     TABLE ACCESS BY INDEX ROWID PRODUCT
        18       INDEX UNIQUE SCAN PK_PRODUCT

19 rows selected.

batchcode@QGF1>

It all makes sense to me, except for step 8, where there is a range scan on the
PK_WIREINFO index. From what you explained, this step is feeding step 7, which
is the FTS of chk_screen. But that seems counter-intuitive. How can there be a
range scan on pk_wireinfo, and it also be the starting point for the plan?
Alternatively, if I think that chk_screen was full table scanned first, followed
by a range scan on pk_wireinfo for the ids produced from the FTS of chk_screen,
that makes complete intuitive sense to me, but then it fails your explanation. I
know this is caused by the PUSH_SUBQ hint used, which is causing the subquery to
be evaluated first. Any feedback would be helpful. Thanks in advance.

Kashif
Followup:
not knowing the tables and what indexes go where -- well -- that makes it sort
of difficult. (you know my opinion of hints too don't you?)
Rating: 5
For anyone interested... January 28, 2003
Reviewer: Kashif from Houston, TX
Thanks Tom, for the explanation. For anyone interested, Chapter 9 of the
Performance Tuning Guide for 9i, "Using EXPLAIN PLAN", is significantly better
than previous releases (pre-9i), especially when it comes to explaining how to
read the EXPLAIN PLAN in the section, "Reading EXPLAIN PLAN Output". This
coupled with Tom's explanation above provided me with a comprehensive
understanding of the EXPLAIN PLAN. Keep up the good work Tom.

Kashif
Rating: 5
2 Points March 26, 2003
Reviewer: Robert from Memphis, USA
Tom,

1) Tom, your explaination of reading an explain plan is, I think, the best and
most succinct explaination I have seen to date... also, I hope to study your
psuedo-code to completely understand Oracle's workings.

2) Thanks to Kashif for the heads-up on the Performance Tuning Guide for 9i. I
will check this out. I agree with the previous folks... the explain plan
explaination in the 8i Performance and Tuning Guide was weak, and didn't explain
it clearly as Tom has.

Thanks,

Robert
Rating: 5
November 07, 2003
Reviewer: A reader
Hi Tom,
I've ran below SQL 2hr. after starting database

  1  select a.file#,b.name,MAXIORTM,MAXIOWTM from v$datafile a,
  2   v$tablespace b,v$filestat c
  3  where a.ts#=b.ts#
  4    and c.file# = a.file#
  5*   and B.NAME = 'TEMP'
SQL> /

     FILE# NAME                             MAXIORTM   MAXIOWTM
---------- ------------------------------ ---------- ----------
         5 TEMP                              1685945       1263

Is writing in TEMP taking too much time??
Please advice.

We are using oracle 8.1.7.4

Thanks
Followup:
you tell us?  i don't know, is it?
Rating: 4
Reading Explain Plan November 07, 2003
Reviewer: A reader from NJ, USA
Tom,

I have read books on tuning that simply states in statements about how each loop
joins are executed. But you have given a pseudo code for how the plan gets
executed which is really very very helpful. I would appreciate if you could
suggest some books that explain each of these execution steps as pseudocodes
rather than lengthy discussions/explanations.

As always thanks much for your clear and concise explanation
Followup:
well, i do that for some (not all) of the access paths in my new book "Effective
Oracle by Design" -- hash joins, sort merges, cartesian joins and so on.  they
are a mix of "lengthly discussion" and psuedo code snippets.
Rating: 4
Forth coming book November 07, 2003
Reviewer: A reader from NJ, USA
Tom,

In this discussion you mention about your forth coming book. Is the one you are
referring to is "Expert Oracle by Design" or is that any other new book that you
are coming up with. I would be excited to see if you can come up with a new book
explaining all of the Oracle tuning tools and tips/techniques
Followup:
It is called "Effective Oracle by Design", releases in August 2003 (see
homepage)
Rating: 4
cpu costing November 08, 2003
Reviewer: Prince from Pakistan
Respected Tom!

You're really performing a great valuable service. I have tried to use the
expalin plan according to your instructions. But it doesn't give
plan_table_output but 'Note: cpu costing is off, 'plan_table' is old version' as
follows. My question is 'What is cpu costing and how it can be enabled or
disabled?' (I am using Oracle9i Enterprise Edition Release 9.2.0.1.0 on MS
Windows 2000 Advanced Server.)

SQL> delete from plan_table;

6 rows deleted.

SQL> explain plan for
  2  SELECT designation.designationcode, designation.name, designation.grade,
  3         designation.pricode
  4      FROM hrms.designation designation, hrms.employees employees;

Explained.

SQL> @E:\Ora92\rdbms\admin\utlxpls

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
-------------

---------------------------------------------------------------------------------
------------
| Id  | Operation                      |  Name                      | Rows  |
Bytes | Cost  |
---------------------------------------------------------------------------------
------------
|   0 | SELECT STATEMENT               |                            |  8521K|
186M|  1360 |
|   1 |  MERGE JOIN CARTESIAN          |                            |  8521K|
186M|  1360 |
|   2 |   TABLE ACCESS FULL            | DESIGNATION                |   451 |
10373 |     3 |
|   3 |   BUFFER SORT                  |                            | 18895 |
   |  1357 |
|   4 |    BITMAP CONVERSION TO ROWIDS |                            |       |
   |       |
|   5 |     BITMAP INDEX FAST FULL SCAN| EMPLOYEE_EMPLOYEETYPE_IDX  |       |
   |       |
---------------------------------------------------------------------------------
------------

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
-------------

Note: cpu costing is off, 'plan_table' is old version

13 rows selected.
Followup:
drop and recreate your plan table using the current script (should be done with
each upgrade -- recreate the plan tables)

currently, in 9i, cpu costing is off by default, this will change in 10g.  it is
an undocumented init.ora parameter for cpu costing right now, so just ignore it.
Rating: 5
Cardinality in EXPLAIN PLAN November 08, 2003
Reviewer: A reader from NJ,USA
Dear Tom,

Thanks for sharing your valuable knowledge with us.

Could you kindly explain how the cardinality is calculated in explain plan
output?

In my below query,
(1)For predicator first_name like 'Sa%' ==> 3 rows returned but Card=1
(2)For predicator first_name like 'S%' ==> 13 rows returned but Card=12

SQL> analyze table employees compute statistics for table for all indexes for
all indexed columns;

Table analyzed.

SQL> set autotrace on
SQL> select employee_id from employees where first_name like 'Sa%';

EMPLOYEE_ID
-----------
        161
        192
        194

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=9)
   1    0   TABLE ACCESS (FULL) OF 'EMPLOYEES' (Cost=2 Card=1 Bytes=9)

Statistics
----------------------------------------------------------
          7  recursive calls
          0  db block gets
         10  consistent gets
          0  physical reads
          0  redo size
        428  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)
          3  rows processed

SQL> select employee_id from employees where first_name like 'S%';

EMPLOYEE_ID
-----------
        100
        116
        117
        123
        128
        138
        161
        166
        173
        192
        194
        203
        205

13 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=12 Bytes=108)
   1    0   TABLE ACCESS (FULL) OF 'EMPLOYEES' (Cost=2 Card=12 Bytes=1
          08)

Statistics
----------------------------------------------------------
          7  recursive calls
          0  db block gets
         10  consistent gets
          0  physical reads
          0  redo size
        517  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)
         13  rows processed

SQL>
Followup:
the cardinality is a best guess ESTIMATE.  It was very close to dead on in this
case, pretty good for a guess no?
Rating: 4
Re: cpu costing November 09, 2003
Reviewer: Prince Faran from Pakistan
Ahaa! thats nice Tom. I have followed your instructions as under:-

SQL> drop table plan_table;

Table dropped.

SQL> @E:\Ora92\rdbms\admin\utlxplan.sql

Table created.

New plan_table is created. May I know plz.. Had the old plan_table any
performance impact or anyother issue until it is not used explicitily by the
user?

Thanks.
Followup:
it just had less "functionality"
Rating: 5
EXPLAIN PLAN Restrictions November 18, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
In the following document at
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/ex_plan.htm#15821
It says that:
<quote>
Oracle does not support EXPLAIN PLAN for statements performing implicit type
conversion of date bind variables. With bind variables in general, the EXPLAIN
PLAN output might not represent the real execution plan.

From the text of a SQL statement, TKPROF cannot determine the types of the bind
variables. It assumes that the type is CHARACTER, and gives an error message if
this is not the case. You can avoid this limitation by putting appropriate type
conversions in the SQL statement.
</quote>

Can you please elaborate on this and explain what the implications might be for
someone trying to tune the application and any precautions/guidelines that must
be kept in mind (while coding and/or while tuning).
Thanks,
Followup:
it should have said something like:

"if you do the really bad practice of relying on implicit conversions, you
should expect bad things to happen"

IMO, you should code:

select * from t where date_column = to_date( :bind, 'date format' )

you should NEVER NEVER code

select * from t where date_column = :bind;

the second query is a bug waiting to happen.  implicit conversions should always
be avoided.
Rating: 2
I think the response could do with some extra info November 23, 2003
Reviewer: Gary from Sydney, Aus
If you've got a PL/SQL block on the lines of
DECLARE
  v_start_date date;
  v_end_date date;
BEGIN
  SELECT result_col FROM <very_complex query>
  WHERE col_date BETWEEN v_start_date AND v_end_date
END;

If you want an explain plan of the query, you should not simply cut and paste
the SQL to do an explain of
  SELECT result_col FROM <very_complex query>
  WHERE col_date BETWEEN :v_start_date AND :v_end_date
because, as stated in the reference, the explain plan doesn't know that the
variables are defined as dates.
You have to go in and put the to_dates...
  SELECT result_col FROM <very_complex query>
  WHERE col_date BETWEEN to_date(:v_start_date,'format') AND
to_date(:v_end_date,'format')

Personally, I'd love an oracle initialization parameter to turn off implicit
data conversions
Rating: 3
Unique scans in a NESTED Loop December 15, 2003
Reviewer: A reader from NJ, USA
Tom,

I have the following questions :

If I have an EXPLAIN PLAN that shows the following plan :
   NESTED LOOPS
      TABLE ACCESS FULL OF A
      TABLE ACCESS BY ROWID OF B
         INDEX UNIQUE SCAN B_IDX_UQ

1. Suppose A has around say 20000 rows. For each row in A, is it a good idea to
perform index lookup followed by a table access of B (even if the index is
unique). If so how to avoid this
2. Suppose the index B_IDX_UQ is such that each index entry points to a
different table block, this will be almost equivalent to reading all of the
blocks in B that are below HWM.
    2a.How to improve the above EXPLAIN PLAN and will be better to use a FTS in
that case?
    2b.Does block selectivity matter (my understanding is block selectivity is
only for index range scans. Is that true)?
    2c.Does clustering factor also relevant in the above case (I remember you
mentioning in one of your discussion that clustering_factor is for index range
scans only and not for index unique scans).
3. The examples I see in books and the one on your site are showing index-range
scans for queries that use single tables. If I have a table-join and an EXPLAIN
PLAN like say,
      <Some join method such as Nested Loops or Hash Join>
          TABLE ACCESS BY INDEX ROWID C
              INDEX RANGE SCAN C_IDX
          TABLE ACCESS BY INDEX ROWID D
              INDEX RANGE SCAN D_IDX
and the D_IDX and C_IDX have both very poor clustering_factors how can we tune
the query above and resolve the problem. Can you provide an example showing
importance of clustering_factor in such kind of joins above.
4. Suppose a query is doing a FTS as opposed to index scan (and I know that you
always emphasize the fact that FTS is not a evil and index lookups are not
always good), is there any way to examine why it is performing a FTS. ie. What
things to look at (basically the steps/methods) to conclude that the CBO has
arrived at the execution plan of using a FTS. ie to basically conclude that CBO
has arrived at the FTS execution path because of such and such reasons...
Followup:
1) the answer is:

    yes
    no
    maybe
    sometimes

depends -- do you want the first row first?  if so, that NL join is great.
is B HUGE -- if so, NL might be really appropriate

tell you what -- analyze the tables, use the cbo and let it decide.

2) again, ANALYZE (and it depends on the size of B as well -- you are making the
unstated assumption that B is smaller or equal in size to A)

so 2a -- use cbo
   2b -- yes it does -- look at the last pair of plans below
   2c -- it comes into play in joins as well (they are almost like index range
         scans in a fashion)

ops$tkyte@ORA9IR2> /*
DOC>
DOC>drop table a;
DOC>drop table b1;
DOC>drop table b2;
DOC>
DOC>create table a
DOC>as
DOC>select rownum id, all_objects.* from all_objects;
DOC>
DOC>create table b1
DOC>as
DOC>select rownum id, rpad('*',2000,'*') data from all_objects;
DOC>alter table b1 add constraint b1_pk primary key(id);
DOC>
DOC>create table b2
DOC>as
DOC>select * from b1 order by reverse(id);
DOC>alter table b2 add constraint b2_pk primary key(id);
DOC>*/
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> analyze table a delete statistics;

Table analyzed.

ops$tkyte@ORA9IR2> analyze table b1 delete statistics;

Table analyzed.

ops$tkyte@ORA9IR2> analyze table b2 delete statistics;

Table analyzed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> set autotrace traceonly explain
ops$tkyte@ORA9IR2> select * from a, b1 where a.id = b1.id;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     TABLE ACCESS (FULL) OF 'A'
   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'B1'
   4    3       INDEX (UNIQUE SCAN) OF 'B1_PK' (UNIQUE)

ops$tkyte@ORA9IR2> select * from a, b2 where a.id = b2.id;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     TABLE ACCESS (FULL) OF 'A'
   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'B2'
   4    3       INDEX (UNIQUE SCAN) OF 'B2_PK' (UNIQUE)

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> analyze table a compute statistics;

Table analyzed.

ops$tkyte@ORA9IR2> analyze table b1 compute statistics;

Table analyzed.

ops$tkyte@ORA9IR2> analyze table b2 compute statistics;

Table analyzed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select * from a, b1 where a.id = b1.id;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2524 Card=29352
Bytes=61463088)
   1    0   HASH JOIN (Cost=2524 Card=29352 Bytes=61463088)
   2    1     TABLE ACCESS (FULL) OF 'A' (Cost=44 Card=29352 Bytes=2641680)
   3    1     TABLE ACCESS (FULL) OF 'B1' (Cost=956 Card=29353 Bytes=58823412)

ops$tkyte@ORA9IR2> select * from a, b2 where a.id = b2.id;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2524 Card=29352
Bytes=61463088)
   1    0   HASH JOIN (Cost=2524 Card=29352 Bytes=61463088)
   2    1     TABLE ACCESS (FULL) OF 'A' (Cost=44 Card=29352 Bytes=2641680)
   3    1     TABLE ACCESS (FULL) OF 'B2' (Cost=956 Card=29353 Bytes=58823412)

ops$tkyte@ORA9IR2> select /*+ index(b1 b1_pk) */ * from a, b1 where a.id =
b1.id;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=10674 Card=29352
Bytes=61463088)
   1    0   MERGE JOIN (Cost=10674 Card=29352 Bytes=61463088)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'B1' (Cost=9846 Card=29353
Bytes=58823412)
   3    2       INDEX (FULL SCAN) OF 'B1_PK' (UNIQUE) (Cost=61 Card=29353)
   4    1     SORT (JOIN) (Cost=828 Card=29352 Bytes=2641680)
   5    4       TABLE ACCESS (FULL) OF 'A' (Cost=44 Card=29352 Bytes=2641680)

ops$tkyte@ORA9IR2> select /*+ index(b2 b2_pk) */ * from a, b2 where a.id =
b2.id;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=29396 Card=29352
Bytes=61463088)
   1    0   NESTED LOOPS (Cost=29396 Card=29352 Bytes=61463088)
   2    1     TABLE ACCESS (FULL) OF 'A' (Cost=44 Card=29352 Bytes=2641680)
   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'B2' (Cost=1 Card=1 Bytes=2004)
   4    3       INDEX (UNIQUE SCAN) OF 'B2_PK' (UNIQUE)

ops$tkyte@ORA9IR2> set autotrace off
ops$tkyte@ORA9IR2>

3) use the CBO!

4) search for 10053 on this site and if you have my new book Effective Oracle by
Design -- see chapter 6.
Rating: 2
Pls help December 16, 2003
Reviewer: A reader from NJ, USA
Tom,

Thanks much for your response. However, I am not able to understand what you are
trying to explain in that example and what numbers to look for in that example.
Is it that you are illustrating how the explain plan changes after the analyze
or is it something more to that that I am not clear of. Pls. help
Also, for Qn 3, you say use CBO. Can you pls. explain.

So are you saying that if you use CBO and appropriately analyze the tables and
indexes, it will automatically come up with a good optimal plan and if it is
then how to ensure that it is the correct optimal plan?
Followup:
look at the plans -- the plans are different for the two queries and the ONLY
difference between the two is the cluster factor for the indexes.  so, in answer
"Does clustering factor also relevant in the above case" - the answer is "yes"
and "here is an example showing it"

my point for #3 was -- use the CBO and let it pick.  It has the rules for
choosing which to use.  Your question:

3. The examples I see in books and the one on your site are showing index-range
scans for queries that use single tables. If I have a table-join and an EXPLAIN
PLAN like say,
      <Some join method such as Nested Loops or Hash Join>
          TABLE ACCESS BY INDEX ROWID C
              INDEX RANGE SCAN C_IDX
          TABLE ACCESS BY INDEX ROWID D
              INDEX RANGE SCAN D_IDX
and the D_IDX and C_IDX have both very poor clustering_factors how can we tune
the query above and resolve the problem. Can you provide an e

doesn't really "make sense".   poor clustering factors (don't really exist, they
are facts -- they are not "good" or "poor" really, they are just facts) do not
lead to poor performance.  I cannot tell you have to tune a hypothetical query
with "some join technique"

that is the job of the optimizer.
Rating: 3
Poor clustering factor December 16, 2003
Reviewer: A reader from NJ, USA
Tom,

I think I am not asking the question correctly to you.

1. My question is that if the clustering factor is poor (say clustering factor =
num of rows for both C and D) and the execution plan is as per shown in my
question, then it would generate lot of logical I/Os (because of index lookup
followed by a table lookup) and a FTS can be much better in that case. Is my
understanding correct?
2. If 1 is true and if we find large number of logical I/Os, then how do we
conclude from that execution plan that :
   Analyzing the table and indexes might provide a better plan?
   It may be because of the poor index clustering factors on those data tables
or that it is just due to data volume (because some queries as such may hit more
data). ie how to back-track from the explain plan back to data distribution?
(because in the example you had shown, as soon as the data was analyzed,
different execution plans were generated based on the index clustering factor
and distribution of data).
    So if I am just given an execution plan, where and how to start and how do I
go from there to conclude that it is because of data distribution, clustering
factor etc. ie.
How do we conclude whether the plan is optimal/correct?
Sorry for keep you asking same questions as I am getting really confused about
the way the CBO is generating execution plans and I am not really sure how to
tie-back the execution plan with data distribution. A detailed
explanation/example on the above questions would be of great help.
3. In the NL question above, you mentioned that NL will be good if the table B
is HUGE. But if the index unique scans for B are such that each index entry
points to a different data block, then how does NL help in that case. My doubt
is if the index entries in B point to different data blocks, then by the time
the nested loop comes to read the same data block, it might have aged out of the
buffer cache in which case it would have to do a physical read causing too much
I/O. Your explanation on this would be of great help.
Followup:
1) no, that plan looks very innocent to me.  I do not see what you see.

consider:

ops$tkyte@ORA9IR2> select /*+ USE_HASH( t1 t2 ) */ *
  2    from t1, t2
  3   where t1.x = t2.a
  4     and t1.y = 5
  5     and t2.b = 5
  6  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=52)
   1    0   HASH JOIN (Cost=3 Card=1 Bytes=52)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=1 Card=1 Bytes=26)
   3    2       INDEX (RANGE SCAN) OF 'T1_IDXY' (NON-UNIQUE) (Cost=1 Card=1)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'T2' (Cost=1 Card=1 Bytes=26)
   5    4       INDEX (RANGE SCAN) OF 'T2_IDXB' (NON-UNIQUE) (Cost=1 Card=82)

it'll use the index to get A ROW (in this example).  so what if the clustering
factor is "poor" or not.

it is a function of the cardinality, not the use of the index per se.

2) if the numbers in the autotrace are way wrong (estimated cardinalities) that
is what indicates an analyze is needed (or a "stronger analyze" with histograms
or something).  You cannot go from "hi LIO's -> you need to analyze", there is
no correlation
Rating: 3
Pls. clarify December 16, 2003
Reviewer: A reader from NJ, USA
Tom,

In the above example, you assume that only 1 row is retrieved.
 1. Is that from the Card= column  in each step of the explain plan? If not how
do you say it gets 1 row.
 2. My question is what if a considerable number of rows are retrieved in both
the steps of the HASH JOIN by using INDEX then TABLE approach and the clustering
factor is low. In that case, my understanding is FTS and INDEX then TABLE
approach will vary considerably. Is that correct?
 3. Out of the 3 info. Bytes= Cost= and Card= which must be taken into
consideration/relevant
Followup:
1) exactly, card=1 says "optimizer thinks a single row"

2) is 100,000 rows considerable?   maybe it is, maybe it is not.  It depends on
the size of the underlying objects.  It is a function of the number of IO's the
optimizer things it'll have to do.

If the cost of doing the index IO's plus table IO's exceeds the cost of full
scanning the table using multi-block IO, then YES (but -- that is exactly what
the optimizer is tasked with doing) the full scan will be "cheaper".

3) all of it.
Rating: 3
Tuning??? December 17, 2003
Reviewer: A reader from NJ, USA
Tom,

1.If CBO is capable of deciding the right path when the tables and
indexes/indexed columns are analyzed properly, then what is that we need to do
to tune a query. How do we conclude that our query is not optimal? Is it by
looking at the Logical I/Os
2. In some of your earlier discussions that the Cost and efficiency of a query
are not inter-related, but here you say the Cost is also important/relevant. Can
you explain/clarify on this.
3. If Card= and Bytes= are also relevant, what should we focus on in our queries
about these figures?
Followup:
1) tune processes and applications.

It goes like this:

user: "system is slow"
you:  "ok, the entire thing, an application, a screen, what is slow?"
user: "oh, ok -- this application is slow"
you:  "ok, the entire thing, a part of it, a specific feature?"
user: "oh, ok -- when I push this button it takes a long time, I push that
button
       alot"

you: "ok, we'll get on it"

now you have instrumented your code, so you run say a tkprof on that section of
that application.  You look for OBVIOUS low hanging fruit (i "grep ^total" in
the tkprof and look for "big" totals -- things taking a long time).  You look at
them.

You either decide "boy, that query does a ton of work -- can we do it better" or
even more optimally "boy, we got that algorithm wrong didn't we.  who did this
row by row stuff -- look, this entire process should be this "update" statement.
"

2) cost is relevant, it helps you understand WHY the optimizer is doing what it
does.  without it, we'd never be able to understand "why"

3) you should make sure they are in line with reality.  Small example:

ops$tkyte@ORA920> create table t as select * from all_objects;

Table created.

ops$tkyte@ORA920> create index t_idx on t(object_id);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table t compute statistics
  2  for table
  3  for all indexes;

Table analyzed.

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

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=316 Bytes=30336)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=3 Card=316 Bytes=30336)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=126)

Hmmm, optimizer things 316 rows -- that is OBVIOUSLY wrong for we know that
object_id is near unique!!  something is amiss.  garbage in, garbage out.  We
need to figure this out, what is wrong -- oh, we didn't actually take a good
look at the column data -- lets do that:

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> select * from t where object_id = 55;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=96)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=3 Card=1 Bytes=96)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=1)

ops$tkyte@ORA920> set autotrace off

ahh, now it got it right.  good data = better plans.

if the card= is way off from reality and the plan is bad, there is a major
problem there -- and the garbage data (or insufficient data) provided to the
optimizer is the cause.
Rating: 3
Execution Plan vs Explain Plan December 19, 2003
Reviewer: A reader from NJ, USA
Tom,

Can you pls. provide me an example of what the Card value in the Execution plan
vs Explain plan means. My understanding is that the Execution Plan provides the
actual cardinality whereas the Explain plan provides the estimated cardinality.
1. Is my understanding correct. It would be really helpful if you could provide
me an example to illustrate this.
2. Pls. provide me info. on where to find the documentation for the 10053 event.
I like to have a very introductory/basic understanding of what this event means,
how to arrive at costs of various access paths, how to interpret the 10053
results etc. I was trying to understand some materials by Wolfgang Brietling but
it looks too advanced to me. Please point me to very basic materials on this

Thanx
Followup:
only if you tell me what the difference between an "explain" and "execution"
plan are?

to me -- they are the same.

There is an explain plan that shows the execution plan.

In an explain plan, we can see estimated cardinalities, the number of rows the
optimizer is expecting.

In a tkprof report, we can see the execution plan.  this time, the ACTUAL number
of rows OBSERVED flowing through each phase of the plan is viewable.

Metalink has documents on 10053.

As well, my new book "effective oracle by design" has a section on it.  I did it
"simple" I think.
Rating: 3
AUTOTRACE December 21, 2003
Reviewer: A reader from NJ, USA
Tom,

I was going through your new book 'Expert Oracle by Design' where you are
discussing about AUTOTRACE. In the AUTOTRACE output, you have two sections :
Execution plan section and the statistics section :
1. Is execution plan section equivalent to the EXPLAIN PLAN and the Card= value
shows only the estimated cardinality.
2. The 'Rows Processed' Statistics section in the Statistics section shows the
actual number of rows processed.
3. If I do not see the Cost=,Card= etc on the explain plan, does that mean that
the CBO is not being used.
Is my understanding on 1,2 and 3 correct. If not please explain me with an
example.
4. If I take a TKPROF report, where do I see the estimated rows and the actual
rows processed.

THanks
Followup:
1) yes

2) yes

3) yes

4) you do not see "estimated" (thats why I often ask for AUTOTRACE *and*
TKPROF's so I can see "actual" vs "estimated").  autotrace = estimated.  tkprof
= "what actually happened".

you see the actual rows processed right in the tkprof -- edit one and it'll just
be staring you in the face.
Rating: 4
Table not analyzed December 21, 2003
Reviewer: A reader from NJ, USA
Tom,

I have now clearly understood what each section means. So my question on this is
:
If the 'Rows Processed' (Card=) value from Autotrace section (Question 1 above)
has a huge disparity with the 'Rows processed' in the Statistics section, then
it means
   1. Means data has not been properly analyzed. Is that true?
   2. Does it also mean that my query is not properly tuned?

Thanks
Followup:
it COULD mean #1.  It definitely means "something is wrong".

it could be insufficient stats (eg: add histograms and all of a sudden the
optimizer "knows more").

it could be a "product issue" (aka: a bug)

it definitely does not imply or mean #2!
Rating: 4
Pls. clarify December 21, 2003
Reviewer: A reader from NJ, USA
Tom,

In one of the discussion title "Physical Reads vs Logical Reads" where you
compare colocated vs. disorganized tables, you are stating the following :

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.

In the above explanation, could you pls. explain me as to what you mean by
"- 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)"

2. In your book Expert-one-on-one Oracle, you mention the following (page 279)
<Quote>
Suppose you have a table where the rows have a primary key populated by a
sequence. As data is added to the table, rows with sequential sequence numbers
are in general 'next' to each other. The table is naturally clustered, in order,
by the primary key (since the data is added in more or less that order). It will
not be strictly clustered in order by the key of course(we would have to use an
IOT to acheive that) but in general....
</Quote>
In the above para, can you pls clarify this with an example as to what you mean
by :
"It will not be strictly clustered in order by the key of course(we would have
to use an IOT to acheive that"

3. What is meant by naturally clustered vs. automatically clustered vs.
uniformly distributed?
Followup:
what I was saying is -- if the rows are next to eachother on the block -- then
the first row we fetched will perform the logical IO to retrieve the block from
the buffer cache.  Rows 2-15 will be read from that same block -- no need to go
back to the buffer cache for the next row, we already have it.  Hence, we do not
need to do anymore LIO's to get the rows from the table.  If the 2cnd row WAS
NOT next to the first row on that block -- we would have discarded that block
and done another LIO to get row 2.

I need no example really, you just need to know that rows will go in where they
fit.  This is typically at the "end" of the table.  The rows will NOT be in
order of the sequence - they will be CLOSE, but they will not be in order of the
sequence.  (think of the session that retrieves a sequence.nextval -- waits 10
seconds -- then inserts the row.  In that 10 seconds, perhaps 100 other rows
were inserted using sequence.nextval.  This row will be "100 away from its
prior/next values).  Unless you use an IOT or some other structure that enforces
placement of data -- you are using a HEAP table and in a HEAP, rows just go
where they fit.

Naturally clustered means that due to the NATURE of the way the data is
inserted, the data will "naturally" (as a side effect, because of) be ordered
nicely.  If you insert 1, 2, 3, 4, 5, 6, 7, 8, .... infinity into a HEAP table,
in order, most of the times you'll observe the data is physically organized that
way. "it naturally" did it.

the other two terms, not familiar with them.
Rating: 3
How to determine if data colocated January 06, 2004
Reviewer: A reader from NJ, USA
Tom,

In the case of heap tables, we can quickly determine whether a column say X
populated by a sequence generated is naturally clustered (or atleast in close
proximity) or not by using the block number of the rowid using the DBMS_ROWID
package. However since IOTs do not have the concept of ROWID, how can we
determine that the data is actually clustered in case of IOT.

Thanks much
Followup:
well, the dbms_rowid thingy isnt very "scalable" is it?  I mean, you have to
look back a row and forward a row for each row.  how do you do that for say
100,000 rows?

In an IOT, the data is ALWAYS clustered by the primary key (thats the point of
the structure entirely!).  It is the other columns that might be clustered and
for that, I would use analyze -- just like I would for a "real" table

ops$tkyte@ORA920PC> create table t ( x int primary key, y int, z int, data
char(80) default 'x' )
  2  organization index;

Table created.

ops$tkyte@ORA920PC> insert into t select rownum, rownum, dbms_random.random, 'x'
from all_objects;

30197 rows created.

ops$tkyte@ORA920PC> create index t_idx1 on t(y);

Index created.

ops$tkyte@ORA920PC> create index t_idx2 on t(z);

Index created.

ops$tkyte@ORA920PC> analyze table t compute statistics for table for all
indexes;

Table analyzed.

ops$tkyte@ORA920PC> select index_name, clustering_factor from user_indexes where
table_name = 'T';

INDEX_NAME                     CLUSTERING_FACTOR
------------------------------ -----------------
SYS_IOT_TOP_42421                              0
T_IDX1                                       400
T_IDX2                                     30146
Rating: 3
Row proximity January 06, 2004
Reviewer: A reader from NJ, USA
Tom,

In a heap table what would be then the best way to determine if a table is
naturally clustered other than the query using DBMS_ROWID package as per my
question above?
Followup:
tell me how you would tell with dbms_rowid in the first place!  for a
non-trivial sized table.

analyze the index (if you don't have an index - you don't care). look at the
clustering factor.
Rating: 5
can we ignore the exec plan? January 30, 2004
Reviewer: steve
Hi Tom,

If we should account on CBO to find best execution plan,
1) should we care about the plan while tuning the sql?
2) can we just focus on statistics generated by SQL_TRACE such as LIO, PIO, or
WAIT number and ignore the plan while tuning the sql?

Thanks!

Steve
Followup:
1) yes
2) no

the cbo is a mathematical model.  it takes inputs - generates outputs.  You need
to sometimes look at a plan and say "gee, thats not right, lets hint to see if
we are right and it is wrong and if so -- look to see why it might be so,
perhaps optimizer_index_caching or optimizer_index_cost_adj needs to be tweaked"
Rating: 5
Have you heard of this ... ? February 13, 2004
Reviewer: Gabe from Internet
Tom,

Can you please express your opinion regarding the following approach to
“performance tuning”?

Background:
Big Java/Oracle web-based implementation for mission critical application (core
functionality for the business) … RUP methodology (tones of “artifacts” lots of
them out of date by now) … brand new data model/architecture … large data
migration from legacy which they only started to analyze (build use-cases) …
project late … dictatorial (alternative opinions not tolerated) … sweat-shop.
Extensive use of a pl/sql (a good architectural decision) … all access is
supposed to be though the pl/sql layer … but performance tuning was perceived to
be just another effort at the end of the project.

Task (in the name of performance tuning):
I was asked to extract a unique (???) list of all the selects (no updates or
deletes) from the database, run explain plan for all of them and produce a list
of top-n by cost.

Note: the Selects are to be extracted from the pl/sql code, view definitions
(????), etc. … not captured with sql_trace or from v$sql or from v$sql_plan or
whatever (the application is not very stable and quite buggy at this time).

I think it is a complete waste of time because:
1.    The volume of data in the test database is only a fraction of the real
data after migration (half of tables are actually empty)
2.    There is no assurance the environment is isolated
3.    Short of writing a parser … I have to manually extract and edit the sql
(packaged functions not listed in the package spec used in the select list,
convert pl/sql variable to actual values or placeholders for bind variables,
remove the into clause, etc.) … the code is still changing and hence there is no
assurance the statements I’m extracting will actually be there later on … unless
I’m automating the whole thing the process is not repeatable (and I was only
given 2-3 days to go though all the code … +125K lines in user_source)
4.    etc.

Am I missing something? My understanding is that the cost is a formula of a cpu
cost and io cost calculated at the time the explain plan is executed … the cost
of one sql doesn’t have anything to do with the cost of another sql … so, all
this comparison of hundreds and hundreds of painfully extracted (and doctored)
sql statements means (imho) … zilch, nada. As I was going through the code I did
identify few bugs … so the byproducts of this effort could be positive … but the
whole thing seems to me totally dumb.

Thanks
Followup:
well, forgetting for a moment that "explain plan lies" and "tkprof/v$sql_plan
can only tell the truth" (and would be tons easier than extracting sql from
plsql....)

and forgetting for a moment that the cost of a query only indirectly relates to
its performance (it should be direct, but hey -- reality -- it is not)

and forgetting that unless you have stats that represent reality (and you seem
not) the cost is just a random number.....

Nah, this is a total and utter waste of time.  You must

a) get a stable, not so buggy thing
b) trace the "slow parts"
c) fix their algorithms and or sql (sql isn't always the culprit, it is as
often, if not more often the algorithms employed that are the issue)

This is a really "bad idea"
Rating: 5
Question on tuning February 13, 2004
Reviewer: PRS from USA
I have following query.
SELECT NVL (SUM (DECODE (rc_status, 'NEW', 1, 0)), 0)
      ,NVL (SUM (DECODE (rc_status, 'PEND', 1, 0)), 0)
  FROM ps_rc_case a, ps_wsi_rccase_flds b
 WHERE a.case_id = b.case_id
   AND a.business_unit = b.business_unit
   AND b.wsi_sub_assign_to = '50003540'
   AND a.rc_status IN ('NEW', 'PEND')

Table ps_rc_case and ps_wsi_rccase_flds have 7.5 million rows. It is using the
right index. But this query takes 13 seconds which is not acceptable to online
application.
 We have ORACLE 9.2.0.4.0 on Sun-solaris 9 (E4500). Each indexe has 1924
extenets on a LMT defined as 4MB uniform size. Any idea will be appereciated as
we want to have less than a second turnaround time required for this.
Follwing is the trace with explain plan.
NVL(SUM(DECODE(RC_STATUS,'NEW',1,0)),0) NVL(SUM(DECODE(RC_STATUS,'PEND',1,0)),0)
--------------------------------------- ----------------------------------------
                                      3                                        6

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3740 Card=1 Bytes=33
          )

   1    0   SORT (AGGREGATE)
   2    1     HASH JOIN (Cost=3740 Card=12046 Bytes=397518)
   3    2       INDEX (RANGE SCAN) OF 'PSEWSI_RCCASE_FLDS' (NON-UNIQUE
          ) (Cost=308 Card=88831 Bytes=1332465)

   4    2       INDEX (FAST FULL SCAN) OF 'PS7RC_CASE' (NON-UNIQUE) (C
          ost=2934 Card=1011449 Bytes=18206082)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      30965  consistent gets
          0  physical reads
          0  redo size
        301  bytes sent via SQL*Net to client
        275  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed
Followup:
well, hows about you tell us

a) what indexes go with what tables
b) what indexes in their entirety (names, list of columns, and the table to
which they belon) exist to be used....

c) AND b.wsi_sub_assign_to = '50003540' -- is that a constant for real or a bind
variable?  if a constant -- how many rows match that.  if a bind, what is

select min( count(*) ), max(count(*)), avg(count(*))
  from ps_wsi_rccase_flds
group by wsi_sub_assign_to

d) what is  select count(*) from the other table where a.rc_status IN ('NEW',
'PEND')
Rating: 4
Answers to the questions you asked February 13, 2004
Reviewer: PRS from USA
Tom,
   Following are the answers for the questions you asked.

a. Table PS_RC_CASE use the index PS7RC_CASE. Table PS_WSI_RCCASE_FLDS uses the
index PSEWSI_RCCASE_FLDS.

b. Index PS7RC_CASE is defined on columns CASE_ID,BUSINESS_UNIT,RC_STATUS. Index
PSEWSI_RCCASE_FLDS defined on columns wsi_sub_assign_to,case_id, business_unit.

Statistics for both the index shown below.
For Index PS7RC_CASE on PS_RC_CASE
select * from dba_indexes where index_name = 'PS7RC_CASE'
LEAF_BLOCKS30496                                            DISTINCT_KEYS
7561526
AVG_LEAF_BLOCKS_PER_KEY 1
AVG_DATA_BLOCKS_PER_KEY 1
CLUSTERING_FACTOR420853                                     NUM_ROWS7561526
                                       SAMPLE_SIZE 7561526
LAST_ANALYZED 2/13/2004 3:07:50 PM
For Index psewsi_rccase_flds on PS_wsi_RCCASE_flds
LEAF_BLOCKS 26316
DISTINCT_KEYS 7561525
AVG_LEAF_BLOCKS_PER_KEY 1
AVG_DATA_BLOCKS_PER_KEY 1
CLUSTERING_FACTOR5837952                                    NUM_ROWS7561525
                                       SAMPLE_SIZE 7561525
LAST_ANALYZED 2/13/2004 1:59:49 PM

c. It is a bind variable. Following is the query result.
select min( count(*) ), max(count(*)), avg(count(*))
  from ps_wsi_rccase_flds
group by wsi_sub_assign_to
MIN(COUNT(*)) MAX(COUNT(*))     AVG(COUNT(*))
------------- ------------- ---------------
1             7477302        72014.533

D. The counts are as inder for the query you asked.
SELECT COUNT(*)
FROM PS_RC_CASE WHERE
rc_status IN ('NEW', 'PEND')
COUNT(*)
--------
6929

      Any help is appreciated.
Thanks,
PRS
Followup:
well, not much is going to help this puppy (except if you only go after the
count(*)'s = 1 that is!)

with an average of 72k rows per wsi_sub_assign_to records per value, well, it's
got tons of stuff to shift through there doesn't it.  or 7,000 index probes (if
we indexed differently) from ps_rc_case into the other table.

Either way, it won't be brilliantly fast -- it is pure "math" at work here.

We don't want to drive from B into A (72k rows is too big).  We'd  have to drive
from A into B -- but even then, it is nasty with 7k index probes (if you indexed
case_id,business_unit,wsi_sub_assign_to in ps_wsi_rccase_flds in that order
(well, the last field being last is the relevant one) it could index probe but I
see that as being a SLOWER alternative to what you have probably)

sorry -- but without changing the underlying structures, setting them up
specifically to answer this sort of question, I do not see you getting there
from here.  Think about (after looking at the above numbers) what work must be
done in order to get that very simple looking row.

We either find 72k rows in table B and then goto table A row by row
or
we have 7k rows in table A and then goto table B row by row
or
we do what it is doing which is to goto both and slam them together in bulk
(most likely the fastest).

You can try this:

index on a(rc_status,business_unit,case_id)
index on b(business_unit,case_id,wsi_sub_assign_to)

it can then use these two indexes to answer the question,just a thought.  But
look at the data, use your knowledge of it to answer the question "what could it
do to do this fast".  If you come up with an idea, let us know and I'll try to
show you the way to the sql that does that.
Rating: 5
Howto explain plan sql with type casting ? February 16, 2004
Reviewer: A reader
(Oracle 9.2)

I have a statement like ...

select * from t where id in (SELECT * FROM TABLE(CAST(arr AS NUMBER_ARRAY)));

How do I run an explain plan on it?

I even took the _transformed_ sql from v$sqltext:

SELECT * from t where id = (SELECT * FROM TABLE(CAST(:b1 AS NUMBER_ARRAY)))

but still ...

abc@abc> explain plan for
  2  SELECT * from t where id = (SELECT * FROM TABLE(CAST(:b1 AS
NUMBER_ARRAY)));
SELECT * from t where id = (SELECT * FROM TABLE(CAST(:b1 AS NUMBER_ARRAY)))
                                                      *
ERROR at line 2:
ORA-00932: inconsistent datatypes: expected - got CHAR

Thank you.
Followup:
if you want to use explain plan -- which assumes ALL binds are character strings
regardless -- you'll have to put in a hard coded value there:

ops$tkyte@ORA920PC> explain plan for
  2  select *
  3    from t
  4   where id = (select * from table( cast( number_array(1,2,3) as
number_array ) ))
  5  /

Explained.

ops$tkyte@ORA920PC> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
--------------------------------------------------

-------------------------------------------------------------------------------
| Id  | Operation                               |  Name        |Rows|Bytes|Cst|
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                        |              |  1 | 100 |  2|
|   1 |  TABLE ACCESS BY INDEX ROWID            | T            |  1 | 100 |  2|
|*  2 |   INDEX UNIQUE SCAN                     | SYS_C007505  |100 |     |  1|
|   3 |    COLLECTION ITERATOR CONSTRUCTOR FETCH|              |    |     |   |
-------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("T"."ID"= (SELECT /*+ */ VALUE(KOKBF$) FROM
              TABLE(CAST("NUMBER_ARRAY"(1,2,3) AS "NUMBER_ARRAY") ) "KOKBF$"))

Note: cpu costing is off

17 rows selected.
Rating: 5
Howto explain plan sql with type casting ? February 17, 2004
Reviewer: A reader
I did try a to_number(:c) ... guess that did not get automagically cast to a
number_array with one element.

Thank you again.
Rating: 4
question February 17, 2004
Reviewer: PRS from USA
Hi Tom,
   I have following query. It does a full table scan on one of the table inspite
of having an index. There are only 63 rows. Looks like oracle optimizer behaving
really odd.

Query:
SQL> l
  1  SELECT SUM(CLAIM_AMOUNT)
  2    FROM PS_RCFCASE_COMPL A,
  3         PS_WSI_RCCASE_FLDS B
  4   WHERE A.CASE_ID = B.CASE_ID
  5     and a.business_unit = b.business_unit
  7*    AND B.WSI_SYSTM_DWNTM_ID = '91'
SQL> /

SUM(CLAIM_AMOUNT)
-----------------
        -37228.56

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=13577 Card=1 Bytes=2
          9)

   1    0   SORT (AGGREGATE)
   2    1     HASH JOIN (Cost=13577 Card=101202 Bytes=2934858)
   3    2       INDEX (FAST FULL SCAN) OF 'PSDWSI_RCCASE_FLDS' (NON-UN
          IQUE) (Cost=2525 Card=100952 Bytes=1514280)

   4    2       TABLE ACCESS (FULL) OF 'PS_RCFCASE_COMPL' (Cost=8016 C
          ard=7580277 Bytes=106123878)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     110131  consistent gets
      82882  physical reads
       5400  redo size
        209  bytes sent via SQL*Net to client
        275  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

   Indexes defined as under.
   Table Name PS_WSI_RCCASE_FLDS -
   Index Name - PSDWSI_RCCASE_FLDS
   COlumn names - (CASE_ID,BUSINESS_UNIT,WSI_SYSTM_DWNTM_ID)

   Table Name - PS_RCFCASE_COMPL
   Index Name - PS_RCFCASE_COMPL
   Column names -(CASE_ID,BUSINESS_UNIT)
  It is not using the index on PS_RCFCASE_COMPL. Both the tables have 7.5
millions rows. Index and tables are analayzed. Index 100% analyzed and table 35%
analyzed.

   This query takes 2 minutes to come back. It is just oracle optimizer not
making the use of an index.

Any help is appreciated.
Followup:
why would you want it to use the index?  did you want it to run *really truly
slow*???

tell me -- what index do you believe would be useful here and more importantly
-- WHY?

I see this query:

 1  SELECT SUM(CLAIM_AMOUNT)
  2    FROM PS_RCFCASE_COMPL A,
  3         PS_WSI_RCCASE_FLDS B
  4   WHERE A.CASE_ID = B.CASE_ID
  5     and a.business_unit = b.business_unit
  7*    AND B.WSI_SYSTM_DWNTM_ID = '91'

Ok, either A or B could be used to "drive" the query.

So, if we use A, we can index into B but we would have to index into B 7,580,277
times!!!  (since there is no predicate on A, every row in A has to be
considered, we'd have to look into B's index 7.5 MILLION times.  Not very
efficient)

If we choose B to drive, the optimizer is thinking "after predicate of ..._id =
'91' is applied" there will be 100,952 rows that we would have to perform an
index range scan on into A (3 LIO's to read the index plus one table access by
index rowid -- 400,000 IO's for that).

So, are these numbers accurate.
If you "hint it" to use the plan you think is much much better -- is it?
If it is, have you looked at optimizer_index_* init.ora parameters (search for
them on this site to read about them)
Rating: 2
Answer February 17, 2004
Reviewer: PRS from USA
Tom,
   I changed the same query following way and it comes back in less than a
second. Also created index PSDWSI_RCCASE_FLDS only on WSI_SYSTEM_DWNTM_ID
column.

SELECT /*+ ordered index(b PSDWSI_RCCASE_FLDS) use_nl(a) */
SUM(CLAIM_AMOUNT)
FROM  PS_WSI_RCCASE_FLDS B, PS_RCFCASE_COMPL A
WHERE A.CASE_ID = B.CASE_ID
and a.business_unit = b.business_unit
AND B.WSI_SYSTM_DWNTM_ID = '91'

    Basically it was doing a FAST FULL SCAN on index
PSDWSI_RCCASE_FLDS which is same as almost full table scan. So I just rebuild
the index with only one column and it worked fine. Also I use hints.

Thanks,
PRS
Followup:
without the hints, what happens -- you changed the schema, the first schema
perhaps wasn't "right" for using the index.

I'd like to totally LOSE The hints.
Rating: 3
questions February 18, 2004
Reviewer: PRS from USA
Hi Tom,
   I also do not like to use hints. But what option do I have here. ORACLE does
not provide any other option here. This are the table designed this way. We have
a good machine with 4 CPU sun e4500 with 8GB RAM. Why Oracle is doing hash joins
and whole thing goes for a toss. This is a simple data volume with 7.5 million
rows in each table. ORACLE claims that it's CBO is the best. But in some case I
really doubt. It is a nightmare sometime to tune some of the queries. You are
left with no option. This is just my perception specially in using the HINTS. I
hate using hins. But I have no option here. What do you think?

Always appreciate your input. You are the GURU. But sometime this CBO is
horrible.

Thanks,
PRS
Followup:
you changed the index -- what is the plan WITHOUT hints using autotrace with the
NEW SCHEMA you have.

forget the old stuff you posted, quite simply "not relevant".  you changed the
entire situation. we are back at square "uno" here -- forget everything that was
said before, you changed it ALL.
Rating: 5
slow query in stored procedure but very fast as single SQL February 23, 2004
Reviewer: A reader
Hi

I have a query inside a procedure, the procedure receives the parameter and pass
that parameter to the query inside as a condition. The thing is the query by
itself runs very fast (instantaneously) no matter if I use bind variables or
literals but inside the procedure it takes 5 minutes!!! I dont understand why is
this, I thought queries like this inside procedures can be simulated by using
bind variables such as

var x number
exec :x=7818654

query here
where phone=:x

But NO, it does not produce the same effect, I am forced to execute the
proceudre everytime I want to test a new HINT!!! How so? Any workaround?
Expected behaviour (hope not... if that's the case we might as well change
rdbms!)
Followup:
can you show us

a) a tkprof from sqlplus (showing fast)
b) a tkprof from plsql (showing slow)

to start with.
Rating: 5
here is the tkprof February 24, 2004
Reviewer: A reader
query:

in sqlplus
var x varchar2(24)
exec :x := 'AA'

here it´s fast because it uses predicate ph.phone = :x

SELECT
                         be.pa_segment_mkt, bc.SEGMENT, co.customer_type,
                         be.pa_segment_org
                    FROM sysadm.ps_cm_phone ph,
                         sysadm.ps_bo_cm_profl_dtl d,
                         sysadm.ps_bo_cm_profile x1,
                         sysadm.ps_bo_rel a,
                         sysadm.ps_bo_rel_cat_item b,
                         sysadm.ps_bo_rel_cm c,
                         sysadm.ps_pa_bc_ext be,
                         sysadm.ps_bc bc,
                         sysadm.ps_rd_company co
                   WHERE ph.phone = :x
                     AND a.rel_type_id = b.rel_type_id
                     AND a.start_dt <=
                            TO_DATE (TO_CHAR (SYSDATE, :"SYS_B_0"),
                                     :"SYS_B_1"
                                    )
                     AND a.end_dt >
                            TO_DATE (TO_CHAR (SYSDATE, :"SYS_B_2"),
                                     :"SYS_B_3"
                                    )
                     AND b.bo_rel_category_id IN (:"SYS_B_4", :"SYS_B_5")
                     AND a.rel_type_id = c.rel_type_id
                     AND a.bo_id_1 = c.bo_id_1
                     AND a.role_type_id_1 = c.role_type_id_1
                     AND a.bo_id_2 = c.bo_id_2
                     AND a.role_type_id_2 = c.role_type_id_2
                     AND c.bo_id = d.bo_id
                     AND c.profile_cm_seq = d.profile_cm_seq
                     AND x1.end_dt >
                            TO_DATE (TO_CHAR (SYSDATE, :"SYS_B_6"),
                                     :"SYS_B_7"
                                    )
                     AND x1.bo_id = d.bo_id
                     AND x1.cm_type_id = d.cm_type_id
                     AND x1.bo_id = a.bo_id_1
                     AND d.eff_status = :"SYS_B_8"
                     AND d.cm_id = ph.cm_id
                     AND a.bo_id_2 = bc.bo_id
                     AND c.bo_id = be.bo_id
                     AND c.bo_id = co.bo_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        1      0.01       0.00          0          3          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.02       0.00          0          3          0           0

in pl/sql

CANT PASTE,it has been running over one hour already and not finishing, the
problem is in PL/SQ it does not use ph.phone=phone!!!
Followup:
I don't see the plan, the plan is what is important -- you need to exit sqlplus
before running tkprof.

and try adding /*+ first_rows */ to the plsql based one, see what happens there.

does this query really get zero rows??  even in the plsql ?
Rating: 4
To "A reader" February 24, 2004
Reviewer: Kim Berg Hansen from Middelfart, Denmark
Hi, "A reader"

In your last sentence you say "ph.phone=phone"...

Does that mean that you have a PL/SQL variable or argument with the same name as
a column in your table ?
That could be the case - if so then rename the variable/argument.
February 24, 2004 Munzer -- Thanks for the question regarding "PL/SQL execeptions", version 8i
You Asked
Tom:

1.  If you have a PL/SQL procedure that generates HTML tags and does basically
inserts into several tables, what exception handler would you be using.  E.g
"WHEN OTHERS" .

2. same thing If you are doing "SELECT" would you be checking for "WHEN
NO-VALUE-FOUND" or else?

Thank you
and we said...
1) i would use named exceptions for those exceptions I expected.

I never ever use when others (that basically means you don't care, any error is
OK).  I would catch things like dup_val_on_index or use pragma exception_init to
map specific errors i was EXPECTING and could recover from only.

when others is almost always a bug unless immediately followed by "raise".

2) a SELECT .. INTO can throw a when no_data_found and too_many_rows.  If I
EXPECTED to have a no_data_found (eg: i was looking for a row and if I found one
great, else run some code) i would have a when no data found.

I rarely have a too_many_rows -- that means the data is wrong somehow probably.
I want that exception to stop my program DEAD.  I don't want to catch things I'm
not expecting.  If you do, you end up ignoring them and you never know they
happened.  I would rather my application FAILS, falls over, stops running then
have it ignore some error.
Review & Followup

Rating: 3
add a record to a table using a sequence like yyyymm**** 200112001-200112****... December 24, 2001
Reviewer: mysdirma from china
hi tom!!
how are you?
can you help me?   I have  a script like down,can you tell me some about this
script, good or bad ,if bad   what can I do  ????/*

REM this script create a sequence like (yyyymm****) and add a record into  a
table
REM written by mysdirma zhnjiangdianxin
REM create table seq(seq_ym number(6),seq_num num(3),seq_id,number(9));
REM create table temp_table(char_col varchar2(20),number_col number(20),date_col
date);
REM table seq is a sigle table
CREATE OR REPLACE PROCEDURE add_temp_table (
   p_char_col            temp_table.char_col%TYPE,
   output       IN OUT   VARCHAR2
)
AS
   v_seq_ym    NUMBER;
   v_seq_num   NUMBER;
   v_seq_id    NUMBER;
   v_ym        NUMBER;
   v_id        NUMBER;
BEGIN
   SELECT TO_NUMBER (TO_CHAR (SYSDATE, 'yyyymm'))
     INTO v_ym
     FROM DUAL;
   SELECT seq_ym, seq_num, seq_id
     INTO v_seq_ym, v_seq_num, v_seq_id
     FROM seq;

   IF v_seq_ym = v_ym
   THEN
      BEGIN
         UPDATE seq
            SET seq_num =   v_seq_num
                          + 1,
                seq_id =   v_seq_id
                         + 1
          WHERE ROWNUM = 1;

         IF SQL%FOUND
         THEN
            SELECT seq_id
              INTO v_id
              FROM seq
             WHERE ROWNUM = 1;

            INSERT INTO temp_table
                        (char_col, num_col, date_col)
                 VALUES (p_char_col, v_id, SYSDATE);

            output := 'Ìí¼ÓÊý¾Ý³É¹¦£¡£¡£¡£¡';
            COMMIT;
         ELSE
            output := 'ÏµÍ³Ã¦ÇëÉÔºó£¡£¡£¡£¡';
            ROLLBACK;
         END IF;
      END;
   ELSE
      BEGIN
         v_id :=   v_ym * 1000
                 + 1;

         UPDATE seq
            SET seq_ym = v_ym,
                seq_num = 1,
                seq_id = v_id
          WHERE ROWNUM = 1;

         IF SQL%FOUND
         THEN
            SELECT seq_id
              INTO v_id
              FROM seq
             WHERE ROWNUM = 1;

            INSERT INTO temp_table
                        (num_col)
                 VALUES (v_id);

            output := 'Ìí¼ÓÊý¾Ý³É¹¦£¡£¡£¡£¡';
            COMMIT;
         ELSE
            output := 'ÏµÍ³Ã¦ÇëÉÔºó£¡£¡£¡£¡';
            ROLLBACK;
         END IF;
      END;
   END IF;
EXCEPTION
   WHEN NO_DATA_FOUND
   THEN
      output := 'SEQ ±íÖÐÎÞÊý¾Ý£¬Óë¹ÜÀíÔ±ÁªÏµ£¡£¡£¡£¡';
      NULL;
END add_temp_table;
/

thanks !!!
2001-12
mysdirma
Rating: 4
no_data_found February 24, 2004
Reviewer: A reader
Hi

I have a procedure which has queries using rownum < 2 and exceptions of
no_data_found. The thing is even the query returns no rows the exception never
triggers, i trace the procedure and found out it always returns one row even the
query by itself returns 0 rows.... Does this mean no_data_found and rownum < 2
cant be used together?
Followup:
no, it does not.

no_data_found is raised only on a SELECT INTO query.  are you dong that and if
so, are you using aggregates without a group by?  (meaning query always returns
at least and at most one row.

   select count(*) from dual where 1=0;

will always return a row.

   select count(*) from dual where 1=0 and rownum < 2

will as well.

give us an example of what you are doing.
February 24, 2004 David -- Thanks for the question regarding "Update statement and Null-values -- update a table from another table", version Oracle 7.3.4
You Asked
Tom,

When updating a column with an update-statement, the value of
some records (records that don't need to be updated), are
changed into the value NULL. I use the next statement:
update    table    name B
  set        columnname    =
    (    select    value
          from    lookup    O
          where    B.keyname = O.keyname
            and    O.Othercolumn = Other_value);

As a result all the necessary changes are made, but also the
records that don't need to be updated: they get the Null-value.
Is there a way of avoiding this, because we do need to update
the records frequently, but not all records at the same time.
Is there a kind of workaround we can use for updating the
records that need to be updated without changing the other
records too with a Null value?

Thanks and greetings,

David Boot
and we said...
There are at least 2 ways to perform this sort of co-related
update correctly.  I'll show my preferred method (update a join)
and then another method that'll work if you cannot put a unique
constraint on LOOKUP(keyname) (which is needed for the join
update).

Here are the test tables:

scott@ORA734.WORLD> create table name
  2  ( keyname int,
  3    columnName varchar2(25)
  4  )
  5  /
Table created.

scott@ORA734.WORLD> create table lookup
  2  ( keyname int PRIMARY KEY,
  3    value varchar2(25),
  4    otherColumn int
  5  )
  6  /
Table created.

scott@ORA734.WORLD> insert into name values ( 100, 'Original
Data' );
1 row created.

scott@ORA734.WORLD> insert into name values ( 200, 'Original
Data' );
1 row created.

scott@ORA734.WORLD> insert into lookup values ( 100, 'New Data',
1 );
1 row created.

scott@ORA734.WORLD> commit;
Commit complete.

here is the "other_value" parameter you are using in the above
update you attempted...

scott@ORA734.WORLD> variable other_value number
scott@ORA734.WORLD> exec :other_value := 1
PL/SQL procedure successfully completed.

scott@ORA734.WORLD> select * from name;

   KEYNAME COLUMNNAME
---------- -------------------------
       100 Original Data
       200 Original Data

Here we update a join.  We can only modify the columns in one
of the tables and the other tables we are *NOT* modifying must
be "key preserved" -- that is, we must be able to verify that at
most one record will be returned when we join NAME to this other
table.  In order to do that, keyname in LOOKUP must either be a
primary key or have a unique constraint applied to it...

scott@ORA734.WORLD> update
  2    ( select columnName, value
  3        from name, lookup
  4       where name.keyname = lookup.keyname
  5         and lookup.otherColumn = :other_value )
  6     set columnName = value
  7  /

1 row updated.

scott@ORA734.WORLD> select * from name;

   KEYNAME COLUMNNAME
---------- -------------------------
       100 New Data
       200 Original Data

See, the other data is untouched and only the rows we wanted
are updated..

scott@ORA734.WORLD> rollback;
Rollback complete.

scott@ORA734.WORLD> select * from name;

   KEYNAME COLUMNNAME
---------- -------------------------
       100 Original Data
       200 Original Data

Now, this way will work with no constraints on anything -- you
do not need the primary key/unique constraint on lookup (but you
better be sure the subquery returns 0 or 1 records!).

It is very much like your update, just has a where clause so
that only rows that we find matches for are actually updated...

scott@ORA734.WORLD> update name
  2     set columnName = ( select value
  3                          from lookup
  4                         where lookup.keyname = name.keyname
  5                           and otherColumn = :other_value )
  6   where exists ( select value
  7                    from lookup
  8                   where lookup.keyname = name.keyname
  9                     and otherColumn = :other_value )
 10  /

1 row updated.

scott@ORA734.WORLD> select * from name;

   KEYNAME COLUMNNAME
---------- -------------------------
       100 New Data
       200 Original Data
Review & Followup

Rating: 4
Update statement and Null-values -- update a table from another table October 12, 2001
Reviewer: Greg from Washington, DC
Great help. Straightforward, to the point, gives options for differeing
scenarios.
Rating: 3
But can Multiple columns be achived April 02, 2002
Reviewer: pawan from USA
Tom,
   I am failing when trying to do the following:
I created two tables t and t1 by select * from user_objects. Both t and t1 have
117 records. Then I took one object_name(PRIMARY_SALESFORE) in t and updated
three fields - object_type,object_id and created.
ADHOC@VOYAGER> select t.object_id,t1.object_id,t.object_type,t1.object_type,
  2  t.created,t1.created
  3  from t,t1
  4  where t1.object_name=t.object_name
  5  and t.object_name='PRIMARY_SALESFORCE';

OBJECT_ID OBJECT_ID OBJECT_TYPE   OBJECT_TYPE   CREATED   CREATED
--------- --------- ------------- ------------- --------- ---------
     2222     17927 ORATAB        TABLE         02-APR-02 01-APR-02

Now when I try to update these three fields in t from t1 I am getting the
following error. What am I doing wrong.
1  update
2  (select t.object_id,t1.object_id,t.object_type,t1.object_type,
3  t.created,t1.created
4  from t,t1
5  where t1.object_name=t.object_name
6  and t.object_name='PRIMARY_SALESFORCE')
7  set t.object_id=t1.object_id,
8  t.object_type=t1.object_type,
9* t.created=t1.created
ADHOC@VOYAGER> /
update
*
ERROR at line 1:
ORA-00904: invalid column name

WHEN I TRIED TO UPDATE JUST one column in T I get error too
ADHOC@VOYAGER> update
  2  (select t.object_id toid,t1.object_id t1oid
  3  from t,t1
  4  where t.object_name=t1.object_name
  5  and upper(t.object_name)='PRIMARY_SALESFORCE')
  6  set toid=t1oid;
update
*
ERROR at line 1:
ORA-01779: cannot modify a column which maps to a non key-preserved table
Followup:
T and T1 are not "visible" outside of the inline view.  You fixed that with the
alias names toid, t1oid.

For the second error, please reread the above answer.  You NEED a primary
key/unique constraint on object_id in T1 to ensure that each row in T joins to
AT MOST 1 row in T1.

Consider, if T had:

OBJECT_NAME          OBJECT_ID
--------------       ----------------
ABC                  xxxx

and T2 had:

OBJECT_NAME          OBJECT_ID
----------------     ------------------
ABC                  123
ABC                  456

what should be the outcome of:

ADHOC@VOYAGER> update
  2  (select t.object_id toid,t1.object_id t1oid
  3  from t,t1
  4  where t.object_name=t1.object_name
  5  and upper(t.object_name)='PRIMARY_SALESFORCE')
  6  set toid=t1oid;

should T have 123 or 456 and under what circumstances?  Since the outcome of
that update is 100% AMBIGOUS, we do not permit it.  We need to ensure key
preserved-ness and do that with a primary key/unique constraint.

You need that index anyway typically for performance.
Rating: 4
Thanks But .. April 02, 2002
Reviewer: pawan from USA
Thanks Tom. I knew if I read your answer (one more time) throughly I would be
able to find the error in my SQL. But ... Anyway, would you be kind enough to
guide me in achieving what I am trying - updating three columns in a table from
another table that has one column (object_name) common and unique but no Primary
key.

Thanks again for the help
Followup:
update t
  set ( f1, f2, f3 ) =
        ( select a, b, c from t2 where t2.some_field = t.some_field )
 where exists ( select a, b, c from t2 where t2.some_field = t.some_field )
/

will do that.  If you cannot put a unique constraint (which is NEEDED in concept
if not in practice, if the some_field field in t2 is NOT unique -- the above
will fail with "subquery returned more then one row" as the update is AMBIGOUS)
Rating: 3
ORACLE OPTIMIZER April 02, 2002
Reviewer: Prashant from NJ
I retireve data from WSI_OPPORTUNITY partitioned table. Which is partioned based
on column RESP_DATE_CREATED column.
It has two partitioned index. 1st on RESP_DATE_CREATED column and 2nd on CUST_ID
column. I have following three queries.
 Query 1 : select * from swbapps.wsi_opportunity where cust_id = 640354 and
resp_date_created between sysdate-10000 and sysdate
    Cost : 202 Execution Time: 1.1 Sec Index Used: CUST_ID

 Query 2: select * from swbapps.wsi_opportunity where cust_id = 640354 order by
resp_date_created desc
     Cost: 1040 Query Execution Time:0.64Sec Index Used: CUST_ID

   Query3: select * from swbapps.wsi_opportunity where cust_id = 640354 and
resp_date_created between sysdate-10000 and sysdate order by resp_date_created
desc
   Cost: 177 Query Execution Time: DID NOT RETURN ANY RESULT AFTER 45 MINUTES
Index Used: RESP_DATE_CREATED

   My question is why query3 is using RESP_DATE_CREATED
   column index and forever it hangs? We are using ORACLE 8.1.5 on SUN SOLARIS
platform. It is really strange the way oracle optimizer behaves.

   Any insight is appreciated.
Followup:
As this has absolutely nothing to do with the original question.... One would
need more information (and this is not the appropriate place to get that) such
as the CREATE tables/indexes/init.ora settings, etc (please don't add them here
-- thats a hint for when you can ask a question -- I'll be asking for that)
Rating: 4
Is this a good approach April 09, 2002
Reviewer: Anant from USA
Thanks for the excellent site. I am trying to update a table based on certain
conditions on the same table. The query looks like

DECLARE
CURSOR c1 IS SELECT ROWID FROM
siebel.s_synd_data
    where
        WHERE
     ROW_ID LIKE 'DDM%'
     AND DATA_SRC_CD = 'DECPRF'
     and POSITION_ID IS NULL;
begin
for cur in c1 loop
     UPDATE SIEBEL.S_SYND_DATA A
     SET A.POSITION_ID = (SELECT B.ROW_ID FROM SIEBEL.S_POSTN B,
SIEBEL.S_ASGN_GRP C
WHERE A.TERR_ID = C.ROW_ID
AND SUBSTR(C.NAME, 1, 5) || '00D' = B.NAME)
   where rowid=cur.rowid;
  if mod(c1%rowcount,50000)=0 then
           commit;
  end if;
end loop;
commit;
end ;
------
The WHERE part of cursor C1 is expected to return 9 million records. My DBA says
that the transaction wil commit after updaing 9 million records. I have commit
after every 50,000 records - Is it  true?
Also what can be done to make this go faster - I know you hate this word withou
any performance metrics but what if I create some index (function based)?
Warm regards
Followup:
If this was up to me -- this is what I would do:

update s_synd_data a   -- sorry, i cannot bring myself to type sie....
   set a.position_id = ( select b.row_id
                           from s_postn b, s_asng_grp c
                          where c.row_id = a.terr_id
                            and b.name = substr(c.name,1,5) || '00D' )
 where row_id like 'DDM%'
   and data_src_cd = 'DECPRF'
   and position_id is null;

if position_id was indexed, I would drop the index, do the update, rebuild the
index unrecoverable and in parallel.

before you say "but it's 9million rows, we must commit" -- I say -- no way.
Just make sure you have sufficient RBS configured (else you are an ORA-01555
just WAITING to happen with your logic -- search this site for 01555 or 1555 to
see what i mean).

It'll not take as much rbs as you think.  Procedural code such as your will

a) run slower then an update
b) generate more redo and undo then a single update
c) be error prone (ora-01555, other logic errors)
d) you already have a logic error, c1%rowcount isn't "cumulative", its the
number
   of rows updated by the last update.  Unless you actually update 50,000 rows
   in one update, you'll never commit (you have a bug)
e) be a really bad idea.

On my system, I just updated 1,099,008 rows:

ops$tkyte@ORA817DEV.US.ORACLE.COM> update big_table set owner = lower(owner);
Elapsed: 00:08:38.66
ops$tkyte@ORA817DEV.US.ORACLE.COM> select used_ublk from v$transaction;

 USED_UBLK
----------
     13002
Elapsed: 00:00:00.01
ops$tkyte@ORA817DEV.US.ORACLE.COM> select 13002*8/1024 from dual;

13002*8/1024
------------
  101.578125
Elapsed: 00:00:00.01
ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from big_table;

  COUNT(*)
----------
   1099008
Elapsed: 00:00:02.41

it took about 100meg of rollback.  You would be looking at a gig of rollback or
so.  What I would do is (with my dba) make sure I had that much, use set
transaction use rollback segment if needed (just create a temporary one for this
update in /tmp or something, drop it later).  Run the update.  Be done with it.

Less thinking, Less work, Less chance for error...
Rating: 4
Update statement April 17, 2002
Reviewer: Bhavesh Tailor from Charlotte, NC, USA
This is very useful example.
Thanks a lot, Tom
Rating: 4
Why am I getting this April 29, 2002
Reviewer: pawan from USA
Tom,
   I do a simple insert into one Synonym while selecting from other synonym (
about 3 million records).

insert into voy_call_prev_sy (
2  select * from voy_call_sy);

insert into voy_call_prev_sy (
*
ERROR at line 1:
ORA-01562: failed to extend rollback    segment    number    3
ORA-01650: unable to extend rollback    segment    R02 by    1280    in
tablespace    RBS02

I have two Rollback segments R01 and R02 both 2 GB.
Followup:
You are generating more then 2gig of rollback (indexes can generate a TON of
undo information).

If you have 2gig allocated and no one else is using it and you get this, you are
generating more then 2gig.
Rating: 3
Thanks April 30, 2002
Reviewer: Pawan from USA
Tom,
   No one was using the system when I ran the INSERT . Yes there were indexes on
both the tables. But once the database was bounced th inserts went without
problem. Do you think that there was some other problem?

Thanks
Followup:
I think there was another user or two on the system personally.  They may not
have been active but they would be enough to jam the RBS if they started a
transaction and left it there.
Rating: 5
Absolutely fabulous April 30, 2002
Reviewer: Dan Kefford from New Haven, CT USA
Dear Tom...

In what version of Oracle did the "update a join" syntax become available? I
have always used the second construction, using the EXISTS clause, to accomplish
this kind of thing. It always did seem kind of silly to have to write the same
exact subquery twice. ;)

You ought to write an Oracle SQL tricks book.
Followup:
believe it is 7.3

ops$tkyte@ORA734.WORLD> create table t1 ( x int primary key, y int );

Table created.

ops$tkyte@ORA734.WORLD> create table t2 ( x int, y int );

Table created.

ops$tkyte@ORA734.WORLD>
ops$tkyte@ORA734.WORLD> insert into t1 values ( 1, 1 );

1 row created.

ops$tkyte@ORA734.WORLD> insert into t1 values ( 2, 2 );

1 row created.

ops$tkyte@ORA734.WORLD>
ops$tkyte@ORA734.WORLD> insert into t2 values ( 2, null );

1 row created.

ops$tkyte@ORA734.WORLD> insert into t2 values ( 3, null );

1 row created.

ops$tkyte@ORA734.WORLD>
ops$tkyte@ORA734.WORLD>
ops$tkyte@ORA734.WORLD> update ( select t1.y t1_y, t2.y t2_y
  2                     from t1, t2
  3                    where t1.x = t2.x )
  4     set t2_y = t1_y
  5  /

1 row updated.

ops$tkyte@ORA734.WORLD> select * from t2;

         X          Y
---------- ----------
         2          2
         3

ops$tkyte@ORA734.WORLD>
Rating: 5
Very useful! July 15, 2002
Reviewer: Cynthia Pinchot from Denver, CO
A much clearer explanation of what it takes to update
a join view, than what was described in the Oracle manuals.
Rating: 5
Parallel dml and update join view August 02, 2002
Reviewer: Rob from Pittsburgh, PA
Tom:

ADHOC@VOYAGER> update
  2  (select t.object_id toid,t1.object_id t1oid
  3  from t,t1
  4  where t.object_name=t1.object_name
  5  and upper(t.object_name)='PRIMARY_SALESFORCE')
  6  set toid=t1oid;

In the above query which demonstrates updating a join view,
I was wondering whether this update will execute in parallel if t is partitioned
and you issue alter session enable parallel dml. I have a similiar update and I
have determined that the best plan to resolve the select stmt would be to FTS
both tables and hash join.

Thanks as always

Rob
Followup:
If all of the other criteria are met (parallel stuff is enabled, tables are set
as "parallel") -- it should be able to.

It would break up the update by partitions on T, full scanning each partition on
T and joining it to T1 after down selecting the full scans with the predicate on
upper(t.object_name).

One way to find out -- run it, then try to select * from t where rownum = 1;  If
that succeeds, no parallal update, otherwise if that fails, parallel update
happened.  Commit and you can select from it.
Rating: 4
August 05, 2002
Reviewer: Vijay Malhotra from San Diego,CF
It's great answer.
Rating: 5
Can this be done, if more than two tables involved? August 27, 2002
Reviewer: Prince.
I have been doing this with two tables in the join.

Can this be done, if I want to update the column of table A with Column of Table
C. The relation between the tables are A -> B -> C, where A is the parent of
all, B is the parent of C.?
Followup:
sigh, such a simple simple example -- you spec'ed it all out -- and yet, you
didn't try???  oh well.

the answer is yes, as long as the rules for "key preserved" are satisified --
that is -- each row in A will appear AT MOST ONCE in the resulting join.  If
not, then no.

Here is the simple example:

ops$tkyte@ORA920.LOCALHOST> create table a ( x int primary key, y int );

Table created.

ops$tkyte@ORA920.LOCALHOST> create table b ( x references a primary key );

Table created.

ops$tkyte@ORA920.LOCALHOST> create table c ( x references b primary key, y int
);

Table created.

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> insert into a values ( 1, null );

1 row created.

ops$tkyte@ORA920.LOCALHOST> insert into a values ( 2, null );

1 row created.

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> insert into b values ( 1 );

1 row created.

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> insert into c values ( 1, 100 );

1 row created.

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> select * from a;

         X          Y
---------- ----------
         1
         2

ops$tkyte@ORA920.LOCALHOST> update ( select a.y a_y, c.y c_y
  2          from a, b, c
  3             where a.x = b.x and b.x = c.x )
  4      set a_y = c_y;

1 row updated.

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> select * from a;

         X          Y
---------- ----------
         1        100
         2

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST>
Rating: 5
August 28, 2002
Reviewer: kalita from UK
Good one...
Rating: 5
WHERE EXISTS qualification in 8.1.7 ? September 30, 2002
Reviewer: Ron Chennells from UK
Tom the original question was for a 7.3.4 datbase.
I'm using 8.1.7.3.
If I choose to use the second method that use suggest.
Do I still need the WHERE EXISTS qualification for this version ?
set columnName = ( select value
                            from lookup
                           where lookup.keyname =  name.keyname
                             and otherColumn = :other_value )
     where exists ( select value
                      from lookup
                     where lookup.keyname = name.keyname
                       and otherColumn = :other_value )

Thanks

Ron Chennells
Followup:
You can either

o update the join as demonstrated
o update via where exists as demonstrated

if you drop the where exists you will update EVERY row in the NAME table,
setting columnName to NULL whenever there isn't a value in LOOKUP for that key.
The where exists causes us to ONLY UPDATE rows that have a mate in LOOKUP.
Rating: 5
I never knew that you could update using Inline view.... September 30, 2002
Reviewer: A reader from BG
Tom,
    Great stuff you have out there.

My question is on this point which is from one of your replies.

d) you already have a logic error, c1%rowcount isn't "cumulative", its the
number of rows updated by the last update.  Unless you actually update 50,000
rows in one update, you'll never commit (you have a bug)

I thought only SQL%ROWCOUNT was the one which was set during a transaction
statement. So, would <CURSOR>%ROWCOUNT also be changed during a transaction?
Please explain further on this.

regards
Followup:
whoops- you know what, i was thinking sql%rowcount myself.

c1%rowcount IS cumulative! bummer....

c1%rowcount is maintained -- it is the number of rows fetched so far (i use
explicit cursor soooo infrequently, I slipped up on that one)
Rating: 4
Love the Update Join.... October 01, 2002
Reviewer: Mike from CT, US
...but I can't get it to work. Every time I try to use it, I get a permissions
error (ORA-01031: insufficient privileges). Could that be related to not having
unique constraints on my fields?...or do you have to have update permissions on
both tables in the join (even if only one table is being updated)? Even though
the second, where exists method works fine, I take a serious performance hit as
I have to query a very large table twice. I would love to use the update join if
possible without having update permissions on the second table. Hope that makes
sense...Thanks! (I'm on version 8.1.7.3.0)
Followup:
you would need update on all tables in the inline view.
Rating: 4
locks October 02, 2002
Reviewer: A reader
Tom,

In case of

update
      ( select columnName, value
          from name, lookup
         where name.keyname = lookup.keyname
           and lookup.otherColumn = :other_value )
       set columnName = value
    /

will both tables name, lookup get locked during the update?
Followup:
ops$tkyte@ORA920.LOCALHOST> alter table emp add dname varchar2(30);

Table altered.

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> update ( select emp.dname edname, dept.dname ddname
  2          from emp, dept
  3                where emp.deptno = dept.deptno )
  4   set edname = ddname;

14 rows updated.

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> select type, id1, (select object_name from
user_objects where object_id = id1 ) oname
  2    from v$lock
  3   where sid = (select sid from v$mystat where rownum=1)
  4      and type = 'TM'
  5  /

TY        ID1 ONAME
-- ---------- --------------------
TM      31715 EMP

Just the table you update has any row locks placed on it
Rating: 5
update October 22, 2002
Reviewer: mo
Tom:

I ran your query trying to update multiple records from one table to another but
did not work.

here is the example:

INS> select * from test;

SSN        NAME                           DOB
---------- ------------------------------ ---------
777        Joe Blow                       01-JAN-02
456        Henry walkman                  17-JAN-02
444        Tony walkman                   18-JAN-02
555        Karin wilsow
888        john kare                      01-MAR-02
3333       Cindy Best                     04-MAR-02
222        Joe  Blow                      24-APR-02
3444       Joe         Blow               24-APR-02
0000                                      24-APR-02
611        John                           18-OCT-02

10 rows selected.

INS> select * from test2;

SSN        PANTS
---------- ----------
611        RED
611        BLUE
456        YELLOW

3 rows selected.

INS> update test
    set pants = (select pants from test2 where test.ssn = test2.ssn)
    where exists (select pants from test2 where test.ssn = test2.ssn);
set pants = (select pants from test2 where test.ssn = test2.ssn)
             *
ERROR at line 2:
ORA-01427: single-row subquery returns more than one row

Is this query limited to one row only.  Can I do it for all records in test.

2.  Is it true that you can do anything in SQL that PL/SQL can do.  Any example
that contradicts this?

Thank you,
Followup:
1) you are missing the point of the error.

It is saying the SUBQUERY

set pants = (select pants from test2 where test.ssn = test2.ssn)

returns more then one row.  Tell me -- should pants be set to RED or BLUE for
611???  Your subquery MUST return at MOST one row.  If it returns two, we have
no idea which of the two to use.

2) No idea where you got that idea.  SQL is a non-procedural language.  PLSQL is
a procedural language.  They are totally different.
Rating: 5
query October 22, 2002
Reviewer: mo
Tom:

Yes I realized that later.  So the queries you suggest will work for tables that
have one-to-one record using a primary key.

SQL> update
      ( select a.pants, b.pants
          from test, test2
         where test.ssn = test2.ssn)
       set pants = pants

or

SQL> update test
    set pants = (select pants from test2 where test.ssn = test2.ssn)
    where exists (select pants from test2 where test.ssn = test2.ssn);
set pants = (select pants from test2 where test.ssn = test2.ssn)

After deleting the child record for 611.

Correct?

ALso, if you had one record that has multiples in the other tables would it stop
and not do antyhing or it will do it for all others except one?

Thank you
Followup:
correct

do not understand last part after "ALso..."
Rating: 5
query October 23, 2002
Reviewer: mo
Tom:

why this one fails:

  1   update
  2         ( select a.pants, b.pants
  3             from test a, test2 b
  4            where a.ssn = b.ssn)
  5*         set a.pants = b.pants
SQL>
SQL> /
        set a.pants = b.pants
                        *
ERROR at line 5:
ORA-00904: invalid column name

The first method worked though. The column is there.

2.  What I mean as the difference between SQL and PLSQL is that is not there
cases (update) where you have to do PLSQL and LOOPS and you can not do it with
SQL alone?

THank you
Followup:
read the comment "But can Multiple columns be achived " again.  Its the answer.

2) of course.  SQL is not a procedural lanaguage.  PLSQL is.  of course there
are things you can do in PLSQL that you cannot do in sql.
Rating: 5
Update statement and Null-values -- update a table from another table November 13, 2002
Reviewer: Rich from NJ
Thanks Tom. This worked well for for me.
Rating: 5
Update a table from another table January 11, 2003
Reviewer: Michael Johnson from London, England
Excellent! Tom does it yet again!

That's my ORA-00904 error taken care of! Not entirely sure why I was getting it
in the first place. Would anyone like to shed some light?

update pcdb_User_am
set pcdb_User_am.PCUS_TITLE = pcdb_user_c.PCUS_TITLE
where pcdb_User_am.PCUS_USER_RN = pcdb_User_c.PCUS_USER_RN

Returns ORA-00904 error

I would just like to say how much this site is helping an Oracle novice such as
my good self!

The book ain't half bad either!

Thanks again!
Followup:
the 904 happened because the above query quite simply doesn't reference
PCDB_USER_C anywhere in a from clause.  It is as if that table didn't exist

you need to reference the table at some point and you didn't.  You tried to use
it, without "declaring" it if you will.
Rating: 4
udpate statement January 29, 2003
Reviewer: Jack from Columbus, OH USA
scott@ORA734.WORLD> update
  2    ( select columnName, value
  3        from name, lookup
  4       where name.keyname = lookup.keyname
  5         and lookup.otherColumn = :other_value )
  6     set columnName = value
  7  /

1 row updated.

scott@ORA734.WORLD> update name
  2     set columnName = ( select value
  3                        from lookup
  4                       where lookup.keyname = name.keyname
  5                           and otherColumn = :other_value )
  6   where exists ( select value
  7                    from lookup
  8                   where lookup.keyname = name.keyname
  9                     and otherColumn = :other_value )
 10  /

1 row updated.

Tom ,
of the above two which one is better performance wise?
I'm guessing the first one, still have a doubt. If not pl. explain me why?
Followup:
neither,

the first one

no wait, the second

I think it is both.....

they will perform differently based on the optimizer you use, the validity of
the statistics, and so on.

In general, if "NAME" was very very small and "LOOKUP" was very very large (yet
indexed on keyname/othercolumn/value) -- the where exists would be very safe.

The update of a join is generally the choice you want to go with.
Rating: 3
Update Statement January 29, 2003
Reviewer: Jack from Columbus, OH USA
Tom,
I found the answer on your site itself. Sorry for the earlier mail.
After implementing this in my code, I 'm getting the following error
ERROR at line 61:
ORA-01779: cannot modify a column which maps to a non key-preserved table.
I read some of your suggestions to have a pk.
In my query , I use this for batch update which cannot have a condition like pk
= XXX.
I look for some status in the table and want to update all matching status
conditions.
Is there any other efficient way of doing this.
(as in sub queries with set and in where clause, I have to join lot of tables
say eg:10 and with same conditions).
thanks again
Followup:
got an example schema (creates) and data (inserts).  make it small and we can
tell you what to do with that.
Rating: 5
Update Master from a Detail Table (detail has composite key) January 30, 2003
Reviewer: saj from NewYork, NY USA
Thanks for all the guidance!
Can you guide me in achieving what I am trying

Updating a master table 2 or more columns
from a child table that has one has a composite key as the primary key

example

Table: Food
(food_id    number primary key
 food_name  varchar2(100)
 calorie    number
 fibre      number
 total_fat  number
)

Sample data for Food
Food_id      food_name  calorie    fibre    total_fat
1      apple         null    null    null
2       beer         null    null    null

Table: Food_Portion
(food_id     number
 portion_id  number
 qty         number
 weight      number
 calorie     number
 fibre       number
 total_fat   number
 ) where food_id + portion_id is primary key

Sample data for Food_Portion
Food_id    Portion_ID qty    weight calorie    fibre  total_fat
1    1       1    10     10    3      3
1    2       2    15     20    5      2
1    3       100    100    60    10     9

2       1          1    10     5    0      0
2    2      .5    6      4    0      0
2    3      100    100    40    0      10

The update should set the calorie, fibre and total_fat in food_table to the
values in food_portion table where the food_portion.qty = 100 and
food_portion.weight = 100

There rule is there will  be only one row per food_id in the food_portion table
with qty = 100 and weight  = 100 ( that is, select food_id from food_portion
where qty  = 100 and weight = 100 will give exactly one row per food_id)

I ran this type of update

update ( select a1, b1 from a, b where a.id = b.id )
set a1 = b1 (this one I haven’t tried)

But It gave me the ‘ORA-01779: cannot modify a column which maps to a non
key-preserved table ‘ error

Then I tried the

update t
  set ( f1, f2, f3 ) =
        ( select a, b, c from t2 where t2.some_field = t.some_field )
 where exists ( select a, b, c from t2 where t2.some_field = t.some_field )

update a
set a1 = ( select b1 from b where b.id = a.id )
   where a.id in ( select id from b )

both of them gave me the
ORA-01427: single-row subquery returns more than one row
Error

My question is…
what is my option other than PL/SQL for this kind of update?
Followup:
If you have:

where t2.some_field = t.some_field )

and you get:

ORA-01427: single-row subquery returns more than one row
Error

then your basic premise that there is only one row/food_id is false.

but anyway, if in fact:

select food_id
  from food_portion
 where qty = 100 and weight = 100
 group by food_id
 having count(*) > 1;

returns ZERO rows (no dups!)  then

ops$tkyte@ORA817DEV> update food
  2     set (calorie,fibre,total_fat) =
  3             (select calorie,fibre,total_fat
  4                    from food_portion fp
  5                   where fp.food_id = food.food_id
  6                     and fp.qty = 100
  7                     and fp.weight = 100 )
  8   where exists
  9             (select calorie,fibre,total_fat
 10                    from food_portion fp
 11                   where fp.food_id = food.food_id
 12                     and fp.qty = 100
 13                     and fp.weight = 100 )
 14  /

is your update.
Rating: 5
Thank You Soooo much January 30, 2003
Reviewer: SAJ
There was one food item in my database which had more than row with qty = 100
and weight = 100. I fixed that row and the query worked. It was a data issue. I
needed to have more where conditions to make the row unique.

Thanks a Million for the prompt response!
Rating: 4
update statement performance January 31, 2003
Reviewer: Jack from Columbus, OH USA
Tom,
I have the following table structures with online application. Orders are
processed in batch. The * fields are populated through online and rest of the
columns are filled for business purpose.I have to perform this update in a batch
application.
order table    (1 million)
------------
order_id    number    *
order_qty    number    *
order_amt    number    *
customer_id    number    *
location_id    number *
order_payment_method_id varchar2 *
order_status    number    *
order_date    date    *
order_process_date date
order_cust_first_name varchar2
order_cust_last_name varchar2
order_payment_method_account_no varchar2,
order_payment_method_type
order_cust_addr1
order_cust_addr2
order_cust_city
order_cust_state
order_cust_zip
order_cust_ct
location_name

customer table (0.75 million)
-------------
customer_id
customer_first_name
customer_last_name
customer_status

customer_address table (2 million)
------------------
customer_id
cust_addr1
cust_addr2
cust_city
cust_state
cust_zip
cust_ct
cust_current_addr varchar2(1)

paymentMethod table (1 million)
-------------------
customer_id     number
payment_method_id number
payment_method_type varchar2
payment_method_account_no varchar2
payment_method_status

location table (5000)
----------------
locaiton_id number
location_name, varchar2
location_address varchar2,
..
..
.

update order
set order_status ='PROCESSED'
order_process_date = sysdate
(order_cust_first_name,
order_cust_last_name,
order_payment_method_type,
order_payment_method_account_no,
order_cust_addr1,
order_cust_addr2,
order_cust_city,
order_cust_state,
order_cust_zip,
order_cust_ct,
location_name) = (select
customer_first_name,customer_last_name,payment_method_type,payment_method_account
_no,

cust_addr1,cust_addr2,cust_city,cust_state,cust_zip,cust_ct,location_name
          from customer,customer_address,paymentMethod ,location
          where order.customer_id = cust.customer_id
          and cust_current_addr='Y'
          and customer_address.customer_id = cust.customer_id
          and payment_method.payment_method_id = order.payment_method_id
          and location.location_id=order.location_id)
where order_status is null
and  order_date = trunc(sysdate)
and
 AND EXISTS
(
 SELECT 1
 FROM customer cust, paymentMethod pm
 WHERE customer_status = 'ACTIVE'
 AND order.customer_id = cust.customer_id
 AND payment_method_status= 'ACTIVE'
 AND order.pm_id = pm.pm_id
)

This update is taking 20 -30 min. based on the volume of processing. I felt the
queries are execute twice , one time in select and one time in where clause.
I want to reduce time on this update.
Pl.suggest me best way of updating this.
a) Is any other way,we can rewrite this query ?
b) Going to pl/sql (bulk collection option) will get I any performance gain. If
so, what is recommended value for bulk collection array size.
c) if pl/sql gives me better performance, will this performance decrease over
the time with more data?
Note: I can't commit in between the batch(business decision).
pl. suggest the best solution as always.
thanks
Jack
Followup:
care to share a db version
Rating: 3
update statement February 01, 2003
Reviewer: Jack from Columbus, OH USA
My db version is 8.1.7.x.
Followup:
well, I was going to play around with it but so many of the column names and
such are not right (making your example "non runnable").....

anyway, in 9i, you'll be able to use MERGE to do this in one pass. The one thing
I can say is:

o consider IN over exists (try it)
o make sure you are using CBO with current and good stats
o if the number of unique "customer_id, order_payment_method_id, location_id"
values being updated is SMALL compared to the number of records (eg: the main
subquery is being executed 100,000 times but with only 100 different inputs),
you may be better off running the subquery into a global temporary table to get
the 100 different rows and then updating a join.
Rating: 5
Useful answer (and great website). March 10, 2003
Reviewer: Lusiana Lusiana from New South Wales, Australia
Great, I did not know you could do an update on a join. It definately solved my
problem in one single statement. Thanks again for your great job providing this
website. It has been a very useful and dependable resource for me.
Rating: 5
Very useful example March 31, 2003
Reviewer: Chip from Denver, CO USA
After seeing the update syntax for a join, found it works quite nicely for
updating a single data source while previewing before and after values:

scott@MSORA920>create table dept_demo as select * from dept;

Table created.

scott@MSORA920>SELECT deptno, dname, INITCAP(dname) Iname, loc, INITCAP(loc)
Iloc FROM dept_demo;

    DEPTNO DNAME          INAME          LOC           ILOC
---------- -------------- -------------- ------------- -------------
        10 ACCOUNTING     Accounting     NEW YORK      New York
        20 RESEARCH       Research       DALLAS        Dallas
        30 SALES          Sales          CHICAGO       Chicago
        40 OPERATIONS     Operations     BOSTON        Boston

scott@MSORA920>edit
Wrote file afiedt.buf

  1  UPDATE(
  2  SELECT deptno, dname, INITCAP(dname) Iname, loc, INITCAP(loc) Iloc FROM
dept_demo
  3* ) SET dname = Iname, loc = Iloc
scott@MSORA920>/

4 rows updated.

scott@MSORA920>edit
Wrote file afiedt.buf

  1* SELECT deptno, dname, INITCAP(dname) Iname, loc, INITCAP(loc) Iloc FROM
dept_demo
scott@MSORA920>/

    DEPTNO DNAME          INAME          LOC           ILOC
---------- -------------- -------------- ------------- -------------
        10 Accounting     Accounting     New York      New York
        20 Research       Research       Dallas        Dallas
        30 Sales          Sales          Chicago       Chicago
        40 Operations     Operations     Boston        Boston

scott@MSORA920>

Thanks :)
Rating: 5
May 01, 2003
Reviewer: A reader from ca, usa
Tom,

From your original example, if instead of having

Update...
SET    columnName = Value;

I have it as:

Update...
SET    Value = columnName;

I get "ORA-01779: cannot modify a column which maps to a non key-preserved
table" error (please see below).

In the query, doesn't columnName return only 1 record as well? Why should it not
work?

SQL> CREATE TABLE name (
  2  keyname    INT,
  3  columnName VARCHAR2(25)
  4  );

Table created.

SQL>
SQL> CREATE TABLE lookup (
  2  keyname    INT PRIMARY KEY,
  3  value      VARCHAR2(25),
  4  otherColumn INT
  5  );

Table created.

SQL>
SQL> insert into name values ( 100, 'Original Data' );

1 row created.

SQL> insert into name values ( 200, 'Original Data' );

1 row created.

SQL> insert into lookup values ( 100, 'New Data', 1 );

1 row created.

SQL> commit;

Commit complete.

SQL> UPDATE (
  2     SELECT a.columnName, b.value
  3     FROM   name a, lookup b
  4     WHERE  a.keyname = b.keyname AND
  5            b.otherColumn = 1)
  6  SET columnName = value;

1 row updated.

SQL> rollback;

Rollback complete.

SQL> UPDATE (
  2     SELECT a.columnName, b.value
  3     FROM   name a, lookup b
  4     WHERE  a.keyname = b.keyname AND
  5            b.otherColumn = 1)
  6  SET value = columnName;
SET value = columnName
    *
ERROR at line 6:
ORA-01779: cannot modify a column which maps to a non key-preserved table
Followup:
SQL> CREATE TABLE name (
  2  keyname    INT,
  3  columnName VARCHAR2(25)
  4  );

Table created.

SQL>
SQL> CREATE TABLE lookup (
  2  keyname    INT PRIMARY KEY,
  3  value      VARCHAR2(25),
  4  otherColumn INT
  5  );

insert into name values ( 1, 'x' );
insert into name values ( 1, 'y' );

insert into lookup values ( 1, null, null );

now, what would the answer be to :

SQL> UPDATE (
  2     SELECT a.columnName, b.value
  3     FROM   name a, lookup b
  4     WHERE  a.keyname = b.keyname AND
  5            b.otherColumn = 1)
  6  SET value = columnName;

should value be x or y?

you need a primary key on the "lookup" tables join column  to remove the
ambiguity -- you do not have that.
Rating: 5
May 01, 2003
Reviewer: A reader from ca, usa
oh, I'm sorry. I din't see the primary key defined for keyname in lookup table.
I was wondering why the update went through when updating lookup, but not name.
Got it. Thanks.
Rating: 4
update table May 02, 2003
Reviewer: A reader from Bangalore India
Tom Excellent example !!!
Rating: 4
singl-row subquery returns > 1 row May 08, 2003
Reviewer: John A Buchanan from Scotland U.K.
Tom,

 I understand the 2 points in this query

1. return 0 or 1 rows only
2. use WHERE EXISTS to stop update to null

I use max to return a single row, but I still get non-selected rows updated to
NULL

 update test
    set pants = (select pants from test2 where test.ssn = test2.ssn)
    where exists (select pants from test2 where test.ssn = test2.ssn);
set pants = (select pants from test2 where test.ssn = test2.ssn)
             *
ERROR at line 2:
ORA-01427: single-row subquery returns more than one row

Suppose pants is type DATE:

update test
    set pants = (select max(pants) from test2 where  test.ssn = test2.ssn)
where pants > '01-JAN-03'
    and exists (select max(pants) from test2 where test.ssn = test2.ssn);

I get all records in test with pants > '01-JAN-03' and no match in test2 updated
to null.

Does my 'where pants > '01-JAN-03' make the difference
Followup:
two things

MOST IMPORTANT THING YOU'LL LEARN TODAY:

compare strings to strings, dates to dates, numbers to numbers.  Never but
Never but Never rely on implicit conversions.  Always but Always use date
formats and to_date to compare a string to a date.  AND USE 4 DIGITS to
represent what year it is.  It takes no more and no less then 4 digits to do
that!

where pants > to_date( '01-JAN-2003', 'dd-mon-yyyy')

that is the only correct way to do that.

Second:

select max(foo) from t where 1=0

always returns a row.  an aggrete with NO GROUP BY always returns a row.
Always.

update test
    set pants = (select max(pants)
                   from test2
                  where test.ssn = test2.ssn)
  where pants > to_date( '01-JAN-2003', 'dd-mon-yyyy' )
    and exists (select pants   /* want to make sure there is at least ONE */
                  from test2
                 where test.ssn = test2.ssn);
Rating: 5
Aggregate functions return 1 row May 09, 2003
Reviewer: John A Buchanan from Scotland, U.K.
Lesson learned

Thanks Tom
Rating: 5
Super June 12, 2003
Reviewer: Kamran from Pakistan
Great aid for such frequently encountering problem
Rating: 4
Clarification sought June 12, 2003
Reviewer: Anirudh from New Delhi, India
I have a table original(cart_key number primary key,address_key number )
and another table lookup(address_key number primary key,add_type number,cart_key
number references originaL).
Due to some basic design flaw, at this point of time we have records going awry
in both the tables.
Address_key column in the original table has no entries i.e NULL (though table
in itself has about 100 rows).
lookup table has multiple entries for each cart_key with the add_type being
different(1,2 or 3) I am required to update table ori from table lookup to fill
the address_key values only where the address type is 1.
Another horible thing is in lookup table there may be a cart key which doesn't
have add_type 1 corresponding to it.
Here is the model of the situation;

Anna anirudh@TEST.WORLD816>create table original(cart_key number primary
key,address_key number );

Table created.
Anna anirudh@TEST.WORLD816>create table lookup(address_key number primary
key,add_type number,cart_key number references original);

Table created.

Anna anirudh@TEST.WORLD816>insert into original values(1000,'');

1 row created.

Anna anirudh@TEST.WORLD816>insert into original values(2000,'');

1 row created.

Anna anirudh@TEST.WORLD816>insert into original values(3000,'');

1 row created.

Anna anirudh@TEST.WORLD816>insert into original values(4000,'');

1 row created.

Anna anirudh@TEST.WORLD816>commit;

Commit complete.

Anna anirudh@TEST.WORLD816>insert into lookup values(101,1,1000);

1 row created.

Anna anirudh@TEST.WORLD816>insert into lookup values(102,2,1000);

1 row created.

Anna anirudh@TEST.WORLD816>insert into lookup values(103,3,1000);

1 row created.

Anna anirudh@TEST.WORLD816>insert into lookup values(104,3,2000);

1 row created.

Anna anirudh@TEST.WORLD816>insert into lookup values(105,2,2000);

1 row created.

Anna anirudh@TEST.WORLD816>insert into lookup values(106,1,3000);

1 row created.

Anna anirudh@TEST.WORLD816>insert into lookup values(107,3,4000);

1 row created.

Anna anirudh@TEST.WORLD816>commit;

Commit complete.

Anna anirudh@TEST.WORLD816>select * from original;
 cls  CART_KEY ADDRESS_KEY
---------- -----------
      1000
      2000
      3000
      4000

Anna anirudh@TEST.WORLD816>select * from lookup;
 clsADDRESS_KEY   ADD_TYPE       CART_KEY
----------- ---------- ----------
        101          1       1000
        102          2       1000
        103          3       1000
        104          3       2000
        105          2       2000
        106          1       3000
        107          3       4000

7 rows selected.

I want to update table original such that the data in the table is like this:
CART_KEY ADDRESS_KEY
---------- -----------
      1000 101
      2000
      3000 106
      4000

Anna anirudh@TEST.WORLD816>ed
Wrote file afiedt.buf

  1  update (select a.address_key w,b.address_key z
  2          from original a,lookup b
  3          where a.cart_key=b.cart_key
  4*         and b.add_type=1) set w=z
Anna anirudh@TEST.WORLD816>/
        and b.add_type=1) set w=z
                              *
ERROR at line 4:
ORA-01779: cannot modify a column which maps to a non key-preserved table

Should I write a PL/SQL code for it.............
Thanks
Followup:
update t1
   set (c1,c2,c3...) = ( select c1,c2,c3...
                           from t2
                          where t1.key(s) = t2.key(s) )
 where exists  ( select c1,c2,c3...
                           from t2
                          where t1.key(s) = t2.key(s) )
/
Rating: 5
great anser TOM July 08, 2003
Reviewer: BT from Hawaii
Rating: 4
cannot select ROWID from a join view July 29, 2003
Reviewer: A reader
Tom,

i am getting the following error when selecting rowid. please let me know what i
am missing... sorry for my ignorance on this ..

if i want to select rowid from join of tables what should i do and where will
oracle take the row id from ?

SQL> select rowid,ORDER_REQUISITION_NUM from V_ORDER_DOCQUOTE2;
select rowid,ORDER_REQUISITION_NUM from V_ORDER_DOCQUOTE2
                                        *
ERROR at line 1:
ORA-01445: cannot select ROWID from a join view without a key-preserved table

create or replace view v_order_docquote3 as
select
a.row_id order_row_id,
a.x_om_office_cd||'-'||a.x_base_req_dsc||'-'||a.x_alt_req_dsc
order_requisition_num,
a.x_cust_po_val_amt purchase_order_value,
a.x_charge_to_dsc ,
a.x_cust_po_num primary_cust_no,
decode(a.x_cust_po_dt,null,to_date(null),a.x_cust_po_dt) customer_po_date,
--a.ou_num customer_index_number,
a.x_addr_line2_dsc ship_to_address_line2,
a.x_addr_line3_dsc ship_to_address_line3,
a.x_addr_line4_dsc ship_to_address_line4,
a.x_base_req_dsc base_requisition_number,
a.x_serve_off_cd sales_office_taking_cd,
a.x_inst_city_dsc ,
a.x_inst_state_cd location,
a.x_prog_payment_cd billing_type,
a.x_future_5_dsc title_transfer_terms,
a.x_bill_sch_dsc billing_terms,
a.x_dest_site_cd order_ext_customer_index_num,
c.name terms_of_payment,
b.name project_name,
b.quote_num proposal_num,
d.addr ship_to_address_line1,
d.city ship_to_city,
d.state ship_to_state,
d.zipcode ship_to_zip,
d.country ship_to_country
  from
 s_order a ,s_doc_quote b ,s_payment_term c,s_addr_org d, s_org_ext e
  where a.quote_id = b.row_id
  and a.accnt_addr_id = d.row_id(+)
  and a.payment_term_id = c.row_id(+)
  and a.accnt_id = e.row_id(+);

thanks for your time.
Followup:
there is no rowid to select.  None of the tables are key preserved.

if you want the rowid from a specific table in there - you would ADD that column
to the view

select a.rowid a_rowid, ......

and then you can select A_ROWID in the select statement.
Rating: 4
key preserved July 29, 2003
Reviewer: A reader
tom,

"None of the tables are key preserved"

i have not given you table structure, how did you find out that none of my
tables are key preserved, please let me know.

Also ...tom, i read through the key preserved postings in your site -but
unfortunately i am not understanding the basics behind that. - true that you
have give a simple illustration in this too. but i am asking you, if you get
chance- can you explain me with the above tables, which i have used for creating
the view. not all tables, just two in that would do.

thanks for your contribution to oracle community.
Followup:
if there were a key preserved table, rowid would have been there.

the error message suggested as such:

ERROR at line 1:
ORA-01445: cannot select ROWID from a join view without a key-preserved table

basically a key preserved table is a table in a JOIN that is gauranteed to
return each of its rows AT MOST once in the query (eg: it will join to at most
ONE other row)

So, in

select * from emp, dept where emp.deptno = dept.deptno;

assuming a primary key on dept(deptno) and nothing else we can say that "Emp is
key preserved".  Why?  because each row in EMP will join to AT MOST one row in
DEPT -- so the record for KING can appear AT MOST once in the result set.  EMP
is a "stable" table in that result set.

DEPT however is NOT key preserved.  the record for deptno=10 will appear many
times in the result set.
Rating: 5
Thanks July 30, 2003
Reviewer: A reader
Tom,
Thanks, Nothing can match your clear way of explaining. your dept,emp example
helped me understand the basics behind it.

Thanks again ..
;-)
Rating: 4
update query August 11, 2003
Reviewer: umesh from blore india
Tom,

Can this be done in a query or should i do a pl/sql block for it
Thanks

scott>create table mfg
    ( mfg_id varchar2(5) primary key ,
     step varchar2(5),
     component varchar2(5),
    parent_id varchar2(5)
    );

insert into mfg ( mfg_id,step,component) values ('m1','s1','c1');
insert into mfg ( mfg_id,step,component) values ('m2','s2','c2');
insert into mfg ( mfg_id,step,component) values ('m3','s3','c3');
insert into mfg ( mfg_id,step,component) values ('m4','s4','c4');

 create table test
 (
 STEP                                   VARCHAR2(5),
 COMPONENT                        VARCHAR2(5),
 P_STEP                                 VARCHAR2(5),
 P_COMPONENT                     VARCHAR2(5)
 );

insert into test values ('s2','c2','s2','c1');

insert into test values ('s2','c2','s2','c1');

insert into test  values ('s3','c3','s3','c2');

insert into test  values ('s3','c3','s3','c2');

commit;

Now i need to update .. can this update be done in a single query

get the mfg_id  where  p_step, p_component   = mfg.step , mfg.component
and  set  parent_id =mfg_id  where test.step , test,component =
mfg.test,mfg.component

my final table of mfg will be

scott>select * from mfg;

MFG_I STEP  COMPONENT  PARENT_ID
----- ----- ---------- ----------
m1    s1    c1            null as no parent
m2    s2    c2            m1
m3    s3    c3            m3
m4    s4    c4            null as no record
Followup:
not following you
Rating: 4
update query August 11, 2003
Reviewer: umesh from bangalore India
scott>select * from mfg;

MFG_I STEP  COMPONENT    PARENT_ID
----- ----- ------------ ----------
m1    s1    c1
m2    s2    c2
m3    s3    c3
m4    s4    c4

scott>select * from test;

STEP  COMPONENT    P_STEP     P_COMPONENT
----- ------------ ---------- --------------------
s2    c2           s1         c1
s2    c2           s1         c1
s3    c3           s2         c2
s3    c3           s2         c2

P_step nad p_compoent in test table hold parent step and parent component for
test.step, test.component

in "test" ( consider first row) s2,c2 has pareants in (s1,c1) that means s1,c1
is the parent of s2,c2

in "mfg" s1,c1 = m1 and s2,c2 =m2

so in  "mfg" i want parent_ids to be updated
in "mfg" s2,c2 will have parent_id = m1

my question is regarding the updation of the mfg.parent_id
PLease let me know wether this could be done in a single query
Thanks for your patience
Followup:
if you look up at your original example -- you'll see the output doesn't make
sense there (m3 is the parent of m3?  there are no s3/c2 pairs, yet test has
them and so on)

Anyway, if we de-dup test, add the requisite primary keys needed to make this
update UNAMBIGOUS, then yes you can:

ops$tkyte@ORA920> create table mfg( mfg_i varchar2(2), step varchar2(2),
component varchar2(2), parent_id varchar2(2) );

Table created.

ops$tkyte@ORA920> alter table mfg add constraint mfg_pk primary
key(step,component);

Table altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table test( step varchar2(2), component varchar2(2),
p_step varchar2(2), p_component varchar2(2) );

Table created.

ops$tkyte@ORA920> alter table test add constraint test_pk primary
key(step,component);

Table altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into mfg values ( 'm1', 's1', 'c1', null );

1 row created.

ops$tkyte@ORA920> insert into mfg values ( 'm2', 's2', 'c2', null );

1 row created.

ops$tkyte@ORA920> insert into mfg values ( 'm3', 's3', 'c3', null );

1 row created.

ops$tkyte@ORA920> insert into mfg values ( 'm4', 's4', 'c4', null );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into test values ( 's2', 'c2', 's1', 'c1' );

1 row created.

ops$tkyte@ORA920> insert into test values ( 's3', 'c3', 's2', 'c2' );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> update (
  2  select a.mfg_i, a.step, a.component, a.parent_id, c.mfg_i p_mfg_i
  3    from mfg a, test b, mfg c
  4   where a.step = b.step
  5     and a.component = b.component
  6     and b.p_step = c.step
  7     and b.p_component = c.component )
  8   set parent_id = p_mfg_i;

2 rows updated.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from mfg;

MF ST CO PA
-- -- -- --
m1 s1 c1
m2 s2 c2 m1
m3 s3 c3 m2
m4 s4 c4

ops$tkyte@ORA920>
Rating: 4
update query August 13, 2003
Reviewer: umesh from blore india
Tom
Can we do this way

update mfg b set parent_id = ( select mfg_id from mfg a
where a.step || a.component = ( select p_step || p_component from
test where step||component = b.step || b.component and rownum = 1) and rownum =
1)

of course each of the concatenated columns  will be wrapped in a lpad to the
length of columns
Followup:
maybe -- if you want to run two slow correlated subqueries for every row in the
table -- sure. go for it.

I did not verify this for correctness -- thats your job :)

it'll assign RANDOM values basically if the necessary keys are not in place (eg:
running the update twice could result in different ANSWERS).   I assume you are
using rownum=1 in a misguided attempt to avoid "subquery returns too many rows"

also, you never need to concat like that, just use regular sql operations:

scott@ORA920LAP> select * from emp where (ename,empno) = ( select ename, empno
from emp where rownum = 1);

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
----------
      7369 SMITH      CLERK           7902 17-DEC-80        800
  20

you can say where (c1,c2,c3) = (select a,b,c from .... )
Rating: 5
ORDER BY and ROWNUM in update sub-query. August 26, 2003
Reviewer: Adam from PA
Tom,

In response to the first question on this topic you said
"but you better be sure the sub-query returns 0 or 1 records!".
Can I use ORDER BY and ROWNUM < 2 to get a single row from the
sub-query ?

Here is my example.

create table name
( keyname int,
  columnName varchar2(25)
);

create table lookup
  ( keyname int,  -- not a unique key --
    value varchar2(25),
    orderkey int
);

-- the data in my case is as follows --
insert into name values (1, 'Some Value');
insert into name values (2, null);
insert into name values (3, null);

insert into lookup values (2, 'New Value 1', 1);
insert into lookup values (2, 'New Value 2', 2);
insert into lookup values (2, 'New Value 3', 3);
insert into lookup values (3, 'New Value 1', 1);
insert into lookup values (3, 'New Value 2', 2);
insert into lookup values (3, 'New Value 3', 3);

update name n set columnName =
  (select value from
    (select value
     from lookup l
     where l.keyname = n.keyname
     order by orderkey)
   where rownum < 2)
where columnName is not null;

The above query fails with the following error. The problem
is that "name" is not accessible in the inner sub-query (at
second level).

ERROR at line 5:
ORA-00904: "N"."KEYNAME": invalid identifier

How can I workaround this problem ?

Thanks in advance,
-Adam.
Followup:
say orderkey is a number between 1 and 9999999999 (10 digits)

columnName = ( select substr(min( to_char(orderKey,'fm0000000000')||value ),11)
                 from lookup l
                where l.keyname = n.keyname )

encode the orderkey to be "minable" and/or "maxable"

substr out the piece you want.  you can get the first/last row that way.
Rating: 4
Good one August 27, 2003
Reviewer: Vijay Anand from Mumbai,India
The solution given by Tom and the way he explains things are outstanding.I
always used Exists clause for update before, I'll follow the Join Update from
now.
Rating: 4
update table August 28, 2003
Reviewer: a reader from india
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.
Rating: 5
September 23, 2003
Reviewer: A reader
Hi Tom,

I want to update one table using other table.

Example.
T1            T2
--------------        -------------
c1                 c1
c2            c2
c3            c3
c4            c4

UPDATE T1
SET   T1.c2  =  ( SELECT T2.c2 FROM T2 WHERE
T1.c1 = T2.c1 )
WHERE   T1.c1 IN (
        SELECT T2.c1 FROM T2
        WHERE T2.c1 = T1.c1 )

Can you please comment, if this ok.

Thanks.
Followup:
hows about

update ( select t1.c2 t1_c2, t2.c2 t2_c2
           from t1, t2
          where t1.c1 = t2.c1 )
  set t1_c2 = t2_c2;

since t2(c1) MUST BE UNIQUE -- it can have a unique/primary key on it and this
works faster
Rating: 5
October 22, 2003
Reviewer: A reader
update t

set ( x,y,z)
       = ( select x,y,z
           from t1
           where t.x ==t1.x)
   where exists
( select x,y,z
           from t1
           where t.x ==t1.x)

Why is it necessary to give the whole query, woudnt it suffice to just say

update t

set ( x,y,z)
       = ( select x,y,z
           from t1
           where t.x ==t1.x)
   where t.x =: some value we know
Followup:
that doesn't make sense.  where did you get "some value you know"

the first query with the where exists -- that will update every row in T with a
value in T2 given that a row exists in T2 to update t with

your query will update every row in T where t.x = some_value -- if no values
exist in T2, t.x,y,z will be set to NULL

they are totally different!
Rating: 5
How about a multi-column key on the lookup table? January 10, 2004
Reviewer: Jim Cain from Fort Lauderdale, FL
I have the following:

CREATE TABLE alpha (
   id NUMBER(2) PRIMARY KEY,
   NAME VARCHAR2(100) UNIQUE,
   status CHAR(1)
);

CREATE TABLE beta (
   networkid NUMBER(2),
   NAME VARCHAR2(100),
   PRIMARY KEY (NAME, networkid)
);

INSERT INTO alpha VALUES (1, 'Jim', 'A');
INSERT INTO alpha VALUES (2, 'Eric', 'A');
INSERT INTO alpha VALUES (3, 'Ryan', 'A');
COMMIT;

INSERT INTO beta VALUES (10, 'Jim');
INSERT INTO beta VALUES (10, 'Eric');
INSERT INTO beta VALUES (20, 'Ryan');
COMMIT;

UPDATE (
   SELECT a.status
   FROM alpha a, beta b
   WHERE a.NAME = b.NAME
   AND b.networkid = 10
) SET status = 'X';

Why is this join not key-preserved? I have specified both columns of beta's key.
Followup:
Looks like a limitation of the key preserved code -- it wants actual "join
columns" -- not a constant value.  (you and i know it is the same, but the
software doesn't)

In 9i, this works:

ops$tkyte@ORA10G> merge into alpha a
  2  using ( select b.*
  3           from alpha a, beta b
  4                  where networkid = 10
  5                    and a.name = b.name ) b
  6  on ( a.name = b.name )
  7  when matched then update set status = 'X'
  8  when not matched then insert (id) values ( null );

2 rows merged.

merge is neat -- it can get around that key preserved issue nicely.  in 10g,
this simplifies to:

ops$tkyte@ORA10G> merge into alpha a
  2  using ( select b.* from beta b where networkid = 10 ) b
  3  on ( a.name = b.name )
  4  when matched then update set status = 'X'
  5  /

2 rows merged.

since you can leave out either the update or insert clause (and even add a
delete if you like)
Rating: 5
Using MERGE to simulate an update join... January 20, 2004
Reviewer: Gary from Indianapolis, IN US of A
Tom

Thanks once again for your tireless efforts.  Ths site is truly the best
resource for us.

When you gave the example of using the following MERGE statement to simulate, as
I read the thread, the update join scenario:

merge into alpha a
  using ( select b.*
           from alpha a, beta b
                  where networkid = 10
                    and a.name = b.name ) b
  on ( a.name = b.name )
  when matched then update set status = 'X'
  when not matched then insert (id) values ( null );

I'm not sure I understand how you can insert a row into 'a' using a primary key
value of 'null'.

I created a similar test case and got the expected (by me anyway) primary key
constraint violation.  What did I miss?

Also, the first test case I tried used the format you specified in another
thread for the update of a join.  In my case, one of my join columns needed an
RPAD function applied to it to match a VARCHAR2 column in my table being updated
to a CHAR column in the table providing the update values.

I couldn't get the system to recognize I was trying to do an update of a join
and I kept getting the infamous 'Can't update a column not supported by a key
preserved table'.

In this case I had a primary key defined on the CHAR column of the 2nd table but
still got the message.  I assumed it was because I was using a function on the
other column even though it was not on my key preserved table.

Here is my update statement and table defs:

desc t

 Name              Null?    Type
 ----------------- -------- ------------
 MODEL             NOT NULL VARCHAR2(25)
 FLAG                       CHAR(1)

desc u

 Name              Null?    Type
 ----------------- -------- ------------
 MODEL_NBR                  CHAR(25)
 UPD_FLAG                   CHAR(1)

update
  2  (select t.flag flag, u.upd_flag upd_flag
  3   from t t, u u
  4   where rpad(t.model, 25) = u.model_nbr)
  5* set flag = upd_flag
gww@dwp1> /
set flag = upd_flag
    *
ERROR at line 5:
ORA-01779: cannot modify a column which maps to a non key-preserved table

Here are my constraints:

CONSTRAINT C TABLE_NAME INDEX_NAME
---------- - ---------- ----------
T_PK       P T          T_IDX

U_PK       P U          U_IDX

The primary key constraints are on the MODEL and MODEL_NBR columns respectively.

This update of a join is very powerful and I would like to use it more if I can
get by this llittle snag.

Any help is greatly appreciated.  BTW.  Your books are great!

G/
Followup:
you cannot insert the null rows -- it is just that in 9i you HAVE to have the
insert part -- but it'll never be executed in this case!  the join in the using
takes care of that.  in 10g, you can leave the insert part off and simplify the
entire thing.

your test case must have caused the insert branch to actually execute.  MY
example, with the join, ensures only that matches would be processed.
Rating: 5
update/join syntax with a function January 27, 2004
Reviewer: Gary from Indianapolis, IN US of A
Tom

Thanks very much for the MERGE info.  I got that to work very nicely.  Can you
comment on the error returned when using the update/join syntax?

Here is my setup:

Table T:
 Name              Null?    Type
 ----------------- -------- ------------
 MODEL             NOT NULL VARCHAR2(25)
 FLAG                       CHAR(1)

Table U:
 Name              Null?    Type
 ----------------- -------- ------------
 MODEL_NBR         NOT NULL CHAR(25)
 UPD_FLAG                   CHAR(1)

Constraints and indexes showing primary keys in place:

TABLE_NAME           C INDEX_NAME
-------------------- - --------------------
T                    P T_IDX
U                    P U_IDX

TABLE_NAME INDEX_NAME COLUMN_NAME COLUMN_POSITION
---------- ---------- ----------- ---------------
T          T_IDX      MODEL       1
U          U_IDX      MODEL_NBR   1

When trying to use the update/join syntax in this case since I have a VARCHAR2
column that needs to join with a CHAR column, I RPAD the column in the table I'm
trying to update in order to leave the column name alone in the secondary table
so the primary key is used to maintain the key preservation.

Here is my update statement and results:

update
(select t.model model, t.flag flag, u.model_nbr model_nbr, u.upd_flag upd_flag
 from t t, u u
 where rpad(t.model, 25) = u.model_nbr)
set flag = upd_flag

gww@dwp1> /
set flag = upd_flag
    *
ERROR at line 5:
ORA-01779: cannot modify a column which
maps to a non key-preserved table

Is the use of a function on one side or the other or both causing the error?

I also tried with a function based index as the primary key on the secondary
table using the TRIM function but to no avail.

Environment:  AIX 4.3.3
              ORACLE 9.2.0.3

Any suggestions are most welcome.  The MERGE syntax is the direction I'm headed
but this particular issue has become a crusade.

Great site!  Great books!  Many thanks from your groupies!

/G
Followup:
it is the function.  for all the optimizer knows, rpad(t.model,25) makes t.model
"not unique" anymore (and it could) hence there is no "primary key"/"unique
constraint" in place here.
Rating: 3
Null Update? February 05, 2004
Reviewer: Rob from MB, Canada
You show how to insert null, what about a merge that updates nothing (really a
insert into where not exists)
Followup:
that would be -- well, insert where not exists i suppose?

insert into table_to_merge_into ( .... )
select table_to_merge_with
  from table_to_merge_with
 where (key_col1, key_col2, .... ) not in ( select key_col1, key_col2, ....
                                              from table_to_merge_into );

using the cbo and assuming key_colN are "NOT NULL", that'll do a nice speedy
hash anti-join.
Rating: 4
February 06, 2004
Reviewer: Rob from Canada
Yes, I realized the simplicy after, I went for a

insert into tablea TA (ID)
(select ID from tableb TB left join TA on TB.ID=TA.ID where TA.ID is null)
Rating: 4
another key not preserved problem February 06, 2004
Reviewer: Ilya from New York, NY
Hi Tom,

I'm having a problem with this update:

CREATE TABLE ALPHA_TEMP (
    SEQ_NUM INTEGER NOT NULL ,
    NUM_KEY INTEGER NULL ,
    NUM_ARR_STR VARCHAR2 (420) NULL ,
    ENTITY_TTL INTEGER NULL ,
    STOP_DSC VARCHAR2 (255) NULL ,
    STOP_DSC_SZ INTEGER NULL ,
    MANUAL VARCHAR2 (1) NULL ,
    COMMENTS VARCHAR2 (150) NULL ,
    CR_OPR VARCHAR2 (9) NULL ,
    CR_DATE DATE  DEFAULT (SYSDATE) NULL ,
    CONSTRAINT PK_ALPHA_TEMP PRIMARY KEY
    (
        SEQ_NUM
    )
)

CREATE TABLE BETA_ENT_TEMP (
    SEQ_NUM INTEGER NOT NULL ,
    ENTITY_TTL INTEGER NULL ,
    ENTITY_NUMS VARCHAR2 (420) NULL
)

UPDATE (SELECT a.entity_ttl AS a_entity_ttl,
               b.entity_ttl AS b_entity_ttl
          FROM alpha_temp a INNER JOIN
          (SELECT seq_num, SUM(entity_ttl) AS entity_ttl
          FROM beta_ent_temp
          GROUP BY seq_num) b ON a.seq_num = b.seq_num)
   SET a_entity_ttl = b_entity_ttl;

This gives me ORA-01779 cannot modify column that maps
to a non-key-preserved table

I am really stumped, because I'm updating column from
alpha_temp, which has a primary key on seq_num
and I'm also joining on primary key column with another table, which should
guarantee that the resulting joined row is matched to the main table.

As far as data, I tried various things with the same result, but in the simplest
case alpha_temp has one row and beta_ent_temp is empty.
If I put a primary key on beta_ent_temp seq_num, it works, but that table is
shared with another program and I cannot modify it.

thanks,
ilya
Followup:
the table being JOINED TO (not being updated) is the one that needs the primary
key and even though you and I *know* the group by is building an implicit
"primary key sort of constraint", it is not being recognized as such.

so.... you can use merge:

ops$tkyte@ORA9IR2> merge into alpha_temp A
  2  using ( select seq_num, sum(entity_ttl) entity_ttl
  3            from beta_ent_temp
  4           group by seq_num ) B
  5  on ( a.seq_num = b.seq_num )
  6  when matched then update set a.entity_ttl = b.entity_ttl
  7  when not matched then insert ( seq_num ) values ( NULL )
  8  /

5 rows merged.

Now, if you do that and you get an error because B has seq_nums that are not
in A, eg:

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into beta_ent_temp
  2  select 55, 55, null from dual;

1 row created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> merge into alpha_temp A
  2  using ( select seq_num, sum(entity_ttl) entity_ttl
  3            from beta_ent_temp
  4           group by seq_num ) B
  5  on ( a.seq_num = b.seq_num )
  6  when matched then update set a.entity_ttl = b.entity_ttl
  7  when not matched then insert ( seq_num ) values ( NULL )
  8  /
merge into alpha_temp A
*
ERROR at line 1:
ORA-01400: cannot insert NULL into ("OPS$TKYTE"."ALPHA_TEMP"."SEQ_NUM")

You would have to (in 9i) filter the data in B:

ops$tkyte@ORA9IR2> merge into alpha_temp A
  2  using ( select b.seq_num, sum(b.entity_ttl) entity_ttl
  3            from beta_ent_temp b, alpha_temp a
  4                   where a.seq_num = b.seq_num
  5           group by b.seq_num ) B
  6  on ( a.seq_num = b.seq_num )
  7  when matched then update set a.entity_ttl = b.entity_ttl
  8  when not matched then insert ( seq_num ) values ( NULL )
  9  /

5 rows merged.

but in 10g, you would only need to leave out the when not matched

ops$tkyte@ORA10G> merge into alpha_temp A
  2  using ( select seq_num, sum(entity_ttl) entity_ttl
  3            from beta_ent_temp
  4           group by seq_num ) B
  5  on ( a.seq_num = b.seq_num )
  6  when matched then update set a.entity_ttl = b.entity_ttl
  7  /
Rating: 5
Update a table from another table February 09, 2004
Reviewer: Steve Booth from Pewaukee, WI USA
Thank You! I spent four hours trying to figure this out before coming here. This
isn't an intuititively obvious answer, but I don't think I'll be forgetting it.
Rating: 5
February 23, 2004
Reviewer: JHT from NJ USA
Tom,

From a previous posting, you recommended...

update test
    set pants = (select max(pants)
                   from test2
                  where test.ssn = test2.ssn)
  where pants > to_date( '01-JAN-2003', 'dd-mon-yyyy' )
    and exists (select pants
                  from test2
                 where test.ssn = test2.ssn);

I have a similar SQL as this one (except that it's a join of 3 tables instead of
just 2 tables).  When a "max(pants)" is required, is there a way to change this
to updating a join view.  I'm using 8i, but also interested in if using 9i will
make any difference (e.g. using the merge option).  Thanks.
Followup:
using merge, sure.

merge into test
using ( select ..... from ...... ) test2
on (join condition)

test2 can in effect "be anything at all"
Rating: 4
Slow February 23, 2004
Reviewer: A reader
Hi,

The merge is good for non-key problem, but it was kind slow when I ran it with
only couple of hundreds records for each two table. DB: 9i

thanks
Followup:
that is not a very scientific sort of statement.

It is kind of blindingly fast for me.

Show us the test case and we'll tell you why in your case it was (and would have
to be) "kind of slow".
Rating: 4
February 24, 2004
Reviewer: JHT from NJ USA
Thanks Tom,  but what about in 8i ("merge" not available), is there a way to
convert this to an update of a join?

update test
    set pants = (select max(pants)
                   from test2
                  where test.ssn = test2.ssn)
  where pants > to_date( '01-JAN-2003', 'dd-mon-yyyy' )
    and exists (select pants
                  from test2
                 where test.ssn = test2.ssn);
Followup:
you'd have to use a gtt - flow the results of

select ssn, max(pants) max_pants
  from test2
 group by ssn;

into a gtt with a primary key on ssn and then update the join

update (select pants, max_pants
          from test, gtt
         where test.ssn = gtt.ssn
           and test.pants > to_date( .... ) )
   set pants = max_pants;
February 24, 2004 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.
Rating: 5
Copying table with LONG datatype. February 23, 2004
Reviewer: Sai. from San Jose, CA USA.
Hi Tom,

I've been waiting to get a slice of your time to ask a question for the past few
weeks.

I would really appreciate if you help me with this follow up question:

We have a 150G table with a LONG datatype column, we need to migrate this db
from 2k to 8k block size. The maximum donwtime we can get is 12 hours. We are
thiking of using combination of shareplex and standby database for this purpose.

Another plan was to copy that 150G table in about 6 hours, sqlplus copy for sure
was taking more than 6 hrs. We have left with Perl DBI, Oracle OCI, or Pro*C.

I would like to parallelise this process by using bunch of OCI threads based on
rowid ranges and insert into new table with lots of freelists.

Can you please help me write a small OCI program to copy a table with LONG
datatype column using APPEND hint in the insert operation.

Thanks a lot.
Followup:
well, my question would be "why do i NEED to migrate from 2k to 8k"

and why couldn't i migrate the long to a clob.....  so I can just use alter
table move in the future to move from tablespace to tablespace (and change the
blocksize on the fly)

and sigh, never a version...

If I had a 150gig table, with a long I'd be looking to fry all of my fish at the
same time -- like "we need a clob, we need partitioning to be able to maintain
this in the future" as well.
Rating: 3
Re: Copying table with LONG datatype February 23, 2004
Reviewer: Sai from San Jose, CA USA
Hi Tom,

Thanks for your help. That is the big constriant from our third party vendor
application, we got no control atleast for now. We will eventually migrate LONG
column to CLOB datatype when we have new application version available.

Can you please help me with writing a OCI program to copy a table with LONG
datatype using APPEND hint (it would be great, if we have something like BULK
insert in Pl/sql).

Thanks for your time.
Followup:
version
version
version
version

and why "2k to 8k", what is the motivation.  perhaps this move is "not going to
do very very much"

whats the science behind the change?
Rating: 3
Re: Copying table with LONG datatype February 23, 2004
Reviewer: Sai from San Jose, CA USA
Hi Tom,

We are on 64 bit Oracle Enterprised version 8.1.7.4

Our troubled third party vendor application does too many full table scans of
huge tables, we don't have any way of changing their code. Apart from that, all
of our other databases are on 8K, it was made as a stadard at our company to
create all 8K blocksize database's. We grow at the rate of 100% every couple of
years. Our Architect's made that decision. Migration to 8K blocksize db would
improve full table scans, and much reduced row chaining/migration. When we get
newer application version later this year, we wouldn't have many of these
problems.

Perfect logic/science doesn't really work all the time at each and every big
companies conflicting to their standards made by architects.

Would you please help me write that OCI program. Your help is very much
appreciated.

Thanks.
Followup:
OCI isn't the way to go.  Too much work.

I'd look at using sqlplus copy command (run in parallel, slice the table up by
rowid ranges or primary key ranges)
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:10498431232211
I'd look as exp/imp (run in parallel again, slice the table up and use query= on
the exp command line)

I'd look at "prove to me that a) setting db_file_multiblock_read_count =
4xNORMAL won't have the same effect as going to an 8k block AND b) prove to me
that we'll reduce row chaining (longs will chain in 8k as well as 2k if they
exceed the block size before I waste a couple 10's or 100's of thousands of
dollars in company time doing this "just because")

those longs are going to be problematic since they will defeat attempts at array
processing.  You'll just have to parallelize it.
Rating: 5
Counting migrated rows. February 24, 2004
Reviewer: Sai from San Jose, CA USA
Hi tom,

Thanks so much for your help on this. One last question:

In one of your articles you said, full table scan would not go to forwaring
rowid when it hits a migrated row, becuase it will eventually get there. Does
this work similarly when we parallelise jobs using rowid ranges, i.e., does the
same row fetched twice in different rowid ranges?

Thanks
Followup:
as far as I know, yes, never really setup a test to "prove" it tho.
February 24, 2004 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;
Rating: 5
Possible with Selective Loading? January 28, 2004
Reviewer: W. Leong from Singapore
First of all, thanx for the posting. It prove most useful for my existing
project where we need to convert dbf (yes, people do use it still) to oracle.

Question: Is the package able to perform selective load?
Example:
begin
dbase_pkg.load_table( 'MY_FILES',
                      't1.dbf',
                      't1',
  'cfield, logical' );
end;
/
(I have tried but it failed with 'too many values' error)

Reason I am asking is because we need to perform a daily migration of dbf to
oracle. Some of the tables are fairly large (150MB) and it takes easily 30 min
just to perform such migration using this package. Given 50% of the data are not
relevant, it will be most helpful if we are able to load only the selective
field and thus speed up the performance.

Cheers
-WK-
Followup:
you have the code, you have the logic, you can make it do whatever you need!
Rating: 5
It works except column format February 24, 2004
Reviewer: Henk Koekkoek from The Netherlands
On one dbf file it works perfectly, on another there is no column format
specified for all but one column. What could be the cause of that (see output
below)

Henk
A.S. Watson
------------------------------------------
Sizeof DBASE File: 840127582
DBASE Header Information:
        Version = 3
        Year    = 2004
        Month   = 2
        Day     = 24
        #Recs   = 5154153
        Hdr Len = 642
        Rec Len = 163
        #Fields = 19

Data Fields:
Field(1) Name = "ARTIKELID
Field(2) Name = "FILIAALID
Field(3) Name = "AANTAL
Field(4) Name = "GELEVERD
Field(5) Name = "BTWPERC
Field(6) Name = "DATUM
Field(7) Name = "TIJD
Field(8) Name = "PREVRECNO
Field(9) Name = "NEXTRECNO
Field(10) Name = "VERVANGT
Field(11) Name = "BRONINKPR
Field(12) Name = "BRONVRKPR
Field(13) Name = "USERINKPR
Field(14) Name = "USERVRKPR
Field(15) Name = "HISTORIEID", Type = C, Len  = 9, Scale= 0
Field(16) Name = "ORDERID
Field(17) Name = "VRDOUD
Field(18) Name = "GIPOUD
Field(19) Name = "GIP

Insert We would use:
insert into
inkhis_tmp("ARTIKELID
,"DATUM
R
UD
(:bv1,:bv2,:bv3,:bv4,:bv5,to_date(:bv6,'yyyymmdd'
),:bv7,:bv8,:bv9,:bv10,:bv11,:bv12,:bv13,:bv14,:bv15,:bv16,:bv17,:bv18,:bv19)

Table that could be created to hold data:
create table inkhis_tmp
(
        "ARTIKELID
        "FILIAALID
        "AANTAL
        "GELEVERD
        "BTWPERC
        "DATUM
        "TIJD
        "PREVRECNO
        "NEXTRECNO
        "VERVANGT
        "BRONINKPR
        "BRONVRKPR
        "USERINKPR
        "USERVRKPR
        "HISTORIEID"   varchar2(9),
        "ORDERID
        "VRDOUD
        "GIPOUD
        "GIP
/

PL/SQL-procedure is geslaagd.
Followup:
stick some debug in there and see what you see.  the code is all there and
fairly straight forward.

without having your exact dbf file, i cannot really say.  I just just the
documented format I saw and implemented code that could read it.
Rating: 5
Excellent package! March 08, 2004
Reviewer: Robert Massey from Atlanta, GA USA
Tom,

I have a need to import a dBase table, and I decided to give your site a search
(as I so often do).  Your dbase_pkg is perfect!  Many thanks!

Robert
February 24, 2004 -- 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.
Rating: 4
shared pool miscellaneaous February 24, 2004
Reviewer: Fred from France
Hi Tom,

I run 8.1.7.0.0 on Solaris 2.8.

I am writting a paper on a database : they dont use BIND, have a huge shared
pool, bla, bla. I *know* that they should use BIND and reduce the shared pool
size but i'd like to *prove* it (like a guy around there always tell about to
prove something :-)).
I know how to prove that BIND rocks, that shared_pool_reserved_size is useless
but i need some clues to prove that they should reduce the shared pool...

SQL> show parameter shared_pool

NAME                                 TYPE    VALUE
------------------------------------ ------- -----------
shared_pool_reserved_size            string  50000000
shared_pool_size                    string  200000000

SQL> select * from v$sgastat where pool = 'shared pool' order by bytes ;

POOL        NAME                            BYTES
----------- -------------------------- ----------
shared pool trigger defini                    424
            trigger source                    508
            trigger inform                    592
            fixed allocation callback         960
            table definiti                   1840
            PLS non-lib hp                   2096
            joxs heap init                   4248
            KGK heap                         4372
            PL/SQL PPCODE                    6060
            PX subheap                       6448
            KGFF heap                        7760
            table columns                   22388
            PX msg pool                     45540
            DML locks                      108000
            State objects                  108444
            message pool freequeue         124552
            sessions                       187572
            event statistics per sess      302064
            enqueue_resources              360000
            PL/SQL MPCODE                  427356
            PL/SQL DIANA                   984812
            ktprhtnew3                    1144312
            db_block_hash_buckets         1179880
            dictionary cache              1658076
            KQLS heap                     2053700
            db_block_buffers              8912896
            library cache                23252764
            sql area                     25855652
            miscellaneous                67721764
            free memory                  77274992
***********                            ----------
sum                                     211760072

1/ shared pool library cache = the executable form of SQL => right ?

2/ free memory = 77 MB (50MB of reserved_size + 27MB of shared pool) => right ?

3/ "shared pool miscellaneous is the sum of all things which initially were too
small to have an individual line item in v$sgastat" => ok but what is "in" this
categorie ? (i have 67 MB of theses small things in my shared pool... ?)

4/ what contains "shared pool sql area" ? => the SQL code ?

5/ What is "shared pool db_block_buffers" ?

Thx by advance,

Fred
Followup:
you would have to setup a simulation.

really large shared pool without binds = eventual ora4031's and need to flush

smaller shared pool without binds = it'll take care of itself.

you cannot look at the memory allocations and show this.
February 24, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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.
Rating: 4
Suggestion for 70 mil rows table February 19, 2004
Reviewer: reader from nj, USA
Tom,

Have a table that consists of 70 mil rows. (total space 3G)
The data is mainly categorized by project_number.
There are around 400 unique project numbers in table & have a data for 5 years
starting 1999.
Data in a single project usually span across multiple years.

Query uses project_number as primary filter.
We have index on the column so no problem selecting data from table. But when we
try to insert / update it takes hours & hours.

Some developer uses 'append' hint to make the query faster.

I think the best solution is list partition , but since we are on 8i I guess
it's not allowed.

Can we go to range partition with project_number?
few examples of project number are
1145
1146
2003
4320
etc..

Need suggestion.
Followup:
you'll have to tell me what you do during the updates/inserts. how many rows are
we talking about here -- I cannot imagine "hours and hours".

how many indexes, how big are the modifications, and what is the process whereby
it is done.
Rating: 4
Suggestion for 70 mil rows table February 19, 2004
Reviewer: reader from nj,USA
Insert around 20,000 to 200,000 rows at a time.

table desc as follows

 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 PROJECT_NBR                           NOT NULL VARCHAR2(8)
 CAMPAIGN_ID                                  VARCHAR2(2)
 CELL_ID                                        VARCHAR2(8)
 EVENT_DATE                               NOT NULL DATE
 RESPONSE_DATE                                      DATE
 CUST_NUM                              NOT NULL VARCHAR2(10)
 CONS_ID                               NOT NULL VARCHAR2(18)
 HH_ID                                         VARCHAR2(20)
 RESPONSE_INFERRED                                  CHAR(1)
 RESPONSE_ACTUAL                                    CHAR(1)
 RESPONSE_INCLINED                                  CHAR(1)

two indexes
1. project_nbr , campaign_id , cell_id ,cust_num
2. cust_num

Modifications are changing RESPONSE_???? columns for given
project_nbr/campaign_id  involves anywhere between 20,000 to 100,000 rows.
Also changing cell_id for given project_nbr/campaign_id
etc..
Followup:
how do you insert them please.

o slow by slow (meant row by row)
o in array inserts of N rows at a time
o using direct path load

etc.....  lay out the algorithm.

how long does 20,000 rows take and 200,000 rows
Rating: 4
Suggestion for 70 mil rows table February 19, 2004
Reviewer: reader from nj,usa
Continuation for my last post.

Updates are done by normal update statement.

Inserts few developers does normal insert developers does insert with append
hint.
Followup:
lets see an example -- give me the algorith you are using.  how many rows are
inserted at a time, per call.
Rating: 4
Suggestion for 70 mil rows table February 19, 2004
Reviewer: reader from nj,USA
one example is as follows

insert /*+APPEND */ into tag_response_customer
(PROJECT_NBR,CAMPAIGN_ID,EVENT_DATE,CUST_NUM,cons_id,HH_ID )
select '2003','AJ',to_date('022004','MMDDYY'),cust_num,'XXXXX',HH_ID
from JOB2003_022004_ROOT1
where select_ind = 'Y' ;

not everyone is using append hint.

but 95% time insert is insert into table by selecting rows from another table.
The actual table may contain anywhere 50,000 to 500,000 rows.
Rows we insert varies from 20,000 rows to 100,000 rows.
also most of the time table from which we select goes in full table scan. since
the where conditions is not indexed.(in above example select_ind is not indexed)

When we spool the file (in above example we will spool from
JOB2003_022004_ROOT1) for even 100,000 rows it does not take more than 15mins.

TIA
Followup:
show us a TKPROF of that please.
Rating: 4
Suggestion for 70 mil rows table February 19, 2004
Reviewer: reader from nj,USA
Tom,

Thanks for your interest.
I don't have alter system access.

I have placed a request for it. It might take a while.
BTW There is a trigger on the table (after insert/delete)
so no update trigger but still update is slow.
Rating: 4
Suggestion for 70 mil rows table February 19, 2004
Reviewer: reader from nj,USA
sorry for immediate reply.

I was dump to say I don't have system access.

I am running tkprof & will get the results once I have it.
Rating: 3
Inserting into partitioned table February 19, 2004
Reviewer: Denise from Virginia,USA
Tom

I created a partitioned table(pgift) that will store
8 mil+ records.
(I'm just posting the partition clause of the create
 table statement)

PARTITION BY HASH (usercode1)
        (partition p_acga tablespace aclj_data,
         partition p_aclj tablespace aclj_data)

since I haven't created any indexes yet I figured
an insert would be easier than an import.

I want to insert from the gift table all the
records from gift where usercode1='ACGA' into p_acga.

In looking through the other threads here I mainly
saw examples of partition by range.

I want to make sure that I partition the correct values
of usercode1(gift) into the correct partition(pgift).

have looked everywhere for an example of a partition
by hash insert statement.

any hints or help is much appreciated.
Followup:
You are missing the point of hash partitions -- you sound like you want to use
LIST partitioning (9i)

the best column to hash on is one with lots and lots of distinct values -- to
get a nice even distribution over many partitions.  A table with two hash
partitions isn't going to buy you too much and unless usercode1 has lots and
lots of values -- it won't be very good at all (good chance ALL of the data ends
up in a single partition!).  You quite simply have NO control over what
partition data goes into with hashing -- it goes where it wants to.  You cannot
ensure that 'ACGA' goes into p_acga.

You would have to use RANGE or LIST partitioning to control what data goes
where.
Rating: 4
thanks Tom... February 20, 2004
Reviewer: Denise from Virginia,USA
for the clarififcation..I'll go back and read up on
partitioning...most likely it will be hash by range then.

My goal is to split the 8 mil records in accordance to
the client they are assigned..this way when we have analysis
reports to run by client the queries don't have to filter
through 8 mil records to process the results.

thanks again...and have a good w/e!
Followup:
"hash by range"?

You sound like a candidate for LIST partitioning then.
Rating: 4
it's FRIDAY!!!! February 20, 2004
Reviewer: Denise from Virginia,USA
you'll have to excuse my brain dysfunction...meant "partition by range"...
however I'll read up on the LIST option.

:~)
Rating: 4
How do I know if table is partitioned February 24, 2004
Reviewer: sonali from waltham, ma
One of our customer sent us database dump.. they have partitioned some of our
tables.. I just did the import from command prompt (9i server). But on of our
procedure is giving the error -
SQL command executed: {CALL TimeSubmit (211, 211, 292121, '22-Feb-2004',
'28-Feb-2004', 20)}
ORA-14402: updating partition key column would cause a partition change
on the imported schema here.. I did do
Slect * from user_tab_partitions and user_ind_partitions both have 0 rows..
where is it looking for partition ?
I can do alter table to all the movement of partition on UPDATE.. but still
puzzled as to where its looking for partition..what query can I do to find it ?

Thanks
Followup:
if they partitioned some of the tables -- you must have some partitioned tables
-- yet you say you have none?

so, perhaps you are in the wrong schema.

  1  select distinct owner, segment_type
  2*   from dba_segments where segment_type like '%PARTITION'
ops$tkyte@ORA9IR2> /

will show you the owners of partitioned objects.

(YOU NEED TO USE BIND VARIABLES!!!! it is of the utmost importance.  also, using
22-feb-2004 is not safe, you should be using to_date( :bv, 'dd-mon-yyyy' ))
February 24, 2004 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://download-west.oracle.com/docs/cd/A91202_01/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.
Rating: 5
native compilation ... February 21, 2004
Reviewer: reader
Is it true that compiled coce of a pl/sql program unit is mapped into the PGA
rather than in Shared pool (sga) in the case native compilation? If this is the
case, if the session logs off that piece of compiled code is lost. How this
helps in sharing? I am a little confused on this. Could you please help in
clarifying this. Thanks.
Followup:
native compilation creates binary object code (a dll in windows, a so, sl file
in unixes and so on)

the OS manages code for us, on windows for example, windows shares the DLL code.
 On unix, the OS shares the text pages for shared objects like that.

the code isn't in the pga, data is in the pga (allocated variables and such) but
the code itself is managed via the traditional OS mgmt routines and shared just
like the single oracle binary is shared.
Rating: 5
If it is interpreted ... February 21, 2004
Reviewer: David
Tom, I read your explanation above for native compiled code that is managed by
the OS. If that is the case, can I configure a smaller shared pool compared to
interpreted codes? Is my understanding correct that interpreted pl/sql codes are
placed in the shared pool now that memory space is saved in the shared pool if i
used native compilation? Is this an advantage with regard to smaller shared
pool? Thanks.
Followup:
perhaps -- never really measured it.  You'd have to natively compile everything.

There is still alot going on in the shared pool - even if all were natively
compiled.

It would not be the motivation to use native compilation, it might be a small
side effect.
Rating: 4
can we set NATIVE compilation globally? February 24, 2004
Reviewer: George from Drexel Hill, PA USA
as always, thanks for your help.. this discussion has already been helpful.

Is it possible to set Native compilation on database startup?

Thanks, George
Followup:
you can set it in the init.ora
February 25, 2004 -- 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.
Rating: 4
Timing of queries before and after flashback February 24, 2004
Reviewer: Eric Peterson from Bellevue, WA
The time it takes to do the same query before and after a flashpoint seems to be
widely different (1 minute versus 9 minutes).  When I ran this the system was
not under any great load nor had many users.

Is there something I need to take into account when running queries after
issuing a flashback?  What would explain the vast difference in the timing of
the same query?  Is this something I should expect when using this package?

We are on HPUX 11 and Oracle 9.2.0.4.  This table (as most of the data on this
machine) is on a 10 minute snapshot from a different Oracle instance.

Thanks for all the great help you provide.
Eric

epeters> SELECT
  2      MAX ( creation_date ) AS max_dt
  3  FROM
  4      subscriber;
MAX_DT
--------------------
24-FEB-2004 11:26:30
1 row selected.
Elapsed: 00:01:105.70

epeters> EXECUTE DBMS_FLASHBACK.ENABLE_AT_TIME ( TO_TIMESTAMP ( '23-FEB-2004
16:00:00', 'DD-MON-YYYY HH24:MI:SS') );
PL/SQL procedure successfully completed.
Elapsed: 00:00:02.50

epeters> SELECT
  2      MAX ( creation_date ) AS max_dt
  3  FROM
  4      subscriber;
MAX_DT
--------------------
23-FEB-2004 15:43:52
1 row selected.
Elapsed: 00:09:597.53
Followup:
You flashed back 1 day -- 24 hours.

Also, that it took over one minute to get the result in the first place
indicates to me "probable full scan of really big table"

Well, every change -- every single change that occurred in the last 24 hours to
that table would have to be undone.

So, lets take a 3.5 million row table and do that max on an unindexed date
field:

big_table@ORA920PC> alter system set sql_trace=true;

Session altered.

big_table@ORA920PC> select max(last_ddl_time) from big_table;

MAX(LAST_
---------
09-JUL-03

1 row selected.

big_table@ORA920PC> a  as of timestamp sysdate-1
  1* select max(last_ddl_time) from big_table as of timestamp sysdate-1
big_table@ORA920PC> /

MAX(LAST_
---------
09-JUL-03

1 row selected.

And tkprof says:

select max(last_ddl_time) 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     13.56      16.02      50526      50541          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4     13.56      16.04      50526      50541          0           1

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=50541 r=50526 w=0 time=16020097 us)
3500000   TABLE ACCESS FULL BIG_TABLE (cr=50541 r=50526 w=0 time=10404911 us)
********************************************************************************
select max(last_ddl_time) from big_table as of timestamp sysdate-1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.03          0          0          0           0
Execute      1      0.01       0.13          1          9          0           0
Fetch        2     13.43      14.71      50526      50541          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4     13.44      14.88      50527      50550          0           1

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=50541 r=50526 w=0 time=14719081 us)
3500000   TABLE ACCESS FULL OBJ#(39083) (cr=50541 r=50526 w=0 time=9631509 us)

showing that hey -- if there isn't anything to undo, this flashback stuff has
no effect on runtime performance.

Which makes sense since EVERY SINGLE QUERY in Oracle is a flashback query in
effect -- always has been.

A flashback query differs not a bit from

a) open query on monday night, go home
b) come back tuesday night and fetch a row

In your case, over the course of 24 hours, enough work has in fact been done to
that table to cause it to have to perform lots of work in order to undo the
changes.  Also, it is most likely that the UNDO needed from a couple of hours
ago would no longer be in the buffer cache (checkpointed hours and hours ago) so
you would see lots of additional PIO's to get this.

You can use SQL_TRACE as I did to see whats up.
Rating: 5
very informative February 25, 2004
Reviewer: Eric Peterson from Bellevue, WA
Excellent as always.

Thanks much
Eric
February 25, 2004 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
Rating: 4
import tables February 24, 2004
Reviewer: A reader
Tom,
trnn1 schema is in DEV and PROD. Say there is one table trnn1.TB1 in PROD I want
to replace with trnn1.TB1 from DEV. But the table definitions of these tables
are not exactly same. Can I export trnn1.TB1 from DEV and sucessfully import it
to trnn1 on PROD without droping trnn1.TB1 in PROD?

Thanks
Mike
Followup:
how exactly do they differ.  then maybe I can say yes or no.
February 25, 2004 -- 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;
Rating: 5
pctused February 24, 2004
Reviewer: reader
If I set PCTUSED = 0, then the blocks are never reused for future inserts? Is
this true? Thanks.
Followup:
no, not at all.

the block will be used for inserts up until it hits it's pctfree setting.  then
it would have to effectively empty out before being used for inserts again.

so, upto pctfree -- block is used for subsequent inserts.  empty the block and
it'll be used for subsequent inserts.
February 25, 2004 -- Thanks for the question regarding "latch contention", version 8.1.6
You Asked
Hi Tom,

I'm trying to identify the source of some sleeps. I did the
following query with these results:

SELECT addr, latch#, gets, misses, sleeps FROM v$latch_children
WHERE sleeps>2000 ORDER BY sleeps desc;

 ADDR        LATCH#      GETS    MISSES    SLEEPS
 -------- --------- --------- --------- ---------
 63448620        66 1.112E+09   4724658      6573
 634350F4        66 609065631   1957482      2327
 633B87C8        66 335239357    255728      2003

Then I used the address of the first row in the following query
and got back no rows selected.

SELECT a.segment_name , a.segment_type , a.owner , a.tablespace_name
FROM sys.dba_extents a,
(SELECT dbarfil,dbablk,class,state FROM x$bh WHERE hladdr='63448620') b
WHERE a.file_id = b.dbarfil
AND b.DBABLK BETWEEN block_id and block_id + blocks -1;

no rows selected

What am I doing wrong to identify the object? The address from
v$latch_children doesn't appear in x$bh?

Thanks
and we said...
That block can be lllllooooonnnnnggggg gone by now (from x$bh).  Is the subquery
returning anything.

If you look at your numbers, you are actually doing excellent.  I would not lose
1 second of sleep over this -- you do not have any serious contention issues
here -- compare the GETS to SLEEPS.

You have 335 MILLION gets and 2000 sleeps.  that means that

0.0005% of your gets were affected.  Hmmmmmm.

2000 by itself sounds big, until you put it next to 335 MILLION.

Look for sleeps as a percentage of gets instead of a raw number like 2000.
Review & Followup

Rating: 2
Seem to have a similar problem... December 17, 2002
Reviewer: Mike from UK
Hi Tom,

we seem to have a similar problem, were trying to benchmark our software, and as
such simulate 50 users busily using the software via PL/SQL scripts.

we've done some tests, and while everything is fine and dandy with a single user
the more we add the worse it gets... we've got to 50 concurrent users now and
things arew getting a little slooooooooow.

we've stat's packed it and found some very worrying things, I searched through
your book (v good btw) and we're looking into some stuff but I still have some
questions...

Our top 2 wait events are quite distressing...

~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
latch free                                        804,573      566,778   43.82
library cache pin                                  15,044      469,781   36.32

The latch free we are fairly happy is due to some very hot index leaf blocks,
we're going to reverse key those to see if that helps the problem.

the other on the libary cache pin is a bit more distrubing.  the stats pack
report also shows an alarming CPU to wall clock%...

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:   99.96       Redo NoWait %:   99.22
            Buffer  Hit   %:   99.96    In-memory Sort %:  100.00
            Library Hit   %:  100.01        Soft Parse %:   99.99
         Execute to Parse %:   95.82         Latch Hit %:   98.95
Parse CPU to Parse Elapsd %:    6.88     % Non-Parse CPU:  100.00

but the others seem quite "nice"

the following also jumped out at me as a good thing,

Statistic                                    Total   per Second    per Trans
--------------------------------- ---------------- ------------ ------------
parse count (hard)                               4          0.0          0.0
parse count (total)                         48,489         69.5         13.8

The bit that then confussed the hell out of me was the Latch sleep breakdown
which showed

                                Get                                  Spin &
Latch Name                    Requests         Misses      Sleeps Sleeps 1->4
-------------------------- -------------- ----------- ----------- ------------
library cache                   4,872,597     389,100     745,712 91090/21933/
                                                                  175257/10082
                                                                  0/0
cache buffers chains           33,991,454      61,916      44,479 31982/22648/
                                                                  3899/3387/0
cache buffers lru chain         5,799,637      32,872       5,635 27605/4935/2
                                                                  96/36/0
undo global data                1,386,214       7,996       1,834 6311/1542/13
                                                                  7/6/0

Which says to me after reading page 491 of your book around 491 times that not
only does a libary cache latch request have a 1 in 10 chance of missing, but if
it does it will probably end up sleeping twice, as most missed laches get
serverd on the 3rd attempt!

eurgh!!  where have we gone wrong?

Thanks in advance for any pointers...

Mike.
Followup:
the execute to parse is

round( 100 * ( 1- PARSES/EXECUTES), 2 )

for that to be very near 100, your parses must be very very near your executes.

What is your application written in?

How many/what kind CPU's do you have?

Why are you parsing and executing statements only ONCE in your application --
you never reuse any SQL?

How is your benchmark setup -- any think times (realistics think times)?
Rating: 3
Answers to Questions December 19, 2002
Reviewer: Mike Jones from UK
Hi, Thanks for the response!

The application is pure PL/SQL.

It's running on a 4CPU (550 MHZ) HP box, which for clarity is running 32bit
Oracle 8.1.7.3 (with some memory patches) on 64 bit HP-UX (historic reasons for
the 32/64 mismatch)

I'm alarmed that we are not reusing any SQL, I can say that most of the SQL in
the application is implicit cursors, using either a ROWID in the where or doing
a bulk collect.  I assume this is the reason?  if so it would seem logical to
convert these to explicit cursors, however I also remember reading a comment
from you that you NEVER use explicit cursors, so how do you get around the
problem?

The stat's pack report I pasted parts of are not realistic in terms of think
times, this was intentional, but also mis-guided.

We have moved on now, to having far more test data in the system, so we are only
working on a small percentage of the total amount of data in the system (but
still as much actual data) and introducing realistic wait times.

This seems like an additional question to me, If you would like I will wait
until I can post a proper Question.

Thanks as always,

Mike.
Followup:
implicit cursors are reused transparently.

one would need to know HOW the test was conducted -- did you

o login
o run a procedure
o logout
o goto 1

or

o login
o run a procedure
o goto 2 for a couple of runs
o logout

if you did the first case, you might expect what we see.  If you did the second,
we would not.

Make sure your benchmark mirrors reality (or it is just a waste of time/energy
and effort)

working on a subset is another "bad idea" unless it is a vertical subset (and
the user base is also a vertical subset).  Eg, only 100 out of the 500 tables
(vertical) but totally full..
Rating: 5
really confussed.... December 19, 2002
Reviewer: A reader
Hi, thanks for the follow ups...

I'll explain it in detail, as what we are seeing seems to go against what you
are saying.

the application in question is a Workflow system, so...

A) user completes an action,
B) the system works out what they have to do next and tells them
C) go to A

on the scenario we tested we set up 50 "plans" that contained in total 250
actions that the user had to do.

we used 1 script which we passed in a parameter which was the plan that the
script would work on, we then ran the script 50 times (concurrently) with each
script given a different plan to work on.

the script goes

a) find an action to do
b) do it (which involves the system working out what they should do next)
c) go back to a

when it finds no more actions to do the script ends and the session is closed.
To mimic the end user, after each action is done a commit is issued by the
script.

the call to complete the action and work out the results, is quite intensive,
this is a package call, we are confident that we are using bind vairables
throughout.

I have two very confussed DBA's, one of them commented, that although the
benchmark did not mirror reality in that it replayed the transactions quicker
than a user would, he would not expect this to have any impact on whether a soft
parse was required or not, and that we clearly don't understand something that
we really all should.

As a side note on the data, we thought that having 50 plans in the sytem and
working on them all was not fair, as in reality, a production system would have
over 60K plans and we would have at most 50 users working through 50 of them. so
we have changed the test data to reflect this, the number tables worked on is
always the same.

As ever thanks for sticking with this.

Mike.
Followup:
does this process do a ton of dynamic sql?
Rating: 5
Dynamic SQL usage December 19, 2002
Reviewer: Mike Jones
Define ton!  It does a lot, yes, each call to the system to "work out the
consequences" could result on average in 100 dynamic SQL statements, however
they would all be very similar, and would use bind variables.  I would say we
would have only 3 or 4 distint ones in terms of the actual SQL that would appear
in V$SQL.

99% of this is via execute immidiate as well, only a small amount (perhaps 3 per
session) use dbms_sql.

I know that execute immidiate's cause a soft parse, I think I can see where you
are going with this.  In short we need to reduce this.  Would using DBMS_SQL
over execute immediate address this?  my memory's hunch is that it would as we
take control of the cursors at that point.

Thanks again, I think I owe you a drink!

Mike.
Followup:
that is your soft parse then -- every native dyanmic sql call is by definition a
soft parse.

In order to reduce some of the latching -- set session_cached_cursors to say 100
and try again.

If the statement is to be executed many times during the course of the session
-- dbms_sql would be better -- then you are able to

o parse ONCE
o execute over and over and over
Rating: 5
Reader December 19, 2002
Reviewer: A reader
May be it is just an oversight
"
the execute to parse is

round( 100 * ( 1- PARSES/EXECUTES), 2 )

for that to be very near 100, your parses must be very very near your executes.

"

For a value of 95.82 in the statspack,
parse/execute = .0418 or 4 parse to 100 executes. Is this
not an acceptable statistics
Followup:
whoops -- you are right ;(  got that backwards.

1 parse, 25 executes is pretty good -- but, in a wicked twist of fate -- turns
out they are doing lots of dynamic sql (native dynamic) which does increase
their parses and if it can be reworked to avoid that....

thanks for the followup.
Rating: 5
buffer cache latches February 24, 2004
Reviewer: Reader
I undertand that 9i allocates latches for buffer cache. Is there a view that I
can use to see how many latches oracle allocated for each of the cache? Thanks.
Followup:
  1* select name, count(*) from v$latch_children where name like '%cache%' group
by name
ops$tkyte@ORA920PC> /

NAME                             COUNT(*)
------------------------------ ----------
cache buffers chains                  512
cache buffers lru chain                 8
....

6 rows selected.
February 25, 2004 rama -- Thanks for the question regarding "Triggers vs Packages !", version 8.1.7
You Asked
Tom,

1.I joined this firm recently and found the following in practice.
Dont write database triggers - reason they ( ex. DBA) said, they would
considerably slow down the DB performance. So, use Packages, procedures,
functions. Fortunately for me, I have the habit of packaging all the source
code. But, is it really true that triggers slow down the database ?
2.If a senior developer needs to take the role of a backup DBA, what are the
primary things he needs to be interested on?
and we said...
1) no, triggers will not "slow down" the database.  However, doing things in
packages in my opinion is many times better and can be more performant then
triggers.

Triggers can lead to convuluted logic, lots of unexpected side effects (they
should have been expected but weren't).

Triggers can make debugger harder, things "just happen".

However triggers can and ARE very very useful.  They should not be OUTLAWED,
they should be used judiciously.

For example, if some column has an edit that says "upper case data only".  I
would vote for a trigger.

If some object has a complex business rule that touches a dozen other tables,
does tons of sql itself, causes other triggers to fire -- gets really tricky --
I would vote for a packaged procedure to do the logic from the get go (easier to
understand).

All features in the database, each and every one, has ups and downs -- pros and
cons.  I hate it when features are outlawed because someone used it
inappropriately at some point in time.  Common sense, experience, testing,
trying alternatives -- that is what should be used.  Not a draconian "thou shalt
never use feature X".

2) the pager number of the real DBA?  How to contact the real DBA when they are
not there?

Depends on your site.  Do you want Junior inexperienced person trying to perform
media recovery (i don't, you can do more harm then good if you don't know what
you are doing).

Do you want Junior inexperienced person adding datafiles due to a lack of space?
 Maybe (maybe you really did run out of space).  Maybe not (maybe we ran out of
space cause of a bad statement that needs to be found and fixed -- not given YET
more space to consume).

It really depends on your needs.  I would ask the real DBA what they felt the
senior developer needed to know (and hopefully that real DBA knows to make
"jr_dba" role and grant it selective privs -- just enough to do what they've
been told they can do, like reset a password or something ;)
Review & Followup

Rating: 5
The programmers view.... September 07, 2002
Reviewer: Chuck Jolley from Oklahoma City, OK USA
The DB I am working on right now has many triggers some of which are several
hundred lines long. We have never had any problem with them creating performance
issues.
They are virtually all row level triggers.

This is how we decide between triggers & packaged code.
Triggers are event handlers and procedures & functions are, well, functions. ;)
Which is appropriate for a given unit of business logic should be immediately
apparent to any experienced programmer.
Chuck
Followup:
Well, I always have to add my 2 cents worth here... ;)  of course....

I agree with your basic premise (common sense must rule) but... as a programmer
and as a guy who is asked to read lots of other peoples code to find bugs -- I
think a trigger that is several hundred lines long violates my rule #1 with
writing code.  That is way way way too long -- too many lines of code -- to be a
single entity.  Me, I would have modularized the trigger into dozens (I like my
code to fit on the screen from top to bottom and be able to see the entire
procedure/function/trigger -- that is what I strive for) of packaged procedures.
 The, the trigger would become about one line long and the procedures would have
munched the data.
Rating: 5
September 07, 2002
Reviewer: A reader
Rating: 5
Maybe a practice based on an older version? September 09, 2002
Reviewer: Connor from UK
I remember reading somewhere (newsgroup/Metalink?) that somewhere in the version
7.1/7.2 vintage, the p-code for triggers was not stored, and thus as soon as the
code got aged out, the next usage would effectively recompile the trigger.  I've
seen this used as the justification for triggers always being just a call to a
proc/pack/et al..

Is there/was there any truth to this?

Cheers
Connor
Followup:
correct -- pre 7.3 triggers were not stored compiled.  They were just text.
they got compiled on every hard parse of them.

That is not why I promote the use of packages in the above comment -- I promote
the use of packages cause I hate subroutines with hundreds of lines of code in
them.  I believe if you have more then 100 lines of code in a single routine,
you've gone too far.  Time to break it down into smaller bit size pieces.
Rating: 3
Performance and Packages September 09, 2002
Reviewer: A reader
Hi Tom,
One of our experienced DBA has given following suggestion.
"going forward pl try to create stored procedures in the packages to improve the
performance and easy maintenance".

Is it True ? Can you prove with the examples.
Thank You
Kalpesh
Followup:
Absolutely 100% true -- I love that DBA.  Except they should have said "you need
a written documented reason to use a standalone procedure"

Here is the #1 reason:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:286816015990
#2 and not less important is that packages promote good coding methodologies --
more modular, procedure hiding, less polution of the namespace (less object
names out there), grouping of related functionality, and so on.

Packages are the ONLY way to go for any real piece of code.
Rating: 4
Table name from trigger February 24, 2004
Reviewer: Sanjaya Balasuriya from Colombo, Sri Lanka
Tom,

First, I'm not sure this question suits this thread.

Say I have a table tab_A(col1 varchar2, col2 varchar2) and a before insert
trigger trg_A on tab_A.
I have another table tab_B.

Can I insert the table name (tab_A) and value of col1 to tab_B through the
trigger ? I want to avoid hard coding the table name in the trigger.

Thanks in advance.
Followup:
no, there is no function.
Rating: 4
HeadStart / CDM Rule engine February 24, 2004
Reviewer: Alex from Montreal
Hello,

In the context of triggers vs procedures, what is
your opinion on Headstart/cdm_rule framework.
There are a lot of "trigger"-driven procedures.

What is your opinion on "event proporgation"
via sequential triggers (under table level) versus procedures that does the same
thing (obove table level)?

Thanks in advance
does the same business from )
Followup:
wow, to search on google for headstart cdm_rule and not have a hit...

No standard web pages containing all your search terms were found.
Your search - headstart cdm_rule - did not match any documents.

guess I would need a pointer?
Rating: 3
February 24, 2004
Reviewer: Alex from Montreal
It is Oracle product :-)

Headstart plug-in for Designer and
CDM Rule Framework
http://otn.oracle.com/products/headstart/index.html
There is also JHeadstart, but I did not try it yet.

Thanks.
Followup:
sorry -- i've never used it.  I'll have to suggest you try the discussion forums
on otn.oracle.com
Rating: 3
CDM RuleFrame February 25, 2004
Reviewer: Rob from Netherlands
just search for "Headstart" and "CDM RuleFrame" and you'll get much more hits.

Good luck,
Rob.
February 25, 2004 Sundar -- Thanks for the question regarding "Orakill utility", version 8.1.6
You Asked
Hi,

I tried to use Orakill to kill a thread. But I am getting an error message
"Orakill unable to attach to the instance err=2"

OS is Windows 2000
Oracle Database Version is 8.1.6

I have used this tool to kill a thread on NT 4.0 server without any problem.

Is there anything special that has to be done on Windows 2000?

Sometime we run into a deadlock issue and Kill session would help us to release
the resources. I have used Orakill and it was helpful. Unfortunately, I can't
use this on the Windows 2000 / 8.1.6 server. Any help on this would be highly
appreciated.

thanks,

Sundar.
and we said...
sounds like you typed in the SID name wrong.  I'm using 8.1.6 on windows 2k and
have not had this issue.

check that the spelling of the sid is correct.

C:\Documents and Settings\Thomas Kyte\Desktop>orakill TKYTE816 1256
Kill of thread id 1256 in instance TKYTE816 successfully signalled.

C:\Documents and Settings\Thomas Kyte\Desktop>orakill TKYTE816x 1256
Could not attach to Oracle instance TKYTE816x: err = 2

when the sid is wrong -- it does not work.
Review & Followup

Rating: 3
Please include the syntax also if possible to help others July 20, 2001
Reviewer: Pichaimani Balasubramanian from San Jose, CA USA
Rating: 4
that works from the console April 25, 2003
Reviewer: eric givler from harrisburg, pa
Orakill works from the server, but not through a terminal services session.  The
error reported here matches the same error we got when running the client.
Rating: 3
Looking for a more elegant solution than orakill February 23, 2004
Reviewer: Matt from Australia
Win2K SP 4; 9.2.0.4
Here is my problem:

1) Running a data load (a bulk insert) from sqlplus
2) The window is killed (using 'x' in corner of window)
3) Data Load is re-started and encounters ORA-54
4) Investigation reveals the previous session is still 'active' and has locks
out on a few tables. Also showsql indicates an insert in in progress.
5) Carry out alter system kill... and session in now status 'KILLED'
6) Leave this for 1 hour to see if PMON will clean up - it does not.

What are my options here?

* I can bounce the DB - would rather not
* I can use orakill - I can, but it makes me nervous (:o))
* I can enable dead client detection (DCD)

Anything else?

What is a "good" setting of EXPIRE_TIME for DCD - 60 seconds?

I am just looking for a "clean" fix for this annoying problem.

Can you please explain why this occurs?
Followup:
pmon did clean up.

it'll happen "slowly", and it'll take a while as well -- and it could be cleaned
up totally and still be in the killed state (waiting for the client which is
never coming back.  tcp/ip, in particular tcp/ip implementations on windows, do
not necessarily tell us "client is gone").

did you check to see if the session still had locks or just if the session
"still exists".

You might consider using dbms_job to avoid the fragile client environment.
Instead of running that long running thing interactively, schedule it as a job.
Rating: 3
Response to previous post February 24, 2004
Reviewer: Matt from Australia
) Running a data load (a bulk insert) from sqlplus
2) The window is killed (using 'x' in corner of window)
3) Data Load is re-started and encounters ORA-54
4) Investigation reveals the previous session is still 'active' and has locks
out on a few tables. Also showsql indicates an insert in in progress.
5) Carry out alter system kill... and session in now status 'KILLED'
6) Leave this for 1 hour to see if PMON will clean up - it does not.

What are my options here?

* I can bounce the DB - would rather not
* I can use orakill - I can, but it makes me nervous (:o))
* I can enable dead client detection (DCD)

Anything else?

What is a "good" setting of EXPIRE_TIME for DCD - 60 seconds?

I am just looking for a "clean" fix for this annoying problem.

Can you please explain why this occurs?

Followup:
pmon did clean up.

[Mat - It did not. I understand that PMON will clear the "KILLED" session and
all locks. The session was still visible as "KILLED" and the locks were still
active after 1 hour. This is a long time (but on a rather quiet development
system)

it'll happen "slowly", and it'll take a while as well -- and it could be cleaned

up totally and still be in the killed state (waiting for the client which is
never coming back.  tcp/ip, in particular tcp/ip implementations on windows, do
not necessarily tell us "client is gone").

did you check to see if the session still had locks or just if the session
"still exists".

[Mat - Yes, the locks and session were still there]

You might consider using dbms_job to avoid the fragile client environment.
Instead of running that long running thing interactively, schedule it as a job.

[Mat - This sounds like an approach, however I am using a set of sqlplus scripts
for a data load (DDL and anonymous PL/SQL ), I don't think it is straighforward
to dbms_job these scripts

I am more concerned about the set of java processes that will be connecting to
the DB. If I understand this issue correctly then if (for some reason) one of
these processes does "unexpectedly" then some session/locks could be left around
for a significant amount of time.

so, is DCD the reolution? If so, is an EXPIRE_TIME of 60 seconds a "bad" value?
(Obviously this depends and it *will* be tested etc in our configuration - just
want your gut feel as to whether this is too small)
]

Cheers,
Followup:
DCD, 60 seconds or even less should be more than sufficient.

the network traffic with dcd is very tiny.
Rating: 3
Document on Orakill February 24, 2004
Reviewer: Gururaj Kulkarni from Sydney Australia
Tom,

Could you give me a link on Orakill ? I would like to read about Orakill.

Thanks
-Gururaj
Followup:
metalink.oracle.com
February 25, 2004 Ravinder -- Thanks for the question regarding "Replication from SQL Server", version 8.1.6
You Asked
Hi Tom,

I have never worked on SQL Server. and now I wants to replicate
a few tables from SQL Server Database into Oracle Database.
I am aleady having the same set of Tables in my Oracle Database,
which I wants to replicate

Question is Can I do this ?? and if yes how ?

Thanks

Ravi
and we said...
You can create a FULL REFRESH, read only snapshot of the data
into Oracle from SQLServer.

You can create a CHANGES ONLY or FAST refresh read only snapshot
from Oracle into SQLServer.

This is done via the transparent gateway to SQLServer.  Once you
have the transparent gateway, SQLServer will appear to be an
Oracle data source which you can query in real time via a
database link or you can use snapshots to copy data in either
direction.

See:

Oracle Heterogeneous Services Concepts
http://download-east.oracle.com/docs/cd/A81042_01/DOC/server.816/a76960/hs_conce.htm#2426
in the

Oracle8i Distributed Database Systems
http://download-east.oracle.com/docs/cd/A81042_01/DOC/server.816/a76960/toc.htm
document. As well
http://www.oracle.com/gateways/
for more info.
Review & Followup

Rating: 3
February 25, 2004
Reviewer: Premchand from INDIA
Tom,

greate!! i got so many usefull tips from u.

As u told incremental replication is not posible from sql-server to oracle using
transparent gatway? then how can i close this issue.
Followup:
ask microsoft i guess?  they would be needing to capture their changes.
February 25, 2004 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.
Rating: 3
NLS_LANG Problem February 25, 2004
Reviewer: Jamil from Saudi
Dear Sir,Tom
  What is the equivalent of the AM and PM for Arabic system date and time , I
have the above  trigger and when I change the NLS_LANGE to ARABIC_UNITED ARAB
EMIRATES.AR8MSWIN1256
 I am getting the wrong  result , but if  I change the NLS_LANGE TO
AMERICAN_AMERICA.ar8mswin1256 it works ,because the above travel_time function
is not working ,when I change to Arabic NLS_LANg  ,because of the below
statement

So I want to make the change for the AM & PM  in the following  statement   :
--------------- BUSH BUTTON
BEGIN
      ST_DATE := TO_CHAR(:J_START_DATE,'MM-DD-RRRR');

      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-RRRR');

      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;

     DATA_RET :=travel_time(ST_DATE,ST_TIME,ST_TIME_STATUS,
                          EN_DATE,EN_TIME,EN_TIME_STATUS);
     MESSAGE('Time Calculated='||DATA_RET);
END;
-------------------------
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;

Best regards

Jamil
Followup:
I've no idea at all.  sorry.
February 25, 2004 Srikrishna -- Thanks for the question regarding "Procedures vs Triggers", version 8.0.5
You Asked
I am trying to tune a trigger of 274 lines code. Since trigger code should not
cross 60 lines according to oracle, I am planing to move this code to a
procedure and calling it from this trigger. This tip I read from some document.

How does it help in terms of performance and memory usage?
and we said...
It is not relevant anymore.  Pre-Oracle7.3, triggers were not stored compiled.
Starting with 7.3 they are.  They are as efficient as procedures.

See
http://asktom.oracle.com/~tkyte/tkprof.html
for info on tkprof, a tool you will find invaluable for tuning Oracle with.
Review & Followup

Rating: 3
Limit on triggers July 03, 2001
Reviewer: Jeroen from Singapore
A good tip.

However, I found that triggers exceeding a source code size larger than 32 KB
(can be checked via column sys.trigger$.actionsize) give problems when trying to
retrieve the source from dictionary views like all_triggers.

Not when you select in SQL*PLUS with a high value for LONG but when you try to
select it into something in PL/SQL.
Rating: 3
32K 64K is there a limit? February 24, 2004
Reviewer: Susan from Watertown, MA
Tom,

Perhaps you can clarify this for me.  Oracle documentation talks about a
platform dependent size limitation of appproximately 32K for triggers.  I have
triggers that are larger than 32K and they compile fine.  I understand this
limitation is "platform dependent" and relates to the DIANA node limitation.
How can I determine the trigger size limitation for my "platform"?  Thanks
Followup:
ouch -- i'm not going to answer your question -- simply because i cannot imagine
32k of code in a single unit like that.

that is 32k of code in the equivalent of a subroutine.  32k -- single procedure
-- think about it.

The only thing I can say is -- you have to read about packages, you must use
packages, you must break this code into something readable, maintainable,
modular.  You must use subroutines that you will find are reusable (reducing the
overall amount of code).  You will find that by moving SQL out of triggers into
packages will improve runtime performance and scalablility.

Triggers should be teeny tiny, tiny, small.  32k -- no way, something is
seriously wrong.
Rating: 2
I understand but.... February 25, 2004
Reviewer: Susan from Watertown, MA
The trigger in question is an auditing trigger.  It records old values for
deletes, there is a line of code for each column that calls our auditing
procedure.  Some of our tables have over 500 columns, thus, 500 calls to the
auditing procedure.  I'm just trying to understand what the real limitations are
because we do have triggers greater than 32K and they compile. Thanks.
Followup:
the limit is not based on size of code, 32k was a "ROT", rule of thumb.  The
limit is on the size of the diana parse tree which is really "not computable" by
us (until we compile code actually)

500 columns, with column level auditing, I'd be looking for something easier,
workspace manager for example.  one command, instant versioning of data without
any code.  you might want to check it out.
Rating: 4
OK Thanks February 25, 2004
Reviewer: A reader
February 25, 2004 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.
Rating: 3
emulate gather stale February 16, 2004
Reviewer: A reader
Hi

Using 8.1.7.4...

We have to analyze Peoplesoft CRM tables periodically. Some of them need
histograms so we must use dbms_stats.gather_table_stats. We also want to use
gather stale option which is not present in dbms_stats.gather_table_stats... How
can we emulate gather stale for this situation???
Followup:
You use dbms_stats.gather_schema_stats with the LIST STALE option to find the
list of objects in need of gathering, then you can call gather_table_stats to
actually do the gather.
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5792247321358#6677439479559
Rating: 5
you can force smon to write tab:modifications February 16, 2004
Reviewer: A reader
exec DBMS_STATS.FLUSH_DATABASE_MONITORING_INFO
Followup:
new with 9i (post-dates the question... and does not apply for their version)
Rating: 4
how to monitor partitioned tables? February 22, 2004
Reviewer: A reader
Hi

I want to use dbms_stats and gathering stale segments only but I find out that
objlist returned by dbms_stats.gather_schema_stats shows us only the table name.
My question is if I run gather_schema_stats gather stale does Oracle analyze the
table several times?

For example

One table emp has 4 partitions, oblist shows this

EMP
EMP
EMP
EMP

Does it analyze EMP 4 times?
Followup:
dump the entire objlist record -- you'll see those are partition references:

type ObjectElem is record (
  ownname     varchar2(32),     -- owner
  objtype     varchar2(6),      -- 'TABLE' or 'INDEX'
  objname     varchar2(32),     -- table/index
  partname    varchar2(32),     -- partition           <<<=== will be filled.
  subpartname varchar2(32),     -- subpartition
Rating: 5
uncommited rows throws monitoring off February 24, 2004
Reviewer: Sravan from Northampton, MA
Hello Tom,
All I know about gather_stats is learnt from this site. Fantastic work and
dedication. Thank you.

9.2.0.2/sqlplus

I setup monitoring on all schema tables.
statistics are gathered on stale objects. I realised that even before the
inserted rows are rolled back or commited, the user_tab_monitoring captures this
info. - which makes the next gather_stats script to analyze this table(as the
tab_motoring says the table changed 20%), even though in reality the row count
has not changed(rollback).

------- monitoring --------
exec dbms_stats.alter_schema_tab_monitoring('SRAV',TRUE);

------- gathering statistics --------
SQL> begin
  2     dbms_stats.gather_schema_stats(
  3     ownname          => 'SRAV',
  4     options          => 'GATHER AUTO',
  5     estimate_percent => dbms_stats.auto_sample_size,
  6     method_opt       => 'FOR ALL INDEXED COLUMNS SIZE 1',
  7     degree           => 2
  8     );
  9*    end;
/
PL/SQL procedure successfully completed.

======= dba_tables output =========
TABLE_NAME  MON   NUM_ROWS SAMPLE_SIZE ANALYZED
--------------- ---------- ----------- --------
SRAV        YES       5669        5669 14-48-45

===========================
SQL> insert into srav select * from dba_tables where rownum <1000;

999 rows created.

SQL> rollback;

Rollback complete.

SQL> select count(*) from srav;
  COUNT(*)
  --------
      5669

------- querying the user_tab_monitoring ----------

SQL> exec dbms_stats.flush_database_monitoring_info;
    select * from USER_TAB_MODIFICATIONS;

TABLE_NAME INSERTS    UPDATES    DELETES   TIMESTAMP TRU
---------- ---------- ---------- --------- --------- ---
SRAV       999          0          0       24-FEB-04 NO

------- gathering statistics  ------------

SQL> begin
  2     dbms_stats.gather_schema_stats(
  3     ownname          => 'SRAV',
  4     options          => 'GATHER AUTO',
  5     estimate_percent => dbms_stats.auto_sample_size,
  6     method_opt       => 'FOR ALL INDEXED COLUMNS SIZE 1',
  7     degree           => 2
  8     );
  9*    end;
/
PL/SQL procedure successfully completed.

-------- dba_tables output --------
TABLE_NAME  MON   NUM_ROWS SAMPLE_SIZE ANALYZED
--------------- ---------- ----------- --------
SRAV        YES       5669        5669 15-05-52

=====================================

This shows that the table was analyzed(difference in timestamp). And this was
done, because the user_tab_monitor had entries, which were eventually rolled
back.

Is this a expected behaviour? If it is, am curious to know a reason as to why
the table was analyzed even though it hasn't REALLY changed ?

Appreciate your response, as always.
Good Day !!!
Followup:
yes, it is....

the monitoring stuff is done "row by row" and "unlatched" -- it would be
horribly expensive otherwise.

rollbacks should almost never never happen.

so, decision to optimize for the most frequent case here was smart -- no real
impact on runtime performance was key.  if monitoring took lots of resources, it
wouldn't be adopted.
Rating: 5
does gather_stats implecitly commit ???? February 24, 2004
Reviewer: Sravan from Northampton, MA
Thanks Tom.
what does "unlatched" in this context mean ?

I also observed that gather_stats is doing an implicit commit ?

SQL> select count(*) from srav;
  COUNT(*)
  --------
      4670
--------- inserting data into table -----------
SQL> insert into srav select * from dba_tables where rownum <1000;

999 rows created.

------- querying the user_tab_monitoring ----------

SQL> exec dbms_stats.flush_database_monitoring_info;
    select * from USER_TAB_MODIFICATIONS;

TABLE_NAME INSERTS    UPDATES    DELETES   TIMESTAMP TRU
---------- ---------- ---------- --------- --------- ---
SRAV       999          0          0       24-FEB-04 NO

------- gathering statistics  ------------

SQL> begin
  2     dbms_stats.gather_schema_stats(
  3     ownname          => 'SRAV',
  4     options          => 'GATHER AUTO',
  5     estimate_percent => dbms_stats.auto_sample_size,
  6     method_opt       => 'FOR ALL INDEXED COLUMNS SIZE 1',
  7     degree           => 2
  8     );
  9*    end;
/
PL/SQL procedure successfully completed.

-------- dba_tables output --------
TABLE_NAME  MON   NUM_ROWS SAMPLE_SIZE ANALYZED
--------------- ---------- ----------- --------
SRAV        YES       5669        5669 15-05-52
===============================================

SQL> select count(*) from srav;
  COUNT(*)
  --------
      5669
================================================
Here, I haven't commited or rolledback the transaction. After inserting(NO
commit or NO rollback), executed the gather_stats, it seems like it "commited"
the data. 999 rows have been added to table and it was analyzed as well. WHY ??
A rollback or commit at this point has no significance.

Please comment !!!
Let me know, if I missed out something critical .....

Thanks again .....
Followup:
unlatched means "unprotected", just let everyone at the counters in memory and
don't worry about overwrites, it is an estimate of the number of modifications,
not exact.  adding a latch would add a point of serialization making this
expensive.

as for the commit, it is so documented:
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96612/d_stats.htm#1011555
that is "why".  the commit is meaningful -- you've modified the data dictionary,
we need to flush cursors from the shared pool and redo their plans, you've done
some heavy duty work there.
Rating: 5
thank you February 25, 2004
Reviewer: Sravan from Northampton, MA
Tom, thanks for pointing me to the resource.
I am just wondering if I need to make any adjustments or changes to the way I
would run the gather_stats ?
We have lot of long running transactions and if it intersects with gather_stats
timing(thereby committing), and lets assume the transaction fails.
How would I go about undoing the changes commited by the gather_stats?
I think this is serious problem. Or am I missing pieces in the big picture.
I can't completely ignore the fact that the transaction will never rollback or
the transaction would never fail ?
Is there a contingency plan, I should think of ?
Thanks for your comments and advice.
Is there another mechanism to avoid this situation ?
Followup:
why would you "uncommitted" the stats???? they are relevant???

I'm not understanding something here.

gather stats is something you run to update statistics.  it sees only committed
data in the database.

the other transactions out there running quite simply are not relevant.

gathering stats should be independent of your transactions -- i don't see the
relationship?
Rating: 5
I will try it again February 25, 2004
Reviewer: Sravan from Northampton, MA
My Bad. did not put it down cleanly.
My brain cells suddenly woke up after reading your statement :
" the other transactions out there running quite simply are not relevant "

I was trying to visualize a situation where I would keep a transaction active(do
a insert) and then run the gather_stats.
DUH ... why would I do it. Why would I serialize my gather_stats with
transactions in the same session. Unless my brain takes a walk :)
Sorry about my confusing statements. Apologize.

Thanks
February 25, 2004 Raj -- Thanks for the question regarding "Rebuilding Indexes", version 5
You Asked
I know this question has been asked earlier and
I am sorry to take up this question slot but I
am confused regarding rebuilding indexes.

If I am interpreting it correctly, you don't
recommend rebuilding indexes at all. I have talked
to two seasoned Oracle dba's and they both
recommend rebuilding indexes at a regular period
of time. We have couple of tables with a million
to two million records but the indexes have never
been rebuilt. Also, we don't have that much
deletion of records going on though we do have
insertions and quite a few selects on those
tables. Do you recommend rebuilding indexes for
such a database.

What would be the implication if we did
have to rebuild the indexes. Would I need lot
of space for doing such a task.

Thanks a lot for your help.

Raj
and we said...
Ask them for the technical reasons WHY they rebuild.

When they say "performance of course" -- ask them for the performance metrics
they took BEFORE the rebuild and AFTER the rebuild.  They won't have any (no one
ever does, no one seems to think about doing that).  They will say "it just goes
faster trust me".

When they say "to reclaim space of course" -- ask them "but how long does it
take before the index is just a big as it was before the rebuild".  You see they
have not reclaimed any space -- the index will just grow again, eventually
hitting a steady state size that it likes to be.

If you need to rebuild your indexes, you need 2x the space -- you'll have the
old and the new index for a period of time.  If you do it online, you'll need
additional space to hold the changes that are made during the rebuild as well.
Review & Followup

Rating: 3
Is your advice against rebuilding a "blanket" one? March 01, 2002
Reviewer: Mohit Dubey from UK
Hi Tom,

Would rebuilding indexes not help in case you want to re-balance the b*tree?
Particularly in case of indexes deriving their values from a monotonically
increasing sequence (as I trust most of the unique/foreign keys are)?

Please throw some more light on this issue!

Mohit.
Followup:
If you can show me mearsurable performance increases or some other measurable,
material benefit -- by all means rebuild.

99.99999999% of the time, no one measures, they just rebuild everything (making
a really big deal out of it, wasting time, cutting off access, generating
overtime hours, etc....

To HAVE to rebuild it a rarity -- not the norm.  Keep good metrics, show the
before and after effects.  Be ready to prove that the rebuild was worth the
effort (i mean, we have to benchmark ourselves in so many other ways -- why is
this any different).
Rating: 3
Rebuilding Indexes March 01, 2002
Reviewer: Rob from Chicago
I find that I have to watch bitmap indexes very closely.
Our fact tables get loaded every day and every so often a bitmap index seems to
grow very rapidly. I rebuild the index and it then holds at a steady state. I
see this often if we delete and reinsert a days worth of info. The performance
improvements comes for the inserts on the table not the select using the index.
Followup:
Agreed -- bitmaps are a special case, easily measurable, definite payback.

Now, about b*trees......
Rating: 5
online index rebuild February 24, 2004
Reviewer: reader
Doc says that when we rebuild index online, the changes are allowed and they are
stored in a "journal table". What happens to the changes in the journal table if
the rebuild aborts for some reason after i started the index rebuild? Are they
lost?  Thanks.
Followup:
they are cleaned up for you.

they are not "lost", they are "not useful anymore" if the rebuild aborts.

You would just start the rebuild again and start journaling anew -- no need for
the old stuff, it would be counter productive to attempt to use it.
Rating: 5
index and table in the same tablespace February 24, 2004
Reviewer: reader
From
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96521/indexes.htm#310
<quote>Using different tablespaces (on different disks) for a table and its
index produces better performance than storing the table and index in the same
tablespace. Disk contention is reduced. <quote>

Tom, is the above still recommended? Thanks.
Followup:
no
Rating: 4
index hijacking February 24, 2004
Reviewer: VKOUL from Lacey, WA USA
Hi Tom

Pls. go through the following

SQL> create table abc (x number);

Table created.

SQL> create index abc_non_unique_idx on abc(x);

Index created.

SQL> insert into abc values (1);

1 row created.

SQL> insert into abc values (1);

1 row created.

SQL> select * from abc;
Press Enter ...

         X
----------
         1
         1

SQL> roll
Rollback complete.
SQL>
SQL> select * from abc;

no rows selected

SQL> @1.lst

                    INDEX_NAME : ABC_NON_UNIQUE_IDX
                    INDEX_TYPE : NORMAL
                   TABLE_OWNER : VKOUL
                    TABLE_NAME : ABC
                    TABLE_TYPE : TABLE
                    UNIQUENESS : NONUNIQUE
                   COMPRESSION : DISABLED
                 PREFIX_LENGTH :
               TABLESPACE_NAME : USERS
                     INI_TRANS : 2
                     MAX_TRANS : 255
                INITIAL_EXTENT : 1048576
                   NEXT_EXTENT : 1048576
                   MIN_EXTENTS : 2
                   MAX_EXTENTS : 2147483645
                  PCT_INCREASE : 0
                 PCT_THRESHOLD :
                INCLUDE_COLUMN :
                     FREELISTS : 1
               FREELIST_GROUPS : 1
                      PCT_FREE : 10
                       LOGGING : YES
                        BLEVEL :
                   LEAF_BLOCKS :
                 DISTINCT_KEYS :
       AVG_LEAF_BLOCKS_PER_KEY :
       AVG_DATA_BLOCKS_PER_KEY :
             CLUSTERING_FACTOR :
                        STATUS : VALID
                      NUM_ROWS :
                   SAMPLE_SIZE :
                 LAST_ANALYZED :
                        DEGREE : 1
                     INSTANCES : 1
                   PARTITIONED : NO
                     TEMPORARY : N
                     GENERATED : N
                     SECONDARY : N
                   BUFFER_POOL : DEFAULT
                    USER_STATS : NO
                      DURATION :
             PCT_DIRECT_ACCESS :
                    ITYP_OWNER :
                     ITYP_NAME :
                    PARAMETERS :
                  GLOBAL_STATS : NO
                 DOMIDX_STATUS :
               DOMIDX_OPSTATUS :
                FUNCIDX_STATUS :

SQL>
SQL> alter table abc add constraint pk_abc primary key
  2  (x) using index;

Table altered.

SQL>  insert into abc values (1);

1 row created.

SQL> /
 insert into abc values (1)
*
ERROR at line 1:
ORA-00001: unique constraint (VKOUL.PK_ABC) violated

SQL>
SQL> @1.lst

                    INDEX_NAME : ABC_NON_UNIQUE_IDX
                    INDEX_TYPE : NORMAL
                   TABLE_OWNER : VKOUL
                    TABLE_NAME : ABC
                    TABLE_TYPE : TABLE
                    UNIQUENESS : NONUNIQUE
                   COMPRESSION : DISABLED
                 PREFIX_LENGTH :
               TABLESPACE_NAME : USERS
                     INI_TRANS : 2
                     MAX_TRANS : 255
                INITIAL_EXTENT : 1048576
                   NEXT_EXTENT : 1048576
                   MIN_EXTENTS : 2
                   MAX_EXTENTS : 2147483645
                  PCT_INCREASE : 0
                 PCT_THRESHOLD :
                INCLUDE_COLUMN :
                     FREELISTS : 1
               FREELIST_GROUPS : 1
                      PCT_FREE : 10
                       LOGGING : YES
                        BLEVEL :
                   LEAF_BLOCKS :
                 DISTINCT_KEYS :
       AVG_LEAF_BLOCKS_PER_KEY :
       AVG_DATA_BLOCKS_PER_KEY :
             CLUSTERING_FACTOR :
                        STATUS : VALID
                      NUM_ROWS :
                   SAMPLE_SIZE :
                 LAST_ANALYZED :
                        DEGREE : 1
                     INSTANCES : 1
                   PARTITIONED : NO
                     TEMPORARY : N
                     GENERATED : N
                     SECONDARY : N
                   BUFFER_POOL : DEFAULT
                    USER_STATS : NO
                      DURATION :
             PCT_DIRECT_ACCESS :
                    ITYP_OWNER :
                     ITYP_NAME :
                    PARAMETERS :
                  GLOBAL_STATS : NO
                 DOMIDX_STATUS :
               DOMIDX_OPSTATUS :
                FUNCIDX_STATUS :

SQL>
SQL> disconnect
Disconnected from 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>

Q1 : How does Oracle implement uniqueness, since the underlying index is
NONUNIQUE ? Does Oracle recreates index on primary key creation, if that is so,
why in index data it says it is NONUNIQUE ?

Q2 : On your site I have read somewhere like "There is no nonunique B*tree, in
case of non-unique indexes, the rowid is concatenated with the value to make it
unique". In this case does it segregate rowid from the value after hijacking ?

Q3 : What effect does it have on optimizer in selecting a plan.

Thanks
Followup:
q1) it doesn't matter "how", only that it "does" at the end of the day.

Since version 8.0 with deferrable constraints, we've only needed AN INDEX to
enforce uniqueness.  Not a unique index, just an index.

q2) not relevant to us.  only relevant that "it works".  the internal mechanics,
not known to us.  all we need know is a primary key/unique constraint will

a) look for the columns to be on the leading edge of any index, if found, use
it, consider:

ops$tkyte@ORA920PC> create table t ( x int, y int );
Table created.

ops$tkyte@ORA920PC> create index t_idx on t(x,y);
Index created.

ops$tkyte@ORA920PC> alter table t add constraint t_pk primary key(x);
Table altered.

ops$tkyte@ORA920PC> select index_name from user_indexes;

INDEX_NAME
------------------------------
T_IDX

ops$tkyte@ORA920PC> drop index t_idx;
drop index t_idx
           *
ERROR at line 1:
ORA-02429: cannot drop index used for enforcement of unique/primary key

b) if not found and constraint is not deferrable, create unique index by default
c) if not found and constraint is deferrable, create non-unique index by default

q3) nothing horribly measurable really.  It would be rare for it to affect a
plan, in light of statistics, the optimizer will understand about how many rows
(1) would come back based on "where pk = value".  That and remember -- indexes
are the tuning tools here.  If you needed a unique index for performance
reasons, then you most certainly should not have created a non-unique one.
Rating: 4
Interesting... February 25, 2004
Reviewer: Invisible from UK
<reader>
Should indexes go in a separate tablespace to tables?
</reader>

<Tom>
No.
</Tom>

...any reason WHY this should be so? Just curiouse...
Followup:
in the olden days....

before logical volumes
before striping
before 500+ table schemas

when you had like 10 tables and 12 indexes and a couple of disks direct
attached....

putting indexes on one and data on the other was an attempt to evenly distribute
IO over both devices.  "couple of index reads - many from cache, one or so from
disk" followed by "table access by index rowid" maybe from cache, maybe not --
would tend to put 1/2 the IO on each of your two disks.

The SAME effect could have been achieved by using really small extents and
making sure each object was in 100 or so extents -- the data would have been
nicely striped (but that ran counter to that extent myth so no one did that)...

Today with 5000 objects in a schema, gigabytes/terabytes of storage, raid
striping, logical volumes -- it just isn't relevant in the least.

Me, I use tablespaces to hold application when appropriate.  Your application
will get A tablespace unless your application has some really large tables.
Then and only then will we carve out extra things to manage.
Rating: 5
Cool. February 25, 2004
Reviewer: Invisible from UK
Thanks for that Tom. :-)

I presume from the above that there's also no particular PROBLEM if the DB is
already set up with everything separated out?
Followup:
nope, just extra stuff to manage.

use tablespaces as an administrative/bookeeping tool, not so much as a tuning
device.
Rating: 5
Not rebuild index definitively? February 25, 2004
Reviewer: A reader
Hi Tom,

What do you think about rebuild index yearly,
or the answer is definitively
NO index rebuild necessary, unless something like block corruption in index or
other rarity like this.

Thanks
Followup:
I do not beleive in a scheduled rebuild of indexes.

I do believe in

a) determining the root cause of a performance related issue
b) correcting it

and if part of b) is rebuilding an index, so be it.  but to rebuild on a
schedule?  No.
Rating: 5
February 25, 2004
Reviewer: A reader
Thanks Tom,
Rating: 3
Rebuilding online reply February 25, 2004
Reviewer: Steve from Midwest
Just to clarify one point to your reply to the question:

"What happens to the changes in the journal table if
the rebuild aborts for some reason after i started the index rebuild? Are they
lost?"

You reply they are cleaned up for you...  I guess the documentation may say that
is the case but we've ran into various issues with both 8i and 9i where the
journal table does not get dropped under certain conditions and future attempts
to rebuild the index (or analyze the table) require intervention.  In 8.1.7.2 if
a session is killed rebuilding an index we had to go out there and update ind$
to get the index status out of "rebuild" status along with manually dropping the
journal table.  In 9.2.0.3 if a session is killed or you ctrl c out of the
rebuild or you get an unable to extent message you need to manually drop the
journal table in order to rebuild it online after the "abort".  I'm sure there's
other conditions that cause problems but this is just a couple.

Just want to highlight this feature is a bit buggy if various conditions are
hit.
February 25, 2004 Ilya -- Thanks for the question regarding "list to table function with string > 4000 chars (CLOB)", version 9.2
You Asked
Hi Tom,

I'm a big fan of yours and this site and very happy to be able to ask my first
question.

1) This question is about performance when dealing with CLOB. My stored
procedure reads records from a table, breaks each record into tokens by a known
delimiter and inserts the resulted tokens into another table. The records
themselves can be > 4000 but definitely less than 8000 chars, so I store them as
CLOB in a table. The tokens are definitely < 4000, so they fit into a sql table
type. I decided to try a sql approach, rather than processing using cursor --
for this purpose I adapted your function in_list from
http://asktom.oracle.com/pls/ask/f?p=4950:8:11852138664860221619::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:210612357425,
and changed the input parameter to type CLOB. However, I found that this change
by itself dramatically decreases performance. If I know in advance that the
records will be < 4000 chars, I can use a function that is practically
identical, except declared to take a VARCHAR2, and it performs much better. An
alternative here would be to process records one row at a time, in which case I
can simply do l_data := in_list(mystring); and then insert values from l_data.
In this case, because I'm calling from pl/sql context in_list will not complain
if the string is > 4000 chars. What would you advise?

Here's the function:

REATE OR REPLACE FUNCTION list_to_two_col_table_large(
   in_string IN CLOB,
   in_separator IN VARCHAR2 := ','
)
   RETURN two_col_table_type
IS
   l_string CLOB;
   l_data two_col_table_type;
   l_val VARCHAR2(4000);
   l_pos NUMBER;
   l_start_pos NUMBER;
BEGIN
   l_data := two_col_table_type();
   l_string := in_string;

   IF in_string IS NULL OR LENGTH(in_string) = 0 THEN
      RETURN l_data;
   END IF;

   l_start_pos := 1;

   LOOP
      EXIT WHEN l_start_pos > LENGTH(l_string);
      l_pos := INSTR(l_string, in_separator, l_start_pos);

      IF l_pos < 1 THEN
         l_pos := LENGTH(l_string) + 1;
      END IF;

      l_val :=
              LTRIM(RTRIM(SUBSTR(l_string, l_start_pos, l_pos - l_start_pos)));

      IF (l_val IS NOT NULL) THEN
         l_data.EXTEND;
         l_data(l_data.COUNT) := two_col_rec_type(NULL, l_val);
      END IF;

      l_start_pos := l_pos + LENGTH(in_separator);
   END LOOP;

   FOR i IN 1 .. l_data.COUNT
   LOOP
      l_data(i).column_int_value := l_data.COUNT;
   END LOOP;

   RETURN l_data;
END list_to_two_col_table_large;

--a function list_to_two_col_table is identical, except that
--all CLOB references are replaced with VARCHAR2 and l_string is --declared as
VARCHAR2(32000);
--I am not including it here to conserve space

create or replace type two_col_rec_type as object
(
    column_int_value number,
    column_str_value   varchar2(4000)
);

create or replace type two_col_table_type as table of two_col_rec_type;

CREATE TABLE records_table
(
    ROW_ID NUMBER(10,0) NOT NULL,
    RECORD CLOB,
    CONSTRAINT PK_records_table PRIMARY KEY(ROW_ID)
)

CREATE TABLE fields_table
(
    ROW_ID NUMBER(10,0) NOT NULL,
    SEQ_NO NUMBER(10,0),
    FIELD_ID NUMBER(10,0),
    RECORD VARCHAR2(4000)
)

truncate table fields_table;
commit;

declare
--l_sep varchar2(1);
l_max_count integer;
begin
l_max_count := 50000;
--l_sep := ';';
INSERT INTO fields_table
      SELECT *
        FROM (SELECT row_id, t.column_int_value AS seq_no, 0 AS field_id,
                     t.column_str_value AS RECORD
                FROM records_table o,
                     TABLE(list_to_two_col_table_large(o.RECORD, ';')) t)
                     WHERE ROWNUM < l_max_count;
end;

--done in 107.406 seconds

declare
--l_sep varchar2(1);
l_max_count integer;
begin
l_max_count := 50000;
--l_sep := ';';
INSERT INTO fields_table
      SELECT *
        FROM (SELECT row_id, t.column_int_value AS seq_no, 0 AS field_id,
                     t.column_str_value AS RECORD
                FROM records_table o,
                     TABLE(list_to_two_col_table(o.RECORD, ';')) t)
      WHERE ROWNUM < l_max_count;
end;

--done in 6.359 seconds

Here's a sample of data (in record_table)

insert into record_table values
(1,
'[0!,];[1!,];[3!,];[8!,];[10!,];[11!,];[12!,];[13!,];[17!,];[18!,];[29!,];[31!,];
[33!,];[47!,];[49!,];[50!,];[51!,];[52!,];[53!,];[57!,];[58!,];[59!,];[60!,]')

insert into record_table values
(2,
'[0!,];[1!,];[3!,];[8!,];[10!,];[11!,];[12!,];[13!,];[14!,];[17!,];[18!,];[31!,];
[33!,];[47!,];[49!,];[50!,];[51!,];[52!,];[53!,];[57!,];[58!,];[59!,];[60!,]')

and so on.

record_table in my test had 10000 rows.
When inserting into fields_table I would limit number of rows to 50000. In
'real' program I may have about 50000 rows in record_table, and number of tokens
in each record usually varies from 10 to 100, although I don't have any actual
limit.

In this particular case all the records are < 300 chars, so
I may be wasting your time, because the obvious solution would be to redesign
the table and avoid CLOB altogether.

However, I have other stored procedures where the record is a string that
consists of words (separated by spaces) and I have to tokenize that string into
words. In that case, I had some records > 4000 chars, although the majority were
< 4000 chars.

Also, I observed a very weird thing:

if in list_to_two_col_table_large I replace all occurrences of l_string with
in_string (that is, do not copy the input parameter into the local variable),
the performance goes to hell -- I can just see in v$sort_usage the LOB_DATA
rising and rising (I usually terminate the statement when it gets to 200 Mb). I
specifically tried in the code to avoid modifying the input CLOB, but I guess it
doesn't help. When I copy in_string into l_string and then use l_string, the
performance is 'normal' and the LOB_DATA is about 2048 Kb. The performance is
still orders of magnitude worse than if the function is declared as taking
VARCHAR2.

Also, a minor annoyance is that in the insert statement I had to hard-code the
separator passed to the table function. If I tried to pass in a local variable
(and initialize it outside the insert statement), I would get a run-time error
(something like 'cannot select from unnested value').

your comments will be much appreciated.

thanks,
ilya
and we said...
well, we can certainly optimize this plsql -- for sure.  In fact, I ran a
comparision of a couple of 156 byte and 64k clobs with your and my "enhanced"
code to find:

ops$tkyte@ORA920PC> declare
  2  l_max_count integer;
  3  begin
  4  l_max_count := 50000;
  5  INSERT INTO fields_table
  6        SELECT *
  7          FROM (SELECT row_id, t.column_int_value AS seq_no, 0 AS field_id,
  8                       t.column_str_value AS RECORD
  9                  FROM records_table o,
 10                       TABLE(list_to_two_col_table_large(o.RECORD, ';')) t)
 11                       WHERE ROWNUM < l_max_count;
 12  end;
 13  /

PL/SQL procedure successfully completed.

Elapsed: 00:12:57.73
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> declare
  2  l_max_count integer;
  3  begin
  4  l_max_count := 50000;
  5  INSERT INTO new_fields_table
  6        SELECT *
  7          FROM (SELECT row_id, t.column_int_value AS seq_no, 0 AS field_id,
  8                       t.column_str_value AS RECORD
  9                  FROM records_table o,
 10                       TABLE(demo_pkg.new_one(o.RECORD, ';')) t)
 11                       WHERE ROWNUM < l_max_count;
 12  end;
 13  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:43.31
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select * from (select * from fields_table MINUS select *
from new_fields_table ) where rownum <= 5;

no rows selected

Elapsed: 00:00:00.32
ops$tkyte@ORA920PC> select * from (select * from new_fields_table MINUS select *
from fields_table ) where rownum <= 5;

no rows selected

Elapsed: 00:00:00.24

so, we can get this to run about 18 times faster just by tuning the logic a
bit.

The "devices" i used

a) dbms_profiler
b) my programming background

I used tricks like "hey, stack variables are expensive to allocate/deallocate,
so lets not do that".  "instead of calling a function that will return the same
value over and over -- lets just call the function once and remember the value
for that call".  "strings are faster than clobs, so if we can move the clob into
a string -- we can make this go faster -- much faster"  "if we allocate lots of
entries in our collection variable - that is faster than doing it one at a time
(dbms_profiler was useful there, much time spent in l_data.extend).

My modified routine became a package (so we could avoid stack variables):

create or replace package demo_pkg
as
    function new_one( in_string in clob, in_separator in varchar2 default ',' )
return two_col_table_type;
end;
/

create or replace package body demo_pkg
as
    l_string     clob;
    l_pos        number;
    l_start_pos  number;
    l_length     number;
    l_data       two_col_table_type := two_col_table_type();
    l_sep_length number;
    l_cnt        number;
    l_real_str   long;

/* I moved my stack variables to the body -- so they would get allocated once,
not once per call...*/

    FUNCTION new_one( in_string IN CLOB,
                      in_separator IN VARCHAR2 := ','
                    ) RETURN two_col_table_type
    IS
    BEGIN
        l_string := in_string;
        l_length := dbms_lob.getLength(l_string);

/* calling dbms_lob.getlength over and over was killing you  */
        IF in_string IS NULL OR l_length = 0
        THEN
          return two_col_table_type();
        END IF;
        l_start_pos := 1;
        l_sep_length := length(in_separator); -- every little bit helps
        l_cnt := 0;

        if ( l_length < 32760 )-- optimize for clobs <32, the vast majority
        then
            l_real_str := l_string || in_separator;
            loop
                l_pos := instr( l_real_str, in_separator );
                exit when (nvl(l_pos,0) = 0);
                if ( l_data.count = l_cnt )  -- allocate chunks at a time
                then
                  l_data.extend( trunc(l_length/5) );
                end if;
                l_cnt := l_cnt+1;
                l_data(l_cnt) := two_col_rec_type(NULL,
                                         TRIM(substr(l_real_str,1,l_pos-1)) );
                l_real_str := substr( l_real_str, l_pos+1 );
                -- removed as many temporaries (l_val) as I could...
                if ( l_data(l_cnt).column_str_value is null )
                then
                  l_cnt := l_cnt-1;
                end if;
            end loop;
        else
            while ( l_start_pos <= l_length )
            LOOP
                l_pos := dbms_lob.instr(l_string, in_separator, l_start_pos);
                IF l_pos < 1 THEN
                   l_pos := l_length + 1;
                END IF;

                if ( l_data.count = l_cnt )
                then
                  l_data.extend( trunc(l_length/5) );
                end if;
                l_cnt := l_cnt+1;
                l_data(l_cnt) := two_col_rec_type(NULL,
                   TRIM(dbms_lob.substr(l_string, l_pos - l_start_pos,
                                        l_start_pos )) );
                if ( l_data(l_cnt).column_str_value is null )
                then
                  l_cnt := l_cnt-1;
                end if;
                l_start_pos := l_pos + l_sep_length;
            END LOOP;
        end if;

        FOR i IN 1 .. l_cnt
        LOOP
            l_data(i).column_int_value := l_cnt;
        END LOOP;
        if ( l_data.count <> l_cnt )
        then
            l_data.trim( l_data.count-l_cnt );
        end if;
        RETURN l_data;
    END;
end;
/

and that was it, hope that helps some.
February 25, 2004 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.
Rating: 4
unexplained writes in tablespaces with indexes February 25, 2004
Reviewer: Mark from ca
Tom, this was very helpful. We are migrating from 8.1.7 rollback segments to 9.2
undo tablespaces (SAP environment). SAP says wait 2 weeks and then drop the old
tollback segment tablespace (rollback seg are offline). I don't understand the 2
week comment so I take the rollback tablespace offline and of course get errors.
I am assuming that if the tablespace gets dropped the delayed block cleanout
will occur, since it doesn't seem to occur when the tablespace is offline. Is my
assumption correct? Thanks for a great site.
Followup:
2 weeks seems rather arbitrary.  A couple of hours should suffice.

You could suffer from 1555's if you offline and drop them "too soon" yes.
February 25, 2004 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
Rating: 5
rownum = 1 and rwnum < 2 January 28, 2004
Reviewer: A reader
Hi

I have this query which runs happily using rownum < 2 but slowly using rownum =
1

  1  SELECT bo_id_contact, interaction_id, interact_begin,
  2         TO_CHAR (interact_begin, 'YYYY-MM-DD-HH24.MI.SS'), interact_status
  3    FROM sysadm.ps_ri_interaction partition(SYS_P692)
  4*  WHERE ROWNUM < 2

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2761 Card=1 Bytes=33)
   1    0   COUNT (STOPKEY)
   2    1     TABLE ACCESS (FULL) OF 'PS_RI_INTERACTION' (Cost=2761 Card=1057390
Bytes=34893870)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          4  consistent gets
          8  physical reads
          0  redo size
        815  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

  1  SELECT bo_id_contact, interaction_id, interact_begin,
  2         TO_CHAR (interact_begin, 'YYYY-MM-DD-HH24.MI.SS'), interact_status
  3    FROM sysadm.ps_ri_interaction partition(SYS_P692)
  4*  WHERE ROWNUM = 1

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2761 Card=1 Bytes=33)
   1    0   COUNT (STOPKEY)
   2    1     TABLE ACCESS (FULL) OF 'PS_RI_INTERACTION' (Cost=2761 Card=1057390
Bytes=34893870)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      18297  consistent gets
      18165  physical reads
          0  redo size
        815  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

I think rownum = 1 is an incorrect use?
Followup:
something fishy -- can we see a tkprof with the real plans?
Rating: 5
seems that cursor_sharing is the guilty boy January 29, 2004
Reviewer: A reader
Hi

cursor_sharing = FORCE change rownum = 1 into rownum = :"SYS_B_0"

seems like that is the problem!
Rating: 3
which one is better ? February 24, 2004
Reviewer: A reader
which one is better ?

select * from ( select id from t1 where name = v_name)
 where rownum = 1;

or

select max(id) from t
where   name= v_name;

(In the view "t" there are multiple rows with the "same name" and "same id" and
I have to select only one of them)
eg. id  name  other_attr
     1 peter   x
     1 peter   y
     2 john    x
     3 tom     z
     1 peter   z

  which one is faster ?

 there can be 10 maximum repetation based on other_attr field. Total record in
table are 4k

The explain plan shows only one difference

 count stopkey   vs. sort aggregate

please advise !!
Followup:
well, they are not comparable, they answer different questions

q1) give me a random ID where name = v_name
q2) give me the MAX id where name = v_name

they are differnet and cannot never ever be compared for performance based on
that fact.
Rating: 3
really ? February 25, 2004
Reviewer: A reader
Hi tom, Thanks for your reply.
 First of all sorry for my mistake. the view name is wrong both the views are t
(not t1 in one of the cases) so we quering against the same view t now.

I listed the view t  below...

eg. id  name  other_attr
     1 peter   x
     1 peter   y
     2 john    x
     3 tom     z
     1 peter   z

if I query it by

select * from ( select id from t where name = 'peter')
 where rownum = 1;

 I will get 1

or

 if I query it using..
select max(id) from t
where   name= 'peter';

  I will get 1  too..

so why aren't they compareable ?

please explain !!
Followup:
ops$tkyte@ORA920PC> create table t1 ( id number, name varchar2(10) );
Table created.

ops$tkyte@ORA920PC> insert into t1 values ( 1, 'peter' );
1 row created.

ops$tkyte@ORA920PC> insert into t1 values ( 1000, 'peter' );
1 row created.

ops$tkyte@ORA920PC> select * from ( select id from t1 where name= 'peter')
  2  where rownum = 1;

        ID
----------
         1

ops$tkyte@ORA920PC> select max(id) from t1 where name = 'peter';

   MAX(ID)
----------
      1000

it is exactly as I said - query 1 returns a random ID for peter.  query 2
returns the max(id) for peter.

tell me -- which do you want?  that'll drive which query you use.
Rating: 4
once again... February 25, 2004
Reviewer: A reader
sorry tom, I think I am not able to explain you the senerio or something.

the objective of this query is to get single row out of multiple rows with the
same (id,name) pair...

eg. id  name  other_attr
     1 peter   x
     1 peter   y
     2 john    x
     3 tom     z
     1 peter   z

if you see this table.

peter will have ONLY one ID that is 1 ever. but multiple occurance of (id,name)
pair
John will have only one ID that is 2 ever. but multiple occurance of (id,name)
pair

let's put it in other way
consider there is another table "user" where id and username are part of the pk
. T is "depandent table" on users. so pk(id,name) pair is migrated from users
(fk) into t and is part of the pk in "T"

in this case name and id pair is unique. so peter will never have id = 1000
peter will always have id = 1 but , basted on the other_attr, there can be a
redudancy,
=> same values of (id,name) pair will be repeated with the different values of
other_attr.

so, it will not be a random number but always same id.
so, inthe result set there will be multiple rows with SAME
(id,name) pair, but we are selecting the first one returned.
in rownum technique
where as we are selecting the maximum (of the same id's)
form group of records with the same ids just to get the 1 row out of multiple
rows

Hope this will make it clearer...:)
Followup:
given all of those additional constraints, just

select id from t where name = 'peter' and rownum = 1;

is sufficient.
Rating: 4
Thanks you... February 25, 2004
Reviewer: A reader
cool. I don't know why I was doing all other things.. this is cool. I should
have done this in the first place.. my bad. you are the man !!
February 25, 2004 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
Rating: 5
How does one find the name (if any) of an index used by a PK February 25, 2004
Reviewer: mike from Minneapolis, MN
My company has created a "database normalizer" which
extracts schema information from our "perfect" database,
and uses this information to make adjustments to databases
that are being upgraded.  For example, we check for missing
and/or extra indexes on tables and add/drop them as needed.
Until now, our assumption has been that a PK constraint always had an analogous
entry in USER_INDEXES, and we have
just run into a case where this is not true.  Is there
a way to determine the name of an index being used by a PK
constraint if it has "chosen" one which already exists?
Followup:
select index_name from user_constraints ....

(what version do you have -- that was added in 9i)
Rating: 5
Just what I was hoping for February 25, 2004
Reviewer: Mike from Minneapolis, MN
Nice to know that you have added the INDEX_NAME to
USER_CONSTRAINTS in 9i.  Most of our sites are 8i,
though they will over time eventually move to newer
versions of Oracle.   I think the solution is to
have our "normalizer" not panic if it is unable
to find an index in USERS_INDEXES with the same name
as the constraint.  Right now we treat that as a error
condition.  Thanks for your very helpful discussions
on the ORA-02270 errors.  I feel much more educated
on the subject now...
Rating: 5
Why not function-based constraints? February 25, 2004
Reviewer: Andrew from Chicago, IL USA
Muddying the constraint/index waters a bit further, I have
encountered the following requirements designing tables:

- Table rows are uniquely identified by a "name"
- Uniqueness must NOT be dependent on case
- Original case of the name must be preserved for the sake
  of presentation

My typical approach in this situation is something like:

CREATE TABLE entity
 ( id   NUMBER(10)
      CONSTRAINT pk_entity
         PRIMARY KEY,
   name VARCHAR2(30)
      CONSTRAINT nn_entity_name
         NOT NULL
 )
/

CREATE UNIQUE INDEX ux_entity_uppname
   ON entity
    ( UPPER( name ) )
/

Without the luxury (necessity?) of function-based indexes,
I suppose you would be forced to define a second column,
say "uppname", and maintain it with triggers.  Having the
luxury, I would rather define only the one "name" column,
add the index, and then be able to say:

--Hypothetical code
ALTER TABLE entity
   ADD
    ( CONSTRAINT nk_entity_uppname
         UNIQUE
          ( UPPER( name ) )
    )

The compromise in my typical approach is not so much in
functionality--the unique, function-based index works like
a charm.  I'm just concerned that I have an index enforcing
a database constraint that can't be declared explicitly.

Do you have a preferred approach to this situation?  Thanks
in advance for any comments.
Followup:
Oh, I do that myself (the fbi)

when you CAN use a constraint -- by all means USE it.

when you cannot use a constraint, by all means find the most efficient method to
implement your needs - and the FBI fills that bill quite nicely.
February 25, 2004 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....
Rating: 5
Please shed some light on multiple undo tablespaces January 23, 2004
Reviewer: Moorthy Rekapalli from Atlanta, GA USA
Tom,

First of all, thank you very much for sharing your knowledge with the Oracle
community.  For the last 3 years, you became my virtual Guru.

Please explain the significance of undo_tablespace parameter when there is more
than one tablespace that shows UNDO in dba_tablespaces.contents column.

Thanks,
Moorthy.
Followup:
just means you created more than one.

only one is being used, the others are offline and unavailable.
Rating: 5
commit time in dba_undo_extents February 19, 2004
Reviewer: reader
Tom, please see below. Why don't I see the commit time? Thanks.

SQL> create table t (x int) tablespace users;

Table created.

SQL> insert into t values(1);

1 row created.

SQL> /

1 row created.

SQL> commit;

SQL> select SEGMENT_NAME,COMMIT_JTIME,COMMIT_WTIME from dba_undo_extents;

SEGMENT_NAME                   COMMIT_JTIME COMMIT_WTIME
------------------------------ ------------ --------------------
_SYSSMU1$
_SYSSMU1$
_SYSSMU1$
_SYSSMU1$
_SYSSMU1$
_SYSSMU2$
_SYSSMU2$
_SYSSMU2$
_SYSSMU3$
_SYSSMU3$
_SYSSMU4$
_SYSSMU4$
_SYSSMU5$
_SYSSMU5$
_SYSSMU5$
_SYSSMU6$
_SYSSMU6$
_SYSSMU6$
_SYSSMU7$
_SYSSMU7$
_SYSSMU7$
_SYSSMU8$
_SYSSMU8$
_SYSSMU8$

24 rows selected.
Followup:
create or replace view dba_undo_extents (
OWNER
,SEGMENT_NAME
,TABLESPACE_NAME
,EXTENT_ID
,FILE_ID
,BLOCK_ID
,BYTES
,BLOCKS
,RELATIVE_FNO
,COMMIT_JTIME
,COMMIT_WTIME
,STATUS
) as
select /*+ ordered use_nl(e) use_nl(f) */
       'SYS', u.name, t.name,
       e.ktfbueextno, f.file#, e.ktfbuebno,
       e.ktfbueblks * t.BLOCKSIZE, e.ktfbueblks, e.ktfbuefno,
       nullif(e.ktfbuectm, e.ktfbuectm),
       nullif(e.ktfbuestt, e.ktfbuestt),
       decode(e.ktfbuesta, 1, 'ACTIVE', 2, 'EXPIRED', 3, 'UNEXPIRED',
              'UNDEFINED')
.....

they are always NULL -- they are deprecated and not filled in.  They were far
too expensive to maintain.
Rating: 2
Manual and Automatic Undo in the same instance? February 23, 2004
Reviewer: Matt from Australia
Is it possible to have an instance use automatic undo and also manually allocate
specific RBS for large TX's?

We have a third party product that has specific RBS requirements that is
currently in the same instance as several application schemas. The preference is
to use automatic undo, but without impacting the third party product that
required specific large named RBS for some of it's TX's.

Can automatic UNDO handle diverse undo requirements? ie: an OLTP type
requirements alonsgside a batch load/processing requiremnt?
Followup:
AUTO is always binary.

You are either in AUTO or you are in MANUAL.  You pick.

You can set undo_suppress_errors to let the 3rd party issue "set transaction use
rollback..." statements and have them silently succeed.  It'll use auto undo
mgmt.

Yes, you can use it for "diverse" requirements.  If you don't like that they get
"big" -- you can always rotate them.

when "too big for your liking"
   create a new undo tablespace
   alter system to point to it
   when all active transactions in old finish drop it
end when.
Rating: 5
can undo tablespace be ASSM? Thanks. February 24, 2004
Reviewer: reader
Followup:
no, and give the circular buffer effect (it is not like a data segment at all)
it would not really "make sense".  the undo data is stored in a big circular
list with the tail chasing the head.  table data isn't stored in that fashion.
there are no freelists, no pctfree/pctused, etc on an undo segment -- space
isn't managed in a heap like normal stuff.

from the sql ref:

Restrictions on undo tablespaces:

....
The only clauses you can specify for an undo tablespace are the DATAFILE clause
and the extent_management_clause to specify local extent management....
Rating: 4
rollback segments placement February 25, 2004
Reviewer: K U from Nashville, TN USA
Previously (<=8i), we could define rollback segment selection order using
'rollback_segments' parameter in initSID.ora file to distribute io_load / reduce
inode_lock contention.

With 9i With Automatic Undo Management, I find the rollback segments created in
a single datafile (when it had enough space for them all). I would rather see
them distributed across multiple datafiles.

Also, for an UNDO tablespace, I cannot specify the storage parameters. (Thought
that by defining minextents or minimum extent clause I could cheat the system
into limiting rbs per datafile - But, 'default storage' clause errors out.)

Docs did not help me. Can you, please?

ALso, what, do you think, is the best practice in this regard?

Regards
KU
Followup:
add multiple datafiles to your undo tablespace then?  they will naturally
"stripe" themselves.

or better yet (much much better yet), much much better yet, use striping at the
OS/raid level (or asm in 10g)

and the init.ora just told Oracle what RBS's were available to that instance, it
was not a load balancing/'reduce inode lock contention (except with OPS but
there it was cross instance contention it was working on)

as for the extent sizing and so on -- AUTO is AUTO.  It does what it does.  AUTO
means "fire and forget, you don't specify stuff"

Manual is manual.  Manual means you can specify away to your hearts content.

You choose - manual or auto.  Like you do when you buy a car (I myself prefer
manual transmissions but automatic undo)...
February 25, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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 :)
Rating: 4
why ?? February 25, 2004
Reviewer: amit poddar from New Haven, CT USA
Hi,

You mentioned in response to the first question:

"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)."

But why if the current session is a select and it needs a CR version of the
block it can allocate onother block on the same chain right ?  then why does it
have to wait for the other session to complete reading into the block.
Followup:
why would you do the physical IO twice?  why not just wait for the IO that is
pending to complete?
Rating: 4
one extra question February 25, 2004
Reviewer: amit poddar from New Haven, CT USA
Hi,

I could have added this to my previous review. but anyway..

Is buffer lock handled in a queue like enqueue and not like latches.
If yes is there any data structues which exposes this queue.
I am asking this since I was looking at finding the blockers and waiters for
buffer busy waits just like the utllockt.sql script for enqueues
Followup:
they are latches, not enqueues.
Rating: 4
clarification February 25, 2004
Reviewer: amit poddar from New Haven, CT USA
so is there a way to find out which session is currently locking a particular
block.
I am asking this because
I have a problem right now where smon is waiting for a block in c_obj# cluster
from today morning. So that means any query against dba views based on tables in
c_obj# cluester which does a full scan just hangs with buffer busy wait on that
particular block.
Followup:
that sounds rather serious actually -- I'd recommend you contact support for
that one.
February 25, 2004 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.
Rating: 2
Can Filler columns in SQLLDR be used in user defined functions? February 25, 2004
Reviewer: Ted from Philly, PA
Hello Tom,

I am trying to use 4 "filler" column in a user defined function in sql loader.
Sqlldr is return ORA-01008 not all variables bound. (on Oracle 8.1.7.2)

Does sqlldr support using a filler column in a user defined function?

Thanks in advanced...  ;-)

CREATE TABLE CMD_EMAIL_USAGE_TED
(
    ACCT_ID     VARCHAR2(15)      NULL,
    CID         NUMBER(10)        NULL,
    CMD_PROMO_ID  NUMBER(11)    NOT NULL,
    VENDOR_ID   VARCHAR2(25)  NOT NULL,
    URL_ID      NUMBER(10)    NOT NULL,
    URL_NAME    VARCHAR2(100) NOT NULL,
    URL_ADDR    VARCHAR2(255) NOT NULL,
    URL_REQ_DTM DATE          NOT NULL)

ctl file--
load data
TRUNCATE
into table CMD_EMAIL_USAGE_TED fields terminated by '~'
(
    ACCT_ID
    ,CID
    ,MST_CMPN_ID filler integer
    ,CMPN_ID     filler integer
    ,PROMO_ID    filler char
    ,SEG_ID      filler char
    ,VENDOR_ID
    ,URL_ID
    ,URL_NAME
    ,URL_ADDR
    ,URL_REQ_DTM date "yyyy-mm-dd"
    ,CMD_PROMO_ID expression
"GET_CMD_PROMO_ID(:MST_CMPN_ID,:CMPN_ID,:PROMO_ID,:SEG_ID)"
)

Function --
CREATE OR REPLACE FUNCTION GET_CMD_PROMO_ID (i_MST_CMPN_ID in number, i_CMPN_ID
in number, i_PROMO_ID in varchar2, i_SEG_ID in varchar2)
RETURN  number IS
v_CMD_PROMO_ID  number;
BEGIN
        -- Get id
        select CMD_PROMO_ID into v_CMD_PROMO_ID
            from CMD_PROMO
            where
            MST_CMPN_ID = i_MST_CMPN_ID and
            CMPN_ID = i_CMPN_ID and
            PROMO_ID = i_PROMO_ID and
            SEG_ID = i_SEG_ID and
            rownum < 2;

return v_CMD_PROMO_ID;

EXCEPTION
   WHEN NO_DATA_FOUND THEN
       RETURN 0;
END;

-- cut from sqlldr output --
   Column Name                  Position   Len  Term Encl Datatype
------------------------------ ---------- ----- ---- ---- ---------------------
ACCT_ID                             FIRST     *   ~       CHARACTER
CID                                  NEXT     *   ~       CHARACTER
MST_CMPN_ID                          NEXT     4           INTEGER
  (FILLER FIELD)
CMPN_ID                              NEXT     4           INTEGER
  (FILLER FIELD)
PROMO_ID                             NEXT     *   ~       CHARACTER
  (FILLER FIELD)
SEG_ID                               NEXT     *   ~       CHARACTER
  (FILLER FIELD)
VENDOR_ID                            NEXT     *   ~       CHARACTER
URL_ID                               NEXT     *   ~       CHARACTER
URL_NAME                             NEXT     *   ~       CHARACTER
URL_ADDR                             NEXT     *   ~       CHARACTER
URL_REQ_DTM                          NEXT     *   ~       DATE yyyy-mm-dd
CMD_PROMO_ID                                              EXPRESSION
    SQL string for column :
"GET_CMD_PROMO_ID(:MST_CMPN_ID,:CMPN_ID,:PROMO_ID,:SEG_ID)"

Record 1: Rejected - Error on table CMD_EMAIL_USAGE_TED.
ORA-01008: not all variables bound

Function works ok called from sqlplus and if hard code in the control file.
Followup:
no, sqlldr just skips the filler fields -- it won't bind them
http://asktom.oracle.com/~tkyte/SkipCols/index.html
shows an alternate techinque that will allow you to skip columns AND pass the 4
skipped columns to that function.
February 25, 2004 Evara -- Thanks for the question regarding "Refresh on Materialized View", version 8.1.7
You Asked
Dear Tom

I create materialized view like this :

create materialized view dtl_budget_mv
build immediate
refresh fast
enable query rewrite
as
select a.company, a.nu_budget_year, a.nu_version,
b.nu_month, b.vc_stock_code,
sum(b.nu_quantity) as nu_quantity
from mst_budget a, dtl_budget b
where a.nu_budget_year = b.nu_budget_year
and a.company = b.company
and a.vc_budget_code = b.vc_budget_code
group by a.company, a.nu_budget_year,
a.nu_version, b.nu_month, b.vc_stock_code;

And then I run my query :

SQL> alter session set query_rewrite_enabled = true;
SQL> select a.company, a.nu_budget_year, a.nu_version,
  2> b.nu_month, b.vc_stock_code, sum(b.nu_quantity) as nu_quantity
  3> from mst_budget a, dtl_budget b
  4> where a.nu_budget_year = b.nu_budget_year
  5> and a.company = b.company
  6> and a.vc_budget_code = b.vc_budget_code
  7> group by a.company, a.nu_budget_year,
  8> a.nu_version, b.nu_month, b.vc_stock_code;

I look in Plan, this query using DTL_BUDGET_MV as materialized view.
but, if I update or insert in table dtl_budget, I run again this query.
I look in Plan does not use the materialized view.
Why ?
I try to refresh manually, did not use too

please advice me, what wrong in my materialized view ?

best regards
and we said...
read
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a85397/statem18.htm#2053386
the MV is not "refresh on commit" hence the MV is "stale" and you must enable
"stale tolerated" or refresh the MV before it can be used again.
Review & Followup

Rating: 2
July 08, 2002
Reviewer: A reader
Rating: 2
Stalre July 08, 2002
Reviewer: Evara from INDO
alter session set query_rewrite_integrity = stale_tolerated;

If I use this statement, my result is not up to date;
I try to refresh like this :
exec dbms_mview.refresh('dtl_budget_mv');

My query still can't use this materialized view.
some missing my parameter ?
Followup:
You must have skipped something somewhere.  I would need a test case from start
to finish in order to see where.  Here is one that shows

a) the MV is used
b) we make the MV "stale" and see it does not get used
c) we refresh it
d) we see the MV getting used again

I would need something similar from you showing something different.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table my_all_objects nologging
  2  as select * from all_objects;

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table my_all_objects add constraint
my_all_objects_pk primary key(object_id);

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create snapshot log on my_all_objects with
rowid ( owner ) including new values;

Materialized view log created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table my_all_objects compute
statistics;

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create materialized view my_all_objects_aggs
  2  build immediate
  3  refresh fast
  4  enable query rewrite
  5  as
  6  select owner, count(*)
  7    from my_all_objects
  8   group by owner
  9  /

Materialized view created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table my_all_objects_aggs compute
statistics;

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=enforced;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly
ops$tkyte@ORA817DEV.US.ORACLE.COM> select owner, count(*)
  2    from my_all_objects
  3   group by owner;

31 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=31 Bytes=310)
   1    0   TABLE ACCESS (FULL) OF 'MY_ALL_OBJECTS_AGGS' (Cost=1 Card=31
Bytes=310)

Statistics
----------------------------------------------------------
         11  recursive calls
         12  db block gets
         18  consistent gets
          0  physical reads
          0  redo size
       2127  bytes sent via SQL*Net to client
        647  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          5  sorts (memory)
          0  sorts (disk)
         31  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into my_all_objects
  2  ( owner, object_name, object_type, object_id, created, last_ddl_time )
  3  values
  4  ( 'New Owner', 'New Name', 'New Type', 1111111, sysdate, sysdate );

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> set autotrace traceonly
ops$tkyte@ORA817DEV.US.ORACLE.COM> select owner, count(*)
  2    from my_all_objects
  3   group by owner;

32 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=59 Card=31 Bytes=155)
   1    0   SORT (GROUP BY) (Cost=59 Card=31 Bytes=155)
   2    1     TABLE ACCESS (FULL) OF 'MY_ALL_OBJECTS' (Cost=10 Card=22832
Bytes=114160)

Statistics
----------------------------------------------------------
        121  recursive calls
         72  db block gets
        357  consistent gets
          0  physical reads
      10352  redo size
       2173  bytes sent via SQL*Net to client
        647  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          3  sorts (memory)
          0  sorts (disk)
         32  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_mview.refresh(
'my_all_objects_aggs' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly
ops$tkyte@ORA817DEV.US.ORACLE.COM> select owner, count(*)
  2    from my_all_objects
  3   group by owner;

32 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=31 Bytes=310)
   1    0   TABLE ACCESS (FULL) OF 'MY_ALL_OBJECTS_AGGS' (Cost=1 Card=31
Bytes=310)

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
        319  consistent gets
          0  physical reads
          0  redo size
       2173  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

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off
Rating: 3
Sum function July 09, 2002
Reviewer: Evara Samsyiar from INDO
Toms, I try your demonstration, and successfully.
the materialized view can use after refresh.
but.
If I include sum function to materialized view,
( ....
as
select owner, count(*), sum(object_id)
...
)
after refresh, this query does not use materialized view.
Why ?

thanks tom, for your advice

best regards
Followup:
sigh, still no complete example.

I'll guess.  You simply added sum(object_id) to the MV and did nothing else.

You cannot fast refresh that one, your refresh didn't refresh.  You need to add
this as well:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create snapshot log on my_all_objects with
rowid ( owner, object_id ) including new values;

Materialized view log 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 materialized view my_all_objects_aggs
  2  build immediate
  3  refresh fast
  4  enable query rewrite
  5  as
  6  select owner, count(*), count(object_id), sum(object_id)
  7    from my_all_objects
  8   group by owner
  9  /

Materialized view created.

Then the view can fast refresh, the refresh will actually refresh (check
user_mviews to verify that) and the query rewrite will work.

Refer to the data warehousing guide, chapter on MV's.  It goes into what must be
included in the MV in order to fast refresh.
Rating: 3
July 10, 2002
Reviewer: gia dinh from SYDNEY,AUSTRALIA
see oracle bug no 2088032
Followup:
And then scratch your head and ask "why am I reading this bug report which
doesn't have anything to do with my problem?"

The solution to this users problem, as stated above, is to correctly create the
MVEIW so that it can be fast refreshed.
Rating: 5
Join and Aggregates function July 11, 2002
Reviewer: Evara from INDO
Dear Tom,
I really confused about join and aggregates function in materialized view.
I read in oracle documentation part Materialized view, they say :
If want materialized view can refresh fast, count(*) must be include with
sum(expr)

I create demonstration table like this :

drop table sales;
drop table dept;
create table dept(
deptno varchar2(10), location varchar2(30),
constraints dept$pk primary key(deptno));

create table sales(
nobukti varchar2(5), tanggal date,
deptno varchar2(10), qty number,
constraints sales$pk primary key(nobukti),
constraints sales$deptno foreign key(deptno)
references dept(deptno));

create snapshot log on dept with
rowid(deptno, location) including new values;

create snapshot log on sales with
rowid(nobukti,tanggal,deptno,qty);

drop materialized view sales_mv;

create materialized view sales_mv
build immediate
refresh fast
enable query rewrite
as
select dept.location, sum(sales.qty), count(sales.qty), count(*)
from dept, sales
where dept.deptno = sales.deptno
group by dept.location;

after I refresh :
exec dbms_mview.refresh('sales_mv','F');
select * from user_mviews;

I look in column AFTER_FAST_REFRESH is STALE.
So my query does not use this materialized view.

What I missing in this case ?. I already add count(*) and sum(expr) in
materialized view declaration, like in oracle documentation

Best regards

Evara Samsyiar
Followup:
Well, I sort of thought I told you what was needed?  Eg: count(COL), in addition
to COUNT(*) is needed with SUM(col) in order to enable fast refresh.

See
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76994/mv.htm#42194
There is a table there that says "if you use this aggregate, you need to also
include this function in the select list...."

So, sum(col) requires you select count(*), count(col) .....
Rating: 5
Refreshing MVs with 8.1.7 and 9.2 July 11, 2002
Reviewer: Sandra from Germany
Tom,

We are writing a reporting system, and we have decided to summarize the data
using MVs, so query time would be nearly instantaneous. However, when we enter a
new record into the database, the insert time is long because at every new DML
we manually refresh the MVs. That's because we cannot tolerate any staleness.
That was the tradeoff. This was very useful because our system is very
query-intensive, with little DML. Therefore, we had to find some way to speed up
a liitle the data entries. We are now using 8.1.7 and are migrating to 9.2.

Ideally we would like to use ON COMMIT, so we would not have to manually refresh
our MVs. However, we have a lot of restrictions telling us that we should use ON
DEMAND, like multiple joins, aggragate funnctions, etc.

Q1. One thing we were not able to test and maybe you could tell us is, in case
we use ON COMMIT, would then this refresh be carried out "in background", that
is, so I would have my prompt immediately back after the COMMIT in my session,
not having to wait for the refresh to complete ?

Q2. In case the refresh occurs in background, can I somehow fragment my MV
select in multi-tier views so I could get rid of the restrictions and be able to
use ON COMMIT ?

Q3. My final attempt is using a "dirty" approach. I have created a procedure
that commits and refreshes the MVs. This refresh is done by using Java to call a
background UNIX process that refreshes the MVs. It works, but I would like to do
it in the database. Do you see any other solution ?

Thank you very much !
Followup:
q1) no, on commit would mean on commit.  You are asking the MV refresh to be
part of YOUR transaction.

q2) not following -- but yes, in 9i you can have mv's of mv's of mv's

q3) use dbms_job, that is how we run things in the background.

Personally -- i would do this.

Use stale tolerated.  Run the refresh in the background.  You will see the
insert in the MV shortly AFTER it was inserted.  Generally, I find you can have
a tolerance for stale tolerated -- if the difference is only minutes.
Rating: 5
fast refresh in oracle817 and oracle901 July 12, 2002
Reviewer: Evara Samsyiar from INDO
Dear tom
Thanks for your adviced.
This is my script to demonstrated about fast refresh on materialiazed view. I
create this script refer from your answer.

drop table sales;
drop table dept;
create table dept(
deptno varchar2(10),
location varchar2(30),
constraints dept$pk primary key(deptno));

create materialized view log on dept
with rowid (deptno, location)
including new values;

analyze table dept compute statistics
for table for all indexes for all indexed columns;

create table sales(
nobukti varchar2(10), tanggal date,
deptno varchar2(10), qty number default 0,
constraints sales$fk foreign key(deptno)
references dept(deptno));

create materialized view log on sales
with rowid(nobukti,tanggal,deptno,qty)
including new values;

analyze table sales compute statistics
for table for all indexes for all indexed columns;

drop materialized view sales_mv;

create materialized view sales_mv
build immediate
refresh fast
enable query rewrite
as
select a.location, count(*) as cnt,
count (b.qty) as cnt_qty, sum(b.qty) as sum_qty
from dept a, sales b
where a.deptno = b.deptno
group by a.location;

analyze table sales_mv compute statistics
for table for all indexes for all indexed columns;

alter session set query_rewrite_enabled = true;
alter session set query_rewrite_integrity = enforced;

I test this script in Oracle 817.
I look that fast refresh does not work in oracle 817.
I test again, again and again for 3 days before.
I frustation why my materialized view cannot refresh using fast refresh method ?

And then, I try to test in Oracle 901...beautifull..
My materialized view can fast refresh successfully :)

So, What difference in oracle 817 and oracle 901 about materialized view fast
refresh method.

Please tell me, what I wrong in this test ?

Best regards
Followup:
In 8i -- the documentation has this to say:

(chapter 8 of the data warehousing guide on MV's)

Materialized Views with Joins and Aggregates

In data warehouses, materialized views would normally contain one of the
aggregates shown in Example 2 below. For fast refresh to be possible, the SELECT
list must contain all of the GROUP BY columns (if present), and may contain one
or more aggregate functions. The aggregate function must be one of: SUM,
COUNT(x), COUNT(*),COUNT(DISTINCT x), AVG, VARIANCE, STDDEV, MIN, and MAX,
and the expression to be aggregated can be any SQL value expression.
If a materialized view contains joins and aggregates, then it cannot be fast
refreshed using a materialized view log. Therefore, for a fast refresh to be
possible, only new data can be added to the detail tables and it must be loaded
using the direct path method.

In 9i the same documentation says:

Fast refresh for a materialized view containing joins and aggregates is possible
after any type of DML to the base tables (direct load or conventional INSERT,
UPDATE, or DELETE). It can be defined to be refreshed ON COMMIT or ON DEMAND.
A REFRESH ON COMMIT, materialized view will be refreshed automatically when a
transaction that does DML to one of the materialized views commits. The time
taken to complete the commit may be slightly longer than usual when this method
is chosen. This is because the refresh operation is performed as part of the
commit process. Therefore, this method may not be suitable if many users are
concurrently changing the tables upon which the materialized view is based.

so, what you are seeing is to be expected for a JOIN mv with Aggregates -- no
fast refresh in 8i (unless you direct path the data), fast refesh = true in 9i.

It is a new feature
Rating: 3
ORA-12054: cannot set the ON COMMIT refresh attribute for the materialized view July 26, 2002
Reviewer: Louis from India
Dear Tom,
        U'r response was useful. I have a different  issue . Pls throw some
light on this
         In my development environment I created a materialized view as
CREATE MATERIALIZED VIEW cc_cdd_view
BUILD IMMEDIATE
REFRESH FORCE ON COMMIT
ENABLE QUERY REWRITE
AS
SELECT CK006.CK006_CC_CODE,
CK006.CK006_CC_NAME,ck006.ck006_modern_cc,CK001.CK001_CDD_CODE,
CK001.CK001_CDD_DESC
FROM CK006_CC_MST CK006,
CK009_GRC_CONSTCY_MST CK009,
CK008_CDC_GRC_MST CK008,
CK007_CDD_CDC_MST CK007,
CK001_CDD_MST CK001
WHERE CK006.CK004_CONSTCY_CODE = CK009.CK004_CONSTCY_CODE
AND CK009.CK003_GRC_CODE = CK008.CK003_GRC_CODE
AND CK008.CK002_CDC_CODE = CK007.CK002_CDC_CODE
AND CK007.CK001_CDD_CODE = CK001.CK001_CDD_CODE;

Here this user is granted with "create materialized view" and "query rewrite" in
addition to the connect and resource roles. The init parameter values related to
this are kept as
QUERY_REWRITE_ENABLED=TRUE
QUERY_REWRITE_INTEGRITY=TRUSTED. Here I did not create any materialized view
logs. Whenever any change is there in any of the related tables it is getting
properly reflected in the materialized view immediately after commit.
               In my production server in addition to all these privilages ,DBA
role is also granted . Now when I try to create the above mentioned materialized
view it gives the error, "ORA-12054: cannot set the ON COMMIT refresh attribute
for the materialized view".
               What could be the reason that it is happening only in Production
server ?. Is there any more settings to be done for creating materialized views
?.
Followup:
do a select * from v$version on "prod" and "dev" and compare the results.
Rating: 3
July 31, 2002
Reviewer: Louis from India
When I executed

select * from v$version;, I got the same result from development and production
as

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 Solaris: Version 8.1.7.0.0 - Production
NLSRTL Version 3.4.1.0.0 - Production

So any other possibilities ???
Followup:
check for missing indexes, constraints, etc.  Make sure you are comparing apples
to apples.

Should be easy for you to post a very simple test case that works in ONE 817 and
not on the other -- test case would create a test user account, grant to them,
log in as them and run all ddl necessary to reproduce.

That'll help LOTS
Rating: 4
Is fast refresh possible if ... August 21, 2002
Reviewer: Ping from VA USA
in the mv there are some sum(decode(....))?
Followup:
yes.
Rating: 4
Is fast refresh possible if there are September 09, 2002
Reviewer: Ping from VA USA
in the materialized view, there are some sum(decode()) like:

sum(decode(col1,1,col2,2,col3,3,col4....))?

The reason I asked is this sum can be sum(col2),sum(col3),sum(col4)...,
depending on the values of col1. To make fast refresh possible, we need to
include count(*), count(col1),count(col2),count(col3)...? Or it is just
impossible to do fast refresh at all?
Followup:
I do not see how the fast refresh could work there, no.

Say you have:

create table t ( id int primary key, x int, y int, z int );

and you created an mv like:

create materialized view mv
refresh fast
as
select x, sum( decode( id, 1, y, z ) ), count(*), count(x), count(y), count(z)
  from t
 group by x
/

(suppose you could, you cannot).

Now, if you started with two rows in T

( 1, 100, 100, 100 )
( 2, 500, 500, 500 )

the MV will have

x    sum( decode( .....
100  100
500  500

Now, you update t set id = 3 where id = 1.

How would anyone know that we need to DELETE row 1 in the MV (we don't KNOW that
row belonged to ID 1, we lost that fact along time ago) and add that deleted
value into row 2?

So no, this isn't doable.  It is a complex query.
Rating: 5
Stale Tolerated September 23, 2002
Reviewer: Bharath from USA
What to do you mean by stale tolerated.I have gone thru the oracle documentation
still i am not getting clear answer.

Thx
Bharath
Followup:
A materialized view can get out of sync with the underlying details (eg: if it
is not a refresh on commit MV -- very typical, most MV's are *not* refresh on
commit)

Stale Tolerated says "use the MV during query rewrite even if the MV is known to
be out of sync with the details".  That is, the data doesn't need to be up to
the second accurate, data that was current 5 or 10 minutes (or however long) ago
is OK for this report.  That is "stale tolerated"
Rating: 3
if drop of MV will cause purge on log table September 23, 2002
Reviewer: June from MD USA
Dear Tom,

I searched all topics and looked over Oracle doc I had, there is no where
indicated that drop of MV on snapshot site will cause the associated log table
on master site purged.

We ran into severe production problem on master site by filling up all archive
logs (total of 5GB). By killing the process, the statement which was killed is
the deletion of from one of the log table; meanwhile, the drop of MV was knocked
off with error msg: ORA-02067: transaction or savepoint rollback required. and
drop of MV is the only thing I could identified during that time frame and with
the OSUSER for that process.

so my question is: how the 'drop materialized view...' statement on snapshot
site trigger the 'delete from mastersite.mlog$...' statement? we had db link
between snapshot site and mastersite. what should I do if I want to drop MV
table with log table on remote (log table will serve for other snapshot accounts
too)?

any suggestion will be appreciated.
Followup:
It should only clean out records relevant to IT.  Just like when you refresh,
the last guy that pulls -- clears the record.

Did you observe something different?  That is was purging records OTHERS would
actually need, or, was it purging records that would no longer be needed since
it wasn't going to be around anymore?
Rating: 3
last refresh time stamp is... September 23, 2002
Reviewer: June from MD USA
last refresh time stamp for the MV (assume on site A) I was trying to drop is on
6/28/01. after that, there are approximately half million records been refreshed
via fast refresh mode for other production snapshot site (such as site B). I
have no way to identify which records it was going to delete by the time we
killed the process.

so from what implicated, it is possible that even though site B has all data
refreshed from master log table and still doing daily refresh, when site A tried
to drop MV which has not been refreshed for long time, Oracle internally tried
to purge data on or before 6/28/01 (about half million) in log table? what is
the suggested way to re-organize snapshot(such as moving tablespace, drop and do
complete refresh) with log table referenced by multiple sites ? Thanks very
much!
Followup:
Why would you need to "reorg" a snapshot, especially a read only one.  Anyway --
when you dropped it, you saw the normal and natural "cleaning of the logs"

the records you saw being deleted -- if I follow you -- are the 1/2 million
added AFTER 6/28/01 that were propagated to site B over time but not to site A.
When you drop site A -- those records are of course not needed and just like
they would have been during a REFRESH of site A -- they were removed (eg: it was
not that you dropped site a that would just do this -- a refresh of A would
have.  You let 1/2 million change records queue up.  You ran out of rollback
unqueuing them).

the suggestion would be not to let records queue up for over a year.
Rating: 4
Lock type JI during refresh on commit in MV September 23, 2002
Reviewer: A reader from USA
Hi Tom,

I have a couple of views which were created using huge tables (2 Gigabyte each),
and  this views take forever to return the results. The explain plan for them
are ok but the problem is connected with the amount of records that they have to
scan is a lot. I created two materialized views which resolve the most
complicate and common logic of those views and I replace those huge tables in
the views with the MV's, but as soon as I implemented the MVs all the batch
processes and the applications which need to insert and update the big tables
(which are being used by the Mvs) began to experience waits for enqueue, library
cache lock, row cache lock, etc. All these sessions were having problems of lock
type JI with the materialized view log on those big tables. The solution that I
found was dropping the materialized views and logs view and after this
everything was ok again.

My question is, if there is an enviroment with a lot of insert/update/deletes is
there any way to have MVs refresh on commit without facing lock type JI every
time when many transactions need to insert/delete/update a row on the master
tables ?

Thanks in advance for any help
Followup:
If you have my book -- you would find that refresh on commit MVs are not
appropriate at all for an environment where many people concurrently modify the
detail tables.

Why?  It is quite simple really.  Suppose you have the EMP table and you wanted
to maintain counts by DEPTNO within emp (eg: select deptno, count(*) from emp).
You do LOTS of inserts and deletes into this emp table.

Now, you have 1,000,000 EMP records and say 1,000 DEPT records.

Now, when you REFRESH the dept rollup upon commit -- obviously that will
SERIALIZE at the DEPTNO level.  If two sessions both insert into DEPTNO=10, you
know both cannot increment the count at that same time right?  They will
serialize.  You have concentrated a large number of records into a small number
of records -- causing big time serialization.

JI locks are locks used to refresh MV's

This is a natural and entirely un-avoidable issue (Many people are trying to
update the same exact record(s)).
Rating: 4
No query re-writing happens November 19, 2002
Reviewer: Praveen from India
Q1.
I created a MV on all_objects table created in my schema
with only the following fields
my_all_objects=(owner,object_name, object_type, object_id)

create materailized view mv_all_obj
build immediate
enable query rewrite
referesh on commit
as
select owner, count(*) from my_all_objects group my owner;

I set compatibility option to 8.1.0
issued ALTER SESSION SET QUERY_REWRITE_ENABLED=TRUE
ALTER SESSION SET QUERY_REWRITE_INTEGRITY=ENFOREDE (I tried with all the three
options too)

But still my view is not used by the oracle-no query rewriting happens!

Q2.
When trying to create a materialized view I get an error:
"ON COMMIT attribute is incompatible with other options"
When I remove the "REFRESH ON COMMIT" clause from the "CREATE MATERIALZED VIEW"
statement, the operation is successfull. But the MV does not get updated after
an update on base table.

1) What are the other refresh options.
2) How they are specified/enforced
3) What are the relative adv/disadv

Thanks in advance

Praveen
Followup:
q1) i see no analyze.  In fact -- I see no full test case as I am prone to give
to you.  do it step by step like I do (like I did right above!!)

without CBO, no rewrite

q2) you know, without an EXAMPLE, it is impossible to tell you WHY.

see
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76994/mv.htm#32933
for the general restrictions.  see that entire chapter for the what/how/why's
(or if you have my book "Expert one on one Oracle" i have a chapter on MV's and
discuss these topics as well)
Rating: 3
Need your help badly February 28, 2003
Reviewer: VJ from BayArea,CA
Hi Tom,

I created a materialized view joining multiple tables. I enabled query rewrite
on the materialized view. I am confused on one thing though.

Let's say that, I have 3 tables t1, t2, t3. I joined these 3 tables and created
the materialized view and grouped by t2.fld1. Now, when i try to run a query
that includes one more field t1.fld1 along with or without the t2.fld1, i see
the explain plan and i see that it is not using the MView. That is bowling me
over. That means that i can only use the t2.fld1 to be able to use the MView.

I need your help badly on this. Even if you can point me to some docs anywhere
on this, i would really appreciate your help...

Thanks

VJ.
Followup:
well, without a real example (like the ones i post!) it is hard to say HOWEVER,
it does seem obvious from what you describe that it cannot use it in your case.

You have:

select t2.fld1, aggregates on t1, t3 columns perhaps......
  from t1, t2, t3
 where ...<joins>...
 group t2.fld1
/

Now it seems you are querying:

select ...
  from t1, t2, t3
 where ....<joins>....
   AND t1.fld1 = 'foo'
 group by t2.fld1
/

well -- that cannot in any way shape or form make use of the MV you created
above -- t1.fld1 has been "aggregated over", rolled up, that level of detail is
GONE.

That would be creating a MV:

create materialized view emp_rollup
as
select dept.deptno, sum(sal) sal
  from dept, emp
 where dept.deptno = emp.deptno
 group by dept.deptno;

and then trying to have

select dept.deptno, sum(sal) sal
  from dept, emp
 where dept.deptno = emp.deptno
   AND emp.job = 'CLERK'
 group by dept.deptno;

use it -- well, it cannot since the level of detail needed to support CLERK is
just gone, you aggregated over it, we have details at the deptno level NOT the
deptno AND job level.

If that is not your situation - you need to give me a clear cut example
Rating: 4
Need Help Badly March 01, 2003
Reviewer: VJ from BayArea,CA
I could understand that the detail is lost. I am sorry i did not provide
complete information. But here you go...

3 Tables - t1,t2,t3. t2 references t1 and t3 references t2. It is a fanning out
one-many relationship (t1->t2->t3)

Now when i created the materialized view, i created it at the t2 level.
Create materialized view x_mv
enable query rewrite
as
select t2.fld1, count(t3.fld2)
from t2, t3
where t2.fld1=t3.fld1
group by t2.fld1.

Now when i write a query like this, it is not using the MView.

Select t1.fld1, t2.fld1,count(t3.fld2)
from t1, t2, t3
where t1.fld1=t2.fld2
and   t2.fld1=t3.fld1
group by t1.fld1, t2.fld1

I would expect it to be rewritten to use the MView but it isn't. Please explain,
if i am making any mistakes...

Thanks
VJ
Followup:
what I meant by

well, without a real example (like the ones i post!)

was -- hey, give me an example I can run in sqlplus.  I feel like I spend half
my life guessing and trying to come up with tests.

I don't know what primary /foreign keys you have OR more importantly DON'T have.

But, in any case -- is it not obvious here?

the MV has

t2.fld1

Your query you want to have use the MV references

t2.fld2

there is NO SUCH FIELD in the MV.  How -- really, tell me -- HOW could you use
the MV here?????  You could get the wrong answer easily....

there just isn't anything to JOIN to -- you wiped out t2.fld2 in the MV, not
there, nothing to join on.  cannot do it.

(seems almost obvious that it could not)

But -- if you believe it should be able to then......

supply the tables for your test case
with the data
and write the query directly against the MV and compare to the query against the
base tables -- showing how Oracle should rewrite the query

and then I'll comment.
Rating: 5
June 27, 2003
Reviewer: A reader
Tom,

In your book expert-on-one page 597, materialized views , I have similar
situation, but i want to accomplish the same in 805.

Thanks
Followup:
sorry -- "new feature as of 8i", no query rewrite in 8.0
Rating: 5
Hi Tom, do you know why this doesn't works September 26, 2003
Reviewer: juancarlosreyesp@yahoo.com from Bolivia
I created previously so I recreate again.

SQL> grant query rewrite to adm;

Concesi¾n terminada con Úxito.

SQL> DROP materialized view log on adm.personas_me;

Log de rÚplica borrado.

SQL> drop materialized view adm.perci;

RÚplica borrada.

SQL> create materialized view log on adm.personas_me
  2  with ROWID, (per_NOMBRE)
  3  including new values
  4  ;

Log de rÚplica creado.

SQL> create materialized view adm.perci
  2  BUILD IMMEDIATE
  3  refresh fast
  4  ON COMMIT
  5  as select per_NOMBRE,count(*) from adm.personas_me
  6  group by per_NOMBRE;

RÚplica creada.

SQL>
SQL>
SQL> alter system set query_rewrite_enabled = true;

Sistema modificado.

SQL> EXECUTE DBMS_MVIEW.REFRESH('PERCI');

Procedimiento PL/SQL terminado con Úxito.

SQL> select per_nombre,count(*) from adm.PERCI
  2  group by per_nombre;

PER_NOMBRE                            COUNT(*)
------------------------------------ ---------
CARLOS JOSE                                  1
LIGIA YANET                                  1
NO IDENTIFICADO                              1

SQL> select per_nombre,count(*) from adm.PERSONAS_ME
  2  group by per_nombre;

PER_NOMBRE                            COUNT(*)
------------------------------------ ---------
CARLOS JOSE                                  1
LIGIA YANET                                  1
NO IDENTIFICADO                              6

SQL>

It seems that SQL> EXECUTE DBMS_MVIEW.REFRESH('PERCI');
doesn't works, and it didn't gave any error.
Followup:
why the schema name everywhere but the refresh?
Rating: 4
I tried both with and without schema September 29, 2003
Reviewer: A reader
Hi Tom, thanks, I am connect as ADM,
I tried with and without schema

SQL>  EXECUTE DBMS_MVIEW.REFRESH('ADM.PERCI');

Procedimiento PL/SQL terminado con Úxito.

SQL> select per_nombre,count(*) from adm.PERCI
  2     group by per_nombre;

PER_NOMBRE                            COUNT(*)
------------------------------------ ---------
CARLOS JOSE                                  1
LIGIA YANET                                  1
NO IDENTIFICADO                              1

I starting to think this is another bug of my database release.
Rating: 5
Stale and Fresh October 04, 2003
Reviewer: A reader
Hi Tom,

Is it possible to control the period of staleness when using query rewrite to a
Materialized View. For example:

If the data in this stale Materialized View is less than 2 hours stale, then
user query rewrite to it, otherwise not.

Thanks.
Followup:
Nope, it is either "stale" or it is "not stale" in our eyes.
Rating: 4
October 17, 2003
Reviewer: A reader
Hi Tom,

I have a very quick question related to materialized view here.

1. Can we create Mviews over views.
2. Can we create Mviews over views on tables in remote database.
3. What all Mviews (what I mean is FAST or COMPLETE, ON COMMIT or ON DEMAND
etc.)we can create in both the above cases.

Thanks in advance
Followup:
1) yes

ops$tkyte@ORA920> create table t ( x int primary key, y int  );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace view v
  2  as
  3  select * from t where x > 50;

View created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create materialized view mv
  2  as
  3  select * from v;

Materialized view created.

2) yes

3) complete for mvs of views.  fast will get you

ops$tkyte@ORA920> create materialized view mv
  2  refresh fast
  3  as
  4  select * from v;
select * from v
              *
ERROR at line 4:
ORA-12015: cannot create a fast refresh materialized view from a complex query

on commit will get you
ops$tkyte@ORA920> create materialized view mv
  2  refresh fast on commit
  3  as
  4  select * from v;
select * from v
              *
ERROR at line 4:
ORA-12054: cannot set the ON COMMIT refresh attribute for the materialized view

but

ops$tkyte@ORA920> create materialized view mv
  2  refresh complete on demand
  3  as
  4  select * from v;

Materialized view created.

works
Rating: 5
October 17, 2003
Reviewer: A reader
Hi Tom,

One more question which I forgot to post earlier:-

Do we always need a materialized view log while dealing with materialized views
,i.e. even if the refresh type is COMPLETE (not FAST).

Once again thanks for the quick response.
Followup:
nope.

example above demonstrated that!
Rating: 5
a simple documented way of finding mv capabilities incl. refresh capabilities October 18, 2003
Reviewer: Menon
This works in 9.0.1.0 and up.

1. run the $ORACLE_HOME/rdbms/admin/utlxmv.sql
   This creates a table called MV_CAPABILITIES_TABLE
2. exec dbms_mview.explain_mview
3. select * from mv_capabilities_table or I use
  select capability_name, possible, related_text, msgtxt
  from mv_capabilities_table
  where mvname = '<MV NAME HERE>';
Rating: 5
oops ! October 18, 2003
Reviewer: Menon
the second step should in the last post should be

2. dbms_mview.explain_mview ( 'YOUR_VIEW_NAME_HERE' )
Rating: 4
Fast refresh / refresh on commit not working October 29, 2003
Reviewer: Habeeb from India
I am using oracle 8.1.5 work group server.

While creating materialized view, "refresh on demand" or with schedule (REFRESH
START WITH SYSDATE NEXT sysdate+1/24) is working.

But refresh on commit (of) fast refresh is not working.

Pl clarify whether, it is not allowed in 8.1.5 work group server.
Followup:
"not working"
"not meaningful"

hows about the error message and code?
Rating: 5
Optimizing MV on demand fast refresh October 29, 2003
Reviewer: David from CA
Hi Tom,
I use 9i r2 and created Materialized View on 200 million records EVENTS table.
The MV_DD_EVENTS is simple GROUP BY to deduplicate events and is used by smaller
2nd-level MVs. I can't merge deduping with other MVs cause it will make MV
non-fast refreshable.

I need to refresh MV_DD_EVENTS after each batch insert wich I want to perform
frequently (let's say every 5 minutes 3000-5000 records).
The problem is that refresh takes about 15 minutes regardless of number of
records inserted. I don't have visibility into refresh process, autotrace and
SQL_TRACE don't show things within, cause it's PL/SQL package.
I have a strong feeling that it spends most of the time doing full table scan of
200 million records, cause I see lot's of IOs via iostat and refresh time varies
only slightly if I insert 1K or 500K records (15 minutes vs. 16 minutes).
How do I get a visibility into refresh process to tune it?

I'm pretty confident with my ability to tune the query, but I don't know what's
happening in DBMS_MVIEW.REFRESH. Even when I get the query from session that
runs DBMS_MVIEW.REFRESH from v$SQLAREA, I can't run it standalone to tune it.

I really need you advice cause I exhausted my troubleshooting skills on this one
:(
Am I missing something obvious?

Thank you very much for you help and sorry if I omitted some important details,
please let me know.
Appreciate your time,
David
Followup:
sql_trace certainl does show us the SQL executed.  turn on sql_trace and lets
see what it is doing.
Rating: 4
MV October 30, 2003
Reviewer: ARC from S'pore
Hi Tom,
Coluld you please explain.
I have a MV, can I write a query to access MV directly or shall I make use of MV
in a query?

Thanks

ARC
Followup:
you "can" write a query directly

but

the sheer "beauty" of the MV is you need NOT TO.

they work like indexes -- applications don't need to know they even exist, they
will be used auto-magically
Rating: 4
Fast refresh not working - materialized view October 31, 2003
Reviewer: Habeeb from India
SQL> create materialized view bbc1_mv
  2  build immediate
  3  refresh on commit
  4  enable query rewrite
  5  as

Error I got while creating:
ORA-00439: feature not enabled: Materialized view rewrite

Version 8.1.5 work group server
Followup:
that indicates 100% that MV rewrite is not supported in workgroup server -- it
is about query REWRITE not the refresh mode at this point.

feature is a feature of enterprise edition.
Rating: 4
materialized view and locking October 31, 2003
Reviewer: Riyaz from South India
Will the tables used will get locked when materialized view is refreshing.

Is this applicable to all kind of materialised views?
Followup:
no they won't -- the base tables will not get locked.

an updateable MV will lock the MV's during the refresh however.
Rating: 3
RE: Optimizing MV on demand fast refresh November 04, 2003
Reviewer: David Katsapov from Oakland, CA
Tom,
I run sql trace and plsql trace,
here is the output but it has nothing on the actual MV refresh operation.
The trace can't fit into this post, so I provide the link:
http://www.geocities.com/katsapov/oracle/FAST_REFRESH_trace.html
In the end I put sql statement from V$SQLAREA that looks more like the actual
refresh operation.

You wrote:
>sql_trace certainl does show us the SQL executed.  turn on >sql_trace and lets
>see what it is doing.
Followup:
can you dump some of the relevant stats from v$sql for that merge?  cpu/elapsed
time/exections in particular
Rating: 5
I wrote a simple example for MV test November 05, 2003
Reviewer: sonali from waltham , ma
I am writing this for 1st time, we have oracle 9.2 database
did this 1st -
DROP MATERIALIZED VIEW LOG ON mwebwork;
CREATE MATERIALIZED VIEW LOG ON mwebwork WITH ROWID, SEQUENCE
(work_id, WORK_ENTITY_TYPE, work_level, work_currency)
INCLUDING NEW VALUES;

work_id is Primary key and no FK in this example.

SQLWKS> CREATE MATERIALIZED VIEW mv_mwebwork
     2> BUILD DEFERRED
     3> REFRESH FAST ON DEMAND
     4> AS
     5> select count(*), Work_ID, Work_Level, Work_Entity_Type, Work_Currency
     6> from mwebwork
     7> where work_entity_type in (3, 4)
     8> group by Work_ID, Work_Level, Work_Entity_Type, Work_Currency
     9> ;
Statement processed.
SQLWKS> exec dbms_mview.refresh('mv_mwebwork','F');
exec dbms_mview.refresh('mv_mwebwork','F')
*
ORA-00900: invalid SQL statement
SQLWKS> execute dbms_mview.refresh('mv_mwebwork','F');
ORA-12057: materialized view "V52DEVOR"."MV_MWEBWORK" is INVALID and must
complete refresh
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 2
Why do I get this error ? What am I missing ?
I really want to do fast refresh.. as these MV are going to have huge number of
records to process.. so I want to update on demand ( we will run this say twice
a day) and just update things that got changed..
Thanks
Sonali
Followup:
you did a deferred build?  so the MV doesn't "exist" really.  you need to do a
complete FIRST in order to FAST (incrementally -- which may not necessarily be
faster then a complete, just incremental) refresh in the future!

but it is funny here -- if work_id is the primary key, to what end is this MV in
the first PLACE????  count(*) = 1 in all cases??

ops$tkyte@ORA920LAP> create table mwebwork
  2  ( work_id int primary key,
  3    work_level varchar2(10),
  4    work_entity_type int,
  5    work_currency varchar2(10),
  6    data varchar2(10)
  7  )
  8  /

Table created.

ops$tkyte@ORA920LAP> exec gen_data( 'MWEBWORK', 100 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> update mwebwork set work_entity_type = 3;

100 rows updated.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> CREATE MATERIALIZED VIEW LOG ON mwebwork WITH ROWID,
SEQUENCE
  2  (work_id, WORK_ENTITY_TYPE, work_level, work_currency)
  3  INCLUDING NEW VALUES;

Materialized view log created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> CREATE MATERIALIZED VIEW mv_mwebwork
  2   BUILD DEFERRED
  3   REFRESH FAST ON DEMAND
  4   AS
  5   select count(*), Work_ID, Work_Level, Work_Entity_Type, Work_Currency
  6   from mwebwork
  7   where work_entity_type in (3, 4)
  8   group by Work_ID, Work_Level, Work_Entity_Type, Work_Currency
  9  /

Materialized view created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select count(*) from mv_mwebwork;

  COUNT(*)
----------
         0

ops$tkyte@ORA920LAP> exec dbms_mview.refresh( 'MV_MWEBWORK', 'F' );
BEGIN dbms_mview.refresh( 'MV_MWEBWORK', 'F' ); END;

*
ERROR at line 1:
ORA-12057: materialized view "OPS$TKYTE"."MV_MWEBWORK" is INVALID and must
complete refresh
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

ops$tkyte@ORA920LAP> select count(*) from mv_mwebwork;

  COUNT(*)
----------
         0

ops$tkyte@ORA920LAP> exec dbms_mview.refresh( 'MV_MWEBWORK', 'C' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> select count(*) from mv_mwebwork;

  COUNT(*)
----------
       100

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> exec gen_data( 'MWEBWORK', 5 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> update mwebwork set work_entity_type = 3 where work_id not
in ( select work_id from mv_mwebwork );

5 rows updated.

ops$tkyte@ORA920LAP> select count(*) from mv_mwebwork;

  COUNT(*)
----------
       100

ops$tkyte@ORA920LAP> exec dbms_mview.refresh( 'MV_MWEBWORK', 'F' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> select count(*) from mv_mwebwork;

  COUNT(*)
----------
       105
Rating: 4
Thanks so much November 06, 2003
Reviewer: Sonali from waltham, ma
If I got it, did you say take out count(*)..
If I take out count(*), I get this error.. so I thought that for FAST REFRESH I
would need count(*).. even when work_id is PK and its always going to be 1..
SQLWKS> CREATE MATERIALIZED VIEW mv_mwebwork
     2> BUILD DEFERRED
     3> REFRESH FAST ON DEMAND
     4> AS
     5> select   Work_ID, Work_Level, Work_Entity_Type, Work_Currency
     6> from mwebwork
     7> where work_entity_type in (3, 4)
     8> ;
from mwebwork
     *
ORA-23415: materialized view log for "V52DEVOR"."MWEBWORK" does not record the
primary key

Other question, how come fast refresh which is incremental is same as complete
refresh ( which is going to delete everything 1st and then readd everything) ?

What does query rewrite enabled do ? I read lots of oracle documents, but did
not get what exactly it does.

Thanks again
Rating: 5
Bulk insert: can switching off MV help December 09, 2003
Reviewer: Saha from USA
Consider a situation where an OLTP environment switches temporarily to Batch
environment for conversion. Huge volume of data will be inserted and updated for
this short span of time. There are MVs on these tables. Will switching off the
MV refresh during this period help? After the conversion we can refresh the MVs.
(Similar to indexes being disabled and enabled before and after heavy insert.

If so, how to do this?
Followup:
will switching off mv refresh make the load faster?

yes, no, maybe.

if you have MV logs, they'll be maintained unless you drop them.
if you drop them, you'll have to recreate them and do FULL (not incremental)
refreshes

might the FULLs be faster then incrementals?  yes, they could be, but maybe not.
 it really depends on how much there is to do.

Just like with indexes, you'll have to benchmark your situation to see if
dropping and recreating the MVs is best for you. (or just dropping and
recreating the logs and doing a full refresh is best)
Rating: 4
Limitation of MV December 10, 2003
Reviewer: A reader
Will I be able to use a materialized view if I have a user defined function in
the select clause of the query ?
Followup:
yes

big_table@ORA9IR2> alter session set query_rewrite_enabled=true;

Session altered.

big_table@ORA9IR2> alter session set query_rewrite_integrity=TRUSTED;

Session altered.

big_table@ORA9IR2>
big_table@ORA9IR2> create or replace function foo( x in varchar2 ) return
varchar2
  2  DETERMINISTIC
  3  as
  4  begin
  5     return x;
  6  end;
  7  /

Function created.

big_table@ORA9IR2>
big_table@ORA9IR2> set autotrace traceonly
big_table@ORA9IR2> select foo(status), count(*) from big_table group by status;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2849 Card=999620
Bytes=4998100)
   1    0   SORT (GROUP BY) (Cost=2849 Card=999620 Bytes=4998100)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1379 Card=999620
Bytes=4998100)

Statistics
----------------------------------------------------------
        301  recursive calls
          0  db block gets
      14440  consistent gets
      13968  physical reads
        300  redo size
        444  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
         14  sorts (memory)
          0  sorts (disk)
          1  rows processed

big_table@ORA9IR2> set autotrace off
big_table@ORA9IR2> pause
big_table@ORA9IR2>
big_table@ORA9IR2> create materialized view big_table_mv
  2  build immediate
  3  enable query rewrite
  4  as
  5  select foo(status), count(*)
  6    from big_table
  7   group by status
  8  /

Materialized view created.

big_table@ORA9IR2> analyze table big_table_mv compute statistics;

Table analyzed.

big_table@ORA9IR2> pause

big_table@ORA9IR2>
big_table@ORA9IR2> variable x varchar2(20);
big_table@ORA9IR2> exec :x := 'VALID';

PL/SQL procedure successfully completed.

big_table@ORA9IR2>
big_table@ORA9IR2> set autotrace traceonly
big_table@ORA9IR2> select foo(status), count(*) from big_table group by status;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=7)
   1    0   TABLE ACCESS (FULL) OF 'BIG_TABLE_MV' (Cost=2 Card=1 Bytes=7)
Rating: 4
Limitations - Contd. from previous thread December 10, 2003
Reviewer: A reader
Assuming the materilized view is replicating data, will I be able to query the
mv than the underlying base tables (ie without the need for setting
query_rewrite) ?
Followup:
yes
Rating: 4
Don't know why it didnt work December 11, 2003
Reviewer: A reader
Hi Tom,

Is there any limitation on using mv in RBO without
query_rewrite/query_rewrite_integrity not set?  If so I am planning to use the
mv as a data aggregation tool whereby  on EOD I can run dbms_job to populate the
mv in an OLTP environment. Can you think of any pitfalls to this approach ?

orcl9i> drop snapshot big_table_mv
  2  /

Materialized view dropped.

Elapsed: 00:00:00.03
orcl9i> drop table big_table
  2  /

Table dropped.

Elapsed: 00:00:00.00
orcl9i>
orcl9i> create table big_table as select * from all_objects
  2  /

Table created.

Elapsed: 00:00:01.09
orcl9i> alter session set query_rewrite_enabled=true;

Session altered.

Elapsed: 00:00:00.00
orcl9i>
orcl9i> alter session set query_rewrite_integrity=TRUSTED;

Session altered.

Elapsed: 00:00:00.00
orcl9i>
orcl9i> create or replace function foo( x in varchar2 ) return
  2  varchar2
  3  DETERMINISTIC
  4  as
  5    begin
  6       return x;
  7    end;
  8  /

Function created.

Elapsed: 00:00:00.00
orcl9i>
orcl9i> set autotrace traceonly
orcl9i> select foo(status), count(*) from big_table group by status;

Elapsed: 00:00:00.03

Execution Plan
----------------------------------------------------------

   0      SELECT STATEMENT Optimizer=CHOOSE

   1    0   SORT (GROUP BY)

   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE'

Statistics
----------------------------------------------------------

         22  recursive calls

          0  db block gets

        360  consistent gets

        345  physical reads

          0  redo size

        446  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)

          1  rows processed

orcl9i>
orcl9i> create materialized view big_table_mv
  2     build immediate
  3     enable query rewrite
  4     as
  5     select foo(status), count(*)
  6       from big_table
  7      group by status
  8  /

Materialized view created.

Elapsed: 00:00:00.03
orcl9i>
orcl9i> analyze table big_table_mv compute statistics;

Table analyzed.

Elapsed: 00:00:00.00
orcl9i>
orcl9i> variable x varchar2(20);
orcl9i> exec :x := 'VALID';

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
orcl9i>
orcl9i> set autotrace traceonly
orcl9i> select foo(status), count(*) from big_table group by status;

Elapsed: 00:00:00.03

Execution Plan
----------------------------------------------------------

   0      SELECT STATEMENT Optimizer=CHOOSE

   1    0   SORT (GROUP BY)

   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE'

Statistics
----------------------------------------------------------

          0  recursive calls

          0  db block gets

        349  consistent gets

        345  physical reads

          0  redo size

        446  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

orcl9i>
orcl9i>
orcl9i>
Followup:
umm, beyond the fact that MV's don't kick in UNLESS you are using the CBO --
nope, not problems with RBO

RBO does not "seem them"

you HAVE to use the CBO.
Rating: 4
December 12, 2003
Reviewer: A reader
Quote "beyond the fact that MV's don't kick in UNLESS you are using the CBO, RBO
does not "seem them"
". Could you please clarify why MV's cannot be used directly in RBO as a summary
table mechanism. I will not need query_rewrite features.
Followup:
because the rbo doesn't see mv's, partitioned tables, iots, bitmapped indexes,
function based indexes, etc etc etc etc etc.

let me put it this way.  If you have the need for mv's, you have an even greater
need for the CBO with all of its advanced access paths.

rbo = NO mv's, no hash joins, no bitmaps, no <lots and lots and lots of stuff>
Rating: 4
Fast Refresh December 15, 2003
Reviewer: A reader
Hi Tom,

Thanks for replying to the previous threads. I understand having user defined
function will nullify the fast refresh option. I have a union-all query with
user defined function declared. Just to populate the mv takes about 2 hours. Is
there any way to speed up/ overcome this ?
Followup:
i find many times the UDF can be written in straight sql.  give me an example to
look at.
Rating: 4
Another Solution December 16, 2003
Reviewer: A reader
Hi Tom,
Thanks for your reply, I thought of creating the materialized without the udf
and creating a view on top of mv with the udf. Hope it will work. Another issue
I am having trouble is fast refresh on  a union-all query. I did create a mv
log, and also included rowid's of all tables accessed, and marker in each query.
The tables in from clause come from multiple schemas and I have prefixed with
the schema name. Still to no avail. Am I still missing something.
Followup:
if you provide me a

o concise
o yet 100% complete

test case to work with (try to reproduce with EMP and DEPT for example) that I
can run, I'll try to tell you if

a) its not supposed to work (and why)
b) its a "problem" that needs to be fixed.
Rating: 3
December 17, 2003
Reviewer: A reader
drop materialized view log on emp;
drop materialized view log on emp2;

create materialized view log on emp with rowid;
create materialized view log on emp2 with rowid;

drop materialized view mv_scott_test
/

create materialized view mv_scott_test build immediate refresh complete on
demand
as select
1 umarker, a.rowid arowid, b.rowid browid, a.empno, a.ename, a.sal
from emp a, emp2 b
where a.empno = b.empno
union all
select 2 umarker, a.rowid arowid, b.rowid browid, a.empno, a.ename, a.sal
from emp a, emp2 b
where a.empno = b.empno
/

alter materialized view mv_scott_test refresh fast on demand
/
exec dbms_mview.refresh('mv_scott_test');
*
ERROR at line 1:
ORA-12015: cannot create a fast refresh materialized view from a complex query
Followup:
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>
ops$tkyte@ORA920> alter table emp add constraint emp_pk primary key(empno);

Table altered.

ops$tkyte@ORA920> alter table emp2 add constraint emp2_pk primary key(empno);

Table altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create materialized view log on emp with rowid;

Materialized view log created.

ops$tkyte@ORA920> create materialized view log on emp2 with rowid;

Materialized view log created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create materialized view mv_scott_test
  2  build immediate
  3  refresh fast
  4  on demand
  5  as
  6  select
  7  1 umarker, a.rowid arowid, b.rowid browid, a.empno, a.ename, a.sal
  8  from emp a, emp2 b
  9  where a.empno = b.empno
 10  union all
 11  select 2 umarker, a.rowid arowid, b.rowid browid, a.empno, a.ename, a.sal
 12  from emp a, emp2 b
 13  where a.empno = b.empno
 14  /

Materialized view created.

ops$tkyte@ORA920> exec dbms_mview.refresh('mv_scott_test');

PL/SQL procedure successfully completed.

don't know your version.  don't know your tables.  don't know your keys... but
-- I can get it to work
Rating: 4
Materialized View - Fast Refresh - ORA-04030 December 29, 2003
Reviewer: KJ from MA,USA
Hi Tom,
     We have an informatica workflow executing unix scripts that refresh 7
materialized views around the same time( within an interval of 30mins). One
particular mview m_trade_fact( the master table had 25 Million rows) gave this
error
"
ORA-12012: error on auto execute of job 1094
ORA-12008: error in materialized view refresh path
ORA-12801: error signaled in parallel query server P010
ORA-04030: out of process memory when trying to allocate 56 bytes (cursor work
he,kenv: ktfcsnp)
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 189
ORA-06512: at line 1
"
The script itself had this error
"ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
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"

Though it gave this error, When I look at all_mviews, the status was VALID and
the refresh_type was COMPLETE. ( The script does a fast refresh )

We placed a TAR on this and sent dumps and logs. We havent got any suggestion on
this yet except that they referred to Bug 3282771 - which I could not get from
metalink.

I added sleep for 10mins (since I know for sure that atleast 5 of the 7 mviews
get refreshed within this time ) within the script to refresh m_trade_fact. It
seems to be working fine.

My question is:
Is there any Init Parameter that I should tune so that the above error doesnt
happen. If so, which one. Anything that I need to look at in particular? Any
suggestions?
Thanks for your time and I appreciate your help.

Regards
Followup:
that bug is "not really a bug" bug.

Basically -- you are running out of memory.  The fact that if you stagger them
indicates this is so (it falls back to a complete since the "changes only"
failed)

By staggering them -- not having them run all at the same time -- you have
enough ram.

Running them all at the same time -- you run out of RAM.  that is all.  you
would decrease the size of the SGA, look at the pga_aggregate_target or sort
area size/hash area size, look at what else is going on at that time, look at
how much memory you actually have available on the machine in order to avoid
running out.
Rating: 4
Followup - MView and 04030 December 30, 2003
Reviewer: KJ from MA, USA
Hi Tom
 Thanks for your response and suggestion. Here are some statistics

  We have two boxes.
  Both BOX1 and BOX2 have 16GB RAM
  BOX1 hosts Informatica and the unix scripts.
  BOX2 hosts two databases ODS and DM
  It is only the informatica workflow that is being executed at this time. No
other process runs.

  ODS and DM are setup the same way

Total System Global Area 3223820768 bytes
Fixed Size                   735712 bytes
Variable Size            2164260864 bytes
Database Buffers         1056964608 bytes
Redo Buffers                1859584 bytes

PGA Aggregate Target : 1048576000
sort_area_size       : 524288
hash_area_size       : 1048576

It looks like there is sufficient RAM. Should we increase the sort_area_size?
Any suggestion with any numbers?
Thanks so much
Regards
Followup:
what is the workarea policy set to?  manual or auto?

what is the OS?
Rating: 4
Followup - MView and 04030 December 30, 2003
Reviewer: KJ from MA, USA
Hi Tom
 Thanks for your response and suggestion. Here are some statistics

  We have two boxes.
  Both BOX1 and BOX2 have 16GB RAM
  BOX1 hosts Informatica and the unix scripts.
  BOX2 hosts two databases ODS and DM
  It is only the informatica workflow that is being executed at this time. No
other process runs.

  ODS and DM are setup the same way

Total System Global Area 3223820768 bytes
Fixed Size                   735712 bytes
Variable Size            2164260864 bytes
Database Buffers         1056964608 bytes
Redo Buffers                1859584 bytes

PGA Aggregate Target : 1048576000
sort_area_size       : 524288
hash_area_size       : 1048576

It looks like there is sufficient RAM. Should we increase the sort_area_size?
Any suggestion with any numbers?
Thanks so much
Regards
Rating: 3
Materlized view and 04030 December 31, 2003
Reviewer: KJ from MA,USA
Hi Tom

 workarea_size_policy is  AUTO
 and the OS is SunOS  5.8

Thanks & Happy New Year!!!
Followup:
is the OS 32 or 64 bit at the release....

any ulimits in place?
Rating: 3
Followup MView and 04030 January 02, 2004
Reviewer: KJ from MA,USA
Hi Tom

Oracle release is :
  Oracle9i Enterprise Edition Release 9.2.0.4.0 - 64bit Production

 maximum file descriptor = 4096
 maximum size of stack segment = 8192

 Tom, I see some ORA-07445 errors in the alertlog (Not due to the MView but some
other select statements or index enable parallel. I could see it happening a few
times in the last three months ). I will place a separate tar on this with
Oracle. I thought I would mention it since this could trigger some reasoning
behind the mview failure as well?

Thanks for your time & help.
Regards
Followup:
use

$ ulimit -a

cut & paste
Rating: 3
Followup January 02, 2004
Reviewer: KJ from MA,USA
Here it is

 $ ulimit -a
time(seconds)        unlimited
file(blocks)         unlimited
data(kbytes)         unlimited
stack(kbytes)        8192
coredump(blocks)     0
nofiles(descriptors) 4096
vmemory(kbytes)      unlimited

Thanks & Regards
Followup:
well, i researched a bit more.  found an issue.  the pga_aggregate_target is
going to give you a soft limit of about 1gig.

In your case -- i would suggest this:

alter session to set the workarea policy to manual.
set nice beefy sort/hash area sizes

update your tar with <Bug:3153463> and have the analyst look at that to confirm
my suspicions.
Rating: 4
Followup January 05, 2004
Reviewer: KJ from MA,USA
Thanks Tom.
 Documentation says "For workarea policy: Specifying MANUAL may result in
sub-optimal performance and poor PGA memory utilization."

 Can i update the workarea policy to Manual and increase Sort_area_size to 1M?
Should I set this only for the sessions that refresh the materialized views?

 I did update the tar with the bug #.
Thanks for your help Tom. Appreciate your time.
Regards
Followup:
you can alter a session, set workarea policy to manual and your session will use
the SAS and HAS

Yes, I would only set this for the sessions in question -- but SAS and HAS don't
have to be limited to 1m here.

The problem is the pga aggregate target is limited to 1gig currently -- you have
gobs of ram, you ran out "artificially" I believe due to this limit.
Rating: 3
Followup January 05, 2004
Reviewer: KJ from MA,USA
Hi Tom,

 we had some ORA-07445 messages in alert log for which i opened another tar. The
analyst had referred to 2805580 for patch #.  The analyst did mention that
 "* Does not occur when pga_aggregate_target is off."

Is it better to have the pga_aggregate_target as OFF?

I hope this isnt diverting from the topic too much.
Also I am going through your archives for more information on this one
(Difference between automatic vs manual PGA setting).

Thanks
Followup:
i don't see such a patch, there is a bug by that number but it is against 10.
not sure what that number is for?

it is better to have it set to auto with an appropriate pga_aggregate_target for
most systems.
Rating: 4
Followup 04030 January 06, 2004
Reviewer: KJ from MA, USA
Hi Tom
 That is a bug# which doesnt have much information.
 <Bug:2805580> SKGM.C: SEGV IN KGHALF() - LRGI3:TKITPART.TSC:TKITPART3.SQL

 1.Tom, does Bug# 3153463 imply that i could set PGA_aggregate_target to more
than 1GB and this wouldnt occur?

 2.If I set SAS and HAS for this particular session (MView refresh )  and if
there are other sessions that are invoked around the same time, will there be
issues with PGA workareas?

 3.These jobs get executed between 5-7AM. I see that
"over allocation count is = 0" which is ideal right? There are very few
onepass-executions starting with PGA size
4mb <= PGA < 8mb. This implies that 1GB of PGA_aggregate_Target Size is good
enough, right?

 I am sorry to bother you with so many questions. I am looking for some handle
here to get this rectified.
Thanks & Appreciate your time
Followup:
1) no, bug 3153463 implies that pga_aggregate_target is putting a limit of 1 gig
on you

2) if you are using workarea = manual, you are not using workareas so the other
sessions should be unaffected

3) 1gig right now is the biggest you can go in 9i (there are patches that can
change that -- thats why i pointed you to this 3153463 and support).
Rating: 4
Thanks Tom January 06, 2004
Reviewer: KJ from MA,USA
Thanks Tom. You have been responsive and of great support. I did not get much
response from the Analyst except that it is an open issue.

I appreciate your time and help.
Thanks again for your suggestions.
Best regards
Rating: 4
why this does not work? January 08, 2004
Reviewer: A reader
Hi

Do you know why I am getting this error?

CREATE OR REPLACE PROCEDURE MV_REFRESH(P_OWNER VARCHAR2, P_NOM_VM VARCHAR2)
AS
BEGIN
   EXECUTE IMMEDIATE 'alter session set workarea_size_policy = manual';
   EXECUTE IMMEDIATE 'alter session set hash_area_size = 67108864';
   EXECUTE IMMEDIATE 'alter session set sort_area_size = 67108864';
   dbms_mview.refresh(tab => P_OWNER||'.'||P_NOM_VM,
                      method = 'c');
EXCEPTION
WHEN OTHERS THEN dbms_output.put_line(sqlcode);
END;
/

LINE/COL ERROR
-------- -----------------------------------------------------------------
7/4      PL/SQL: Statement ignored
8/23     PLS-00201: identifier 'METHOD' must be declared
Followup:
method =

instead of

method =>

perhaps.

It is trying to pass a boolean value that is the result of "method = 'c'" but
you have no variable "method" i suppose.
Rating: 4
mv refresh and stats January 22, 2004
Reviewer: A reader
Hello Sir,
         After we refresh an MV do we also need to explicitly
analyze the mv for updated stats on table and indexes or this is built in the
refresh (dbms_mv.refresh) itself ?
Followup:
you'd probably want to alter mv_name monitoring  and gather stale stats on the
schema periodically.  the refresh won't gather stats -- and in fact, you might
not need to gather stats -- but monitoring+gather stale will get them when they
are needed.
Rating: 5
Changes in underlying tables February 25, 2004
Reviewer: A reader
What is the best way to get structural changes reflected in materialized views?
If a column gets added to a table we want materialized view to show that column
as well, is that even possible without recreating the MV ?
Followup:
you have to recreate the MV -- just like you would a real view.  The MV doesn't
go bad after you add the column -- but your desire to add the column to the MV
will require a rebuild.
February 25, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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://download-west.oracle.com/docs/cd/B10501_01/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://download-west.oracle.com/docs/cd/B10501_01/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://download-west.oracle.com/docs/cd/A87860_01/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?
Rating: 5
db_file_name_convert and log_file_name_convert February 21, 2004
Reviewer: reader
Are these parameters used in logical standby database parameter file? I was
reading the document and it is suggesting to rename all of the data files using
ALTER DATABSE RENAME command as one of the steps in creating logical standby. Is
there any reason why these parameters cannot be used in logical standby? Thanks.

Following is a copy and paste from data guard concept guide (pdf doc).

4.2.11 Rename the Datafiles on the Logical Standby Database
On the logical standby database, rename all of the datafiles that were
identified in Section 4.2.1 and copied from the primary database. For example:

SQL> ALTER DATABASE RENAME FILE ’/disk1/oracle/oradata/payroll/system01.dbf’
2> TO ’/disk1/oracle/oradata/payroll/standby/system01.dbf’;
Followup:
yes you can and the documentation goes through that as well.
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96653/init_params.htm#77575
Rating: 5
db_file_name_convert ..... February 22, 2004
Reviewer: reader
Thanks.
(1)I looked at the above link, it says the parameter is applicable for physical
standby only. That means if I had to rename a file in primary database, do I
have to manually do the same in the logical standby?

(2) If I set the protection mode in 9i R2 to one of these maximum protection or
availability or performance for the standby database, is it possible to
dynamicall change this mode or do I have to shutdown both primary and standby
before I change the protection mode? Thanks.

(3) If for some reason my standby is down and assume that it was set in maximum
performance. When I bring the standby up would it automatically detect the
archive gap and applies them? Thanks for your time. Hope you don't mind asking
these questions in this thread.

Thanks.
Followup:
1) just search the data guard concepts/admin guide for that and you'll see
things like:

...
You must rename primary database datafiles in the standby database control file.
You can either manually rename the datafiles (see Section B.4) or set up the
DB_FILE_NAME_ CONVERT initialization parameter on the standby database to
automatically rename the datafiles (see Section 6.3.4).
......

it is for the standby.

2) quote from sql ref:

...
SET STANDBY [DATABASE] Clause Use this clause to specify the level of protection
for the data in your database environment. You specify this clause from the
primary database, which must be mounted but not open.
......

it is an "offline" operation.  database must be mounted but not open.

3) see
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96653/log_apply.htm#1017313
Rating: 5
Synch vs Aynch February 23, 2004
Reviewer: reader
I understand synch as LGWR writes redo info to standby redolog at the same time
it writes to primary redo log. What does asynch mean? Does it mean LGWR writes
to standby redo log after some time? Thanks.
Followup:
async means "not syncronous"

lgwr can write to the standby in one of three ways:

a) syncronous -- write to the standby and wait for a confirmation that write
succeeded.

b) asyncronous -- write to the standby but do not wait for confirmation (fire
and forget).

c) really long asyncronous, actually let ARCH move the redo over to standby
after redo log fills up
Rating: 5
Is ARCH process sync or async? February 25, 2004
Reviewer: David
Tom, can ARCH process ASYNC? Thanks.
Followup:
can arch process async what?
February 25, 2004 -- 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://download-west.oracle.com/docs/cd/B10501_01/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.
Rating: 5
Logical standby February 25, 2004
Reviewer: reader
After reading the doc, logical standby is not the solution for "zero data loss".
Is this a correct understanding? Only Physical standby provides "zero data loss"
protection.

Also, I read that logical standby can be created on a different OS from the
primary? How is this possible? Thanks.
Followup:
a true logical standby is homogenous (same OS) and can run in zero data loss
mode.

zero data loss mode requires the redo be written LOCALLY and REMOTELY, it cares
not if the recipient is a physical or logical.


Listing

Recent Archives Advanced Search FAQ by Category Most Popular Hot Articles Your Questions [ Listing ] RSS Feed
Subject | Article | Last Updated (East coast time)
primary key violated | article only | Feb 26, 2004 08:04 am
Replication | article only | Feb 26, 2004 08:18 am
Linux-Getting Started | article only | Feb 26, 2004 08:57 am
Group By Clause | article only | Feb 26, 2004 09:03 am
Qs on V$ views and a Q on OAS | article only | Feb 26, 2004 09:54 am
Variables in IN operator | article only | Feb 26, 2004 09:55 am
Combination of Locally Managed and Dictionary Managed TS | article only | Feb 26, 2004 10:00 am
Sequences | article only | Feb 26, 2004 10:06 am
Complex Queries and Materialized Views | article only | Feb 26, 2004 12:44 pm
Convert varchar2 to Date format | article only | Feb 26, 2004 01:38 pm
why multiple versions? What would be the cause of BIND_MISMATCH? | article only | Feb 26, 2004 02:17 pm
How does v$session_longops work ? | article only | Feb 26, 2004 02:19 pm
Date datatype | article only | Feb 26, 2004 02:24 pm
Help with comparison logic | article only | Feb 26, 2004 03:20 pm
Using a Collection in A where clause | article only | Feb 26, 2004 03:25 pm
Append Hint | article only | Feb 26, 2004 04:25 pm
V$SESSION_LONGOPS | article only | Feb 26, 2004 06:14 pm
How CBO uses histograms/data distribution when using bind variables? | article only | Feb 26, 2004 06:21 pm
Rownum | article only | Feb 27, 2004 07:36 am
user_tables | article only | Feb 27, 2004 07:38 am
Character Set conversion | article only | Feb 27, 2004 09:23 am
IMPLICIT CURSOR ATTRIBUTE SQL%NOTFOUND NOT WORKING | article only | Feb 27, 2004 10:05 am
oracle 9i Data Warehousing features | article only | Feb 27, 2004 10:10 am
Listing roles and privs recursively | article only | Feb 27, 2004 10:12 am
clob datatype | article only | Feb 27, 2004 02:37 pm
Analyze and DBMS_STATS | article only | Feb 27, 2004 03:07 pm
temporary tablespace | article only | Feb 27, 2004 03:13 pm
Index and Indexes | article only | Feb 27, 2004 04:35 pm
ORA-22818: subquery expressions not allowed here. | article only | Feb 27, 2004 05:38 pm
Restricted session | article only | Feb 27, 2004 07:22 pm
Backup and Recovery -- why extra redo is generated. | article only | Feb 28, 2004 10:26 am
difference between sql with clause and inline | article only | Feb 28, 2004 10:30 am
On Rollback Segment | article only | Feb 28, 2004 12:29 pm
Datafile selection within tablespace | article only | Feb 28, 2004 12:41 pm
"Best" query for testing a connection | article only | Feb 28, 2004 02:41 pm
Pivoting a result set | article only | Feb 28, 2004 08:46 pm
how to get user information? | article only | Feb 29, 2004 08:55 am
Space information | article only | Feb 29, 2004 09:09 am
Concurrent Transactions | article only | Feb 29, 2004 09:15 am
auto undo vs manual | article only | Feb 29, 2004 11:12 am
DB/Application slow after migrating to 9i | article only | Mar 01, 2004 07:12 am
Replication | article only | Mar 01, 2004 07:37 am
HAVING clause of GROUP BY. | article only | Mar 01, 2004 07:40 am
What difference between SQLJ,JSP(Java Stored Procedure) & PL/SQL Java | article only | Mar 01, 2004 08:08 am
ID's without Gaps | article only | Mar 01, 2004 08:41 am
Question Regarding HASH JOIN ! | article only | Mar 01, 2004 08:59 am
Sybase to Oracle | article only | Mar 01, 2004 09:06 am
Rollback segment needed via database link? | article only | Mar 01, 2004 09:14 am
Attachments Using pl/sql mail procedure | article only | Mar 01, 2004 09:18 am
index | article only | Mar 01, 2004 09:54 am
Proper use of DB Links in App Development/Data Warehousing | article only | Mar 01, 2004 10:32 am
ORA-12015 while creating materialized view using aggregates and join on two tables | article only | Mar 01, 2004 10:53 am
owa_cookie -- i don't see it in the cookies.txt file | article only | Mar 01, 2004 12:33 pm
Radio botton/ check box, updating multiple rows using OWA | article only | Mar 01, 2004 01:40 pm
Question on DECODE usage . | article only | Mar 01, 2004 02:49 pm
How to pass a '&' in url. | article only | Mar 01, 2004 03:18 pm
Sql tuning | article only | Mar 01, 2004 04:49 pm
Bind Variable Questions | article only | Mar 01, 2004 05:09 pm
global temp table | article only | Mar 01, 2004 06:50 pm
Fastest way to save off Duplicates | article only | Mar 02, 2004 07:10 am
Monitoring Alert file | article only | Mar 02, 2004 07:18 am
Varrays | article only | Mar 02, 2004 07:20 am
outer join | article only | Mar 02, 2004 07:38 am
Improve performance while loading data | article only | Mar 02, 2004 07:38 am
partitioning | article only | Mar 02, 2004 07:47 am
Flashback Query | article only | Mar 02, 2004 07:50 am
bulk | article only | Mar 02, 2004 08:59 am
Predicate pushing in views containing analytic functions | article only | Mar 02, 2004 06:45 pm
Tuning SQL statements (general) | article only | Mar 02, 2004 06:49 pm
Package header/body diff. 8i & 9i | article only | Mar 02, 2004 06:50 pm
Analyze Command | article only | Mar 02, 2004 06:52 pm
external table and date format | article only | Mar 02, 2004 06:58 pm
Database Backup/Export | article only | Mar 02, 2004 07:03 pm
dump file size | article only | Mar 02, 2004 07:12 pm
Temporary tablespaces | article only | Mar 03, 2004 08:57 am
question on job queue.How does it works | article only | Mar 03, 2004 09:17 am
Intermedia query result order by date desc | article only | Mar 03, 2004 09:39 am
which is faster? static cursor or dynamic cursor | article only | Mar 03, 2004 09:42 am
Parallel Execution and the Large Pool | article only | Mar 03, 2004 09:45 am
SQLLoader | article only | Mar 03, 2004 09:48 am
Hybrid Replication | article only | Mar 03, 2004 09:51 am
free text search | article only | Mar 03, 2004 09:52 am
Kill a session from database procedure | article only | Mar 03, 2004 09:53 am
PL/SQL Table and Sorting | article only | Mar 03, 2004 09:57 am
problem with date formats | article only | Mar 03, 2004 09:59 am
Can UTL_FILE package used to create ASCII files | article only | Mar 03, 2004 10:00 am
Checking Sysdba login | article only | Mar 03, 2004 03:06 pm
SQL_TRACE -- how to enable in other sessions. | article only | Mar 03, 2004 03:18 pm
RAID configuration | article only | Mar 03, 2004 03:19 pm
New database and password file | article only | Mar 03, 2004 03:21 pm
number formatting and how to display the primary key constraint name, primary key columns and also the foreign key names and columns. | article only | Mar 03, 2004 03:25 pm
Usage of Analytic Functions within a query having grouping | article only | Mar 03, 2004 03:27 pm
Indexes | article only | Mar 03, 2004 03:36 pm
Saveing the Output from a Dynamic SQL | article only | Mar 03, 2004 04:05 pm
configuring >1 listener on unix | article only | Mar 03, 2004 04:20 pm
row(s) 1 - 95 of 95
February 26, 2004 mike -- Thanks for the question regarding "primary key violated", version 8.1.5
You Asked
Tom,
I have created a table as:

create table t1
as
select object_id empno, owner||'.'||object_name ename,
       object_type city, mod(object_id,100) deptno,
       status division
  from all_objects
/

insert /*+ APPEND */ into t1
select empno + rownum+cnt,  ename,city ,deptno , division from t1,
(select count(*) cnt from t1)
;

.. a few more time for the INSERT as above.
then I tried to:
TRINH@TEST> alter table t1 add constraint t1_pk primary key(empno);
alter table t1 add constraint t1_pk primary key(empno)
*
ERROR at line 1:
ORA-02437: cannot validate (TRINH.T1_PK) - primary key violated

I beleive that empno is the candiate for PK:

SBU@TEST> select distinct count(empno) from t1;

COUNT(EMPNO)
------------
      387744

TRINH@TEST> select count(*) from t1;

 COUNT(*)
---------
   387744

TRINH@TEST> select count(*) from t1 where empno is null;

 COUNT(*)
---------
        0

What did it go wrong?
and we said...
try select count( DISTINCT empno ) from t1 -- NOT select distinct count(empno)
from t1.  The count(distint empno) will tell you the count of the distinct
empno's in t1 whereas distinct count(empno) will return you the distinct COUNTS
of non-null empnos....

The dups are happening due to something like this:

assume object_ids 1 .. 1,000,000 are in use.
assum there are 10,000 objects in all_objects

When you do the create table as select, EMPNO = 10,002 was put into t1 (for
example).  There was some object with object_id = 10,002.

Assume also that an object with object_id = 1 existed.

Now, when you do the insert /*+ append */ -- you'll get object_id = 1 from
all_objects, add 10,000 to it (thats CNT the first time) and then add 1 (rownum
-- it just happened to be the first row returned).  Now you have just put 10,002
in the table -- AGAIN.

Maybe use

insert /*+ APPEND */ into t1
select rownum+cnt,  ename,city ,deptno , division from t1,
(select max(empno)+1 cnt from t1)
;

instead ( note -- removed empno from the function in the select list, just using
rownum and cnt and cnt is now the max(empno)+1 )....
Review & Followup
Rating: 3
Kind of related February 24, 2004
Reviewer: Matt from Australia
Given the following;

create table tst(id number, sd number, ed number)
/
insert into tst values (0, 1, 2)
/
insert into tst values (0, 1, 3)
/
insert into tst values (0, 1, 4)
/
insert into tst values (0, 3, 5)
/
insert into tst values (0, 6, 10)
/
insert into tst values (0, 6, 10)
/
insert into tst values (0, 6, 11)
/
insert into tst values (1, 2, 3)
/
insert into tst values (1, 4, 5)
/

I want to CTAS a table with a PK on (id,sd)

create table copy_tst(
       id
      ,sd
      ,ed
      , constraint ct_pk primary key (id, sd)
)
as select id
      ,sd
      ,ed
  from tst

How do I simulate the PK using a select so that a consistent set of datagets
into the table? For example:

ID     SD      ED
==     ==      ==
0    1    2
0    3    5
0    6    10
1    2    3
1    4    5

Followup:
i don't understand the question -- what do you mean "simulate the pk"?

you have a consistent set there?

Rating: 3
Explaination February 25, 2004
Reviewer: Matt from Australia
Sorry, I wasn't being clear.

The table I was to create from the source data (stored in another table) has a
PK on the first two columns. If I use the CTAS as defined then I encounter a
:
"ORA-02437 cannot validate (string.string) - primary key violated "

I want to incorporate the PK validation into the select so that I can get all
the data I need in one select without any extra work.

Can this be done?

Followup:
tell me, which of the

create table tst(id number, sd number, ed number)
/
insert into tst values (0, 1, 2)
/
insert into tst values (0, 1, 3)
/
insert into tst values (0, 1, 4)
/
insert into tst values (0, 3, 5)
/
insert into tst values (0, 6, 10)
/
insert into tst values (0, 6, 10)
/
insert into tst values (0, 6, 11)
/
insert into tst values (1, 2, 3)
/
insert into tst values (1, 4, 5)
/

0,1
0,6

tuples did you want?  you'll need to tell me which rows to reject and which to
keep.  then sure, we can do that.

please don't say "the first" or "the last", there is no such thing in relational
tables.  Say "keep the one with the minimun value in column X" or something.

Rating: 3
More Info... February 25, 2004
Reviewer: Matt from Australia
Err, "the first"..No,..."the last" ;o)

Well the duplicates are:

insert into tst values (0, 1, 2)
/
insert into tst values (0, 1, 3)
/
insert into tst values (0, 1, 4)
/
insert into tst values (0, 6, 10)
/
insert into tst values (0, 6, 10)
/
insert into tst values (0, 6, 11)
/

I don't really care which tuples get loaded. So, I would say (arbitrarily) give
me the row with the largest value in the column "ed"

But what if there was also the following:

insert into tst values (99, 6, 10)
/
insert into tst values (99, 6, 10)
/

then there is a tie. In which case, I need only one row to get in (and I
*really* don't care which one, this time).

THanks as always

Followup:
ops$tkyte@ORA920PC> create table copy_tst(
  2         id
  3        ,sd
  4        ,ed
  5        , constraint ct_pk primary key (id, sd)
  6  )
  7  as select id
  8        ,sd
  9        ,ed
 10    from (select id, sd, ed,
 11                 row_number() over (partition by id,sd order by ed desc NULLS
last ) rn
 12            from tst
 13         )
 14   where rn = 1;

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select * from copy_tst;

        ID         SD         ED
---------- ---------- ----------
         0          1          4
         0          3          5
         0          6         11
         1          2          3
         1          4          5

February 26, 2004 Nikunj -- Thanks for the question regarding "Replication", version 8.0.5/8.1.6
You Asked
hi tom,

please send me detail steps for how can i do replication for
below perpose.

i have windows nt server 4.0, with Oracle 8.0.5 on both side.

i would like to create readonly replica with the singal table.

thankx,
nikunj
and we said...
Well, first I would urge you to read the replication guide.
There are no shortcuts to knowledge.  See
http://download-east.oracle.com/docs/cd/A81042_01/DOC/server.816/a76959/toc.htm
The problem is there is more then one way to tackle this problem
and only YOU can decide which is correct.

One method:

o on the "primary site" issue "create snapshot log on T"

o on the copy site, create a database link to primary

o on the copy site issue:

create snapshot my_copy
refresh fast
start with sysdate
next trunc(sysdate)+1+3/24
as
select * from t@other_database;

o on the copy site, set up job_queue_processes and
job_queue_interval in the init.ora file

Now, every night at 3am, this snapshot will copy all of the days
changes from the other site and apply them.  Yes, the interval
can be shorter, it can be longer, it can be manual, whatever.
Review & Followup
Rating: 5
Solutions that has increased my confidence December 11, 2003
Reviewer: shashikant naik from India
Dear Tom,

   Beleive me, not only this article but I am reading and trying to read more
and more data from you site. I like the ways you  answer every question. I am
learning a  lot from this site.

Rating: 5
Replicating partitioned table February 19, 2004
Reviewer: Alvin from Philippines
I'm planning to replicate a partitioned table (read only snapshot), the table is
partitioned by month and the partitioning key data type is in number (
milliseconds since epoch).

Is it possible to replicate data only from the recent 3 months data thereon ?
via the predicate where date_in_ms > (dec_ms) ??

Followup:
yes.  a RO snapshot can do that.

Rating: 5
Replicate table type February 25, 2004
Reviewer: Alvin from Philippines
Let's say i want to replicate table 'orig_t' with a read only snapshot
'repli_t'. 'orig_t' is a partitioned table and i occasionally age out 3 months
worth of data.

1. If 'repli_t' is not partitioned and i aged out data in 'orig_t' how will it
affect 'repli_t' ?

1.a. will i have a too high a HWM ?
1.b. will oracle tag the aged-out data as 'deleted' ? hence sending a 'delete'
change in the snapshot_log ?

2. Can 'repli_t' be a partition table also ? so i can age out data too..

3. If i add another column to 'orig_t' will the change be reflected on 'repli_t'
?

Followup:
you are going to want to read:
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96520/mv.htm#721
and in particular:
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96520/mv.htm#32940
(partitioning MV's)
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96520/mv.htm#45734
(rolling MV's)

February 26, 2004 Meyer -- Thanks for the question regarding "Linux-Getting Started", version 8.16
You Asked
Tom,

I want to learn to use Linux and do not have ANY experience with it yet so I am
not sure of the best way to go. I have used AIX for basic functions but not sure
if it will be similar.

What version do you run personally ....and what do you recommend (laptop and/or
server). Will there be a GUI?

Any tips or anything else to point out...and is the DB and IAS certified/avail
for these specific versions?

Thanks,
Meyer
and we said...
I am using Redhat linux version 8.0 (after upgrading from 7.2 to 7.3...).  I
never ever considered upgrading from windows version X to version Y to version
Z.  The upgrade was so simple and went flawlessly and I don't have gobs of
incompatible DLL's laying all around me.

I run it on two different desktops (one compaq at work, one dell at home) and a
toshiba laptop as well.

There is a GUI -- a GUI that believe it or not predates windows by some years.
X11.  The window manager I am personally fond of it KDE (comes with).  There are
dozens of others out there.

You'll find it very window-esqe.  Got a little start menu, desktop, drag and
drop -- the whole thing (just runs a tad faster).

DB and Tools are supported and shipping on linux all over the place.  Internally
at Oracle -- many of our demo machines used to showcase the product are running
on RAC + Linux now.

Tip:

It is not windows.  Do not expect it to be.  It is different and that is what
makes it good.  If it were the same, it would be rather boring.

Make sure you have time, time to learn, time to explore.

It made working with my desktops fun again.
Review & Followup
Rating: 5
Linux Server October 22, 2002
Reviewer: Jerry from Washington, DC
Another option, which I've had fun with, is to keep Windows, and get a second
machine on which to install Linux.  Then network them together with a couple of
cheap ethernet cards.  Then access the linux box with telnet and SQL*Net.

You'll learn basic networking, Oracle network administration, as well as Oracle
on a Unix-type platform.

Rating: 4
Desktop version of redhat? October 22, 2002
Reviewer: Jim Kennedy from Portland, OR USA
Can we use personal or professional vs advanced server?  I don't mind spending
$150 or less, but $800 is a little out of my league just to try it out and learn
more Linux, and setting up Oracle on Linux.  Its coming out of my pocket for
professional development.

Followup:
I've been using the totally for free one from the redhat website myself.  No
support (well, I did buy redhat 7.3 workstation for $70 usd so I do have access
to their support)....

Rating: 5
I like it October 22, 2002
Reviewer: Martin Guillen from Argentina
I'm learning Oracle and I have decided to learn it on Linux.
If you like IT, the Operating Systems, programming or anything related to
Computer Science then Linux is the way to go.
This is my opinion and I don't like to offend people that prefer other
OSs.(flame wars starts too fast in this topic)
I'm using SuSE and is a pretty good distribution.
If you are coming from Windows take your time to learn.
In the beginning Linux is some hard (not too much) to learn but definitively it
is valuable and funny.
Good Luck!

PS: When it comes you new book, Tom?

Followup:
I've put pen to paper -- it is due out april'ish 2003...

Rating: 4
October 22, 2002
Reviewer: Robert from Memphis, TN USA

Rating: 5
Thanks October 22, 2002
Reviewer: Meyer
Thanks,

From Red Hat
They have three kinds:

1)Personal: for desktops (39$)

2)Profesional: for desktops w/more features (149$)

3)Advanced Server: Enterprise Server's
(799.00, 1499.00, 2499.00) depending on support level.

Do you know if Oracle will run on #2, according to the tech person at Red Hat
Oracle is only certified to run on #3, but since it is my laptop and a
play/learn instance I only care if it runs and could deal w/quirks no-support.
Before I order, Is that what you are using? Do you have a DB instance and/or IAS
on #2 or just the server?

Thanks,
Meyer

Followup:
I've been using professional.

I have the database and some tools (OEM, Jdev).

I do not have iAS running myself.  I know people who do however (some of the
people that work for me for example)

Anyone out there care to comment on what linux they have and what they are
running on it?

Rating: 3
...Informative but what, oh, what to do withOracle ? October 22, 2002
Reviewer: robert from PA
for a split second I thought I got to AskLinus.com ;)
but hey it's your show, Tom and you can do anything you like...

Followup:
VBG...

Rating: 4
You can use 7.3 for Everything, But... October 22, 2002
Reviewer: Jeremy Smith from Salt Lake City, UT
There's no support for 9iAS R2 on any Redhat but Advanced Server.  Advanced
Server is basically 7.2+kernel hacks and clustering utilities.  If you're just
trying to learn, you don't need it for yourself.  I wrote up some directions for
installing 9iDBR2 and 9iASR2 on Redhat 7.3 (pretty much just a collection of
what's out there on otn).  I can't guarantee they're flawless, but they work for
me.  I'll be putting them up on the web in the next week or so.

On learning linux:  the best way to do it is to get rid of Windows...or at least
relegate it to only playing games.  Do all of your home and business work on it.
 It's like learning a language.  Total immersion is the way to go.

Followup:
It is just like with windows NT -- only supported really on NT Server, not NT
Workstation but for playing about -- it works find.

Total immersion -- you will be assimilated, resistance is futile.  I like it.  I
haven't booted windows for weeks....

Rating: 5
Linux rocks October 22, 2002
Reviewer: Jeremy from Chicago
I've been running Oracle, OEM, and JDEV on Linux Redhat 7.x and now 8.0 for a
few months now and haven't looked back to windoze once.  At first I had some
concern since people send me excel spreadsheets to use for updatinfo info in the
db, but OpenOffice handles these just fine!

Also, it is nice not having to ftp files to a unix box so I can
sed,awk,cut,grep,etc. If you really want to stay with windows checkout
http://www.redhat.com/software/cygwin/
:)

Rating: 4
October 22, 2002
Reviewer: Tianhua Wu from Atlanta, GA
I have been used Oracle on Linux for more than three years since Oracle 8.0.5
and Redhat 6.2.  Now I have Oracle 9.2.0 on RedHat 7.3.  It may be a little bit
more challange for the beginners, but it is totally worth it.  I still remebered
it took me several long days to make Oracle 8.0.5 worked properly on RedHat 6.2.
 But once you are through it, you will understand the OS and Oracle much much
better.  Besides, it is much easier to install nowadays.  I use KDE myself too.
It is awesome.

Rating: 4
Oracle Linux Support not 100% yet... October 22, 2002
Reviewer: Jeremy Smith from Salt Lake City, UT
You have to understand that Oracle is pretty quirky about what distributions
they support and with what packages.  I'm sympathetic to the fact that they're
trying to test a _huge_ product on many different platforms.  Supporting every
linux distribution is probably pretty unrealistic.  Yet although I've always
been able to do it eventually, there's almost always bugs that show up that I
think should have been easily solved in the Linux versions of the product.
Therefore, it helps dramatically to understand UNIX pretty well in order to work
through them.

For instance, here's one I hit last night.  I use dbca to create the setup
scripts for a new database, and run the scripts from the command line (faster
than doing it through the java gui).  If you look at the main shell script it
creates, it looks like this:

#!/bin/sh

mkdir /u01/app/oracle/admin/db01/bdump
mkdir /u01/app/oracle/admin/db01/cdump
mkdir /u01/app/oracle/admin/db01/create
[more ommitted]
setenv ORACLE_SID db01
[runs the rest of the scripts]

Well, there's two problems with this script, and they're pretty basic UNIX
stuff.  Dbca will let you change the directories to anything you
want...including directories where the parents don't exist.  So, if you want
this script to work, you're going to want to change the mkdir lines to
mkdir -p [dirname]

Also, "setenv" is a csh/tcsh thing and doesn't work either in plain Bourne "sh"
as the script indicates, or in "bash" which is actually what sh is on most linux
distributions.  What you really want is an export ORACLE_SID=db01 or better yet
an "ORACLE_SID=db01; export ORACLE_SID" to be compatible with real /bin/sh.

[Let me say now this may be in the release notes or metalink, but still...]

My guess is that Oracle tests the full functionality of each product only on a
limited selection of UNIX versions.
Which means, of course, that you're going to end up with some issues like this
on most platforms.  Are they super serious?  Not really, but they're prevalent
enough that you can expect to solve one or two every time unless you're using
the super-preferred platform(s).  (Solaris and HPUX?) Supposedly RedhatAS 2.1 is
destined to become a "preferred" platform, but for now it has similar problems.

Rating: 4
October 31, 2002
Reviewer: kubi from London , United Kingdom

Rating: 5
connecting to oracle November 12, 2002
Reviewer: atul from india
Sir,

I'm using red hat linux 7.2 with oracle 8.1.7
I'm writing a script to connect to oracle from bash.

it's test script ...it goes like this

--------------------------------------------------------
export ORACLE_SID=odb
export ORACLE_HOME=/usr/ora01/app/product/8.1.7
echo "Started"
echo 'connect internal\n select sysdate from dual;\n exit' | svrmgrl

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

It gives me error like
MGR 04546 Invalid connect string

Could you tell me how to do it??

Thanks.
atul

Followup:
I use

svrmgrl <<EOF
connect internal
select sysdate from dual;
exit
EOF

myself.  easier to code, easier to read and I know it works.

Rating: 5
But do you like the GUI? November 12, 2002
Reviewer: Shrek from MD
<VBG>? Miss that old usenet language,
FWIW:
From my viewpoint, the only positive about Linux is that I am not dependent on
OEM as I am on Windows, because I can run bash, OTOH, I want to run common GUIs
like netca, I get all kinds of java awt bugs, I run dbassist and it never shows
up on the screen, then I do a ps, and I see that Oracle has launched many many
jvm's, I get frustrated, and modify tnsnames, or sqlnet files by hand, and I
know OEM doesn't like that, but I don't need OEM on Linux, as much as I do on
Windows.

I know my original install wasn't very clean, but there seems to be some
incompatibility with Oracle 8.1.7 and the native .so versions that come with
7.2, peronally I would like to have the same positive experience as you all do,
but I don't think I am there yet.

BFN
Shrek

Followup:
I haven't had any issues with the gui's personally -- i've used 7.2, 7.3 and 8.0
RH.  I would recommend you use a slight more current release of Oracle -- lots
of stuff that make it more then worthwhile.

Rating: 3
Does anyone have a working autostart/stop script? November 12, 2002
Reviewer: Eric Ma from Berkeley Heights, NJ USA
I copied a working script from Solaris to RedHat 8.0 but it failed to autostart
Oracle 9.2.0.  The script is essentially the same as the one from Oracle's
installation manual, and it is placed under /etc/init.d, and symlinked in
/etc/rc2, 3, and 5.d.  Any suggestions?

Followup:
so, what does the script look like -- I cannot see your terminal from where I'm
sitting today ;)

Rating: 4
Linux November 13, 2002
Reviewer: Piotr Jarmuz from Braunschweig, Germany
I started with Slackware distribution then used Mandrake with KDE on my notebook
and now I use Debian (best for server side) I also run Oracle on it although it
is not officially supported on this platform... well I have never had slightest
problems with it. I don't need GUI tools (never liked them really). I used them
only to install Oracle software. Is there any more UNIX-like way to that?

Shrek: As far as lots of jvm are concerned it is due to the way Linux implements
threads. It does not have threads like e.g. Solaris. It "emulates" them with
processes. Those processes, however, share all the file system info, file
descriptors, memory maps, signal settings, etc. They have private stacks in
kernel and userland and separate process structure and this is why they show up
after ps command as separate processes.

It's interesting to notice the difference between multithreading styles used in
Oracle and in JVM. Oracle has explicite shared memory management (shmat(2))
while JVM implicite (thru clone(2)).

Linux is cool! It's soooooooo much fun to read old <<experts'>> emails giving
severe criticism to Linux some 8-10 years ago. But Linux stood the probe of
time.

Linux + Oracle + Apache + Perl = "the sky is the limit" ;)

Regards.

Rating: 5
Startup shutdown under Linux November 13, 2002
Reviewer: Arun Gupta from Harrisburg, PA USA
Please search on Metalink for the following:
Note:1016388.102
LINUX: DBSHUT FAILS WHEN ISSUING REBOOT, INIT6, OR SHUTDOWN
The Solaris shutdown scripts won't work with Linux since it needs a
/var/lock/subsys/<filename> file to be created. You may want to check that in
the dbstart script supplied with Oracle 9.2, the script checks for init.ora file
for startup. If you are using spfile, then the script will fail to start the
database.

Apart from these two issues, I was able to run Oracle 9.2, OEM, DBCA, DBA Studio
and every other Oracle supplied GUI program under RHL7.3/8.0. No problems once
the configuration is right. Since the fonts were not very good, I use vmware to
run Windows XP as a virtual machine in Linux and use all the GUI tools/Oracle
client from within Windows to access Oracle database running on Linux.

Rating: 3
My autostart/stop script November 13, 2002
Reviewer: Eric Ma from Berkeley Heights, NJ USA
Below is the script that runs fine on Solaris, but not Linux:

#!/bin/sh

ORA_HOME=/export/data/oracle/ora92
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 &
su - $ORA_OWNER -c "$ORA_HOME/bin/lsnrctl start listener" &
;;
'stop')
su - $ORA_OWNER -c $ORA_HOME/bin/dbshut &
su - $ORA_OWNER -c "$ORA_HOME/bin/lsnrctl stop listener" &
;;
esac

Do you see any problems?  My RH Linux starts up in the GUI mode.

Followup:
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:387218751430
that is what I use.  I don't like dbstart/dbshut cause I don't maintain oratabs
or anything like that.

Rating: 5
O/S November 14, 2002
Reviewer: mo
Tom:

1.  What problems do you see with windows O/S? It seems you do not like it much?

2.  Is not linux similar to unix? or does it have user interface like windows?

3.  I thought oracle runs best on sun solaris unix, then HP unix and then IBM
aix? Am I wrong?

Thank you,

Followup:
1) I like open systems. I like choice.

2) linux is so similar to unix that I feel very comfortable with it -- linux has
a nice interface (window manager) these days -- very comfortable with it.  In
fact it has dozens of window managers (looks and feels) -- you can actually pick
and choose from one of zillions

3) so wrong.

Rating: 4
November 14, 2002
Reviewer: A reader
what are you using as the "linux" office
i mean how do you read word docs or power point presentations

Followup:
http://www.codeweavers.com/home/
for when I have to run ppt/word

OpenOffice works great too for most stuff.

      Rating: 4
Here are some
      Linux links...and suggestion for a GUI tool, if you want
      one.  November 15, 2002
Reviewer:  Jeremy
      Smith  from Salt Lake City, UT
Where you can get 99% of the questions you've asked answered.

http://www.tldp.org/
http://www.linux.org/
http://www.redhat.com/oracle/
Lately, I've been playing with a tool called TOra, which can be found at
http://www.globecom.se/tora
.
I've been pretty impressed with its maturity, although it doesn't do everything
yet. Coupled with the Java-based tools Oracle provides, and of course the
standard command-line tools, there's no reason as an Oracle developer you can't
be Windows-free.

      Rating: 3
Who said that
      ?  April 01, 2003
Reviewer:  Robert
       from PA
Here:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2451893530442
You said:
>> Now, I'm not a linux admin, never used linux -- but I use solaris all of the
time....

Now which Tom was THAT ?

          Followup:

that was Tom in 12-Jan-2002 ;)

I made the switch in August 2002 I believe (and my guess was right ;)

      Rating: 4
FUP Who said
      that  April 02, 2003
Reviewer:
      A reader
Tom said
"I made the switch in August 2002"

Now thats a surprise , i would have thought you would have
been using it for years.

I made the switch when I had Win95 and Linux installed
on my 486DX50, boosted it to 24Mb Ram...
Ran some heavy performance tests
Win95 made no difference still a pig,
Linux made FAR better use of resources and handled much
greater loads then Win95 could

      Rating: 4
RH 8.0 -
      Oracle9iR2  April 02, 2003
Reviewer:  Van
      Driessche Erik  from Belgium
Hello,

I've installed OEM from the 9iR2 CD and on a Linux RH 7.3 everyting works fine.
On a Linux RH 8, some features didn't work. Like The OEM prefered credentials
(username/password for unix servers/ databases/ listeners who blanks out ...

Never found the reason why, but have seen this on twice different Intel based
servers. Either discover nodes causes a lot of troubles on RH8, not on a RH 7.3

      Rating: 4
To Jeremy
      Smith:  April 07, 2003
Reviewer:  Robert
       from PA
>>.  I wrote up some directions for
installing 9iDBR2 and 9iASR2 on Redhat 7.3 (pretty much just a collection of
what's out there on otn).  I can't guarantee they're flawless, but they work for

me.  I'll be putting them up on the web in the next week or so.
---------------

Jeremy, what's your URL ?

thanks
rchin@panix.com

sorry, Tom , don't know how else to find this guy.

          Followup:

i sent him an email...

      Rating: 5
What about
      Oracle Designer  April 07, 2003
Reviewer:
      T.Williamson  from USA
I really like Linux for a server, but I gave up on using it as my workstation.
One of the reasons was that Oracle Designer does not come in a Linux version,
and one of my clients likes designer. I also had a great deal of trouble
figuring out how to get my laptop X-windows resolution to look good for
over-head projection demos, fonts never did look good. Also got tired of trying
to find a compatible "Office" clone with all the correct fonts etc.... Seemed
like I was spending more time trying to make Linux work than getting my job
done...

But I found that it compares favorably to other server OS's like Solaris and
AIX.

So for me, Linux for my server and Windows for my workstation.

          Followup:

RH 8, with xft support -- I've got all of the "windows" fonts, using crossover
office -- I have ms office for when I need it.  KDE's Konsole with Lucida
Console 14point bold font works awesome for infocus on a big screen.

So for me -- less and less (and soon) no ms stuff at all

      Rating: 4
7.3
      instructions  April 07, 2003
Reviewer:  Jer
      Smith  from Salt Lake City, UT
Here you are.  Just threw them up...I wanted to put other useful stuff up to but
I haven't gotten to it.
If you have improvements or the instructions don't work, please email me (the
mailto link is in the doc)

http://www.xmission.com/~alceste/oracle/oraclerh73install.html
(for Oracle DB)

http://www.xmission.com/~alceste/oracle/oracle9iASrh73install.html
(for 9iAS Infrastructure and associated 9i DB)

      Rating: 5
Will Oracle
      port Designer to Linux  April 07, 2003
Reviewer:
      T.Williamson  from USA
Tom,
Do you know if Oracle plans to port Designer to Linux?
What about Discoverer?

          Followup:

designer, no idea -- but discoverer, it is 3tier.  It is everywhere already.

      Rating: 3
Red Hat 9 Pro
      or Base  April 07, 2003
Reviewer:  Robert
       from PA
Im going to get RH 9 (shipping today !)...
anyone knows if I need to get the Professional edition
in order to have a better chance installing 9i
or I can get away with the cheap base edition ?

Thanks !

      Rating: 4
Redhat
      9  April 07, 2003
Reviewer:  Jeremy Smith
       from Salt Lake City, UT
As far as I understand, there's no technical difference between the version
branded as "professional" ($150), the regular ($40)  and the free-for-download
versions. Those differences are all in the support and packaging (what manuals
you get), and the freebie CD.

BUT there are differences between those two versions and those branded
"Enterprise Linux." If you're serious about running on Linux in production, I'd
recommend those because you're going to want them for support from Redhat and
Oracle.

      Rating: 5
Professional
      Versus Regular  April 07, 2003
Reviewer:
      Jerry  from Washington, DC USA
Don't think there is a difference either.  The kernel and shared library
versions are what matters.

Here's one thing I ran into running Linux and Oracle on low-end Intel, and it
made a measurable difference on my hard disks.

http://linux.oreillynet.com/pub/a/linux/2000/06/29/hdparm.html

      Rating: 3
9i on
      Glibc2.???  April 08, 2003
Reviewer:  Robert
       from PA
I learned Oracle 8i is compiled against Glibc2.1.x..
what's 9i compiled against ?

Thanks

          Followup:

I have yet to try Oracle9i on RH 9 -- I haven't heard anything good or bad as
yet (although I do know that crossover office which I depend on right now isn't
on RH9 due to glibc incompatibilities so I'll be waiting a bit).

      Rating: 3
Run LINUX from
      CD-ROM  April 09, 2003
Reviewer:  Robert
       from PA
For those guys itching to get a feel of LINUX,
check out "knoppix" at...

http://www.knoppix.org/
Runs right from the CD-ROM...Wheee

      Rating: 4
Red Hat
      Enterprise Linux AS 2.1 (DE)  May 15, 2003

Reviewer:  David  from Bay Pines, FL USA
I have been using the developer edition (Destin) of RHEL AS 2.1 With a $60 USD
subscription you can down load the ISOs for DE from their web site. After that
follow the directions for Oracle 9i Release 2 on Linux in the latest Release
notes.

I found that it is useful to set the ulimit on stacks to 2048 when installing.
(ksh) (ulimit -s 2048)
It prevents a hangup in dbca and netca.

      Rating: 4
Upgrade on box
      with Oracle installed  June 23, 2003
Reviewer:
      Robert  from PA
>>I am using Redhat linux version 8.0 (after upgrading from 7.2 to 7.3...).

Tom,
do you have Oracle installed on that box ? (dumb me..of course...)
Is Oracle in separate partition from Linux ?

Thanks

          Followup:

yes, oracle is installed on this machine, in /usr/oracle.

      Rating: 4
9i on
      Glibc2.???   July 24, 2003
Reviewer:  Raj
       from India
Hi Tom,

I installing the Oracle 9i release 1 Enterprise Edition on Redhat Linux 9
personal edition.

When I start the runInstaller there is a glibc2.2 error. But I do not find RH
has provided the same on the installation disks.

Do you have any idea where could i download the same.

Regards

          Followup:

totally not supported

totally not working

      Rating: 4
Oracle 9i in
      Red Hat 8  August 12, 2003
Reviewer:  apl
       from INDIA
I can't start the oracle 9i installation in RedHat 8.0.i am getting the error
'Java RunTime Environment was not found at E/bin/java.Hence, the oracle
universal installer cannot be run'.
i downloaded the JRE version 1.3.1 and set its path. but still i am getting the
error?

          Followup:

I had no problems in that environment -- you are running ./runInstaller from the
local directory right.

      Rating: 4
Still the same
      error  August 14, 2003
Reviewer:  apl
       from India
I've copied the contents of all the 3 disks in different folders in the local
directory. I've installed jre 1.3.1_09
and set the jre path to the bin directory (/usr/local/jre1.3.1_09/bin). Still
the same error.

          Followup:

sorry, contact support.  we use our own jre during the install...

I've never had an issue, not sure what to tell you.

      Rating: 4
to apl of
      India  August 14, 2003
Reviewer:  Robert
       from PA
Why is it necessary for you to copy from disks ?
My installation from CD-ROM was so smooth I was actually surprised 3+ old PIII
($150 ;), RH8.0 ($0 ;), 9iR2 ($0 ;), JDev($0 ;) (Thanks Tom for reminding us
that one can do PL/SQL stuff with this cross-platform tool)

Did you do some homework ?

Here are 2 links to docs that's pretty comprehensive:

http://www.puschitz.com/InstallingOracle9i.shtml
http://www.xmission.com/~alceste/oracle/oraclerh73install.html

      Rating: 3
oracle 8.1.6 on
      Redhat 8.0  August 22, 2003
Reviewer:  APL
       from India
Is it possible to install oracle 8.1.6 on RedHat 8.0.if so can you give me the
documents for installation.

          Followup:

don't know.  816 is so out of support as it is.

if you want to use really old software for one piece, you must be willing to use
really old software and in many cases, old hardware for the rest.

      Rating: 3
problem in
      starting database creation  August 28, 2003

Reviewer:  APL  from INDIA
I installed oracle 8.1.6 in red hat linux 8.0. after installation at the time of
database creation i am getting the following error.
oracleora8:relocation error :/ora8......./lib/libjox.so:
undefined symbol:--fixunssfd.
What will be the problem

          Followup:

if you want to use really really old unsupported database software, you must
expect to use really really old unsupported operating system software as well.

since RH 8.0 did not even exist when 816 was coded -- don't know what to tell
you.  You might be able to "hack it" to work but it won't be a very stable
configuration.

need to use software that was written with eachother in mind...

      Rating: 4
setting
      preferred credentials  August 29, 2003
Reviewer:
      Jitender  from UK
I have oracle 9.2 on RH8 on my laptop.I have problem setting the preferred
credentials on OEM.It dosen't get saved.Is it a bug? How can we trace what is
happening ?

      Rating: 5
To Learn
      Linux  August 30, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
I am also trying to lear Linux these days and want to migrate from Win to Linux.
I am taking help forum

http://www.linuxquestions.org/questions/

      Rating: 5
For help with
      Oracle on RedHat..  September 03, 2003
Reviewer:
      Harish  from London, England
I have succesfully managed to install Oracle 9i onto my GNU/Debian(Woody) box.

For those of you who are having some problems with your install, check:
http://www.puschitz.com/InstallingOracle9i.shtml

The help provided on this is specific to Redhat, but is also applicable to other
distro's also.  I used it to install on Debian after all ;-).

Hope this helps if you are having problems...
Harish

      Rating: 5
Not
      certified.  September 08, 2003
Reviewer:
      Sikandar Hayat  from Pindi Bhattian - Pakistan
"I am using Redhat linux version 8.0 (after upgrading from 7.2 to 7.3...)."

TOM as per my info these version are not certified for Oracle installation. Only
RH 7.1 and AS 2.1 are certified but as you are using these version of RH then
what kind of problems we may face if we don't install on 7.1 or 2.1AS?

          Followup:

don't know, never did it.  using certified supported configurations lets you use
support.  using a non-supported configuration will make it harder to use
support.

      Rating: 5
any production
      system on REDHAT LINUX ORACLE-9i2 ??  September 15, 2003

Reviewer:  linux  from Richmond,verrginia,USA

Are anybody using oracle 9i-2  on redhat enterprise linux- AS 2.1 (kernel
2.4.9-e.3, glibc 2.2.4-26 )in your production system? If yes ,are you using
intel X86 compatible or intel Itanium 2 systems? Which machine are you using ?
If you are using any other version of Linux  oracle in your production system
please let us know here.Thanks!

      Rating: 5
Great
      history  September 17, 2003
Reviewer:  Wayne
       from San Antonio, TX
I really enjoyed the historical aspect of this thread.

Specifially for Raj's question, installing oracle 8i on Redhat requires that you
change the header files - they are at the download location.

Being newer to linux, I would appreciate Tom letting me contact some of the
people he knows that are running 9ias on linux.

Thanks!

      Rating: 5
We are using
      Oracle9iR2 production system on Linux  September 25, 2003

Reviewer:  A reader
To "linux  from Richmond,verrginia,USA", we are using Oracle 9.2.0.2 on RedHat
Linux 7.1, with customized kernel 2.4.19 (I compiled the kernel myself),
glibc-2.2.2-10, and (last but not least) a special version of binutils
(binutils-2.10.91.0.4-1 needed at installation). The CPU is 1 GHz dual Intel
Pentium III (quite lame by today's standard). We have used it for two years by
now (initial installation was 9iR1 [9.0.1]). There were some problems with 9iR1.
With 9iR2 it has been almost flawless. Now it's a 500 GB data warehouse.

      Rating: 5
9iAS on
      Linux  October 08, 2003
Reviewer:  Wayne
       from San Antonio
I am interested in someone sharing some knowledge about using RH or SUSE for a
test install of 9iAS rel 2.

Any takers?

      Rating: 5
some question
      on linux configuration  November 30, 2003

Reviewer:  A reader
HiTom
Hope you had a great thanxgiving! I have some
quick questions.

Do you have 10g installed on your laptop?
I am thinking of buying a dell laptop and installing
10G (or 10gbeta) on it.
However, I am not sure how much power
(specifially cpu and RAM) should be ok. Most likely
my laptop will come with windows (bcos I dont think
we get linux bundled laptops directly) so i would
be partitioning it to install linux.
Can you kindly share with us what your laptop config
is, in terms of the following?
cpu,
memory,
hard disk
linux version

Also, are there any concerns if i also install 9i on the
same box (apart from needing more hard disk space). Mostly
only one of them (1oG or 9i) will be up at a time.

Many thanx!:)

          Followup:

Yes I do have 10g

I'm running RHAS3.0 right now -- but windows will work as well (or RHAS2.1)

I have a 1.6ghz Mobile Pentium with 1gig of RAM, 60gig hard disk and RedHat
Advanced Server 3.0

I have 9iR1 and 9iR2 on the same, all living happily (amazing how easy things
can be without that copyprotection stuff -- ooppps, meant the registry of
course.  I started hating windows when I couldn't just copy most programs from
one place to another since they were so deeply intertwined with the registry.  I
loved ini files, seem to be making a comeback in the form of XML configuration
files :)

      Rating: 5
thank you
      Tom!  November 30, 2003
Reviewer:
      A reader
One more quick question - Do you know if I can directly
get RHAS3.0 in my laptop instead of windows (I dont think
I will need Windows for my purposes)? Or do i have to
get windows, partition - bla bla..

Apologize for being slightly off-topic and have a great
day!! Please keep up the great work - you are the best!

          Followup:

no idea, the first thing I do with a new laptop these days is

a) reformat the disk
b) install linux

it is up to the people selling to you...

      Rating: 4
start oracle
      automatically  December 01, 2003
Reviewer:
      Nikunj N. Thaker  from India
Hi tom,

I have oracle ee 9.2 and redhat linux professional 9.
i had try to start oracle automatically.

i had created a orastart file in /opt/ora9/product/9.2/ as below

#!/bin/bash

su - oracle << EOF
lsnrctl start
sqlplus "/ as sysdba" << EOF
startup
exit
EOF

i had changed the owner of the file to root.
i had created soft link of the file in directory init.d,rc3.d,rc0.d but still
oracle can't start automaticaly.

in command line if i type ./orastart it works fine.

At present i am evaluting oracle on linux i don't have much expertize on linux
so pls. guide me in detail.

Regards,
Nikunj

          Followup:

you have EOF in there way too much.

in /etc/init.d create a script "mystartup"

in there just put

su - oracle -c orastart

that is the script you want to link to in rc3.d, then in $ORACLE_HOME/bin,
create orastart

lsnrctl start
sqlplus "/ as sysdba" << EOF
startup
exit
EOF

thats all.

      Rating: 4
start oracle
      automatically  December 02, 2003
Reviewer:
      Nikunj N. Thaker  from India
hi tom,
thanks i had follow ur steps but can't get success.
ur first steps in /etc/init.d create a script "mystartup"
i had created

[root@nikunj 9.2]# cd /etc/init.d/
[root@nikunj init.d]# ls -la myorastart
-rwxr-xr-x    1 root     root           36 Dec  2 11:06 myorastart
[root@nikunj init.d]# cat myorastart
#!/bin/bash
su - oracle -c orastart

ur second steps
[root@nikunj init.d]# cd /etc/rc3.d/
[root@nikunj rc3.d]# ls -la myorastart
lrwxrwxrwx    1 root     root           22 Dec  2 11:07 myorastart ->
/etc/init.d/myorastart

ur third steps
[root@nikunj rc3.d]# cd $ORACLE_HOME/bin
[root@nikunj bin]# ls -la orastart
-rwxr-xr-x    1 root     root           73 Dec  2 11:27 orastart
[root@nikunj bin]# cat orastart
#!/bin/bash

lsnrctl start
sqlplus "/ as sysdba" << EOF
startup
exit
EOF

but still my oracle can't start automatically i have to type mannually
myorastart on terminal.

[root@nikunj root]# cd /etc/init.d/
[root@nikunj init.d]# cat myorastart
#!/bin/bash
su - oracle -c orastart
[root@nikunj init.d]# ./myorastart

LSNRCTL for Linux: Version 9.2.0.1.0 - Production on 02-DEC-2003 18:04:49

Copyright (c) 1991, 2002, Oracle Corporation.  All rights reserved.

Starting /opt/ora9/product/9.2/bin/tnslsnr: please wait...

TNSLSNR for Linux: Version 9.2.0.1.0 - Production
System parameter file is /opt/ora9/product/9.2/network/admin/listener.ora
Log messages written to /opt/ora9/product/9.2/network/log/listener.log
Listening on: (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC)))
Listening on:
(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=nikunj.bppl.com)(PORT=1521)))

Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=EXTPROC)))
STATUS of the LISTENER
------------------------
Alias                     LISTENER
Version                   TNSLSNR for Linux: Version 9.2.0.1.0 - Production
Start Date                02-DEC-2003 18:04:49
Uptime                    0 days 0 hr. 0 min. 0 sec
Trace Level               off
Security                  OFF
SNMP                      OFF
Listener Parameter File   /opt/ora9/product/9.2/network/admin/listener.ora
Listener Log File         /opt/ora9/product/9.2/network/log/listener.log
Listening Endpoints Summary...
  (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC)))
  (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=nikunj.bppl.com)(PORT=1521)))
Services Summary...
Service "OEMREP.bppl.com" has 1 instance(s).
  Instance "OEMREP", status UNKNOWN, has 1 handler(s) for this service...
Service "PLSExtProc" has 1 instance(s).
  Instance "PLSExtProc", status UNKNOWN, has 1 handler(s) for this service...
Service "ora9i" has 1 instance(s).
  Instance "ora9i", status UNKNOWN, has 1 handler(s) for this service...
The command completed successfully

SQL*Plus: Release 9.2.0.1.0 - Production on Tue Dec 2 18:04:49 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to an idle instance.

SQL> ORACLE instance started.

Total System Global Area  235999352 bytes
Fixed Size                   450680 bytes
Variable Size             201326592 bytes
Database Buffers           33554432 bytes
Redo Buffers                 667648 bytes
Database mounted.
Database opened.
SQL> 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
[root@nikunj init.d]#

Regards,
Nikunj

          Followup:

you need to link to it properly.

look at the names of the other scripts in there

S10network

for exmaple -- S for "start"   10 for "after 9, before 11"

link to it as S99local  for example.  (and get a linux admin book! :)

      Rating: 4
Informative,
      diverse thread  December 02, 2003
Reviewer:
      Fraser Talbot  from Houston, TX USA
This thread gives a full array of perspectives of using Oracle on Linux.

      Rating: 5
Oracle9iAS in
      RH8.0  December 02, 2003
Reviewer:  Karthik
       from Charlotte,USA
Tom
I believe Oracle9iAS is supported only in RH7.3 and RHAS version.

Could you please suggest Application Server to use with Oracle9i in RH8.0?

Thanks

          Followup:

well, since Oracle9i itself is not "supported" on RH8.0.....

I am now using RHAS3.0 and 2.1 on all machines.

      Rating: 5
10G
      download?  December 05, 2003
Reviewer:
      A reader
Hi Tom
You mentioned you have installed 10G on your
laptop. Did you get it from an internal site or
is 10G available for download to general
developer community?
Can you provide us with the url for downloading
10g?

thanx a bunch!

          Followup:

not yet, think "christmas present"

it is beta still.

      Rating: 4
How to
      start/shutdown oracle automatically   December 20, 2003

Reviewer:  Nikunj Thaker  from India
How to start/shutdown oracle automatically

Place the below script in /etc/rc.d/init.d with name oracle
#!/bin/sh
#
#   Startup/shutdown script for the Common UNIX Printing System (CUPS).
#
#   Linux chkconfig stuff:
#
#   chkconfig: 2345 90 10
#   description: Startup/shutdown script for the Oracle 9i Database
#
#

# Source function library.
if [ -f /etc/init.d/functions ] ; then
    . /etc/init.d/functions
elif [ -f /etc/rc.d/init.d/functions ] ; then
    . /etc/rc.d/init.d/functions
else
    exit 0
fi

DAEMON=oracle

prog=oracle

start () {
    echo -n $"Starting $prog: "
    su - oracle -c orastart
    RETVAL=$?
    # start daemon
    echo "Oracle Started."
    [ $RETVAL = 0 ] && touch /var/lock/subsys/oracle
    return $RETVAL
}

stop () {
    # stop daemon
    echo -n "Stopping $prog: "
    su - oracle -c orastop
    RETVAL=$?
    echo "Oracle Stooped."
    [ $RETVAL = 0 ] && rm -f /var/lock/subsys/oracle
}

restart() {
    stop
    start
}

case $1 in
    start)
        start
    ;;
    stop)
        stop
    ;;
    restart)
        restart
    ;;
    *)

    echo "Usage: $DAEMON {start|stop|restart|condrestart|reload|status}"
    exit 1
esac

exit $RETVAL
chmod +x /etc/rc.d/init.d/oracle
chkconfig --add oracle
this will add files are start with S and K in respective directory as per
chkconfig: 2345 90 10 here it will put files start with S90oracle in rc2.d,
rc3.d, rc4.d and rc5.d and K10oracle file in rc0.d rc1.d and rc6.d.
Create file orastart and orastop as below in oracle_home/bin directory for me
oracle home is /opt/ora9/product/9.2
my orastart script as below
#!/bin/bash

lsnrctl start
sqlplus "/ as sysdba" << EOF
startup
exit
EOF
my orastop script as below
#!/bin/bash

lsnrctl stop
sqlplus "/ as sysdba" << EOF
shutdown immediate
exit
EOF

      Rating: 4
beginer
      question  December 23, 2003
Reviewer:  Fernando
      Sanchez  from Madrid, Spain
I've tried several times to install the 8.1.7 release on Mandrake 9.1, but when
executing runInstaller nothing happens. I think I have a problem about the
location of the 'jre' and the file oraparam.ini where it seems to look for it.

Any advice? Would someone recommend me something to read?

Thanks in advance.

      Rating: 4
Linux is
      cool  January 05, 2004
Reviewer:  H.S.Anand
       from New Delhi, India
Happy New Year to you tom!

As a new year resolution, I have removed Windows (or "Windoze" as you love to
call it) and have loaded R.H. Linux 8.0 (Psyche) -[I wonder why they call it
that].

It has been a really easy install. Specially for a Person, who knows ZERO linux.
I have been playing around for the past week and Will be installing
Oracle 9i rel.2 Tonight.

It is great to work on a system that offers so much For FREEEEE!!

thank you for all the knowledge that you have shared with us through this forum.

Cheers!
Anand

      Rating:
      5
Christmas?  January 06, 2004
Reviewer:
      A reader
>Followup:
>not yet, think "christmas present"

Christmas, 2004?

Seriously, any news on release date of 10G?

          Followup:

"soon"

think before valentines day.

      Rating: 5
Here it
      is...  February 09, 2004
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
Monday, February 9, 2004
Download Oracle Database 10g
Oracle Database 10g software and documentation are now available:  Download the
software and documentation
 Explore Oracle By Example hands-on tutorials
 Get technical information at the Oracle Database 10g Product Center

http://otn.oracle.com/index.html

      Rating: 5
10g on Fedora /
      Redhat 9 ?  February 11, 2004
Reviewer:
      Chris  from Boston, MA USA
Will Oracle 10g run on Redhat versions later than 8.0 or 8.1, i.e. on the
current Freeware Redhat linux, "Fedora"?

I know that it's certified only on the expensive Redhat OSs, or 8.x versions.
But some guys seem to have Oracle 9i running on newer versions of Redhat, by
tweaking a few things.

          Followup:

10g is supported on RH AS 2.1 and 3.0 only.

"expensive" is sort of a relative term is it not?  I mean - you either want a
supported, supportable, consistent, implementable platform or -- not.

      Rating: 3
Oracle on
      Fedora/Redhat  February 11, 2004
Reviewer:
      A reader
Tom I disagree with you 100% Infact you need to check the facts on Distributions
that Oracle 10g is supporting. UnitedLinux (one of 3 distros oracle supports)
for all practical purposes is no more supported than Redhat9 or Fedora (
http://news.com.com/2100-7344-5146194.html
Oracle is definitely going to alienate big companies like Novell and small
companies like mine by supporting either dead Linux (United Linux)-- which btw
was one of SCO's Initiative-- or $4000 version of free software(Redhat 3).

Given the resources and apparent Linux dedication Oracle implies, it either
needs to become distribution independent and become Kernel dependent especially
since oracle install is not FHS (Filesystem Hierarchy Standard) dependent. Or
create its own free distribution--which ain't rocket science.

What baffles me is that 10g runs just fine on Fedora and about 2 other
"un"supported distributions i tested..So why all this fuss??

Them marketing folks sure know something we techies just don't get...

Now Tom I know you like to give Windows ME example but that case was different,
unlike that desktop version of windows almost all Linux distributions offer
Server Configurations and are widely used by big and small companies on
workstations, in development, for testing and production systems.

Comparing Windows versions with Linux Distributions is just as naive as
comparing Oracle to Ximian.

Im refering to your comments here
http://asktom.oracle.com/pls/ask/f?p=4950:8:10472030655596263859::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:12257624552691,

          Followup:

thats cool.  you are allowed to disagree.

it however won't change the reality of things.

we support two linux distributions

red hat, popular in the US.
united, popular in Europe (and far far far from "dead")

please don't clump me in with "we techies" -- cause I know what it takes to
support "real software" running "real systems".

      Rating: 5
Start
      with  February 25, 2004
Reviewer:  Marcio

Tom, I'm going to install linux on my home, just to prove migration facts,
performance stuff, etc... I didn't want spend money with, so wich would be the
costless linux distribution certified by Oracle to training 8i (8.1.7), 9i
(9.2.0) and 10g ?
Thanks,

          Followup:

we officially support RH ES and RH AS:

http://www.redhat.com/software/rhel/
so, to have a supportable platform -- you'll need to buy an OS.  It can be
installed on other "free" ports, but there will be gotchas and workarounds.

* Oracle will support Red Hat Enterprise Linux AS/ES on any platform or
driver(s) that Red Hat supports. It is a requirement that the OS (binary) has
not been modified.

* Red Hat Enterprise Linux : Red Hat Enterprise Linux (version 2.1 and version
3) family includes Red Hat Enterprise Linux AS, Red Hat Enterprise Linux ES and
Red Hat Enterprise Linux WS. Oracle products are supported on Red Hat Enterprise
Linux AS, Red Hat Enterprise Linux ES ONLY.

* UNITED LINUX 1.0: Oracle will continue to work closely with UnitedLinux and
provide customer support for UnitedLinux 1.0, throughout its life cycle. The
products supported by Oracle, as part of UnitedLinux, include Conectiva powered
by UnitedLinux 1.0, Turbolinux powered by UnitedLinux 1.0 and SuSE SLES8.

February 26, 2004 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.

      Rating: 3
Group by
      cluase  February 24, 2004
Reviewer:  Ganesh
       from India
exllent discussion .. very useful .

Is there any limit in oracle 9i , the maximum columns in group by clause.
how many Columns can we give in GROUP BY, ORDER BY

Regds
Ganesh

          Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96536/ch44.htm

      Rating: 4
Group by cluase
        February 26, 2004
Reviewer:  Ganesh
       from India
Hi Tom,
I got the relavent information from the link. I am preparing one document on
limitaions. Most of the limitation ,I got from the above link.
still i am not able fine the below information

1. Columns referenced by a view
2. Columns per SELECT statement
3. Parameters per stored procedure
4. Nested stored procedure levels
5. Tables per SELECT statement
6. XML support
Tom ! can you please provide me the above. Thanks in advance.

          Followup:

1) $ oerr ora 1792
01792, 00000, "maximum number of columns in a table or view is 1000"

ops$tkyte@ORA920PC> declare
  2          l_stmt long := 'create or replace view v as select 1 c1';
  3  begin
  4          for i in 2 .. 2000
  5          loop
  6                  l_stmt := l_stmt || ',1 c'||i;
  7          end loop;
  8          l_stmt := l_stmt || ' from dual';
  9          execute immediate l_stmt;
 10  end;
 11  /
declare
*
ERROR at line 1:
ORA-01792: maximum number of columns in a table or view is 1000
ORA-06512: at line 9

2) unlimited in theory, limited by practical limits
3) same as #2
4) same as #2
5) same as #2
6) unlimited?  not sure what you mean

ops$tkyte@ORA920PC> variable x refcursor
ops$tkyte@ORA920PC> declare
  2          l_stmt long := 'select 1 c1';
  3  begin
  4          for i in 2 .. 2000
  5          loop
  6                  l_stmt := l_stmt || ',1 c'||i;
  7          end loop;
  8          l_stmt := l_stmt || ' from dual';
  9          open :x for l_stmt;
 10  end;
 11  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> print x

        C1         C2         C3         C4         C5         C6         C7
    C8         C9        C10        C11       C12
---------- ---------- ---------- ---------- ---------- ---------- ----------
---------- ---------- ---------- ---------- ----------
...............

February 26, 2004 Bhavesh -- Thanks for the question regarding "Qs
      on V$ views and a Q on OAS", version 8i

      You Asked
Hello Tom,
This time I have several Qs:
1. I read somewhere that there are total 132 V$ views
   in Oracle 7. Is this the same number in Oracle8i?
   Or is it more?
2. When I re-start my instance, do ALL the V$ views
   truncated? If NOT all, can u categorize which
   ones are truncated and which are not? Why?
3. I have re-started my instance with "timed_statistics = true".
   And then, after several hours of DB usage, I
   ran "UTLBSTAT.SQL".
   Now, I forgot to run "UTLESTAT.SQL", and I shut down the
   instance. After I re-started the instance
   ("timed_statistics = true"
   is still there), just for curiosity, I ran "UTLESTAT.SQL", and
   viewed "report.txt". In this report, in
   "Select Library cache statistics" section, GETs/PINs/RELOADs
   are negative numbers. And, many of the statistics
   are negative in other sections also.
   Are these all garbage numbers - 'coz I did not
   run "UTLESTAT.SQL" before shutdown ? OR, are they
   represents any other characteristic ?
4. We are using OAS 4.0 on NT (the database is on
   UNIX-Sun/Solaris). I put a file1.xls file on the NT
   server. I want to give the user a link - clicking on
   which he should get "open/save" dialogue and then he can
   open/save the file1.xls file. How I can achieve this
   functionality on OAS ?

Thanks in advance,
Bhavesh Patel

      and we said...
1) there are about 180 or so in 8i

2) all are "truncated" -- not really truncated but reset.  v$ views are "fake"
tables and are based on in memory structures.  counters, timers and so on are
all reset.

3) utlbstat/estat cannot be run across restarts -- they must be run in the same
instance without an intervening reboot.  bstat just snapshots many of the V$
tables.  Estat takes that snapshot and substracts them from the current values.
By restarting and reseting the counters -- many things would be negative.

4) depends on the browser.  with MSIE -- you'll have to physically rename
file1.xls.  MSIE doesn't obey mime types and when it sees .xls will run excel.
You SHOULD be able to set the mime type for .xls to application/octet-stream and
the browser SHOULD put up the save as dialog -- MSIE will not.  With netscape --
you just do what I described, set the mime type for .xls files to
application/octet-stream and you'd be done.  with MSIE, you'll need to rename
file1.xls to file1.dat or something and convince your users to rename it (or
train them to right click on it to save, left click to view)
Review &
      Followup

      Rating:
      3
OK  February 26, 2004
Reviewer:  Ram
       from Bangalore,India
Dear Tom,
In the view v$RSRC_PLAN_CPU_MTH,what is the expansion for
RSRC AND MTH?Does MTH -> Mean Time Histograms?
Please do reply.

          Followup:

RSRC = Resource
MTH = method

This view shows all available CPU resource allocation methods for resource
plans.

February 26, 2004 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.

      Rating:
      4
  February 26, 2004
Reviewer:  Leo
      Yung  from Hong Kong
Is there any method to sort the column according to the values in IN
operator?e.g.

select colA from tabA where colA in (2,3,4);
result: colA
        ----
          2
          3
          4

But if the sql is select colA from tabA where colA in (4,3,2);
result: colA
        ----
          4
          3
          2

          Followup:

no, you can:

select cola from taba where cola in (4,3,2) order by
decode( cola, 4, 1, 3, 2, 2, 3 )
              ^     ^     ^

if you like.

February 26, 2004 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.

      Rating: 4
Can I use
      locally Managed Tabelspaces for all apps?  February 26, 2004

Reviewer:  Hannibal  from Austria/Europe
Hi Tom,

I found a oracle tool (sql trace analyzer) at metalink 224270.1. I wanted to
test it but when tried
to run

create tablespace TRCA datafile '<some_path>trca01.dbf'
size 100M autoextend on next 100M permanent
default storage ( initial 1M next 1M)
extent management dictionary;

I got

ERROR at line 1:
ORA-12913: Cannot create dictionary managed tablespace

No surprise since my system tablespace is locally managed. The documentation to
this tool clearly
says : 'you need to install this TRCA tool on a dictionary managed tablespace'
...

So i started to read about locally and dictonary managed Tabelspaces to find out
why the documentation
tells my to create a dictonary managed tablespace - also asked my DBA. But I
cant find any reason! I
searched this forum and you keep people telling, 'use locally managed
tablespaces - faster, better' and
i found no reason why some application running in a dictonary managed tablespace
should not run in a
locally managed one.

Am I missing something? Is there a reason why the trace analyzer (or any other
appplication) MUST have
the dictonary managed tabelspace? Are they intercangeable in any situation or
are there things where
you MUST keep them in the dictonary managed tablespace or things would fail?

Our DBA keeps creating the system tablespace as locally managed. He told me this
is the default for
installing the database when I asked him why he does not use dictonary managed
ones for system.

Sorry, when this questions sounds a little bit strange/silly. Matter of factly,
it was 100% sure for me I could
use a locally managed tablespace for all my applications (I do so) until i found
the trace-analyzer and did read
the documentation and I started to ask myself 'why does this docu from oracle
say : use dictonary managed
 tablespace? Is there someting I missed and I would mess up my database by
running it on a locally managed
tablespace?'

1) Are locally and dictonary managed tablespaces 100% intercangeable?

2) What are the arguments for running system as Locally managed or dictonary
managed?

thanks

Hannibal

          Followup:

the reason here is because their script, that they have not updated, includes a
create tablespace command that is dictionary managed.  no other reason.

The reason you have to use dictionary managed is solely because "that is what
the script is expecting".

you can certainly change the script, drop the dictionary managed part and it
should never know.

February 26, 2004 Venkat -- Thanks for the question regarding
      "Sequences", version 8.0.6

      You Asked
Hi Tom,
   What you suggest is the best way to generate system generated numbers. As
Oracle Sequences are not gap free, can you please suggets an alternative.

Thanks..

Prasad

      and we said...
I would recommend ignoring the fact that they are not gap free.

Period. (i've said that lots here, many many discussions on it.  No one has ever
presented a good reason for gap free numbers beyond "it's always been that way")

Review &
      Followup

      Rating:
      3
Sequences  June 22, 2002
Reviewer:
      Venkatesa Prasad  from India
Tom,
   Suppose I cannot use sequences for my PK generator, then what is the best way
to generate.

   Well, I cannot use sequences for basically 3 reasons.

  1. My numbers have to be generated financial year-wise.
      - Then I have to create sequences 1 for each financial year. isn't it?

  2. I have a master table which tells what are the various voucher types and
when user selects a given voucher type in the transcation, system has to
generate a number for that type and that too it should start afresh for every
month. I cannot create sequences before itself, as I dont know how many voucher
types will get added each month.

   3. Third is related to gaps. Suppose in the front end (we user Developer 2000
- Forms 4.5), the user saves the data but leaves the session uncommitted (I mean
'POST' is issued). Then sequence is ost without the data getting committed. I
mean the saved data is rolled back. Then how to avoid gaps.

Can you clarify, please.

Venkatesa Prasad

          Followup:

1) why -- why not just know that "1-100323" are fy1.  100324-2431223 are fy2 and
so on.  Since you "reset" the numbers for each year, this obviously isn't a
primary key by itself -- the pk must be "FY + ID" (two columns).  This id is
just to keep it unique within the year.

2) why again?  why?

3) well, if you use a gap free approach -- you know what'll happen in this case?
 NO ONE ELSE IN YOUR ENTIRE SYSTEM WILL BE ABLE TO WORK, that is what.  Why?
cause this session with uncommitted data will have the id generating table
locked.

Sigh, I hesitate to show you this, but here goes.  Gap free, non-concurrent,
highly NON-scalable:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table ids ( name varchar2(30), id
number );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace function get_nextval(
p_name in varchar2 ) return number
  2  as
  3     l_id number;
  4  begin
  5     update ids set id = id+1 where name = upper(p_name)
  6     returning id into l_id;
  7
  8     if ( sql%rowcount = 0 )
  9     then
 10            raise_application_error( -20001, 'No such id name ' || p_name );
 11     end if;
 12     return l_id;
 13  end;
 14  /

Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( get_nextval( 'foo'
) )
BEGIN dbms_output.put_line( get_nextval( 'foo' ) ); END;

*
ERROR at line 1:
ORA-20001: No such id name foo
ORA-06512: at "OPS$TKYTE.GET_NEXTVAL", line 10
ORA-06512: at line 1

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into ids values ( 'FOO', 0 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( get_nextval( 'foo'
) )
1

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( get_nextval( 'foo'
) )
2

PL/SQL procedure successfully completed.

Now, when you want to reset, just update ids set id = 0 where name = 'whatever'.

Yuck.

      Rating:
      5
sequnces  November 12, 2002
Reviewer:
      mo
Tom:

I have a production instance that started to skip sequnce numbers.  The first 15
records created were fine and created with number 1000 - 1015 but the nafter oct
1 I statrted noticing I got 1020, 1043 and 1060.

I am only doing a select on the sequence.next val in my save procedure.  First I
select ito into a variable and the ni use the variable in my insert statement.
The reason I use a variable instead of seq.nextval is that I insert into
multiple tables with same sequnce numbers.

What do you think may be happening that causing this?  This is a web application
using stored procedures.

          Followup:

There is no such thing as a sequence "skipping numbers".  In order for a
sequence to "skip numbers" it would have to first assure gap free number
sequences and they explicitly DO NOT.

Sounds like your shared pool is way too small and the sequence is getting aged
out (not used enough).

But -- I would just *NOT WORRY ABOUT IT AT ALL* (unless your shared pool is
really too small, fix that of course but don't worry about the sequence)

a sequence is used to generate a primary key/unique value. Nothing more, nothing
less.  any assumptions about "gap free" or "lost numbers" is in vain.

      Rating:
      5
sequence  November 12, 2002
Reviewer:
      mo
TOm:

<Sounds like your shared pool is way too small and the sequence is getting aged
out (not used enough).  >

Can you elaborate on how can a sequence get aged out if it is not used.  How
does it increment if you are nor running (select seq.nextval from dual).

2. The primary key is used by client as a reference number. It is really awkward
to have then refer to PO 1000 and the npo 1050. It makes you think that there
are 50 po's in the system when in fact there are only 2.

Thank you,

          Followup:

things in the shared pool (where sequences are cached) are aged out using an LRU
(least recently used). Hence, if you select s.nextval and then don't touch S for
a while and we need space in the shared pool -- out goes S and out goes any
cached values for S.

2) no it isn't.  why is it?  count(*) tells them how many po's are in the
system.  A po number doesn't, cannot.

Think about this.  Its the days before computers.  Everything is on paper.  You
have a stack of numbered PO's in a tablet.  The current number is 1 on the top.
You fill out PO #1 and put it in the OUT box.  You bump your coffee onto this
tablet spilling it.  Whoops.  The coffee soaks the next 20 sheets which you tear
off and through away.  You are on PO 21 -- are you confused?  Do you think there
are 20 po's in your out box?  No -- you see there is one PO there.

Getting back to today. If you've told your users to look at the PO number to
figure out how many there are -- they've been mislead.  There is a fact about
sequences -- an UNDENIABLE, UNESCAPABLE fact -- they are not gap free, will
never be gap free, they will have gaps!.  No matter WHAT you do, you cannot
look at the sequence number (your po number) and make any conclusions about the
count of PO's in your system -- none.

      Rating: 5
How does
      sequences avoid serialization?  December 18, 2002

Reviewer:  Sam  from San Mateo, CA
Tom,

As usual, your answers are most helpful.

I read this in the Oracle9i Application Developers Guide:
"Sequences eliminate serialization and improve the concurrency of your
application".  Is this really true?  Or do they just mean "sequences minimizes
serialization because it's native in Oracle and is really fast especially when
the numbers are cached"?  What gets locked when I do my_sequence_s.nextval?

Also, I saw this statement in my trace file:
update seq$ set increment$=:2,minvalue=:3,maxvalue=:4,cycle#=:5,order$=:6,
  cache=:7,highwater=:8,audit$=:9
where
 obj#=:1

Could you tell me what operation caused this SQL?

          Followup:

there is a lightweight latch used to serialize access for a split instant.  100%
de-serialization, no, 99.99% -- yes.

The alternative is to serialize entire TRANSACTIONS...

that is recursive SQL, everytime you advance the sequence in the SGA beyond the
cache size, we need to update a database table in a recursive (autonomous)
transaction

      Rating: 3
Strong
      reason  December 19, 2002
Reviewer:  Tony
       from India
  Tom, You say that gap free numbers are not required at all. I hope you will
agree with the following case:
  Generated numbers should have built-in intelligence so that end users will
easily identify the counts and other business related thing. For example,
prefixing purchase order number with vendor code will defintely help the end
users to know the vendor by merely seeing the PO number. Only numbers (PO
number, Request number, voucher number...etc) with lot of gaps will confuse the
users. There are valid reasons , as above, to have gap free numbers. We can't
ignore the importance of gap free numbers just becoz Oracle doesn't have the
feature.

          Followup:

I disagree totally, 100%

Your argument will be "well, they used to use paper and the paper was
sequentially numbered.  they relied on that number".

My counter argument will be "well, I just spilled my coffee on your tablet of
paper destroying the top 20 pieces of paper.  I put them in the garbage.  Did
the business shutdown?  No, of course not".

We void checks all of the time.  Same thing.

If your end users rely on "counts" embedded in a number, they are getting BAD
information.  Period.  There are cancelled orders.  There will be undone
transactions.  The counts will be *wrong*.

Knowing the vendor by looking at the "po" number -- trivial.  the PO number is
obviously a compound field on a printed report -- eg:

select vendor_id || '-' || primary_key PO_NUMBER, ....
  from table

If gaps confuse users -- it is only because you told them there was some implied
meaning to it.  There is no other reason -- cause the old paper based system
sure couldn't have taught them this.

      Rating:
      5
  December 19, 2002
Reviewer:  Jeff
      Larimore  from Miami, Florida
Tom,

Is Oracle considering creating a column type of IDENTITY whereby Oracle will
automatically assign a unique value to a column?

This would create a sequence tied to the column automatically and would not
require the user to define a sequence for the column, and then create an insert
trigger to call it, which you I know you recommend against.

I've seen this in other DBMS servers and have found it extremely user-friendly,
self-documenting and convienient.

Thank you,
Jeff

          Followup:

I will submit this to the people that can make it happen...

      Rating: 5
That nasty word
      "embedded"  December 19, 2002
Reviewer:  Connor
      McDonald  from UK
The problem with "informational" columns, for example, the PO number telling you
how many there are, is that it breaks a lot of the rules for relational
databases.  Scalar columns represent an attribute (singular not plural).

There is nothing worse than being forced to work on a system where column P
contains "02X3751" because it was

- create in the year '02'
- is of type 'X'
- is the 3751'st recording of such
Ugh!!  It doesn't take long before your start seeing hideous queries like:

where substr(P,4,4) = '3751'

and voila! Welcome to function-based-index heaven...

What's wrong with a year, type, and occurrence column ?

          Followup:

exactly -- and then a view to synthesize these "human friendly" things

select vendor_id || '-' || primary_key PO_NUMBER, ....
  from table

people do seem to have an irrational fear of views for some reason....

store the data in its component pieces, glue them together however you see fit
on OUTPUT.

      Rating:
      4
  December 19, 2002
Reviewer:  Tianhua
      Wu  from Atlanta, GA
I agree in most case the gap should not be a problem at all.  However, in some
country (e.g. Italy), you can not have the gap by law (if I understood
correctly).

          Followup:

what did they do when the coffee got spilt?

what do they do when a customer cancels an order?

what would be the purpose of such a law?  I mean -- what is to stop me from just
creating a dummy order for $0 or just to go back and put the orders in out of
sequence.

I would just create a view:

select a.*, rownum LAW_ABIDING_STUFF from t;

I guess.... no gaps there and just as meaningful!

      Rating: 3
Sequence
      generation  December 19, 2002
Reviewer:  pk
       from alaska
For sequence generation why don't you do this :-
PK>create table temp1(id number(12));

Table created.

Elapsed: 00:00:00.51
PK>select * from temp1;

no rows selected

Elapsed: 00:00:00.10
PK>select nvl(max(id),0) + 1 seq_num from temp1;

   SEQ_NUM
----------
         1

Elapsed: 00:00:00.31
PK>

          Followup:

can you spell "serialization"

I can, you just did in SQL ;)

read:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3379873654938
I *hate* that method more than most anything you can do (well, not as much as
not using bind variables -- but a very very close second)

      Rating: 5
Try this
      then...  December 19, 2002
Reviewer:  Connor
      McDonald  from UK
session 1:
  select max(...)+1

session 2:
  select max(...)+1

Ooops....data corruption.

      Rating:
      3
  December 20, 2002
Reviewer:  Dave
       from Ohio
Tianhua's point is an important one, and the legal requirement to avoid gaps in
sequences is a real one in many countries.

Commonly it applies to tax-related "documents" such as invoices -- if a tax
authority finds a missing value in a sequence of invoices it raises a red flag
to them that there is something fishy going on (although I have to add that even
if there were no apparant rationale for the law, it has to be complied with if
only because a company will be prosecuted and fined for not being in compliance.
They will then sue their software vendor.)  For all the scenarios of spilt
coffee, cancelled orders, dummy orders etc. there is a legally sanctioned
resolution.

The view-method would be legally unacceptable as the invoice number must not be
subject to change.

However that doesn't mean that you have to cripple the performance of your
application by serializing the generation of invoices, for example. Consider
whether you could PK the invoice with a regular (performant) methodology, then
update the records later on as a batch process, such as ...

update invoices set invoice_number=inv_seq.next_val;

... or ...

select max(invoice_number) from invoices into l_max_inv_number;
update invoices set invoice_number=l_max_inv_number+rownum
where invoice_number is null;

You can put a unique constraint on the nullable invoice_number field, and maybe
a function-based index also to give fast identification of the null invoice
number rows.

Tom, I'm not familiar with Oracle Applications, but perhaps you could get
someone from the Applications team to address how they deal with requirements
like this -- I bet that they are familiar with the scenario, and have a way of
coping with it.

          Followup:

But my point seems to be getting totally missed.

anyway.

you next_val -- not gap free. 100% not gap fere.

Your select max(), not very concurrent (eg: leads to duplicates out the
wahzoo)...

They do it like you would with your checkbook.  It is called a "void".  fill in
dummy records.  Assign the numbers when you close the books, not when filling
them.

Tell me -- what happened when someone spilt coffee on the invoice forms???

      Rating:
      4
  December 20, 2002
Reviewer:  Tianhua
      Wu  from Atlanta, GA
select a.*, rownum LAW_ABIDING_STUFF from t;
...

Unfortunately, the rownum will be different for the same record in different
queries.

The law is law, there are not too much we can do about it.  And it indeed makes
life much difficult.  On the other hand, it may generate new positions.  :)

      Rating:
      5
Hmmmm  December 20, 2002
Reviewer:
      George Johnson  from UK
I totally accept the gap-free argument, I can't see what the problem is, if
numbers are skipped due to cancellations, employee incompetance (split coffee),
then surely simply keeping an official record that missed numbers are "voided"
should satisfy TAX officials, but I guess my real question would be, what on
earth is a "whazoo"? (I'm from the UK you see.)

Tks in adv

          Followup:

I guess I spelled it wrong ;)

Main Entry: wa·zoo
Pronunciation: (")wä-'zü
Function: noun
Etymology: origin unknown
Date: 1983
- up the wazoo also out the wazoo : in excess <we've got lawyers up the wazoo --
Steven Bochco>

http://www.m-w.com/cgi-bin/dictionary?va=wazoo

      Rating:
      3
  December 20, 2002
Reviewer:  Dave
       from Ohio
I understand your point, which is that gap-free sequences are logically
meaningless and highly non-concurrent, no? However the point that I was helping
to make is even more simple --  it is a legal requirement in many cases to have
gap-free sequences. Full-stop, that's it. We may not like it, it may not be
logical, it may not be performant, but it is the law. What software house or
development team can afford to have a policy that legal requirements can be
ignored?

It was my intention that the methods I suggested should allow concurrency in
generating the new records by delaying the attempt to generate gap-free
sequences until after the insert. The methods were to be implemented as a
post-creation batch job (say DBMS_JOB every few minutes) which would not have to
compete with other sessions for the invoice_number sequence, or would not have
to use a sequence at all. In either case it would be a single update process.

When someone spilt coffee on the form, you kept the damaged form to show the
authorities. If an invoice is generated incorrectly it is kept, marked as void,
and regenerated under a new number.

          Followup:

The "gap free" numbers can (and should be) assigned after the fact, during a
bulk process, the closing of the books.  I did not realize that is what you were
also proposing.  I see the next_val in there -- that'll never work.  sequences
by definition promise NOT to be gap free.

Ok, someone drops the cigarrette on the form...
The form was left in the rain, totally obliterating it...
The forms blew away in a wind storm...
<any one of 1,000 other scenarios>

      Rating: 3
Legal
      reasons  December 20, 2002
Reviewer:  Tony

Tom, in our country also, gap-free invoice, voucher numbers are mandatory in
legal terms. Even if we happen to generate wrong voucher, it can be cancelled.
But the cancelled vouchers should be in the system during auditing. If the
voucher is hand written one , we can atleast try to convince the auditors to
some extent saying that its lost. As we use computers, we can't convince the
auditors. we can't say that performance/concurrency is the reason for not having
gap-free numbers.

          Followup:

assign the numbers in a batch process AFTER the fact.

does it matter if the number is assigned right now, this instant.  Or tonight at
midnight.

(funny, the fact that computer wise we can assign these numbers *after the fact*
makes them *ever less meaningless*, they are useless for any purpose -- except
to consume disk space of course)

      Rating: 5
Federal
      Regulations and Sequences  December 20, 2002

Reviewer:  Pedro Diaz  from Dominican Republic

Well, here I go...
In my country You have to match the pre-printed document number with the
computer generated number, it is restricted by laws to have a gap in any case.
To make things more dificult EVERY branch office need its own sequence and it
most be GAP FREE.

We tray many ways but no one gets it done.  We have:

A. SELECT MAX( NVL( invoice_num, 0) ) +1
    FROM invoices
      WHERE branch_office = :branch_num

B. SELECT Last_invoice + 1
    FROM branch_sequences
    Where branch_office = :branch_num

C. The Trigger / Package Approach:
create or replace package body invoice_sequence_pkg is

   type sequence_type is table of invoices.invoice_num%type
                          index by binary_integer;

   type branch_office_type is table of invoices.branch_num%type
                          index by binary_integer;

   sequence_table  sequence_type;

   branch_table    branch_office_type;

   CURSOR last_sequence is
      SELECT A.branch_num, NVL( max( A.invoice_num ), 0 ) last_invoice
        FROM invoices A
        WHERE invoice_date >='01-Jul-01'
      GROUP BY a.branch_num;

   procedure Init_tables is
   begin

         actual_sequence    := 0;

      sequence_table.delete;

         branch_table.delete;

   end;

   procedure Sequence_Branch_Office is
   begin
     FOR branch_sequence IN last_sequence LOOP
        sequence_table( branch_sequence.branch_num ) :=
branch_sequence.last_invoice;
     END LOOP;
   end Sequence_Branch_Office;

   function netx_sequence( branch_num_in in invoices.branch_num%type) return
invoices.invoice_num%type is
   begin
      return ( NVL ( sequence_table( cbranch_num_in ), 0 ) + 1 );
   end;

end invoice_sequence_pkg;

Calling it from Statement lavel and Row Level triggers in invoice table.

Its one of this correct, helpfull, or does exists any better way to do it.

          Followup:

short of moving?

you should do it like this:

  UPDATE branch_sequences
     set last_invoice = last_invoice+1
   where branch_offset = :branch_num
  RETURNING last_invoice into :invoice_number;

  if (sql%rowcount = 0)
  then
      do the insert of 1
  end if;

you want to use the UPDATE, not a select, in order to impose serialization and
avoid artificial "dups" that will cause transactions to fail.

      Rating: 5
Sequences are
      just that Sequential  December 20, 2002
Reviewer:
      Jeff Larimore  from Sunny Miami Florida
Great topic!

It seems clear that there will be a requirement for a geometric sequence
generator (no gaps) in some app somewhere.

Now if gaps or serialization are not important, then simply a unique random
number would do.

1.  Sequence - generates a series of numbers in order. -Oracle has this, its
called a Sequence.

2.  Geometric sequence - generates a series of numbers whereby each term differs
from previous term by same amount, ie 1,2,3 or 10,20,30., no gaps  - We need
this from Oracle.

Tom,
I tried once to put something like seq.nextval in the default value of a column,
hoping that this would fool Oracle into automatically grabbing the next value of
the sequence if none was specified for the column on insert.

I believe it didn't like that, but felt this was a smooth way of creating the
equivalent of an IDENTITY column.

Have you got a way to make that work?

Thanks,
Jeff

          Followup:

you use a trigger + sequence to accomplish that.

"geometric sequence" as you call it -- you already got it:

create table gseqs ( name varchar2(30), val int );

create or replace function gseq_nextval( p_name in varchar2 ) return number
as
   l_val number;
begin
   update gseqs
      set val = val+1
    where name = p_name
   returning val into l_val;

   if ( sql%rowcount = 0 )
   then
        raise_application_error( -20001, 'No such sequence exists' );
   end if;
   return l_val;
end;

      Rating: 5
What is this
      system sequence IDGEN1$.  February 26, 2004

Reviewer:  Saibabu Devabhaktuni  from San Jose, CA
      USA.
Hi Tom,

I was hoping may be you could shed some light on this question:

What is this Sequence "IDGEN1$" is used for. I think
it has some relation with "ID_GENS$" table.

"ID_GENS$" table has a single row telling how many of
total id generators are in effect.

I did a google search on it, not even a single useful link, and metalink is more
of a make up to Oracle documentation. How does Oracle use this sequence IDGEN1$
and for what purpose. I understand, we don't need to dig too deep in to
internals, but it won't take anything away from Oracle. Every other software
vendors provide documentation/support or some forums way too deep in to details
than Oracle does. Is this a strategy of keeping complexity unexposed attract
more people bind to it?

          Followup:

Umm, every other vendor?  Really.  Where is the MS internals website for
sqlserver?  and IBM's for DB2 (well, they'd need quite a few actually given they
have so many flavors of DB2 and then informix and redbrick and so on --
something like 7 or 8 RDBMS's).

Let me ask you -- why do you need to know?  Is it causing some issue on your
system?

February 26, 2004 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

      Rating:
      2
Complex?  February 24, 2004
Reviewer:
      Jon  from Hartford, CT
I thought this would tip me off to my problem, but it didn't.  Can you tell me
why this is a complex view?

SQL> create materialized view mv_current_fund_value
  2  build immediate
  3  refresh FAST on demand
  4  as
  5  (
  6  select *
  7  from
  8   fund_value
  9  where
 10          FUND_UNT_PRC_DT = '23-feb-2004'
 11  );
 fund_value
 *
ERROR at line 8:
ORA-12015: cannot create a fast refresh materialized view from a complex query

SQL> desc fund_value
 Name                            Null?    Type
 ------------------------------- -------- ----
 FUND_ID                         NOT NULL NUMBER
 FUND_UNT_PRC                             NUMBER(9,6)
 PRCNG_CD                        NOT NULL VARCHAR2(4)
 FUND_RT                         NOT NULL NUMBER(8,7)
 FUND_UNT_PRC_DT                 NOT NULL DATE
 PRCSSR_ID                       NOT NULL VARCHAR2(8)
 PRCSS_DT                        NOT NULL DATE
 CNVRSN_STTS_CD                           VARCHAR2(1)

          Followup:

help us recreate the entire situation:

ops$tkyte@ORA817DEV> create table t1 (
  2   FUND_ID                          NUMBER primary key ,
  3   FUND_UNT_PRC                             NUMBER(9,6),
  4   PRCNG_CD                         VARCHAR2(4) NOT NULL,
  5   FUND_RT                          NUMBER(8,7) NOT NULL,
  6   FUND_UNT_PRC_DT                  DATE NOT NULL,
  7   PRCSSR_ID                        VARCHAR2(8) NOT NULL,
  8   PRCSS_DT                         DATE NOT NULL,
  9   CNVRSN_STTS_CD                           VARCHAR2(1)
 10  )
 11  /

Table created.

ops$tkyte@ORA817DEV> create materialized view log on t1;

Materialized view log created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create materialized view t2
  2  build immediate
  3  refresh fast
  4  as
  5  (
  6  select * from t1 where fund_unt_prc_dt = '22-feb-2004'
  7  );

Materialized view created.

      Rating: 3
They
      why?  February 24, 2004
Reviewer:  Jon
       from Hartford, CT
That works for me, too, but I am still at a loss as to the original problem.

Is it because I am not the owner of the base table (there is a public synonym to
it)?  I can create a MV but not with the fast option.

Or is it possible that I don't have access to the MV log?

          Followup:

could be the MV log

that is why I ask for a test case.  I find that in 9999 times out of 10,000 in
the development of a test case -- i find the issue (or my own "bug":)

so, set it up, with multiple user accounts -- i usually use "user a" and "user
b"

      Rating:
      3
Wow!  February 26, 2004
Reviewer:
      A reader
9999 out of 1000!  Those ARE good odds. ;)

Just to follow up, I finally got this done by running it on the schema of the
owner of the table, so I am assuming it is some sort of permissions/privileges
thing.  Gotta say I find the error message a "tad misleading".

          Followup:

i fixed that typo :0

February 26, 2004 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 ?!

      Rating: 5
Clean up data
      in date format  January 25, 2004
Reviewer:  Sean
       from NJ, USA
Hi Tom,

After database migration.  Some of data in date field are not what we wanted,
such as year 2001 becomes 0001.
I tried to fix it, but the method I used is not very neat.
Here is the sample.

create table t(c date, c1 date);

insert into t(c)
values('01-JAN-0001');

insert into t(c)
values('01-JAN-0080');

insert into t(c)
values('01-JAN-0999');

insert into t(c)
values('01-JAN-2001');

insert into t(c)
values('01-JAN-1999');

update t
set c1 = decode(substr(to_char(c, 'YYYY'), 1, 3), '000',
         to_date(to_char(c, 'DD-MON')||'-'||'200'||substr(to_char(c, 'YYYY'), 4)
                ));
update t
   set c1= decode(substr(to_char(c, 'YYYY'), 1, 2), '00',
        to_date(to_char(c, 'DD-MON')||'-'||'19'||substr(to_char(c, 'YYYY'), 3)
                ))
where substr(to_char(c, 'YYYY'), 3,1) <> '0';

update t
   set c1 = decode(substr(to_char(c, 'YYYY'), 1, 1), '0',
        to_date(to_char(c, 'DD-MON')||'-'||'1'||substr(to_char(c, 'YYYY'), 2)
                ))
where substr(to_char(c, 'YYYY'), 2,1) <> '0';

SQL> select to_char(c, 'YYYY') c, to_char(c1, 'YYYY') c1 from t;

C    C1
---- ----
0001 2001
0080 1980
0999 1999
2001
1999
1999

Thanks so much for your help.

Sean

          Followup:

looks like someone used 2 character years :(

just

update t set c1 = to_date( to_char(c1,'dd-mon-yy'), 'dd-mon-rr' );

ops$tkyte@ORA9IR2> select * from t;

C                    C1
-------------------- --------------------
01-jan-0001 00:00:00
01-jan-0080 00:00:00
01-jan-0999 00:00:00
01-jan-2001 00:00:00
01-jan-1999 00:00:00

ops$tkyte@ORA9IR2> update t set c = to_date(to_char(c,'dd-mon-yy'), 'dd-mon-rr'
);

5 rows updated.

ops$tkyte@ORA9IR2> select * from t;

C                    C1
-------------------- --------------------
01-jan-2001 00:00:00
01-jan-1980 00:00:00
01-jan-1999 00:00:00
01-jan-2001 00:00:00
01-jan-1999 00:00:00

      Rating: 5
Change data in
      date format  January 26, 2004
Reviewer:  Sean
       from NJ, USA
Thanks so much.

But how does 'rr' do this magic?

Sean

          Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96540/sql_elements4a.htm#35239

      Rating: 5
Please clarify
      relation between nls_date_format and to_date  February 25,
      2004
Reviewer:  Moorthy Rekapalli  from Atlanta, GA
      USA
Tom,

Please look at the following and help me understand the relation between
nls_date_format and to_date.

I explicitly altered the NLS_DATE_FORMAT at session level and was expecting that
TO_DATE will throw an error for not supplying 4 digit year.  Instead, it
inserted the row and it is neither 1952 nor 2052.  But, it is 0052.

SQL> alter session set nls_date_format = 'DD-MON-YYYY';

Session altered.

SQL> create table test
  2  (dob_str  varchar2(30)
  3  ,dob_date date
  4  );

Table created.

SQL> insert into test(dob_str, dob_date)
  2  values ('01-APR-52', TO_DATE('01-APR-52'));

1 row created.

SQL> select * from test;

DOB_STR                        DOB_DATE
------------------------------ -----------
01-APR-52                      01-APR-0052

Some additional info on my environment:

SQL> select * from v$version;

SQL> select value
  2    from nls_database_parameters
  3   where parameter = 'NLS_DATE_FORMAT';

VALUE
----------------------------------------
DD-MON-RR

SQL> 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

Thanks in advance,
Moorthy Rekapalli

          Followup:

that is the year 0052, the year 52 is just as valid as the year 100 as is 1965
as is 2525.

You cannot use 2 characters to represent that which takes a MINIMUM of 4 to
accurately convey.

      Rating: 5
Agreed, we use
      4 digits for year. But, I need the help on this
      concept.  February 25, 2004
Reviewer:  Moorthy
      Rekapalli  from Atlanta, GA USA
Tom,

I follow your suggestion. I also suggest everyone to use 4 digits for year (even
if they don't ask - I suggest anyway:)

After the IT industry has gone through the effort of fixing the Y2K issue in a
hard way, everyone should have learned their lesson.

My question is regarding clearing up the concept.  As per Oracle Documentation
URL:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96540/functions137a.htm#1003595
"
Text description of to_date
TO_DATE ( char [, fmt [, 'nlsparam']] )

If you omit fmt, then char must be in the default date format.
"

My session nls_date_format was DD-MON-YYYY and my database/instance level
nls_date_format was DD-MON-RR.

Even if you give the fmt of DD-MON-RR in the TO_DATE, it will interpret as 1952.
 When fmt is not specified, I was expecting TO_DATE to take the fmt of either
session level or database level NLS_DATE_FORMAT setting.  Also, we are using the
default of AMERICA for NLS_TERRITORY at instance level.

In a nutshell, I was expecting either the year 1952 (if Oracle was taking
database level setting) or an ORA- error (if Oracle was taking session level
setting) for not supplying the 4 digits for year.

It did not do both.  That's why, I want to clearup my understanding.  If you can
help on this, that will be great.

In any project, there will be several developers.  For whatever reason, if a
developer is not following 4 digit year standard and trying to insert with two
digts, I want database engine to throw an error rather than accepting it, which
might have side affects like this.

Again, Thanks for your help.
Moorthy Rekapalli.

Begin Excerpt from Oracle Documentation:

Purpose
TO_DATE converts char of CHAR, VARCHAR2, NCHAR, or NVARCHAR2 datatype to a value
of DATE datatype. The fmt is a date format specifying the format of char. If you
omit fmt, then char must be in the default date format. If fmt is 'J', for
Julian, then char must be an integer.

The default date format is determined implicitly by the NLS_TERRITORY
initialization parameter, or can be set explicitly by the NLS_DATE_FORMAT
parameter.

The 'nlsparam' has the same purpose in this function as in the TO_CHAR function
for date conversion.

Do not use the TO_DATE function with a DATE value for the char argument. The
first two digits of the returned DATE value can differ from the original char,
depending on fmt or the default date format.

End Excerpt from Oracle Documentation.

          Followup:

the number 52 and 0052 "are the same"

YYYY says "4 digit year"

52 = 0052

that is all, it'll accept both numbers for the year 52.

Just like

1-jan-2004

is just as ok as 01-jan-2004 is...

"In any project, there will be several developers.  For whatever reason, if a
developer is not following 4 digit year standard and trying to insert with two
digts, I want database engine to throw an error rather than accepting it, which
might have side affects like this."

design standards, peer reviews, code walkthrus, testing were designed to catch
this.  if you have developers working in a vacum.....  without review, you will
have many such issues.

      Rating: 5
Thanks - now
      this is crystal clear to me  February 25, 2004

Reviewer:  Moorthy Rekapalli  from Atlanta, GA USA

Tom,

Thanks for clarifying.  So far, I was under the impression that Oracle will do
some kind of string parsing to compare the to_date('char') with nls_date_format
setting.

As per my tests and your explanation, it is clear that in YYYY format, it will
consider two digit year AS four digit year with leading zeros.

Thanks,
Moorthy.

          Followup:

and 3 digits and 1 digit as well!

ops$tkyte@ORA920PC> select to_date( '1' ), to_date( '12'), to_date('123'),
to_date( '1234' ) from dual;

TO_D TO_D TO_D TO_D
---- ---- ---- ----
0001 0012 0123 1234

      Rating: 5
Date
      Format  February 25, 2004
Reviewer:  Giri
       from Groton, USA
Tom,
I need to write function to convert the any give date format to below format
YYYYMMDD

Input format may be
1)DD-MON-YYYY
2)dd/mm/yyyy
3)yyyy/mm/dd
some times column may contain pure characters like
'hi tom'. In this case I need to return 'hi tom' only.
Basically the column is varchar2 data  type. It can take any format,But if it is
a date filed I need to convert to yyyymmdd.

          Followup:

and what pray tell happens when the input is actually mm/dd/yyyy.  sigh.  GIGO
rules i guess (google "define: gigo",  love that feature of google -- define...)

if it is one of those three, and those three only, I might look at the
substr(str,3,1) and decode

ops$tkyte@ORA920PC> select x, to_date(x,decode(substr(x,3,1),
  2                       '-','dd-mon-yyyy',
  3                       '/','dd/mm/yyyy',
  4                           'yyyy/mm/dd')) from t;

X               TO_DATE(X
--------------- ---------
01-jan-2004     01-JAN-04
01/01/2004      01-JAN-04
2004/01/01      01-JAN-04

      Rating: 5
varchar2
      date  February 26, 2004
Reviewer:  GIRI
       from GROTON, USA
Tom,
Some times there will be a space betwnn the characters like below
01 - JAN - 2003
01 / JAN / 2003
01 / 12 /2003.
 In this case I cannot use substr and decode for character position.

          Followup:

you can replace spaces to nothing easily enough.

February 26, 2004 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.

      Rating: 4
What can be
      done to alleviate this ?  February 25, 2004

Reviewer:  Neil  from UK
Hi Tom

1. If we have multiple entries in v$sql_shared_cursor for the same address, then
does that mean that the statement is being re-parsed ?  and if so is it a soft
or hard parse.
Can anything be done to prevent this bind mismatch / bind size issue ?

2. If I have multiple entries for the same address, but none of the columns in
v$sql_shared_cursor has a value of 'Y', what does that mean ? I am confused
because we have a statements that vary only by one bind variable, which is
assigned in pl/sql by selecting nextval from a sequence.

The sql can be seen in v$sql :-

SQL> select
SQL_TEXT,LOADED_VERSIONS,OPEN_VERSIONS,EXECUTIONS,INVALIDATIONS,PARSING_USER_ID,P
ARSING_SCHEMA_ID,ADDRESS,TYPE_CHK_HEAP,HASH_VALUE,CHILD_NUMBER,MODULE,IS_OBSOLETE
 from v$sql where address='00000003FDB4A980'

SQL_TEXT
---------------------------------------------------------------------------------
-------------------
LOADED_VERSIONS OPEN_VERSIONS EXECUTIONS INVALIDATIONS PARSING_USER_ID
PARSING_SCHEMA_ID
--------------- ------------- ---------- ------------- ---------------
-----------------
ADDRESS          TYPE_CHK_HEAP    HASH_VALUE CHILD_NUMBER
---------------- ---------------- ---------- ------------
MODULE                                                           IS_
---------------------------------------------------------------- ---
SELECT  X_AE_CITY, X_BE_CITY, ORDERS_COUNT, FAULTS_COUNT , X_ASSET_NUM,
INSTALL_DT FROM RESULT_TAB
 WHERE QUERY_ID = :1 ORDER BY X_ASSET_NUM ASC
              0             0          0             7               0
      0
00000003FDB4A980 00                 52054600            0
JDBC Thin Client                                                 N
SELECT  X_AE_CITY, X_BE_CITY, ORDERS_COUNT, FAULTS_COUNT , X_ASSET_NUM,
INSTALL_DT FROM RESULT_TAB
 WHERE QUERY_ID = :1 ORDER BY X_ASSET_NUM ASC
              1             1       1416             6              24
     24
00000003FDB4A980 00                 52054600            1
JDBC Thin Client                                                 N
SELECT  X_AE_CITY, X_BE_CITY, ORDERS_COUNT, FAULTS_COUNT , X_ASSET_NUM,
INSTALL_DT FROM RESULT_TAB
 WHERE QUERY_ID = :1 ORDER BY X_ASSET_NUM ASC
              0             0          0             6               0
      0
00000003FDB4A980 00                 52054600            2
JDBC Thin Client                                                 N
SELECT  X_AE_CITY, X_BE_CITY, ORDERS_COUNT, FAULTS_COUNT , X_ASSET_NUM,
INSTALL_DT FROM RESULT_TAB
 WHERE QUERY_ID = :1 ORDER BY X_ASSET_NUM ASC
              0             0          0             8               0
      0
00000003FDB4A980 00                 52054600            3
JDBC Thin Client                                                 N
SELECT  X_AE_CITY, X_BE_CITY, ORDERS_COUNT, FAULTS_COUNT , X_ASSET_NUM,
INSTALL_DT FROM RESULT_TAB
 WHERE QUERY_ID = :1 ORDER BY X_ASSET_NUM ASC
              0             0          0             5               0
      0
00000003FDB4A980 00                 52054600            4
JDBC Thin Client                                                 N
JDBC Thin Client                                                 N
SELECT  X_AE_CITY, X_BE_CITY, ORDERS_COUNT, FAULTS_COUNT , X_ASSET_NUM,
INSTALL_DT FROM RESULT_TAB
 WHERE QUERY_ID = :1 ORDER BY X_ASSET_NUM ASC
              0             0          0             5               0
      0
00000003FDB4A980 00                 52054600            6
JDBC Thin Client                                                 N
SELECT  X_AE_CITY, X_BE_CITY, ORDERS_COUNT, FAULTS_COUNT , X_ASSET_NUM,
INSTALL_DT FROM RESULT_TAB
 WHERE QUERY_ID = :1 ORDER BY X_ASSET_NUM ASC
              0             0          0             5               0
      0
00000003FDB4A980 00                 52054600            7
JDBC Thin Client                                                 N
SELECT  X_AE_CITY, X_BE_CITY, ORDERS_COUNT, FAULTS_COUNT , X_ASSET_NUM,
INSTALL_DT FROM RESULT_TAB
 WHERE QUERY_ID = :1 ORDER BY X_ASSET_NUM ASC
              0             0          0             3               0
      0
00000003FDB4A980 00                 52054600            8
JDBC Thin Client                                                 N
SELECT  X_AE_CITY, X_BE_CITY, ORDERS_COUNT, FAULTS_COUNT , X_ASSET_NUM,
INSTALL_DT FROM RESULT_TAB
 WHERE QUERY_ID = :1 ORDER BY X_ASSET_NUM ASC
              0             0          0             2               0
      0
00000003FDB4A980 00                 52054600            9
JDBC Thin Client                                                 N
SELECT  X_AE_CITY, X_BE_CITY, ORDERS_COUNT, FAULTS_COUNT , X_ASSET_NUM,
INSTALL_DT FROM RESULT_TAB
 WHERE QUERY_ID = :1 ORDER BY X_ASSET_NUM ASC
              0             0          0             1               0
      0
00000003FDB4A980 00                 52054600           10
JDBC Thin Client                                                 N
SELECT  X_AE_CITY, X_BE_CITY, ORDERS_COUNT, FAULTS_COUNT , X_ASSET_NUM,
INSTALL_DT FROM RESULT_TAB
 WHERE QUERY_ID = :1 ORDER BY X_ASSET_NUM ASC
              0             0          0             1               0
      0
00000003FDB4A980 00                 52054600           11
JDBC Thin Client                                                 N

12 rows selected.

But in v$sql_shared_cursor, I see

SQL> select * from v$sql_shared_cursor where KGLHDPAR ='0
SQL> select * from v$sql_shared_cursor where KGLHDPAR ='00000003FDB4A980';

ADDRESS          KGLHDPAR         UNB SQL OPT OUT STA LIT SEC EXP BUF PDM INS
SLA TYP AUT BIN DES
---------------- ---------------- --- --- --- --- --- --- --- --- --- --- ---
--- --- --- --- ---
LAN TRA ROW INS INS REM LOG INC OVE SQL MV_ USE TYP NO_ FLA
--- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
00000003FDE3AE68 00000003FDB4A980 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
00000003F6866FC8 00000003FDB4A980 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
00000003FB19B1B8 00000003FDB4A980 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
00000003F7F2B338 00000003FDB4A980 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
00000003FC39EF58 00000003FDB4A980 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
00000003FC6809B0 00000003FDB4A980 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
00000003FA2387E8 00000003FDB4A980 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   N0000003FDB4A980';

So nothing is set to 'Y'.

SO why are they seen as separate statements, and why only 7 rows in
v$sql_shared_cursor for the 12 in v$sql ?

Many thanks

Neil

          Followup:

1) each child cursor represents a hard parse.  short of using consistent bind
sizes, no.

2) those queries are coming from jdbc, not plsql -- plsql would use :b1, :b2,
:b3 and so on.

looks like a table that is invalidated often (indexed, stats gathered, ddl
performed, etc) and the child cursors are there due to the invalidations.

      Rating: 5
which child
      cursor is executed by a session ?  February 25, 2004

Reviewer:  Alberto Dell'Era  from Milan, Italy

Joining v$session.sql_address and v$sql.address would get *all* the child
cursors "associated" with a session - is it possible (9iR2) to get the single
child cursor which is/was really executed by the session ?

tnx
alberto

          Followup:

in 9ir2, it is hard (dbms_sql -- and the number returned by that as the cursor
handle will let you do it easily)

select distinct sid, sql_hash_value, child_number
from v$session a, v$sql b, dba_kgllock c
where b.child_address = c.kgllkhdl
and a.saddr = c.kgllkuse
and a.sql_address = b.address
/

is a query i've seen people use, I have not personally vetted it myself.

in 10g, it is much more straight forward (SQL_CHILD_NUMBER added to v$session).

      Rating: 4
So each child
      is hard parsed, why 0 for executions  February 26, 2004

Reviewer:  Neil campbell  from UK
Tom

1) if each child is hard parsed, why is only one of the rows above showing a
value for executions ? Can it be parsed without being executed ?

2)All the entries in v$sql have invalidations, yet not all appear in
v$sql_shared_cursor - why is that ?

3) RE: consistent bind sizes - I am confused, beacause the value for the bind in
the WHERE QUERY_ID = :1 predicate is ust a sequence number currently at about
85000 and incrementing. I'm not certain how the statement is constructed in
Java, but how would it be prepared so that it made Oracle think it was such a
difference in size ?
Any chance of an example ?

4) Table getting invalidated is interesting. I wasn;t really aware of that.
However, I am not aware of any ddl, index creation or stats gathering as it is
effectivly a temporary table (not a Global Temporary table) for sotring the
resulst of the queries. Is there anything else that can cause the invalidations.

Best Regards

Neil

          Followup:

1) absolutely, happens all of the time.  generic class libraries do it,
developers do it, sure.

2) you'll have queries in your shared pool that reference tables that don't even
exist anymore.  they get cleaned out as space is needed.

3) why do you think this was a bind mismatch -- since you have all N's and bind
mismatch is one of the columns I don't think you do.  You just asked about "how
to solve bind mismatches" and the answer is "make sure you use the same bind
size"

4) authorizations as well for example. truncates too.

      Rating: 5
Why no bind
      mismatch ??  February 26, 2004
Reviewer:
      Kevin  from Ireland
this is very interesting Tom!

But in the example above from Neil, if the problem was due to inconsistent bid
sizes, wouldn't it show in v$sql_shared_cursor ? In that example it doesn't ?

          Followup:

correct, they do not have a bind mismatch -- i was just answering the question
"how to avoid bind mismatch"

      Rating: 5
Ahh - I had
      assumed it was bind mismatch  February 26, 2004

Reviewer:  Neil Campbell  from UK
Ok - so because I see the QUERY_ID = :1
I know the developers are using binds - thats good.

But, we are also causing the parsed version to be invalidated because of a table
invalidation, or perhaps something else ?

How can I investigate further what is causing these child cursors to be created
?

thanks very much

Neil

          Followup:

well, you could use a mixture of auditing and DDL triggers on the table to see
what might be messing with the table itself.

a "temporary" table might lead me down the "truncate" path of thinking -- makes
sense perhaps?

February 26, 2004 Andre -- Thanks for the question regarding "How
      does v$session_longops work ?", version 8.1.6

      You Asked
Hi again Tom,

I have tried to use the v$session_longops view to track database
operations, but I´ve noticed that only some kinds of them really
appear.

Besides, for example, the CREATE INDEX statement is split into
multiple sub-operations (sort, merge, etc.)

I am aware I have to first analyze the object to be tracked and
set TIMED_STATISTICS or SQL_TRACE, so I can use this feature.

The questions are:

- What operations are really "trackable" ?

- Why does some operations are split into sub-operations and how
can I know to which operation these sub-operations belong ?

Regards.

      and we said...
In Oracle 8.0, the view V$Session_longops was introduced to
allow Server processes to indicate some idea of how far they had
progressed  (specifically RMAN).

In Oracle8i, this list was expanded to include:

Rman Backup and Restore
Parallel Query
Recovery ( Crash and Media )
Full Table scans               **
Sorting

** Only considered a 'Longop' if the table exceeds 10,000 blocks

   formatted blocks.

why are some operations broken into smaller chunks?  I guess
because it makes sense to. In a parallel index create -- there
are lots of sorts going on and then we need to merge them all
back together into 1 index.
Review & Followup

      Rating: 5
6 seconds or 2
      seconds  November 02, 2001
Reviewer:  Nag

To be sure:

The conditions for a operation to appear in v$session_longops, what we require
is

1.the table exceed 10,000 blocks
2.The operation should be more than 6 seconds.
3.The operation should be one of
Rman Backup and Restore
Parallel Query
Recovery ( Crash and Media )
Full Table scans               **
Sorting

( is there  a list of  other server process which are covered by
v$session_longops, can you give the link to the specific page which contains all
the above mentioned information about v$session_longops.)

      Rating:
      5
Wor  February 26, 2004
Reviewer:
      Wor  from USA
Hi Tom,
We are suddenly experiencing extreme DB slowness.
Users are accesing the site on real time basis and running reports too.
How do I know which SQL's are taking time ( basically longer time ) or maybe
indexes not used etc..
I need to know the exact SQL's which are taking longer time in sorted order.
Is there any way ?

Thanks in advance,
Wor

          Followup:

statspack, its all about statspack.

just run it, wait 10 minutes, run it again.  you'll see what you ask for.

February 26, 2004 Hassan -- Thanks for the question regarding "Date
      datatype", version 7.3

      You Asked
Having just finished doing my Oracle developer track and now
preparing for the OCP certification, I'm developing a small
database. I'm having problems regarding the DATE datatype. Is it
possible that we can change the default DATE datatype i.e.,
DD-MM-YY, specially when we are creating a table and similarly
when we are populating the Date column, is it possible if we can
enter the date entries in a different format like 23/12/00?

      and we said...
What you want to change is the default date mask for a date --
not the "date datatype" itself.

By default, the default format is either DD-MON-YY or in 8.1.6
and up, it is DD-MON-RR.

You can change this at the session level via an ALTER SESSION:

ops$tkyte@ORA8I.WORLD> create table t ( x date );
Table created.

ops$tkyte@ORA8I.WORLD> alter session set nls_date_format =
'dd/mm/rr';

Session altered.

ops$tkyte@ORA8I.WORLD> insert into t values ( '23/12/00' );
1 row created.

ops$tkyte@ORA8I.WORLD> select x, to_char(x,'dd-mon-yyyy') from
t;

X        TO_CHAR(X,'
-------- -----------
23/12/00 23-dec-2000

Now, you can do it at the database level in the init.ora
parameter but this won't work in general (changing at the
session level in your application is suggested).  See

http://osi.oracle.com/~tkyte/Misc/NLSDateFormat.html
for why this is.

I would suggest using a 4 digit century+year if at all possible.
 Also, read up on the difference between RR and YY to make sure
you understand that:

ops$tkyte@ORA8I.WORLD> create table t ( x date );

Table created.

ops$tkyte@ORA8I.WORLD> alter session set nls_date_format =
'dd/mm/rr';

Session altered.

ops$tkyte@ORA8I.WORLD> insert into t values ( '23/12/99' );

1 row created.

ops$tkyte@ORA8I.WORLD> alter session set nls_date_format =
'dd/mm/yy';

Session altered.

ops$tkyte@ORA8I.WORLD> insert into t values ( '23/12/99' );

1 row created.

ops$tkyte@ORA8I.WORLD> select x, to_char(x,'dd-mon-yyyy') from
t;

X        TO_CHAR(X,'
-------- -----------
23/12/99 23-dec-1999
23/12/99 23-dec-2099

Review &
      Followup

      Rating: 3
Trigger after
      logon  January 30, 2004
Reviewer:  Marcio
       from br
Tom, Is possible to keep track a connection since logon untill prompt SQL > ?
Explain:
I used your solution
http://osi.oracle.com/~tkyte/Misc/NLSDateFormat.html
-- 9i works well, but in 8i doesn't. I would like to
know about track, I used this technique:

ops$t_mp00@MRP817> create table t ( x varchar2(45) );

Table created.

ops$t_mp00@MRP817>
ops$t_mp00@MRP817> create or replace trigger after_logon
  2  after logon on database
  3  begin
  4     insert into t select 'Before ' || sysdate from dual;
  5     execute immediate 'alter session set nls_date_format = ''dd/mm/yyyy,
hh24:mi''';
  6     insert into t select 'After ' || sysdate from dual;
  7  end;
  8  /

Trigger created.

ops$t_mp00@MRP817>
ops$t_mp00@MRP817> connect /
Connected.
ops$t_mp00@MRP817>
ops$t_mp00@MRP817> select t.*, sysdate from t;

X                                             SYSDATE
--------------------------------------------- --------
Before 30/01/04                               30/01/04
After 30/01/2004, 08:54                       30/01/04

2 rows selected.

But in 9i works dandy!

ops$t_mp00@MRP920> create table t ( x varchar2(45) );

Table created.

ops$t_mp00@MRP920>
ops$t_mp00@MRP920> create or replace trigger after_logon
  2  after logon on database
  3  begin
  4     insert into t select 'Before ' || sysdate from dual;
  5     execute immediate 'alter session set nls_date_format = ''dd/mm/yyyy,
hh24:mi''';
  6     insert into t select 'After ' || sysdate from dual;
  7  end;
  8  /

Trigger created.

ops$t_mp00@MRP920>
ops$t_mp00@MRP920> connect /
Connected.
ops$t_mp00@MRP920>
ops$t_mp00@MRP920> select t.*, sysdate from t;

X                                             SYSDATE
--------------------------------------------- -----------------
Before 30/01/04                               30/01/2004, 08:54
After 30/01/2004, 08:54                       30/01/2004, 08:54

There would be something replace my nls_date_format after firing trigger, but I
don't know how to keep track this.

          Followup:

looks like it is working -- it is just that you have to physically break the
connection to the database - not just re-log in in 8i:

ops$tkyte@ORA817DEV> create or replace trigger after_logon
  2  after logon on database
  3  begin
  4     insert into t select 'Before ' || sysdate from dual;
  5     execute immediate
  6     'alter session set nls_date_format = ''dd/mm/yyyy, hh24:mi''';
  7     insert into t select 'After ' || sysdate from dual;
  8  end;
  9  /

Trigger created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> connect /
Connected.
ops$tkyte@ORA817DEV> select sysdate from dual;

SYSDATE
---------
30-JAN-04

ops$tkyte@ORA817DEV> exit
Disconnected from Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production

Fri Jan 30 08:34:33 EST 2004
(tkyte@aria-dev) /export/home/tkyte
> plus

SQL*Plus: Release 8.1.7.0.0 - Production on Fri Jan 30 08:36:10 2004

(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

ops$tkyte@ORA817DEV> select sysdate from dual;

SYSDATE
-----------------
30/01/2004, 08:36

ops$tkyte@ORA817DEV>

      Rating: 3
RE: Trigger
      after logon  January 30, 2004
Reviewer:
      Marcio  from Brazil
You're right, but it replaces with default DD/MON/YY as soon as you reconnect
inside of sql*plus...

ops$t_mp00@MRP817> exit
Disconnected from Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production
With the Partitioning option
JServer Release 8.1.7.0.0 - Production

c:\migracao\loads>sqlplus /

SQL*Plus: Release 8.1.7.0.0 - Production on Fri Jan 30 18:30:16 2004

(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

ops$t_mp00@MRP817> select sysdate from dual;

SYSDATE
-----------------
30/01/2004, 18:30

1 row selected.

ops$t_mp00@MRP817> connect /
Connected.
ops$t_mp00@MRP817> select sysdate from dual;

SYSDATE
--------
30/01/04

1 row selected.

Is this an expected behave?
FYI - I've commented my login.sql concern ALTER SESSION SET NLS_DATE_FORMAT,
and tried to create trigger with AFTER LOGON ON SCHEMA as well.

          Followup:

looks like a sql plus issue.  when you connect in sqlplus, it doesn't fire off a
new dedicated server.

You'll have to persue this with support at this point (or don't use "connect" i
guess in plus).  It probably won't affect developed applications which don't
disconnect/connect like this.

      Rating: 3
Bytes for Date
      ...  February 20, 2004
Reviewer:  reader

Hi Tom,

I've a basic question. Date has 7 bytes in 8i and 9 bytes in 9i. What is the
additional 2 bytes for? Is it TZM and TZH that count? Please explain.

Thanks in advance.

          Followup:

date has 7 bytes in all releases.

TIMESTAMP has 7 to 11 in 9i and up. (and it would be timezone and/or fractional
seconds consuming the extra bytes)

      Rating:
      5
  February 26, 2004
Reviewer:
      reader
On 9i, using default nls_date_format, why does this happen ?

  1* SELECT TO_CHAR(TO_DATE('26/02/03','DD/MM/YYYY'),'DD/MM/YYYY') FROM DUAL
SQL> /

TO_CHAR(TO
----------
26/02/0003

          Followup:

the nls_date_format does not come into play here since you are being explicit.

TO_DATE('26/02/03','DD/MM/YYYY')

is the same as

TO_DATE('26/02/0003','DD/MM/YYYY')

is the same as

TO_DATE('26/02/3','DD/MM/YYYY')

is the same as

TO_DATE('26/02/003','DD/MM/YYYY')

  1  select to_date( '1/1/3','dd/mm/yyyy'),
  2         to_date( '1/1/03','dd/mm/yyyy'),
  3         to_date( '1/1/003','dd/mm/yyyy'),
  4         to_date( '1/1/0003','dd/mm/yyyy')
  5*  from dual
ops$tkyte@ORA920PC> /

TO_DATE('1/ TO_DATE('1/ TO_DATE('1/ TO_DATE('1/
----------- ----------- ----------- -----------
01-jan-0003 01-jan-0003 01-jan-0003 01-jan-0003

leading zeroes on a number are meaningless.  I assume you are asking "why is it
the year 3".

remember it takes no more than and equally no less than 4 characters to
represent 2003, 1903, 1803 and so on.  03 is the year 3.

February 26, 2004 Jack -- Thanks for the question regarding "Help
      with comparison logic", version 8.1.7.4

      You Asked
I am creating triggers to populate a transaction table.

for each of the fields in the trigger
i have to perform the following logic :  ( logic taken from
requirements )

If old value is NULL and new value is not null OR
old value is different from the new value OR
the old value is set and the new value is NULL then

      Insert record into transaction table

One additional note --- if old value is set and new value is NULL
the set corresponding field in tranaaction record to <a default value>.

My problem:

How can this SQL be written to perform the condition checking
and then determine which condition it was ( which of the OR
conditions ) caused the code to branch into the IF statement to
begin with ....

would appreciate some input .....

      and we said...
decode it:

ops$tkyte@ORA920PC> select newval, oldval,
  2         decode( newval,
  3                 oldval, 'No change',
  4                 NULL, 'New is null, old is not null',
  5                        decode( oldval,
  6                                NULL, 'New is not null, old is null',
  7                                      'Both not null, just not the same' ) )
  8    from t;

    NEWVAL     OLDVAL DECODE(NEWVAL,OLDVAL,'NOCHANGE',
---------- ---------- --------------------------------
                      No change
         1          1 No change
         1          2 Both not null, just not the same
         1            New is not null, old is null
                    1 New is null, old is not null

February 26, 2004 Andrew -- Thanks for the question regarding
      "Using a Collection in A where clause", version 9i

      You Asked
I want to do the following :

  TYPE grp IS TABLE OF NUMBER;
  grpData   grp:= grp(4,5,6,7);

  SELECT *
  FROM       anyTable
  WHERE      NumberColumn IN grpData;

Is this possible ?

Thanks

Andy

      and we said...
ops$tkyte@ORA920PC> create or replace type grp as table of number
  2  /

Type created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> variable x refcursor
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> declare
  2          grpData grp := grp( 4,5,6,7 );
  3  begin
  4          open :x for
  5          select *
  6            from all_users
  7           where user_id in ( select *
  8                from table(cast( grpData as grp ) ) );
  9  end;
 10  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> print x

USERNAME                          USER_ID CREATED
------------------------------ ---------- ---------
SYSTEM                                  5 07-MAR-03

February 26, 2004 -- 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.

      Rating:
      4
Hints  February 16, 2004
Reviewer:  M S
      Reddy  from USA
Hi Tom,
   Can you please tell me what is the use of this HINT.

/*+BYPASS_UJVC*/ .

Thanks,
M S Reddy

          Followup:

bypass updateable join view hint.

not for external use.  internal only.

      Rating:
      5
BYPASS_UJVC  February 26, 2004
Reviewer:
      M S Reddy  from USA
Hi tom,
   I have a question on this hint.
  Do you think using this hint in a query that uses
a dblink would cause any error and bring down the database.

Thanks,
M S Reddy.

          Followup:

if it did, it would be a bug.

however, the most likely solution from support would be "don't use that hint, it
is not documented"

hence, my hesitance to suggest it.

February 26, 2004 -- Thanks for the question regarding
      "V$SESSION_LONGOPS", version 8.1.6

      You Asked
Tom:

when i query v$session_longops, i got the following result:

select username ,sid,serial# from v$session_longops;
SYS              12          1
John             28        438
HP_DBSPI         42         39
HP_DBSPI         42         39
HP_DBSPI         40         74

what should i do if i want to kill john's longops?
Alter system kill session '28,438';
ERROR at line 1:
ORA-00030: User session ID does not exist.

when i query v$session,
select username,sid,serial# from v$session where sid=28;
USERNAME                              SID    SERIAL#
------------------------------ ---------- ----------
mike                                   28       3762

and select count(0) from v$session where username='HP_DBSPI'
return 0.
seems it is totally irrelevant between this v$session and
v$session_longops.

      and we said...
data in v$session_longops hangs out until those slots are needed for reuse.  You
should approach v$session_longops with specific sid,serial#'s -- you do not
query the table by itself.  You can join this to v$session or whatnot.

You should ignore rows in there that are not for current sessions.  You cannot
clear them.
Review & Followup

      Rating: 4
Using
      v$session_longops with large batch posting  January 14, 2004

Reviewer:  Denise  from Virginia, USA
Hi Tom

I know you are out of the country this week hopefully
you'll get to read this whenever you can find time.

I've gone through all the v$session_longops threads
and read the chapters in your book on dbms_app_info as well.

problem:
we have a feature in our third party software that allows
for importing data through a functionality called Lockbox.

When we have large files for importing through this module,
(approx 10,000 rows)...the entire system appears to just
hang. When we have smaller files(100,600,1000 rows) the
function performs with no problems(or so it seems in the
foreground).

I copied the dbms_application_info and showsql scripts
from here(website)and tried to apply them via reading
your instructions in appendix A of your book.
Not sure if I am using them correctly however I am
pasting the results here:
============================================================
SQL> declare
  2   l_owner varchar2(30) default 'GUI';
  3   cnt number default 0;
  4  begin
  5    dbms_application_info.set_client_info('owner= '||l_owner);
  6    for x in(select * from user_jobs where user=l_owner)
  7     Loop
  8       cnt := cnt + 1;
  9       dbms_application_info.set_action('processing row '||cnt);
 10     End Loop;
 11  end;
 12  /
SQL> show errors;
No errors.
SQL> @showsql

USERNAME               SID    SERIAL# PROCESS   STATUS
--------------- ---------- ---------- --------- ----------
GUI                      7        319           ACTIVE
GUI                     16        915 1664:316  ACTIVE
--------------------
GUI(16,915) ospid = 1664:316 program = SQLPLUSW.EXE
Wednesday 10:40  Wednesday 10:42 last et = 0
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$SESSI
--------------------
GUI(7,319) ospid =  program =
Wednesday 10:32  Wednesday 10:42 last et = 526
SELECT  distinct(p.idnumber)  FROM phone ph, salutations s1, salutations s2,
additional_demographics ad, prospect p, address_view a  WHERE p.idnumber !=
:p_id    and p.salutcode = s1.salutcode(+) and p.csalutcode = s2.salutcode(+)
and p.idnumber = ad

USERNAME        MODULE          ACTION          CLIENT_INFO
--------------- --------------- --------------- ------------------------------
GUI(16,915)     01@ showsql.sql                 owner= GUI
SQL> /

USERNAME        MODULE          ACTION          CLIENT_INFO
--------------- --------------- --------------- ------------------------------
GUI(16,915)     SQL*Plus                        owner= GUI
SQL>
============================================================

The session in question (7,319)...doesn't appear to be doing
anything although it says 'ACTIVE'.

I also ran a trace and tkprof, here is the result:

TKPROF: Release 8.1.7.0.0 - Production on Wed Jan 14 11:00:07 2004

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

Trace file: ora00184.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.01          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        1      0.01       0.01          0          0          0           0

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: 12     (recursive depth: 1)
********************************************************************************

BEGIN dbms_session.set_sql_trace(TRUE); END;

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          2          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        1      0.00       0.00          0          2          0           1

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: 12
********************************************************************************

select obj#,type#,ctime,mtime,stime,status,dataobj#,flags,oid$, spare1
from
 obj$ where owner#=:1 and name=:2 and namespace=:3 and(remoteowner=:4 or
  remoteowner is null and :4 is null)and(linkname=:5 or linkname is null and
  :5 is null)and(subname=:6 or subname is null and :6 is null)

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        2      0.00       0.00          0          5          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      0.00       0.00          0          5          0           1

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: SYS   (recursive depth: 1)
********************************************************************************

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        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          4          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          4          0           1

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: SYS   (recursive depth: 1)
********************************************************************************

select /*+ index(idl_sb4$ i_idl_sb41) +*/ piece#,length,piece
from
 idl_sb4$ where obj#=:1 and part=:2 and version=:3 order by piece#

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.00       0.00          1          8          0           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      0.00       0.00          1          8          0           2

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: SYS   (recursive depth: 1)
********************************************************************************

select /*+ index(idl_ub1$ i_idl_ub11) +*/ piece#,length,piece
from
 idl_ub1$ where obj#=:1 and part=:2 and version=:3 order by piece#

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          5          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          1          5          0           1

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: SYS   (recursive depth: 1)
********************************************************************************

select /*+ index(idl_char$ i_idl_char1) +*/ piece#,length,piece
from
 idl_char$ where obj#=:1 and part=:2 and version=:3 order by piece#

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.02          1          5          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.02          1          5          0           1

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: SYS   (recursive depth: 1)
********************************************************************************

select /*+ index(idl_ub2$ i_idl_ub21) +*/ piece#,length,piece
from
 idl_ub2$ where obj#=:1 and part=:2 and version=:3 order by piece#

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          7          0           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          1          7          0           2

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: SYS   (recursive depth: 1)
********************************************************************************

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          0          2          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          2          0           0

Misses in library cache during parse: 0
Optimizer goal: RULE
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.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: 0
Optimizer goal: RULE
Parsing user id: SYS   (recursive depth: 1)
********************************************************************************

declare
 l_owner varchar2(30) default 'GUI';
 cnt number default 0;
begin
  dbms_application_info.set_client_info('owner= '||l_owner);
  for x in(select * from all_objects where user=l_owner)
   Loop
     cnt := cnt + 1;
     dbms_application_info.set_action('processing row '||cnt);
   End Loop;
end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.05       0.06          0          0          0           0
Execute      1     24.74      44.76          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3     24.79      44.82          0          0          0           1

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: 12
********************************************************************************

select node,owner,name
from
 syn$ where 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: 0
Optimizer goal: RULE
Parsing user id: SYS   (recursive depth: 2)
********************************************************************************

SELECT *
FROM
 ALL_OBJECTS  WHERE USER = :b1

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    36571     14.18      14.36         53     602663          8       36570
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    36575     14.18      14.36         53     602663          8       36570

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: 12     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
  34895  FILTER
  35015   FILTER
  35014    NESTED LOOPS
     63     TABLE ACCESS FULL USER$
  35014     TABLE ACCESS BY INDEX ROWID OBJ$
  35076      INDEX RANGE SCAN (object id 34)
      1   FIXED TABLE FULL X$KZSPR
      0   FIXED TABLE FULL X$KZSPR
      0   FIXED TABLE FULL X$KZSPR
      0   FIXED TABLE FULL X$KZSPR
      0   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
  21641   NESTED LOOPS
 252189    FIXED TABLE FULL X$KZSRO
 238861    TABLE ACCESS BY INDEX ROWID OBJAUTH$
 238861     INDEX RANGE SCAN (object id 101)
   2993   TABLE ACCESS BY INDEX ROWID IND$
   3008    INDEX UNIQUE SCAN (object id 36)

********************************************************************************

select distinct(-privilege#),nvl(option$,0)
from
 sysauth$ where grantee#=:1 and privilege#<0

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        9      0.00       0.00          0          0          0           0
Execute      9      0.00       0.00          0          0          0           0
Fetch      157      0.00       0.00          0         19          0         148
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      175      0.00       0.00          0         19          0         148

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: SYS   (recursive depth: 2)

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.05       0.06          0          0          0           0
Execute      2     24.74      44.76          0          2          0           2
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4     24.79      44.82          0          2          0           2

Misses in library cache during parse: 0

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       20      0.00       0.00          0          0          0           0
Execute     22      0.01       0.01          0          0          0           0
Fetch    36744     14.18      14.38         57     602724          8       36728
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    36786     14.19      14.39         57     602724          8       36728

Misses in library cache during parse: 0

    6  user  SQL statements in session.
   18  internal SQL statements in session.
   24  SQL statements in session.
********************************************************************************
Trace file: ora00184.trc
Trace file compatibility: 8.00.04
Sort options: default

       1  session in tracefile.
       6  user  SQL statements in trace file.
      18  internal SQL statements in trace file.
      24  SQL statements in trace file.
      14  unique SQL statements in trace file.
   73525  lines in trace file.
============================================================
I know this is lengthy and you are very busy...whenever
you get a chance to look this over and help me decipher
what this output means that would be great and extremely
helpful.

take care..

          Followup:

the dbms_application_info calls sort of need to be in the LONG running process.
not in a loop that loops over i and does nothing...

      Rating: 2
thanks
      Tom...  January 14, 2004
Reviewer:  Denise
       from Virginia, USA
do you mean the code needs to be inserted within the package
body that executes the Lockbox procedure or process?...
trying to get the hang of this stuff.

thanks for taking the time to respond..

          Followup:

yes.  exactly -- if you want to instrument the code, you need to instrument the
CODE.

      Rating: 5
long running
      queries.  February 26, 2004
Reviewer:  Wor
       from USA
Hi Tom,
I ran the following SQL.

select * from v$sql,v$session_longops where sql_address = address and
sql_hash_value = hash_value order by totalwork desc
Snap shot of results..

For some SQL's
TOTALWORK = 15038520810882992217
START_TIME = 1-Jan-1988 12:00:01 AM
MESSAGE = : !šp : 15038520810882992217 out of 15038520810882992217 ø done
TIME_REMAINING = 0
etc..

1) What is this TOTALWORK and why is this so high ? Is some thing wrong with the
query.
2) START_TIME is 1988 , the project was not een started at that time. For some
SQL's I see start_time as 2021. Who sets this parameter.
3)We rebooted Database server recently than why it is still showing dated 1988
SQL's.
4) Am i running the correct query to find out long running queries.

Thanks in advance.

          Followup:

only entries that have a valid SID mean anything here (I usually use
time_remaining > 0 if I want to see all currently active ones but the view is
meant to be looked up by SID -- to monitor a sid)

the other entries are either

a) leftover from some previous run
b) totally uninitialized garbage

to find "long running stuff in current sessions", use last_call_et in v$session
along with "status = 'ACTIVE'"  last_call_et (with timed statistics=true) will
tell you how long they've been doing whatever they last submitted.  You can
search this site for showsql.sql to see how to marry up v$session with v$sql to
see what SQL they are executing and last_call_et will tell you how long they've
been doing it.

February 26, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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://download-west.oracle.com/docs/cd/B10501_01/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.

      Rating: 5
what can this
      be  February 25, 2004
Reviewer:
      A reader
Hi

I was given a query to see why it was performing slow. The database uses
cursor_sharing = FORCE. The query has bind variables and it performs quite well,
if I put literals instead of nested loop join it does a two index hash join and
performs poorly. The funny thing is we dont generate histograms how can use of
literals and bind variable affect plan (and using cursor_sharing!)???

          Followup:

if you put literals in with cursor_sharing=force, it'll bind -- soooo -- catch
22.

please give me a "for example" with the relevant tkprofs to show what you are
saying...

      Rating: 5
bind var vs
      histograms  February 26, 2004
Reviewer:
      Connor  from Aus
Even for bind variables, histograms could have an impact on the optimizer.  eg

SQL> create table t
  2  as select rownum x, trunc(dbms_random.value(1,100)) y
  3  from all_Objects;

Table created.

SQL> analyze table t compute statistics;

Table analyzed.

SQL> select column_name, density
  2  from user_tab_columns
  3  where table_name = 'T';

COLUMN_NAME                       DENSITY
------------------------------ ----------
X                              .000031763
Y                               .01010101

SQL> analyze table t compute statistics for table for all columns;

Table analyzed.

SQL> select column_name, density
  2  from user_tab_columns
  3  where table_name = 'T';

COLUMN_NAME                       DENSITY
------------------------------ ----------
X                              .000031763
Y                              .006666667

I would presume that different density could possibly yield different plans....

Cheers
Connor

          Followup:

Interestingly -- (as an aside) both of the examples above used histograms, just
of differing sizes.  It won't be the density in as much as "no histograms" vs
"some histograms".  consider:

ops$tkyte@ORA920PC> variable x number
ops$tkyte@ORA920PC> exec :x := 55;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create table t
  2    as select rownum x, decode( mod(rownum,10), 0, 25, 9, 75,
trunc(dbms_random.value(1,100))) y
  3    from all_Objects;

Table created.

ops$tkyte@ORA920PC> create index t_idx1 on t(x);

Index created.

ops$tkyte@ORA920PC> create index t_idx2 on t(y);

Index created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> analyze table t compute statistics for table for all
indexes;

Table analyzed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select column_name, density,
  2         (select count(*)
  3            from user_tab_histograms
  4           where table_name = 'T'
  5             and column_name = x.column_name) hist_size
  6    from user_tab_columns x
  7    where table_name = 'T';

COLUMN_NAME                       DENSITY  HIST_SIZE
------------------------------ ---------- ----------
X                                                  0
Y                                                  0

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

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=303 Bytes=3333)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=303 Bytes=3333)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX1' (NON-UNIQUE) (Cost=1 Card=121)

ops$tkyte@ORA920PC> select * from t where y = :x;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=10 Card=303 Bytes=3333)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=10 Card=303 Bytes=3333)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX2' (NON-UNIQUE) (Cost=1 Card=121)

ops$tkyte@ORA920PC> set autotrace off
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select column_name, density,
  2         (select count(*)
  3            from user_tab_histograms
  4           where table_name = 'T'
  5             and column_name = x.column_name) hist_size
  6    from user_tab_columns x
  7    where table_name = 'T';

COLUMN_NAME                       DENSITY  HIST_SIZE
------------------------------ ---------- ----------
X                              .000033057          2
Y                               .01010101          2

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

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=6)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=1 Bytes=6)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX1' (NON-UNIQUE) (Cost=1 Card=1)

ops$tkyte@ORA920PC> select * from t where y = :x;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=11 Card=306 Bytes=1836)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=11 Card=306 Bytes=1836)

but yes, that is yet another way that histograms can affect "even with
binds"...

      Rating: 5
so "compute
      statistics" is equivalent to ...  February 26, 2004

Reviewer:  Alberto Dell'Era  from Milan, Italy

analyze table t compute statistics
for table
for all columns SIZE 1
for all indexes;

"SIZE 1" being the non-obvious part.

correct ?

          Followup:

that is correct.

February 27, 2004 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 );

      Rating: 5
ORs and
      Rownum  January 20, 2004
Reviewer:
      A reader
Hi

We have a very simple query in 8.1.7.4

select count(account_id)
from account
where holder_id = :b
   or payer_id = :b
  and rownum = 1

This runs lightling fast in RULE because it transforms OR into UNION ALL (both
holder_id and payer_id are indexed)

If I use CBO it full scans the table which has 1 million of rows. Surprisingly
if I modify the query to

select count(account_id)
from account
where (holder_id = :b
   or  payer_id = :b)
  and rownum = 1

It transofmrs this into UNION ALL and runs very fast too!

Is this a bug?

          Followup:

what happens when you tell it "the first rows are what I'm interested in"

use /*+ FIRST_ROWS */ and see what happens.

      Rating: 3
'OR' plus 'AND'
      with no brackets = 'CONFUSION'  January 21, 2004

Reviewer:  Gary  from Sydney, Aus
select count(account_id)
from account
where holder_id = :b
   or payer_id = :b
  and rownum = 1

Given that the query is probably 'wrong' (ie doesn't do anything that can
logically be wanted) then it is hardly surprising that it confuses the
optimizer.

Are you asking for where
1) holder_id is 'x' or (payer_id is 'y' and rownum =1), or
2) (holder_id is 'x' or payer_id is 'y') and rownum =1

      Rating: 5
ORs and Rownum
        January 21, 2004
Reviewer:  A reader

Hi

I used FIRST_ROWS but no luck, it still uses FTS.

I copied the table to 9i and it runs ok, sounds a bug in 8.1.7.4?

          Followup:

if there are any bugs here, it sounds like a bug in the RBO actually after
reviewing the above comment :)

  1  select * from all_users
  2* where 1=1 or 1=0 and rownum=1
ops$tkyte@ORA9IR2> /

USERNAME                          USER_ID CREATED
------------------------------ ---------- ---------
SYS                                     0 12-MAY-02
SYSTEM                                  5 12-MAY-02
OUTLN                                  11 12-MAY-02
DBSNMP                                 19 12-MAY-02
.....

your use of or is in fact "improper".  use the parens and see what happens -- it
totally changes the meaning of the query.

You seem to want to find "the first one" and count it.  But, your query is just
like:

select count(account_id)
  from (select rowid, account_id from t where holder_id = :b
        UNION ALL
        select rowid, account_id from t where payer_id = :b and rownum = 1)
/

      Rating: 5
but then why it
      runs fast in 9i?  January 21, 2004
Reviewer:
      A reader
Hi

If you say it´s a RBO bug why it runs fast in 9i and not 8i?

          Followup:

fix your sql please, put the parens in and then lets talk.

      Rating: 5
and look
      this  January 21, 2004
Reviewer:
      A reader
please look this

SQL> var b1 NUMBER
SQL> exec :b1:=2438072

PL/SQL procedure successfully completed.

SQL> SELECT COUNT (bill_scope_id)
  2    FROM swb.om_bill_scope_inst
  3   WHERE titular_id = :b1 OR payer_id = :b1 ;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=10)
   1    0   SORT (AGGREGATE)
   2    1     CONCATENATION
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'OM_BILL_SCOPE_INST' (Cost=1
Card=1 Bytes=10)
   4    3         INDEX (RANGE SCAN) OF 'RL_BILL_FK10' (NON-UNIQUE) (Cost=3
Card=1)
   5    2       TABLE ACCESS (BY INDEX ROWID) OF 'OM_BILL_SCOPE_INST' (Cost=1
Card=1 Bytes=10)
   6    5         INDEX (RANGE SCAN) OF 'RL_BILL_FK' (NON-UNIQUE) (Cost=3
Card=1)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          8  consistent gets
          0  physical reads
          0  redo size
        307  bytes sent via SQL*Net to client
        358  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          7  sorts (memory)
          0  sorts (disk)
          1  rows processed

I dont really care if there is rownum or not or the order of operator
precedence, the problem here is with rownum it choose FTS but without or with
brackets it choose the proper plan... Looks like a bug...!!!

          Followup:

YOU HAVE A BUG when you add "and rownum = 1"

Pleaes, fix your query and then lets talk -- ok.

      Rating:
      5
rownum  January 21, 2004
Reviewer:
      Potkin Nemat  from London
If I really really want to select a row based on rownum, I select it from an
inline view or subquery. Rownum must be aliased in the query though.

SELECT *
FROM
(select ROWNUM r, i.* from tablename i)
WHERE R = 5

          Followup:

well, that certainly gets you a random row for sure.  not very meaningful -- the
"5th row" from an unordered result set.

the right way for this will be:

select *
  from ( select a.*, rownum r
           from ( your_query_here with ORDER BY)
          where rownum <= :x )
 where r >= :x;

that'll be better than "where r=5"

      Rating: 4
Views and
      ROWNUM  February 26, 2004
Reviewer:  Mark
       from Cambridge, UK
Hi Tom,

I have a performance problem (wrong Optimizer choice) when using rownum in a
view in Oracle 8.1.7.4.

The view (see below, most columns left out for readability) combines the actual
table called dbentry with its audit table dbentry_audit.
The idea of the view is to find the row as it was in a particular point in time,
that is the most recent row before the pck_release.release_time.

So far for my shaky explanation; I just want to show you what's happening now.
First a view here without any ROWNUM, it works okay. See execution plan.

CREATE OR REPLACE VIEW DBENTRY_VW
AS
     SELECT  dbentryid, timestamp
     FROM     datalib.dbentry
     WHERE    timestamp < pck_release.release_time
     UNION
     SELECT   dbentryid, timestamp
     FROM     datalib.dbentry_audit
     WHERE    timestamp < pck_release.release_time
     ORDER BY timestamp DESC
/

View created.

select dbentryid, to_char(timestamp, 'dd-mon-yyyy hh24:mi:ss')
from dbentry_vw
where dbentryid =  67846230
/

Execution Plan
----------------------------------------------------------
SELECT STATEMENT Optimizer=CHOOSE (Cost=22 Card=2 Bytes=44)
  VIEW OF 'DBENTRY_VW' (Cost=22 Card=2 Bytes=44)
    SORT (UNIQUE) (Cost=16 Card=2 Bytes=28)
      UNION-ALL
        TABLE ACCESS (BY INDEX ROWID) OF 'DBENTRY' (Cost=1 Card=1 Bytes=13)
          INDEX (UNIQUE SCAN) OF 'DBENTRY_PKEY' (UNIQUE) (Cost=3 Card=1)
        TABLE ACCESS (BY INDEX ROWID) OF 'DBENTRY_AUDIT' (Cost=1 Card=1
Bytes=15)
          INDEX (RANGE SCAN) OF 'DBENTRY_AUDIT_DBENTRYID' (NON-UNIQUE) (Cost=4
Card=1)

 DBENTRYID TO_CHAR(TIMESTAMP,'D
---------- --------------------
  67846230 25-feb-2004 16:33:30
  67846230 01-sep-2000 23:30:13
  67846230 26-may-2000 03:01:48
  67846230 25-may-2000 13:35:00
  67846230 27-apr-2000 16:09:42

....

Now what I want is the top row, not five rows. So I alter the view a bit, see
below.
Next thing you know, the plan is completely changed. The full table scan is
unacceptable because these tables are huge. The statistics are up to date.

It is the View messing it up here. Because if I use the first view (the fast
one) and would query that with 'where rownum=1' it's fast; but if I make
rownum=1 part of the view itself, it fails.

Help ??

CREATE OR REPLACE VIEW DBENTRY_VW
AS
 SELECT * FROM (
     SELECT  dbentryid, timestamp
     FROM     datalib.dbentry
     WHERE    timestamp < pck_release.release_time
     UNION
     SELECT   dbentryid, timestamp
     FROM     datalib.dbentry_audit
     WHERE    timestamp < pck_release.release_time
     ORDER BY timestamp DESC
 )
 WHERE ROWNUM =1
/

View created.

select dbentryid, to_char(timestamp, 'dd-mon-yyyy hh24:mi:ss')
from dbentry_vw
where dbentryid =  67846230
/

Execution Plan
----------------------------------------------------------
SELECT STATEMENT Optimizer=CHOOSE (Cost=583111 Card=5461380 Bytes=120150360)
  VIEW OF 'DBENTRY_VW' (Cost=583111 Card=5461380 Bytes=120150360)
    COUNT (STOPKEY)
      VIEW (Cost=583111 Card=5461380 Bytes=120150360)
        SORT (UNIQUE STOPKEY) (Cost=507841 Card=5461380 Bytes=78342610)
          UNION-ALL
            TABLE ACCESS (BY INDEX ROWID) OF 'DBENTRY' (Cost=4715 Card=1789045
Bytes=23257585)
              INDEX (RANGE SCAN) OF 'I_DBENTRY_TIMESTAMP' (NON-UNIQUE)
(Cost=4688 Card=1789045)
            TABLE ACCESS (FULL) OF 'DBENTRY_AUDIT' (Cost=429674 Card=3672335
Bytes=55085025)

          Followup:

what is Pck_release.release_time?  where does it come from.

      Rating: 3
More details on
      last question  February 26, 2004
Reviewer:
      Mark
It's just a timestamp in a package. See below.

PACKAGE
pck_release
AS
  timestamp DATE := sysdate;
  FUNCTION release_time
  RETURN date;
END;

PACKAGE BODY
pck_release
AS
  FUNCTION release_time
  RETURN date
  IS
   BEGIN
      RETURN pck_release.timestamp;
   END;
END;

At first I had a single row table with that timestamp as part of the query but
that did not make any difference in the optimizer plan.  It actually looked
worse.

Likse so :
create table release ( time date);
insert into release values (sysdate);

select dbentryid, to_char(timestamp, 'dd-mon-yyyy hh24:mi:ss')
from dbentry_vw
where dbentryid =  67846230

 CREATE OR REPLACE VIEW
 dbentry_vw
 AS
 SELECT DBENTRYID,TIMESTAMP
 FROM (
      SELECT   dbentryid, timestamp
      FROM     datalib.dbentry
              ,release
      WHERE    timestamp < time
      UNION
      SELECT   dbentryid, timestamp
      FROM     datalib.dbentry_audit
              ,release
      WHERE    timestamp < time
      ORDER BY timestamp DESC
 )
 WHERE ROWNUM =1

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=7034048 Card=114688979
Bytes=2523157538)
   1    0   VIEW OF 'DBENTRY_VW' (Cost=7034048 Card=114688979 Bytes=2523157538)
   2    1     COUNT (STOPKEY)
   3    2       VIEW (Cost=7034048 Card=114688979 Bytes=2523157538)
   4    3         SORT (UNIQUE STOPKEY) (Cost=5173213 Card=114688979
Bytes=2677395608)
   5    4           UNION-ALL
   6    5             MERGE JOIN* (Cost=424209 Card=37569944 Bytes=826538768)
                           :Q192870

                              01

   7    6               SORT* (JOIN) (Cost=8 Card=21 Bytes=189)
                           :Q192870

                              01

   8    7                 TABLE ACCESS* (FULL) OF 'RELEASE' (Cost=1 Card=21
Bytes=189)                      :Q192870

                              00

   9    6               SORT* (JOIN) (Cost=424201 Card=35780899 Bytes=465151687)
                           :Q192870

                              01

  10    9                 TABLE ACCESS* (FULL) OF 'DBENTRY' (Cost=71207
Card=35780899 Bytes=465151687)         :Q192870

                              01

  11    5             MERGE JOIN (Cost=1964276 Card=77119035 Bytes=1850856840)
  12   11               SORT (JOIN) (Cost=8 Card=21 Bytes=189)
  13   12                 TABLE ACCESS (FULL) OF 'RELEASE' (Cost=1 Card=21
Bytes=189)
  14   11               SORT (JOIN) (Cost=1964268 Card=73446700
Bytes=1101700500)
  15   14                 TABLE ACCESS (FULL) OF 'DBENTRY_AUDIT' (Cost=429674
Card=73446700 Bytes=1101700500)

   6 PARALLEL_TO_SERIAL            SELECT /*+ ORDERED NO_EXPAND USE_MERGE(A2) *
                                   / A1.C0,A2.C0,A1.C1,A2.C1,A2.C2 FROM :Q19287
                                   000 A1,(SELECT /*+ NO_EXPAND ROWID(A3) */ A3
                                   ."TIMESTAMP" C0,A3."DBENTRYID" C1,A3."TIMEST
                                   AMP" C2 FROM "DATALIB"."DBENTRY" PX_GRANULE(
                                   0, BLOCK_RANGE, DYNAMIC)  A3) A2 WHERE A2.C0
                                   <A1.C0

   7 PARALLEL_COMBINED_WITH_PARENT
   8 PARALLEL_FROM_SERIAL
   9 PARALLEL_COMBINED_WITH_PARENT
  10 PARALLEL_COMBINED_WITH_PARENT

          Followup:

what does:

 CREATE OR REPLACE VIEW
 dbentry_vw
 AS
 SELECT DBENTRYID,TIMESTAMP
 FROM (
      select * from
      (SELECT   /*+ FIRST_ROWS */ dbentryid, timestamp
      FROM     datalib.dbentry
              ,release
      WHERE    timestamp < time
      order by timestamp desc)
      where rownum = 1
      UNION
      select * from
      (SELECT  /*+ FIRST_ROWS */ dbentryid, timestamp
      FROM     datalib.dbentry_audit
              ,release
      WHERE    timestamp < time
      ORDER BY timestamp DESC)
      where rownum = 1
      order by timestamp desc
 )
 WHERE ROWNUM =1

do?

      Rating: 3
still same
      problem  February 27, 2004
Reviewer:  Mark
       from Cambs, UK
That view - for some reason gives an error.

CREATE OR REPLACE VIEW
 dbentry_vw
 AS
 SELECT DBENTRYID,TIMESTAMP
 FROM (
      select * from
      (SELECT   /*+ FIRST_ROWS */ dbentryid, timestamp
      FROM     datalib.dbentry
              ,release
      WHERE    timestamp < time
      order by timestamp desc)
      where rownum = 1
      UNION
      select * from
      (SELECT  /*+ FIRST_ROWS */ dbentryid, timestamp
      FROM     datalib.dbentry_audit
              ,release
      WHERE    timestamp < time
      ORDER BY timestamp DESC)
      where rownum = 1
      order by timestamp desc
 )
 WHERE ROWNUM =1

/

order by timestamp desc
               *
ERROR at line 21:
ORA-00904: invalid column name

When done without that line (which was redundant), the view works but first_rows
did not give a better result.

CREATE OR REPLACE VIEW
 dbentry_vw
 AS
 SELECT DBENTRYID,TIMESTAMP
 FROM (
      select * from
      (SELECT   /*+ FIRST_ROWS */ dbentryid, timestamp
      FROM     datalib.dbentry
              ,release
      WHERE    timestamp < time
      order by timestamp desc)
      where rownum = 1
      UNION
      select * from
      (SELECT  /*+ FIRST_ROWS */ dbentryid, timestamp
      FROM     datalib.dbentry_audit
              ,release
      WHERE    timestamp < time
      ORDER BY timestamp DESC)
      where rownum = 1
      --order by timestamp desc
 )
 WHERE ROWNUM =1

/

View created.

select dbentryid, to_char(timestamp, 'dd-mon-yyyy hh24:mi:ss')
from dbentry_vw
where dbentryid =  67846230
/

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=7944072
Card=114688979 Bytes=2523157538)
   1    0   VIEW OF 'DBENTRY_VW' (Cost=7944072 Card=114688979 Bytes=2523157538)
   2    1     COUNT (STOPKEY)
   3    2       VIEW (Cost=7944072 Card=114688979 Bytes=2523157538)
   4    3         SORT (UNIQUE STOPKEY) (Cost=7944072 Card=114688979
Bytes=2523157538)
   5    4           UNION-ALL
   6    5             COUNT (STOPKEY)
   7    6               VIEW (Cost=1106323 Card=37569944 Bytes=826538768)
   8    7                 SORT (ORDER BY STOPKEY) (Cost=1106323 Card=37569944
Bytes=826538768)
   9    8                   NESTED LOOPS (Cost=529695 Card=37569944
Bytes=826538768)
  10    9                     TABLE ACCESS (FULL) OF 'RELEASE' (Cost=1 Card=21
Bytes=189)
  11    9                     TABLE ACCESS (BY INDEX ROWID) OF 'DBENTRY'
(Cost=25224 Card=35780899 Bytes=46515
          1687)

  12   11                       INDEX (RANGE SCAN) OF 'I_DBENTRY_TIMESTAMP'
(NON-UNIQUE) (Cost=16356 Card=3578
          0899)

  13    5             COUNT (STOPKEY)
  14   13               VIEW (Cost=4172376 Card=77119035 Bytes=1696618770)
  15   14                 SORT (ORDER BY STOPKEY) (Cost=4172376 Card=77119035
Bytes=1850856840)
  16   15                   MERGE JOIN (Cost=1964276 Card=77119035
Bytes=1850856840)
  17   16                     SORT (JOIN) (Cost=8 Card=21 Bytes=189)
  18   17                       TABLE ACCESS (FULL) OF 'RELEASE' (Cost=1 Card=21
Bytes=189)
  19   16                     SORT (JOIN) (Cost=1964268 Card=73446700
Bytes=1101700500)
  20   19                       TABLE ACCESS (FULL) OF 'DBENTRY_AUDIT'
(Cost=429674 Card=73446700 Bytes=110170
          0500)

          Followup:

I messed up on that anyway.

but, then again -- so did you!!!

In looking at this more closely:

CREATE OR REPLACE VIEW DBENTRY_VW
AS
 SELECT * FROM (
     SELECT  dbentryid, timestamp
     FROM     datalib.dbentry
     WHERE    timestamp < pck_release.release_time
     UNION
     SELECT   dbentryid, timestamp
     FROM     datalib.dbentry_audit
     WHERE    timestamp < pck_release.release_time
     ORDER BY timestamp DESC
 )
 WHERE ROWNUM =1
/
View created.

That view will get the maximum DBEntryId all right -- but it'll be a "constant
one".  You see the order by, union and then WHERE ROWNUM=1 is applied AND
THEN:

select dbentryid, to_char(timestamp, 'dd-mon-yyyy hh24:mi:ss')
from dbentry_vw
where dbentryid =  67846230
/

"where dbentryid = xxxxxx" would be applied -- AFTER the where rownum.  You
cannot incorporate into your view the where rownum=1.  That happens FIRST and
then the predicate in your query is applied.  Consider:

ops$tkyte@ORA920PC> create table dbentry( dbentryid number, timestamp date );

Table created.

ops$tkyte@ORA920PC> create table dbentry_audit( dbentryid number, timestamp date
);

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert into dbentry values ( 67846230, sysdate-1);

1 row created.

ops$tkyte@ORA920PC> insert into dbentry_audit values ( 67846230, sysdate-1);

1 row created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert into dbentry values ( 123, sysdate-1/2);

1 row created.

ops$tkyte@ORA920PC> insert into dbentry_audit values ( 123, sysdate-1/2);

1 row created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> CREATE OR REPLACE VIEW DBENTRY_VW
  2  AS
  3   SELECT * FROM (
  4       SELECT  dbentryid, timestamp
  5       FROM     dbentry
  6       WHERE    timestamp < sysdate
  7       UNION
  8       SELECT   dbentryid, timestamp
  9       FROM     dbentry_audit
 10       WHERE    timestamp < sysdate
 11       ORDER BY timestamp DESC
 12   )
 13   WHERE ROWNUM =1
 14  /

View created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select dbentryid, to_char(timestamp, 'dd-mon-yyyy
hh24:mi:ss')
  2  from dbentry_vw
  3  where dbentryid =  67846230
  4  /

no rows selected

ops$tkyte@ORA920PC> select dbentryid, to_char(timestamp, 'dd-mon-yyyy
hh24:mi:ss')
  2  from dbentry_vw
  3  where dbentryid =  123
  4  /

 DBENTRYID TO_CHAR(TIMESTAMP,'D
---------- --------------------
       123 26-feb-2004 19:35:17

you cannot do the rownum part in your view, not possible unless you want to
parameterize the query against dbentry with dbentryid just like you did with the
timestamp column.

      Rating: 4
Doh
      !  February 27, 2004
Reviewer:  Mark
       from Cambridge, UK
Haha, I did mess it up eh ? The query ran so long I didn't let it finish to see
the view was incorrect in the first place. Next time I'll be more patient.

I don't see how to do it in a view now. But thanks for the help !

February 27, 2004 Sathish -- Thanks for the question regarding
      "user_tables ", version 8.1.7

      You Asked
CREATE TABLESPACE MID_DATA11
    DATAFILE 'K:\ORACLEDATAFILES\MD03\MID\DATA\DATA11.DBF' SIZE 409600K
AUTOEXTEND OFF
EXTENT MANAGEMENT LOCAL UNIFORM SIZE 5120K     ONLINE    PERMANENT;

So each extent is 5120K and the db block size being 8k , each extent is 640
blocks .

Now i create a table

SQL> create table emp(empno varchar2(10)) tablespace mid_data11 ;

Table created.
SQL> select segment_name , extent_id , blocks , bytes/1024/1024 "MBytes"
  2  from dba_extents
  3  where segment_name='EMP' ;

EMP
         0        640          5

It just created 1 extent of 640 blocks .

I imported a table from 7.3.4 db called "sub_groups" .

SQL> r
  1  select segment_name , extent_id , blocks , bytes/1024/1024 "MBytes"
  2  from dba_extents
  3* where segment_name='SUB_GROUPS'

SUB_GROUPS
         0        640          5

( Truncated to make it shorter )

SUB_GROUPS
        39        640          5

40 rows selected.

SQL> analyze table sub_groups compute statistics ;

Table analyzed.

Elapsed: 00:00:00.61
SQL> select blocks , empty_blocks from user_tables
  2  where table_name='SUB_GROUPS';

        15        25584

My question is why am i seeing so many empty blocks . When i did the import if
the data could fit into 15 blocks then all it would need is one extent .
why did it create 40 extents ? where am i conceptually wrong ?
where else do i have to look ?

Thanks
Sathish .

      and we said...
Look at that import -- it'll have an INITIAL extent size for the table.

If you have a locally managed tablespace with 1meg extents and create a table
with an INITIAL of 10meg, it'll oblige you by allocating 10 1meg extents.

You might have used "compress=y" on the export (which is deadly).  You should
use compresss=N so that exp does not add up all of the extents in the table and
puts them as the initial.

followup to comments 1-3 below

It will use the initial, next, minextents, pctincrease and allocate that much
space.  Eg: you have initial=1m, next = 1m, pctincrease = 50, minextents = 5.
You have an LMT with 0.5m extents.

Oracle will compute:

1+1+(1*1.5)+(1*1.5)*1.5+(1*1.5)*1.5*1.5
---------------------------------------
                                  9.125

round it up to 9.5 and allocate that much space for you.  From then on in -- its
0.5 extents.

By "ignore" -- it does not use the initial/next sizes when creating individual
extents.  It will preallocate as much space as you ask for however.

followup to comment 4 below

you inserted 10,000 number pairs into a table.  That will be much less then 5m
of data.

That table is:

ops$tkyte@ORA8I.WORLD> create table t ( x int, y int ) tablespace system storage
( initial 8k next 8k )
  2  /

Table created.

ops$tkyte@ORA8I.WORLD> insert into t select rownum, rownum from all_objects
where rownum <= 10000;

10000 rows created.

ops$tkyte@ORA8I.WORLD> exec show_space('T');
Free Blocks.............................1
Total Blocks............................19
Total Bytes.............................155648
Unused Blocks...........................0
Unused Bytes............................0
Last Used Ext FileId....................1
Last Used Ext BlockId...................38248
Last Used Block.........................1

PL/SQL procedure successfully completed.

(search for showspace on my site for that procedure if you want it)...  It takes
19 8k blocks -- about 152k.

When you exported, export would have either set initial = 152 (if compress=y) or
8 (if compress=n).

Upon import, with a 5m initial extent, you would get 1 extent.  You would have
had to of generated at least 5.00000001m of data to get more extents (or used a
really big MINEXTENTS)

followup to comment 5 below

compress in exp means "should I compress all of your currently allocated extents
into a single intial extent".  When you say compress=y, exp will sum up all of
the currently allocate extents and that will become your INITIAL.  Consider:

ops$tkyte@ORA8I.WORLD> create table t ( x int ) tablespace system storage (
initial 8k next 8k minextents 10 );

Table created.

ops$tkyte@ORA8I.WORLD> !exp userid=/ tables=t

Export: Release 8.1.6.2.0 - Production on Wed Jun 27 20:26:45 2001
...
About to export specified tables via Conventional Path ...
. . exporting table                              T          0 rows exported
Export terminated successfully without warnings.

ops$tkyte@ORA8I.WORLD> !imp userid=/ full=y show=y

Import: Release 8.1.6.2.0 - Production on Wed Jun 27 20:26:58 2001
...
. importing OPS$TKYTE's objects into OPS$TKYTE
 "CREATE TABLE "T" ("X" NUMBER(*,0))  PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRA"
 "NS 255 LOGGING STORAGE(INITIAL 90112
  NEXT 8192 MINEXTENTS 10 MAXEXTENTS 505"
 " PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT) TABLESPAC"
 "E "SYSTEM""
. . skipping table "T"

Import terminated successfully without warnings.

ops$tkyte@ORA8I.WORLD> drop table t;

Table dropped.

ops$tkyte@ORA8I.WORLD> create table t ( x int ) tablespace system storage (
initial 8k next 8k );

Table created.

ops$tkyte@ORA8I.WORLD> !exp userid=/ tables=t

Export: Release 8.1.6.2.0 - Production on Wed Jun 27 20:27:27 2001
....
About to export specified tables via Conventional Path ...
. . exporting table                              T          0 rows exported
Export terminated successfully without warnings.

ops$tkyte@ORA8I.WORLD> !imp userid=/ full=y show=y

Import: Release 8.1.6.2.0 - Production on Wed Jun 27 20:27:31 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

Export file created by EXPORT:V08.01.06 via conventional path
import done in WE8ISO8859P1 character set and US7ASCII NCHAR character set
. importing OPS$TKYTE's objects into OPS$TKYTE
 "CREATE TABLE "T" ("X" NUMBER(*,0))  PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRA"
 "NS 255 LOGGING STORAGE(INITIAL 16384
 NEXT 8192 MINEXTENTS 1 MAXEXTENTS 505 "
 "PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE"
 " "SYSTEM""
. . skipping table "T"

Import terminated successfully without warnings.

See the effect on the initial extent when using the compress=y (default) setting
- the minextents of the first table create get added to the initial extent
Review &
      Followup

      Rating:
      5
Thanks  June 27, 2001
Reviewer:
      Sathish  from Richmond , VA
It was a test Export/Import . I left compress at default .
I will make sure i set compress=N when i do the final
export . I am also happy i noticed it now during test .

      Rating: 5
On Second
      thoughts ......................  June 27, 2001

Reviewer:  Sathish  from Richmond , VA
1 - My Import log just says the number of rows imported .

2 - When i use compress=N while exporting ,
Suppose the table in 7.3.4 DB was created with an
initial extent of 3M and next extent of 3M ,
Import will still try to create 3 extents of 1M on
the Locally Managed tablespace that i created .

3- I always thought Locally Managed tablespaces ignored
the storage parameters given during create table statement .

      Rating: 3
From the manual
        June 27, 2001
Reviewer:  Sathish
       from Richmond, VA
Storage Parameters in Locally Managed Tablespaces
When you allocate a locally managed tablespace, you cannot specify default
storage parameters or minimum extent size. If AUTOALLOCATE is specified, the
tablespace is system managed with the smallest extent size being 64K. If UNIFORM
SIZE is specified, then the tablespace is managed with uniform size extents of
the specified SIZE. The default SIZE is 1M.

When you allocate segments in a locally managed tablespace, the storage clause
is interpreted differently than for dictionary-managed tablespaces. When an
object is created in a locally managed tablespace, Oracle uses its INITIAL,
NEXT, and MINEXTENTS parameters to calculate the initial size of the object's
segment

So Tom the storage clause is not ignored but interpreted
differently .
Can you tell me how it is interpreted ?

Thanks !
Sathish

      Rating:
      3
  June 27, 2001
Reviewer:
      A reader
I tested this scenario, but didn't get it.
what i did is at one database, i created a table named create table testme( a
int, b int) storage(initial 8k next 8k);

begin
for i in 1..10000 loop
insert into testme values (i,i);
end loop;
end;
/

export table testme

then at second database, i create tablespace big_data datafile
'f:\whatever\big_data.dbf' size 300000k extent management local uniform size
5120k;

then i import the file.

i only create 1 extent instead of many, what's different between my scenario and
Sathish's?

      Rating:
      3
  June 27, 2001
Reviewer:
      A reader
this question give us some scenario that set compress incorrectly can cause
disaster, but what i don't know is
when should we set it to true and when shoule we set it to false?

i don't understand why after we set compress=y, it still cause imported table
generate some many extents, based on my understanding, compress means less,
which means when set it to N, it may cause the scenario that Sathish present,
but if we set Compress=y , it should aviod that situation.

      Rating: 5
Export, Blob
      Datatypes  February 27, 2004
Reviewer:
      Reader  from India
while importing, i encounter problem with tables containing Blob datatype and of
different tablespace (not in the importing Database).  need your help.

Thank you very much

          Followup:

ok, i'll help if I can....

now, care to share what error you might be getting.  I cannot see your monitor
from where I am sitting right now.

February 27, 2004 Reji -- Thanks for the question regarding
      "Character Set conversion", version 8.1.6

      You Asked
Tom:
   I have a DB with character set WE8ISO8859P1 and would like to
convert the DB into UTF-8.  Is it possible? if the answer is yes, how?

Thx
Reji

      and we said...
Yes it is.

See

http://download-east.oracle.com/docs/cd/A81042_01/DOC/server.816/a76966/ch3.htm#47136
for the steps.
Review & Followup

      Rating:
      1
  February 27, 2004
Reviewer:
      A reader
PAGE NOT FOUND

          Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96529/ch2.htm#101203
fixed..

February 27, 2004 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

      Rating: 4
We use ROWNUM
      to do this   February 26, 2004
Reviewer:
      sonali  from waltham, ma
select count(*) into into l_cnt
 from emp
where ename = 'JOHN' and rownum=1;

will return 0 if no rows exist, 1 if at least one row does.

 Instead of this code -

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.

Which one is better performance wise.. with ROWNUM ?, yours with EXISTS or the
exception NO_DATA_FOUND.. I am always confused as to which one to use. Most of
our situations are same as in this thread...Like if count(*) >0 then do
something else do something else.
Thanks

          Followup:

rownum is the least resource intensive of all.

      Rating: 4
But with rownum
      I will do that query twice right ?  February 27, 2004

Reviewer:  sonali  from waltham, ma
Sorry, but I am still not clear on this one..
In this example I do rownum=1; to check if the row exists and then if it exists
I select it again so I will not get no_data_found error.. but if the row exists
I am doing the same select twice where as if I use NO_DATA_FOUND exception the
select will be done only once.. so wouldn't NO_DATA_FOUND exception use be more
efficient than the rownum query check ?
Also if the table has large data wouldn't doing the where clause twice be more
expensive with rownum query.

/*with rownum use*/
select count(1) into inCounter
  from ETHolidayCal
 where Holiday_Set=holidaySet
       and tlDate between Holiday_Beg_Date and Holiday_End_Date
       and rownum=1;

if fromTCAdd=10 and inCounter>0 then
   select Holiday_Pay_Code
     into dPayCode
     from ETHolidayCal
    where Holiday_Set=holidaySet
          and tlDate between Holiday_Beg_Date and Holiday_End_Date and rownum=1;

else
     goto EndIt; -- or some other code here
end if;

/*with no_data_found use*/
if fromTCAdd=10 and inCounter>0 then
   select Holiday_Pay_Code
     into dPayCode
     from ETHolidayCal
    where Holiday_Set=holidaySet
          and tlDate between Holiday_Beg_Date and Holiday_End_Date and rownum=1;
   EXCEPTION
    WHEN NO_DATA_FOUND THEN
        goto EndIt; -- or some other code here

end if;

What do you think ?
Thanks

          Followup:

that is an entirely different question than you asked above.

avoiding the count all together is by far the most efficient and correct way to
do it.

I do not "count" to see if I should do something.

I just try to do it and if there is no data to process, so be it.

just code

   begin
      select * into ... from t where ...
      process record.
   exception
      when no_data_found then
           do not process record
   end;

and be done with it.

February 27, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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://download-west.oracle.com/docs/cd/B10501_01/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://download-west.oracle.com/docs/cd/B10501_01/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://download-west.oracle.com/docs/cd/B10501_01/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://download-west.oracle.com/docs/cd/B10501_01/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.

      Rating: 2
Oracle 9i2
      features in Warehouse Builder  February 27, 2004

Reviewer:  Sebastien De Clercq  from Brussels,
      Belgium
Hi Tom,

I am facing a VLD-1113 error but METALINK, "the support website", returned no
hits !

This error is related to the use of a windowing aggregate functions :
AVG(NBR_CLI) OVER (PARTITION BY REF_GROUP).

The error message is :
"VLD-1113: The output expression for AVG_PCL.OUTGRP1.AVG_NBR_CLI cannot contain
an aggregation function. Mapping components do not allow the use of aggregation
functions inside user-defined expressions except in the Aggregation operator".

BUT the 'Aggregation operator' does not allowed the use of OVER ( PARTITION BY
...).

Any idea ?

Many Thanks in advance,

Sebastien

          Followup:

OWB does not currently support analytic functions.

      Rating: 5
Analytic
      Functions in OWB  February 27, 2004
Reviewer:
      Sebastien De Clercq  from Brussels, Belgium
Many Thanks for this disappointing, but good to know, response !

It is strange that my OWB 'user-defined expression' using PERCENTILE_CONT is
validated by OWB but the generated code can't be compiled once deployed in the
db.

Anyway, let 's hope OWB will support analytic functions ASAP !

Sebastien

February 27, 2004 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.

      Rating: 4
Grants on
      USER_TABLES  February 27, 2004
Reviewer:
      Vinnie  from Orlando
Is there anything special about granting select user_tables to a role?
>GRANT SELECT ON USER_TABLES TO my_role;

But when I log as my username that has been granted that role I can't see
anything in user_tables?

          Followup:

do you have any tables to see?

if the query executes but returns 0 records, you just don't own anything (but
you had access to the view)

if you want to see tables you have access to, use all_tables

February 27, 2004 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://download-east.oracle.com/docs/cd/A81042_01/DOC/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.

      Rating: 5
Did I mess up
      already ?   January 28, 2004
Reviewer:  Amit
       from Oh
Hi,

My users paste their resume in a textarea in html form (infact the same
functionality as this review form in asktom). Some of the characters are coming
in as '¿' - which probably happens when they paste it from word, and most of the
time these are the double quote marks from word.

Is this because of character conversion ?

Is there any way at all I can get back the original data ?

If eventually I need to convert to a blob - is there a way I can convert clob to
blob without messing up yet again ?

Thanks

          Followup:

some of those characters can be control characters as well (or bullet points and
other "non character characters" -- like your double quotes)

this could be a character set conversion at many points

o between word and browser
o between browser and middle tier
o between middle tier and database

we can only control the last one (we being the database).  You would make sure
your middle tiers NLS_LANG was set to the character set of the database itself.
java however can muck this up as well since it uses ucs2 or something like that
as ITS character set.

why would you need to convert a clob to a blob.  you can, you would read the
clob a piece at a time and then call utl-raw to cast the varchar2 you just got
into a raw and dbms_lob to write append it to the blob.

      Rating: 5
How do we
      convert the other way?  February 27, 2004

Reviewer:  Steve  from UK
So, I have a blob in a column (uploaded csv via mod_plsql).  I need to parse the
file into various tables, how would I convert to a clob, or would it be better
to user intermedia text?

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1352202934074
it shows how to read a bfile (blob for all intents and purposes) line by line
and treat it as a varchar2.  all you have to do is add the parse of the csv
line.

      Rating:
      5
  February 27, 2004
Reviewer:
      A reader
Tom,

Can you please give some info regd when to use blobs vs clobs.

Thanks.

          Followup:

simple

use clobs when you have the equivalent of a really long varchar2 (text)

use blobs otherwise (.doc, .xls, .binary data of any sort)

      Rating:
      5
pctversion  February 27, 2004
Reviewer:
      reader
pctversion say 20%. is it percentage of LOB tablespace space or LOB segment size
reserved for before images? Thanks.

          Followup:

there is no such thing as a "lob tablespace" really -- a lob is in a tablespace,
the lob "has a tablespace" but the tablespace isn't a lob tablespace.

from the application developers guide for lobs:

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.

February 27, 2004 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.

      Rating: 4
Statistics for
      Partioned tables  February 05, 2004
Reviewer:
      Nathan  from United Kingdom
Dear Tom,
 Your answers have been comprehensive and helpful all the time, just another
quick one ( hopefully) from me.

When we the Estimate_percent parameter and sent it to 0.00001 in
DBMS_STATS.GATHER_TABLE_STATS , would it mean only that percent of the blocks be
scanned for analysis ? Our gather_stats takes with estimate takes hours to
complete , My Senior DBA is skeptical of the percentage option and reckons that
it could be a bug , he concluded by watching V$session_longops ( as the same
number of blocks were scanned ) . I have not checked it yet but apparently the
percentage does work on non-partitioned tables . Is this true ? Is there a bug
in oracle 9i release 1.0 software

Thanks and Regards
Vish

          Followup:

hows about you share the details -- such as the entire, full command you are
using plus the type of table you are doing this to and some facts about the
table itself.

0.00001 would be a "not so good number" to use, pretty darn small it would be.

      Rating: 2
Analyze and
      DBMS_STATS  February 06, 2004
Reviewer:  chandu
      singh  from Bangalore, India
Hi
 i think it is a best to have the percentage of minium to 10% . is that right if
not pls explain.

          Followup:

why -- whats the reasoning (not that you are wrong, but -- why do you think that
:)

it could be 1%
it could be 10%
it could be 100%

it just depends on "how evenly distributed the data is in general".  If the data
is large-ish AND pretty much evenly distributed, or you are not computing
histograms -- 1% might be dandy.

Me, I like to compute whenever possible personally.  Only when it gets really
too large -- and then estimate as high as you can (realizing of course anything
over 49% is really compute in disguise)

      Rating: 2
Analyze and
      DBMS_STATS  February 06, 2004
Reviewer:  chandu
      singh  from Bangalore, India
Hi
 i think it is a best to have the percentage of minium to 10% . is that right if
not pls explain.

      Rating:
      5
DBMS_STATS.AUTO_SAMPLE_SIZE Issues  February 06,
      2004
Reviewer:  RyuO  from Washington, DC USA

My experience was that DBMS_STATS is wonderful, but requires a lot of wrapping.
Here is the wrapper proc I evolved:

CREATE OR REPLACE PROCEDURE analyze_schema(
   p_schema_name IN VARCHAR2 := USER,
   p_Estimate_Pct IN PLS_INTEGER := NULL) IS
   -- like DBMS_UTILITY.ANALYZE_SCHEMA, but using DBMS_STATS
   c_proc_name          CONSTANT VARCHAR2(128) := UPPER('analyze_schema');
   c_Def_Estimate_Pct   CONSTANT PLS_INTEGER := 35;
   c_Min_Degree         CONSTANT PLS_INTEGER := 1;
   c_Def_Degree         CONSTANT PLS_INTEGER :=
LEAST(c_Min_Degree,DBMS_STATS.DEFAULT_DEGREE);
      -- default degree is somehow 32767 at the moment
   c_Def_Options        CONSTANT VARCHAR2(30) := 'GATHER AUTO';
      -- 'GATHER' means analyze all objects.
      -- 'GATHER AUTO' means only objects that have changed.
   v_Estimate_Pct PLS_INTEGER;
      -- v_Estimate_Pct can also be set to:
      --    DBMS_STATS.AUTO_SAMPLE_SIZE (Oracle sets this, but it has been known
to be 0...)
      --    NULL (which means, look at everything, e.g., 'COMPUTE')
   v_schema_name        ALL_TABLES.OWNER%TYPE := NVL(UPPER(p_schema_name),USER);
   v_Msg_Text           VARCHAR2(2000);
BEGIN
   CASE p_Estimate_Pct
      WHEN NULL THEN
         v_Estimate_Pct := 100;
      WHEN 100 THEN
         v_Estimate_Pct := 100;
      ELSE
         IF DBMS_STATS.AUTO_SAMPLE_SIZE = 0 THEN
            IF p_Estimate_Pct BETWEEN 1 AND 99 THEN
               v_Estimate_Pct := p_Estimate_Pct;
            ELSE
               v_Estimate_Pct := c_Def_Estimate_Pct;
            END IF;
         ELSE
            v_Estimate_Pct := DBMS_STATS.AUTO_SAMPLE_SIZE;
         END IF;
   END CASE;
   DBMS_STATS.GATHER_SCHEMA_STATS(
      ownname => v_schema_name,
      estimate_percent => v_Estimate_Pct,
      cascade => TRUE,
      degree => c_Def_Degree,
      options => c_Def_Options);
      -- "cascade" means analyze indexes too.
   v_Msg_Text := c_proc_name||'-> Schema['||v_schema_name||'],
Option['||c_Def_Options||'], Sampling['||v_Estimate_Pct||'%]';
   DBMS_OUTPUT.PUT_LINE(v_Msg_Text);
EXCEPTION
   WHEN OTHERS THEN
      v_Msg_Text := c_proc_name||'-> EXCEPTION: '||SQLERRM;
      RAISE_APPLICATION_ERROR(-20199,v_Msg_Text);
END analyze_schema;

My questions are related to AUTO_SAMPLE_SIZE:
1. How come it is always 0? That's what I always see, generally in 9.2.0.3 and
9.2.0.4 on Solaris.
2. What should the default estimate percentage be, if DBMS_STATS can't tell me?
Anecdotal evidence suggests that the ANALYZE/ESTIMATE percentage should be
35-40%, but then ANALYZE and DBMS_STATS are different...

          Followup:

requires....

OR "permits".

it requires NO WRAPPING.

It quite handily permits it easily.

you'll have to provide me a reproducible, small, simple, yet 100% complete test
case to demonstrate what you mean by "0"

ops$tkyte@ORA9IR2>  exec dbms_stats.gather_table_stats(
user,'BIG_TABLE',estimate_percent => dbms_stats.auto_sample_size );

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> select sample_size from user_tables where table_name =
'BIG_TABLE';

SAMPLE_SIZE
-----------
     100000

(which also shows "does not require", but would "permit" wrapping.... )

      Rating: 3
understanding
      sample_percent  February 25, 2004
Reviewer:
      A reader
Oracle 9.2.0.2
I observed that when estimate_percent is specified, the gather_schema_stats is
still doing a compute statistics. The sample_size=num_rows. Am I incorrect in
concluding that its ignoring the 25% and estimating 100% of rows ? If so, why ?
OR have I overlooked at any setting ?

Please look at the following Case :
------- # of rows from T --------
SQL> select count(*) from T
  COUNT(*)
  --------
    600980

---- Querying the dba_tables ---------
SQL> select table_name, monitoring, num_rows, sample_size,
to_char(last_analyzed, 'HH24-mi-ss') ANALYZED from dba_tables;

TABLE_NAME            MON   NUM_ROWS SAMPLE_SIZE ANALYZED
--------------------- --- ---------- ----------- --------
T                     YES     600980      600980 15-39-16

------ inserting data ----------
SQL> insert into T select * from T

600980 rows created.

SQL> commit;
Commit complete.

--------- row count from T ---------
SQL> select count(*) from T;
  COUNT(*)
----------
   1201960

-------- Querying the dba_tables ---------
SQL> select table_name, monitoring, num_rows, sample_size,
to_char(last_analyzed, 'HH24-mi-ss') ANALYZED from dba_tables;

TABLE_NAME            MON   NUM_ROWS SAMPLE_SIZE ANALYZED
--------------------- --- ---------- ----------- --------
T                     YES     600980      600980 15-39-16

---------- gather_schema_stats ---------
begin
  dbms_stats.gather_schema_stats(
       ownname          => 'TEST',
       estimate_percent => 25,
       method_opt       => 'FOR ALL INDEXED COLUMNS SIZE 1',
       degree           => 2,
       cascade          => FALSE,
       options          => 'GATHER AUTO'
       );
  end;
/
PL/SQL procedure successfully completed.

-------- Querying the dba_tables ---------
SQL> select table_name, monitoring, num_rows, sample_size,
to_char(last_analyzed, 'HH24-mi-ss') ANALYZED from dba_tables;

TABLE_NAME            MON   NUM_ROWS SAMPLE_SIZE ANALYZED
--------------------- --- ---------- ----------- --------
T                     YES    1201960     1201960 15-48-45

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

          Followup:

straight from the docs here:

GATHER AUTO: Gathers all necessary statistics automatically. Oracle implicitly
determines which objects need new statistics, and determines how to gather those
statistics. When GATHER AUTO is specified, the only additional valid parameters
are ownname, stattab, statid, objlist and statown; all other parameter
settings are ignored. Returns a list of processed objects.

      Rating: 5
got it ...
      but  February 26, 2004
Reviewer:
      A reader
Tom, I understand it better now.
I took out the invalid parameters.
But my question still lingers :
Why is the gather_schema_stats analyzing all 100% of rows. I changed only
10%(50K of rows) out of 450K rows.
My dbms_job wakes up every few days to execute gather_schema_stats. Now, if I
have 10-15 tables which have changed by more than 10%, then analyzing these
tables 100%(compute) will take lot of time.
I know oracle automatically calculates how much percentage to analyze. But
everytime why is it analyzing all 100% of the rows? Why is it not estimating ?

=========
begin
   dbms_stats.gather_schema_stats(
         ownname          => 'TEST',
         options          => 'GATHER AUTO'
         );
    end;
/
==============

          Followup:

it does not take that long to analyze 1,000,000 rows.

gather stale does not re-analyze just "changed rows"
gather stale re-analyzes the entire object.

If you don't want AUTO, don't use AUTO, use gather stale and fill in the
remaining blanks (but for such small tables, i would get it all)

      Rating:
      4
hmmm  February 27, 2004
Reviewer:
      A reader
Hi Tom,
the thing am fighting is I like to use "stale" because I can specify the
percentage to analyze and take advantage of the degree. AUTO is good because it
automatically analyzes newly added tables.

Best of both worlds would be :
1) analyze newly added tables -> AUTO does this. STALE doesn't.

2) analyze only percentage of rows, not 100% -> AUTO is  analyzing 100%. STALE
can do percentage.

Is it possible ?

Thanks as always

          Followup:

sounds like you might want:

a) alter schema tab monitoring
b) gather empty
c) gather stale

for your degree of control.  consider:

ops$tkyte@ORA920PC> create table t1 as select * from all_users;

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> exec dbms_stats.alter_schema_tab_monitoring

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> select table_name, num_rows from user_tables;

TABLE_NAME                       NUM_ROWS
------------------------------ ----------
T1

ops$tkyte@ORA920PC> exec dbms_stats.gather_schema_stats( user, options =>
'gather empty' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> select table_name, num_rows from user_tables;

TABLE_NAME                       NUM_ROWS
------------------------------ ----------
T1                                     45

that picked it up, this

ops$tkyte@ORA920PC> exec dbms_stats.gather_schema_stats( user, options =>
'gather stale' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> select table_name, num_rows from user_tables;

TABLE_NAME                       NUM_ROWS
------------------------------ ----------
T1                                     45

did nothing..

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create table t2 as select * from all_users;

Table created.

ops$tkyte@ORA920PC> insert into t1 select * from t1;

45 rows created.

ops$tkyte@ORA920PC> insert into t1 select * from t1;

90 rows created.

ops$tkyte@ORA920PC> insert into t1 select * from t1;

180 rows created.

ops$tkyte@ORA920PC> commit;

Commit complete.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> exec dbms_stats.alter_schema_tab_monitoring

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> select table_name, num_rows from user_tables;

TABLE_NAME                       NUM_ROWS
------------------------------ ----------
T1                                     45
T2

ops$tkyte@ORA920PC> exec dbms_stats.gather_schema_stats( user, options =>
'gather empty' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> select table_name, num_rows from user_tables;

TABLE_NAME                       NUM_ROWS
------------------------------ ----------
T1                                     45
T2                                     45

that got t2 and...

ops$tkyte@ORA920PC> exec dbms_stats.gather_schema_stats( user, options =>
'gather stale' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> select table_name, num_rows from user_tables;

TABLE_NAME                       NUM_ROWS
------------------------------ ----------
T1                                    360
T2                                     45

ops$tkyte@ORA920PC>

that got t1...

      Rating:
      5
perfecto  February 27, 2004
Reviewer:
      A reader
pretty GOOD !
Thank you much.
Have a good weekend.

February 27, 2004 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.

      Rating:
      5
  January 30, 2004
Reviewer:
      A reader
Tom,

This is what i noticed on my test server.

sys@TEST> ed
Wrote file afiedt.buf

  1  SELECT d.NAME, t.NAME AS tablespace_name
  2  FROM V$DATAFILE d, V$TABLESPACE t
  3* WHERE t.TS# = d.TS#(+)
sys@TEST> /

NAME                                               TABLESPACE_NAME
--------------------------------------------------
------------------------------
/u01/oracle/oradata/test/system01.dbf              SYSTEM
/u01/oracle/oradata/test/undotbs01.dbf             UNDOTBS1
                                                   TEMP
/u01/oracle/oradata/test/indx01.dbf                INDX
/u01/oracle/oradata/test/users01.dbf               USERS
/u01/oracle/oradata/test/oem_repository.dbf        OEM_REPOSITORY

6 rows selected.

My temp tablespace does not have a datafile..

1)What do you think might be the reason for that.
2)Will it use system tablespace for sorting since temp datafile is not there.

          Followup:

you are right -- temporary tablespaces don't have datafiles, they have
tempfiles.

look at v$tempfile.

      Rating: 4
Temp LMT and
      DMT  February 10, 2004
Reviewer:  Alvin
       from Philippines
You said and i quote :
"Followup:
You should be using true temporary tablespaces (you have a permanent tablespace
marked temporary -- you have "data files", not "temp files")"

1.) When you say permanent tablespace marked "temporary" does it mean Temp DMT
??

Our temporary tablespace is a DMT one. Initially ive enabled the autoextend
clause and it has grown to 8 Gig. I've issued the autoextend off clause now i
get in my db logs the following error.

ORA-1652: unable to extend temp segment by 8 in tablespace              TEMP
Tue Feb 10 14:20:32 2004
ORA-1652: unable to extend temp segment by 8 in tablespace              TEMP

2.) After a sorting operation of a session has ended are  a.the resources
consumed released immediately ?
b. or is it released when the user logs off ?
c. released when the db is shutdown ?

3.) How can i alleviate my system of the ORA-1652 error in TEMP tablespace ?
a. Do i enable autoextend clause ?
b. Make another Temp LMT ? and make it the default temp tablespace for all of my
users ?

4.) If i created a TEMP LMT. When's a good time to drop the TEMP DMT ?
a. After a shutdown to be sure that nobody has a "hold" on any temp segments ?
b. After creating the Temp LMT ?

          Followup:

1) no, I mean a "create temporary tablespace...." using LMTs.

You are out of space, you need more than 8gig of temp.

2) after a session has retrieved the rows, closed the cursor - the temp space is
released.

when the cursor closes, the space is released.  this'll happen at db shutdown,
at logoff OR when the application simply closes the cursor

3) add more space?

making it an LMT won't let it store more stuff - just store it more efficiently
and make your backups faster (you'll be using TEMPFILES, not DATAFILES,
tempfiles are not backed up)

4) anytime it lets you.  just create a new temp, alter the users to point to it
(or in 9i, alter the database to set the default temp tablespace and anyone with
the default temp tablespace will be altered automagically)

then try to drop it, it won't let you if someone is using it.

      Rating: 5
1 user hogging
      all of the sort segment ?  February 26, 2004

Reviewer:  Alvin  from Philippines
I've been hitting the ora-01652 error and i queried the v$sort_usage to check
who's using the sort segments and here are my findings.

  1* select * from v$sort_usage
sql> /

USER                           SESSION_     SESSION_NUM SQLADDR          SQLHASH
TABLESPACE
------------------------------ -------- --------------- -------- ---------------
-------------------
CONTENTS  SEGTYPE          SEGFILE#         SEGBLK#         EXTENTS
BLOCKS        SEGRFNO#
--------- --------- --------------- --------------- ---------------
--------------- ---------------
SYS                            F3607A48           25714 E9F49A84      1705880752
TEMP
TEMPORARY SORT                    4         4079362          499117
3992936               4

From 8 Gig my temp tablespace (DMT) has grown into 34 Gig. Does the user 'sys'
need that much sorts ??

Some of my queries are hitting.

ORA-03232: unable to allocate an extent of 35 blocks from tablespace 3

And tablespace 3 points to the temporary tablespace.

          Followup:

are you using sys for your own queries?

what is sys doing - what query(s) is it running.

Apparently the user 'sys' does need this space, now you need to figure out why.
(no, by default this does not happen).

So, find out what query(s) sys is running and then we can look at the "why"
part.

      Rating: 5
additional info
      on the above posts.  February 26, 2004
Reviewer:
      Alvin  from Philippines
sql> select * from v$sort_segment;

TABLESPACE_NAME                    SEGMENT_FILE   SEGMENT_BLOCK     EXTENT_SIZE
 CURRENT_USERS   TOTAL_EXTENTS
------------------------------- --------------- --------------- ---------------
--------------- ----
   TOTAL_BLOCKS    USED_EXTENTS     USED_BLOCKS    FREE_EXTENTS     FREE_BLOCKS
 ADDED_EXTENTS
--------------- --------------- --------------- --------------- ---------------
--------------- ----
  FREED_EXTENTS   FREE_REQUESTS        MAX_SIZE      MAX_BLOCKS   MAX_USED_SIZE
MAX_USED_BLOCKS   MA
--------------- --------------- --------------- --------------- ---------------
--------------- ----
MAX_SORT_BLOCKS    RELATIVE_FNO
--------------- ---------------
TEMP                                          4          153946               8
             1          524287
        4194296          499117         3992936           25170          201360
        524287         3750490
              0               0          524287         4194296          524287
       4194296          499117
        3992936               4

      Rating: 4
ORA-3232 or
      ORA-1652?  February 26, 2004
Reviewer:  Dave
      Shui  from Burnaby, BC Canada
Dear Tom,

Correct me if I am wrong.

I noticed that Alvin encountered different error from ORA-01652 when his temp
tablespace grew to 34g:

ORA-03232: unable to allocate an extent of 35 blocks from tablespace 3.
(3 -> temporary tablespace)

This error indicates that the size of segment requested was larger than the
"NEXT" extent of the temporary tablespace.

In this case it won't help even though there is more space.

He can either increase the value of NEXT for the temporary tablespace or
decrease the value of HASH_MULTIBLOCK_IO_COUNT.

          Followup:

ahh, very good eye.  did not even think about that (not an issue in the current
software, no more ora-3232 errors)

> oerr ora 03232
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.

      Rating: 5
How to size
      temp tablespace  February 27, 2004
Reviewer:
      Arun Gupta  from Harrisburg, PA USA
Tom,
How should I determine the size of the temp tablespace? Client feels that 7GB
temp tablespace in a DW is too big. The cubes still keep running out of temp
segments. The largest table in the query is only 500MB, others are only few
hundred rows each. How to justify a 7GB temp tablespace?
Thanks

          Followup:

client is wrong then if you keep running out.

disk is cheap, even expensive disk is cheap.  cheaper then "waiting an hour for
query to fail" over and over.

what is your db version?  dbms_xplan in 9ir2 can be used to show you the
estimated temp space needed by a query.

February 27, 2004 Michael -- Thanks for the question regarding
      "Index and Indexes", version 8.1.6.0

      You Asked
Hi Tom,

I would like to ask questions about indexes.

Suppose I have a table t1 which has columns a, b, c and d.
Columns a and b are the primary keys of t1, and hence a primary key index is
built on them automatically.
If I qurey a sql likes "select a, d from t1 where b = ??", will the primary key
index (a, b) be used in that case.

Also, I want to know that can an index on two or more columns(e.g. c, d) be used
in the sql statement that containts part of the index in the where condition
(e.g. "select a, b from t1 where c = ??")?  Or do I need to build another index
(i.e. on column c only) for that query?

Thanks for you help in advance
Michael

      and we said...
for:

... If I qurey a sql likes "select a, d from t1 where b = ??", ....

the answer is probably not -- using a hint you might be able to force it to FULL
SCAN the index to find the rows in the table but under normal circumstances --
it would not use that index.

For the second query you have -- we will most definitely make use of that index
on (c,d) to answer queries against C by itself.

Review &
      Followup

      Rating: 3
Why indexed
      columns in primary key cannot be used seperately?  September
      13, 2001
Reviewer:  Michael  from HK
Tom,

Why is it yes in the second query but not in the first query?

Is it not allowed to be used seperately in PK index?

          Followup:

In the first query, the user wants to get columns A, D from T.  there is an
index on (a,b).  The query is:  select a,d from t where b = ?

In order to use the index in this case, we would have to inspect EVERY index
entry (the index is sorted by A, not by B -- b=5 could be ANYWHERE in the
index).  Additionally -- since you want columns A, D as the output -- everytime
we find an index entry where b=?, we would then have to do a table access by
rowid to get the value for D.

It is most likely that rather then full scan the index and do single block IO to
retrieve the row, we would opt to full scan the table instead.  It is most
LIKELY...  It depends on the size, shape, volume and distribution of data.

It has nothing to do with the fact that it is a PK index, it is just an index
where the columns are not in the optimal ordering to be used for a particular
query.

For the second query -- the data in the index is sorted by C so a predicate like
"c=?" can be used efficiently against that index using an index range scan.

      Rating: 4
Scanning
      composite index without predicate on index prefix
      column  September 13, 2001
Reviewer:
      Padders  from UK
Tom,

I believe 9i has index skip scan algorithm for this situation and that it
performs excellently. Can you point out any detailed documentation on skip scan?

          Followup:

See

http://download-west.oracle.com/otndoc/oracle9i/901_doc/server.901/a87503/optimops.htm#51553
it works well if and only if the leading edge of the index (a in the example
above) has very few discrete values and the optimizer understands that.  For
example and index on (GENDER,EMPNO) where gender has the values 'M', 'F' and
EMPNO is unique.  A query such as:

select * from t where empno = 5;

might consider (in 9i) using that index on t to satisfy the query in a "skip
scan" method -- meaning the query will be processed conceptually like this:

select * from t where GENDER='M' and empno = 5
UNION ALL
select * from t where GENDER='F' and empno = 5;

In order to demonstrate this, I used the following script:

rop table t;

create table t
as
select decode(mod(rownum,2), 0, 'M', 'F' ) gender, all_objects.*
  from all_objects
/
create index t_idx on t(gender,object_id)
/

analyze table t compute statistics
for table
for all indexes
for all indexed columns
/

alter session set sql_trace=true;
select * from t t1 where object_id = 55;
alter session set sql_trace=false;

drop table t;

create table t
as
select chr(mod(rownum,256)) gender, all_objects.*
  from all_objects
/
create index t_idx on t(gender,object_id)
/

analyze table t compute statistics
for table
for all indexes
for all indexed columns
/

alter session set sql_trace=true;
select * from t t2 where object_id = 55;
alter session set sql_trace=false;

When the optimizer understands there are a few "sub indexes" by gender (as in
the first case), it will use a skip scan:

select *
from
 t t1 where object_id = 55

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.00       0.00          0          7          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.01          0          7          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 23

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID T
      1   INDEX SKIP SCAN (object id 19845)

In the second case, there were 256 "sub indexes" and the optimizer properly
adjusted its query plan:

select *
from
 t t2 where object_id = 55

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.04       0.03          1        266          9           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.05       0.04          1        266          9           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 23

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS FULL T

As you can see -- in this case -- the skip scan did lots less work...

      Rating: 4
Thank
      You.  June 14, 2003
Reviewer:  Christo
      Kutrovsky  from Ottawa, ON Canada
Great example. Thanks you for showing it to us.

      Rating: 5
quick question
      on composite index  February 08, 2004
Reviewer:
      steve  from NYC
Hi Tom,

you mentioned the index entries are sorted by the leading edge col in the index.
In the case of a composite index, e.g. (a,b,c), are all the index entries are
sorted by a first, then by b and last by c?
i.e.,
  (a1,b1,c1)
  (a1,b1,c2)
  (a1,b1,c3)
  (a1,b2,c1)
  (a2,b1,c1)
  (a2,b2,c1)
 if ai<aj, i<j.

thanks!

Steve

          Followup:

yes, sort by a, inside of a by b, inside of b by c.

      Rating:
      5
sweet!  February 10, 2004
Reviewer:
      James Blanding  from Kingston, RI USA
The second reply was EXCEEDINGLY useful.  Thanks! :)

      Rating: 5
Is skip
      scanning index an automatic feature or can be turned on and off?
      Thanks.  February 19, 2004
Reviewer:  reader

          Followup:

it's an automatic feature -- if optimizer_features_enable is set to 9.x.x or
above.  (so yes, you can turn it off but you'll turn off lots of stuff)

      Rating: 5
Indexing on
      individual columns for fast retrieval  February 25, 2004

Reviewer:  dharma  from CA USA
Thanks Tom the explanation was excellent.

We have a table with ~million rows and more to be added, and 80 columns. We show
the table in forms 6 and the user can query on 20 columns. We have the primary
key and a couple of indexes and the table is analyzed. But when queried on some
columns (form the application in forms 6i) they tend to be very slow. our team
suggests to create individual indexes on each on the 20 columns. Would fast full
scan be involved in this or creating composite indexes help. We cannot restrict
the User from querying on only certain columns as that feature is definitely
needed.

Also data is loaded using sqlloader daily, very rare inserts, frequent updates
possible and not OLTP.

As always your suggestion would be of great help.

Thanks,
-dharma
BTW Db is Oracle 9i rel 2

          Followup:

hard to say with this limited information.

do they update the queried columns? or the other 60 columns?

      Rating:
      4
Thanks!  February 27, 2004
Reviewer:
      dharma  from CA, USA
Thanks for the immediate reply.

Not all of the 60 columns are updated. Only 10 to 20 of them would be updated,
also there are updates on atleast 6-10 of the 20 queryable columns.

Also when querying, they can query by individual column, or multiple columns -
there is no hard and fast rule there. Thats where I get stuck. :(

Would be glad to provide any other info.

Thanks Once again.
-dharma

          Followup:

sorry -- so bitmap indexes are out (if the 20 were not updated during the day,
20 bitmap indexes on the individual columns would be useful)

What about using TEXT?  Index a function or set of columns on the table and use
contains with section searching?  a single index, all columns available in it.

February 27, 2004 -- 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

      Rating: 4
same error,
      only in a materialized view  February 27, 2004

Reviewer:  Craig  from NW Arkansas
CREATE MATERIALIZED VIEW mv_dm_dne4_order AS
    (SELECT me.id AS transaction_number
          , me.account_id
          , (SELECT NAME FROM v_account WHERE id = account_id) AS account_name
from v_mode_execution me);

I get an ORA-22818 subquery expressions not allowed here

Is there a limitation on materialized views that prevent subqueries in the
select statement?  Please, help me understand why I'm seeing this error.

Thanks!!

          Followup:

it is filed as a doc bug to have this restriction documented and it is in 10g
(documented).

http://download-west.oracle.com/docs/cd/B12037_01/server.101/b10736/basicmv.htm#sthref431

in this case

select me.id, me.acccount_id, va.name
  from v_mode_execution me, v_account va
 where me.account_id = va.id(+)

would more than satisfy your requirement and could even be "changes only" (aka
'fast') refreshable if the requisite primary key exists.

      Rating: 5
Not what I
      wanted to hear...  March 01, 2004
Reviewer:
      Craig  from NW Arkansas
...but it did answer my question as to why I couldn't make it work.  Thanks yet
again!

February 27, 2004 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.

      Rating: 1
ALTER SYSTEM
      QUIESCE RESTRICTED   February 27, 2004
Reviewer:
      Johnny  from USA
'alter system enable restricted session' does not "kick out" user automatically.
It just blocks new user who does not have the 'restrict session' privilege to
login, similar to 'ALTER SYSTEM QUIESCE RESTRICTED', which blocks new non-dba
users. However, I can scarely see the instances when non-dba users have been
granted to the 'restrict session' privilege.

There is a practical advantage to introduce the QUIESCE state, however. It
allows all current transactions to complete, and then freezes the user's
session. To use 'restrict session' to do the same job, DBA might need to have a
procedure that is able to keep monitoring the status of the existing
transactions and kill the session as soon as the transaction gets completed.

          Followup:

I said "you would KICK OUT", not that "it kicks 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. "

you just said the same thing I did.

February 28, 2004 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 :)

      Rating: 4
Why fractured
      blocks only matter the first time?  February 27, 2004

Reviewer:  Doug  from CT, USA
Tom - the first time a block is updated - is the update suspended/blocked until
we can log a complete block image so that the block is consistent?  Why doesn't
a fractured block matter the second or third time it is updated.  In other
words, let's say that File 2, block 1 is updated but the copy command hadn't
gotten there yet.  When it does get there, it is the 3rd or 4th time it's been
updated and it is fractured but we're not logging a completely consistent block
image to redo like we did say a minute ago.  How come this doesn't matter?
Thanks.

          Followup:

No, it is just that instead of logging into the redo log buffer "just enough to
redo that change", we log the block.

It doesn't matter on the second and third and nth times because of how recovery
would proceed.

Lets say "block 5" in file "55" was backed up "fractured".

Further, we know the first time block 5 was modified AFTER the backup began we
logged the modified image of block 5 in the redo log buffer (and hence into the
online redo log files upon commit and hence into the archives upon log switch).

Now, say block 5 is updated 500 times -- we only log the minimum data to restore
block 5 after that initial write.

Now, we fail and need to restore this file 55.  We goto the backups and we lay
down this old copy of file 55.  We start processing the archives/online redo
logs to catch this file up.  We hit the first change for block 5 after the
backup was taken and get the entire block image for block 5.  So, even if block
5 was totally corrupt on disk -- fractured, binary zeros, whatever -- we have
block 5 as it should be after that first update.  We lay down that entire block
and then apply the incremental updates from the arch/redo log files to catch it
up.

February 28, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/whatsnew.htm#972592

      Rating:
      3
OK  February 25, 2004
Reviewer:
      Catherine  from OH,USA
Dear Tom,
Can the following inline view be transformed into a
correlated subquery?
sql> select deptno,max_sal,(select ename from emp where
        sal = max_sal and rownum = 1),min_sal,(select ename
     from emp where sal = min_sal and rownum = 1)
      from(
            select deptno,max(sal) as max_sal,min(sal) as
              min_sal from emp group by deptno
           );
Could you please help?
Thanks in advance.

          Followup:

I do not see the relationship between

a) inline view -- the equivalent of a view or table to be selected from

b) a correlated subquery -- something you use in a predicate to restrict rows.

Your query looks a bit funky.  The question it seems to be answering is:

generate the max sals by DEPTNO

report back a random ename that makes that sal by deptno, even if that ename
doesn't work in that DEPT!!!

consider:

ops$tkyte@ORA920PC> create table emp ( deptno number, sal number, ename
varchar2(20) );

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert into emp values ( 10, 100, 'first row' );

1 row created.

ops$tkyte@ORA920PC> insert into emp values ( 10, 50, 'second row' );

1 row created.

ops$tkyte@ORA920PC> insert into emp values ( 20, 100, 'third row' );

1 row created.

ops$tkyte@ORA920PC> insert into emp values ( 20, 50, 'fourth row' );

1 row created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select deptno,
  2         max_sal,(select ename from emp where sal = max_sal and rownum = 1),
  3         min_sal,(select ename from emp where sal = min_sal and rownum = 1)
  4     from( select deptno,max(sal) as max_sal,min(sal) as
  5           min_sal from emp group by deptno
  6           );

    DEPTNO    MAX_SAL (SELECTENAMEFROMEMPW    MIN_SAL (SELECTENAMEFROMEMPW
---------- ---------- -------------------- ---------- --------------------
        10        100 first row                    50 second row
        20        100 first row                    50 second row

 is that what you really wanted?  or is this what you meant:

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select distinct deptno,
  2         first_value(sal) over (partition by deptno order by sal, rowid)
min_sal,
  3         first_value(ename) over (partition by deptno order by sal, rowid)
min_ename,
  4         first_value(sal) over (partition by deptno order by sal DESC, rowid)
max_sal,
  5         first_value(ename) over (partition by deptno order by sal DESC,
rowid) max_ename
  6    from emp
  7  /

    DEPTNO    MIN_SAL MIN_ENAME               MAX_SAL MAX_ENAME
---------- ---------- -------------------- ---------- --------------------
        10         50 second row                  100 first row
        20         50 fourth row                  100 third row

      Rating: 5
WITH and
      complex types  February 25, 2004
Reviewer:
      vll  from USA
Hi, Tom!
Great thanks as usual for all you are doing for us!!!

I'm using WITH a lot in my new projects and it really helps. But recently I
tried to include in WITH query some complex types (mostly - XMLTYPE fields) from
one of my tables. And I never had a chance to make it working - it just freezes
and after a minute or 2 gives an error "lost communication with a server" or
someting like this. It's 9i release 2. All my tests just tell me that with
simple types WITH is working fine, as soon as you have complex type - you have a
problem. Are you by chance aware of any problem with this? I can post my query,
but it's really large and I don't think it can help...

          Followup:

if you have an example that returns with the 3113 - -please file a tar with
support, that would be a "bug"

      Rating: 5
I'm getting the
      same error  February 26, 2004
Reviewer:
      Steve  from Phoenix, AZ USA
I'm also getting the 3113 error (on 9.2.0.4), but it's happening inconsistently.
 For a while, the error was coming up about 50% of the time.  A few hours later,
it was less than 10%.  When the error doesn't happen, I find no problems with
the results of the process.

Briefly, my process is running a packaged procedure which passes a PL/SQL
collection (varray) to a remote (dblink) packaged procedure which creates a flat
file for that data using UTL_FILE.  In the procedure that builds the PL/SQL
collection, several other collections (each is a SQL table type) are used to
pass data between packaged functions.  These collections are being used in SQL
statements using "TABLE( CAST(" in the functions.

One of the things I've had a problem with is deciding where to define the PL/SQL
types (remote site or local site).  Both options have resulted in problems.  The
current setup is defining on the remote site.

I don't know if "vll from USA" has a similar setup, but if so, I hope you will
let us know if you find a solution, and I will do the same.

Tom, can you think of any hints and tips you could give us as far as the best
approach for doing this kind of thing?

          Followup:

you *need* to contact support to resolve a 3113 like this.  it means "back end
went away - crashed".

      Rating: 5
Found a way
      around the 3113  February 27, 2004
Reviewer:
      Steve  from Phoenix, AZ USA
It doesn't appear that anything was actually crashing, since I was using both
instances in other windows which continued to work before and after the error.
Anyway, I found a workaround for the error, which I'll describe below in case
anyone is doing something similar.  But first, a little soapbox action:

I was reluctant to contact Oracle Support because, in the 15+ years that I've
worked on Oracle, I've gotten very little help from them.  The responses I
usually get are either that I have to upgrade to a later version or that the
feature I'm using wasn't meant to be used that way.  In either case, I spend
hours communicating with them and still have to find a workaround.  So now I
always try to find a workaround first and contact Support only as a last resort.

<getting off soapbox>

The application I'm working on is available on an internet site and allows the
users to retrieve a variety of statistical data in various formats (report,
chart, or CSV file).  There are two instances involved, the first (call it DB1)
contains the data warehouse and the second (call it DB2) contains the
internet-related data.  Each is on a separate Unix box.

The 3113 error was happening in the routines that create the CSV file.  A
request is initiated on DB2 by a user.  Packaged functions and procedures are
called on DB1 to build the data in a PL/SQL varray.  The varray is passed back
to DB2 to build the file using utl_file so the user can download it.  Since the
same type must be used on both sides to avoid a type mismatch, the type must be
defined on either DB1 or DB2 and referenced via dblink by the other.

The first attempt to do this was by calling the packaged routines on DB1 which
built the varray and called a procedure on DB2 passing it the varray.  When the
type was defined on DB1, DB2 didn't recognize the type.  When the type was
defined on DB2, the 3113 error came up intermittently.

The new variation calls a packaged procedure on DB2 which creates the varray
(empty) and passes it as an IN OUT parameter to a procedure on DB1 which calls
the other functions to build the varray.  When the type was defined on DB2, I
wasn't able to make it work.  But, when the type was defined on DB2, everything
seemed to be okay.  I've run it about 50 times so far, and it is working
consistently.

Hopefully, this information will be helpful to someone out there.  If anyone
would like more detail on this let me know.

Tom, thanks for all the help you provide on this site.  Sorry for not following
your advice on contacting Oracle Support, but, as I said, that's not worked out
very well for me in the past.

          Followup:

your *session* crashed -- your dedicated server crashed.  the instance did not
fail, your session did.

support is actually pretty good at 3113, ora-600's, things that create trace
files.  for the "feature wasn't meant to be used like that", i cannot really
respond, I'd need an example - but many times i find that to "be the case"
(using shared server to run really long transactions -- yet getting upset that
no one can log in since the shared servers are all currently busy.  using
replication for failover/DR yet not having any conflict detection/resolution
routines and getting upset when the other site comes backup and starts pushing
deferred transactions.  the list is very very long)

but, anyway.

If you have a reproducible test case (and it seems you do) it would really
benefit us all by filing the tar with it.  You have something concrete they can
work with -- and instead of others hitting this issue in the future, they would
just have it "work".  Thats my soapbox -- if you don't tell them there is a
problem, problem might well as not exist and problem will never get fixed for
anyone.

      Rating: 5
3113
      again  March 02, 2004
Reviewer:  Steve
       from Phoenix, AZ USA
Good point, Tom.  I will try to put together a reproducible test case - one
which is not so tightly tied to our environment, but still gets the 3113.  I
need to concentrate on getting my project finished, since it is already overdue,
but I should be able to work on this later in the week or early next week.
Thanks for your help.

February 28, 2004 Miles -- Thanks for the question regarding "On
      Rollback Segment", version 8.1.7

      You Asked
Hi Tom,

Can you just clarify something for us.  Our production DBA said something that I
just find very odd and hard to accept.  I couldn't find any documentation that
would confirm/deny the DBA's statement.   He said that if we increase the number
of rollback segments in our database, it will have an adverse impact on the
online backup of the database.  The dba exact words is "Increasing the number of
rollback segments will have a negative impact on the 'Online' backups because it
would increase the time needed to do a backup."  Is this true?

Thanks in advance.

      and we said...
only in that you "have more disk to backup" -- but it should add a neglible
amount of disk relative to the rest of the database.

This would not be a consideration in the addition (or not) of more rollback
segments.  The only increase in time after adding 100meg of rollback would be
the additional time to backup 100m of disk (which makes sense).  But -- so what?
 if you need more for contention issues that plague you the rest of the day,
spending 2 minutes to copy this extra stuff would pay for itself immediately.
February 28, 2004 Saminathan -- Thanks for the question regarding
      "Datafile selection within tablespace ", version 8.1.7.3

      You Asked
Dear Tom,

Thanks for you help and support.

We are using permanent tablespace as TEMP tablespace(I don't know why it has
been created  that way. it is been long time. We have to live with this until 9i
upgrade) with the size of 1GB (only one datafile, say T1.dbf). To build larger
snapshot I did add additional datafile with the size of 3GB (say datafile
T2.dbf).
Both datafiles are created from raw device.

After snapshot has been created I took T2.dbf to offline. Also I confirmed that
no objects has been created in TEMP tablespace.

Now I am getting an error saying that "datafile /../../dsk13/T2.dbf" cannot be
read at this time" if I try to create an index OR try to run large
storedprocedure (even though I have enough free space in TEMP tablespace).

If I try to bring T2.dbf online again it needs media recovery required.In 8i,
there is no option to delete datafile as well.

My questions are
1) How do I overcome this issue?

2) How oracle selects datafile within tablespace (even though T1.dbf is online
with enough space why oracle is looking for T2.dbf offline datafile)

3) Shall I create another TEMP tablespace(TEMP1) and assign all the users to
TEMP1. Then I can take TEMP tablespace offline.
Is it a correct way to overcome this issue?

Your comments are really appreciated.

      and we said...
Once added, a file cannot be removed from a tablespace.  Never.

You will simply

a) drop that tablespace
b) recreate it (and might as well make it a true temporary whilst you are at
it!)

that'll be the only way to fix this.  Once you add a datafile, we expect it to
be there forever. You gave it to us. It becomes part of our structure.  You can
shrink it, you can grow it, but you can never take it away.

And since you are using a permanent tablespace -- you must back it up and
restore it (that is why it needs media recovery, it is quite cabable of
containing permanent objects -- we don't really care that it is not, it is that
it could and hence we need to recover it)

So, you'll offline drop that datafile.  You'll drop that tablespace.  And then
you will simply recreate it since it is just temp after all.  Nothing lost.
Review &
      Followup

      Rating: 5
offline drop
      it, then ...  February 28, 2004
Reviewer:
      SR
Tom,

couldn't he
create datafile 'xyz' as 'abc'
using the archivelogs it will be able to build it again?

just curious.
thanks

          Followup:

it is temp -- drop it, recreate it.  trivial.

February 28, 2004 Jerry -- Thanks for the question regarding
      ""Best" query for testing a connection", version 9,2,0

      You Asked
Our application uses a jdbc connection pool.  We use the option to have the
connection tested before being returned to us.  We are currently using a simple
query against dual:

select 1 from dual;

Is there a "better" query to use?  (I'd really like to use "values 1".)

      and we said...
why "test the connection"  at all?  just use it?  in the event of an error --
fix it.

You have to do that anyway, consider:

a)  grab connection
b)  "test connection"
c)  use connection but connection fails cause after you tested but before you
    used it something went wrong.

testing a connection is a utter waste of time...  but if you persist, insist,
feel you must just:

   "begin ? := 1; end;"

don't query, just run a small piece of plsql -- but again, it is a waste of CPU
cycles as it proves "nothing" other than at the particular point in time you
touched the connection, it worked.  It doesn't mean or imply "it will continue
to work" so you need error handling anyhow.

Review &
      Followup

      Rating: 2
Why we test the
      connection  February 28, 2004
Reviewer:  Jerry
      Brenner  from San Mateo, CA USA
We're using an open source connection pool implementation and we're relying on
that implementation to return us a valid connection.  The connection pool will
test the connection before returning it to us.  If it is bad, then it will toss
that connection and try another one. If there is some major problem, like the
database is down, then an exception will be thrown and reported out to the user.

Here's a situation that testing the connection solves:

Connection pool has N connections.
Something bad happens to the network or the database and the problem is fixed.
A user connects to our application.  We get an invalid connection back from the
connection pool because of the problem mentioned above.  At this point, the only
way to clean the connection pool is to restart the application.  If the
connection pool does the testing, then it discovers that the connection is bad
(and all others are bad).  It throws away the bad connections and gets a new
valid connection to return.

          Followup:

the application can take care of "something bad happens to connection" just as
easily by

a) asking for it
b) trying to use it
c) upon failure getting another

I've seen systems where much of their resources were spent "testing these
connections" needlessly.

but, sigh, if you persist, just begin ?:=1; end;  or even just execute "begin
null; end;" and check the return code from the execute.

February 28, 2004 -- 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

      Rating: 5
ORA-06502
      PL/SQL: numeric or value errorstring  February 23, 2004

Reviewer:  Mike  from Germany
Oracle 8.1.7.4 on HP Unix

Tom,

I am trying to pivot a rather large query..and hitting the max length of long
variable which is 32760.
I have declared a variable as long...and in that varable..I put in the
concatenated string to build
--max( decode, col, "something", null):

WHILE C_OUT_REC_MAX%FOUND LOOP

   DBMS_OUTPUT.PUT_LINE ( LENGTH (vMAX) );

   IF vMAX IS NULL THEN
      vMAX := 'MAX( DECODE (' || pCOLID || ', ' || vMAXID || ', PROJECT_PER_EMP,
NULL) ) "' || vMAXID || '"';
   ELSE
      vMAX := vMAX || ',' || CHR(10) ||
              'MAX( DECODE (' || pCOLID || ', ' || vMAXID || ', PROJECT_PER_EMP,
NULL) ) "' || vMAXID || '"';

   END IF;

FETCH C_OUT_REC_MAX INTO vMAXID, vMAXDS;

END LOOP;

........

This query has lot of columns to pivot....
so I am hitting the limit on long variable.

Is there a better way to do this?

Thanks.

          Followup:

in sql the limit is actually 4000 characters and is "not surpassable".  you will
stop at 4000 plain and simple.

you can pivot hundreds of columns

you cannot concatenate them into a single field as you'll hit the 4000 wall

      Rating:
      5
ora-06502  February 23, 2004
Reviewer:
      Mike  from Germany
After getting vMAX...i was going to concatenate vMAX to a 'SELECT '...
so they will be individual columns.
I am doing this in PL/SQL because I don't know the no. of the pivoting column
ahead of time...

Here is the query I am pivoting:

SELECT EMPLID,
      MAX( DECODE (GSAID, 11, PROJECT_PER_EMP_GSA, NULL) ) "11",
      MAX( DECODE (GSAID, 11.5, PROJECT_PER_EMP_GSA, NULL) ) "11.5"
FROM

(
SELECT A.EMPLID, A.GSAID, A.PROJECT_PER_EMP_GSA
FROM
(SELECT E.EMPLID,
        P.GSAID,
        COUNT(P.PROJID) OVER(PARTITION BY E.EMPLID, P.GSAID) PROJECT_PER_EMP_GSA
FROM EM_PROJ E, PROJ_GSA P
WHERE E.PROJID = P.PROJID
AND EMPLID IN ('X1', 'X2')
AND GSAID IN (11,11.5)
) A
GROUP BY A.EMPLID, A.GSAID, A.PROJECT_PER_EMP_GSA
)

GROUP BY EMPLID;

EMPLID      11         11.5
----------- ---------- ----------
X1                1          2
X2                5          9
2 rows selected.

Now this is when I know the GSAID'S I need to pivot by...
If I take the "AND GSAID IN (11,11.5)" clause out...
I have no control over GSAID's...
I can get to them with a query...but i don't know how to build
      MAX( DECODE (GSAID, 11, PROJECT_PER_EMP_GSA, NULL) ) "11",
      MAX( DECODE (GSAID, 11.5, PROJECT_PER_EMP_GSA, NULL) ) "11.5"
      .....
      ....

clause just in sql.

This same query will decide the possible GSAID's to pivot on
so I have to actually run this query twice..
once to get the distinct GSAID'S to pivot on...
and then to get the pivoted resultset.

So, I have to turn to PL/SQL..and build the MAX clause in there..
and that's where i hit the long limit.

          Followup:

so, you are saying the query itself is longer than 32k?

guess I would alias project_per_emp_gsa to "A"

SELECT EMPLID,
      MAX( DECODE (GSAID, 11, A, NULL) ) "11",
      MAX( DECODE (GSAID, 11.5, A, NULL) ) "11.5"
FROM

(
SELECT A.EMPLID, A.GSAID, A.PROJECT_PER_EMP_GSA
FROM
(SELECT E.EMPLID,
        P.GSAID,
  COUNT(P.PROJID) OVER(PARTITION BY E.EMPLID, P.GSAID) PROJECT_PER_EMP_GSA A
FROM EM_PROJ E, PROJ_GSA P
WHERE E.PROJID = P.PROJID
AND EMPLID IN ('X1', 'X2')
AND GSAID IN (11,11.5)
) A
GROUP BY A.EMPLID, A.GSAID, A.PROJECT_PER_EMP_GSA
)

and do likewise for all columns to "make it smaller".  else you'll be going with
dbms_sql, not ref cursors.

      Rating: 5
ORA-01467: sort
      key too long???  February 23, 2004
Reviewer:
      Mike  from Germany
Tom...
Thanks for your earlier help...
So now I have the query but when I try to run it in sql plus..
I get ORA-01467: sort key too long...
Documentation suggests:
Reduce the number of columns or group functions involved in the operation.

I am grouping by just two columns..rest are max(decode...)
columns that i get after pivoting.

So, I was considering may be reducing the no. of columns in one record set..like
breaking up the recordset...may be in to two or three...

What is the limit on number of columns that go in one recordset....like I can
have maximum 770 columns...so can I break it up into may be 3 recordsets....250
each...

or is there a better way to deal with this error?

Thanks

          Followup:

it is a function of your block size in older releases.  You can theoretically
have as many columns as you like in a query -- but things like this many
aggregates can blow that -- especially in older versions.

what if you pivot the other way instead?  put the group by ACROSS and the
current "across" columns "down"

      Rating: 5
sort key not
      found....  February 23, 2004
Reviewer:  Mike
       from Germany
SELECT EMPLID,
      MAX( DECODE (GSAID, 11, PROJECT_PER_EMP_GSA, NULL) ) THE_11,
      MAX( DECODE (GSAID, 11.5, PROJECT_PER_EMP_GSA, NULL) ) THE_11_5
FROM

(
SELECT A.EMPLID, A.GSAID, A.PROJECT_PER_EMP_GSA
FROM
(SELECT E.EMPLID,
        P.GSAID,
        COUNT(P.PROJID) OVER(PARTITION BY E.EMPLID, P.GSAID) PROJECT_PER_EMP_GSA
FROM EM_PROJ E, PROJ_GSA P
WHERE E.PROJID = P.PROJID
AND EMPLID IN ('X1', 'X2')
AND GSAID IN (11,11.5)
) A
GROUP BY A.EMPLID, A.GSAID, A.PROJECT_PER_EMP_GSA
)

GROUP BY EMPLID;

"
what if you pivot the other way instead?  put the group by ACROSS and the
current "across" columns "down"
"

-->

like pivot on emplid?
Wouldn't the resultset be totally different then?

          Followup:

of course -- but it would be equivalent.

row major or row minor, same "matrix", just turned on a different end.

      Rating: 3
This may help
      ...  February 23, 2004
Reviewer:  Russell
       from Melbourne, Australia
When confronted with a similar problem (having to pivot an unknown number of
columns), I implemented the following, which works for me.  It is run from a SQL
script on the unix host, and spools a new sql file, which the original script
executes later.

/**************************************************
** Start a query that writes SQL for a crosstab  **
** Takes all segment_type values in a table and  **
** turns them into row headings                  **
**************************************************/
spool rf_out.sql
PROMPT select region
select ', sum(decode(segment_type,''' ||segment_type||''',1,0))' || segment_type
from ull_comp group by segment_type;
select ', count(1) TOTAL
from ull_comp
group by region;' from dual
/

spool off

@rf_out

/*******************************************/

The example above gives a count of segments for each region.  The PROMPT, and
first line of Select statement, are creating the new select statement.

The result is a query that lists each segment type,  in the body of the decode
statement, that is checked later on.

I am not sure if there are the same limitations of size (32k) in a SQL statement
like this

          Followup:

the problem above was the massive number of aggregates - so many that it
exceeded the sorting limit.  this'll suffer the same issue for it uses SUM like
the other one used MAX

      Rating: 5
SORT key not
      found  February 24, 2004
Reviewer:  Mike
       from Germany
Tom,

pivoting the other way..is this what you were suggesting:

SELECT GSAID,
      MAX( DECODE (EMPLID, 'x1', PROJECT_PER_EMP_GSA, NULL) ) "x1",
      MAX( DECODE (EMPLID, 'x2', PROJECT_PER_EMP_GSA, NULL) ) "x2"
FROM

(
SELECT A.EMPLID, A.GSAID, A.PROJECT_PER_EMP_GSA
FROM
(SELECT E.EMPLID,
        P.GSAID,
        COUNT(P.PROJID) OVER(PARTITION BY E.EMPLID, P.GSAID) PROJECT_PER_EMP_GSA
FROM EM_PROJ E, PROJ_GSA P
WHERE E.PROJID = P.PROJID
AND EMPLID IN ('x1', 'x2')
AND GSAID IN (11,11.5)
) A
GROUP BY A.EMPLID, A.GSAID, A.PROJECT_PER_EMP_GSA
)

GROUP BY GSAID;

GSAID      x1      x2
---------- ---------- ----------
        11          1          5
      11.5          2          9
2 rows selected.

          Followup:

yes, the thing that resulted in many columns becomes many rows...

      Rating: 5
Sort key not
      found  February 24, 2004
Reviewer:  Mike
       from Germany
Well there are more employees than GSA codes...so turning it around won't do me
any good...either...

          Followup:

sorry -- don't know what else to suggest.

      Rating: 4
sort key too
      long  February 25, 2004
Reviewer:  Steve
       from Chicago, Il
I was able to help the "sort key too long" problem in my application by by
reducing the column width in my query.

The table column was defined as varchar2(400) but the specific data I was using
in my query was always much less than that..

I changed this:
 MAX( DECODE (GSAID, 11, PROJECT_PER_EMP_GSA, NULL) ) THE_11,

to this:
MAX( DECODE (GSAID, 11, substr(PROJECT_PER_EMP_GSA,1,40), NULL) ) THE_11,

and it gave me enough extra room to do what I needed.

          Followup:

excellent

      Rating: 3
WHY getting
      dupes in Pivot  February 28, 2004
Reviewer:
      A reader
I need to get pivot results for those employees whose deptno belongs to deptno
of enames matching 'CLA%'.
The inner query returs 3 rows but the pivot gives me columns for ename_6
also.???( pls note there are 2 names like CLA in empl table )
If create a temp table for the inner query it gives me the right results. How to
solve this dup problem.

Thanx
1)
SELECT d.deptno,
               e.ename,
               e.sal,
               e.job,
               ROW_NUMBER () OVER (PARTITION BY e.deptno ORDER BY job) rn
          FROM dept d, empl e
         WHERE e.deptno = d.deptno
           AND d.deptno IN (SELECT deptno
                              FROM empl
                             WHERE ename LIKE 'CLA%')

DEPTNO ENAME      SAL       JOB       RN
------ ---------- --------- --------- ---------------------------------------
    10 CLARKEY    1300.00   CLERK     1
    10 CLARK      2450.00   MANAGER   2
    10 KING       5000.00   PRESIDENT 3
3 rows selected

2)
SELECT deptno,
       MAX (DECODE (rn, 1, ename, NULL)) ename_1,
       MAX (DECODE (rn, 1, sal, NULL)) sal_1,
       MAX (DECODE (rn, 1, job, NULL)) job_1,
       MAX (DECODE (rn, 2, ename, NULL)) ename_2,
       MAX (DECODE (rn, 2, sal, NULL)) sal_2,
       MAX (DECODE (rn, 2, job, NULL)) job_2,
       MAX (DECODE (rn, 3, ename, NULL)) ename_3,
       MAX (DECODE (rn, 3, sal, NULL)) sal_3,
       MAX (DECODE (rn, 3, job, NULL)) job_3,
       MAX (DECODE (rn, 4, ename, NULL)) ename_4,
       MAX (DECODE (rn, 4, sal, NULL)) sal_4,
       MAX (DECODE (rn, 4, job, NULL)) job_4,
       MAX (DECODE (Rn, 5, ename, NULL)) ename_5,
       MAX (DECODE (rn, 5, sal, NULL)) sal_5,
       MAX (DECODE (rn, 5, job, NULL)) job_5,
       MAX (DECODE (rn, 6, ename, NULL)) ename_6,
       MAX (DECODE (rn, 6, sal, NULL)) sal_6,
       MAX (DECODE (rn, 6, job, NULL)) job_6
  FROM (SELECT d.deptno,
               e.ename,
               e.sal,
               e.job,
               ROW_NUMBER () OVER (PARTITION BY D.deptno ORDER BY job) rn
          FROM dept d, empl e
         WHERE e.deptno = d.deptno
           AND d.deptno IN (SELECT deptno
                              FROM empl
                             WHERE ename LIKE 'CLA%'))
                             GROUP BY DEPTNO;
o/p:
DEPTNO    ENAME_1    SAL_1    JOB_1    ENAME_2    SAL_2    JOB_2    ENAME_3
SAL_3    JOB_3    ENAME_4    SAL_4    JOB_4    ENAME_5    SAL_5    JOB_5
ENAME_6    SAL_6    JOB_6
10    CLARKEY    1300    CLERK    CLARKEY    1300    CLERK    CLARK    2450
MANAGER    CLARK    2450    MANAGER    KING    5000    PRESIDENT    KING    5000
   PRESIDENT

3)
CREATE TABLE TEMP_EMPL AS
 SELECT d.deptno,
               e.ename,
               e.sal,
               e.job,
               ROW_NUMBER () OVER (PARTITION BY e.deptno ORDER BY job) rn
          FROM dept d, empl e
         WHERE e.deptno = d.deptno
           AND d.deptno IN (SELECT deptno
                              FROM empl
                             WHERE ename LIKE 'CLA%')
SELECT deptno,
       MAX (DECODE (rn, 1, ename, NULL)) ename_1,
       MAX (DECODE (rn, 1, sal, NULL)) sal_1,
       MAX (DECODE (rn, 1, job, NULL)) job_1,
       MAX (DECODE (rn, 2, ename, NULL)) ename_2,
       MAX (DECODE (rn, 2, sal, NULL)) sal_2,
       MAX (DECODE (rn, 2, job, NULL)) job_2,
       MAX (DECODE (rn, 3, ename, NULL)) ename_3,
       MAX (DECODE (rn, 3, sal, NULL)) sal_3,
       MAX (DECODE (rn, 3, job, NULL)) job_3,
       MAX (DECODE (rn, 4, ename, NULL)) ename_4,
       MAX (DECODE (rn, 4, sal, NULL)) sal_4,
       MAX (DECODE (rn, 4, job, NULL)) job_4,
       MAX (DECODE (Rn, 5, ename, NULL)) ename_5,
       MAX (DECODE (rn, 5, sal, NULL)) sal_5,
       MAX (DECODE (rn, 5, job, NULL)) job_5,
       MAX (DECODE (rn, 6, ename, NULL)) ename_6,
       MAX (DECODE (rn, 6, sal, NULL)) sal_6,
       MAX (DECODE (rn, 6, job, NULL)) job_6
  FROM (SELECT * FROM TEMP_EMPL)
                             GROUP BY DEPTNO     ;
o/p:
DEPTNO    ENAME_1    SAL_1    JOB_1    ENAME_2    SAL_2    JOB_2    ENAME_3
SAL_3    JOB_3    ENAME_4    SAL_4    JOB_4    ENAME_5    SAL_5    JOB_5
ENAME_6    SAL_6    JOB_6
10    CLARKEY    1300    CLERK    CLARK    2450    MANAGER    KING    5000
PRESIDENT
********************************************************
CREATE TABLE DEPT (
  DEPTNO  NUMBER (2)    NOT NULL,
  DNAME   VARCHAR2 (14),
  LOC     VARCHAR2 (13),
  CONSTRAINT PK_DEPT
  PRIMARY KEY ( DEPTNO ) );
CREATE TABLE EMPL (
  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));
ALTER TABLE EMPl ADD  CONSTRAINT FK_DEPTNO
 FOREIGN KEY (DEPTNO)
  REFERENCES DEPT (DEPTNO);
INSERT INTO DEPT ( DEPTNO, DNAME, LOC ) VALUES (
10, 'ACCOUNTING', 'NEW YORK');
INSERT INTO DEPT ( DEPTNO, DNAME, LOC ) VALUES (
20, 'RESEARCH', 'DALLAS');
INSERT INTO DEPT ( DEPTNO, DNAME, LOC ) VALUES (
30, 'SALES', 'CHICAGO');
INSERT INTO DEPT ( DEPTNO, DNAME, LOC ) VALUES (
40, 'OPERATIONS', 'BOSTON');
COMMIT;
INSERT INTO EMPL ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES (
'7782', 'CLARK', 'MANAGER', '7839',  TO_Date( '06/09/1981 12:00:00 AM',
'MM/DD/YYYY HH:MI:SS AM')
, '2450', NULL, 10);
INSERT INTO EMPL ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES (
'7839', 'KING', 'PRESIDENT', NULL,  TO_Date( '11/17/1981 12:00:00 AM',
'MM/DD/YYYY HH:MI:SS AM')
, '5000', NULL, 10);
INSERT INTO EMPL ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES (
'7934', 'CLARKEY', 'CLERK', '7782',  TO_Date( '01/23/1982 12:00:00 AM',
'MM/DD/YYYY HH:MI:SS AM')
, '1300', NULL, 10);
COMMIT;

          Followup:

the only way I get your results is to "accidently" load the data in there twice.

Be interseting for you to add "rowid" to the results and see if in fact clarkey
is the same clarkey or if you accidently loaded the data twice before running
the second query.

      Rating: 2
did with the
      rowid and same dup result  February 28, 2004

Reviewer:  A reader
I am surprised why you dont get the same result.
I am listing the individual tables values and the query I put with rowid.
You see the rowids are repeated for rn = 4,5,6 whereas the
inner table just has 3 rows 1,2,3.
Any clues to this.
Thanx for your Help.

SQL> select * from dept ;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 ACCOUNTING     NEW YORK
        20 RESEARCH       DALLAS
        30 SALES          CHICAGO
        40 OPERATIONS     BOSTON

SQL> select * from empl ;

     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 CLARKEY    CLERK           7782 23-JAN-82       1300
  10

SELECT d.rowid ri_dept,e.rowid ri_emp,d.deptno,
               e.ename,
               e.sal,
               e.job,
               ROW_NUMBER () OVER (PARTITION BY D.deptno ORDER BY job) rn
          FROM dept d, empl e
         WHERE e.deptno = d.deptno
           AND d.deptno IN (SELECT deptno
                              FROM empl
                             WHERE ename LIKE 'CLA%')
AAALkgAABAAAM/SAAA    AAALzIAAJAAAC80AAC    10    CLARKEY    1300    CLERK    1
AAALkgAABAAAM/SAAA    AAALzIAAJAAAC80AAA    10    CLARK    2450    MANAGER    2
AAALkgAABAAAM/SAAA    AAALzIAAJAAAC80AAB    10    KING    5000    PRESIDENT    3

SELECT deptno,
       MAX (DECODE (rn, 1, ename, NULL)) ename_1,
       MAX (DECODE (rn, 1, sal, NULL)) sal_1,
       MAX (DECODE (rn, 1, job, NULL)) job_1,
       MAX (DECODE (rn, 1, ri_emp, NULL)) ri_emp_1,
       MAX (DECODE (rn, 2, ename, NULL)) ename_2,
       MAX (DECODE (rn, 2, sal, NULL)) sal_2,
       MAX (DECODE (rn, 2, job, NULL)) job_2,
              MAX (DECODE (rn, 2, ri_emp, NULL)) ri_emp_2,
       MAX (DECODE (rn, 3, ename, NULL)) ename_3,
       MAX (DECODE (rn, 3, sal, NULL)) sal_3,
       MAX (DECODE (rn, 3, job, NULL)) job_3,
              MAX (DECODE (rn, 3, ri_emp, NULL)) ri_emp_3,
       MAX (DECODE (rn, 4, ename, NULL)) ename_4,
       MAX (DECODE (rn, 4, sal, NULL)) sal_4,
       MAX (DECODE (rn, 4, job, NULL)) job_4,
              MAX (DECODE (rn, 4, ri_emp, NULL)) ri_emp_4,
       MAX (DECODE (Rn, 5, ename, NULL)) ename_5,
       MAX (DECODE (rn, 5, sal, NULL)) sal_5,
       MAX (DECODE (rn, 5, job, NULL)) job_5,
              MAX (DECODE (rn, 5, ri_emp, NULL)) ri_emp_5,
       MAX (DECODE (rn, 6, ename, NULL)) ename_6,
       MAX (DECODE (rn, 6, sal, NULL)) sal_6,
       MAX (DECODE (rn, 6, job, NULL)) job_6,
              MAX (DECODE (rn, 6, ri_emp, NULL)) ri_emp_6
  FROM (SELECT d.rowid ri_dept,e.rowid ri_emp,d.deptno,
               e.ename,
               e.sal,
               e.job,
               ROW_NUMBER () OVER (PARTITION BY D.deptno ORDER BY job) rn
          FROM dept d, empl e
         WHERE e.deptno = d.deptno
           AND d.deptno IN (SELECT deptno
                              FROM empl
                             WHERE ename LIKE 'CLA%'))

                             GROUP BY DEPTNO    ;

DEPTNO    ENAME_1    SAL_1    JOB_1    RI_EMP_1    ENAME_2    SAL_2    JOB_2
RI_EMP_2    ENAME_3    SAL_3    JOB_3    RI_EMP_3    ENAME_4    SAL_4    JOB_4
 RI_EMP_4    ENAME_5    SAL_5    JOB_5    RI_EMP_5    ENAME_6    SAL_6    JOB_6
  RI_EMP_6
10    CLARKEY    1300    CLERK    AAALzIAAJAAAC80AAC    CLARKEY    1300    CLERK
   AAALzIAAJAAAC80AAC    CLARK    2450    MANAGER    AAALzIAAJAAAC80AAA    CLARK
   2450    MANAGER    AAALzIAAJAAAC80AAA    KING    5000    PRESIDENT
AAALzIAAJAAAC80AAB    KING    5000    PRESIDENT    AAALzIAAJAAAC80AAB

          Followup:

went back to 817 and reproduced.    I see what is happening, they lost a
sort(UNIQUE) on the "in" filter predicate.  In 817, the plan is:

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (GROUP BY)
   2    1     VIEW
   3    2       WINDOW (SORT)
   4    3         MERGE JOIN
   5    4           SORT (JOIN)
   6    5             NESTED LOOPS
   7    6               TABLE ACCESS (FULL) OF 'EMPL'
   8    6               INDEX (UNIQUE SCAN) OF 'PK_DEPT' (UNIQUE)
   9    4           SORT (JOIN)
  10    9             TABLE ACCESS (FULL) OF 'EMPL'

step 9 is the problem -- it should be as it is in 9i:

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (GROUP BY)
   2    1     VIEW
   3    2       WINDOW (SORT)
   4    3         MERGE JOIN
   5    4           SORT (JOIN)
   6    5             NESTED LOOPS
   7    6               TABLE ACCESS (FULL) OF 'EMPL'
   8    6               INDEX (UNIQUE SCAN) OF 'PK_DEPT' (UNIQUE)
   9    4           SORT (JOIN)
  10    9             VIEW OF 'VW_NSO_1'
  11   10               SORT (UNIQUE)
  12   11                 TABLE ACCESS (FULL) OF 'EMPL'

Your workaround is to add "and rownum > 0" to the inner query:

WHERE e.deptno = d.deptno
           AND rownum > 0
           AND d.deptno IN (SELECT deptno
                              FROM empl
                             WHERE ename LIKE 'CLA%'))

and please contact support to file a bug against 817 with your simple testcase.

(versions are always appreciated, makes for less guessing)

      Rating: 5
Thanx a Million
      But  February 28, 2004
Reviewer:
      A reader
Thanx a lot for your help. the rownum trick worked.
1)Can you kindly explain why/how rownum > 0 that fixed the problem . How could
one come up with this witty solution ? You are great

BTW  the version I tested this 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.

The same thing happens even for Oracle 9.2 on Linux
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
5 rows selected

          Followup:

rownum > 0 is explained in a little more detail in my book effective Oracle by
design but it basically causes "no merging" to happen (you may well find a
no_merge hint to have the same effect).  It built the inner query AND THEN
pivoted it, instead of merging everything....

in 9204, it is "fixed"

February 29, 2004 -- 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)...

      Rating: 4
compile
      problems after upgrade  February 27, 2004

Reviewer:  Dean  from Nashville
I've had to do that because I'm on 7.3.4

I'm having the wierd problem that a couple of triggers and packages that make
calls to v$session for user info will not compile under 9.

I get a 942 error.  But If I just select from SQL, I get the results no problem.
 Is this a bug or is it hidden in the docs?  I've scoured the migration manuals
and can't find this issue (rowid's were easy!).

          Followup:

http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html
you lost a grant, in 7.3 you had "grant select on v_$session to trigger_owner"
and in 9.x you forgot to do that as SYS.

      Rating:
      4
  February 27, 2004
Reviewer:
      michael  from USA
How about 3 tier? Can I use this way to get Browser's IP?

          Followup:

not in the database, the client there is the middle tier, the middle tier
(whatever you are using there) would have to tell you -- for example, if using
MOD_PLSQL, it would be owa_util.get_cgi_env('REMOTE_ADDR').

      Rating: 5
background
      process info  February 28, 2004
Reviewer:
      reader
(1) In unix one can use ps -ef|grep <sid> to see all of the background
processes. Similarly, within SQLPLUS, what views give me this information.

(2) Also, on windows how do I get the same info as I get using ps command in
unix? Thanks

          Followup:

v$process

you'd have to ask your windows SA what commands they might use, there are
various utilities to do so -- but not being a windows user, I don't use them

      Rating: 5
Background
      process info  February 29, 2004
Reviewer:  Mark
      A. Williams  from Indianapolis, IN USA
If you are interested, here is what I use:

col PROCESSNAME format a32

select   vs.sid SID,
         vb.name NAME,
         vp.program PROCESSNAME,
         vp.spid THREADID
from     v$session vs,
         v$process vp,
         v$bgprocess vb
where    vb.paddr != '00'
and      vb.paddr = vp.addr
and      vp.addr = vs.paddr
order by 1;

       SID NAME  PROCESSNAME                      THREADID
---------- ----- -------------------------------- ------------
         1 PMON  ORACLE.EXE                       1832
         2 DBW0  ORACLE.EXE                       1844
         3 LGWR  ORACLE.EXE                       1856
         4 CKPT  ORACLE.EXE                       1932
         5 SMON  ORACLE.EXE                       1944
         6 RECO  ORACLE.EXE                       1948
         7 CJQ0  ORACLE.EXE                       1952
         8 QMN0  ORACLE.EXE                       1956

For "listing" the process from a Windows Command Prompt, I use the tlist.exe
utility from Microsoft.  It can be downloaded from here:
http://www.microsoft.com/downloads/details.aspx?FamilyID=c055060b-9553-4593-b937-c84881bca6a5&DisplayLang=en
Here is some sample output from that:

C:\>tlist oracle.exe
1628 oracle.exe
   CWD:     c:\oracle\9.2\DATABASE\
   CmdLine: c:\oracle\9.2\bin\ORACLE.EXE CDS9I
   VirtualSize:   254476 KB   PeakVirtualSize:   256280 KB
   WorkingSetSize:105996 KB   PeakWorkingSetSize:106000 KB
   NumberOfThreads: 14
   1632 Win32StartAddr:0x018ee2dc LastErr:0x000003e5 State:Waiting
   1656 Win32StartAddr:0x77ddbd17 LastErr:0x000003e5 State:Waiting
   1708 Win32StartAddr:0x00719b70 LastErr:0x000003e5 State:Waiting
   1832 Win32StartAddr:0x0042f360 LastErr:0x00000000 State:Waiting
   1844 Win32StartAddr:0x0042f360 LastErr:0x00000000 State:Waiting
   1856 Win32StartAddr:0x0042f360 LastErr:0x000000b7 State:Waiting
   1932 Win32StartAddr:0x0042f360 LastErr:0x00000000 State:Waiting
   1944 Win32StartAddr:0x0042f360 LastErr:0x00000000 State:Waiting
   1948 Win32StartAddr:0x0042f360 LastErr:0x00000000 State:Waiting
   1952 Win32StartAddr:0x0042f360 LastErr:0x00000000 State:Waiting
   1956 Win32StartAddr:0x0042f360 LastErr:0x00000000 State:Waiting
   1964 Win32StartAddr:0x0042f360 LastErr:0x00000000 State:Waiting
   1968 Win32StartAddr:0x0042f360 LastErr:0x00000000 State:Waiting
   2344 Win32StartAddr:0x0042f8b0 LastErr:0x00000000 State:Waiting
        9.2.0.4 shp  0x00400000  ORACLE.EXE
  5.1.2600.1217 shp  0x77f50000  ntdll.dll
  5.1.2600.1106 shp  0x77e60000  kernel32.dll
        9.2.0.4 shp  0x60600000  oraclient9.dll
        9.2.0.4 shp  0x610a0000  oracore9.dll
        9.2.0.4 shp  0x612a0000  oranls9.dll
        9.2.0.4 shp  0x613a0000  oraunls9.dll
  7.0.2600.1106 shp  0x77c10000  MSVCRT.dll
     5.1.2600.0 shp  0x71ad0000  WSOCK32.dll
  5.1.2600.1240 shp  0x71ab0000  WS2_32.dll
     5.1.2600.0 shp  0x71aa0000  WS2HELP.dll
  5.1.2600.1106 shp  0x77dd0000  ADVAPI32.dll
  5.1.2600.1254 shp  0x78000000  RPCRT4.dll
  5.1.2600.1263 shp  0x771b0000  ole32.dll
  5.1.2600.1106 shp  0x77c70000  GDI32.dll
  5.1.2600.1255 shp  0x77d40000  USER32.dll
  5.1.2600.1106 shp  0x76b40000  WINMM.dll
        9.2.0.4 shp  0x60800000  oravsn9.dll
        9.2.0.4 shp  0x60500000  oracommon9.dll
        9.2.0.4 shp  0x026f0000  orageneric9.dll
        9.2.0.6 shp  0x62fc0000  oraxml9.dll
     7.0.2600.0 shp  0x00320000  MSVCIRT.dll
        9.2.0.6 shp  0x630f0000  oraxsd9.dll
        9.2.0.4 shp  0x61480000  oran9.dll
        9.2.0.4 shp  0x61400000  oranl9.dll
        9.2.0.4 shp  0x61960000  oranldap9.dll
        9.2.0.4 shp  0x615a0000  orannzsbb9.dll
        9.2.0.4 shp  0x62000000  oraldapclnt9.dll
        9.2.0.4 shp  0x616b0000  orancrypt9.dll
    3.50.5016.0 shp  0x77120000  OLEAUT32.dll
                     0x62300000  ORATRACE9.dll
        9.2.0.4 shp  0x61730000  oranro9.dll
        9.2.0.4 shp  0x617c0000  oranhost9.dll
        9.2.0.4 shp  0x617d0000  oranoname9.dll
        9.2.0.4 shp  0x616a0000  orancds9.dll
        9.2.0.4 shp  0x61820000  orantns9.dll
        0.0.0.0 shp  0x10000000  oranms.dll
        0.0.0.0 shp  0x00330000  oranmsp.dll
        9.2.0.4 shp  0x60a00000  orapls9.dll
        0.0.0.0 shp  0x62500000  oraslax9.dll
        9.2.0.4 shp  0x61350000  orasnls9.dll
        9.2.0.4 shp  0x60810000  orawtc9.dll
        9.2.0.3 shp  0x62600000  orasql9.dll
        9.2.0.4 shp  0x60820000  oraodm9.dll
        9.2.0.4 shp  0x60e00000  oraplp9.dll
                     0x02980000  orajox9.dll
        9.2.0.4 shp  0x61af0000  oransgr9.dll
        0.0.0.0 shp  0x60000000  orawwg9.dll
  5.1.2600.1106 shp  0x71c20000  NETAPI32.dll
                     0x62f00000  ocijdbc9.dll
        0.0.0.0 shp  0x03130000  ORAIMR9.Dll
        9.2.0.4 shp  0x61850000  oranbeq9.dll
        9.2.0.4 shp  0x61910000  orantcp9.dll
     5.1.2600.0 shp  0x71a50000  mswsock.dll
  5.1.2600.1106 shp  0x76f20000  DNSAPI.dll
     5.1.2600.0 shp  0x76fb0000  winrnr.dll
  5.1.2600.1106 shp  0x76f60000  WLDAP32.dll
     5.1.2600.0 shp  0x76fc0000  rasadhlp.dll
  5.1.2600.1106 shp  0x76f90000  Secur32.dll
  6.0.2800.1106 shp  0x5ad70000  uxtheme.dll
     5.1.2600.0 shp  0x71a90000  wshtcpip.dll

C:\>

HTH,

Mark

      Rating:
      5
Thanks  March 01, 2004
Reviewer:
      reader
Mark, Thanks much. Very useful.

February 29, 2004 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

      Rating: 5
Trivial space
      info.  February 29, 2004
Reviewer:  Sai
       from San Jose, CA USA.
Hi Tom,

I would really appreciate if you can help me write a select query to get space
information as below.

We have a 2T database with one standby database. We are planning to build
another standby with sub-set of data, fewer tablespaces, from the existing
standby. Here are the requirements:

The filesystem lay-out on sub-set standby host (Host-B) is completely different
from the existing standby host (HOST-A).

On HOST-B, each filesystem can be filled with 80G of data, like that we have 8
filesystems.

On HOST-A, datafiles range from 1G to 8G in size.

Now the question is: How do I get the list of datafiles from the HOST-A, with
sum size around 80G, which will be copied over to HOST-B filesystem. Like this,
we need 8 sets of datafiles list. Our target standby database size would be
around 600G.

Thanks.

          Followup:

bin fitting problems like that do not lend themselves nicely to SQL based
solutions as you have to sort of develop all of the combinations and find the
best fit.

sounds like a maintenance nightmare waiting to happen here -- keeping track of
all of the renamed files, etc.

February 29, 2004 PRS -- Thanks for the question regarding
      "Concurrent Transactions", version 9.2.0.4.0

      You Asked
Hi,
  Is there anyway to determine concurrent transactions at the database end? I
mean no of users at a given moment always doing something. This is the questions
from the load testing of the oracle database point of view.
your help is appreciated.

Thanks,
PRS

      and we said...
active users at a point in time = select count(*) from v$session where
status='ACTIVE'

active transactions (write transactions) = select count(*) from v$transaction.

but -- for load testing, I would just take 15 minute statspack.snaps and use the
generated reports for analysis.  Infinitely more meaningful than a point in time
"whats happening right now" query.
February 29, 2004 eva -- Thanks for the question regarding "auto
      undo vs manual", version 9.0.1

      You Asked
dear tom, ...

What's big different between auto undo segment and manual undo segment. Which
the best rollback segment configuration ?

      and we said...
one you

a) pick the number
b) size
c) correct the number and size of when you either hit contention or get the
dreaded 1555

the other you

a) tell us how long to retain undo

we size, we configure the number of, we tune it when contention comes up, we
avoid the 1555 by retaining the undo for as long as you tell us.

Me, I use auto unless for whatever reason (really high end, fine tuned system) I
need manual.  Sort of like most of us drive automatic cars (no stick shift) but
a race car driver might prefer the fine tuning they can do with manual.

And -- you can switch back and forth rather easily, so give AUTO a go.
Review &
      Followup

      Rating: 4
when ora-01555
      is comming  February 29, 2004
Reviewer:  eva

Dear tom,

on your book expert one-on-one (first edition),
on chapter 5 page 190, you demonstrate how the
"ora-01555:snapshot too old" is comming.

but how can I try that program to demonstrate
for oracle 9i using automatic undo.

Cause on your book effective oracle by design
chapter 4 page 236, you don't issue anything about ORA-01555.

I try to set undo_retention = 1, and I run your sample program
on expert one-on-one. But I the ORA-01555 is not coming.

or in automatic undo does't come again?

best regards

          Followup:

well, part of the goal of implementing AUM was to make removing ora-1555's
"easier".  You would achieve that by setting the undo retention sufficiently and
ensuring you had sufficient disk space to allow undo to grow.  Then the system
would extent a rollback segment instead of wrapping and reusing the space if the
undo retention period had not yet been met (it is the wrapping and reusing that
cause 1555's).

In your case, even though you set undo_retention to 1, I would assume that you
had undo retention much larger in the past and your undo segments are already
"large" (and hence did not wrap during your test).  For example:

  1  select segment_name,
  2         round(bytes/1024/1024,1) mbytes,
  3         round( ratio_to_report(bytes) over ()*100,2) rtr,
  4         round( sum(bytes) over()/1024/1024, 1 ) total
  5*   from dba_segments where segment_type = 'TYPE2 UNDO'
ops$tkyte@ORA9IR2> /

SEGMENT_NAME                       MBYTES        RTR      TOTAL
------------------------------ ---------- ---------- ----------
_SYSSMU13$                            1.1       3.27       34.2
_SYSSMU14$                            8.1      23.75       34.2
_SYSSMU15$                            1.1       3.27       34.2
_SYSSMU16$                            1.1       3.27       34.2
_SYSSMU17$                            1.1       3.27       34.2
_SYSSMU18$                            8.1      23.75       34.2
_SYSSMU19$                            8.1      23.75       34.2
_SYSSMU20$                            2.1        6.2       34.2
_SYSSMU21$                            2.1        6.2       34.2
_SYSSMU22$                            1.1       3.27       34.2

10 rows selected.

and that is from just my little testing system (single user, nothing big
generally).

So, in that system, with just that little bit of undo configured, I run that
same test from expert one on one Oracle:

ops$tkyte@ORA9IR2> drop table t;

Table dropped.

ops$tkyte@ORA9IR2> create table t as select * from all_objects;

Table created.

ops$tkyte@ORA9IR2> create index t_idx on t(object_id);

Index created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> alter system set undo_retention=1;

System altered.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2      l_cnt number default 0;
  3  begin
  4      for x in ( select rowid rid, t.* from t where object_id > 0 )
  5      loop
  6          if ( mod(l_cnt,100) = 0 )
  7          then
  8              commit;
  9          end if;
 10          update t
 11             set object_name = lower(object_name)
 12           where rowid = x.rid;
 13          l_cnt := l_cnt + 1;
 14      end loop;
 15      commit;
 16  end;
 17  /

PL/SQL procedure successfully completed.

and it did not fail -- but only because 34meg of UNDO was more than sufficient
to hold that work -- we did not wrap around.  In Expert one on one Oracle, we
had a "use rollback segment" where we used a really small rbs for the updates to
force the issue.

So, can we still get 1555 in 9i with AUM?  Absolutely:

ops$tkyte@ORA9IR2> create undo tablespace undo_new datafile size 512k;

Tablespace created.

ops$tkyte@ORA9IR2> alter system set undo_tablespace = undo_new;

System altered.

ops$tkyte@ORA9IR2> @test
ops$tkyte@ORA9IR2> declare
  2      l_cnt number default 0;
  3  begin
  4      for x in ( select rowid rid, t.* from t where object_id > 0 )
  5      loop
  6          if ( mod(l_cnt,100) = 0 )
  7          then
  8              commit;
  9          end if;
 10          update t
 11             set object_name = lower(object_name)
 12           where rowid = x.rid;
 13          l_cnt := l_cnt + 1;
 14      end loop;
 15      commit;
 16  end;
 17  /
declare
*
ERROR at line 1:
ORA-01555: snapshot too old: rollback segment number 2 with name "_SYSSMU2$" too
small
ORA-06512: at line 4

I just had to setup a way too small undo tablespace, start using it, don't let
it grow (no autoextend) and have the undo retention set really small.

In the "real world (tm)", you don't need such an artificially small undo
tablespace -- just one that is "too small".

      Rating:
      5
Thanks  March 01, 2004
Reviewer:
      eva
thanks tom, ...excellent

I need this demonstrate, cause I will to show for my student. This case to learn
that with AUM we can remove ORA-01555 easily

best regards

March 01, 2004 -- 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)??!??

      Rating: 5
Slow Query in
      Siebel Application  February 25, 2004
Reviewer:
      David Piazza  from San Mateo, CA
We have a Siebel Application that took 7 minutes to run.  I increased
PGA_AGGREGATE_TARGET and the query is now down to two minutes.  I'm still trying
to improve it.  I set PGA_AGGREGATE_TARGET to 1GB.  I took an extended trace,
statspack, and PGA statistics during the two minutes it ran.  My questions are:
1) This is a select statement, and the extended trace shows a lot of 'direct
path write'.  Statspack says the PGA cache hit is 100%. PGA advisory shows the
PGA is probably too big.  Direct path writes mean writing to TEMP, right?
Doesn't 100% PGA cache hit mean it's being sorted in memory?  What are all of
the direct path writes and can I cut down on the amount of them?  2) PGA
Advisory always shows estimated PGA cache hit at 51%-52%.  Shouldn't it being
going with each 100% PGA cache hit?
============================================================
Extended trace file(Abbreviated)
============================================================
Dump file /u00/oracle/admin/dibsn/udump/dibsn_ora_2403.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 = /u00/oracle/product/920/v64
System name:    SunOS
Node name:
Release:    5.8
Version:    Generic_108528-21
Machine:    sun4u
Instance name: dibsn
Redo thread mounted by this instance: 1
Oracle process number: 21
Unix process pid: 2403, image: oracle@nsu251 (TNS V1-V3)

*** 2004-02-24 16:42:15.517
*** SESSION ID:(27.93) 2004-02-24 16:42:15.516
APPNAME mod='siebmtshmw@nsu251 (TNS V1-V3)' mh=0 act='' ah=0
=====================
PARSING IN CURSOR #4 len=3458 dep=0 uid=1070 oct=3 lid=1070 tim=2903412901739
hv=2954447095 ad='c6b8e938'
SELECT
      T15.LAST_UPD_BY,
      T15.CREATED,
      T15.ROW_ID,
      T15.CONFLICT_ID,
      T15.CREATED_BY,
      T15.CREATED,
      T15.LAST_UPD,
      T15.MODIFICATION_NUM,
      T8.ROW_ID,
      T15.OU_ADDR_ID,
      T1.MAIN_PH_NUM,
      T15.ACT_CLOSE_DT,
      T15.X_IBSN_CUST_SATISFIED,
      T3.NAME,
      T1.CUST_STAT_CD,
      T15.X_IBSN_IMPACT,
      T15.SR_PROD_VERSION,
      T2.EMAIL_ADDR,
      T8.MODIFICATION_NUM,
      T10.FST_NAME,
      T15.X_IBSN_CRITICAL_REASON,
      T3.INTEGRATION_ID,
      T15.X_IBSN_REPT_SUBISS,
      T15.OWNER_EMP_ID,
      T15.X_IBSN_MAIL_TYPE,
      T15.X_IBSN_ORIG_ZIP_ID,
      T15.CST_CON_ID,
      T8.CREATED,
      T10.LAST_NAME,
      T8.LAST_UPD_BY,
      T4.NAME,
      T15.SR_REPRODUCE,
      T2.FST_NAME,
      T15.SR_SUB_STAT_ID,
      T15.ASGN_USR_EXCLD_FLG,
      T15.PR_CON_ORG_ID,
      T8.CREATED_BY,
      T15.CST_OU_ID,
      T12.ZIPCODE,
      T15.PRI_LST_ID,
      T7.X_IBSN_CUSTNAME,
      T1.OU_NUM,
      T15.X_IBSN_ACCT_TYPE,
      T2.WORK_PH_NUM,
      T15.OWNER_OU_ID,
      T1.OU_TYPE_CD,
      T8.PAR_ROW_ID,
      T2.LAST_NAME,
      T15.SR_SEV_CD,
      T15.SR_PRIO_CD,
      T11.LOGIN,
      T8.ATTRIB_05,
      T14.LOGIN,
      T10.ROW_ID,
      T8.ATTRIB_45,
      T13.SERIAL_NUM,
      T15.X_IBSN_DEST_ZIP_ID,
      T1.DESC_TEXT,
      T8.ATTRIB_03,
      T10.WORK_PH_NUM,
      T9.ZIPCODE,
      T15.SR_STAT_ID,
      T15.SR_CST_NUM,
      T15.BU_ID,
      T15.RATE_LST_ID,
      T15.X_IBSN_ESCALATION,
      T15.ORG_PRDINT_ID,
      T1.X_IBSN_ACCT_NAME,
      T1.INTEGRATION_ID,
      T15.X_IBSN_REPT_ISSUE,
      T15.SR_SUB_AREA,
      T15.ASGN_MANL_FLG,
      T1.ROW_ID,
      T8.LAST_UPD,
      T1.PR_ADDR_ID,
      T15.ASSET_ID,
      T1.PR_SYN_ID,
      T15.X_IBSN_SR_TYPE,
      T1.PR_SRV_AGREE_ID,
      T1.LOC,
      T15.ROW_STATUS,
      T15.SR_TITLE,
      T11.LOGIN,
      T15.ASGN_SYS_FLG,
      T6.LAST_NAME,
      T5.SVC_CALENDAR_ID,
      T15.SR_NUM,
      T15.BILLABLE_FLG,
      T15.SR_AREA,
      T15.SR_CAT_TYPE_CD,
      T1.MAIN_FAX_PH_NUM,
      T2.PR_HELD_POSTN_ID,
      T15.PRDINT_ID,
      T1.NAME,
      T15.DESC_TEXT,
      T6.FST_NAME,
      T3.ROW_ID,
      T15.X_IBSN_REPT_CAT,
      T15.AGREE_ID,
      T15.SR_SUBTYPE_CD,
      T15.PR_SYMPTOM_CD,
      T10.PR_OU_ADDR_ID,
      T8.CONFLICT_ID,
      T15.SR_TYPE_CD
   FROM
       SIEBEL.S_ORG_EXT T1,
       SIEBEL.S_CONTACT T2,
       SIEBEL.S_ORG_EXT T3,
       SIEBEL.S_ORG_EXT T4,
       SIEBEL.S_ENTLMNT T5,
       SIEBEL.S_CONTACT T6,
       SIEBEL.S_ADDR_ORG T7,
       SIEBEL.S_SRV_REQ_X T8,
       SIEBEL.S_ZIPCODE T9,
       SIEBEL.S_CONTACT T10,
       SIEBEL.S_USER T11,
       SIEBEL.S_ZIPCODE T12,
       SIEBEL.S_ASSET T13,
       SIEBEL.S_USER T14,
       SIEBEL.S_SRV_REQ T15
   WHERE
      T15.PR_CON_ORG_ID = T3.PAR_ROW_ID (+) AND
      T15.CREATED_BY = T14.PAR_ROW_ID (+) AND
      T15.X_IBSN_DEST_ZIP_ID = T12.ROW_ID (+) AND
      T15.OWNER_EMP_ID = T2.ROW_ID AND
      T15.OWNER_EMP_ID = T11.PAR_ROW_ID (+) AND
      T8.ATTRIB_03 = T6.PAR_ROW_ID (+) AND
      T15.OU_ADDR_ID = T7.ROW_ID (+) AND
      T15.ASSET_ID = T13.ROW_ID (+) AND
      T15.CST_CON_ID = T10.PAR_ROW_ID (+) AND
      T15.AGREE_ID = T5.ROW_ID (+) AND
      T15.CST_OU_ID = T1.PAR_ROW_ID (+) AND
      T15.OWNER_OU_ID = T4.PAR_ROW_ID (+) AND
      T15.X_IBSN_ORIG_ZIP_ID = T9.ROW_ID (+) AND
      T15.ROW_ID = T8.PAR_ROW_ID (+) AND
      (T15.TEMPLATE_FLG != 'Y')
   ORDER BY
      T15.CREATED DESC
END OF STMT
PARSE#4:c=0,e=202,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=3,tim=2903412901731
WAIT #4: nam='SQL*Net message to client' ela= 2 p1=1413697536 p2=1 p3=0
WAIT #4: nam='SQL*Net more data to client' ela= 66 p1=1413697536 p2=2002 p3=0
WAIT #4: nam='SQL*Net more data to client' ela= 54 p1=1413697536 p2=2003 p3=0
WAIT #4: nam='SQL*Net message from client' ela= 2682 p1=1413697536 p2=1 p3=0
WAIT #4: nam='SQL*Net more data from client' ela= 20 p1=1413697536 p2=2 p3=0
EXEC #4:c=0,e=265,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=3,tim=2903412908304
*** 2004-02-24 16:42:30.442
WAIT #4: nam='direct path write' ela= 194978 p1=201 p2=39561 p3=31
WAIT #4: nam='direct path write' ela= 198233 p1=201 p2=39592 p3=31
WAIT #4: nam='direct path write' ela= 191796 p1=201 p2=39623 p3=31
WAIT #4: nam='direct path write' ela= 185470 p1=201 p2=39654 p3=31
WAIT #4: nam='direct path write' ela= 27722 p1=201 p2=39685 p3=4
WAIT #4: nam='direct path write' ela= 191402 p1=201 p2=39433 p3=31
WAIT #4: nam='direct path write' ela= 579408 p1=201 p2=39464 p3=31
WAIT #4: nam='direct path write' ela= 24123 p1=201 p2=39557 p3=4
WAIT #4: nam='direct path write' ela= 197607 p1=201 p2=39305 p3=31
WAIT #4: nam='direct path write' ela= 192888 p1=201 p2=39336 p3=31
WAIT #4: nam='direct path write' ela= 191281 p1=201 p2=39367 p3=31
WAIT #4: nam='direct path write' ela= 264295 p1=201 p2=39398 p3=31
WAIT #4: nam='direct path write' ela= 146731 p1=201 p2=39177 p3=31
WAIT #4: nam='direct path write' ela= 201026 p1=201 p2=39208 p3=31
WAIT #4: nam='direct path write' ela= 200723 p1=201 p2=39239 p3=31
WAIT #4: nam='direct path write' ela= 193132 p1=201 p2=39270 p3=31
WAIT #4: nam='direct path write' ela= 31729 p1=201 p2=39301 p3=4
WAIT #4: nam='direct path write' ela= 191503 p1=201 p2=39049 p3=31
WAIT #4: nam='direct path write' ela= 192982 p1=201 p2=39080 p3=31
WAIT #4: nam='direct path write' ela= 185468 p1=201 p2=39111 p3=31
WAIT #4: nam='direct path write' ela= 191698 p1=201 p2=39142 p3=31
WAIT #4: nam='direct path write' ela= 32895 p1=201 p2=39173 p3=4
WAIT #4: nam='direct path write' ela= 209686 p1=201 p2=38921 p3=31
WAIT #4: nam='direct path write' ela= 192669 p1=201 p2=38952 p3=31
WAIT #4: nam='direct path write' ela= 191747 p1=201 p2=38983 p3=31
WAIT #4: nam='direct path write' ela= 191431 p1=201 p2=39014 p3=31
                 .
                 .
                 .
WAIT #4: nam='direct path read' ela= 701 p1=201 p2=37497 p3=16
WAIT #4: nam='direct path read' ela= 464 p1=201 p2=27534 p3=31
WAIT #4: nam='direct path read' ela= 491 p1=201 p2=37781 p3=31
WAIT #4: nam='direct path read' ela= 926 p1=201 p2=39561 p3=31
WAIT #4: nam='direct path read' ela= 47 p1=201 p2=27079 p3=31
WAIT #4: nam='SQL*Net message to client' ela= 12 p1=1413697536 p2=1 p3=0
FETCH
#4:c=38750000,e=133384647,p=811,cr=2584398,cu=16,mis=0,r=2,dep=0,og=3,tim=2903546
293018
WAIT #4: nam='SQL*Net message from client' ela= 6509 p1=1413697536 p2=1 p3=0
WAIT #4: nam='SQL*Net message to client' ela= 1 p1=1413697536 p2=1 p3=0
FETCH #4:c=0,e=209,p=0,cr=0,cu=0,mis=0,r=2,dep=0,og=3,tim=2903546300212
WAIT #4: nam='SQL*Net message from client' ela= 3618 p1=1413697536 p2=1 p3=0
WAIT #4: nam='SQL*Net message to client' ela= 2 p1=1413697536 p2=1 p3=0
FETCH #4:c=0,e=161,p=0,cr=0,cu=0,mis=0,r=2,dep=0,og=3,tim=2903546304170
WAIT #4: nam='SQL*Net message from client' ela= 3442 p1=1413697536 p2=1 p3=0
WAIT #4: nam='SQL*Net message to client' ela= 1 p1=1413697536 p2=1 p3=0
WAIT #4: nam='SQL*Net more data to client' ela= 56 p1=1413697536 p2=2199 p3=0
FETCH #4:c=0,e=310,p=0,cr=0,cu=0,mis=0,r=2,dep=0,og=3,tim=2903546308079
WAIT #4: nam='SQL*Net message from client' ela= 4107 p1=1413697536 p2=1 p3=0
WAIT #4: nam='SQL*Net message to client' ela= 1 p1=1413697536 p2=1 p3=0
FETCH #4:c=0,e=174,p=0,cr=0,cu=0,mis=0,r=2,dep=0,og=3,tim=2903546312530
=========================================================
Statspack(Abbreviated)
=========================================================
STATSPACK report for

DB Name         DB Id    Instance     Inst Num Release     Cluster Host
------------ ----------- ------------ -------- ----------- ------- ------------
DIBSN          808090191 dibsn               1 9.2.0.4.0   NO      su251

            Snap Id     Snap Time      Sessions Curs/Sess Comment
            ------- ------------------ -------- --------- -------------------
Begin Snap:      23 24-Feb-04 16:42:20       20       5.5
  End Snap:      24 24-Feb-04 16:44:39       20       5.5
   Elapsed:                2.32 (mins)

Cache Sizes (end)
~~~~~~~~~~~~~~~~~
               Buffer Cache:     1,024M      Std Block Size:         8K
           Shared Pool Size:     1,024M          Log Buffer:     1,600K

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ------

          Followup:

hows about the tkprof -- it'll summarize the waits for us and show us what steps
of the plan are doing writes as well.

      Rating: 4
Tkprof - more
      info  February 25, 2004
Reviewer:  David
      Piazza  from San Mateo, CA
There was also one query before this, to check on privileges before this.  I
also forgot to mention that we can't go to CBO with Siebel.  Below is the output
from tkprof, and after that some statspack.

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     38.75     133.38        811    2584398         16          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        7     38.75     133.38        811    2584398         16          10

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: 1070

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                       6        0.00          0.00
  SQL*Net more data to client                     3        0.00          0.00
  SQL*Net message from client                     5        0.00          0.02
  SQL*Net more data from client                   1        0.00          0.00
  direct path write                             576        0.57         96.33
  direct path read                               15        0.00          0.01

********************************************************************************

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      2      0.00       0.00          0          0          0           0
Fetch        6     38.75     133.38        811    2584431         16          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       10     38.75     133.38        811    2584431         16          10

Misses in library cache during parse: 0

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                       8        0.00          0.00
  SQL*Net message from client                     7        0.00          0.02
  SQL*Net more data from client                   2        0.00          0.00
  SQL*Net more data to client                     3        0.00          0.00
  direct path write                             576        0.57         96.33
  direct path read                               15        0.00          0.01

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: dibsn_ora_2403.trc
Trace file compatibility: 9.00.01
Sort options: default

       1  session in tracefile.
       2  user  SQL statements in trace file.
       0  internal SQL statements in trace file.
       2  SQL statements in trace file.
       2  unique SQL statements in trace file.
     805  lines in trace file.

============================================================
Statspack - Abbreviated
============================================================
STATSPACK report for

DB Name         DB Id    Instance     Inst Num Release     Cluster Host
------------ ----------- ------------ -------- ----------- ------- ------------
DIBSN          808090191 dibsn               1 9.2.0.4.0   NO      su251

            Snap Id     Snap Time      Sessions Curs/Sess Comment
            ------- ------------------ -------- --------- -------------------
Begin Snap:      23 24-Feb-04 16:42:20       20       5.5
  End Snap:      24 24-Feb-04 16:44:39       20       5.5
   Elapsed:                2.32 (mins)

Cache Sizes (end)
~~~~~~~~~~~~~~~~~
               Buffer Cache:     1,024M      Std Block Size:         8K
           Shared Pool Size:     1,024M          Log Buffer:     1,600K

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:              2,840.23            394,792.00
              Logical reads:             14,774.64          2,053,675.00
              Block changes:                  4.29                597.00
             Physical reads:                  5.84                812.00
            Physical writes:                115.78             16,094.00
                 User calls:                  1.00                139.00
                     Parses:                  0.14                 19.00
                Hard parses:                  0.01                  1.00
                      Sorts:                  0.64                 89.00
                     Logons:                  0.00                  0.00
                   Executes:                  1.20                167.00
               Transactions:                  0.01

  % Blocks changed per Read:    0.03    Recursive Call %:    56.70
 Rollback per transaction %:    0.00       Rows per Sort:  1494.17

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:  100.00
            Buffer  Hit   %:  100.00    In-memory Sort %:   98.88
            Library Hit   %:   99.10        Soft Parse %:   94.74
         Execute to Parse %:   88.62         Latch Hit %:  100.00
Parse CPU to Parse Elapsd %:  100.00     % Non-Parse CPU:   99.98

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   18.40   18.40
    % SQL with executions>1:   54.21   54.25
  % Memory for SQL w/exec>1:   63.69   63.70

Top 5 Timed Events
~~~~~~~~~~~~~~~~~~                                                     % Total
Event                                               Waits    Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
direct path write                                     576          96    65.05
CPU time                                                           46    30.94
control file parallel write                            44           4     3.03
log file sync                                           2           1      .64
db file parallel write                                  4           0      .34
          -------------------------------------------------------------
 Wait Events for DB: DIBSN  Instance: dibsn  Snaps: 23 -24
-> 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                     576          0         96    167    576.0
control file parallel write            44          0          4    102     44.0
log file sync                           2          0          1    472      2.0
db file parallel write                  4          0          0    124      4.0
direct path read                       15          0          0      1     15.0
db file sequential read                 1          0          0      7      1.0
control file sequential read           62          0          0      0     62.0
log file parallel write                 6          6          0      0      6.0
SQL*Net more data to client             1          0          0      0      1.0
SQL*Net message from client           135          0        548   4059    135.0
SQL*Net message to client             136          0          0      0    136.0
          -------------------------------------------------------------
 Background Wait Events for DB: DIBSN  Instance: dibsn  Snaps: 23 -24
-> 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            43          0          4    104     43.0
db file parallel write                  3          0          0    145      3.0
control file sequential read           12          0          0      0     12.0
log file parallel write                 5          5          0      0      5.0
rdbms ipc message                     144        142        395   2742    144.0
pmon timer                             47         47        135   2876     47.0
smon timer                              1          1         45  45155      1.0
          -------------------------------------------------------------
 SQL ordered by Gets for DB: DIBSN  Instance: dibsn  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

                                                     CPU      Elapsd
  Buffer Gets    Executions  Gets per Exec  %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
         80,472           12        6,706.0    3.9     1.74      1.71 4126713147
Module: siebmtsh@eagnmnsu251 (TNS V1-V3)
  select ROW_ID, PAR_REQ_ID    from SIEBEL.S_SRM_REQUEST   where
 STATUS = 'QUEUED'     and EXPIRATION_DT  <  SYSDATE

Instance Activity Stats for DB: DIBSN  Instance: dibsn  Snaps: 23 -24

Statistic                                      Total     per Second    per Trans
--------------------------------- ------------------ -------------- ------------
CPU used by this session                       4,582           33.0      4,582.0
CPU used when call started                     4,583           33.0      4,583.0
CR blocks created                                  0            0.0          0.0
DBWR checkpoint buffers written                    7            0.1          7.0
DBWR checkpoints                                   0            0.0          0.0
DBWR transaction table writes                      2            0.0          2.0
DBWR undo block writes                             3            0.0          3.0
SQL*Net roundtrips to/from client                128            0.9        128.0
active txn count during cleanout                  17            0.1         17.0
background checkpoints completed                   0            0.0          0.0
background checkpoints started                     0            0.0          0.0
background timeouts                              160            1.2        160.0
buffer is not pinned count                 1,947,541       14,011.1  1,947,541.0
buffer is pinned count                     2,113,922       15,208.1  2,113,922.0
bytes received via SQL*Net from c              6,154           44.3      6,154.0
bytes sent via SQL*Net to client              23,771          171.0     23,771.0
calls to get snapshot scn: kcmgss                282            2.0        282.0
calls to kcmgas                                   12            0.1         12.0
calls to kcmgcs                                   10            0.1         10.0
change write time                                  1            0.0          1.0
cleanout - number of ktugct calls                 34            0.2         34.0
cleanouts and rollbacks - consist                  0            0.0          0.0
cleanouts only - consistent read                   9            0.1          9.0
cluster key scan block gets                       12            0.1         12.0
cluster key scans                                  3            0.0          3.0
commit cleanout failures: callbac                  1            0.0          1.0
commit cleanouts                                  86            0.6         86.0
commit cleanouts successfully com                 85            0.6         85.0
commit txn count during cleanout                  28            0.2         28.0
consistent changes                                 0            0.0          0.0
consistent gets                            2,053,002       14,769.8  2,053,002.0
consistent gets - examination              1,773,851       12,761.5  1,773,851.0
cursor authentications                             1            0.0          1.0
data blocks consistent reads - un                  0            0.0          0.0
db block changes                                 597            4.3        597.0
db block gets                                    586            4.2        586.0
deferred (CURRENT) block cleanout                 37            0.3         37.0
enqueue conversions                               25            0.2         25.0
enqueue releases                                 263            1.9        263.0
enqueue requests                                 263            1.9        263.0
execute count                                    167            1.2        167.0
free buffer requested                             39            0.3         39.0
immediate (CR) block cleanout app                  9            0.1          9.0
immediate (CURRENT) block cleanou                 22            0.2         22.0
index fetch by key                           842,946        6,064.4    842,946.0
index scans kdiixs1                          100,245          721.2    100,245.0
leaf node 90-10 splits                             1            0.0          1.0
leaf node splits                                  10            0.1         10.0
logons cumulative                                  0            0.0          0.0
messages received                                  4            0.0          4.0
messages sent                                      4            0.0          4.0
no buffer to keep pinned count                     0            0.0          0.0
no work - consistent read gets               186,722        1,343.3    186,722.0
opened cursors cumulative                         18            0.1         18.0
parse count (failures)                             0            0.0          0.0
parse count (hard)                                 1            0.0          1.0
 Instance Activity Stats for DB: DIBSN  Instance: dibsn  Snaps: 23 -24

Statistic                                      Total     per Second    per Trans
--------------------------------- ------------------ -------------- ------------
parse count (total)                               19            0.1         19.0
parse time cpu                                     1            0.0          1.0
parse time elapsed                                 1            0.0          1.0
physical reads                                   812            5.8        812.0
physical reads direct                            811            5.8        811.0
physical writes                               16,094          115.8     16,094.0
physical writes direct                        16,087          115.7     16,087.0
physical writes non checkpoint                16,089          115.8     16,089.0
prefetched blocks                                  0            0.0          0.0
process last non-idle time                         0            0.0          0.0
recursive calls                                  182            1.3        182.0
recursive cpu usage                              486            3.5        486.0
redo blocks written                              840            6.0        840.0
redo buffer allocation retries                     0            0.0          0.0
redo entries                                     310            2.2        310.0
redo log space requests                            0            0.0          0.0
redo log space wait time                           0            0.0          0.0
redo size                                    394,792        2,840.2    394,792.0
redo synch time                                   61            0.4         61.0
redo synch writes                                  1            0.0          1.0
redo wastage                                   1,504           10.8      1,504.0
redo write time                                  138            1.0        138.0
redo writes                                        6            0.0          6.0
rollback changes - undo records a                  2            0.0          2.0
rollbacks only - consistent read                   0            0.0          0.0
rows fetched via callback                    842,458        6,060.9    842,458.0
session connect time                               0            0.0          0.0
session logical reads                      2,053,675       14,774.6  2,053,675.0
session pga memory max                    23,339,416      167,909.5 ############
session uga memory max                    22,238,720      159,990.8 ############
shared hash latch upgrades - no w            100,271          721.4    100,271.0
shared hash latch upgrades - wait                  0            0.0          0.0
sorts (disk)                                       1            0.0          1.0
sorts (memory)                                    88            0.6         88.0
sorts (rows)                                 132,981          956.7    132,981.0
switch current to new buffer                       0            0.0          0.0
table fetch by rowid                       1,554,641       11,184.5  1,554,641.0
table fetch continued row                          0            0.0          0.0
table scan blocks gotten                       9,376           67.5      9,376.0
table scan rows gotten                        89,287          642.4     89,287.0
table scans (long tables)                          0            0.0          0.0
table scans (short tables)                        31            0.2         31.0
transaction rollbacks                              1            0.0          1.0
user calls                                       139            1.0        139.0
user commits                                       1            0.0          1.0
user rollbacks                                     0            0.0          0.0
workarea executions - onepass                      0            0.0          0.0
workarea executions - optimal                     93            0.7         93.0
          -------------------------------------------------------------
 Tablespace IO Stats for DB: DIBSN  Instance: dibsn  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)
-------------- ------- ------ ------- ------------ -------- ---------- ------
TEMP_TS
            28       0    0.4    29.0          623        4          0    0.0
UNDO_TS
             0       0    0.0                    5        0          0    0.0
DATA_TS
             0       0    0.0                    3        0          0    0.0
INDEX_TS
             0       0    0.0                    2        0          0    0.0
PERFSTAT
             1       0   10.0     1.0            0        0          0    0.0
SYSTEM
             0       0    0.0                    1        0          0    0.0
          -------------------------------------------------------------
 File IO Stats for DB: DIBSN  Instance: dibsn  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)
-------------- ------- ------ ------- ------------ -------- ---------- ------
DATA_TS                  /d00/oradata/dibsn/data_ts01.dbf
             0       0                           3        0          0

INDEX_TS                 /d00/oradata/dibsn/index_ts01.dbf
             0       0                           2        0          0

PERFSTAT                 /d00/oradata/dibsn/perfstat.dbf
             1       0   10.0     1.0            0        0          0

SYSTEM                   /d00/oradata/dibsn/system01.dbf
             0       0                           1        0          0

TEMP_TS                  /d00/oradata/dibsn/temp_ts01.dbf
            28       0    0.4    29.0          623        4          0

UNDO_TS                  /d00/oradata/dibsn/undo_ts1.dbf
             0       0                           5        0          0

          -------------------------------------------------------------
 Buffer Pool Statistics for DB: DIBSN  Instance: dibsn  Snaps: 23 -24
-> 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      127,040 100.0   2,368,257           0         11       0        0       0
          -------------------------------------------------------------

Instance Recovery Stats for DB: DIBSN  Instance: dibsn  Snaps: 23 -24
-> 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     0    18        339       2385       2334      92160       2334     500000
E     0    18        391       3476       3160      92160       3160     500000
          -------------------------------------------------------------

Buffer Pool Advisory for DB: DIBSN  Instance: dibsn  End Snap: 24
-> Only rows with estimated physical reads >0 are displayed
-> ordered by Block Size, Buffers For Estimate

        Size for  Size      Buffers for  Est Physical          Estimated
P   Estimate (M) Factr         Estimate   Read Factor     Physical Reads
--- ------------ ----- ---------------- ------------- ------------------
D             96    .1           11,910         32.67          1,567,666
D            192    .2           23,820         13.94            668,827
D            288    .3           35,730         10.05            482,216
D            384    .4           47,640          1.00             48,117
D            480    .5           59,550          1.00             47,992
D            576    .6           71,460          1.00             47,992
D            672    .7           83,370          1.00             47,992
D            768    .8           95,280          1.00             47,992
D            864    .8          107,190          1.00             47,992
D            960    .9          119,100          1.00             47,992
D          1,024   1.0          127,040          1.00             47,992
D          1,056   1.0          131,010          1.00             47,992
D          1,152   1.1          142,920          1.00             47,992
D          1,248   1.2          154,830          1.00             47,992
D          1,344   1.3          166,740          1.00             47,992
D          1,440   1.4          178,650          1.00             47,992
D          1,536   1.5          190,560          1.00             47,992
D          1,632   1.6          202,470          1.00             47,992
D          1,728   1.7          214,380          1.00             47,992
D          1,824   1.8          226,290          1.00             47,992
D          1,920   1.9          238,200          1.00             47,992
          -------------------------------------------------------------
 PGA Aggr Target Stats for DB: DIBSN  Instance: dibsn  Snaps: 23 -24
-> B: Begin snap   E: End snap (rows dentified with B or E contain data
   which is absolute i.e. not diffed over the interval)
-> PGA cache hit % - percentage of W/A (WorkArea) data processed only in-memory
-> Auto PGA Target - actual workarea memory target
-> W/A PGA Used    - amount of memory used for all Workareas (manual + auto)
-> %PGA W/A Mem    - percentage of PGA memory allocated to workareas
-> %Auto W/A Mem   - percentage of workarea memory controlled by Auto Mem Mgmt
-> %Man W/A Mem    - percentage of workarea memory under manual control

PGA Cache Hit % W/A MB Processed Extra W/A MB Read/Written
--------------- ---------------- -------------------------
          100.0                6                         0

                                             %PGA  %Auto   %Man
  PGA Aggr  Auto PGA   PGA Mem    W/A PGA    W/A    W/A    W/A   Global Mem
  Target(M) Target(M)  Alloc(M)   Used(M)    Mem    Mem    Mem    Bound(K)
- --------- --------- ---------- ---------- ------ ------ ------ ----------
B     1,024       910       61.1       31.4   51.4  100.0     .0     52,428
E     1,024       914       82.6        7.8    9.4  100.0     .0     52,428
          -------------------------------------------------------------

PGA Aggr Target Histogram for DB: DIBSN  Instance: dibsn  Snaps: 23 -24
-> Optimal Executions are purely in-memory operations

    Low    High
Optimal Optimal    Total Execs Optimal Execs 1-Pass Execs M-Pass Execs
------- ------- -------------- ------------- ------------ ------------
     8K     16K             72            72            0            0
    16K     32K             10            10            0            0
    32K     64K              3             3            0            0
    64K    128K              2             2            0            0
   256K    512K              2             2            0            0
   512K   1024K              2             2            0            0
     1M      2M              2             2            0            0
          -------------------------------------------------------------

PGA Memory Advisory for DB: DIBSN  Instance: dibsn  End Snap: 24
-> When using Auto Memory Mgmt, minimally choose a pga_aggregate_target value
   where Estd PGA Overalloc Count is 0

                                       Estd Extra    Estd PGA   Estd PGA
PGA Target    Size           W/A MB   W/A MB Read/      Cache  Overalloc
  Est (MB)   Factr        Processed Written to Disk     Hit %      Count
---------- ------- ---------------- ---------------- -------- ----------
       128     0.1          3,501.3          3,379.5     51.0          0
       256     0.3          3,501.3          3,379.5     51.0          0
       512     0.5          3,501.3          3,379.5     51.0          0
       768     0.8          3,501.3          3,379.5     51.0          0
     1,024     1.0          3,501.3          3,379.5     51.0          0
     1,229     1.2          3,501.3          3,379.5     51.0          0
     1,434     1.4          3,501.3          3,379.5     51.0          0
     1,638     1.6          3,501.3          3,379.5     51.0          0
     1,843     1.8          3,501.3          3,379.5     51.0          0
     2,048     2.0          3,501.3          3,379.5     51.0          0
     3,072     3.0          3,501.3          3,379.5     51.0          0
     4,096     4.0          3,501.3          3,379.5     51.0          0
     6,144     6.0          3,501.3          3,379.5     51.0          0
     8,192     8.0          3,501.3          3,379.5     51.0          0
          -------------------------------------------------------------

init.ora Parameters for DB: DIBSN  Instance: dibsn  Snaps: 23 -24

                                                                  End value
Parameter Name                Begin value                       (if different)
----------------------------- --------------------------------- --------------
audit_trail                   DB
background_dump_dest          /u00/oracle/admin/dibsn/bdump
compatible                    9.2.0.4
control_files                 /d00/oradata/dibsn/control01.ctl,
core_dump_dest                /u00/oracle/admin/dibsn/cdump
db_block_size                 8192
db_cache_size                 1073741824
db_domain                     USPS.GOV
db_file_multiblock_read_count 32
db_files                      200
db_name                       dibsn
global_names                  TRUE
java_pool_size                0
job_queue_processes           5
large_pool_size               16777216
log_archive_dest              /d00/oradata/dibsn/arch
log_archive_format            dibsn_%t_%s.arc
log_archive_start             FALSE
log_buffer                    1638400
log_checkpoint_interval       500000
max_dump_file_size            100240
 **** MESSAGE TRUNCATED ****

          Followup:

don't care about statspack here

*NEED* the plan with the statistics next to it.  like this:

select *
from
 big_table where owner = 'SCOTT'

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       15      0.01       0.06         36        222          0         204
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       17      0.02       0.07         36        222          0         204

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 63

Rows     Row Source Operation
-------  ---------------------------------------------------
    204  TABLE ACCESS BY INDEX ROWID OBJ#(30511) (cr=222 r=36 w=0 time=66608 us)
    204   INDEX RANGE SCAN OBJ#(30531) (cr=18 r=4 w=0 time=52263 us)(object id
30531)

without those stat records, I cannot tell.  You need to make sure the cursor in
question is closed to get the stat records in the trace file

      Rating:
      4
Plan  February 25, 2004
Reviewer:  David
      Piazza  from San Mateo, CA
The output of the tkprof for the time period didn't show a plan.  I've pasted
the plan from a similar run for the same query, just a little bit earlier...

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      2      0.01       0.01          0          0          0           0
Fetch        5     38.57     135.02        811    2584364         16          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        8     38.58     135.03        811    2584364         16          10

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: 1040

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  SORT ORDER BY (cr=2584364 r=811 w=16086 time=135027403 us)
 116893   NESTED LOOPS OUTER (cr=2584364 r=0 w=0 time=32745795 us)
 116893    NESTED LOOPS OUTER (cr=2352085 r=0 w=0 time=30317702 us)
 116893     NESTED LOOPS OUTER (cr=2117976 r=0 w=0 time=26138732 us)
 116893      NESTED LOOPS  (cr=1884188 r=0 w=0 time=23162321 us)
 116916       NESTED LOOPS OUTER (cr=1650400 r=0 w=0 time=20527537 us)
 116916        NESTED LOOPS OUTER (cr=1417926 r=0 w=0 time=17699311 us)
 116916         NESTED LOOPS OUTER (cr=1417926 r=0 w=0 time=17194639 us)
 116916          NESTED LOOPS OUTER (cr=1417926 r=0 w=0 time=16621733 us)
 116916           NESTED LOOPS OUTER (cr=1068617 r=0 w=0 time=13573507 us)
 116916            NESTED LOOPS OUTER (cr=890187 r=0 w=0 time=11759108 us)
 116916             NESTED LOOPS OUTER (cr=656540 r=0 w=0 time=8509761 us)
 116916              NESTED LOOPS OUTER (cr=422752 r=0 w=0 time=6439045 us)
 116916               NESTED LOOPS OUTER (cr=246642 r=0 w=0 time=4434551 us)
 116916                NESTED LOOPS OUTER (cr=246642 r=0 w=0 time=3897303 us)
 116916                 TABLE ACCESS FULL S_SRV_REQ (cr=12814 r=0 w=0
time=1171274 us)
 116910                 TABLE ACCESS BY INDEX ROWID S_USER (cr=233828 r=0 w=0
time=2142510 us)
 116910                  INDEX UNIQUE SCAN S_USER_U2 (cr=116918 r=0 w=0
time=1098468 us)(object id 73361)
      0                TABLE ACCESS BY INDEX ROWID S_ASSET (cr=0 r=0 w=0
time=159187 us)
      0                 INDEX UNIQUE SCAN S_ASSET_P1 (cr=0 r=0 w=0 time=43542
us)(object id 64978)
  88054               TABLE ACCESS BY INDEX ROWID S_ZIPCODE (cr=176110 r=0 w=0
time=1580226 us)
  88054                INDEX UNIQUE SCAN S_ZIPCODE_P1 (cr=88056 r=0 w=0
time=808235 us)(object id 73673)
 116893              TABLE ACCESS BY INDEX ROWID S_USER (cr=233788 r=0 w=0
time=1629940 us)
 116893               INDEX UNIQUE SCAN S_USER_U2 (cr=116895 r=0 w=0 time=836169
us)(object id 73361)
 116822             TABLE ACCESS BY INDEX ROWID S_CONTACT (cr=233647 r=0 w=0
time=2777513 us)
 116822              INDEX UNIQUE SCAN S_CONTACT_U2 (cr=116825 r=0 w=0
time=1192275 us)(object id 65967)
  89214            TABLE ACCESS BY INDEX ROWID S_ZIPCODE (cr=178430 r=0 w=0
time=1368238 us)
  89214             INDEX UNIQUE SCAN S_ZIPCODE_P1 (cr=89216 r=0 w=0 time=695398
us)(object id 73673)
 116393           TABLE ACCESS BY INDEX ROWID S_ADDR_ORG (cr=349309 r=0 w=0
time=2569253 us)
 116393            INDEX UNIQUE SCAN S_ADDR_ORG_P1 (cr=232916 r=0 w=0
time=1388676 us)(object id 64617)
      0          TABLE ACCESS BY INDEX ROWID S_ENTLMNT (cr=0 r=0 w=0 time=195184
us)
      0           INDEX UNIQUE SCAN S_ENTLMNT_P1 (cr=0 r=0 w=0 time=39297
us)(object id 67667)
      0         TABLE ACCESS BY INDEX ROWID S_ORG_EXT (cr=0 r=0 w=0 time=136823
us)
      0          INDEX UNIQUE SCAN S_ORG_EXT_U3 (cr=0 r=0 w=0 time=40472
us)(object id 70193)
 115918        TABLE ACCESS BY INDEX ROWID S_ORG_EXT (cr=232474 r=0 w=0
time=2327534 us)
 115918         INDEX UNIQUE SCAN S_ORG_EXT_U3 (cr=116556 r=0 w=0 time=1026256
us)(object id 70193)
 116893       TABLE ACCESS BY INDEX ROWID S_CONTACT (cr=233788 r=0 w=0
time=2210582 us)
 116893        INDEX UNIQUE SCAN S_CONTACT_P1 (cr=116895 r=0 w=0 time=938553
us)(object id 65966)
 116893      TABLE ACCESS BY INDEX ROWID S_ORG_EXT (cr=233788 r=0 w=0
time=2448011 us)
 116893       INDEX UNIQUE SCAN S_ORG_EXT_U3 (cr=116895 r=0 w=0 time=827132
us)(object id 70193)
 116890     TABLE ACCESS BY INDEX ROWID S_SRV_REQ_X (cr=234109 r=0 w=0
time=3500634 us)
 116890      INDEX RANGE SCAN S_SRV_REQ_X_U1 (cr=117219 r=0 w=0 time=1984483
us)(object id 72867)
 116138    TABLE ACCESS BY INDEX ROWID S_CONTACT (cr=232279 r=0 w=0 time=1900900
us)
 116138     INDEX UNIQUE SCAN S_CONTACT_U2 (cr=116141 r=0 w=0 time=1032064
us)(object id 65967)

          Followup:

thats nested loop heck isn't it.  ugh.

anyway, its all about the last step:

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  SORT ORDER BY (cr=2584364 r=811 w=16086 time=135027403 us)

that is where all of the write IO is.

can you try with workarea set to manual and a manually set sort area size.
(there is a the fact that a single session will never get more then something
like 5% of the pga aggregate target here as well.... I think it is 5%, have to
go look that up again..)

      Rating: 3
A
      question...  February 25, 2004
Reviewer:
      Matt  from Australia
I saw the Direct IO wait (which I understand is a write to temp and so part of
the sort which your suggestion tunes).

Can you clarify whether the sort results in 10 rows, or whether the sort is
actually sorting 10 rows?

I curious as to what the actual query is doing, it looks to me like there is a
distinct on a join or something like that. ie: processing the 116910 rows and
pulling out 10.

Would an indexing strategy that reduced the number of rows that came out of the
step:

 116910                 TABLE ACCESS BY INDEX ROWID S_USER (cr=233828 r=0 w=0
time=2142510 us)

also help?

I am thinking that by reducing the number of rows that get to the sort we might
resolve the problem with the sort to disk.

Of course, it all depends on what the query is.

I'm just trying to develop my understanding of query tuning and this caught my
eye...
...I've been reading Guy Harrison's book - I am pretty impressed although some
of the material seems to be a little dated with respect to 9i.

Regards.

Thanks.

          Followup:

the sort phase returned 10 rows to the client.  it looks like the client asked
to sort tons of records but was only interested in the first 10.

sort of like what you might see with:

declare
    l_cnt number := 0;
begin
    for x in ( select * from big_table order by last_ddl_time desc )
    loop
        l_cnt := l_cnt+1;
        exit when l_cnt = 10;
    end loop;
end;

                                                         select * from big_table
order by last_ddl_time desc

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.07          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch       10     32.46      68.81      52293      50541         39          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       13     32.48      68.89      52293      50541         39          10

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 60     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  SORT ORDER BY (cr=50541 r=52293 w=51296 time=68818828 us)
3500000   TABLE ACCESS FULL BIG_TABLE (cr=50541 r=50526 w=0 time=13681214 us)

(well exactly what you might see).

A normal fix would be

a) first_rows hint
b) index on that order by column

but a) is precluded by Siebel

      Rating: 2
9i
      upgrade  February 25, 2004
Reviewer:  Jim
       from CA, USA
Performance on 9i is not good and everything is related to
optimizer_features_enable = 9.2.0.
It is not a good idea to migrate unless ORACLE solves the issue related to this
parameter and the hidden params it turns on. maybe a good idea to skip this bug
product and wait for 10g

      Rating: 4
9.2 Performance
      is not a "Bug"  February 25, 2004
Reviewer:
      Richard  from Bensenville, IL
We've been on 9.2.0.4 for 10 weeks, upgrading from 8.1.7.4. After our initial
upgrade we too saw some performance problems in the database, queries taking
longer to run, disk IO higher, cpu usage nearly maxed out.
Turns out most of the query performance problems were due to stale statistics
and query hints that were no longer relevant. We also found an OEM 9.2 bug on
HPUX that caused an interminable amount of delete activity from an OEM table
that churned up tons of disk IO and cpu usage, not to mention periodic spikes in
archived redo log generation.
After recomputing stats on the relevant tables and indexes, removing hints that
were not absolutely necessary, and cleaning up the OEM repository manually, our
cpu and disk IO dropped to less than HALF the level from 8.1.7.4! And, since we
recently doubled the memory on the server, our query performance (data warehouse
environment) has increased quite a bit with the larger buffer cache and sort
area size.

      Rating: 5
interesting
      situation,  February 26, 2004
Reviewer:  SR

Tom,

the filter at each level of the Siebel app produced 1k or more rows but at end
only 10 rows?

Can following sql shed some light

   select t15.*, t2.* from t15, t2
   WHERE
      T15.OWNER_EMP_ID = T2.ROW_ID AND
      (T15.TEMPLATE_FLG != 'Y')
   ORDER BY
      T15.CREATED DESC

I am just curious as to how 10 rows.

Thanks

          Followup:

they only fetched that many and stopped.

      Rating:
      3
workarea_size_policy set to manual  February 27,
      2004
Reviewer:  DAP  from San Mateo, CA
I set workarea_size_policy to manual, and sort_area_size to 50000000.  The
elapsed time was just about the same as before, with workarea_size_policy set to
auto, and the plan looks the same.  The application returns 10 rows and displays
them to the screen.  When a '+' is clicked on, then the next 10 rows show, and
so on.

TKPROF: Release 9.2.0.1.0 - Production on Fri Feb 27 14:47:43 2004

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Trace file: dibsn_ora_21216.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

SELECT
      T15.LAST_UPD_BY,
      T15.CREATED,
      T15.ROW_ID,
      T15.CONFLICT_ID,
      T15.CREATED_BY,
      T15.CREATED,
      T15.LAST_UPD,
      T15.MODIFICATION_NUM,
      T8.ROW_ID,
      T15.OU_ADDR_ID,
      T1.MAIN_PH_NUM,
      T15.ACT_CLOSE_DT,
      T15.X_IBSN_CUST_SATISFIED,
      T3.NAME,
      T1.CUST_STAT_CD,
      T15.X_IBSN_IMPACT,
      T15.SR_PROD_VERSION,
      T2.EMAIL_ADDR,
      T8.MODIFICATION_NUM,
      T10.FST_NAME,
      T15.X_IBSN_CRITICAL_REASON,
      T3.INTEGRATION_ID,
      T15.X_IBSN_REPT_SUBISS,
      T15.OWNER_EMP_ID,
      T15.X_IBSN_MAIL_TYPE,
      T15.X_IBSN_ORIG_ZIP_ID,
      T15.CST_CON_ID,
      T8.CREATED,
      T10.LAST_NAME,
      T8.LAST_UPD_BY,
      T4.NAME,
      T15.SR_REPRODUCE,
      T2.FST_NAME,
      T15.SR_SUB_STAT_ID,
      T15.ASGN_USR_EXCLD_FLG,
      T15.PR_CON_ORG_ID,
      T8.CREATED_BY,
      T15.CST_OU_ID,
      T12.ZIPCODE,
      T15.PRI_LST_ID,
      T7.X_IBSN_CUSTNAME,
      T1.OU_NUM,
      T15.X_IBSN_ACCT_TYPE,
      T2.WORK_PH_NUM,
      T15.OWNER_OU_ID,
      T1.OU_TYPE_CD,
      T8.PAR_ROW_ID,
      T2.LAST_NAME,
      T15.SR_SEV_CD,
      T15.SR_PRIO_CD,
      T11.LOGIN,
      T8.ATTRIB_05,
      T14.LOGIN,
      T10.ROW_ID,
      T8.ATTRIB_45,
      T13.SERIAL_NUM,
      T15.X_IBSN_DEST_ZIP_ID,
      T1.DESC_TEXT,
      T8.ATTRIB_03,
      T10.WORK_PH_NUM,
      T9.ZIPCODE,
      T15.SR_STAT_ID,
      T15.SR_CST_NUM,
      T15.BU_ID,
      T15.RATE_LST_ID,
      T15.X_IBSN_ESCALATION,
      T15.ORG_PRDINT_ID,
      T1.X_IBSN_ACCT_NAME,
      T1.INTEGRATION_ID,
      T15.X_IBSN_REPT_ISSUE,
      T15.SR_SUB_AREA,
      T15.ASGN_MANL_FLG,
      T1.ROW_ID,
      T8.LAST_UPD,
      T1.PR_ADDR_ID,
      T15.ASSET_ID,
      T1.PR_SYN_ID,
      T15.X_IBSN_SR_TYPE,
      T1.PR_SRV_AGREE_ID,
      T1.LOC,
      T15.ROW_STATUS,
      T15.SR_TITLE,
      T11.LOGIN,
      T15.ASGN_SYS_FLG,
      T6.LAST_NAME,
      T5.SVC_CALENDAR_ID,
      T15.SR_NUM,
      T15.BILLABLE_FLG,
      T15.SR_AREA,
      T15.SR_CAT_TYPE_CD,
      T1.MAIN_FAX_PH_NUM,
      T2.PR_HELD_POSTN_ID,
      T15.PRDINT_ID,
      T1.NAME,
      T15.DESC_TEXT,
      T6.FST_NAME,
      T3.ROW_ID,
      T15.X_IBSN_REPT_CAT,
      T15.AGREE_ID,
      T15.SR_SUBTYPE_CD,
      T15.PR_SYMPTOM_CD,
      T10.PR_OU_ADDR_ID,
      T8.CONFLICT_ID,
      T15.SR_TYPE_CD
   FROM
       SIEBEL.S_ORG_EXT T1,
       SIEBEL.S_CONTACT T2,
       SIEBEL.S_ORG_EXT T3,
       SIEBEL.S_ORG_EXT T4,
       SIEBEL.S_ENTLMNT T5,
       SIEBEL.S_CONTACT T6,
       SIEBEL.S_ADDR_ORG T7,
       SIEBEL.S_SRV_REQ_X T8,
       SIEBEL.S_ZIPCODE T9,
       SIEBEL.S_CONTACT T10,
       SIEBEL.S_USER T11,
       SIEBEL.S_ZIPCODE T12,
       SIEBEL.S_ASSET T13,
       SIEBEL.S_USER T14,
       SIEBEL.S_SRV_REQ T15
   WHERE
      T15.PR_CON_ORG_ID = T3.PAR_ROW_ID (+) AND
      T15.CREATED_BY = T14.PAR_ROW_ID (+) AND
      T15.X_IBSN_DEST_ZIP_ID = T12.ROW_ID (+) AND
      T15.OWNER_EMP_ID = T2.ROW_ID AND
      T15.OWNER_EMP_ID = T11.PAR_ROW_ID (+) AND
      T8.ATTRIB_03 = T6.PAR_ROW_ID (+) AND
      T15.OU_ADDR_ID = T7.ROW_ID (+) AND
      T15.ASSET_ID = T13.ROW_ID (+) AND
      T15.CST_CON_ID = T10.PAR_ROW_ID (+) AND
      T15.AGREE_ID = T5.ROW_ID (+) AND
      T15.CST_OU_ID = T1.PAR_ROW_ID (+) AND
      T15.OWNER_OU_ID = T4.PAR_ROW_ID (+) AND
      T15.X_IBSN_ORIG_ZIP_ID = T9.ROW_ID (+) AND
      T15.ROW_ID = T8.PAR_ROW_ID (+) AND
      (T15.TEMPLATE_FLG != 'Y')
   ORDER BY
      T15.CREATED DESC

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      2      0.02       0.02          0          0          0           0
Fetch        5     39.11     133.15        190    2584538          4          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        8     39.13     133.18        190    2584538          4          10

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: 1070

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  SORT ORDER BY (cr=2584538 r=190 w=16083 time=133159165 us)
 116902   NESTED LOOPS OUTER (cr=2584538 r=0 w=0 time=33450991 us)
 116902    NESTED LOOPS OUTER (cr=2352247 r=0 w=0 time=31034499 us)
 116902     NESTED LOOPS OUTER (cr=2118120 r=0 w=0 time=26982220 us)
 116902      NESTED LOOPS  (cr=1884314 r=0 w=0 time=23955279 us)
 116925       NESTED LOOPS OUTER (cr=1650508 r=0 w=0 time=21232313 us)
 116925        NESTED LOOPS OUTER (cr=1418020 r=0 w=0 time=18381378 us)
 116925         NESTED LOOPS OUTER (cr=1418020 r=0 w=0 time=17934260 us)
 116925          NESTED LOOPS OUTER (cr=1418020 r=0 w=0 time=17413237 us)
 116925           NESTED LOOPS OUTER (cr=1068684 r=0 w=0 time=14271972 us)
 116925            NESTED LOOPS OUTER (cr=890248 r=0 w=0 time=12399599 us)
 116925             NESTED LOOPS OUTER (cr=656583 r=0 w=0 time=8861286 us)
 116925              NESTED LOOPS OUTER (cr=422777 r=0 w=0 time=6722518 us)
 116925               NESTED LOOPS OUTER (cr=246661 r=0 w=0 time=4660756 us)
 116925                NESTED LOOPS OUTER (cr=246661 r=0 w=0 time=4108752 us)
 116925                 TABLE ACCESS FULL S_SRV_REQ (cr=12814 r=0 w=0
time=1204769 us)
 116919                 TABLE ACCESS BY INDEX ROWID S_USER (cr=233847 r=0 w=0
time=2179138 us)
 116919                  INDEX UNIQUE SCAN S_USER_U2 (cr=116927 r=0 w=0
time=1101627 us)(object id 73361)
      0                TABLE ACCESS BY INDEX ROWID S_ASSET (cr=0 r=0 w=0
time=160858 us)
      0                 INDEX UNIQUE SCAN S_ASSET_P1 (cr=0 r=0 w=0 time=45211
us)(object id 64978)
  88057               TABLE ACCESS BY INDEX ROWID S_ZIPCODE (cr=176116 r=0 w=0
time=1620757 us)
  88057                INDEX UNIQUE SCAN S_ZIPCODE_P1 (cr=88059 r=0 w=0
time=826905 us)(object id 73673)
 116902              TABLE ACCESS BY INDEX ROWID S_USER (cr=233806 r=0 w=0
time=1684509 us)
 116902               INDEX UNIQUE SCAN S_USER_U2 (cr=116904 r=0 w=0 time=867956
us)(object id 73361)
 116831             TABLE ACCESS BY INDEX ROWID S_CONTACT (cr=233665 r=0 w=0
time=3027409 us)
 116831              INDEX UNIQUE SCAN S_CONTACT_U2 (cr=116834 r=0 w=0
time=1314118 us)(object id 65967)
  89217            TABLE ACCESS BY INDEX ROWID S_ZIPCODE (cr=178436 r=0 w=0
time=1421983 us)
  89217             INDEX UNIQUE SCAN S_ZIPCODE_P1 (cr=89219 r=0 w=0 time=716932
us)(object id 73673)
 116402           TABLE ACCESS BY INDEX ROWID S_ADDR_ORG (cr=349336 r=0 w=0
time=2616650 us)
 116402            INDEX UNIQUE SCAN S_ADDR_ORG_P1 (cr=232934 r=0 w=0
time=1402699 us)(object id 64617)
      0          TABLE ACCESS BY INDEX ROWID S_ENTLMNT (cr=0 r=0 w=0 time=147622
us)
      0           INDEX UNIQUE SCAN S_ENTLMNT_P1 (cr=0 r=0 w=0 time=39583
us)(object id 67667)
      0         TABLE ACCESS BY INDEX ROWID S_ORG_EXT (cr=0 r=0 w=0 time=140892
us)
      0          INDEX UNIQUE SCAN S_ORG_EXT_U3 (cr=0 r=0 w=0 time=40347
us)(object id 70193)
 115925        TABLE ACCESS BY INDEX ROWID S_ORG_EXT (cr=232488 r=0 w=0
time=2406276 us)
 115925         INDEX UNIQUE SCAN S_ORG_EXT_U3 (cr=116563 r=0 w=0 time=1099726
us)(object id 70193)
 116902       TABLE ACCESS BY INDEX ROWID S_CONTACT (cr=233806 r=0 w=0
time=2281919 us)
 116902        INDEX UNIQUE SCAN S_CONTACT_P1 (cr=116904 r=0 w=0 time=934200
us)(object id 65966)
 116902      TABLE ACCESS BY INDEX ROWID S_ORG_EXT (cr=233806 r=0 w=0
time=2515105 us)
 116902       INDEX UNIQUE SCAN S_ORG_EXT_U3 (cr=116904 r=0 w=0 time=846813
us)(object id 70193)
 116899     TABLE ACCESS BY INDEX ROWID S_SRV_REQ_X (cr=234127 r=0 w=0
time=3375594 us)
 116899      INDEX RANGE SCAN S_SRV_REQ_X_U1 (cr=117228 r=0 w=0 time=1929708
us)(object id 72867)
 116144    TABLE ACCESS BY INDEX ROWID S_CONTACT (cr=232291 r=0 w=0 time=1888523
us)
 116144     INDEX UNIQUE SCAN S_CONTACT_U2 (cr=116147 r=0 w=0 time=1067857
us)(object id 65967)

          Followup:

50,000,000 is just about what you are using with the pga aggregate target I
believe.  try something larger and make sure sort_area_retained_size is set to
the *same* value.

and you might find that if you index CREATED and CREATED is NOT NULL:

ops$tkyte@ORA9IR2> create table t1 ( x int primary key, created date NOT NULL,
template_flg varchar2(1), y int );
Table created.

ops$tkyte@ORA9IR2> create index t1_idx on t1(created);
Index created.

ops$tkyte@ORA9IR2> create table t2 ( x int references t1, y int );
Table created.

ops$tkyte@ORA9IR2> create index t2_idx on t2(x);
Index created.

ops$tkyte@ORA9IR2> create table t3 ( x int references t1, y int );
Table created.

ops$tkyte@ORA9IR2> create index t3_idx on t3(x);
Index created.

ops$tkyte@ORA9IR2> create table t4 ( x int references t1, y int );
Table created.

ops$tkyte@ORA9IR2> create index t4_idx on t4(x);
Index created.

ops$tkyte@ORA9IR2> create table t5 ( x int references t1, y int );
Table created.

ops$tkyte@ORA9IR2> create index t5_idx on t5(x);
Index created.

ops$tkyte@ORA9IR2> set autotrace traceonly explain
ops$tkyte@ORA9IR2> select *
  2    from t1, t2, t3, t4, t5
  3   where t1.x = t2.x(+)
  4     and t1.x = t3.x(+)
  5     and t1.x = t4.x(+)
  6     and t1.x = t5.x(+)
  7     and t1.template_flg <> 'Y'
  8   order by created desc
  9  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS (OUTER)
   2    1     NESTED LOOPS (OUTER)
   3    2       NESTED LOOPS (OUTER)
   4    3         NESTED LOOPS (OUTER)
   5    4           TABLE ACCESS (BY INDEX ROWID) OF 'T1'
   6    5             INDEX (FULL SCAN DESCENDING) OF 'T1_IDX' (NON-UNIQUE)
   7    4           TABLE ACCESS (BY INDEX ROWID) OF 'T5'
   8    7             INDEX (RANGE SCAN) OF 'T5_IDX' (NON-UNIQUE)
   9    3         TABLE ACCESS (BY INDEX ROWID) OF 'T4'
  10    9           INDEX (RANGE SCAN) OF 'T4_IDX' (NON-UNIQUE)
  11    2       TABLE ACCESS (BY INDEX ROWID) OF 'T3'
  12   11         INDEX (RANGE SCAN) OF 'T3_IDX' (NON-UNIQUE)
  13    1     TABLE ACCESS (BY INDEX ROWID) OF 'T2'
  14   13       INDEX (RANGE SCAN) OF 'T2_IDX' (NON-UNIQUE)

ops$tkyte@ORA9IR2> set autotrace off

You might find the RBO uses the index to avoid having to get ALL of the rows and
then sort as it did in the example above.  That'll return the first 10 rows
"asap"

      Rating: 3
Disk fetches
      down with workarea_size_policy set to manual  February 27,
      2004
Reviewer:  DAP  from San Mateo, CA
I forgot to mention in my previous reply that disk fetches went down to 190 from
811, but the elapsed time didn't improve.

      Rating: 5
Why upgrade to
      9i may be slow...  February 27, 2004
Reviewer:
      Peter Tran  from Houston, TX USA
We ran into the same situation on our initial upgrade to 9i.

It turned out that we had degree of parallelism (DOP) set on the tables and
indexes.  With 9i, Oracle deprecated the OPTIMIZER_PERCENT_PARALLEL parameter
(actually made it a hidden parameter with _OPTIMIZER_PERCENT_PARALLEL, but set
it to 100), which essentially causes the optimizer to favor FTS using PQ.

See my questions to Tom at:
http://asktom.oracle.com/pls/ask/f?p=4950:61:12403065903107147593::::P61_ID:1657270217898
Check the DOP on your tables and index.  If it's an OLTP application, make sure
you're not getting caught with PQ.

You can use:

select table_name, degree from tabs where degree > 1;
select index_name, degree from ind where degree > 1;

Otherwise, 9i is great!  A lot of cool features missing in 8i.

HTH,
-Peter

      Rating: 5
Tom, Siebel
      problem - I think you nailed it!!!  February 28, 2004

Reviewer:  SR
That was very good. I guess I got trapped in thinking since its driver table you
would need all rows of the t15. But it turns out the order by clause and 10 rows
is all it cares. Great solution!

Have a great day!

      Rating: 5
Big query
      improvement  February 29, 2004
Reviewer:
      DAP  from San Mateo, CA
I had pga_aggregate_target set to 250MB, and then increased it to 1GB, with
little or no improvement.

I benchmarked values for sort_area_size of 100MB, 200MB, 300MB, 400MB, 500MB,
and 250MB, with sort_area_retained_size set to sort_area_size, and settled on an
optimum sort_area_size value of 250MB.  There is a massive improvement in
response for the query, and a drill down from one of the returned records, from
7 minutes, and 4 minutes, to 40 seconds, and 45 seconds respectively.

So, with workarea_size_policy set to manual, and using sort_area_size, a session
can use more sort space because if pga_aggregate_target is used, then a session
uses only 5% of the pga_aggregate_target.  I had pga_aggregate_target set to
1GB, and when I set sort_area_size to 50MB, I noticed about the same
performance.  50MB is 5% or 1GB, so this makes sense, unless it was a
coincidence.

The test machine I was using had 4GB of memory.  Using the 80%-20% rule, Oracle
could use 3.2GB.  Since the SGA is set to 2.2GB, this leaves about 1GB for
sorting.  With sort_area_size and sort_area_retained_space set to 250MB, this
means only four sessions can be running this large query at the same exact time
without the system starting to page and swap, correct?

Once they're done with sorting, they release the sort space, correct?, or do
they still have a sort_area_retained_size of 250MB of memory still allocated?

I've read the following paper for more insight on sizing:

http://www.ixora.com.au/newsletter/2000_11.htm
Your example and execution plan of using an index on the created column was
really good, and shed a lot of light on what is happening, and how the query may
be further tuned.  That's the next thing I'll look into.  With Siebel, you can't
just willy, nilly put an index on the table without using the Siebel object
navigator, and I think I read, you have to disable an index and not drop it.

I want to note that that I changed the optimizer_features_enable to 9.2.0 from
8.1.7 and the RBO execution plan remained the same, as was stated in bug 3086748
in metalink.

I want to say that we have been using and upgrading our non-Siebel applications
to Oracle9i for the last year or more without any major problems, and have
really benefitted using the features of the external tables, undo tablespaces,
logical standby, and spfile to name just a few.

I also want to say thanks for your help on this problem.  This query has been a
thorn in our side so to speak as we learn how to use, and maintain Siebel with
an Oracle backend.  I have been using AskTom for just under four years, within
Oracle and on the outside, and it has been invaluable.  AskTom is my first line
of defense, and when we get stumped on an Oracle issue, my manager sometimes
says, "See what Tom says." -Thanks...

          Followup:

sort_area_size is allocated via sbreak() calls.

work area memory by memmap and memunmam() calls.

sort_area, once allocated to a process, stays there.  the OS would have to page
out the unused memory pages.

work areas are allocated, attached, used, detached and returned to the OS.

work areas follow the 90/10 rule.  They are generally better than using sort
area/hash area sizes -- but for some batch jobs, singular exceptions -- a manual
sort area size (a huge one) can be useful.

March 01, 2004 David -- Thanks for the question regarding
      "Replication", version Oracle 8/8i

      You Asked
Hi, Tom

I have a question regarding to synchronous bidirectional
replication and advanced replication in Oracle 8i, what is the
difference between them, and how do you decide which one to use?

What about snapshot, can you explain it in your own words and
distinguish the usage compare to the above.  Thank you.

David Chen.

      and we said...
Syncronous replication uses triggers to maintain two copies of
the data in two different databases at the same time.  When I
insert into my "local" copy of table T, a trigger will insert
that data into the "remote" copy of T as part of my transaction.
 When I commit -- Oracle will actually do a two phase commit
ensuring the data is commited in either both locations or
NEITHER location.

Syncronous replication demands that both databases be available,
the network is up, and so on.  I would not recommend using it.
If both sites need some piece of data -- have one of them use a
database link to query it from the other database, don't impede
all transactions (introducing another potential point of failure
for your application) by using syncronous replication.

Asyncronous advanced replication uses a store and forward
mechanism.  When I insert a row into my local copy of table T,
Oracle will store in a queue on the same machine a copy of that
transaction.  After we commit, Oracle will PUSH that copy of
that transaction to the remote site and replay it.  Hence
advanced async replication uses a store and forward mechanism.
If the network is down or the remote database is unavailable, my
transactions are just queued up locally and will get propagated
when the systems are all available again.

Snapshots are similar to the above but are not stored with
"transactional" boundaries.  We do not replay each and every
insert/update to a row -- we just push over the final result.
For this, we use a snapshot log -- this snapshot log is placed
on the "master" table.  When you insert a row, a row is inserted
into the snapshot log with the PRIMARY KEY of the new row and
the letter "I" for insert.  If you update this row, another row
is placed into the log with the primary key and the letter "U".
If you delete a row -- its primary key and the letter "D" is
stored.  When we refresh a snapshot, we use this log to
effectively:

o delete by primary key every row in your copy that was UPDATED
or DELETED

o insert all of the rows who have a row in the snapshot log with
an INSERT or UPDATED

That syncronizes the snapshot copy with the master as of that
point in time.

See

http://download-east.oracle.com/docs/cd/A81042_01/DOC/server.816/a76959/repover.htm#14080
for a further discussion of the two types of replication.
Review &
      Followup

      Rating:
      3
  March 16, 2001
Reviewer:  nandini
       from USA

      Rating:
      3
  July 15, 2001
Reviewer:
      Pichaimani  from San Jose, CA USA

      Rating: 3
Snapshot
      Review  August 22, 2001
Reviewer:  Deanna
       from South San Francisco, USA
I needed to find additional information regarding the syntax of how to create a
snapshop, and whether the snapshot log was automatically created or if the user
had to create that as well.

The other information presented in the article was definately worth reading

      Rating: 2
bidirectional
      replication using triggers  February 05, 2002

Reviewer:  raj bhalla  from india
hi tom,
       in your answer you only explan the way but i found difficulty will doing
replication bidirectional using triggers . so pl write some sample code for that
.and also explain another possiblities if one server fail then we connected with
other server & did work. on such condition how we get transcation data in
between this days to another server

      Rating: 4
Pull from
      snapshot site  October 24, 2002
Reviewer:  Abu
      Ahmadh  from SG
"Asyncronous advanced replication uses a store and forward mechanism.  After we
commit, Oracle will PUSH copy of transaction to the remote site and replay it".

Does it mean the master site requires update/insert privilege on the target
snapshot table?  Or could it be other way that target site pull from the master
site for fast refresh?  The reason for this is master site should not update any
tables in the snapshot site.  Please throw some light on this.

          Followup:

Using ADVANCED peer to peer replication -- we push.

o you do transaction on database a
o database a queues a copy of the transaction
o you commit
o database A pushes the transaction to database B
o database B applies the transaction

Using snapshots we PULL -- database B would "pull" the changes from database A.

      Rating:
      4
replication  October 17, 2003
Reviewer:
      Sanchit Jindal  from Delhi, India
Excllent Explanation.

      Rating:
      5
Replication  November 24, 2003
Reviewer:
      atul  from India
Hi,

I'm getting following o/p for following query..

select last_date,this_date,broken from dba_jobs;

LAST_DATE THIS_DATE B
--------- --------- -
23-NOV-03           N
24-NOV-03           N
24-NOV-03 24-NOV-03 N
24-NOV-03           N
24-NOV-03           N
24-NOV-03           N
04-OCT-03           Y
24-NOV-03           N

You can see broken =Y for one row,

How to resolve that,and it shows for old date,why?

Thanks.
Atul

          Followup:

use dbms_job.broken( :job_no, FALSE )

that is the date the job was last scheduled for, before it got broken.

      Rating:
      3
Good  January 08, 2004
Reviewer:
      Satyaki  from Mumbai
Tom
I have got oracle 8.1.7 loaded on two linux servers in different locations.

can u suggest me how can I go for replication between both?

what the script will be?

thanx

          Followup:

the script starts with

a) read the documentation

http://download-west.oracle.com/docs/cd/A87860_01/doc/index.htm

see the bottom of that page.

      Rating: 3
Checking
      it  January 09, 2004
Reviewer:  Satyaki
       from Mumbai
thnax
I am checking it, but little confuse. are u telling me to check parallel server
setup.

but is it wisable to setup parallel server where both the servers at physically
two differnet location connected with VSAT.

          Followup:

meant:

http://download-west.oracle.com/docs/cd/A97329_03/web.902/a90855/toc.htm
Distributed Databases and Replication
    Oracle8i Distributed Database Systems     HTML     PDF
    Oracle8i Replication *     HTML     PDF
    Oracle8i Replication Management API Reference *     HTML     PDF

the other link was the top level for all 8i docs.

      Rating:
      3
Doubt  January 10, 2004
Reviewer:
      Badri  from Bangalore, India
Hi tom,

Do we use replication for only DML statements?? If we execute a DDL statement,
will it automatically execute the same statement in the replicated database
too?? Please let me know.

regards

          Followup:

read the replication guides.  with n-way master to master replication you can
replicate a schema (ddl) as well as data.  you would not simply issue "create",
you use the replication api and it'll keep it in sync.

      Rating: 3
Missing
      link  January 10, 2004
Reviewer:  Satyaki
       from Mumbai
Tom
Sorry to say but the link given by you is of 9i app server documentation.
any way I got those documentations from search and taking notes from it.

But one thing is that they are of oracle 8.1.6 and they are not for linx. Should
I use Unix configuration mode.

thanx

          Followup:

arg, cut and paste issues to the max.  ok, here goes, third try

http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/index.htm
thats the right stuff and everything is platform independent as far as the
database goes.

      Rating: 5
Can I do this
      type of Replication  March 01, 2004
Reviewer:
      Piyush  from India
Hi Tom,

I am some what comfortable with Oracle Server Technology,
I want to learn Replication, for that I have a Windows Machine, and two instance
belonging to two databases, Piyush and Piyush1 on same machine. in order to
learn i have kept small sizes of db in both, only one tablespace user with four
tables, emp, dept, job, grade_mst.

I want to replicate from piyush to Piyush1.

What exactly i should do now..can you please explain me or can you please inform
me some link..

thanx in advance..

          Followup:

well, you'd have to pick one of

a) streams

http://otn.oracle.com/pls/db92/db92.docindex?remark=homepage#index-STR
b) basic read only replication
c) advanced update anywhere replication

http://otn.oracle.com/pls/db92/db92.docindex?remark=homepage#index-ADV
first -- see

http://otn.oracle.com/pls/db92/db92.docindex?remark=homepage
for all of the docs.

March 01, 2004 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;

      Rating: 4
More Group
      By  February 25, 2004
Reviewer:  Vinnie
       from Orlando
Tom if I have the following:

table A
name varchar2
name_data blob

How would a write a query to return
a result set like the following:

select name, sum of dbms_lob.getlength(name_data) for length>4000, sum of
dbms_lob.getlength(name_data) for length<=4000, count of the number of name with
lob size>4000, count of the number of name with lob size <=4000
group by name

Thanks

          Followup:

how does "select name, .... " fit into it?  name would seem to be "almost
unique, if not unique".

did you really want this at the NAME level or should this query return a single
row with 4 counts.

regardless -- pretend my inline view is your query against table A, either way
one of these two is what you asked for:

scott@ORA920PC> select count( case when dbms_lob_length  < 1000 then 1 end ) a,
  2         count( case when dbms_lob_length >= 1000 then 1 end ) b,
  3             count( distinct case when dbms_lob_length  < 1000 then name end
) c,
  4             count( distinct case when dbms_lob_length >= 1000 then name end
) d
  5    from (
  6  select ename name, sal dbms_lob_length
  7    from emp
  8         )
  9  /

         A          B          C          D
---------- ---------- ---------- ----------
         2         12          2         12

scott@ORA920PC>
scott@ORA920PC>
scott@ORA920PC> select name,
  2         count( case when dbms_lob_length  < 1000 then 1 end ) over() a,
  3         count( case when dbms_lob_length >= 1000 then 1 end ) over() b,
  4             count( distinct case when dbms_lob_length  < 1000 then name end
) over() c,
  5             count( distinct case when dbms_lob_length >= 1000 then name end
) over() d
  6    from (
  7  select ename name, sal dbms_lob_length
  8    from emp
  9         )
 10  /

NAME              A          B          C          D
-------- ---------- ---------- ---------- ----------
ADAMS             2         12          2         12
ALLEN             2         12          2         12
BLAKE             2         12          2         12
CLARK             2         12          2         12
FORD              2         12          2         12
JONES             2         12          2         12
KING              2         12          2         12
MARTIN            2         12          2         12
MILLER            2         12          2         12
SCOTT             2         12          2         12
TURNER            2         12          2         12
WARD              2         12          2         12
JAMES             2         12          2         12
SMITH             2         12          2         12

14 rows selected.

      Rating:
      3
?  February 25, 2004
Reviewer:
      Vinnie  from Orlando
Perhaps I need to explain more:)

I would like to know the size of the blob data for a given name where the LOB is
stored inline or out.

Ex. Dataset

name       blob size
SMITH      500
SMITH      10000
JONES      5000
JONES      6000
ANDREWS    100

The result I am looking for is:
NAME       COUNT<=4000   BYTES   COUNT>4000   BYTES
SMITH      1             500     1            10000
JONES      0             0       2            11000
ANDREWS    1             100     0            0

Hope this is a better example

          Followup:

variation on a theme here.....

scott@ORA920PC> select name,
  2         count( case when getlen <= 1000 then 1 end ) "cnt<=1000",
  3             sum( case when getlen <= 1000 then getlen end ) "bytes",
  4         count( case when getlen > 1000 then 1 end ) "cnt>1000",
  5             sum( case when getlen > 1000 then getlen end ) "bytes"
  6    from ( select job name, sal getlen from emp )
  7   group by name
  8  /

NAME                            cnt<=1000      bytes   cnt>1000      bytes
------------------------------ ---------- ---------- ---------- ----------
ANALYST                                 0                     2       6000
CLERK                                   2       1750          2       2400
MANAGER                                 0                     3       8275
PRESIDENT                               0                     1       5000
SALESMAN                                0                     4       5600

      Rating:
      3
Nice  February 26, 2004
Reviewer:
      Catherine  from OH,USA
Hi Tom,
Nice to meet you.I have the sample tables as follows and My Query's intention
was "Pick up
the enames from emplang table who have all the lang skills listed in the lang
table".

SQL>  select * from emplang;

ENM                            LANGS

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

Joe                            C++

Jack                           Pl/sql

James                          Java

Joe                            Java

Joe                            Pl/sql

Jack                           C++

Jack                           Java

Ford                           PHP

8 rows selected.

SQL> select * from lang;

LANGS

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

Java

Pl/sql

C++

1)
     SQL> select distinct enm from emplang e1
          2   where not exists(select * from lang where not exists
                               (select * from emplang e2 where e1.enm
          3                          = e2.enm and e2.langs = lang.langs))
  4  /

ENM

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

Jack

Joe

2)
SQL> select distinct enm from emplang e
  2   where e.langs = lang.langs
  3   group by langs
  4   having count(e.langs) = (select count(langs) from lang);
 where e.langs = lang.langs
                      *
ERROR at line 2:
ORA-00904: invalid column name

But the second query fails.The first one is not mine but it works well.
My questions are
1)Could you please explain the first query with the *not exists* condition?
  How it fetches the correct data?
2)Why the second query fails?
3)Any diff.ways that this query can be put?
 Could you please add any enhancements to the query that can improve
performance?
Please do reply.
Thanks in advance.

          Followup:

homework?

how did you know to write the first query if you cannot explain it?

it would be a good exercise for you to write the psuedo code of that query.

in a nutshell -- it looks at every record in the EMP table and says only KEEP
the record if there are not any LANGS in LANGS such that this record does not
have that lang.  confusing?  perhaps, write out the psuedo code for that.

  take each record in emplang
     if we find a record in LANG such that there is not a record in EMPLANG for
that enm/lang then do not keep this emplang record (the not exists on lang won't
be satisified)

the 2cnd query fails for the obvious? reason that there is no lang table in
scope on line 2.  there is a subquery against lang, but there is no lang in the
outer query.

perhaps you meant:

select enm
  from emplang, lang
 where emplang.langs = lang.langs
 group by enm
having count(*) = ( select count(distinct langs) from lang )
/

which is probably the best way to code such a query.

      Rating:
      4
Excellent  February 27, 2004
Reviewer:
      Catherine  from OH,USA
Hi Tom,
Thanks for your excellent explanation.Do you have any docs.
pertaining to *Oracle market share* in RDBMS market?Any useful link you have?Any
help would be appreciated.
Thanks for your followup.

          Followup:

goto www.oracle.com and search for

market share

      Rating: 4
Nice To
      see  February 28, 2004
Reviewer:  Donovan
       from IL,USA
Dear Tom,
Regarding Catherine's post,Both the queries work dandy but
I am also not confident in the *Not exists * condition.
 How does the query proceed?The innermost subquery selects
rows but when we use not exists while proceeding up the data,Wouldn't that
reverse the rows selected?Does it proceed
as
   not (
          not(true)) => true.
Iam confused with that.Could you please explain the each part of the query?
Thanks in advance.
Please do reply.

          Followup:

SQL> select distinct enm from emplang e1
          2   where not exists(select * from lang where not exists
                               (select * from emplang e2 where e1.enm
          3                          = e2.enm and e2.langs = lang.langs))
  4  /

for each record in e1
loop

  if we cannot find any records in LANG such that
      there isn't a matching record for that LANG.LANGS and E1.ENM in the
      original emplang table
  then
      this record in e1 must have all of the LANGS in the LANG table, output
      this record
  end if;

end loop

don't know how else to say it.  just read it like it was a subroutine, maybe
that'll help.  read it as a big loop and each "where not exists" is just a
lookup (not a negation, a selection criteria).

      Rating:
      4
Thanks  February 28, 2004
Reviewer:
      Donovan  from IL,USA
Dear Tom,
Thanks for your response.

      Rating:
      3
OK  March 01, 2004
Reviewer:  Ram
       from Bangalore,India
Dear Tom,
I need a query that calculates sum of sal deptwise and the
total of sal for the entire table.Can we use rollup and cube
for this requirement of the grouping function?I mean like
 sql>select decode(grouping(deptno),10,..,20..
      ...
      rollup ..
I need a query like this.I can use SQL*PLUS reporting tool
but is there any other way?
Could you please help?
Bye!

          Followup:

  1  select decode( grouping(deptno)+grouping(ename), 1, 'Dept total', 2,
'Total' ),
  2         deptno, ename, sum(sal)
  3  from emp
  4* group by rollup( deptno, ename )
scott@ORA920PC> /

DECODE(GRO     DEPTNO ENAME        SUM(SAL)
---------- ---------- ---------- ----------
                   10 KING             5000
                   10 CLARK            2450
                   10 MILLER           1300
Dept total         10                  8750
                   20 FORD             3000
                   20 ADAMS            1100
                   20 JONES            2975
                   20 SCOTT            3000
                   20 SMITH             800
Dept total         20                 10875
                   30 WARD             1250
                   30 ALLEN            1600
                   30 BLAKE            2850
                   30 JAMES             950
                   30 MARTIN           1250
                   30 TURNER           1500
Dept total         30                  9400
Total                                 29025

18 rows selected.

March 01, 2004 Jennifer -- Thanks for the question regarding
      "What difference between SQLJ,JSP(Java Stored Procedure) & PL/SQL
      Java", version 8.1.6

      You Asked
Hi Tom;

I really want to understand what difference between SQLJ,JSP and
PL?SQL with Java.

Thanks

      and we said...
SQLJ is a method of embedding SQL in Java, much like Pro*C is a
method of embedding SQL in C/C++.

SQLJ is a programming tool for java programers.  Instead of
putting jdbc calls into an application, they can embed SQL calls
in java and have a precompiler generate the JDBC code for them.

A little SQLJ snippet might be:

    for(int i = 0; i < list.length; i++)
    {
        element = list[i];
        #sql { INSERT INTO DIR_LIST (FILENAME)
               VALUES (:element) };
    }

the #sql is the SQLJ

A JSP is a java server page.  This is where you embed JAVA into
HTML. it allows you to have dynamic content in an other static
page.  You can actually mix SQLJ with JSP if you want -- or just
use JDBC to access the database from java.

PL/SQL with Java -- that would be either PLSQL calling a java
stored procedure or vice versa.
Review &
      Followup

      Rating: 2
PL/SQL vs
      SQLJ  October 31, 2001
Reviewer:
      A reader  from ca
perhaps not answerable, but are there tools that will convert business logic
written as pl/sql into sqlj?

      Rating: 3
Executing SQLJ
      file from SQL Plus.  March 01, 2004
Reviewer:
      Anil Pant  from India
I have a SQLJ file in which '&&' is used for logical and when i call this file
from SQL Plus it is asking for arguments for this. Even if i set escape ` it is
replacing the '&&' with null causing the java procedure to compile with errors.
My sqlj file starts like this

create or replace and compile java source named productdetail as
package mine.project.dm;
import java.sql.SQLException;

So when i use loadjava it is giving problem with the words like
create, replace etc

Pls help me if any of you know the workaround.

          Followup:

SQL> set define off

March 01, 2004 Reddy -- Thanks for the question regarding "ID's
      without Gaps", version 8.1.6

      You Asked
I have a batch process which inserts about 100 rows into a table
every 2 hours. I don't have a problem with insert.

The problem is there is a document_id field. This field gets
populated from a Oracle sequence. No gaps are allowed in this
field (business rule). Recently I found gaps. The problem is
there are some transactions rolled back due to some errors. Now
I am looking for alternative solutions to solve this problem.
Any ideas ?

      and we said...
change the rule.  No gaps = serialization = slow slow slow.

Sequences gaurantee gaps.  If you shutdown and restart, you will
probably find gaps as well.

Since you know have gaps, it might be a really good time to
convince someone that gap free numbers is a hold over from the
days when you did this processing on paper and the impact of
doing it on a computer system is really bad.

Short of that, you have to create a table like:

my_seqs ( seq_name varchar2, seq_val number );

and a function like:

create function get_next_val( p_seq_name in varchar2 )
return number
as
   l_val number;
begin
   update my_seqs set seq_val = seq_val + 1
     where seq_name = p_seq_name
    returning seq_val into l_val;

   return l_val;
end;

but remember -- you'll now have a level of concurrency that is
basically ONE user at a time.  Not a very scalable solution, but
if you have one user it would work OK.
Review &
      Followup

      Rating: 2
Sorrily it
      doesn't meet the requierement  February 13, 2004

Reviewer:  Marc Blum  from Aachen, Germany

Sure your solution provides the possibility to retrieve gapfree numbers. But if
the application throws away the retrieved number, the data in the table itself
will become gappy/gapped (?).

One solution is to add a before insert trigger, which retrieves the number.

My preferred solution is to bulletproof the datamodel itself via constraints.
That way it will become impossible for any application to corrupt my business
rule:

SQL> SET ECHO ON
SQL> alter session set nls_language=american;

Session altered.

SQL>
SQL> CREATE TABLE t
  2  (text           VARCHAR2(10),
  3   rg#            NUMBER,
  4   rg#_previous   NUMBER,
  5   CONSTRAINT uk_rg#
  6      UNIQUE(rg#),
  7   CONSTRAINT uk_rg#_previous
  8      UNIQUE(rg#_previous),
  9   CONSTRAINT rg#_previous_fk
 10      FOREIGN KEY (rg#_previous)
 11      REFERENCES t(rg#),
 12   CONSTRAINT rg#_gapfree
 13      CHECK(
 14             (
 15                  rg#                IS NOT NULL
 16              AND rg#_previous       IS NOT NULL
 17              AND rg# - rg#_previous = 1
 18             )
 19            OR
 20            (    rg#          IS NULL
 21             AND rg#_previous IS NULL)
 22            OR
 23            (    rg#          = 1
 24             AND rg#_previous IS NULL)
 25            ),
 26   CONSTRAINT rg#_positiv
 27      CHECK(sign(rg#) = 1)
 28   )
 29  /

Table created.

SQL>
SQL> INSERT INTO t VALUES ('dummy',NULL,NULL);

1 row created.

SQL> INSERT INTO t VALUES ('first',1,NULL);

1 row created.

SQL> INSERT INTO t VALUES ('second',2,1);

1 row created.

SQL> INSERT INTO t VALUES ('third',3,2);

1 row created.

SQL> COMMIT;

Commit complete.

SQL>
SQL> INSERT INTO t VALUES ('fourth',-1,NULL);
INSERT INTO t VALUES ('fourth',-1,NULL)
*
ERROR at line 1:
ORA-02290: check constraint (VSM_OWNER.RG#_POSITIV) violated

SQL> INSERT INTO t VALUES ('fourth',7,NULL);
INSERT INTO t VALUES ('fourth',7,NULL)
*
ERROR at line 1:
ORA-02290: check constraint (VSM_OWNER.RG#_GAPFREE) violated

SQL> INSERT INTO t VALUES ('fourth',7,6);
INSERT INTO t VALUES ('fourth',7,6)
*
ERROR at line 1:
ORA-02291: integrity constraint (VSM_OWNER.RG#_PREVIOUS_FK) violated - parent
key not found

SQL> INSERT INTO t VALUES ('fourth',7,3);
INSERT INTO t VALUES ('fourth',7,3)
*
ERROR at line 1:
ORA-02290: check constraint (VSM_OWNER.RG#_GAPFREE) violated

SQL> INSERT INTO t VALUES ('fourth',4,3);

1 row created.

SQL> COMMIT;

Commit complete.

SQL>
SQL>
SQL> DROP TABLE t;

Table dropped.

          Followup:

gap free numbers are just a "bad" idea.

how many ways can you spell "serialization"

the requirement is generally found "to not be really a requirement" in *all* of
my experiences.  even where people tell me "its the law" (found not really to be
true).

tell me -- what happened in the day of paper (to which people point to and say
this is why we must, because we did in the past) when you

a) lost a tablet with some invoice numbers
b) spilled coffee on some, destroying them before using them

well -- you just accounted for them.  that is all that ever needs be done.

but anyway -- if you want to build slow non-scalable systems, this certainly
works.  have fun with all that.

You need to show all of the code -- you have to

a) select max(rg#) out first
b) before you can insert rg# and rg#+1
c) only to get blocked cause someone else was doing a) at about the same time
d) to go back to a again once you get the "unique constriant violated" to try
all over again.

      Rating:
      5
  February 13, 2004
Reviewer:  Zlatko
      Sirotic  from Croatia
See:

http://www.quest-pipelines.com/pipelines/plsql/tips02.htm#NOVEMBER

Regards,
Zlatko Sirotic

          Followup:

just as yucky and un-necessary.

code could be alot more efficient as:

  update sg
     set id = id+1
   where table_name = l_table_name
  returning id into l_id;

  if (sql%rowcount = 0) then
     set it and
     insert it
  end if;

be a ton ton less code in anycase.

But -- i really dislike both approaches.

      Rating: 1
Dislike your
      attitude  March 01, 2004
Reviewer:  Marc
      Blum  from Aachen, Germany
Tom,

like many many others I really appreciate your deep knowledge und your
commitment to educate the developer-community about efficient usage of the
product "ORACLE database server".

What I dislike is your growing harshness and rude tone. And it's constantly
growing. I don't understand, why your big success and growing popularity results
in being unfriendly!? Do you think, all your effords are wasted? Have you the
feeling of: "All my teaching, all my praying doesn't have any effect."?

I'll keep on buying your books and reading your answers, but I prefer not to
communicate directly with you because your attitude towards people seems a
little bit disturbed.

bye
Marc

          Followup:

huh?

this is a pet peeve of mine, I've always blasted this

a) serial
b) slow
c) non-scalable

"implementation" -- everytime, going back to day 1 of my RDBMS career.

Every time, each time, equally as vocal.  What you did "works" but it is a, b,
and c. (and I might add d) when actually calmly looked at and explained -- not
necessary.  I have yet to find an actual case where by it was)

If you tell me which part of this was "harsh or rude", I'll gladly address.

I've always stated my opinion when I believe what is being done is wrong.  I
don't want people to use this approach -- leaving a "solution" here without a
comment if I feel the solution is a "bad idea" wouldn't be a good thing either.

March 01, 2004 OP
      -- Thanks for the question regarding "Question Regarding HASH JOIN !",
      version 8.1.6.0

      You Asked
Hi Tom,

I have read in the book that whenever we are dealing with
a small table for Eg DEPT and a large Table EMP ,then
to improve the pewrformance HASH join can be implemented,
where the small table can be kept in memory and can be
used in the Join with the large table (EMP in this case).

Q1) Can you please tell me the syntax to apply HASH JOIN.

Q2) Is there any THUMP RULE that the HASH JOIN should be
applied to tables with some specific number of Records.

Thanks and Regards
OP Lalwani

      and we said...
There is no "syntax" -- the optimizer does this for you.  You can use a HINT
(see the designing and tuning for performance guide for all of the hints) to
make the optimizer "want" to use a hash join if you like but in general, if you
use the CBO and analyze your tables -- it'll do it for you.  That is how to
apply a hash join (you don't, the optimizer does)

There are no rules of thumb - the optimizer will look at many things, table
size, estimated cardinality, existing indexes, optimizer goal, hash area size,
etc, etc, etc to determine when and if a hash join is appropriate.
Review &
      Followup

      Rating: 1
I have a
      problem with hash join  March 01, 2004
Reviewer:
      Nopparat V.  from Thailand
I found some strange thing on version 9.2.0.1 (again).

SQL> set autotrace on explain
SQL> select aa.flag, aa.re_coverage_type, aa.total_re_prm,
  2    aa.total_re_comm, bb.total_re_prm, bb.total_re_comm
  3  from aa, bb
  4  where aa.flag = bb.flag
  5    and aa.re_coverage_type = bb.re_coverage_type ;

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=10 Card=1 Bytes=68)
   1    0   HASH JOIN (Cost=10 Card=1 Bytes=68)
   2    1     VIEW OF 'AA' (Cost=5 Card=1 Bytes=34)
   3    2       SORT (GROUP BY) (Cost=5 Card=1 Bytes=42)
   4    3         FILTER
   5    4           NESTED LOOPS (Cost=3 Card=1 Bytes=42)
   6    5             TABLE ACCESS (FULL) OF 'RE_RATIO' (Cost=2 Card=1
           Bytes=21)

   7    5             TABLE ACCESS (BY INDEX ROWID) OF 'RE_POLICY' (Co
          st=1 Card=1 Bytes=21)

   8    7               INDEX (UNIQUE SCAN) OF 'RE_POLICY_PK' (UNIQUE)
   9    1     VIEW OF 'BB' (Cost=5 Card=1 Bytes=34)
  10    9       SORT (GROUP BY) (Cost=5 Card=1 Bytes=44)
  11   10         FILTER
  12   11           NESTED LOOPS (Cost=3 Card=1 Bytes=44)
  13   12             TABLE ACCESS (FULL) OF 'RE_RATIO' (Cost=2 Card=1
           Bytes=23)

  14   12             TABLE ACCESS (BY INDEX ROWID) OF 'RE_POLICY' (Co
          st=1 Card=1 Bytes=21)

  15   14               INDEX (UNIQUE SCAN) OF 'RE_POLICY_PK' (UNIQUE)

SQL> select /*+rule*/ aa.flag, aa.re_coverage_type, aa.total_re_prm,
  2    aa.total_re_comm, bb.total_re_prm, bb.total_re_comm
  3  from aa, bb
  4  where aa.flag = bb.flag
  5    and aa.re_coverage_type = bb.re_coverage_type ;

FL RE TOTAL_RE_PRM TOTAL_RE_COMM TOTAL_RE_PRM TOTAL_RE_COMM
-- -- ------------ ------------- ------------ -------------
NA 01     22267.11      8906.844    15586.977     6234.7908
NA 04         1950           780         1365           546
NA 05       1534.5             0      1074.15             0
NA 07           90             0           63             0

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: RULE
   1    0   MERGE JOIN
   2    1     SORT (JOIN)
   3    2       VIEW OF 'BB'
   4    3         SORT (GROUP BY)
   5    4           NESTED LOOPS
   6    5             TABLE ACCESS (FULL) OF 'RE_RATIO'
   7    5             TABLE ACCESS (BY INDEX ROWID) OF 'RE_POLICY'
   8    7               INDEX (UNIQUE SCAN) OF 'RE_POLICY_PK' (UNIQUE)
   9    1     SORT (JOIN)
  10    9       VIEW OF 'AA'
  11   10         SORT (GROUP BY)
  12   11           NESTED LOOPS
  13   12             TABLE ACCESS (FULL) OF 'RE_RATIO'
  14   12             TABLE ACCESS (BY INDEX ROWID) OF 'RE_POLICY'
  15   14               INDEX (UNIQUE SCAN) OF 'RE_POLICY_PK' (UNIQUE)

As you see above, only difference between the first and the second query is
/*+rule*/ hint. The first one returns no row
but the second return 4 rows (correct answers)

The following are view scripts and data :

SQL> create or replace view aa as
  2  select a.renew_flag||'A' flag, a.re_coverage_type,
  3    sum(nvl(a.quota_prm*b.quota_ratio/100,0) +
  4        nvl(a.surplus_prm*b.surplus_ratio/100,0)) total_re_prm,
  5    sum(nvl(a.quota_prm*b.quota_ratio/100
  6     * (nvl(b.comm_rate1,0)+nvl(b.comm_rate2,0))/100,0) +
  7        nvl(a.surplus_prm*b.surplus_ratio/100
  8     * (nvl(b.comm_rate1,0)+nvl(b.comm_rate2,0))/100,0)
  9     ) total_re_comm
 10  from re_policy a, re_ratio b
 11  where a.policy_id = b.policy_id and a.re_year = b.re_year
 12    and a.re_quarter = b.re_quarter and a.reinsurer_group = '2'
 13    and a.re_coverage_type = b.re_coverage_type
 14    and a.re_year = '2547' and a.re_quarter = '1'
 15  group by a.renew_flag, a.re_coverage_type ;

View created.

SQL> create or replace view bb as
  2  select a.renew_flag||'A' flag, a.re_coverage_type,
  3    sum(nvl(a.quota_prm*b.quota_ratio/100,0) +
  4        nvl(a.surplus_prm*b.surplus_ratio/100,0)) total_re_prm,
  5    sum(nvl(a.quota_prm*b.quota_ratio/100
  6     * (nvl(b.comm_rate1,0)+nvl(b.comm_rate2,0))/100,0) +
  7        nvl(a.surplus_prm*b.surplus_ratio/100
  8     * (nvl(b.comm_rate1,0)+nvl(b.comm_rate2,0))/100,0)
  9     ) total_re_comm
 10  from re_policy a, re_ratio b
 11  where a.policy_id = b.policy_id and a.re_year = b.re_year
 12    and a.re_quarter = b.re_quarter and a.reinsurer_group = '2'
 13    and a.re_coverage_type = b.re_coverage_type
 14    and a.re_year = '2547' and a.re_quarter = '1'
 15    and b.reinsurer_id = '04'
 16  group by a.renew_flag, a.re_coverage_type ;

View created.

SQL> select * from aa ;

FL RE TOTAL_RE_PRM TOTAL_RE_COMM
-- -- ------------ -------------
NA 01     22267.11      8906.844
NA 02    445358.75    111339.688
NA 04         1950           780
NA 05       1534.5             0
NA 07           90             0

SQL> select * from bb ;

FL RE TOTAL_RE_PRM TOTAL_RE_COMM
-- -- ------------ -------------
NA 01    15586.977     6234.7908
NA 04         1365           546
NA 05      1074.15             0
NA 07           63             0

If I create table as select from these views, the problem will disappear.

          Followup:

please contact support (and you'll really want to look at applying the 9204
patch, that will be one thing they will ask you to do -- 9201 is out of support
and many known issues have been fixed)

March 01, 2004 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)

      Rating:
      4
Review  March 01, 2004
Reviewer:  Aj
       from A
Hi Tom,
   I have been convincing my company guys about
oracle database as they are moving towards java based
tecnology.They have existing system which uses sybase
db.Now they seems to considering oracle database :)
but they have come back to me with various
question(comparision with sybase).Can you comment
something about this..It will be great,if i have your
expert comments...Here is the database selection
criteria....

a)Backup & recovery---hot backups,journaling,recovery
machanisms(ease of use),replication
b)Availablity(clustering)
c)Transaction support(read committed)
d)Track record/refrences
e)Tested on large database ( > 500 gb)
f)performance
g)portability
h)Jboss compatibility
i)Ddev/dba tools
j)Jdbc support
k)Odbc support
l)Longevity(roadmap for future development)
m)Commericial Support
n)Quality of Implementation
o)Ansi sql level support
p)Tunability
q)Data integrity support
r)Supports Macrons

It will be easy for me to write something about above
topic once I have your expert comments.

Thanks,

Cheers
  Aj

          Followup:

we beat them in every area.  If you have specific questions, i'd be glad to
answer but that is a day long agenda you just stuck up there.

see

http://otn.oracle.com/pls/db10g/portal.portal_demo3?selected=1
tons of information -- we have clustering, they do not.  we have a much longer
track record (see www.oracle.com for references and such).  large databases?
the worlds largest RDBMS's run on Oracle (
http://www.wintercorp.com/VLDB/2003_TopTen_Survey/TopTenwinners.asp
.  and so on

      Rating: 4
SQL Server To
      Oracle  March 01, 2004
Reviewer:  Tarun Babu
       from India
Hi Tom,
    I really appreaciate your help in this site. We have a specific requirement.
We are operating with two different databases One SQL Server and the other
Oracle8i. The application is Telecom Billling system.
Whenever we insert or update a row in a table CustomerBills in oracle we want
the same row in the table in the SQL server database too. Is it possible to
achive ? I heard triggers can help. But, how if both are two different
databases?

          Followup:

gateways can do this
http://www.oracle.com/gateways/
however i would strongly counsel AGAINST this
approach.

Using a syncronous approach like this means both systems have to be constantly
up and every transaction against this table will incurr a 2pc.

I would rather queue a message using AQ and have a process that you write (could
just be a stored procedure) dequeue the message and apply it to SQLServer over
the gateway.

In this fashion, the availability of SQLserver (the other database in general)
will not affect the availability of the main instance AND the transaction will
be "just as fast" according to your end users as it is today.

Build loosely coupled systems....

      Rating: 4
Oracle to SQL
      Server  March 02, 2004
Reviewer:  Tarun
       from India
Thanx a lot Tom for your valuable suggestions and quick reply.

March 01, 2004 -- Thanks for the question regarding "Rollback
      segment needed via database link?", version 8.1.7

      You Asked
Hi Tom,

   I want to transfer a 2G table from database1 to database2 (both are the same
version, on different server) via database link (use insert append). We want to
test the performance against the transportable tablespace. Is there any rollback
segment needed in this process to keep the read consistent image on database1
(assume there is update/insert/delete on database1 during the process)? If yes,
should I need to create a rollback segment with 2G? And how to specify the
rollback segment on the remote database?

Thanks,
David

      and we said...
you will need sufficient RBS configured in database1 in order to ensure that it
does not wrap around and get reused during the period of time it takes to do the
insert /*+ append */ into database 2.  the RBS on db1 will be used for read
consistency.

Creating a "single" 2gig rbs won't buy you much since the probability of an
ora-1555 is directly related to the size of the SMALLEST rbs you have, not the
largest.  You want to have sufficient, equi-sized RBS's on db1.  Your DBA should
know what to do here.

The RBS on db2 is hardly worth mentioning since the only RBS generated would be
for data dictionary updates, not for table updates (assuming no indexes on the
table being inserted into of course).  insert append is used to bypass UNDO
generation on the table itself.  No need to specify any RBS for db2.
Review &
      Followup

      Rating: 3
2 GB table
      copy  March 01, 2004
Reviewer:  Dilip
      Dandekar  from Pune, India
Export / Import of this table may be much simpler.

          Followup:

why?

you would have the same exact consistent read issue on db1
but a larger RBS issue on db2

      Rating: 4
How can I
      specify the rollback segment in remote database?  March 01,
      2004
Reviewer:  A reader
Hi Tom,

   Thanks for the info. But I am still don't understand your statement :
"Creating a "single" 2gig rbs won't buy you much since the probability of an
ora-1555 is directly related to the size of the SMALLEST rbs you have, not the
largest.  You want to have sufficient, equi-sized RBS's on db1". Do you mean
that creating many small rollback segment is better than creating a large
rollback segment on db1?

Thanks,
David

          Followup:

No, not at all.

I am saying "the probability of ora-1555 is related to the size of the smallest
rbs you have"

if you have 15 2gig RBS's and 1 512k rbs -- your probability of an ora-1555 is
based mostly on the 512k rbs.  It will be used as often as the 2gig rbs's but it
will "wrap around" and reuse itself much faster.  If someone needs the undo that
was in that small one -- and it no longer exists -- ora-1555 is the result.

When using manual undo:

You want to have equi-sized RBS's

Each of which is large enough to not wrap around under normal conditions -- for
the amount of time it takes to execute your long running queries.

Say you generate "about 5meg of undo per minute" during peak (5meg is just a
number, pretend it is "N")

Now, you have queries that need to run for 10minutes.

You need at least 75meg of undo (room for comformt, 10*5 = 50meg, 75 just makes
us feel better).

So, if you decide you want 15 rollback segments (you have about 10-20 concurrent
transactions for example) each RBS should be about 5meg in size nominally.

In that fashion, you should find your RBS is not wrapping around and reusing
itself inside of the 10 minute window.  You should not be recieving the ora-1555

If you are using automatic undo:

set undo retention to 900 (about 15 minutes).

make sure you have sufficient disk space allocated to the undo tablespace to
allow it to hold 15 minutes of undo.

      Rating: 5
Great
      explanation  March 02, 2004
Reviewer:
      A reader
Hi Tom,

   Thanks for your explanation.

David

March 01, 2004 sreekanth -- Thanks for the question regarding
      "Attachments Using pl/sql mail procedure", version 9.2.0.4.0

      You Asked
Tom,

We are in the middle of testing our database migration from 8.1.7.4 to
9.2.0.4.0.  Right now in our 8.1.7.4 version we use pl/sql procedure to send
emails using utl_smtp, as utl_file cannot read binary files(like word and pdf),
we only attatch text documents through this email procedure. After our migration
to 9.2.0.4.0 we will be able to read the binary files using utl_file, so can i
attach word and pdf files using pl/sql procedure using  utl_smtp.write_raw_data
and utl_file.get_raw to read the file?

thanks
sreekanth.g

      and we said...
bfiles and dbms_lob have always been able to read binary files!

I would use bfiles instead personally.  Just create a directory and use
dbms_lob.read on it.

You can do this today in 8i!

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1352202934074
for example (reads the alert log using a bfile)
Review &
      Followup

      Rating:
      5
thanks  March 01, 2004
Reviewer:
      sreekanth gadhiraju  from Tampa, Fl USA
Thanks.

      Rating:
      1
  March 02, 2004
Reviewer:
      A reader

March 01, 2004 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 )

      Rating:
      4
Index  January 30, 2004
Reviewer:
      reader
Hi Tom,

Is there any way to see reverse key index info?
Any view, function or something else?

Thanks for your help.

          Followup:

ops$tkyte@ORA9IR2> create table t ( x int );

Table created.

ops$tkyte@ORA9IR2> create index t_idx on t(x) reverse;

Index created.

ops$tkyte@ORA9IR2> select index_type from user_indexes where
  2  index_name = 'T_IDX';

INDEX_TYPE
---------------------------
NORMAL/REV

      Rating:
      3
Index  January 30, 2004
Reviewer:
      reader
Thanks for the quick response.

1.Can it be seen by using dump function (besides user_indexes)?

2.This type of indexing is only used for unique indexes when inserts to the
table are always in the ascending order of the indexed columns. Why is it so?

Thanks for your precious time and valuable solution.

          Followup:

1) no, not really -- you'd be dumping the column value after it was reversed.
you just need to query the data dictionary to see if it is "reversed"

2) that is the most likely use case for it.  it helps prevent a really "hot"
right hand side of the index by spreading the inserts all over the index
structure.

      Rating:
      4
Index  January 30, 2004
Reviewer:
      reader
Thanks for the prompt reply. U r great as usual.

U mean theres no need to colesce the leaf blocks if reverse key indexing is used
for unique index?

Thanks.

          Followup:

wow, massive keyboard failure.  it is losing vowels like crazy.  That must be
driving you crazy I bet.  I'd have to buy a new keyboard right away.

No, I was not talking about coalescing or rebuilding at all.

I was describing how the reverse key index could be used to spread the insert
active all over the index instead of just on the right hand side - decreasing
insert contention on the index (spreading the update load all over the structure
instead of just the right hand side of it)

      Rating:
      3
Nice  March 01, 2004
Reviewer:
      R.Chacravarthi  from Chennai,India
Dear Tom,
Is it possible to cache index data blocks in any part of the
Instance's SGA?
Please do reply.

          Followup:

as long as that part is called "a buffer cache", sure.  that is where we cache
data blocks, of which indexes are a class of.

they can be in the keep pool, the recycle pool, the default buffer cache or any
one of the 4 other sized caches (2k, 4k, 8k, 16k, 32k caches -- one will be
default leaving the other 4).

      Rating:
      4
OK  March 01, 2004
Reviewer:  John
       from Sydney,Australia
Dear Tom,
Is there a manual way to synching index data with table data?Does oracle does
that automagically when the base table
undergoes any modifications?
Please do reply.

          Followup:

index maintainance "just happens" (except for a TEXT index but you didn't say
anything about that)

you need do nothing to have indexes maintain themselves.

March 01, 2004 David -- Thanks for the question regarding
      "Proper use of DB Links in App Development/Data Warehousing", version
9.2.0

      You Asked
Recently, the proper usage of db links has become a hot topic of discussion.  I
have discouraged db links in OLTP and data warehousing applications based on
performance issues and creation of additional dependency betweeen applications.
I have advocated a centralized repository that uses etl to replicate commonly
used tables to databases that require specific tables from our central ODS.

I have been unable to find a definitive guide on the using db links, do you have
any best practices or thoughts on this issue.

      and we said...
I would be on your side here.

Anytime you rely on a dblink, you are increasing the probability that the system
is "down" since it no longer is just "you" that causes downtime but "them" (the
remote databases) as well.

Also, there are certain unavoidable "performance related" characteristics of
dblinks.  In OLTP, i could not imagine wanting them in the mix.

The more loosely coupled you can build your systems, the better.

Anyone else want to add their 2cents.
Review &
      Followup

      Rating: 5
RE: Anyone else
      want to add their 2cents.  March 01, 2004

Reviewer:  Mark A. Williams  from Indianapolis, IN
      USA
insert into toms_bucket values (.02);
commit;

We typically discourage the use of dblinks for various and sundry reasons as
well.

- Mark

      Rating: 4
How do we
      Integrate applications  March 01, 2004
Reviewer:
      Siva  from PA USA
I am trying to solve the problem of application integration in my organization.
I am doing from bottom up , when in fact I think the initiative needs to come
from the top.

I see the following problem as the major issue.

The data has been copied to multiple databases using snapshots and database
link. ( I think it is Oracle's fault for making Distributed computing so easy!!)
.

Each group has built their own kingdom with their own repository of data, with
their own twist to the meaning of business entities.

It is something like this. A customer record is created at one place. It is
copied to another database using DB link or snapshot . Now they add some status
fields which makes the meaning a little different. Another database copies it.
They add a few records directly into that table. Now they have a definition of
customer which is a super set of the original source.

We are in a such a mess for many other entities like this. I think it was
because everyone turned into a programmer.

For example, If a business group wanted to send out reports to some customers.
Instead of working with the Core IT group. The business group hired his own
luitinents, created a database , copied a few tables from here and there, put
together a web site and 20 people generate report from this web site. The
Information Technology group seems to operate with manthra business users are
the king. This new application which was put together without much thought has
become a business need and that needs to be supported.

How do we stop this proliferation of application and how to rectify the
proliferation.

Mani-

          Followup:

you are right that it needs to be TOP DOWN

What is easiest for "coders" (and please remember, I am first and foremost a
coder, a developer -- i'm talking about myself here)

a) total control
b) do it yourself
c) do it in isolation
d) don't do it in the "large", do it for the end user you talk to

that is, build a stove pipe.  for them, this is the path of least resistance.
they can work alone, need no dba help, need no rules, etc.  (this applies to a
small team as well)

we had this problem to (still do, are fixing it).  In our own apps, we had
"customers" all over the place.  all different.  We now have a single customer
model -- a single source of truth.  All of the apps either do or will feed from
this (they were changed).  everyones requirements are captured in the sole
place.  there is a single point of "truth".

at the end of the day, the stove pipes make the business users think they are
happy, but the business as a whole suffers.  "data is king", "applications are
icing".  This situation happens all of the time as businesses go from 'small' to
bigger than small.  As this data proliferates, it'll get worse and worse.  the
same stuff will be done in N different ways.

Here is an example I use (of dis-integration that annoys me personally).  Won't
name the company but...

a) my credit card expired
b) i goto rent a product from this company
c) my gold/green/premier/whatever reservation wasn't there (cause the card
expired)
d) the person at the counter couldn't update my profile. they could manually
process a transaction but I had to go elsewhere to update my credit card info,
they could not do it
e) so, i wanted to lodge a "complaint".  guess what, different system --
couldn't be done from their system. had to goto a 3rd place.

so, they had 3 customer databases -- none of them talking, all of them doing
different bits (you don't even want to guess what might happen with an address
change -- who owns that).

I cannot tell you how to stop this -- other than to say that you need a
corporate IT strategy, from the top, with buy in all around (consolidation).  it
took us a couple of years to do it internally for our own systems, but we did it
and it was worth it.

March 01, 2004 Rateesh -- Thanks for the question regarding
      "ORA-12015 while creating materialized view using aggregates and join on
      two tables ", version 8.1.7

      You Asked
I am trying to create a materialized view to speed up the reports. I want it to
be fast refreshed and query rewrite enabled. It includes a join on two tables .
Following is Create statement i executed

CREATE MATERIALIZED VIEW PGSALES.MV_SALES
TABLESPACE SALES
NOLOGGING
CACHE
NOPARALLEL
BUILD IMMEDIATE
REFRESH FAST
     ON DEMAND
     WITH ROWID
 USING DEFAULT LOCAL ROLLBACK SEGMENT
ENABLE QUERY REWRITE AS
SELECT  H.LOCATIONCODE,H.INVOICEDATE,D.ITEMNO,SUM(D.QUANTITY) QTY,
SUM(D.DISCOUNTAMOUNT) DISC,SUM(D.QUANTITY*D.RATE) GROSSAMT
FROM POSINVHDR H ,POSINVDTL D
WHERE H.POSINVOICENO=D.POSINVOICENO
GROUP BY H.LOCATIONCODE,INVOICEDATE,ITEMNO

I am getting the following error

ORA-12015: cannot create a fast refresh snapshot from a complex query

But i want it to be fast refresh. this happens only when i take the product of
quantity and rate ( which i require ). If i use sum(quantity) alone the snapshot
is getting created and i am able to do a fast refresh.  Why is it not possible
to use product . i tried adding a column in the detail i.e quantity*rate then
using it in the snapshot although this works i cannot modify the base tables. Is
there any other work around for this.

      and we said...
well, before you get really excited -- you did understand that the MV would only
have been "refreshed fast" if all you did was a DIRECT PATH LOAD right?  In 8i,
it was documented that MV's with joins and aggregates would be fast refreshed IF
and ONLY IF you loaded with the direct path loader (mv logs with incremental
changes could not be used):

http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76994/mv.htm#42151
so, I doubt it would have been fast refreshed in reality regardless (you
probably are not loading with the direct path loader right?)

It would have been FULLY refreshed regardless.  We could rollup the details by
themselves to the level of posinvoiceno -- meaning the report would just have to
join and aggregate a little, not alot.

Or,  you could just go with a complete refresh -- run the nasty query once every
now and again and benefit from its existence for all of the other queries...

Or, in 9i, this restriction is removed....

Review &
      Followup

      Rating:
      4
Thanks..Something that i did not know  March 01,
      2004
Reviewer:  Rateesh Mohan  from Dubai
Thanks for taking time to answer the questions

Never knew that fast refresh would not work with conventional DML's . Got to
know more about materialized view after going through the documentation

I cannot use both the suggestions since rolling upto the level of posinvoiceno
does not help 'much' in reducing the time taken for the reports because in the
reports it is further linked to more than 7 tables. Also doing a full refresh
every half an hour is also not possible as the refresh it self is taking more
than half an hour .

   Since I cannot find anyother work around to fullfill my requirements  what i
am planning to do is to create trigger on the detail table on the fields that i
require and make summarized table which contains the joined-aggregated  values.
Make the reports point to this summary table. What are you thoughts on this ?

Thanks once again

          Followup:

it'll have an impact on the running application -- both a serialization issue
(many rows in the detail point to the same aggregate row, so two users that did
not "hit eachother" in the past will start blocking and locking eachother).  the
trigger itself will impact the overall performance as well.  you'll need to
watch/measure that impact.

be careful if H.LOCATIONCODE,H.INVOICEDATE are updateable -- the trigger on the
details will have to LOCK that record before reading it if so and you'll need to
cascade those updates down from the parent to the aggregate tables.

think through all of the concurrency issues (and remember multi-versioning and
read consistency)

March 01, 2004 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

      Rating: 4
if no cookie,
      no app for you  March 01, 2004
Reviewer:
      robert  from ct
package body wict is
 -----------
BEGIN
    utl_timer.capture;
    IF g_sess_cookie_val IS NULL THEN
       g_sess_cookie_val := cookies.get_wict_session_cookie;
    END IF;
END wict;

------
Tom, I check for cookie in the body init section...is there a way to just exit
completely from within this section if no cookie set ?

thanks

          Followup:

raise_application_error() will do that.

March 01, 2004 Shruti -- Thanks for the question regarding
      "Radio botton/ check box, updating multiple rows using OWA", version
      4.0.8.1

      You Asked
Tom,

This new site is very user friendly.

I have a question regarding radio button/ checkbox.

I want to write a code in PL/SQl/owa.

I want to  select some rows based on some criteria and then I
want checkbox or radio button for each row and if I check and
submit the multiple rows I want to update those records for each
rows.

How can I do this?

Thanks,
Shruti

      and we said...
See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:384818739348
for an example that should get you going.
Review &
      Followup

      Rating: 3
radio buttons
      not addressed in answer.  March 20, 2002
Reviewer:
      Job Miller  from Dayton, OH
I thank you for this service.  It is truly invaluable to me.

Your solution addresses elements that can be named the same in a form.  sets of
radio buttons can only be named the same for that one set, unlike text
fields/checkboxes/etc..

Imagine you want a yes/maybe/no radio button set for every row returned.  How do
you name them so that your procedure can handle them without knowing ahead of
time how_many there are going to be?

Any thoughts or suggestions would be tremendously appreciated.  I am hoping that
since the original question asked specifically for you to address radio buttons,
you can re-address the original question or point to another resource that does.

The solution presented also relies on the browser to submit the values in the
form in the order in which they appear in the form.  Last I heard most browsers
handle it this way, although you cannot be guaranteed of this by the
specification.  (I have never seen the spec, just read the Perl CGI docs which
make this claim as well)

Thanks Tom!

          Followup:

You cannot use radio buttons like that for the very reasons you point out -- the
browser won't POST any data for a radio button that is not on.  The arrays get
"messed" up.

I use a drop down list with the words YES/MAYBE/NO in it.  The user toggles this
list.  The list always has value -- hence the indexes into the array correspond
to the rows themselves.

I can only address that which can be done.  Since radio buttons explicitly
CANNOT be passed when set null (see below), you cannot use them in this generic
fashion.

The browsers post the data in that order, if they did not -- virtually 100% of
web based apps would break.

See

http://www.w3.org/MarkUp/html-spec/html-spec_8.html#SEC8.2
...
The fields are listed in the order they appear in the document with the name
separated from the value by `=' and the pairs separated from each other by `&'.
Fields with null values may be omitted. In particular, unselected radio
buttons and checkboxes should not appear in the encoded data, but hidden fields
with VALUE attributes present should. (26)
...

that is from

Hypertext Markup Language - 2.0
September 22, 1995
T. Berners-Lee (1)
D. Connolly (1)
(1) MIT/W3C

      Rating: 5
Thanks
      again  March 21, 2002
Reviewer:  Job Miller
       from Dayton, OH
Initially I wasn't referring to the fact that NULLs screw up the order.  Even if
the radios are all set, you still can not use radio buttons.

Each set would have to have a unique form element name, otherwise they would
function as one set of radios.  In most other web languages(perl/php) this would
be ok because you could name each set something different dynamically, but not
in pl/sql where the parameters to the procedure have to be defined ahead of
time.  An array won't work, because the form will only give you one value for
300 radio elements all named the same thing (100 DB rows with 3 radio choices
each)

I overlooked the obvious.  Thank you for pointing it out.

Job

          Followup:

Well, you might be interested in:

http://technet.oracle.com/docs/products/ias/doc_library/1022doc_otn/apps.102/a90099/feature.htm#1006742
then.  Instead of passing formal named parameters, you can pass an array of
Names and an array of values to the procedure, sort of like getting a
QUERY_STRING but already parsed.

      Rating:
      5
Great!  March 22, 2002
Reviewer:  Job
      Miller  from Dayton, OH
Thank you for your additional response.  That was exactly what I was looking
for.

      Rating: 5
RADIO
      Buttons  March 01, 2004
Reviewer:  mo

Tom:

I have a requirement to create a web page that displays a list of books and a
radio button with 3 values for each "Found, Not Found, Other". The default value
will always be "Found".

1.  When I created the form and I named the radio button element with "p_radio",
only the last record displayed showed the value defaulted to "Found". All others
were not checked.  I think this is the nature of radio buttons in HTML.  To get
around this problem I changed the name of the radio element for each record by
using a variable.  So my elements were names like "p_radio_1", "p_radio_2" etc.

I know you can't pass radio values as array in HTML.  What I do set a hidden
text field and when the user checks the radio I set the hidden value to whatever
the button value is and then I submit it with the form.

Now my problem is that I am displaying one page for all records in the query.
There can be 100 books.  If I create the element names dynamically I have to
input these names in the "Save Procedure" I submit to.  I guess there is no way
to build those input paraemters dynamically into the save procedure. SO I have
to create manually 100 parameters and set them to null. This way I can handle
whatever gets submitted:

1a.  Is there a limit on how many input parameters you can have in a procedure?
1b.  Is there a way to build input parameters dynamically in a procedure based
on how may records are displayed in previous procedure?
1c.  Do you know of a better way to resolve the above problem other than using a
drop doen list?
1d.  Is there a way to not submit the radio button value to the next form? Just
the hidden value.
Thank you,

          Followup:

1) that is the nature of radio groups period -- everywhere.  Only one "button"
in a named group can be pushed (like old fashioned radio buttons in car radios)

this is why you don't really see very many radio buttons out there.  I use drop
down lists personally.

1a) nope, just practical ones (like how many variables do you want to code for?)
1b) don't know what you mean
1c) you could use flexible parameter passing, you'll get two arrays basically.
it is documented
1d) thats a javascript question, not sure.

March 01, 2004 Rajesh -- Thanks for the question regarding
      "Question on DECODE usage .", version 8.1.7

      You Asked
We have a requirement to check if a numeric column is within a particular range
and assign it some character value
 Eg:-
       CASE WHEN mynum BETWEEN 1 AND 2
                 'less than 2 '
            WHEN mynum BETWEEN 2 AND 3
                 'greater than 2 AND less than 3'
            WHEN mynum BETWEEN 3 AND 4
                 'greater than 3 AND less than 4'
            WHEN mynum BETWEEN 4 AND 5
                 'greater than 4 AND less than 5'
            ELSE
                 'out of range'
       END my_num_desc

The problem is that I need to do this in a Pl/Sql package function where CASE
statement is not supported.(We are working on Oracle 8i).

One option might be to use dynamic sql but would prefer not to .
What is an efficient way of doing the above using DECODE.
Will this be more efficient than using CASE and dynamic sql ?

Thanks
Rajesh

      and we said...
you can use a VIEW as well -- the view can "hide" the case.  That would be by
far the easiest approach (and performant).

Your case might not be "right", it should read "less than OR EQUAL to 2" and
likewise for the remaining values.  But, if it is OK, then:

ops$tkyte@ORA9IR2> create table t ( x number );

Table created.

ops$tkyte@ORA9IR2> insert into t select rownum from all_users where rownum <= 5;

5 rows created.

ops$tkyte@ORA9IR2> insert into t
  2  select x+0.01 from t
  3  union all
  4  select x-0.01 from t;

10 rows created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select x,
  2         decode( sign(x-1), -1, 'out of range',
  3             decode( sign(5-x), -1, 'out of range',
  4                decode( ceil(x),
  5                        1, 'less than 2',
  6                        2, 'less than 2',
  7                        3, 'greater than 2 and less than 3',
  8                        4, 'greater than 3 and less than 4',
  9                        5, 'greater than 4 and less then 5' ))),
 10         CASE WHEN x BETWEEN 1 AND 2 then
 11                   'less than 2 '
 12              WHEN x BETWEEN 2 AND 3 then
 13                   'greater than 2 AND less than 3'
 14              WHEN x BETWEEN 3 AND 4 then
 15                   'greater than 3 AND less than 4'
 16              WHEN x BETWEEN 4 AND 5 then
 17                   'greater than 4 AND less than 5'
 18              ELSE
 19                   'out of range'
 20         END my_num_desc
 21    from t
 22  /

         X DECODE(SIGN(X-1),-1,'OUTOFRANG MY_NUM_DESC
---------- ------------------------------ ------------------------------
         1 less than 2                    less than 2
         2 less than 2                    less than 2
         3 greater than 2 and less than 3 greater than 2 AND less than 3
         4 greater than 3 and less than 4 greater than 3 AND less than 4
         5 greater than 4 and less then 5 greater than 4 AND less than 5
      1.01 less than 2                    less than 2
      2.01 greater than 2 and less than 3 greater than 2 AND less than 3
      3.01 greater than 3 and less than 4 greater than 3 AND less than 4
      4.01 greater than 4 and less then 5 greater than 4 AND less than 5
      5.01 out of range                   out of range
       .99 out of range                   out of range
      1.99 less than 2                    less than 2
      2.99 greater than 2 and less than 3 greater than 2 AND less than 3
      3.99 greater than 3 and less than 4 greater than 3 AND less than 4
      4.99 greater than 4 and less then 5 greater than 4 AND less than 5

15 rows selected.

March 01, 2004 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.

      Rating: 4
always always
      escape_sc ?  March 01, 2004
Reviewer:
      robert  from ct
Tom, would you (always) be willing to take the extra processing and escape_sc
the curl parameter like below ? thanks

htp.p(webutil.format_cell(l_activedue,'999,999,999,999',
curl => htf.escape_sc('wict.curstat?pType=2&pTypesum=' || l_activeduesum ||
'&pTypemin=' || l_activeduemin)));

          Followup:

that would be wrong, it would be:

'wict.curstat?pType=2&pTypesum=' || htf.escape_sc( l_activeduesum ) ||
'&pTypemin=' || htf.escape_sc( l_activeduemin ) ))

and yes, I would if activedumsum/min were STRINGS (seems that they might be
numbers, numbers are generally safe -- it is things with embedded spaces, &, <,
> and so on that'll kill you every time)

      Rating: 5
Just what I was
      looking for  March 03, 2004
Reviewer:  Anu
      Sinha  from Dallas,TX
Thank you very much.

March 01, 2004 suresh -- Thanks for the question regarding "Sql
      tuning", version 8.1.7

      You Asked
For the below mentioned query,why its going for table access,
eventhough concatendated index is available in team_id and availability_date
with other columns.

SELECT
MTA.AVAILABLE_HOURS AS AVAILABLE_HOURS,
MTA.TEAM_ID AS TEAM_ID,
MTA.AVAILABILITY_DATE AS AVAILABILITY_DATE
FROM M_TEAM_AVAILABILITY MTA
WHERE
 EXISTS(SELECT  'X'
        FROM M_ASSIGNMENT_DAY DAY,
            V_OMD_CURRENT_PROCESS_INST OMD
        WHERE MTA.TEAM_ID = DAY.TEAM_ID
        AND MTA.AVAILABILITY_DATE =
            DAY.ASSIGNMENT_WORK_DATE
        AND DAY.LOAD_ID = OMD.LOAD_ID
        AND OMD.PROCESS_ID = 'MT039')
 ORDER BY TEAM_ID,AVAILABILITY_DATE,AVAILABLE_HOURS

explain plan:
SELECT STATEMENT Optimizer=CHOOSE (Cost=1714 Card=57352 Bytes=974984)
  SORT (ORDER BY) (Cost=1714 Card=57352 Bytes=974984)
    FILTER
      TABLE ACCESS (FULL) OF M_TEAM_AVAILABILITY (Cost=1598 Card=57352
Bytes=974984)
      NESTED LOOPS (Cost=6 Card=1 Bytes=36)
        TABLE ACCESS (BY INDEX ROWID) OF OMD_PROCESS_INST (Cost=4 Card=1
Bytes=16)
          INDEX (RANGE SCAN) OF OPI_PK (UNIQUE) (Cost=3 Card=1)
            SORT (AGGREGATE)
              INDEX (RANGE SCAN) OF OPI_PK (UNIQUE) (Cost=3 Card=763 Bytes=9919)
        INDEX (RANGE SCAN) OF MAD_IDX1 (NON-UNIQUE) (Cost=2 Card=2 Bytes=40)

Here for the table m_team_availability its having around 1million records and as
per the explain plan it process 57k rows so in this
case i am assuming index access path should be the efficient way.

      and we said...
that is a large chunk of the table -- 57k rows of only 1,000,000 (about 5.7%)...

consider, big table has 1,000,000 rows.  we'll get 55,555 of them:

ops$tkyte@ORA9IR2> column cpu new_val C
ops$tkyte@ORA9IR2> column hsecs new_val H

ops$tkyte@ORA9IR2> select value cpu, hsecs
  2    from v$mystat, v$statname, v$timer
  3   where v$mystat.statistic#=v$statname.statistic#
  4     and v$statname.name = 'CPU used by this session'
  5  /

       CPU      HSECS
---------- ----------
         1  443563966

ops$tkyte@ORA9IR2> set autotrace traceonly
ops$tkyte@ORA9IR2> select /*+ FIRST_ROWS */ * from big_table where exists (
select null from t where t.id = big_table.id);

55555 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=102675 Card=51246
Bytes=5790798)
   1    0   NESTED LOOPS (Cost=102675 Card=51246 Bytes=5790798)
   2    1     SORT (UNIQUE)
   3    2       TABLE ACCESS (FULL) OF 'T' (Cost=11 Card=51246 Bytes=666198)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=2 Card=1
Bytes=100)
   5    4       INDEX (UNIQUE SCAN) OF 'BIG_TABLE_PK' (UNIQUE) (Cost=1 Card=1)

forced the index and here is the results

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     170459  consistent gets
      16494  physical reads
          0  redo size
    4608627  bytes sent via SQL*Net to client
      41232  bytes received via SQL*Net from client
       3705  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      55555  rows processed

ops$tkyte@ORA9IR2> set autotrace off
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select cpu, hsecs, cpu-&C, hsecs-&H
  2    from (
  3  select value cpu, hsecs
  4    from v$mystat, v$statname, v$timer
  5   where v$mystat.statistic#=v$statname.statistic#
  6     and v$statname.name = 'CPU used by this session'
  7         )
  8  /
old   1: select cpu, hsecs, cpu-&C, hsecs-&H
new   1: select cpu, hsecs, cpu-         1, hsecs- 443563966

       CPU      HSECS      CPU-1 HSECS-443563966
---------- ---------- ---------- ---------------
       275  443564574        274             608

2.74 cpu seconds, 6.08 seconds by the wall clock... Now, let it do what it
wants

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> set autotrace traceonly
ops$tkyte@ORA9IR2> select * from big_table where exists ( select null from t
where t.id = big_table.id);

55555 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2634 Card=51246 Bytes=5790798)
   1    0   HASH JOIN (Cost=2634 Card=51246 Bytes=5790798)
   2    1     SORT (UNIQUE)
   3    2       TABLE ACCESS (FULL) OF 'T' (Cost=11 Card=51246 Bytes=666198)
   4    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1397 Card=1000000
Bytes=100000000)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      18100  consistent gets
      13603  physical reads
        420  redo size
    4608627  bytes sent via SQL*Net to client
      41232  bytes received via SQL*Net from client
       3705  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      55555  rows processed

ops$tkyte@ORA9IR2> set autotrace off
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select cpu, hsecs, cpu-&C, hsecs-&H
  2    from (
  3  select value cpu, hsecs
  4    from v$mystat, v$statname, v$timer
  5   where v$mystat.statistic#=v$statname.statistic#
  6     and v$statname.name = 'CPU used by this session'
  7         )
  8  /
old   1: select cpu, hsecs, cpu-&C, hsecs-&H
new   1: select cpu, hsecs, cpu-       275, hsecs- 443564574

       CPU      HSECS    CPU-275 HSECS-443564574
---------- ---------- ---------- ---------------
       502  443565140        227             566

less cpu, less time by the wall clock, less work over all

Now, is there an index with MTA.AVAILABILITY_DATE on the *leading edge* of the
index? (eg: the first column).  If not, there is almost no chance of using it in
this case -- but even if there is, indexes do not mean "fast=true".

March 01, 2004 Wachaspati -- Thanks for the question regarding
      "Bind Variable Questions", version 9.2.0.4

      You Asked
I have the following Pl/SQL Packaged Procedure - getNotificationId - which
OUTPUTS a REF CURSOR (a list of NotificationIDs)

CREATE OR REPLACE package ssi_notification_pkg as
  TYPE notify_cursor_type IS REF CURSOR ;
  Procedure getNotificationId(P_1      in  varchar2,
                              P_2      in  varcahr2,
                              attrs    out notify_cursor_type) ;
end ssi_notification_pkg;
/

CREATE OR REPLACE PACKAGE BODY ssi_notification_pkg AS

procedure  getNotificationId (P_1      in  varchar2,
                              P_2      in  varcahr2,
                              attrs    out notify_cursor_type)
as
begin
  OPEN attrs for
  'Select .... ' ||
  'from .... ' ||
  'where .... AND' ||
  'col1 = :1, col2 = :2'
  USING P_1, P_2 ;
end;

END ssi_notification_pkg;
/

Here are some facts about the system in which it is used :
(1) The Packaged procedure - getNotificationId - is called from a JSP page.
(2) The JSP page calling it, is the home page of the application (called by
nearly 3500 distinct Users everyday)
(3) P_1 parameter is essentially one of these UserNames.
(4) The package is therefore called/used 3500 times every day
(5) The idea is to make it fast by tuning the cursor query (part already done)
and by using Bind Variables

Questions :
(1)
Is it essential to use the construct as shown above with :1, :2 and the USING
clause Or was it "just-the-same" to use :
  OPEN attrs for Select .... from .... where .... AND col1 = P_1, col2 =
P_2;

(2)
If I use the "dynamic sql" approach with "Bind variables" (the way in
procedure) am I ignoring the fact that all Pl/SQL variables are bind variables
(as you say in many of your remarks) and am just doing more work unnecessarily.

(3)
Is there any gain in doing the way the Procedue does as opposed to simply saying
:

 OPEN attrs for Select .... from .... where .... AND col1 = P_1, col2 = P_2;

(4)
Is it right to conclude that by doing the way it is done in Procedure, the
Parse-Tree will be re-utilized thereby  avoiding Hard-Parse and by doing as
above, the cursor's query will need parsing everytime

      and we said...
1) no, you only want to use dynamic sql when the text of the query MUST change.
You just want to:

   open attrs for
    select ....
      from ...
     where .... and col1 = p_1 and col2 = p_2;

2) just doing a tad more work than necessary

3) nope....

the differences runtime/performance wise are minimal:

DOC>create or replace procedure p1( x in out sys_refcursor, p_1 in varchar2, p_2
in varchar2 )
DOC>as
DOC>begin
DOC>        open x for select * from dual where dummy = p_1 or dummy = p_1;
DOC>end;
DOC>/
DOC>create or replace procedure p2( x in out sys_refcursor, p_1 in varchar2, p_2
in varchar2 )
DOC>as
DOC>begin
DOC>        open x for 'select * from dual where dummy = :p_1 or dummy = :p_1'
using p_1, p_2;
DOC>end;
DOC>/
DOC>*/
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec runstats_pkg.rs_start

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> declare
  2          l_c sys_refcursor;
  3  begin
  4      for i in 1 .. 3500
  5          loop
  6                  p1( l_c, 'x', 'y' );
  7                  close l_c;
  8          end loop;
  9  end;
 10  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec runstats_pkg.rs_middle

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> declare
  2          l_c sys_refcursor;
  3  begin
  4      for i in 1 .. 3500
  5          loop
  6                  p2( l_c, 'x', 'y' );
  7                  close l_c;
  8          end loop;
  9  end;
 10  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec runstats_pkg.rs_stop(1000)
Run1 ran in 91 hsecs
Run2 ran in 99 hsecs
run 1 ran in 91.92% of the time

Name                                  Run1        Run2        Diff
LATCH.shared pool                   10,569      14,078       3,509
LATCH.library cache pin             14,054      21,058       7,004
LATCH.library cache                 24,583      31,594       7,011

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
66,356      84,054      17,698     78.94%

PL/SQL procedure successfully completed.

But the code is a little harder with dynamic sql AND you lose the dependency
tracking (which to me is the biggest loss!).  The latching is a tad bit better
too (which is always a "good thing(TM)")

You are benefiting from soft parsing in both cases fully and in exactly the same
manner.

Review &
      Followup

      Rating: 4
Some more
      clarifications requested  March 01, 2004
Reviewer:
      Wachaspati Pandey  from Tampa, Florida USA
(1)
<QUOTE> no, you only want to use dynamic sql when the text of the query MUST
change </QUOTE>

I think the text of my query is changing as for every call to the procedure P_1
and P_2 are brand NEW values - the resulting SQL will be brand new. Please help
me understand this better.

(2)
Also, please explain what you meant by saying :
<QUOTE> AND you lose the dependency
tracking (which to me is the biggest loss!).  </QUOTE>

(3)
Going by your response and your arguments earlier - "Pl/SQL - the beauty is that
you use Pl/SQL variables and you are automatically using BIND VARIABLES", I have
 questions :

(a) Does it mean I would never ever need to use Bind variables, EXECUTE
IMMEDIATE in Pl/SQL based program units -except for cases that do DDL, dynamic
column selection or dynamic where conditions..

(b) Does it mean that :
    SAYING
      select col1 into v_col from .. where col2 = P_2
    IS SAME AS
      EXECUTE IMMDIATE 'Select col1 from ..where col2 = :1'
      INTO v_col
      Using P_1
    If yes, WHEN WILL I EVER NEED TO USE EXECUTE IMMEDIATE
    Please give examples (other than DDLs)

(c) If Pl/sql variables are bind variables, why do you say
    this in one of your postings :
    Insert into ... values (:1, :2, :3)
    USING P_1, P_2, P_3 is better as it reuses parse tree.

(4)
Give me some examples - may be a link - which illustrates
situations where you do NEED to use "Bind variables" and
"Execute Immediate" in Procedures/Packages/Functions. I remember places where
you chant the same thing again and again - "Use Bind variables" - and today, I
am lost - where do I use them in pl/sql !!! Please help.

(5) Lastly, if somebody has taken the NDS and Bind variable approach when it is
really not required (as in the example I gave in my original question) - IS IT
GOING TO BE A DRASTIC NEGATIVE IMPACT ?

I really appreciate your promptness and prowess - I wish I had 1% of yours

          Followup:

1) the sql isn't changing - only the INPUTS (binds) are.

the where clause is constant, where col1 = :x and col2 = :y, the values for x
and y (like parameters to a subroutine) are variant.  the TEXT of the query is
the same.

Unless your example was too simplified....

2) if you have access to my book "Effective Oracle by Design", i go deeper on
this but...

 1  select name, referenced_owner, referenced_name
  2  from user_dependencies
  3* where name in ('P1', 'P2' )
ops$tkyte@ORA9IR2> /

NAME  REFERENCED_OWNER               REFERENCED_NAME
----- ------------------------------ -----------------------------------------
P1    SYS                            DUAL
P1    PUBLIC                         DUAL
P1    SYS                            STANDARD
P2    SYS                            STANDARD
P1    SYS                            SYS_STUB_FOR_PURITY_ANALYSIS
P2    SYS                            SYS_STUB_FOR_PURITY_ANALYSIS
P1    OPS$TKYTE                      DUAL

7 rows selected.

We can see that P1 is dependent on DUAL.  If dual changes, P1 will take care of
itself.  If someone says "hey, who is using dual", we can find out.

P2 hides this since we don't know what p2 uses.....

3a) everytime you reference a plsql variable in static sql it IS a bind
variable.  The decision to use dynamic sql should be made only when you CANNOT
do it statically.  It is impossible to NOT BIND correctly with static SQL in
plsql.  It is EASY to NOT BIND correctly with dynamic sql.  You have to think
more with dynamic sql, static sql is the way to go.

3b) yes, that is correct -- logically they are the same.  the static sql is
infinitely prefered however.

You'll know when you need execute immediate -- when you say "hey, I don't know
at compile time what table I'll be using, I don't know at compile time what
columns I'll be accessing".  When you don't know at compile time, you might be
using dynamic sql.

3c) give me the context.  that is better than 'insert into .... values ( ' || x
|| ',' || y || ',' || z || ')' but not as good as static sql.  Perhaps they did
not know what TABLE they needed to insert into.....

4) don't feel lost.  Just realize "plsql variables ARE BIND VARIABLES in sql".
Then you'll realize you've been binding all along and just never realized it!

bind variables and execute immediate are sort of orthogonal concepts -- you do
not use execute immediate because you want binds.  You use execute immediate
because you did not know at compile time what the SQL would be.

5) no, I tried to show that above.  It will not be drastic.  It does tend to
latch a bit more with dynamic sql, but not 100's of thousands, just a couple.

March 01, 2004 moorthy -- Thanks for the question regarding
      "global temp table", version 8.1.7

      You Asked
Can you give me a situation where you need to use a global temp table??what are
the pros and cons??

      and we said...
do you have my book "Expert one on one Oracle"? (i cover this)

I'm of the mindset that they are almost never needed, they make sql server
programmers feel 'more at home' since they are used so frequently there -- but
in Oracle -- you don't:

   put data into temp table 1;
   put data into temp table 2;
   put data into temp table 3;
   return join of t1, t2, t3;

you just return
   select *
     from ( query1 ) t1, (query2) t2, (query3) t3
    where ....;

The optimizer deals with optimizing -- not you.

I've used them (temp tables) in real applications. One application was a palm
sync conduit -- the conduit running on the pc would dump the contents of the
palm database into Oracle, run a stored procedure to reconcile the changes and
return the updates.  The temp table was used as an interface table.

So, it is rare to need them and you'll "sort of know you do" when you do as
there won't be any other effective way to perform what you need.

Review &
      Followup

      Rating: 3
difference
      between "temp table" and "global temp table"  February 21,
      2004
Reviewer:  Dev  from India
Hi Tom,
Have to disagree with you on that one. Some reporting procedures are so complex
that they simply cannot be expressed in plain SQL ( or even analytical functions
for that matter ).

I have a question though what is the difference between "temp table"  and
"global temp table".

Keep up the good work

          Followup:

show me.  give me an example.

if you can:

  a) populate table1
  b) populate table2
  c) populate table3
   and so on

and return some convulated join thereof, I can do that in a single query.

so.... show me.

temp table and global temp table are synonymous in Oracle.

      Rating: 3
No
      difference  February 22, 2004
Reviewer:
      Venkatesh  from Chennai, India
Dev,

To my knowledge temp table = global temp table.

You just cannot create a temp table without the keyword "global".

Tom, your comment would be solicited.

          Followup:

in Oracle that is correct.

      Rating:
      5
  February 22, 2004
Reviewer:  Riaz
      Shahid  from PRAL, Lahore, Pakistan
to my Knowledge:
global temp table is accesible to everyone while temp is not

          Followup:

In ANSI there are two types of temp tables

a) global, you create this and it is registered in the data dictionary, it lives
"forever".  the global pertains to the schema definition

b) local, it is created "on the fly" and disappears after it's use.  you never
see it in the data dictionary.

Oracle does global
SQLserver does local

for example.

      Rating: 3
Global Temp
      Table  February 23, 2004
Reviewer:  Dhimant
      Antani  from Gujarat - India
First of all let me say that this is the best site when it comes to Oracle
Database. I did like to have more info on Global Temp Tables. Please provide
some examples if possible

Thanks and Regards,

Dhimant

          Followup:

simply search this site for

global temporary table

you'll see plenty

      Rating:
      5
  February 23, 2004
Reviewer:  Riaz
      Shahhid  from PRAL, Lahore, Pakistan
Dhimant !

Just earch this sitr for "Temporary tables" or "Global Temprary Tables" and you
will find lots about that.

      Rating: 4
Forced to use
      global temporary table, how to avoid it ?  February 23, 2004

Reviewer:  Ashiq Shamsudeen A  from Trichy, Tamil
      Nadu, India
Hi tom,

   I've forced to use a global temporary table to hold data. Is any way out in
avoiding it. Look the below Package

package body test_pkg as

 procedure proc_b(
    i_tracking_no  in  varchar2,
    my_ref_cur     out test_pkg.my_cv
 ) as

 begin

    for i in    (select    cl_number,hts_no,part_id,product_desc,source_country
        from    classification_product_master
        where    cl_number = i_tracking_no)
    loop

    insert into class_test (
                htsno,
                us_hsno,
                cl_number,
                status,
                rec_status,
                hts_desc,
                part_id,
                product_desc,
                source_country
                   )
            select
                i.hts_no,
                hts_chapter_no,
                i.cl_number,
                'Y',
                rec_status,
                hts_desc,
                i.part_id,
                i.product_desc,
                i.source_country
            from    lcs_hts_mult_desc
            where   (hts_chapter_no,effective_date) = (select hts_chapter_no,
max(effective_date)
                                    from lcs_hts_mult_desc
                                    where country_id   = '02'
                                    and hts_chapter_no = i.hts_no
                                    and language_cd='EN'
                                    group by hts_chapter_no);
    end loop;

    open my_ref_cur for select * from class_test;

 end proc_b;

end test_pkg;

  In the classification_product_master table i've duplicates values for hts_no
,say

select hts_no from classification_product_master
where ....

hts_no
3924104000
3924104000
3924104000
3924104000
7323940026
7323940026
7323940026
7323940026
7323940026
7323940026
7323940026
7323940026
8214909000
8214909000
8214909000
8214909000
8214909000
8309900000
8309900000

   Using this values I'll be passing to the query

select hts_chapter_no, max(effective_date)
from lcs_hts_mult_desc
where country_id   = '02'
and hts_chapter_no = i.hts_no <===
and language_cd='EN'
group by hts_chapter_no

   Is any another way around to avoid using this temp table ?

          Followup:

that logic is not any different than this query:

select i.hts_no, j.hts_chapter_no, i.cl_number, 'Y',
       j.rec_status, j.hts_desc, i.part_id,
       i.product_desc, i.source_country
  from classification_product_master i,
       lcs_hts_mult_desc j
 where i.cl_number = I_TRACKING_NO
   and (j.hts_chapter_no,j.effective_date) =
       (select hts_chapter_no, max(effective_date)
          from lcs_hts_mult_desc
         where country_id = '02'
           and hts_chapter_no = i.hts_no
           and language_cd = 'EN'
         group by hts_chapter_no );

Your existing logic does not "de-dup" any rows from
classification_product_master, it takes every row in that table and joins it to
the "most recent" row in lcs_hts_mult_desc table (so if there were 15 rows in
classification_product_master with hts_no "12345", you would get 15 (or more)
rows back.

It might be more efficient to simply query:

select *
  from (
select i.hts_no, j.hts_chapter_no, i.cl_number, 'Y',
       j.rec_status, j.hts_desc, i.part_id,
       i.product_desc, i.source_country,
       j.effective_date,
       max( case
            when country_id = '02'
                 and
                 language_cd = 'EN'
            then j.effective_date
             end )
          over ( partition by j.hts_chapter_no ) max_effective_date
  from classification_product_master i,
       lcs_hts_mult_desc j
 where i.cl_number = I_TRACKING_NO
   and i.hts_no = j.hts_chapter_no
       )
 where effective_date = max_effective_date
/

that joins I to J by hts_no (which is what you do) and keeps only the record
with the effective_date = max( effective_date ) for rows with that hts_no and
country_cd = '02' and language_cd = 'EN'.

You were never forced to use a gtt, never.

      Rating: 4
ON COMMIT
      DELETE ROWS  February 23, 2004
Reviewer:
      Dušan  from London, England
I sometimes use them as a "multi-record" application interface to the underlying
database.
In such a scenario, an application inserts a number of records in a "global
temporary on commit delete rows" table and calls a stored proc to do something
with a whole passed record set, usually some inserts, updates and-or merge
statements, on a whole bulk of records, never on one by one in a loop. Upon
commit or rollback, contents of temp table just disappear.
From the perspective of the application, the interface could not be simpler -
start transaction, do a number of inserts and call a stored proc when the whole
set is ready to be processed.
How useful do you find this way of using global temporary tables in your
practice, Tom?

          Followup:

You just described the same thing I did in my answer :)

as an interface table they are excellent.

      Rating:
      5
Also...  February 23, 2004
Reviewer:
      Kashif  from Reston, VA
Hi Tom,

I've also used GTTs when I've run into a query that takes a while to execute,
and I need to execute that query over and over in my procedure. I simply dump
the contents of the query once into the GTT at the beginning, and then hit the
GTT as many times as I need throughout the procedure for the data. Some might
argue that well, you might not be pulling in the latest set of the data, e.g. if
some user added data to the underlying tables of the query after I store the
resultset in the GTT, to which I have to say that this is happening in the
middle of the night, no users on the database, so no chance of that happening.
Your thoughts?

Kashif

          Followup:

I'd have to understand why I needed the result over and over and over again.  If
so, so be it -- but many times, I find I can take the procedural logic that
needed that set time and again and reduce it to a much smaller set of
non-procedural code.

It would be a valid use case, yes.

      Rating: 3
temp table
      useful - did I say that?  February 27, 2004

Reviewer:  dev  from fla
I'm probably asking for trouble, since there are so many ways to approach this
problem,  but here is a case where a two step approach involving an intermediate
table works more quickly than a single query.

Start with a query that performs well, add a predicate ( the one  below that
includes smk_u ), watch the perfectly good execution plan change slightly,
performance go out the window. The original plan used hash joins, new plan uses
hash joins then nested loops, performance is unacceptable.   I know, work on
tuning the query.  To me it is quicker to divide and conquer -  I am considering
using a temp table as a workaround as demonstrated below.

Original query - if you remove the DECODE(NVL predicate it runs quickly, with
that predicate it is just too slow.

SELECT
    tran_pd, cal_date, sku0, store, SUM(sal_u) sal_u, SUM(smk_u) smk_u,
SUM(sal_r) sal_r, temp_price, perm_price
 FROM
(SELECT
         tran_pd, cal_date,sku0, store,  sal_u,smk_u , sal_r,
           pricing_bp.get_temp_price(store, cal_date,sku0) temp_price,
           pricing_bp.get_perm_price(store,cal_date,sku0) perm_price
               FROM
               skudaily skud, calendar c
              WHERE tran_pd BETWEEN :p_start_pd and  :p_end_pd AND store =
:store
               AND NVL(sal_u,0) - DECODE(:p_sales, 1,0,NVL(smk_u,0)) <> 0
               AND c.cal_pd = tran_pd
                AND sku0 in
                     (SELECT tmsk_sku0 FROM rpt_temp_sku0s
                       WHERE tmsk_request_id = :p_req_id )
)
GROUP BY tran_pd, sku0, store , cal_date
/

where skudaily is a well-indexed, mature union view with millions of rows.  I
know, use partitioned tables.

The fairly easy, fairly fast workaround..

variable p_req_id number;
variable p_start_pd varchar2(8);
variable p_end_pd varchar2(8);
variable p_sales number;
variable store number;

begin
 :p_req_id := &&1;
 :p_start_pd := '20030131';
 :p_end_pd := '20040131';
 :p_sales := 1;
 :store := &&2;
end;
/
alter session set optimizer_goal = CHOOSE;

insert into junk_pjk_gtemp
SELECT
  tran_pd, cal_date,sku0, store,  sal_u,smk_u , sal_r
FROM
  skudaily skud, calendar c
WHERE tran_pd BETWEEN :p_start_pd and :p_end_pd
  AND c.cal_pd = tran_pd
  AND sku0 in
  (SELECT tmsk_sku0 FROM rpt_temp_sku0s
   WHERE tmsk_request_id = :p_req_id )
AND store = :store
/
commit;

SELECT
    tran_pd, cal_date, sku0, store, count(*) num_rows, SUM(sal_u) sal_u,
SUM(smk_u) smk_u, SUM(sal_r) sal_r,
    pricing_bp.get_temp_price(store, cal_date,sku0) temp_price,
    pricing_bp.get_perm_price(store,cal_date,sku0) perm_price
 FROM junk_pjk_gtemp
WHERE NVL(sal_u,0) - DECODE(:p_sales, 1,0,NVL(smk_u,0)) <> 0
group by tran_pd, cal_date, sku0 , store
/

By moving the problem predicate to the second query, I can execute the first
query using the efficient execution plan.
It works, it's faster, the annoying predicate is used on the small data set in
the temp table.   I didn't have to try to out think the optimizer, I kept the
nice plan it gave me and instead of struggling with tuning the query I used a
temp table.   Fire away...

          Followup:

replace junk_pjk_gtemp with:

SELECT
  tran_pd, cal_date,sku0, store,  sal_u,smk_u , sal_r
FROM
  skudaily skud, calendar c
WHERE tran_pd BETWEEN :p_start_pd and :p_end_pd
  AND c.cal_pd = tran_pd
  AND sku0 in
  (SELECT tmsk_sku0 FROM rpt_temp_sku0s
   WHERE tmsk_request_id = :p_req_id )
AND store = :store
and rownum >= 0

and leave as one query -- but I'd be really surprised that a predicate on
nvl(column) would do that personally.  it is not like that predicate would help
us towards an index.

      Rating: 3
Global
      temporary tables as a big 'in list'  February 28, 2004

Reviewer:  Dale Ogilvie  from New Zealand
I think the discussion here about using GTT's as an 'interface' table  matches
our usage. We have a master table containing a few thousand rows, and a detail
table containing tens of millions of rows with a foreign key to the master
table, and a GTT with only one column designed to receive inserts of master
table primary key values. In addition to this we have a stored procedure that
performs a   query (for reporting purposes) on the detail table using a query
like (but much more complex than) this:

SELECT
   detail.*
FROM
   detail,
   gtt
WHERE detail.master_id = gtt.master_id;

At the moment I'm not sure that our schema and the query above is as good as it
can be. Specifically:

1. gtt.master_id is not declared unique, whereas master.master_id is unique,
should it be?
2. gtt.master_id does not have a foreign key relationship defined to master. Can
(should) a fk to master be defined for the column on the gtt?
3. My third concern is to do with the variability in the number of rows that can
be inserted into gtt, anything from 1 to 5000. We currently have inserted stats
on the gtt to tell the CBO that it contains 0 rows, so the CBO will choose a
plan based on this information, and thereafter all queries will use this plan.
My concern is that a plan that is optimal for 1 row, turns out to be not so
flash for 1000. In practice we can expect either 10 or 1000 rows in gtt. What is
the best approach to handle this variablity? I have looked at the
DYNAMIC_SAMPLING and DYNAMIC_SAMPLING_EST_CDN hints which look promising.

SELECT /*+ dynamic_sampling(gtt 10) dynamic_sampling_est_cdn(gtt) */
   detail.*
FROM
   detail,
   gtt
WHERE detail.master_id = gtt.master_id;

Does using these hints mean that we are effectively doing a hard parse each time
the stored proc is called?

          Followup:

Ok, here big_table is 5,000,000 row table.

GTT is like yours is I assume....

@connect big_table/big_table
drop table gtt;

create global temporary table gtt ( id int ) on commit delete rows;

create or replace procedure foo( p_rand in number default dbms_random.value( 0,
5000 ))
as
    l_cnt  number := 0;
begin

    insert into gtt
    select rownum
      from big_table
     where rownum <= p_rand;

    dbms_output.put_line( sql%rowcount || ' rows in gtt' );

    for x in ( select /*+ dynamic_sampling(gtt 10) dynamic_sampling_est_cdn(gtt)
*/ big_table.*
                 from big_table, gtt
                where big_table.id = gtt.id )
    loop
        l_cnt := l_cnt+1;
    end loop;
    dbms_output.put_line( l_cnt || ' rows fetched' );
    commit;
end;
/

@trace
exec foo(0)
exec foo
exec foo
exec foo(5000)

the result of that TKPROF was:

select /*+ dynamic_sampling(gtt 10) dynamic_sampling_est_cdn(gtt) */ big_table.*
                 from big_table, gtt
                where big_table.id = gtt.id

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          2          0           0
Execute      4      0.00       0.00          0          0          0           0
Fetch    11716      1.26       1.12          0      58579          0       11712
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    11721      1.26       1.12          0      58581          0       11712

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 152  (BIG_TABLE)   (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
  11712  NESTED LOOPS  (cr=58579 r=0 w=0 time=901210 us)
  11712   TABLE ACCESS FULL GTT (cr=11731 r=0 w=0 time=228424 us)
  11712   TABLE ACCESS BY INDEX ROWID BIG_TABLE (cr=46848 r=0 w=0 time=483374
us)
  11712    INDEX UNIQUE SCAN BIG_TABLE_PK (cr=35136 r=0 w=0 time=260253
us)(object id 35091)

Now, that is showing us 1 hard parse (misses in library) and no subsequent
reparses.  This is what we expect -- plsql parses once per session for us and
the first one was hard since we just created GTT for the very first time....Note
the query plan -- nested loops.

Now, we want to see what happens in other sessions:

disconnect
!tk
@connect big_table/big_table
@trace
exec foo(50000)
disconnect
!tk

That tkprof shows:

select /*+ dynamic_sampling(gtt 10) dynamic_sampling_est_cdn(gtt) */ big_table.*
                 from big_table, gtt
                where big_table.id = gtt.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    50001      5.41       5.15        639     250006          0       50000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    50003      5.41       5.15        639     250006          0       50000

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 152  (BIG_TABLE)   (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
  50000  NESTED LOOPS  (cr=250006 r=639 w=0 time=4143709 us)
  50000   TABLE ACCESS FULL GTT (cr=50006 r=0 w=0 time=1020992 us)
  50000   TABLE ACCESS BY INDEX ROWID BIG_TABLE (cr=200000 r=639 w=0
time=2339062 us)
  50000    INDEX UNIQUE SCAN BIG_TABLE_PK (cr=150000 r=0 w=0 time=1195898
us)(object id 35091)

Missing in library = 0, so it too was soft parsed -- if there were hard parsed
(with 50,000 rows) what would have happened?

@connect big_table/big_table
alter system flush shared_pool;
@trace
exec foo(50000)

select /*+ dynamic_sampling(gtt 10) dynamic_sampling_est_cdn(gtt) */ big_table.*
                 from big_table, gtt
                where big_table.id = gtt.id

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.02          0          2          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch    50001     35.13      74.99      78818      96819          0       50000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    50003     35.15      75.01      78818      96821          0       50000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 152  (BIG_TABLE)   (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
  50000  HASH JOIN  (cr=96819 r=78818 w=6720 time=74166346 us)
  50000   TABLE ACCESS FULL GTT (cr=82 r=0 w=0 time=68518 us)
5000000   TABLE ACCESS FULL BIG_TABLE (cr=96737 r=72098 w=0 time=37932714 us)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
  50000   NESTED LOOPS
  50000    TABLE ACCESS (FULL) OF 'GTT'
5000000    TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF 'BIG_TABLE'
      0     INDEX (UNIQUE SCAN) OF 'BIG_TABLE_PK' (UNIQUE)

A very different plan with a hard parse (note how explain plan lies -- the first
plan there is from the trc file's STAT records -- the other was "explain=u/p".
EP cannot see certain things like bind variable types, bind variable values,
results of dynamic sampling and so on)

So, after all of that.  here are my responses

1) probably not, it would just tend to slow down the insertions and dynamic
sampling would figure it out

2) nah, that would be useful with a MV and query rewrite and such, but not so
here.

3) here we have the crux of the problem.  Once parsed -- it be parsed.  One
approach would/could be for you to use a REF CURSOR and open a query based on
thresholds (ref cursors are soft parsed or hard parsed upon each open, unlike
static SQL but this is probably OK for this query, you are not going to run it a
thousand times a second).

That said, maybe you decide "you know, every 1000 rows we would like the
opportunity to revisit this particular plan".  You could use an approach like
this:

create or replace procedure foo( p_rand in number default dbms_random.value( 0,
5000 ))
as
    l_cnt  number := 0;

    cursor just_a_template is select big_table.* from big_table, gtt;

    type array is table of just_a_template%rowtype;
    l_data array;
/* that just a template really is just a template so we can easily
   define a record to fetch into -- an array of records really (i know
   you must be on 9ir2 that can do this since you are using dynamic sampling
*/

    l_cursor sys_refcursor;
begin

    insert into gtt
    select rownum
      from big_table
     where rownum <= p_rand;

    dbms_output.put_line( sql%rowcount || ' rows in gtt' );

/* here is the trick -- spoon feed the size into the query, this will
   not only make the sql text different every 1000 rows -- but it'll
   give the optimizer "about how many rows".  why did I pick 1000?  no
   reason, it was an easy number to work with */
    open l_cursor
     for 'select /*+ cardinality( gtt ' || trunc(sql%rowcount/1000)*1000 || ')
*/
                 big_table.*
            from big_table, gtt
           where big_table.id = gtt.id';

    loop
        fetch l_cursor bulk collect into l_data limit 100;
        l_cnt := l_cnt+l_data.count;
        exit when l_cursor%notfound;
    end loop;
    close l_cursor;
    dbms_output.put_line( l_cnt || ' rows fetched' );
    commit;
end;
/

If you tkprof that, you'll see it does nested loops for small numbers, hash
joins for larger numbers.

      Rating: 5
Handling
      variability in GTT contents  February 28, 2004

Reviewer:  Dale Ogilvie  from New Zealand
Thanks. I expect your cardinality hint approach is the way to go. One question
though, given these two tkprof results from your reply:

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    50001      5.41       5.15        639     250006          0       50000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    50003      5.41       5.15        639     250006          0       50000

and

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.02          0          2          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch    50001     35.13      74.99      78818      96819          0       50000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    50003     35.15      75.01      78818      96821          0       50000

Why is it that the second one that did the hard parse is so much more expensive
than the first which only did a soft parse? Where is all the extra CPU and
elapsed time use coming from? The dynamic_sampling on a fatter GTT? The hash
join?

          Followup:

it was the effect of having the default optimizer_index_* settings, causing the
index range scans to be aborted "too soon" in the mix.  for that simple of a
query, to get 50,000 well clustered rows from 5,000,000 -- probably should be an
index range scan.

big_table@ORA9IR2> alter session set optimizer_index_caching    =0;
Session altered.

big_table@ORA9IR2> alter session set optimizer_index_cost_adj   =100;
Session altered.

big_table@ORA9IR2> select /*+ cardinality(gtt 50000) */ big_table.*
  2    from big_table, gtt
  3   where big_table.id = gtt.id
  4  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=20705 Card=50000
Bytes=5650000)
   1    0   HASH JOIN (Cost=20705 Card=50000 Bytes=5650000)
   2    1     TABLE ACCESS (FULL) OF 'GTT' (Cost=11 Card=50000 Bytes=650000)
   3    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=6943 Card=5000000
Bytes=500000000)

big_table@ORA9IR2> alter session set optimizer_index_caching    =90;
Session altered.

big_table@ORA9IR2> alter session set optimizer_index_cost_adj   =10;
Session altered.

big_table@ORA9IR2> select /*+ cardinality(gtt 50000) */ big_table.*
  2    from big_table, gtt
  3   where big_table.id = gtt.id
  4  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5012 Card=50000 Bytes=5650000)
   1    0   NESTED LOOPS (Cost=5012 Card=50000 Bytes=5650000)
   2    1     TABLE ACCESS (FULL) OF 'GTT' (Cost=11 Card=50000 Bytes=650000)
   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=2 Card=1
Bytes=100)
   4    3       INDEX (UNIQUE SCAN) OF 'BIG_TABLE_PK' (UNIQUE)

big_table@ORA9IR2>
big_table@ORA9IR2> set autotrace off

      Rating: 4
dynamic
      sampling  March 01, 2004
Reviewer:  Dave
       from Downers Grove, IL USA
Tom, you've listed examples using sql_trace/tkprof to grab the dynamically
altered plan when dynamic_sampling was involved.  Is there any way through V$
views (or other) to see this change?

          Followup:

v$sql_plan

March 02, 2004 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

      Rating: 5
why
      delete?  March 01, 2004
Reviewer:
      A reader
Cant we simply do this?

insert into emp select * from dup b
     where rowid in (select rowid
                      from dup c
                      where  b.empno = c.empno
                        and rownum < 2)

          Followup:

not sure where it is fitting in -- but you need to delete from EMP to get rid of
the dups first no?  show the entire process, start with a table with dups and
work through it -- then we can say

      Rating: 5
oops
      sorry  March 01, 2004
Reviewer:
      A reader
sorry I was actualy trying to answer the first follow up, anyway I think it fit
in your initial example as well

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.

The INSERT part can be something like

insert into t select * from dup b
     where rowid in (select rowid
                      from dup c
                      where  b.a = c.a
                        and b.b = c.b
                        and b.c = c.c
                        and rownum < 2)

instead of select distinct

          Followup:

I'd rather "bulk" perform than use the correlated update which would be run like
a nested loop

   for x in ( select * from dup )
   loop
          select rowid from dup where ....
          if x.rowid = that rowid then
             insert
          end if
   end loop

so yes, you could, but I would not want to (you'd need an index on the key
columns)

perhaps:

insert into t
select ....
  from ( select t.*,
           row_number() over (partition by KEY_COLS order by something) rn
          from t )
where rn = 1;

that would be nice if the entire row as not "duped" -- just some defining key
and you wanted to keep one row for each key.

March 02, 2004 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.

      Rating: 5
Renaming alert
      log  March 01, 2004
Reviewer:  Martin
      Guillen  from Buenos Aires, Argentina
Hi Tom: Is it bad to rename the alert log (e.g. mv alert_sid.log alert_sid.old)
in a regular basis?
I want to do it for my check alert log script.
Oracle creates a new one if the file doesn't exists but I want  to be safe.
Thank You,
Martin.

          Followup:

you can do that, yes, most people do over time to roll the log.

it is "safe"

      Rating:
      4
clarification  March 01, 2004
Reviewer:
      amit poddar  from New Haven, CT USA
Hi,
You mentioned that we can rename alert log without a problem.
I thought that oracle keeps the file open throught out its life (until it is
shut down). Hence that would mean even if we rename the file it would keep
writing to the renamed file since the renamed file would just be a hard link to
the orignal inode, since the running oracle process woule never know that the
file was renamed.

Could you please tell me where am I going wrong , since I know I am wrong since
I have tested it. It works as you mentioned.

Following is the output from my test:

HOP7) fuser alert_HOP7.log1
alert_HOP7.log1:    44884   47740   48510   51344   51602   51860   52118
52376   52634   52892   53150
HOP7) fuser alert_HOP7.log
alert_HOP7.log:
HOP7)

You see the old file still has process attached to it but the new file does not
have any process attached to it.

          Followup:

no, it opens for append, writes, and closes that file each time.

background trace files (like an smon, pmon, snp, etc) behave as you describe,
but not the alert.

      Rating: 4
more
      clarification  March 01, 2004
Reviewer:  amit
      poddar  from New Haven, CT USA
Hi,

Then
why does fuser show the old alert log file still used by the processes

HOP7) fuser alert_HOP7.log1
alert_HOP7.log1:    44884   47740   48510   51344   51602   51860   52118
52376   52634   52892   53150
HOP7)

HOP7) ps -ef|grep 44884
  oracle  44884      1   0   Feb 29      -  0:21 ora_pmon_HOP7
  oracle 194860 181500   2 16:00:07     p6  0:00 grep 44884
HOP7) ps -ef|grep 47740
  oracle  47740      1   0   Feb 29      -  0:15 ora_dbw0_HOP7
  oracle  75384 181500   3 16:00:12     p6  0:00 grep 47740
HOP7) ps -ef|grep 48510
  oracle  48510      1   1   Feb 29      -  3:29 ora_lgwr_HOP7
  oracle 106416 181500   1 16:00:17     p6  0:00 grep 48510
HOP7) ps -ef|grep 51344
ps   oracle  51344      1   0   Feb 29      -  0:51 ora_ckpt_HOP7
  oracle 208240 181500   2 16:00:22     p6  0:00 grep 51344
-eHOP7) ps -ef|grep 51602
  oracle  51602      1   0   Feb 29      -  0:51 ora_smon_HOP7
  oracle 208242 181500   1 16:00:27     p6  0:00 grep 51602
HOP7) ps -ef|grep 51860
  oracle  51860      1   0   Feb 29      -  0:00 ora_reco_HOP7
  oracle 296136 181500   2 16:00:32     p6  0:00 grep 51860
HOP7) ps -ef|grep 52118
  oracle  52118      1   0   Feb 29      -  0:00 ora_snp0_HOP7
  oracle 310896 181500   2 16:00:37     p6  0:00 grep 52118
HOP7)

and so on

          Followup:

i'm not familar with fuser

      Rating: 4
Alert log file
      size  March 01, 2004
Reviewer:
      A reader
Does alert log file size have any impact on performance. For example, if alert
log file size is so huge oracle process has to open a BIG file and write the
content, then close it. Right?

          Followup:

I've never measured it, it would have to be really huge -- a seek to the end
isn't that expensive (it does not have to read the entire file to find the end
or anything)

      Rating: 4
OEM,
      maybe?  March 02, 2004
Reviewer:  Dan
       from Yukon Territory, Canada
Getting back to the original question... OEM is kind of made for this kind of
thing.  We have a standard "Production DB" event we register against our
production databases, one of the tests is for Alert log activity.  I can't
recall what the criteria are, but basically if something boring gets cut to the
alert log, you get no notification, an "interesting" event makes a nice little
yellow flag pop up and a "very interesting" (like an ORA-600) makes a red flag
pop up (and email/page you if you want it to).

I used to be kind of down on OEM ("GUI - phooey!") but hey, it works, you're
already paying for it, and most of us probably have better things to do with our
life (...or at least the part of our life our employeers are paying us for...)
than reinvent this wheel.

P.S. lots of other cool event test in there as well - broken jobs, full disks
etc. you can even get it to watch for corrupt blocks.

March 02, 2004 moorthy -- Thanks for the question regarding
      "Varrays", version 8.1.7

      You Asked
Tom
I have a question regarding Varrays. Why exactly we need them and can you give
me an example of its usage.Its good learning from you
Thanks

      and we said...
I've never used a varray.  I used nested table collections in my plsql code.

I never use varray's or nested tables as a persistent storage mechanism (on
disk).  Only nested tables in my code (as an array type).  Varrays are limiting
in that you have to define the max upper bound -- nested tables you do not, so I
do not use varrays in my code.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1116249548781

Review &
      Followup

      Rating:
      3
But..  March 01, 2004
Reviewer:  dwl
       from uk
am i correct in saying though, that if you are passing data from jdbc to oracle
and you need to pass arrays or elements or records then a varray of objects is
the only way to do it?
This would be a good use of varrays over other types.
plsql tables and nested tables do not work in this case.

Please correct me if this is not true though!

          Followup:

nested tables certainly work -- just as varrays do (in fact the code is the
same).

I actually prefer to use global temporary tables when passing "arrays of
records" from 3gl to PLSQL.  Infinitely easier (to me)

      Rating:
      3
How?  March 01, 2004
Reviewer:
      Esther  from VA,USA
Dear Tom,
How would you do that?I mean passing rows of global temp table to procedure or
function anything like that.Would you
use REF CURSOR for that?Please show the p-code.
Bye!

          Followup:

I would have java array insert scalars into a gtt and then call a procedure, the
procedure would read the gtt with bulk collect if it wanted to process as an
array or just read the gtt row by row to process.

      Rating: 3
Why not VARRAYS
      for persistent storage?  March 01, 2004
Reviewer:
      Matt  from Australia
There is a paper going around our office which suggests that using varrays to
store arrays of integer values in a DB (think along the lines of lots of days of
meter readings - a reading every 5 minutes) would be more efficient than using
NUMBERs.

The argument centralises around the disk space saving of using these types.

My opinion is that we would lose soo much more than we gain (developer time,
concurrency, speed). Plus we could gain the space benefits by defining
appropriate tables (with NUMBERS) and compressing these (gaining the compression
on the primary key).

What are your thoughts?

Why do you not use VARRAYS for persistent storage?

          Followup:

tell me how you would be USING those numbers.  You'll not be using them very
nicely in SQL anymore in a varray.

and don't forget, a varray will quickly become a blob stored out of line (over
4,000 bytes -> out of line) meaning it is stored in chunks (4001 bytes will take
AT LEAST a chunk of space which is AT LEAST a block of space) and will need to
be accessed via a LOB INDEX.

hows about an IOT with primary key = parent_id+timestamp -> data.  using index
key compression (parent_id stored once)

data is stored ordered.

data is fully "sql friendly"

no extra index structures lying about...

(if you want to know why i do not use them for persistent storage, i wrote about
that in expert one on one Oracle -- in the chapter on object types.).

      Rating:
      3
Thanks!  March 01, 2004
Reviewer:
      Matt  from Australia
I agree with you wholeheartedly, especially about the SQL friendly comment

An IOT (your suggestion) can even be nicely partitioned!

I have the book, but I still haven't read it cover to cover ...it gets used as
more of a reference (by both me and the projects I work on).

      Rating: 4
Are sql inserts
      (not direct path) compressible in IOT  March 01, 2004

Reviewer:  Rob  from Pittsburgh, PA
Tom:

Does key compression in an IOT work the same as compression in an index i.e. all
entries remain compressed even if not inserted via direct path. In a heap table
rows are only compressed if inserted in direct path mode or CTAS.

          Followup:

correct, index key compression works all of the time.  segment (block level)
compression only works on direct path operations (load, appends, alter moves)

March 02, 2004 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 )
       )

      Rating: 3
Performance
      issue  February 09, 2004
Reviewer:  Justin
       from PA
I'n having a peformance problem with this query:

SELECT DISTINCT t1.fi_nr,
                t1.redemption_dt,
                t1.redemption_am,
                t1.void_reason_cd,
                t1.void_dt,
                t1.reissued_fi_cd,
                t1.reissued_to_vendor_cd,
                t9.vendor_id,
             t6.family_id,
                t7.Person_id,
                t1.lost_stolen_cd,
                t1.lost_stolen_dt,
                t1.bank_extract_in,
                t1.fi_proc_status_cd,
             decode(rtrim(t7.last_name),NULL,'') + ', ' +
decode(rtrim(t7.first_name),NULL,'') as PartName,
                t1.fi_picked_up_in,
             t1.not_to_exceed_am,
                t1.t022_id,
                t1.first_use_dt,
                t1.last_use_dt,
                t1.last_updt_user_id,
                t1.last_updt_ts,
                t1.last_updt_prg_id
FROM t038_fi t1
LEFT JOIN t040_FI_Spec t2 on t1.t040_id = t2.t040_id
LEFT JOIN t071_Prescription t3 on t2.T071_id = t3.T071_id
LEFT JOIN t067_Participant t4 on t3.t067_id = t4.t067_id
LEFT JOIN t069_Person t7 on t4.t069_id = t7.t069_id
LEFT JOIN t033_Fam_members  t5 on  t4.t069_id = t5.t069_id
LEFT JOIN t014_Benefit_Fam t6 on  t5.t014_id = t6.t014_id
LEFT JOIN t093_vendor t9 on t1.t093_id = t9.t093_id
WHERE t5.effective_dt = (select max(effective_dt) from t033_fam_members t10
where t7.t069_id = t10.t069_id)
AND t7.person_id in (376566, 120750, 120751, 495142)
ORDER BY t6.family_id asc, t7.Person_id asc, t1.first_use_dt desc;

This query is executed with execute immediate and is built dynamically by
passing the person ids in as a varchar2.

version 9.2.0.4

Here are table counts:

wic_owner8@DEV> select count(*) from t040_FI_Spec;

         COUNT(*)
=================
          2788236

wic_owner8@DEV> select count(*) from t071_Prescription;

         COUNT(*)
=================
          1061599

wic_owner8@DEV> select count(*) from t067_Participant;

         COUNT(*)
=================
           473394

wic_owner8@DEV> select count(*) from t069_Person;

         COUNT(*)
=================
           711300

wic_owner8@DEV> select count(*) from t033_Fam_members;

         COUNT(*)
=================
           798470

wic_owner8@DEV> select count(*) from t014_Benefit_Fam;

         COUNT(*)
=================
           272898

wic_owner8@DEV> select count(*) from t093_vendor;

         COUNT(*)
=================
             2118

If I change the outer joins to inner joins, the query executes in less than 1
second.
In SQL Server (much to my chagrin) the outer join version executes in less than
1 second.

Here is the execution plan for the outer join version... eeEWW

Execution Plan
==========================================================
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1866655 Card=3584
Bytes=1150464)
   1    0   SORT (UNIQUE) (Cost=1039445 Card=3584 Bytes=1150464)
   2    1     FILTER
   3    2       SORT (GROUP BY) (Cost=1039445 Card=3584 Bytes=1150464)
   4    3         HASH JOIN (Cost=212235 Card=7954446 Bytes=2553377166)
   5    4           INDEX (FAST FULL SCAN) OF 'T033_COLSET1_INDEX' (NON-UNIQUE)
(Cost=4 Card=798605 Bytes=1118
          0470)

   6    4           FILTER
   7    6             HASH JOIN (OUTER)
   8    7               HASH JOIN (OUTER) (Cost=129856 Card=7085309
Bytes=1601279834)
   9    8                 HASH JOIN (OUTER) (Cost=103509 Card=7085309
Bytes=1466658963)
  10    9                   HASH JOIN (OUTER) (Cost=80934 Card=5583430
Bytes=1005017400)
  11   10                     HASH JOIN (OUTER) (Cost=66068 Card=5583430
Bytes=910099090)
  12   11                       HASH JOIN (OUTER) (Cost=49697 Card=5583430
Bytes=804013920)
  13   12                         HASH JOIN (OUTER) (Cost=32461 Card=5583430
Bytes=703512180)
  14   13                           TABLE ACCESS (FULL) OF 'T038_FI' (Cost=9403
Card=5583430 Bytes=597427010)
  15   13                           TABLE ACCESS (FULL) OF 'T040_FI_SPEC'
(Cost=1899 Card=2789739 Bytes=530050
          41)

  16   12                         TABLE ACCESS (FULL) OF 'T071_PRESCRIPTION'
(Cost=841 Card=1061688 Bytes=1911
          0384)

  17   11                       TABLE ACCESS (FULL) OF 'T067_PARTICIPANT'
(Cost=710 Card=473389 Bytes=8994391)
  18   10                     TABLE ACCESS (FULL) OF 'T093_VENDOR' (Cost=8
Card=2118 Bytes=36006)
  19    9                   INDEX (FAST FULL SCAN) OF 'T033_COLSET1_INDEX'
(NON-UNIQUE) (Cost=4 Card=798605 By
          tes=21562335)

  20    8                 TABLE ACCESS (FULL) OF 'T014_BENEFIT_FAM' (Cost=327
Card=273034 Bytes=5187646)
  21    7               TABLE ACCESS (FULL) OF 'T069_PERSON' (Cost=1392
Card=711346 Bytes=57619026)

Here is the execution plan for the inner join version...

Execution Plan
==========================================================
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=42 Card=1 Bytes=321)
   1    0   SORT (UNIQUE) (Cost=39 Card=1 Bytes=321)
   2    1     FILTER
   3    2       SORT (GROUP BY) (Cost=39 Card=1 Bytes=321)
   4    3         HASH JOIN (Cost=37 Card=62 Bytes=19902)
   5    4           TABLE ACCESS (BY INDEX ROWID) OF 'T038_FI' (Cost=2 Card=2
Bytes=214)
   6    5             NESTED LOOPS (Cost=28 Card=62 Bytes=18848)
   7    6               NESTED LOOPS (Cost=15 Card=36 Bytes=7092)
   8    7                 NESTED LOOPS (Cost=10 Card=14 Bytes=2492)
   9    8                   NESTED LOOPS (Cost=8 Card=6 Bytes=960)
  10    9                     NESTED LOOPS (Cost=6 Card=6 Bytes=846)
  11   10                       NESTED LOOPS (Cost=4 Card=5 Bytes=570)
  12   11                         NESTED LOOPS (Cost=3 Card=4 Bytes=400)
  13   12                           INLIST ITERATOR
  14   13                             TABLE ACCESS (BY INDEX ROWID) OF
'T069_PERSON' (Cost=2 Card=4 Bytes=324)
  15   14                               INDEX (RANGE SCAN) OF 'X069IE01'
(UNIQUE) (Cost=1 Card=4)
  16   12                           TABLE ACCESS (BY INDEX ROWID) OF
'T067_PARTICIPANT' (Cost=2 Card=1 Bytes=1
          9)

  17   16                             INDEX (UNIQUE SCAN) OF 'X067IE01' (UNIQUE)
  18   11                         TABLE ACCESS (BY INDEX ROWID) OF
'T033_FAM_MEMBERS' (Cost=2 Card=1 Bytes=14)
  19   18                           INDEX (RANGE SCAN) OF 'X033FK02'
(NON-UNIQUE)
  20   10                       TABLE ACCESS (BY INDEX ROWID) OF
'T033_FAM_MEMBERS' (Cost=2 Card=1 Bytes=27)
  21   20                         INDEX (RANGE SCAN) OF 'X033FK02' (NON-UNIQUE)
  22    9                     TABLE ACCESS (BY INDEX ROWID) OF
'T014_BENEFIT_FAM' (Cost=2 Card=1 Bytes=19)
  23   22                       INDEX (UNIQUE SCAN) OF 'X014PK' (UNIQUE)
  24    8                   TABLE ACCESS (BY INDEX ROWID) OF 'T071_PRESCRIPTION'
(Cost=2 Card=2 Bytes=36)
  25   24                     INDEX (RANGE SCAN) OF 'X071FK01' (NON-UNIQUE)
  26    7                 TABLE ACCESS (BY INDEX ROWID) OF 'T040_FI_SPEC'
(Cost=2 Card=3 Bytes=57)
  27   26                   INDEX (RANGE SCAN) OF 'X040FK01' (NON-UNIQUE)
  28    6               INDEX (RANGE SCAN) OF 'X038FK03' (NON-UNIQUE)
  29    4           TABLE ACCESS (FULL) OF 'T093_VENDOR' (Cost=8 Card=2118
Bytes=36006)

I completely agree with you about understanding the question before you tune a
query. This query answers the question, so I am in tuning mode.

Does this query fall under the category of one of your previous answer that the
outer joins are useless because of the "t7.personid IN (..." in the predicate?

I have been looking in EOBD (your book) on page 505 to see if that seems
prudent, but I feel I should be able to get this query working without jumping
through hoops.

Thanks much,

          Followup:

FROM t038_fi t1
LEFT JOIN t040_FI_Spec t2 on t1.t040_id = t2.t040_id
LEFT JOIN t071_Prescription t3 on t2.T071_id = t3.T071_id
LEFT JOIN t067_Participant t4 on t3.t067_id = t4.t067_id
LEFT JOIN t069_Person t7 on t4.t069_id = t7.t069_id
LEFT JOIN t033_Fam_members  t5 on  t4.t069_id = t5.t069_id
LEFT JOIN t014_Benefit_Fam t6 on  t5.t014_id = t6.t014_id

LEFT JOIN t093_vendor t9 on t1.t093_id = t9.t093_id
WHERE t5.effective_dt = (select max(effective_dt) from t033_fam_members t10
where t7.t069_id = t10.t069_id)
AND t7.person_id in (376566, 120750, 120751, 495142)
ORDER BY t6.family_id asc, t7.Person_id asc, t1.first_use_dt desc;

do you understand what left join does?

t5 should not be left joined to.  You have t5.column = <some value>.  Well, if
t5.column must be equal to some value -- it cannot be NULL hence, outer joining
to it doesn't make sense.

t7.column = t10.column -- again, t7 as an outer join target = bad id.

lose the outerjoins, use them EXACTLY if and ONLY IF You know for a fact you
need them.

      Rating:
      4
  February 09, 2004
Reviewer:
      reader
Hi, Tom

I have the following two questions about the outer join
1.
    SELECT ...
    FROM
    .
    .
    .
     AND T.A   =    T1.A (+)
     AND T.B   =    T1.B(+)
     AND T.C   =    T1.C  (+)
     AND T1.D (+)  = 'Y'
     AND T1.E (+)  = 'Y'
     .
     .
     .

  What is means
    "AND T1.D (+)  = 'Y'
     AND T1.E (+)  = 'Y'"

Are they same as
"
AND T1.D   = 'Y'
AND T1.E   = 'Y'"

question2

What is the follow outer join for ?

SELECT ...
FROM T,T1
WHERE
.
.
.
AND
SYSDATE
BETWEEN
T1.DATA1(+) AND T2.DATA2(+);
.
.
.

Thank you!

          Followup:

1) think of 'Y' and 'Y' as columns "D" and "E" from T, like this:

ops$tkyte@ORA9IR2> create table t ( a int, b int, c int );

Table created.

ops$tkyte@ORA9IR2> insert into t values ( 1,1,1);

1 row created.

ops$tkyte@ORA9IR2> create table t1 ( a int, b int, c int, d char(1), e char(1)
);

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select *
  2    from t, t1
  3   where t.a = t1.a(+)
  4     and t.b = t1.b(+)
  5     and t.c = t1.c(+)
  6     and 'Y' = t1.d(+)
  7     and 'Y' = t1.e(+)
  8  /

         A          B          C          A          B          C D E
---------- ---------- ---------- ---------- ---------- ---------- - -
         1          1          1

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select *
  2    from t, t1
  3   where t.a = t1.a(+)
  4     and t.b = t1.b(+)
  5     and t.c = t1.c(+)
  6     and 'Y' = t1.d
  7     and 'Y' = t1.e
  8  /

no rows selected

Now you can see what happens.  If you outer join to T1 and in the predicate you
have

t1.any_column = <some value>

you KNOW for a fact that either

a) you meant to outer join to some value
b) you didn't need to outer join in the first place...

2) does not compute, where is t2?

      Rating: 3
Partially it
      seems...  February 10, 2004
Reviewer:
      Justin  from PA
An FI is considered a "bank check". We need to have check data returned to the
screen that may not have bank redemption information yet. In other words, we may
want to see check data for a check that has not been redeemed yet.

Here is what I understand about an outer join. If I need to see data for the
driving table whether the other tables in the query return data or not. Is that
correct? And T038_FI is my driving table, correct?

So is this what you are advocating?

FROM t038_fi t1
LEFT JOIN t040_FI_Spec t2 on t1.t040_id = t2.t040_id
LEFT JOIN t071_Prescription t3 on t2.T071_id = t3.T071_id
LEFT JOIN t067_Participant t4 on t3.t067_id = t4.t067_id
INNER JOIN t069_Person t7 on t4.t069_id = t7.t069_id
INNER JOIN t033_Fam_members  t5 on  t4.t069_id = t5.t069_id
LEFT JOIN t014_Benefit_Fam t6 on  t5.t014_id = t6.t014_id
LEFT JOIN t093_vendor t9 on t1.t093_id = t9.t093_id
WHERE t5.effective_dt = (select max(effective_dt) from t033_fam_members t10
where t7.t069_id = t10.t069_id)
AND t7.person_id in (376566, 120750, 120751, 495142)
ORDER BY t6.family_id asc, t7.Person_id asc, t1.first_use_dt desc;

Thanks very much for your help.

          Followup:

well, actually -- it goes further then that.

Lets just look at T5 for a moment.

t5.effective_dt = "some value" means -- no need to outer join to t5 since if we
did -- effective_dt would be NULL and NULL is never equal to some value.  Soooo

t4 must not need to be outer joined to EITHER.  why?  Cause we look up the
record in T5 based on T4 and if t4.t069_id is NULL, well, there won't be a mate
in T5 and since we are not outer joining to T5 well -- t4.t069_id being null
doesn't work for us.  So, t4 doesn't need it either.

Suggestion:  sit down and rip this puppy apart.  Understand what you are going
for and why.  By having the predicates on t5 and t7 -- you've probably blasted
the outer joins out of "meaningful existence" here.

      Rating: 5
Very helpful
      response  February 10, 2004
Reviewer:
      Justin  from PA
Thank you very much Tom.

You're up early!, and I appreciate that :)

      Rating: 5
Outer
      join  February 10, 2004
Reviewer:  Krishna
       from India

      Rating: 5
outer
      join  February 10, 2004
Reviewer:  reader

Hi, Tom

 Thank you for the previous answers.

I have a furter question:

If

select count(*)
 from t, t1
 where t.a = t1.a(+)
  and t.b = t1.b(+)
  and t.c = t1.c(+)
  and 'Y' = t1.d(+)
  and 'Y' = t1.e(+)

will be the same with

select count(*)
 from t, t1
 where t.a = t1.a(+)
  and t.b = t1.b(+)
  and t.c = t1.c(+);

  ?

  OR for some reason I only want to count some fields of table t from this
query,

  are the
    and 'Y' = t1.d(+)
    and 'Y' = t1.e(+)
  filters ?

  because if
   and 'Y' = t1.d(+)
   and 'Y' = t1.e(+)
  are not filters , I can get rid of the join wiht t1.

 Can I get rid of the join wiht t1 in this case ?

 2, Sorry for the typo for previous question

  should be :

  What is the follow outer join for ?

  SELECT ...
  FROM T,T1
  WHERE
  .
  .
  T.a=T1.a(+)
  AND
  SYSDATE
  BETWEEN
  T1.DATA1(+) AND T1.DATA2(+)
  .
  .
  .
   ;

 What is means
 "
 SYSDATE
  BETWEEN
  T1.DATA1(+) AND T1.DATA2(+)
  "

outer join here ?

 Thank you!

          Followup:

You are going to want to start trying to conceptualize what the outer join does.
 Once you can "imagine it", you'll be able to answer these questions readily.

The 'Y' constraints only keep records from T1 where they are 'Y' and if no 'Y'
records exist, it'll make up one.  So, knowing that, all you need is an example
where there are lots of T1 records that match EXCEPT FOR 'Y' and it is easy to
see that no, those two queries are no where similar:

ops$tkyte@ORA9IR2> select * from t;

         A          B          C
---------- ---------- ----------
         1          1          1

ops$tkyte@ORA9IR2> select * from t1;

         A          B          C D E
---------- ---------- ---------- - -
         1          1          1 n n
         1          1          1 n n
         1          1          1 n n
         1          1          1 n n

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select count(*)
  2   from t, t1
  3    where t.a = t1.a(+)
  4      and t.b = t1.b(+)
  5            and t.c = t1.c(+)
  6              and 'Y' = t1.d(+)
  7                    and 'Y' = t1.e(+)
  8  /

  COUNT(*)
----------
         1

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select count(*)
  2   from t, t1
  3    where t.a = t1.a(+)
  4      and t.b = t1.b(+)
  5            and t.c = t1.c(+)
  6  /

  COUNT(*)
----------
         4

ops$tkyte@ORA9IR2>

Your comments in #1 after that -- about removing the join, don't compute.  If
you just wanted to count T, T1 should not be in the picture at all.  If you
wanted to count the effects of joining T to T1 where (d,e) = ('Y','Y') OR there
are no (d,e) = ('Y','Y'), you need the outer join.

what you need to code here is 100% dependent on the question you want to answer.

2) it means the same thing as anything when you see (+).  It says "if no match
in the other table --t1 in this case -- for the join condition (yes, joins do
not have to just be '=', they can be ANY operation really) make up a row with
all nulls please"

ops$tkyte@ORA9IR2> select * from t;

         A
----------
         1
         2

ops$tkyte@ORA9IR2> select * from t1;

         A DATA1     DATA2
---------- --------- ---------
         1 10-FEB-04 12-FEB-04
         2 09-FEB-04 10-FEB-04

ops$tkyte@ORA9IR2> select *
  2    from t, t1
  3   where t.a = t1.a (+)
  4     and sysdate between t1.data1 (+) and t1.data2 (+)
  5  /

         A          A DATA1     DATA2
---------- ---------- --------- ---------
         1          1 10-FEB-04 12-FEB-04
         2

ops$tkyte@ORA9IR2>

so, all rows in T come back AND IF there is a row in T1 such that date1 <=
sysdate <= date2 -- return that data as well.

      Rating:
      3
OK  February 25, 2004
Reviewer:
      George  from OR,USA
Dear Sir,
Can we do Outer join with a *single* table?Please specify a
simple example for that?
Bye!

          Followup:

does not make sense.  give me a concrete example of what you mean by that.

      Rating: 4
Outer
      Join?  February 26, 2004
Reviewer:  Vinnie
       from Orlando
Tom,

I have the following:
Table A
table_name
col_name

Data for A
EMP    NAME
EMP    LNAME
EMP    ID

Table B
table_name
col_name
flag

Data for B:
EMP FNAME Y
EMP LNAME N

I would like to return all COL_NAMES from both tables that match & also any
col_names in table A that are not in table B.  But only if B.FLAG = 'Y'.

ex.
result set
FNAME
ID

          Followup:

how can a row in A that is not in B have a B.FLAG='Y' pray tell?

Seems more like you want all rows in A that are not in B (a not in or not
exists) UNIONED with all rows in B that have B.FLAG='Y' (a simple predicate).

      Rating: 3
Full Outer Join
      Bug???  February 28, 2004
Reviewer:  Nopparat
      V.  from Thailand
Hi Tom,
Could you help me find out what's a problem on this query:

SQL> select nvl(a.pay_qtr, b.pay_quarter) pay_q,
  2         nvl(a.clm_l,0) clm_l, nvl(a.clm_a,0) clm_a,
  3         nvl(a.clm_t,0) clm_t, nvl(b.clm_h,0) clm_h
  4  from
  5   (select pay_qtr,
  6      sum(decode(flag_death,'Y', decode(plan_type,'L', re_pay_amt,0),0))
clm_l,
  7      sum(decode(flag_death,'Y', decode(plan_type,'R', re_pay_amt,0),0))
clm_a,
  8      sum(decode(flag_death,'N', decode(plan_type,'R', re_pay_amt,0),0))
clm_t
  9    from claim_paid_by_reinsurer
 10    where reinsurer_id = :reinsurer_id and pay_year = :pay_year
 11    group by pay_qtr) a
 12  full outer join
 13   (select pay_quarter, sum(re_pay_amt) clm_h
 14    from claim_h_paid_by_reinsurer
 15    where reinsurer_id = :reinsurer_id and pay_year = :pay_year
 16    group by pay_quarter) b
 17  on (a.pay_qtr = b.pay_quarter) ;
select nvl(a.pay_qtr, b.pay_quarter) pay_q,
*
ERROR at line 1:
ORA-00918: column ambiguously defined

I cannot find any columns that are ambiguous. And also when
I use left outer join instead, it give me then correct answers.

SQL> select nvl(a.pay_qtr, b.pay_quarter) pay_q,
  2         nvl(a.clm_l,0) clm_l, nvl(a.clm_a,0) clm_a,
  3         nvl(a.clm_t,0) clm_t, nvl(b.clm_h,0) clm_h
  4  from
  5   (select pay_qtr,
  6      sum(decode(flag_death,'Y', decode(plan_type,'L', re_pay_amt,0),0))
clm_l,
  7      sum(decode(flag_death,'Y', decode(plan_type,'R', re_pay_amt,0),0))
clm_a,
  8      sum(decode(flag_death,'N', decode(plan_type,'R', re_pay_amt,0),0))
clm_t
  9    from claim_paid_by_reinsurer
 10    where reinsurer_id = :reinsurer_id and pay_year = :pay_year
 11    group by pay_qtr) a
 12  left outer join
 13   (select pay_quarter, sum(re_pay_amt) clm_h
 14    from claim_h_paid_by_reinsurer
 15    where reinsurer_id = :reinsurer_id and pay_year = :pay_year
 16    group by pay_quarter) b
 17  on (a.pay_qtr = b.pay_quarter) ;

P      CLM_L      CLM_A      CLM_T      CLM_H
- ---------- ---------- ---------- ----------
1      50000          0      50000          0

Here is version I used :

SQL> select version from v$instance ;

VERSION
-----------------
9.2.0.1.0

In the query above, the first subquery has one row and the second has no row.

          Followup:

can you give me a full, simple, small test case to play with?  (create tables,
et. al.  something I too can run)

      Rating: 3
More
      details  February 29, 2004
Reviewer:  Nopparat
      V.  from Thailand
I'm afraid that I can't give a simple and small test case for you.
claim_paid_by_reinsurer and claim_h_paid_by_reinsurer are some complex views.
Did the full outer join have any problem when using on these views?

Anyway, the following is the structure of the view

SQL> desc claim_paid_by_reinsurer
 Name                           Null?    Type
 ---------------------------------------------------
 CLM_DOC_NO                     NOT NULL VARCHAR2(10)
 PLAN_TYPE                      NOT NULL VARCHAR2(1)
 RIDER_CODE                     NOT NULL VARCHAR2(2)
 OCCUR_DATE                              DATE
 CLM_CAUSE                               VARCHAR2(7)
 FLAG_DEATH                              VARCHAR2(1)
 POLICY_ID                               VARCHAR2(7)
 RE_YEAR                                 VARCHAR2(4)
 RE_QUARTER                              VARCHAR2(1)
 RE_COVERAGE_TYPE                        VARCHAR2(2)
 CERT_ID                                 VARCHAR2(6)
 REINSURER_ID                   NOT NULL VARCHAR2(2)
 TOTAL_SUM                               NUMBER(8)
 ER_FLAG                                 VARCHAR2(1)
 PAY_DATE                                DATE
 PAY_AMT                                 NUMBER(10,2)
 PAY_YEAR                                VARCHAR2(4)
 PAY_QTR                                 VARCHAR2(1)
 RE_PAY_AMT                              NUMBER
 PAY_RATE                                NUMBER(5,2)

SQL> desc claim_h_paid_by_reinsurer
 Name                           Null?    Type
 ---------------------------------------------------
 POLICY_ID                      NOT NULL VARCHAR2(7)
 REINSURER_ID                   NOT NULL VARCHAR2(2)
 PAY_DATE                                DATE
 PAY_YEAR                                VARCHAR2(4)
 PAY_QUARTER                             VARCHAR2(1)
 RE_PAY_AMT                              NUMBER

and here are their data :

SQL> select reinsurer_id, pay_year, pay_qtr,
  2    flag_death, plan_type, re_pay_amt
  3  from claim_paid_by_reinsurer ;

RE PAY_ P F P RE_PAY_AMT
-- ---- - - - ----------
02 2547 1 Y L      50000
02 2549 1 N R      50000
02 2549 1 N R      12500
05        Y R      22500
04        Y R      52500
02 2547 1 N R      50000
05 2547 4 Y L      22500
04 2547 4 Y L      52500
05 2547 2 Y L      15000
04 2547 2 Y L      35000
05 2547 2 Y R      15000

RE PAY_ P F P RE_PAY_AMT
-- ---- - - - ----------
04 2547 2 Y R      35000
02 2549 1 Y R      50000
02        Y R      50000

14 rows selected.

SQL> select reinsurer_id, pay_year, pay_quarter,
  2    re_pay_amt
  3  from claim_h_paid_by_reinsurer ;

RE PAY_ P RE_PAY_AMT
-- ---- - ----------
06                 0
02              3250
06             76500

Thank you very much for your helps.

          Followup:

I actually get a "nasty error" on this one, not as nice as "ambigous column".

but only in 9201, not in 9204.  In 9204 -- I'm not having any issues.

I'll have to refer you to support for this one, I cannot reproduce your issue
example (would need the views and base tables I suppose)

      Rating: 3
Filtering rows
      after outer join  March 02, 2004
Reviewer:
      Jackson Murphy  from TX,USA
Dear Tom,
After we do a outer join,Is it possible to discard some rows
from the result set?
like emp.deptno = dept.deptno(+)
 and dept.deptno not in (10,20)
 Is it possible to do like this?
Please do reply.

          Followup:

why outer join at all then?

if you say "and dept.deptno not in (10,20)" that is like saying

"and dept.deptno not in (10,20) AND dept.deptno is not null"

since NULL is not "not in(10,20)" (actually null is neither NOT IN nor IN
10,20).

what might make sense would be:

 from emp, (select * from dept where deptno not in (10,20) ) dept
where emp.deptno = dept.deptno(+)

or

  from emp, dept
 where emp.deptno = dept.deptno(+)
   and (dept.deptno is null OR dept.deptno not in (10,20) )

or

  from emp, dept
 where emp.deptno = dept.deptno(+)
   and EMP.deptno not in (10,20)

not sure what you want really -- you'd have to phrase in english the result set
you desired.  something like:

  I want a report for EVERY employee.  If that emp is in a deptno other
  than 10,20 -- I would like the dept information as well

that would be:

 from emp, (select * from dept where deptno not in (10,20) ) dept
where emp.deptno = dept.deptno(+)
or
  from emp, dept
 where emp.deptno = dept.deptno(+)
   and (dept.deptno is null OR dept.deptno not in (10,20) )

  I want a report for every employee except for deptnos 10,20.  I would like
  dept information for that employee when available.

that would be:

  from emp, dept
 where emp.deptno = dept.deptno(+)
   and EMP.deptno not in (10,20)

March 02, 2004 Pawan -- Thanks for the question regarding
      "Improve performance while loading data", version 8.1.7

      You Asked
Tom,
    I am presently working on a Data Warehouse project which is using
Informatica for mapping and loading data into an Oracle database (8.1.7 on HP UX
11.0). The data is in flat files and some of the hundreds of tables in the
Warehouse are HUGE (300-500 million records). The designers have not partioned
the base tables into which this data is being loaded. Their contention is that
since we are using SAN ( storage Area Network) that has multiple disks we don't
need to partion the tables. I am not sure if this is right. Also they have
primary keys on the tables. My question is - what is the best way to speed up
the loading process? What init.ora parameters need to be set? If they agree to
partition then whether it is better to partion by range or by hash? Is it
possible to use parallel loading of a table if yes then How?
    When I question I am told that since they are using Informatica we will have
this kind of performance. I don't understand why one would use a 1/2 million
package and have such horrible performance. Please let me know in yr opinion
what is the most optimum method of loading such huge amount of data.

Thanks in advance.

      and we said...
The designers of this system are not database guys are they.  Well, that is a
rhetorical question -- they aren't.

Partitioning provides many things -- partition elimination can make your queries
go faster in a data warehouse.  many times faster.

Partitioning allows for easy aging of data, rolling windows.  Something
virtually every data warehouse does.

Partitioning allows for higher availability when that soft corruption kicks in
and it will.  better to have a single, small partition offline to be rebuilt
then the entire table.

Partitioning eases adminstrative burdens.  It is a fact that performing
operations on small objects is inherently easier, faster and less resource
intensive then performing the same operations on a large object.

Partitioning makes mass updates -- something you might need to do in a data
warehouse, might not -- faster.  You can use parallel DML, but only if you
partition.

And the list goes on.

We've had this "multiple disk" thing from day 1 in databases.  There has ALWAYS
been more then one disk.  Enter raid striping -- a SANS didn't do anything new
here.  Your designers haven't worked on large data warehouses -- or if they
have, they've never used partitioning and don't understand it.

Loading a huge amount of data -- i would be using sqlldr in parallel direct path
mode.  I would be using parallel index builds with no logging.  It would not
take very long at all.

Review &
      Followup

      Rating: 4
Just a quick
      addition, if I may please  February 03, 2002

Reviewer:  Ravi  from USA
Hi Tom, If I may add to what you said:
What about disabling constraints and dropping indexes before loading so much
amount of data?? And, that way, if you have the comfort of rebuilding the index
during database inactive time (like nights), you can reduce time a lot.

And how about altering table to 'NOLOGGING' before doing dataload because we
have the data in ascii files anyways.

Just a quick addition: Parallel DML can also be done on a non-partitioned table,
right?? The only difference is it will add above the HWM. Right? Please correct
me if I'm wrong.

          Followup:

yup -- parallel direct path loads without logging will get you all that as well.
 The indexes will be usuable and have to be rebuilt.

PDML (parallel updates/deletes) need partitioning.  Parallel INSERT INTO SELECT
* FROM does not.

      Rating: 3
Improve
      performance while loading data  February 03, 2002

Reviewer:  Tony  from Australia
Pawan,
I just joined an organisation which is almost having the same env., as you
mentioned in your scenario(Informatica/ Oracle 8.1.6/HP-UX 11.0). This site was
also not using partitioning for their FACT tables. I can really understand the
people's environment.
In order to change the old believes (when partitioning was not available), you
have to convince them, may be by simulating in some test environment. I had to
do the same, once "THE REAL BENEFITS OF PARTITIONING" (as mentioned by Tom) are
proved, the resistance/belief will be change very quickly.
Just to give an idea, with partitioning some of the queries response time has
improved by even 50%, and even loading time reduced by 50%.
Also keep in mind the benefits of Local and Bitmap indexes. All the best!

      Rating: 4
Thanks a
      lot  February 05, 2002
Reviewer:  Pawan
       from USA
Thanks Tom and Ravi,
      You people have vindicated whatever I hve been saying but the million
dollar question should I take the Boss headlong and show her this answer and
pack my bags.

Thanks again

      Rating: 5
Partitioning
      Rules  February 07, 2002
Reviewer:  Johan
      Snyman  from Johannesburg, South Africa
Another HUGE benefit of partitioning is that one can load new data into a table
with the same structure as the partitioned table, but without any indexes.

Then you create all the indexes the partitioned table have on the newly loaded
table, add an empty partition to the partitioned table, swap the newly loaded
table with the empty partition (within a few seconds) and voila - you have added
millions of indexed rows in no (or very little) time at all.

It gives you the best of both worlds : loading without index maintenance AND
only indexing the newly loaded data.

      Rating:
      3
  March 01, 2004
Reviewer:
      A reader  from india
Tom,
    I am presently working on a Data Warehouse project which is using
informatica for mapping and loading data into an Oracle database (8.1.7 on
windows 2000 and 4 cpu 2.40 ghz and i gb ram).There data is in some relational
tables and some of hunderds of huge table in siebel datawarehousing .When
loading 43 lakhs of data into siebel datawaregousing it takes huge amount of
time even disable all constraints and drop indexes.Even if rebuild single index
it takes 8 hours to rebuild  .We are not using any partioning any table in
datawarehousing .Please suggest what is the best way of loading such huge data

Thanks in advance
MRao

          Followup:

one cannot really comment on that.

1gig of ram is "sort of small", but you would have to identify at least what
your bottlenecks are - your major issues.  (statspack might be a good place to
start).  I can say that I know informatica to be infamously "row by row" which
is the slow way to do things.

      Rating:
      3
  March 02, 2004
Reviewer:  mohan
       from India
Tom
I agree ram is small .If use statspac for identifying bottleneck which one i
should focus i mean top 5 wait events.

Thnaks in advance
Mrao

          Followup:

the biggest one.

March 02, 2004 Aparajita -- Thanks for the question regarding
      "partitioning", version 8.1.7

      You Asked
Hello Tom,

I have a requirement which is as follows:

I have a table A which is truncated everyday before loading data into it which
contains approx 2 milion rows. The table has a column called UPDATE_DATE which
is a date field. Now I have to take the data of table A over db_link and then
populate the table B which is supposed to be containg historical data of table A
(with same column names etc.), the requirement is that I have to keep 30 months
of historical data of table A in Table B. I have to drop the oldest partition of
table B that is older than 30 months. I have thought of using range partitioning
on UDATE_DATE, i.e. creating every day one partition and then do a parallel
insert as I cann't use exchange partition over dblink. Is my method is correct?
Can you please explain with an example. With my idea there will be around 900
partitions?
Also can you please let me know what are the things I need to consider(creating
local index etc..)Thanks in advance for your help

      and we said...
Well, here is a different -- faster -- less down time is us -- approach.

Consider that two million row table is called big_Table.  Do it like this:

REM drop table big_table;
REM
REM CREATE TABLE "BIG_TABLE"."BIG_TABLE" ("OWNER" VARCHAR2(30) NOT NULL
REM ENABLE, "OBJECT_NAME" VARCHAR2(30) NOT NULL ENABLE, "SUBOBJECT_NAME"
REM VARCHAR2(30), "OBJECT_ID" NUMBER NOT NULL ENABLE, "DATA_OBJECT_ID"
REM NUMBER, "OBJECT_TYPE" VARCHAR2(18), "CREATED" DATE NOT NULL ENABLE,
REM "LAST_DDL_TIME" DATE NOT NULL ENABLE, "TIMESTAMP" VARCHAR2(19),
REM "STATUS" VARCHAR2(7), "TEMPORARY" VARCHAR2(1), "GENERATED"
REM VARCHAR2(1), "SECONDARY" VARCHAR2(1))
REM partition by range (object_id)
REM ( partition the_only_one values less than (maxvalue) tablespace users )
REM /
REM
REM insert /*+ APPEND */ into big_table select * from all_objects;
REM commit;
REM insert /*+ APPEND */ into big_table select * from big_table;
REM commit;
REM insert /*+ APPEND */ into big_table select * from big_table;
REM commit;
REM insert /*+ APPEND */ into big_table select * from big_table;
REM commit;
REM insert /*+ APPEND */ into big_table select * from big_table;
REM commit;
REM insert /*+ APPEND */ into big_table select * from big_table;
REM commit;
REM insert /*+ APPEND */ into big_table select * from big_table;
REM commit;
big_table@ORA920.US.ORACLE.COM>
big_table@ORA920.US.ORACLE.COM>
REM create table t tablespace users
REM as
REM select OWNER, OBJECT_NAME, SUBOBJECT_NAME,
REM -OBJECT_ID object_id, DATA_OBJECT_ID, OBJECT_TYPE,
REM CREATED, LAST_DDL_TIME, TIMESTAMP,
REM STATUS, TEMPORARY, GENERATED, SECONDARY
REM from big_table;
REM alter table t modify (object_id not null);

Now, T is the table with the "new" 2 million rows in it.  Our goal:

a) load big_table with ASAP (minimize downtime, there hasn't been any downtime
yet -- T was loaded without impacting anybody)

b) make it so we can get the data from BIG_TABLE to another database (why why
why another database?  would make much greater sense to use a single database
but anyway)

So, we:

big_table@ORA920.US.ORACLE.COM> set timing on
big_table@ORA920.US.ORACLE.COM>
big_table@ORA920.US.ORACLE.COM> select object_id from big_table where rownum=1;

 OBJECT_ID
----------
        30

Elapsed: 00:00:00.16
big_table@ORA920.US.ORACLE.COM> select object_id from t where rownum=1;

 OBJECT_ID
----------
       -30

Elapsed: 00:00:00.09
big_table@ORA920.US.ORACLE.COM>
big_table@ORA920.US.ORACLE.COM> alter table big_table
  2  exchange partition the_only_one
  3  with table t
  4  without validation
  5  /

Table altered.

Elapsed: 00:00:00.10
big_table@ORA920.US.ORACLE.COM>
big_table@ORA920.US.ORACLE.COM> select object_id from big_table where rownum=1;

 OBJECT_ID
----------
       -30

Elapsed: 00:00:00.00
big_table@ORA920.US.ORACLE.COM> select object_id from t where rownum=1;

 OBJECT_ID
----------
        30

Elapsed: 00:00:00.00
big_table@ORA920.US.ORACLE.COM>

Now, big_table is reloaded with virtually no downtime!

Now, what we do is transport the tablespace with T (contains the old data from
BIG_TABLE) and attach it to the other database and just do the switcher-roo over
there.
Review
      & Followup

      Rating:
      4
partitioning  October 20, 2002
Reviewer:
      Aparajita  from UK
Tom,
I don't know whether I have made my question clear enough or not. The table A as
referred to in my question has a date field (update_date) and contains 2 million
rows for a single update_date. My history table B is supposed to contain data
from A, which will be populated from A on a daily basis and the data retention
need for the history table is 30 months. Now I am not sure from your answer
about whether I should create a partition using update_date as key or not on
table B. Are you suggesting soem other field for partitioning key? Also please
can you discuss a little on local indexes and how to handle that in this
situation. Due to tablespace constraint I have to create table B on another
database. Now can you please clear me the following lines from your answer. I
did n't understand what you meant by 'switcher-roo'

"Now, what we do is transport the tablespace with T (contains the old data from
BIG_TABLE) and attach it to the other database and just do the switcher-roo over
"
there.

Thanks very much for your help.

          Followup:

What I understand:

tableA is filled with data every night (2 million rows)

there is a column updated_date in tableA which I assume is the same for all 2
million rows?  (well, that is what I assumed -- that it is the load date)

So, I gave you a fast way to

o reload table a without any down time (load into a staging table, swap stage
with table a)

o a way to get the 2 million rows there were in a to table b fast (just
transport the tablespace that contains the "swapped" data)

o now, to add this data to table B, just swap this newly transported table with
an empty partition in B and wah-lah -- switcher-roo is done.   You have loaded A
with new data and B contains the data from yesterdays copy of A.

You would use local indexes on everything and just copy the indexes along with
the table data as you moved it from place to place.

      Rating: 4
some more
      questions  October 31, 2002
Reviewer:  AD
       from UK
This is follow up of my earlier mail on this subject. The requirement has
suddenly changed and now it is like this.

I have to keep 12 months of data in the table. The table has to be partitioned
on update_date( one partition every day). But for manageability purposes the
data should be in 4 different tablespaces which should not be hard coded. Same
is for the indexes that is they should be in different tablespaces and should
not be hardcoded. Index tablespaces are different from data tablespaces.

CREATE TABLE A
     ( COL1
       COL2
       ....
       UPDATE_DATE  )
      PARTITION BY RANGE ( UPDATE_DATE)
     ( PARTITION A_part_20021028 VALUES LESS THAN TO_DATE(' 2002-10-28',
'yyyy-mm-dd')
       TABLESPACE tsa ,
       PARTITION A_part_20021029 VALUES LESS THAN TO_DATE(' 2002-10-29',
'yyyy-mm-dd')
       TABLESPACE tsa ,
       PARTITION A_part_20021030 VALUES LESS THAN TO_DATE(' 2002-10-30',
'yyyy-mm-dd')
        TABLESPACE tsa ,
       ......
       PARTITION A_part_20030130 VALUES LESS THAN TO_DATE(' 2003-01-30',
'yyyy-mm-dd')
        TABLESPACE tsb,
       PARTITION A_part_20030430 VALUES LESS THAN TO_DATE(' 2003-04-30',
'yyyy-mm-dd')
        TABLESPACE tsc,
       PARTITION A_part_20030730 VALUES LESS THAN TO_DATE(' 2003-07-30',
'yyyy-mm-dd')                    TABLESPACE tsd)

Can you please give me a quick example on this.

      Rating:
      4
  November 01, 2002
Reviewer:  AD
       from UK
Tom,

Can you please reponse to my questions I requested earlier.  Below is the link.
Thanks in advance

http://asktom.oracle.com/pls/ask/f?p=4950:8:1535483::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:6050339907954,%7Baparajita%7D

          Followup:

just use dynamic sql in plsql or whatever to script it.  tablespace names *must*
be hardcoded (cannot be bind variables) in sql statements so you'll be using a
script of some sort.

      Rating:
      4
clarifications  November 01, 2002

Reviewer:  AD  from UK
Tom,

I refer to the link below.

http://asktom.oracle.com/pls/ask/f?p=4950:8:1535719::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1238800184155,%7Bpartitions%7D
"A partition exchange is simply an exchange of segments -- no movement of data
takes place.  It is a data dictionary update". That means if I have a partition
P1 of a partitioned table A in tablespace ta, and I exchange this partition with
a table B in tablespace Tb, then Partition P1 of Table A will now reside in
tablespace Tb and table B will reside in tablespace ta. with refernce to local
index what is needed to be done so that index tablespaces are swapped like this.
Is there any other mechanism aparat from
"The ALTER INDEX...REBUILD PARTITION statement rebuilds one partition of an
index. It cannot be used on a composite-partitioned table. At the same time as
you recreate the index, you can move the partition to a new tablespace or change
attributes"
How otherwise you meet the criteria of creating a partition P1 of table A
residing in tablespace t1, index of partition P1 in tablespace ti1 and so on...
Pleases elaborate.

Once again many thanks for your time.

          Followup:

And I refer you to a new Oracle9i feature that did not exist when I wrote that
response (always look at the version in the answer, the answer is specific to a
release in many cases - as new features are added)

see

http://download-west.oracle.com/docs/cd/A91202_01/901_doc/server.901/a90117/whatsnew.htm#969846

      Rating: 4
local index
        November 02, 2002
Reviewer:  AD  from
      UK
Tom,

Many Thanks for the prompt response.
This is specific to oracle 8.1.7 .

I just want to know how you can create local index so that each index partition
resides in different tablespaces. I am still not clear whether I have made my
point clear or not. Please reply me back if my question is not clear.

          Followup:

Here is a trick I use often to figure out the full syntax of something when I
don't feel like reading the SQL Reference guide.  I use exp/imp on a "minimum
example".  Exp/Imp is very "verbose"

Here you go:

ops$tkyte@ORA920.US.ORACLE.COM> 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_01 VALUES LESS THAN (MAXVALUE)
 10  )
 11  ;

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create index t_idx on t( x ) local;

Index created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> host exp userid=/ tables=t

Export: Release 9.2.0.1.0 - Production on Sat Nov 2 12:25:20 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                              T
. . exporting partition                        PART_95          0 rows exported
. . exporting partition                        PART_96          0 rows exported
. . exporting partition                        PART_01          0 rows exported
Export terminated successfully without warnings.

ops$tkyte@ORA920.US.ORACLE.COM> host imp userid=/ full=y show=y

Import: Release 9.2.0.1.0 - Production on Sat Nov 2 12:25:20 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 "T" ("X" NUMBER(*,0), "COLLECTION_YEAR" NUMBER(*,0))  PCTFREE "
 "10 PCTUSED 40 INITRANS 1 MAXTRANS 255 TABLESPACE "USERS" LOGGING PARTITION "
 "BY RANGE ("COLLECTION_YEAR" )  (PARTITION "PART_95" VALUES LESS THAN (1996)"
 "  PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 STORAGE(INITIAL 65536 FREEL"
 "ISTS 1 FREELIST GROUPS 1) TABLESPACE "USERS" LOGGING NOCOMPRESS, PARTITION "
 ""PART_96" VALUES LESS THAN (1997)  PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRAN"
 "S 255 STORAGE(INITIAL 65536 FREELISTS 1 FREELIST GROUPS 1) TABLESPACE "USER"
 "S" LOGGING NOCOMPRESS, PARTITION "PART_01" VALUES LESS THAN (MAXVALUE)  PCT"
 "FREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 STORAGE(INITIAL 65536 FREELISTS "
 "1 FREELIST GROUPS 1) TABLESPACE "USERS" LOGGING NOCOMPRESS )"
. . skipping partition "T":"PART_95"

. . skipping partition "T":"PART_96"

. . skipping partition "T":"PART_01"

 "CREATE INDEX "T_IDX" ON "T" ("X" )  PCTFREE 10 INITRANS 2 MAXTRANS 255 LOGG"
 "ING LOCAL(PARTITION "PART_95"  PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(I"
 "NITIAL 65536 FREELISTS 1 FREELIST GROUPS 1) TABLESPACE "USERS" LOGGING, PAR"
 "TITION "PART_96"  PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 65536 "
 "FREELISTS 1 FREELIST GROUPS 1) TABLESPACE "USERS" LOGGING, PARTITION "PART_"
 "01"  PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 65536 FREELISTS 1 F"
 "REELIST GROUPS 1) TABLESPACE "USERS" LOGGING ) "
Import terminated successfully without warnings.

ops$tkyte@ORA920.US.ORACLE.COM>

that shows the full verbose syntax for doing this, with tablespaces and all.

      Rating: 5
some more on
      local index  November 03, 2002
Reviewer:  AD
       from UK
Tom,
Thanks for providing such a detailed example. I should not have continued this
discussion further but I really need this point clear. This is fine if we know
the tablespaces for indexes (for differnt partitions ) at the time of creating
the local index. But that is not the case with my development requirement. As
the DBA insists that he is not going to create all the future tablespaces for
storing the data/ indexes at this point of time. They will be created later as
and when they are required. He thinks that these tablespaces should be parameter
driven of number of months of data etc.
Could you please tell me what should be the way to create the local index in
that situation. Do you think there is no other alternative than using "The ALTER
INDEX...REBUILD PARTITION" for each time a partition is added so that the index
patrtition is residing in a specific tablespace. Is it at all a good idea or I
have to think something else.

          Followup:

ops$tkyte@ORA920.US.ORACLE.COM> CREATE TABLE "OPS$TKYTE"."T" ("X" NUMBER(*,0),
"COLLECTION_YEAR"
  2  NUMBER(*,0)) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 TABLESPACE
  3  "USERS" LOGGING PARTITION BY RANGE ("COLLECTION_YEAR" ) (PARTITION
  4  "PART_95" VALUES LESS THAN (1996) PCTFREE 10 PCTUSED 40 INITRANS 1
  5  MAXTRANS 255 STORAGE(INITIAL 65536 FREELISTS 1 FREELIST GROUPS 1)
  6  TABLESPACE "USERS" LOGGING NOCOMPRESS, PARTITION "PART_96" VALUES
  7  LESS THAN (1997) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
  8  STORAGE(INITIAL 65536 FREELISTS 1 FREELIST GROUPS 1) TABLESPACE
  9  "USERS" LOGGING NOCOMPRESS ) ;

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> CREATE INDEX "OPS$TKYTE"."T_IDX" ON "T" ("X" )
PCTFREE 10 INITRANS 2
  2  MAXTRANS 255 LOGGING LOCAL(PARTITION "PART_95" PCTFREE 10 INITRANS 2
  3  MAXTRANS 255 STORAGE(INITIAL 65536 FREELISTS 1 FREELIST GROUPS 1)
  4  TABLESPACE "USERS" LOGGING, PARTITION "PART_96" PCTFREE 10 INITRANS 2
  5  MAXTRANS 255 STORAGE(INITIAL 65536 FREELISTS 1 FREELIST GROUPS 1)
  6  TABLESPACE "USERS" LOGGING ) ;

Index created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> alter table t add partition part_97 values less
than (1998) tablespace tools
  2  /

Table altered.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select 'TABLE', partition_name, tablespace_name
  2    from user_tab_partitions
  3   where table_name = 'T'
  4   union all
  5  select 'TABLE', partition_name, tablespace_name
  6    from user_ind_partitions
  7   where index_name = 'T_IDX'
  8  /

'TABL PARTITION_NAME                 TABLESPACE_NAME
----- ------------------------------ ------------------------------
TABLE PART_95                        USERS
TABLE PART_96                        USERS
TABLE PART_97                        TOOLS
TABLE PART_95                        USERS
TABLE PART_96                        USERS
TABLE PART_97                        TOOLS

6 rows selected.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> alter index t_idx rebuild partition part_97
tablespace xdb;

Index altered.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select 'TABLE', partition_name, tablespace_name
  2    from user_tab_partitions
  3   where table_name = 'T'
  4   union all
  5  select 'TABLE', partition_name, tablespace_name
  6    from user_ind_partitions
  7   where index_name = 'T_IDX'
  8  /

'TABL PARTITION_NAME                 TABLESPACE_NAME
----- ------------------------------ ------------------------------
TABLE PART_95                        USERS
TABLE PART_96                        USERS
TABLE PART_97                        TOOLS
TABLE PART_95                        USERS
TABLE PART_96                        USERS
TABLE PART_97                        XDB

6 rows selected.

would be the way to go if you really truly think you need, want or desire
the index to be in a separate tablespace.  Me, I would keep it in the same
tablespace probably -- makes the tablespace self contained (easier to transport
or unplug).  There isn't any real performance benefit to separating these things
or anything.  I would probably just let them be together.

Since the index partition is empty, the rebuild it instantaneous.

      Rating: 5
Indexes
        November 03, 2002
Reviewer:  AD  from
      UK
Tom,
Thanks so much for your help with this. I have read few discussions on this site
about the issue of keeping the index and data in separate tablespaces and I
definitely appreciate your suggestions of keeping them in same tablespaces but
some people dont think very much logically. That is the reason of keeping them
in separate tablespaces.

I am partitioning on update_date but most of the queries asked on this table
will be something like

select * from table A
where col1 =to_date('03-nov-2002', 'dd-mon-yyyy')
and   col2 = 'A'

or

Select * from table A
where col2='A'

Here col1 is date field and let's say with 400,000 distinct values and col2 with
three distinct values in 2 million rows table. What kind of indexes should be
preferred.
1)Local nonprefixed index on col1 and local nonprefixed bitmap index on col2
2) or local nonprefixed composite index on (col1, col2)
or something else.

Your suggestion will be very much appreciated.

          Followup:

yuck -- you partition on update_date but query on two unrelated columns.

if you have 10 partitions -- you'll need to do 10 index range scans on col1.  If
you have N partitions -- you'll need to do N index range scans!!!

That won't be *faster* that is for sure.

one would need to know the distribution of col2 in order to say -- is the value
you really query on 'A'?  or was 'A' just an example? how many 'A's

if you want the queries to go fast -- i'm thinking "global index"

I'm definitely knowing -- YOU will want to benchmark and test various
implementations.

      Rating: 5
Further points
      on indexing  November 05, 2002
Reviewer:  AD
       from UK
Tom,

First of all thanks for your help.
I have taken note of your suggestion for a global index. But I have two points
to understand
i) It may not be possible to keep each index partition in separate tablespace in
case of global index - and that is why my choice is restricted to use local
index
ii)It is a case of rolling window where I may need to rebuilt the global index -
which may be time consuming.

My question is as I don't know exactly what kind of queries will be running on
this partition table - is it advisable to the users for the inclusion of
update_date (partition_key) in all their queries so that partition elimination
takes palce. Can you please suggest.

          Followup:

i) false -- each index partition can be in an different partition

ii) 9i lets you use "update global indexes" to maintain a global index during
these operations

it only makes sense to have update date in the query IF it makes sense to have
it there.

      Rating: 3
asking help
      about partioned indexes  January 29, 2004

Reviewer:  Fernando Sánchez  from Madrid, Spain

I'm a developer who's been asked to modify the structure of a database and I
have some doubts (concept problems indeed).

I'm working with version 8.1.7 and I have a table that must be partitioned by
range on a date column. Initially the table was thought to have a primary key on
other column, I think the index associated with that column should also be
partitioned. Am I right?
More questions:
Should the index be local or global?
Can I create the partioned index without dropping the primary key? If not, how
can I force the old pk column to be unique after creating a non unique partioned
index on it?

Apart from that, the table has 2 foreign keys on fields that are also unique and
has 2 unique indexes associated with them. Should these indexes also be
partioned as the one of the primary key?

Any advice will be welcome. Thanks in advance.

          Followup:

I cannot tell you if you are right or not.  partitioning is a tool, both a
non-partitioned and a partitioned index make sense in different cases.

If the column to be indexed is UNIQUE, and the column is not the partition key
itself, then it must be GLOBAL.  You cannot have a unique locally partitioned
index unless the columns in the index are part of the partition key itself.

do you have either of my books -- i discuss the ins and outs and what you want
to consider with partitioning in both.

      Rating:
      5
thanks  January 29, 2004
Reviewer:
      Fernando Sánchez  from Madrid, Spain
I don't have any of your books. Can you tell what their titles are?

          Followup:

see my home page.

      Rating:
      2
Partitioning  March 01, 2004
Reviewer:
      Sandeep  from UK
Hi Tom,

I have been trying to get an opportunity to ask you a question for the last few
weeks... but seems that you are
utterly swamped with queries at the moment, thus not able to take any more!

Since this thread is relevant to my question, I can only hope that your cursory
glance would fall on this someday
:-)

Anyway... here goes.

I have a *big* table which is being used in the following
manner:

1. Every 2 hours data is loaded into it.

2. There are 2 materialized views on it, which get FAST
   refreshed. The refresh query for MV1 groups by
   various columns, pertinent ones being:
   a) Day_Of_Week
   b) Hour_Of_Day
   c) Week_Of_Year
   d) Service_number
   e) Year
   The second MV uses a Group by but does not use any "time
   parameters" for the grouping (unlike MV1).

   Of course, both these MV's get refreshed every 2
   hours (on commit)

3. There is another script run every night to delete
   records from this table which are more than 100 days
   old.  So basically this table will contain only 100
   days worth of data at any given time.

4. Some queries from the application directly query the
   big table based on service_number column (and do not
   group by).

(The service_number can be thought of as a telephone
 number)

The table has grown to 25GB and we really need to partition it!!

I wanted your opinion about which columns could be used for the partitioning?
for eg: 24 (hours of day) * 7 (day of week)

Should there be a global index on the service_number?

Any thoughts would help.

Regards,
Sandeep

          Followup:

well, what is the goal behind your using partitioning in this case.

what operation do you want to maximize?
what version do you have?

      Rating:
      2
Partitioning  March 02, 2004
Reviewer:
      Sandeep  from UK
Hi Tom,

Thanks for responding!

Apologies for not providing that necessary info.

>> well, what is the goal behind your using partitioning in
      this case.
--> a) one lame excuse... table is big.. 25GB+
       doing a count(*) or even trying to
       analyze the table is virtually impossible. Due to
       this I can't even find out the state of the
       indexes.. they are being hammered ~2 hours...
       so I can only imagine that they are not in a *good
       state*.

    b) sometimes we have an "Invalid ROWID error" while the
       snapshots on the big table are getting refreshed.
       This never happened before, i.e when the table was
       small. This *could* be happening due to sheer size
       of the table resulting in slowness and thus some
       row deletion which takes place before the
       refresh or something else I don't seem to understand
       at the moment, to be honest.

    c) With partitioning, we hope to make the refresh
       of the child MV's quicker. The logic(?) being:
       If a new data load is done into the big table, it
       will only have data which is pertinent to a specific
       hour, thus only that partition *should* be
       touched... and provided we get our physics right..
       we can partition the MV's in such a way that only
       some partitions are touched thus speeding up the
       whole "load-to-refresh" process.

    d) Manageability/ Recovery.. in case this main table of
         system gets *corrupted*.

>> what operation do you want to maximize?
     a) We need to maximize the "load-to-refresh" process.
        Currently trying to load even 200,000 records can
        take anytime from 2 hrs to 4 hrs! (I have found
        that when hotbackups are taking place during night
        then this time tends to increase)
        There is another issue with the loading itself.
        Basically, its an INSERT /*+ PARALLEL */ big_table
        INTO SELECT * FROM ... statement which calls some
        PL/SQL functions (ugh) as well to *massage* the
        data..
        Anyway..  the major time still goes while
        refreshing those MVs' (There is one more nested MV
        at level 3)... So from my observation.. total time
        taken for the "load-to-refresh" can be roughly
        broken like this:
       #1 INSERT big_table INTO SELECT * FROM
          load_table        -  20%
       #2 DELETE FROM load_table WHERE id between :1
          and :2 -  10%
       #3 then COMMIT which causes: -   60%
             FAST Refresh of MV1 (level 1)
             FAST Refresh of MV2 (level 1)
             FAST Refresh of MV3 (level 2)
       #4 Deletion of all snapshot logs from -  10%

    b) We also need to improve the process which deletes
       records every night from this table and uses:
         " WHERE date_field < ( SYSDATE - 100 ) "

>> what version do you have?
     This is 8174 (on unix)

I know how you despise about partitioning being thought of as fast=true.. but in
this case maybe there is a valid
reason (or desperation :-)?

Regards,
Sandeep

          Followup:

a) partitioning won't speed that up in all likelyhood (estimating stats would,
parallel would)

b) no, it would be something else for sure.  invalid rowid means "rowid points
to something outside of where this segment exists".  You'd have to be
deallocating (truncating) or just hitting "something really wrong"

c) ahh, now we are getting somewhere.  This and this alone will drive your
partitioning scheme.  You'll have to be careful not to negatively impact other
processing (via use of global indexes and such) but this is going to be your key
decision point for "how do we partition".

Have you read the data warehousing guide?  it has pretty good info on using
partitioning and MV's refreshes?

d) it's only 25gig, recovery from backup should be doable pretty fast.  have you
had corruption?  partitioning will make it "more recovable" (chunks of
recoverability).  but the partitioning scheme will be driven by c

You have valid reasons, now you'll just have to check out the data warehouse
guide:

http://download-west.oracle.com/docs/cd/A87860_01/doc/index.htm
specifically:

http://download-west.oracle.com/docs/cd/A87860_01/doc/index.htm
to understand what you can do in 8i with partitioning and MV's

March 02, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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%

      Rating: 4
Can we filter a
      set of records ?  January 26, 2004
Reviewer:
      Sanjaya Balasuriya  from Colombo, Sri Lanka
Tom,

With flashback queries, can filter a set of records ?

For example, at 14.00 can I select the records inserted between 08.00 and 11.00
?

Thanks in advance.

-San

          Followup:

in 10g -- yes, easily, using the versions between clause in SQL.

In 9i, sort of.  You can detect "new primary keys" (using minus -- you can find
all of the keys as of 11am that did not exist at 8am) and then retrieve the rest
of the data.

      Rating:
      4
Thanks  January 27, 2004
Reviewer:
      Sanjaya Balasuriya  from Colombo, Sri Lanka
Thanks Tom.

I read the documentation and identified that I can identify insertions and
deletion with simple queries using minus.
And to identify updates I can check some key with some other data.

I'd like to know that is flashback query is a solid method for such a thing ?
Currently, we use triggers to identify data changes (updates, inserts, deleles).
But these triggers slows downs the operations.
Is it a good idea to replace this trigger system with flashback query system ?
Is it reliable ?

Thanks in advance.

And when are you going to release 10g ?

          Followup:

flashback is not forever.  there is always the chance that the data you want to
flashback won't be in undo anymore (well, until 10g when you can "guarantee" the
undo retention)

first qtr of 2004

      Rating: 5
Flashback
      forever?  March 02, 2004
Reviewer:
      Cefers.br  from Brasil
[...well, until 10g when you can "guarantee" the undo retention...]

About the statement above, how exactally will this "undo guarantee" work? In
10g, will it be possible to flashback to any time in the past? (I heard
something about, but I´m not sure).

Thank you.

          Followup:

the undo retention is still "5 days max" but you can now say "guarantee the undo
retention I set is met.  do not prematurely expire undo extents"

March 02, 2004 -- 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.

      Rating: 3
FORALL
      limit?  February 24, 2004
Reviewer:  Mark
       from USA
Your book states for BULK COLLECT that 100 row limit "feels about right" in your
experience for performance and system resource concerns.  The examples on this
site show examples using BULK COLLECT with a limit of 100 to load an array, then
FORALL to do DML based on the array - so it too is dealing with 100 rows in
these examples. But is this limiting of FORALL "about right" at 100 rows?  If I
load my array without using BULK COLLECT should I be concerned about the maximum
number of rows I might need to process in the FORALL? Or would it be my rollback
segment that defines this upper limit, and whatever fits in that would be OK for
the FORALL to deal with from a performance standpoint? Thanks.

          Followup:

it is a memory thing (limit 1000 will take 10 times the memory limit 100 would).

it is a throughput thing -- think about it like this.  You are going to pave a
brick driveway.  You have a pile of bricks, a wheelbarrow and two people.

You can

a) have one person bring the other a single brick -- brick by brick by brick.
One at a time.  Not very efficient.  the guy laying the bricks (the client
application) spends lots of time waiting as the other guy goes back and forth
with a single brick at a time.

b) have one person load a couple of hundred bricks in the wheelbarrow. It takes
a while to load up this wheelbarrow so the brick layer sits there idle.
Additionally, it takes a while to navigate this ton of bricks in the wheelbarrow
(the poor guy is struggling to push it over the ground and trying not to spill
them all).  Finally the brick layer gets a ton of bricks and starts to process
them (this takes a while), then the whole process repeats.  Lots of "spurty"
activity

c) have one person load a reasonable number of brick in the wheelbarrow.  Run
with them over to the bricklayer, lay them get more and so on.  Less bursts,
better "flow", the work gets done with lots less roundtrips than A) but with
lots less effort than B)

This analogy holds pretty well for array fetching.  In fact, in 10g, they have
silently implemented the 100 rule in the engine itself for plsql!!!  (for
fetching anyway).  Watch this:

ops$ora10g@ORA10G> alter session set sql_trace=true;

Session altered.

ops$ora10g@ORA10G> begin
  2  for x in ( select * from all_objects ) loop null; end loop;
  3  end;
  4  /

PL/SQL procedure successfully completed.

SELECT *
FROM
 ALL_OBJECTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.07       0.22          3        127          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch      465     14.62      18.72        483     221246          0       46463
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      467     14.70      18.94        486     221373          0       46463

465 fetchs, 46k rows....  in 9i and before, there would have been 46k fetches.

      Rating: 3
but a 'good'
      limit for FORALL?  February 25, 2004
Reviewer:
      Mark  from USA
Very interesting and neat feature of 10g - and I understand the ideal balance on
a BULK COLLECT, trips vs. amount, good analogy.  But what about the FORALL
update?  If I have 1000 rows in an array, should I be concerned that that is so
high for one FORALL to work?  What about 10,000 rows in the array? Etc.
Thanks.

          Followup:

same analogy works in reverse, same picture, different perspective in general.

memory, network slam, spurts of processing.

I like 100

      Rating: 5
bulk collect
      for lobs?  March 02, 2004
Reviewer:  reader
       from PA
Your insight about bulk insert of lobs was most useful.

For bulk collect is there any reason you can't bulk collect lob locators into a
collection?  Is it just the standard bulk collect like:

clob_table (id number, doc clob)

DECLARE
   TYPE ClobRecTab IS TABLE OF clob_table%ROWTYPE;
   clob_recs ClobRecTab;
   CURSOR c1 IS
      SELECT id,clob FROM clob_table;
BEGIN
   OPEN c1;
   FETCH c1 BULK COLLECT INTO clob_recs;
END;

          Followup:

ops$tkyte@ORA920PC> create table t
  2  as
  3  select owner, view_name, to_lob(text) text
  4   from all_views
  5   where rownum <= 100;

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> declare
  2          type array is table of t%rowtype index by binary_integer;
  3          l_rec array;
  4  begin
  5          select * bulk collect into l_rec from t;
  6  end;
  7  /

PL/SQL procedure successfully completed.

You can, it just fetches the lob locator.  It won't load up the 4gig of lob data
into memory or anything like that.

March 02, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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.

      Rating: 3
How to make
      NO_PUSH_PRED work  March 02, 2004
Reviewer:
      AB  from UK
Tom

I have a table with millions of "good" rows and a few junk rows. I am trying to
filter the bad records on a VARCHAR2 representation of a date so I can TO_DATE
the good records.
I was simply using :-

SELECT /*+ NO_PUSH_PRED (a) */
       some_columns
FROM  (
       SELECT some_columns
       FROM   some_table
       WHERE  SUBSTR(char_date_column,1,1) BETWEEN '0' AND '9'
      ) a
,     some_other_table b
WHERE some_join_condition
AND   TO_DATE(a.char_date_column, 'YYYY-MM-DD') < TRUNC(SYSDATE, 'YEAR')

However, the predicate is being pushed into the "a" in-line view, because I am
getting
TO_DATE errors against the few bad records. If I bring the SUBSTR outside the
in-line view and use
ORDERED_PREDICATES, then it seems happy. Any suggestions ?

Regards
AB

          Followup:

use CASE

where some_join_condition
  and case when substr( char_date_column,1,1) between '0' and '9'
           then to_date( .... )
           else null
       end < trunc( .... );

March 02, 2004 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?

      Rating: 5
PL versus
      SQL  March 02, 2004
Reviewer:  Gabriel
       from Montreal, Canada
Hello Tom,

I've been trying to convince my project manager that if I can do stuff in SQL is
much better than doing them in PL/SQL. But he will have none of it. He thinks
that an update will place row locks for far too long for him and make other
transactions wait. I replied that PL can slow down his whole system if not
needed etc. etc.

I then made a small test case using the runstats package that you developped. It
is again obvious to me that the latches are serializable and on our env of 1000
concurrent connection that will have a big impact on scalability (as you can see
I read your book ;-) ). But now I am at a loss to interpret some of the results
of the runstats. Can you please help me with the ones that I didn't fill up and
maybe correct me on the ones that I filled, or just send me a link where I can
find the explanations (or both?).

exec runstats_pkg.rs_start;

declare
x varchar2(20);
cursor c is
select substr(sapfile_row,0,(instr(sapfile_row,'|'))-1) cv from sapfile;
begin
for cv in c loop
update hs_so_test hst
   set XVUMATERIALSTATUS=
      (select ltrim(replace(substr(sapfile_row,(instr(sapfile_row,'|'))),'|','
'))info
       from sapfile
       where sapfile.SAPFILE_ROW not like ('HDR%')
       and
       sapfile.SAPFILE_ROW not like ('TRL%')
       and
       hst.WORKITEMID=x)
   where hst.workitemid  in (select
substr(sapfile_row,0,(instr(sapfile_row,'|'))-1) from sapfile);
   commit;
end loop;
end;
/

exec runstats_pkg.rs_middle;

update hs_so_test hst
   set XVUMATERIALSTATUS=
      (select ltrim(replace(substr(sapfile_row,(instr(sapfile_row,'|'))),'|','
'))info
       from sapfile
       where sapfile.SAPFILE_ROW not like ('HDR%')
       and
       sapfile.SAPFILE_ROW not like ('TRL%')
       and
       substr(sapfile_row,0,(instr(sapfile_row,'|'))-1)=hst.WORKITEMID)
   where hst.workitemid  in (select
substr(sapfile_row,0,(instr(sapfile_row,'|'))-1) from sapfile);

exec runstats_pkg.rs_stop(500);

PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.

0 rows updated.

Run1 ran in 50 hsecs
Run2 ran in 10 hsecs
Run1 ran in 500% of the time

Name                                Run1      Run2      Diff
STAT...session logical reads       1,442       789      -653
STAT...consistent gets               903       246      -657
LATCH.cache buffers chains         4,123     3,075    -1,048
STAT...table scan rows gotten      2,439       609    -1,830
STAT...sorts (rows)                5,715     3,878    -1,837
LATCH.row cache enqueue latch     25,130     5,028   -20,102
LATCH.row cache objects           37,670     7,537   -30,133
STAT...session pga memory              0   655,360   655,360
STAT...session uga memory              0 1,574,344 1,574,344

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
68,759    17,052   -51,707 403.23%

PL/SQL procedure successfully completed.

session logical reads      :2 times more that current or other blocks were
requested.

consistent gets            :3.5 times more the number of blocks that were
requested.

cache buffers chains       :???

table scan rows gotten     :4 times more the number of rows processed

sorts (rows)               :1.5 time more the number of rows sorted

row cache enqueue latch    :???

row cache objects          :???

session pga memory         :???

session uga memory         :???

Thank you very much,

          Followup:

cache buffers chains are latches we use to access blocks in the buffer cache
(correlates well with your logical reads)

row cache latches protect the dictionary cache (shared definitions)

you would expect uga/pga memory to go up.  if you run again, they stay the same.
 everything you do will "increase memory usage".

March 02, 2004 Mark -- Thanks for the question regarding
      "Package header/body diff. 8i & 9i", version 8.1.7

      You Asked
Tom:

Consider the following:

SQL> connect scott@cds2
Enter password:
Connected.
SQL> @get_version

PRODUCT                          VERSION
-------------------------------- --------------------------------
NLSRTL                           3.4.1.0.0
Oracle8i Enterprise Edition      8.1.7.2.1
PL/SQL                           8.1.7.2.0
TNS for 32-bit Windows:          8.1.7.2.0

4 rows selected.

SQL> create or replace package mtest as
  2  procedure aproc(p_in varchar2 default null);
  3  end;
  4  /

Package created.

SQL> create or replace package body mtest as
  2  procedure aproc(p_in varchar2 default 'abc') is
  3  begin
  4  null;
  5  end;
  6  end;
  7  /

Package body created.

SQL> show errors
No errors.

SQL> connect scott@cds1
Enter password:
Connected.
SQL> @get_version

PRODUCT                          VERSION
-------------------------------- --------------------------------
NLSRTL                           9.0.1.1.1
Oracle9i Enterprise Edition      9.0.1.1.1
PL/SQL                           9.0.1.1.1
TNS for 32-bit Windows:          9.0.1.1.0

4 rows selected.

SQL> create or replace package mtest as
  2  procedure aproc(p_in varchar2 default null);
  3  end;
  4  /

Package created.

SQL> create or replace package body mtest as
  2  procedure aproc(p_in varchar2 default 'abc') is
  3  begin
  4  null;
  5  end;
  6  end;
  7  /

Warning: Package Body created with compilation errors.

SQL> show errors
Errors for PACKAGE BODY MTEST:

LINE/COL
---------------------------------------------------------------------
ERROR
---------------------------------------------------------------------
2/17
PLS-00593: default value of parameter "P_IN" in body must match that of spec

SQL>

I understand the error message from 9i - I'm just wondering why I don't get the
same error message from 8i?

Thanks,

Mark

      and we said...
It was a bug -- the spec always determined what would actually be defaulted, the
body was ignored.  This bug was corrected in 9i so this programming error would
be detected by the compiler.

The spec and body must match exactly now.
Review &
      Followup

      Rating: 5
Designer
      Bug  October 07, 2001
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Tom:

I was looking for confirmation on this, as there is a bug (#1236289) in Designer
that is marked as fixed.  The problem is, the "fix" changed the default value
for a parameter in the package body only, and not in the package spec as well.
So, as you've pointed out, the package spec default value (which is apparently
incorrect) still "wins" over the changed package body default value.

I always appreciate your prompt responses and insight...

- Mark

      Rating: 4
Spec & Body
      - Can't we use different parameter name?  March 02, 2004

Reviewer:  Saminathan  from NJ,USA
Dear Tom,

Thanks for your help and support.

I understand that Package Spec and Body should have the
1)same number of parameters
2)same datatypes
3)same default values

But it does not make sense to have the SAME PARAMETER NAME.

CREATE OR REPLACE PACKAGE pkg1 AS
PROCEDURE proc1(i_p1 IN VARCHAR2,i_p2 IN NUMBER);
END;
/
Package created.

CREATE OR REPLACE PACKAGE BODY pkg1 AS
PROCEDURE proc1(i_p1 IN VARCHAR2,i_p2 IN NUMBER) AS
BEGIN
NULL;
END;
END pkg1;
/
Package body created.

CREATE OR REPLACE PACKAGE BODY pkg1 AS
PROCEDURE proc1(i_p2 IN VARCHAR2,i_p3 IN NUMBER) AS
BEGIN
NULL;
END;
END pkg1;
/

PLS-00323: subprogram or cursor 'PROC1' is declared in a package specification
and must be defined in the package body

          Followup:

yes it does.  many times you use named notation in plsql

  p( x => 55 );

the name is important.

the spec and body must match.

March 02, 2004 Shah -- Thanks for the question regarding
      "Analyze Command", version 7.3.3.6

      You Asked
Hi,

I'm not really understand what is the usage of analyze command
i.e.
compute statistic, estimate statistic and validate structure;
and when is the best time to use/run it.

I'd like to know what is the impact if I choose validate
structure ?

Thanks

      and we said...
You would use the analyze command to compute or estimate
statistics when you are using the cost based optimizer (CBO).
If you are not using the CBO but rely on the RBO -- don't
analyze est|compute stats at all -- it would not be necessary.

So, unless you are currently doing it -- don't just "do it".  Do
it in a test instance and fully test your application to see the
impact.  Your query plans will change -- sometimes for the
better and sometimes not.  You spent some amount of time tuning
your queries in your existing system and by using the CBO after
an analyze -- it will change the plans and necessitate some
amount of re-tuning.

As for the validate structure, it is typically used only if
there is a suspicion that there is a problem.  I would say most
people do not use it on a regular basis -- only when they
suspect an issue.  The side effects of using it are:

o it will fail with

ERROR at line 1:
ORA-00054: resource busy and acquire with NOWAIT specified

if there are active transactions

o it will block inserts/updates/deletes to the table while it is
processing.
Review & Followup

      Rating: 3
Meaning of
      Suspicion/Problem  March 17, 2002
Reviewer:
      Tony  from Australia
Hi Tom,

Could you please elaborate the meaning of suspicion/problem. The reason I want
to know is:
I'm planning to convert a huge non-partitioned table to a partitioned table, and
in order to have a fall back arrangement, I'm going to take the export of the
existing table. What precautions should I take (apart from space)? so in case I
may end up in import, I should not have any problem. Is it good to run validate
structure before export???
Regards,

          Followup:

Well, #1 and first and foremost is

DO THIS ON A TEST SYSTEM.  It would be a really truly bbbbaaaaaddd idea to walk
into your production system and take a non-partitioned table and partition it
and "see what happens" (i'll tell you what will happen, you'll mess everything
up and be really mad at us cause it got messed up).

As with any "major" change, if you don't test it on a test system, under load,
with the real application, to see how it behaves -- it'll almost certainly
behave differently then you expect.

Ok, so lets say you have this test system (cause if you do it on production
without testing, you are on your own).  Then you need not back it up via
export/import -- you'll have the data in the database.  You will

0) disable your application
1) create a new table "partitioned" as select * from old_table 2) index new
table
3) add constraints to new table
4) add triggers, whatever remains to new table
5) drop old table
6) rename new table to old table
7) enable your application
8) test it

(note in 9i, steps 0 and 7 may be skipped with online redefs, none of the others
can be!)

If you don't like what you have, do steps 0-8 again but create a non-partitioned
table instead of partitioned.

Now, when you goto production, which is RUNNING IN ARCHIVELOG MODE (else its
just not production right), you might want to take a backup (hot) sometime
shortly before you do this operation, just as a precaution but thats it.  If you
need to recover, you have your backups.

      Rating:
      3
Thanks  March 18, 2002
Reviewer:
      Tony  from Australia
Hi Tom,
Thanks for your prompt reply.
The reason I asked for validate structure or any problem may come in Production
not because of skipping testing of the whole partitioning process but just in
case any thing may be specific to Production which can not be tested in Test
environment like, block corruption (You can through light here). The idea is to
take maximum possible precautions before even starting the process.
The export was considered because it is a data warehouse system, load is huge
but once a month, and DB running is not running in archivelog mode.
Regarding 2, as the indexes are also huge, I am thinking of creating indexes on
new table after dropping the old table as in this way I can reclaim the index
space from old table and use in new table (index space). Of course, the index
creation script will be ready before dropping the old table.

          Followup:

Well, instead of taking an EXPORT (if the data is huge, it is really big -- an
export import would take a very very very long time).

Transport the tablespaces containing the table and indexes off to tape.  In the
event you want to go back, you can snap these tablespaces back on the database
just like restoring a datafile.  No reload necessary.

      Rating: 4
Analyze
      table  March 18, 2002
Reviewer:  Irfan
       from Kuwait
Hi Tom,
We use Analyze table t compute statistics, occassionally to check and calculate
the actual size of table. Does this has any side effects?

          Followup:

Sure, any query that accesses that table will use the CBO (cost based
optimizer).  So, if you do not do this analyze frequently enough and the stats
get stale -- bad plans will result.  If you do not do this analyze to all tables
you query, bad plans will result.

      Rating: 2
Analyze stats
      not being imported  April 22, 2003
Reviewer:
      Reader  from USA
When I use TTS everything works wonderfully, except, that some tables have the
analyze stats imported and some do not last_analyzed column in dba_tables is
null. I think all the parameters are correct, and are as follows :
For Export:
transport_tablespace=y
tablespaces=x,y
triggers=y
constraints=y
grants=y
statistics=compute
file=expdat.dmp

For Import:
TRANSPORT_TABLESPACE=Y
DATAFILES= ...
TABLESPACES=x,y
GRANTS=y
fromuser=noname
touser=noname
constraints=n
analyze=y
FILE=expdat.dmp

Please help,
thank you.

      Rating:
      5
Wor  March 02, 2004
Reviewer:  Wor
       from USA
Hi Tom,
We just found that in development database none of the tables are analyzed using
this query.

SELECT * FROM all_tables WHERE last_analyzed IS NULL AND owner = 'LIS';

Whereas in QA and production all tables are analyzed.

So going ahead I want to sync all 3 environments with all tables analyzed.
On this I have following questions.

1. What command I use for this ? Do I need to 'DELETE' and re-create the
statistics ?

analyze table <table name> compute statistics
   for table
   for all indexes
   for all indexed columns
OR
analyze table <table name> compute statistics
   for table
   for all indexes
OR
analyze table <table name> compute statistics

2. How do I know which commands were used in QA and production when they were
analyzed ?

3. Before I analyze do I need to take precautions to avoid unwanted corrupting
the database ? e.g increasing tablespace
I am new to oracle. please tell me even the smallest precaution I need to take.

4. I read that too many times analyzing will degrade the performance ? Is that
true ? what if I delete the statistics each time I run analyze will it still
degrade?

Thanks

          Followup:

1) ask the dba's what they will be using for real.  you want them to be the same
after all.

2) ask the dba's (last_analyzed will tell you when)

3) no, no corruption.  you might need more temp, but it'll tell you if it does.

4) not analyzing degrades performance too.  If you are going to be using
analyzed objects in production, you better develop with them.

March 02, 2004 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

      Rating: 5
Handling
      midnight  January 12, 2004
Reviewer:  Craig
       from NW Arkansas
Tom -
You have been an huge help to us in getting our external tables working well.
However, we do have (at least) one more question.

The source system is handing us a date/time field in 24 hour format.  But when
the source is midnight, it is passed to us as 24:00:00, rather than 00:00:00.
Is there any mask we can apply to handle this numbering convention?

Thanks again!!

          Followup:

map it as a string in the external table definition and then use SQL to convert
it:

decode( substr( dt_field,n,2),
        24, to_date( ....., substr( dt_field, 1, n-1 ) || '00' ||
                 substr( dt_field,n+2 ),
        to_date( ....., dt_field )

(where n is the offset of the hour field

      Rating: 4
Date
      format  January 13, 2004
Reviewer:
      A reader  from Virginia USA
I initially tried to define the date columns as DATE, however I was having
difficulties loading the data; some of the date columns, in the file, where
blank.  I used your suggestion to define as varchar and use to_date.  Works
great!

I read in the Database Utilities manual that the SEQUENCE parameter can be used.
 I've tried, but failed to succesfully use this parameter. Would it be possible
for you to provide an example?

          Followup:

just use ROWNUM in the query. easier than reading the docs :)

      Rating:
      3
Nice  March 02, 2004
Reviewer:  Ram
       from Bangalore,India
Hi Tom,
When we load data from flat file into an external table and
if the flat file has some null values,How to discard the
null values?
Please do reply.

          Followup:

well, you don't load a flat file into an external table -- you use an external
table to query a flat file.

and all you would need to do to skip records where some fields are null would
be:

select * from external_table where field1 is not null and field2 is not null and
so on...

just use SQL to filter the data.

      Rating: 4
Thanks
        March 02, 2004
Reviewer:  Ram  from
      Bangalore,India
Dear Tom,
Thanks for your response.If the flat file resides in a remote database,How to
query that?Is it possible to use a
dbLink that can link us to the remote flat file?
Please do reply.
Thanks in advance.

          Followup:

a dblink can access a remote table, the remote table can be an external table so
that is how you could do that, you would create an external table in the remote
database.

      Rating: 5
External Tables
      Nulls in WHERE clause  March 02, 2004
Reviewer:
      Richard Smith  from Bensenville, IL USA
FYI, when querying an external table, if any column referenced in the WHERE
clause has null values, those records will be implicitly filtered from the
result set. To test it, for a know set of data having some null values in column
B for example:
SELECT * FROM EXT_TABLE WHERE B IS NULL;
Will return no rows even if half the rows have values in column B and half are
nulls.

          Followup:

umm, no:

ops$tkyte@ORA9IR2.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@ORA9IR2.US.ORACLE.COM> host flat scott/tiger emp > /tmp/emp.dat

ops$tkyte@ORA9IR2.US.ORACLE.COM> select * from external_table where comm is
null;

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
------
      7369 SMITH      CLERK           7902 17-DEC-80        800
20
      7566 JONES      MANAGER         7839 02-APR-81       2975
20
      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
      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

10 rows selected.

they work normally with nulls

March 02, 2004 -- 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)

      Rating: 5
export for
      backup purpose  March 02, 2004
Reviewer:
      Eugene  from Seattle
Hi Tom,
Our DBA is out for a week. Our prod system just came to almost complete stop. I
was the one to whom everyone turned to. I noted that export was running at the
same time. I killed it and everything went back to normal. After that I asked my
manager why we export most (if not all) tables during the day. The answer was:
we use it as a backup during the day. Restore from export, he said, is way
faster than from any other means.
I always thought that export/import was designed to move data from A to B.
Export will not move data that is currently accessed.
Am I right? What would your respoce be to my manager?

Thanks,

          Followup:

export is not a good backup tool, no.  It'll be a point in time image of some
data (eg: if you export a table at 2pm, it'll be 2pm data forever, never 1pm
data, never 3pm data).

I cannot imagine exporting during the day, no.  If you know how to recover,
recovery from a hot backup from last night isn't going to be terribly difficult
AND you can recover all of the data (eg: if you need to recover at 3pm, and you
have a 2pm export -- to bad, 2-3pm just "never happened".... now if you use
proper backups on the other hand...)

      Rating: 4
export for
      backup purpose   March 02, 2004
Reviewer:
      Eugeen  from Seattle
Tom,
Thanks alot for your input.

Eugene

      Rating:
      5
  March 02, 2004
Reviewer:
      A reader
Tom, I was also expecting to read "Time for a new manager" :-)

March 02, 2004 -- 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.

      Rating: 4
dump size &
      set_int_param_in_session  March 02, 2004
Reviewer:
      Vlado  from Norristown, PA
My max_dump_file_size is set to 5M in spfile. I'm trying to use the
sys.dbms.set_int_param_in_session procedure to change the file size and then
trace the session:

exec
sys.dbms_system.set_int_param_in_session(44,17313,'max_dump_file_size',2147483647
)

exec dbms_system.set_ev(44,17313,10046,8,'');

However, when I check my trace file it still has a 5M limit -> *** DUMP FILE
SIZE IS LIMITED TO 5242880 BYTES ***

I'm running 9iR2 on AIX...

Does this mean that the setting in the spfile is the upper limit and that the
only way to create a bigger trace file is to issue an ALTER SYSTEM command?

          Followup:

it does look like that setting is being ignored in the other session yes.  I can
reproduce your findings easily.  I don't see any bugs filed against it (so you
might consider filing one) but using alter system seems to be the only
workaround.

March 03, 2004 Joy -- Thanks for the question regarding
      "Temporary tablespaces", version 7.3.4.0.0

      You Asked
What am I missing if I do not use the 'TEMPORARY' keyword when
creating a temporary tablespace?

I have a situation where a tablespace has been created without
using the keyword. Users have been created with this tablespace
as their temporary tablespace and things seem to be working
fine.  Temporary segments are being created in it and seem to be
removed when no longer needed.

My question is, if all this happens as required, why does one
need to specify TEMPORARY at creation?

      and we said...
Right now you are using a PERMANENT tablespace for temporary
use.  When you do this, we must make expensive, serialized
recursive SQL calls to allocate extents in this tablespace and
to free them when we are done.  This is alot of housekeeping
that on an active system will be extremely noticable.  It can
literally kill performance.

By using a true TEMPORARY tablespace -- we will allocate an
extent ONCE and then keep it.  These extents will be managed not
via the data dictionary but in memory.  This means the expensive
recursive sql we do to allocate an extent and then free it is
removed.  This can dramatically increase performance if you use
lots of temp space frequently.  In this case, you will see your
temporary tablespace start at 0 bytes allocated upon startup and
then it will gradually "fill".  You will never see an extent
freed in this tablespace until you shutdown (or alter the
tablespace in some way).  This is normal and is OK -- we are not
losing extents, we are just managing them internally -- in
memory, instead of in the data dictionary.
Review &
      Followup

      Rating: 5
That's why my
      temporary tablespace is always full....  September 06, 2001

Reviewer:  Johanna Ivy  from Denver, CO USA

Thanks for the explanation of how temporary tablespaces are managed.  We just
allocated another 1 Gig on our temporary tablespace, due to a few ORA-1652
errors.  I noticed in my DBA studio view, that as soon as we ran the script that
was using up the tablespace it took up 200 Mb of the new space, but never gave
it back.  I was beginning to wonder if the next time we ran it, we would run out
of space again.  Good to know it wasn't supposed to shrink.

      Rating:
      4
  April 24, 2002
Reviewer:  kiro
       from Bulgaria
Tom,
You Said:
"You will never see an extent
freed in this tablespace until you shutdown (or alter the
tablespace in some way)".
Is it means that if I have in such tablespace 1G used space, new transaction
that need 'temp' space will allocate new extend or will reuse existing?
Please explain.
Thanks

          Followup:

reuse existing. As I said:

"This is normal and is OK -- we
are not losing extents, we are just managing them internally -- in memory,
instead of in the data dictionary. "

      Rating: 4
alter
      tablespace TEMP temporary;  August 11, 2002

Reviewer:  Mike C  from NY, NY
I have the similar situation. I have the tablepsace TEMP, in fact it was created
as permanent tablespace. I want to convert it to the temporary tablespace:

alter tablespace temp temporary
/

I do not know if there are some of the side affects of converting , if there
were some objects had been created in TEMP.

Thanks

          Followup:

that is a sort of temporary tablespace.

Better to

drop tablespace temp
create TEMPORARY TABLESPACE TEMP tempfile .....

      Rating: 4
raises a
      q  August 11, 2002
Reviewer:  Meyer Tollen
       from Baltimore, MD
Was there ever a reason to use a non-temp temp instead of a real temp? (Maybe in
7.3.4 HPux?).

We have a production system (not ours) that vendor runs on 7.3 with a
"permanent" temp...

Thanks.

          Followup:

whoops -- sorry, create temporary tablespace is "new" with 8.0 -- doesn't apply
in this particular.

      Rating:
      4
Reader  August 11, 2002
Reviewer:
      A reader
Tom,

What is the advantage of
"create temporary tablespace ..."
vs.
" create tablespace TEMP ... temporary ...."
besides the datafiles are not backed up.

Thanks

          Followup:

bingo.

also, on OS's that support it -- the tempfiles are created "sparse", meaning
they create instantly and will actually allocate space only as needed (some view
this as a negative benefit, depends on your perspective)

      Rating: 4
Unable to
      allocate extent size x on Temp Tablespace  September 10,
      2002
Reviewer:  SK  from NJ
Hi Tom,

Is it possible to get an error like the above when temp tablespace is only 10%
full (OEM View). I am facing this error in my large updates/deletes. Under what
conditions this is possible?

Thanks

          Followup:

sure, depends on HOW the temp tablespace was created.

You should be using a true temporary tablespace "create temporary tablespace"
with tempfiles, not datafiles, in a locally managed tablespace.

      Rating: 3
Follow
      up  September 10, 2002
Reviewer:  SK
       from NJ
I am using Temp tablespace (raw) as I am running Oracle 9iR2 RAC. Will this
happen here as well?

Thanks

          Followup:

will what happen here, there were many tiny discussions going on

In general I can say "yes" cause RAC won't fundementally change the way temp
works really.

      Rating: 5
SMON process
      occupying 2.2 GB of TEMP  September 10, 2002

Reviewer:  Sree  from MA USA
I was worried to see a SMON process occupying about 2.2 GB of TEMP space and not
getting freedup. (Oracle 8.1.7.2.0)

select l.sid
   ,l.type "Tran Type"
   ,o.object_name "Object Name"
   ,decode(l.lmode,
           0,'NONE',
           1,'NULL',
           2,'Row-SELECT (SS)',
           3,'Row-X (SX)',
           4,'SHARE',
           5,'SELECT/Row-X (SSX)',
           6,'EXCLUSIVE') "Mode"
   ,l.request "Request"
   ,s.type "Type"
from dba_objects o
   ,v$session s
   ,v$lock l
where l.id1=o.object_id and
   s.sid=l.sid and
   l.type != 'MR'
 order by l.sid

SID    Tran Type  Object Name    Mode      Request   Type
------------------------------------------------------------
8    TS         I_OBJ#    Row-X (SX) 0      BACKGROUND

SID 8 belongs to SMON process.

A query for segments in tablespace TEMP gives following:
Schema    TableSpace  Type    Allocated(m)    Extents
--------------------------------------------------------
SYS    TEMP        TEMPORARY    2283.125    562

Is this okay too?

Thanks in advance

          Followup:

Umm, how did you correlate the first query with the second? and what is the
second query?

I mean, all I see is

a) smon is doing some cleanup on obj$
b) you have 2.2 gig allocated to temp

what do you see?

      Rating: 5
SMON process
      occupying 2.2 GB of TEMP   September 11, 2002

Reviewer:  Sree  from MA USA
Tom, I just wanted to show what is occupied in TEMP tablespace by second query.
There was no correlation.
(select owner,tablespace_name,SEGMENT_TYPE,
BYTES/1024/1024,EXTENTS
from dba_segments
where tablespace_name='TEMP')

Actually a "select * from V$SORT_SEGMENT" proved that all the extents showing as
used are in fact free and available for use.

Thank you very much for help.

          Followup:

right - so SMON never was occupying 2.2 gig of temp ;)

      Rating: 5
how to see temp
      tablespace extent freed  September 18, 2002

Reviewer:  tammy pickett  from USA
Tom: You wrote this: You will never see an extent freed in this tablespace until
you shutdown (or alter the tablespace in some way).

I did see my temp tablespace is full and what command I need to use "alter
tablespace temp..." to change the 75GB temp tablespace not full from dbartision
view?? size: 76,000MB, used: 76,000MB

          Followup:

but it is not full, it is just allocated.  It is just waiting to be used.

query the v$sort_segment and v$sort_usage to see what is really up with that
temp tablespace.

      Rating: 5
Difference
      between creating temporary, and specifying temporary
      keyword  January 13, 2003
Reviewer:  A
      Reader  from UK
Tom,

Can you explain please what the difference between:

CREATE TEMPORARY TABLESPACE ......

and

CREATE TABLESPACE ........ TEMPORARY

is ?  Does it make any difference ?

Regards,

Paul

          Followup:

yes, one uses tempfiles (CREATE TEMPORARY...) and one uses datafiles (create
tablespace).

use create temporary tablespace -- tempfiles are never backed up, easily
recreated, not logged -- they are special and treated differently than datafiles
are.

      Rating: 4
dba_segments
      equivalent for temp tablespace ?  January 13, 2003

Reviewer:  Sameer  from NJ
If I create temporary tablespace temp, I cannot see segments allocated in it
using select * from dba_segments where tablespace_name = 'TEMP' or segment_type
= 'TEMPORARY'. Is there another view in the dictionary where temp segments are
stored ?

Even segfile# from v$sort_usage doesn't match up with file_id from
dba_data_files or dba_temp_files.

How do I know what temp segment is being used by any user process if I use
create temporary tablespace type of ts ?

          Followup:

v$sort_usage and v$sort_segment are where they are stored.

the file id is off by DB_FILES -- so, if you

select segfile# - db_files
  from v$sort_usage, (select value db_files from v$parameter where name =
'db_files' )
/

you'll get the file id you can use against dba_temp_files....

(oh, you'll be looking for "extents" -- not segments, one big old temp segment
that the system manages and doles out extents of as needed.)

      Rating: 3
Who/what is
      consuming my temp tablespace  January 27, 2003

Reviewer:  MSaeed  from Canada
Hi Tom:

Somehow our datafile belonging to a tablespace defined as temporary tablespace
has grown to 10gb.

How can I find out what caused this and how will I be able to reclaim this
space.

Would creating a pemanent t/s and assigning it to the users as their temporary
tablespace help ?

Thanks a lot in advance for your time and help.

Moh'd

          Followup:

Somehow - well, you let it.  You configured it to be permitted to grow that
large and someone did something "big".

So, that is the how.

Now, temp stays allocated -- we just manage it and dole it out as needed.  You
can "resize" depending on how you created it.

Insufficient details to really tell you what to do however - how was it created,
it is true tempfiles or datafiles, is it a DMT with thousands of extents to
worry about or a LMT where this won't make a difference.  And so on.

One way -- create a new one, alter users to this new one, drop old one.

      Rating:
      4
V$sort_usage  April 03, 2003
Reviewer:
      kumar  from India
Tom,

When i query the v$sort_usage, i get the USER column showing the name of the
user who is executing this query  rather than the user who is actually using the
temp space for running some other queries. Can you explain why this is happening
?

Thanks

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:374218170986#414663428077

      Rating: 5
temporary
      tablespace  June 11, 2003
Reviewer:  Nasir
      Sarwar
Excellent Tom.

I have some confusion about TYPE temporary tablespace in Oracle 8i and 9i.
1. If a user's TYPE temporary tablespace is changed to TYPE permanent
tablespace, then what tablespace that user is using for sorting queries which
cannot be carried  out wholly in memory?
2. Now if a user's TYPE temporary tablespace is dropped then what tablespace
user is using for sorting queries which cannot be carried  out wholly in memory?

Can you please answer the above questions

          Followup:

1) you can use a permanent or temporary tablespace for sort.  we don't care.
there are three types of tablespace you can use:

create temporary tablespace TEMP tempfile ....;  <<== this is the one you should
use

create tablespace temp datafile .... TEMPORARY;  <<== this is 2cnd "best".  it
uses datafiles but disallows the creation of permanent objects in it, meaning we
can manage the space much more efficiently

create tablespace temp datafile .... PERMANENT;  <<=== this stinks, but works.

2) no where, you get an error.

      Rating: 4
sort
      segment  August 21, 2003
Reviewer:
      A reader
Tom,

I have a basic doubt,
sort segment is created when instance is started and temporary segments are
created when a sort operation are active. - right?

then when is sort segment being used?

sorry for my ignorance..

          Followup:

there is generally a single sort segment, in which there are many extents and
when we sort, you get one of the extents for your user and when you are done you
give it back.

you do not create new temp segments -- you use extents in a single temp segment.

      Rating: 5
Great, thanks
      for making me understand in few lines  August 22, 2003

Reviewer:  A reader

      Rating: 4
what is this
      temporary segments ?  December 26, 2003
Reviewer:
      Jagjeet Singh  from INDIA
#### 1 ####

Select segment_name,segment_type,bytes/1024 from user_segments

SEGMENT_NAME                   SEGMENT_TYPE       BYTES/1024
------------------------------ ------------------ ----------
JC                             TABLE                     128
4.33                           TEMPORARY                3072
4.1569                         TEMPORARY                  64
4.1601                         TEMPORARY                  64

SQL> set lines 72

SQL> Select * from user_tablespaces where tablespace_name = 'TEST';

TABLESPACE_NAME                BLOCK_SIZE INITIAL_EXTENT NEXT_EXTENT
------------------------------ ---------- -------------- -----------
MIN_EXTENTS MAX_EXTENTS PCT_INCREASE MIN_EXTLEN STATUS    CONTENTS
----------- ----------- ------------ ---------- --------- ---------
LOGGING   FOR EXTENT_MAN ALLOCATIO SEGMEN
--------- --- ---------- --------- ------
TEST                                 2048          65536
          1  2147483645                   65536 ONLINE    PERMANENT
LOGGING   NO  LOCAL      SYSTEM    MANUAL

          Followup:

i don't really know what or why I'm looking at here?

I can see there are some temp segments (you are using old fashioned dictionary
managed tablespaces and permanent tablespaces for TEMP or you are runing some
creates in other sessions and they are creating temporary segments that will
eventually be converted.

I see a query getting info about the tablespace TEST (and no idea why I see that
-- whats the relationship here....)

      Rating:
      4
Re:  December 29, 2003
Reviewer:  JS
       from india
Sir,
Test is my default tablespace.
I am showing you that this is a LMT tablespace.

          Followup:

so, that says nothing about the temporary segments.

      Rating: 5
benchmark
      between Type-2 and Type-3 tablespace  January 08, 2004

Reviewer:  Sami  from NJ,USA
Dear Tom,

Thanks for all your kind support.

<AskTom>
1) you can use a permanent or temporary tablespace for sort.  we don't care.
there are three types of tablespace you can use:

create temporary tablespace TEMP tempfile ....;  <<== this is the one you should
use

create tablespace temp datafile .... TEMPORARY;  <<== this is 2cnd "best".  it
uses datafiles but disallows the creation of permanent objects in it, meaning we

can manage the space much more efficiently

create tablespace temp datafile .... PERMANENT;  <<=== this stinks, but works.
</AskTom>

Our DB has Type-3 TEMPORARY tablespace (using datafile).
We can not go for Type-1 because still we are in 8.1.7.3.
The best choice for us would be moving from Type-3 to Type-2.

Do you have any simple program to do the benchmark between Type-2 and Type-3? So
that I can easily convince folks.

Thanks Tom,
Sami

          Followup:

you too can use type1

  1* create temporary tablespace ttt tempfile '/tmp/ttt.dbf' size 5m
ops$tkyte@ORA817DEV> /

Tablespace created.

there is not much difference between 2 and 3 in terms of performance, 2 just
doesn't let you actually create permanent objects in there (think of it as an
edit).

HOWEVER, hey -- unless you've identified this as being "a problem", best to
leave well enough alone sometimes.  Especially in an unsupported database
release.  You don't really want to change a thing right now.

      Rating: 4
Memory
      Exhausted  February 05, 2004
Reviewer:
      A reader
Hi, TOm,

We have a process(complicated involved) which is running fine before(after we
tunned it, normally process only several hunds of records)), the process takes
about 10 to 15 minutes to finish. But just started from yestoday, the temp
spaces getting increased gradualy with the process going, and eventually, the
process crashed(memory full, temp spaces growing to 10G and we stop it). We
didn't change any part of the process(package and procedure). So, do you have
any threads on your mind regarding what could be the reasons?

THanks a million

          Followup:

data could be radically different, causing different results.

could be a change in a query plan.

You would expect it to be "a query" so when you see temp growing, see what query
it is running and take a look at that.

      Rating:
      4
How  February 06, 2004
Reviewer:
      A reader
Yes,
We identified it is a query, but it is in the form of internal language:
SELECT bolco#, file#.....
...

How we could interpret it to a meanful format?

THanks,

          Followup:

hows about you show the entire thing?

      Rating: 4
Enqueue- SS
      Exclusive  March 01, 2004
Reviewer:  Alvin
       from Philippines
My users are complaining that their queries are super-slow. I've traced some
queries and most of them have p1=1397948422...  I have used the enqueue_decode
function in your book and i got ' SS Exclusive ' locks.

As per oracle documentation SS means sort segment. Is oracle not re-releasing
the temp segments after use ??

WAIT #2: nam='enqueue' ela= 308 p1=1397948422 p2=3 p3=1
WAIT #2: nam='enqueue' ela= 310 p1=1397948422 p2=3 p3=1
WAIT #2: nam='enqueue' ela= 307 p1=1397948422 p2=3 p3=1
WAIT #2: nam='enqueue' ela= 308 p1=1397948422 p2=3 p3=1
WAIT #2: nam='enqueue' ela= 308 p1=1397948422 p2=3 p3=1
WAIT #2: nam='enqueue' ela= 307 p1=1397948422 p2=3 p3=1
WAIT #2: nam='enqueue' ela= 309 p1=1397948422 p2=3 p3=1
WAIT #2: nam='enqueue' ela= 308 p1=1397948422 p2=3 p3=1
WAIT #2: nam='enqueue' ela= 308 p1=1397948422 p2=3 p3=1
WAIT #2: nam='enqueue' ela= 308 p1=1397948422 p2=3 p3=1
WAIT #2: nam='enqueue' ela= 308 p1=1397948422 p2=3 p3=1
WAIT #2: nam='enqueue' ela= 308 p1=1397948422 p2=3 p3=1

  1  select p1,state, sum(seconds_in_wait) ,count(*), enqueue_decode(p1)
enqueue_dec
  2  from v$session_wait
  3* group by p1,state
17:00:22 rtbe@RTBE.SUNFIRE01>
17:00:22 rtbe@RTBE.SUNFIRE01> /

        P1 STATE               SUM(SECONDS_IN_WAIT)   COUNT(*) ENQUEUE_DEC
---------- ------------------- -------------------- ----------
------------------------------
         0 WAITING                            20917          1    No lock
         3 WAITING                                0          1    Row-Exclusive
        81 WAITING                                0          1
       300 WAITING                              268          2
      4288 WAITING                                1          1
      6000 WAITING                                0          4
     24076 WAITING                                1          1
     28130 WAITING                              137          1
    180000 WAITING                             5156          1  
 675562835 WAITING                         18900857        358 (D
1397948422 WAITING                           439884         37 SS Exclusive
1413697536 WAITED SHORT TIME                      0          1 TC
1413697536 WAITING                             5777          2 TC
1650815232 WAITING                            10230          2 be
4039132012 WAITED SHORT TIME                      0          1 

  1  select sid, ROW_WAIT_OBJ#  ,ROW_WAIT_FILE#,ROW_WAIT_BLOCK#, ROW_WAIT_ROW#
  2* from v$session where status='ACTIVE'
17:06:07 rtbe@RTBE.SUNFIRE01>
17:06:07 rtbe@RTBE.SUNFIRE01> /

       SID ROW_WAIT_OBJ# ROW_WAIT_FILE# ROW_WAIT_BLOCK# ROW_WAIT_ROW#
---------- ------------- -------------- --------------- -------------
         1            -1              0               0             0
         2            -1              0               0             0
         3            -1              0               0             0
         4            -1              0               0             0
         5            -1              0               0             0
         6            -1              0               0             0
        11            -1              0               0             0
        16            -1              0               0             0
        40            -1              0               0             0
        73            -1              0               0             0
        80            -1              0               0             0
        91            -1              0               0             0
       113            -1              0               0             0
       129            -1              0               0             0
       140            -1              0               0             0
       173            -1              0               0             0
       184            -1              0               0             0
       192            -1              0               0             0
       194            -1              0               0             0
       208            -1              0               0             0
       218            -1              0               0             0
       221            -1              0               0             0
       232            -1              0               0             0
       243            -1              0               0             0
       245            -1              0               0             0
       248            -1              0               0             0
       254            -1              0               0             0
       259            -1              0               0             0
       261            -1              0               0             0
       262            -1              0               0             0
       270            -1              0               0             0
       274            -1              0               0             0
       304            -1              0               0             0
       305            -1              0               0             0
       306            -1              0               0             0
       308            -1              0               0             0
       315            -1              0               0             0
       322            -1              0               0             0
       339            -1              0               0             0
       340            -1              0               0             0
       349            -1              0               0             0
       356            -1              0               0             0
       371            -1              0               0             0
       376            -1              0               0             0
       377            -1              0               0             0
       399            -1              0               0             0
       404            -1              0               0             0
       408            -1              0               0             0
       413            -1              0               0             0
       414            -1              0               0             0
       420            -1              0               0             0

51 rows selected.

          Followup:

what version are you using and what type of tablespace is your temporary
tablespace (is it a permanent in disguise as a temporary or a true temporary
with locally managed extents - or what)

no, it is not due to "not releasing space" -- your users would get "unable to
allocate" immediately if it were.

      Rating: 4
SS
      Exclusive...  March 01, 2004
Reviewer:
      Alvin  from Philippines
Our db is 8.1.7.0.0 running on a Solaris 5.8 OS. Our front end is developed in
Java, i extracted the sql via TOAD and ran it on sqlplus. The results and waits
are the same.

Our Temp tablespace is a DMT. I have just bounced the DB 5 days ago.

Inserts and Updates doesnt have problems some queries are not affected.

I was planning to post the statspack report here. Unfortunately my perfstat
session suffers from the same wait event. I traced my perfstat session and the
wait event is as follows.

Node name:      sunfire01
Release:        5.8
Version:        Generic_108528-27
Machine:        sun4u
Instance name: rtbe

*** SESSION ID:(278.54208) 2004-03-02 02:23:57.819
WAIT #27: nam='enqueue' ela= 307 p1=1397948422 p2=3 p3=1
WAIT #27: nam='enqueue' ela= 307 p1=1397948422 p2=3 p3=1
WAIT #27: nam='enqueue' ela= 308 p1=1397948422 p2=3 p3=1
WAIT #27: nam='enqueue' ela= 307 p1=1397948422 p2=3 p3=1
WAIT #27: nam='enqueue' ela= 307 p1=1397948422 p2=3 p3=1
WAIT #27: nam='enqueue' ela= 307 p1=1397948422 p2=3 p3=1

I'm boggled. I don't know where to look anymore.

I do plan to

1. Create a LMT Temp tempfile
2. point the users to temp_lmt
3. Bounce the db
4. Drop the tablespace temp DMT
5. Remove the OS files of Temp DMT.

I will be looking at the patch set (ours is yet to be patch) and check whether
its a bug. Our temp file has grown from 8gig -35 gig for the past week.

          Followup:

your DMT -- is it temporary or permanent.

your DMT -- what is the extent size.

and what is your sort area_size/sort area retained set to....

what is the number of sorts and sorts to disk....

sounds like

your dmt has really small extents, you sort alot, you sort to disk alot and you
are lining up doing heavy duty space allocation

all of which would be alleviated by

o upping sort area size/sort area retained if set small
o using a CREATE TEMPORARY tablespace with tempfiles not datafiles (makes it
LMT)
o using a larger extent size in the temporary tablespaces (to avoid massive
space mgmt requests)

      Rating: 4
Bdump trace
      logs.  March 01, 2004
Reviewer:  Alvin
       from Philippines
By the way, i did check the oracle logs found in the bdump directory. Nothing
unusual there also.

      Rating: 4
I killed the
      sessions in wait.  March 02, 2004
Reviewer:
      Alvin  from Philippines
Your right... the extents are 64ks and the pctincrease was set 50% thats why it
grew from 8g - 34g in 1 weeks time. Thanks a bunch !

TABLESPACE_NAME                 INITIAL_EXTENT     NEXT_EXTENT     MIN_EXTENTS
  MAX_EXTENTS    PCT_INCREASE
------------------------------ --------------- --------------- ---------------
--------------- -----
     MIN_EXTLEN STATUS    CONTENTS  LOGGING   EXTENT_MAN ALLOCATIO PLU
--------------- --------- --------- --------- ---------- --------- ---
TEMP                                     65536           65536               1
                            0
          65536 ONLINE    TEMPORARY LOGGING   DICTIONARY USER      NO

TEMP_LMT                               5242880         5242880               1
                            0
        5242880 ONLINE    TEMPORARY NOLOGGING LOCAL      UNIFORM   NO

I've another question about nologging, standby databases and Temp LMT.

Last year (Sept 18) i accidentally issued a nologging CTAS but i did cancel it
before it finished, this invalidated one tablespace (tblspace where i issued the
CTAS to reside in ) in my standby db coz when i "opened" and tried to query any
object from that table i get an error (Sorry i didnt cut and pasted the said
error).

I thought that one the datafile of the said tablespace got corrupt but the test
shows otherwise. Anyways i recreated my standby db around Oct.

NAME                                               UNRECOVER
-------------------------------------------------- ---------
/export/spare/ora_dat/system01.dbf
/export/spare/ora_dat/tools01.dbf
/export/spare/ora_dat/rbs01.dbf
/export/spare/ora_dat/temp01.dbf
/export/spare/ora_dat/users01.dbf
/export/spare/ora_dat/indx01.dbf
/export/spare/ora_dat/drsys01.dbf
/export/spare/ora_dat/cdr_01.dbf                   18-SEP-03
/export/spare/ora_dat/cdr_02.dbf                   18-SEP-03
/export/spare/ora_dat/cdr_03.dbf                   18-SEP-03
/export/spare/ora_dat/cdr_04.dbf                   18-SEP-03
/export/spare/ora_dat/cdr_05.dbf                   18-SEP-03
/export/spare/ora_dat/cdr_06.dbf

1. Are standby DB's that sensitive to nologging ?? even when the nologging
activity didn't push thru/finished or was cancelled ??

2. Normally, When i create a tablespace i get an error in my standby DB's logs
where it looks for the datafile of the new tablespace.

I would 'begin backup' the new tablespace and copy the datafile to the standby
db's location and mount the standby db and everything would be fine.

But when i created a Temp LMT i didn't specify it to be nologging. Standby DB
didn't ask for the datafile of the new tablespace (TEMP LMT).

I'm not that concerned this time because its just the temp tablespace that i
added.

On to the question.

So far I am OK right ?? or do i need to copy the temp_lmt datafile to the
standby db w/c to me makes no sense since no logs would be applied against it.

          Followup:

1) standby's are sensitive to nologging yes since they are built based on it.
(in 9i, you can force logging, so nologging is a big "no op")

2) temporary tablespaces are not logged and not "standby'ed"

You would just create a new temp upon opening the standby, they are not
"standby'ed"

      Rating: 5
SS Exclusive no
      more ( but still monitoring )  March 02, 2004

Reviewer:  Alvin  from Philippines
I haven't restarted my instance since i created the TEMP LMT yesterday. So my
sort to disk might contain sorts from the old temp DMT as well.  I just killed
the sessions in wait and redirected all of the users temp tablespace to the LMT
one.

As queried from V$sysstat my sort statistics are as follows.

     STATISTIC# NAME
CLASS           VALUE
--------------- --------------------------------------------------
--------------- ---------------
            188 sorts (memory)
64        20780617
            189 sorts (disk)
64             123
            190 sorts (rows)
64       243940718

3 rows selected.

Elapsed: 00:00:00.41
sql> show parameter sort

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
nls_sort                             string
sort_area_retained_size              integer 2048000
sort_area_size                       integer 2048000
sort_multiblock_read_count           integer 2

Do i have to...

1. Increase my
Sort_area_size to 10mb
Sort_area_retained_size =5 mb
sort_multiblock_read_count = 640 (8kb block size)

because my TEMP LMT's extent sizes are in 5 mb chunks ? Basically aligning
everything to 5mb

2. Whats a good ratio for the sorting parameters with respect to extent size of
uniform temp extents ?

          Followup:

5m is overboard.  1 or 2meg (since you have 2m sort area size) would be more
reasonable (or at least something divisible by 2)....

You want the lmt extents to fit nicely in there, 1m is good and positions you
well for 9i with pga_aggregate_target

March 03, 2004 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;

      Rating: 2
How to execute
      a job after 1 day from sysdate  January 13, 2004

Reviewer:  Robert  from USA
I am new to job scheduling and almost Oracle also. How to schedule a job to run
after 24 hours from current sysdate including hours,minutes and seconds.

          Followup:

exec dbms_job.submit( :job, 'thing;', sysdate+1 );

      Rating: 5
how to remove
      jobs  January 18, 2004
Reviewer:
      A reader  from san francisco
Hi Tom:
I added a job, now i wanted to remove the job.
i did this
DBMS_JOB.REMOVE(2);

but it didnot help. the job is still running. any idea what is happening?

          Followup:

you cannot remove a running job.

what you can do is

a) alter system set job_queue_processes=0;
b) alter system kill session 'x,y';  where x,y is the sid/serial# of the running
job
c) dbms_job.remove, commit;
d) alter system set job_queue_processes=N; where N = number of job queues you
want

that always works for me.

      Rating: 3
No need to set
      job_queue_process=0 to kill a running job  January 18, 2004

Reviewer:  Jurij Modic  from Slovenia
Tom, just want to throw my 0.02$ on the subject of killing a running job.
Setting job_queue_process to 0 just to kill the running job could be a bit of
overkill - we wouldn't want to disturb the execution of other jobs that might be
due to start just in next few seconds.

More elegant way is:
a) dbms_job.broken(:runnig_job,'TRUE'); commit;
b) alter system kill session 'x,y';
c) dbms_job.remove, commit;

          Followup:

well. given that the whole of a,b,c,d should take no more than 15 seconds....
but yet, that is a viable path as well (your a,b,c).

      Rating:
      5
  January 19, 2004
Reviewer:
      A reader
Tom,

If 'ss' are for seconds but what are these five 'sssss'

scott@TEST> select to_char(sysdate,'sssss'),sysdate from dual;

TO_CH SYSDATE
----- -------------------
28939 01/19/2004 08:02:19

Thanks.
Greg.

          Followup:

seconds since midnight.

      Rating:
      5
dbms_jobs  February 12, 2004
Reviewer:
      S.Darshan  from Singapore
Every day my job is running at particular time at night. It will run every day
one time. But last night it did n't run. No failures at all. No Error message at
alert log file. How can we find the reason? Can you pls give me some solutions?

With best regards
S.Darshan

          Followup:

so, what sort of logging do you have in your job.

what is running your job. (could be anything, dbms_job, oem, somthing else,
cron, at, whatever)

      Rating:
      5
dbms_job  February 14, 2004
Reviewer:
      S.Darshan  from Singapore
Thank you very much for your reply.
It is a oracle dbms_job.
Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production
With the Partitioning option
System name:    SunOS
Release:    5.8
Version:    Generic_108528-26
Machine:    sun4u

The follwoing are related informaiton:
SQL> select name,value from v$parameter where name  like 'job%';

NAME                                     VALUE
---------------------------------------- -------------------------
job_queue_processes                      4
job_queue_interval                       60

SQL> select job,to_char(last_date,'DD-MON-YY HH24:MI') Last_date,
  2         to_char(next_date,'DD-MON-YY HH24:MI') Next_date,
  3         broken,interval,what,failures
  4  from user_jobs;

      JOB LAST_DATE       NEXT_DATE       B INTERVAL                  WHAT

--------- --------------- --------------- - -------------------------
---------------
     1882 14-FEB-04 04:45 15-FEB-04 00:40 N trunc(sysdate)+1.027778
pkgjob.mid;

         Based on the Intervel it will run every day midnight 12.40. But last
night It did n't run.  I ran dbms_job.run manually to exec the  above job. So
that last time 04.45.

        What coluld be cause?  Can you help me?

With best regards
S.Darshan

          Followup:

anything in the alert log?
any jobs running?

too late to look now but -- did you happen to look at the failures OR broken
columns before running it (which would reset them, so too late to look see now)

      Rating: 5
a job marked as
      broken after 16 tries  March 02, 2004
Reviewer:
      A reader  from nyc, usa
hi tom,

I have a dbms job to refresh a materilized view between 2 unix boxs. In order to
cover the down time ( about 2 hours for manintenance ) of the machine where the
base table resides, what value i should set for JOB_QUEUE_INTERVAL if the
interval for executing the job is specified as 1 minute?

1) if I set JOB_QUEUE_INTERVAL to 59 sec, since JOB_QUEUE_INTERVAL <
job_exec_interval  I will get exponential backoff for every 1,2,4,8,16,32,64,128
min,
which will cover 2 hr down time.

2) if JOB_QUEUE_INTERVAL = 600 (10 minutes),
  the job will try once every 10 minutes, for up to 16 tries, so 160 min covers
2 hr down time.

which way is better?

and where can I get specific time of 16 attemps a job tried?

Thanks

steve

          Followup:

why not set the next date to 2 hours from now and leave the interval alone?  or
break the job and unbreak it after the downtime window is done?

March 03, 2004 Daniel -- Thanks for the question regarding
      "Intermedia query result order by date desc", version 9.0.1

      You Asked
Hi Tom,

I want intermedia query result sorted by date desc (last in first out)
but can not afford sorting (20 million rows web application).

table created as:

create table menus
( id   number primary key,
  recipe_xml_text clob,
  date_created date
)

intermedia index created as:

create index xml_idx on menus (recipe_xml_text)
indextype is ctxsys.context
parameters ('section group ctxsys.auto_section_group')

regular index created as:
create index date_created_idx on menus (date_created desc)

query A:
select id,date_created from menus where
contains(recipe_xml_text,'keyword within tags')>0
and date_created <= SYSDATE
and rownum < 101 order by date_created desc;

The query result set did not satisfy the last in first out by date.

query B: added first_rows hint.
select /*+ FIRST_ROWS */ id,date_created from menus where
contains(recipe_xml_text,'keyword within tags')>0
and date_created <= SYSDATE
and rownum < 101 order by date_created desc;

the matched rows came out with latest date first,but if there is no match on the
search text,
the query did not come back right away.

Both queries ended up with the same Execution Plan:
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=59 Card=1
          Bytes=210)

   1    0   COUNT (STOPKEY)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'MENUS
          ' (Cost=55 Card=1 Bytes=210)

   3    2       INDEX (RANGE SCAN) OF 'DATE_CREATED_INDEX' (NO
          N-UNIQUE) (Cost=6 Card=1)

Is there a solution for query B to come back right away if no match is found ?

I know it's difficult to combine domain index with regular index (to use xml_idx
and date_created_idx simultaneously),If only the score can be used here,is there
any
way I can manipulate the score (like put more weight on the date_created) so
that
order by score desc will generate the desired (last in first out) result ?

Thank You!

      and we said...
Well, your last query there says:

o goto the index and row by row check each row if the row contains that data
(20,000,000 times!!!)

If the "latest" rows don't have matches, this will take a long time.

o the query that uses the text index first AND THEN sorts will be very very fast
if there are a couple of hundred rows.

It is my humble opinion that if a search returns more then say 500 hits, it is a
totally useless search.  It NEEDS to be refined.  No one is EVER going to look
at more then that (goto google or anywhere, you generally get 10 hits/page, 10
pages to click thru... Have you ever gone further then that?)

In fact, goto google and search for Oracle.  They show 10 hits/page.  They
showed me 83 pages -- I am 100% sure there are more then 830 pages out there
with "oracle".  In fact on the very last page they say:

"In order to show you the most relevant results, we have omitted some entries
very similar to the 822 already displayed."

basically saying "refine your SEARCH!!!!"

What I do is this:

o limit the text search to 500 hits.
o order by WHATEVER after that.

sorting 500 rows will take fractions of a second, finding 500 rows will too.  I
then also put up "your search returned WAY too much data, here are 500 good
hits.  Please refine your criteria to get more meaningful results".

You cannot manipulate the score, the score is based on the words in the text and
the search terms.
Review & Followup

      Rating: 3
Strange
      behavior on inline join  March 01, 2004
Reviewer:
      Jimmy Lee  from Taipei, Taiwan.
Hi Tom,

Here is similiar case, yet temporary table comes some interesting result. Is
there any reason why inline join could not run as fast as PK of temporary table?
Thanks.

Create table Objects (GUID varchar2(20) not null, CreateDt date, primary
key(GUID));
Create table ObjectContent (GUID varchar2(20) not null, Content clob, primary
key(GUID), FOREIGN KEY(GUID) REFERENCES Objects(GUID));
CREATE INDEX OBJGLUEX ON ObjectContent(Content) indextype IS ctxsys.context
parameters ('lexer my_lexer');

Case 1: Straightforward query on table join, it takes more than 30 seconds...

select /*+ FIRST_ROWS */ * from (
   select /*+ FIRST_ROWS */ a.*, rownum rnum from (
       select /*+ FIRST_ROWS */ distinct o.GUID, o.CreateDT
         from Objects o, ObjectContent oc
         where oc.guid=o.guid
         and contains (oc.Content, 'Power') > 0 order by o.CreateDT desc
       ) a
       where rownum <= 20
    ) where rnum > 0;

20 rows selected.

Execution plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=6 Card=1 Bytes=60)
   1    0   VIEW (Cost=6 Card=1 Bytes=60)
   2    1     COUNT (STOPKEY)
   3    2       VIEW (Cost=6 Card=1 Bytes=47)
   4    3         SORT (UNIQUE STOPKEY) (Cost=4 Card=1 Bytes=94)
   5    4           NESTED LOOPS (Cost=3 Card=1 Bytes=94)
   6    5             TABLE ACCESS (BY INDEX ROWID) OF 'OBJECTCONTENT' (Cost=2
Card=1 Bytes=47)
   7    6               DOMAIN INDEX OF 'OBJGLUEX' (Cost=0)
   8    5             TABLE ACCESS (BY INDEX ROWID) OF 'OBJECTS' (Cost=1 Card=1
Bytes=47)
   9    8               INDEX (UNIQUE SCAN) OF 'SYS_C003964' (UNIQUE)

Statistic
----------------------------------------------------------
       4907  recursive calls
          0  db block gets
      68956  consistent gets
       9934  physical reads
          0  redo size
       1702  bytes sent via SQL*Net to client
        514  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
         20  rows processed

Case 2: Straightforward query on table join, it takes totally less than 10
seconds...

create global temporary table Temp (rid rowid not null, primary key(rid) ) on
commit delete rows ;
insert into Temp select rowid from ObjectContent oc where contains (oc.Content,
'Power') > 0;
select /*+ FIRST_ROWS */ * from (
   select /*+ FIRST_ROWS */ a.*, rownum rnum from (
       select /*+ FIRST_ROWS */ distinct o.GUID, o.CreateDT
         from Objects o, ObjectContent oc, Temp t
         where oc.guid=o.guid and oc.rowid = t.rid
         order by o.CreateDT desc
       ) a
       where rownum <= 20
    ) where rnum > 0;

15317 row created.

Execution plan
----------------------------------------------------------
   0      INSERT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=9)
   1    0   DOMAIN INDEX OF 'OBJGLUEX' (Cost=0 Card=1 Bytes=9)

Statistic
----------------------------------------------------------
       4963  recursive calls
      47689  db block gets
       5571  consistent gets
         47  physical reads
    4623532  redo size
        630  bytes sent via SQL*Net to client
        597  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      15317  rows processed

20 rows selected.

Execution plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=302 Card=20
Bytes=1200)
   1    0   VIEW (Cost=302 Card=20 Bytes=1200)
   2    1     COUNT (STOPKEY)
   3    2       VIEW (Cost=302 Card=8168 Bytes=383896)
   4    3         SORT (UNIQUE STOPKEY) (Cost=153 Card=8168 Bytes=808632)
   5    4           NESTED LOOPS (Cost=4 Card=8168 Bytes=808632)
   6    5             NESTED LOOPS (Cost=4 Card=991351 Bytes=91204292)
   7    6               INDEX (FAST FULL SCAN) OF 'OBX' (NON-UNIQUE) (Cost=4
Card=76453 Bytes=3593291)
   8    6               INDEX (UNIQUE SCAN) OF 'SYS_C003977' (UNIQUE)
   9    5             INDEX (UNIQUE SCAN) OF 'SYS_C004117' (UNIQUE)

Statistic
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      79417  consistent gets
        539  physical reads
          0  redo size
       1702  bytes sent via SQL*Net to client
        514  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
         20  rows processed

          Followup:

select /*+ first_rows */ guid, createdt
  from objects
 where guid in (select guid
                  from objectContent
                 where contains( content, 'Power' ) > 0)
 order by createDT desc

first_rows on that inner most query in your case (with a distinct and a join)
could be "the reason".  assuming that guid is the primary key of objects (seems
reasonable), the distinct isn't necessary and it looks like you just want "in"

Another way to do it would be:

select /*+ FIRST_ROWS */ * from (
   select /*+ FIRST_ROWS */ a.*, rownum rnum from (
       select /*+ FIRST_ROWS */ o.GUID, o.CreateDT
         from Objects o,
              (select distinct guid from ObjectContent where
contains(content,'Power') > 0 ) oc
         where oc.guid=o.guid
         order by o.CreateDT desc
       ) a
       where rownum <= 20
    ) where rnum > 0;

which pretty much mimicks your approach -- in fact, yours could probably go
faster even if you inserted the distincted GUID's not rowids.

      Rating:
      2
  March 02, 2004
Reviewer:  Jimmy
      Lee  from Taipei, Taiwan
Hi Tom,

You precious advice just happens to be the weird point about GUID (PK of table)
and rowid (physical row id). I tried lots combinations on 'first_rows' hint,
'distinct' keyword, 'in' join or subquery. None of them could be even close to
the speed of using rowid as PK of temporary table.

In other words, using rowid, instead of GUID, as primary key of temporary table
has HUGE different on the inserting temporary table. Once temporary table was
generated, no matter using GUID or rowid as primary key, the speed on table join
was not much different. It matches the slow-speed observation on "select guid
from objectContent where contains( content, 'Power' ) > 0".

The performance bottleneck seems to be that ctxsys.context stores rowid instead
of PK inside DR$ tables. Then it takes tons of time to looking up PK by rowid.

I tried to use IOT table but ctxsys.context could not be secondary index of IOT
table.

Oracle version is 9.2.0.4 (result is as the same as 9.2.0.1).

Any idea? Thanks so much on your help

          Followup:

try

select /*+ FIRST_ROWS */ * from (
   select /*+ FIRST_ROWS */ a.*, rownum rnum from (
       select /*+ FIRST_ROWS */ o.GUID, o.CreateDT
         from Objects o,
              (select distinct rowid rid from ObjectContent where
contains(content,'Power') > 0 and rownum >= 0 ) oc
         where oc.rowid=o.rid
         order by o.CreateDT desc
       ) a
       where rownum <= 20
    ) where rnum > 0;

      Rating:
      2
  March 03, 2004
Reviewer:  Jimmy
      Lee  from Taipei, Taiwan
Hi Tom,

Retrive rowid in subquery to join with original table, just like your sample, is
just slow as others. Using rowid as primary key of tamporary table still run
much faster (about 1:4 ratio or more). Reasonable it is to retrieve all rows if
we need to sort query result, yet doubtful it is to understand the performance
difference.

Thanks.

March 03, 2004 Rohil -- Thanks for the question regarding "which
      is faster? static cursor or dynamic cursor", version 9i

      You Asked
hi tom,
"which is faster "a static cursor OR a dynamic cursor(using cursor variables and
REF cursor)" ?
is there any significant difference in execution speeds? (or does it depend on
anything else?)

Given an option of using static and dynamic cursor, which one should be used?

thanx in advance
bye
rohil

      and we said...
STATIC code is always better.

You use dynamic sql only when there is quite simply NO WAY to do it statically.

Static sql has the advantages of

o speed
o ease of coding
o ease of maintenance
o automatic dependency tracking (if you statically reference something, it is
  recorded in the data dictionary and you can query up that fact.  If you
  dynamically reference it -- it is NOT recordable)
o ease of debugging/testing (with dynamic sql, the query isn't generated until
  runtime -- will that query work?  I don't know until i run EVERY possible
  combination.  With STATIC sql I know at compile time that its valid sql)

Always use static sql until you cannot.
Review &
      Followup

      Rating: 4
Statis Vs
      Dynamic Cursor  September 03, 2002
Reviewer:
      Ashok Shinde  from Ipswich, United Kingdom
This has helped me alot. But can you use the static cursor with the datatypes
like long, blob, lob ?
Thanks,
Ashok

          Followup:

Yes, Yes, and finally -- yes.

      Rating: 3
But what about
      this case  September 03, 2002
Reviewer:  Ed
       from UK
Tom,

I saw this SQL statement as a static piece of SQL in a procedure (that works).
Surely the NVLs are a bad idea, and this is a case for using Native Dynamic SQL
even though the static version works.

SELECT forename,
       surnames .... etc
  FROM az_data
 WHERE surname LIKE UPPER(p_surname)||'%'
   AND id = NVL(p_id,id)
   AND cat_code = NVL(p_cat_code,cat_code)
   AND amt BETWEEN NVL(p_low_amt,0) AND NVL(p_hi_amt,9999999999999)
   AND req_date BETWEEN NVL(p_low_date,TO_DATE('20000101','YYYYMMDD'))
AND NVL(p_hi_date,TO_DATE('29990101','YYYYMMDD'))
   AND postcode LIKE UPPER(p_postcode)||'%'
ORDER BY surname, forenames;

          Followup:

This is a case where dynamic sql is called for, PROBABLY, maybe, it depends...

It is quite probable that this statement would run better if the where clause
(which apparently isn't really known until runtime) was generated at runtime.

Now, if the query is using an index on SURNAME or POSTCODE or SURNAME,POSTCODE
-- that would not be the case.  The STATIC sql (as it is) would be better in all
probability.

But yes, thanks for making me tone it down a tad.  Common sense must be applied.
 See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1288401763279
for how I might code this if such an index was not being used (or if ID was
highly selective and indexed for example -- meaning if the query had "where id =
p_id" it should use the index on ID)

      Rating: 5
Query
        September 03, 2002
Reviewer:  San
       from US
Hello Tom,

I have a table to record employee activity,

Table: Employee_activity

EmployeeId   Start_time    End_time(date)  Activity   day
1             9.00 Am       10.00 am        Worked    11/01
1            10.00 Am       11.00 Am        lunch     11/01
2             9.00 Am       12.00 Am        worked    11/01
1             9.00 Am       10.30 Am        worked    11/02
1            11.00 Am       12.00 Am        worked    11/02

I like to get a output like this (record for the unaccounted time),

EmployeeId   Start_time    End_time(date)  Activity   day
1             9.00 Am       10.00 am        Worked    11/01
1            10.00 Am       11.00 Am        lunch     11/01
1            11.00 am        5.00 Pm       Unaccounted 11/01

2             9.00 Am       12.00 Am        worked    11/01
2            12.00 Am        5.00 Pm      Unaccounted 11/01

1             9.00 Am       10.30 Am        worked    11/02
1            10.30 Am       11.00 am     Unaccounted  11/02
1            11.00 Am       12.00 Am        worked    11/02
1            12.00 Am        5.00 pm     unaccounted  11/02

The total working hours is 8.

I am using oracle 8.1.7. Can you please explain me how I can do this in a single
query. If this cannot be done in a single query please suggestion me other
options with example.

Thanks Tom.
Have a blessed and highly favored day.

      Rating: 2
Thanks for the
      question regarding "which is faster? static cursor or dynamic cursor",
      version 9i  March 02, 2004
Reviewer:  butch
      larue  from las vegas, nevada usa
this is interesting BECAUSE according to Oracle 8i application developers guide,
chapt9, dynamic sql, performanance improvements,  "The performance of native
dyunamic SQL in PL/SQL is comparable to the performance of statis SQL because
the PL/SQL interpretter has built in support...."  I am becoming dissolutioned
as we have a system that creates and runs dynamic sql that is waaaaaaaaaay
sloooooooooooow.  I am trying to remedy the situation but dont know where to
begin.  As the time on the clock runs along, fewer and fewer statements per time
increment are run.  What are the first tuning steps to take?  Memory? Disk?

          Followup:

dyunamic SQL in PL/SQL is comparable to the performance
                          ^^^^^^^^^^

dynamic sql is not necessarily any slower than static sql for raw query
performance.  not at all.

have you tkprofed.
have you shown that "static sql is waaaaaaaaaaaaaaay faster"?
have you used statspack at all
have you begun to id where the bottleneck(s) are?

that is the first step (memory, disk - not a chance. find out what the problem
is and then address it -- not the other way around)

      Rating:
      3
OK  March 03, 2004
Reviewer:
      Srinivas  from Hyderabad,India
Dear Tom,
Any other better way for this query?
 sql>select * from worker where age > 35;
Please do reply.

          Followup:

why?

March 03, 2004 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.

      Rating: 3
Monitoring
      large pool memory consumption.  January 13, 2004

Reviewer:  Jay Nelson  from Minneapolis, MN

The answers were very concise (and made sense).  Very nice.

      Rating:
      4
parallel_automatic_tuning=TRUE?  February 05, 2004

Reviewer:  Vinnie  from Orlando
Tom,

I have a table that is dynamically created at run time.  This table is loaded
with data via a JAVA app & speed is essential.  The developers are considering
multithreading the app.  IN your opinion would it help to set
parallel_automatic_tuning=TRUE & create the table with the parallel clause?

If so, do we need to do anything else in the INSERT or SELECT statements against
this table to utilize the parallelizm?
THanks

          Followup:

you want to do it fast, do SQL stuff in plsql and use external tables and BULK
operations (like insert /*+ append */ and maybe parallel query)

if you have a program doing "insert into tables values...." -- parallel =
"no-op" for you.  the client would have to do all of the parallelization.

if you want the least code that runs the fastest -- that'll be plsql with
external tables using bulk operations.

      Rating:
      3
Good  March 03, 2004
Reviewer:
      Gururaj  from Pune,India
Hi Tom,
When we use a parallel hint,what can be the maximium no.of
processes that can be started in parallel by the system and
also what is the max.number of instances that an Oracle database can have?
Thanks in advance.

          Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96536/ch45.htm#288247
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96536/ch45.htm#288175

March 03, 2004 --
      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://download-west.oracle.com/docs/cd/A87860_01/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.

      Rating: 5
SQL
      Loader  February 22, 2004
Reviewer:  Zilani
       from Dhaka
Hi Tom
I have a problem of using SQL Loader. I have a table as follows-
SQL> desc Mtblabc;
 Name                 Null?    Type ---------------------------
 abc_ID       NOT NULL VARCHAR2(16)

 abc_ADDRESS           ADDRESS

 abc_TYPE     NOT NULL CHAR(1)

there is a column of object type(ADDRESS).

SQL> desc address;
 Name        Null?    Type
 -----------------------------------------------------
 HOUSE_NO_ROAD    VARCHAR2(256)
 VILLAGE_CODE   VARCHAR2(2)
 UPAZILLA_CODE   VARCHAR2(2)
 UNION_CODE      VARCHAR2(2)
 DISTRICT_CODE   VARCHAR2(3)

How do I create the control file for this object type column. I did it in the
following way-

-- File Name : mtblabc.ctl
-- File Creation Date : 19-FEB-04

LOAD DATA
 INFILE  'mtblabc.txt'
 BADFILE  'mtblabc.bad'
APPEND
 INTO TABLE
mtblabc
 TRAILING NULLCOLS
 (
abc_id               CHAR TERMINATED BY ",",
abc_address        VARRAY COUNT (ADDRESS)
(
ADDRESS     COLUMN OBJECT
(
 house_no_road     TERMINATED BY ",",
 village_code     TERMINATED BY ",",
 upzilla_code     TERMINATED BY ",",
 union_code     TERMINATED BY ",",
 district_code     TERMINATED BY ","
)
)

abc_type        CHAR TERMINTATED BY ","

)

Is it the right way . Please help me.
When i wants to load from this ctl file it gives error.
give me some suggestions or any related documents.

Thanks in advance.

          Followup:

well, what error and what does your input file look like and how about giving me
"create" statements so I can try your example?

      Rating: 5
SQL
      Loader  March 03, 2004
Reviewer:  Zilani
       from Dhaka
Thanks Tom for your patience. I have already solved my problem. Any way I am
also giving you the ?create? command as following and the control file. My
table is a large table so I cut it and gave you a small portion and used ?abc?
for understanding. You can try it .Better suggestions will be appreciated. In
.txt file in control file I have to give the full path. Without the path the
sqlldr could not read the .txt file. Is there any way to avoid the full path.
Zilani

CREATE OR REPLACE TYPE ADDRESS AS OBJECT
(    house_no_road    VARCHAR2(256),
    village_code     VARCHAR2(2),
        union_code       VARCHAR2(2),
    upazilla_code    VARCHAR2(2),
        district_code    VARCHAR2(3)
        )

CREATE  TABLE  Mtblabe
(     abc_ID       NOT NULL VARCHAR2(16),
     abc_ADDRESS           ADDRESS,
 abc_TYPE     NOT NULL CHAR(1)
);

**********Control File mtblabc.ctl**********
LOAD DATA
 INFILE  'F:\CreditBureauDatabase\TechnicalDevelopment\Loader\mtblabc.txt'
 BADFILE  'F:\CreditBureauDatabase\TechnicalDevelopment\Loader\mtblabc.bad'
APPEND
 INTO TABLE
mtblabc
 TRAILING NULLCOLS
 (
abc_id               CHAR TERMINATED BY ",",
abc_address        COLUMN OBJECT     (
         house_no_road     CHAR TERMINATED BY ",",
         village_code     CHAR TERMINATED BY ",",
         union_code     CHAR TERMINATED BY ",",
         upazilla_code     CHAR TERMINATED BY ",",
         district_code     CHAR TERMINATED BY ","),
abc_type        CHAR TERMINATED BY ","
)

*************Data File mtblabc.txt**********
1,1,2,3,4,5,1

          Followup:

run sqlldr from the directory where the file to be loaded resides.  it uses the
current working directory.

      Rating: 5
SQL
      Loader  March 07, 2004
Reviewer:  Zilani
       from Dhaka
Thanks Tom
A great job.

March 03, 2004 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.

      Rating: 2
Object Staus =
      ERROR in Multi Master Replication [Oracle9i Rel. 2 on MS Windows
      2000]  February 26, 2004
Reviewer:  Faran

Hi Tom,

An asynchronous multimaster replication environment was configured many days
before. All was working fine. But today, when I quried the dba_repobject at the
master site; it shows error for the status of several objects i.e.

SQL> select  sname, oname, type, status, generation_status
  2  from    dba_repobject
  3  where   gname = 'AMS_REPG'
  4  and     sname = 'HRMS'
  5  and     status <> 'VALID';

SNAME  ONAME                  TYPE             STATUS  GENERATIO
------ ---------------------- ---------------- ------- ---------
HRMS   CITY                   TABLE            ERROR   GENERATED
HRMS   COUNTRY                TABLE            ERROR   GENERATED
HRMS   DEPARTMENT             TABLE            ERROR   GENERATED
HRMS   DESIGNATION            TABLE            ERROR   GENERATED
HRMS   DIVISION               TABLE            ERROR   GENERATED
HRMS   EMPLOYEES              TABLE            ERROR   GENERATED
HRMS   EMPLOYEE_STATUS        TABLE            ERROR   GENERATED
HRMS   STATE                  TABLE            ERROR   GENERATED

8 rows selected.

But when I run the same query against other database in the same replication
environment; it shows <status> VALID for the same objects. I have tried by
inserting, updating and deleting the rows of table <employees> these are
propagating properly. But I am anixious how that it may cause any serious error.
Please guide me what would be disadvantages in this scenario and how can the
status changed to <VALID> rather than <ERROR>.

Thanks as always :)

Faran

          Followup:

For the propagation and queuing of data changes to be successful to remote
sites, replicated tables (objects) must display as valid at all replication
sites. They must also have the associated internalised triggers and packages
defined. Run the following query to check the replicated tables (objects):

  select oname, status, generation_status,
         replication_trigger_exists, internal_package_exists
  from  dba_repobject
  where status != 'VALID'
     or replication_trigger_exists != 'Y'
     or internal_package_exists != 'Y';

If the above query returns replication objects that do not have the associated
internalised triggers or packages, it may be necessary to re-generate
replication support. If after re-generation the replication objects still show
as invalid, run the following statement to ensure that all dependant SYS and
SYSTEM objects are valid:

  column owner format a25
  column object_name format a30
  column object_type format a15

  select owner, object_name, object_type
  from  dba_objects
  where status != 'VALID'
  and   owner in ('SYS', 'SYSTEM');

If problems persist, contact Oracle Support Services.

      Rating: 2
After
      compiliation of invalid objects => Object Staus = ERROR
        February 27, 2004
Reviewer:  Prince Faran

On master site the following query has been run.

SQL> select owner, object_name, object_type
  2  from   dba_objects
  3  where  status != 'VALID'
  4    and  owner in ('SYS', 'SYSTEM');

OWNER  OBJECT_NAME                OBJECT_TYPE
------ -------------------------- ------------
SYS    ALL_REPPARAMETER_COLUMN    VIEW
SYS    DBA_REPPARAMETER_COLUMN    VIEW
SYS    DBMS_REPCAT_RGT_CUST       PACKAGE BODY
SYS    USER_REPGROUPED_COLUMN     VIEW
SYS    USER_REPPARAMETER_COLUMN   VIEW
SYS    _ALL_REPCOLUMN_GROUP       VIEW
SYS    _ALL_REPCONFLICT           VIEW
SYS    _ALL_REPGROUPED_COLUMN     VIEW
SYS    _ALL_REPPARAMETER_COLUMN   VIEW
SYS    _ALL_REPRESOLUTION         VIEW

10 rows selected.

After compiling all of the above objects. I have re-run the query.

SQL> select owner, object_name, object_type
  2  from   dba_objects
  3  where  status != 'VALID'
  4    and  owner in ('SYS', 'SYSTEM');

no rows selected.

The above query has showed the same result i.e. "no rows selected." on remaining
sites.

Then I run the following query on the master site.

SQL> select  oname, status, generation_status,
  2          replication_trigger_exists,
  3          internal_package_exists
  4  from    dba_repobject
  5  where   gname = 'AMS_REPG'
  6    and   sname = 'HRMS'
  7    and   status != 'VALID'
  8     or   replication_trigger_exists != 'Y'
  9     or   internal_package_exists != 'Y';

ONAME                          STATUS     GENERATIO R I
------------------------------ ---------- --------- - -
CITY                           ERROR      GENERATED Y Y
COUNTRY                        ERROR      GENERATED Y Y
DEPARTMENT                     ERROR      GENERATED Y Y
DESIGNATION                    ERROR      GENERATED Y Y
DIVISION                       ERROR      GENERATED Y Y
EMPLOYEES                      ERROR      GENERATED Y Y
EMPLOYEE_STATUS                ERROR      GENERATED Y Y
STATE                          ERROR      GENERATED Y Y

8 rows selected.

Tom! still all is going well. But just the status representation is ERROR.

Bye,
Faran

          Followup:

i'll let you work this one through support.  I just don't do this replication
thing myself (don't believe in replication of bits and bytes personally, but
that is another story all together...)

      Rating: 2
replication of
      bits and bytes   March 03, 2004
Reviewer:
      Prince Faran  from Pakistan
Dear Tom!

You don't believe in replication of bits and bytes. Would you like to please
refer me to any link where can I read the explanation of your statement. I have
been using advanced replication of tables having rowdependencies with
parallelism=>2. Are you in favour of replication through oracle data streams or
which method/way of replication do you prefer?

As always :)
Thanks

          Followup:

If you want to use replication, you must design your entire application around
it.  Most people try to slap replication on after the fact (with disastrous
results) or try to glue replication on top of SAP, Oracle Apps whatever (not
possible, if the 3rd party app doesn't design itself to replicate bits and
bytes, it'll not)

I find it "hard"
I find it cumbersome to develop for it at the data level.

I find it easy at the business transaction level (eg: do this address change for
tom please.  Don't replicate the 15 updates, audit records, etc etc that
represent the address change - just ask that other system to "change toms
address")

that is what I mean.  message based interactions, not bits and bytes
replication.

March 03, 2004 Rajiv -- Thanks for the question regarding "free
      text search", version 9.2

      You Asked
can you please tell how to achieve the free text search in varchar2 fields

are there any new types of indexes for this?

i want to search like contains and search for an exact phrase for the strings
that were given with double quotes(")... can i leverage any oracle capabilities?

i am very new to context search or intermedia search.

thanks

      and we said...
That is exactly what Oracle Text (aka interMedia, aka context) is all about.

Check out:

http://download-west.oracle.com/docs/cd/B10501_01/nav/docindex.htm#index-TEX
Review
      & Followup

      Rating: 4
How big is your
      column?  October 08, 2002
Reviewer:  Adrian
      Billington  from UK
Tom

I know you hate rules of thumb but at what column/data size generally do you
start to think about Oracle Text ? To put this into context, we have a set of
reports that need to perform string matching within a column containing URLs
captured in our web logs. The average length of a URL in our system is
approximately 250 characters, so we are simply using instr at the moment...

Thanks

Adrian

          Followup:

it is not the width, it is the height.

If you find that using instr runs sufficiently fast (because of other predicates
perhaps) AND the height of the data (number of rows) you need to instr on will
remain more or less constant (not grow lots) -- then stick with it.

It is when you have to do the instr trick on a couple of thousand or more rows
that you'll start to realize "hey, this is slow".  That is when an indexing
technique such in Oracle Text starts to become appealing.

Just like scott.emp doesn't really need an index on EMPNO....  Until you put a
thousand rows in there anyway....

      Rating:
      4
  October 08, 2002
Reviewer:  Paul
       from UK
Tom,

Somehow I got into my head that Intermedia was used for searching documents (in
LOBs) rather than small columns.

I have a system where the user wants to be able to search for a name (could be
Company name or an individual) in a column (VARCHAR2(80)).  I know it's not an
ideal database design, but we've got huge amounts of legacy data from a
multitude of systems.

Would this be a suitable candidate for using Intermedia?

What are the disadvantages / overheads / performance issues with using
Intermedia?

Thanks,

Paul

          Followup:

It would be a good use -- Oracle text is useful for "skinny" as well as "fat"
data.  It gives you case insensitive (or case sensitive if you like) keyword
searching, stemming, wildcards, fuzzy, soundex, and more searches.  You can do
phrases or discrete words and use AND or OR and so on.

Oracle Text (aka intermedia/context) is just an indexing technique so the normal
overheads of "indexes" in general applies (takes storage for example, takes
time)

Disadvantages -- well, given that you want to search for words in a string and
unless you use Text you have to sort of "instr" and search on the fly, there
aren't any really.  Its like asking "what are the disadvantages of a b*tree
index on the EMPNO column of EMP".  the disadvantage of not have such an index
so outweighs anything else when emp has a couple of thousand rows and you
constantly "select * from emp where empno = :x"

check out the documentation -- plenty of examples on this site as well.  Its
something you can test out fairly quickly to see if it applies in your case.

      Rating: 4
Got
      it...  October 08, 2002
Reviewer:  Adrian
      Billington  from UK
Tom

Your followups (to mine and Paul from UK) have really helped. The height is
significant for me - we are talking about roughly 20K rows daily. Further to
this, some ad hoc queries run against a partition containing 2 million of these
URLs, so it looks like I should give Oracle Text a go...

Regards

Adrian

      Rating: 4
Free Text
      Search  March 03, 2004
Reviewer:  Zilani
       from Dhaka
Hi Tom
I am facing a problem. I am developing a Credit bureau Database. It will contain
initially more then 15 million member information. I have to develop a search
engin to find a person from a member registration table depending on some
parameter. the parameters are First name, Last name, Middle name,Alias
name,Father's name, Mother's name, Occupation and so on. I used soundex function
for prelimenary search. There need an advanced search for getting the person
closer to the paameters. The problem is that one can enter his/her name in
different spelling. For example my name "Zilani" can be entered as "Zilani",
"jilani","jeelani","gilani","zelani","zilanee","jilanee" and so on . I have to
find all the spelling. it can be occur in all the parameters. all the fields
here are varchar2. I am using oracle 9.2. How can i get the most closer persons
from 15 millions of member. I will be ever greatfull to you.Actually if i could
do it then it will be a graet job for our country as our citizens have no ssn no
and  uniquely identify a person is very difficult.

Thanks in advance.

Zilani

          Followup:

sorry, I'm not personally familar with the algorithms for finding zilani like
that.  not my area of expertise.

March 03, 2004 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

      Rating: 5
Drawback of
      killing session  February 26, 2004
Reviewer:
      kom  from USA
Hi Tom,
Our users will execute adhoc report queries say 10,000 a day on real time web
application. Now some queries can return results in 1 min or some may take 5
hours to execute. I am in no position to filter which query will take how much
time to execute.

I need to develop some kind of intelligence on which queries to execute
immediately based on time they will take to execute or to form a queue to handle
the big queries execution one at a time so that I do not stress my db server.

One method which I am thinking of is , send all queries to db server and put
some kind of query timeout say 1 min, if the sql does not execute in 1 min than
KILL the session and queue the SQL in a batch one at a time. In this way I will
end up killing alot of sessions. Is there any drawback of killing sessions. Will
the resources be used correctly.

Is the above way correct or please suggest me BEST way to handle this
requirement.

Thanks in advance

          Followup:

I would put all in a queue.

1 minute is 60 times too long for a web based application.  5 hours is just
"longer".

No one should have to sit for a minute wondering "hmm, is my query ever going to
come back" (i would not necessarily call this "real time").

So, I would use a queue of some form for all -- and then have some notification
method (eg: email them a URL that when they click on it, instant report.  SMS
them when it is ready.  give them a page they can goto to check the status.
whatever you want...) to let them know it is done.

      Rating:
      5
Additon  February 27, 2004
Reviewer:
      kom  from USA
Hi Tom,
I told the business team that we need to queue all the incoming report request
and notify the user as you mentioned, but they do not agree with this
.
They want all reports to be executed when requested and only those ones which do
not execute in 1 min should be queued.
1) is there any command which will tell near realistic time to execute the
query.
2) Will killing alot of sessions ( time < 1 min ) harm the system.

Can oracle save me in this case ?
I have oracle 8.1.7

Please suggest me the BEST way to handle this requirment.

          Followup:

1) not really

2) yes, it'll waste that much time constantly.

The saviour would not be running things that take 1 minute interactively in a
browser. could you imagine if google did that?

ugh.  guess you could use the resource manager IN 9i and above, but not in
8i.  In 8i, you'd have to let the silly thing run for a minute and have the
system kill it (using a resource plan).

      Rating: 5
documentation
      on resource plan  March 01, 2004
Reviewer:
      Kom  from USA
Hi Tom,
I have oracle 8.1.7 on win 2k
I would like to read more on resource plan for my above requirment.
Can you please send me a link.
Thanks in advance,

          Followup:

otn.oracle.com has all documentation.  you are interested in the Admin Guide
(chapter on resource mgmt).  All versions from 8.0 on up are there -- you want
to look at either 9ir2 or 10g docs.

      Rating:
      5
  March 01, 2004
Reviewer:  SN
       from Big Apple
Kom, you might want to tell your business users about Oracle Discoverer - It has
the feature of predicting the expected run-time (with near accuracy). If it is
too long then they can submit it in batch mode. The system you describe can't be
classified "real time".

      Rating: 2
Killing the
      session of a running job  March 03, 2004
Reviewer:
      Faran
Tom! how can we kill a session handling a long running internal job to update
some very large tables. When I kill the session handling that particular job; It
simply says that the session has been marked killed; but it doesn't release the
locks on the underlying objects (involved in update by the job). I wanna know
any method (in Oracle9i Rel. 2) by which Oracle can release the locks in such a
case.

Thanks :)
Faran

          Followup:

it is in the ACT of releasing the locks (watch used_ublk in v$transaction, you
can watch it rolling back)

March 03, 2004 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"

      Rating:
      5
  March 02, 2004
Reviewer:
      A reader
My select:
SELECT
    "ORDER_ITEMS"."ITEM_ID",
"ORDER_ITEMS"."ORDER_ID",
"LOCATIONS"."LOCATION_ID",
"LOCATIONS"."ROOM" || "LOCATIONS"."PARTITION" || "LOCATIONS"."SHELF" as
location,
"LOT_DESCRIPTIONS"."LOT_NUMBER",
"LOT_DESCRIPTIONS"."EXPIRE_DATE"
FROM
    "OE"."ORDER_ITEMS" "ORDER_ITEMS",
    "INV"."LOCATIONS" "LOCATIONS",
    "INV"."LOCATION_LOTS" "LOCATION_LOTS",
    "INV"."LOT_DESCRIPTIONS" "LOT_DESCRIPTIONS"
WHERE
    "ORDER_ITEMS"."INVENTORY_ID" = "LOCATIONS"."INVENTORY_ID" AND
    "LOCATIONS"."LOCATION_ID" = "LOCATION_LOTS"."LOCATION_ID"(+) AND
    "LOCATION_LOTS"."DESC_ID" = "LOT_DESCRIPTIONS"."DESC_ID"(+) AND
    "ORDER_ITEMS"."ORDER_ID" = 440555
ORDER BY
    "ORDER_ITEMS"."ITEM_ID" ASC,
    "LOT_DESCRIPTIONS"."EXPIRE_DATE" ASC,
    "LOCATIONS"."LOCATION_ID" ASC

item_id   location_id  location  lot_number  expire_date
1825660    74258        LB5409
1825660    74259        LB5410
-----------------------------------------------------------
1825661    73039        ARA20
1825661    76237        AA0001
-----------------------------------------------------------
1651993    76236        KG01      F03NPJY     4/1/2004
1651993    63433        DOCK2     F03NPJY     12/31/2006

Now my sorting requirements is to resort item_id by first location in it leaving
sorting by exp. date and location_id inside every item_id.
other words, I need:

item_id   location_id  location  lot_number  expire_date
1825661    73039        ARA20
1825661    76237        AA0001
-----------------------------------------------------------
1651993    76236        KG01      F03NPJY     4/1/2004
1651993    63433        DOCK2     F03NPJY     12/31/2006
-----------------------------------------------------------
1825660    74258        LB5409
1825660    74259        LB5410

Thanks in advance.

          Followup:

not sure what you mean.

looks like order by location, item_id, expire_date, location_id

      Rating: 5
Generic Sorting
      and Searching routine based on Dynamic conditions  March 03,
      2004
Reviewer:  Anand  from Hyderabad, India

Hi Tom...

I have tried using this technique and it works wonderfully  But I have a few
doubts for which I need clarifications.

(1) What is the use of the keyword "EXTEND" in that code.
(2) In case if i need to pass my ORDER BY Clause dynamically what is the
procedure to do so ?
(3) If i need to pass a condition on one of the columns and need a search to
return on the records which match that condition ... how that need to be done...
(4) As we are creating this TYPE as a OBJECT in SQL... it reckon it wont be a
generic procedure meaning that i have to create multiple types for each of my
tables. Is there any way to create a GENERIC SEARCH and SORT routines using the
above technique ?

Thanks for now and always...

Anand

          Followup:

1) see what happens when you don't use it (or see the plsql guide -- it
describes all of this stuff).  it makes the array one bigger.  I'm not useing an
index by table so we must allocate it ourselves

2) dynamic sql, you'll have to dynamically do the query since you don't know
what it is at runtime.

3) see #2

4) absolutely.  You see -- you MUST know what the types are (you are stuffing
data in there, you have written CODE -- this code must have the record defintion
"known")

Your generic "search and sort" api is just sql.  Anywhere in your code you have
a collection variable, you can just execute;

   select * from TABLE( cast( THAT-VARIABLE as its_type ) ) where .... order
by....;

you don't need a generic search and sort API -- SQL is that generic SEARCH and
SORT API

March 03, 2004 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?

      Rating: 4
what do you
      recommend  March 03, 2004
Reviewer:  dwl
       from uk
So i am just wondering what you believe is best practice for writing code in
applications (apart from moving all the sql into the database!)??

If we have a java app using jdbc to oracle 8.1.7.4.

1) Then should any sql that references and compares dates in the app code, do an
explicit TO_DATE on each date??

ie  WHERE mydatefield = TO_DATE('01/01/2001', 'DD/MM/RRRR');

Obviously this makes a call to the TO_DATE function, so is this the most
efficient way i.e. more efficient than forcing all jdbc clients to use a certain
nls_date_format eg using a logon trigger or env variable and then coding eg:

WHERE mydatefield = '01/01/2001';

ie no call to TO_DATE. or is this so neglible that i shouldn't worry about it?
I'm mainly thinking about some app which may run some sql code hundreds or
thousands of times a day, so calling TO_DATE a lot!

2) If the client either odbc or jdbc connects to the database without any
registry settings or environmental settings for nls_date_format, then which
format would it pick up, the nls_instance_parameter settings??
What if NLS_LANG is set in the registry but not the nls_date_format??

3) How can i use sqlplus to emulate the jdbc/odbc clients. ie i want to test
some sql using the same nls settings that the client would use. At the moment i
issues an explicit allter session set nls_date_format in my glogin.sql script
but if i want sqlplus to use the nls_instance_parameters what should i do,
delete the glogin script??

Thanks for your help.

          Followup:

1) yes, use to_date.  else  you introduce unavoidable ambiguities into the code.

2) odbc is almost always on windows, windows ALWAYS has registry settings.  The
problem is some clients will get it from the db, some from the client, some set
on their own -- you cannot control it.  that is why i like #1

3) yes, just don't alter and sqlplus will do what the client would do.

March 03, 2004 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

      Rating:
      5
Util_file  January 28, 2004
Reviewer:
      vani  from Oakland ,CA USA
This is really helpful.whenever I get any error.
one and only onething strikes me "ASK TOM"

      Rating: 5
Wonderful
      code-but i got an error !!  March 03, 2004

Reviewer:  sathya  from India
The code provided for creating CSV is perfect. But when I ran the code, I got
and error message:

ORA-06562: type of OUT argument must match type of column or bind variable

This error comes in the COLUMN_VALUE procedure...

please help...

          Followup:

hows about a teeny teeny tiny example (you should just need to create a one
column table for us to demonstrate)

March 03, 2004 Saibabu -- Thanks for the question regarding
      "Checking Sysdba login", version 8.1.5

      You Asked
Hi Tom,
       I have been wondering how to check whether a particular
user logged in as a sysdba or sysoper or normal id.
   After working at sqlprompt long time sometimes I forget
whether my login id is sys or sys as sysdba.Please guide me.
 Thanks
 Saibabu

      and we said...
ops$tkyte@DEV816> show user
USER is "OPS$TKYTE"
ops$tkyte@DEV816> connect / as sysdba;
Connected.
ops$tkyte@DEV816> show user
USER is "SYS"
ops$tkyte@DEV816> connect /
Connected.
ops$tkyte@DEV816> show user
USER is "OPS$TKYTE"
ops$tkyte@DEV816>

Review &
      Followup

      Rating: 3
Is sysdba a
      privilege or a role ?  November 13, 2002
Reviewer:
      Ravi  from Reading, UK
Tom,

Is SYSDBA a privilege or a role ?  How do I find which users have the
priivege/role to connect as SYSDBA ?

Thanks in advance.

          Followup:

It is like a "magic" role.

sys@ORA920.LOCALHOST> select * from v$pwfile_users;

USERNAME                       SYSDB SYSOP
------------------------------ ----- -----
SYS                            TRUE  TRUE
OPS$TKYTE                      TRUE  FALSE

and in unix -- anyone in the DBA group has it (at the OS level, if they use OS
authentication)

      Rating: 5
execution
      plan  November 13, 2002
Reviewer:
      A reader
Hi, Tom,

What kind roles should i be granted in order to do:
set autotrace on explain;

I have some problem when i doing that right now, I think
i lack some role.
But when I conect / as sysdba, the same problem, what is
wrong?

Thanks

          Followup:

http://asktom.oracle.com/~tkyte/article1/autotrace.html

      Rating: 4
as for set
      autotrace on  November 13, 2002
Reviewer:
      chao_ping  from china
run $ORACLE_HOME/sqlplus/admin/plustrce.sql as system.
run $ORACLE_HOME/rdbms/admin/utlxplan.sql as system.
Create public synonym plan_table for system.plan_table;
grant all on plan_table to public;
grant plustrace  to public;
I think this will help.

And for sysdba user login, autotrace won't display anything.

      Rating: 4
and for
      sysdba  November 13, 2002
Reviewer:
      chao_ping  from china
I think checking v$sesstat will give a better view of whether a user is login as
sys or sysdba.

      Rating: 4
Oops,
      sorry  November 13, 2002
Reviewer:
      chao_ping  from china
should be session_privs;
sorry.

          Followup:

that would be the answer to "how do i tell if I am logged in as sysdba"

if the question is "who CAN log in as sysdba...." it wouldn't work ;)

      Rating: 4
regarding magic
      'sysdba '  March 03, 2004
Reviewer:  reader
       from nj, usa
Tom

I created a user abc & gave connect & resource grants.

I was astonished to see that with this user I can issue a command as
connect / as sysdba

I thought I am creating just a user...
Without admin privs.

using 9.0.2 oracle on NT

          Followup:

your OS account is the one with the privs -- not the database user.

You connected with "/" which says "please use the OS to authenticate me"

March 03, 2004 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

      Rating: 5
SQL_TRACE in
      DEDICATED Server & Connection Pool  January 22, 2004

Reviewer:  Sami  from NJ,USA
Dear Tom,

<asktom>
Using SQL_TRACE with MTS is virtually useless.  The trace data gets written all
over the place.
</asktom>

What is the idle world scenario to address this issue?

How about connection pool in dedicated server env? Is there a better way to
collect the trace for "user interested actions"?

Ours is a web-based environment where multiple connection pools and huge number
of connections for each pool. We are trying to improve specific "User Targeted
Actions".

If I turn on SQL_TRACE at instance level, it will generate vast amount of trace
log and not easy to figure out the problem. Also I don't want to run the SQL
statements separately. It should come from Application Server
so that it can simulate real time.

Anticipating your valuable commets

          Followup:

the idle world?

      Rating: 5
Sorry Tom,
      Spelling mistake "IDEAL", not idle  January 22, 2004

Reviewer:  A reader

          Followup:

with a connection pool - there is "no ideal" approach.

If I wanted to trace a connection pooled application (and I also do), I would

a) make it so the application helped me (eg: developers have to think about how
their code will be maintained in the long haul -- tuned, diagnosed, etc)

b) when in "trace mode", the application would grab AND KEEP its connection
during the period of time it was tracing.

c) when done tracing the application would OPTIMALLY close the physical
connection -- so as to close the trace file.

so, unless the application is will to help, it is near impossible to trace in 9i
and before.

      Rating: 5
Extended Trace
      for other session  January 26, 2004
Reviewer:
      Sami  from NJ,USA
Dear Tom,
<AskTom>
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.
</AskTom>

How do I enable EXTENDED TRACE (using dbms_system or some other way)?

One way I can use Logon Trigger and in the trigger body
"alter session set events '10046 trace name context forever, level 12';"

But the user is already connected to the database and business(end user) does
not want to bring down the application and/or relogin?

Is there any other way to accomplish the same?

Your help/support is always appreciated.

          Followup:

execute sys.dbms_system.set_ev(46, 13217, 10046, 12, '');

would be a way to set the 10046 event in that session with level 12.

      Rating: 5
Thanks Tom.
      Impressed with your response time!  January 26, 2004

Reviewer:  Sami

      Rating: 4
So do we get
      some help in 10g ?  March 03, 2004
Reviewer:
      Neil
Tom

you said

"so, unless the application is will to help, it is near impossible to trace in
9i and before. "

Does this imply there is something that will help in 10g, and if so, what is it
?

Regards

Nil

          Followup:

http://download-west.oracle.com/docs/cd/B12037_01/server.101/b10752/sqltrace.htm#20113

      Rating: 4
how about
      dbms_support  March 03, 2004
Reviewer:  eric
      givler  from harrisburg, pa
We use the dbms_support package that is supplied with 8i and it provides a trace
with binds that you can easily read in the trace file.

If I want to trace a front end application, like reports or forms, I'll put a
dbms_application_info.set_module() call in startup.  Then in TOAD session
monitor, I'll find the sid/serial# of that session (or via script), jump to toad
sql editor and issue a start trace, run the form, and then stop trace.  Tom's
trace files script shows where it is at and what it is named.

For reports, since it is a secondary connection, I grab that session's info, and
run the calls right inside the report.  Generally, all I ever want is the EXACT
sql statement that was run, and what the binds were, and this works well in 8i.

Also, we've added a trace option in our menu to allow the user to exec a trace
on/off from inside the app.

March 03, 2004 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)

      Rating:
      5
  March 02, 2004
Reviewer:  learning
      every day  from usa
We are trying to create oracle 9i  database in a machine where we will have 11
logical disks .All of them are in RAID1 .As all are mirrored ,actually in total
there will be 22 (11 * 2)disks.Also there are 3 array controllers.
1st array controller has 1 disk
2nd array controller has 5 disk
3rd array controller has 5 disk
And we have 2 databases to migrate to this machine with 11 disks in hand. How we
should distribute datafiles,undo datafile ,temp datafiles,online redolog
files,archive logs,control files etc for the 2 9i oracle database ?Any
recomendation on the physical design so that we can avoid all kind of contention
(i/o ,process etc).
Thanks!

          Followup:

SAME.... strip and mirror everything....

It would be really hard to say.  11 devices isn't very many and I don't know the
size/activity of the various databases.  I'd just like a nice big striped system
I guess and let the hardware do the work.

      Rating:
      5
thank!  March 03, 2004
Reviewer:
      A reader
Currently all 11 disks are in RAID1.
Are you suggesting to cnvert all of them to RAID01 (or RAID10) instead?

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:359617936136#3618225060708

March 03, 2004 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"

      Rating: 5
connect as
      sysdba  February 04, 2004
Reviewer:  David
       from Dallas, TX
Hi Tom,

connect as sysdba is confusing me. My OS is Solaris8. My OS user account is in
dba group.  We are using Oracle9i Enterprise Edition Release 9.2.0.4.0 - 64bit
Production
JServer Release 9.2.0.4.0 - Production

Here is my problem:

I connect as sysdba,
Oracle asked me Enter user-name: and I typed sys
Oracle asked me to Enter password: I entered whatever I liked to enter and
Oracle let me in.

Here is my question:

Why Oracle asked user name and password but not really care about what the
password I entered?

Example:

dli@test> connect as sysdba
Enter user-name: sys
Enter password:
Connected.
dli@test> show user
USER is "SYS"

Best Regards,

David

          Followup:

Oracle isn't asking.

sqlplus is

ops$tkyte@ORA10G> connect as sysdba
Enter user-name: foobar
Enter password:
Connected.
sys@ORA10G> show user
USER is "SYS"

I do not have a foobar user -- you are just making sqlplus "happy".  You don't
need a username/password to connect locally "as sysdba" but sqlplus doesn't
realize that.

connecting as sysdba always means you are SYS, just as connecting as SYSOPER
always means you are PUBLIC.

      Rating: 5
question reg
      sysdba  March 03, 2004
Reviewer:  reader
       from nj , usa
I am on NT

If I create user & give connect grant , I can still connect to oracle 'as
sysdba'

Is this b'cos I am using NT ??

How will be the behaviour on Unix ??

          Followup:

it is because of the OS account you are using, nothing to do with any existing
Oracle database accounts (please see the other place you asked this same thing)

March 03, 2004 Famy -- Thanks for the question regarding "number
      formatting and how to display the primary key constraint name, primary key
      columns and also the foreign key names and columns.", version 8.1.6

      You Asked
Tom

1.I have a varchar2 column which can have output like

$2,500.00 or
$25,654.00 or
$2,23,345.00

this can go up to billions.

Now this is a varchar2 column, I want to convert it to a number, and format it
with the format 999,999 , there should be no  .00.

When Iam trying to do that Iam getting a problem due to the $ sign and also the
commas.

Can you help me in this regard.

2.For a given table , I want to display the primary key constraint name, primary
key columns and also the foreign key names  and columns ( and the tables they
come from) relating to the
respective primary keys. Can you help me in showing how to retrieve this data.

Thank you

      and we said...
ops$tkyte@ORA9I.WORLD> select x,
  2             to_number( x, '$999,999,999,999.99' ) x_num,
  3             to_char(to_number( x, '$999,999,999,999.99' ),'999,999,999')
x_num
  4    from t;

X                         X_NUM X_NUM
-------------------- ---------- ------------
$2,500.00                  2500        2,500
$25,654.00                25654       25,654
$2,321,345.00           2321345    2,321,345

Either or of the following will do it (add more max(decodes as needed)

ops$tkyte@ORA9I.WORLD> create table par ( x int, y int, constraint p_pk primary
key(x,y) );

Table created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> create table c1 ( a int, b int, constraint c1_fk foreign
key(a,b) references par );

Table created.

ops$tkyte@ORA9I.WORLD> create table c2 ( l int, m int, constraint c2_fk foreign
key(l,m) references par );

Table created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> select a.constraint_name, decode( a.constraint_type, 'P',
'primary', 'R', 'foreign' ), b.column_name
  2    from user_constraints a, user_cons_columns b
  3   where
  4     (( a.constraint_type = 'P' and a.table_name = 'PAR' )
  5      or
  6      ( a.constraint_type = 'R' and a.r_constraint_name  =
  7                     ( select constraint_name
  8                         from user_constraints
  9                        where constraint_type = 'P'
 10                          and table_name = 'PAR' )
 11      ) )
 12     and a.constraint_name = b.constraint_name
 13   order by a.constraint_type, a.constraint_name, b.position
 14  /

CONSTRAINT_NAME                DECODE( COLUMN_NAM
------------------------------ ------- ----------
P_PK                           primary X
P_PK                           primary Y
C1_FK                          foreign A
C1_FK                          foreign B
C2_FK                          foreign L
C2_FK                          foreign M

6 rows selected.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> select constraint_name, constraint_type,
  2         rtrim(
  3         max(decode(position,1,constraint_name,null)) || ','||
  4         max(decode(position,2,constraint_name,null)) || ','||
  5         max(decode(position,3,constraint_name,null)) || ','||
  6         max(decode(position,4,constraint_name,null)) || ','||
  7         max(decode(position,5,constraint_name,null)), ',' ) columns
  8    from (
  9  select a.constraint_name,
 10         a.constraint_type,
 11         b.column_name,
 12         b.position
 13    from user_constraints a, user_cons_columns b
 14   where
 15     (( a.constraint_type = 'P' and a.table_name = 'PAR' )
 16      or
 17      ( a.constraint_type = 'R' and a.r_constraint_name  =
 18                      ( select constraint_name
 19                          from user_constraints
 20                         where constraint_type = 'P'
 21                           and table_name = 'PAR' )
 22      ) )
 23     and a.constraint_name = b.constraint_name
 24     )
 25  group by constraint_name, constraint_type
 26  order by 2, 1
 27  /

CONSTRAINT_NAME                C COLUMNS
------------------------------ - ------------------------------
P_PK                           P P_PK,P_PK
C1_FK                          R C1_FK,C1_FK
C2_FK                          R C2_FK,C2_FK

ops$tkyte@ORA9I.WORLD>

Review &
      Followup

      Rating: 5
Well... the
      transposing you are doing...  April 04, 2002

Reviewer:  Famy
in the second query will be more beneficial if you use the column_name, instead
of constraint_name.. just think about it

in your case the same constraint name is printed multiple number of times. where
as if we us the column_name , we can see the different column names in that
specific foreign key...

what do you say..

my two cents

          Followup:

read it again - thats what I say.

The "second" query (assuming you mean the first query of the constraint
question) shows the constraint name (important and needed) as well as the column
name (equally important and relevant).

The "third" query shows the same data as the first, just in a different format.
they both show the same AMOUNT and type of information.

      Rating: 5
Was working on
      something similar ;)  April 04, 2002
Reviewer:
      Yogeeraj  from Mauritius
Hi,

oops! Should it not have been (column_name instead of constraint name):
===========================================================
select constraint_name, constraint_type,
    rtrim(
    max(decode(position,1,column_name,null)) || ','||
    max(decode(position,2,column_name,null)) || ','||
    max(decode(position,3,column_name,null)) || ','||
    max(decode(position,4,column_name,null)) || ','||
    max(decode(position,5,column_name,null)), ',' ) columns
===========================================================

I tried the same query as a user with dba roles. It just hangs!! why?

When i replace user_xxx with dba_xxx and add the OWNER clause, it works fine!!

===========================================================
swprod@CMTDB.CMT.MU> r
  1  select constraint_name, constraint_type,
  2      substr(rtrim(
  3      max(decode(position,1,column_name,null)) || ','||
  4      max(decode(position,2,column_name,null)) || ','||
  5      max(decode(position,3,column_name,null)) || ','||
  6      max(decode(position,4,column_name,null)) || ','||
  7      max(decode(position,5,column_name,null)), ',' ),1,45) columns
  8     from (
  9   select a.constraint_name,
 10          a.constraint_type,
 11          b.column_name,
 12          b.position
 13     from dba_constraints a, dba_cons_columns b
 14    where
 15       (( a.constraint_type = 'P' and a.table_name = 'KNT100' )
 16       or
 17        ( a.constraint_type = 'R' and a.r_constraint_name  =
 18                       ( select constraint_name
 19                           from dba_constraints
 20                          where constraint_type = 'P'
 21                            and table_name = 'KNT100'
 22                       and owner='SWPROD')
 23       ) )
 24      and a.constraint_name = b.constraint_name
 25      and a.owner='SWPROD'
 26      and b.owner='SWPROD'
 27      )
 28  group by constraint_name, constraint_type
 29* order by 2, 1

CONSTRAINT_NAME                C COLUMNS
______________________________ _ _____________________________________________
PK_KNT100                      P ORDNUM,FABSEQ,KMCHCOD,SEQNUM
FK_KNT120_1                    R ORDNUM,FABSEQ,KMCHCOD,SEQNUM

Elapsed: 00:00:00.48
swprod@CMTDB.CMT.MU>

===========================================================

Thanks for your time.

best regards
Yogeeraj
PS. Will your next book include something about BACKUP and Recovery? How to
migrate to Oracle's Backup and Recovery Manager for Database backup - "Expert
one-on-one Oracle" (Page 340)? (the gist of "Oracle 8i Operating System Backup
and Recovery Guide ;) ) The way you present your material is simple and clear!!
(30mb/30MB/30mB/30Mb or K,k - scientist look at it differently ;))

          Followup:

It does not just hang -- its that without the "and owner = " on DBA_* views, the
query is going to be somewhat inefficient.

The "primary key" of user_constraints is constraint_name
The "primary key" of dba_constraints is constraint_name, owner

When you changed views, in order to not only get the answer fast but in fact to
get the RIGHT ANSWER you have to put OWNER on all references.

I'm not planning on a backup and recovery book -- looking at a performance
related one :)

      Rating: 5
Much
      better.......  April 05, 2002
Reviewer:
      Yogeeraj  from Mauritius
hi again,

Much better now:
============================================================
swprod@CMTDB.CMT.MU> r
  1  select substr(constraint_name,1,15) "Constrait Name", constraint_type,
  2      substr(rtrim(
  3      max(decode(position,1,column_name,null)) || ','||
  4      max(decode(position,2,column_name,null)) || ','||
  5      max(decode(position,3,column_name,null)) || ','||
  6      max(decode(position,4,column_name,null)) || ','||
  7      max(decode(position,5,column_name,null)), ',' ),1,45) columns, substr(r
_constraint_name,1,15) "References"
  8     from (
  9   select a.constraint_name,
 10          a.constraint_type,
 11          b.column_name,
 12          b.position,
 13     a.r_constraint_name
 14     from dba_constraints a, dba_cons_columns b
 15    where
 16       (( a.constraint_type = 'P' and a.table_name = 'KNT100' )
 17       or
 18        ( a.constraint_type = 'R' and a.r_constraint_name  =
 19                       ( select constraint_name
 20                           from dba_constraints
 21                          where constraint_type = 'P'
 22                            and table_name = 'KNT100'
 23                       and owner='SWPROD')
 24       ) )
 25      and a.constraint_name = b.constraint_name
 26      and a.owner='SWPROD'
 27      and b.owner='SWPROD'
 28      )
 29  group by constraint_name, constraint_type, r_constraint_name
 30* order by 2, 1

Constrait Name  C COLUMNS                                       References
_______________ _ _____________________________________________ _______________
PK_KNT100       P ORDNUM,FABSEQ,KMCHCOD,SEQNUM
FK_KNT120_1     R ORDNUM,FABSEQ,KMCHCOD,SEQNUM                  PK_KNT100

Elapsed: 00:00:00.86
swprod@CMTDB.CMT.MU>
============================================================
Cheers
Yogeeraj

      Rating: 5
These queries
      are very useful  April 05, 2002
Reviewer:
      Kulguru
Tom

If I want to put these scripts into a sql script, how should I add the set
conditions along with the script, so that whenever I execute the script those
settings take effect. Can you show an example with one of the above queries.

Further, the above (last two) queries are awesome, and will give quick details
as far as constraints are concerned. Tom, is it possible to find out what is the
kind of relationship between 2 given tables, i.e. 1 to 1 or 1 to many or many to
one , or many to many.

Do the above scripts help in that aspect in any way, or do you have another sql
to do this kind thing, or should it be done using pl/sql.

Can you kindly show us the way, in this regard.

Thank you

          Followup:

Just change 'PAR' to '&1'

Then put the sql into a script and run it as:

@scriptname PAR

and that would do the same -- &1, &2, ... those are substition values passed to
the script.

If the relationship is 1:1 -- there should be ONE table (IMHO).  But anyway, you
would have to see if the foreign key columns are also involved in a Unique or
Primary key constraint on that table -- so yes ,you can find 1-1 if you write a
tricky enough query.  1:M -- that should be assumed.  M:M -- you'll NEVER see
that as that takes three tables and two 1:M relationships -- there is no such
thing as a M:M relationship really....

You can write the 1:1 vs 1:M in a sql query using subtle modifications of the
above (a challenge for the reader).  But as I said -- its probably just a waste
of time as ALL of them should be 1:M.

      Rating: 5
..everything in
      a script  April 05, 2002
Reviewer:  Yogeeraj
       from Mauritius
hi

hope this helps..

Regards
Yogeeraj
============================================================
--check_pffkcons.sql
--YD-05/04/2002
SET VERIFY OFF;
SET DEFINE ON;
define MOWNER = '&MOWNER'
define MTABLE = '&MTABLE'
select substr(constraint_name,1,15) "Constrait Name", constraint_type,
     substr(rtrim(
     max(decode(position,1,column_name,null)) || ','||
     max(decode(position,2,column_name,null)) || ','||
     max(decode(position,3,column_name,null)) || ','||
     max(decode(position,4,column_name,null)) || ','||
     max(decode(position,5,column_name,null)), ',' ),1,45) columns,
substr(r_constraint_name,1,10) "References", substr(status,1,3) Sts
    from (
  select a.constraint_name,
         a.constraint_type,
         b.column_name,
         b.position,
    a.r_constraint_name,
    a.status
    from dba_constraints a, dba_cons_columns b
   where
      (( a.constraint_type = 'P' and a.table_name = '&&MTABLE' )
      or
       ( a.constraint_type = 'R' and a.r_constraint_name  =
                      ( select constraint_name
                          from dba_constraints
                         where constraint_type = 'P'
                           and table_name = '&&MTABLE'
                      and owner= '&&MOWNER')
      ) )
     and a.constraint_name = b.constraint_name
     and a.owner='&&MOWNER'
     and b.owner='&&MOWNER'
     )
 group by constraint_name, constraint_type, r_constraint_name, status
order by 2, 1;
SET DEFINE OFF;
============================================================

      Rating:
      5
Tom  April 05, 2002
Reviewer:  Famy

"
If the relationship is 1:1 -- there should be ONE table (IMHO).  But anyway, you
would have to see if the foreign key columns are also involved in a Unique or
Primary key constraint on that table -- so yes ,you can find 1-1 if you write a
tricky enough query.  1:M -- that should be assumed."

Can you gift us a utility which does the above.

From the above what do you mean by '1:M -- that should be assumed.'
Thank you

          Followup:

Ah, come on... Can't you take up the challenge?

...
You can write the 1:1 vs 1:M in a sql query using subtle modifications of the
above (a challenge for the reader).  But as I said -- its probably just a
waste of time as ALL of them should be 1:M.
.......

;)

When I said 1:M should be assumed is that

a) M:M -- doesn't exist, we can rule that out
b) 1:1 -- shouldn't exist IMO, I rule that out
c) 1:M -- thats all that is left over!

I know I can do it -- just wanna have others think about it (SQL queries like
that can be fun sometimes!)

      Rating: 4
how should we
      determine  March 01, 2004
Reviewer:
      A reader
tom

how should we determine how many 9's to format with (999,999,999,999.99' ), and
how we cover for negative numbers.

Sometimes my field can return just zero , how should I cover for that.

Thanks

          Followup:

you have to answer that first question -- you know the data, no me :)

negative numbers will just take care of themselves. they will plop a "-" in
there.

you have to decide what you want to do for zero as well.

the bottom line is -- pretty much anything you can think of, the formats can do.
 so, what do you want to have happen

      Rating:
      5
  March 03, 2004
Reviewer:
      A reader
What does the following format decipher to .. what is the max number this format
can support

-NNNGNNNGNN0

I want the negative numbers in brackets, how should I achieve that

          Followup:

see

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96540/sql_elements4a.htm#34597
that will decipher to

ERROR at line 1:
ORA-01481: invalid number format model

(and to get brackets, use PR -- but see the table above, it shows you your
options)

March 03, 2004 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!!!

      Rating: 4
Fishing out a
      row as of a specific date  January 15, 2004

Reviewer:  Michael  from Allentown, PA
I have a mock audit table based on the hr.employee. I copied all the data from
employee into employee_aud and added some columns for sys_tm, os_usr, operation
(update,etc). I have been using some of your advice in other threads to organize
the data using the lag function. Still learning on those analytics. Now, I have
a different problem. One of the requirements is to pull a row out of the audit
table for a specific PK (in my test case, employee_id) showing the data as of a
given date, even if there is no sys_tm that matches it. We cannot use WM for
versioning, due to unique constraints not PKs. So if I have a number of updates
for a given employee_id (108) where sys_tm dates are 01-JAN-98, 12-FEB-98,
30-JUN-98, and
26-SEP-98. For each update, the triggers populate all of the columns in the
audit table: if the column did not change, it has the old value, if it did
change, the new value. Someone wants to know what the data looked like on
15-MAY-98. I have been trying to tweak the logic above but I keep returning all
of the rows that match those 4 dates.
Here is my latest attempt, but it still returns all 4 dates listed above, not
just the most recent one preceding the 15-MAY-98 date.

select * from (select employee_aud.*, max(sys_tm) over (partition by sys_tm)
asofdate
from employee_aud) where sys_tm <= '30-JUN-99'
and employee_id = 108;

What I am trying to get is the row with the sys_tm 12-FEB-98. That would
represent what the data for that row looked like on 30-JUN-98, because there
were no updates in between.

          Followup:

select *
  from ( select *
           from employee_aud
          where sys_tm <= TO_DATE( :bv, 'dd-mon-yyyy' )
          order by sys_tm DESC )
 where rownum = 1;

you want the max record such that the sys_tm is less than or equal to the date
in question -- ordering and rownum can do that.

comments:

o compare dates to dates, strings to strings, numbers to numbers -- never
anything else

o use 4 digits for a year.

      Rating: 5
Works
      Great!  January 16, 2004
Reviewer:  Michael
       from Allentown, PA
Your "Expert One on One" book is on order. (I should have ordered it when it
came out!).

          Followup:

small world -- i grew up in bethlehem pa ;)  not too far from you.

      Rating:
      5
  January 16, 2004
Reviewer:
      A reader
Yeps ... And don't forget to order EOBD once you have finished the first one :-)

      Rating:
      5
  January 16, 2004
Reviewer:
      A reader
It looks as if Tom's missing home !!!!

      Rating: 5
group
      partitions  February 03, 2004
Reviewer:
      A reader
Hi

let´s say I have data like this

deptno     empno
-------    --------
10         1001
10         1002
20         2003
20         2004

how can I obtain a result such as

deptno     empno      numrow
-------    --------   --------
10         1001       1
10         1002       1
20         2003       2
20         2004       2

partition by deptno but I dont know which function to use!

cheers

          Followup:

You cannot really with analytics -- you already have a nice "partition" column
to group by and what-not (deptno).

You could use regular old sql

scott@ORA10G> select emp.deptno, emp.empno, a.rn
  2    from emp, ( select deptno, rn
  3                  from ( select deptno, rownum rn
  4                           from ( select distinct deptno
  5                                    from emp
  6                                   order by deptno )
  7                       )
  8              ) a
  9   where     emp.deptno = a.deptno
 10   order by rn, empno
 11  /

    DEPTNO      EMPNO         RN
---------- ---------- ----------
        10       7782          1
        10       7839          1
        10       7934          1
        20       7369          2
        20       7566          2
        20       7788          2
        20       7876          2
        20       7902          2
        30       7499          3
        30       7521          3
        30       7654          3
        30       7698          3
        30       7844          3
        30       7900          3

14 rows selected.

      Rating: 4
What
      about?  February 03, 2004
Reviewer:
      A reader
SQL> select deptno, ename, dense_rank() over (order by deptno);

    DEPTNO ENAME      DENSE_RANK()OVER(ORDERBYDEPTNO)
---------- ---------- -------------------------------
        10 CLARK                                    1
        10 KING                                     1
        10 MILLER                                   1
        20 SMITH                                    2
        20 ADAMS                                    2
        20 FORD                                     2
        20 SCOTT                                    2
        20 JONES                                    2
        30 ALLEN                                    3
        30 BLAKE                                    3
        30 MARTIN                                   3
        30 JAMES                                    3
        30 TURNER                                   3
        30 WARD                                     3

          Followup:

doh, very good -- very good :)

I was thinking about it the wrong way......  that does it for sure.

      Rating:
      5
Query  February 07, 2004
Reviewer:  A
      reader
Tom,

Could you please show me how to do this.

table C1
--------
cust_no    acct_no    type    balance

11    111    A    500
22    222    A    100

table C2
--------
cust_no    acct_no    type    balance

11    1111    B    50
22    2222    B    60

table C3
---------
cust_no    acct_no    type    balance

33    888    C    100
11    333    C    400

table C4 (resultant) table
I want to generate another table which will be the union of tables C1, C2, C3
but the derived_balance field will be as per the following

if Type is either 'A' or 'B' then add the balance else if type is 'C' then
subtract
it and derived a resultant balance for each cust_no. Rest of the fields will
just be the unions.

cust_no    acct_no    Type    balance        derived_balance
11    111    A        500        500+50-400
11    1111    B        50        500+50-400
11    333    C        400        500+50-400

22    222    A        100        100+60
22    2222    B        60        100+60

33    888    C        400        0+0-400

Thanks very much,

          Followup:

ops$tkyte@ORA9IR2> select cust_no, acct_no, type, balance,
  2         sum(xbalance) over (partition by cust_no) derived_balance
  3    from ( select c1.*, balance xbalance from c1
  4           union all
  5                   select c2.*, balance xbalance from c2
  6                   union  all
  7                   select c3.*, -1 * balance xbalance from c3
  8             )
  9  order by cust_no
 10  /

   CUST_NO    ACCT_NO T    BALANCE DERIVED_BALANCE
---------- ---------- - ---------- ---------------
        11        111 a        500             150
        11        111 b         50             150
        11        333 c        400             150
        22        222 a        100             160
        22        222 b         60             160
        33        888 c        100            -100

6 rows selected.

(you have a typo in your example output -- 33/889 is -100)

      Rating: 5
Can Analytics
      Help me ?  February 14, 2004
Reviewer:  Riaz
      Shahid  from PRAL, Lahore, Pakistan
Consider:

SQL> select * From test;

         A          B C
========== ========== =
         1        100 Y
         2        200 Y
         3        200 Y
         4        300 Y
         5        100 N
         6        200 Y
         7        200 Y
         8        300 Y
         9        100 Y
        10        200 N
        11        200 Y
        12        300 Y
        12        300 Y

13 rows selected.

I have to choose all the records whose status = 'Y'
and display the following info.

A     B     Count

 1    4     4
 6    9     4
11   12     2

Regards

          Followup:

ops$tkyte@ORA9IR2> select min(a), max(a), count(*)
  2    from (
  3  select a, c, max(rn) over (order by a) grp
  4    from (
  5  select a, c,
  6         case when c = 'N' and lag(c) over (order by a) = 'Y'
  7                  then row_number() over (order by a)
  8          end rn
  9    from t
 10         )
 11   where c = 'Y' or rn is not null
 12         )
 13   where c = 'Y'
 14   group by grp
 15   order by grp nulls first
 16  /

    MIN(A)     MAX(A)   COUNT(*)
---------- ---------- ----------
         1          4          4
         6          9          4
        11         12          3

Analytics rock...
Analytics roll....

(in order to figure this thingy out -- please run the queries from the inside
out to see how they "build" on one another...)

      Rating: 1
Little
      Correction...  February 14, 2004
Reviewer:  Riaz
      Shahid  from PRAL, Lahore, Pakistan
I need output like:

I have to choose all the records whose status = 'Y'
and display the following info.

MinA  MaxA  Count

 1    4     4
 6    9     4
11   12     2

Where MinA & MaxA are values for column A with an interval of 3 (if
possible,else the interval will be according to the last value, as shown in the
last row of output)

      Rating: 5
Excellent ! ! !
        February 16, 2004
Reviewer:  Riaz Shahid
       from PRAL, Lahore, Pakistan
Thats just brilliant. I was wondering from where you get )simple) ideas to solve
difficult (looking) problems.

Thanks a lot

      Rating: 3
Counting the
      number of occurances  February 17, 2004
Reviewer:
      A reader
Perhaps this is a new question but...

I have a report to write that I thought at first would be very straight forward.
but I'm actually completely stumped.  I *guess* that analytic functions will do
me grand but I can't fathom how to do it.

The base table looks like...

IDENTIFIER             NUMBER,
ACCOMMODATION_COMP_PLANNED    DATE,
ACCOMMODATION_COMP_FORECAST    DATE,
BASE_BUILT_PLANNED        DATE,
BASE_BUILT_FORECAST        DATE,
BUILT_POWER_RIGGED_PLANNED    DATE,
BUILT_POWER_RIGGED_FORECAST    DATE,
DAS_COMMISSION_PLANNED        DATE,
DAS_COMMISSION_FORECAST        DATE,
DETAIL_DESIGN_APP_PLANNED    DATE,
DETAIL_DESIGN_APP_FORECAST    DATE,

(In actual fact I have 19 of these date "pairs" but I figure these few is enough
for demostration purposes)

It's also worth pointing out that some of these dates may be null (as that
particular identifier does not do that activity)

The output I'm looking for is (Excuse the column name chopping)...

DATE    ACCOM_PLANNED    ACCOM_FCAST, BASE_PLANNED, BASE_FCAST, BUILT_PLANNED,
BUILT_FCAST, DAS_PLANNED, DAS_FCAST, DETAIL_PLANNED, DETAIL_FCAST, ...
JAN 99       23               24           40          42           150
  140         120          90          130             190
FEB 99     34               34           50          50            43
 54          34          54           45              45
...

So I want to count the number of occurances by month,  I thought a whole load of
count(*) over (partion by to_char(column,'MON-YY') would do it but to no avail
(I get lots of dups) I'm completely confussed as to how I'd structure the SQL to
do this, in the end I hope to turn this into a view so it can just be
selected...

Thanks,

Mike.

          Followup:

well, couple of things - first, we need a set of dates to report on.  you can
either query the table to determine what the min/max dates could be -- or
generate a set of dates.

I'm going to generate a set of dates, starting with this sample data:

DOC>create table t ( id number, ap date, af date, bp date, bf date, cp date, cf
date);
DOC>
DOC>insert into t
DOC>select rownum,
DOC>       add_months( trunc(sysdate,'y'), dbms_random.value( 0, 12 ) ),
DOC>       add_months( trunc(sysdate,'y'), dbms_random.value( 0, 12 ) ),
DOC>       add_months( trunc(sysdate,'y'), dbms_random.value( 0, 12 ) ),
DOC>       add_months( trunc(sysdate,'y'), dbms_random.value( 0, 12 ) ),
DOC>       add_months( trunc(sysdate,'y'), dbms_random.value( 0, 12 ) ),
DOC>       add_months( trunc(sysdate,'y'), dbms_random.value( 0, 12 ) )
DOC>  from all_objects;
DOC> analyze table t compute statistics for table;
DOC>*/

ops$tkyte@ORA920PC> select add_months( to_date('01-jan-2004'), rownum-1 ) dt
  2    from all_objects
  3   where rownum <= 12
  4  /

DT
---------
01-JAN-04
01-FEB-04
01-MAR-04
01-APR-04
01-MAY-04
01-JUN-04
01-JUL-04
01-AUG-04
01-SEP-04
01-OCT-04
01-NOV-04
01-DEC-04

12 rows selected.

we'll generate the months for 2004 and then we can:

ops$tkyte@ORA920PC> select dt,
  2         sum(decode(trunc(ap,'mm'),dt,1)) ap,
  3         sum(decode(trunc(af,'mm'),dt,1)) af,
  4         sum(decode(trunc(bp,'mm'),dt,1)) bp,
  5         sum(decode(trunc(bf,'mm'),dt,1)) bf,
  6         sum(decode(trunc(cp,'mm'),dt,1)) cp,
  7         sum(decode(trunc(cf,'mm'),dt,1)) cfA
  8    from (
  9  select add_months( to_date('01-jan-2004'), rownum-1 ) dt
 10    from all_objects
 11   where rownum <= 12
 12         ),
 13             t
 14   group by dt
 15  /

DT                AP         AF         BP         BF         CP        CFA
--------- ---------- ---------- ---------- ---------- ---------- ----------
01-JAN-04       2521       2489       2545       2445       2472       2566
01-FEB-04       2529       2462       2493       2547       2522       2540
01-MAR-04       2513       2587       2427       2493       2569       2460
01-APR-04       2544       2642       2531       2528       2534       2465
01-MAY-04       2461       2500       2530       2556       2514       2559
01-JUN-04       2527       2482       2557       2526       2546       2517
01-JUL-04       2548       2528       2502       2471       2599       2519
01-AUG-04       2548       2561       2508       2524       2558       2552
01-SEP-04       2556       2453       2528       2582       2422       2466
01-OCT-04       2447       2510       2455       2507       2460       2483
01-NOV-04       2570       2465       2568       2549       2557       2530
01-DEC-04       2486       2571       2606       2522       2497       2593

12 rows selected.

Yes, that is a cartesian join -- but it needs to be since each row in T might
need to be joined to EVERY row in the set of generated dates.

or, you can take this approach:

ops$tkyte@ORA920PC> select dt, sum(ap), sum(af), sum(bp), sum(bf), sum(cp),
sum(cf)
  2    from (
  3  select trunc(ap,'mm') dt, count(*) ap, 0 af, 0 bp, 0 bf, 0 cp, 0 cf from t
group by trunc(ap,'mm') union all
  4  select trunc(af,'mm') dt, 0, count(*), 0, 0, 0, 0 from t group by
trunc(af,'mm') union all
  5  select trunc(bp,'mm') dt, 0, 0, count(*), 0, 0, 0 from t group by
trunc(bp,'mm') union all
  6  select trunc(bf,'mm') dt, 0, 0, 0, count(*), 0, 0 from t group by
trunc(bf,'mm') union all
  7  select trunc(cp,'mm') dt, 0, 0, 0, 0, count(*), 0 from t group by
trunc(cp,'mm') union all
  8  select trunc(cf,'mm') dt, 0, 0, 0, 0, 0, count(*) from t group by
trunc(cf,'mm')
  9         )
 10   group by dt
 11  /

DT           SUM(AP)    SUM(AF)    SUM(BP)    SUM(BF)    SUM(CP)    SUM(CF)
--------- ---------- ---------- ---------- ---------- ---------- ----------
01-JAN-04       2521       2489       2545       2445       2472       2566
01-FEB-04       2529       2462       2493       2547       2522       2540
01-MAR-04       2513       2587       2427       2493       2569       2460
01-APR-04       2544       2642       2531       2528       2534       2465
01-MAY-04       2461       2500       2530       2556       2514       2559
01-JUN-04       2527       2482       2557       2526       2546       2517
01-JUL-04       2548       2528       2502       2471       2599       2519
01-AUG-04       2548       2561       2508       2524       2558       2552
01-SEP-04       2556       2453       2528       2582       2422       2466
01-OCT-04       2447       2510       2455       2507       2460       2483
01-NOV-04       2570       2465       2568       2549       2557       2530
01-DEC-04       2486       2571       2606       2522       2497       2593

12 rows selected.

      Rating: 3
Replacing
      attached code with Analytic Function?  March 02, 2004

Reviewer:  Denni50  from NA
Tom

I'm developing a step matrix report(as we call it, which
may be an incorrect terminology based on what I am trying
to achieve).

So far the logic behind what I am trying to achieve is
shown in the code below:

FUNCTION GetDollars(amt IN NUMBER) RETURN NUMBER IS
 AmtRange NUMBER:=0;

BEGIN
    CASE amt
      WHEN (BETWEEN .01 and 9.99) THEN AmtRange :=1;
      WHEN (BETWEEN 10 and 19.99) THEN AmtRange :=2;
      WHEN (BETWEEN 20 and 29.99) THEN AmtRange :=3;
      WHEN (BETWEEN 30 and 39.99) THEN AmtRange :=4;
      WHEN (BETWEEN 40 and 49.99) THEN AmtRange :=5;
      WHEN (BETWEEN 50 and 59.99) THEN AmtRange :=6;
      WHEN (BETWEEN 60 and 69.99) THEN AmtRange :=7;
      WHEN (BETWEEN 70 and 79.99) THEN AmtRange :=8;
      WHEN (BETWEEN 80 and 89.99) THEN AmtRange :=9;
      WHEN (BETWEEN 90 and 99.99) THEN AmtRange :=10;
      WHEN (BETWEEN 100 and 249.99)THEN AmtRange :=11;
      WHEN (BETWEEN 250 and 999999.99)THEN AmtRange :=12;
      ELSE     AmtRange:=13;    --if NULL
    END CASE;
    RETURN AmtRange;
END;

FUNCTION GetDates(dte IN DATE,dteComp IN DATE) RETURN NUMBER IS
 DteRange NUMBER:=0;
 monDiff  NUMBER:=0;
BEGIN
    monDiff:= MONTHS_BETWEEN(dteComp,dte);
    CASE monDiff
      WHEN (BETWEEN 0 and 3) THEN DteRange :=1;
      WHEN (BETWEEN 3 and 6) THEN DteRange :=2;
      WHEN (BETWEEN 6 and 12) THEN DteRange :=3;
      WHEN (BETWEEN 12 and 18) THEN DteRange :=4;
      WHEN (BETWEEN 18 and 24) THEN DteRange :=5;
      WHEN (BETWEEN 24 and 36) THEN DteRange :=6;
      WHEN (BETWEEN 36 and 48) THEN DteRange :=7;
      WHEN (BETWEEN 48 and 999999) THEN DteRange :=8;
      ELSE     DteRange:=9;    --if NULL
    END CASE;
    RETURN DteRange;
END;

I'm trying to achieve a grid like report where AmtRange
would represent the horizontal(Column portion)of the
report and the DteRange would represent the vertical(rows)
and results would be processed in 'buckets' accordingly.

my question is:
can this laborious process be better accomplished using
AF and if so..perhaps give an example to get me started
in the right direction.

thanks a bunch..

          Followup:

I hope you are not going to call these from SQL -- if so, I would move 100% of
the logic out of plsql and directly into a view.  You'll find it *many* times
faster.

You can definitely make it "smaller" with a simple divide - -but I do not see
analytics helping you here, they work across rows, this is just simple "take
this continous set of inputs and map them to this discrete set of outputs using
this method" (eg: a function)

      Rating: 4
thanks
      Tom...  March 03, 2004
Reviewer:  Denni50
       from NA
As I think about this more and more...initially I was
hoping AF could somehow perform this need more efficently
but as you explained AF would not work in this case.

Plans are to call this from a stored procedure.
The Functions I posted were just one part of the entire
procedure. Create a table where the output would be
inserted then create a report from that table.

Now I'm planning to install Oracle Developer Reports 6i
on my 9i db. I've worked with Forms and know you can call
PL/SQL procedures, triggers..etc

For right now I'm going to create a scaled down test
table with donors who have count(gftamt)=1 and
gftamt > 0.(we have nondonors who do not give but respond to campaigns,we keep
track of them for purposes of converting them into donors and assign them a
gftamt=0)
We have multi donors but for now I want to focus on single
donors.

Then get the procedure to insert how many donors
gave in the 0-3 month, .01-9.99 range
            0-3 month,  10-19.99 range
            0-3 month,  20-29.99 range...and on and on

which would look like:

       .01-9.99    10-19.99  20-29.99   30-39.99
0-3       xx         xx        xx          xx
3-6       xx         xx        xx          xx
6-12      xx         xx        xx          xx
...and so on

you mention something about using a *divide* routine
and using a *view*...could you expand on that.

          Followup:

the stored procedures are going to impose a huge cost on you.

I would URGE you to use views -- strongly URGE.

Instead of:

FUNCTION GetDates(dte IN DATE,dteComp IN DATE) RETURN NUMBER IS
 DteRange NUMBER:=0;
 monDiff  NUMBER:=0;
BEGIN
    monDiff:= MONTHS_BETWEEN(dteComp,dte);
    CASE monDiff
      WHEN (BETWEEN 0 and 3) THEN DteRange :=1;
      WHEN (BETWEEN 3 and 6) THEN DteRange :=2;
      WHEN (BETWEEN 6 and 12) THEN DteRange :=3;
      WHEN (BETWEEN 12 and 18) THEN DteRange :=4;
      WHEN (BETWEEN 18 and 24) THEN DteRange :=5;
      WHEN (BETWEEN 24 and 36) THEN DteRange :=6;
      WHEN (BETWEEN 36 and 48) THEN DteRange :=7;
      WHEN (BETWEEN 48 and 999999) THEN DteRange :=8;
      ELSE     DteRange:=9;    --if NULL
    END CASE;
    RETURN DteRange;
END;

and then

select dte, dteComp, getDates( dte, dteComp ) xxx from t;

you would:

create or replace view v
as
select dte, dteComp,  case
       when ( months_between(dte,dteComp) between 0 and 3) then 1
       when ( months_between(dte,dteComp) between 3 and 6) then 2
       when ( months_between(dte,dteComp) between 6 and 12) then 3
       when ( months_between(dte,dteComp) between 12 and 18) then 4
       when ( months_between(dte,dteComp) between 18 and 24) then 5
       when ( months_between(dte,dteComp) between 24 and 36) then 6
       when ( months_between(dte,dteComp) between 36 and 48) then 7
       when ( months_between(dte,dteComp) between 48 and 99999) then 8
       else 9
       end
 xxx
 from t;

and have your reports use V instead of T.

and by divide, i just meant that you could do some divisions on the numbers to
roll them up "shorter" but in a much less clear fashion.  I'd stick with case IN
a view.

      Rating: 5
THANKS!!!
      Tom  March 03, 2004
Reviewer:  Denni50
       from NA
I understand what you mean now...since Views are like
stored queries anyhow...thanks for the explanation and
example.

:~)

      Rating:
      4
anayltics  March 03, 2004
Reviewer:
      kit  from england
HI,

are you recommending the views to compliment your theory that if it can be done
by sql then use it opposed to plsql stored procs

Also can you please expand on analytics works across rows

          Followup:

I use views to hide complexity like the case above.  I would not want to code
the case statement for all 50 reports I need to generate (i made up the number
50, it is "whatever").

If you look at the data dictionary -- the ALL_, USER_, DBA_ views are littered
with this using DECODE()....

analytics work "across rows".  functions like "lag()" reach "back" in the result
set, "lead()" reach forward.

sum() over (partition by deptno)

sums OVER all of the records by deptno...

and so on -- analytics work "across" rows.  this function (above function) is
working soley on the data within a single row.

March 03, 2004 --
      Thanks for the question regarding "Indexes ", version 8.1.7.3.0

      You Asked
Hi Tom,
I always thought that when a function is applied on an indexed column, that will
atomatically avoid the used of that index.
Here I have an example of a query that originally was using the index_1 on
column_a because of the hint that the query had.
After a while someone modified the query and the only change was to add a nvl
funtion on columa_a. After this modification the explain plan changed and the
query started doing a full scan of the index_1.
What I don't understand is why the database is still using index_1, shouldn't
the database have avoided using index_1 because of the nvl, regardless of the
hint in the query for that index?

index_1: on column_a

query
select /*+ index (T1 index_1 ) */
    distinct T2.column_c,
    T2.column_d
from table_1 T1,
    table_2 T2
where nvl(T1.column_a,1)=1
and t1.column_c= t2.column_c
and t2.column_x=:b1;

Execution Plan
----------------------------------------------------------
  0      SELECT STATEMENT Optimizer=CHOOSE (Cost=231510 Card=1 Bytes=30)

  1    0   SORT (UNIQUE) (Cost=231510 Card=1 Bytes=30)
  2    1     MERGE JOIN (Cost=231507 Card=1 Bytes=30)
  3    2       SORT (JOIN) (Cost=183397 Card=260163 Bytes=5203260)
  4    3         TABLE ACCESS (BY INDEX ROWID) OF 'TABLE_1' (Cost=181648
Card=260163 Bytes=5203260)

  5    4           INDEX (FULL SCAN) OF 'INDEX_1' (UNIQUE) (Cost=99416
Card=260163)

  6    2       SORT (JOIN) (Cost=48111 Card=7539667 Bytes=75396670)
  7    6         INDEX (FAST FULL SCAN) OF 'PK_TABLE_2' (UNIQUE) (Cost=3108
Card=7539667 Bytes=75396670)

Thanks

      and we said...
Ahh - the joys of hints (hints are something we should avoid, not use).  You
might want to read:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6601251003901
for the future and get rid of those hints.

In any case -- the reason the index was used was because you TOLD us "please use
this index".  so, the optimizer found a way to use it -- as asked.

Indexes can be used in many ways.  Here the index was read and every row in it
was inspected.  Normally, if the nvl wasnt there -- it would have range scanned
-- here it FULL SCANNED the index, inspecting each and every entry in it --
performing the NVL on the indexed value to determine if it should keep the row
or not.

Great example of exactly why you DON'T want to use hints -- why they should be
the last choice.

Review &
      Followup

      Rating: 4
enforcing
      histogram usage  November 27, 2002
Reviewer:
      Scott  from Chicago, IL USA
I'm in a situation where I'm trying my damn'dest to do "the right" thing, but
the cbo won't help me out here, whether that's a limitation of my own of the
optimizer I'm still not sure.  I have a reasonably simple query:

SELECT a.code,
       c.category,
       count(*)
  FROM table_a  a,
       table_b  b,
       table_c  c
 WHERE a.code = 'qwerty'
   AND b.a_id = a.id   -- pk/fk join
   AND c.b_id = b.id   -- pk/fk join
 GROUP BY
       a.code,
       c.category;

Now table_a has a couple of dozen rows and table_b has a couple hundred.
However table_c has many million rows.  A given value of a.code will pretty much
always correspond to a dozen or so rows of table_b.  However, a given row of
table_b may correspond to millions of rows of table_c or a handful or anywhere
in between.

So I want the path to use the fk index between table_b & table_c when there are
only a handful of total rows corresponding to a.code, but not to waste time with
it otherwise.  My concern is that the fact that we have table_b in between a & c
may preclude the cbo from making it's best decision.  All pk & fk columns are
indexed.  The fk index on table_c is on (b_id,category) so the query doesn't
need to even bother with the table.  Here's my failed attempt to gather the
requisite data (the optimizer never uses the table_b/table_c index):

BEGIN

  dbms_stats.gather_table_stats(
    ownname => 'ME',
    tabname => 'TABLE_A',
    method_opt => 'FOR ALL INDEXED COLUMNS',
    cascade => TRUE );

  dbms_stats.gather_table_stats(
    ownname => 'ME',
    tabname => 'TABLE_B',
    method_opt => 'FOR ALL INDEXED COLUMNS',
    cascade => TRUE );

  dbms_stats.gather_table_stats(
    ownname => 'ME',
    tabname => 'TABLE_C',
    estimate_percent => 10,
    block_sample => TRUE,
    method_opt => 'FOR COLUMNS B_ID,CATEGORY' );

END;

Scott

          Followup:

CBO or otherwise -- i fail to see how any optimizer would be able to do this.

given a code in A we get a dozen rows.
this in turn will give us a dozen squared rows in AxB (144 say)

Now, we have 144 random b.id's -- one of which might want to use an index, one
of which might not want to use an index (b.id = 1 might get a dozen rows from C,
b.id = 2 might get 1 million rows from C)

I don't see what the cardinality of the set of rows from A has to do with
anything.  Suppose it was ALWAYS 2 rows from A.  You get a random two rows from
B.  The b.id values are NOT KNOWN until the query is already in the middle of
processing.  It is far too late to decide whether to use and inex or not for C
and further -- you might feel that row 1 should use an index and row 2 should
not.  A wholly impossible situation.

I fail to see how the optimizer could even begin to do what you are thinking --
it would have to change the plan IN THE MIDDLE of the plan - for each row.

Tell you what, I give you this query:

SELECT a.code,
       c.category,
       count(*)
  FROM table_a  a,
       table_b  b,
       table_c  c
 WHERE a.code = 'asdf'
   AND b.a_id = a.id   -- pk/fk join
   AND c.b_id = b.id   -- pk/fk join
 GROUP BY
       a.code,
       c.category;

You can have any piece of information the optimizer can have -- I'll also assure
you that

o 1 row will be returned from A
o this 1 row will be matched to 2 rows in B
  (this is inside info that the CBO doesn't get)

Now, should we use the index on C when accessing it?  If so, why.  If not, why
not?

      Rating:
      5
  November 27, 2002
Reviewer:
      A reader
Thanks for answering me tom.
I understand your point, but I am still confuse with some aspects.
You said <quote> In any case -- the reason the index was used was because you
TOLD us "please use this index".  so, the optimizer found a way to use it -- as
asked.  <quote>

Let me know if I am right with the following statement:"when a function is
applied on an indexed column, that will atomatically avoid the used of that
index."

So if my statement is correct and if I take into account that hints are
suggestions (they may or may not be used by the optimizer), if I combine this
two facts shouldn't the database avoid to use the index in that case. Why did
the optimizer try to find a way to use the index when a function was applied in
the column of the index? Shouldn't be the nvl function enough reason to totally
discard the hint and avoid to use the index?

Thanks again

          Followup:

"when a function is
applied on an indexed column, that will atomatically avoid the used of that
index."

absolutely not -- false.  it would be accurate to say that

select * from t where x = 5;

can use an index range scan on an index on t(x) whereas

select * from t where x+0 = 5;

CANNOT use an index range scan on an index on t(x)

The second query can use an index fast full scan or an index full scan however
to find all of the values of X, add 0 to it and see if that equals 5 and if so
-- access the table by rowid.

Your premise that the use of a function obviates the use of an index is flawed.
It obviates using it in the manner you were thinking (index range scan).

Consider that sometimes you want the index to be used like this.  Consider:

big_table@ORA817DEV.US.ORACLE.COM> set autotrace on
big_table@ORA817DEV.US.ORACLE.COM>
big_table@ORA817DEV.US.ORACLE.COM> select /*+ full( big_table ) */ object_id,
object_name
  2    from big_table
  3   where nvl(object_id,-1) = -1;

 OBJECT_ID OBJECT_NAME
---------- ------------------------------
        -1 /1001a851_ConstantDefImpl

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=658 Card=64 Bytes=1344)
   1    0   TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=658 Card=64 Bytes=1344)

Statistics
----------------------------------------------------------
          0  recursive calls
        117  db block gets
      22862  consistent gets
      13353  physical reads
          0  redo size
        454  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

big_table@ORA817DEV.US.ORACLE.COM> select /*+ index( big_table big_table_idx2 )
*/ object_id, object_name
  2    from big_table
  3   where nvl(object_id,-1) = -1;

 OBJECT_ID OBJECT_NAME
---------- ------------------------------
        -1 /1001a851_ConstantDefImpl

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3498 Card=64 Bytes=1344)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=3498 Card=64
Bytes=1344)
   2    1     INDEX (FULL SCAN) OF 'BIG_TABLE_IDX2' (NON-UNIQUE) (Cost=3434
Card=64)

Statistics
----------------------------------------------------------
          8  recursive calls
          0  db block gets
       3442  consistent gets
       3326  physical reads
          0  redo size
        454  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

big_table@ORA817DEV.US.ORACLE.COM>
big_table@ORA817DEV.US.ORACLE.COM> set autotrace off

See -- here by using the index as a "skinny table" and knowing that very few
rows would satisfy the request -- using the INDEX was very much prefered.  We
scanned tons less blocks and only accessed the table once

      Rating:
      5
  November 27, 2002
Reviewer:
      A reader
Thank you for clarifying my wrong perception. What I don't understand is why the
people who teach oracle always say that when you apply a function on a indexed
column the index will not be used. Saying that they contribute to the
misconception. Anyway...

Now, I run the following examples and I obtained the following:
Table_1 and table_2 are analyzed every week, index_1(t1.column_a),
index_table2(t2.column_x)

1-
select
    distinct T2.column_c,
    T2.column_d
from table_1 T1,
    table_2 T2
where T1.column_a=1234
and t1.column_c= t2.column_c
and t2.column_x=:b1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=11 Card=1 Bytes=30)
   1    0   SORT (UNIQUE) (Cost=11 Card=1 Bytes=30)
   2    1     NESTED LOOPS (Cost=8 Card=1 Bytes=30)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'TABLE_1' (Cost=4 Card=4
Bytes=80)

   4    3         INDEX (RANGE SCAN) OF 'INDEX_1' (NON-UNIQUE) (Cost=3 Card=4)

   5    2       TABLE ACCESS (BY INDEX ROWID) OF 'TABLE_2' (Cost=1 Card=15752284
Bytes=157522840)

   6    5         INDEX (UNIQUE SCAN) OF 'INDEX_TABLE2' (UNIQUE)

2-
But , if I add +0 to the following condition: t1.column_a+0=1234 the index is
not use and the explain plan shows a full scan, look:

select
    distinct T2.column_c,
    T2.column_d
from table_1 T1,
    table_2 T2
where T1.column_a+0=1234
and t1.column_c= t2.column_c
and t2.column_x=:b1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=73169 Card=1 Bytes=30)

   1    0   SORT (UNIQUE) (Cost=73169 Card=1 Bytes=30)
   2    1     NESTED LOOPS (Cost=73166 Card=1 Bytes=30)
   3    2       TABLE ACCESS (FULL) OF 'TABLE_1' (Cost=73162 Card=4 Bytes=80)

   4    2       TABLE ACCESS (BY INDEX ROWID) OF 'TABLE_2' (Cost=1 Card=15752284
Bytes=157522840)

   5    4         INDEX (UNIQUE SCAN) OF 'INDEX_TABLE2' (UNIQUE)

3-
But, if I add the hint for the index, the optimizer uses the index, look

select /*+ index (T1 index_1 ) */
    distinct T2.column_c,
    T2.column_d
from table_1 T1,
    table_2 T2
where T1.column_a+0=1234
and t1.column_c= t2.column_c
and t2.column_x=:b1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=155582 Card=1 Bytes= 30)

   1    0   SORT (UNIQUE) (Cost=155582 Card=1 Bytes=30)
   2    1     NESTED LOOPS (Cost=155580 Card=1 Bytes=30)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'TABLE_1' (Cost=155576 Card=4
Bytes=80)

   4    3         INDEX (FULL SCAN) OF 'INDEX_1' (UNIQUE) (Cost=163763 Card=4)

   5    2       TABLE ACCESS (BY INDEX ROWID) OF 'TABLE_2' (Cost=1 Card=15752284
Bytes=157522840)

   6    5         INDEX (UNIQUE SCAN) OF 'INDEX_TABLE2' (UNIQUE)

My questions are, taking into account what you told me before,
1-shouldn't the optimizer have chosen a full scan on index_1, in the second
case,instead of doing a full scan?
2-Why do you think the optimizer chose to run a full scan on table_1 in that
case?

Thanks

          Followup:

1) no, it is a function of costs.  The cost of the full table scan was:

TABLE ACCESS (FULL) OF 'TABLE_1' (Cost=73162 Card=4 Bytes=80)

The cost of the index full scan (single block io) was

INDEX (FULL SCAN) OF 'INDEX_1' (UNIQUE) (Cost=163763 Card=4)

Tell me -- which query actually performed better?

      Rating:
      5
  November 27, 2002
Reviewer:
      A reader
Tom,

Making a comparison between the full table scan and the full index scan, the
full index scan is the one that performs better.

Thanks

      Rating:
      5
  December 06, 2002
Reviewer:
      A reader
Hi Tom,

For the previous statement
"when a function is
applied on an indexed column, that will atomatically avoid the used of that
index."

You said <quote>
absolutely not -- false.  it would be accurate to say that

select * from t where x = 5;

can use an index range scan on an index on t(x) whereas

select * from t where x+0 = 5;

CANNOT use an index range scan on an index on t(x)

<end quote>

But in  your book "Expert one-on-one " Chapter 7: Why isn't my index getting
used? you said
<quote>
case 3
For an indexed column, you query using:
select * from t where f(indexed_column) =value

and find that the index on INDEX_COLUMN is not used. this is due to the use of
the function on the column. You indexed the values of the INDEX_COLUMN - not the
value of f(INDEXED_COLUMN). The index is nullified here. You can index the
fuction if you choose to do it
<end quote>

I am little confuse with this, because this looks like contradictory. Which rule
should we follow, the one that you detailed in the case 3 in your book or your
answer in this posting.?

Thanks for all your help.

          Followup:

that does not say that index cannot be used - it says "so you are finding we are
not using your index -- why?"  well, we stopped using it for a range scan on
that query -- we found a different way to process the query and it doesn't use
your index anymore because it doesn't MAKE SENSE to use that index.

It is not that the index could not be used in some way, shape or form for that
query -- it is that the index is NOT BEING used for that query.

The index could be used -- just not in an index range scan (which is the way
most of us see indexes being used -- on many a system, there is no other use of
an index)

case #3 stands.  if you have a query:

select * from t where indexed_column = value;

and you believe it should use an index (range scan) but won't -- you probably
are doing an implicit function which is causing us to avoid using the index.
You would want to make sure that VALUE is the same datatype as indexed_column to
avoid that.

      Rating: 4
Indexes due to
      usage of functions  December 12, 2002
Reviewer:
      Lalit  from The Netherlands
Thanks for clarifying the doubt. It is going to be of great help to me.

One of my procedure is selecting data from a single table. This table is having
1,100,000 records. It is selecting 100 columns and doing a group by with 25
columns
Select a,b,c......,sum(x),sum(y),sum(z)......from table_name
group by a,b,c (25 column name). {no where condition and only one table in from
clause}

This table which is having 1,100,000 records does not have any indexes. This sql
takes a lot time for execution.
Can you suggest me some way of improving the performance of this select
statement. Should I create indexes on my table? Should I use any hints for
improving the performance.

Thanks in advance

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6501620260742
and up your sort_area_size considerably and watch the run time go way down.  1.1
million records isn't very much.

you don't want any indexes for such a query -- full scan is the only way to go

      Rating: 4
Indexes due to
      usage of functions   December 14, 2002
Reviewer:
      Lalit  from The Netherlands
Thanks a lot Tom.

It worked for me. When I made the sort_area_size of 100 MB, the execution time
is reduced from 75 mins to 40 mins. This is a great time gain for me,
Thanks!!!!!!!!

Do you suggest to increase the sort_area_size even more? Are there any
disadvantages of increasing sort_area_size?

For this particular case, what we are doing is, selecting from 1 table (having
1.1 million records), with 2 different processes running in parellel, doing
different group by and inserting in a table. This table is same for both the
inserts.

Process 1 -
Insert into table2
(select a,b,c...(37
columns),sum(x.usd_ex_rate),sum(y.usd_ex_rate),sum(z.usd_ex_rate) from table1
group by a,b,c...(37 columns))

Process 2 -
Insert into table2
(select a,b,c...(37
columns),sum(x.eur_ex_rate),sum(y.eur_ex_rate),sum(z.eur_ex_rate) from table1
group by a,b,c...(37 columns))

The two process running in parellel and inserting in the same table(table2), do
you have any suggestions to optimize this. Due to the increase in sort area size
now both the processes are taking 40 mins in parellel.

Thanks again for your valuable suggestions.

          Followup:

If these are batch jobs being run when the system is somewhat "otherwise not
used", feel free to analyze if upping the sort area size would help even more.

You would look to see if you are doing an SORTS TO DISK during this period.  If
you are -- then perhaps a larger sort area size would be beneficial, as long as
you have sufficient free RAM on the machine to support it.

      Rating: 3
Parallel
      Execution  December 14, 2002
Reviewer:
      Lalit  from The Netherland
Had tried to increase the sort_area_size, but there was not a big gain in time
(time reduced by 3 mins).

Sorry for asking the 2nd question in the same series. As the question was
related to the same process, I had put in here.

Can you have a look at the 2 process I had mentioned and suggest me some
solution which will be more optimized. The only difference in the two processes
is, the multiplication by usd_exchange_rate and eur_exchange rate.

Process 1 -
Insert into table2
(select a,b,c...(37
columns),sum(x.usd_ex_rate),sum(y.usd_ex_rate),sum(z.usd_ex_rate) from table1
group by a,b,c...(37 columns))

Process 2 -
Insert into table2
(select a,b,c...(37
columns),sum(x.eur_ex_rate),sum(y.eur_ex_rate),sum(z.eur_ex_rate) from table1
group by a,b,c...(37 columns))

Both these processes are running in parellel (with the increased sort_area_size
for both the session) and inserting in the same table (table2)

          Followup:

try

insert into table2
select a, b, c, ....,
       decode( rn, 1, x_usd_ex_rate, x_eur_ex_rate ),
       decode( rn, 1, y_usd_ex_rate, y_eur_ex_rate ),
       decode( rn, 1, z_usd_ex_rate, z_eur_ex_rate )
  from ( select a, b, c, .....,
              sum(x.usd_ex_rate) x_usd_ex_rate,
              sum(y.usd_ex_rate) y_usd_ex_rate,
              sum(z.usd_ex_rate) z_usd_ex_rate,
              sum(x.eur_ex_rate) x_eur_ex_rate,
              sum(y.eur_ex_rate) y_eur_ex_rate,
              sum(z.eur_ex_rate) z_eur_ex_rate
         from table1
         group by ..... ),
       ( select 1 rn from dual UNION ALL select 2 rn from dual )
/

and just do it in a single pass -- you don't need two queries.

      Rating: 5
index full
      scan, index fast full scan and index range scan  March 01,
      2004
Reviewer:  A reader
Hi

I have some queries which uses indexes quite happily and nice performance, but
some times these same queries in my testing environment chooses index full scan
instead of range scan and the queries are deadly slow.... What makes CBO chooses
full scan instead of range scan? And Why it does use fast full scan instead of
full scan?

Difference between fast full scan and full scan?

Cheers

          Followup:

full scans will be used when a range scan cannot.  fast full scans will be used
to avoid a full scan of the base table itself.

ops$tkyte@ORA920PC> create table t ( x int not null, y char(80) );
Table created.

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

ops$tkyte@ORA920PC> exec dbms_stats.set_table_stats( user, 'T', numrows =>
1000000, numblks => 100000 );
PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> set autotrace traceonly explain
ops$tkyte@ORA920PC> select /*+ first_rows */ * from t where x > 55;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=10 Card=50000
Bytes=4750000)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=10 Card=50000
Bytes=4750000)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=9000)

here, a range scan -- easy to see why.  where x > 55, read index to find the
first x > 55 really really fast

ops$tkyte@ORA920PC> select /*+ first_rows */ * from t where x <> 55 order by x;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=66 Card=50000
Bytes=4750000)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=66 Card=50000
Bytes=4750000)
   2    1     INDEX (FULL SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=26 Card=50000)

here an index full scan.  a range scan does not really work with <> but in
order to get the data sorted and returned "first rows wise", it makes sense to
read the index fully, in order, starting from the beginning and return the data
sorted (skipping 55 of course).

ops$tkyte@ORA920PC> select x from t;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=1000000 Bytes=13000000)
   1    0   INDEX (FAST FULL SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=5 Card=1000000
Bytes=13000000)

 and here a fast full scan -- using the index "as if it were a table", it'll
use large multi-block IO to read the index out of order (as it exists on disk,
not as a index structure a block at a time).

ops$tkyte@ORA920PC> set autotrace off
ops$tkyte@ORA920PC>

I'd need an example where you believe "range scan" should be choosen but a full
scan was in order to comment.  You can set it up like I did above, create the
tables, use dbms_Stats and simulate your issue.  then I can comment.

      Rating: 5
This might be
      the reason  March 01, 2004
Reviewer:
      A reader
I have also seen quite a few times where Oracle chooses index full scan instead
of range scan and the performance was poor compared with range scan. I think the
problem was with Clustering Factor, it was almost as high as number of rows. May
be that is the reason CBO chooses index full scan instead of range scan?
Nevertheless the choice made by CBO was poor :-0

          Followup:

but an index full scan is a RANGE scan in the grandest sense.  It is a range
scan from the beginning to the end.  if the CBO was avoiding the range, a full
scan would have really scared it.

or do you mean a fast FULL scan?

      Rating: 5
what is the
      difference between fast full and full scan?  March 01, 2004

Reviewer:  A reader
What is the difference between these two? Cant index fast full scan do what
index full scan does??

          Followup:

an index FULL scan is a range scan that starts from the lowest and goes to the
highest (or vice versa with descending).

It reads the index a block at a time (random single block IO's) and processes
the data "in sorted order"

an index FAST FULL scan is sort of like a full tablescan.  It reads the entire
index structure using multi-block IO and only processes leaf blocks.  It treats
the index "as if it were a table" and doesn't read the data in any sort of
sorted order.

so, data from an INDEX FULL SCAN comes out slowly (single block IO) but in
sorted order.  data from an INDEX FAST FULL SCAN comes out "fast" (multi-block
IO) but not sorted (sort of like a skinny version of the base table)

      Rating: 5
from this
      link  March 03, 2004
Reviewer:
      A reader
Hi

Recently I read this follow up in this link about index skip scan

http://asktom.oracle.com/pls/ask/f?p=4950:8:4150753808325416416::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:5671539468597,
There when index_ss is specified CBO does index full scan but when the leading
column (the column with low distinct values) is specified CBO does range scan
which is much faster. What can that be?

          Followup:

we left that question here:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5671539468597#15899125628439
that is, with data supplied, i cannot answer.

March 03, 2004 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"

      Rating: 3
great!, but...
      do I need an EXTPROC or ?  March 03, 2004

Reviewer:  George Liblick  from Drexel Hill, PA

Tom,

I hope you'll forgive me for posting a question here, but I don't know where
else to turn...  I've scoured your site, (one of) your book, and the Oracle
Documentation.  Lots of help on how to implement a dynamic, bulk select
statement.  But, and I'm surprised its not more common, I can't find any showing
how to select a variable number of columns into ae expandable two dimensional
array.

Can you confirm if this can be done, has to be done in a C-OCI program, or can't
:-(

My current effort/example:

CREATE OR REPLACE procedure XX
is
   dyncursor     INTEGER default dbms_sql.open_cursor;
   sqlstmt varchar2(32767);
   column_count integer := 3;
   type datacells_t is table of dbms_sql.varchar2_table index by binary_integer;
   datacells datacells_t;
   onecell dbms_sql.varchar2_table;
   rowcount integer;
   display_line VARCHAR2(256);
BEGIN
   -- in production, sqlstmt is built from table data
   sqlstmt:= 'select username, user_id, created from all_users';

   dbms_sql.parse( dyncursor, sqlstmt, dbms_sql.native );

   FOR i in 1..column_count LOOP
      dbms_sql.DEFINE_ARRAY( dyncursor, i, datacells(i), 100, 1 );
   END LOOP;

   rowcount := dbms_sql.execute( c => dyncursor );
   LOOP
      rowcount := dbms_sql.fetch_rows( c => dyncursor );

      FOR j in 1..column_count LOOP
         dbms_sql.column_value( dyncursor, j, datacells(j) );
      END LOOP;

      FOR i in 1..rowcount LOOP
         display_line := 'ROW ' || i ;
         FOR j in 1..column_count LOOP
            display_line := ', ' || display_line || datacells(j)(i);
         END LOOP;
         dbms_output.put_line( display_line );
      END LOOP;

      EXIT WHEN rowcount <> 100;
   END LOOP;
END;
/

Procedure created.

SQL> exec xx
BEGIN xx; END;

*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at "ARIACORE.XX", line 18
ORA-06512: at line 1

...

If I replace the return variable in the DEFINE_ARRAY with "onecell" as defined
in that example, everything is OK except that I can't look ahead across the
columns, something I need to do.  Right now I'm doing all this without array
processing, but its really slow :-(

Thanks so much,

George

          Followup:

well, it doesn't work that way. We can do this -- but using a single dimensioned
array and reserving:

1 .. Array_Size for column 1
Array_size+1 .. 2*Array_size for column 2

and so on.  Here is an example:

ops$tkyte@ORA9IR2> create or replace procedure dbms_sql_select
  2  as
  3      l_theCursor     integer default dbms_sql.open_cursor;
  4      l_columnValue   dbms_sql.varchar2_table;
  5      l_empty         dbms_sql.varchar2_table;
  6      l_status        integer;
  7      l_cnt           number := 0;
  8      l_start  number default dbms_utility.get_time;
  9      l_descTbl       dbms_sql.desc_tab;
 10      l_colCnt        number;
 11      l_array_size    number := 10;
 12  begin
 13
 14      dbms_sql.parse( l_theCursor,
 15                     'select username, user_id, created from all_users',
 16                      dbms_sql.native );
 17
 18      dbms_sql.describe_columns( l_theCursor, l_colCnt, l_descTbl );
 19
 20      for i in 1 .. l_colCnt
 21      loop
 22          dbms_sql.define_array
 23          ( l_theCursor, i, l_columnValue,
 24            l_array_size, (i-1)*l_array_size+1 );
 25      end loop;
 26
 27      l_status := dbms_sql.execute( l_theCursor );
 28      loop
 29          l_status := dbms_sql.fetch_rows(l_theCursor);
 30          for i in 1 .. l_colCnt
 31          loop
 32              dbms_sql.column_value(l_theCursor,i,l_columnValue);
 33          end loop;
 34
 35          for i in 0 .. l_status-1
 36          loop
 37              for j in 0 .. l_colCnt-1
 38              loop
 39                  dbms_output.put( ' col(' || (j+1) || ')= ' ||
 40                     l_columnValue( (l_columnValue.first+i) +
(j*l_array_size) )
 41                  );
 42              end loop;
 43              dbms_output.new_line;
 44          end loop;
 45
 46          l_cnt := l_status+l_cnt;
 47          exit when l_status <> l_array_size;
 48          l_columnValue := l_empty;
 49      end loop;
 50      dbms_sql.close_cursor( l_theCursor );
 51      dbms_output.put_line( L_cnt || ' rows processed' );
 52  end;
 53  /

Procedure created.

ops$tkyte@ORA9IR2> exec dbms_sql_select
col(1)= SYS col(2)= 0 col(3)= 12-MAY-02
col(1)= SYSTEM col(2)= 5 col(3)= 12-MAY-02
col(1)= OUTLN col(2)= 11 col(3)= 12-MAY-02
col(1)= DBSNMP col(2)= 19 col(3)= 12-MAY-02
col(1)= WMSYS col(2)= 21 col(3)= 12-MAY-02
col(1)= ORDSYS col(2)= 30 col(3)= 12-MAY-02
col(1)= ORDPLUGINS col(2)= 31 col(3)= 12-MAY-02
col(1)= MDSYS col(2)= 32 col(3)= 12-MAY-02
col(1)= CTXSYS col(2)= 33 col(3)= 12-MAY-02
col(1)= XDB col(2)= 35 col(3)= 12-MAY-02
col(1)= ANONYMOUS col(2)= 36 col(3)= 12-MAY-02
col(1)= WKSYS col(2)= 39 col(3)= 12-MAY-02
col(1)= WKPROXY col(2)= 40 col(3)= 12-MAY-02
col(1)= ODM col(2)= 44 col(3)= 12-MAY-02
col(1)= ODM_MTR col(2)= 45 col(3)= 12-MAY-02
col(1)= LBACSYS col(2)= 42 col(3)= 12-MAY-02
col(1)= OLAPSYS col(2)= 46 col(3)= 12-MAY-02
col(1)= HR col(2)= 48 col(3)= 12-MAY-02
col(1)= OE col(2)= 49 col(3)= 12-MAY-02
col(1)= PM col(2)= 50 col(3)= 12-MAY-02
col(1)= SH col(2)= 51 col(3)= 12-MAY-02
col(1)= QS_ADM col(2)= 53 col(3)= 12-MAY-02
col(1)= QS col(2)= 54 col(3)= 12-MAY-02
col(1)= QS_WS col(2)= 55 col(3)= 12-MAY-02
col(1)= QS_ES col(2)= 56 col(3)= 12-MAY-02
col(1)= QS_OS col(2)= 57 col(3)= 12-MAY-02
col(1)= QS_CBADM col(2)= 58 col(3)= 12-MAY-02
col(1)= QS_CB col(2)= 59 col(3)= 12-MAY-02
col(1)= QS_CS col(2)= 60 col(3)= 12-MAY-02
col(1)= OPS$TKYTE col(2)= 105 col(3)= 03-MAR-04
col(1)= A col(2)= 104 col(3)= 03-MAR-04
col(1)= BIG_TABLE col(2)= 63 col(3)= 28-NOV-03
col(1)= DEMO col(2)= 64 col(3)= 28-NOV-03
col(1)= U1 col(2)= 81 col(3)= 26-DEC-03
col(1)= IOT_HEAP col(2)= 102 col(3)= 06-FEB-04
col(1)= UTILITY col(2)= 70 col(3)= 28-NOV-03
col(1)= DEMO002 col(2)= 71 col(3)= 28-NOV-03
col(1)= DEMO006 col(2)= 72 col(3)= 28-NOV-03
col(1)= DEMO007 col(2)= 73 col(3)= 28-NOV-03
col(1)= DEMO008 col(2)= 74 col(3)= 28-NOV-03
col(1)= PERFSTAT col(2)= 82 col(3)= 02-JAN-04
col(1)= FLOWS_FILES col(2)= 83 col(3)= 06-JAN-04
col(1)= HTMLDB_PUBLIC_USER col(2)= 84 col(3)= 06-JAN-04
col(1)= FLOWS_010404 col(2)= 85 col(3)= 06-JAN-04
col(1)= DEMO_SCHEMA col(2)= 86 col(3)= 06-JAN-04
col(1)= SERGIO col(2)= 87 col(3)= 06-JAN-04
col(1)= AUTOTRACE_EXAMPLE col(2)= 99 col(3)= 16-JAN-04
col(1)= SCOTT col(2)= 92 col(3)= 14-JAN-04
48 rows processed

PL/SQL procedure successfully completed.

      Rating:
      5
splendifico!  March 03, 2004
Reviewer:
      George Liblick  from Drexel Hill, PA
well, it would be nice if it worked... but this is a pretty good substitute for
a true 2D array - it took me a minute to realize that the array size isn't
limiting but rather the fetch size, after that I don't see any real drawback..?

and, your prompt help is very much appreciated :-)

George

March 03, 2004 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.

      Rating: 5
Listener
      questions  March 03, 2004
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
Tom,
The listeners are giving me a hard time. I have following questions:
a) In 8i onwards, automatic service registration registers the database with
default listener. Should the default listener have the name LISTENER and PORT
1521 or can any named listener (say LISTENER_XYZ) listening on port 1521 becomes
the default listener?
b) I just installed a 10g database on a server which is already running 9i
databases. The 10g database automatically registered with 9i listener. Whenever
I try to connect to 10g database, I get an error that listener failed to spawn a
dedicated server process. To get around this, I created a second 10g listener on
port 1421. Is there a better way ?

Thanks

          Followup:

a) any listener, it is the "default port" that is connects to -- doesn't really
care about the name.

b) you should use just the 10g listener for all, if you use the highest
versioned listener, you can get away with a single listener for all databases.
running more than one listener works fine however.


Listing

Recent Archives Advanced Search FAQ by Category Most Popular Hot Articles Your Questions [ Listing ] RSS Feed
Subject | Article | Last Updated (East coast time)
Drop index before insert? | article only | Mar 04, 2004 08:05 am
Avoiding overlap values... | article only | Mar 04, 2004 08:09 am
sql query results | article only | Mar 04, 2004 08:13 am
Data file size | article only | Mar 04, 2004 11:03 am
Features of 9i | article only | Mar 04, 2004 12:33 pm
Clarification and Question about UGA/PGA and sort_area_sze | article only | Mar 04, 2004 12:44 pm
How does this delete works? | article only | Mar 04, 2004 01:11 pm
Outer Join w/Subquery "Table" | article only | Mar 04, 2004 01:13 pm
SQL query | article only | Mar 04, 2004 01:25 pm
Can i pass "C" structures to Oracle | article only | Mar 04, 2004 02:10 pm
what is redo log sync and what more can be done about it | article only | Mar 04, 2004 02:24 pm
Consider Cost or Time - trying to compare the COST of two queries | article only | Mar 04, 2004 02:29 pm
For calculating data growth | article only | Mar 04, 2004 02:30 pm
LIstener | article only | Mar 04, 2004 02:44 pm
Parsing Times from Character String | article only | Mar 04, 2004 03:33 pm
Reg. DBMS_STAT | article only | Mar 04, 2004 07:22 pm
JDBC : SQL vs PL/SQL, Which performs better | article only | Mar 04, 2004 07:45 pm
SYSDATE behavior in SQL and PL/SQL | article only | Mar 05, 2004 07:45 am
How to preventing front-end users accessing the db via sqlplus , etc | article only | Mar 05, 2004 08:04 am
rename a table | article only | Mar 05, 2004 08:35 am
index fragmentation | article only | Mar 05, 2004 08:37 am
A Custom Error Handling System | article only | Mar 05, 2004 08:58 am
how to do outer joins in multiples tables. | article only | Mar 05, 2004 10:24 am
Automating standby databases | article only | Mar 05, 2004 10:39 am
Multiple rows from table into one row in refcursor | article only | Mar 05, 2004 12:57 pm
Hierarchical Tree | article only | Mar 05, 2004 01:26 pm
Invoke SQL loader from stored procedure? | article only | Mar 05, 2004 01:27 pm
Performance Tuning | article only | Mar 05, 2004 03:58 pm
No of occurences of a string within another string | article only | Mar 05, 2004 04:00 pm
Package Error | article only | Mar 05, 2004 07:08 pm
FORALL Update - Updating multiple columns | article only | Mar 06, 2004 08:15 am
About tables details | article only | Mar 06, 2004 11:25 am
Zero devide problem | article only | Mar 06, 2004 01:51 pm
download patch | article only | Mar 06, 2004 02:47 pm
update | article only | Mar 06, 2004 08:27 pm
connect by | article only | Mar 06, 2004 08:42 pm
DBWR and Server process | article only | Mar 07, 2004 09:28 am
HTMLDB and XMLDB | article only | Mar 07, 2004 11:19 am
Update | article only | Mar 07, 2004 11:39 am
size of tablespaces & datafiles | article only | Mar 07, 2004 11:43 am
Random Number Generation | article only | Mar 07, 2004 01:10 pm
Undo Tablespaces. | article only | Mar 07, 2004 04:52 pm
Storage Parameters in TableSpace/Tables | article only | Mar 08, 2004 07:56 am
Serializable transaction | article only | Mar 08, 2004 08:13 am
Generate XML | article only | Mar 08, 2004 11:25 am
Generate XML in desired format | article only | Mar 08, 2004 12:32 pm
Issues with rebuilding indexes | article only | Mar 08, 2004 01:52 pm
difference between sql type and plsql types | article only | Mar 08, 2004 01:56 pm
Problem of the rows getting updated while they are being currently displayed on the online screen | article only | Mar 08, 2004 02:51 pm
list all user(s) group by group_list | article only | Mar 09, 2004 10:48 am
SSL | article only | Mar 09, 2004 10:59 am
Replicate or not !!! | article only | Mar 09, 2004 11:43 am
MTS | article only | Mar 09, 2004 11:44 am
SQ*Plus /nolog | article only | Mar 09, 2004 11:45 am
Pragma Serially_reusaable | article only | Mar 09, 2004 11:48 am
in and out variables - whats the difference | article only | Mar 09, 2004 11:59 am
Avoiding redo while Deleting | article only | Mar 09, 2004 12:17 pm
Modeling graphs inside a relational database | article only | Mar 09, 2004 12:28 pm
Sequences | article only | Mar 09, 2004 03:24 pm
Nologging does not work (yes it does...) | article only | Mar 09, 2004 03:35 pm
Logical Standby database | article only | Mar 09, 2004 10:27 pm
Describing a MEMBER of a package by non-owner | article only | Mar 09, 2004 10:37 pm
import of a 8.1.7 export file | article only | Mar 09, 2004 10:41 pm
Fetch across commit | article only | Mar 09, 2004 10:44 pm
temporary tablespace WORKAREA_SIZE_POLICY | article only | Mar 09, 2004 10:45 pm
Can interMedia index creation be done in parallel? | article only | Mar 09, 2004 10:47 pm
Cursor Variable and Bind Variables | article only | Mar 09, 2004 10:49 pm
consistent gets -- Very puzzling | article only | Mar 10, 2004 09:15 am
Materialized Views | article only | Mar 10, 2004 09:30 am
Input Parameters | article only | Mar 10, 2004 10:10 am
Advanced Queueing | article only | Mar 10, 2004 03:32 pm
Processing/Parsing XML | article only | Mar 10, 2004 03:33 pm
Listener Question | article only | Mar 10, 2004 03:35 pm
Loading Image Problem | article only | Mar 10, 2004 05:27 pm
SQL*Loader - How can I put header info on each record | article only | Mar 10, 2004 05:33 pm
Oracle for Windows or Linux | article only | Mar 10, 2004 05:34 pm
row(s) 1 - 76 of 76
March 04, 2004 Ping -- Thanks for the question regarding "Drop index before insert?", version 8.1.7
You Asked
I did it in a stored procedure -- this 90K records can be inserts, updates or
deletes (mostly insert), depending on an action flag.

The fact table is partitioned and the indexes involved are local partitioned.
The partitioned is based on a column called transaction date. Right now we have
5 partitions, each with half year's data of about 2G in size. Thanks, Ping

Hi Tom,

There is a very strange thing happened to me: I have a fact table (not very big
1.9 mil records now but will grow) with 9 bitmap indexes. I have to insert some
records (90K) everyday.

The strange thing is it could not finish the insert because the index tbs and
the rollback tbs keep grow until fail out of space. But if I drop those bitmap
indexes, then the insert can proceed and after I recreate those indexes, the
space used for index is less than 10% of the size when the previous insert
failed.

I guess I can drop-insert-recreate indexes, but with the data grows in this
table, recreate will take longer and longer time. SO I want to ask you, from
what I describe can you tell me what is going wrong?

Thanks, Ping
and we said...
Ok, I'll recommend a book -- Jonathan Lewis's Practical Oracle8i.  He has a
*really* good discussion on this in there, along with tons of other stuff.  If
you don't want to get the book (wrong choice IMHO), you can read an online
article of his on this topic as well
http://www.dbazine.com/jlewis3.html

Basically, you are doing singleton updates/inserts/deletes against this table.
That is really bad for bitmaps -- horrible.  You can disable the index, perform
the operation and then rebuild the local index partitions in parallel
(unrecoverable even) afterwards.  That would be the most efficient approach for
this data warehouse incremental load.
Review & Followup
Rating: 5
Rebuild local bitmap index March 02, 2004
Reviewer: A reader
HI Tom,

Can you show me how to "disable" a bitmap index and then rebuild it?

I was thinking the best way for rebuilding local bitmap indexes on partition
table is to drop it before DML operation then recreate them afterwards.

Thanks,

Followup:
ops$tkyte@ORA9IR2> create table t
  2  ( object_id, owner )
  3  partition by hash(object_id) partitions 2
  4  as
  5  select object_id, owner from all_objects
  6  /

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create bitmap index t_idx on t(owner) LOCAL;

Index created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> column partition_name new_val P
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select partition_name
  2    from user_ind_partitions
  3   where index_name = 'T_IDX'
  4     and rownum = 1;

PARTITION_NAME
------------------------------
SYS_P215

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> alter index t_idx modify partition &P unusable;
old   1: alter index t_idx modify partition &P unusable
new   1: alter index t_idx modify partition SYS_P215 unusable

Index altered.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> alter session set skip_unusable_indexes=true;

Session altered.

ops$tkyte@ORA9IR2> insert into t select * from t partition(&p);
old   1: insert into t select * from t partition(&p)
new   1: insert into t select * from t partition(SYS_P215)

15408 rows created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select index_name, partition_name pname, status
  2    from user_ind_partitions
  3   where index_name = 'T_IDX';

INDEX_NAME                     PNAME                          STATUS
------------------------------ ------------------------------ --------
T_IDX                          SYS_P215                       UNUSABLE
T_IDX                          SYS_P216                       USABLE

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> alter index t_idx rebuild partition &P;
old   1: alter index t_idx rebuild partition &P
new   1: alter index t_idx rebuild partition SYS_P215

Index altered.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select index_name, partition_name , status
  2    from user_ind_partitions
  3   where index_name = 'T_IDX';

INDEX_NAME                     PARTITION_NAME                 STATUS
------------------------------ ------------------------------ --------
T_IDX                          SYS_P215                       USABLE
T_IDX                          SYS_P216                       USABLE

If you drop them, you might lose them.......  here, they are always there.

Rating: 5
Bitmap Index Rebuild March 03, 2004
Reviewer: A reader
Hi Tom,

We have partition table of 10+ millions rows, with bitmap indexes on it for
speeding up reporting. Everynight new rows are inserted row by row.

I noticed the size of the index tablespace containing the bitmap indexes
increases very quickly, but if I drop and rebuild the indexes, the size becomes
much smaller. I also tried ALTER INDEX ... PARTITION ... REBUILD and the size
doesn't shrink much.

Could you help explain the story behind this? Also please show me how I can get
the physical storage size of a (bitmap) index.

Thanks.

Followup:
the alter index rebuild of a partition will squash it down just as much as
anything -- are you using DMT's or LMT's and do you use a storage clause for
your DMT's if that is what you are using?

there is allocated size and used size -- search this site for

show_space

and you'll find a script that shows you what a segment is doing space wise.

bitmaps and row by row processing are pretty much "not something you want to
do", they break down rather quickly.

Rating: 5
More info. March 03, 2004
Reviewer: A reader
Hi Tom,

Thanks for your answer. I am using LMT with Auto Allocation and Manual Segment
Space Management.

Followup:
then I would need to see an example of where a rebuild did much worse than a
drop/create as far as space goes.   a cut and paste of the commands used and the
queries and their results that show the space utilization.

March 04, 2004 -- 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 :)

Rating: 3
A similar requirement... February 05, 2004
Reviewer: Matt from Australia
I need to load lots of data very quickly from an external table the data looks
like:

id,value,from_date,to_date

I need to load this data subject to the following requirements:

1) There can be no overlaps for a particular id (so one records to_date is equal
to the next records from_date.
2) There must be a full set of data from a given historic date date (roughly
three years in the past).
3) All the data for each id must span the period from the historic date as close
to the current date as possible (given the data available).

For example:

If historic date is 01/01/2003 (DD/MM/YYYY), this is the data that I want to
load (I need to discard data that does not marry the previous records to_date to
the next records from date:

id  value from_date    to_date
1   10    01/01/2003   01/02/2003
1   12    01/02/2003   21/05/2003
1   19    21/05/2003   01/06/2003
1   25    01/06/2003   01/12/2003
1   25    01/12/2003   01/02/2004

How would one generate a select to pull out data that marries like this?

Also, I need to constrain the schema so that only the data that satifies these
rules will be accepted (similar to the original post). Does the method described
in this post still apply to 9.2 or is there a better way?

Best Regards,

Followup:
tell me, what if the inputs do have overlaps.  what logic decides what rows are
actually kept (and why)

what does "there must be a full set of data" mean actually?

Rating: 3
Additional info.... February 05, 2004
Reviewer: Matt from Australia
(I'll answer your Q's in a sec).

I'm working at this problem from two ends.

1) a query to pull out the valid data from the external table subject to my
requirements in order to create a table

2) a loader to go through the data that was not retreived by the query. The
loader will bulk load these values and constraints will identify what the
problem was with the data (so it can be investigated and fixed)

So, I think I need

a) A suitable query to gather the data according to the requirements (more
detail will follow).

b) A mechanism to constrain the target table so that 'bad' data cannot get in
(and a constraint or trigger will raise an error that may be caught and logged)

The logic for keeping records is this:

a) The record with the earliest from_date for a particular id must have a
from_date of 01/01/2003 (DD/MM/YYYY) (the historic date in the example above).
b) The set of records for a particular id must completely span the period (with
no gaps - ie: it fishtails so that current from_date=previous to_date) that
starts on 01/01/2003 (the historic date) and continues up to the present.
However;
   provided that the data fishtails throughout the period it is allowable for
the most recent record to finish on a date before the current date.
d) there can be no duplicate data (even if the rows are the same eg: two records
with the same from and to dates.)
e) no to_date can be strictly greater than the current date.

So, you can see (I hope) that the logic  requires me to valiodate "sets" of rows
(defined by the id) such that the following are "sets" are accepted:

       HistDate                              Now
          |                                   |
ID: 1     |-----|
ID: 1           |-----------|
ID: 1                       |------|
ID: 1                              |----------|
ID: 2     |---------|
ID: 2               |---|
ID: 2                   |-----|
ID: 2                         |---------|
ID: 3     |---------------------------------|
ID: 4     |-----------------------------------|

and the following sets would be rejected:

       HistDate                              Now
          |                                   |
ID: 5   |-----|                                  <- before HistDate
ID: 6     |-----------|                            <- a gap
ID: 6                     |------|
ID: 6                            |------------|
ID: 7     |---------|
ID: 7               |-----|                        <- overlap
ID: 7                   |-----|
ID: 7                         |---------|
ID: 8     |-------------------------------------|  <- greater than current date
ID: 9     |---------|
ID: 9               |--------------------|
ID: 9               |--------------------|         <- duplicate
ID: 9                                    |----|

A "full set of data" means a consistent set of data for the id.

When loading the data I could order the loads by id and from_date to simplify
the validation. In that the data would be loaded in a (from_date) time ordered
fashion, which would satify the logic described above.

Best Regards,

Rating: 3
I've posted this as a separate question... February 06, 2004
Reviewer: Matt from Australia
...lots of info in here and I know the rules :o)

Followup:
this looks like something you might have to do procedurally -- as I could easily
envision multiple covering sets for a given "id" are possible -- even probable
and SQL isn't going to be the easiest thing to deal with that..

Sorry but this looks like "a program", not a simple query.  especiall since a
single row might be used in more than one "set" and SQL 'hates' that, since it
would only return the row 'once'

or, is it that all of the rows for a given ID are to be used to create the
covering set??  that we could probably do.

Rating: 3
February 08, 2004
Reviewer: Matt from Australia
I guess it is possible that there may be multiple covering sets for an id (but
the original post provides a means of avoiding overlaps for an id so we could
catch this). So I think that at least part of this can be done.

"or, is it that all of the rows for a given ID are to be used to create the
covering set??  that we could probably do."

I recieve the "clean" data and I am expecting that this will be possible (your
statement above). I need to capture the data that satisfies the conditions
described and if there is more data than could be loaded flag this as problem
data. In reality I will initially load the 'good' data for an id and identify
the problem in the data that could not be loaded for the same id (and then the
whole data for the id will be fixed ready for the next load run)

Rating: 3
Locking strategy in "discount_info_ai" above March 03, 2004
Reviewer: Matt from Australia
In the trigger discount_info_ai you use

    status := dbms_lock.request
             ( id                => 123,
               lockmode          => dbms_lock.x_mode,
               release_on_commit => TRUE );

to serialise access to the table. So each time the table is accessed and the
trigger fires the lock with user defined id 123 is requested.

Isn't there some additional logic required examine the status value and act
accordingly? ie:
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96612/d_lock2.htm#1001077
says the possible return values are:

0 - Success
1 - Timeout
2 - Deadlock
3 - Parameter error
4 - Already own lock specified by id or lockhandle
5 - Illegal lock handle

Shouldn't the trigger only proceed on a status of 0 and raise and exception
otherwise?

Cheers

Followup:
good point, we would not catch #2 (only possible problem).

we wait forever (never timeout).
parameters are AOK.
if we already own the lock thats ok.
the lock handle is ok (cause we are not using it)

So, there should be a check for a status of 2 afterwards, just in case.  thanks

March 04, 2004 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
/
Review & Followup
Rating: 4
Use of Decode March 02, 2004
Reviewer: A Reader
Dear TOm,

I have a data in table where the values are

AA, BC, DD are to be coded as X1
WA, YC, ZD, VE are to be coded as Y1
and so on.

How can It can be done using a single SQL statement.

Thanks

Followup:
decode or case can do that.

select decode( c, 'aa', 'x1', 'bc', 'x1', 'dd', 'x1',
                  'wa', 'y1', ...... )

Rating: 3
DECODE March 03, 2004
Reviewer: A Reader
I have a data in table where the values are

AA, BC, DD are to be coded as X1
WA, YC, ZD, VE are to be coded as Y1
and so on.

How can It can be done using a single SQL statement.

Thanks

Followup:
decode or case can do that.

select decode( c, 'aa', 'x1', 'bc', 'x1', 'dd', 'x1',
                  'wa', 'y1', ...... )

But its a very long list of values approx 200 and its increasing. I have table
where I have maintained the  values like this

col1   col2
aa      x1
bb      x1
cc      x1
ee      y1
ff      y1

and so on.

And writing decode with hard coded values is very difficult to maintain.

Please tell me if some other solution is there.

Regards

Followup:
Join.  Either:

create or replace view V
as
select t.*, lookup.col2
  from T, lookup
 where t.col1 = lookup.col2
/

or

create or replace view V
as
select t.*, (select col2 from lookup where lookup.col1 = t.col1 ) col2
  from t
/

March 04, 2004 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

Rating: 3
how about data block error ? March 03, 2004
Reviewer: ye from China
hi Tom, I met something strange. The
environment is Dell Pc Server, linux Ent AS 2.1, oracle9iEE, 4G ram, oracle core
was re-compiled for support 2.7g SGA, each data file is 10G, next increase
segment is 10G, max 30G, total are over 400G.

In recent days, when I execute the SQL such as: insert into table1 select * from
table2 , I often met the data block error , but not all the time, about 8 out of
10.

I tried:
1. At first, FScheck showed error, but I fixed it . now FScheck show no error.
2. move the data to a new tablespace.
3. export the schema and import to another tablespace with new data files.
4. minish the datafile's size to 10G, max 20G.

Still can't work.

what happened ? Is it because of datafiles are too big ?

Followup:
hows about sharing the exact text of the error code/message

Rating: 4
:) March 04, 2004
Reviewer: ye from china
the following errors were given in sqlplus:
ora-01578 oracle data block corrupted (file #mmm, block #nnn)
ora-01110 data file yy: 'xxx.dbf'
ora-06512 at  "user.SP", line 33
ora-06512 at line 1

the following errors were given in bdump/alert.log:

Corrupt block relative dba: 0x03432862 (file 13, block 206946)
Bad header found during user buffer read
Data in bad block -
 type: 6 format: 2 rdba: 0x03d26d31
 last change scn: 0x0000.00683db4 seq: 0x1 flg: 0x06
 consistency value in tail: 0xffa00601
 check value in block header: 0x6934, computed block checksum: 0xbc48
 spare1: 0x0, spare2: 0x0, spare3: 0x0
***
Reread of rdba: 0x03432862 (file 13, block 206946) found same corrupted data
***

......same block.....
....

lastest 2:

Corrupt block relative dba: 0x02c37501 (file 11, block 226561)
Bad header found during user buffer read
Data in bad block -
 type: 6 format: 2 rdba: 0x02d3cecd
 last change scn: 0x0600.7ff462bf seq: 0x1 flg: 0x06
 consistency value in tail: 0x83f70601
 check value in block header: 0x3aeb, computed block checksum: 0xef92
 spare1: 0x0, spare2: 0x0, spare3: 0x0
***
Reread of rdba: 0x02c37501 (file 11, block 226561) found same corrupted data
***
Corrupt block relative dba: 0x02c37501 (file 11, block 226561)
Bad header found during user buffer read
Data in bad block -
 type: 6 format: 2 rdba: 0x02d3cecd
 last change scn: 0x0600.7ff462bf seq: 0x1 flg: 0x06
 consistency value in tail: 0x83f70601
 check value in block header: 0x3aeb, computed block checksum: 0xef92
 spare1: 0x0, spare2: 0x0, spare3: 0x0
***
Reread of rdba: 0x02c37501 (file 11, block 226561) found same corrupted data

---------------------------------
Tom, you are great, your advice reminded me.
When I calmed down and checked this alert.log , I found  that the errors
happened for the first time was Feb.15, much earlier than I had thought:

Corrupt block relative dba: 0x0358c9b8 (file 13, block 1624504)
Fractured block found during user buffer read
Data in bad block -
 type: 6 format: 2 rdba: 0x0358c9b8
 last change scn: 0x0600.985f011a seq: 0x49 flg: 0x04
 consistency value in tail: 0x30033101
 check value in block header: 0x20ca, computed block checksum: 0xe9db
 spare1: 0x0, spare2: 0x0, spare3: 0x0
***
Reread of rdba: 0x0358c9b8 (file 13, block 1624504) found same corrupted data
Sun Feb 15 01:23:04 2004

.......

Errors in file /oracle/admin/xxx/udump/xxx_ora_6784.trc:
ORA-01578: ORACLE data block corrupted (file # 14, block # 1405670)
ORA-01110: data file 14: '/data/oradata/xxx3.dbf'
ORA-12801: error signaled in parallel query server P001
ORA-01578: ORACLE data block corrupted (file # 13, block # 1624504)
ORA-01110: data file 13: '/data/oradata/xxx2.dbf'
Sun Feb 15 01:35:32 2004

Tom, if you want to see the full log, I can email it to you, about 1Mb . :)

Followup:
You'll want to work with support on this -- it'll either be hardware problem or
bad interaction between bits of software.

March 04, 2004 Prasanna -- Thanks for the question regarding "Features of 9i", version 8i
You Asked
Hi Tom,
My application currently runs on 8i and we are planning to upgrade it to ORACLE
9i.
I would like to know few things before we go ahead,

1) What are the steps to be considered before migration from 8i to 9i. Most
documentations talks about Oracle 7 To 8i Migration in detail, but not about the
8i to 9i migration.

2) What enhancements are there in PL/SQL in 9i over 8i.
i.e any features not available / available but not allowed in PL/SQL in 8i and
allowed in 9i. Can you please provide me with the list of those features.

this can help me to decide whether we genuinely require 9i.

Regards
YERRA
and we said...
1) for a migration -- it is purely evolutionary, no big changes like from 7 to 8
for an existing app (there was that whole rowid thing, absolute to relative file
numbering and so on).

An 8i app should (barring bugs or changes in the the expected behavior) run "as
is".  You can then go back and add 9i features to it as you see fit.

2) see
http://otn.oracle.com/docs/products/oracle9i/doc_library/901_doc/appdev.901/a89856/whatsnew.htm#967650
for "whats new in ps/sql"

perhaps the biggest "whats new" (i haven't played with it but have high hopes)
is Native compilation of PLSQL:

Improve performance by compiling Oracle-supplied and user-written stored
procedures into native executables, using typical C development tools. This
setting is saved so that the procedure is compiled the same way if it is later
invalidated.
Review & Followup
Rating: 4
July 15, 2001
Reviewer: A reader

Rating: 5
Migration from 8i to 9i June 06, 2002
Reviewer: Munz from Reston, USA
Tom:

We have a html web application running as stored procedures in 8.1.7 database.

THe DBA wants to upgrade from 8.1.7 to 9.0.2 on IBM/AIX server.

Is there anything that we need to do or be aware of for the web applications.
Do you have to test the application again using the 9i database?

Thank you,

Followup:
Well, anytime you change ANYTHING -- you need to test.  Period.

I would say an upgrade from 817 to 902 would deserve a "test" -- definitely,
100%, yes, certainly.

Rating: 5
Upgrade June 06, 2002
Reviewer: A reader
And even an upgrade to 9.2.0 as I don't believe there is such a beast as 9.0.2
:)

Followup:
sure there is.  There was a 9.0.2 patchset.

Rating: 5
Do you have to test the application again using the 9i database? June 06, 2002
Reviewer: Mark A. Williams from Indianapolis, IN USA
Munz:

"Do you have to test the application again using the 9i database?"

What else would you do?  Go with "Well, gee, it compiled under 9i, so it *must*
be ok... no reason to do a proper job here"?

This isn't meant as a "slam", but of the many questions you've posed here,
surely you've learned enough to be able to answer that question after a little
thought...

- Mark

Rating: 5
9.0.2 Patchset June 07, 2002
Reviewer: Mark A. Williams from Indianapolis, IN USA
Tom:

Which platform for the 9.0.2 patchset?  I'm looking in MetaLink and only see:

9.2.0.1
9.0.1.3
9.0.1.2
9.0.1.1
9.0.1.0

Thanks!

Mark

Followup:
Contact support -- there is a 9.0.2 out there (ias and others are built on that
set of RSF's)

it's the stuff that comes with the current ias release.  There may not have been
a 9.0.2 standalone db release now that I peek around.

Rating: 4
What's new in 9i R2 ? June 07, 2002
Reviewer: Andre from Brazil
What can you say are the most important new features in 9i R2 compared to R1 ?
Right now, I'm studying R1, but the OU books often say something like "... in a
later release of 9i ..."

Followup:
So far -- personal favorites are

o logical standby, as opposed to physical standby.  the logical standby has all
of the features of the current (9i and before) physical standby but it can also:

  - be on a different OS/platform (hetergenous)
  - be open for reading and even writing whilst being recovered to
  - supports the addition of additional structures against the data being
"guarded"
    -- such as additional indexes and materialized views (making it excellent
for
    offloading dss type work)

to name a few

o rename a column (yeah)

o DBA can grant on another schema's objects (yeah)

Rating: 4
No more GATHER STALE needed ? June 10, 2002
Reviewer: Andre from Brazil
Is it true 9i R2 has a new feature with which I don't have to "download"
statistics generated by MONITORING using GATHER STALE any more ?

Followup:
Sort of -- but not quite.  You still need to analyze...

see
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/sql_1016.htm#26070

      Rating:
      4
Continued  June 11, 2002
Reviewer:
      Andre  from Brazil
Well, it seems to me DYNAMIC SAMPLING is still just a worthy turnaround in some
cases. But I imagine you still recommend using DBMS_STATS or ANALYZE with jobs
or crontab. Right ?

          Followup:

Yes, where dynamic sampling might come in handy is....

o tables that frequently and continous change in size
o maybe useful with global temporary tables, but I haven't really played with
that as yet.

      Rating: 5
New
      features  June 12, 2002
Reviewer:  Andre
       from Brazil
Short answers would be fine...

1 - Is Data Guard sold separately from Oracle9i ?
2 - Can you give me or direct me to an overview of: EXPROC, create library, and
their use with create procedure/function ? What are "agents" in this context ?
(I know what a .so lib in UNIX is)
3 - I understand how an HS agent works internally. It seems to be used for
connecting the Oracle server to other systems. Can you give me an example of
such systems?

          Followup:

1) it is part of 9i EE (enterprise)

2) my book -- i spend 70 pages on extprocs in C....  Agents are new with 9i,
they let you run an extproc process for each library if you want.  this is so
that

  a) code you write can run as "user1", code we write can run as "user2"
(different
     OS users with different OS privs
  b) if your code crashes, it won't crash our extproc (and vice versa ;)

3) sqlserver, db2, any odbc compliant data source using the odbc gateway, etc
etc
   etc.

      Rating: 3
Oracle 9i new
      features details  July 08, 2002
Reviewer:  N

Hi tom.
Please point where to find O9i 9.0.1.1.1 features and its details
Thanks

          Followup:

General question to all you out there --

is the documentation really that hard to discover???
is otn.oracle.com an "unknown" resource???

(this is not a slam to "N" here, I just really am wondering if something is
totally broken).

goto
http://otn.oracle.com/
click on documentation.  Then, since you are interested in the database, pick
the release you want (901).  Then, to get an inclusive list of books click on
"list of books".  Then click on New

      Rating: 4
Can you explain
      "native" execution?  July 17, 2002
Reviewer:
      Doug  from Woodbury, CT USA
Tom - you said way up at the top that your favorite feature was native PL/SQL
execution.  I looked a the doc.  I'm confused.  What's so un-native about PL/SQL
in the first place? (I've always been confused by that term).

          Followup:

Native COMPILATION -- not execution.

Native compilation (ncomp for short) is the ability to have plsql compiled into
object (binary) code on the target operating system INSTEAD of it being turned
into OS independent pcode (psuedo code) that is intepreted at runtime.

It makes it run faster.

      Rating:
      1
  July 18, 2002
Reviewer:
      A reader

      Rating: 5
What is common
      SQL parser? Is this new in 9i? Thanks.  February 24, 2004

Reviewer:  David

          Followup:

in 8i and before plsql had its parser, the database had its parser and so on.
What this lead to was this behaviour at times:

ops$tkyte@ORA817DEV> select count(*) over () from dual;

COUNT(*)OVER()
--------------
             1

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> begin
  2      for x in ( select count(*) over () from dual )
  3      loop
  4          null;
  5      end loop;
  6  end;
  7  /
    for x in ( select count(*) over () from dual )
                                    *
ERROR at line 2:
ORA-06550: line 2, column 37:
PLS-00103: Encountered the symbol "(" when expecting one of the following:
, from

we know that sql is valid but the plsql parser hadn't yet caught up to this
new feature, the workaround was:

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> declare
  2          type rc is ref cursor;
  3          C rc;
  4          l_cnt number;
  5  begin
  6          open c for 'select count(*) over () from dual';
  7          loop
  8                  fetch c into l_cnt;
  9                  exit when c%notfound;
 10          end loop;
 11          close c;
 12  end;
 13  /

PL/SQL procedure successfully completed.

to use dynamic sql to hide the construct from plsql and let the rdbms parse
the query itself.  In 9i, with the common parser (eg: sharing code), this is
corrected for once and for all

      Rating:
      5
Thanks.  February 25, 2004
Reviewer:
      David

      Rating: 4
logical
      standby  March 04, 2004
Reviewer:  Venkat
       from India
Tom,

In logical db, redo from primary is converted into SQL and it is applied in the
standby database. Will it take more time when compared to redo apply in
physical?

Thanks,
Venkat

          Followup:

sure - but -- the db is open for read write access which is something that is
just "not possible" with a physical standby

March 04, 2004 --
      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

      Rating: 4
How can session
      pga memory be more than the physical RAM ?  March 04, 2004

Reviewer:  Vivek Sharma  from Bombay, India

Dear Tom,

I have an Oracle 8i Database with version as 8.1.7.4.. The Server has around 24
GB of Physical Ram and the Size of the SGA is around 2.4 GB (modified the
libraries to relocate the SGA limit). The Sort_Area_size is 10M and
sort_area_retained_size is 5M. During the peak time, the number of sessions
connected to the database is around 1300. The output of the below mentioned
query is

select name, value/1024/1024/1024
from v$sysstat where name like '%memory%'

NAME                      Value in GB
------------------------- -----------
session uga memory         3.39125823
session uga memory max     58.3202833
session pga memory         115.802932
session pga memory max     121.057358

My Question is

How can the PGA grow upto 115 GB and max UGA grew upto 58 GB ? Will it not
affect the performance due to paging and swapping ? How do I avoid this ?

Regards
Vivek

          Followup:

it is a cumulative number since instance startup -- it is not "very meaningful"

March 04, 2004 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

      Rating: 5
When both Child
      and Parent tables are key preserved  March 04, 2004

Reviewer:  Tommy Raun  from Denmark
Dear Tom,

In the following testcase in Oracle 9R2 could you please explain how Oracle
decides what tabel it will delete from.

ops$tr@9R2> CREATE TABLE t1 AS SELECT * FROM all_objects ;

Table created.

ops$tr@9R2> CREATE UNIQUE INDEX t1_uk ON t1 (object_id) ;

Index created.

ops$tr@9R2> CREATE TABLE t2 AS SELECT * FROM t1 ;

Table created.

ops$tr@9R2> CREATE UNIQUE INDEX t2_uk ON t2 (object_id) ;

Index created.

ops$tr@9R2> DELETE (
  2     SELECT 1
  3     FROM t1, t2
  4     WHERE t1.object_id = t2.object_id
  5  ) ;

13938 rows deleted.

ops$tr@9R2> SELECT (SELECT COUNT(*) FROM t1) t1_count,
  2         (SELECT COUNT(*) FROM t2) t2_count
  3  FROM DUAL ;

  T1_COUNT   T2_COUNT
---------- ----------
         0      13938

1 row selected.

ops$tr@9R2> ROLLBACK;

Rollback complete.

ops$tr@9R2> DELETE (
  2     SELECT 1
  3     FROM t2, t1
  4     WHERE t1.object_id = t2.object_id
  5  );

13938 rows deleted.

ops$tr@9R2> SELECT (SELECT COUNT(*) FROM t1) t1_count,
  2         (SELECT COUNT(*) FROM t2) t2_count
  3  FROM DUAL ;

  T1_COUNT   T2_COUNT
---------- ----------
     13938          0

1 row selected.

Is it the order of the tables in the FROM-clause that Oracle uses to deside
which table to delete from?

I am having trouble finding information about inline UPDATE/DELETE in the
documentation. Could you please tell me where to look?

          Followup:

looks like it is either

a) not supposed to work
b) incorrectly documented

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96590/adg03sch.htm#1093
so, I'll have to file a bug to figure out if it is a) or b)

I'll file it as a doc bug and let them decide if

a) it is not supposed to work (and hence will become a database bug)
b) is incorrectly documented (and they will fix that)

      Rating:
      5
  April 01, 2004
Reviewer:  VP
       from USA
Excellent

March 04, 2004 C. David -- Thanks for the question regarding
      "Outer Join w/Subquery "Table"", version 7.3.4

      You Asked
Hi Tom, can this be done?

SELECT
  <whatever...>
FROM
  table_A LEFT JOIN
  (SELECT rec_id,code FROM table_B WHERE select_col = 123)
table_subq
  ON table_A.rec_id = table_subq.rec_id

I wanted to use a subquery in the select block, but apparently I
need some version of 8 to do that...

I'm kind of stuck.  Any suggestions, in case the above is
impossible?  I need to do an outer join between the tables
because I know that most records in A won't have a record in B.
However, the records in B are typed, and I'm only concerned with
one type, so I want to filter out the others, but while avoiding
filtering the records in A without a match.

Thanks much!
David

      and we said...
pretend emp is TABLE_A and dept is TABLE_B and select_col is
deptno in the following (and 10 is your 123).  Doesn't this do
it for you??

scott@TKYTE816> select a.ename, b.deptno
  2  from emp a, dept b
  3  where a.deptno = b.deptno(+)
  4  and b.deptno(+) = 10;

ENAME          DEPTNO
---------- ----------
SMITH
ALLEN
WARD
JONES
MARTIN
BLAKE
CLARK              10
SCOTT
KING               10
TURNER
ADAMS
JAMES
FORD
MILLER             10

14 rows selected.

Review &
      Followup

      Rating: 4
How to convert
      SQL92  December 05, 2001
Reviewer:  Sivababu
       from INDIA
Hello TOM,

How to convert this query into SQL92 standard like supporting ORACLE 9i,
SQLSERVER and SYBASE etc.

select a.ename, b.deptno
    from emp a, dept b
    where a.deptno = b.deptno(+)
    and b.deptno(+) = 50
 It will return the rows eventhoug the deptno 50 doesn't have the record.

Please reply me.

regards,
sivababu

          Followup:

That query makes no sense to me -- why would you want it.  All is does is:

  bring back every row in EMP and if the deptno is 50, give me the DEPT info as
well

I understand the outer join from EMP to DEPT but what would the "b.deptno(+) =
50" do beside blank out the dept????

Ok, you can achive this via:

scott@ORA9I.WORLD> select a.ename, b.deptno
  2      from emp a, dept b
  3      where a.deptno = b.deptno(+)
  4      and b.deptno(+) = 50
  5  /

ENAME          DEPTNO
---------- ----------
SMITH
ALLEN
WARD
JONES
MARTIN
BLAKE
CLARK
SCOTT
KING
TURNER
ADAMS
JAMES
FORD
MILLER

14 rows selected.

scott@ORA9I.WORLD>
scott@ORA9I.WORLD> select a.ename, b.deptno
  2    from emp a LEFT OUTER JOIN ( select * from dept where deptno = 50 ) b
  3    on ( a.deptno = b .deptno )
  4  /

ENAME          DEPTNO
---------- ----------
SMITH
ALLEN
WARD
JONES
MARTIN
BLAKE
CLARK
SCOTT
KING
TURNER
ADAMS
JAMES
FORD
MILLER

14 rows selected.

Thats ANSI and does the same thing.

      Rating: 4
ANSI Standard -
      Oracle 9.0.1  January 05, 2002
Reviewer:
      sivababu  from INDIA
Hello TOM,
 I have the doubt about working the above query. Can i use both queries or will
it effect the performance any one of this query?. But i'm getting same result
and same COST. Which one i can use.

 select e.ename, d.deptno, d.dname from emp e
 left outer join ( select * from dept  where LOC = 'NY') d
 on (d.deptno = e.deptno)
 where  e.ename = 'KING'

result :

ENAME          DEPTNO DNAME
---------- ---------- --------------
KING

 select e.ename, d.deptno, d.dname from emp e
 left outer join  dept d
 on (d.deptno = e.deptno and LOC = 'NY')
 where  e.ename = 'KING'
 /

ENAME          DEPTNO DNAME
---------- ---------- --------------
KING

thanks and Expecting your reply.

with regards,
sivababu

          Followup:

Not only are the costs the same (not that you can really compare the costs of
two DIFFERENT queries, you can only really compare the costs of the different
plans generated for a SINGLE query -- the costs are compared within the set of
plans generated for a single query by the CBO to determine which one is best.  A
query with a cost of 1,000 might outrun a query with a cost of 2 -- cost is not
necessarily an indicator of performance!) but the PLANS are identical.

The queries are semantically identical, they result in the same plans, they are
in fact the same for all intents and purposes.

      Rating: 3
Why doesn't
      this query work with a max function in the subquery?  March
      04, 2004
Reviewer:  A reader
Making a small change to the subquery breaks it.

select a.ename, b.deptno
from emp a LEFT OUTER JOIN ( select max(deptno) from dept ) b
on ( a.deptno = b .deptno );

ERROR at line 3:
ORA-00904: "B"."DEPTNO": invalid identifier

Does that make sense?

Can you help me with the syntax?

Thanks

          Followup:

because there is no "deptno" in the "b" table -- there is a max(deptno).

alias it  "select max(deptno) deptno ...."

      Rating: 4
how about
      aliasing the max(deptno)? :}  March 04, 2004

Reviewer:  A reader
Why do you have to alias max(deptno) ?

      Rating: 4
uhm forget i
      asked  March 04, 2004
Reviewer:
      A reader

March 04, 2004 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

      Rating: 5
highest
      frequency and positional analysis  March 04, 2004

Reviewer:  kw  from USA
Hi Tom,
As always your responses are great! Thanks in advance.

In addition to above question=>
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
1/1/2003      6   39    40     45    23   9
1/8/2003      1   6     42     43    19   21
1/16/2003     6   9     15     43    19   21
etc etc

I want to have 2 different outputs ( 2 queries )
1) Highest Frequency
I want a output like
Number =>  Frequency
6 =>  3
9 =>  2
19 =>  2
21 =>  2
45 =>  1 etc.. in sorted order by frequency desc.

2) Positional ayalysis
Each number was pull how many times in each postion.
e.g.
Number  ball1 ball2 ball3 ball4 ball5 ball6
1         1    0      0    0      0    0
2         0    0      0    0      0    0
.
.
6         2    1      0    0       0    0
etc..

I am new to oracle and I tried to write these 2 queries on my own but my results
never came close to what I want, Please help

          Followup:

I won't make any scalability claims here - but it answers the question:

ops$tkyte@ORA9IR2> create table tt
  2  as
  3  select rownum b from all_objects where rownum <= 49;

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select b, count(*),
  2         count( decode( b1, b, 1 ) ) b1,
  3         count( decode( b2, b, 1 ) ) b2,
  4         count( decode( b3, b, 1 ) ) b3,
  5         count( decode( b4, b, 1 ) ) b4,
  6         count( decode( b5, b, 1 ) ) b5,
  7         count( decode( b6, b, 1 ) ) b6
  8    from t, tt
  9   where b in ( b1, b2, b3, b4, b5, b6 )
 10   group by b
 11   order by 2 desc
 12  /

   B COUNT(*)   B1   B2   B3   B4   B5   B6
---- -------- ---- ---- ---- ---- ---- ----
   6        3    2    1    0    0    0    0
  19        2    0    0    0    0    2    0
  21        2    0    0    0    0    0    2
  43        2    0    0    0    2    0    0
   1        1    1    0    0    0    0    0
  23        1    0    0    0    0    1    0
  40        1    0    0    1    0    0    0
  45        1    0    0    0    1    0    0
  42        1    0    0    1    0    0    0
  39        1    0    1    0    0    0    0
   8        1    0    1    0    0    0    0
   9        1    0    0    0    0    0    1
  15        1    0    0    1    0    0    0

13 rows selected.

      Rating: 5
Really think
      you should have held out for a share of the loot  March 04,
      2004
Reviewer:  Jim  from Portland, OR USA

Tom,
You should have held out for a share of the loot on the lottery. :-)

March 04, 2004 Uday -- Thanks for the question regarding "Can i
      pass "C" structures to Oracle", version 8i

      You Asked
Hi Tom,
I want to pass a "C" structure as a parameter to Oracle procedure/function.
Is this possible or should i have to pass each variable by variable if i cant
pass a C structure.
  This requirement came up because our PRO*C programs gets the structures and
does the DML operations by passing variable by variable which consumes time.
So instead if i can pass the struncture as it is i can do the rest of the
operations in the procedure/function which i write in the database which will
improve my performacne.

Hope i made the question clear.Let  me know if you require any clarification on
the question.

Thanks & Regards
Uday.
++++++++++++++++++++++++++++++++++++++++++++++++++
Additional information:

Ex:I am giving a prototype of the code:

typedef struct
{ int x;
  int y;
  Struct st1;
} my_rec;

typedef st1
{
   int xx;
   int yy;
   int zz;
}

suppose i have a procedure in oracle:
Create procedure Test(Integer one,???????)
{
}

Can ??????? be a "C" structure.

Thanks & Regards
Uday.

      and we said...
It can be a C structure if called from SQL.  Consider:

create or replace function p(p_x in number, p_y in number )
return number
as
begin
   return p_x+p_y;
end;
/

static void process()
{
exec sql begin declare section;

typedef struct
{ int x;
  int y;
} my_rec;

my_rec data;
int    x;
exec sql end declare section;

    data.x = 1;
    data.y = 2;

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();
    exec sql select p(:data) into :x from dual;

    printf( "return value= %d\n", x );
}

that works.  Note that it must be a FUNCTION.  Also, it cannot have any OUT
parameters.  Lastly, if it modifies the database, it MUST be an autonomous
transaction.

Another method would be:

scott@DEV816> create or replace function p(p_x in OUT number, p_y in OUT number
)
  2  return number
  3  as
  4          l_tmp number := p_y;
  5  begin
  6          p_y := p_x;
  7          p_x := l_tmp;
  8     return p_x+p_y;
  9  end;
 10  /

Function created.

scott@DEV816> create global temporary table p_interface_table
  2  ( x int, y int, return_code number )
  3  on commit delete rows
  4  /

Table created.

scott@DEV816> create or replace trigger p_interface_table_trigger
  2  BEFORE insert on p_interface_table for each row
  3  begin
  4          :new.return_code := p(:new.x, :new.y);
  5  end;
  6  /

Trigger created.

static void process()
{
exec sql begin declare section;

typedef struct
{ int x;
  int y;
  int return_code;
} my_rec;

my_rec data;
int    x;
exec sql end declare section;

    data.x = 1;
    data.y = 2;

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();
    exec sql insert into p_interface_table values ( :data );

    exec sql select * into :data from p_interface_table;

    printf( "x, y, return value= %d, %d, %d\n",
             data.x, data.y, data.return_code );
}

That lets you do in out variables and does not make you use an autonomous
transaction if the procedure/function (you can use either here of course)
modifies the database as well.

Review &
      Followup

      Rating: 1
INSERT, STRUCT,
      DATE  March 04, 2004
Reviewer:  Peter Smith
       from ENGLAND
I have a program using array inserts of the old variety, i.e. an array for each
column:

 FOR :aNumRows EXECUTE S_YEAR  USING
  :mArrDate[0]:mArrDateInd, ...

I want to convert this to an array of structure inserts.
To do this you have to lie in the PREPARE and pass a single struct, but pass the
array in
  EXECUTE S_YEAR USING :mCdrStruct[0]:mCdrStructInd[0];

All this is fine, except that my table contains a date column.  Usually I use
to_date() to populate this but in the array version all you can pass is the
array of structures and it's indicator array.

So my structure must contain a date not a char[].

Approaches:

typedef char MyDate[7];
EXEC SQL TYPE MyDate IS DATE;
typedef struct
{ ...
  myDate date_start;
}
s.date_start[0] = 119;
s.date_start[1] = 204; /* 2004 */
s.date_start[2] = 3; /* march */
...

Doesn't work.

OCIDate: Cannot put OCIDate in array variables!

Default of to_date(), assume that ascii DD-MAR-04 works.
But it doesnt.

How can one do array of struct inserts if the table contains a date?

          Followup:

don't even consider using the 7 byte date format, it'll cause you no end of
misery.

Just set your nls_date_format and use strings as always:

void process( void )
{
exec sql begin declare section;

struct test_data
{
    char    str[15];
    char    dt[25];
    int     num;
} test_d[10];

int     n;
exec sql end declare section;
int     i;

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();
    for( i = 0; i < 10; i++ )
    {
        sprintf( test_d[i].str, "data%d", i );
        sprintf( test_d[i].dt, "%d/03/2004", i+1 );
        test_d[i].num = i;
    }
    n = 10;

    exec sql alter session set nls_date_format = 'dd/mm/yyyy';
    exec sql for :n insert into t (str,dt,num) values (:test_d );
    exec sql commit;
}

March 04, 2004 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.

      Rating: 4
Switch takes
      over 1 minute  March 04, 2004
Reviewer:
      yazid  from france
Hello,
I have a prod database that the switch takes an average of more than 1 Minute :

Beginning log switch checkpoint up to RBA [0x268f.2.10], SCN: 0x03e9.b28bba13
Thread 1 advanced to log sequence 9871
  Current log# 5 seq# 9871 mem# 0: /disk3/pdco1/var/redolog/redo1.log
Mon Mar  1 13:15:07 2004
Completed checkpoint up to RBA [0x268f.2.10], SCN: 0x03e9.b28bba13
Mon Mar  1 13:15:53 2004
Beginning log switch checkpoint up to RBA [0x2690.2.10], SCN: 0x03e9.b28c3f97
Thread 1 advanced to log sequence 9872
  Current log# 6 seq# 9872 mem# 0: /disk3/pdco1/var/redolog/redo2.log
Mon Mar  1 13:21:29 2004
Completed checkpoint up to RBA [0x2690.2.10], SCN: 0x03e9.b28c3f97
Mon Mar  1 13:22:20 2004
Beginning log switch checkpoint up to RBA [0x2691.2.10], SCN: 0x03e9.b28ca165
Thread 1 advanced to log sequence 9873
  Current log# 2 seq# 9873 mem# 0: /disk3/pdco1/var/redolog/redo3.log
Mon Mar  1 13:30:07 2004
Completed checkpoint up to RBA [0x2691.2.10], SCN: 0x03e9.b28ca165
Mon Mar  1 13:30:56 2004
Beginning log switch checkpoint up to RBA [0x2692.2.10], SCN: 0x03e9.b28d2627
Thread 1 advanced to log sequence 9874
  Current log# 5 seq# 9874 mem# 0: /disk3/pdco1/var/redolog/redo1.log
Mon Mar  1 14:01:56 2004
Completed checkpoint up to RBA [0x2692.2.10], SCN: 0x03e9.b28d2627
Mon Mar  1 14:04:24 2004

As we can see I have 3 redo groups, every redo file is 150Mo

My questions :
1)What is -the acceptable time for a switch ? I think one minute is huge.
2) What is the reason for this ? (not a lot redo groups, may be LGWR is blocked
by DBWR)

No other alerte in the <SID>ALRT.log

thanks.

          Followup:

that is not the time for the switch.  the switch was INSTANTANEOUS.

this is the time to write the blocks from the buffer cache onto disk, in the
background, slowly so as to not swamp the machine.

1 minute seems "dandy"

there is nothing wrong here -- this is normal.  You don't want DBWR (lgwr isn't
really involved here) to totally squash you.  You just want to make sure that
dbwr is flushing FAST enough so as to have flushed the blocks before the redo
log file that fired the checkpoint needs to be reused - and in your case, it is
(there are no "checkpoint not completes" in your logs so it is happening more
than fast enough)

March 04, 2004 Vijay -- Thanks for the question regarding
      "Consider Cost or Time - trying to compare the COST of two queries",
      version 8.1.5

      You Asked
Hi,
I am using Cost based optimer for my application. I have 2
queries; assume Query 1 and Query 2.

The cost of execution of query 1 is around 400 and the cost of
execution of Query 2 is 200.

But the execution time seems to be reverse. Query 1 executes
faster and query 2 seems to execute slower. I checked this out
by setting the "set timing on" command on sqlplus.

This seems to me as little contradictory, cost being high and
execution time lesser. Does the cost have a relationship with
the time ?

Which one is recommended to be used for high performance (
minimum response time ?

Regards
Vijay

      and we said...
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.

Review &
      Followup

      Rating: 5
Consider Cost
      or Time - trying to compare the COST of two queries  March
      15, 2002
Reviewer:  Venkateshwaran  from India

It was very useful information.

Thanks Tom.

      Rating: 3
then how do we
      tune SQL?  May 26, 2002
Reviewer:
      A reader
hi tom

if cost is not relative in 2 queries or even same query but with different
execution plan (onme with hint one without) what can we use to base our tuning
SQL strategies? The only way is run it and see which one is faster...? That
would sound blizzard though

for example if I have

select nom_proceso
from md_grupo a, md_aplicacion b, md_tipo_carga c, md_proceso d
where a.cod_grupo=b.cod_grupo
and b.cod_aplicacion=c.cod_aplicacion
and c.cod_tipo_carga=d.cod_tipo_carga
and d.cod_tipo_carga=4

I get cost 5

if I add a hint

select /*+ ORDERED */ nom_proceso
from md_grupo a, md_aplicacion b, md_tipo_carga c, md_proceso d
where a.cod_grupo=b.cod_grupo
and b.cod_aplicacion=c.cod_aplicacion
and c.cod_tipo_carga=d.cod_tipo_carga
and d.cod_tipo_carga=4

I get cost 7, since you said we cannot compare the costs of queries can I say
the second statement is faster if it returns the rows faster?

          Followup:

why does it sound bizzare?

You can say NOTHING about the speed of query two over query one.  All you've
done is use a hint to artifically inflate the costs used by the CBO internally
for all other access plans.  Hints work explicitly by playing with the internal
costs -- making the path you want us to take look more appealing.

If getting the lowest cost resulted in the fastest query -- the CBO would get it
perfect every time without your help ever!

      Rating: 4
ok then the
      only way is running it  May 26, 2002
Reviewer:
      A reader
thank you tom

I will run the queries now, instead of comparing costs

cheers

      Rating:
      5
Cost  May 27, 2002
Reviewer:  Andre

Speaking of costs, I'm trying to mesure the cost of a query using SQL*PLUS's
autotrace. I've noticed tha "physical reads" stat in autotrace seems to be ONLY
the blocks fetched though IO, whereas "physical reads" in V$SYSSTAT is the
combination of db block gets, consistent gets both from IO and memory. It is
right ?

          Followup:

physical reads on v$sysstat is physical IO as well.  On a single user system:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select name, value from v$sysstat where name
= 'physical reads';

NAME                                VALUE
------------------------------ ----------
physical reads                       7612

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly statistics;
ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from all_objects;

Statistics
----------------------------------------------------------
        261  recursive calls
          4  db block gets
      76412  consistent gets
        497  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
          8  sorts (memory)
          0  sorts (disk)
          1  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA817DEV.US.ORACLE.COM> select name, value from v$sysstat where name
= 'physical reads';

NAME                                VALUE
------------------------------ ----------
physical reads                       8116

ops$tkyte@ORA817DEV.US.ORACLE.COM> select 8116-7612 from dual;

 8116-7612
----------
       504

ops$tkyte@ORA817DEV.US.ORACLE.COM>

That shows that whilst the query itself tool 76,412 logical IO's, there were
only about 500 physical IO's performed -- both via autotrace reporting as well
as v$sysstat querying.  Remember v$sysstat is SYSTEM statistic, autotrace uses
v$sessstat SESSION statistics.

      Rating: 4
Couple of
      queries....  May 27, 2002
Reviewer:  Ruck
       from Singapaore
Tom,

Your explanation is very useful. But I still have couple of queries.

    1. Does it mean the lowest cost may not get you the fastest query path?
    2.How does this cost is calculated? Just based on the resource utilization
and it doesn't bother about the execution speed of the query?

Thanks,

Ruck.

          Followup:

1) yes.

2) The cost is a set a numbers assigned by the optimizer to various steps of a
query based on available statistics.  These costs are then factored together to
come up with an over all query cost -- a single number that assigns a relative
"cost" to a query plan.  We then pick the plan for a given query with the lowest
cost.  In theory, this will be the best performing query (that after all is the
goal).  In reality, it doesn't work that way 100% of the time.

      Rating: 4
can we compare
      cost of SAME query?  June 14, 2002
Reviewer:
      A reader
hi tomn

you said we cannot compare cost of 2 queries but can we compare of same query
but different optimizer_mode such as

first_rows and all_rows????

if for example

select * from emp in first_rows I get cost 10 and in all_rows cost 20 does that
mean that first_rows is better?

          Followup:

NO.  NO. NO.  NO.

The optimizer mode is a tool you can use to ALTER (on purpose) the COST
associated with various steps.

That is what your example shows.  By changing the optimizer goal, we've
increased the cost of some process that was "cheap" in the first rows case (eg:
index access is "cheap" in first rows).  All rows makes that "more expensive".
For all we know -- the SAME plan might be generated with different costs!  what
then, would one be better then the other?

You cannot compare costs across queries, even if the queries are "identical".

You cannot make any judgments about the relative performance of a query based on
the cost (queries with tiny costs may take days to run, queries with huge costs
could run in milliseconds).

I wish we did not even expose this number so easily .....

      Rating: 5
Finally We Can
      Put This (apparent) MisConception to Bed!!!  June 14, 2002

Reviewer:  Robert Wood  from Memphis, TN
Hi Tom,

Thanks for being a breath of fresh air in the stuffy, aloof world of Oracle
information!

Can you please confirm, yes or no....
What I hear you saying is this...
1) There is NO information we can use from the cost value.
2) We should COMPLETELY IGNORE the cost value.

Thanks,

Robert.

          Followup:

Well, no.  I did not say that.  I said:

... I wish we did not even expose this number so easily ....

they are useful, when properly used (eg: when you are TRYING to influence the
optimizer via hints and TRYING to figure out why it is doing what it is doing).

Jonathan Lewis's most awesome book -- "Practical Oracle8i" shows how to
practically use the cost in diagnosing a query for example.

      Rating: 1
I think we are
      missing a point here  June 14, 2002
Reviewer:
      An Oracle fan  from USA
Comparing the same query using COST is perfect legitimate because that is what
the COST is for.  What we expect is the optimizer to permute all possible
combinations and pick up the plan with the least cost.  If you could rewrite the
same SQL to get a cheaper cost theoretically it should be faster.  If it is not
I guess it is an optimizer bug.  Also if the COST is not just based on logical
reads but on the CPU, then comparing the SQL in the same instance is still
legitimate.

          Followup:

I'll agree with one statement you make:

"Comparing the same query using COST is perfect legitimate because that is what
the COST is for.  What we expect is the optimizer to permute all possible
combinations and pick up the plan with the least cost."

that is accurate.  After that, you are missing the point.

I don't know how to say it

I'll shout

THE COST HAS NOTHING WHATSOEVER TO DO WITH THE RUNTIME PERFORMANCE OF A QUERY.

YOU CANNOT COMPARE THEM AND SAY "THIS WILL BE FASTER"

IT IS NOT A BUG, IT IS A DESIGN FEATURE, THE WAY IT WORKS.  IT IS DOING THIS ON
PURPOSE.

YOU ARE PROJECTING YOUR THOUGHTS OF HOW YOU WOULD HAVE DONE IT -- instead of
understanding how it is actually done. (hey, i have a case study of that in my
book actually -- what happens when you assume something is true and it is
false.)

I give up.  Explain this output to me if you can.  The queries do the same
thing, they have the same cost.  why are the performance characteristics so
radically different? (hint -- it is not a bug, repeat, it is not a bug.  answer,
well, apparently you cannot really compare them... the costs that is)

ops$tkyte@ORA817DEV.US.ORACLE.COM> variable x varchar2(25)
ops$tkyte@ORA817DEV.US.ORACLE.COM> set timing on
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from big_table a
  2  where object_name like '%' || :x;

1101008 rows selected.

Elapsed: 00:05:21.46

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=431 Card=54951 Bytes=5495100)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=431 Card=54951
Bytes=5495100)
   2    1     INDEX (RANGE SCAN) OF 'OBJECT_NAME_IDX' (NON-UNIQUE) (Cost=52
Card=54951)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
    1166039  consistent gets
      21649  physical reads
        256  redo size
  102352990  bytes sent via SQL*Net to client
    8147825  bytes received via SQL*Net from client
      73402  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1101008  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ full( a ) */ * from big_table a
  2  where object_name like '%' || :x;

1101008 rows selected.

Elapsed: 00:01:50.15

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=431 Card=54951 Bytes=5495100)
   1    0   TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=431 Card=54951
Bytes=5495100)

Statistics
----------------------------------------------------------
          0  recursive calls
         27  db block gets
      87396  consistent gets
      14409  physical reads
          0  redo size
  128246913  bytes sent via SQL*Net to client
    8147825  bytes received via SQL*Net from client
      73402  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1101008  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off

      Rating: 2
then how the
      heck these tools...  June 15, 2002
Reviewer:
      A reader
tom

If we cannot tune a query WITHOUT running it then how the heck  tools like
Oracle Enterprise Manager, Quest SQL Navigator tune a SQL without running it?

          Followup:

they apply a set of rule, heuristics (like the things I have in my head after
doing it for 15 years)...

I can look at a query, and a plan -- and with a KNOWLEDGE of the data
(statistics to the software, even more knowledge to a human) -- I can generally
"make the query better".  Not always, but many times.

That is the software does, it applies a series of rules to the queries and
suggests (based on rules of thumb) enhancements to the schema or the query that
would make it better.

I've yet to see any software take a query though and say things like "well, if
you remove that non-necessary outer join, use the analytic functions instead
it'll run much faster".  I see them say "add this index" sort of advice (very
basic).

      Rating: 5
Query
      tuning  June 16, 2002
Reviewer:  Anton
       from New Zealand
Tom.

So, just to clarify, for tuning SQL, we should be using tkprof/explain and
looking at the parse/fetch/execute stats and CPU stats and concentrating on
lowering those, rather than lowering the query cost ?

PS - keep up thinking !!!

          Followup:

Well, let me put it this way.

I never look at the cost personally -- unless I'm trying to figure out why the
optimizer choose what it did (and even then, you need to use an event to have
the optimizer dump more information so you can see the reasons it assigned the
costs that it did)

      Rating: 1
Provide some
      more info  June 21, 2002
Reviewer:  Same Oracle
      fan from USA  from USA
Could you provide the information like the DB_FILE_MULTIBLOCK_READ_COUNT, the
statistics of the BIG_TABLE like BLOCKS, NUM_ROWS, and the index statistics like
LEAF_BLOCKS, CLUSTERING_FACTOR, NUM_ROWS etc.,

Are we sure the statistics are correct and not manipulated? What if I set the
index statistics to something crazy?  It is really bizarre to see the COST of
INDEX ACCESS and FTS to be same.  Have you got any other example without using
"%"?

I still believe comparing the COST for the same query is directly proportional
to its run time.

          Followup:

You can do it yourself.

Frankly, I don't think anyone has accused me of "faking" a result to make a
point before.  Geez.

Yes, the stats were correct
No, the stats were not manipulated

but you know what -- EVEN IF THEY WERE, IT TOTALLY PROVES MY POINT, factually I
might add -- not by belief.

Anyway, it is not bizzare -- it is (as I said) to be expected.  The costs of two
different queries cannot be compared, not judgement as to performance may be
made, you -- you should definitely consider them random numbers.  Others, who
read Jonathan Lewis's great book -- they can use them rationally, to understand
WHY the optimizer did what it did.

It is not about what you believe to be true.  It is about what is in fact true
vs not true.

Here is the test, done on a system running:

big_table@ORA817DEV.US.ORACLE.COM> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle8i Enterprise Edition Release 8.1.7.3.0 - Production
PL/SQL Release 8.1.7.3.0 - Production
CORE    8.1.7.0.0       Production
TNS for Solaris: Version 8.1.7.3.0 - Production
NLSRTL Version 3.4.1.0.0 - Production

big_table@ORA817DEV.US.ORACLE.COM> show parameter db_file_mul

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
db_file_multiblock_read_count        integer 100

big_table@ORA817DEV.US.ORACLE.COM> show parameter block_size

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
db_block_size                        integer 8192

big_table@ORA817DEV.US.ORACLE.COM> exec show_space( 'BIG_TABLE' )
Free Blocks.............................0
Total Blocks............................20096
Total Bytes.............................164626432
Unused Blocks...........................63
Unused Bytes............................516096
Last Used Ext FileId....................7
Last Used Ext BlockId...................27529
Last Used Block.........................1

PL/SQL procedure successfully completed.

Here is the test you can run yourself.  Nuff said.  Facts is facts, beliefs --
well, nuff said.

big_table@ORA817DEV.US.ORACLE.COM> create table big_table as select * from
all_objects;

Table created.

big_table@ORA817DEV.US.ORACLE.COM>
big_table@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into big_table select *
from big_table;

22811 rows created.

big_table@ORA817DEV.US.ORACLE.COM> commit;

Commit complete.

big_table@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into big_table select *
from big_table;

45622 rows created.

big_table@ORA817DEV.US.ORACLE.COM> commit;

Commit complete.

big_table@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into big_table select *
from big_table;

91244 rows created.

big_table@ORA817DEV.US.ORACLE.COM> commit;

Commit complete.

big_table@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into big_table select *
from big_table;

182488 rows created.

big_table@ORA817DEV.US.ORACLE.COM> commit;

Commit complete.

big_table@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into big_table select *
from big_table;

364976 rows created.

big_table@ORA817DEV.US.ORACLE.COM> commit;

Commit complete.

big_table@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into big_table select *
from big_table;

729952 rows created.

big_table@ORA817DEV.US.ORACLE.COM> commit;

Commit complete.

big_table@ORA817DEV.US.ORACLE.COM>
big_table@ORA817DEV.US.ORACLE.COM> create index object_name_idx on
big_table(object_name) parallel 8 unrecoverable;

Index created.

big_table@ORA817DEV.US.ORACLE.COM>
big_table@ORA817DEV.US.ORACLE.COM> analyze table big_table compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns;

Table analyzed.

big_table@ORA817DEV.US.ORACLE.COM>
big_table@ORA817DEV.US.ORACLE.COM> variable x varchar2(25)
big_table@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain
big_table@ORA817DEV.US.ORACLE.COM>
big_table@ORA817DEV.US.ORACLE.COM> select *
  2    from big_table a
  3   where object_name like '%' || :x;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=577 Card=72996 Bytes=7080612)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=577 Card=72996
Bytes=7080612)
   2    1     INDEX (RANGE SCAN) OF 'OBJECT_NAME_IDX' (NON-UNIQUE) (Cost=69
Card=72996)

big_table@ORA817DEV.US.ORACLE.COM>
big_table@ORA817DEV.US.ORACLE.COM> select /*+ full(a) */ *
  2    from big_table a
  3   where object_name like '%' || :x;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=577 Card=72996 Bytes=7080612)
   1    0   TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=577 Card=72996
Bytes=7080612)

big_table@ORA817DEV.US.ORACLE.COM>
big_table@ORA817DEV.US.ORACLE.COM> set autotrace off
big_table@ORA817DEV.US.ORACLE.COM>
big_table@ORA817DEV.US.ORACLE.COM> select count(*) from big_table;

  COUNT(*)
----------
   1459904

      Rating: 5
I totally agree
      with you. I have one question though?  June 22, 2002

Reviewer:  Prince
Isn't it almost always, applying hint to a normal query (query without any hint)
has higher cost.

I tested many cases and its the case in all my test cases.

I believe, oracle might already have considered the path (which I choose with
the hint) and found out that it was costlier than the one chosen by oracle
(present plan).

Just academic interest, Do you have an example where we can add some hints and
still can get a lower cost?

Thanks,

          Followup:

Yes, generally.

But as to "in all my test cases" -- i just gave you one where this is not the
case, so you know now that it is not a rule, but a generalization.

Oracle will not "reason" about the query -- which is what your 3 paragraph seems
to imply (be neat, maybe in Oracle20i).  It applies rules, assigning numbers
(costs) to various access paths.  It weights those numbers, adds em up, and
compares.  Nothing more, nothing less.  The "magic" is in the assignment of the
numbers only. (It is really has nothing to do with "how long" something will
take to execute -- just the number / weight assigned to a specific operation)

Here is an example going the other way:

big_table@ORA817DEV.US.ORACLE.COM> alter session set optimizer_goal=first_rows;
Session altered.

big_table@ORA817DEV.US.ORACLE.COM>  select * from big_table where object_name is
not null order by object_name;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=1454076 Card=1459904
Bytes=141610688)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=1454076
Card=1459904 Bytes=141610688)
   2    1     INDEX (FULL SCAN) OF 'OBJECT_NAME_IDX' (NON-UNIQUE) (Cost=7356
Card=1459904)

big_table@ORA817DEV.US.ORACLE.COM> select  /*+ all_rows */ * from big_table
where object_name is not null order by object_name;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: ALL_ROWS (Cost=46883 Card=1459904
Bytes=141610688)
   1    0   SORT (ORDER BY) (Cost=46883 Card=1459904 Bytes=141610688)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=577 Card=1459904
Bytes=141610688)

So, here, by overriding the environment (the environment consists of many
init.ora parameters other then optimizer goal - multi-block-read-count,
sort-area-size, hash-join-enabled, etc etc etc) I got a query via a hint with a
much lower cost.

Does it run faster?  Maybe, maybe not ;)

      Rating: 1
Here we go
      again  June 27, 2002
Reviewer:  Same Oracle
      fan  from CA, USA
Ok, let me rephrase this again.  You can compare the same SQL when you are
trying to tune that SQL.  If you can tune the SQL to get a lower cost than
whatever CBO comes up with, it should perform better (lets not get to the run
time because it depends on the physical characteristics like file system, disk
speed etc.)  I'm not wishing CBO to behave like this but thats how CBO works.

Cost CANNOT be treated as a random number.  Till 8i Cost is a metric based on
the I/O (logical reads) and starting 9i there is an option to make the Cost
based on CPU also along with I/O (calling the API
DBMS_STATS.GATHER_SYSTEM_STATS)

The very fact COST is exposed is to give the user an idea about the unit of work
or resource it is estimating to use for the SQL.

I'm disappointed to see you pick up a SQL which is useless to prove your point.
You are anyhow interested in selecting all the rows from the table and you are
hiding that fact behind "like and %".  Since CBO goes by the stats provided to
it from dictionary tables and by the selectivity estimates assigned already,
"like" results in 5% selectivity of the total rows (5%(1459904) = 72996).  Thats
why it always comes up with an index access plan.  If you wanted to prove your
point you can always pass null to your bind variable and let the SQL selects all
the rows using INDEX which will be the most expensive way than FTS and claim
your plan with FTS hint is faster than the default CBO plan.

If you know, the user will not pass a value, then why introduce a bind variable?

What if, you passed a value for your bind variable?  Have you tested this
scenario?

I'm not saying all the above after reading from a manual.  I've been working on
CBO for couple of years and I'm saying the above from my experience.  I can also
reiterate, if you can come up a tuned SQL where the COST is cheaper than the
default CBO plan, then it is a CBO bug.  It is not what I wish, it is how it is.

          Followup:

(for another question, I added a column ID with a primary key on it and
reanalyzed the table and all indexes and all indexed columns)

Then, according to you, this is a "bug" right? (just want to be clear on this)

big_table@ORA817DEV.US.ORACLE.COM> select * from big_table where exists ( select
null from big_table bt2 where bt2.id = big_table.id );

1459904 rows selected.

Elapsed: 00:03:34.90

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=577 Card=72996 Bytes=7518588)
   1    0   FILTER
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=577 Card=72996
Bytes=7518588)
   3    1     INDEX (UNIQUE SCAN) OF 'BIG_TABLE_PK' (UNIQUE) (Cost=2 Card=1
Bytes=5)

Statistics
----------------------------------------------------------
          0  recursive calls
        105  db block gets
    4495763  consistent gets
      15584  physical reads
          0  redo size
  185831456  bytes sent via SQL*Net to client
   10803611  bytes received via SQL*Net from client
      97328  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1459904  rows processed

big_table@ORA817DEV.US.ORACLE.COM> select * from big_table where id in ( select
id from big_table );

1459904 rows selected.

Elapsed: 00:02:41.62

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1760 Card=1459904
Bytes=157669632)
   1    0   HASH JOIN (Cost=1760 Card=1459904 Bytes=157669632)
   2    1     INDEX (FAST FULL SCAN) OF 'BIG_TABLE_PK' (UNIQUE) (Cost=90
Card=1459904 Bytes=7299520)
   3    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=577 Card=1459904
Bytes=150370112)

Statistics
----------------------------------------------------------
          0  recursive calls
        147  db block gets
     119328  consistent gets
      15622  physical reads
          0  redo size
  185831456  bytes sent via SQL*Net to client
   10803611  bytes received via SQL*Net from client
      97328  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1459904  rows processed

Two queries, semantically identical (will always give the same answer).  Cost of
faster one is 3x that of the slower under identical conditions.

      Rating: 2
It is still a
      bug !!  July 17, 2002
Reviewer:  An Oracle
      fan  from CA, USA
Yes, this is a bug.  Actually if you try your same query in 9i database this
issue is fixed.  You should see your query with EXISTS will show more COST than
the one with IN.

I tried the same test case as yours in 9.2 and here are the numbers.

Case 1)

select * From big_test b
 where exists
      (select 1 from big_test b1
        where b.id = b1.id);

500000 rows selected.

Elapsed: 00:02:46.36

Execution Plan
----------------------------------------------------------
SELECT STATEMENT Optimizer=CHOOSE (Cost=50190 Card=25000 Bytes=250000)
FILTER                                                          TABLE ACCESS
(FULL) OF 'BIG_TEST' (Cost=190 Card=25000 Bytes=250000)
    INDEX (UNIQUE SCAN) OF 'BIG_TEST_U1' (UNIQUE) (Cost=2 Card=1 Bytes=4)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
    1534495  consistent gets
          0  physical reads
          0  redo size
    9018679  bytes sent via SQL*Net to client
     367166  bytes received via SQL*Net from client
      33335  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
     500000  rows processed

Case 2)
select * From big_test b
 where id in
      (select id from big_test b1);

500000 rows selected.

Elapsed: 00:02:14.11

Execution Plan
----------------------------------------------------------
SELECT STATEMENT Optimizer=CHOOSE (Cost=639 Card=500000 Bytes=7000000)
  HASH JOIN (Cost=639 Card=500000 Bytes=7000000)
    INDEX (FAST FULL SCAN) OF 'BIG_TEST_U1' (UNIQUE) (Cost=160 Card=500000
Bytes=2000000)
    TABLE ACCESS (FULL) OF 'BIG_TEST' (Cost=190 Card=500000 Bytes=5000000)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      35545  consistent gets
          0  physical reads
          0  redo size
    9018679  bytes sent via SQL*Net to client
     367166  bytes received via SQL*Net from client
      33335  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
     500000  rows processed

Note the COST difference.  In 8.1.7 the EXISTS showed a cheaper cost than IN
which is a bug.

      Rating: 5
Controversial
      topic  January 06, 2003
Reviewer:  Tahir
       from Irvine, CA
Tom – I have tested both scenarios as presented by Oracle Fan and theoretically
I agree it does make sense for COST to be a tuning metric but after testing in
both 8.1.7 and 9.2.0 I did not find the evidence suggesting that 8.1.7 CBO has a
bug which is fixed in 9.2. For instance COST of "IN" and "EXISTS" queries has
exactly the same COST in 9.2 which should make "IN" less costly. I see that it’s
different form 8.1.7 which is expected but where is the bug? Could you please
confirm that if you find the same? I did not see any further discussions on this
controversial topic.
Thank you.

10:11:33 TK@DB92> select * from
10:11:34   2    cbo_test where exists  (select 1 from cbo_test ct
10:11:34   3                            where cbo_test.object_id = ct.object_id)
10:11:34   4  /
1067520 rows selected.
Execution Plan
----------------------------------------------------------     0
SELECT STATEMENT Optimizer=CHOOSE (Cost=91721 Card=68321280 Bytes=6900449280)
      1                  0
  MERGE JOIN (Cost=91721 Card=68321280 Bytes=6900449280)
          2                  1
    SORT (UNIQUE)  3         2  INDEX (FULL SCAN) OF 'ID_IDX' (NON-UNIQUE)
(Cost=2359 Card=1067520 Bytes=4270080)
          4                  1
    SORT (JOIN) (Cost=89362 Card=1067520 Bytes=103549440)
          5                  4
      TABLE ACCESS (FULL) OF 'CBO_TEST' (Cost=1433 Card=1067520 Bytes=103549440)

Statistics
----------------------------------------------------------
          0  recursive calls
        195  db block gets
      17090  consistent gets
      67915  physical reads
          0  redo size
    9320953  bytes sent via SQL*Net to client
     498444  bytes received via SQL*Net from client
      71169  SQL*Net roundtrips to/from client
          1  sorts (memory)
          1  sorts (disk)
    1067520  rows processed

10:22:38 TK@DB92> select * from cbo_test
10:23:10   2      where object_id in (select ct.object_id from cbo_test ct);
1067520 rows selected.

Execution Plan
----------------------------------------------------------
          0
SELECT STATEMENT Optimizer=CHOOSE (Cost=91721 Card=68321280 Bytes=6900449280)
      1                  0
  MERGE JOIN (Cost=91721 Card=68321280 Bytes=6900449280)
          2                  1
    SORT (UNIQUE)
          3                  2
      INDEX (FULL SCAN) OF 'ID_IDX' (NON-UNIQUE) (Cost=2359 Card=1067520
Bytes=4270080)
          4                  1
    SORT (JOIN) (Cost=89362 Card=1067520 Bytes=103549440)
          5                  4
      TABLE ACCESS (FULL) OF 'CBO_TEST' (Cost=1433 Card=1067520 Bytes=103549440)
Statistics
---------------------------------------------------------
          0  recursive calls
        195  db block gets
      17090  consistent gets
      67967  physical reads
          0  redo size
    9747969  bytes sent via SQL*Net to client
     498444  bytes received via SQL*Net from client
      71169  SQL*Net roundtrips to/from client
          1  sorts (memory)
          1  sorts (disk)
    1067520  rows processed

          Followup:

you have a different test, not knowing what the parameters are, cannot really
comment.

      Rating: 5
Just different
      table name  January 06, 2003
Reviewer:
      Tahir  from Irvine, CA
I used different table name but other than that it's all the same.

TK@DB92> select * from v$version;
BANNER
------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.1.0 - 64bit 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

 select name, type, value
 from v$parameter
 where name like 'db_file_mul%'

NAME                            TYPE       VALUE
------------------------------- ---------- ------
db_file_multiblock_read_count            3 16

db_block_size = 8192

1. create table cbo_test as select * from all_objects;
2. create index id_idx on cbo_test(object_id) parallel 8 unrecoverable;
3. analyze table big_table compute statistics
for table for all indexes for all indexed columns;
4. insert /*+ append */ into cbo_table select *
from cbo_table; to generete enough data.

          Followup:

some other setting is different -- i see it chose a sort merge (i got his same
results by the way).

perhaps you have disabled hash joins or you have an enourmous sort area.  Try
with as vanilla an init.ora as you can and see.

      Rating: 5
Good
      catch  January 06, 2003
Reviewer:  Tahir
       from Irvine, CA
I in fact did set hash_join_enabled=false but that is the only one at lest I can
recall. So is it still a bug and COST can not/should not be used as a measuring
stick?

          Followup:

I do not use cost as a measuring stick, no.

      Rating:
      5
  January 07, 2003
Reviewer:
      A reader
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

      Rating: 5
Cost of query
      is not related to performance of query  January 07, 2003

Reviewer:  Wolfgang Breitling
To those who cling to the illusion that the "cost" of an explained sql is in any
way related to the performance, read Tom's lips: "It is not".
The cost is used during the parse to pick one plan among all the ones considered
(the one with the lowest "cost"). Once it has served that purpose and a plan is
chosen, it is meaningless. In particular, it can NOT be compared to the cost of
a another explain. If everything is the same between the two explain, the
resulting plans and their costs will be the same. If they are not, then
something is different and you can not compare the results anymore.
In pre-Oracle9, the "cost" is roughly equal to the estimated number of logical
reads (db blocks needed to find the answer). That estimate of the number of
blocks to be visited can be different from reality for a number of reasons, only
a few of which could be considered bugs. A lot of the reasons for wrong
estimates are due to data distribution and dependencies. That is what Tom refers
to a human knowledge about the data which goes far beyond what the optimizer can
discern from the statistics. For a presentation I am giving at the Hotsos
Performance Symposium (IOUG wasn't interested) I have prepared testcases where
the same query on tables with virtually identical statistics returns vastly
different numbers of rows to show that it is impossible for the optimizer to
come up with accurate estimates and therefore the best plan in all cases.

      Rating: 3
reply to
      Wolfgang Breitling   January 07, 2003
Reviewer:
      Mikito harakiri
Of course, cost and execution time are related. Why did they add CPU costing to
9i? Why are they adding caching statistics? Why are they making query rewrite
cost based? Those improvements serve a singular purpose: to eliminate any
discrepancy between real performance and estimated cost. Because if there is
such a discrepancy what is the basis to claim that the selected plan better than
just a random plan?

It is well known that query optimization is a hard problem. On a pretty modest
query optimizer can easily be confused by complex predicates, corellation
between the column values, bind variables, and many other factors. This is why
it why cost today is a very rough approximation of the execution time. This,
however, should be considered as a bug, not a feature.

      Rating: 4
Response to
      Mikito harakiri   January 07, 2003
Reviewer:
      Wolfgang Breitling
Cost and execution time are NOT related. There are plenty of examples that prove
that they are not. The comparison of costs can only be made by the optimizer
while it is parsing a sql and is evaluating different access paths. Once an
access path is chosen it is invalid to compare its cost number to that of a
different plan and draw any conclusion from it.
When parsing a sql the optimizer, in addition to using the gathered statistics -
table as well as system in 9i - the optimizer has to make many assumptions about
the data (uniform distribution for example in the absence of histogram data) and
if the reality differs from the assumption the plan that came out with the
lowest cost based on the assumptions can very well be far inferior to a plan
that has a higher cost number (based on the assumptions) but is better suited to
the reality and hence performs better. It is a rather far stretch to call the
inability of the optimizer to accurately estimate certain characteristics of the
data in the database, given limited information, a bug.

      Rating: 5
cost of
      execution   February 14, 2003
Reviewer:  Birna
       from Iceland
tremendously helpful, thanks

      Rating: 5
trial
      outline  April 23, 2003
Reviewer:  Peter
       from Hungary
I think this topic is extremely important because many  Oracle programmers use
costs to optimize their SQLs' performance. This can happen because practice
justifies them: in everyday practice smaller cost means faster execution almost
always (for semantically equal selects). This way there is relation between cost
and execution time -- at least in a statistical sense.

Taking notice of all the above follows the following.

Oracle (CBO) must pick up one from the possible execution plans. It tries to
pick up the "best" one. The decision is based on costs (...). "Best" mostly
means fastest. In Oracle 9 in general it is impossible to determine exactly
which plan will result in the fastest execution. But usually it is possible
exactly enough (through costs). This way cost represents execution time usually
with sufficient accuracy. Maybe this is a side effect (so one can say "they are
not related") but this side effect is the substance of cost. And Oracle (CBO)
uses it essentially for this. As newer Oracle versions come the relation is keep
getting tighter between cost and time.

Are these true?

          Followup:

well, yes and no, and sort of.

Since we'll always have knobs and dials to twist -- to artificially adjust costs
(optimize_index_* for example, mulitblock read counts, and so on...) the
relationship between COST and an amount of time will always be fuzzy.

The ultimate goal is to have it so that for a given set of plans generated for
an individual query -- if all external influences are described correctly (we do
that with init.ora parameters today), the plan with the lowest cost FOR THAT
query will be the fastest plan for that query.

Will you be able to compare that cost X for query Y to cost A for query B and
say query Y will run faster/slower then B -- weeeelll.... I'm not 100% convinced
yet that it is truly obtainable 100% of the time.  Incremental improvements will
get us there eventually I suppose -- but it is a really hard task to undertake.

      Rating: 3
so:
      how?  April 24, 2003
Reviewer:  Peter
       from Hungary
Thank you for your quick answer! Now I am sure that the hinge is to understand
that the relation (between cost and time) is of statistical nature that is why
anyone can show off individual examples to verify his/her standpoint and that is
why these examples doesn't really prove the strict relation or the very opposite
of it. And that is why we can use costs for tuning our SQLs but we never can be
sure of our conclusions drawn from costs.

Above you confirmed that (if we need more reliable information) "for tuning SQL,
we should be using tkprof/explain and looking at the parse/fetch/execute stats
and CPU stats". Please explain this. What are the steps of getting these data?
What system tables/views should we examine and when? What those data means? Etc.
(Feel free to refer to the points of the Oracle (or other freely available)
documentation.)

TIA.

          Followup:

what, you won't buy my book!  gotta be free -- ok, thats easy

http://download-west.oracle.com/docs/cd/B10501_01/nav/docindex.htm#index-PER
the performance tuning guide, chapter on sql_trace and tkprof.

      Rating: 5
sql
      timing  August 26, 2003
Reviewer:  Tony
       from Canada
Tom,
    Thanks a lot for all your help,
When we set timing on from sqlplus does the resulting time include the time to
desplay all the results, my query returns 10,000 rows, just wondering if I can
just get the actual time without displaying the results on screen (if possible)

          Followup:

set timing on
set autotrace traceonly
run query

is one way -- or

alter session set sql_trace=true;
set termout off
run query
set termout on

in a script and use tkprof

      Rating: 3
Still in doubt
      - Can read ur lips but dont know if I should
      agree  September 02, 2003
Reviewer:  Mahesh
       from Australia
Scenario - CBO used rest all conditions vanilla

Query 1 and Query 2 - different queries returning same result set.

explain plan performed for both queries but queries not actually executed.

Consider cost of query 1 to be higher than cost of query 2.

Logout and restart database - run query 1 and check the time taken.

Logout and restart database again - run query 2 and check the time taken.

What do u anticipate about the time taken by each query and its relation to the
cost if any ? What are the actual results ?

          Followup:

no ideas...

(and you would have to reboot server, if you were trying to flush buffer cache,
since you are probably using a buffered file system -- not that is matters since
rebooting the server is as "not real world" as restarting the database)

      Rating: 1
Could you give
      some idea after trying ?  September 02, 2003

Reviewer:  Mahesh  from Australia
The reason I am trying to do this is as follows -

1. If Cost is not to be considered for better query decisions, then Oracle works
like -

Query 1 is submitted returning a data set. Optimiser randomly assigns it a cost
200 and then finds multiple ways to do it better and assigns the best way to
execute it at say 180 and hence thats the plan on which it will execute that
query.

Query 2 is submitted returning same data set. Optimiser randomly assigns a cost
of 300 and then finds multiple ways to do it better and assigns the best way to
execute it at say 260 and hence thats the plan on which it will execute that
query.

Since the algorithm generates the first cost used to compare amongst various
execution plans for a query totally at random hence the cost is not comparable
between two queries. Is that the best explanation ?

2. The reason I feel case 1 above is not true. Optimiser while deciding on
various execution plans for query 1 should have considered the execution plan
for query 2 as both return same result set OR vice versa. Am I right ?

If yes then cost can be considered for query performance decisions.

If no then the first random cost assignment for a query by the optimiser
algorithm holds true.

Lastly, the reason I am doing this analysis. When a developer comes to a DBA and
says which query is better in terms of time performance, the DBA should be able
to say query 1 will take less time than query 2 considering that it is the only
query fired on a newly started database. This is because if I fire query 1 or
query 2 in whatever sequence the time for 2nd query is going to be affected by
presence of some data blocks in buffer and so on. Thats why the database
restart.

But the fact being in real world you dont want to always fire 2 queries and then
note that their performance times and decide which is a better query.

This all reasoning to say that cost can be used to decide on query performance
time is going to be wrong if you say
"Optimiser algorithm assigns the cost completely at random each time a query is
submitted even though the queries may be returning the same result set."

I would appreciate getting your views in details rather than "no ideas". I
understand its a well chewed topic but unfortunately the CBO is still there and
query performance is a big time issue always. Please help.

          Followup:

i understand what you are saying -- I'm also saying given the numerical
information no one can with 100% certaintly say ANYTHING accurate.

they can only say "what is supposed to happen", not what will.

the numbers from the CBO are a predication based on its knownledge of the data,
the system, and many other factors.

A tweak here (change a setting) and the numbers change radically.  The same
query -- same plan -- lower cost.  Now, did tweaking that parameter really make
the query go faster??  if that setting were db_file_multi_block_read_count --
maybe it did.  if that setting was optimizer_index_cost_adj -- no way, that
won't affect runtime performance -- just the costing of the query.

#1 of what you say above is not accurate at all.  there is no randomness
involved anywhere.  it is a mathematical model into which inputs are fed and an
output derived.

a database restart, as I said, is wholly insufficient to clear blocks from
caches AND furthermore is totally non-real world.

      Rating: 3
Randomness
      ruled out....  September 03, 2003
Reviewer:
      Mahesh  from Australia
Ok then one query to help me try and conclude my understanding

Query 1 works on a table returns 400000 rows out of a million.

Query 2 works on the same table returns same 400000 rows out of a million.

Will the optimiser algorithm while working out best execution plan for query 1
as part of its selection process assign a cost to a possible execution plan for
query 2 as both return the same result set and then make a choice of execution
plan for query 1 on basis of least cost ?

Would it be true vice versa as well ?

          Followup:

query plans are generated for a query -- without looking at other query plans.
they are generated independently.

i don't see how query 1 and query 2 could be even remotely related to eachother
in general.  they are apples and toaster ovens -- nothing but nothing in common.

      Rating: 4
Its cleared a
      lot of cobwebs and concepts of COST  September 04, 2003

Reviewer:  Mahesh  from Australia
Thanks that explains a lot.

      Rating: 4
Rules which
      govern the Rule based  September 05, 2003

Reviewer:  Anil Pant  from Hyderabad
Can u list down the rules which govern when rule based optimizer is set ? Else
is there any book or link where I can go through

Thanks
Anil Pant

          Followup:

easier to say when it WON'T be used.

the cbo is used when:

o any of the tables accessed in a query are analyzed and the optimizer mode is
not set to rule

o you access an object that requires the CBO -- such as a partitioned table,
parallel table, index organized table and so on.

o your optimizer goal is set to anything other then choose or rule

o you use a hint

so, if you have analyzed tables, access a "special" table, force it via a
optimizer mode or hint -- you are using the CBO.

Else, you are using the rbo.

      Rating: 4
Rule v/s Cost
      ........  September 08, 2003
Reviewer:  Anil
      Pant  from Hyderabad, India
You said cost is used when ...

(a) you access an object that requires the CBO -- such as a partitioned table,
parallel table, index organized table and so on.
Only if I set optimzer as Cost or Choose then Cost is used in above case. If I
set as Rule then Rule is used but still it may effect the performance. Im I
correct ?

(b) you use a hint
so, if you have analyzed tables, access a "special" table, force it via a
optimizer mode or hint -- you are using the CBO.

I could not completely get this statement. Pls explain

          Followup:

a) no, incorrect.  you will NEVER use the rbo against a partitioned table -- it
quite simply doesn't recognise that object.  We can see this simply:

ops$tkyte@ORA920LAP> create table t1 ( x int primary key ) organization index;

Table created.

ops$tkyte@ORA920LAP> create table t2 ( x int primary key );

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> alter session set optimizer_mode=rule;

Session altered.

ops$tkyte@ORA920LAP> set autotrace traceonly explain;
ops$tkyte@ORA920LAP> select * from t1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=RULE (Cost=4 Card=2000 Bytes=26000)
   1    0   INDEX (FAST FULL SCAN) OF 'SYS_IOT_TOP_40222' (UNIQUE) (Cost=4
Card=2000 Bytes=26000)

ops$tkyte@ORA920LAP> select * from t2;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=RULE
   1    0   TABLE ACCESS (FULL) OF 'T2'

ops$tkyte@ORA920LAP> set autotrace off

see how the IOT has the cost/card/bytes -- that means "cbo was definitely used".
 the rbo doesn't understand "iot"

b) i was saying

so, if you are

o accessing tables that are analyzed
o access a special table (IOT, partitioned, parallel, etc)
o use the optimizer mode and say "first_rows" for example
o or use a hint

you are pretty much using the CBO.  autotrace can be used to see which

      Rating: 4
what then is a
      better metric in approximating a faster execution
      time  September 12, 2003
Reviewer:  bobspa
       from PHILippines
tom,
 i have come across this thread searching for clues in optimizing query
performance.
 going back to the 2 query formats giving different costs, you made it so clear
that lower optimizer costs does not correlate with faster execution times. well
said...
 BTW, event 10035 gives us a window as to how the optimizer concluded the best
plan.
 so, given different execution plans for a given query, is there a way by which
we can pick a plan(for faster exec) based on your "heuristics"?
 OR, do we just go back to the "test a subset of data then conclude" principle
on a "live/production" system. ( doing the tests on a development system then
relying solely on elapsed times might not be very conclusive as
datafiles/indexfiles are laid out differently thus impacting read seek times).
  i was thinking that perhaps the plan with fewer LOGICAL I/O's (consistent gets
+ buffer gets from the stats) will generally run better, notwitstanding the
access paths/join methods used.
  what do you think?
  thanks

          Followup:

you cannot go solely by LIO's as you need to see how the query scales.  It is
easy to have cases where a query does exponentially MORE LIO's as the volume of
data scales up.  what you need to do is find out how the query scales and that
is really really hard.  you would need to say "well, when there are 100 rows, it
does this, 1,000 rows it does this, 10,000 rows this, 100,000 rows this" and
plot the scale.  Then, you can sort of forecast what it will do.

it is my belief that test/dev should be a mirror of prod -- if you want smooth
running prod's anyway.  before inflicting anything on the end users, you would
scale test things.

      Rating: 4
and the metric
      for scale up would be?  September 26, 2003

Reviewer:  bobspa  from the Phils.
oops ... maybe i wan't very clear on the "... test a subset on a live/production
db ..."
i was focusing on this environment from a reactive tuning perspective -- to get
an  understanding as to why a query is "misbehaving".
 so going back to the previous notes from this thread,other LIO threads,
tkprofs, etc. and basing on my limited knowledge on the optimizer, when
searching for a better access path for a given SQL, what do we do (Given the
system params):
   1. do an explain plan  ==> not too good to rely on as it is just predictive,
heavily dependent on table/index stats no matter how outdated they may be, in
the absence of stats assign some "heuristic" value per kke.c . right?
   2. re-analyzing the tables and indices (plus histograms) on the fly is not
likewise very attractive as we don't know how other modules are manipulating our
targetted objects i.e. bitmap locks for a range of blocks during an update
   3. trying to understand further CBOs behavior, we set event 10035, re-run the
sql, tediously review the output.( BTW, would you happen to know the level
settings for 10058 ?)
   4. if not yet satisfied with access path chosen by CBO, pump in some hints,
re-run the query, loop until OK.
  *** i believe you are aware that sub-optimal plans generated by CBO are very
common for queries involving 6 tables or more, mainly due (perhaps) to the
default setting of _optimizer_search_limit to 5 -- which decimates search space
pruning ***
  all along the tkprof analyses, we are concentrating at cpu times and block
visits as these are tightly related.( although i have seen some big hash joins
with fewer LIOs, as compared to sort/merge or index-<whatever> scans but eats up
a lot of CPU time). never mind the elapsed times and session waits as these are
functions of concurrency...
  you were saying query scaling... how is that? isn't this a function of the
number of CPUs and parallel slaves? purportedly, the more the merrier as long as
it does not encourage swapping/trashing/memory shortage, CPU shortage, processes
shooting up wildly. these are ENV issues which are always given during tuning.
reloading of blocks(thus increasing LIO further) to the cache thru an index
range scan is likewise unpredictable as it is a function of data load -- this
can't be simulated on a test/dev system, thus the PROD/live option during a
lesser workload.
now, i'm confused. given a static ENV, if a query running very fast with 100K
rows runs badly at 1000K rows, does'nt scale-up as you say, is that a design
flaw, or is it the fact that CBO choose a different access plan believing that
it will get a better throughput? a sub-optimal plan, in which case , we add a
hint ...
   so , what is a better metrics for evaluating if a query's plan is optimal,
and will scale-up ?
as an aside, plan stability was introduced to tell CBO not to infringe with our
endeared plan, which implies that CBO might opt for a red herring despite the
presence of stats, histograms, bitmap indices or otherwise...
  your comments please ...

          Followup:

1) and not only that -- but can you tell from an EP whether it is "good" or not?
 I can't

3) do you really mean 10053?

4) no, you use hints to prove the optimizer wrong and then we figure out why.  i
don't like hints in general - in real code.

when I was talking about scale I was talking about testing the query against a
subset of the data.  that ONLY works if the same query against the full set of
data does the same amount of work

eg: in order to get 5 rows of output from query X against 1,000 rows of data, we
find it takes the same amount of work as against a 1,000,000,000 row table (the
query does the same work regardless of the table size)

or does that query take many orders of magnitude more work to achieve its goal.

or it is somewhere in between.

when testing against a subset, if and only if all of your queries fall into that
first category will your work be meaningful.  else, it is not.  that is what I
was saying with regards to "scale".  how does the query "scale" as the data
volume "scales"

take the query "select * from emp where empno = :x".  assuming empno is the
primary key -- that query scales.  it'll take about the same amount of work
regardless of the number of rows in EMP.

take the query "select count(*) from emp where job = :x".  that query does not
scale in the same way.  the more rows in emp, PROBABLY the more work that query
must do in order to get the answer -- assuming the number of distinct values of
job does not change dramatically.

      Rating: 4
Oracle
      Question  September 26, 2003
Reviewer:
      Mahesh  from India
Dear Sir,
I have different question on Orcle.  Oracle is prepared in which language?.
For e.g: WE create table and got the message
Table Created.
What is the actual programming behind it.  I want to generate such code.  Please
send me the code then i can write small module of that.  What exactly behind it.

Bye
Thank's
Mahesh

          Followup:

er?

Oracle is written in C.

sqlplus is written in C.

it sounds like you want to rewrite sqlplus.  you would code that in C using OCI
(the Oracle Call interface) to execute the sql.

      Rating:
      4
Oracle  September 28, 2003
Reviewer:
      Mahesh  from India
Dear sir,
Thank's very much for replying.  HOw to use OCI.  SEnd me some tips for that.
Send me the code also

Mahesh

          Followup:

tip: read the documentation
code: found in the documentation

http://download-west.oracle.com/docs/cd/B10501_01/nav/docindex.htm#index-OCI

      Rating:
      4
  September 28, 2003
Reviewer:
      Mahesh  from India
Ok.
Is it 9i doc or 8i doc.
Which one I should read.

Mahesh

          Followup:

you should read the one that is most relevant to you?

I pointed you to the 9i doc, no version from you -- so i point you to the most
current stuff.

      Rating:
      4
rejoinder  September 30, 2003
Reviewer:
      bobspa
rejoinder:

"1) and not only that -- but can you tell from an EP whether it is "good" or
not?
 I can't"
  what?? if you can't, then nobody does -- this makes almost everybody plain
pretenders ...sigh :<
 seriously, with your stature, you won't be needing any QEP to tell if a SQL
construct is "good" or not. From the other threads, you always give samples that
uses fewer LIOs and latches, to persuade that it runs better... per your
comments from runstats.sql ... so, how does everything adds-up?
"3) do you really mean 10053?"
  yup, not 10035 (erred twice) ... OR, maybe 10060 ...
   but the outputs of these events are inconsequential if we are not satisfied
with CBOs decision, so we go to item (4), as a last resort ...

going back to scale-up, help me tune my understanding...
  aside from the "classic case" of predicates evaluating to primary/unique key
accesses, what else scales? certainly not hash joins, range scans, FFS. someone
from years ago cries that "scale up" was Oracle's buzzword when PQO was
introduced.

          Followup:

stature or not, you have to benchmark -- I do.  I cannot look at just any query
and say "its good".  I use those tools to try out different ideas using my
knowledge of how things work.  I can generally come up with a couple of
different ways -- all that need to be benchmarked and studied.

      Rating: 4
Oracle
        October 03, 2003
Reviewer:  Mahesh
       from India
Dear sir,
Thanks very much.  I will read which most relevant to me.
I will try my level best.

Thanks
Mahesh

      Rating: 4
Count(*)
        October 06, 2003
Reviewer:  Mahesh
       from India
Dear sir,
If I have 20 tables.  Each tables contains 10 rows.  I want total count from
that 2o tables.  How to do this?

For e.g : In genrel  scott has 3 tables emp,dept and salgrade
In emp : 11 rows
     Dept : 5 rows
    Salgrade: 4 rows

If I fired  below query then I will get output correctly.  But this is not a
correct method, if we have 40 tables or 1000 tables.
  1  select  count(*) + a+b cnt   from emp,
  2       (select count(*) a from dept),
  3  (select count(*) b from salgrade)
  4*     group by a,b
SQL> /

      CNT
---------
       20

How to do this ?

Thanks
Mahesh

          Followup:

it is sort of what I would catagorize as "a bad idea" here.  I cannot imagine
the relevance of "the sum of rows in N tables" -- unless you have a really bad
design and the correct answer for that would be "fix it"

you have to hard code each table reference

select (select count(*) from t1)+(select count(*) from t2)+.......
  from dual;

is one way.  you could write a function using dynamic sql that accepted a
tablename as input as well.

ops$tkyte@ORA920> create or replace
  2  function get_rows( p_tname in varchar2 ) return number
  3  AUTHID CURRENT_USER
  4  as
  5      l_columnValue    number default NULL;
  6  begin
  7      execute immediate
  8         'select count(*)
  9            from ' || p_tname INTO l_columnValue;
 10
 11      return l_columnValue;
 12  end;
 13  /

Function created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select sum( get_rows(table_name)  )
  2    from user_tables
  3  /

SUM(GET_ROWS(TABLE_NAME))
-------------------------
                       66

      Rating: 4
"Procedure for
      emp"  October 11, 2003
Reviewer:  Mahesh
       from India
Dear sir,
Please see the following procedure and give me how to solve it.

  1  create or replace procedure q4
  2  (v_ename out emp.ename%TYPE,
  3  v_empno out emp.empno%TYPE,
  4  v_sal out emp.sal%TYPE)
  5  AS
  6  begin
  7  FOR i IN 6000..8000 loop
  8   select ename, sal into v_ename, v_sal from emp
  9   where empno = i;
 10   dbms_output.put_line(v_ename);
 11  end loop;
 12  exception
 13  when no_data_found then
 14  dbms_output.put_line('Sorry');
 15* end;
 16  /

Procedure created.

SQL> execute q4;
BEGIN q4; END;

      *
ERROR at line 1:
ORA-06550: line 1, column 7:
PLS-00306: wrong number or types of arguments in call to 'Q4'
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

I want all the employees whose
empno is between 6000 and 8000.

Thanks
Mahesh

          Followup:

well lets see -- you have a procedure q4

It expects 3 out parameters.

You have given it no OUT parameters.  hence it fails.

I don't understand your logic at all -- there are so many things wrong with this
procedure.

if you want all emps between 6000 and 8000, you run a SINGLE query.  procedure
would be simple

procedure q4
is
begin
  for x in ( select ename from emp where empno between 6000 and 8000 )
  loop
     dbms_output.put_line( x.ename );
  end loop;
end;
/

      Rating:
      4
Procedure  October 17, 2003
Reviewer:
      Mahesh  from India
Dear Sir,
Thanks very much for sending reply.I did it, it is working
successfully.

Thanks
Girish

      Rating: 4
Differences
      between 7.0,8i,9i  October 28, 2003
Reviewer:
      Mahesh  from India
Dear sir,
Please tell me the major 10 differences between Oracle 7.0 , 8i and 9i
I want difference between them all three.  I read the book but unable to find
any differences between them.

Kind regards,
GIrsh

          Followup:

hmmm, you read the 8i "getting to know" guide and the 9i "new features guide"
and didn't find any differences????

goto otn.oracle.com

click on documentation

goto either the 8i docs and read the "getting to know" guide or the 9i docs and
read the "new features guide"

      Rating: 4
Oracle
        November 04, 2003
Reviewer:  Mahesh
       from India
Hi,
I have a differene question.  The design of oracle is totally based on
engineering concept and it has all grea features, then why user can not recover
the table once it is droped.  Once it is droped it is preety dropped, we can
recover the table from backup utility or some another method also.
But why we can no recover the table by giving simple command.   Why Oracle made
such things that we can not pass DDL statement into DML.  Is there any specific
reason for dropping table.

Thanks,
Mahesh

          Followup:

Oracle 10g has an "undrop" command.  It is implemented using a recycle bin
concept.  Meaning, as long as there is tons of free space in the tablespace and
we don't need to reuse the dropped tables extents -- you can undrop it.  But, if
you drop a table and then immediate recreate a new table that consumes lots of
space in that tablespace -- the odds are going down that you can undrop the
table.

I don't know what you mean by "we cannot pass ddl statement into dml"

      Rating:
      4
VARRAY  November 04, 2003
Reviewer:
      Mahesh  from India
Hello Sir,
Thanks for the reply on dropping table query.
NOw my queseion on VARRAY :
I am trying to write a function that will as its input parameters take a
character string representing date formatted in at least seven different ways.
Within the function, store all of those formats in a VARRAY. Function should
return a DATE.
Please tell me how to use VARRAY concept in this.

Thanks
Mahesh

          Followup:

sounds like a bad idea -- especially -- ESPECIALLY if the formats include

dd/mm/yyyy
mm/dd/yyyy

sounds like a really really bad idea.  sounds like you are going to have garbage
in your DATABASE!

ops$tkyte@ORA920PC> create or replace function my_to_date( p_str in varchar2 )
return date
  2  as
  3          type array is table of varchar2(30);
  4
  5          l_fmts array := array( 'dd-mon-yyyy', 'Mon DD YYYY' );
  6          l_dt   date;
  7  begin
  8          for i in 1 .. l_fmts.count
  9          loop
 10          begin
 11                  l_dt := to_date( p_str, l_fmts(i) );
 12                  return l_dt;
 13          exception
 14                  when others then null;
 15          end;
 16          end loop;
 17          raise_application_error( -20001, p_str || ' cannot be converted' );
 18  end;
 19  /

Function created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select my_to_date( 'fa' ) from dual;
select my_to_date( 'fa' ) from dual
       *
ERROR at line 1:
ORA-20001: fa cannot be converted
ORA-06512: at "OPS$TKYTE.MY_TO_DATE", line 17

ops$tkyte@ORA920PC> select my_to_date( '01-jan-2003' ) from dual;

MY_TO_DAT
---------
01-JAN-03

ops$tkyte@ORA920PC> select my_to_date( 'Jan 01 2003' ) from dual;

MY_TO_DAT
---------
01-JAN-03

ops$tkyte@ORA920PC>

      Rating:
      4
VARRAY  November 06, 2003
Reviewer:
      Mahesh  from India
Dear sir,
Thanks for sending the code.  This is nested tble you used in that code.  I want
from VARRAY, but i did it and it is woking fine. Now i want 10 different formats
of dates ,you have taken only 2 formats,then user can choose any one  of that
formats of dates.  How to do it ?

Thanks
Mahesh

          Followup:

why do you want a varray.

a nested table is easier.

you can put in any number of dates in that array, it was just "an example".  i
thought it was self evident that you would just list N dates in that array.

you can make it a varray easily - just change the create type.  but -- to what
end?  it would only limit the number of elements in the array.

I never use varray's, never.

      Rating: 5
Optimizer
      Cost  November 06, 2003
Reviewer:  Ash
       from Aus
I have always thought that the Cost shown in plan always depended upon resouces
used such as cpu time, no of disk reads and so on. So if more of these resources
are used, cost could go up and runtime may come down or go up irrespective of
cost.
Is this right ?
Thanks for all your support.

          Followup:

ESTIMATED -- guestimated.

if it were perfect, this would be true.  It cannot be perfect (only hindsight is
20/20)

      Rating: 4
Drop
      table  November 10, 2003
Reviewer:  Mahesh
       from India
Dear sir,

Oracle 10g has an "undrop" command.
What is Oracle 10g ? Where it is used ? Still anybody using Oracle 10g?

Thnaks
Mahesh

          Followup:

Oracle 10g is the next release of Oracle after Oracle 9iR2.  It's the next
release.

      Rating: 1
Mahesh
      NOOOOOOO  November 10, 2003
Reviewer:  Matt
       from UK
Why don't you do some searches before adding simple questions to a completely
unrelated thread.

Everything you could need can be found at:

technet.oracle.com

      Rating: 3
What do these
      figures mean???  November 10, 2003
Reviewer:
      A reader  from NJ, USA
Tom,

I have the 2 queries with same execution plans and cost but differnt rows/bytes
based on whether I put a hint or not.

Query - 1 (with hint)
====================
SELECT      /*+ORDERED USE_NL(pd,pcd) */ p.position_id,
pd.position_detail_id,            pd.Alt_Market_Value,
            pd.Alt_Market_Value_Income,  pd.Alt_Book_Value,
pcd.Alt_GAAP_Cost_Value,
            pd.Base_FX_Unrealized_GL,     pd.Alt_Base_Unrealized_GL,
pd.Alt_Base_FX_Unrealized_GL,
            pd.Alt_Accrued_Income,         pd.Local_to_sec_basis_fx_rate,
pd.Income_Receivable_Local,
            pd.Income_Receivable_Base,      pd.Alt_Income_Receivable
       FROM holdingdbo.position p,
               holdingdbo.position_detail pd,
            holdingdbo.position_cost_detail pcd
      WHERE p.position_id            = pd.position_id
        AND pd.position_detail_id = pcd.position_detail_id
         AND p.update_date        >= '09-nov-03'
         AND p.src_intfc_inst      = 17

SELECT STATEMENT Hint=CHOOSE        1 K         1767
  NESTED LOOPS        1 K    88 K    1767
    NESTED LOOPS        1 K    76 K    703
      TABLE ACCESS BY INDEX ROWID    POSITION    16      304      47

        INDEX RANGE SCAN    I_POSITION_UPDATE_DATE    187           4

      TABLE ACCESS BY INDEX ROWID    POSITION_DETAIL    89      3 K    41

        INDEX RANGE SCAN    UK_POSITION_DETAIL    89           5

    TABLE ACCESS BY INDEX ROWID    POSITION_COST_DETAIL    1      9      3

      INDEX UNIQUE SCAN    SYS_C002482    1           2
Query 2 (Without hint)
======================
SELECT      p.position_id,                     pd.position_detail_id,
pd.Alt_Market_Value,
            pd.Alt_Market_Value_Income,  pd.Alt_Book_Value,
pcd.Alt_GAAP_Cost_Value,
            pd.Base_FX_Unrealized_GL,     pd.Alt_Base_Unrealized_GL,
pd.Alt_Base_FX_Unrealized_GL,
            pd.Alt_Accrued_Income,         pd.Local_to_sec_basis_fx_rate,
pd.Income_Receivable_Local,
            pd.Income_Receivable_Base,      pd.Alt_Income_Receivable
       FROM holdingdbo.position p,
               holdingdbo.position_detail pd,
            holdingdbo.position_cost_detail pcd
      WHERE p.position_id            = pd.position_id
        AND pd.position_detail_id = pcd.position_detail_id
         AND p.update_date        >= '09-nov-03'
         AND p.src_intfc_inst      = 17

SELECT STATEMENT Hint=CHOOSE        1 K         1767
  NESTED LOOPS        1 K    88 K    1767
    NESTED LOOPS        1 K    76 K    703
      TABLE ACCESS BY INDEX ROWID    POSITION    16      304      47

        INDEX RANGE SCAN    I_POSITION_UPDATE_DATE    187           4

      TABLE ACCESS BY INDEX ROWID    POSITION_DETAIL    89      3 K    41

        INDEX RANGE SCAN    UK_POSITION_DETAIL    89           5

    TABLE ACCESS BY INDEX ROWID    POSITION_COST_DETAIL    85 M    733 M    3

      INDEX UNIQUE SCAN    SYS_C002482    1           2
1. Can you pls. explain me why the figures are different even though the cost
and execution plans are similar.
2. What do these figures mean and what to look for in both of the above EXPLAIN
PLAN. Both of the above queries select around 56583 records.
3. What does it mean by 85M in Rows and 733M in Bytes. Why is it 85M and 733M in
second query 1 and 9 in first query and what difference does the hint make to
the query even though they have the same execution plans.
4. The Rows says 1K, but the total number of rows retreived by the query is 58K.
5. Any ideas on how to read this output and the output produced by EXPLAIN PLAN
section in TKPROF output such as the Card etc. I am struggling to interpret the
EXPLAIN PLAN output. Pls let me know what to look for in these plans and where
to focus our efforts in the plan
6. As soon as I showed the query without the hint, he immediately spotted the
problem and asked me to put an hint (which is the first query). What would have
prompted him to put the hint and if I were him how to diagonise such problems

Your help in this regard is highly appreciated.

          Followup:

the end result numbers are not any different?

but basically it is saying "you know, if you let me do it my way, i'll apply the
predicates like this resulting in N rows from this table and M rows from over
here.

If you make me do it your way, i'll have to goto this table and get L rows and
then apply this other predicate to get K rows.

the end result is 1k rows -- in both cases

      Rating: 2
How hints made
      the difference  November 11, 2003
Reviewer:
      A reader  from NJ USA
Tom,

My question is that even though they show the same execution plans in both
cases, why did putting the hint made a huge difference in the number of bytes
particularly for the last table position_cost_detail that shows a bytes of 733M
without hints and only 9 with hints).
1. I would like to know how to interpret these figures and what these figures
mean. I particularly want to know about Cost, Card, Rows and Bytes
2. How does putting a hint drastically change these figures
3. How can I get a clue that I need to use a hint or is it going to be a
trial-and-error approach.

          Followup:

it is pure "math" here.

you said "hey, apply these predicates and join these tables" -- without hints
the optimizer said,

you know, I'll goto table t1 and get 50 rows given the predicate on t1, then
we'll join that to t2 and get on average 2 rows for each row in t1 given the
predicate and join condition on t2, resulting in 100 rows

you said "no no, don't drive from t1, drive from t2"

the optimizer says then

ok, we'll goto t2 and get 1,000 rows based on the predicate.  we'll join that to
t1 and get an average of 0.1 rows resulting in 100 rows

you made it do a different amount of work to START with -- but then the answer
of course would be the same.

here is a simple example.  t1 indexed by owner, t2 indexed by object_type.

if you say "where owner = scott", that'll get very few rows.
if you say "where object_type = table", that'll get a ton.

so you join t1 to t2 and have a predicate:

where t1.owner = scott and t2.object_type = table

if you do t1 FIRST, you get a few rows, then join to t2.
if you do t2 FIRST, you get a TON of rows, then join to t1.

you should drive from t1 into t2, your ordered use_nl "hint" changed that and
said drive from t2 to t1.  you changed the cardinalities by changing the order
of application of predicates.

you should (IMHO) use hints only when

a) you've identified a problem
b) you want to see if there is a "better" way
c) you use hints for testing, to see if there is a better way
d) you then figure out what statistics you are missing based on that in order to
get the hints the heck out of there.  i don't use hints in real code (with the
exception of "safe" hints like FIRST_ROWS -- hints that give the optimizer
INFORMATION but don't tell it exactly HOW to process the query

(if you have my new book, effective oracle by design, I go over this in more
detail)

DOC>create table t1 as select * from all_objects;
DOC>alter table t1 add constraint t1_pk primary key(object_id);
DOC>create index t1_idx on t1(owner);
DOC>analyze table t1 compute statistics
DOC>for table for all indexes for all indexed columns;
DOC>
DOC>create table t2 as select * from all_objects;
DOC>alter table t2 add constraint t2_pk primary key(object_id);
DOC>create index t2_idx on t2(object_type);
DOC>analyze table t2 compute statistics
DOC>for table for all indexes for all indexed columns;
DOC>*/
ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> set autotrace traceonly explain
ops$tkyte@ORA920LAP> select *
  2    from t2, t1
  3   where t1.owner = 'SCOTT'
  4     and t2.object_type = 'TABLE'
  5     and t1.object_id = t2.object_id;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=12 Card=10 Bytes=1920)
   1    0   NESTED LOOPS (Cost=12 Card=10 Bytes=1920)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=2 Card=10 Bytes=960)
   3    2       INDEX (RANGE SCAN) OF 'T1_IDX' (NON-UNIQUE) (Cost=1 Card=10)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'T2' (Cost=1 Card=1 Bytes=96)
   5    4       INDEX (UNIQUE SCAN) OF 'T2_PK' (UNIQUE)

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select /*+ ORDERED USE_NL( t2, t1 ) */ *
  2    from t2, t1
  3   where t1.owner = 'SCOTT'
  4     and t2.object_type = 'TABLE'
  5     and t1.object_id = t2.object_id;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=253 Card=10 Bytes=1920)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=253 Card=1 Bytes=96)
   2    1     NESTED LOOPS (Cost=253 Card=10 Bytes=1920)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'T2' (Cost=38 Card=1048
Bytes=100608)
   4    3         INDEX (RANGE SCAN) OF 'T2_IDX' (NON-UNIQUE) (Cost=3 Card=1048)
   5    2       BITMAP CONVERSION (TO ROWIDS)
   6    5         BITMAP AND
   7    6           BITMAP CONVERSION (FROM ROWIDS)
   8    7             INDEX (RANGE SCAN) OF 'T1_IDX' (NON-UNIQUE)
   9    6           BITMAP CONVERSION (FROM ROWIDS)
  10    9             INDEX (RANGE SCAN) OF 'T1_PK' (UNIQUE)

ops$tkyte@ORA920LAP> set autotrace off

      Rating: 4
Execution plans
      did not change  November 12, 2003
Reviewer:
      A reader  from NJ, USA
Tom,

Your explanation on this is very clear. But what I am confused about is that
both of the plans in my example show the same execution path. The only
difference is in the bytes/rows accessed. My understanding and your example
shows that the bytes/rows changed as a result of change in execution plan
whereas in my case, the execution plan did not change,but only the bytes/rows
changed (decreased) as a result of the hint.

Also, you mention that you will investigate as to why it chose the plan and once
it is figured out, you will remove the hint. How do I approach this
investigation and what steps needs to be followed and the things that needs to
be checked

          Followup:

can you post a nicely (not wrapped) formatted AUTOTRACE TRACEONLY EXPLAIN.

I'm not really sure what numbers I'm looking at here.

      Rating: 4
Why cost
      doesn't appear  January 05, 2004
Reviewer:
      OracleO  from India
Hi Tom,

I ran the following autotrace, but dont get to see the 'cost' variable.
here is the copy and paste of sqlplus session. Any ideas why cost isnot
appearing. Oracle version 8.1.7.0.1.
------------------------------------------------
oracleo@oracle:indiadb>set autotrace traceonly
oracleo@oracle:indiadb>/

304 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   FILTER
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'OBJ$'
   3    2       INDEX (RANGE SCAN) OF 'I_OBJ2' (UNIQUE)
   4    1     TABLE ACCESS (CLUSTER) OF 'TAB$'
   5    4       INDEX (UNIQUE SCAN) OF 'I_OBJ#' (NON-UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        840  consistent gets
          0  physical reads
          0  redo size
      16470  bytes sent via SQL*Net to client
       2641  bytes received via SQL*Net from client
         22  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        304  rows processed
------------------------------------------------------

Thanks,
OracleO

          Followup:

when using the RBO (rule based optimizer), there are no costs.

you are using the RBO -- I use autotrace like that just to see which optimizer
was in fact used.  no COST/CARD/BYTES -> RBO

      Rating: 3
Your
      Book  January 05, 2004
Reviewer:
      A reader  from NJ, USA
Tom,

In your book do you cover the following as you mentioned above?
<quot>
a) you've identified a problem
b) you want to see if there is a "better" way
c) you use hints for testing, to see if there is a better way
d) you then figure out what statistics you are missing based on that in order to
get the hints the heck out of there.  i don't use hints in real code (with the
exception of "safe" hints like FIRST_ROWS -- hints that give the optimizer
INFORMATION but don't tell it exactly HOW to process the query
</quote>

How do you identify step d. ie. to determine missing statistics based on putting
the hint and removing the hint. Pls explain. Is that detailed in your new book?

          Followup:

the chapter on the tools I use (autotrace, explain plan and the 10053 trace
event) as well as the chapter on the CBO talk about this.

      Rating: 3
Not using RBO
      !  January 06, 2004
Reviewer:  OracleO
       from India
No Tom. I am not using RBO as the output shows optimizer = CHOOSE !!

Am i right.

Thanks for your time.
OracleO

          Followup:

you are wrong.  choose means "hey Oracle -- choose which optimizer you want to
use, the RBO or CBO"

the database chose to use the RBO in your case. It'll do that when

o there are no stats on any referenced object
o you are not accessing any objects that require the cbo (partitioned tables for
example, iots for example)
o there are no valid hints (other then RULE hint of course ;)

      Rating: 3
TKPROF
      output  January 07, 2004
Reviewer:
      A reader  from NJ, USA
Tom,

1. In a TKPROF output, I see somethings like cr,r,w,us, time etc under Row
Source Operation. Can you explain what those figures are and can we get any
clues by looking at them. Also, I do not see these things discussed in "Expert
One-on-One Oracle". Are they available in "Effective Oracle By Design". If not,
where can I get info on those.
Rows     Row Source Operation
-------  ---------------------------------------------------
 937495  UPDATE  (cr=1918058 r=2957 w=0 time=203219226 us)
 937495   INDEX UNIQUE SCAN SYS_C002495 (cr=1874992 r=1973 w=0 time=20988911
us)(object id 227224)

2. Also, on the 9i Trace output, I see sections on various Wait Events. Does
that mean we no longer need to use the 10046 (extended trace) event in 9i

3. I have the OPTIMIZER_GOAL as FIRST_ROWS on the UPDATE statement and I use a
BULK UPDATE using the FORALL syntax. My DBA says that since it is using
FIRST_ROWS, it is trying to do a row-by-row process and asked me to change the
optimizer mode to CHOOSE or ALL_ROWS.
    Is his claim true? Also, what is the use of
    OPTIMIZER_GOAL in a DML. My understanding is that it is
    valid only for a query.
    Will parallel DML be helpful if the table is not
    partitioned. Also, how do we use parallel DML with BULK
    operations?
    Right now, I use a the following construct :
    OPEN cursor
    LOOP
        BULK fetch
        some update statements
    END LOOP
    CLOSE cursor
    My DBA says, we could use SELECT FOR UPDATE and using
    WHERE CURRENT OF in UPDATE to improve performance. How
    do we do that in a FORALL construct.
4. I have difficulty in interpreting as to why a certain DML took a long time to
execute. Any help on that is greatly appreciated.
I have a situation where an update of  937495 rows in each of the 3 tables and
it takes 14 minutes for the entire process to complete  (for all 3 tables
together in spite of using bulk operations) and I update the tables one after
another. Any way to do this in Oracle for all 3 tables at same time.

Thanks

          Followup:

1) they are new with 9ir2.

from Effective Oracle By Design:

...
Now, we see even more detail. Not only do we get the plan and the number of rows
flowing through each step of the plan, but we also see logical I/O, physical
I/O, and timing information. (This 9.2.0.2 example was performed on a different
server, so these statistics are slightly different than the ones shown earlier.)
These are represented as follows:

·   cr is consistent mode reads, showing consistent gets (logical I/O)
·   r is physical reads
·   w is physical writes
·   time is elapsed time in millionths of a second; us stands for
microsecond
.......

2) you NEED the 10046 to get the wait events recorded in the trace file.

what it means is you no longer need to read the trace file to find the waits,
tkprof is showing them to you.

3) and tom says "lose the procedural code", just UPDATE.

done

loop
  fetch
  update
end

just

  update

first_rows just affects plans -- it does not make it "row by row".  if you are
happy with the plan, it is just dandy (but first_rows in an update, which does
not return until the last row is processed "doesn't make sense")

4) crystal ball is in the shop -- sorry, wth that level of detail -- no way I
can answer (other then to reiterate that perhaps you need 3 updates with no
procedural code!)

      Rating: 5
Optimizer
      operation  March 01, 2004
Reviewer:  Wayne
       from Texas
Tom

How have you been?

This article is helping me, but I am trying to figure out (1) what affects the
Optimizer and its choice of operation?  We have the same code & tables on a test
and on a PROD box  and the PROD is running dog slow (and choosing a full table
scan over the index search).

(2)  How does the clustering factor actually effect the CBO?
Or, what are the apples and oranges that I am actually comparing?

The PROD box is returning a query in 24 seconds and in TEST it is about 3.

Like you always say, "sho me!"  ... clustering factor is within 2% of each other
on the PROD and TEST
Clustering/rows is 70.xx% on PROD and it is 69.xx% on TEST for one of the
tables.

I have looked at all of the arcane and seldom used parameters that I can think
of.
Test was imported out of production last week.  Test tablespace is on a LMT.  I
moved the tables, rebuilt the indexes and analyzed tables (and checked the
date/time group in dba_tables & indexes.)

Thanks

Wayne

          Followup:

lets see

a) the autotrace traceonly explain from both
b) the tkprof from both (using the same inputs)

      Rating: 4
optimizer
      Hierarchy   March 01, 2004
Reviewer:
      A reader

 Hi tom,

   I know if I am retriving all the rows from the table, I need a full
tablescan. but if I am searching a value based on key or some select operation
where I am retriving a small amout of data, ( Using cost base analysis)what
should be the "general Hierarchy" of the plan

like nested tables vs. hash join vs. sort mearge ... ??

how do I compare those ?

          Followup:

well, it depends really.

select * from emp, dept where emp.deptno = dept.deptno;

Now, i'm accessing EMP by deptno, "searching based on a key or some select
operation"

but, this would look like a HASH join to me..  however

select * from emp, dept where emp.deptno = dept.deptno and dname like 'A%';

might be a nested loops.

I would categorize/GENERALIZE it this way (generalizations never hold up 100%
remember):

if i want first rows, nested loops
  if i want all rows, hash
never want sort merge, that happens when you disable hash joins ;)

      Rating: 4
Thanks
      !!  March 01, 2004
Reviewer:  A reader

thanks tom.

  what does the nested loop really do ? is it

 for (int i, i< count; i++)
  for (int j , j < i, j++ )
    { some operation ;
      }

 is this ? or something else ?

          Followup:

thats sort of it, yes.  if you are interested, i put the psuedo code for the
access paths in my new book "Effective Oracle by Design"

      Rating:
      5
autotrace  March 01, 2004
Reviewer:
      Wayne  from San Antonio
TEST ENVIRONMENT

SQL> set autotrace traceonly explain;
SQL> SELECT sum(total_trnd), sum(total_reqd), sum(avail_trnd), sum(avail_reqd)
FROM trn_event_pers_summary s,        v_pers_unit v  WHERE cmdstr_cs_id      =
95986  AND s.pers_id = v.pers_pers_id   AND element_id = 1683150;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2363 Card=1 Bytes=10
          2)

   1    0   SORT (AGGREGATE)
   2    1     NESTED LOOPS (OUTER) (Cost=2363 Card=1 Bytes=102)
   3    2       NESTED LOOPS (OUTER) (Cost=2363 Card=1 Bytes=97)
   4    3         NESTED LOOPS (OUTER) (Cost=2362 Card=1 Bytes=86)
   5    4           NESTED LOOPS (Cost=2361 Card=1 Bytes=76)
   6    5             NESTED LOOPS (Cost=2327 Card=17 Bytes=1003)
   7    6               NESTED LOOPS (Cost=2327 Card=17 Bytes=935)
   8    7                 NESTED LOOPS (Cost=2327 Card=17 Bytes=850)
   9    8                   NESTED LOOPS (Cost=2327 Card=17 Bytes=816)
  10    9                     NESTED LOOPS (Cost=603 Card=862 Bytes=18
          102)

  11   10                       NESTED LOOPS (Cost=3 Card=20 Bytes=240
          )

  12   11                         TABLE ACCESS (BY INDEX ROWID) OF 'CO
          MMAND_STRUCT' (Cost=2 Card=1 Bytes=10)

  13   12                           INDEX (UNIQUE SCAN) OF 'CS_PK' (UN
          IQUE) (Cost=1 Card=1)

  14   11                         INDEX (FULL SCAN) OF 'VRANK_RANK_NUM
          ERIC_UK' (UNIQUE) (Cost=1 Card=20 Bytes=40)

  15   10                       TABLE ACCESS (BY INDEX ROWID) OF 'MANP
          OWER' (Cost=30 Card=203329 Bytes=1829961)

  16   15                         INDEX (RANGE SCAN) OF 'MP_PASCODE_I'
           (NON-UNIQUE) (Cost=1 Card=203329)

  17    9                     TABLE ACCESS (BY INDEX ROWID) OF 'PERSON
          NEL_FEED' (Cost=2 Card=145255 Bytes=3921885)

  18   17                       INDEX (RANGE SCAN) OF 'PFEED_MP_FK_I'
          (NON-UNIQUE) (Cost=1 Card=145255)

  19    8                   INDEX (UNIQUE SCAN) OF 'VVL_FLYER_PK' (UNI
          QUE)

  20    7                 INDEX (UNIQUE SCAN) OF 'VAFSC_PK' (UNIQUE)
  21    6               INDEX (UNIQUE SCAN) OF 'PERS_PK' (UNIQUE)
  22    5             TABLE ACCESS (BY INDEX ROWID) OF 'TRN_EVENT_PERS
          _SUMMARY' (Cost=2 Card=498 Bytes=8466)

  23   22               INDEX (UNIQUE SCAN) OF 'TRNEVPERS_PK' (UNIQUE)
           (Cost=1 Card=498)

  24    4           TABLE ACCESS (BY INDEX ROWID) OF 'UTC_GROUP_MEMBER
          S' (Cost=1 Card=189060 Bytes=1890600)

  25   24             INDEX (UNIQUE SCAN) OF 'UGM_PFEED_FK_I' (UNIQUE)
  26    3         TABLE ACCESS (BY INDEX ROWID) OF 'UTC_GROUP' (Cost=1
           Card=6875 Bytes=75625)

  27   26           INDEX (UNIQUE SCAN) OF 'UG_PK' (UNIQUE)
  28    2       INDEX (UNIQUE SCAN) OF 'VUTC_PK' (UNIQUE)

PROD ENVIRONMENT

SQL> set autotrace traceonly explain;
SQL> SELECT sum(total_trnd), sum(total_reqd), sum(avail_trnd), sum(avail_reqd)
FROM trn_event_pers_summary s,        v_pers_unit v  WHERE cmdstr_cs_id      =
95986  AND s.pers_id = v.pers_pers_id   AND element_id = 1683150;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1596 Card=1 Bytes=10
          2)

   1    0   SORT (AGGREGATE)
   2    1     NESTED LOOPS (OUTER) (Cost=1596 Card=1 Bytes=102)
   3    2       NESTED LOOPS (OUTER) (Cost=1596 Card=1 Bytes=97)
   4    3         NESTED LOOPS (OUTER) (Cost=1595 Card=1 Bytes=86)
   5    4           NESTED LOOPS (Cost=1594 Card=1 Bytes=76)
   6    5             NESTED LOOPS (Cost=1560 Card=17 Bytes=1003)
   7    6               NESTED LOOPS (Cost=1560 Card=17 Bytes=935)
   8    7                 HASH JOIN (Cost=1560 Card=17 Bytes=850)
   9    8                   NESTED LOOPS (Cost=611 Card=80106 Bytes=32
          84346)

  10    9                     HASH JOIN (Cost=611 Card=80106 Bytes=312
          4134)

  11   10                       NESTED LOOPS (Cost=3 Card=20 Bytes=240
          )

  12   11                         TABLE ACCESS (BY INDEX ROWID) OF 'CO
          MMAND_STRUCT' (Cost=2 Card=1 Bytes=10)

  13   12                           INDEX (UNIQUE SCAN) OF 'CS_PK' (UN
          IQUE) (Cost=1 Card=1)

  14   11                         INDEX (FULL SCAN) OF 'VRANK_RANK_NUM
          ERIC_UK' (UNIQUE) (Cost=1 Card=20 Bytes=40)

  15   10                       TABLE ACCESS (FULL) OF 'PERSONNEL_FEED
          ' (Cost=603 Card=145255 Bytes=3921885)

  16    9                     INDEX (UNIQUE SCAN) OF 'VVL_FLYER_PK' (U
          NIQUE)

  17    8                   TABLE ACCESS (FULL) OF 'MANPOWER' (Cost=34
          3 Card=203329 Bytes=1829961)

  18    7                 INDEX (UNIQUE SCAN) OF 'VAFSC_PK' (UNIQUE)
  19    6               INDEX (UNIQUE SCAN) OF 'PERS_PK' (UNIQUE)
  20    5             TABLE ACCESS (BY INDEX ROWID) OF 'TRN_EVENT_PERS
          _SUMMARY' (Cost=2 Card=492 Bytes=8364)

  21   20               INDEX (UNIQUE SCAN) OF 'TRNEVPERS_PK' (UNIQUE)
           (Cost=1 Card=492)

  22    4           TABLE ACCESS (BY INDEX ROWID) OF 'UTC_GROUP_MEMBER
          S' (Cost=1 Card=189118 Bytes=1891180)

  23   22             INDEX (UNIQUE SCAN) OF 'UGM_PFEED_FK_I' (UNIQUE)
  24    3         TABLE ACCESS (BY INDEX ROWID) OF 'UTC_GROUP' (Cost=1
           Card=6905 Bytes=75955)

  25   24           INDEX (UNIQUE SCAN) OF 'UG_PK' (UNIQUE)
  26    2       INDEX (UNIQUE SCAN) OF 'VUTC_PK' (UNIQUE)

          Followup:

ok, can you do a 10053 level trace on each and just post the differences in the
init.ora parameters?  eg:

alter session set events '10053 trace name context forever, level 1'

then autotrace traceonly explain in both, take the trace files and looking at
the "PARAMETERS USED BY THE OPTIMIZER" section, diff them -- see if anything is
"different"

      Rating:
      5
followup  March 01, 2004
Reviewer:
      Wayne  from Texas
Thanks Tom!

  The output will be out to you soon.

  However, I was just told that the Resource Plan could be a problem depending
upon the Operating System.

  Could that be true?

Wayne

          Followup:

you mean with the dbms_resource_manager stuff?  I'm not aware of that affecting
the plan, it uses the developed plan, but I don't think it influences it.

      Rating: 5
DBMS_resource
      and optimizer  March 01, 2004
Reviewer:
      A reader
Tom,

  I will send the full tkprofs to your email.

Here is the timing results - I think it may be that the underlying user is
included in the batch resource plan.  I cannot think of another good reason for
the bigger box to spend that much extra time on the query.

PROD

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.85       0.84          0         23          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        2      6.41      31.73       6052      12326         24           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      7.26      32.57       6052      12349         24           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 20853

TEST

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      1.57       1.70         50       1772          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.34       2.03        485      18517          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      1.91       3.73        535      20289          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 26694

          Followup:

nah, that won't be it  -- i'd be interested in the parameters here.

      Rating: 5
Parameters
      affecting optimizer  March 01, 2004
Reviewer:
      Wayne  from Texas
$> cat server_params.dif
$> diff training_params production_params > server_params.dif

7c7
< HASH_AREA_SIZE = 393216
---
> HASH_AREA_SIZE = 1048576
53,54c53,54
< DB_FILE_MULTIBLOCK_READ_COUNT = 8
< SORT_AREA_SIZE = 393216
---
> DB_FILE_MULTIBLOCK_READ_COUNT = 16
> SORT_AREA_SIZE = 1048576

--   **********************  TRAINING SERVER

training> cat training_params
***************************************
PARAMETERS USED BY THE OPTIMIZER
********************************
OPTIMIZER_FEATURES_ENABLE = 8.1.7
OPTIMIZER_MODE/GOAL = Choose
OPTIMIZER_PERCENT_PARALLEL = 0
HASH_AREA_SIZE = 393216
HASH_JOIN_ENABLED = TRUE
HASH_MULTIBLOCK_IO_COUNT = 0
OPTIMIZER_SEARCH_LIMIT = 5
PARTITION_VIEW_ENABLED = FALSE
_ALWAYS_STAR_TRANSFORMATION = FALSE
_B_TREE_BITMAP_PLANS = FALSE
STAR_TRANSFORMATION_ENABLED = FALSE
_COMPLEX_VIEW_MERGING = FALSE
_PUSH_JOIN_PREDICATE = FALSE
PARALLEL_BROADCAST_ENABLED = FALSE
OPTIMIZER_MAX_PERMUTATIONS = 80000
OPTIMIZER_INDEX_CACHING = 0
OPTIMIZER_INDEX_COST_ADJ = 100
QUERY_REWRITE_ENABLED = FALSE
QUERY_REWRITE_INTEGRITY = ENFORCED
_INDEX_JOIN_ENABLED = FALSE
_SORT_ELIMINATION_COST_RATIO = 0
_OR_EXPAND_NVL_PREDICATE = TRUE
_NEW_INITIAL_JOIN_ORDERS = FALSE
_OPTIMIZER_MODE_FORCE = TRUE
_OPTIMIZER_UNDO_CHANGES = FALSE
_UNNEST_SUBQUERY = FALSE
_PUSH_JOIN_UNION_VIEW = FALSE
_FAST_FULL_SCAN_ENABLED = TRUE
_OPTIM_ENHANCE_NNULL_DETECTION = TRUE
_ORDERED_NESTED_LOOP = FALSE
_NESTED_LOOP_FUDGE = 100
_NO_OR_EXPANSION = FALSE
_QUERY_COST_REWRITE = TRUE
QUERY_REWRITE_EXPRESSION = TRUE
_IMPROVED_ROW_LENGTH_ENABLED = TRUE
_USE_NOSEGMENT_INDEXES = FALSE
_ENABLE_TYPE_DEP_SELECTIVITY = TRUE
_IMPROVED_OUTERJOIN_CARD = TRUE
_OPTIMIZER_ADJUST_FOR_NULLS = TRUE
_OPTIMIZER_CHOOSE_PERMUTATION = 0
_USE_COLUMN_STATS_FOR_FUNCTION = TRUE
_SUBQUERY_PRUNING_ENABLED = TRUE
_SUBQUERY_PRUNING_REDUCTION_FACTOR = 50
_SUBQUERY_PRUNING_COST_FACTOR = 20
_LIKE_WITH_BIND_AS_EQUALITY = FALSE
_TABLE_SCAN_COST_PLUS_ONE = FALSE
_SORTMERGE_INEQUALITY_JOIN_OFF = FALSE
_DEFAULT_NON_EQUALITY_SEL_CHECK = TRUE
_ONESIDE_COLSTAT_FOR_EQUIJOINS = TRUE
DB_FILE_MULTIBLOCK_READ_COUNT = 8
SORT_AREA_SIZE = 393216
***************************************
BASE STATISTICAL INFORMATION

--  PRODUCTION SERVER

production> cat production_params
***************************************
PARAMETERS USED BY THE OPTIMIZER
********************************
OPTIMIZER_FEATURES_ENABLE = 8.1.7
OPTIMIZER_MODE/GOAL = Choose
OPTIMIZER_PERCENT_PARALLEL = 0
HASH_AREA_SIZE = 1048576
HASH_JOIN_ENABLED = TRUE
HASH_MULTIBLOCK_IO_COUNT = 0
OPTIMIZER_SEARCH_LIMIT = 5
PARTITION_VIEW_ENABLED = FALSE
_ALWAYS_STAR_TRANSFORMATION = FALSE
_B_TREE_BITMAP_PLANS = FALSE
STAR_TRANSFORMATION_ENABLED = FALSE
_COMPLEX_VIEW_MERGING = FALSE
_PUSH_JOIN_PREDICATE = FALSE
PARALLEL_BROADCAST_ENABLED = FALSE
OPTIMIZER_MAX_PERMUTATIONS = 80000
OPTIMIZER_INDEX_CACHING = 0
OPTIMIZER_INDEX_COST_ADJ = 100
QUERY_REWRITE_ENABLED = FALSE
QUERY_REWRITE_INTEGRITY = ENFORCED
_INDEX_JOIN_ENABLED = FALSE
_SORT_ELIMINATION_COST_RATIO = 0
_OR_EXPAND_NVL_PREDICATE = TRUE
_NEW_INITIAL_JOIN_ORDERS = FALSE
_OPTIMIZER_MODE_FORCE = TRUE
_OPTIMIZER_UNDO_CHANGES = FALSE
_UNNEST_SUBQUERY = FALSE
_PUSH_JOIN_UNION_VIEW = FALSE
_FAST_FULL_SCAN_ENABLED = TRUE
_OPTIM_ENHANCE_NNULL_DETECTION = TRUE
_ORDERED_NESTED_LOOP = FALSE
_NESTED_LOOP_FUDGE = 100
_NO_OR_EXPANSION = FALSE
_QUERY_COST_REWRITE = TRUE
QUERY_REWRITE_EXPRESSION = TRUE
_IMPROVED_ROW_LENGTH_ENABLED = TRUE
_USE_NOSEGMENT_INDEXES = FALSE
_ENABLE_TYPE_DEP_SELECTIVITY = TRUE
_IMPROVED_OUTERJOIN_CARD = TRUE
_OPTIMIZER_ADJUST_FOR_NULLS = TRUE
_OPTIMIZER_CHOOSE_PERMUTATION = 0
_USE_COLUMN_STATS_FOR_FUNCTION = TRUE
_SUBQUERY_PRUNING_ENABLED = TRUE
_SUBQUERY_PRUNING_REDUCTION_FACTOR = 50
_SUBQUERY_PRUNING_COST_FACTOR = 20
_LIKE_WITH_BIND_AS_EQUALITY = FALSE
_TABLE_SCAN_COST_PLUS_ONE = FALSE
_SORTMERGE_INEQUALITY_JOIN_OFF = FALSE
_DEFAULT_NON_EQUALITY_SEL_CHECK = TRUE
_ONESIDE_COLSTAT_FOR_EQUIJOINS = TRUE
DB_FILE_MULTIBLOCK_READ_COUNT = 16
SORT_AREA_SIZE = 1048576
***************************************
BASE STATISTICAL INFORMATION

          Followup:

7c7
< HASH_AREA_SIZE = 393216
---
> HASH_AREA_SIZE = 1048576
53,54c53,54
< DB_FILE_MULTIBLOCK_READ_COUNT = 8
< SORT_AREA_SIZE = 393216
---
> DB_FILE_MULTIBLOCK_READ_COUNT = 16
> SORT_AREA_SIZE = 1048576

Larger hash area size and larger multiblock read counts.  that would do it.

do this in test

alter session set hash_area_size = 1048576;
alter session set DB_FILE_MULTIBLOCK_READ_COUNT = 16;

and see if you don't get the same behaviour (that would explain "why" they are
different)

      Rating:
      5
Awsome!  March 02, 2004
Reviewer:
      Wayne  from Texas
Tom,

  .27 seconds is a pretty site for sore eyes.

  Now then what are my implications of:
(1)  reducing hash_area_size
(2)  reducing DB_FILE_MULTIBLOCK_READ_COUNT
(3)  reducing both?

Thanks!

Wayne

          Followup:

besides having a consistent set of parameters in test and development so you can
expect near same behaviors :)

reducing 1) will make hash joins look less enticing.  reducing 2) will make full
scans more costly.  reducing both will just be "cumulative"

      Rating:
      5
Statistics  March 04, 2004
Reviewer:
      Mike  from Germany
Oracle 9.2.0.4

Tom,

We just moved from 8.1.7.4 (Rule Based) to 9.2.0.4 (Cost Based)...
And obviously...I am seeing some changes in my sleeping habbits :)
Before..I could see that the index was missing..and add the index..and boom..the
query is faster..now I know that this won't be true...and am having a hard time
explaining that to developers...any way here is my questions:

These are plans for the same query on two different databases:

production:
636 rows selected.

Elapsed: 00:00:01.09

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=125 Card=219 Bytes=2
          5185)

   1    0   HASH JOIN (Cost=125 Card=219 Bytes=25185)
   2    1     TABLE ACCESS (FULL) OF 'CP_ORG' (Cost=3 Card=241 Bytes=1
          0122)

   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'PROJ_PROJECT' (Cost=12
          1 Card=219 Bytes=15987)

   4    3       INDEX (RANGE SCAN) OF 'I_PROJ_PROJECT_UPPER_PROJID' (U
          NIQUE) (Cost=4 Card=438)

Statistics
----------------------------------------------------------
       4453  recursive calls
          0  db block gets
      11896  consistent gets
          0  physical reads
          0  redo size
      35420  bytes sent via SQL*Net to client
        570  bytes received via SQL*Net from client
         44  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        636  rows processed

development:
636 rows selected.

Elapsed: 00:00:01.04

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=1 Bytes=4114)
   1    0   NESTED LOOPS (Cost=5 Card=1 Bytes=4114)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'PROJ_PROJECT' (Cost=4
          Card=1 Bytes=72)

   3    2       INDEX (RANGE SCAN) OF 'I_PROJ_PROJECT_UPPER_PROJID' (U
          NIQUE) (Cost=3 Card=1)

   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'CP_ORG' (Cost=1 Card=1
           Bytes=4042)

   5    4       INDEX (UNIQUE SCAN) OF 'I_CP_ORG_ORGID' (UNIQUE)

Statistics
----------------------------------------------------------
       4453  recursive calls
          0  db block gets
      12563  consistent gets
          0  physical reads
          0  redo size
      35049  bytes sent via SQL*Net to client
        570  bytes received via SQL*Net from client
         44  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        636  rows processed

question 1:
This query was running really slow..when we moved it to production...so I saw
that some of the tables it used were not analyzed/monitored..so that I fixed
that...and query ran really faster....now those same tables were not analyzed in
development either...but query ran faster...
another difference was nested loops vs hash join...and my favorite (the one I am
trying to get out of my head)...one is doing a FTS on CP_ORG.

question 2:
Do statistics drop on a snapshot table when we run complete refresh on it?

Thanks..

          Followup:

question 1: isn't a question?  (but full scans are not evil)

question 2: it just does a truncate and insert /*+ append */ for a complete
refresh (or delete + insert  if part of a snapshot group).  So, no, it does not
drop the stats but the stats are in definite need of "regathering"

March 04, 2004 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.

Review &
      Followup

      Rating: 4
what if
      ?  March 04, 2004
Reviewer:  A reader

I need to know as of today how much DB grew over the last year.Assuming I dont
have those tables you asked to create.
Any clues to this sir? Any v$ tables storing such info or any system views ?

          Followup:

nope, if you didn't write down how big it was last year somewhere you won't be
able to figure this out.

the 9i and before databases did not have a "long term memory"

March 04, 2004 Praveen -- Thanks for the question regarding
      "LIstener", version 8.1.5

      You Asked
Just wanted to check up lsnrctl is having the following
-rwsr-s--x   1 oracle   dba       494456 Dec  7  1999 lsnrctl
permissions for all of my servers.just needed to know whether it was shipped
with oracle or it was done here .is it the same for all the people.this
permission set means anybody can start/stop my listener.
I am going to change it to -rwsr-s---
does this have any problems.
Thanks
Praveen

      and we said...
A more correct method would be to password protect the listener functions.

See the net8 admin guide for info but in short -- you can:

LSNRCTL> change_password
Old password:    <just hit enter if you don't have one yet>
New password:
Reenter new password:
Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=slackdog)(PORT=1521)))
Password changed for LISTENER
The command completed successfully

LSNRCTL> set password
Password:
The command completed successfully

LSNRCTL> save_config
Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=slackdog)(PORT=1521)))
Saved LISTENER configuration parameters.
Listener Parameter File   /d01/home/oracle8i/network/admin/listener.ora
Old Parameter File   /d01/home/oracle8i/network/admin/listener.bak
The command completed successfully
LSNRCTL>

Now, you need to use a password to do various operations (such as STOP) but not
others (such as STATUS)

followup to comment two below

$ oerr tns 1169
01169, 00000, "The listener has not recognized the password"
// *Cause:  The security feature of the listener is preventing the intended
// operation.
// *Action: Some commands like stopping the listener are only intended to be
// done by DBAs. A client can only use LSNRCTL to get the status of a listener
// and the version of the listener software running. The command that the
// client attempted may not have been appropriate. If a DBA is attempting
// the command then use the "set password" command at the LSNRCTL prompt
// with one of the appropriate passwords in the PASSWORDS_<listener_name> entry
// in LISTENER.ORA. If you do not know a password then you are trying to
// execute an inappropriate command.

so, either you changed the password and you didn't really type in the correct
password or you had a typo.

if need be, reset the password manually by shutting down (kill it if you need
to) the listener and editing the listener.ora

Review &
      Followup

      Rating: 4
Good one
        July 18, 2001
Reviewer:  Gururaj Kulkarni
       from Norcross, GA USA
Thakns Tom

      Rating: 4
Problem with
      save_config  July 18, 2001
Reviewer:  Gururaj
      Kulkarni  from Norcross, GA USA
Hi Tom,

I tried using the commands which you have mentioned. While
using the save_config, I got the error "TNS-01169: The
listener has not recognized the password ".
I tried to stop the service and it is giving the same
problem. How do I fix this ?

Thanks in Advance
-Gururaj

      Rating: 4
To Check Status
      of Listener  July 18, 2001
Reviewer:  Suresh
       from NewYork, USA
Tom,
   Sometimes when I try to connect to Oracle, I get a message 'Listener not up'
or something..I am not sure of exact wordings. How do I check the status of the
Listener? If it is not up, how do I bring it Up.

      Rating: 3
Respnse to
      previous post  July 19, 2001
Reviewer:
      Connor  from UK
"lsnrctl status" to show the status
"lsnrctl start"  to start the listener

      Rating: 4
What's that One
      Extra Listener "Handler" ?  June 16, 2003

Reviewer:  Robert  from PA
9iR2/Linux

My lsnrctl status :

Services Summary...
Service "OEMREP.localdomain" has 1 instance(s).
  Instance "OEMREP", status UNKNOWN, has 1 handler(s) for this service...
Service "PLSExtProc" has 1 instance(s).
  Instance "PLSExtProc", status UNKNOWN, has 1 handler(s) for this service...
Service "ora1.localdomain" has 2 instance(s).
  Instance "ora1", status UNKNOWN, has 1 handler(s) for this service...
  Instance "ora1", status READY, has 1 handler(s) for this service...
Service "ora1XDB.localdomain" has 1 instance(s).
  Instance "ora1", status READY, has 1 handler(s) for this service...
The command completed successfully

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

Why is there one EXTRA "handler" for "ora1" instance (my test instance) that's
of status UNKNOWN ? :
"Instance "ora1", status UNKNOWN, has 1 handler(s) for this service..."

Thanks

      Rating: 5
ORA-01034:
      ORACLE not available  December 29, 2003
Reviewer:
      atul  from India
Hi,

When i try to connect to my database using tnsentry it gives
error "ORA-01034: ORACLE not available"

sqlplus sys/prereldb@ieeceu51_622.world
ERROR:
ORA-01034: ORACLE not available

But without that it's working fine

sqlplus sys/prereldb

connected

Oracle is owned by user oracle,and listener is stared with username "porb0804".

Will that is the cause of problem?
Should listner process owner be same as oracle owner?

Thank.
Atul

          Followup:

well, no one could say for sure that the database you access via
"ieeceu51_662.world" is even remotely the same as you access via "nothing"

but basically, you've misconfigured something either in your tnsnames.ora file
(wrong oracle home, wrong oracle sid) or your listener.ora file.

      Rating: 4
ORA-27121
      unable to determine size of shared memory segment  January
      02, 2004
Reviewer:  P.S.Vinodh  from India,Chennai

Hi Tom ,
iam facing problem while connecting to database using sqlplus.

ORA-01034: ORACLE not available
ORA-27121 unable to determine size of shared memory segment
SVR4 Error :13: Permission denied.

i checked with metalink but nothing is working .. what would be the problem...

can you please throw me some light on it tom

thanks in advance

          Followup:

[tkyte@localhost tkyte]$ oerr ora 27121
27121, 00000, "unable to determine size of shared memory segment"
// *Cause: shmctl() call failed
// *Action: check permissions on segment, contact Oracle support

sounds like your permissions are set wrong.  did you run root.sh on install?
are the perms on the oracle binary set right (they should be setuid). if you use
ipcs, do the segments already exist and are they owned by the right guy.

i do assume that since you said you hit metalink, you read this note right:

Article-ID:         <Note:235469.1>

      Rating: 5
Permission
      Problem  January 06, 2004
Reviewer:
      P.S.Vinodh  from India,Chennai
Hi tom,
the problem is i have tried with

1)changed the permission of the binary files .
2)setuid enabled.

Other thing is in that same server all other database are working actually we
created the test database. but now we could not able to login but all other
database are working fine.. with that..

can you help me out in this.

          Followup:

so, show us the output of:

$ env
$ ls -l $ORACLE_HOME/bin/oracle
$ sqlplus u/p

a DIRECT cut and paste -- no edits please.

      Rating: 5
permission
      problem  January 07, 2004
Reviewer:  vinodh
       from India, Chennai
Hi tom,
as you said i have given below in that oracle binary file sticky bit i have
reverted back to orginal permission becoz if it has sticky bit then sysdba is
not getting connected .

: bash-2.03$ env
PWD=/db/rgprd
TZ=US/Pacific
ORACLE_SID=rgstgnew
HZ=
HOSTNAME=nplsjstgdb01
MACHTYPE=sparc-sun-solaris
EDITOR=vi
ORACLE_HOME=/oracle/9.2.0
LOGNAME=rgprdora
SHLVL=1
SHELL=/bin/bash
HOSTTYPE=sparc
OSTYPE=solaris
HOME=/db/rgprd
TERM=vt100
PATH=/usr/local/bin:/usr/bin::/db/rgprd/bin:/obacktrack-3.4.00-64-o8lib/bin:/usr
/ccs/bin:/oracle/9.2.0/bin:/usr/ccs/bin
_=/usr/bin/env
bash-2.03$ ls -l $ORACLE_HOME/bin/oracle
-rwxr-xr-x   1 oracle   dba      68208176 May 28  2003 /oracle/9.2.0/bin/oracle
bash-2.03$ sqlplus test/test@rgstgnewp

SQL*Plus: Release 9.2.0.3.0 - Production on Tue Jan 6 22:12:24 2004

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

ERROR:
ORA-01034: ORACLE not available
ORA-27121: unable to determine size of shared memory segment
SVR4 Error: 13: Permission denied

          Followup:

then it is owned by the entirly wrong person (eg: whoever installed this messed
up big time in a fashion I've not seen before)

do an ipcs -a and see who owns this shared memory segment.

the oracle binary must be owned by that user.
the oracle binary must be setuid for that username.

      Rating:
      5
thanks  January 12, 2004
Reviewer:
      P.S.Vinodh  from India, Chennai
Hi Tom,

Our Apps DBA installed that..
Thanks for your time tom,

      Rating:
      1
ORA-01034,ORA-27101 and Linux Error :2: No such file or
      directory  March 04, 2004
Reviewer:
      Lawrence  from Canada
I tried it on my Redhat Enterprise 3 and Oracle10g,but sqlplus still failed:

ORA-01034 ORACLE not available
ORA-27101 share memory realm does not exist
Linux Error :2: No such file or directory

I searched on metalink.oracle.com,asktom.oracle.com and others website,peoples
said maybe ORACLE_HOME, ORACLE_SID or kernel parameters are not correct,I tried
in different ways,unfortunately, is not helpful. There are so many people
encounter the problems, why Oracle does not consider to fix it???

thanks,

Lawrence

here are some files:
1.tnsname.ora:

SALES =
( DESCRIPTION =
( ADDRESS = ( PROTOCOL = TCP) (HOST = localhost.localdomain)(PORT = 1521))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = sales)
)
)

EXTPROC_CONNECTION_DATA =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC))
)
(CONNECT_DATA =
(SID = PLSExtProc)
(PRESENTATION = RO)
)
)

2.listener.ora:
SID_LIST_LISTENER =
(SID_LIST =
(SID_DESC =
(SID_NAME = PLSExtProc)
(ORACLE_HOME =/home/oracle/product/10.1.0/db_1)
(PROGRAM = extproc)
)
)

LISTENER =
(DESCRIPTION_LIST =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC))
)
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST=localhost.localdomain)
(POST = 1521))
)
)
)

3.kernel parameters:
kernel parameters:
kernel.shmall = 2097152
kernel.shmmax = 2147483648
kernel.shmmni = 4096
kernel.sem = 250 32000 100 128
fs.file-max = 65536
net.ipv4.ip_local_port_range = 1024 65000

add following line to:
/etc/security/limits.conf:

* soft nproc 2047
* hard nproc 16384
* soft nofile 1024
* hard nofile 65536

add following line to:
/etc/pam.d/login

add following line to:

session required /lib/security/limits.so

add following line to:
/etc/profile

if [ $USER = "oracle" ]; then
if [ $SHELL = "/bin/ksh" ]; then
ulimit -p 16384
ulimit -n 65536
else
ulimit -u 16384 -n 65536
fi
fi

4.bash_profile

PATH = $PATH:$HOME/bin
umask 022
export ORACLE_BASE=/home/oracle
export ORACLE_HOME=ORACLE_BASE/product/10.1.0/db_1
export ORACLE_SID=sales
export ORACLE_TERM=xterm
LD_LIBRARY_PATH=$ORACLE_HOME/jdk/fre/lib/i386:$ORACLE_HOME/jdk/fre
export LD_LIBRARY_PATH
export PATH=$ORACLE_HOME/bin:$PATH

5.part of env:
SSH_AGENT_PID=2697
HOSTNAME=localhost.localdomain
SHELL=/bin/bash
TERM=xterm
NLS_LANG=AMERICAN
USER=oracle
LD_LIBRARY_PATH=/home/oracle/product/10.1.0/db_1/jdk/jre/lib/i386:/home/oracle/pr
oduct/10.1.0/db_1/jdk/jre
ORACLE_SID=sales
ORACLE_BASE=/home/oracle
USERNAME=oracle
PATH=/USR/kerberos/bin:/usr/local/bin:/usr/bin:/lib:/usr/X11R6/bin:/home/oracle/b
in:/home/oracle/product/10.1.0/db_1/bin
ORACLE_TERM=xterm
HOME=/home/oracle
LOGNAME=oracle
ORACLE_HOME=/home/oracle/producr/10.1.0/db_1
XAUTHORITY=/home/oracle/.xauthority
OLDPWD=/home/oracle/product/10.1.0/db_1/network/admin

          Followup:

there is nothing to fix, except something in your configuration is why....

and unfortunately you have none of the needed stuff here.

I cannot see what command line you used to try and connect (eg: was it over the
network, local, how)

I cannot see what ORACLE_HOME and ORACLE_SID were used to start the database.

I can see an obvious typo in your ORACLE_HOME environment variable -- producr.
Hmmm....

March 04, 2004 Meyer -- Thanks for the question regarding
      "Parsing Times from Character String", version 9.2.0

      You Asked
Tom,

/*
additional info:
I changed version to 9.2 only
I can code it if my method (specified below) is correct but it should pipe,
Interested in your thoughts if you have another approach or other logic, thanks!
*/

I am working with a system that has a table like this

CREATE TABLE resource_schedule
    (resource_id                    NUMBER NOT NULL,
    schedule_date                   DATE NOT NULL,
    scheduled                      VARCHAR2(288));

resource_id is a number
schedule_date is any date (truncated to midnight on insert)
scheduled looks like this:

UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU
UUUUUUUUUSSSSSSSSSSSSSSSSSSAAAAAAAAAAAAAAAAAAAAAAAAAASSSSSSSSAAAAAAAAAAAAAAAAAAAA
AAAAAAAASSSSSSSSAAAAAAAAAAAAAAAAAASSSSSSAAUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU
UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU

It is a 288 char field with each char representing 5 minutes of the day (so a
full 24 hours are represented), the chars are either:

U = Unavailable
A = Available
S = Scheduled

This is not my data model, and I can not change anything about it (vendor
created/supported application)...but I am going to get the data into my DB (Via
DB link) and make a read only application that displays the data in better ways
than their app does.

For a single record, what is the best way to list contiguous blocks of time by
type (U,A,S)...meaning (resource_id, type, start-time, end-time)?

Thinking create a function that loops and parses each char via substr...makes a
note of the first new type...and prev char...then does an an insert if current
not the same as prev char...(or function that returns records). Interested if
you have a better way and your thoughts on best approach...

if you think we are thinking the exact same thing that would be fine for an
answer (I can code it), and if you have a another approach your thoughts are
appreciated.

Also, have you seen this data model before in a scheduling system and what do
you think of it?

Thanks,
Meyer

      and we said...
you'll want to do a verification on this but.... we can approach it like this:

ops$tkyte@ORA9IR2> create table t ( resource_id number, schedule_date date,
schedule varchar2(288) );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t values ( 1, trunc(sysdate),
  2
'UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU
UUUUUUUUUUSSSSSSSSSSSSSSSSSSAAAAAAAAAAAAAAAAAAAAAAAAAASSSSSSSSAAAAAAAAAAAAAAAAAAA
AAAAAAAAASSSSSSSSAAAAAAAAAAAAAAAAAASSSSSSAAUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU
UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU'
  3  );

1 row created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace type myScalarType as object
  2  ( resource_id number, code varchar2(1), five_count number, start_time date,
end_time date)
  3  /

Type created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace type myArrayType as table of myScalarType
  2  /

Type created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace function parse( p_cursor in sys_refcursor )
return myArrayType
  2  PIPELINED
  3  as
  4      l_rec t%rowtype;
  5      l_data myScalarType := myScalarType(null,null,null,null,null);
  6      l_len number;
  7  begin
  8
  9      loop
 10          fetch p_cursor into l_rec;
 11          exit when p_cursor%notfound;
 12          l_data.resource_id := l_rec.resource_id;
 13          l_data.start_time := l_rec.schedule_date;
 14          loop
 15              exit when (l_rec.schedule is null);
 16              l_data.code := substr( l_rec.schedule, 1, 1 );
 17
 18              l_len := length(l_rec.schedule);
 19              l_rec.schedule := ltrim(l_rec.schedule, l_data.code);
 20              l_data.five_count := (l_len-nvl(length(l_rec.schedule),0));
 21              l_data.end_time := l_data.start_time + (5 *
(l_len-nvl(length(l_rec.schedule),0))) /60/24;
 22              pipe row( l_data );
 23              l_data.start_time := l_data.end_time;
 24          end loop;
 25      end loop;
 26      close p_cursor;
 27      return;
 28  end;
 29  /

Function 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> select * from TABLE( parse( cursor( select * from t ) ) );

RESOURCE_ID C FIVE_COUNT START_TIME           END_TIME
----------- - ---------- -------------------- --------------------
          1 U         90 04-mar-2004 00:00:00 04-mar-2004 07:30:00
          1 S         18 04-mar-2004 07:30:00 04-mar-2004 09:00:00
          1 A         26 04-mar-2004 09:00:00 04-mar-2004 11:10:00
          1 S          8 04-mar-2004 11:10:00 04-mar-2004 11:50:00
          1 A         28 04-mar-2004 11:50:00 04-mar-2004 14:10:00
          1 S          8 04-mar-2004 14:10:00 04-mar-2004 14:50:00
          1 A         18 04-mar-2004 14:50:00 04-mar-2004 16:20:00
          1 S          6 04-mar-2004 16:20:00 04-mar-2004 16:50:00
          1 A          2 04-mar-2004 16:50:00 04-mar-2004 17:00:00
          1 U         84 04-mar-2004 17:00:00 05-mar-2004 00:00:00

10 rows selected.

(five count is just there for "debugging")
Review &
      Followup

      Rating: 5
6
      stars!!!  March 04, 2004
Reviewer:  Meyer
       from Baltimore, MD
Thanks Tom, this works faster then my original approach and accepts multiple
records!!!

Meyer

      Rating: 5
Tom
        March 06, 2004
Reviewer:  A reader

You're awesome

March 04, 2004 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

      Rating: 3
Some quick
      question  March 03, 2004
Reviewer:  Matt
       from Australia
Is there any way of "seeing" what sample size Oracle decided to use when
gathering stats using "dbms_stats.auto_sample_size"?

I want to know whether Oracle is doing something that looks sensible, given my
data.

Is it documented as to how the sample size is generated?

After what version can we generate stats on system? If at all.
After what version can we generate stats on sys? If at all.

I'm on (9.2.0.4).

Thanks yet again.

          Followup:

its in the data dictionary after you analyze.

it is not documented and it can and will change over time.

      Rating: 3
a question on
      the sample size when using
      dbms_stats.auto_sample_size  March 04, 2004

Reviewer:  Sandy  from Chicago,IL USA
I am testing dbms_stats , estimating with the auto_sample_size.
DBMS_STATS.GATHER_SCHEMA_STATS                     (ownname=>''PHI'',

estimate_percent=>DBMS_STATS.AUTO_SAMPLE_SIZE,
 method_opt=>''FOR ALL COLUMNS SIZE AUTO'',
cascade=>TRUE)

When I check the sample size , I find the num_rows equal to the sample size for
all my tables. I thought that the auto_sample_size would do an estimate based on
the data and not compute. Is that a wrong assumption? Because of this the stats
gathering on our large tables is taking a lot of time : 10 hours.

          Followup:

You'll find the algorithms evolve over time.  The algorithm does a small sample
and if not happy with it -- scales it up (rather rapidly) and once it gets into
the 40's -- it pretty much gets into "100% analyze" mode (anything above 49% is
really 100%)

Things that will cause it to jump really fast currently are actually pretty
common -- almost all null columns and almost but not quite unique columns.  If
you are interested in playing with this in 9i, you can contact support to talk
about the information in bug3131551 - there are events that can be set to tweak
the behavior -- or in 10g, it just "happens".

(or just use your own fixed %)

      Rating: 4
Thanks
        March 08, 2004
Reviewer:  A reader

Thanks Tom for your response. I will check with support about the events.

sandy

March 04, 2004 -- 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!!!

      Rating: 5
WITH
      AS  March 04, 2004
Reviewer:  A reader
       from Orange, CA
Tom, I've never seen above type of SQL could you please refer me to a document
where I can find more information regarding this or perhaps can you explain?
I mean the [WITH player AS (select ...] part of it. It looks as a new 9i
feature.

Thanks

          Followup:

search for

"subquery factoring"

in quotes on this site for more info

March 05, 2004 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.

      Rating: 4
Sysdate in
      PLSQL and sysdate in SQL  January 15, 2004

Reviewer:  Sandeep M Deokar  from India
hi tom
i want to ask you a simple question from your view but a bit difficult from mine
we use one PLSQL code here
declare
sysdate date ;
begin
..
..
end;
it works fine
that means we can define sysdate as a name of a variable in PLSQL block
where as
SQL> create table te(sysdate number);
create table te(sysdate number)
                *
ERROR at line 1:
ORA-00904: : invalid identifier
and we cant define sysdate as column name of table name
why this diffrence is there

          Followup:

well, sometimes it is not ours to ask why -- but just to look at it and say
"really bad idea, we shouldn't do that".  would you really define a variable as
sysdate?

  1  declare
  2  sysdate date;
  3  begin
  4  for x in ( select sysdate y from dual ) loop
  5  dbms_output.put_line( '----' || x.y || '----' );
  6  end loop;
  7* end;
big_table@ORA9IR2> /
--------

PL/SQL procedure successfully completed.

that would be a really utterly bad "idea".

      Rating:
      5
Current_Date  February 11, 2004
Reviewer:
      Anindya  from Mumbai, India
Hi Tom,
  Faced a problem quite similar to "sysdate" variable declaration here. One
table having column name "CURRENT_DATE" started giving problem under 9i (table
was probably migrated from 8i to 9i). Had to rename that column. A select from
that column would always return system date rather than data from table.
But this is not the output in case of user defined functions.

What is the preference given for table columns, sql and user defined functions?

Since a column name may match a supplied function with some future release, what
are the precautions we can take?

Thanks

          Followup:

SQL takes precedence (keywords, reserved words)

the truly paranoid could use quoted identifiers -- eg:

select "CURRENT_DATE" from t

would have solved the problem as well. -- it would use your column.

short of that, you could use a prefix -- C$ or something (c = column)

      Rating: 4
current_date
      and sysdate  March 04, 2004
Reviewer:  john

Tom,

can you please tell us how different is this new current_date from sysdate?

          Followup:

current_date is timezone aware, sysdate is not.

March 05, 2004 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.

      Rating: 4
How should the
      n-tier (such as Tomcat) authenticate itself?  March 02, 2004

Reviewer:  Jim Nasby  from Austin, TX
Tom, what would you recommend for having the middle-ware authenticate itself? My
java coworkers are telling me that standard practice is to store the database
password in a config file on the server, but it seems this is as risky as
storing a role password in a config file on the server.

          Followup:

me, i don't like having middle tiers authenticate themselves.  they are just
software :)

they'll need to encrypt that password, and then they need to worry about how to
secure the key, and keep the passwords in sync....

sorry -- don't have a "really good answer"

      Rating:
      4
  March 03, 2004
Reviewer:  Jim
      Nasby  from Austin, TX
Hrm, maybe I should describe what I've been planning to do...

Rather than create our own system for managing user logins for our webapp, I'm
giving users accounts in oracle, and various application roles (right now we
just have an admin and a user role). Admins need to be able to add users, so
they have alter, create, and drop user granted to that role.

But I don't want admins to be able to arbitrarily connect to the database via
any tool and start dropping users at will; I only want them to have the admin
role when logged in via our application. It seems the best way to do this is to
login via a proxy account and have a stored proc verify that before allowing the
user to enable the admin role.

I'm using user administration as the example here, but there's plenty of other
things this will extend to; I'd like to use VPD features to limit visibility to
certain things based on what role a user has, for example.

Ideally, the webapp would just pass the user's credentials on to Oracle and let
it decide if the connection is to be allowed or not, but then it seems that
there's no way to limit user's ability to activate these roles that they should
only be using via our application. Of course n-tier proxy auth doesn't allow for
that, but our application could at least attempt to connect to oracle as the
user before it re-connects as itself and proxies in as the user.

I hope this makes sense... Is there a better way to limit access to the
application roles that doesn't involve n-tier authentication? Or am I heading
down the wrong road entirely?

          Followup:

Or put the application logic in the database -- in the form of a stored
procedure so that your application to add/drop users becomes nothing more than
lipstick to make this application look prettier than SQLPlus does.

In that fashion, they are ALWAYS using your application -- they are granted the
ability to run a stored procedure, stored procedure decides if they should be
able to do the operation based on whatever logic and then does it.  the users
need not have alter any user or anything like that -- only the OWNER of the
procedure would.

In this fashion, you can lock it down as tight as you like.

Also, this was a typo right?

"Of course n-tier proxy auth doesn't allow for that,"
                             ^^^^^^^^

that is exactly what ntier supports!  You can use an application role that can
only be enabled by the ntier account.

      Rating:
      5
  March 03, 2004
Reviewer:  Jim
      Nasby  from Austin, TX
Sorry, that paragraph is most unclear. What I was trying to say is that I'd like
to use the n-tier feature of using a procedure to validate that the user can
access the admin role, but also use the user's actual credentials to login to
the database. Is there a way to login to the database via the application using
the user's credentials *and* ensure that they can only activate these roles if
they login via the application?

Yes, I've thought about using AUTHID DEFINER for doing these things, but it
looks like that might be a fair amount of extra work with how things are
structured right now. I'm using Oracle Objects for most of the API to the
database, and I've implemented methods in the objects to handle the
corresponding DML that would be needed to store the information in the actual
tables (I'm not storing objects in tables). Unfortunately, there doesn't appear
to be any way to grant permissions to individual methods of objects; either you
can do everything in the object or nothing. Because of this, I'm granting
permission to execute DML on the base tables themselves, based on the different
roles. Also, I want to be able to use VPD features to limit what a user can see,
and my understanding is that the only way to do that is if procedures either do
all the checking themselves (sounds like extra work and a source for bugs), or
they run as the current user and let the VPD stuff handle everything.

So, while I can change my user management code to handle permissions on it's
own, I certainly don't want to be doing that for everything else. Granted, at
least with a VPD even if someone did get access to the tables outside the
application and could run arbitrary DML, they'd only be able to modify their own
stuff. But I'd still feel better if they were forced to do all DML through the
API I've defined, even though I use RI, constraints, triggers, etc. to enforce
rules wherever I can.

So...

Is there any way to have n-tier auth require the end-user's credentials as well
as the middleware's credentials?

Or, is there another way to secure the application roles that doesn't require
n-tier auth? Maybe storing role passwords in a table that only the middleware
has access to (I know... yuck)?

Thanks so much for your time!

          Followup:

"Is there a way to login to the database via the application using
the user's credentials *and* ensure that they can only activate these roles if
they login via the application?"

Well, not really, maybe.  The way N-Tier would ensure this is:

a) application logs in using credentials only IT knows (eg: user=APP, pw=secret)
b) application says "it is really bob" (eg: user=BOB)
c) your procedure that enables the role would say "is it really APP logged in on
behalf of BOB - if yes, then enable role, else do not"

Now, if BOB logs in directly -- what can your procedure ask?  It can ask "hey,
is this BOB".

It can also ask "what is the IP address of the client" (app server).  If you
make it so bob cannot log into the app server machine, that might work.
Procedure would say "if user=bob AND ip_addres in (set of allowed) then ok"

It can also ask "what is the name of the program that is connecting to me (in v$
tables)".  This however is easily spoofed.

      Rating: 5
What About
      Digital Certificates  March 03, 2004
Reviewer:
      A reader  from South Australia
Tom

Thanks for your valuable response as usual.

Please comment on the role of digital certificates (dc)installed on the client
desktop, in addition to other methods of authentication?

Is a dc relevant/applicable at all? Would it be easily setup, stolen etc?

Regards
Hien

          Followup:

PKI authentication is very strong and pretty easy for end users.  It takes
something you have (the cert) and somethig you know (a password to enable the
cert).

If you can use it, if it fits into your architecture, its a pretty strong
choice.

      Rating:
      5
Application-based Security  March 04, 2004

Reviewer:  John Gilmore  from London, Unted Kingdom

Hi Tom,

We have a Forms application which currently has all privileges (select, insert,
update, delete) granted to public.  All security is controlled within the
application by enabling and disabling menu selections etc.

My task is to implement some form of security within this mess, however they are
not prepared to go down the FGAC path due to the amount of work involved.

The best I can come up with is to use password-protected application roles which
would be enabled with a call to set_role within a Forms startup trigger.  I know
it's not totally secure due to the issues outlined above but is there anything
further that can be done given their reluctance to implement FGAC?

Thaks for your help.

          Followup:

"All security is controlled within the application "

hah -- that's funny almost (if it weren't so bad)  there is no security
here......

the password protected role is as good as anything else (it is really hard to
design in security after the fact -- imagine if cars did not come with locks on
them, what strange things would people do to secure their cars -- maybe a big
lock and chain wrapped around the car would work...).

      Rating: 4
How to
      preventing front-end users accessing the db via sqlplus
        March 05, 2004
Reviewer:  Iain Blacklaw
       from LONDON - ENGLAND
We overcame the problem by encrypting the application password. I.e. all
application passwords were  initially reset to match the username but encrypted.
When the  application user next logs on they are forced to change their
password. This password will only be recognised by the application. If the user
tries to log on via sqlplus, toad, etc with their application username and
password the connection will fail.

      Rating: 5
Another Idea
      ...  March 05, 2004
Reviewer:  John Gilmore
       from London, United Kingdom
The application runs on a dedicated server and someone has suggested checking
the logon server in a global database trigger, if it's the server which runs the
application then let them log in otherwise refuse the login attempt.  Do you see
any problems with this approach?

          Followup:

see above:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:9312070542748#16129677694162
that was one of the suggestions -- look at who is logging in and the machine
(app server machine) being used.

      Rating: 3
Ask
      Tom  March 08, 2004
Reviewer:  David Shink
       from Los Angeles, CA USA
     The Q & A gave me a good idea of what can and cannot be done, and how
effective the things that can be done are.
I would certainly recomment this.  It was more helpful than just searching on
Metalink.

March 05, 2004 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.

      Rating: 5
renaming a
      table that is being actively queried  March 05, 2004

Reviewer:  steve  from Montreal, QC
Hi Tom,

We have a large table T (20 million rows) that is 'refreshed'
every night. Table T_U (8.3 million rows) contains all the
records that are either new or updates to T.

The updates were excruciatingly slow. So what we found to work
very fast is:

  create table T_NEW nologging as
    select all rows from T, T_U
    where T.pk = T_U.pk (+)
      and T_U.x is null;

  insert /*+ append */ into T_NEW
     select * from T_U;

Next, we build all the indexes on T_NEW. E.g.

  create index T_NEW_ACNT_ID(account_id)

Then (and this is the step I need help with), we
'swap' the tables via a rename. In a PL/SQL block,
we do the following:

  alter INDEX T_ACNT_ID rename to T_OLD_ACNT_ID;

  alter INDEX T_NEW_ACNT_ID rename to T_ACNT_ID;

  alter table T rename constraint T_PK to T_OLD_PK;

  alter table T_NEW rename constraint T_NEW_PK to T_PK;

  alter table T rename to T_OLD;

  alter table T_NEW rename to T;

Question:
=========

  What if table T is being used when we attempt this rename.
  What will happen? Will the command wait? Is there a better
  way to do this (offline table)? Is there some sort of WAIT
  we can specify ourselves?

  You see where I'm going with this one...

Thanks Tom!

          Followup:

well, I would not worry about the index names, I would just "make up new ones"
every day (use a sequence or whatnot).  Same with the table name.

Then repoint a view or synonym (this is what I do every night - repoint a
synonym.  In 10g this will be doable without even invalidating stored procedures
which is way cool...)

In any case, renaming won't hurt running queries BUT it will cause invalidations
of stored procedures and views (until 10g) which can cause issues.

People running a stored procedure won't be affected HOWEVER, people who attempt
to run that procedure will be blocked until the ones that are running it finish
(so it can recompile itself).  Whether that affects you or not only you can
judge.

Did you try:

update ( select t.c1 t_c1, t.c2 t_c2, ...
                t_u.c1, tu_c1, t_u.c2 tu_c2, .....
           from t, t_u
          where t.pk = t_u.pk )
  set t_c1 = tu_c1, .....
/

insert /*+ append */ into t
select * from t_u
MINUS
select * from t;

(or select t_u.* from t_u, t where t_u.pk = t.pk(+) and t.pk is null)

you might be surprised if not (compared to doing it row by row (slow by slow))

      Rating: 5
Awesome
      answer!  March 05, 2004
Reviewer:  steve
       from Montreal
Awesome answer!

I really really like the idea of repointing the synonym!

I will try the update you mentioned and post the results.

Thanks Tom ever so much.

Steve

      Rating: 2
great
        April 01, 2004
Reviewer:  V.Vijay Kaushik
       from Chennai,India
Explanation was great

March 05, 2004 kathy -- Thanks for the question regarding "index
      fragmentation", version 8.1.6

      You Asked
Hi Tom,

i have a question about the index fragmentation, on the famous white paper "How
to stop defragmenting and start living: the definitive world of fragmentation"
by Bhaskar Himatsingka and Juan Loaiza, on section 4.2, they provide a script to
tell if the index can reduce a blevel, the procedure is this:
1.) analyze index index_name validate structure
2.) select name  NAME,
    (br_rows_len*100)/(br_blk_len*br_blks) BRANCH_UTILIZATION,
    ((lf_rows_len - del_lf_rows_len)*100)/ (lf_blk_len*lf_blks) LEAF_UTILIZATI,
    decode (sign(ceil(log(br_blk_len/(br_rows_len/br_rows),
            lf_blk_len/((lf_rows_len - del_lf_rows_len)/(lf_rows - del_lf_rows)
           +1 - height), -1,'YES','NO') CAN_REDUCE_LEVEL
  from index_stats;

i have a couple of indexes, after run the script, "CAN_REDUCE_LEVEL" shows yes,
after i did a "alter index xxx rebuild ...", the flag still shows yes, why the
"height" didn't reduce after "rebuild" ?

      and we said...
I think the operative word here is "CAN_REDUCE_LEVEL" -- it is CAN, not
"will".

This query is (as they say) based on 'the formula existent in most database
textbooks on how to calculate expected optimal height of the
tree based on the statistics'

EXPECTED optimal height
^^^^^^^^

It is a query to help you locate candidates, it does not assure you "you will
lower the height".

My opinion -- 99.9% of all reorgs, rebuilds, etc are a total and utter waste of
time and energy.  We spend way way way too much time losing sleep over this
non-event.

If you are going to spend time on this exercise -- make sure you come up with a
way to MEASURE what you've just done in some quanitative fashion you can report
to your mgmt (eg: these rebuilds I spend X hours a week doing save us from doing
X IO's every day, or let us do Y more transactions then otherwise possible, or
.....)   No one, but no one, seems to do that (keep metrics).  They just feel
"it must be better".  Who knows -- you may actually be DECREASING performance!!
(you'll never know until you measure)

Review &
      Followup

      Rating:
      4
Excellent  March 12, 2002
Reviewer:
      pawan  from USA
I think Tom has gone over this atleast 10 times and I totally agree with him. I
used to awake at 2 AM reorganizing Indexes and never kept any metrices so am not
sure whether it helped. My question though is if there exists with Tom a
script/method to gather statistics on  a regular basis ( before and after).

Thanks

          Followup:

things like statspack (IO's and waits), application logs (showing transactions
processed, response times), tkprof reports are what you need.

      Rating:
      5
LMT  March 13, 2002
Reviewer:  Dusan
      Valasek  from Slovakia
"How
to stop defragmenting and start living: the definitive world of fragmentation" -
there is the only answer: Start living in Locally Managed Tablespaces.

      Rating: 2
Index
      fragmented in LMT  March 05, 2004
Reviewer:
      KJ  from USA
Hi Tom

 This is a DW database - 9.2.0.4

analyze index NON_TRADE_ACCOUNT_BALANCE_PK validate structure;

select trunc(100*(del_lf_rows/lf_rows)) pct_deleted, pct_used from index_stats -
gave a 47%

It is a LMT - Automatic Allocation and Auto Segment Space management. Should we
check for fragmentation even with LMT's? I see few other indexes with the same
issue too.
Thanks

          Followup:

that is not fragmented.

that might be very very very very normal for that index.

rebuilding it might just result in increased redo
or
rebuilding it might result in reduced LIO's during the day

All this means is you apply inserts, updates and deletes against this index.

Try alter index iname coalesce;  that might be all you need (an online
operation).

      Rating: 2
Index
      fragmented in LMT  March 05, 2004
Reviewer:
      KJ  from USA
Hi Tom

 This is a DW database - 9.2.0.4

analyze index NON_TRADE_ACCOUNT_BALANCE_PK validate structure;

select trunc(100*(del_lf_rows/lf_rows)) pct_deleted, pct_used from index_stats -
gave a 47%

It is a LMT - Automatic Allocation and Auto Segment Space management. Should we
check for fragmentation even with LMT's? I see few other indexes with the same
issue too.
Thanks

March 05, 2004 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.

      Rating: 2
Do you think
      that this is a design bug?  March 02, 2004

Reviewer:  Oleksandr Alesinskyy  from Germany

Tom,

Do you agree that inavaliability of such information on a server is a obvious
design bug in Oracle and it has to fixed ASAP?

Yours sinkerely,

Oleksandr Alesinskyy

          Followup:

No, I believe if you have a stored procedure that catches an exception, it is
doing so to DEAL WITH IT.  Stored procedures should not capture "when others" --
the client will get the error, the client can do with it what it wants
(including sending it right back to another procedure to get logged if they
like).

You can search this site for dbms_trace to see another method.

but if you do see it as an obvious short coming, as a supported customer, you
have the ability to file enhancement requests.  You would need only to develop
the business case and submit it to support.

      Rating: 5
10g
      DBMS_UTILITY.FORMAT_ERROR_BACKTRACE  March 02, 2004

Reviewer:  Gary  from Sydney, Aus
Oracle 10g seems to have a new function in DBMS_UTILITY called
FORMAT_ERROR_BACKTRACE :

This procedure displays the call stack at the point where an exception was
raised, even if the procedure is called from an exception handler in an outer
scope. The output is similar to the output of the SQLERRM function, but not
subject to the same size limitation.

This MIGHT be what you are looking for (though I don't have a db to test this
on, but can only see the documentation).

http://download-west.oracle.com/docs/cd/B12037_01/appdev.101/b10802/d_util.htm#1003800

          Followup:

very nice, thanks for pointing that out -- it does that.

ops$tkyte@ORA10G> create or replace procedure p3
  2  as
  3  begin
  4          raise_application_error( -20001, 'you lose' );
  5  end;
  6  /

Procedure created.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> create or replace procedure p2
  2  as
  3  begin
  4          p3;
  5  end;
  6  /

Procedure created.

ops$tkyte@ORA10G> create or replace procedure p1
  2  as
  3  begin
  4          p2;
  5  end;
  6  /

Procedure created.

ops$tkyte@ORA10G> begin
  2          p1;
  3  exception
  4          when others then
  5                  dbms_output.put_line( '"' ||
dbms_utility.FORMAT_ERROR_BACKTRACE || '"' );
  6                  dbms_output.put_line( '"' || sqlcode || ' ' || sqlerrm ||
'"' );
  7                  raise;
  8  end;
  9  /
"ORA-06512: at "OPS$TKYTE.P3", line 4
ORA-06512: at "OPS$TKYTE.P2", line 4
ORA-06512: at "OPS$TKYTE.P1", line 4
ORA-06512: at line
2
"
"-20001 ORA-20001: you lose"
begin
*
ERROR at line 1:
ORA-20001: you lose
ORA-06512: at line 7

      Rating: 1
Design bug
      discussion  March 03, 2004
Reviewer:  Oleksandr
      Alesinsky  from Germany
>Followup:
>No, I believe if you have a stored procedure that catches an exception,
>it is doing so to DEAL WITH IT.
>Stored procedures should not capture "when others" --
>the client will get the error,
>the client can do with it what it wants
>(including sending it right back to another procedure to
>get logged if they like).

Do you really believe that such approach is very useful in batch processing -
when I shall not stop batch processing, e.g. hundred thousands of records to
deal with one erroneous record (or may be not errorneous but not fitting
existing program code). And what to do if this batch is performed completely
inside server by means of DBMS_JOB? There is no "client" at all.

More or less standard approach in batch processing is to process as much as
possible and log all exception for further investigation.

It seems that in 10g problem is resolved, but what to do in prior versions?

          Followup:

the source of the error must catch the error then.   you put exceptions where
you expect them (eg: at the lowest processing of the row -- you know the row
failed, you know why the row failed, you log that the row failed and continue).

I would not even really care that it was line 55 of procedure p3 called from
line 1023 of procedure foo called from line 23452 of x -- p3 would have been the
"process this record procedure" and p3 would have CAUGHT the expected error (row
failed processing for reason X) and p3 would have logged this fact.

Here - you are EXPECTING an error

Error you are expecting "row failed processing"

why did the row fail processing?  your code would have told you that (via RAISE
<named exception> or raise_application_error).

what if the error is unexpected (eg: out of space) -- do you want to continue ?
no, you cannot continue.

So, I'm back to "there are things your code expects, catches, deals with".
"there are things your code does not expect and would terminate for"

      Rating: 3
Why does oracle
      provide only 1000 custom error numbers   March 03, 2004

Reviewer:  A reader
Why does oracle provide only 1000 custom error numbers
from 20k to 21K ?

is it very small for mid to large system ?

      Rating: 1
Design bug
      discussion - 2  March 04, 2004
Reviewer:
      Oleksandr Alesinskyy  from Germany
>I would not even really care that it was line 55 of procedure p3 called from
>line 1023 of procedure foo called from line 23452 of x -- > p3 would have been
the ....

>Here - you are EXPECTING an error

>Error you are expecting "row failed processing"

>why did the row fail processing?  your code would have told you that (via RAISE

><named exception> or raise_application_error).

Yes, I  agree with you almost completely. Definitely, exception has be carched
in lowes-level routine. But ... This routine have several SQL operators in it.
And it is quite tedious and boring to write begin/exception/end block around
each of them. And, BTW, code becomes much less readable.  It is much easier to
have one exception handler and the procedure end. And if this handler would log
error and original line it will be more then enough.

          Followup:

guess we'll have to agree to disagree -- having a log record that "source code
line 532 blew up" is much less useful than "caught exception for sqlcode 1401
while validating column foobar, value of foobar was X"

I prefer code that does the work for me (for the guy monitoring the failure).
Can you imagine, you are the DBA, the developer gave you a procedure.  You know
now that "there was a 1401 on line 532 of procedure foo" -- now what?  what
field was wrong?  what caused the 1401?  how does the DBA set about figuring out
what the root cause was?

I as the DBA would much rather you as the programmer would have caught the
exception (you were expecting) and raised a meaningful error so I can fix it.

I do not find it to make code unreadable -- rather the opposite in fact.
Readable, maintainable and generally more usuable by others who do not read code
for a living.

      Rating: 3
Multiple
      statements in one block  March 04, 2004
Reviewer:
      andrew  from ca, usa
This is a common approach to identify which statement failed in a block if you
don't want individual handlers for each statement:

SQL> DECLARE
  2     v_stage   VARCHAR2 (20);
  3     v_dummy   VARCHAR2 (20);
  4  BEGIN
  5     v_stage    := 'stage 1 of 2';
  6     SELECT 'XYZ' INTO v_dummy FROM DUAL WHERE 1 = 1;
  7
  8     v_stage    := 'stage 2 of 2';
  9     SELECT 'XYZ' INTO v_dummy FROM DUAL WHERE 1 = 0;
 10  EXCEPTION
 11     WHEN NO_DATA_FOUND
 12     THEN
 13        raise_application_error (-20501, 'Whoops: no_data_found at ' ||
v_stage);
 14  END;
 15  /
DECLARE
*
ERROR at line 1:
ORA-20501: Whoops: no_data_found at stage 2 of 2
ORA-06512: at line 13

          Followup:

I'd go one step further if you don't mind :)

change lines 5/8 into:

dbms_application_info.set_client_info( 'stage ....' );

and line 13 into "|| userenv(client_info)"

and you now have not only accomplished the goal of being informative when you
have an error, but you let the DBA peek at v$session to see where you are (set
action and module too!!!!)  for long running things use set session longops,
even better.

      Rating: 2
Design bug
      discussion - 3  March 05, 2004
Reviewer:
      Oleksandr Alesinskyy  from Getmany
To some extent you are right. But...

1. I don't care about DBA - it is not his resposibilty to resolve the problems
with content.

2. Even if would surround each and any SQL statement with his own exception
block I may be usable to provide meaningful error. E.g. if I have "ORA-01401
Insrted value to large for column" I have no means to say which column and which
value (and this is true for many other error codes as well, but inconsistent
error info in Oracle is quite separate story, we have discussed it ~1 year ago).
So, in such cases one error handler that log error code and line that has caused
an error is as good as lot of error handlers around each statement.

3. Concerning workaround with "stage:=stage+1" - this is exactly what we do, but
as any thing that require additional human efforts it is somewhat error-prune.

4. Concerning readability - it is depend heavily on the taste but I hardly can
understant how code in which 3/4 of lines are just "overhead" can be more
readable then code that contain only essential lines. The very meaning of
exception handling machanisms is to separate error processing from normal code
flow and it is completely lost if we heavily intemix normal code with exception
handling as you suggest.

          Followup:

1) you are kidding right?  you have this job for the rest of your life?  and you
are immortal right -- cause if you get hit by the #14 bus tomorrow -- hmm, ok.

2) back to square one, that insert should have the exception block.

3) and makes the code more maintainable from each and every perspective.

and i vehemently disagree with "somewhat error prone".

4) overhead is in the eye of the beholder.  Easily 50% of my code (easily) or
more is code that is there to help us out when it "goes wrong".

Change the NO to YES in this URL for example.  Every piece of code -- every
piece of code -- is instruemented to the n'th degree.

Imagine the database without the "overhead" of V$ tables, sql_trace, etc.

If you EXPECT an exception -- catch it, deal with it, move on.

if you do not -- you cannot MOVE ON (by definition -- your program is in
"unknown state foobar" in the most literal of senses).  If you expect 'this
insert can fail, might fail and if it fails -- it is OK, we log that fact --
rollback to this savepoint, whatever and continue processing over here'.  If you
never expect that insert to fail (eg: insert into audit_trail) and it does --
bamm, you are dead. stop processing, something really bad has happened.

There are exactly two types of errors in the world

a) expected, catch em, deal with em, move on
b) unexpected, nothing you can do

It is exactly this "ignore errors" sort of stuff that gets garbage in and
garbage out.

Again, we are at the "we'll HAVE to agree to disagree" cause I just disagree.  I
would love it if 90% of code were overhead -- if that code helped me

a) diagnose issues
b) tune
c) audit

code is cheap.  fixing it ain't.  your code is not your code, it is code someone
else will be running some day.

Remember "GPF's", how useful.  "program x enountered exception foo at program
offset 0xAFBC4323"

great for a programmer (maybe, that is questionable).

Writing bullet proof code takes time, it takes instrumentation.  My goal has
always been to make it so that I can disappear after writing something and have
people that use it be able to figure out "what went wrong"....

      Rating: 4
Design bug
      discussion -4  March 06, 2004
Reviewer:
      Oleksandr Alesinskyy  from Germany
>1) you are kidding right?  you have this job for the rest of your life?  and
you
>are immortal right -- cause if you get hit by the #14 bus tomorrow -- hmm, ok.

I am not kidding - system is installed on many sites and DBA no nothing about it
internals (and don't suppose to know). Support team manage it.

>2) back to square one, that insert should have the exception block.

And how this exception block help to know which value for which column goes
wrong? For this error (and many others) such information is not provided by
Oracle. So this block is no more useful then single block for a whole procedure.

>3) and makes the code more maintainable from each and every perspective.

>and i vehemently disagree with "somewhat error prone".

If I have to add something each and every time then it is obvious then once I
(and I guess you as well) will forget. So it is error prune, If I have this
information provided automatically chances to not obtain it are much smaller.

>4) overhead is in the eye of the beholder.  Easily 50% of my code (easily) or
>more is code that is there to help us out when it "goes wrong".

I guess you slightly misunderstood me. I meant only that I prefer logically
separate normal processing bbranch and exception processing branch and not mix
and match them. BTW, have you ever heard about "aspect programmng"? I guess it
is one of the greatest ideas in software development. It is pity that it still
"in pampers" yet.

>Change the NO to YES in this URL for example.  Every piece of code -- every
>piece of code -- is instruemented to the n'th degree.

The only problem that high level of instrumentation can cause high level of bugs
in instrumentation itself. Especially if I have "mix and match" this
instrumentation into the each new development in 10**n places. It is very
ponderable reason to separate normal processing and error handling. In this case
it is much easier to implant existing instruments into new development.

>Imagine the database without the "overhead" of V$ tables, sql_trace, etc.

Quite easy. DBase-III :) Really I have absolutely nothing against
instrumentation of code - just opposite. Really I complain that inability to
access proper line number (as well as all other Oracle RDBMS incosistentcies in
providng information about exceptions) is exception handler prevent
instrumentation in easy and convinient way and force to make those
instrumentation with more efforts then really required.

>If you EXPECT an exception -- catch it, deal with it, move on.

>If you expect 'this
>insert can fail, might fail and if it fails -- it is OK, ...
>If you never expect that insert to fail (eg: insert into audit_trail) and it
does --
bamm, you are dead...

!00% agreed. The only thing that during processing on one input row I may do
dozen inserts and/or updates and at the moment of processing if ANY of them
fails I need to do the same thing - log when was fail, where was fail and which
input roew caused it. This I have to be able to do with single exception block -
save the fact that this exception block has no access to line number in which
fail occured.
To be honest I wish from Oracle with each exception much more - some exceprion
description structure with following information (in "machine-readable" form :)
:
1. Error code
2. Procedure name (packagage name and procedure name in case of packaged
procedure) from that cause this exception
3. SQL statemnt text (if exception in SQL)
4. Position in this text (in case of parse error)
5. Bind variables values (if applicable)
6. Name of database object (view, table and so on) if applicable
7. Column name (if applicable)
8. Name of violated constraint (if applicable)

I understand that to the moment it is only the figment of the fevered
imagination.

The most interesting thing that all (or almost all this information is available
from client and. to some extent, from the server side code as well, byt one
pieces are available from DBMS_SQL, other - from server-side Jave, some - from
plain PL/SQL. Later I guess has the least amount of those information and I
always wonder - why.

>I would love it if 90% of code were overhead -- if that code helped me

>a) diagnose issues
>b) tune
>c) audit

And I wouldbe absolutely glad if I will be able to do it not with 90% code but
with 20% (or 30%). And it will be definitely possible with proper support from
database cote.

I am not against instrumentation of code - I against the situation in which for
this purpose I have devote 80-90%
of code. In some situations it is unavoidable, but whaen IO need quite simple
thing like elementary logging and auditing it is untolerable.

>code is cheap.  fixing it ain't.  your code is not your >code, it is code
someone
?else will be running some day.

Exactly. So it have to be well structured and clean.

> My goal has
> always been to make it so that I can disappear after ?>writing something and
have
>people that use it be able to figure out "what went ?wrong"....

My as well (for last 24 years that I'm in professional software development). I
just want better support in achiving this goal from tools that I use.

March 05, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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.

      Rating: 1
Outer join
      query is confusing me.  March 05, 2004
Reviewer:
      Dave Blake  from UK
Firstly sorry about saying the above question was not useful... If you read it
again you will see why...

On with the question...

Tom, I have a problematic outer join query that I wondered if you could give
some pointers on.

Firstly I have a number of tables as shown below...

 ___________________
 | OFFICES          |
 |__________________|
 | ID               |
 | NAME             |
 |                  |
 |__________________|
          |
          |
 _________/\________
 | DEPARTMENTS      |
 |__________________|
 | ID               |
 | NAME             |
 | OFFICE_ID        |
 |__________________|
          |
          |
          |
 _________/\_________      __________________
 | EMPLOYEE_NUMBERS  |     | PAYBANDS        |
 |___________________|     |_________________|
 | PAYBAND_NAME      |     |                 |
 | DEPT_ID           |>----| NAME            |
 | VALUE             |     |                 |
 |___________________|     |_________________|

As you can see from above (hopefully the format will be OK when I submit this
page) there is a hierarchy between OFFICES -> DEPARTMENTS -> EMPLOYEE_NUMBERS
and PAYBANDS is a lookup table.

The query I am trying to produce should select the total employees for each
payband and produce a null value for each payband that does not have an
employee.

Below is a query that works at department level.

          SELECT pb.NAME, NVL(SUM (en.VALUE),0)
           FROM EMPLOYEE_NUMBERS en, PAYBANDS pb
           WHERE en.PAYBAND_NAME(+) = pb.NAME
            AND en.DEPT_ID(+)  = 'HR'
          GROUP BY (pb.NAME)

Now, the place that I get stuck is when I try to do the same at OFFICE level.
EG. Selecting data for all departments in the office. The closest I can get is
below.

          SELECT pb.NAME, NVL(SUM (en.VALUE),0)
           FROM EMPLOYEE_NUMBERS en, PAYBANDS pb,
                DEPARTMENTS d, OFFICES o
           WHERE en.PAYBAND_NAME(+) = pb.NAME
            AND en.DEPT_ID = d.ID
            AND d.OFFICE_ID = o.ID
            AND o.NAME = 'London Branch'
          GROUP BY (pb.NAME)

I understand that this does not work because of the following lines.
         WHERE en.PAYBAND_NAME(+) = pb.NAME
            AND en.DEPT_ID = d.ID

The second join cancels out the outer join on the first line. I wondered if you
could help me as I seem to have it a dead end with this query.

Thanks a lot

Dave Blake

          Followup:

what above question? I see no prior question/input from you?  anyway.....

starting with this data:

ops$tkyte@ORA9IR2> select * from offices;

        ID NAME
---------- ------------------------------
         1 Here
         2 There

ops$tkyte@ORA9IR2> select * from departments;

        ID NAME                            OFFICE_ID
---------- ------------------------------ ----------
        10 dept10                                  1
        20 dept20                                  1
        30 dept30                                  1
       100 dept100                                 2
       200 dept200                                 2
       300 dept300                                 2

6 rows selected.

ops$tkyte@ORA9IR2> select * from employee_numbers;

PAYBAND_NAME       DEPT_ID      VALUE
--------------- ---------- ----------
underpaid               10       1000
just right              20       1001
just right              30       1002
underpaid              100       2000
just right             200       2001

ops$tkyte@ORA9IR2> select * from paybands;

NAME
------------------------------
underpaid
just right
overpaid

we can see there are a couple of ways to write your query.  one is:

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select pb.name, sum(en.value)
  2    from paybands pb, employee_numbers en
  3   where pb.name = en.payband_name(+)
  4   group by pb.name
  5  /

NAME                           SUM(EN.VALUE)
------------------------------ -------------
just right                              4004
overpaid
underpaid                               3000

and another is:

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select name,
  2        (select sum(value)
  3           from employee_numbers en
  4          where en.payband_name = pb.name ) val
  5    from paybands pb
  6  /

NAME                                  VAL
------------------------------ ----------
underpaid                            3000
just right                           4004
overpaid

Now, to get your report at the "office" level.  what we need is

a) the cartesian product between paybands and offices to get ever office/payband
combo

b) the join between employee numbers and departments (so we can roll emps upto
the office level)

if we had those two tables -- it would be straightforward -- just outer join A
to B. We don't but inline views let us pretend we do:

ops$tkyte@ORA9IR2> select pb_name, o_name, sum(value)
  2    from (
  3  select pb.name pb_name, o.name o_name, o.id o_id
  4    from paybands pb, offices o
  5         ) a,
  6         (
  7  select value, office_id, payband_name
  8    from employee_numbers, departments
  9   where dept_id = id
 10         ) b
 11   where o_id = office_id(+)
 12     and pb_name = payband_name(+)
 13   group by pb_name, o_name
 14  /

PB_NAME         O_NAME          SUM(VALUE)
--------------- --------------- ----------
overpaid        Here
overpaid        There
underpaid       Here                  1000
underpaid       There                 2000
just right      Here                  2003
just right      There                 2001

6 rows selected.

we can use the same scalar subquery trick as well to remove the outer join and
do it more "row by row"

ops$tkyte@ORA9IR2> select pb.name pb_name, o.name o_name, o.id o_id,
  2        (select sum(value)
  3           from employee_numbers en, departments d
  4          where en.payband_name = pb.name
  5            and en.dept_id = d.id
  6            and d.office_id = o.id) val
  7    from paybands pb, offices o
  8  /

PB_NAME         O_NAME                O_ID        VAL
--------------- --------------- ---------- ----------
underpaid       Here                     1       1000
just right      Here                     1       2003
overpaid        Here                     1
underpaid       There                    2       2000
just right      There                    2       2001
overpaid        There                    2

6 rows selected.

which one works best depends on the application.  You might well find that the
scalar subquery with a FIRST_ROWS hint is best for "get me the first rows fast"
where as the outer join is best for "get me all of the rows fast"

      Rating: 5
Thanks a
      lot.  March 05, 2004
Reviewer:
      A reader
Thanks a lot for the above answer Tom.

Just one question - you say about using the FIRST_ROWS hint. Am I correct in
thinking that Oracle is moving towards just using cost based analysis rather
than rule based? If this is correct then surely it is better to start trying to
avoid using hints now.

If I am wrongly informed then sorry for wasting your time!

Dave

          Followup:

in 10g, the RBO is officially "not supported", only the CBO is.

There are good hints, hints that give the optimizer INFORMATION.  first_rows
tells the optimizer useful stuff "hey, i am interested in initial response time
here, I might be asking for 10000 rows but only fetch the first could
dozen/hundred"

all_rows tells it something useful.
append tells it something useful
dynamic_sampling
cardinality
parallel
driving_site
choose
cursor_sharing_exact
rewrite

they all give the optimizer more information -- they are "good".  the others
that tell it how to do something, they are the ones I don't like to see used "as
a matter of fact"

March 05, 2004 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://download-east.oracle.com/docs/cd/A81042_01/DOC/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://download-west.oracle.com/docs/cd/A87860_01/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://download-west.oracle.com/docs/cd/A87860_01/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://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76962/ch4.htm#74665

      Rating: 5
Stanby DB: SE
      to EE  March 04, 2004
Reviewer:  Olaf
       from Germany
Tom,

is it technical possible to implement D/R solution for our SE database using EE
as a standby. It was in previous versions of Oracle that primary and standby
must have the same OS, same version of Oracle and even the same patch level. Is
it still so?
We have Oracle 9.2 on the both sides (primary and standby)
From licensing view our primary server is SE and standby is EE. How can we use
the features of EE to implement standby?

Olaf

          Followup:

managed standby is available as a feature of EE only - not SE.

      Rating: 3
EE as standby
      database/SE as Primary  March 05, 2004
Reviewer:
      Olaf  from Germany
Tom,

my question was whether it is possible to have SE database as primary and EE
database as standby. I am going to manuell transfer the archive log file (per
batch OS copy job).
But is it possible to apply this archived logs from SE to EE edition. Are there
any tricks here. The both databases are 9.2 (Eearly Oracle meant, that primary
and standby must be of the same version and even have same patch level...)
Is is still so?

Thanks for all you help you gave me in last 5 years.

Olaf.

          Followup:

should it work?  sure, probably.  "no reason it should not"

is it tested internally?  no.
is it a supported configuration?  no.
should you do it? not a chance.

since you have to "DIY" (do it yourself) given that the primary is SE, you would
be best off STICKING with SE on the other end.

      Rating: 4
Question on
      standbys   March 05, 2004
Reviewer:  Chris
      Slatterry  from Dublin, Ireland

Ok, I'll bite.

you mention MANAGED standbys. What's an UNmanaged standby and can it be done
with SE ?

Can I set up a standby,shut it down and copy all the log files across to it and
open and resynch manually once a day ... ?

Also, and prob. a silly question : EE  is required for Managed Standby. ok.

Can the standby be SE ? [ Much cheaper cost option for something only needed for
trouble that we can only use Read Only if at all except in case of trouble ]

          Followup:

unmanaged is "DIY"

o you backup prod
o you restore to standby
o you transport the logs as they are archived
o you apply the logs to standby (standby is in constant recovery mode)

http://download-east.oracle.com/docs/cd/A87860_01/doc/server.817/a76995/standbyc.htm#29722

March 05, 2004 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

      Rating: 4
Dynamic
      Transpose  March 04, 2004
Reviewer:  robert
       from ct
8.1.7

greetings, tom,

I am trying to use your pivot code in the book (page579.sql)

I neeed to transpose the result of a big view that looks somewhat like this:

http://32.97.40.160/graphic/result03042004.gif
here is the test data:

http://32.97.40.160/graphic/test_webreportdata.sql
so the transpose should look like:

                mm_yy_1.........mm_yy_n
sitehits
homepagehits
usersessions

But i can't make it work....I don't really got any columns to ANCHOR on , do I ?

my code looks like this:
--------------------------------------------
DECLARE
  l_arr_mmyy           sqlutil.ARRAY;
  l_arr_anchor_columns sqlutil.ARRAY ;
  l_refcur sqlutil.refcursor ;
  l_sql VARCHAR2(30000) :=
   'SELECT mm_yy, sitehits, homepagehits, usersessions from  test_webreportdata'
;

CURSOR cur_mmyy IS
 SELECT mm_yy FROM test_webreportdata ;

 CURSOR cur_anchor_columns IS
 SELECT column_name
   FROM user_tab_columns
  WHERE table_name = 'TEST_WEBREPORTDATA'
    AND data_type = 'NUMBER' ORDER BY column_id;

BEGIN
    OPEN cur_mmyy;
    FETCH cur_mmyy BULK COLLECT INTO l_arr_mmyy ;
    CLOSE cur_mmyy ;

    OPEN cur_anchor_columns;
    FETCH cur_anchor_columns BULK COLLECT INTO  l_arr_anchor_columns ;
    CLOSE cur_anchor_columns ;

    sqlutil.pivot(p_max_cols => l_arr_mmyy.count,
                  p_query  => l_sql,
                  p_anchor => l_arr_anchor_columns,
                  p_pivot  => l_arr_mmyy,
                  p_cursor => l_refcur);

Can you give it a shot ? thanks

          Followup:

well, thats a double pivot -- take the colunms, make them rows -- then take the
rows and make them columns.

Now, if you wanted to use my pivot routine, the call would look like:

ops$tkyte@ORA9IR2> begin
  2      my_pkg.pivot
  3      ( p_max_cols_query => 'select count(*) from test_webreportdata',
  4        p_query => 'select what, mm_yy, val,
  5                           row_number() over ( partition by what
  6                                               order by mm_yy ) rn
  7                      from (select what,
  8                                   decode( what, ''SITEHITS'', sitehits,
  9                                      ''HOMEPAGEHITS'', homepagehits,
 10                                       ''USERSESSIONS'', USERSESSIONS ) val,
 11                                   mm_yy
 12                              from test_webreportdata,
 13                               (select ''SITEHITS'' what from dual union all
 14                                select ''HOMEPAGEHITS'' from dual union all
 15                                select ''USERSESSIONS'' from dual) )',
 16        p_anchor => my_pkg.array( 'WHAT' ),
 17        p_pivot  => my_pkg.array( 'MM_YY', 'VAL' ),
 18        p_cursor => :x );
 19  end;
 20  /

That pivots the columns into rows (join to dual) and then pivots MM_YY and VAL
into columns.  Now, you probably want VAL to have column headings of MM_YY --
that's not what my pivot does, you can do that however, you'll have to "diy" a
bit.  It would look like this:

ops$tkyte@ORA9IR2> select what,
  2         max( decode( mm_yy, '01-00', val ) ) "01-00",
  3         max( decode( mm_yy, '01-01', val ) ) "01-01",
  4         max( decode( mm_yy, '01-02', val ) ) "01-02",
  5             /* .... */
  6             max( decode( mm_yy, '12-03', val ) ) "12-03"
  7    from (
  8  select what, mm_yy, val
  9    from (select what,
 10                 decode( what, 'SITEHITS', sitehits,
 11                               'HOMEPAGEHITS', homepagehits,
 12                               'USERSESSIONS', USERSESSIONS ) val,
 13                 mm_yy
 14            from test_webreportdata,
 15                (select 'SITEHITS' what from dual union all
 16                 select 'HOMEPAGEHITS' from dual union all
 17                 select 'USERSESSIONS' from dual) )
 18                  )
 19    group by what
 20  /

WHAT                                01-00      01-01      01-02      12-03
------------------------------ ---------- ---------- ---------- ----------
HOMEPAGEHITS                            0          0          0          0
SITEHITS                                0         99          3         68
USERSESSIONS                            0         49          2         11

3 rows selected.

The bit in bold is what you have to DIY.  You would need to query up MM_YY
values and dynamically build that part of the select list (the rest of the query
is 'static').

      Rating: 5
order the
      columns correctly  March 05, 2004
Reviewer:
      robert  from CT
Awesome ! Thanks a million, tom. Works like a charm !
I coded to dynamic generate the p_query parameter for my_pkg.pivot.

http://32.97.40.160/graphic/result03052004.gif
Question:
Now I added "to_date" to "ORDER BY" in the analytic...this
will fully ensure that MMYY columns come out in month-year ASC order, correct ?

 my_pkg.pivot
  ( p_max_cols => l_arr_mm_yy.COUNT,
    p_query => 'select what, mm_yy, val,' ||
               'row_number() over ( partition by what' ||
               'order by to_date(mm_yy, ''mmyy'') ) rn' ||
....----

As for the MMYY column name, I don't think I am gonna mess with 'DIY', I can use
the values in array l_arr_mm_yy
that contains the values as long as I am sure the result columns are lined up
correctly.
Thanks

          Followup:

the order by to_date should probably be RR, not YY  (it takes 4 digits to
represent a year!!!)

with YY 99 becomes 2099
with RR 99 becomes 1999 (well, for the next 45 years it does)

March 05, 2004 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

      Rating: 5
Problem
      displaying blank spaces in web browser  March 05, 2004

Reviewer:  David Schwartz  from NJ, USA
Thanks Tom.
Ran into a problem displaying blank spaces in web browser. i.e., If I pad the
name with spaces (to indicate level) then the spaces are stripped out of the
browser display.
Normally, you need to use &nbsp; to display spaces.

How can I code for this in the query?

          Followup:

I use <pre> myself.

the best font ever is Courier :)

put, you can replace( string, ' ', '&nbsp;' )

March 05, 2004 Andrew -- Thanks for the question regarding
      "Invoke SQL loader from stored procedure?", version 9.2

      You Asked
Problem: I am trying to load data from deliminated data file to tables. To
satisfy biz requirement I need to:

1. Create shadow table
2. Invoke SQL Loader
3. If too many bad record, stop and email admin
4. Otherwise after successful loading rename shadow table to real table - done.

I can use shell script to execute a stored proc to create shadow tables and then
invoke SQL Loader and then do the rest. But is there better way (e.g. using a
single java stored procedure or PL/SQL) to do this in one shot? The obstacle as
I can see is to invoke SQL Loader from within the procedure and report back the
result. Is it possible at all?

      and we said...
Use external tables!!!

search this site for "external table"

if you need to load arbitrary files, you can just use execute immediate in your
procedure to create the external table, execute immediate an "insert into select
* from", and then execute immediate 'drop table external_table'

it can have bad limits and everything, just like sqlldr.
Review &
      Followup

      Rating: 5
Great new
      perspective!  March 05, 2004
Reviewer:  Andrew
      Chen  from Irving, TX
It is a great suggestion and I will try it out. Hope the performance is good (5
million records daily). I will follow up after test. Thanks Tom!

          Followup:

you'll find external tables to be at least as fast as sqlldr

insert /*+ append */ = direct path load

insert = convention path load with the ability to do complex SQL on the data as
it is loaded.

March 05, 2004 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 :)

      Rating: 5
How to set
      level 12 sql trace at instance level?  February 20, 2004

Reviewer:  cosmos  from CA, USA
I am reading your Effective Oracle by Design and Cary's book on performance
tuning. Both are great! Performance tuning through extended SQL Trace level 12
is unbelievable. Great job by Oracle technical folks!

Is there a way to set level 12 at an instance level? Your book as well as Cary's
talks about how to set level 12 on a session level, not at instance level. I
understand why session level data collection is important, but I also need to
analyze SQL trace for the whole instance. Can you please help? Thanks.

          Followup:

You do not want to do that!

It would kill you.  Really.  You would only trace at the session level, trust me
-- it would be killer.

if you really want to, you can either set the event in the init.ora or

ALTER SYSTEM SET EVENTS '10046 TRACE NAME CONTEXT FOREVER, LEVEL 12';
ALTER SYSTEM  SET EVENTS '10046 TRACE NAME CONTEXT OFF';

which turns on tracing for all NEW sessions. Existing sessions and
background processes are unaltered. dbms_system can be used to turn it on in
existing ones.

      Rating:
      5
instrumenting  February 21, 2004
Reviewer:
      Austin  from Pa
Hi Tom

while reviewing this site i cam across the term "INSTRUMENTING" code many a
times.I dont have experience with database programming much..But what i
understood from insyrumenting code is something like we do in shell scripts.

say
some code
echo $myvar           -->Instrumenting nyvar
some other code
echo $myothervar      -->Instrumenting myothervar

in production i would just comment out that code..

something similar to dbms_output in ur pl/sql

is my understanding right ?If not could you please elaborate on what you mean by
instrumenting.

I also had a look at your other example wherein you ask to type Y instead on N
in your browser I did not understand this.

Please bear with my lack of comprehension english is not my first langauge.

regards
Austin

          Followup:

Here is a short excerpt from my book "Effective Oracle by Design" on this topic
(with the disucssion of the tools available to instrument in the book as it gets
rather long)

Instrument the system

Many of you might be looking at the title of this section and asking yourself
"what the heck is that, what does he mean by 'Instrument the system'". It is
really quite simple - what I mean is that you will liberally and in great volume
spread debug code in your code. To instrument the code means to embed in your
applications, in your systems, the ability to generate copious amounts of trace
information at will.

This will provide you the ability to do two things:

* Debug the code without a debugger: just like Oracle development does. When you
run into a problem with the database - support has you enable "trace events"
that generate TRC files containing tons of diagnostic information. This enables
support and development to debug your problems - without ever touching your
system.  Imagine for a moment Oracle not having SQL_TRACE or the EVENT system
used by support.  Imagine what impact that would have.

* Identify where bad performance is in your system: If you are building N-Tier
applications with many moving pieces, this is extremely vital. When the support
desk calls you and says "it is going slow", how will you find out where your
system is slowing down without the ability to have the code itself tell you what
is taking a long time?

There are those that will say "but this will add overhead" or "this is something
I really don't need to do". To that I say you are wrong on both counts. Overhead
is something you can remove without losing much benefit. Instrumentation is
something that if you do not have or remove - takes away considerable
functionality. As for the "this is something I don't need to do", that would
only be true if your systems never break, never needs diagnostics, never suffers
from performance issues. If that is true - then sure, you don't need to do that
(and - send me your email address, I have a job offer for you).

As anecdotal evidence of the need to instrument your code, we'll take a look at
a couple of real world examples. First and foremost is SQL_TRACE in the
database. Hopefully everyone is aware of this powerful tool (if not - see the
Oracle Performance guide). Here is an instrumentation we should all be using to
tune our applications, to identify performance bottlenecks. Every piece of code
that executes SQL in the database is already on its way to being partially
instrumented.

Another example is asktom.oracle.com. Just go there and click on any article
from the home page. You'll see a URL similar to:

http://asktom.oracle.com/pls/ask/f?p=...::NO::

If you simply type over that URL and replace the word NO with YES - you'll see
the same page but with lots and lots of state/timing information dumped into it.

NOTE: the preceding URL is just a "for example one".  It is not a functional
URL, you may goto the asktom website to get a "real" one to test this on

All of the applications I work on - every single one - has this feature in some
shape or another (this tracing ability). If someone reports a slow page - I can
easily and quickly determine what exact region of the page is causing the issue.
If a certain query that paints part of the page is taking an inordinate amount
of time, it will be obvious where that is due to this instrumentation. In
addition to this instrumentation inside of the page, we audit each and every
page access as well. That means - for every single page accessed in my
application -every page, every time, we insert an audit trail record containing
whatever information we desire. On asktom, I nominally capture the IP Adress,
Browser, Time of request, duration of the request, what page in what application
was requested among other bits of data. This allows me to rapidly respond to
questions like "how many people use asktom, how many pages (versus images and
such) do you serve up everyday". This allowed me for example to respond
immediately to this question that was posted:

From last 24 hours, I have observed that accessing your site, clicking on 'ask
question', 'read question','Review Question' etc., all gone slow (taking around
2-3 minutes instead of couple of seconds before). Are others facing the same
problem?

I just went to my statistics page, generated right from my audit trail as part
of my system (clicked a button) and
viewed a report.

    Now, that instantly told me it was not the database and in fact, it probably
wasn't a widespread problem at all (that is, this person was having an isolated
issue). The reason I could tell it wasn't the database - the Avg Elap/Page. It
fell right into the norm, what I was expecting. The database was generating
pages as per normal. The reason I could tell it wasn't a widespread problem? The
number of page views fell well within my normal range (I have another report I
look at from time to time that tells me page views per day/week and so on).
Basically I know that during the week I get between 25,000 and 35,000 page views
a day on average from 2,000 to 3,000 different IP addresses. The last 24 hours
on asktom.oracle.com fell well within the norm. If pages had been taking 2 to 3
minutes to generate instead of well under a second, well, people would have
stopped clicking on them and the hit counts would have been way down.

So, within a couple of seconds, I was able to rule out the database, the
webserver and my front end network at least. As it turned out - I asked people
coming to asktom.oracle.com to tell me "how is the performance" and without
exception it was "normal" - further confirming that this was an isolated
incident, somewhat outside of my control. Fortunately, the poster of this
question came back to say "it was resolved". As they were somewhere deep inside
of Australia and I'm on the East Coast of the US - I can only speculate that
there was some nasty network issue between us and them that got corrected.

The last example I'll use is a piece of code I wrote a long time ago - when this
"internet" thing was just catching on. Many of you are familiar with mod_plsql,
an Apache module Oracle provides that allows URL's to run PL/SQL stored
procedures - this is the way asktom.oracle.com works for example. Well, the
precursor to mod_plsql was a thing called OWA - the Oracle Web Agent. This was
originally a CGI-BIN program that shipped with the Oracle Internet Server
version 1.0 (OIS) which became the Oracle Web Server version 2.0 and 2.1 (OWS).
The OWA "cartridge" as it was called was fairly simple in its functionality - it
ran stored procedures and that was about it. I re-implemented the same concept
in 1996 but added lots of stuff like support for file upload, file download,
flexible argument passing, database authentication, an <ORACLE> tag (like PSP's
PLSQL Server Pages), compression, a web "timed statistics" and so on - many
features you now see in the mod_plsql module. The success of my piece of
software prompted them to include these features into the supported code. I
called this piece of software OWAREPL and put it up on the internet. It was
downloaded thousands of times and I still hear about people using it today. It
is a piece of C code, about 3,500 lines of it, using OCI (the Oracle Call
Interface) to interact with the database. Now, since I do not write 100% bug
free code and people used this software in manners I never anticipated (as
people are known to do), I needed to be able to remotely debug this piece of
code. Fortunately, I had anticipated this happening so I had the code fully
instrumented and able to dump huge amounts of diagnostic information. Time after
time I was able to remotely diagnose and either fix or suggest a workaround
issues with this piece of software - all via email. I never needed to physically
log into another machine outside of Oracle in order to diagnose this. Not only
that but since this piece of code ran as a CGI-BIN or as a dynamically loaded
cartridge under OWS/OAS - using a debugger was out of the question. Without this
trace information - I never would have been able to support this piece of
software, I never would have been able to diagnose any of these issues. All I
had to do when someone hit a problem was email back:

Please set debugModules = all in the sv<webservername>.app configuration file.
That will generate a trace file after you restart the web server.  Run your
application, reproduce the issue and then email me the resulting trace file.

Without exception - I was able to see what went wrong, correct it one way or the
other, and get them running. If I didn't have this ability - my only answer
would have been "gee, I don't know, sorry".

Hopefully - you now realize how beneficial this technique is, the question
remains now -- how can I instrument my code? There are many ways and we'll
explore them from the database on out to the typical application tiers (client
server and N-Tier applications).

      Rating: 4
SET
      EVENTS  March 05, 2004
Reviewer:  James
      Blanding  from Kingston, RI USA
Is the syntax for the "alter system set events" command documented somewhere?  I
get the feeling it's one of those unsupported voodoo commands.  It would be cool
to know what else it can do, though.  I've seen other words besides 'context' as
well, like 'heapdump' and 'errorstack'...what's the difference?  I can imagine
what heapdump might look like...is errorstack similar to what you see when a
PL/SQL program errors out with an unhandled exception?  Also, are the various
error codes Oracle uses are synonymous with the event numbers?  I know you like
to say a little knowledge can be a dangerous thing...

          Followup:

the 10046 trace event (and 10053) are two very "safe".

In general, yes, events are voodoo.  These two however are "good magic".

They are the only two I talk about.

      Rating: 3
SET
      EVENTS  March 05, 2004
Reviewer:  James
      Blanding  from Kingston, RI USA
OK, fair enough.  I did find a few crumbs of information on metalink.

March 05, 2004 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

      Rating:
      5
INSTR  March 04, 2004
Reviewer:  mo

Tom:
Two questions on 8i database:
1.  If I want to strip a string from words ending with semicolon, how wo
uld you do that:
E.G:
If col1='Damaged; Missing; Other; Sky is blue'
I want to display:
'Sky is blue'.
2.  I want to update a word in a string:
e.g.:
col1='This packet is for you'
I want to change the word packet to box so it will be:
col1='This box is for you'
Do I have to update the whole string or I can do search and replace.

          Followup:

1) i suppose    'Hello this; is; in; the; middle; World' should be 'Hello
World'?

if so, it is is more complex than "show me the text after the last ; in the
string", we'll need to parse it to accomplish this in 9i (or load a regular
expression package into the db).

in 10g, regexp can do this:

ops$tkyte@ORA10G> select x,
trim( regexp_replace( ' ' || x, ' [A-Za-z0-9]*;', '' ) ) y from t;

X                                                  Y
-------------------------------------------------- --------------------
Damaged; Missing; Other; Sky is blue               Sky is blue
Hello this; is; in; the; middle; World             Hello World

2) replace.  trim( replace ( ' ' || x || ' ', ' packet ', ' box ' ) )

      Rating:
      5
INSR  March 04, 2004
Reviewer:  mo

Tom:

For 1 above, and using 8i database  can't I use owa_util regular expression
procedures or a combination of INSTR and REPLACE to accomplish it?

          Followup:

ops$tkyte@ORA9IR2> create or replace function f( p_str in varchar2, p_src in
varchar2, p_rep varchar2 ) return varchar2
  2  is
  3          l_temp long := p_str;
  4  begin
  5          owa_pattern.change( l_temp, p_src, p_rep, 'g' );
  6          return l_temp;
  7  end;
  8  /

Function created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select x, trim( f( ' ' ||x, ' [A-Za-z0-9]*;', '' ) ) y from
t;

X                                        Y
---------------------------------------- --------------------
Damaged; Missing; Other; Sky is blue     Sky is blue
Hello this; is; in; the; middle; World   Hello World

      Rating:
      5
OWA_PATTERN  March 05, 2004
Reviewer:
      mo
Tom:

Thanks a lot, It works very nicely.

If I have one sentence with two words where the first one is "Missing Reels;"
would I be able to do it in one SQL statement (take out the words with ";" and
the word "Missing").

What I did is I ran the "F" function twice. I took the resulting string and ran
it through the function again by looking for the word "Missing".

Thank you,

          Followup:

why would the word "missing" go way?

you asked to remove words ending with ';'

missing does not?

      Rating:
      5
kumar  March 15, 2004
Reviewer:
      kumar
great ..works perfectly.. this is exactly what we are looking for.

March 05, 2004 Raj -- Thanks for the question regarding "Package
      Error", version 8.1.5

      You Asked
SCOTT.UTILITIES_PKG.FILE_OPEN@DBLINK_NYC( p_UtlFileDir, --UTLpath
                                          p_Logfile,    --logfile name
                                          'w',          --Mode
                                          2000,         --line size
                                p_logfilehandle,   --file handle (OUT)
                             v_utility_error );  --Error (OUT),if any

Hi Tom,

I am getting the following error below when I try to execute the package
above.It works fine if I execute it in it's instance but gives an error when I
execute it using DBLink. I'm calling it exactly the way it's defined.Could you
please help me find the what the error is ? I checked every possible thing the
manual says like I checked spelling,datatypes etc.
I checked the privileges also and it has the execute privileges.
Please help!!!

Error: (1):PLS-00306: Wrong number or types of arguments in call
                      to 'FILE_OPEN'.
       (2):Statement ignored

Thanks in advance.

--------------------------------------------------------------------
Hi Tom,
Here's the code you've asked me to provide for file_open-

PROCEDURE File_Open( p_UtlFileDir     IN   VARCHAR2,
                     p_FileName       IN   VARCHAR2,
                     p_Mode           IN   VARCHAR2,
                     p_Max_LineSize   IN   BINARY_INTEGER,
                     p_FileHandle     OUT  UTL_FILE.FILE_TYPE,
                     p_UtilityError   OUT  VARCHAR2) IS
BEGIN
  p_UtilityError := NULL;
  p_FileHandle := UTL_FILE.FOPEN (p_UtlFileDir, p_FileName, p_Mode,
p_Max_Linesize);
EXCEPTION
WHEN UTL_FILE.INVALID_PATH THEN
  p_UtilityError := 'invalid_path';
WHEN UTL_FILE.INVALID_MODE THEN
  p_UtilityError := 'invalid_mode';
WHEN UTL_FILE.INVALID_FILEHANDLE THEN
  p_UtilityError := 'invalid_filehandle';
WHEN UTL_FILE.INVALID_OPERATION THEN
  p_UtilityError := 'invalid_operation';
WHEN UTL_FILE.READ_ERROR THEN
  p_UtilityError := 'read_error';
WHEN UTL_FILE.WRITE_ERROR THEN
  p_UtilityError := 'write_error';
WHEN UTL_FILE.INTERNAL_ERROR THEN
  p_UtilityError := 'internal_error';
WHEN OTHERS THEN
  p_UtilityError := SQLCODE||'-'||SQLERRM;
END File_Open;

FYI, This procedure is in Oracle version 8.1.7 but I'm calling it from another
Oracle instance version 8.1.5.

Thanks in advance.
Raj-

      and we said...
Ok, the variable:

p_logfilehandle

is defined using the UTL_FILE package in the 8.1.5 instance.  It is calling a
package that requires a UTL_FILE record from the 8.1.7 instance -- that is the
cause of the mismatch.

Consider (p is a procedure on ora8i.world that has an out parameter of
utl_file.file_type)

tkyte@TKYTE816> declare
  2          x       utl_file.file_type;
  3  begin
  4          p@ora8i.world( x );
  5  end;
  6  /
declare
*
ERROR at line 1:
ORA-06550: line 4, column 2:
PLS-00306: wrong number or types of arguments in call to 'P'
ORA-06550: line 4, column 2:
PL/SQL: Statement ignored

tkyte@TKYTE816> create synonym utl_file_remote for utl_file@ora8i.world;
Synonym created.

tkyte@TKYTE816> declare
  2          x       utl_file_remote.file_type;
  3  begin
  4          p@ora8i.world( x );
  5  end;
  6  /

PL/SQL procedure successfully completed.

Now you'll get the file type back.  You can only use this with utl_file_remote.
the local UTL_FILE will not operate on that file.
Review &
      Followup

      Rating: 5
Great
      Solution  May 15, 2001
Reviewer:  Raj

It really works now. Thank you very much Tom.

      Rating: 4
wrong number or
      types of arguments in call to procedure  January 17, 2004

Reviewer:  e musgraves  from Irvine, CA
I'm experiencing a similar problem and also checked all the obvious for a root
cause.  I'm not calling anything remote, it's all in the same instance.  My own
pkg has 3 procs.  proc 1 calls proc 2 which calls proc 3.  I get the "wrong
number..." error at compile time on the call to proc 2 but after I added the
code for proc 3.  I hope this makes sense to you.  Proc 1 and 2 work fine until
I add proc 3.  Proc 3 compiles successfully with 3 IN parameters that were
originally passed to proc 1, then 2, now 3.  But when I add an OUT parameter in
proc 2 for proc 3 to use as an IN parameter, then I get the error above at
compile time.  Please help.

          Followup:

sort of need an "example"

i find (999 out of 1000 times) that in the generation of a

a) simple
b) small
c) 100% complete
d) yet concise (see b)

test case -- I find my own error :)

      Rating: 5
Too Good
      Tom.....  January 18, 2004
Reviewer:  Asif
      Momen
Superb response.

      Rating: 5
ORA-06550
      PLS-00306  March 05, 2004
Reviewer:  Wor
       from USA
Hi Tom,
We have a very random and occasional problem on our production server.
We have Oracle 8.1.7.

Sometime we get the following error, only when running stored procedures
returning ref-cursors, When the error occurs it consistently fails for all SP's
returning ref-cursors until we bounce the server.
The wierd part is during this time SQL's work fine.

 ORA-06550: line 1, column 7:
PLS-00306: wrong number or types of arguments in call to 'SP_PROC_NAME1'
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

 (Source: Microsoft OLE DB Provider for Oracle)
 (SQL State: (null))
 (NativeError: 6550)
         No Help file available

I am unable to find any direction to resolve this issue.
Please help
Wor

          Followup:

Sorry, I've never used the MS ole db provider -- you'll need to contact MS (or
give the Oracle OLE DB provider).

You can contact Oracle support as well to see if there are any known issues - I
just don't "do windows" so have never experienced this.

March 06, 2004 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.

      Rating: 4
Bulk Binding
      arrays  March 06, 2004
Reviewer:  Kapil
       from Singapore
Hi Tom,

You said that in 8i it would not be possible to bulk collect in arrays of
records. I have a similar question for bulk binds. Can we bulk bind arrays of
records or arrays of some other data type using the forall statement in 8i. In
case it is not possible then can we bulk bind arrays of records/"some data type"
using subscripting to bind the element seperately?

Kapil

          Followup:

You can bulk bind arrays of SCALARS with forall

either a record of arrays
or individual arrays.

March 06, 2004 Anand -- Thanks for the question regarding "About
      tables details", version 9i RAC

      You Asked
Hi tom,
I just want to know how can i find out about any particular table that when it
was last accessed, modified and data inserted into it...

Anand

      and we said...
You would have to enable AUDITING on a table in order to capture that level of
detail.

See

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96521/audit.htm#1108
for details.
March 06, 2004
      Mladen -- Thanks for the question regarding
      "Zero devide problem", version 8.1.7

      You Asked
Hi Tom,

My problem is handling of zero_divide exception.
I'm using INSERT INTO T1 (SELECT... FROM T2) syntax. Tables T1 and T2 are known
only at the runtime.
SELECT contains many calculations as per example:
SELECT C1/C2,C3+(C4/C5)... FROM T2

I would like to identify rowid of the row raising zero_devide exception.

Thank you very much for everything that you done on this site. It is very
helpful for me.

Best regards.
Mladen

      and we said...
select decode( c2, 0, to_number(null), c1/c2 )

will accomplish your goal safely.  Use decode to avoid evaluating a zero divide
condition.

Note: the to_number is not "extra", it is necessary to avoid implicit
conversions to and from strings.

Note2: I'm assumging when c2 is zero, the returned value should be "unknown", if
it should be zero or some other number, it is OK to return that as well:

 decode( c2, 0, 0, c1/c2 )

March 06, 2004 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.

      Rating: 2
metalink
      registration  March 06, 2004
Reviewer:  Hash
       from pakistan
sorry to ask this foolish question but i dont have a metalink registration.it
asks for some identification which is dont understand could u plz help me

          Followup:

metalink is the support site, support has to have been purchased.  See your
DBA/whomever would have this information (assuming your company did purchase
that service)

March 06, 2004 raul -- Thanks for the question regarding
      "update", version 9i

      You Asked
I have  two tables
one
say table  carton_information
it has
delivery_id, pallet_lpn_id, inventory_item_id,carton_quantity,f_date
1101         228            4                 10
                                              15
                                              15
                                              15
                                              15
                                              15
                                              15

i.e the total quantity count for inventory_item_id 4   is   100 for
pallet_lpn_id = 228 and delivery_id = 1101

now there is another table lot_table which gives
inventory_item_id, lot_number import_date  quantity
4                  abc        1/5/04        95
4                  xyz        1/15/04      5

i.e. the item 4 i am getting from two lots one with 95 quantity
and the other with 5 quantity

the carton_quantity is divided in 7 parts in table carton_information
by factor called conversion rate i.e. 15

100/15  so first carton is partially filled with 10
and the rest is filled with 15 each
thus the total of quantity becomes 100

now i have to update table carton_information  f_date
first 6 with date 1/5/04
and remaining 1 with 1/15/04
to be approximately right  b'cause the lot abc has import date
of 1/5/04 and is has 95 quantity so approxmately
i will fill 6 and 1/2 carton

so i have to update first 6 with 1/5/04
and rest with 1/15/04

this is for 1 item
there can be multiple items
and a item quantity can come from any number of lots

how should i go about

Thanks
Rahul

      and we said...
well, basically we need a view like this:

ops$tkyte@ORA9IR2> select inventory_item_id,
  2         import_date,
  3         quantity,
  4         sum(quantity) over
  5           (partition by inventory_item_id
  6                order by import_date ) running_tot
  7    from t2
  8  /

INVENTORY_ITEM_ID IMPORT_DA   QUANTITY RUNNING_TOT
----------------- --------- ---------- -----------
                4 05-JAN-04         95          95
                4 15-JAN-04          5         100

which gives us a running total of the quantity for each inventory_item_id sorted
by import_date.  Then we need a view like this:

ops$tkyte@ORA9IR2> select inventory_item_id,
  2         carton_quantity,
  3         rowid rid,
  4         sum(carton_quantity) over
  5           (partition by inventory_item_id
  6                order by rowid) running_tot
  7    from t1
  8  /

INVENTORY_ITEM_ID CARTON_QUANTITY RID                RUNNING_TOT
----------------- --------------- ------------------ -----------
                4              10 AAAIoFAAJAAAA9lAAA          10
                4              15 AAAIoFAAJAAAA9lAAB          25
                4              15 AAAIoFAAJAAAA9lAAC          40
                4              15 AAAIoFAAJAAAA9lAAD          55
                4              15 AAAIoFAAJAAAA9lAAE          70
                4              15 AAAIoFAAJAAAA9lAAF          85
                4              15 AAAIoFAAJAAAA9lAAG         100

7 rows selected.

(i'm using ROWID as I don't know what your primary key is, rowid will be useful
in the merge below as well, you can sort by ANYTHING you see fit to sort by
however).  It gives a running total of what we need row by row.

Now, all we need to do is "join":

ops$tkyte@ORA9IR2> with
  2  v1 as
  3  ( select inventory_item_id,
  4           import_date,
  5           quantity,
  6           sum(quantity) over
  7             (partition by inventory_item_id
  8                  order by import_date ) running_tot
  9      from t2
 10  ),
 11  v2 as
 12  ( select inventory_item_id,
 13           carton_quantity,
 14           rowid rid,
 15           sum(carton_quantity) over
 16             (partition by inventory_item_id
 17                  order by rowid) running_tot
 18      from t1
 19  )
 20  select v2.rid,
 21         v2.inventory_item_id,
 22         v2.carton_quantity,
 23         min(v1.import_date) import_date
 24    from v1, v2
 25   where v1.inventory_item_id = v2.inventory_item_id
 26     and v2.running_tot <= v1.running_tot
 27   group by v2.rid, v2.inventory_item_id, v2.carton_quantity
 28  /

RID                INVENTORY_ITEM_ID CARTON_QUANTITY IMPORT_DA
------------------ ----------------- --------------- ---------
AAAIoJAAJAAAA9lAAA                 4              10 05-JAN-04
AAAIoJAAJAAAA9lAAB                 4              15 05-JAN-04
AAAIoJAAJAAAA9lAAC                 4              15 05-JAN-04
AAAIoJAAJAAAA9lAAD                 4              15 05-JAN-04
AAAIoJAAJAAAA9lAAE                 4              15 05-JAN-04
AAAIoJAAJAAAA9lAAF                 4              15 05-JAN-04
AAAIoJAAJAAAA9lAAG                 4              15 15-JAN-04

7 rows selected.

Using that, we can easily update the original base table by rowid.

Now, I blew up your data somewhat -- to about 1/2 million rows in the table to
be updated like this:

ops$tkyte@ORA9IR2> begin
  2      for i in 1 .. 16
  3      loop
  4          insert into t1
  5          select inventory_item_id+power(2,i), carton_quantity, f_date
  6            from t1;
  7          insert into t2
  8          select inventory_item_id+power(2,i), import_date, quantity
  9            from t2;
 10          commit;
 11      end loop;
 12  end;
 13  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:01.66
ops$tkyte@ORA9IR2> create index t1_idx on t1(inventory_item_id);

Index created.

Elapsed: 00:00:02.21
ops$tkyte@ORA9IR2> create index t2_idx on t2(inventory_item_id);

Index created.

Elapsed: 00:00:00.49
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> analyze table t1 compute statistics;

Table analyzed.

Elapsed: 00:00:03.57
ops$tkyte@ORA9IR2> analyze table t2 compute statistics;

Table analyzed.

Elapsed: 00:00:01.45

And wanted to see what happens with a single inventory item id update:

ops$tkyte@ORA9IR2> merge into t1
  2  using (
  3  with
  4  v1 as
  5  ( select inventory_item_id,
  6           import_date,
  7           quantity,
  8           sum(quantity) over
  9             (partition by inventory_item_id
 10                  order by import_date ) running_tot
 11      from t2
 12     where inventory_item_id = 4
 13  ),
 14  v2 as
 15  ( select inventory_item_id,
 16           carton_quantity,
 17           rowid rid,
 18           sum(carton_quantity) over
 19             (partition by inventory_item_id
 20                  order by rowid) running_tot
 21      from t1
 22     where inventory_item_id = 4
 23  )
 24  select v2.rid,
 25         v2.inventory_item_id,
 26         v2.carton_quantity,
 27         min(v1.import_date) import_date
 28    from v1, v2
 29   where v1.inventory_item_id = v2.inventory_item_id
 30     and v2.running_tot <= v1.running_tot
 31   group by v2.rid, v2.inventory_item_id, v2.carton_quantity
 32  ) X
 33  on (t1.rowid = x.rid)
 34  when matched then update set t1.f_date = x.import_date
 35  when not matched then insert (inventory_item_id) values ( NULL );

7 rows merged.

Elapsed: 00:00:00.01

vs the entire set:

ops$tkyte@ORA9IR2> merge into t1
  2  using (
  3  with
  4  v1 as
  5  ( select inventory_item_id,
  6           import_date,
  7           quantity,
  8           sum(quantity) over
  9             (partition by inventory_item_id
 10                  order by import_date ) running_tot
 11      from t2
 12  ),
 13  v2 as
 14  ( select inventory_item_id,
 15           carton_quantity,
 16           rowid rid,
 17           sum(carton_quantity) over
 18             (partition by inventory_item_id
 19                  order by rowid) running_tot
 20      from t1
 21  )
 22  select v2.rid,
 23         v2.inventory_item_id,
 24         v2.carton_quantity,
 25         min(v1.import_date) import_date
 26    from v1, v2
 27   where v1.inventory_item_id = v2.inventory_item_id
 28     and v2.running_tot <= v1.running_tot
 29   group by v2.rid, v2.inventory_item_id, v2.carton_quantity
 30  ) X
 31  on (t1.rowid = x.rid)
 32  when matched then update set t1.f_date = x.import_date
 33  when not matched then insert (inventory_item_id) values ( NULL );

458752 rows merged.

Elapsed: 00:00:44.06

given that:

ops$tkyte@ORA9IR2> update t1 set f_date = f_date;
458752 rows updated.

Elapsed: 00:00:18.39

44 seconds looks "reasonable" to me...
Review &
      Followup

      Rating:
      5
Update  March 06, 2004
Reviewer:
      rahul
Thanks a lot for your reply
i forget to mention
the primary key for the table
carton_information is

Delivery_id, pallet_lpn_id, carton_id
which i didn't mentioned before

i want to ask if

the table has
carton_information
delivery_id pallet_lpn_id
1101        228
inventory_item_id carton_id carton_quantity f_date
4                         1         10
                          2         10
                          3         10
                          4         10
                          5         10
                          6         10
                          7         10
                          8         10
                          9         10
                          10        10
and for the lot
the unique key is
delivery_id,pallet_lpn_id,inventory_item_id
del_id pall_lpn_id inventory_item_id lot_nu lot_qu import_d
1101   228          4                 hhh    57     1/7/04
                                      ggg    27     2/10/04
                                      yyy    16     3/20/04
can this be approximately assigned to the carton_quantity
in f_date by the query which you have given

          Followup:

did you try it out to see what it did and if the end results were satisfactory?

      Rating:
      5
update  March 08, 2004
Reviewer:
      A reader
Thanks Tom as always your solution was excellent!

      Rating:
      4
  March 10, 2004
Reviewer:  George Lee
      (Broadway Photos Supply Ltd.)  from Hong Kong
Explanation is crytal clear and to the point.

This is the first time I know MERGE syntax and usage.

I think I can absorb the new syntax and its usage more easily and get impressed
more with a real life case.

Thanks for all readers and our master TOM.

March 06, 2004 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

      Rating: 5
Love it, but
      ...  March 04, 2004
Reviewer:  Peter Tran
       from Houston, TX USA
Hi Tom,

What happens if you have a dataset where the top mgr refers to himself?  Using
your example, if the MGR = 7839 for KING and I run the same query you have
Oracle gives me a:

ORA-01436: CONNECT BY loop in user data

A connect by record cannot reference itself otherwise we have an infinite loop
situation.

Is there anyway I can filter this out?  In other words, I still want the answer
you got earlier, but now I have the situation where KING refers to himself in
the MGR column.

Thanks,
-Peter

          Followup:

that case is easy -- since we have the "loop" in a single row, we can simply
filter it out in the "connect by" clause:

ops$tkyte@ORA9IR2> create table emp as select * from scott.emp;

Table created.

ops$tkyte@ORA9IR2> update emp set mgr = empno where mgr is null;

1 row updated.

ops$tkyte@ORA9IR2> commit;

Commit complete.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select lpad(' ',level*2,' ')||ename ename, empno, mgr
  2    from emp
  3   START WITH ename = 'KING'
  4  CONNECT BY PRIOR EMPNO = MGR
  5  /
ERROR:
ORA-01436: CONNECT BY loop in user data

no rows selected

ops$tkyte@ORA9IR2> select lpad(' ',level*2,' ')||ename ename, empno, mgr
  2    from emp
  3   START WITH ename = 'KING'
  4  CONNECT BY PRIOR EMPNO = MGR AND empno <> mgr
  5  /

ENAME                EMPNO        MGR
--------------- ---------- ----------
  KING                7839       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.

And in 10g, you have NOCYCLE to avoid the loops anywhere:

ops$tkyte@ORA10G> select lpad(' ',level*2,' ')||ename ename, empno, mgr
  2    from emp
  3   START WITH ename = 'KING'
  4  CONNECT BY NOCYCLE PRIOR EMPNO = MGR
  5  /

ENAME                EMPNO        MGR
--------------- ---------- ----------
  KING                7839       7839
    JONES             7566       7839
      FORD            7902       7566
        SMITH         7369       7902
      SCOTT           7788       7566
        ADAMS         7876       7788
    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.

      Rating:
      5
Awesome...  March 04, 2004
Reviewer:
      Peter Tran  from Houston, TX USA
Thanks for the quick turn-around.

The combination of Tom Kyte and Oracle really rocks.

Awesome...awesome...awesome.

-Peter

      Rating: 5
Can I get the
      parent position w.r.t. to the rownum?  March 05, 2004

Reviewer:  Peter Tran  from Houston, TX USA

Hi Tom,

Is it possible for me to generate the ParentPosition index w.r.t. to the
assigned ROWNUM?  Of course, ROWNUM starts at 1, but my example below is using
base 0.  Either way, you can see where I'm getting at with the example below.

Currently, I'm doing this mapping in code.  It would be much better if I can do
this within SQL.

Thanks,
-Peter

ROWNUM ENAME                EMPNO        MGR   ParentPos
------ --------------- ---------- ----------   -------------
  0    KING                7839       7839        0
  1      JONES             7566       7839        0
  2        FORD            7902       7566        1
  3          SMITH         7369       7902        2
  4        SCOTT           7788       7566        1
  5          ADAMS         7876       7788        4
  6      BLAKE             7698       7839        0
  7        ALLEN           7499       7698        6
  8        WARD            7521       7698        6
  9        MARTIN          7654       7698        6
 10        TURNER          7844       7698        6
 11        JAMES           7900       7698        6
 12      CLARK             7782       7839        0
 13        MILLER          7934       7782        12

          Followup:

what is "parentPos"

      Rating:
      3
ParentPos  March 06, 2004
Reviewer:
      Peter Tran  from Houston, TX USA
ParentPos is the RowNum value of the parent.

King is the parent of Jones, Blake, and Clark which is why ParentPos for them is
0.

Blake is the parent of Alan, Ward, Martin, Turner, and James, so you see their
ParentPos is 6 because Blake's RowNum is 6.

Thanks,
-Peter

          Followup:

scott@ORA9IR2> select rnum, ename, empno, mgr,
  2             substr( scbp2, 1, instr(scbp2,',')-1 ) parentpos
  3    from (
  4  select a.*,
  5             substr( scbp, instr(scbp, ',', -1, 2 )+1 ) scbp2
  6    from (
  7  select rownum-1 rnum, rpad( ' ', 2*level, ' ' ) || ename ename, empno, mgr,
  8          sys_connect_by_path( rownum-1, ',' ) scbp
  9    from emp
 10   start with mgr is null
 11   connect by prior empno = mgr
 12         ) a
 13             )
 14  /

      RNUM ENAME                     EMPNO        MGR PARENT
---------- -------------------- ---------- ---------- ------
         0   KING                     7839
         1     JONES                  7566       7839 0
         2       SCOTT                7788       7566 1
         3         ADAMS              7876       7788 2
         4       FORD                 7902       7566 1
         5         SMITH              7369       7902 4
         6     BLAKE                  7698       7839 0
         7       ALLEN                7499       7698 6
         8       WARD                 7521       7698 6
         9       MARTIN               7654       7698 6
        10       TURNER               7844       7698 6
        11       JAMES                7900       7698 6
        12     CLARK                  7782       7839 0
        13       MILLER               7934       7782 12

14 rows selected.

      Rating: 5
Just
      amazing...  March 06, 2004
Reviewer:  Peter
      Tran  from Houston, TX USA
Excellent!

Thanks!
-Peter

      Rating: 4
CONNECT
      BY  March 09, 2004
Reviewer:  ANOOP GUPTA
       from INDIA
it would very useful for me to understand connect by
specially to get first and last in tree.

one solution i also have for geting first and last
in tree.
select *
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
  where a.ref_recp is null
  or  a.orig_recp not in (select nvl(ref_recp,'NULL')
                                               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
                                         )

its output is similar as your client want

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

March 07, 2004 ali -- Thanks for the question regarding "DBWR
      and Server process", version 8.0.5

      You Asked
Hi sir,

this is ali from hyderabad pakistan. i am little bit confused,
how oracle server determine about the changed blocks (dirty
blocks) after a checkpoint event by which the DBWR writes dirty
buffers in data files and still user do not commit the
transaction or rollback. i mean.

1. If user decided to rollback the transaction, what steps
server process will take to roll back the transaction effect.

2. And if user decided to commit the transaction , what steps
server process will take to make the change permanent, becuase
the transaction data already written in data files because of
any prvious checkpoint (suppose log file switch).

thanks

ali

      and we said...
It is complex -- here is a short scenario.

You update 1,000 rows with a single statement.  Oracle will:

o generate modified datablocks in the SGA
o generate rollback records in the SGA
o generate redo for BOTH datablocks and rollback in the SGA

We may flush some of the REDO generated to the logfiles during
this process (or not). Remember also that before we checkpoint a
block -- the REDO that protects that block is flushed first,
before DBWR can flush it.  There is never a block on disk that
doesn't have its redo written first.  Let's say the system fails
right now in the middle of our update.  We know the above 3
things have happened and either:

o some REDO had been written to the logs and NO data/rollback
blocks have been. When the system restarts, this REDO will be
applied. This will reconstruct the ROLLBACK blocks as well as
the modified DATABLOCKS.  After we roll forward in recovery, we
will roll back using the rollback blocks we just reconstructed.
It will be as if the transaction never happened.

o some REDO and SOME data/rollback blocks have been written.
Same as above then -- we rollforward, applying the REDO to the
blocks that were not checkpointed -- and then we rollback.

o NO redo had been written to the logs.  When the system
restarts -- it is simply as if the transaction never happened.
we are done. no recovery needed.

Now, let's say the system does not fail during our update and it
succeeds.  We have

o generated redo and it is in the SGA (probably)
o generated modified datablocks and they are in the SGA
o generated rollback records and they are in the SGA.

we commit.  LGWR flushes our redo to disk.  Thats it -- we've
committed at that point.  If the system fails right after we
commit, when we restart we apply the REDO to redo that
transaction -- we have all of the information needed to replay
it and we just do.

Hope that helps.

If we do flush some redo to disk and we crash
Review &
      Followup

      Rating: 2
number of dirty
      buffers reaches the a threshold value  March 11, 2001

Reviewer:  ali  from hyderabad,pakistan
dear sir tom,

infact i was confused by the steps taking when a checkpoint occurs if it reaches
its threshold value (how we determine this limit). next thing , is DBWR writes
all dirty blocks
(data and rollback) to their respective segments, whether its transaction became
committed or not, if is this happend then is DBWR writes again on changed data
from rollback segment data, if user rollback the transaction.

thanks

ali

      Rating:
      5
  December 18, 2003
Reviewer:
      A reader
greate help. thanks a lot

      Rating: 2
Threshold size
      for DB_BUFFER  March 07, 2004
Reviewer:
      Jayesh  from Chennai,India
Hello Tom,
  You did'nt give any reply for specifying the Threshold size. In oracle Book
also its mentioned..But did'nt give any valid reasons or any way to specify
it..?

          Followup:

you use parameters like fast_start_mttr target, fast_start_io_target (superceded
by prior parameter), log checkpoint interval and such to control how aggresive
dbwr is at keeping the buffer cache "clean"

March 07, 2004 Houman -- Thanks for the question regarding
      "HTMLDB and XMLDB", version 9.2.0.4

      You Asked
Hi Tom

I am currently working on a htmldb project. The choice of my company between
Oracle or Microsoft in regard to html or web based solutions is mostly depending
the outcome of this project. I have the following questions

1- In the final phase of my project, I need to read a CLOB field from a database
table that contains report or document in XML format to be publish on web. I
know that I need to have some kind of style sheet to interpet the document and
this is basically all I know at this time. Could you give me a hint about the
tools, XMLDB procedures and/or the easiest way I can show my report that I've
received in XML format? What steps are involved?

2- Could you disscus about HTMLDB Vs other tools and languages for web
development such as Java and J2ee ones? Can we rely on HTMLDB to develop a fully
transaction oeriented application on the web?

Thank you

      and we said...
1) well, you would just be applying a style sheet to the XML to convert it from
XML into HTML for display purposes.  A style sheet is nothing more than a
"little program" (you'll be using a style sheet to convert XML into HTML or
anything else regardless of the technology you ultimately choose)

you'll want to browse

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96620/toc.htm
and specifically

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96620/xdbxcases2.htm#1061781
as that demo demonstrates exactly what you need (convert XML into pretty HTML)

2) HTMLDB is more than capable of developing fully transaction oriented web
based applications -- we've done many of them.  Pretty much anything you want to
do is doable out of the box.

I'd be hard pressed to describe the application that HTML DB cannot do, cannot
do easily, cannot do in a more than scalable enough manner.
Review &
      Followup

      Rating:
      3
  March 08, 2004
Reviewer:
      A reader

March 07, 2004 Faisal -- Thanks for the question regarding
      "Update", version 8.1.7

      You Asked
Hi Tom,

Just a quick question,  I need to update table as follows

I have a table

create table t (clm varchar2(2), clmnt number, sf varchar2(3), amt
number,sf_status varchar2(2),clm_status varchar2(2),
 primary key (clm, clmnt, sf));

with data

insert into t (clm,clmnt,sf,amt) values ('A',1,'ABC',100);
insert into t (clm,clmnt,sf,amt) values ('A',1,'XYZ',0);
insert into t (clm,clmnt,sf,amt) values ('A',2,'ABC',500);
insert into t (clm,clmnt,sf,amt) values ('A',2,'XYZ',0);

which looks like before update as

CLM    CLMNT    SF    AMT    SF_STATUS    CLM_STATUS
A     1    ABC    100      null              null
A     1    XYZ    0      null              null
A     2    ABC    500      null              null
A     2    XYZ    0      null              null

Now, I need to update sf_status and clm_status columns for each row with the
following rules

For each clm, clmnt and sf, if Amt is > 0 then sf_status = OP else CL

For each clm if sum of Amt at clm level is > 0 then clm_status = OP else CL

How can I update both columns in a single update statement?

The result should look like as follows

CLM    CLMNT    SF    AMT    SF_STATUS    CLM_STATUS
A     1    ABC    100      OP              OP
A     1    XYZ    0      CL              OP
A     2    ABC    500      OP              OP
A     2    XYZ    0      CL              OP

Thanks in advance

Saeed

      and we said...
You can do this in a single statment, but if you want it fast -- we may well
want to "two step it".

Consider:

tkyte@ORA8I> select * from t;

CL      CLMNT SF         AMT SF CL
-- ---------- --- ---------- -- --
A           1 ABC        100
A           1 XYZ          0
A           2 ABC        500
A           2 XYZ          0
B           1 ABC        100
B           1 XYZ       -100
B           2 ABC        500
B           2 XYZ       -500

8 rows selected.

tkyte@ORA8I> commit;

Commit complete.

tkyte@ORA8I>
tkyte@ORA8I> update t
  2     set sf_status = decode(sign(amt), 1, 'OP', 'CL' ),
  3         clm_status = ( select decode( sign(sum(amt)), 1, 'OP', 'CL' )
  4                          from t t2
  5                         where t2.clm = t.clm )
  6  /

8 rows updated.

tkyte@ORA8I> select * from t;

CL      CLMNT SF         AMT SF CL
-- ---------- --- ---------- -- --
A           1 ABC        100 OP OP
A           1 XYZ          0 CL OP
A           2 ABC        500 OP OP
A           2 XYZ          0 CL OP
B           1 ABC        100 OP CL
B           1 XYZ       -100 CL CL
B           2 ABC        500 OP CL
B           2 XYZ       -500 CL CL

8 rows selected.

that is your single statement.  for hundreds or even thousands of rows, it'll
be satisfactory -- getting big though, it'll probably not be the best approach.
I'd be looking at something like this perhaps

tkyte@ORA8I>
tkyte@ORA8I> rollback;

Rollback complete.

tkyte@ORA8I>
tkyte@ORA8I> create global temporary table gtt ( clm varchar2(2) primary key,
amt number );

Table created.

tkyte@ORA8I> insert into gtt
  2  select clm, sum(amt) from t group by clm;

2 rows created.

tkyte@ORA8I>
tkyte@ORA8I>
tkyte@ORA8I> update (select sf_status, clm_status,
  2                 decode(sign(t.amt),1,'OP','CL') new_sf,
  3                 decode(sign(t2.amt),1,'OP','CL') new_clm
  4            from t, gtt t2
  5           where t.clm = t2.clm )
  6    set sf_status = new_sf, clm_status = new_clm;

8 rows updated.

tkyte@ORA8I>
tkyte@ORA8I> select * from t;

CL      CLMNT SF         AMT SF CL
-- ---------- --- ---------- -- --
A           1 ABC        100 OP OP
A           1 XYZ          0 CL OP
A           2 ABC        500 OP OP
A           2 XYZ          0 CL OP
B           1 ABC        100 OP CL
B           1 XYZ       -100 CL CL
B           2 ABC        500 OP CL
B           2 XYZ       -500 CL CL

8 rows selected.

tkyte@ORA8I>

Review &
      Followup

      Rating: 4
Quick
      Response  March 07, 2004
Reviewer:  Faisal
       from Canada
Tom , you r genious !!! Thanks a million

March 07, 2004 -- Thanks for the question regarding "size of
      tablespaces & datafiles", version 9.2.0.4

      You Asked
If using raw datafiles, are there any reasons to keep the maximum size at 2000
Gig?  Also is there any reason to put large tables in their own tablespace
instead of with other objects?  I already use the defrag criteria for the size
of the objects I put in my tablespaces.  So I guess question is if there is any
reason to have many small tablespaces vs. fewer large ones.

      and we said...
any reason to keep at 2000m (assume you mean 2000m or just shy of 2gig, not just
shy of 2terabytes) -- No, there is not, not if your OS supports big files
anyway.

Is there any reason to put large tables.... sure, if they are truly large.  You
use tablespaces for administrative (not tuning) reasons.  A large object by
itself can be considered more manageable -- your basic unit of backup and
recovery is a tablespace -- having a single terabyte tablespace would be
unmanageable -- having a couple 100gig tablespaces would be more manageable.  It
is all about "what is the biggest thing you want to manage as a unit"

Everyone I've ever met has had their own unique definition of "small and large".
 You must set your thresholds -- but 10s or 100s of gig in a tablespace is not
"scary" especially as things evolve over time (like the addition of block level
recovery -- the unit of recovery is a block perhaps now, so the tablespaces can
in fact get "larger" without incurring large downtimes to recover since you are
not recovering the entire thing perhaps).

Review &
      Followup

      Rating:
      5
  March 08, 2004
Reviewer:
      A reader  from heathrow, fl
Thanks!  Very helpful!

March 07, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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.

      Rating: 3
Wrong
      range?  March 07, 2004
Reviewer:  Michal
       from Frankfurt
<quote>
dbms_random.normal will return a "random" "normally distributed" number between
-1 and +1 (most at 0).
<quote>

Try this:

DECLARE
x BINARY_INTEGER;
BEGIN
x := 123456;
-- Seed (overloading BINARY_INTEGER or CHAR)
DBMS_RANDOM.SEED(x);
FOR x IN 1 .. 100 LOOP
DBMS_OUTPUT.PUT_LINE(DBMS_RANDOM.NORMAL);
END LOOP;
END;
/

and you'll see:
.6552453068736144004189012503457494380011
-.1998072752961617437697821628544956536385
.3616098566482172556647023002713899797728
-.76374538378660583258238903668412171048
2.26313928540300668848441957444690328863
.0987309681945767405833233098422463301788
2.74257619226505398219534872384171306092
-.2652023947009487738914447653006839953976
.3469172911120469484388168373254850902541
1.21220943182047716230612464378150888317
-.5507118148915647513893381978398223287084
1.35837102234415831658204538872406601983
-.9015120693203381537942783928117923860256
.7446059610015514456119755613915469050156
.7171427438288355010869838787707389810995
-.7340478738237179598845112752918758516098
.3411346277117262435118046913527693254062
.1432293523333120672398100110338354864665
-1.32875907830606105786071613261960764497
-1.59036719813875108820221952610750594883
2.51443321535476818233522333506466990204
...

So I doubt that the range you have mentioned (-1,+1) is right.

??

          Followup:

sorry you are correct --  the +/- 1 was for the stddev of the normal
distribution (most of the data will fall within +/- 1 of 0

ops$tkyte@ORA9IR2> create table t
  2  as
  3  select dbms_random.normal from all_objects;

Table created.

ops$tkyte@ORA9IR2> select avg(normal), stddev(normal) from t;

AVG(NORMAL) STDDEV(NORMAL)
----------- --------------
 -.00250014     1.00260327

ops$tkyte@ORA9IR2> edit test

ops$tkyte@ORA9IR2> @test
ops$tkyte@ORA9IR2> drop table t;

Table dropped.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create table t
  2  as
  3  select dbms_random.normal from all_objects;

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select avg(normal), min(normal), max(normal), stddev(normal)
from t;

AVG(NORMAL) MIN(NORMAL) MAX(NORMAL) STDDEV(NORMAL)
----------- ----------- ----------- --------------
 .001419749  -3.9844695  4.15448314     .999318265

      Rating:
      5
  March 07, 2004
Reviewer:  rabeila
       from pk

March 07, 2004 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.

      Rating: 5
How to drop
      undo tablespace  March 07, 2004
Reviewer:
      Arun  from Patiala India
hi Tom
by mistake size of the undo tablespace given 20G ,we analysed it when space was
not left on the system,we tried to reduce the size but it gives error.we have
created a new undo tablespace and mentioned in the init ora file. old undo
tablespace took offline and deleted undo datafile (physically) but oracle using
old undo tablespace.I have tried to remove it from OEM but it shows some
segments dependency and status of those segment is null instead of
offline(status seen from dba_rollback_segs).
How can i put these segments offline and drop old undo tablespace

          Followup:

ouch -- hope you really didn't do what it sounds like you did.

first, you should have just (assuming 9i AUM here)

a) created new
b) alter system set undo_tablespace=new_one;
c) drop tablespace old_one including datafiles (might have to wait for active
transactions to complete)

if you erased the OS files without dropping the tablespace -- well, you could be
in a bad way.  This is time to call support and get an analysis of where you are
and what you've done.

      Rating: 5
SYSTEM Rollback
      segment  March 07, 2004
Reviewer:  Shankar
       from Houston, TX
(1) Is there any relation between SYSTEM rollback segment and UNDO_TABLESPACE
undo segments containing user transactions?

(2) When the transaction tables on the header of undo segments in
UNDO_TABLESPACE are created, does oracle use SYSTEM rollback segment to keep
before image values of these changes as well? Also, whenever the transaction
table is modified in the users undo segments headers, the before images are also
saved in SYSTEM rollback segments?

I was just curious what is the role of SYSTEM rollback segment? Thanks as
always. You are the best.

          Followup:

1) no -- other then one is a rollback segment and the other is a tablespace that
contains other rollback segments.  It'll not use the system rollback segment
except for things it must (mostly exceptional cases and system transactions --
like holding undo for in doubt distributed transactions so as to not jam up the
regular undo segments)

see

http://otn.oracle.com/pls/db92/db92.drilldown?remark=&word=%22system+rollback%22&book=&preference=
for more info on them.

2) the internal system transactions to create an undo tablespace and the
segments therein, system rollback.  after that, its all about "itself".

      Rating: 3
overwriting it
      ??  March 07, 2004
Reviewer:  Michal
       from Frankfurt
I admit that I haven’t read a concept so you can consider it as a waste
question.
However I’m little bit worried about "please retain undo for 20minutes before
overwriting it". Let’s say that I set a retention period for 20 minutes that
should be sufficient for the most of my DMLs.

What happen when I run an unusual, long running DML and it run out of 20 minutes
timeout? I suppose that Oracle behaves consistently and raise an error (an
equivalent of “ORA-01555: snapshot too old (rollback segment too small)" and
roll back my transaction.

I don’t expect that it’ll really overwrite another rollback information within
an undo tablespace (preventing different transaction to use an undo information
for potential rollback), doesn’t it?

Thanks.

          Followup:

it will overwrite rollback data from COMMITED transactions only.  it is "safe".

March 08, 2004 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://download-east.oracle.com/docs/cd/A81042_01/DOC/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://download-west.oracle.com/docs/cd/B10501_01/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://download-west.oracle.com/docs/cd/B10501_01/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

      Rating: 4
initial extent
      size for locally managed temporary tablespace  March 05,
      2004
Reviewer:  Jian  from DC
Hi Tom,
I quoted this from Metalink
"
- Use the following formula for calculating the value for NEXT:    (n*s + b)
with
 n = positive integer,
 s = value of SORT_AREA_SIZE initialization parameter,
 b = value of DB_BLOCK_SIZE initialization parameter.   Doing so, you achieve
there will be enough space in every extent    to store header-block information
and the multiple sort run data.
  The above formula is only appropriate for dictionary managed temp tablespaces.

"

question1
1) It implys the header-block is 1 block in size, right?
2) How to set the initial size for locally managed temporary space if the
formular applies to the DMT only? I think the theory is the same so the formular
should be the same, shouldnt we set each extent in size to hold multiple
sort_area_size plus 1 header block?

Many thanks!

          Followup:

1) yes
2) 1m is good for LMTs, especially as you go towards pga_aggregate_targets.  You
can try "sort area size" but is everyone using the same one (not with
pga_aggregate_target they aren't)

      Rating: 5
LMTs and
      NEXT  March 07, 2004
Reviewer:  reader

If I specified NEXT 50M in an LMT with uniform size 1M, would oracle allocate 50
1M extents as well? Thanks.

          Followup:

not unless you had "minextents 2"

initial, next, pctincrease, minextents are used upon segment creation with LMTS
(it figures out what would have been allocated in a DMT and allocates at least
that much) and not used thereafter.

      Rating: 5
not
      clear  March 07, 2004
Reviewer:  reader

<quote>not unless you had "minextents 2" <quote>

Tom, could you please elaborate on this? Thanks.

          Followup:

Ok, use 64k instead of 1m (so 3200k instead of 50m)

If you used:

a) initial 64k next 3200k minextents 1

Oracle would have allocated 64k in a DMT.  If you use:

b) initial 64k next 3200k minextents 2

Oracle would have allocated 3264k in a DMT.  In an LMT, Oracle would allocate:

a) AT LEAST 64k of space
b) AT LEAST 3264k of space

So, if you use NEXT and have minextents set so that NEXT actually comes into
play on the initial allocation -- then yes, NEXT will be used to compute space.
Consider:

ops$tkyte@ORA9IR2> create tablespace demo
  2  datafile size 5m
  3  extent management local uniform size 64k
  4  /

Tablespace created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create table t1 ( x int ) storage ( initial 64k next 3200k )
tablespace demo;

Table created.

ops$tkyte@ORA9IR2> create table t2 ( x int ) storage ( initial 64k next 3200k
minextents 2)
  2  tablespace demo;

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select segment_name, sum(blocks), count(*)
  2    from user_extents
  3   where segment_name in ( 'T1', 'T2' )
  4   group by segment_name
  5  /

SEGMENT_NAME                   SUM(BLOCKS)   COUNT(*)
------------------------------ ----------- ----------
T1                                       8          1
T2                                     408         51

So, T2 used 50 more 64k extents to cover itself.  NEXT was used during the
create but will not be used anymore after that.

      Rating: 5
alter table
      ....  March 07, 2004
Reviewer:  David

If I do alter table t storage (next 50), will oracle allocate fifty 1M extents
if uniform size is 1M? Thanks.

          Followup:

no, it is used only during the creation of the segment in an LMT

March 08, 2004 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.

      Rating: 3
Serialize a
      transaction and use a specific Rollback segment  March 08,
      2004
Reviewer:  Rateesh Mohan  from Dubai
hi tom,

I have a procedure for populating two tables in a serialized fashion.The insert
itself causes too much rollback and comes out with error cannot extend rollback
segment. For this purpose i have created an additional rollback segment with
very large extents( as big as 100 mb)  and max extents as 25  but the problem is
 i am unable to set the transaction as seriazable and to use a particular
rollback segment . It says both has to be the first statement.

So my questions are

1) Serialized insert's/update's DOES NOT cause more rollback than normal insert.
is this correct please confirm .

2) Is there any way for a serializable transaction to use a specific rollback
segment

thanks ,

          Followup:

I'm a big big big fan of equi sized RBS.  sounds like you have not setup
sufficient RBS for your system.

Serializable is going to be deadly on a long running transaction - You would be
best off by simply locking the source tables -- else you are going to get
"cannot serialize access" after running for a while (or 1555 as the odds of 1555
relate to the size of your smallest rbs, not your largest and you don't seem to
have enough rbs in the first place)

1) correct
2) not that I'm aware of -- I've never tried because I've never used 'use
rollback segment'

You might be able to use INSERT /*+ APPEND */ to minimize the amount of undo
generated.

      Rating: 4
Some
      stat  March 08, 2004
Reviewer:  Alex
       from Montreal
I had some experience with benchmarking
TX_SERIALIZABLE via jdbc. In averagely
busy environment (~20 TPS, very rare clashes)
serializable level was 10-20% slower than
READ_COMMITED. (Oracle 817).

note: 10-20% overheat was for simple jdbc calls.
For call with heavy resultset workout overheat
was only ~5%

As described in Oradocs, while in TX_SERIALIZABLE,
Oracle has to mark selected rows with timestamp and
transaction id (doing more work than in
TX_READ_COMMITED). SO some overhead is expected,
but it is "practically" small.

If TX going for > 60-120 sec then
"ORA-08177 can't serialize access for this transaction"
is rather friquent guest here.

Thus, we did not accepted "serializable" for long
transactions (between HTML requests in our case) and settled
for HTTPSession and/or additional "status" column in
database.

          Followup:

serializable is appropriate only for very very short transactions (in any
database -- in others, you would get deadlocks instead of 8177).

we run our TPC-C's with serializable.

In serializable, all you've done is frozen your SCN as of the start of the
transaction -- Oracle does not mark records "specially" in this mode.  Any
overhead would be due to the extra work to provide the consistent read for data
that has changed since your transaction began.  Serializable by itself doesn't
add any real additional work.

March 08, 2004 salman -- Thanks for the question regarding
      "Generate XML", version 9.2.0.4

      You Asked
Tom,

I am really sorry about the typos and inconvinince caused to you.
Let me explain it little better. I am trying to create an XML for product
information. I have following three tables containing items data:

SQL> desc product_to_eoc
 Name                            Null?    Type
 ------------------------------- -------- ----
 ITEM_ID                         NOT NULL NUMBER
 CREATION_DATE                   NOT NULL DATE
 CREATED_BY                      NOT NULL NUMBER
 LAST_UPDATE_DATE                NOT NULL DATE
 LAST_UPDATED_BY                 NOT NULL NUMBER
 ENABLED_FLAG                    NOT NULL VARCHAR2(1)
 DOMAIN                                   CHAR(9)
 EXTERNAL_SYSTEM_CODE                     CHAR(6)
 FAMILY                                   VARCHAR2(30)
 ACTIVE                          NOT NULL VARCHAR2(10)
 MSRP                                     NUMBER
 PART_NUMBER                              VARCHAR2(81)
 DESCRIPTION                              VARCHAR2(240)
 MANUFACTURE_NAME                         VARCHAR2(240)
 LINE                                     VARCHAR2(40)
 MODEL_NUM                                VARCHAR2(30)
 PART_TYPE                                VARCHAR2(30)
 COLOR                                    VARCHAR2(30)
 LONG_DESCRIPTION                         VARCHAR2(150)
 LIFE                                     VARCHAR2(30)
 MFG_PART                                 VARCHAR2(30)
 MATERIAL                                 VARCHAR2(30)
 MINUTE_MANAGER                           VARCHAR2(30)
 PACKAGING                                VARCHAR2(30)
 SERIAL_TYPE                              VARCHAR2(30)
 SOURCE                                   VARCHAR2(30)
 TRANSCEIVER_ONLY                         VARCHAR2(30)
 IRDB                                     VARCHAR2(30)

SQL> desc revision_num
 Name                            Null?    Type
 ------------------------------- -------- ----
 ITEM_ID                         NOT NULL NUMBER
 CREATION_DATE                   NOT NULL DATE
 CREATED_BY                      NOT NULL NUMBER
 LAST_UPDATE_DATE                NOT NULL DATE
 LAST_UPDATED_BY                 NOT NULL NUMBER
 PART_NUMBER                              VARCHAR2(30)
 MOD_LEVEL                  VARCHAR2(10)
 REVISION_NUM                  NUMBER
SQL> desc part_attributes
 Name                            Null?    Type
 ------------------------------- -------- ----
 ITEM_ID                         NOT NULL NUMBER
 CREATION_DATE                   NOT NULL DATE
 CREATED_BY                      NOT NULL NUMBER
 LAST_UPDATE_DATE                NOT NULL DATE
 PART_NUMBER                              VARCHAR2(30)
 FREQUENCY                                VARCHAR2(30)
 HEIGHT                                   VARCHAR2(30)
 LENGTH                                   VARCHAR2(30)
 WIDTH                                    VARCHAR2(30)

I have to provide XML for two sections, one for product info and one for product
attributes. In products section I have to provide mod_level as subpart of the
product query. Also the product and product attributes sections are nested in
the given Cdo type attributes, for which I do not have any tables. I am using
the following XMLGEN query to insert data into a CLOB column:

declare
qryCtx DBMS_XMLGEN.ctxHandle;
result CLOB;
BEGIN
qryCtx := dbms_xmlgen.newContext('SELECT part_number,description, domain,
family,line, part_type,
active, manufacture_name, external_system_code, msrp, model_num, color,life,
mfg_part, material,
minute_manager, packaging,serial_type, transceiver_only, IRDB from
product_to_eoc where family in
(''PHONE'', ''ACCESSORY'', ''COLLATERAL'',''KTS'')');
dbms_xmlgen.setRowsettag(qryCtx,'Products');
dbms_xmlgen.setRowtag(qryCtx,'Product');
result := DBMS_XMLGEN.getXML(qryCtx);
insert into product_data values(result);
DBMS_XMLGEN.closeContext(qryCtx);
END;

However I am having hard time to include the attributes as a second row under
the root tag.
Please see the updated required xml file below:

<Products>
<Product>
    <ProductInfoCdo type="Cdo">
            <ProductInfoCdoCol type="CdoCol">
            <ProductInfoCdoCol_ type="Cdo">
                    <PartNumCdo  type="Cdo">
<description>description_Val</description>
<domain>domain_Val</domain>
<part_number>part_number_Val</part_number>
<active>active_Val</active>
<family>family_Val</family>
<line>line_Val</line>
<part_type>part_type_Val</part_type>
<category_code>category_code_Val</category_code>
<external_system_code>external_system_code_Val</external_system_code>
<reference_part_descr>reference_part_Val</reference_part_descr>
<reference_part_number>reference_part_num_Val</reference_part_number>
<manufacture_name>Val</manufacture_name>
<msrp>Val</msrp>
<model_num>Val</model_num>
</PartNumCdo>
<RevisionCdo  type="Cdo">
   <mod_level/>
</RevisionCdo>
</ProductInfoCdoCol_>
</ProductInfoCdoCol>
    </ProductInfoCdo>
</Product>
<FA>
   <FlexAttCdo type="Cdo">
    <FlexAttCdoCol type="CdoCol">
    <FlexAttCdoCol_ type="Cdo">
        <PartNumCdo type="Cdo">
        <part_number>part_number_Val</part_number>
        </PartNumCdo>
        <FaPartNumCdo type="Cdo">
              <height>Val</height>
              <width>val</width>
                 <length>Val</length>                   </FaPartNumCdo>
    </FlexAttCdoCol_>
</FlexAttCdoCol>
  </FlexAttCdo>
</FA>
</Products>

Hope this makes some sense.

I am very novice to XML and trying to do it going through the answers found in
your forum and the OTN documentation.

Really appreciate for your time and help.

Thanks,

Salman Ahmed

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

Hi, Tom

I amtrying to generate an XML and have tried the use the dbms_xmlgen in 9.2. I
am only able to create the XML output as follows:
<Products>
 <Product>
  <PART_NUMBER>RTL.61565</PART_NUMBER>
  <DESCRIPTION>INS PRO 99-00 LEX SEE CAT OB</DESCRIPTION>
  <DOMAIN>Equipment</DOMAIN>
  <FAMILY>COLLATERAL</FAMILY>
  <LINE>DEFAULT</LINE>
  <ACTIVE>Obsolete</ACTIVE>
  <EXTERNAL_SYSTEM_CODE>ORACLE</EXTERNAL_SYSTEM_CODE>
  <MSRP>0</MSRP>
 </Product>
</Products>

as compare to the required format as below:

<Products>
<Product>
    <ProductInfoCdo type="Cdo">
        <ProductInfoCdoCol type="CdoCol">
            <ProductInfoCdoCol_ type="Cdo">
            <PartNumCdo  type="Cdo">
<description isDirty="true">description_Val</description>
<domain isDirty="true">domain_Val</domain>
<part_number isDirty="true">part_number_Val</part_number>
<active isDirty="true">active_Val</active>
<family isDirty="true">family_Val</family>
<line isDirty="true">line_Val</line>
<part_type isDirty="true">part_type_Val</part_type>
<x_category_code isDirty="true">x_category_code_Val</x_category_code>
<x_external_system_code
isDirty="true">x_external_system_code_Val</x_external_system_code>
<x_reference_part_descr
isDirty="true">x_reference_part_descr_Val</x_reference_part_descr>
<x_reference_part_number
isDirty="true">x_reference_part_number_Val</x_reference_part_number>
<x_eff_date isDirty="true">10/30/2003</x_eff_date>
<x_exp_date isDirty="true">10/30/2003</x_exp_date>
<x_manufacture_name isDirty="true">Val</x_manufacture_name>
<x_msrp isDirty="true">Val</x_msrp>
<model_num isDirty="true">Val</model_num>
</PartNumCdo>
<RevisionCdo  type="Cdo">
<mod_level/>
</RevisionCdo>
</ProductInfoCdoCol_>
</ProductInfoCdoCol>
    </ProductInfoCdo>
</Product>
<FA>
   <FlexAttCdo type="Cdo">
    <FlexAttCdoCol type="CdoCol">
    <FlexAttCdoCol_ type="Cdo">
        <PartNumCdo type="Cdo">
        <part_number>part_number_Val</part_number>
        </PartNumCdo>
        <FaPartNumCdo type="Cdo">
              <x_height isDirty="true">Val</x_height>
              <x_width isDirty="true">val</x_width>
                 <x_length isDirty="true">Val</x_length>
</FaPartNumCdo>
    </FlexAttCdoCol_>
</FlexAttCdoCol>
  </FlexAttCdo>
</FA>
</Products>

Can you please help me to understand how can I have two different row names and
include the tags shown as
<ProductInfoCdo type="Cdo">
        <ProductInfoCdoCol type="CdoCol">
            <ProductInfoCdoCol_ type="Cdo">
            <PartNumCdo  type="Cdo">

Thanks,

Salman

      and we said...
I asked Sean Dillon, our XML technologist, to take a look at this here's what he
had to say:

Hi Salman,

DBMS_XMLGEN is a great supplied package, but in 9.2.0.4 I have a hard time
recommending it over using SQLX operators to generate XML out of the database.
SQLX is a set of new operators introduced in Oracle9i Release2 that allow you to
create XML from relational queries.  You can find all the information you'd like
to know in the Oracle9i Database Developers Guide - XML DB.  Let me show you an
example of SQLX in action, then point you to a number of other Ask Tom questions
that also use SQLX to generate XML if you'd like to see some more practical
examples.

Here are a few tables from the HR schema:

hr@GROOVYDB> 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@GROOVYDB> 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@GROOVYDB> desc locations
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 LOCATION_ID                               NOT NULL NUMBER(4)
 STREET_ADDRESS                                     VARCHAR2(40)
 POSTAL_CODE                                        VARCHAR2(12)
 CITY                                      NOT NULL VARCHAR2(30)
 STATE_PROVINCE                                     VARCHAR2(25)
 COUNTRY_ID                                         CHAR(2)

So we'll put together a quick SQLX query that produces some nested, related XML:

hr@GROOVYDB> select xmlelement("Department",
  2           xmlattributes(d.department_id as "departmentId"),
  3           xmlelement("DepartmentName", d.department_name),
  4           ( select xmlagg(
  5                  xmlelement("Employee",
  6                    xmlattributes(employee_id as "employeeId"),
  7                    xmlforest(
  8                      first_name as "FirstName",
  9                      last_name as "LastName",
 10                      email as "Email",
 11                      phone_number as "PhoneNumber"),
 12                      ( select xmlelement("Manager",
 13                                 xmlattributes(e.manager_id as "managerId"),
 14                                 xmlforest(
 15                                   m.first_name as "FirstName",
 16                                   m.last_name as "LastName",
 17                                   m.email as "Email",
 18                                   m.phone_number as "PhoneNumber"
 19                                 )
 20                               )
 21                          from employees m
 22                         where m.employee_id = e.manager_id
 23                      )
 24                    )
 25                  )
 26               from employees e
 27              where e.department_id = d.department_id ) "Employees"
 28         )
 29    from departments d
 30   where department_id = 100
 31  /

XMLELEMENT("DEPARTMENT",XMLATTRIBUTES(D.DEPARTMENT_IDAS"DEPARTMENTID"),XMLELEMEN
--------------------------------------------------------------------------------
<Department departmentId="100">
  <DepartmentName>Finance</DepartmentName>
  <Employees>
    <Employee employeeId="108">
      <FirstName>Nancy</FirstName>
      <LastName>Greenberg</LastName>
      <Email>NGREENBE</Email>
      <PhoneNumber>515.124.4569</PhoneNumber>
      <Manager managerId="101">
        <FirstName>Neena</FirstName>
        <LastName>Kochhar</LastName>
        <Email>NKOCHHAR</Email>
        <PhoneNumber>515.123.4568</PhoneNumber>
      </Manager>
    </Employee>
    <Employee employeeId="109">
      <FirstName>Daniel</FirstName>
      <LastName>Faviet</LastName>
      <Email>DFAVIET</Email>
      <PhoneNumber>515.124.4169</PhoneNumber>
      <Manager managerId="108">
        <FirstName>Nancy</FirstName>
        <LastName>Greenberg</LastName>
        <Email>NGREENBE</Email>
        <PhoneNumber>515.124.4569</PhoneNumber>
      </Manager>
    </Employee>
    <!-- additional rows cut out for brevity -->
  </Employees>
</Department>

So that's one example, but there are plenty of others.  Instead of me giving you
a bunch of URLs, I'm just going to say use the SEARCH facility on this AskTom
site to look for additional posts about SQLX.  Search for "SQLX", and you'll get
a list of entries.  It would be helpful to read through these as there are a
variety of issues covered in Tom's Q&A's...

Good luck!

----------------------------------------------------------
you put in a bogus email address.  that is why you didn't hear from me.

update WWC_ASK_SUBMITTED_QUESTIONS$ set email = 'salman147@yahoo.com' where
email = 'salman147@yaho.com';
1 row updated.

I fixed it.

i'd really sort of need the example table you are working with here and what the
meaning of all of the extra stuff is -- like where does <description
isDirty="true">description_Val</description> even come from???

March 08, 2004 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.

      Rating: 5
On
      8.1.7  January 28, 2004
Reviewer:
      A reader
Dear Tom/ Sean,
Please have another look at the Staline Xavier's question above, if we do have
SYS_XMLGEN() available, what option do we have to get a single XML per row of
data selected?

The example taken above on Oracle 8.1.7 database, how to get the same output as
with sys_xmlgen on Oracle 9i?
Many thanks

          Followup:

just select more than one row?  our example has but one master row.  put two in
there.

there is just one row selected here -- the master.

      Rating: 1
Didn't
      understand you for a change!  February 02, 2004

Reviewer:  A reader
Dear Tom,

The XML in 8.1.7 is generated like this

<?XML version='1.0'?>
<ROWSET>
  <ROW>
  ...
  </ROW>
  <ROW>
  ...
  </ROW>
...
<ROWSET>

This includes all the rows that the query will output.

The requirement is to have like this:
<?XML version='1.0'?>
  <ROW>
  ...
  </ROW>

<?XML version='1.0'?>
  <ROW>
  ...
  </ROW>
...

That is one XML per row selected. This can be shown to be achived in Oracle 9.2
using SYS_XMLGEN

My request to you is to guide me for a solution on 8.1.7

Regards,

          Followup:

sorry -- i read your text:

... if we do have
SYS_XMLGEN() available, what option do we have to get a single XML per row of
data selected? ....

and sort of "assumed" you meant what you typed.

In 8i, you'd probably be using a bit of plsql procedural code and dbms_xmlquery.

      Rating:
      4
documentation  February 04, 2004
Reviewer:
      Diana  from NY USA
Does anyone have a suggestion?
When I click on the link for the documentation that you provide, I am redirected
to the general index documentation page, at which point I have no idea where to
go.
What am I doing wrong?

          Followup:

otn moved the docs on us, all of the URLS changed :(

http://otn.oracle.com/pls/db92/db92.docindex?remark=homepage#index-XML
is the pointer to the XML documentation -- all of the links were into those
docs.

here are the "updated" links.

SYS_XMLGEN:

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96620/xdb12gen.htm#1026350
SYS_XMLGEN with objects:

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96620/xdb12gen.htm#1026578
SYS_XMLAGG:

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96620/xdb12gen.htm#1031117
Generating XML data from the database (The SQL XML stuff):

http://download-west.oracle.com/docs/cd/B10501_01/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://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96621/toc.htm

      Rating: 5
generate or
      process xml file size limit using dbms package.  February
      25, 2004
Reviewer:  Richard  from Washington

Hi Tom,

How are you doing today.  I remember that oracle dbms_xmlsave, dbms_xmlquery or
dbms_xmlgen by default use DOM parser, which will parse the whole xml document
into memory, we are about to generate a file over 50,000 typical records?

please advise what will be the size limit for processing (import) or generating
(export) an external xml file using dbms_xmlsave, dbms_xmlquery?

Can I elect to choose SAX parser instead of DOM parser through Oracle Package?

Please advise.  Thanks for help.

          Followup:

I asked Sean Dillon, our XML technologist, to take a look at this and here's
what he had to say:

------------------
What version of the database are you using?  Keep in mind, when you use the DOM
parser to put entire documents into memory, memory starts to REALLY get taxed as
documents get larger and larger.  I have not benchmarked this operation, I'd
encourage you to try a couple of different approaches.

First, if you're on Oracle9i Release2 or higher, don't use any of the DBMS_*
packages at all!  Instead, use SQLX.  SQLX is a set of sql operators you can use
in a query to generate XML documents/fragments.  I STRONGLY URGE YOU to search
for SQLX on AskTom for examples and explanations of SQLX.  There's no need to
keep rewriting the explanation, just look it up and check it out.  You can read
all about it in the Oracle9iR2 Database Developer's Guide for XML DB as well
(Oracle Documentation can be found on OTN).

The next step I'd try, if you are generating XML documents to be shared with
others and you're NOT on 9iR2, and the document will not need to be parsed or
whatnot in the transaction you're generating the document from... is basic
PL/SQL.  Write a custom PL/SQL function to create an XML document
programatically in a CLOB, and use the results of the CLOB as your XML document.
 There's more work to be done here, but there's NO reason to incur that huge
in-memory DOM tree if you can avoid it (which is what you'd be using with some
of the DBMS_ packages.

Finally, if SQLX or a programattic PL/SQL approach don't work for you, THEN try
dbms_xmlquery or dbms_xmlgen.  These are what I'd consider last resorts if the
other options don't work.  Try the other approaches, first! :)

Hope that helps!

March 08, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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;
/

      Rating: 4
help with
      partitioned indexes problem  March 08, 2004

Reviewer:  Fernando Sanchez  from Madrid,Spain

Sceanario:

We have a partitioned table with 2 local indexes, having the following default
values (USER_PART_INDEXES) DEFAULT_TABLESPACE_NAME='RECHARGE_10_I'
DEFAULT_INITIAL_EXTEND=10240

We found in Alert_SID.log six times the error 'ORA-01652: unable to extend temp
segment by 10240 in TABLESPACE RECHARGE_10_I' but when relaunching the
partitioning process several times we got the expected result with the
partitions and indexes allocated OK in their right tablespaces.

We don't understand why we don't get the error again without having changed the
tablespace or why the error happened once.

          Followup:

how are you doing this?  a drop+create inside of a single block of code?  a
little more detail please.

      Rating: 4
Giving
      details  March 09, 2004
Reviewer:  fernando
      sanchez  from Madrid, Spain
Executed Code:
--------
    v_str1:='alter table '||p_table_name||' add partition '||p_table_name||
           '_'||p_rango||' values less than (to_date('''||
           p_rango||''',''YYYYMMDD'')) tablespace '||p_tablespace_name;

    dbms_output.put_line(v_str);
    dbms_sql.parse(v_crs, v_str, dbms_sql.native);

    for r_indices in c_indices(p_table_name) loop

      v_str2:='alter index '||r_indices.index_name||'        rebuild partition
'||
             p_table_name||'_'||p_rango||' tablespace '||
             replace(p_tablespace_name,'_D','_I');

      dbms_output.put_line(v_str);
      dbms_sql.parse(v_crs, v_str, dbms_sql.native);

    end loop;

    dbms_sql.close_cursor(v_crs);
    v_retorno:=0;
    return v_retorno;

  exception
    when others then
      init('crear_particion');
      errlib.err_to_defaults('No se ha podido crear la particion
error:'||sqlcode||' '||sqlerrm);

    if dbms_sql.is_open(v_crs) then
       dbms_sql.close_cursor(v_crs);
    end if;

    v_retorno:=1;
    return v_retorno;
---------------

The error occurs when v_str1 is executed and table partition was created but the
local indexes associated  (with default values) that have to be created raise
exception when others with ORA-1652 we told you.

Thanks in advance.
I hope that it's clear but if you need more information please let us know.

March 08, 2004 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.

      Rating: 3
Need your
      help  March 04, 2004
Reviewer:  Devan
       from Bangalore,India
Hi Tom,
In the create type body statement,I came across the clause
"return self as result is pl/sql block or call spec"?How to
use this clause?Does 'self' keyword point to the attributes
defined with in the type declaration?If you have any simple
example, please do write a followup.
Thanks in advance.

          Followup:

It provides a method to create your own constructor, your own procedural logic
to setup the object type:

ops$tkyte@ORA9IR2> create or replace type myType
  2  as object
  3  ( x int,
  4    y date,
  5    z varchar2 (25),
  6    constructor function myType return self as result,
  7    constructor function myType( some_number in number ) return self as
result
  8  )
  9  /

Type created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace type body myType
  2  as
  3
  4  constructor function myType return self as result
  5  is
  6  begin
  7     x := 55;
  8     y := sysdate;
  9     z := 'Hello World';
 10     return;
 11  end;
 12
 13  constructor function myType( some_number in number ) return self as result
 14  is
 15  begin
 16     x := some_number;
 17     y := sysdate+some_number;
 18     z := 'Hello World ' || some_number;
 19     return;
 20  end;
 21
 22  end;
 23  /

Type body created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2          x myType := myType();
  3          y myType := myType(2);
  4  begin
  5          dbms_output.put_line( x.x || ', ' || x.y || ', ' || x.z );
  6          dbms_output.put_line( y.x || ', ' || y.y || ', ' || y.z );
  7  end;
  8  /
55, 04-MAR-04, Hello World
2, 06-MAR-04, Hello World 2

PL/SQL procedure successfully completed.

      Rating: 4
Nice as
      usual  March 05, 2004
Reviewer:  Jim
       from tx,usa
Dear Tom,
In creating types we find "map or order member function"
What is the diff. between "map or order keywords"?.
Please do reply.
Bye!

          Followup:

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96594/adobjbas.htm#459409

      Rating:
      3
Nice  March 08, 2004
Reviewer:  Ram
       from Bangalore,India
Dear Sir,
 Please have a look at the following objects.
 sql>create type address_ty as object(door_no number,streetname
varchar2(30),city varchar2(30));
Now if we create a table as
 create table customer(
            custname varchar2(30),
            address address_ty);
  or
  create table customer(
            custname varchar2(30),
            address REF address_ty);

Are both statements equivalent?
 What role can REF play in the 2nd statement?what is the
difference between two statements?
Please do reply.

          Followup:

no, they are not remotely similar.  the first creates a table capable of holding
a custname and an address (the data of an address).  the second can store a
custname and a POINTER to an address object stored in some other table.

the REF is like a pointer  - it points to an object instance, typically an
instance in another table.

it is "like" a foreing key, only not as good (no RI)

March 08, 2004 Abhinay -- Thanks for the question regarding
      "Problem of the rows getting updated while they are being currently
      displayed on the online screen", version 8.1.7

      You Asked
Hi,
We  are developing an online application and for this there is search button.If
it is pressed it brings some 100 records but on the screen only first 10 records
are shown.When next page button is pressed it bring next 10 records.My problem
is that we decide the number of pages
before opening the cursor, so for 100 records 10 pages(screen) are required.But
in the mean time if some records are deleted say 15 records, now the updated
page count should be 9 instead of 10.But for me its still 10 and when the next
page button after the page 9 is pressed, it gives error beacuse cursor does not
have any record.

Is there a way out to get the latest updated row count of the cursor.

Thanks and Regards
Abhinay Kumar

      and we said...
Here is my answer on this:

Say "Here is page 1 of about 10 or so pages (maybe more, maybe less)"

Approximate the number pages, don't be definitive about it and you CANNOT GO
WRONG.

I do it here.

Google does it.

All of the search engines do it.  It is not only acceptable -- its the only way
to do it

a) quickly
b) in a scalable fashion
c) without wasting (utterly and totally wasting) a TON of resources

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3489618933902
and if you search for google on there -- click on that search i have and goto
page 3 - see what happens (and then hit previous page and see what happens)

If google can do it -- so can we.

I find people want access to their data.  If you let them paginate, give
them their data, they get over that you don't know exactly how much data their
search will return (the first 10 hits are the most IMPORTANT anyway -- have you
ever actually paged through 10 pages on a google search?  I haven't.)

Review &
      Followup

      Rating: 5
What about
      missing/skipped rows?  March 08, 2004
Reviewer:
      Gianluca Albera  from Torino, Italy
In "getting rows N through M of a result set" your technique, how do you handle
the problem of having rows updated/deleted during pagination?
I mean, you show page 1 (records from 1 to 10) of an ordered cursor coming from
a query and then some one else delete (or update a row in such a way it no
longer appears in the output of your query) a record that for example is in
position 5 of your ordered cursor. When you click next page end show record from
11 to 20, you are actually skipping one record (the one was in position 11
before the delete or update).

I agree with you of not showing the real number of records (so not requiring a
select count(*)) but as a user, if I paginate through a query, I still want to
retrieve all the records and not to skip some of them because other users
changed data while I'm browsing. This is essential in our application in which
if I have 30 rows coming back from a query we need to read them all (and if
someone update or delete one, we may miss that one, but not another one as
showed in the example before)

I have tried with success your suggestion to cache (with an insert in a meta
table) the output from my query and to paginate on it (extremely useful for
long-lasting queries) but I would like to avoid the overhead of those inserts
(furthermore you need to fix an upper bound "a priori" to limit the number of
inserted rows and again I'm not very pleased with this).

I hope you have some suggestions...
Thanks in advance,
    Gianluca Albera

          Followup:

yup, that is an issue -- and unless you use flashback query, one that is
unavoidable.

I "ignore" the issue.  You are on page 1, you are on page 2.

You sound like you might want to pass an SCN from page to page and use "select
.. as of scn :n"

      Rating: 4
Other options
      ....  March 09, 2004
Reviewer:  J. Laurindo
      Chiappa  from Sao Paulo, SP, Brazil
Sometimes I solve this issue doing this : having a "date of last insert/update"
in the table(s), and keep (in a global variable, maybe) the current date when
the paginated search begun. If someone alter a record, the last modif/insert
date will be GREATHER than the date of running search, then display a message
like 'this records were altered, please re-query".

Regards,

 Chiappa

March 09, 2004 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.
Review & Followup

      Rating: 4
what if I don't
      know the data type of the elements?  March 08, 2004

Reviewer:  Leonardo Zacche  from Rio de Janeiro, RJ,
      Brazil
Tom,

Is it possible to modify this function to return a table of unknown?

possible inputs:
a) ' ''Leo'', ''Tom'', ''Others'' ' --> returns a table of varchar
b) '12, 15, 8' --> returns a table of integer

in fact I want to use this function in a IN clause:
eg:
(...) where id_param in str2table('12, 15, 8');
or (...) where login in str2table(' ''Leo'', ''Tom'', ''Others'' ');

thanks a lot
LZ

          Followup:

nope, not going to happen.  you have strings all across the board there.

if you know in a query that it'll be a number:

  select to_number(column_value) from TABLE( str2table( .... ) )

      Rating: 4
seems not to
      work whithin IN clause  March 08, 2004
Reviewer:
      Leonardo Zacché  from Rio de Janeiro, RJ, Brazil
Tom,

thanks or your comments. I'll have 2 str2table functions: one for table of
strings and another for table of numbers.

btw,
see:

======================== START ======================
top@jb_des1> desc TTOPCAT0
 Nome                                                  Nulo?    Tipo
 ----------------------------------------------------- --------
------------------------------------
 ID_CTG                                                NOT NULL NUMBER(2)
 ID_CAG_AE                                             NOT NULL NUMBER(2)
 DE_CTG                                                NOT NULL VARCHAR2(25)
 TS_OPR                                                NOT NULL DATE
 CD_USU_OPR                                            NOT NULL VARCHAR2(10)
 NU_ORD_CTG                                                     NUMBER(2)

top@jb_des1> select * from TTOPCAT0;

    ID_CTG  ID_CAG_AE DE_CTG                    TS_OPR   CD_USU_OPR NU_ORD_CTG
---------- ---------- ------------------------- -------- ---------- ----------
         1          1 NOVATO                    16/02/02 CONVERSAO           1
         2          4 PROTAGONISTA              16/02/02 CONVERSAO           1
         3          4 COADJUVANTE               16/02/02 CONVERSAO           2
         4          4 NOVATO                    16/02/02 CONVERSAO           3
         5          5 NOVATO                    16/02/02 CONVERSAO           4
         6          5 COLABORADOR               16/02/02 CONVERSAO           5
         7          5 SENIOR                    16/02/02 CONVERSAO           3
         8          5 TITULAR                   16/02/02 CONVERSAO           2
         9          5 TOP                       16/02/02 CONVERSAO           1
        10          1                           16/02/02 CONVERSAO           2
        11          2                           16/02/02 CONVERSAO           1
        12          3                           16/02/02 CONVERSAO           1
        13          6                           16/02/02 CONVERSAO           1
        14          7                           16/02/02 CONVERSAO           1
        15          7 GERAL                     16/02/02 CONVERSAO           1
        16          8                           16/02/02 CONVERSAO           1
        17          9                           16/02/02 CONVERSAO           1

17 linhas selecionadas.

top@jb_des1> SELECT * from TTOPCAT0
  2  where NU_ORD_CTG in (3,4,5);

    ID_CTG  ID_CAG_AE DE_CTG                    TS_OPR   CD_USU_OPR NU_ORD_CTG
---------- ---------- ------------------------- -------- ---------- ----------
         4          4 NOVATO                    16/02/02 CONVERSAO           3
         5          5 NOVATO                    16/02/02 CONVERSAO           4
         6          5 COLABORADOR               16/02/02 CONVERSAO           5
         7          5 SENIOR                    16/02/02 CONVERSAO           3

top@jb_des1> select * from ttopcat0
  2  where nu_ord_ctg in str2table('3,4,5');
where nu_ord_ctg in str2table('3,4,5')
                    *
ERRO na linha 2:
ORA-00932: inconsistent datatypes

top@jb_des1> select * from ttopcat0
  2  where nu_ord_ctg in table(str2table('3,4,5'));
where nu_ord_ctg in table(str2table('3,4,5'))
                    *
ERRO na linha 2:
ORA-00936: missing expression

top@jb_des1>
========================= END =======================

Can you see the point I'm mising?

Thanks in advance,
LZ

          Followup:

where nu_ord_ctg in
   ( select to_number(column_value) from table( str2table('1,2,3' ) );

      Rating: 4
non-nested
      table?  March 08, 2004
Reviewer:  Leonardo
      Zacché  from Rio de Janeiro, RJ, Brazil
Tom,

I still can't make it work!
I did what you suggested.
See:

top@jb_des1> select * from ttopcat0
  2  where nu_ord_ctg in ( select to_number(column_value)
  3                        from table( str2table('3,4,5') )
  4                      );
                      from table( str2table('3,4,5') )
                                  *
ERRO na linha 3:
ORA-22905: cannot access rows from a non-nested table item

Forgot to mention: my Oracle is 8.1.6(.3.0). Does it affect?

Thanks,
LZ

          Followup:

depending on the release you may need;

from table ( cast( str2table(...) AS yourTypeName ) )

March
      09, 2004 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.

      Rating: 4
UTL_HTTP2
      sockettype errors  March 08, 2004
Reviewer:  A
      Reader  from CA, USA
Hi Tom:

Some questions on utl_http.
1) Re: UTL_HTTP coverage in Appendix A of your book
   Has anything changed with 9.2.0 release since this chapter was written?
2) Does UTL_HTTP2 work under 10g?
3) I ran into an error in installing UTL_HTTP2. I have executed the install
scripts against a number of 9iR2 instances and the errors are consistent. Has
anyone reported this issue? Any idea why? Thanks.

sys@ORACLE9201> @install
Java created.
CorrelationType
Type created.
CorrelationArrayType
Type created.
CorrelatedArray Spec
Type created.
CorrelatedArray Body
Type body created.
No errors.
Package created.
No errors.
Package body created.
Type created.
No errors.
Warning: Type Body created with compilation errors.
Errors for TYPE BODY SOCKETTYPE:
LINE/COL ERROR
-----------------------------------------------------------------
92/63    PLS-00593: default value of parameter "P_PORTNO" in
                    body must match that of spec

SQL*Plus: Release 9.2.0.1.0 - Production on Mon Mar 8 17:11:14 2004

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Enter password:

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

sys@ORACLE9201> begin
  2  print (utl_http2.request('
http://www.amazon.com/'
);
  3  end;
  4  /
begin
*
ERROR at line 1:
ORA-04068: existing state of packages has been discarded
ORA-04063: stored procedure "SYS.SOCKETTYPE" has errors
ORA-06508: PL/SQL: could not find program unit being called
ORA-06512: at "SYS.UTL_HTTP2", line 184
ORA-06512: at "SYS.UTL_HTTP2", line 212
ORA-06512: at "SYS.UTL_HTTP2", line 19
ORA-06512: at line 2

It seems to be choking on this:
member procedure initiate_connection( p_hostname in varchar2, p_portno in number
default 119)

Any help would be very much appreciated. Thanks.

          Followup:

1) absolutely, utl_http in 9i does what my utl_http2 in 8i did

2) i haven't tried it as utl_http has more than sufficient capabilities in the
9i/10g line

3)
LINE/COL ERROR
-----------------------------------------------------------------
92/63    PLS-00593: default value of parameter "P_PORTNO" in
                    body must match that of spec

should be the give away -- apparently i have a bug in my code that was
acceptable in 8i but not 9i.  look at the spec of that type - see what default I
used there and make sure I'm using the same (or any) in the body.

      Rating: 5
THANKS a lot,
      Tom!!!  March 09, 2004
Reviewer:  A Reader
       from CA, USA

March 09, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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.

      Rating:
      4
replication  March 09, 2004
Reviewer:
      Tarun Babu  from India
Hi Tom,
     We have our database x at a different location and y database at our
location, which is development database and we are testing all the proc,pkgs and
forms. We need two table t1 and t2 along with entire data from our x database
into y. There is no dblinks etc. I am wondering how to get these table to y
database. Can we generate any script file from it? Please help.

          Followup:

exp userid=u/p tables=(t1,t2)

ftp the file (in binary mode) to them

let them imp it.  (read about export/import in the server utilities guide)

      Rating: 4
replicate or
      not  March 11, 2004
Reviewer:  Tarun
       from India
Thanks tom for the reply.
I got the tables, by exp/imp.

March 09, 2004 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"

      Rating: 4
Reading 9i
      docs.  March 09, 2004
Reviewer:  Alvin
       from Philippines
As per 9i docs.
---8<---

For shared server, each session using a dispatcher is routed to a shared server
process, and trace information is written to the server's trace file only if the
session has enabled tracing (or if an error is encountered). Therefore, to track
tracing for a specific session that connects using a dispatcher, you might have
to explore several shared server's trace files.

The DBMS_SESSION and DBMS_SYSTEM packages can also be used to control SQL
tracing for a session.

---8<---

What does the above mean ?

Since 1 dispatcher can handle several sessions.... when i trace 1 particular
session via 'alter session set sql_Trace=true;' will all of the sessions under
that dispatcher be traced as well ?

          Followup:

tracing is a "session thing" when you use alter SESSION -- it is not a process
thing.

so no, it doesn't trace everything going thru that dispatcher -- just the
SESSION that enabled it will be trace

March 09, 2004 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.

      Rating:
      3
ok  March 09, 2004
Reviewer:  Ram
       from Bangalore,India
Dear Tom,
How to get Oracle SID?Does it default to the database_name?

          Followup:

  1* select instance_name from v$instance
ops$tkyte@ORA9IR2> /

INSTANCE_NAME
----------------
ora9ir2

      Rating: 5
Tom has nerves
      of steel   March 19, 2004
Reviewer:  Sam
       from Atlanta GA, USA
I admire not only your expertise, but - even more - your patience! :)

March 09, 2004 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

      Rating:
      3
OK  March 09, 2004
Reviewer:
      Gerhard  from Dusseldorf,Germany
Dear Sir,
I would like to declare a variable in a package and upon
each call it must be incremented.But the variable is resetting itself to its
initialized value after each call.
How to get around with this?Any solution you have?
 for example
  sql>exec dbms_output.put_line(mypack.i)//put as 1
  sql>exec dbms_output.put_line(mypack.i)//put as 2
  sql>exec dbms_output.put_line(mypack.i)//put as 3
Is it possible to declare a static variable in Oracle?
Please do reply.
Bye!

          Followup:

put it in the package body, not in the procedure itself (eg: make it global)

ops$tkyte@ORA9IR2> create or replace package demo_pkg
  2  as
  3          function f return number;
  4  end;
  5  /

Package created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace package body demo_pkg
  2  as
  3          g_global_number number := 0;
  4
  5          function f return number
  6          is
  7          begin
  8                  g_global_number := g_global_number+1;
  9                  return g_global_number;
 10          end;
 11
 12  end;
 13  /

Package body created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec dbms_output.put_line( demo_pkg.f );
1

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec dbms_output.put_line( demo_pkg.f );
2

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec dbms_output.put_line( demo_pkg.f );
3

PL/SQL procedure successfully completed.

March
      09, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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...

      Rating: 3
Internal
      Implementation  March 08, 2004
Reviewer:
      Dhrubo  from Kolkata,INDIA
Hi Tom
I have some basic questions regarding IN OUT implementation
My understanding says --
OUT
---
"Inside the program, an OUT parameter acts like a variable that has not been
initialized. In fact, the OUT parameter has no value at all until the program
terminates successfully (without raising an exception, that is). During the
execution of the program, any assignments to an OUT parameter are actually made
to an internal copy of the OUT parameter. When the program terminates
successfully and returns control to the calling block, the value in that local
copy is then transferred to the actual OUT parameter. That value is then
available in the calling PL/SQL block. "
IN
--
"IN Parameter is basically a protected pointer."

So why is this kind of implementation?Because in case of OUT I feel that has
memory overhead and is a complex way of doing thing.In case of IN we could have
just passed by value.Please explain

          Followup:

in -> pointer passed, data is not copied

in out -> value is copied to a temporary, temporary is available read write in
the procedure, temporary copied to value upon successful return

out -> temporary is assigned NULL, temporary is used in routine, upon success
temporary is copied to output.

ops$tkyte@ORA9IR2> create or replace procedure p( p_in in number,
  2                                 p_in_out in out number,
  3                                 p_out out number,
  4                                 p_fail in boolean )
  5  is
  6  begin
  7          dbms_output.put_line
  8          ( 'on the way in ' || p_in || ',' || p_in_out || ',' || p_out );
  9          p_in_out := p_in;
 10          p_out    := p_in;
 11          if ( p_fail )
 12          then
 13                  raise program_error;
 14          end if;
 15          dbms_output.put_line
 16          ( 'on the way out ' || p_in || ',' || p_in_out || ',' || p_out );
 17  end;
 18  /

Procedure created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2          l_in     number := 55;
  3          l_in_out number := 100;
  4          l_out    number := 200;
  5  begin
  6          dbms_output.put_line
  7          ( 'before ' || l_in || ',' || l_in_out || ',' || l_out );
  8          begin
  9                  p( l_in, l_in_out, l_out, TRUE );
 10          exception
 11                  when others then null;
 12          end;
 13          dbms_output.put_line
 14          ( 'after call 1 ' || l_in || ',' || l_in_out || ',' || l_out );
 15          p( l_in, l_in_out, l_out, FALSE );
 16          dbms_output.put_line
 17          ( 'after call 2 ' || l_in || ',' || l_in_out || ',' || l_out );
 18  end;
 19  /
before 55,100,200
on the way in 55,100,
after call 1 55,100,200
on the way in 55,100,
on the way out 55,55,55
after call 2 55,55,55

PL/SQL procedure successfully completed.

so, IN is passed by reference (no copy, fast).

OUT is returned by VALUE (copy upon success)

IN OUT is passed by VALUE (copy on way in, copy on way out)

and OUT/IN OUT can be modified sometimes with the NOCOPY hint

      Rating: 4
Why OUT is
      implemented in that way?  March 08, 2004
Reviewer:
      Dhrubo  from Kolkata,India
Hi TOM,
      I now understand why we implemented IN as a protected pointer but you gave
no explanation about why OUT is implemented in that fashion.Isn't copying slow
and that method  entails higher memory overhead ( though memory is cheap these
days) am i too curious?
Will be highly enlightened by your explanations

          Followup:

it is the very defintion of the langage (borrowed from Ada).

your OUT parameter is done conceptually like this:

      set out_parameter to null
      call procedure and get temporary back
      if success then out_parameter := temporary;

it makes for predicable code, if procedure "fails", your data is in a known
state.

see this example:

ops$tkyte@ORA9IR2> create or replace package demo_pkg
  2  as
  3          procedure p1( x out number, fail in boolean, fail2 in boolean );
  4          procedure p2( x out NOCOPY number, fail in boolean, fail2 in
boolean );
  5  end;
  6  /

Package created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace package body demo_pkg
  2  as
  3
  4  procedure p1( x out number, fail in boolean, fail2 in boolean )
  5  is
  6  begin
  7          x := 1;
  8          if (fail) then raise program_error; end if;
  9          x := 2;
 10          if (fail2) then raise program_error; end if;
 11          x := 3;
 12  end;
 13
 14  procedure p2( x out number, fail in boolean, fail2 in boolean )
 15  is
 16  begin
 17          x := 1;
 18          if (fail) then raise program_error; end if;
 19          x := 2;
 20          if (fail2) then raise program_error; end if;
 21          x := 3;
 22  end;
 23
 24  end;
 25  /

Package body created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2          x number  := 55;
  3  begin
  4          begin demo_pkg.p1( x, false, false ); exception when others then
null; end;
  5          dbms_output.put_line( 'p1 f,f ' || x );
  6          x:=55;
  7          begin demo_pkg.p1( x, true, false ); exception when others then
null; end;
  8          dbms_output.put_line( 'p1 t,f ' || x );
  9          x:=55;
 10          begin demo_pkg.p1( x, false, true ); exception when others then
null; end;
 11          dbms_output.put_line( 'p1 f,t ' || x );
 12          x:=55;
 13
 14          begin demo_pkg.p2( x, false, false ); exception when others then
null; end;
 15          dbms_output.put_line( 'p2 f,f ' || x );
 16          x:=55;
 17          begin demo_pkg.p2( x, true, false ); exception when others then
null; end;
 18          dbms_output.put_line( 'p2 t,f ' || x );
 19          x:=55;
 20          begin demo_pkg.p2( x, false, true ); exception when others then
null; end;
 21          dbms_output.put_line( 'p2 f,t ' || x );
 22          x:=55;
 23  end;
 24  /
p1 f,f 3
p1 t,f 55
p1 f,t 55
p2 f,f 3
p2 t,f 1
p2 f,t 2

PL/SQL procedure successfully completed.

Now, p2 (which uses the "unsafe" nocopy -- pass by reference) returns
"unpredicable" results where as the p1 procedure returns consistent, expected
results.

March 09, 2004 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

      Rating: 4
different RBS
      for different DMLS?  March 09, 2004
Reviewer:
      A reader
Tom,

I remeber reading on this site somewhere that we can have different Rollback
Segments for different DML operations. Does it mean that only these transaction
will be writing redo info on these RBS and no other transaction can write on
this? Is it possible? and how if true?
Can you show it with a small example?

Many thanks.

          Followup:

no, that is not true.

March 09, 2004 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.

      Rating: 4
Spatial
      Objects. Where are they?  March 08, 2004
Reviewer:
      Alex  from Minsk, Belarus
Hello.
You are talking a lot about different types of geometries.
Could you please tell me if it possible to visualize them?
(I just want to find a program which could draw Oracle Spatial Objects. It would
be wonderful if it could work with Oracle 8i in Windows 2000)
And one more question: is it possible to assign a label and color to a Spatial
Object?
Thank you in advance.

          Followup:

there is a mapviewer included with iAS (app server).  it visualizes spatial data
on a map.

      Rating: 5
Does violate
      1NF and Normalization is mispracticed (not overrated)  March
      09, 2004
Reviewer:  Shouvik  from India
Please refer to the following link, to get a definition of 1NF.

http://databases.about.com/library/weekly/aa080501a.htm
It says:
Recall that 1NF sets the very basic rules for an organized database:
· Eliminate duplicative columns from the same table.
· Create separate tables for each group of related data and identify each row
with a unique column (the primary key).

and then probably goes on to describe the problem described here.

<Aru>
use it in their linear-program optimization calculations.
</Aru>
Tell me if they wanted to use several of Oracle's functions, Regression, best
fit, etc, won't they need the points in row format.

In college we used to learn Navier Stoke's equation in fluid mechanics. Every
other flu-mech equation is a special case of it and all of its terms are never
needed. Does that mean it is overrated.

I would advice please normalize and denormalize if you think necessary. Never
leave it unnormalized or says "I donot care what the heck normalization is"

Normalization is a theory. Use it and donot use it you cannot use it (with
existing computing power). But donot despise it.

Tom please take back the word "overrated".

Boolean Algebra was introduced long before Computers came into being. I could
not be practised fully then. Any theory which could not be practised at any
given time should not be termed as "underrated".

          Followup:

nope, won't take it back.

if you use it as a mantra, it is overrated.

Look, I was just making a point -- anything taken to an extreme is just that --
something taken to an extreme.

do you always normalize?
no.

just take it for what it was, a comment.

      Rating: 5
this is the
      link I meant  March 09, 2004
Reviewer:
      Shouvik  from India
http://databases.about.com/library/weekly/aa081901a.htm

      Rating: 4
Re: 'I thought
      similarity is obvious. '  March 09, 2004
Reviewer:
      Andy Hardy  from Loughborough,UK
The similarity is obvious to you, because you are inferring something about the
data structure based on the names of the columns or you have some underlying
knowledge of the data.

We don't.

In both cases, someone has stated that their requirement is that they store x
pieces of data against a single entity. Nothing more, nothing less.

March 09, 2004 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

      Rating: 4
something
      confusing...  March 09, 2004
Reviewer:
      Alexander  from Ukraine
Hi, Tom!
I have a question I was led to with help of sequence.

1. Such type was created:

create or replace type someTest#typ as object
(
  atr_1 number,
  atr_2 number,
  atr_3 number,
  ...
  atr_N number,

  constructor function someTest#typ RETURN SELF AS Result

)

create or replace type body someTest#typ is

  constructor function someTest#typ RETURN SELF AS Result is

  begin
     select sometest#typ_s.nextval into atr1
     from dual;
     atr_2:=atr_1;
     atr_3:=atr_2;
     ...
     atr_N:=atr_N-1

     return;
  end someTest#typ;

end;

2. A table:
create SomeTest#tbl;

3. Inserting a record:
insert into sometest#tbl
values(sometest#typ());

insert into sometest#tbl
values(sometest#typ());

insert into sometest#tbl
values(sometest#typ());

4.After select * from sometest#tbl; I get:

ATR1 ATR2 ATR3 ATR4 ATR5
---- ---- ---- ---- ----
   2    3    4    5    6
   8    9   10   11   12
  14   15   16   17   18

It seems that constructor raises as many times as attributes in my type or
something like that...
Could you please explain such effect

Thanx!

          Followup:

Here is the workaround (only happens in the insert ... values() )

ops$tkyte@ORA10G> declare
  2          x sometest#typ := sometest#typ();
  3  begin
  4          null;
  5          dbms_output.put_line( x.atr_1 );
  6          dbms_output.put_line( x.atr_2 );
  7  end;
  8  /
1
1

PL/SQL procedure successfully completed.

ops$tkyte@ORA10G> create table SomeTest#tbl ( y char(1), x sometest#typ );

Table created.

ops$tkyte@ORA10G> insert into sometest#tbl values( 'a', sometest#typ() );

1 row created.

ops$tkyte@ORA10G> insert into sometest#tbl values( 'b', (select sometest#typ()
from dual) );

1 row created.

ops$tkyte@ORA10G> select * from sometest#tbl;

Y
-
X(ATR_1, ATR_2)
---------------------------------------------------------------------------------
--------------------------------------------------
a
SOMETEST#TYP(3, 4)

b
SOMETEST#TYP(5, 5)

refer to bug 1793798 on metalink

March 09, 2004 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://download-east.oracle.com/docs/cd/A81042_01/DOC/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.

      Rating: 3
Redo while
      DELETING  March 09, 2004
Reviewer:  Prasad
       from UK
Hi Tom,

We are doing an archive operation where we are selecting and then deleting a few
million records.we are commiting after every 0.1 million rows (I know you will
dislike this :o)
Now this delete process generates a huge redo. We are in archive log mode on
Oracle 8.1.7.4 (HP 11).
Last friday according to Production DBAs, this archiving process spawaned 1000s
of archive logs and the DB was crashed.
so i need your suggestions on this please.

An early responce (as usual) will be highly appreciated.

Thanks,
    Prasad

          Followup:

it would not have crashed the database.

perhaps your dba's failed to provide sufficient space for the archives (and the
database, in a selfless act of self preservation -- stopped doing work -- until
the dba's freed sufficient space).

perhaps your dba's did not get the archives off of the system fast enough

but I doubt the system crashed due to high archive generation

you might consider partitioning (and just truncating a partion to purge data)
or
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:890462882545
use a CTAS to copy the rows to keep and just drop the old table

those are all "no redo generated if you like" options.

      Rating:
      4
  March 09, 2004
Reviewer:  Prasad

hi Tom,

As usual you are very right.The DB didn't crash, it sort of hanged.
I'm doubtfull about the CTAS option as monthly i delete about 30m rows & that
table has around 500m rows.so with CTAS i'll have to create a table with 470m
data. so again that will be time consuming, won't it?

But we've decided to partition these tables in near future.
So shudn't be a problem after that.

But plese give your valuable opinion about CTAS in my case.

Many thanks again.
            Prasad

          Followup:

depends -- it could be less time to create 470m rows without
logging/undo/indexes then to delete 30million indexed rows.

benchmark it on a couple million rows, see what you see.

March 09, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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://download-west.oracle.com/docs/cd/B10501_01/server.920/a96653/createls.htm#1035815
has the restrictions all listed..

      Rating: 5
Logical standby
      question  January 23, 2004
Reviewer:  SR

Tom,

here is the situation I hope you can help me understand

We have EMC process which cuts a business continuity volume (bcv) for us.
Production database synced, broken and detached. Its like a instance recovery
process on bcv. Using the onlines logs the instance is recovered on the bcv and
mounted. At this point this database is as good as production (except it is
slightly behind).

Now this is what I want to do

1) keep all (most may be) production based tablespaces in sync with production
(meaning the ability to apply archive log, not necessarily at pace with time) -
Thus I want to keep them for disaster recovery purpose. 1 day behind is fine,
just they shoud be able to rollforward.

2) I have report users who would like to build some indexes and create some
tables in this database. I was hoping I will provide them a brand new tablespace
and let them do what ever in it without touching any bit from rest of
tablespaces (system, rbs I am not sure how this will work)

3) while system is operational, use RMAN COPY command or best if just O/S copy
to copy data files (as #1 means the files are really not writeen to since files
are recoverable using prod archive logs)

4) Since, the bcv will be cut once a day and I really don't plan on applying
redologs for rest of the day - user report/index is all this instance will be
used (of course for disaster this will be a fallback - we have a seperate
standby which is in sync with prod with .25 delay)

I was hoping Logical Standby works for me, because traditional 'alter database
open read only' won't allow table/index creation. In short, Logical Standby will
be for two reasons (1) allow creation of indexes, tables (2) allow backups. If
this works, its a pot of gold! What do you think? I won't be applying any
archive logs even though is Logical Standby thus performance hit won't be an
issue.

I really really appreciate your comments.

Again, thanks in advance and hope to hear from you at your convenience.

Regards

          Followup:

why would you use bcv's for this?

You just described a standby database - we do this out of the box, this is
supplied functionality.  There is a logical standby which allows the redo to be
applied via sql to the standby and users can in fact create additional
structures like MV's and indexes.

bcv won't have anything to do with this situation.

Also, remember, a DR site should be miles away from production -- not "bcv"
distances away.

you WILL be applying archive logs.

      Rating: 5
my original
      question  January 24, 2004
Reviewer:  SR

Tom,

I understand your concerns on bcv and why do it etc.
The situation is that way for now. I am looking for a step in correct direction
by using the Logical Standby (LS).

Lets even forget about the fact its bcv. Its a recovred database - ready to be
opened (and then it will not be same as production) OR kept at mount and keep
applying redo logs OR just simply use it to take backup.

My goal is to use it such that 1) users can do indexes, tables 2) I can still
take backup (knowing its still useful for production recovery).

And from your answer it seems like Logical Standby will do this - correct? I
will not fork over any more redo logs, but thats ok. Because this feature of
Logical Standby is what I am most interested in (1) ability to open database (2)
you can still use datafiles of LS over at prod. That's all.

No, its not really all things are done right here. But I am just looking for
this thing to work. Then I will look at doing it "right" as you said by thinking
through it.

Again, thanks for your help

          Followup:

You need to read the data guard docs.  they tell you step by step what you need
to do in order to set this up.

You CANNOT use datafiles of logical (it is logical, not a physical copy) on the
production box.  You will be backing up production in the same old fashion.

      Rating: 5
Tom,
      thanks  January 24, 2004
Reviewer:  SR

That turned out to be a little bit of disappointment for me. I read the document
and like you mention, its a new database, just with data integrity as original.

I think it will be so much interesting if I can make my data tablespaces
readonly and open the database (I know it doesn't work that way now) - however I
don't want to lose the production connection of these tablespaces. Is there any
option?

Again, thanks for the information

          Followup:

You would have to use a physical standby with can be opened read only -- but it
will be ACCUMULATING redo logs not applying them (so it is point in time) and
you cannot create any auxillary structures like indexes and mvs since -- well --
it is a PHYSICAL copy.

You'll need to figure out which is more important to you

a) restore to production from standby
b) add additional structures to standby

A and B are mutually exclusive by their very implementations.

      Rating: 2
Logical Standby
      Database  March 09, 2004
Reviewer:  Earl
      Coombs  from USA
Hi Tom,
None of your links work anymore.  When I click on any of your links like :

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96653/createls.htm#1035815
I get forwarded to the index page:

http://otn.oracle.com/documentation/index.html
This is not good!

Earl

          Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96653/create_ls.htm#1073769

March 09, 2004 Tom -- Thanks for the question regarding
      "Describing a MEMBER of a package by non-owner", version 8.1.7

      You Asked
I create a package "p", owned by user "packager_owner".  Package p has member m.
 I can do this, while logged in as package_owner:

describe p.m

and it gives me the calling sequence description.

But, if I log in as another user "other_user" who has been granted execute
permission on the package "p", if I do:

describe package_owner.p.m

it says it never heard of it.  I also tried creating a public synonym for
package_owner.p and then do:

describe p.m

but it still does not find it.

I noticed if I do:

  grant all on p to other_user

then I can successfully describe p.m as other_user, but only in SQL Worksheet.
It does NOT work in sqlplus.

Any ideas on how the non-owner can describe a member of a package it does not
own?  (I understand I can describe the package as a whole, but, programatically,
I need to be able to describe the member).

Thanks,
Tom B.

      and we said...
Well -- I don't use sql "worksheet" personally -- prefering greatly sqlplus to
pretty much all other tools...

In plus, you can only describe the package -- desc p.m doesn't work there (not
since 7.3 anyway).

You can always query ALL_ARGUMENTS:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select argument_name, data_type from
  2  all_arguments where owner = 'OPS$TKYTE' and package_name = 'PKG' and
object_name = 'M';

ARGUMENT_NAME                  DATA_TYPE
------------------------------ ------------------------------
X                              NUMBER

Perhaps that will satisfy your programatic needs?

Review &
      Followup

      Rating:
      4
  June 23, 2002
Reviewer:  aya
       from egypt
describe us the steps for making the packger?

          Followup:

I assume you mean the Oracle Software packager (which has nothing whatsoever to
do with this question)

You goto:

http://otn.oracle.com/docs/products/osp/content.html
You download the pdf, you follow the instructions....  chapter 7, a tutorial,
might be of interest to you.  It has step 1 .. step 8 laid out from start to
finish.

      Rating:
      4
  June 23, 2002
Reviewer:  aya

how can i use  the packager?

      Rating:
      5
  June 27, 2002
Reviewer:  R Diab

hi Tom,
please i want u to describe how can i make my data base portable using software
packager?
i.e.... i follow the instructions  in chapter7  but no result?

          Followup:

I've never used it.  It does not make your database "portable".  It is a tool to
take software you've developed and create an installable version of it.

I'll say again "but no result" is as vague as your initial "it does not work" in
the other question you tagged this on.

It has the step by steps right in the docs -- step 1, step 2, step 3 and so on.
If you have followed those and they do not work -- then you have an excellent
reason to contact support.  With something like "but no result" there is quite
simply NOTHING I can personally do.

      Rating: 5
manual
      database  June 27, 2002
Reviewer:  RDiab
       from egy
Dear Tom,
please say to me how can i make my database manualy ?
i want to install my database in adifferent machine , but manualy .
i wiat your response.

          Followup:

You read the ADMIN guide for that platform -- every platform has such a guide.
For example on windows, you might read:

http://download-west.oracle.com/docs/cd/A87860_01/doc/win.817/a73008/ch5.htm#1004964
for example.

      Rating: 5
Image on the
      web  June 27, 2002
Reviewer:  Diab
       from egy
Dear Tom,
i design my database it contain a field called image .
i want every one connected to my database on the web to add his photo from his
client from browser, i use the function read_image_file, but it get the
imagelocation from my server not from his client ,? what is the answer for this
problem?
thank u

          Followup:

Diab,

as the form you used to submit this says "please don't use this area to ask a
new question"  This has nothing to do with anything on this page.  Nothing.

      Rating: 3
Finding owner
      of the current package  March 09, 2004
Reviewer:
      robert  from ct
tom,
Is there a way from WITHIN a procedure/function inside a package to get the
value of the owner of the package ?

Thanks

          Followup:

dbms_utility.format_call_stack

http://asktom.oracle.com/~tkyte/who_called_me/index.html

March 09, 2004 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

      Rating: 3
Export 8.1.7
      tables from 9.0.1.1.1  March 09, 2004
Reviewer:
      Denni50  from NA
Hi Tom

Recently installed 9.0.1.1.1 and imported 8.1.7 table(s).

I'm getting ready to upgrade to 9iR2 but first want to
export the 8.1.7 tables that were imported to 9.0.1.1.1
just in case something goes wrong.

I keep getting the following errors:

EXP-00008
ORA-06550
PLS-00201
EXP-00083...they all seem to be related to the SYS.LT_EXPORT_PKG.

question:
when importing tables to a higher version are there scripts
that should be run to bring the tables up to the same version.

PLS-00201 states: SYS.LT_EXPORT_PKG must be declared.
Is this a package that needs to be compiled?

The export seems to be working...it went through the above
error messages but then started the export identifying
user schema...table...etc.

thanks for any insight.

          Followup:

GRANT EXECUTE ON sys.lt_export_pkg TO exp_full_database

as SYS

      Rating:
      3
Tom....  March 09, 2004
Reviewer:
      Denni50  from NA
here's what I found:
The package is invalid in the system...how it happened is
anyone's guess. I believe I read somewhere there is an
oracle bug #2448243 on this issue and some company has come up with a
workaround.....anyhow...I looked at the other
SYS.packages and they do not have a ";" at the end.

I logged in as user/pwd as sysdba and tried to remove
the ";" at the end of the package body code but it won't
let me change it.

I never ever go into sys and "play" around with any of
sys' objects...so..is there a way for me to get this
package to a valid status.

SQL> ed
Wrote file afiedt.buf

  1  select owner, status, object_name from dba_objects
  2* where object_type='PACKAGE BODY' and object_name='LT_EXPORT_PKG'
SQL> /

OWNER                          STATUS
------------------------------ -------
OBJECT_NAME
--------------------------------------------------------------------------------
SYS                            INVALID
LT_EXPORT_PKG

Line # = 4859 Column # = 1 Error Text = PLS-00103: Encountered the symbol ";"

          Followup:

I don't see anything about that , i would not say it looks like #2448243, if you
can, you should file with support for this one.

      Rating: 3
one last
      thing...  March 09, 2004
Reviewer:  Denni50
       from NA
the export completed in spite of the SYS.LT_EXPORT_PKG.
it terminated with:
"export completed successfully with warnings"...so I'm
assuming I have a good,valid export...correct?

          Followup:

you can do an imp show=yes to see if it "works", if the file is readable

      Rating: 3
thanks
      Tom...  March 10, 2004
Reviewer:  Denni50
       from NA
I'm just going to upgrade to 9iR2 and hopefully these
"bugs" will resolve themselves.

have a good day..

btw...here's the company quote on this issue:

"The ####### post-install script implements a workaround for Oracle bug id#
2448243 which occurs during full database exports. This bug causes ORA-1031 and
EXP-00008 when executing SYS.LT_EXPORT_PKG during a full database export.
According to Oracle Metalink, this bug is not scheduled to be fixed until Oracle
10, thus the workaround allows full functionality of the Export/Import utilities
in the meantime."

March 09, 2004 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.

      Rating: 3
But how do I
      workaround ORA-01555?  March 09, 2004
Reviewer:
      Dan Loomis  from Raleigh, NC
We have a long running cursor loop that updates specific process data (flag,
message, date) once the row is moved from stage to base.  For *long* running
processing this results in ORA-01555, so other than removing the cursor loop
completely and/or chunking up the table into smaller pieces (resulting in
smaller, quicker loops), what's the best approach?  Here's an example:

declare

        --'U' are all unprocessed stage records
    cursor         l_cursor is
        select * from big_stage_table where process_flag = 'U';

    l_record     l_cursor%ROWTYPE;
    l_count        number;

begin

    --dbms_output.put_line( 'opening cursor' );
    open l_cursor;

    --dbms_output.put_line( 'fetching first row' );
    fetch l_cursor into l_record;

    l_count    := 0;

    while l_cursor%found loop

        l_count := l_count + 1;

        insert into big_base_table (
            owner,
            object_name,
            subobject_name,
            object_id,
            data_object_id,
            object_type,
            created,
            last_ddl_time,
            timestamp,
            status,
            temporary,
            generated,
            secondary
        )
        values
        (
            l_record.owner,
            l_record.object_name,
            l_record.subobject_name,
            l_record.object_id,
            l_record.data_object_id,
            l_record.object_type,
            l_record.created,
            l_record.last_ddl_time,
            l_record.timestamp,
            l_record.status,
            l_record.temporary,
            l_record.generated,
            l_record.secondary
        );

        --Set the processing details, which results in ORA-01555 eventually
                --'P': Processed
        update     big_stage_table
        set     process_flag = 'P',
                process_msg = 'Row processed.',
                process_date = sysdate
        where    object_id = l_record.object_id;

        if (l_count = 10000) then

            commit;
            l_count := 0;

        end if;

        --dbms_output.put_line( 'fetching next row' );
        fetch l_cursor into l_record;

    end loop;

    commit;

    --dbms_output.put_line( 'closing cursor' );
    close l_cursor;

    commit;

    exception
        when others then

            --dbms_output.put_line( 'error! -> ' || sqlerrm );

            if ( l_cursor%isopen) then
                close l_cursor;
            end if;

end;
/

My inclination is to decouple the updating of the processing data with the main
loop, but that would result in a separate table that I would merge that data
into big_stage_table after the loop completes.  Is that the right track, or is
there another, better approach?

Thanks in advance.

          Followup:

why not

update big_stage set processed_flag = 'in process'
insert /*+ append */ into table select * from big_table where processed_flag='in
process';
update big_stage set processed_flag = 'done'..... where processed_flag = 'in
process';
commit;

it'll most like run so much faster then this row by row thing that the 1555 will
just take care of itself

(else you need larger rbs's, that is what 1555 means at the end of the day)

      Rating: 3
Re: But how do
      I workaround ORA-01555?  March 09, 2004
Reviewer:
      Dan Loomis
I completely agree that your approach is best.  But, for argument's sake, let's
say one bulk insert statement is not feasible.  For each row in big_stage_table
there are many, many complex validations/tranformations that happen before the
data is ultimately inserted/updated into base, which cannot be boiled down
easily into a few statments.

So, assuming we *have to* process row by row (and can't increase undo retention
any more) how can I best avoid ORA-01555 from a code perspective, using the
example above?

          Followup:

I gave you two workarounds

a) go faster
b) make rbs bigger <<<----

and I'll add #3

c) don't commit. (but that'll probably imply b)

short of copying the data out of big stage into a table all for your own use....

      Rating: 4
Close and
      Re-open cursor  March 10, 2004
Reviewer:
      Gary  from Sydney, Aus
In the example given, another option is to still commit every 10,000 but close
the cursor before the commit and re-open it after.

This should work in this case because
a) It isn't using a FOR cursor loop so it can be explicitly closed and re-opened
b) The update on the table (setting the process flag to 'P') and the query on
the cursor (process_flag 'U') means you won't re-process the already processed
rows

Closing and re-opening the cursor will have a performance penalty (small if
process_flag is indexed).

Tom's update/insert/update will still give better performance though.

      Rating: 4
Another
      solution   March 24, 2004
Reviewer:  Sam
       from Modesto, CA. USA
Another solution that we have used is to use Global Temporary Tables.  We
re-wrote some ETL processes for an ODS that did row by row processing and were
very slow.  The processing window was reduced by about 75%.  By this I mean one
hour processing time before now takes 15 minutes.

•    Create a Global Temporary Table (preserve rows on commit) that includes all
columns in your big_base_table.  If necessary include additional columns to
control the process or for interim calculations.
•    Insert a set of the available data from your big_stage_table, 200,000 or
more rows.
•    Use SQL statements to select the additional data needed to complete the
record.
•    Commit to free up your rollback.
•    Update rows in big_base_table where the PK exists
•    Insert rows into big_base_table where the PK does not exist
•    Or use Merge to do both at once.
•    Update the processed status of these rows in  big_stage_table
•    Commit to end the transaction.
•    Truncate the GTT
•    Begin with next set of records

You can use a bulk collect query into a table object to dynamically calculate
what the ranges should be to use in your initial insert into the global
temporary table.  Use a loop to work through the object storing the begin and
end values and include then as variables in the select statement.
This assumes that you have an easily identifiable Primary Key for the update
back to big_stage_table, or you can include the row id from big_stage_table.
Use SQL%ROWCOUNT to keep track of how many rows have been inserted/updated at
each step and you can add error checking to be sure that the number of rows
processed equals the number updated as processed in big_stage_table.
You can also create indexes on the global temporary table.  You need to
understand your data and how the update queries might benefit.  Could help or
hurt processing speed.  Usually helps with the GTT fields that will become PK in
big_base_table.

March 09, 2004 Juan Carlos -- Thanks for the question regarding
      "temporary tablespace WORKAREA_SIZE_POLICY", version 9.2

      You Asked
Hi Tom,
Thank you for the last paper, but I still
Don´t have clear how to size the temporary tablespace,
in theory it should be a multiply of the SORT_AREA

PGA_AGGREGATE_TARGET = 100M
WORKAREA_SIZE_POLICY = AUTO

With this two parameters how would you set
the  size for the tablespace ?

and the  LOCAL UNIFORM SIZE for this I set to 64k is this right?

Thank you

      and we said...
Went to the developers on this and they (one worked on the pga agg. tgt and the
other on LMTS) say

....
If you use TEMPORARY tablespaces, 1M extent size  is usually good enough (which
is a default for Locally Managed Temporary tablespaces). If the application uses
temporary  tables a lot, the extent size could be smaller, say 256K, since every
usage requires at least one extent.  In this case, it is even more important to
use locally managed tablespaces - dictionary managed one, even if it is
TEMPORARY, may not be able to handle too many small extents as well.
.........

(which makes me feel good -- my gut said "at least 256k and a power of 2 above
that")
Review
      & Followup

      Rating:
      5
  September 27, 2002
Reviewer:
      A reader
Thank you

      Rating: 5
Calculating
      Uniform Size  September 27, 2002
Reviewer:  Jan
      van Mourik  from Houston, TX
Tom,

note 188846.1 (Optimising performance with temporary tablespaces) on Metalink
gives us this formula:

- Calculate the SIZE parameter of the EXTENT MANAGEMENT LOCAL UNIFORM SIZE
clause as:

(n*s + b)

n = integer
s = value of SORT_AREA_SIZE
b = value of DB_BLOCK_SIZE

Should we still do that in 9i?

          Followup:

Only if you are using sort_area_size

      Rating: 3
dont have to
      worry about SORT_AREA_SIZE and HASH_AREA_SIZE???  January
      05, 2004
Reviewer:  Venki  from India
Does it mean that, by setting WORKAREA_SIZE_POLICY to AUTO, we dont have to
worry about SORT_AREA_SIZE and HASH_AREA_SIZE???

          Followup:

it means that if you set it to auto, they are not used in dedicated server
connections (they would be in shared server connections in 9i).

workarea -> auto + dedicated server   IMPLIES SAS/HAS are ignored
workarea -> auto + shared server      uses SAS/HAS in 9i
workarea -> manual                    uses SAS/HAS in all.

      Rating: 3
Why the
      difference with shared servers?  March 09, 2004

Reviewer:  Matt  from Australia
I am slowly starting to understand the benefits (and limitations) of
workarea_size_policy=auto.

I just wondered what the reason might be for shared server connections not using
the pga_aggregate_target values.

My guess is that there is an overhead of tracking this information for each user
process and this overhead far outweighs any benefits for large numbers of users
using shared server connections.

What is the real reason?

          Followup:

it is just not implemented in 9i -- it is in 10g.

March 09, 2004 Daryl -- Thanks for the question regarding "Can
      interMedia index creation be done in parallel?", version 8.1.7

      You Asked
According to the syntax diagrams for CREATE INDEX, if domain index parameters
are specified, the PARALLEL clause can't be used. Is there any way to
parallelize index creation of interMedia indexes (interMedia Text in particular)
so that additional CPUs can actually be brought in to get it done. For some of
our tables, the creation of this index takes several hours, with much of the
delay caused by waiting for the INSO filter timeout to expire (in the event of
unindexable documents). Parallelizing this would seem to help with this problem.
Do you agree?

      and we said...
see

http://download-west.oracle.com/docs/cd/A87860_01/doc/inter.817/a77063/csql5.htm#19446
it supports parallel create index.

Review &
      Followup

      Rating:
      5
Doh.  June 26, 2002
Reviewer:  Daryl

Well, I've searched and searched Technet and couldn't find that link. Very
helpful. Thanks.

      Rating: 1
For a domain
      index?  December 12, 2002
Reviewer:
      Claudius
how can i use the index attributes in a domain index clause?

          Followup:

sometimes wish I could put stars in here too....

0 stars to you for providing really no details about anything.  which
attributes, which domain index.  if it is interMedia/Text -- have you read the
documentation (which has examples and such). it is all online.

      Rating:
      5
Ups...  January 07, 2003
Reviewer:
      Claudius
Sorry,

i tried it and got this message

ORA-29850 invalid option for creation of domain indexes

Cause: The user specified an invalid option like ASC, DESC, SORT or a parallel
clause, partitioning clause or physical attributes
clause.
Action: Choose one of the valid clauses for creation of domain indexes.

But it was my fault... I used also the NOLOGGING clause.

      Rating: 5
Just for the
      record  April 09, 2003
Reviewer:  Daryl

Parallel CREATE INDEX for interMedia in 8.1.7 requires the underlying table to
be partitioned.

      Rating: 5
partitioned MV
      & Parallel Domain Index  February 25, 2004

Reviewer:  Saminathan Seerangan  from Jersey
      City,NJ,USA
Dear Tom,

Thanks for all your help.

My Oracle Vetsion 8.1.7.3

I like to create a PARTITIONED meterialized view(Snapshot) based on a
NON-PARTITION table which is located on remote DB(using db link SOURCE_DB_LNK).

I need partitioned MV because I have to use Parallel Domain Indexing (otherwise
it is taking 22 Hrs)

  CREATE SNAPSHOT mv2
  STORAGE (INITIAL 1M NEXT 1M PCTINCREASE 0)
  TABLESPACE noncrm_data
  LOB(Y) STORE AS (TABLESPACE NONCRM_INDEX)
  build IMMEDIATE USING INDEX TABLESPACE noncrm_index
      STORAGE (INITIAL 1M NEXT 1M PCTINCREASE 0)
  REFRESH fast
  AS SELECT * FROM tab1@SOURCE_DB_LNK;

Questions
1) is it possible. If so kindly provide me an example.

          Followup:

on easy way is to use "on prebuilt table"

that is, create the MV table as you like it...

then create the mv on top of it...

then full refresh it to get it "caught up" so it can be incrementally refreshed.

(hint -- use LMTS and forget about initial, next, pctincrease forever)....

      Rating: 4
NOLOGGING
      workaround for DOMAIN index   March 09, 2004

Reviewer:  Sami  from NJ,USA
Dear Tom,

I like to use the NOLOGGING workaround for DOMAIN index provided by metalink.

http://metalink.oracle.com/metalink/plsql/ml2_documents.showDocument?p_database_id=NOT&p_id=135046.1
We are using one tablespace for all indexes (B-Tree,Domain,etc) and one
tablespace for data.

After creating DOMAIN index, is it possible to set the tablespace back to
logging?

          Followup:

yes.  but it is only an attribute assigned to newly created objects in that
tablespace -- it does not affect redo generation at all.

      Rating: 4
NOLOGGING
      -Space is saved in the redo log files  March 09, 2004

Reviewer:  Sami
Dear Tom,
<AskTom>
NOLOGGING does not affect redo generation at all.
</AskTom>

but... the below document says
<Doc>
Creating an index with NOLOGGING has the following benefits:
Space is saved in the redo log files.
</Doc>

http://download-west.oracle.com/docs/cd/B12037_01/server.101/b10739/indexes.htm#sthref2127
Kindly enlighten me

          Followup:

taken out of context, you can make anything mean anything.

they were asking about the nologging attribute of a tablespace and changing it.
I pointed out that "it will not affect redo generation for any existing objects
one way or the other -- it is simply a default attribute"

March 09, 2004 Peter -- Thanks for the question regarding
      "Cursor Variable and Bind Variables", version 8i

      You Asked
Hi Tom,

I'm trying to use a cursor variable in the manner below.
However, I'd like to use a bind variable for my v_in_string
(see the code below). How do I accomplish that?
I tried to use "USING", but it didn't work.
Thanks

CREATE OR REPLACE PROCEDURE test_proc IS
   TYPE t_ref_cursor IS REF CURSOR;
   v_ref_cursor t_ref_cursor;

   v_select_string VARCHAR2(4000);
   v_in_string VARCHAR2(100) := '1, 2, 3';

BEGIN
   v_select_string := 'SELECT * ' ||
                      'FROM   test_table ' ||
                      'WHERE  pk_id IN (' || v_in_string || ')';

   OPEN v_ref_cursor FOR
      v_select_str;

   -- ...rest of the code...
END test_proc;
/

      and we said...
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  /

Function created.

ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM> variable x varchar2(255)
ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM> exec :x := '1,2,3, 4, 5'

PL/SQL procedure successfully completed.

ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM> select *
  2    from all_users
  3   where user_id in ( select * from table( cast ( str2tbl(:x) as myTableType
) ) )
  4  /

USERNAME                          USER_ID CREATED
------------------------------ ---------- ---------
SYSTEM                                  5 21-JUN-01

Review &
      Followup

      Rating:
      5
  November 28, 2001
Reviewer:  Peter
       from CA, USA
Tom,

Is it worth going through all this just so we can use bind variables?  Would the
performance be much worse in this case if we just use a dynamic SQL without
using bind variables?

Thanks

          Followup:

YES IT WOULD BE.

At the very very least, you could do this:

CREATE OR REPLACE PROCEDURE test_proc IS
   TYPE t_ref_cursor IS REF CURSOR;
   v_ref_cursor t_ref_cursor;

   v_select_string VARCHAR2(4000);
   v_in_string VARCHAR2(100) := '1, 2, 3';

BEGIN
   v_select_string := 'SELECT * ' ||
                      'FROM   test_table ' ||
                      'WHERE  pk_id IN (' || v_in_string || ')';

   execute immediate 'alter session set cursor_sharing=FORCE';
   OPEN v_ref_cursor FOR
      v_select_str;
 execute immediate 'alter session set cursor_sharing=EXACT';

   -- ...rest of the code...
END test_proc;

That way, you would only get as many different query plans stuffed up in your
shared pool as you had number of in-list elements, that is, you would have:

select * from t where id in ( :1 )
select * from t where id in ( :1, :2 )
select * from t where id in ( :1, :2, :3 )
....

instead of hundreds/thousands of unique statements.

Yes, this is extremely imporant. (and once you've written str2table once -- its
part of your database, no muss, no fuss, its just there)

      Rating:
      4
  March 09, 2004
Reviewer:  Rob
      Katofiasc  from Chicago, IL
I implemented the same logic and it works fine, the only problem is I cannot get
Oracle to pick up the index on what would be in your query the user_id column.
If I try to force the index using a hint, Oracle will only do a index fast full
scan. Any suggestions on how I can get Oracle to pick up the index correctly.
The data type returned is the same as the column that is indexed.

Thanks

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3779680732446#15740265481549

      Rating:
      3
  March 29, 2004
Reviewer:
      A reader

March 10, 2004 -- Thanks for the question regarding "consistent
      gets -- Very puzzling", version 8.1.6

      You Asked
Tom:

create table test( a int);
begin
for i in 1..10000 loop
insert into test values (i);
end loop;
end;

set autotrace on
select count(0) from test;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'TEST'

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
         20  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
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

select * from test where a=10;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (FULL) OF 'TEST'

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
         21  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
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

select * from test;
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (FULL) OF 'TEST'

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
        686  consistent gets
          0  physical reads
          0  redo size
     185864  bytes sent via SQL*Net to client
      74351  bytes received via SQL*Net from client
        668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      10001  rows processed

question:
1.
when query count(0), what exactly did oracle do? since we don't have index, did
oracle went to every block and query the number?
if so, why consistent gets is much lower that select * from test?
both are full scan table.

2.
why select * from test have much higher consistent gets than
select * from test where a=10?
since there are no index, oracle need to go to every block to get the value, it
should be same consistent gets, although the first one return more rows, but it
does't matter.

Regards,

      and we said...
q1) select count(0) is just like

select count(*)
  from ( select 0 from t )
/

yes, oracle went to each block to find the rows to give you a zero

q2) its a side effect of your arraysize.  You must have an 8k blocksize cause I
reproduced this exactly.

We were expecting about 20 consistent gets right?  Well, the default array size
in sqlplus is 15 rows / fetch.  10000/15 = 666.66666.  Well, 666+20 = 686 --
whoah there -- 686 is our consistent gets!

Thats what happened.  When you fetched 15 rows, Oracle paused, gave you the
data.  When you went back to get the next 15, it got the buffer again to resume
your query.

Watch what happens with different array sizes, starting with 15:

ops$tkyte@8i> select * from test;

10000 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
        686  consistent gets
          0  physical reads
          0  redo size
     108813  bytes sent via SQL*Net to client
      46265  bytes received via SQL*Net from client
        668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      10000  rows processed

ops$tkyte@8i> set arraysize 1000
ops$tkyte@8i> select * from test;

10000 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
         30  consistent gets
          0  physical reads
          0  redo size
      86266  bytes sent via SQL*Net to client
        942  bytes received via SQL*Net from client
         11  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      10000  rows processed

ops$tkyte@8i> set arraysize 5000
ops$tkyte@8i> select * from test;

10000 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
         22  consistent gets
          0  physical reads
          0  redo size
     149793  bytes sent via SQL*Net to client
        382  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      10000  rows processed

Other interesting thing to note is that as the array size gets too large -- the
amount of data transferred goes up.  there is a diminishing marginal return on
the array size so don't go OVERBOARD.

followup to comment one

Set the arraysize to some constant as it will not matter for single row fetches
whether it is too big but having it too small for lots of rows (NOT just full
scans -- lots of rows) does impact you.

Review &
      Followup

      Rating:
      4
  June 29, 2001
Reviewer:
      A reader
does that mean when we do full table scen we want array size a little bit
bigger, when query by rowid, we want arraysize smaller?

      Rating: 3
Tested it but
      got different results   April 30, 2002
Reviewer:
      ZS  from Australia
I am a bit confused now, though you explained it well, but I tested it in my
envirnment and got different results.
My query returns 122 rows with a select *.

Arraysize of 15 --> number of consistent gets was=1051 (122/15=8) which means
1043 + 8 = 1051 (good so far)
so I would assume according to the example above that when I do count(*) number
of consistent gets would be 1043, right? BUT
Arrayszie of 15 with count(*) --> consistent gets = 920 I dont understand where
this comes from?

I did more testing:
Arraysize of 122 with select * --> consistent gets = 1043
Arraysize of 122 with select count(*) --> consistent gets = 920

Arrayszie of 5000 with select * --> consistent gets = 1043
Arrayszie of 5000 with select count(*) --> consistent gets = 920

I would appreciate if you can explain.
thanks,
ZS

          Followup:

The count(*) returns how many rows?  1.

The array size does even begin to kick in.  That statement is processed in ONE
call.

Also, the count(*) may very well *never be touching the table*.  It might be
just using an index.

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>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec show_space( 'T' )
Free Blocks.............................0
Total Blocks............................320
Total Bytes.............................2621440
Unused Blocks...........................4
Unused Bytes............................32768
Last Used Ext FileId....................7
Last Used Ext BlockId...................40969
Last Used Block.........................60

PL/SQL procedure successfully completed.

so the table consumes about 316 blocks..

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;
22905 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
       1823  consistent gets
         46  physical reads
          0  redo size
    2704019  bytes sent via SQL*Net to client
     169811  bytes received via SQL*Net from client
       1528  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      22905  rows processed

22905/15 = 1527+316 = 1843 which is about our consistent gets (what we were
expecting)....

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from t;

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
        319  consistent gets
         46  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
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

And the count(*), since it was done in a SINGLE CALL -- no arraysize -- only
did a consistent get on each block ONE TIME.  Hence it did 319 consistent gets
(which is about the size of the table)

Now, adding a primary key to the table:

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> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from t;

Statistics
----------------------------------------------------------
         79  recursive calls
          4  db block gets
         82  consistent gets
          1  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
          1  sorts (memory)
          0  sorts (disk)
          1  rows processed

we can see that the consistent gets go way down.  why?  because the optimizer
counted the rows in the index, not the table in this case.

      Rating:
      5
  May 01, 2002
Reviewer:
      A reader
Hi tom,

 we calculate hit ratio like
(1-(phycial_read/(db_block_get+consistance get))

but according to your reply here i think consistent gets or db_block_get is
physical read (it is not logical).

Can you explian about this.

          Followup:

db_block_gets + consistent_gets = LOGICAL IO
physical_reads                  = PHYSICAL IO

I don't see where I might have mislead you on that here -- can you point out
which part of the above indicated to you that they were physical IO?

      Rating:
      5
  May 01, 2002
Reviewer:
      A reader
Tom

I think i am confused here about local i/o and physical i/o.
Sorry about that(it's not your fault).

But I am confused here.

If i issue query first time. Noting will be in memory So
db_block_get and consistant get will be physical read.
(am i Right?)

If i issue query second time.lots of block will be in memory.Here it will be
logical i/o.

Am i right here?

Thanks.

          Followup:

You might be right, you might not be.   Some other query and/or operation may
have cached the data already.

On the second point, you might be right, you might not be.  It depends on

o the amount of data touched
o the size of the buffer cache
o what else is going on in the system
o the use of various features/functions in the database (eq: parallel query)

In general, yes, you would expect in many cases that the second execution would
be almost all logical io.

      Rating:
      4
Reader  May 01, 2002
Reviewer:
      A reader
Consistenet gets is based upon re-constructing a block
for consistent read. Hence it is a function of only the
number of db_blocks to be read.

If you say that it is altered by the arraysize, do you
suggest that, due to arraysize, some blocks are read
muliple times and hence some blocks have > 1
consistent read in the process

Thanks

          Followup:

No, you are wrong in your statement.

A consistent get is a block gotten in read consistent mode (point in time mode).
 It MAY or MAY NOT involve reconstruction (rolling back).

Db Block Gets are CURRENT mode gets -- blocks read "as of right now".

Some blocks are processed more then once, yes, the blocks will have more then 1
consistent read in the process.  Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t as select * from all_objects;

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec show_space( 'T')
Free Blocks.............................0
Total Blocks............................320
Total Bytes.............................2621440
Unused Blocks...........................4
Unused Bytes............................32768
Last Used Ext FileId....................7
Last Used Ext BlockId...................40969
Last Used Block.........................60

PL/SQL procedure successfully completed.

Table has 316 blocks, 22,908 rows..

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly statistics;

ops$tkyte@ORA817DEV.US.ORACLE.COM> set arraysize 15
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;
22908 rows selected.

here with an array size of 15, we expect

22908/15 + 316 = 1843 consistent mode gets.  db block gets -- they were for
performing the FULL SCAN, they had nothing to do with the data itself we
selected

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
       1824  consistent gets
        170  physical reads
          0  redo size
    2704448  bytes sent via SQL*Net to client
     169922  bytes received via SQL*Net from client
       1529  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      22908  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM> set arraysize 100
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

22908 rows selected.

Now, with 100 as the arraysize, we expect

22908/100 + 316 = 545 consistent mode gets.

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
        546  consistent gets
        180  physical reads
          0  redo size
    2557774  bytes sent via SQL*Net to client
      25844  bytes received via SQL*Net from client
        231  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      22908  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM> set arraysize 1000
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

22908 rows selected.

now, with arraysize = 1000, we expect:

22908/1000+316 = 338 consistent mode gets...

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
        342  consistent gets
        222  physical reads
          0  redo size
    2534383  bytes sent via SQL*Net to client
       2867  bytes received via SQL*Net from client
         24  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      22908  rows processed

so yes, the blocks are gotten in consistent mode MORE THEN ONCE when the array
fetch size is lower then the number of rows to be retrieved in this case

This is because we'll be 1/2 way through processing a block -- have enough rows
to return to the client -- and we'll give UP that block.  When they ask for the
next N rows, we need to get that halfway processed block again and pick up where
we left off.

      Rating: 3
how does this
      formula works?  May 01, 2002
Reviewer:
      A reader
22908/15 + 316 = 1843 consistent mode gets.  db block gets -- they were for
performing the FULL SCAN, they had nothing to do with the data itself we
selected

how did you get 22908/15 + 315 would be the total consistent gets?

          Followup:

read the original question / answer.  I went through it there.

In this case, the numbers:

22908 = number of rows
15 = arraysize.

22908/15 = number of times you'll fetch

so, that is the number of times you'll have to read a block more then once.

315 was the number of blocks in the table (in a perfect world, we would do 315
consistent gets.  With an arraysize of 15 and 22908 rows, we'll add 22908/15
consistent gets to that total)...

      Rating: 4
I got that srt
      of idea but I was confused  May 01, 2002
Reviewer:
      A reader
Hi

So a low arraysize in sql*plus can actually distort autotrace stats and affects
performance?

So I guess autotrace is not that accurate or TKPROF will do the same?

How can we set arraysize let's for example in Developer Reports or AD HOC Tools
like Business Objects to improve the select * from table_a like queries
performance

          Followup:

No -- it won't "distort them".

You can use the array size in sqlplus to see what your APP would do.

Array size by default is 15 in plus
10 in JDBC
2 in pro*c
1 in OCI
??? in odbc (no idea, never use it)

It is NOT distorted -- it is reality!  It shows what really happens as you use
different array sizes to fetch.

TKPROF -- that would show exactly the same information.  The same -- from jdbc
to plus to pro*c (sqlplus is JUST an oci application)

with reports see:

ARRAYSIZE (RWBLD60)

Description ARRAYSIZE is the size of the array in kilobytes for use with ORACLE
array processing.
Syntax
[ARRAYSIZE=]n

with business objects -- refer to their documentation.

      Rating: 3
arent
      consistent gets in blocks?  May 01, 2002
Reviewer:
      A reader
arent consistent gets calculated in blocks? if that was the case how can we say
number_of_rows/arraysize + total_blocks_read would be the total consistent gets?

number_of_rows/arraysize dont give us in blocks does it?

The numbers suggests it is correct but this is really confusing since I cannot
find it anywhere documentated

          Followup:

It is because we need to get a block more then ONCE.

Suppose we do that simple "select * from t"

Suppose every block has 16 rows on it.

Suppose we have an array size of 15.

We do the first fetch.  That'll get block 1 (one consistent get) and get the
first 15 rows and return.

Now, we do the second fetch.  That'll get block 1 again (two consistent gets)
and then get block 2 to get the next 14 rows (three consistent gets).  We
return.

We do the 3rd fetch.  We get block 2 again (four consistent gets) to get the
remaining 2 rows and then get block 3 (five consistent gets) to get the next 13
rows.

And so on.  We end up getting every block 2 times -- because we stopped
processing it partway through to return the result to the client.

Hence, if you full scan a table with N rows and use an array fetch of M rows,
you stand a change of getting the blocks an extra N/M times.

      Rating: 4
from your last
      example  May 02, 2002
Reviewer:
      A reader
Hi Tom

from your last example it would be true if we have 16 rows per block, what if we
have 10 rows per block? We would just visit the block once right?
If we have 46 rows instead then first block would be visited 4 or 5 times?

first time to get 15 rows
second to get another 15
their to get another 15
and fourth to get the remaining one
fifth the full table scan visit, I am not sure if fifth should exist but since
all your examples add a previos visit for the Full Table Scan...

Finally I dont understand why a large arraysize generates more network traffic,
I thought it must decrease but we see the oppsite

          Followup:

Ok, 10 rows/block:

on first fetch we:
   get block 1  (1 consistent get)   10 rows
   get block 2  (2 consistent gets)   5 rows / 5 left

on second fetch we:
   get block 2 (3 consistent gets) 5 rows / 0 left
   get block 3 (4 consistent gets) 5 rows / 5 left

....

and so on.  We visit EVERY OTHER block 2 times (the N/M still holds here.  If I
have 100 rows on 10 blocks and an array size of 10 -- I'll add 10 consistent
gets for a grand total of 20 consistent gets).

      Rating: 3
I don't get
      :-(  May 02, 2002
Reviewer:  Igor  from
      France
Hi

"We were expecting about 20 consistent gets right?  Well, the default array size

in sqlplus is 15 rows / fetch.  10000/15 = 666.66666.  Well, 666+20 = 686 --
whoah there -- 686 is our consistent gets!"

Why 20? I also don't get point for "666+20 = ... "

 Hope to get answer

                                         Igor

          Followup:

Sorry --- I've explained this beyond my best ability at this point.  I've
explained many many times.  This is my last try:

The table has 20 blocks in it.
You would expect 20 consistent gets on a full scan.
But, with an array size of 15 we will revisit some blocks MORE THEN ONCE.

In fact, we might revisit upto 10000/15 = 666 blocks more then ONCE.

This is because we "start" and "stop" the query in the middle of a block.  (see
the two followups immediately above this).

If you full scan a table T that has N blocks and R rows and you use an array
fetch size of A, we will typically perform the following number of consistent
gets:

N + R/A

We have to read N blocks (that should be obvious)
We might revisit R/A blocks more then once (since we stop in the middle)

      Rating: 5
How should we
      tune this?  October 14, 2002
Reviewer:  A
      Reader  from USA
Hi Tom,
A full table scan of a table with 770555 rows takes more than 2 minutes to get
the results. It looks that both the "consistent gets" and the "physical reads"
are high for a 13 data block gets. Where should we start to tune the
database/query?  We have to use the query.

Thank you in advance.

SQL> select * from TAB1;

770555 rows selected.

Elapsed: 00:02:147.52

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1447 Card=752634 Byt
          es=161816310)

   1    0   TABLE ACCESS (FULL) OF 'TAB1' (Cost=1447 Card=752
          634 Bytes=161816310)

Statistics
----------------------------------------------------------
          0  recursive calls
         13  db block gets
      74096  consistent gets
      21627  physical reads
          0  redo size
   55033565  bytes sent via SQL*Net to client
    5702495  bytes received via SQL*Net from client
      51372  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
     770555  rows processed

          Followup:

13 db block gets are blocks gotten in CURRENT mode -- they were blocks that told
us were all of the other blocks were!  (they were the segment headers).

set arraysize 100

and try again in sqlplus (and watch your consistent gets start to go down)

Now, also bear in mind -- this is not two minutes just to read the blocks.  This
is two minutes to

o read the blocks
o send them over the network
o have sqlplus process them

      Rating: 5
consistent
      gets  January 04, 2003
Reviewer:  Steve
       from NJ, USA
Hi Tom,

1) When you talk about tuning SQL, you said just reducing
    LIO (
http://asktom.oracle.com/pls/ask/f?p=4950:8:1814295::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:6749454952894,%7Blios%7D
. So increase ArraySize is one way
to reduce LIO ( consistent gets). Is this right?

2) what is the relationship between number of "consistent gets" + "db block
gets" and number of current (tkprof output)?  Based on the formular you ive us
in this question: consistent gets = N+R/A, so current = N + R/A ?

3) what is relationship  the between physical reads and disk (tkprof output)?

4) Is the following show_space system procedure? if that is one you wrote, May I
have that?

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec show_space( 'T' )
Free Blocks.............................0
Total Blocks............................320
Total Bytes.............................2621440
Unused Blocks...........................4
Unused Bytes............................32768
Last Used Ext FileId....................7
Last Used Ext BlockId...................40969
Last Used Block.........................60

Thanks

Steve

          Followup:

1) in some cases it'll reduce the LIO's yes (in many cases).  In all cases --
using a healthy but not "overweight" arraysize is recommended - in all cases.

2) not sure what N+r/a is (hopefully that is not A=arraysize cause it doesn't go
that way -- the arraysize can have an effect - I used a case where I could
predicate the effect but there are many more cases where

  a) it'll not have an effect
  b) it'll have an effect but a wholly unpredicable one.

db block gets (autotrace) are current (tkprof)
consistent gets(autotrace) are query (tkprof)

3) one to one - physical reads(autotrace) is disk (tkprof)

4)

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:231414051079

      Rating:
      5
  January 05, 2003
Reviewer:  Dave
       from Ohio
To respond to "A Reader"s question earlier, in business Objects the array fetch
size is defined as part of the named connection used for universes or free-hand
SQL.

      Rating: 5
Low - Hanging
      Tuning Fruit ?  January 12, 2003
Reviewer:
      Jerry  from Washington, DC
I work for a company that buys hardware that we grow out of, not into, so I am
always on the lookout for tuning ideas, both in the app, and to some extent, the
instance (even though I am a developer).

You mention some apps and their default arraysizes.  We run PeopleSoft on
Oracle, which uses COBOL and SQR on a midtier UNIX app server.  Should/could we
try and tune those array sizes for any gain?  If so, would that be in Net8?  I
note that OCI has an array size of 1.

Thanks for your time.  Can't tell you how much I've learned from your site.  It
is part of my Sunday routine.

"
Array size by default is 15 in plus
10 in JDBC
2 in pro*c
1 in OCI
"

          Followup:

It would be a question for peoplesoft to answer -- they wrote the code.  OCI can
do anysize, they would have had to of programmed support for more than one row
at a time.

      Rating: 4
Why should the
      SQL PLUS array size affect the plan?  January 13, 2003

Reviewer:  Tony  from India
Why should the SQL PLUS array size affect the statistics/plan? In general, we
tune queries in Sql Plus but the query is invoked from the middle tier. I don't
understand the relavence of array size in Sql Plus?. Only middle tier
component's array size matters. We use COM component. How to set the array size
in COM object?

          Followup:

the arraysize doesn't affect the plan.

they arraysize DOES affect the statistics (it would be a shame if it didn't!)

The arraysize in sqlplus demonstrates that "hey, I should look at my client and
set the bulk fetch size appropriately".  Pretty much all tools have the ability
to do it.

A com object is like saying "I use CORBA, how to set arraysize in CORBA".
Doesn't make sense.  COM isn't a "language", COM doesn't do sql.  You look at
the language you are using.  If it is an open systems language like C, Java --
I'lll be glad to tell you.  If it is a closed language like VB -- you'll have to
refer to the docs for that -- I've no idea as I've never considered programming
with them.

      Rating: 4
Why should the
      SQL PLUS array size affect the plan?  January 13, 2003

Reviewer:  Tony  from India
Continuation of my earlier question..

    What I understand from your explanation is that only client side array size
has to be set appropriately to minimize the consistent gets. Nothing to do with
the database. Am I correct? Developers are least bothered about the array size
of the middle tier. Now I understand the importance.

Sorry if my question is repeatative.

          Followup:

Developers are the ONLY bothered people with regards to this.  They are
responsible for making sure the batch fetch size in their client applications is
set to a reasonable number (i like 100 personally)

      Rating: 3
For Ref Cursor
      also?  January 13, 2003
Reviewer:  Tony
       from India
We extensively use ref cursor in our application.
Do we have to set array size even if refcursor is used?

          Followup:

yup.  if you want the client to fetch N rows at a time where N is some number
you wish to control.

      Rating: 5
Consistent Gets
      - Why LIO?  January 13, 2003
Reviewer:
      Jerry  from Washington, DC USA
After reading Gorman's "THE SEARCH FOR INTELLIGENT LIFE IN THE COST-BASED
OPTIMIZER" paper referenced on your site, a question about consistent gets came
to mind.

If cgets are LIO, but involve constructing a read-consistent image of a block,
wouldn't that involve referencing rbs, which in my mind would (or at least
could) involve PIO?

          Followup:

Nope, rollback is to be found in the buffer cache just like anything else.  It
is just a rollback SEGMENT after all -- just another segment (like an index
segment).

      Rating: 4
Extra CR / CU
      ?  February 18, 2003
Reviewer:  Amit
       from Dallas, USA
Hi Tom-

During testing of above examples- I came across this:
I built 2 tables - both containing a single row.
One is created and populated with CTAS, the other is created empty, and the row
is inserted and commited.
Both tables have one extent consisting of 2 8K blocks.

1. Why does oracle need to perform 5 CR, 12 CU while scanning the 1st table
(containing 2 blocks)?
2. Why does oracle need to perform 1 CR, 4 CU while scanning the 2nd table
(containing 2 blocks)?

create table t1ctas storage (initial 1k next 1k pctincrease 0 ) tablespace users

as select rownum n from dual
/
create table t1ins storage (initial 1k next 1k pctincrease 0)  tablespace users
as select * from t1ctas where 1=0
/
insert into t1ins  select rownum n from dual
/
commit;

set arraysize 100;
alter session set events '10046  trace name context forever, level 1';

select * from t1ctas            -- CR=5  CU=12
/
select * from t1ins             -- CR=1  CU=4
/
select * from t1ctas            -- CR=10 CU=24
union all select * from t1ctas
/
select * from t1ins             -- CR=8  CU=2
union all select * from t1ins
/
select * from t1ctas            -- CR=6  CU=16
union all select * from t1ins
/
exit;

*** SESSION ID:(11.1483) 2003-02-17 23:22:29.809
APPNAME mod='SQL*Plus' mh=3669949024 act='' ah=4029777240
=====================
PARSING IN CURSOR #1 len=70 dep=0 uid=37 oct=42 lid=37 tim=1283779 hv=1445189883
ad='5302b64'
alter session set events '10046  trace name context forever, level 1'
END OF STMT
EXEC #1:c=1,e=38,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=3,tim=1283784
=====================
PARSING IN CURSOR #1 len=47 dep=0 uid=37 oct=3 lid=37 tim=1283805 hv=4134657170
ad='5310df8'
select * from t1ctas            -- CR=5  CU=12
END OF STMT
PARSE #1:c=0,e=8,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=3,tim=1283811
EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=3,tim=1283812
FETCH #1:c=0,e=2,p=1,cr=5,cu=12,mis=0,r=1,dep=0,og=3,tim=1283816
FETCH #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=3,tim=1283818
STAT #1 id=1 cnt=1 pid=0 pos=0 obj=40021 op='TABLE ACCESS FULL T1CTAS '
=====================
PARSING IN CURSOR #1 len=46 dep=0 uid=37 oct=3 lid=37 tim=1283840 hv=740128398
ad='530d86c'
select * from t1ins             -- CR=1  CU=4
END OF STMT
PARSE #1:c=0,e=8,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=3,tim=1283846
EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=3,tim=1283847
FETCH #1:c=0,e=0,p=0,cr=1,cu=4,mis=0,r=1,dep=0,og=3,tim=1283849
FETCH #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=3,tim=1283851
STAT #1 id=1 cnt=1 pid=0 pos=0 obj=40022 op='TABLE ACCESS FULL T1INS '
=====================
PARSING IN CURSOR #1 len=78 dep=0 uid=37 oct=3 lid=37 tim=1283877 hv=3911934752
ad='5313e54'
select * from t1ctas            -- CR=10 CU=24
union all select * from t1ctas
END OF STMT
PARSE #1:c=0,e=9,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=3,tim=1283884
EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=3,tim=1283886
FETCH #1:c=0,e=0,p=0,cr=5,cu=12,mis=0,r=1,dep=0,og=3,tim=1283888
FETCH #1:c=0,e=0,p=0,cr=5,cu=12,mis=0,r=1,dep=0,og=3,tim=1283890
STAT #1 id=1 cnt=2 pid=0 pos=0 obj=0 op='UNION-ALL '
STAT #1 id=2 cnt=1 pid=1 pos=1 obj=40021 op='TABLE ACCESS FULL T1CTAS '
STAT #1 id=3 cnt=1 pid=1 pos=2 obj=40021 op='TABLE ACCESS FULL T1CTAS '
=====================
PARSING IN CURSOR #1 len=76 dep=0 uid=37 oct=3 lid=37 tim=1283923 hv=3592920098
ad='530ac48'
select * from t1ins             -- CR=8  CU=2
union all select * from t1ins
END OF STMT
PARSE #1:c=0,e=11,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=3,tim=1283931
EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=3,tim=1283933
FETCH #1:c=0,e=0,p=0,cr=1,cu=4,mis=0,r=1,dep=0,og=3,tim=1283935
FETCH #1:c=0,e=0,p=0,cr=1,cu=4,mis=0,r=1,dep=0,og=3,tim=1283937
STAT #1 id=1 cnt=2 pid=0 pos=0 obj=0 op='UNION-ALL '
STAT #1 id=2 cnt=1 pid=1 pos=1 obj=40022 op='TABLE ACCESS FULL T1INS '
STAT #1 id=3 cnt=1 pid=1 pos=2 obj=40022 op='TABLE ACCESS FULL T1INS '
=====================
PARSING IN CURSOR #1 len=77 dep=0 uid=37 oct=3 lid=37 tim=1283969 hv=1374823634
ad='52ffcd8'
select * from t1ctas            -- CR=6  CU=16
union all select * from t1ins
END OF STMT
PARSE #1:c=0,e=10,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=3,tim=1283977
EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=3,tim=1283979
FETCH #1:c=0,e=0,p=0,cr=5,cu=12,mis=0,r=1,dep=0,og=3,tim=1283981
FETCH #1:c=0,e=0,p=0,cr=1,cu=4,mis=0,r=1,dep=0,og=3,tim=1283983
XCTEND rlbk=0, rd_only=1
STAT #1 id=1 cnt=2 pid=0 pos=0 obj=0 op='UNION-ALL '
STAT #1 id=2 cnt=1 pid=1 pos=1 obj=40021 op='TABLE ACCESS FULL T1CTAS '
STAT #1 id=3 cnt=1 pid=1 pos=2 obj=40022 op='TABLE ACCESS FULL T1INS '

          Followup:

it just does in 8i for some reason.  In 9i, it does not.

Note that most people would never see this as a single row table created via
CTAS is not very common.  if you do a "real sized" table -- you'll see the
additional header gets (the db block get -- current mode gets) but averaged out
over a large full scan, it is less than "noise"

      Rating:
      4
  April 03, 2003
Reviewer:  Robert
       from Memphis, USA

      Rating: 5
how do I know
      how many blocks my query read?  April 17, 2003

Reviewer:  A reader
Hi

How do I determine how many database blocks were read in my query from autotrace
utility or TKPROF?

          Followup:

consitent gets:

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'TEST'

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
         20  consistent gets

or in tkprof query + db block gets.

      Rating: 5
the thing is,
      doesnt that deoend on arraysize?  April 18, 2003

Reviewer:  A reader
Hi

I can see you get different consistent gets if we use different array size, so
how can that be the number of data blocks...?

I mean I can see 100000 consistent gets or 10000 consistent gets for same query,
does that mean my query reads different number of blocks????

          Followup:

yes it does -- by definition.

consistent gets = logical IO's = number of block gets.

it does not equal the number of DISTINCT blocks you processed if that is what
you mean, it means the number of block gets (which require cpu cycles and
latches = locks and so on)

      Rating:
      5
  April 18, 2003
Reviewer:
      A reader
Hi Tom,

 I am working on oracle from last 6 year. I am reading your site from begining
(Aug 2000).

But when it comes to consistant get and db block get
I am more confused then ever before. Please help help!!!

You said
 1) "A consistent get is a block gotten in read consistent mode (point in time
mode).
 It MAY or MAY NOT involve reconstruction (rolling back)."

What do you mean by "point in time mode" (Hearing this word from last couple of
years but lot confusing...

What do you mean by "It MAY or MAY NOT involve reconstruction (rolling back)."

Please elobarate in this issue

Thnaks

          Followup:

if you've been reading since then -- you've heard me say:

"if you read the concepts guide AND
you retain 10% of that information YOU'LL HAVE
90% more knowledge about Oracle then most people out there..."

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96524/c21cnsis.htm#2570
all about multi-versioning.

All queries done by Oracle are done "at a point in time", consistently -- read
that (read the entire book in fact) and you'll get an understanding of the
single most important concept to understand about Oracle ever.

If you have my book "Expert one on one Oracle" -- i cover it in gory detail in
there as well.

      Rating: 5
Would dbms_jobs
      be affected by arraysize?  April 18, 2003

Reviewer:  Shiv  from UK
Tom,

Thanks in advance.
Can a  dbms_job be optimized using arraysize? if so how does one set it. or it
doesn't matter since it doesn't run as a client.

cheers
Shiv

          Followup:

PLSQL can be optimized using BULK COLLECT (array fetching) and FORALL (bulk
modification)

search the PLSQL manual's table of contents for those terms and read on....

      Rating: 5
regarding
      number of blocks to read  April 18, 2003
Reviewer:
      A reader
Hi

you said that consistent gets = LIO = block gets

I guess you mean the times Oracle goes to fetch the blocks, but I was asking the
number of physical blocks Oracle needs to read! How how many times but rather,
how many blocks.

For example to satisfy a query Oracle needs to read 100000 database blocks (8k,
so 8k*100000 around 800MB), from where we can get this statistics??? Consistent
gets is the number of times we need to fetch

I guess it´s

arraysize*number of consistent gets but if we dont know the arraysize?

          Followup:

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
         20  consistent gets
          0  physical reads

that would be that number.

it might be 0, it might be 20000000, it might be 22 to read 100,000 database
blocks in answer to a query (for PIO's)

      Rating: 3
from your first
      example  April 19, 2003
Reviewer:
      A reader
hello

from your first example

ops$tkyte@8i> select * from test;

10000 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
        686  consistent gets
          0  physical reads
          0  redo size
     108813  bytes sent via SQL*Net to client
      46265  bytes received via SQL*Net from client
        668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      10000  rows processed

ops$tkyte@8i> set arraysize 1000
ops$tkyte@8i> select * from test;

10000 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
         30  consistent gets
          0  physical reads
          0  redo size
      86266  bytes sent via SQL*Net to client
        942  bytes received via SQL*Net from client
         11  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      10000  rows processed

one is 696 consistent gets and the other 30, 4 db block gets in both cases

did Oracle read 4 database blocks only to fetch 10000 rows?!?!?!

          Followup:

db block gets are blocks gotten in CURRENT (as opposed to consisten) mode.  They
are block gets we did to discover the extent map for the table (to know what to
read).  We get that in "current, as of right now" mode instead of "in consistent
read, as of the point in time the query began" mode.

Oracle did 4 current mode gets in both cases to figure out what blocks to read.

In the first example, it did 686 logical IO's
In the second, only 30

It did no physical IO.

      Rating:
      5
Reader  April 19, 2003
Reviewer:
      A reader
Tom,

If Oracle wants to read the extent map from the
segment header, why there are 4 db block gets instead of 1.

In my following test I get 0 db block gets

I have created table t as select * from dba_objects;

SQL> select count(*) from t;

  COUNT(*)
----------
     29387

<bounced the database>

SQL> get afiedt.buf
  1  set termout off
  2  set autotrace on
  3  spool c:\tmp\t.log
  4  select * from t;
  5* spool off
SQL> start afiedt.buf

Excerpt from spool file=>
=========================
Statistics
------------------------------------
        188  recursive calls
          0  db block gets
       2359  consistent gets
        402  physical reads
          0  redo size
    2049661  bytes sent via SQL*Net to client
      22048  bytes received via SQL*Net from client
       1961  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      29387  rows processed

Thanks

          Followup:

because -- just because.  In different releases -- different count.

In your example, you didn't do a full table scan -- apples to toaster oven
comparision.

You are using the CBO
You have a primary key on T
It read the index using an index full scan.

Or you are using Oracle9i and new features like ASSM...

Or ....

Let me put it this way -- if you see 4 there, and you did a simple full table
scan -- then the 4 db block gets are what I said they where.

I did not say "you shall always see 4 there", rather, that when you do -- thats
what they are.

      Rating: 4
from your first
      example  April 20, 2003
Reviewer:
      A reader
sorry I didnt make the question clear.... I really wanted to ask how many
database blocks (in memory or disk whatever) did Oracle read in order to satisfy
your query and not how many LIO or PIO.

Definitely the number of blocks visited is not number of consistent gets since
consistent get varies with arraysize, so in your example how many blocks did
Oracle need to read?

          Followup:

what is the difference?

What do  you mean by "how many blocks did Oracle need to read".

To me that is one of two things:

o how many block gets did Oracle do in order to satisfy my query.  That is
logical IO

o how many blocks did Oracle physically read from disk in order to satisfy my
query.  That is phyiscal IO.

Those are the only two numbers that mean anything.  Those are the only two
numbers you can get.

That there are 100 blocks in the table, and we did 1,000 Logical IO's to read it
-- of those two numbers the only relevant one is "1,000".

      Rating: 4
the difference
      is  April 20, 2003
Reviewer:  A reader

hi

the difference is I wanted to know how many blocks did Oracle need to read in
order to satisfy a query, I wanted to know how much MB or GB did Oracle need to
read. Consistents gets are fine but for example if I see a huge consistent gets
but I did not know the arraysize someone might think geez it's read tons to
satisfy this query, but it was probably because a low arraysize, who knows? If
we dont know arraysize it's hard to determine how "large" or how "bad" is that
high number of consistent gets. So since consistent gets is determined by the
arraysize I wanteds to know some value which does not depend on another
parameter, for example the number of database blocks needed to read. I guess for
a same query with same execution plan Oracle should read X number of database
blocks which would YMB (database block size * number of database blocks). I am
not that conmcerned about PIO at this time, I was wondering how do we know the
number of database blocks Oracle needs to read, so for example from that
determine how much db cache is my query using, we definitely cannot calculate
that with consistent get right?

          Followup:

need to read -- that is PIO

need to process that is LIO

nothing else is relevant nor obtainable.

TKPROF would tell you if arraysize is a factor -- divide fetches into rows - if
the number is 1, they aren't using array fetches.

A FACTOR in consistent gets is arraysize.

ARRAYSIZE does not determine consistent gets.

      Rating:
      5
  April 21, 2003
Reviewer:
      A reader
Hi Tom,

Consistant get means reconstructing block from rollback segment. Am I right ?

If so then it is reading rollback segment which resides on tablespace and it
doing PIO then why we call it LIO ???

If not then throght some light on this.

Thanks for excellent web site!!!

          Followup:

consistent gets means getting a block "as of a point in time" and MAY require
read asides to the rollback segment in order to reconstruct the block.  emphasis
on MAY.

the RBS is just a segment (that is what the S is for).  It resides in the cache
just like an index, table or anything else does.

Here is an example showing the "read asides" to the rbs.  It shows that we MAY
require a read to the rbs by running the same exact query -- but doing lots of
work in between:

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>
ops$tkyte@ORA920> variable x refcursor
ops$tkyte@ORA920> variable y refcursor
ops$tkyte@ORA920>
ops$tkyte@ORA920> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA920> begin
  2     open :x for select * from t X;
  3     open :y for select * from t Y;
  4  end;
  5  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> print x

         X
----------
         1

ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2      for i in 1 .. 10000
  3      loop
  4          update t set x = i;
  5          commit;
  6      end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> print y

         X
----------
         1

ops$tkyte@ORA920>

So, they return the same exact information but TKPROF shows us:

SELECT * from t 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          7          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          7          0           1

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS FULL T (cr=7 r=0 w=0 time=162 us)
********************************************************************************
SELECT * from t Y

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.04       0.06          0      10002          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.04       0.06          0      10002          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 83     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS FULL OBJ#(34399) (cr=10002 r=0 w=0 time=60325 us)

that it took 10k consistent gets for Y -- versus 7 for X -- both queries
return the same data -- it is just that for the first fetch from Y we had to
"undo" the 10,000 updates that happened between the OPEN of Y and the fetch.

      Rating:
      5
  April 21, 2003
Reviewer:
      A reader
Hi Tom

what do you think about DB2 calim on this

http://www-/
3.ibm.com/software/data/pubs/papers/readconsistency/readconsistency.pdf

          Followup:

I like this quote:

<quote>
The reality is that with Oracle, as with any other database, you design and code
your application with an understanding of the underlying isolation and
concurrency model
</quote>

I've been saying that for years myself...

but then they say:

<quote>
No other database vendor has implemented Oracle's Multi Version Read Consistency
isolation nor has it proven to be a performance advantage in industry standard,
ISV or real life customer benchmark.
</quote>

well, they sort of need to do their homework -- I would like us to be the only
ones with this feature -- but we ain't.  RDB (which we bought a while ago) had
it for a loooonnnnnggg time.  Borlands Interbase (someone bought that I believe)
did.  Heck, mysql does it.  So, to make us look "unique", that is very nice
of them -- but not really true.  And given that I don't even see DB2 in the top
10 tpc-C results -- even on AIX for goodness sake -- don't see where they are
going with this benchmark concept (oh thats right, they don't actually run the
tpc-c's, not even on their own machines)....  Ok, going to the SAP website

http://www.sap.com/benchmark/index.asp?content=http://www.sap.com/benchmark/ato2tier.asp
looks like the top ones in this ISV benchmark (run by a company that doesn't
necessarily promote Oracle) would be .... (not DB2).

Oh, i see way down at the bottom they refer to a read only benchmark -- a tpc-h.
 How appropriate in a paper describing CONCURRENCY CONTROLS that affect
reads/writes at the same time, (not).

Oh I see they say we bypass our MV in the tpc-c because we use serializable.
Boy -- just shows a TOTAL and FUNDEMENTAL lack of understanding on their part of
how MV works in Oracle -- they totally 100% missed the boat.  Our serializable
is even MORE depenedent on multi-versions then our read committed is!!!

Now, they say:

<quote>
With Oracle, the data you see in any query is the data as it existed at the
start of that query. This does not conform to any ANSI standard isolation level,
nor is it the way other RDBMSs work.
</quote>

Wrong and Wrong (2 for 2).  ANSI describes isolation in terms of "phenomena".
They take three phenomena and create 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 -- Serializable and Repeatable Read are synonymous.  Read uncommitted
with its dirty read is unTHINKABLE.

but wait -- lets see what DB2 has to say in their technical documentation - not
their marketecture documentation:

<quote>
IBM Redbook for DB2

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.
</quote>

what they are talking about there is "getting the right answer".  In DB2 getting
the right answer from a query that accesses more then one record requires
leaving shared read locks behind -- that is sort of a bummer in a multi-user
environment isn't it.

or, going onto the read uncommitted level they are so proud of (apparently)

<quote>
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

</quote>

well, thats not too brilliant.

Of course, I've already addressed the "hey no one else does it like this" (which
is a really poor argument don't you think.  Is IBM a company that would jump off
a bridge because everyone else does??  I don't get where they are going with
that little tidbit of inaccuracy)

It is funny that they continue on to bash our detour into the RBS to read around
locks -- when they themselves would STOP.  "Bad Oracle -- they use extra CPU,
Good IBM -- we just stop you cold, no wasting of a resource you cannot put in
the bank and use later, no siree bob!"

Great -- they get into a technical discussion of how Oracle manages rollback and
manage to totally muck it up in the first paragraph:

<quote SRC=IBM accuracy=LOW>
Rollback segments are simply disk space set aside to store old images of data.
They are physically a set of operating system files

inaccurate -- they are a segment that may span tons of files/logical volumes

of a predefined size

inaccurate -- they can grow and shrink

that work in a circular fashion.

accurate to a point -- but they get it wrong in the following

That is, old data is put into rbs 1 followed by rbs 2 and so on. When all of the
rollback segments are full, the process wraps back around to the first segment.

wrong wrong wrong -- so wrong.

Undo Tablespaces in Oracle 9i work in a similar fashion except that the
information is stored in an Oracle managed tablespace which is made up of
operating system data files so the process is almost identical. There is a DBA
management burden to configure and maintain rollback segments.

lets look below, after this quote for a tidbit on that

How big should your rollback segments be? What if you have a single transaction
that does not fit into your rollback segments? What happens if you run out of
space?

in IBM with fixed sized logs that cannot wrap (as opposed to Oracle) -- how
big should your logs be?  what if you have a single transaction that doesn't fit
into your logs?  what if you run out of space?   POT calling KETTLE black
here?

</quote>

Ok, so Oracle unduly burdens you with RBS?  How about:

<quote src= IBM udb release notes>

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.&#65533;
</quote>

hey, whats this lock escalation thing and why didn't they talk about this
"feature" in their marketecture paper?  What undue burden does this "feature"
place on the DB2 dba.

Oh yeah, they forget to mention in 9i using undo tablespaces, the dba sets up an
undo retention period and -- oh yeah, thats it.

continueing on:

<quote>
Well running out of space is not a problem...Oracle simply cancels your
transaction! Yes that's right, if there is not enough rollback segment space for
your transaction, the transaction fails. ...
</quote>

How evil that would be (if it were true).  But wait -- ask IBM "hey, db2 guys,
what happens if i run out of log?  oh -- you cancel my transaction.  Well, I
don't think that is very nice do you?"

a) we cancel the statement -- not the transaction.
b) we can suspend the statement allowing the dba to add more space if needed
c) we can dynamically grow the space (do their logs do that?) if needed making
this self correcting if need be
d) we can even set up quotas to prevent runaways from consuming too much space
(hey, db2, can you?)

as for the tiring repeated quotes of "these are a bear to manage", tell me --
how about maxlocks, lock esculation, sizing of logs, sizing of memory -- etc etc
etc in DB2?  you are trading X for Y here.

They then go onto say Oracle is page based -- not row based (this is stretching
it a BIT far don't you think...)

<quote>
If a transaction is looking for record 5 on block 106, it may have to clone and
reconstruct several different version of block 106 even if record 5 has never
changed. With DB2, the concurrency mechanisms work on the row level so if one
transaction is locking row Y and another transaction wants to look at row X
(even if they are both on the same page), then both transaction would be able to
proceed.
</quote>

Well, lets see.  Change the example to

transaction looking for record 5.
someone is modifying record 5.

Oracle gives you -- record 5.

DB2 gives you -- the hand.  As in "stop", "wait", do not pass go, do not collect
$200 go directly to jail.  (dirty reads i guess would be their "get out of jail
free" card -- if you want dirty data)

<quote>
Similarly if you took a DB2 application and ported it directly to Oracle, you
would find that it may have problems with ORA-1555 because of the frequent
commits in the application.
</quote>

Oh that is rich.  And why did the DB2 program have frequent commits?  Why aren't
the transactions "right sized"?  Why do you commit all of the time -- even
before the TRANSACTION is finished in DB2?  Could it be because -- gasp --
readers block writers and writes block reads in DB2? (yes, yes it could be).

<quote>
Each implementation does what it is designed to do and it's the application
development that either makes the best use of the technology or runs into
issues.
</quote>

Now it is like talking to a schizophrenic person.  Moment of lucidity wrapped in
periods of craziness.  That quote above -- beautiful, perfect, excellent --
totally correct.

They actually back up my premise that "database idenpedence is a farce",
unfortunately -- they do so in a paper so riddled with mistakes that is calls
into question even the accurate information.

Then they get into the locking/blocking stuff (bottom of page 8 into 9) and say

"Note that this last example is the default behaviour with DB2"

What they don't mention is that it is the ONLY behavior with DB2.  Unless you
want to read UNCOMMITTED -- dirty data that is.

      Rating:
      5
  April 21, 2003
Reviewer:
      A reader
Tom,

DB2 page contain statement like

"Oracle's concurrency model is page based(block) not row based
In Oracle, the SCN is stored in the header of each data block (a.k.a. page).
So if any record on that block is modified, the SCN for that block is updated.
If a transaction is looking for record 5 on block 106, it may have to clone and
reconstruct several different version of block 106 even if record 5 has never
changed."

If there is five row in a block and One get changed and we are reading rows
which are not changed. According to DB2 document it has to reconstruct image
from rollback segment
fot the rows which haven't changed .

If this is the case then it is not right..

      Rating: 5
Great
      Explaination  April 22, 2003
Reviewer:  Sam

I ponder, what Mr. DB2 has to say on this..

      Rating: 5
db block gets
      on update.  May 06, 2003
Reviewer:  Jerry
       from Washington, DC
I think this is my favorite thread.  One question came up regarding the
statistics for db block gets during an update.  It seems to me that db block
gets very closely approximates the number of rows involved in an update, even
though it is a block.

Could Oracle be accessing or updating the block header for each row updated?

Here is my working example (8.1.7 on AIX):

SQL> create table t as select * from all_objects;

Table created.

SQL>
SQL> exec show_space('T');
Free Blocks.............................0
Total Blocks............................900
Total Bytes.............................7372800
Unused Blocks...........................29
Unused Bytes............................237568
Last Used Ext FileId....................31
Last Used Ext BlockId...................4033
Last Used Block.........................16

PL/SQL procedure successfully completed.

SQL>
SQL> update t set object_id = object_id;

67118 rows updated.

SQL>
SQL> commit;

Commit complete.

SQL>
SQL> set autotrace on exp stat
SQL> set timing on
SQL>
SQL> /* once again after parsing/caching */
SQL>
SQL> update t set object_id = object_id;

67118 rows updated.

Elapsed: 00:00:45.01

Execution Plan
----------------------------------------------------------
   0      UPDATE STATEMENT Optimizer=CHOOSE
   1    0   UPDATE OF 'T'
   2    1     TABLE ACCESS (FULL) OF 'T'

Statistics
----------------------------------------------------------
          0  recursive calls
      68751  db block gets
        979  consistent gets
          0  physical reads
   16924764  redo size
        545  bytes sent via SQL*Net to client
        458  bytes received via SQL*Net from client
          6  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      67118  rows processed

SQL>
SQL> commit;

Commit complete.

Elapsed: 00:00:00.00
SQL>
SQL> set autotrace off
SQL> set timing off
SQL> insert into t select * from t;

67118 rows created.

SQL>
SQL> commit;

Commit complete.

SQL>
SQL> exec show_space('T');
Free Blocks.............................1
Total Blocks............................1740
Total Bytes.............................14254080
Unused Blocks...........................0
Unused Bytes............................0
Last Used Ext FileId....................31
Last Used Ext BlockId...................4443
Last Used Block.........................60

PL/SQL procedure successfully completed.

SQL>
SQL> set autotrace on exp stat
SQL> set timing on
SQL> update t set object_id = object_id;

134236 rows updated.

Elapsed: 00:01:04.05

Execution Plan
----------------------------------------------------------
   0      UPDATE STATEMENT Optimizer=CHOOSE
   1    0   UPDATE OF 'T'
   2    1     TABLE ACCESS (FULL) OF 'T'

Statistics
----------------------------------------------------------
          0  recursive calls
     137468  db block gets
       1739  consistent gets
          0  physical reads
   33839832  redo size
        546  bytes sent via SQL*Net to client
        458  bytes received via SQL*Net from client
          6  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
     134236  rows processed

SQL>
SQL> commit;

          Followup:

yes it does -- don't forget to include indexes that could be on the table as
well....

      Rating:
      4
  May 07, 2003
Reviewer:  Arash
       from Canada
Hi Tom,

  I can't say how much I learnt from your site. I did understand how to
calculate 'Consistent Gets' for 'SELECT' statement and also what is 'db block
read' in update statement. But I don't know how to calculate or what is the
number of 'Consistent Gets' in update statement. I attached the example:

D820> exec show_space('T');
Free Blocks.............................5

Total Blocks............................164

Total Bytes.............................1343488

Unused Blocks...........................1

Unused Bytes............................8192

Last Used Ext FileId....................4

Last Used Ext BlockId...................38555

Last Used Block.........................15

PL/SQL procedure successfully completed.

D820> update t set object_id = object_id;

12016 rows updated.

Execution Plan
----------------------------------------------------------

   0      UPDATE STATEMENT Optimizer=CHOOSE

   1    0   UPDATE OF 'T'

   2    1     TABLE ACCESS (FULL) OF 'T'

Statistics
----------------------------------------------------------

       5706  recursive calls

      13109  db block gets

       3823  consistent gets

          0  physical reads

    2922208  redo size

        485  bytes sent via SQL*Net to client

        837  bytes received via SQL*Net from client

          6  SQL*Net roundtrips to/from client

          1  sorts (memory)

          0  sorts (disk)

      12016  rows processed

------------------------------------------------------
D820> select * from t;

12016 rows selected.

Execution Plan
----------------------------------------------------------

   0      SELECT STATEMENT Optimizer=CHOOSE

   1    0   TABLE ACCESS (FULL) OF 'T'

Statistics
----------------------------------------------------------

          0  recursive calls

          3  db block gets

        242  consistent gets

          0  physical reads

          0  redo size

    1676901  bytes sent via SQL*Net to client

      10132  bytes received via SQL*Net from client

         83  SQL*Net roundtrips to/from client

          0  sorts (memory)

          0  sorts (disk)

      12016  rows processed

Why 'Consistent Gets' is higher in update statement than select !? The same
arraysize(150), same table, same datablocks...

I would appreciate it if you elaborate this more.
Thanks for your time,
Arash

          Followup:

an UPDATE (and delete for that matter - and sometimes an insert) have two
components -- a consistent READ part and a modification part.

inline views will be useful here to see this, instead of coding:

update t set object_id = object_id;

say you wrote:

update
  ( select *
      from t )
 set object_id = object_id;

the part in "bold" is done as a consistent read -- the update, modification part
regets the block in current mode to actually update the data.

Why must this be? well, consider what would happen if:

create table t ( x int );
create t_idx index on t(x);

update t set x = x+100 where x > 5;

Now, say that update used t_idx to get the data, to find rows where x >5.  Now,
we get the first row -- x = 5, we update to 105 and update the index et.al.
Now, we continue one and eventually we get into rows where x > 100.... We find a
row 105 -- we already updated (but do not realize this in current mode, only in
consistent read mode).  We would make it 205 and so on.

You would have an infinite loop.  Don't laugh -- sqlserver (MS and Sybase) both
have the ability for this to happen.  I remember the first time I saw it happen
-- I wrote a program to read a table and generate a complex update.  I wrote the
update to stdout.  I had the update go into isql, something like this:

isql (with a query) | filter_to_generate_updates | isql (to do the updates)

that table had 5000 rows in it.  without consistent reads -- and since their
update will sometimes be a "delete+insert" -- what happened was row 1 became row
5001 and so on.  when we got to row 5000 instead of stopping, it kept reading.

25,000 plus updates later I killed it -- really confused.  took a while to
figure out what exactly had happened.

      Rating:
      3
  May 08, 2003
Reviewer:
      A reader
Thanks Tom.

      Rating:
      5
  May 08, 2003
Reviewer:
      A reader
Tom,
Excellent example.
Could you explain

"modification part regets the block in current mode to actually update the
data."

Thanks

          Followup:

say you:

create table t ( x int );

insert into t values ( 1 );
insert into t values ( 2 );
commit;

update t set x = x+1;

Oracle will

a) read T in consistent read mode as of the point in time the update was started
b) that'll get the single block T is on in consistent mode.
c) Oracle will see the first row -- x=1 -- and say "hey, I gotta update that"
d) Oracle will get that block in current mode, modify it and let go of it
e) Oracle will see the second row -- x=2 -- and say "hey...."
f) Oracle will get that block in current mode, modify and let go of it.

consider:

ops$tkyte@ORA920> create table t ( x int );
Table created.

ops$tkyte@ORA920> insert into t select rownum from all_users;
46 rows created.

ops$tkyte@ORA920> commit;
Commit complete.

ops$tkyte@ORA920> set autotrace traceonly statistics
ops$tkyte@ORA920> update t set x = x where rownum <= 1;

Statistics
----------------------------------------------------------
          2  db block gets
          7  consistent gets
          1  rows processed

ops$tkyte@ORA920> update t set x = x where rownum <= 2;

Statistics
----------------------------------------------------------
          2  db block gets
          7  consistent gets
          2  rows processed

ops$tkyte@ORA920> update t set x = x where rownum <= 5;

Statistics
----------------------------------------------------------
          5  db block gets
          7  consistent gets
          5  rows processed

ops$tkyte@ORA920> update t set x = x where rownum <= 10;

Statistics
----------------------------------------------------------
         10  db block gets
          7  consistent gets
         10  rows processed

ops$tkyte@ORA920> update t set x = x where rownum <= 20;

Statistics
----------------------------------------------------------
         20  db block gets
          7  consistent gets
         20  rows processed

The number of consistent gets is "consistent" (we full scanned the table in
consistent read mode each time).  The number of db block (current mode) gets
goes up as the rows modified goes up.

      Rating:
      5
  May 09, 2003
Reviewer:
      A reader
Hi Tom,
Now I am getting clear on this.

One other question

When you update table number of DB BLOCK GET goes up and up. In your example if
you update 5 rows DB BLOCK GET would be 5, for 10 rows it wold be 10 and for 20
it would be 20 and so on.....

What does that means when you update "update t set x = x where rownum <= 20" ?

Is table spread across 20 blocks or Table spread across 20 block in buffer
cahce?

Could you explain this?
Thanks,

          Followup:

that table (very very small table) would have been on a single block.  the same
block was gotten over and over and over.

ops$tkyte@ORA920> create table t ( x int );

Table created.

ops$tkyte@ORA920> insert into t select rownum from all_users;

46 rows created.

ops$tkyte@ORA920> select dbms_rowid.rowid_block_number(rowid), count(*) from t
  2  group by dbms_rowid.rowid_block_number(rowid);

DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)   COUNT(*)
------------------------------------ ----------
                                 584         46

there was one block, block 584...

      Rating:
      5
  May 09, 2003
Reviewer:
      A reader
I got it

Thanks

      Rating:
      5
  May 12, 2003
Reviewer:
      A reader
Hi Tom,

  Here is the summary of what I've understood uptill  now.
  update ( select * from t ) set object_id = object_id;
  select "select * from t" would do consistent read . (Ie If query start at 10
SCN then it will read all block below 10 SCN.
  If the block is not available then it will reconstruct image from rollback
segment.)
  Update portition would do CURRENT READ (ie It won't care about SCN it will get
latest value for the block)
  Please confirm this.

 Thanks

          Followup:

yes, confirmed.

      Rating:
      4
  May 28, 2003
Reviewer:
      A reader
Hi Tom,

  I understood what you mean by giving the following example :
"
create table t ( x int );
create t_idx index on t(x);

update t set x = x+100 where x > 5;

Now, say that update used t_idx to get the data, to find rows where x >5.  Now,
we get the first row -- x = 5, we update to 105 and update the index et.al.
Now, we continue one and eventually we get into rows where x > 100.... We find a

row 105 -- we already updated (but do not realize this in current mode, only in
consistent read mode).  We would make it 205 and so on.
"

But I couldn't simulate in MSSQL 2000 and Sybase. I mean it works fine. It
doesn't work like forever loop. It just dose the update.

example:
set transaction isolation level read uncommitted

create table t (x integer)

begin
declare @i integer;
SET @i=1;
while @i<1000
begin
  insert into t(x) values(@i)
  SET @i = @i + 1
end
end

select * from t

begin transaction
update t
set x = x+5
where x> 100

(899 row(s) affected)

Could you please tell me what cause to have this weird problem "You would have
an infinite loop.  Don't laugh -- sqlserver (MS and Sybase) both have the
ability for this to happen. " ?

Thanks for your time

          Followup:

process 1 reads and prints to stdout

process 2 converts data into update statement

process 3 updates data

( using a pipe, i had isql | awk | isql ).  If the row is updated via a
DELETE+INSERT and there are no clustered indexes, you may will be able to
reproduce this as the insert tends to go at the end of the table.

unfortunately, I don't have either of those products installed (well, actually
one won't install on any computer I have, no DLLs you know)...

You can see a similar effect by doing this

o create a large table with "known data" -- something that will take a couple of
seconds to full scan.

o make sure you know the "first" record that will be hit in the full scan and
the "last record".  eg, in Oracle I might:

insert into this_table ( some known value );
insert into this_table select lots of data;
insert into this_table ( some other known value );

Now, make sure one of the columns is a number you can "sum up".

o have two sessions ready to go

o in session one "select sum(of_that_number) from table",  remember that number.
 also, remember what value row 1 has.

o while that is running, but before it is done do these updates in session 2

  update table set of_that_number = 0 where <predicate to get the first row>;
  update table set of_that_number = <value that was in row1> where <last row>;

o make sure the updates run after the first row was read, but before the last.
Tell me, is the sum you get an answer that ever existed in your database at any
point in time?  The results can be even more amusing if

   o the update to zero is to some number other then zero (say 5)
   o the update to row 1 causes it to move to the end of the table (or
     even the middle) as it might (might not, they got better at that over time)

you can end up reading row 1 more then once giving you really ambigous results.

      Rating: 4
Many changes to
      a block in different SCNs  July 01, 2003
Reviewer:
      Oren  from Israel
Hi Tom.

Suppose a specific data block has been changed at SCNs 10, 13, 15 and 20.
And suppose that a query that started at SCN 14 needs a record from this block
(when the block is already "signed" as SCN 20).

So the block should be reconstructed from the rollback segment that contains the
"SCN 15" change AND from the rollback segment that contains the "SCN 13" change,
right?
Now, how does Oracle know which rollback segments it should read? Does it go
over all the existing rollback segments and look for that block? (I guess this
would be too expensive)

And if the rollback segment that contains the "SCN 13" change has already been
overridden, we'll get "snapshot too old", right?
But assuming the rollback segment that contains the "SCN 10" change has not been
overridden yet, how does Oracle know NOT to reconstruct the block from the "SCN
10" change (and to give ora-1555 instead)?
Is there some kind of a list for each data blcok, containing all the SCNs (or
rollback segment slots) that changed that block?

Does my question indicate I misunderstand something crucial in multi-versioning
:-( ?
Many thanks,
Oren.

          Followup:

the block header has this information.  the transaction header in the block
itself has this information.

      Rating: 4
SP2-0612: Error
      generating AUTOTRACE report when arraysize is
      reduced..????  July 01, 2003
Reviewer:
      A reader  from MN, USA
RKPD01> create table test
  2  ( x char(2000),
  3    y char(2000),
  4    z char(2000),
  5    a char(1000));

Table created.

RKPD01> begin
  2  for i in 1..10 loop
  3  insert into test values(chr(i), chr(i+1), chr(i+2), chr(i+3));
  4  end loop;
  5  end;
  6  /

PL/SQL procedure successfully completed.

RKPD01> commit;

Commit complete.

RKPD01> select count(*) from test;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'TEST'

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
         12  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
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

RKPD01> show array
arraysize 15
RKPD01> select * from test;

10 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (FULL) OF 'TEST'

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
         12  consistent gets
          0  physical reads
          0  redo size
      71296  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)
         10  rows processed

RKPD01> set arraysize 10
RKPD01> select * from test;

10 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (FULL) OF 'TEST'

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
         12  consistent gets
          0  physical reads
          0  redo size
      71296  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)
         10  rows processed

RKPD01> set arraysize 5
RKPD01> select * from test;
SP2-0612: Error generating AUTOTRACE report

10 rows selected.

SP2-0612: Error generating AUTOTRACE report

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (FULL) OF 'TEST'

SP2-0612: Error generating AUTOTRACE STATISTICS report
RKPD01> select * from test;
SP2-0612: Error generating AUTOTRACE report

10 rows selected.

SP2-0612: Error generating AUTOTRACE report

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (FULL) OF 'TEST'

SP2-0612: Error generating AUTOTRACE STATISTICS report
RKPD01> set arraysize 100
RKPD01> select * from test
  2
RKPD01> /

10 rows selected.
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (FULL) OF 'TEST'

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
         12  consistent gets
          0  physical reads
          0  redo size
      71296  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)
         10  rows processed

RKPD01> set arraysize 5
RKPD01> /
SP2-0612: Error generating AUTOTRACE report

10 rows selected.

SP2-0612: Error generating AUTOTRACE report

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (FULL) OF 'TEST'

SP2-0612: Error generating AUTOTRACE STATISTICS report

as you see i m getting this error when i reduce the arraysize to a low value but
not when i set it sufficiently high.. what is the reason for the same....am i
doing something wrong here?? Oracle 8.1.7

          Followup:

looks like a small issue with 8i sqlplus and autotrace with arraysizes below 9.

not a "big deal"

      Rating: 5
Thanks for the
      quick response :)  July 01, 2003
Reviewer:
      A reader  from MN, USA

      Rating: 5
more the
      consistent gets more the usage of cpu? Thanks.  August 09,
      2003
Reviewer:  Reader

          Followup:

more cpu, more latches (which are locks) more work.

      Rating: 5
SO, SETTING THE
      ARRAY SIZE IS IMPORTANT TO MINIMIZE THE CPU USAGE?
      THANKS.  August 10, 2003
Reviewer:  reader

          Followup:

IT IS IMPORTANT for performance (less network roundtrips), for scalability (less
latching), and for resource utilization (less resources used).

yes....

      Rating: 5
why I am not
      seeing values......?  August 11, 2003
Reviewer:
      reader
Please see below. Am I missing something? Why can't I see the statistics? Thanks
for your time.

SQL> @$ORACLE_HOME/rdbms/admin/utlxplan
SQL> rem
SQL> rem $Header: utlxplan.sql 29-oct-2001.20:28:58 mzait Exp $ xplainpl.sql
SQL> rem
SQL> Rem Copyright (c) 1988, 2001, Oracle Corporation.  All rights reserved.
SQL> Rem NAME
SQL> REM    UTLXPLAN.SQL
SQL> Rem  FUNCTION
SQL> Rem  NOTES
SQL> Rem  MODIFIED
SQL> Rem        mzait   10/26/01  - add keys and filter predicates to the plan e
SQL> Rem        ddas    05/05/00  - increase length of options column
SQL> Rem        ddas    04/17/00  - add CPU, I/O cost, temp_space columns
SQL> Rem        mzait   02/19/98 -  add distribution method column
SQL> Rem        ddas    05/17/96 -  change search_columns to number
SQL> Rem        achaudhr        07/23/95 -  PTI: Add columns partition_{start, }
SQL> Rem        glumpkin        08/25/94 -  new optimizer fields
SQL> Rem        jcohen  11/05/93 -  merge changes from branch 1.1.710.1 - 9/24
SQL> Rem        jcohen  09/24/93 - #163783 add optimizer column
SQL> Rem        glumpkin        10/25/92 -  Renamed from XPLAINPL.SQL
SQL> Rem        jcohen  05/22/92 - #79645 - set node width to 128 (M_XDBI in ge)
SQL> Rem        rlim    04/29/91 -         change char to varchar2
SQL> Rem   Peeler        10/19/88 - Creation
SQL> Rem
SQL> Rem This is the format for the table that is used by the EXPLAIN PLAN
SQL> Rem statement.     The explain statement requires the presence of this
SQL> Rem table in order to store the descriptions of the row sources.
SQL>
SQL> create table PLAN_TABLE (
  2          statement_id    varchar2(30),
  3          timestamp       date,
  4          remarks         varchar2(80),
  5          operation       varchar2(30),
  6          options         varchar2(255),
  7          object_node     varchar2(128),
  8          object_owner    varchar2(30),
  9          object_name     varchar2(30),
 10          object_instance numeric,
 11          object_type     varchar2(30),
 12          optimizer       varchar2(255),
 13          search_columns  number,
 14          id              numeric,
 15          parent_id       numeric,
 16          position        numeric,
 17          cost            numeric,
 18          cardinality     numeric,
 19          bytes           numeric,
 20          other_tag       varchar2(255),
 21          partition_start varchar2(255),
 22          partition_stop  varchar2(255),
 23          partition_id    numeric,
 24          other           long,
 25          distribution    varchar2(30),
 26          cpu_cost        numeric,
 27          io_cost         numeric,
 28          temp_space      numeric,
 29          access_predicates varchar2(4000),
 30          filter_predicates varchar2(4000));

Table created.

SQL>
SQL> set autotrace traceonly statistics
SQL> select * from t;

6197 rows selected.

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)
       6197  rows processed

          Followup:

you are logged in as sys.

You should never use sys, system, or ANY of the oracle accounts for anything.
they are "ours"

Create your own account to play with.

      Rating: 5
Why the
      difference in consistent gets?  September 05, 2003

Reviewer:  Tony  from INDIA
I have two tables, SMALL and BIG

SQL> select count(*) from small;

    COUNT(*)
    ----------
     104

SQL> select count(*) from big;

  COUNT(*)
   ----------
     34486

I issued the query SELECT COUNT(*) FROM small, big (without join) in RBO and got
different consistent gets and elapse time when the table order was changed. Why
the consistent get was different when table order changed? Why the consistent
get was different for CBO also? Plans are given below: Please clear my doubt.

RBO:
-------
SQL> select /*+ rule */ count(*) from small, big;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: RULE
   1    0   SORT (AGGREGATE)
   2    1     NESTED LOOPS
   3    2       TABLE ACCESS (FULL) OF 'BIG'
   4    2       TABLE ACCESS (FULL) OF 'SMALL'

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     172822  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

SQL> select /*+ rule */ count(*) from big,small;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: RULE
   1    0   SORT (AGGREGATE)
   2    1     NESTED LOOPS
   3    2       TABLE ACCESS (FULL) OF 'SMALL'
   4    2       TABLE ACCESS (FULL) OF 'BIG'

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      40773  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

CBO:
------

SQL> select count(*) from small, big;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4058 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     MERGE JOIN (CARTESIAN) (Cost=4058 Card=3586544)
   3    2       TABLE ACCESS (FULL) OF 'SMALL' (Cost=2 Card=104)
   4    2       BUFFER (SORT) (Cost=4056 Card=34486)
   5    4         TABLE ACCESS (FULL) OF 'BIG' (Cost=39 Card=34486)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        397  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
          1  sorts (memory)
          0  sorts (disk)
          1  rows processed

          Followup:

the plan is:

  for x in ( select * from T1 )
    for y in ( select * from T2 )
        output x+y
    end loop
  end loop

from the rbo.  The formula for the consistent gets will then be:

number_of_blocks_in_t1 + number_of_rows_in_t1 * number_of_blocks_in_t2

so, here are some sample numbers to play with:

ops$tkyte@ORA920> declare
  2      l_small_rows   number := 104;
  3      l_big_rows     number := 34486;
  4
  5      type numArray is table of number;
  6
  7      l_small_blocks numArray := numArray( 1,     2,    4,    8,   16 );
  8      l_big_blocks   numArray := numArray( 300, 600, 1200, 2400, 4800 );
  9
 10      l_small_drives number;
 11      l_big_drives number;
 12  begin
 13      for i in 1 .. l_small_blocks.count
 14      loop
 15          for j in 1 .. l_big_blocks.count
 16          loop
 17              dbms_output.put_line( 'small blocks = ' || l_small_blocks(i) ||
 18                                    ' big blocks = ' || l_big_blocks(i) );
 19
 20              l_small_drives := (l_small_blocks(i) +
l_small_rows*l_big_blocks(j));
 21              l_big_drives   := (l_big_blocks(i) +
l_big_rows*l_small_blocks(j));
 22              dbms_output.put_line( 'small drives = ' || l_small_drives ||
 23                                    ' big drives = ' || l_big_drives ||
 24                                    ' difference = ' || abs(
l_big_drives-l_small_drives ) );
 25          end loop;
 26      end loop;
 27  end;
 28  /
small blocks = 1 big blocks = 300
small drives = 31201 big drives = 34786 difference = 3585
small blocks = 1 big blocks = 300
small drives = 62401 big drives = 69272 difference = 6871
small blocks = 1 big blocks = 300
small drives = 124801 big drives = 138244 difference = 13443
small blocks = 1 big blocks = 300
small drives = 249601 big drives = 276188 difference = 26587
small blocks = 1 big blocks = 300
small drives = 499201 big drives = 552076 difference = 52875
small blocks = 2 big blocks = 600
small drives = 31202 big drives = 35086 difference = 3884
small blocks = 2 big blocks = 600
small drives = 62402 big drives = 69572 difference = 7170
small blocks = 2 big blocks = 600
small drives = 124802 big drives = 138544 difference = 13742
small blocks = 2 big blocks = 600
small drives = 249602 big drives = 276488 difference = 26886
small blocks = 2 big blocks = 600
small drives = 499202 big drives = 552376 difference = 53174
small blocks = 4 big blocks = 1200
small drives = 31204 big drives = 35686 difference = 4482
small blocks = 4 big blocks = 1200
small drives = 62404 big drives = 70172 difference = 7768
small blocks = 4 big blocks = 1200
small drives = 124804 big drives = 139144 difference = 14340
small blocks = 4 big blocks = 1200
small drives = 249604 big drives = 277088 difference = 27484
small blocks = 4 big blocks = 1200
small drives = 499204 big drives = 552976 difference = 53772
small blocks = 8 big blocks = 2400
small drives = 31208 big drives = 36886 difference = 5678
small blocks = 8 big blocks = 2400
small drives = 62408 big drives = 71372 difference = 8964
small blocks = 8 big blocks = 2400
small drives = 124808 big drives = 140344 difference = 15536
small blocks = 8 big blocks = 2400
small drives = 249608 big drives = 278288 difference = 28680
small blocks = 8 big blocks = 2400
small drives = 499208 big drives = 554176 difference = 54968
small blocks = 16 big blocks = 4800
small drives = 31216 big drives = 39286 difference = 8070
small blocks = 16 big blocks = 4800
small drives = 62416 big drives = 73772 difference = 11356
small blocks = 16 big blocks = 4800
small drives = 124816 big drives = 142744 difference = 17928
small blocks = 16 big blocks = 4800
small drives = 249616 big drives = 280688 difference = 31072
small blocks = 16 big blocks = 4800
small drives = 499216 big drives = 556576 difference = 57360

PL/SQL procedure successfully completed.

As you can see -- the number of consistent gets for that plan is radically
affected by the NUMBER OF BLOCKS in the table.

the cbo plan -- it is not even remotely comparable.  but it basically read BIG
once, sorted it and read it from temp (temp reads are NOT consistent gets)

      Rating: 5
Client
      Arraysize won't affect the consistent gets?  September 06,
      2003
Reviewer:  Tony  from India
Thanks for my question on "Why the difference in consistent gets?". You have
given a new formula to calculate consistent gets. But you have mentioned on many
occations that consistent get (logical read, if I'm not wrong) depends on client
array size, in my case SQL Plus array size. Please clarify.

          Followup:

clarity is in the original answer and followups??

that was the crux of the first answer on this page.  do you have a specific
question?

      Rating: 5
More consistent
      gets Less elapsed time. Why?  September 06, 2003

Reviewer:  Tony  from India
[Continuation of the above question....]

When consistent get is more, elapsed time is less. And
When consistent get is less, elapsed time is more for the above query plan for
the question on "Why the difference in consistent gets?"

Why elapsed time is inversely proportional to consistent gets in my case?.
Please clear my doubt.

          Followup:

give me a "for example" to work with at least.

      Rating: 5
Why elapsed
      time is high when consistent get is low?  September 08, 2003

Reviewer:  Tony  from India
Why elapsed time is high when consistent get is low and elapsed time is low when
consistent get is high?

For example:

SQL>  select count(*) from small;

  COUNT(*)
   ----------
     104

SQL> select count(*) from big;

  COUNT(*)
   ----------
   34486

SQL> select /*+ rule */ count(*) from small, big;

Elapsed: 00:00:01.08

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: RULE
   1    0   SORT (AGGREGATE)
   2    1     NESTED LOOPS
   3    2       TABLE ACCESS (FULL) OF 'BIG'
   4    2       TABLE ACCESS (FULL) OF 'SMALL'

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     172822  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

SQL>  select /*+ rule */ count(*) from big, small;

Elapsed: 00:00:02.00

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: RULE
   1    0   SORT (AGGREGATE)
   2    1     NESTED LOOPS
   3    2       TABLE ACCESS (FULL) OF 'SMALL'
   4    2       TABLE ACCESS (FULL) OF 'BIG'

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      40773  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

          Followup:

use tkprof.

first, the autotrace stuff takes time and is counted.

second, you are seeing the total elapsed time -- including sqlplus "screen
paint", network, etc

use tkprof to find out for real what is taking place.

      Rating:
      5
Outstanding  September 10, 2003
Reviewer:
      Jim  from MA
Tom, you're still the one!  This thread is rockin!

      Rating: 5
Setting
      arraysize for an oci app  October 06, 2003

Reviewer:  Ma$e
Tom:

Following up on the arraysize discussion.

I have an app that connects to the oracle d/b via oci calls. How can I test the
effect of arraysize for this app ?
Does arraysize even come into consideration for an oci app ?

Thanks again for your help.

Ma$e

          Followup:

yes, it makes sense/comes into consideration.

Look at the OCIDefine functions -- valuep can be an array.

      Rating: 4
Can't find
      OCIDefine function calls  October 06, 2003

Reviewer:  Ma$e
Tom:

I searched for OCIDefine function calls in the source code library and found no
hits. Chances are pretty good that we are using the OCI 7 calls. I tried looking
for its equivalent name from the "old days" and did not get anywhere.

Would you know what this function call was known in its previous incarnation ?

Thanks in advance.

Ma$e

          Followup:

ocidefinebypos

for example in 8 and above

see $ORACLE_HOME/rdbms/demo/oci06.c for "old style" array fetch example

      Rating: 5
Sorry I also
      forgot to ask another Q.  October 06, 2003

Reviewer:  Ma$e
How can the end user set the array size for an oci app without access to the
source code?

Or in other words:

We are s/w developpers: How can we give the option to the user of setting the
value for this to the end user?

Can it be set via a logon trigger or some other means, for the user that we use
to establish an oci connection to the d/b with ?

Thanks
Ma$e

          Followup:

you, as developers, would have a parameter file with configuration options that
the end user could edit.

it is a CLIENT thing, cannot be done in the server.

      Rating: 5
What was I
      thinking....  October 06, 2003
Reviewer:
      A reader
Hi Tom:

Absolutely correct. The arraysize setting is a client side setting. What was I
thinking....

So let me understand. The application would open the parameter file and assign a
value to an internal variable which we would pass to the oci function call as
our array size (simillar to the init.ora)!!! You can tell I'm a DBA.

Any other possible way?

Since this is unlikely to happen unless I can demonstrate to the developpers
(yet another battle between a DBA and the other guys) that this will benefit the
end user in a tangible form that the developpers can understand. So I need to be
able to demonstrate without any shadow of doubt of the benefits for a user
settable array size.

I found a "C" programming example on metalink:

http://metalink.oracle.com/metalink/plsql/ml2_documents.showFrameDocument?p_database_id=NOT&p_id=73823.1
However this example does not demonstrate "roi" for different arraysize that the
developpers can understand.

Can you help (please wear both hats - developper and DBA) ?

Thanks

Ma$e

          Followup:

the ROI?

I do that all of the time with runstats and sqlplus.  I did it in chapter 2 of
my new book "Effective Oracle By Design".  I do it in my "whats wrong
presentation" i give from time to time.  Here is short excerpt showing the
effects of array fetching on logical IO's from the book:

<quote>
Array Size Effects

The array size is the number of rows fetched (or sent, in the case of inserts,
updates, and deletes) by the server at a time. It can have a dramatic effect on
performance.

To demonstrate the effects of array size, we'll run the same query a couple of
times and look at just the consistent get differences between runs:

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

ops$tkyte@ORA920> set autotrace traceonly statistics;

ops$tkyte@ORA920> set arraysize 2
ops$tkyte@ORA920> select * from t;
29352 rows selected.

Statistics
----------------------------------------------------------
      14889  consistent gets

Note how one half of 29,352 (rows fetched) is very close to 14,889, the number
of consistent gets. Every row we fetched from the server actually caused it to
send two rows back. So, for every two rows of data, we needed to do a logical
I/O to get the data. Oracle got a block, took two rows from it, and sent it to
SQL*Plus. Then SQL*Plus asked for the next two rows, and Oracle got that block
again or got the next block, if we had already fetched the data, and returned
the next two rows, and so on.

Next, let's increase the array size:

ops$tkyte@ORA920> set arraysize 5
ops$tkyte@ORA920> select * from t;
29352 rows selected.

Statistics
----------------------------------------------------------
       6173  consistent gets

Now, 29,352 divided by 5 is about 5,871, and that would be the least amount of
consistent gets we would be able to achieve (the actual observed number of
consistent gets is slightly higher). All that means is sometimes in order to get
two rows, we needed to get two blocks: we got the last row from one block and
the first row from the next block.

Let's increase the array size again:

ops$tkyte@ORA920> set arraysize 10
ops$tkyte@ORA920> select * from t;
29352 rows selected.

Statistics
----------------------------------------------------------
       3285  consistent gets

ops$tkyte@ORA920> set arraysize 15
ops$tkyte@ORA920> select * from t;
29352 rows selected.

Statistics
----------------------------------------------------------
       2333  consistent gets

ops$tkyte@ORA920> set arraysize 100
ops$tkyte@ORA920> select * from t;
29352 rows selected.

Statistics
----------------------------------------------------------
        693  consistent gets

ops$tkyte@ORA920> set arraysize 5000
ops$tkyte@ORA920> select * from t;
29352 rows selected.

Statistics
----------------------------------------------------------
        410  consistent gets

ops$tkyte@ORA920> set autotrace off

As you can see, as the array size goes up, the number of consistent gets goes
down. So, does that mean you should set your array size to 5,000, as in this
last test? Absolutely not.

If you notice, the overall number of consistent gets has not dropped
dramatically between array sizes of 100 and 5,000. However, the amount of RAM
needed on the client and server has gone up with the increased array size. The
client must now be able to cache 5,000 rows. Not only that, but it makes our
performance look choppy: The server works really hard and fast to get 5,000
rows, then the client works really hard and fast to process 5,000 rows, then the
server, then the client, and so on. It would be better to have more of a stream
of information flowing: Ask for 100 rows, get 100 rows, ask for 100, process
100, and so on. That way, both the client and server are more or less
continuously processing data, rather than the processing occurring in small
bursts.
</quote>

and to put it in terms they (coders) will understand -- array fetching goes
faster.  On:

http://asktom.oracle.com/~tkyte/flat/index.html
I have a pro*c program that accepts an array size as input.  Look at the
difference between single row fetching:

[tkyte@tkyte-pc array_flat]$ time ./array_flat userid=big_table/big_table
'sqlstmt=select * from big_table where rownum <= 100000' arraysize=1 > /dev/null

Connected to ORACLE as user: big_table/big_table

Unloading 'select * from big_table where rownum <= 100000'
Array size = 1
ID,OWNER,OBJECT_NAME,SUBOBJECT_NAME,OBJECT_ID,DATA_OBJECT_ID,OBJECT_TYPE,CREATED,
LAST_DDL_TIME,TIMESTAMP,STATUS,TEMPORARY,GENERATED,SECONDARY
100000 rows extracted

real    1m10.250s
user    0m30.852s
sys     0m2.365s
[tkyte@tkyte-pc array_flat]$ !!
time ./array_flat userid=big_table/big_table 'sqlstmt=select * from big_table
where rownum <= 100000' arraysize=1 > /dev/null

Connected to ORACLE as user: big_table/big_table

Unloading 'select * from big_table where rownum <= 100000'
Array size = 1
ID,OWNER,OBJECT_NAME,SUBOBJECT_NAME,OBJECT_ID,DATA_OBJECT_ID,OBJECT_TYPE,CREATED,
LAST_DDL_TIME,TIMESTAMP,STATUS,TEMPORARY,GENERATED,SECONDARY
100000 rows extracted

real    1m13.662s
user    0m20.514s
sys     0m0.910s

versus array fetching:

[tkyte@tkyte-pc array_flat]$ time ./array_flat userid=big_table/big_table
'sqlstmt=select * from big_table where rownum <= 100000' arraysize=100 >
/dev/null

Connected to ORACLE as user: big_table/big_table

Unloading 'select * from big_table where rownum <= 100000'
Array size = 100
ID,OWNER,OBJECT_NAME,SUBOBJECT_NAME,OBJECT_ID,DATA_OBJECT_ID,OBJECT_TYPE,CREATED,
LAST_DDL_TIME,TIMESTAMP,STATUS,TEMPORARY,GENERATED,SECONDARY
100000 rows extracted

real    0m6.145s
user    0m3.816s
sys     0m0.211s
[tkyte@tkyte-pc array_flat]$
[tkyte@tkyte-pc array_flat]$ !!
time ./array_flat userid=big_table/big_table 'sqlstmt=select * from big_table
where rownum <= 100000' arraysize=100 > /dev/null

Connected to ORACLE as user: big_table/big_table

Unloading 'select * from big_table where rownum <= 100000'
Array size = 100
ID,OWNER,OBJECT_NAME,SUBOBJECT_NAME,OBJECT_ID,DATA_OBJECT_ID,OBJECT_TYPE,CREATED,
LAST_DDL_TIME,TIMESTAMP,STATUS,TEMPORARY,GENERATED,SECONDARY
100000 rows extracted

real    0m5.491s
user    0m3.795s
sys     0m0.230s
[tkyte@tkyte-pc array_flat]$

that should raise an eyebrow or two -- hey, you mean we can make our code
run faster with array fetching!

      Rating: 5
How can I thank
      you....  October 07, 2003
Reviewer:  Ma$e

Tom:

There is so much value in your response. I can't than you enough for this. I'm
still digesting your response.

One thing for sure I am going to read your book first before asking you
questions that have been answered there. I feel bad. I do own both your books.
However, it's your comments that preceede or follow your quotes that are
invaluble.

Thanks one again. May God bless you.

Ma$e

      Rating:
      4
Clarification  October 27, 2003
Reviewer:
      A reader  from NJ, USA
In your response to one of the reader's queries, you had mentioned a case where
a table has 100 rows across 10 blocks with an array size of 10 and the total
consistent gets is 20. Can you explain me how you arrived at 20. My calculation
is as follows :

100 rows
10 rows/block
Array size 10

First fetch (1 consistent get) - 10 rows/0 rows left
Second fetch (2 consistent gets) - 10 rows/0 rows left
....
Tenth fetch (10 consistent gets) - 10 rows/0 rows left

My question is that since the number of rows/block is the same as the array size
in the above example, it should do 10 consistent gets exactly. Can you explain
me how you arrived at 20 consistent gets instead 10.Is Oracle revisiting blocks
again or how you have arrived at 20 consistent block gets?

          Followup:

it picks up where it left off -- on the last block.  you know it had 10 rows, I
know it had 10 rows, but the database just said "get me 10 and remember where we
stopped".  it didn't look necessarily to see that "hey, there isn't an 11th row,
we should pick up on the NEXT block next time"

with the full scan extent map reading -- it is actually "more" then 20

      Rating: 5
consistent gets
      and db block gets   October 30, 2003
Reviewer:
      Ivan  from China
Tom -

  I am confused about db block gets and consistent gets.

  1. db block gets can only be concerned for full table
     scan. Why ?
     Why it is not related to the data we selected ?

  2. Consistent gets can be two things( Pleas clarify )
     i. from buffer cache if buffer cache has the data. For
        example,
        Person A selected the table T. After, Person B
        selected the table T again.
        So Person B can get the data in memory. This can be
        called consistent gets.

     ii. from rollback segment.
         Person A selected a row in table A. But that row
         was updated by Person B.
         So Person A needs to get the data from rollback
         segment. It will cause
         two things to happen.
         Frist ,  Physical reads  - from rollback segmet
         into buffer cache.
         Second,  consistent gets - reading the data from
         buffer cache.

Ivan

          Followup:

1) it is not concerned only for full table scans.  It is a mode blocks are
gotten in in many cases.

blocks are either gotten in current mode (db block gets) or consistent read mode
(consistent gets).  We get the blocks in the mode we need them.  If you do not
know what "consistent gets" means -- please see the concepts guide and read the
concurrency control sections -- and learn about multi-versioning and read
consistency.

2) consistent gets ALWAYS come from the buffer cache.

rollback is buffered in the cache.

but yes, a consistent get may well involved reading rollback data for the
affected segment.

      Rating: 5
meaning of
      current mode   October 30, 2003
Reviewer:
      Ivan  from China
Tom
    I am not still clear about the current mode.

    Please clarify whether I am correct .

    Assume every block has a time in it. It will
    record the most updated time if it is modified.

    For example, at time t, Person A select * from t.

    Consisten mode means getting the block wiht time <= t.

    If we need  to get a block with time > t, we need to
    roll it back to get the consistent data.

    Since we are performing full table scan, we need
    currnet mode(the block with most updated time) to tell
    us where is the segment header.

          Followup:

current mode = get the block as it exists right now.

consistent mode = get the block as it existed at some point in the past.

      Rating: 4
Total Reads vs
      Memory reads  November 03, 2003
Reviewer:
      A reader  from NJ, USA
Tom,

In the book "Performance Tuning Tips and Techniques(TUSC)", Rich indicates that
the Total reads = "Consistent Gets" + "Db Block Gets" and the Memory reads =
Total Reads - Disk Reads. My understanding is Total Reads = Logical I/O +
Physical I/O where Logical I/O = Consistent Gets + Db Block Gets.
    a. Can you please clarify me as to what he means by Memory reads.
    b. Since Physical I/O is initiated by a Logical I/O, the memory reads are
only those reads that do not include the physical I/o part of it.

Please clarify

          Followup:

that is wrong.

A physical IO performed in order to read a block into the cache will be followed
by a subsequent logical IO to get the block out of the buffer cache.  We can
observe this via something like this:

big_table@ORA920LAP> alter tablespace users offline;

Tablespace altered.

big_table@ORA920LAP> alter tablespace users online;

Tablespace altered.

big_table@ORA920LAP>
big_table@ORA920LAP> select blocks from user_tables where table_name =
'BIG_TABLE';

    BLOCKS
----------
     13804

big_table@ORA920LAP> set autotrace on
big_table@ORA920LAP> select /*+ FULL(big_table) */ count(*) from big_table;

  COUNT(*)
----------
   1000000

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=998400)

Statistics
----------------------------------------------------------
        458  recursive calls
          0  db block gets
      13769  consistent gets
      13647  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
          6  sorts (memory)
          0  sorts (disk)
          1  rows processed

big_table@ORA920LAP> set autotrace off

The alter's invalidate the buffers in the cache, ensuring a PIO.  As you can
see, if we use the formula:

memory reads = total reads - disk reads

we'd get it wrong.  memory reads = consistent gets+db block gets.

I don't know what he means by memory reads, sometimes information taken out of
context is hard to interpret.  I don't know if it is a "mistake" or just taken
out of context.

There are other nuances to consider as well -- direct IO done to TEMP (reads and
writes) will appear as PIO but not as LIO -- that is, you can have a query that
does more PIO then LIO (rare, but can happen) due to this.

I've been asked from time to time to put out "challenges".  As I'm pressed for
time this morning -- I'll try one.

In the following output -- we always process 10,000 or 20,000 rows.  Only
difference is a sort to disk.  Soooooo tell me -- why the big differences in
LIO's and PIO's between the two???

but it shows that the above formulas are not really accurate. some PIO's are
followed by LIO's -- this is the "normal" case.  some PIO's (direct reads) are
not (hash joins, sorts may well result in this)

big_table@ORA920LAP> alter session set workarea_size_policy=manual;

Session altered.

big_table@ORA920LAP> alter session set sort_area_size = 65537;

Session altered.

big_table@ORA920LAP>
big_table@ORA920LAP> set autotrace  traceonly
big_table@ORA920LAP> select * from big_table where rownum <= 10000;

10000 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1329 Card=10000 Bytes=940000)
   1    0   COUNT (STOPKEY)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1329 Card=998400
Bytes=93849600)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        794  consistent gets
        134  physical reads
          0  redo size
     773075  bytes sent via SQL*Net to client
       7825  bytes received via SQL*Net from client
        668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      10000  rows processed

big_table@ORA920LAP> select * from big_table where rownum <= 10000 order by
1,2,3,4,5,6,7,8;

10000 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=82617 Card=10000 Bytes=940000)
   1    0   SORT (ORDER BY) (Cost=82617 Card=10000 Bytes=940000)
   2    1     COUNT (STOPKEY)
   3    2       TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1329 Card=998400
Bytes=93849600)

Statistics
----------------------------------------------------------
          0  recursive calls
          2  db block gets
        138  consistent gets
        257  physical reads
          0  redo size
     773086  bytes sent via SQL*Net to client
       7825  bytes received via SQL*Net from client
        668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          1  sorts (disk)
      10000  rows processed

big_table@ORA920LAP> select * from big_table where rownum <= 20000;

20000 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1329 Card=20000 Bytes=1880000)
   1    0   COUNT (STOPKEY)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1329 Card=998400
Bytes=93849600)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       1591  consistent gets
        257  physical reads
          0  redo size
    1570644  bytes sent via SQL*Net to client
      15162  bytes received via SQL*Net from client
       1335  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      20000  rows processed

big_table@ORA920LAP> select * from big_table where rownum <= 20000 order by
1,2,3,4,5,6,7,8;

20000 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=82617 Card=20000
Bytes=1880000)
   1    0   SORT (ORDER BY) (Cost=82617 Card=20000 Bytes=1880000)
   2    1     COUNT (STOPKEY)
   3    2       TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1329 Card=998400
Bytes=93849600)

Statistics
----------------------------------------------------------
          0  recursive calls
          3  db block gets
        279  consistent gets
        536  physical reads
          0  redo size
    1570607  bytes sent via SQL*Net to client
      15162  bytes received via SQL*Net from client
       1335  SQL*Net roundtrips to/from client
          0  sorts (memory)
          1  sorts (disk)
      20000  rows processed

big_table@ORA920LAP> set autotrace off

      Rating: 4
Consistent
      Gets  November 03, 2003
Reviewer:
      A reader  from NJ, USA
Tom,

In one of the discussions for Consistent gets, you mention that
"it took 10k consistent gets for Y -- versus 7 for X -- both queries return the
same data -- it is just that for the first fetch from Y we had to "undo" the
10,000 updates that happened between the OPEN of Y and the fetch."

You mention that we need to reduce the number of "consistent gets" in order to
fine tune a query. In this example, there is no problem with our query because
it just reads one record from table. However, the "consistent gets" figure is so
huge because of the UPDATE and the way Oracle uses to reconstruct a consistent
image and so we might try to determine as to why this query made such huge
consistent gets to return just 1 row. Given this, how do we differentiate
between :
 a. A really bad query with a large number of "consistent gets"
 b. A single-row query that shows a large number of "consistent gets" just
because of an UPDATE that happened between the OPEN and FETCH (as in the example
above).
 c. What other trace result might indicate that there is no problem with the
query, but it is just because of the UPDATE that happened in between and how can
we reduce the consistent gets in the above case

Thanks

          Followup:

this sort of shows why the outline I had in chapter 10 of Expert one on one
Oracle is so important.

FIRST

a) tune in isolation. get that app really tuned in single user mode.  here you
are isolated from external events like this

THEN

b) tune in multi-user situations -- test to scale.  then side effects from other
users will become apparent (locking, blocking, things like this)

and lastly

c) tune the "system" after A and B are done.

In an OLTP environment -- for the query to be really affected by this consistent
read mechanism means you are running a really really long query in a system with
tons of updates.  Normally the queries would be short and sweet -- the odds of
having to undo 10,000 updates would be exceedingly small.

In a read mostly environment -- it won't happen of course.

So, it is more of a "mixed workload" environment and would only be suspected in
the execution of long running queries against active tables (eg: your KNOWLEDGE
of the charactertistics of the system will be crucial in looking at information)

      Rating: 4
Colocated vs
      Disorganized  November 05, 2003
Reviewer:
      A reader  from NJ USA
Tom,

I read your book Expert One-on-One Oracle on the topic on indexes where you
explain about how physical placement of data affects consistent gets. I have the
following understanding and I have the following questions :

Understanding :

Non-SQL*Plus environment, processing 1 row at a time, data colocated :

Assume that we want to retreive 100 rows ie x between 1 and 100 and that the
index and table blocks are not in buffer cache. Oracle reads the index block
which constitutes 1 physical I/O (in turn a 1 logical I/O). Since each rowid in
the index block points to the same data block, Oracle will read the first data
block from disk and returns the first row which constitutes 1 physical I/O (in
turn 1 logical I/O). It gets the next record from the index block and since the
data is colocated it finds the next record in the same table block as the first
one and so this time it performs just 1 logical I/O. Continuing in this manner,
Oracle will at the best case perform N+1 logical reads (1 index block read and N
table block read) for  retreiving N colocated rows

Questions :

1. Is my understanding correct
2. In that example in your book, Oracle performs 2909 logical I/Os for getting
20000 rows in colocated table. Can you explain me how Oracle performed 2909
consistent reads?
3. The only difference in the case of colocated and disorganised is that Oracle
may have to perform lot of physical reads in the case of disorganized table
because the index entry may not point to a row in the same physical block.
However, colocated table will perform only logical I/O since the data are closer
to each other. Is it correct?
4. Is the autotrace got using SQL*PLus which uses an arraysize of 15?

          Followup:

1) well, it'll get the index block to find the table block, and then get the
index block to find the next table block, and so on.

ops$tkyte@ORA920LAP> set echo on
ops$tkyte@ORA920LAP> /*
DOC>drop table t;
DOC>create table t ( x int, y int );
DOC>create index t_idx on t(x);
DOC>insert into t select rownum , rownum from all_objects;
DOC>*/
ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA920LAP> begin
  2          for x in ( select * from t where x < 100 )
  3          loop
  4                  null;
  5          end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> begin
  2          for x in ( select * from t where x < 200 )
  3          loop
  4                  null;
  5          end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

select * from t where x < 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      100      0.00       0.00          0        200          0          99
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      102      0.00       0.00          0        200          0          99

Rows     Row Source Operation
-------  ---------------------------------------------------
     99  TABLE ACCESS BY INDEX ROWID T (cr=200 r=0 w=0 time=1748 us)
     99   INDEX RANGE SCAN T_IDX (cr=101 r=0 w=0 time=984 us)(object id 41082)
********************************************************************************
select * from t where x < 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          0          0          0           0
Fetch      200      0.00       0.03          0        400          0         199
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      202      0.00       0.03          0        400          0         199

Rows     Row Source Operation
-------  ---------------------------------------------------
    199  TABLE ACCESS BY INDEX ROWID T (cr=400 r=0 w=0 time=29098 us)
    199   INDEX RANGE SCAN T_IDX (cr=201 r=0 w=0 time=27490 us)(object id 41082)

so, 100 rows -- 200 consistent gets.  200 rows -- 400 consistent gets.  2
consistent gets per row....

2) well -- it is a matter of the number of rows per block, the array size and
such.  It just "was" in this case.  do that test case and play with the
arraysize - set it up and watch that go down. set it down and watch it go up.

3) no, no it isn't.  you need to look closer at the example.  look at the
consistent gets between colocated and disorganized.  It is all about the LOGICAL
IO, even more so then physical.  they may well perform the same amount of
physical io.

4) yes...

      Rating: 4
N + 1
      gets  November 06, 2003
Reviewer:
      A reader  from NJ, USA
Tom,

Then could you pls. explain me under what situations we would get a best case of
N + 1 gets to retreive N rows. Is it that the best case will always be 2 * N?

      Rating:
      4
Help  November 06, 2003
Reviewer:
      A reader  from NJ, USA
Tom,

If Oracle is anyway going to do 2 consistent gets per row even if the data is
colocated, then can you explain me what difference it makes if the data is
colocated or disorganized. The only thing if the data is colocated is that the
table block will already be in the buffer cache. But if the data is disorganized
the index entry may point to the table block not in the buffer cache and so
Oracle will perform a physical I/O and then a logical I/O. (So an extra physical
I/O). If that is the case, why so much difference between consistent gets if the
data is colocated and disorganized. In that case, the difference should only be
in the physical reads and not "consistent gets" (since consistent gets is
performed in both cases to access the block from buffer cache). Guess, I am
still not understanding clearly what consistent gets means. Pls explain
Does consistent gets increase each time the same block in the buffer cache is
visited again or only if a new block is brought over into the buffer cache?

      Rating: 4
Consistent Gets
      - Misleading  November 06, 2003
Reviewer:
      A reader  from NJ, USA
Tom,

I have a table called LOT_LEVEL_POSITION in our development environment and it
has a non-unique index LOT1 on 3 columns
(POSITION_ID,SECURITY_ALIAS,LOT_NUMBER). There are around 63191 rows in that
table in our development database and around 53214898 on our production m/c. I
ran the following 2 queries on the development database and here are the
autotrace output.

select
position_id,security_alias,lot_number,orig_lot_number,update_date,update_source
  from holdingdbo.lot_level_position llp
 where 1 < (select count(*)
              from holdingdbo.lot_level_position i
             where i.position_id = llp.position_id
               and i.security_alias = llp.security_alias
               and i.lot_number = llp.lot_number
)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     116325  consistent gets
         36  physical reads
          0  redo size
     377716  bytes sent via SQL*Net to client
      22640  bytes received via SQL*Net from client
        607  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       9080  rows processed

select
position_id,security_alias,lot_number,orig_lot_number,update_date,update_source
  from holdingdbo.lot_level_position llp
 where (position_id,security_alias,lot_number) in (
select  position_id,security_alias,lot_number
  from holdingdbo.lot_level_position
 group by position_id,security_alias,lot_number
 having count(*) > 1
)

Statistics
----------------------------------------------------------
          0  recursive calls
          6  db block gets
       3064  consistent gets
       3195  physical reads
          0  redo size
     377716  bytes sent via SQL*Net to client
      22640  bytes received via SQL*Net from client
        607  SQL*Net roundtrips to/from client
          0  sorts (memory)
          1  sorts (disk)
       9080  rows processed

Based on the above results, I extrapolated the "consistent gets" (which was a
guess on my part) to my production database volume. Surprisingly, the first
query which showed more consistent gets on my development database ran more
quickly on the production database very quickly and the second query which
showed less consistent gets ran more slowly and took almost 00:59:3541.73 to
complete.

1. Is my extrapolation approach correct. If not how can I simulate this on my
development environment. I want to mimic my production volumes.
2. You insist on reducing logical I/O and so is "consistent gets" alone reliable
or any other factors to be considered (such as disk reads, db block gets, sorts
etc)
3. In my second query, why my physical reads greater than logical reads (3195 >
3064 + 6)
4. Why my first query was faster on production database even though it showed a
large number of "consistent gets" on my development database based on which I
did my extrapolation
5. What other tuning ratios I need to use and any other tuning
tricks/tips/figures I need to look for in my trace.
5. Is cost figures reliable and can I assume queries with lower costs will be
faster and any formula I can use as to why Oracle arrived at "x number of
consistent gets"

          Followup:

queries scale in many different ways.

there are queries that have the same number of CG's (consistent gets) regardless
of the volume of data (eg: select * from t where pk = :x)

there are queries that will use N*CG's -- where N is some multiple of the data.
So, if you have 100,000 rows, it'll do 1*CG, if you have 200,000 rows it'll do
200,000 rows

There are other queries that will use exp(CG) meaning -- it'll get worse and
worse and worse in a linear/exponetial fashion as the volume increases.

      Rating: 4
N+1 best case
      vs 2*N  November 06, 2003
Reviewer:
      A reader  from NJ, USA
Tom,

In one of your discussions, you mentioned that the best case consistent reads is
N+1 and worst case is 2*N. Also, in the earlier example, you mentioned that
Oracle will perform 2*(number of rows) "consistent gets" when the rows are
accessed using the index-then-table approach. Can you let me know when the N+1
consistent gets (the best case) occur

          Followup:

i didn't mention that.

someone else did.

i showed it to be "not correct"

      Rating: 3
Consistent
      Gets  November 06, 2003
Reviewer:
      A reader  from NJ, USA
Tom,

You mentioned that the number of CGs is data volume dependent. ie. queries that
may be N*CGs or exponential. So how do we rely on CGs and how do we write
queries that are data volume independent

          Followup:

you cannot -- it is the nature of the query itself!!  it is a characteristic of
the underlying data and how it works.

Ok, lets say you have a table:

create table emp ( empno int primary key, ename varchar2(25), deptno int );

very simple.  The rules are:

a) there are never more then 100 depts in any company, EVER.  data is somewhat
distributed equally over deptnos

b) empno is unique

c) we know NOTHING about ename really

Well, the query:

select * from emp where empno = :x

will scale with pretty much the SAME number of CG's regardless of the number of
rows.  There will be 2/4 LIO's against an index followed by a table access by
index rowid.  You'll have between 3/5 LIO's for that query regardless of whether
you have 10,000 or 10,000,000,000 rows.

The query:

select * from emp where deptno = :x

will scale somewhat linearly with respect to the volume of data.  In general,
the number of rows returned will be total_number_of_rows_in_table/100.  If you
have 10,000 rows, you would expect 100 returned value.  10,000,000,000 rows --
100,000,000 rows.  the number of CG's will go up in a somewhat predicable
fashion as the number of rows increases.

The query

select * from emp where ename = :x

is a sort of an "unpredicable" query.  Say :x = 'SMITH'.  Well, That might
return 1 row, all rows, most rows, some rows, many rows from the table (smith is
a very popular name).  It could be that SMITH is not a popular name in small
companies located in California.  It could be that SMITH is the *most* popular
name for small companies located in Texas.  So, this query -- even with small
tables could return wildly different CG's in different implementations -- due
solely to data skew.

does that make sense?

Now, consider

select ..
  from t1, t2, t3, t4, t5 ....
 where ......

and you can see, it gets harder to generalize.  IF YOU KNOW your data and what
it means, you can generalize that.

I guess one way to do this would be

a) set up your tables
b) fill them with a bit of data
c) analyze & explain plan your queries
d) fill them with a bit more data
e) goto c/d a couple of times

then, you can look at the row sources to see how the queries are going to scale.
 for example, let's run the above scenario using "emp"

ops$tkyte@ORA920PC> create table emp ( empno int primary key, ename
varchar2(25), deptno int );

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create index emp_ename_idx on emp(ename);

Index created.

ops$tkyte@ORA920PC> create index emp_deptno_idx on emp(deptno);

Index created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert into emp
  2  select object_id, owner, mod(rownum,100)
  3    from (select * from all_objects order by reverse(object_name) )
  4   where rownum <= 100;

100 rows created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> variable x number
ops$tkyte@ORA920PC> variable y varchar2(25)
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> analyze table emp compute statistics;

Table analyzed.

ops$tkyte@ORA920PC> set autotrace traceonly explain
ops$tkyte@ORA920PC> select * from emp where empno = :x;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=10)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=1 Card=1 Bytes=10)
   2    1     INDEX (UNIQUE SCAN) OF 'SYS_C007176' (UNIQUE)

ops$tkyte@ORA920PC> select * from emp where deptno = :x;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=10)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=2 Card=1 Bytes=10)
   2    1     INDEX (RANGE SCAN) OF 'EMP_DEPTNO_IDX' (NON-UNIQUE) (Cost=1
Card=1)

ops$tkyte@ORA920PC> select * from emp where ename = :y;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=20 Bytes=200)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=2 Card=20 Bytes=200)
   2    1     INDEX (RANGE SCAN) OF 'EMP_ENAME_IDX' (NON-UNIQUE) (Cost=1
Card=20)

ops$tkyte@ORA920PC> set autotrace off
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert into emp
  2  select object_id, owner, mod(rownum,100)
  3    from (select *
  4            from all_objects
  5                   where not exists (select null
  6                                       from emp
  7                                                          where
empno=object_id)
  8           order by reverse(object_name) )
  9   where rownum <= 900;

900 rows created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> analyze table emp compute statistics;

Table analyzed.

ops$tkyte@ORA920PC> set autotrace traceonly explain
ops$tkyte@ORA920PC> select * from emp where empno = :x;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=10)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=2 Card=1 Bytes=10)
   2    1     INDEX (UNIQUE SCAN) OF 'SYS_C007176' (UNIQUE) (Cost=1 Card=1000)

ops$tkyte@ORA920PC> select * from emp where deptno = :x;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=10 Bytes=100)
   1    0   TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=10 Bytes=100)

ops$tkyte@ORA920PC> select * from emp where ename = :y;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=59 Bytes=590)
   1    0   TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=59 Bytes=590)

ops$tkyte@ORA920PC> set autotrace off
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert into emp
  2  select object_id, owner, mod(rownum,100)
  3    from (select *
  4            from all_objects
  5                   where not exists (select null
  6                                       from emp
  7                                                          where
empno=object_id)
  8           order by reverse(object_name) )
  9   where rownum <= 9000;

9000 rows created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> analyze table emp compute statistics;

Table analyzed.

ops$tkyte@ORA920PC> set autotrace traceonly explain
ops$tkyte@ORA920PC> select * from emp where empno = :x;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=11)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=2 Card=1 Bytes=11)
   2    1     INDEX (UNIQUE SCAN) OF 'SYS_C007176' (UNIQUE) (Cost=1 Card=10000)

ops$tkyte@ORA920PC> select * from emp where deptno = :x;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=100 Bytes=1100)
   1    0   TABLE ACCESS (FULL) OF 'EMP' (Cost=4 Card=100 Bytes=1100)

ops$tkyte@ORA920PC> select * from emp where ename = :y;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=294 Bytes=3234)
   1    0   TABLE ACCESS (FULL) OF 'EMP' (Cost=4 Card=294 Bytes=3234)

ops$tkyte@ORA920PC> set autotrace off

so, we see:

    rows  =>    100         1,000            10,000
empno = :x        1             1                 1
deptno =:x        1            10               100
ename = :y       20            59               294

now, you see how each of those will "scale".

It is the nature of the data itself.

      Rating: 3
Card in
      Execution Plan  November 07, 2003
Reviewer:
      A reader  from NJ, USA
Tom,

So does that mean the Card= in the Execution plan section is the same as
"consistent gets". If not what does "Card=" in the Execution plan mean and does
the figures in your output like 1,10,59,20,100,294 show the number of
"consistent gets". If not what do these figures mean?

    rows  =>    100         1,000            10,000
empno = :x        1             1                 1
deptno =:x        1            10               100
ename = :y       20            59               294

Pl. explain

          Followup:

No, card = cardinality = estimated number of rows.

consistent gets and card may be miles and miles apart.

      Rating: 5
multiblock read
      making difference?  November 22, 2003
Reviewer:
      Sudhir  from Lexington, KY
Tom,

In your test case when you used example:

"big_table@ORA920LAP> select * from big_table where rownum <= 10000;

10000 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1329 Card=10000 Bytes=940000)
   1    0   COUNT (STOPKEY)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1329 Card=998400
Bytes=93849600)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        794  consistent gets
        134  physical reads
          0  redo size
     773075  bytes sent via SQL*Net to client
       7825  bytes received via SQL*Net from client
        668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      10000  rows processed

big_table@ORA920LAP> select * from big_table where rownum <= 10000 order by
1,2,3,4,5,6,7,8;

10000 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=82617 Card=10000 Bytes=940000)
   1    0   SORT (ORDER BY) (Cost=82617 Card=10000 Bytes=940000)
   2    1     COUNT (STOPKEY)
   3    2       TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1329 Card=998400
Bytes=93849600)

Statistics
----------------------------------------------------------
          0  recursive calls
          2  db block gets
        138  consistent gets
        257  physical reads
          0  redo size
     773086  bytes sent via SQL*Net to client
       7825  bytes received via SQL*Net from client
        668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          1  sorts (disk)
      10000  rows processed
"

Somehow I feel, having order by is making some difference in the way
db_file_multiblock_read is being used in one and not in the other. Is that why?
I didn't even think 'order by' and 'rownum <= 10000' are valid combinations (for
most cases)

As always, thanks for creative solutions.

          Followup:

multi-block-read count affects:

a) the cost of the full scan as seen by the CBO
b) the performance of the full scan if we have to do physical IO

it will not in this case affect consistent gets....

All I wanted to do was show

1) getting 10,000 rows
2) getting 10,000 rows and sorting them

so, rownum was OK for this demo.

      Rating: 5
Tom, I am
      missing the point, please help!  November 22, 2003

Reviewer:  Sudhir  from Lexington, KY
Without sort

794  consistent gets

with sort

138  consistent gets

whats going on? Thats why I was thinking of multiblock issue.

If you can clear the confusion, Thanks in advance.

          Followup:

sort = read all - sort it, put in into private memory, retrieve it

no sort = read it bit by bit by bit as you hit it.

the sort was like a "bulk fetch with arraysize = ALL ROWS at once", less
consistent gets overall since we "consistently got them" all at once - instead
of row by row by row.

(it was part of the 'challenge')

      Rating:
      5
amazing!  November 22, 2003
Reviewer:
      Sudhir  from Lexington, KY USA
Tom,

In this case is it likely that the operation with sort may have run faster than
one without sort? And do you think there is something like a pattern here that
may be useful in general.

Again, thanks for the solution.

          Followup:

No, do not think about using a "sort" as a "pattern"

Use order by when you need sorted data.

It used more memory in the server process, it did more work to get the first row
then the last, you waited longer to start seeing data, it used perhaps more CPU.

      Rating: 5
Thank you!
      EOM  November 23, 2003
Reviewer:  Sudhir

      Rating: 5
db block
      gets+consistent gets > Total Number of Blocks for a
      table.  January 08, 2004
Reviewer:  Sami
       from NJ,USA
CREATE TABLE MY_LOOKUP1
(
  LOOKUPID      NUMBER(8)    NOT NULL,
  SERVICEID       NUMBER(8)    NOT NULL,
  LOOKUPABBREV  VARCHAR2(20) NOT NULL,
  LOOKUPDESC    VARCHAR2(40) NOT NULL,
  LANGUAGEID    NUMBER(8)    DEFAULT 30    NOT NULL,
  ACTIVEFLAG    VARCHAR2(1)  DEFAULT 'Y'   NOT NULL,
  CREATEDATE    DATE         DEFAULT NULL  NOT NULL,
  UPDATEDATE    DATE         DEFAULT NULL  NOT NULL
)
TABLESPACE MY_DATA_TS
PCTUSED    40
PCTFREE    10
INITRANS   1
MAXTRANS   255
STORAGE    (
            INITIAL          2K
            NEXT             2K
            MINEXTENTS       1
            MAXEXTENTS       505
            PCTINCREASE      50
            FREELISTS        1
            FREELIST GROUPS  1
            BUFFER_POOL      DEFAULT
           )
LOGGING
NOCACHE
NOPARALLEL;

INSERT INTO MY_LOOKUP1 select * from MY_LOOKUP;
5 rows inserted.

ANALYZE TABLE MY_LOOKUP1 COMPUTE STATISTICS FOR TABLE FOR ALL INDEXES FOR ALL
INDEXED COLUMNS;
Table Analyzed.

SQL> select blocks,empty_blocks,num_rows,avg_row_len from  dba_tables where
TABLE_NAME='MY_LOOKUP1';

    BLOCKS EMPTY_BLOCKS   NUM_ROWS AVG_ROW_LEN
---------- ------------ ---------- -----------
         1            0          5          50

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=5 Bytes=250)
   1    0   TABLE ACCESS (FULL) OF 'MY_LOOKUP1' (Cost=1 Card=5 Bytes=250)

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
          2  consistent gets
          0  physical reads
          0  redo size
       1085  bytes sent via SQL*Net to client
        431  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          5  rows processed

Questions:
##########

TABLE STATISTICS from DBA_TABLES are below:
===========================================
NUM_ROW    =5
BLOCKS    =1
EMPTY_BLOCKS=0
AVE_ROW_LEN=50

(Q1) BLOCKS=1 (This segment has only one block).
It does mean that I have all my records in ONE block. Right?

(Q2) If (Q1) correct, why does statistics show
    4  db block gets
    2  consistent gets
I have everything in ONE block, why am I getting 6 (4+2) blocks.

(Q3)Bytes=250 in Explain Plan which is AVE_ROW_LEN * NUM_ROW. Correct?

Tom,Kinly enlighten me the relationship between DBA_TABLES statistics number and
AutoTrace Statistics.

Thanks,
Sami

          Followup:

for a full scan, we read the segment header to get the extent map.  you are
seeing that extra read done to figure out what to read for you.

      Rating: 5
Consistent Gets
      are still very puzzling  January 09, 2004

Reviewer:  Ik  from Bangalore, India
Tom,

I always used to think that 'Consistent gets' happens only when Oracle tries to
provide read consistency. That is when you have uncommitted transactions.

(1) Iam confused here between current mode gets (buffer reads) and consistent
gets. In the original question, why is there the need for read consistency?
Every read should be current mode, right?

(2)
I did the same test as the person who posted the original question here. I got
the same results.

Now, i dropped the table (tes) and now used 2 sessions. Inserted those 10000
records in one session (NO COMMIT) and then queried from another SQLPlus
session. Here is what i got.

select  * from tes
/
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      10040  consistent gets
          0  physical reads
        832  redo size
         90  bytes sent via SQL*Net to client
        242  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

Consistent gets have shot up. Is that because transaction was left uncommitted
by the other session? Can you please explain why the numbers are high.

(3) Unrelated to the above 2 questions - I was trying to artificially shoot up
the consistent reads by forcing an index access. It seems that the optimizer
won't budge from its stand. It goes for FTS always. Why so?

create unique index idx_test on tes(a)

select /*+ INDEX(tes idx_test)*/
    *
from
    tes
Operation    Object Name    Rows    Bytes    Cost    TQ    In/Out    PStart
PStop

SELECT STATEMENT        82           2
  TABLE ACCESS FULL    TES    82      1 K    2

Thank you very much for the valuable service you are doing to us Oracle
developers.

Thank You.

          Followup:

we use consistent gets to get a block consistently (as of a point in time) it
may or may NOT require a read aside to the RBS but the block is gotten
"consistently" regardless.

1) no, we ALWAYS get the blocks in consistent read mode.  You cannot tell until
you get to the block if the data is "good enough for you".  You always process
in CR mode therefore.

2) yes, it is read consistency there.  Your query read each block and had to
roll back all of the inserts on that block in order to see that "hey, this block
is actually EMPTY as far as I'm concerned"

3) the index is probably on a NULLABLE column.

ops$tkyte@ORA920PC> create table t ( x int, y int );

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create index test_idx on t(x);

Index created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> set autotrace traceonly explain
ops$tkyte@ORA920PC> select /*+ index( t test_idx ) */ * from t;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=82 Bytes=2132)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=2 Card=82 Bytes=2132)

ops$tkyte@ORA920PC> alter table t modify x NOT NULL;

Table altered.

ops$tkyte@ORA920PC> select /*+ index( t test_idx ) */ * from t;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=826 Card=82 Bytes=2132)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=826 Card=82 Bytes=2132)
   2    1     INDEX (FULL SCAN) OF 'TEST_IDX' (NON-UNIQUE) (Cost=26 Card=82)

ops$tkyte@ORA920PC> set autotrace off

since entirely null keys are not put into b*tree indexes -- we cannot use the
index if X allowed nulls (you would not get the right answer since the index
does not contain all of the rows in the table)

      Rating: 3
Still come
      confusion  January 20, 2004
Reviewer:
      A reader  from NJ, USA
Tom,

I have two environments integration (SPI1) and Benchmark (BMK1) and I have the
following query running on the same environments.

SELECT /*+ ORDERED USE_NL(pcd,p) INDEX(pd) */
 p.position_id,                      pd.position_detail_id,
pd.Alt_Market_Value,
pd.Alt_Market_Value_Income,  pd.Alt_Book_Value,
pcd.Alt_GAAP_Cost_Value,
pd.Base_FX_Unrealized_GL,     pd.Alt_Base_Unrealized_GL,
pd.Alt_Base_FX_Unrealized_GL,
pd.Alt_Accrued_Income,         pd.Local_to_sec_basis_fx_rate,
pd.Income_Receivable_Local,
pd.Income_Receivable_Base,      pd.Alt_Income_Receivable
FROM holdingdbo.position_detail pd,
     holdingdbo.position_cost_detail pcd,
     holdingdbo.position p
WHERE pd.update_date        >= '17-JAN-04'
  AND    p.position_id            = pd.position_id
  AND p.src_intfc_inst      = 17
AND pd.position_detail_id = pcd.position_detail_id

I have the following statistics information :

BMK1
====
                               Clus.
Table      Index    Blocks    Rows     Factor    Col
T1         I1    15848    876751     225197    Col1
           I2    15848    876751     832570    COl2
           I2    15848    876751     832570    Col3
           I2    15848    876751     832570    Col4
           I3    15848    876751     242262    Col5

T2       I11    1086082    92017221 1809257  Col1
T2       I12    1086082    92017221 25658678 Col2

T3       I21    1670338    60198216 7946507  Col1
       I22    1670338    60198216 59152225 Col2
       I23    1670338    60198216 30525320 Col3
       I24    1670338    60198216 19431172 Col4
       I24    1670338    60198216 19431172 Col2
       I24    1670338    60198216 19431172 Col5
       I24    1670338    60198216 19431172 Col6
       I24    1670338    60198216 19431172 Col7

SPI1
====

Table    Index    Blocks    Rows    Clus Factor Col
T1    I1    196587    557024    372737        Col1
    I2    196587    557024    547845        Col2
    I2    196587    557024    547845        Col3
    I2    196587    557024    547845        Col4
    I3    196587    557024    410857        Col5

T2    I11    904315    38124980 3716600    Col1
    I12    904315    38124980 32805420    Col2

T3    I21    2247043    39646070 11968130    Col1
    I22    2247043    39646070 38879830    Col2
    I23    2247043    39646070 33992900    Col3
    I24    2247043    39646070 26692950    Col4
    I24    2247043    39646070 26692950    Col2
    I24    2247043    39646070 26692950    Col5
    I24    2247043    39646070 26692950    Col6
    I24    2247043    39646070 26692950    Col7

The above query executed in the above two environments produced two different
timings. My questions are :

1. Is hints always good to put in a query
2. If the execution plans are same in the two environments, what are the other
factors to look for in the two environments (besides the num rows, blocks,
clustering factor as above) that contribute to this time difference. ie. how to
find the exact cause of this time difference
3. How do we check if all the tables/indexes etc are analyzed
4. If the tools like AUTOTRACE, TKPROF, SQL_TRACE etc are used in both the
environments, what would be the sequence of steps (like a checklist for myself)
that could help me find the exact cause of the problem.
5. I read your books on these chapters, but when it comes to practical tuning
cases as above, I am not sure what could be the best possible way to tackle such
problems.
6. How to infer from consistent gets/physical reads etc from TKPROF/AUTOTRACE
output

A detailed explanation on this would be of great help

Thanks

          Followup:

1) no
2) wait events -- use 10046 level 12 trace event (search for 10046 on this site
if that doesn't mean anything to you) and see what it is waiting for.

and remember, two machines differ in thousands of ways.

3) queries against the data dictionary, last analyzed is in there as well as
sample sizes

4) since I don't know what they "problem"  is or even if there is one, a
checklist won't help.  it is usually "obvious".  do you have a 9i tkprof with
wait events that one could look at.

5) show us a tkprof - it should be somewhat "obvious"

6) you do not infer from tkprof -- it is spelled out (there is a physical reads
column, there are current and query mode (logical) columns.  they are told to
you.

from autotrace, again, they are spelled out -- they are right there.  consistent
gets, db block gets ( logical ios) and physical reads.  no infering, just
reading.

      Rating: 3
Pls
      explain  January 20, 2004
Reviewer:
      A reader  from NJ, USA
Tom,

In your response to the above response, you mentioned that it could differ in
thousands of ways. I would like to get some information from you as to a couple
of ways that the two could be different. Please provide with a list that is very
significant ( I understand that the list is huge, but at least would be helpful
if you could list a couple of them that I could easily check)

          Followup:

nope, as i said -- show us the tkprof and it should be "obvious"

      Rating: 5
why network
      packets increases?  February 07, 2004
Reviewer:
      A reader
Hi

Why the amount of data transferred goes up when you set a big arraysize? Has it
got something to do with tcp/ip packet size?

          Followup:

goes up and down.  40k is "noise" as far as I'm concerned.  test was really
skinny, not a good test.

we'll just attribute it to gremlins this time....

bigger, fatter table:

ops$tkyte@ORA9IR2> set autotrace traceonly statistics;
ops$tkyte@ORA9IR2> select * from all_objects;

29512 rows selected.

Statistics
----------------------------------------------------------
    2097689  bytes sent via SQL*Net to client

ops$tkyte@ORA9IR2> set arraysize 100
ops$tkyte@ORA9IR2> /

29512 rows selected.

Statistics
----------------------------------------------------------
    1895377  bytes sent via SQL*Net to client

ops$tkyte@ORA9IR2> set arraysize 1000
ops$tkyte@ORA9IR2> /

29512 rows selected.

Statistics
----------------------------------------------------------
    1863191  bytes sent via SQL*Net to client

ops$tkyte@ORA9IR2> set arraysize 5000
ops$tkyte@ORA9IR2> /

29512 rows selected.

Statistics
----------------------------------------------------------
    1860287  bytes sent via SQL*Net to client

      Rating:
      5
  February 19, 2004
Reviewer:  Alison
      Tonner  from Scotland
Hi Tom, thanks for all the information provided in the rest of this thread.

I was wondering if you could clarify something.

When I change the optimizer mode from first_rows_10 to first_rows_100 the cost
of the plan goes up, even though the consistant_gets and physical reads goes
down.

I do see that the cardinallity gets higher but still not sure why the cost goes
up instead of down?

SYSTEM-ORCL->alter session set optimizer_mode = first_rows_10;

Session altered.

Elapsed: 00:00:00.00
SYSTEM-ORCL->select * from customers c, sales s
  2  where c.cust_id = s.cust_id
  3  and cust_last_name = 'Smith'
  4  and s.time_id <= '31-DEC-98';

359 rows selected.

Elapsed: 00:06:01.01

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=22 Card=10 Bytes=2890)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMERS' (Cost=22 Card=1
Bytes=130)
   2    1     NESTED LOOPS (Cost=22 Card=10 Bytes=2890)
   3    2       PARTITION RANGE (ITERATOR)
   4    3         TABLE ACCESS (FULL) OF 'SALES' (Cost=3 Card=201 Bytes=31959)
   5    2       BITMAP CONVERSION (TO ROWIDS)
   6    5         BITMAP AND
   7    6           BITMAP CONVERSION (FROM ROWIDS)
   8    7             INDEX (RANGE SCAN) OF 'CUSTOMERS_PK' (UNIQUE)
   9    6           BITMAP CONVERSION (FROM ROWIDS)
  10    9             INDEX (RANGE SCAN) OF 'CUST_CUST_LAST_NAME_IDX'
(NON-UNIQUE) (Cost=1 Card=641)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     877531  consistent gets
       2731  physical reads
          0  redo size
      21137  bytes sent via SQL*Net to client
        756  bytes received via SQL*Net from client
         25  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        359  rows processed

SYSTEM-ORCL->rem Now with FIRST_ROWS_100
SYSTEM-ORCL->pause

SYSTEM-ORCL->alter session set optimizer_mode = first_rows_100;

Session altered.

Elapsed: 00:00:00.00
SYSTEM-ORCL->select * from customers c, sales s
  2  where c.cust_id = s.cust_id
  3  and cust_last_name = 'Smith'
  4  and s.time_id <= '31-DEC-98';

359 rows selected.

Elapsed: 00:00:01.09

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=61 Card=100 Bytes=15900)
   1    0   TABLE ACCESS (BY LOCAL INDEX ROWID) OF 'SALES' (Cost=61)
   2    1     NESTED LOOPS (Cost=61 Card=100 Bytes=15900)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMERS' (Cost=5 Card=1
Bytes=130)
   4    3         INDEX (RANGE SCAN) OF 'CUST_CUST_LAST_NAME_IDX' (NON-UNIQUE)
(Cost=3 Card=1)
   5    2       PARTITION RANGE (ITERATOR)
   6    5         BITMAP CONVERSION (TO ROWIDS)
   7    6           BITMAP AND
   8    7             BITMAP MERGE
   9    8               BITMAP INDEX (RANGE SCAN) OF 'SALES_TIME_BIX'
  10    7             BITMAP INDEX (SINGLE VALUE) OF 'SALES_CUST_BIX'

Statistics
----------------------------------------------------------
         14  recursive calls
          0  db block gets
       3191  consistent gets
        497  physical reads
          0  redo size
      20130  bytes sent via SQL*Net to client
        756  bytes received via SQL*Net from client
         25  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        359  rows processed

          Followup:

you cannot compare the cost of two different plans under two different settings
of optimizer parameters.

doesn't make sense.

the optimizer parameters cause the costing model to "change", that is their
goal.  by changing them -- you changed the method by which the numbers are
assigned.  It would be expected that the costs are "different", the way these
parameters work in general is by changing the costing model.

Now, your test is a little invalid.  You asked "please optimize to get the first
10 rows ASAP" and then fetched all 359.  You "lied" to the optimizer ;)  Use
this:

alter session set sql_trace=true;
declare
   cnt number := 0;
begin
  for x in ( select * from customers c, sales s
    where c.cust_id = s.cust_id
    and cust_last_name = 'Smith'
    and s.time_id <= TO_DATE( '31-DEC-1998' ) )  /* pet peeve of mine */
  loop
     cnt := cnt + 1;
     exit when (cnt=10);
  end loop;
end;
/

and use tkprof to analyze the outcome with different optimizer settings.

      Rating:
      5
  February 19, 2004
Reviewer:
      Alison
Thanks - that's all I needed to know

Just the idea that they are apples and oranges and can't be compared in that way
makes sence.

Thanks as usuall for your fast, consise reply.

      Rating: 5
Low LIO != Fast
      responce??  March 10, 2004
Reviewer:  Prasad
       from UK
Hi Tom,

The above discussion more than clearly says what the "consistent gets" is all
about.
We consider it as a part of LIO. And you always suggests for lowering LIOs. So i
did a test case where I lowered the LIO (by increasing the arraysize
15...1000...4000) But as i lowered the LIO my execution(Elapsed) time increased
instead.How is it? Am I missing something??

here is the test i carried out.
----------------------------------------
create table t as select * from all_objects;

Table created.

exec show_space( 'T' )

Free Blocks.............................0
Total Blocks............................137
Total Bytes.............................1122304
Unused Blocks...........................25
Unused Bytes............................204800
Last Used Ext FileId....................61
Last Used Ext BlockId...................1677
Last Used Block.........................20

set timing on

select * from t;
8704 rows selected.

Elapsed: 00:00:18.82

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
        689  consistent gets
          0  physical reads
          0  redo size
     780354  bytes sent via SQL*Net to client
      47343  bytes received via SQL*Net from client
        582  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       8704  rows processed

set arraysize 1000

select * from t;
8704 rows selected.

Elapsed: 00:00:19.18

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
        124  consistent gets
         99  physical reads
          0  redo size
     732878  bytes sent via SQL*Net to client
       1011  bytes received via SQL*Net from client
         10  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       8704  rows processed

set arraysize 4000

select * from t;
8704 rows selected.

Elapsed: 00:00:24.57

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
        118  consistent gets
          0  physical reads
          0  redo size
     732380  bytes sent via SQL*Net to client
        525  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       8704  rows processed

          Followup:

I've also said that there are diminishing marginal returns to everything.  I
found an arraysize of about 100 is the "sweet spot" for array fetching in
general.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:12226527910608#15916220123225
yes, the goal is to reduce LIO's in general when tuning a query.  array fetching
is a tool but only to a point.

      Rating:
      5
Perfect  March 10, 2004
Reviewer:
      Prasad  from UK
set arraysize 100

select * from t;
8664 rows selected.

Elapsed: 00:00:12.74

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
        201  consistent gets
        111  physical reads
          0  redo size
     735856  bytes sent via SQL*Net to client
       7329  bytes received via SQL*Net from client
         88  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       8664  rows processed

Thanks
    Prasad

March 10, 2004 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://download-east.oracle.com/docs/cd/A81042_01/DOC/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

      Rating:
      5
mv's  March 08, 2004
Reviewer:  Dave
       from UK
SQL> desc account_poll
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 ACCOUNT_ID                                NOT NULL VARCHAR2(35)
 POLL_ID                                   NOT NULL NUMBER(11)
 OVERALL_SCORE                             NOT NULL NUMBER(3)
 SECONDARY_SCORE                           NOT NULL NUMBER(11)
 CPART_ID                                           NUMBER(38)
 FIRST_ACTIVE_DATE_TIME                             DATE
 LAST_ACTIVE_DATE_TIME                              DATE
 FIRST_ALARM_DATE_TIME                              DATE
 LAST_ALARM_DATE_TIME                               DATE
 ACTION_TYPE                                        NUMBER(1)
 ACTIONED_TS                                        NUMBER(11)
 ROAMER                                             NUMBER(1)
 ACCOUNT_TYPE                                       NUMBER(3)
 ACTIONED_POLL_ID                                   NUMBER(11)
 PREPAID                                            NUMBER(1)
 DATA_SCORE                                         NUMBER(7,2)
 ACTIONED_DATE_TIME                                 DATE
 MAX_ALARM_ID                                       NUMBER(38)
 BEH_FRAUD                                          NUMBER(3)
 BEH_INC                                            NUMBER(3)
 BEH_UNUSUAL                                        NUMBER(3)
 HOT_RE                                             NUMBER(3)
 OVERLAP                                            NUMBER(3)
 HIGH_VAL                                           NUMBER(3)
 HIGH_COST                                          NUMBER(3)
 ABS_FEAT                                           NUMBER(3)
 LONG_DUR                                           NUMBER(3)
 DUR_PAT                                            NUMBER(3)
 SHORT_CALL                                         NUMBER(3)
 IMEI_IMSI                                          NUMBER(3)
 IMSI_IMEI                                          NUMBER(3)
 PREPAY                                             NUMBER(3)
 HIGH_FREQ                                          NUMBER(3)
 GENERIC                                            NUMBER(3)
 HOT_3G                                             NUMBER(3)
 SUMMARY                                            NUMBER(3)
 RULES                                              NUMBER(3)
 NOTE                                               NUMBER(1)
 CONFIRMED                                          NUMBER(1)
 PREV_ALLOCATED_USER                                VARCHAR2(30)

Table has circa 50,000 records (but will grow larger)

Issue Query:

INSERT INTO C3G_TEMP_ACCOUNT_LIST

SELECT AP.CPART_ID CPART_ID,AP.ACCOUNT_ID,
AP.ACCOUNT_TYPE ACCOUNT_TYPE,AP.FIRST_ACTIVE_DATE_TIME
FIRST_ACTIVE_DATE_TIME,SYSDATE - AP.FIRST_ACTIVE_DATE_TIME  ACTIV_DT,
AP.SECONDARY_SCORE SECONDARY_SCORE,UA.ASSIGNED_TO_USER_ID
ASSIGNED_TO_USER_ID,(',' || DECODE(AP.BEH_FRAUD,2,'2,')  ||
DECODE(AP.BEH_INC,39,'39,')  || DECODE(AP.BEH_UNUSUAL,48,'48,')  ||
DECODE(AP.HOT_RE,59,'59,')  || DECODE(AP.OVERLAP,88,'88,')  ||
DECODE(AP.HIGH_VAL,90,'90,')  || DECODE(AP.HIGH_COST,91,'91,')  ||
DECODE(AP.ABS_FEAT,116,'116,')  || DECODE(AP.LONG_DUR,165,'165,')  ||
DECODE(AP.DUR_PAT,170,'170,')  || DECODE(AP.SHORT_CALL,175,'175,')  ||
DECODE(AP.IMEI_IMSI,176,'176,')  || DECODE(AP.IMSI_IMEI,178,'178,')  ||
DECODE(AP.PREPAY,180,'180,')  || DECODE(AP.HIGH_FREQ,185,'185,')  ||
DECODE(AP.GENERIC,210,'210,')  || DECODE(AP.HOT_3G,249,'249,')  ||
DECODE(AP.SUMMARY,256,'256,')  || DECODE(AP.RULES,283,'283,') )
ALARM_STRING,AP.NOTE ANOTE,AP.MAX_ALARM_ID MAX_ALARMS,AP.ACTION_TYPE
ACTION_TYPE,AP.DATA_SCORE DATA_SCORE,AP.CONFIRMED,AP.PREV_ALLOCATED_USER
PREV_ALLOCATED_USER,DECODE(NVL(SIGN(AP.ACTIONED_DATE_TIME -
AP.LAST_ALARM_DATE_TIME ),1),1,0,1),USER,AP.PREPAID

FROM USER_ACCOUNT UA,ACCOUNT_POLL AP

WHERE AP.POLL_ID = 71  AND AP.ACCOUNT_TYPE IN
(SELECTA.ACCOUNT_TYPE   FROM VIEW_DEFINITION A,CEREBRUS_USER B  WHERE B.VIEW_ID
=
A.VIEW_ID  AND UPPER(B.USER_ID) = USER ) AND AP.ACCOUNT_ID = UA.ACCOUNT_ID(+)
SQL> /

(I know the query is bad - it can change too because of the lack of binds at
present)

SQL> set autotrace traceonly
17753 rows created.

Execution Plan
----------------------------------------------------------
   0      INSERT STATEMENT Optimizer=CHOOSE (Cost=162 Card=285 Bytes=49305)
   1    0   HASH JOIN (OUTER) (Cost=162 Card=285 Bytes=49305)
   2    1     HASH JOIN (Cost=160 Card=285 Bytes=39045)
   3    2       VIEW OF 'VW_NSO_1' (Cost=8 Card=2 Bytes=26)
   4    3         SORT (UNIQUE) (Cost=8 Card=2 Bytes=90)
   5    4           HASH JOIN (Cost=3 Card=2 Bytes=90)
   6    5             TABLE ACCESS (FULL) OF 'CEREBRUS_USER' (Cost=1 Card=1
Bytes=19)
   7    5             TABLE ACCESS (FULL) OF 'VIEW_DEFINITION' (Cost=1Card=164
Bytes=4264)
   8    2       PARTITION HASH (ALL)
   9    8         TABLE ACCESS (BY LOCAL INDEX ROWID) OF 'ACCOUNT_POLL'
(Cost=151 Card=14256 Bytes=1767744)
  10    9           INDEX (RANGE SCAN) OF 'I2_ACCOUNT_POLL' (NON-UNIQUE)
(Cost=24 Card=14256)
  11    1     TABLE ACCESS (FULL) OF 'USER_ACCOUNT' (Cost=1 Card=443
Bytes=15948)

So I can see it uses the index of account_poll (which is on the column poll_id)
- that is good

Now this table, account_poll gets locked a few times during data processing when
it is being updated, this locks
users out for up two 2 minutes. So we created a materialized view as if I am
correct in my understanding this mv will
be used instead of the table if possible.

SQL> create materialized view log on account_poll with primary key including new
values

Materialized view log created.

SQL> create materialized view account_poll_mv -- to create an exact copy
build immediate
refresh on commit fast
enable query rewrite
as select * from account_poll;

Materialized view created.

SQL> create index I1_MV on account_poll_mv (account_id);
SQL> create index I2_MV on account_poll_mv (poll_id);
SQL> exec dbms_stats.gather_table_stats('CEREBRUS','ACCOUNT_POLL',
cascade=>true);
SQL> show parameter query

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
query_rewrite_enabled                string      TRUE
query_rewrite_integrity              string      enforced

When I run the same query as before, I get exactly the same plan, so it wasn't
using the MV at all

SQL> select operation, object_name from v$sql_plan where object_name like
'%MV%';

no rows selected

That proves it.  My question is when will the MV be used?  Only when the base
table has locks or am I doing something
wrong.

Also if I change the query to use the MV directly, I get this plan

17753 rows created.

Execution Plan
----------------------------------------------------------
   0      INSERT STATEMENT Optimizer=CHOOSE (Cost=55 Card=334 Bytes=59452)
   1    0   HASH JOIN (OUTER) (Cost=55 Card=334 Bytes=59452)
   2    1     HASH JOIN (Cost=53 Card=334 Bytes=47428)
   3    2       VIEW OF 'VW_NSO_1' (Cost=8 Card=2 Bytes=26)
   4    3         SORT (UNIQUE) (Cost=8 Card=2 Bytes=90)
   5    4           HASH JOIN (Cost=3 Card=2 Bytes=90)
   6    5             TABLE ACCESS (FULL) OF 'CEREBRUS_USER' (Cost=1 Card=1
Bytes=19)
   7    5             TABLE ACCESS (FULL) OF 'VIEW_DEFINITION' (Cost=1 Card=164
Bytes=4264)
   8    2       TABLE ACCESS (FULL) OF 'ACCOUNT_POLL_MV' (Cost=44 Card=16724
Bytes=2157396)
   9    1     TABLE ACCESS (FULL) OF 'USER_ACCOUNT' (Cost=1 Card=443
Bytes=15948)

Which is very simlar, but it is doing an fts on the MV - it so happens that the
number of consistent gets and I/O's
is the same. The execution time is roughly the same as well. Is the CBO being
clever and realising an index is
not necessary here or am I being tricked in someway.

SO my questions are:

1. When will the MV be used (Assuming we don't explicitly code it).
2. Why wasn't the index used when the index was used in the original query (same
method of gathering stats)
3. When updating the table (update account_poll ....)  will the MV be updated
and then the base table be updated or will it
go for the base table right away.  This is mainly for when the base table has
locks on it - what will the users queries do

Or is my understanding of MV's totally incorrect and it will only use the MV
when the query submitted to it is select *
from account_poll

If I am  totally going down the wrong path, what else do you suggest, design is
something I cannot do something about really - just want to stop users getting
locked out somehow

Thanks

          Followup:

lets backup for a moment -- why would you create a read only MV to get around a
"locking" issue?  Queries are not blocked by locks in Oracle?  Whats the
motivation here?  Users do not, will not get locked out of reading by locks.

(but why would it use the mv when the mv is 100% the same as the base table - it
would only use an mv if the query would benefit from using the mv and here it
would not)

      Rating:
      5
lockers  March 08, 2004
Reviewer:
      Dave
Thanks, will get back to you tomorrow about why the users are getting locked
out, there are 3 procedures which all update that account_poll table one after
the other and while they are running, the users queries just stop working (the
ones that update that table).

I will see what locks are being held and what my queries are waiting - but from
memory they were all waiting on enqueues

      Rating:
      5
mv  March 09, 2004
Reviewer:  Dave

I just checked and it is updates blocking updates.  So need to come up with a
way of stopping them, this is probably a design change but thought I would ask
anyway.

Is there any facility I can use to stop a writer blocking and having my write
'buffered' of some sort.

There is no problem with the writes conflicting each other either

Thanks

          Followup:

if you have udpates blocking updates -- are you *sure* you don't have write
conflicts (sounds like classic "lost update" issues here)

      Rating: 3
Dead
      link  March 09, 2004
Reviewer:  westsan
       from Tokyo, Japan
http://download-east.oracle.com/docs/cd/A81042_01/DOC/server.816/a76994/toc.htm

          Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96520/mv.htm#721

      Rating:
      4
mv  March 10, 2004
Reviewer:  Dave

Hi Tom, what do you mean by "lost update" issues.

We have two queries one like this

 EXECUTE IMMEDIATE
    'update /*+ apv */
        (select a.cpart_id as a_c,
        a.FIRST_ACTIVE_DATE_TIME as f_a,
        a.LAST_ACTIVE_DATE_TIME as l_a,
        a.FIRST_ALARM_DATE_TIME as f_ad,
        a.LAST_ALARM_DATE_TIME as l_ad,
        a.ACTION_TYPE as a_t,
        a.ACTIONED_DATE_TIME as a_ts,
        a.ROAMER as roa,
        a.ACCOUNT_TYPE as act_type,
        a.ACTIONED_POLL_ID as a_p_id,
        a.PREPAID as pre,
        a.note as note,
        ap.cpart_id as apa_c,
        ap.FIRST_ACTIVE_DATE_TIME as apf_a,
        ap.LAST_ACTIVE_DATE_TIME as apl_a,
        ap.FIRST_ALARM_DATE_TIME as apf_ad,
        ap.LAST_ALARM_DATE_TIME as apl_ad,
        ap.ACTION_TYPE as apa_t,
        ap.ACTIONED_DATE_TIME as apa_ts,
        ap.ROAMER as aproa,
        ap.ACCOUNT_TYPE as apact_type,
        ap.ACTIONED_POLL_ID as apa_p_id,
        ap.PREPAID as appre,
        ap.note as apnote
        from account a, account_poll ap
        where ap.account_id=a.account_id
        and ap.poll_id = '||p_poll_id||') apv
        set apa_c=a_c,
        apf_a=f_a,
        apl_a=l_a,
        apf_ad=f_ad,
        apl_ad=l_ad,
        apa_t=a_t,
        apa_ts=a_ts,
        aproa=roa,
        apact_type=act_type,
        apa_p_id=a_p_id,
        appre=pre,
        apnote=note';

and  then for example

    UPDATE account_poll
       SET action_type = p_action,
           actioned_date_time = SYSDATE,
           actioned_ts = alarm_manager.get_time_in_seconds(SYSDATE)
     WHERE account_id = p_account_id;

          Followup:

if you have two different sessions that get blocked on each others update --
that means session 1's changes are going to be overwritten by session 2's
changes and session 2 will NEVER have looked as session 1's changes.  This is
called the lost update problem.  it is as if session 1 never did anything.

Now, I did not reverse engineer your entire update (you aliased all of the
columns making a direct compare hard) but query 1 updates
account_poll.action_type, query 2 updates it as well.

if the "big update" is running and has updated account_id = 55 and the little
update comes along for id=55, it will block. when it unblocks it will blindly
overwrite the changes of the other guy.  If that is OK, then so be it -- but it
is called in database terms "lost update".  You've processed a row without even
inspecting it.  the application that did the little update may well have

read the row out.....
<other big update starts>
application makes decision to update based on what it read....
application does update <<<=== lost update, bad data

only you can ascertain if this is meaningful to you.

      Rating:
      5
thanks  March 10, 2004
Reviewer:
      A reader
The lost update here is fine then as the smaller update will hold the most up to
date information so when the column gets updated we want that info and dont care
about the update from the bigger update (its updating it from itself) - so I am
fine with that.

Just wish I could get rid of the blocking :-) but it seems as if I am stuck with
it without some design changes

March 10, 2004 Naresh -- Thanks for the question regarding
      "Input Parameters", version 9.2.0.4

      You Asked
I developed this function and it is not genereting any output. The query in the
function is built dynamically based on input parameters. I ran the query outside
of the function and it retrieves correct data. I am not sure if there is a
special way to pass parameters. Any tips would be helpful.
Thank you,
Naresh

CREATE OR REPLACE PACKAGE BODY MMP.stammp_utils

AS

-- This function will accept date and product group as an input and will
generate the
-- data accordingly
-- eg. exec get_auction_byproduct('03/03/2004', '03/03/2004','yes', 'yes')

FUNCTION get_auctions_byproduct(dtAuctionFrom VARCHAR, dtAuctionTo VARCHAR,
prodGrpMMP VARCHAR,prodGrpSAV VARCHAR)
RETURN SYS_REFCURSOR
AS
  p_rs SYS_REFCURSOR;
  prodMMP vARCHAR2(255);
  prodSAV vARCHAR2(255);

BEGIN

  IF (prodGrpMMP = 'yes') THEN
    prodMMP := '''MMP'''||', ' || '''MMP1''';
  END IF;

  IF (prodGrpSAV = 'yes') THEN
    prodSAV := 'SAVRS'||', ' || 'SAV07' ;
  END IF;

    OPEN p_rs FOR
          select * from products p where (p.product_name in (prodGrpMMP) or
p.product_name in (prodSAV));

      RETURN p_rs;
END;

END stammp_utils;

This is what the query should be based on user's input. Running this query
outside of the function works fine.
select * from products p where (p.product_name in ('MMP', 'MMP1') or
p.product_name in ('SAVRS', 'SAV07'));

      and we said...
if you run the query

select * from products p where (p.product_name in ( '''MMP'', ''MMP1''' ) or
...)

what happens then?  You see, that is the query you are running.  there is only
one string in the IN LISTS there.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:139812348065
for an example. but in your case, this would be "best"

  IF (prodGrpMMP = 'yes' and prodGrpVal = 'yes')
  THEN
    v1 := 'MMP';
    v2 := 'MMP1';
  end if;

  IF (prodGrpSAV = 'yes') THEN
    v3 := 'SAVRS';
    v4 := 'SAV07' ;
  END IF;

  open p_rs for
  select *
    from product
   where product_name in ( v1, v2, v3, v4 );

and just let v1..v4 default to NULL otherwise.
Review &
      Followup

      Rating: 4
It
      worked  March 10, 2004
Reviewer:  Naresh
      Krishnani  from New York, NY USA
Thank you for your prompt response. Looking at your recommendation/tips I made
appropriate changes and it worked!

March 10, 2004 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://download-west.oracle.com/docs/cd/A91202_01/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.

      Rating: 5
Methods of
      dequeue  March 10, 2004
Reviewer:  Martin
       from UK
Hi Tom,
given what you've said above, i.e. that you would (prior to 9i, of course, where
notification is a great mechanism), use a PL/SQL process constantly dequeueing
until a relevant message appears, do you think there would be any advantage in
using, say, a Java stored procedure to do the polling?

The reason I'm asking is that I remember hearing that JSPs are faster at
LOOP-type operations than interpreted PL/SQL?

I'd be interested to hear also what the architecture of systems you have worked
on / seen at various clients which use AQ is, especially those with high message
rates.

Thanks in advance

          Followup:

java, faster than plsql, in the database?  no, not really.

I've always used plsql dequeues in the database (with the auto notification) or
java outside the database.

      Rating: 5
What message
      rates?  March 10, 2004
Reviewer:  Martin
       from UK
Thanks for your quick response.

I was basing the JSP faster than PL/SQL simply due to the fact that PL/SQL is
interpreted, whereas Java is "more" compiled. So, I would expect LOOPs to be a
little more efficient, not accessing the database, but just in terms of the
polling speed. Oh well, just shows how much I know....

As a follow on question, what level of performance have you seen AQ with
notification acheive? i.e. in terms of message rates etc.

Again thanks for your invaluable help

          Followup:

plsql is "as compiled" as java -- they both result in "byte code" that is
intpreted by a VM (plsql runs in a vm, java runs in a vm)

both can be natively compiled into object code as well.

AQ is all about sizing really.  You'd want to benchmark your situation --
everything depends on the size of the messages at the end of the day.

March 10, 2004 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.

      Rating: 3
Need some more
      input  March 09, 2004
Reviewer:  student
       from India
First I would like to thank for such a nice site, learnt a lot from this GREAT
site. Not sure if this is the correct thread, I am not getting a chance to ask
in a new question.

Need your help on the following.

I will have to generate a RDF report based on the XML information stored in a
CLOB column.
Following is the structure of the XML CLOB.
Could you please suggest how can I load the XML in a table,so that,
we could easily query the table rows to print the RDF report.
How do I load the XML in a table ? what are the possible table columns I will
have to keep ?

I am very new to XML tech. Please help.

<SHOW_SHIPMENT_005>
  <CNTROLAREA>
    <BSR>
      <VERB value="SHOW">SHOWN</VERB>
      <NOUN value="SHIPMENT">SHIPMENT</NOUN>
      <REVISION value="005">005</REVISION>
    </BSR>
    <SENDER>
      <LOGICALID>GLEN-STAMFORD</LOGICALID>
      <COMPONENT>ASN</COMPONENT>
      <TASK>SHOW</TASK>
      <REFERENCEID>2002-62949-1673*110664-1</REFERENCEID>
      <CONFIRMATION/>

      <LANGUAGE>ENG</LANGUAGE>
      <CODEPAGE/>
      <AUTHID>STAMFORD</AUTHID>
    </SENDER>
    <DATETIME qualifier="CREATION">
      <YEAR>2004</YEAR>
      <MONTH>02</MONTH>
      <DAY>12</DAY>
      <HOUR>10</HOUR>
      <MINUTE>51</MINUTE>
      <SECOND>01</SECOND>
      <SUBSECOND>0000</SUBSECOND>
      <TIMEZONE>+0000</TIMEZONE>

    </DATETIME>
  </CNTROLAREA>
  <DATAAREA>
    <SHOW_SHIPMENT>
      <SHIPMENT>
        <DATETIME qualifier="DOCUMENT">
          <YEAR>2004</YEAR>
          <MONTH>02</MONTH>
          <DAY>12</DAY>
          <HOUR>10</HOUR>
          <MINUTE>51</MINUTE>
          <SECOND>01</SECOND>
          <SUBSECOND>0000</SUBSECOND>

          <TIMEZONE>+0000</TIMEZONE>
        </DATETIME>
        <DATETIME qualifier="SHIP">
          <YEAR>2004</YEAR>
          <MONTH>01</MONTH>
          <DAY>05</DAY>
          <HOUR>12</HOUR>
          <MINUTE>00</MINUTE>
          <SECOND>00</SECOND>
          <SUBSECOND>0000</SUBSECOND>
          <TIMEZONE>+0000</TIMEZONE>
        </DATETIME>
        <DATETIME qualifier="DELIVSCHED">

          <YEAR>2004</YEAR>
          <MONTH>01</MONTH>
          <DAY>05</DAY>
          <HOUR>12</HOUR>
          <MINUTE>00</MINUTE>
          <SECOND>00</SECOND>
          <SUBSECOND>0000</SUBSECOND>
          <TIMEZONE>+0000</TIMEZONE>
        </DATETIME>
        <QUANTITY qualifier="NETWEIGHT">
          <VALUE>45415</VALUE>
          <NUMOFDEC/>
          <SIGN/>

          <UOM>LB</UOM>
        </QUANTITY>
        <QUANTITY qualifier="TOTWEIGHT">
          <VALUE>45415</VALUE>
          <NUMOFDEC/>
          <SIGN/>
          <UOM>LB</UOM>
        </QUANTITY>
        <DOCUMENTID>2002-62949-1673*110664-1</DOCUMENTID>
        <SHIPPERID/>
        <NOTES index="1">HOLLY</NOTES>
        <USERAREA>
          <CARRIERCODE/>

          <QUANTITY qualifier="OTHER">
            <VALUE>45415</VALUE>
            <NUMOFDEC>4</NUMOFDEC>
            <SIGN/>
            <UOM>LB</UOM>
          </QUANTITY>
          <ATTRIBUTE9>105165*110664-1</ATTRIBUTE9>
        </USERAREA>
        <PARTNER>
          <PARTNRID/>
          <PARTNRTYPE>ShipFrom</PARTNRTYPE>
          <NAME index="1">core LTD</NAME>
          <PARTNRIDX>STAMFORD</PARTNRIDX>

          <ADDRESS>
            <CITY/>
            <COUNTRY/>
            <POSTALCODE/>
            <STATEPROVN/>
          </ADDRESS>
        </PARTNER>
        <PARTNER>
          <PARTNRID/>
          <PARTNRTYPE>ShipTo</PARTNRTYPE>
          <NAME index="1"/>
          <PARTNRIDX>CITY_10538</PARTNRIDX>
          <ADDRESS>

            <ADDRLINE index="1"/>
            <ADDRLINE index="2"/>
            <CITY/>
            <COUNTRY/>
            <POSTALCODE/>
            <STATEPROVN/>
          </ADDRESS>
        </PARTNER>
        <SHIPITEM>
          <QUANTITY qualifier="ITEM">
            <VALUE>45415</VALUE>
            <NUMOFDEC/>
            <SIGN>+ </SIGN>

            <UOM>LB</UOM>
          </QUANTITY>
          <ITEM/>
          <ITEMX/>
          <NOTES index="1">MT HOLLY SC</NOTES>
          <DOCUMNTREF>
            <DOCTYPE>PurchaseOrder</DOCTYPE>
            <DOCUMENTID>110008888</DOCUMENTID>
            <PARTNRID/>
            <PARTNRTYPE/>
            <DESCRIPTN/>
            <DOCUMENTRV/>
            <LINENUM>2</LINENUM>

            <SCHLINENUM>1</SCHLINENUM>
            <USERAREA>
              <DOCUMENTRELEASE/>
            </USERAREA>
          </DOCUMNTREF>
          <DOCUMNTREF>
            <DOCTYPE>PackingSlip</DOCTYPE>
            <DOCUMENTID>COEE^1318</DOCUMENTID>
            <PARTNRID/>
            <PARTNRTYPE/>
            <DESCRIPTN/>
            <DOCUMENTRV/>
            <LINENUM/>

            <SCHLINENUM/>
            <USERAREA/>
          </DOCUMNTREF>
        </SHIPITEM>
        <DOCUMNTREF>
          <DOCTYPE>BillOfLading</DOCTYPE>
          <DOCUMENTID>105555</DOCUMENTID>
          <PARTNRID/>
          <PARTNRTYPE/>
          <DESCRIPTN/>
          <DOCUMENTRV/>
          <LINENUM/>
          <SCHLINENUM/>

          <USERAREA/>
        </DOCUMNTREF>
        <DOCUMNTREF>
          <DOCTYPE>PackingSlip</DOCTYPE>
          <DOCUMENTID>COEE^1318</DOCUMENTID>
          <PARTNRID/>
          <PARTNRTYPE/>
          <DESCRIPTN/>
          <DOCUMENTRV/>
          <LINENUM/>
          <SCHLINENUM/>
          <USERAREA/>
        </DOCUMNTREF>

      </SHIPMENT>
    </SHOW_SHIPMENT>
  </DATAAREA>
</SHOW_SHIPMENT_005>

          Followup:

http://otn.oracle.com/pls/db92/db92.docindex?remark=homepage#index-XML
given that I don't really know what an "rdf" is, I'll point you to the XML
developers guide (you'll want to at least skim it -- not too long - so as to be
aware of your options.  then come back and read it in depth)

one solution would be to give oracle the dtd/xmlschema -- give it the
opportunity to create the object types, load the xml into that and just start
querying it.

there are many other options as well.

      Rating:
      2
  March 10, 2004
Reviewer:
      A reader
Hi Tom,

He is referring to Oracle Reports.
When reports are saved it has the extension of .rdf.

          Followup:

right -- then I don't see how you would convert xml into rdf but you can

a) load the xml into the database so that
b) you can run reports against data in the database

March 10, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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://download-west.oracle.com/docs/cd/B10501_01/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
...

      Rating: 3
Use one
      listener in either oracle 8i or 9i in PC for two servers to
      work  January 29, 2004
Reviewer:  David
      Jiang  from Houston, USA
Tom,

I know you can setup the TNS_NAMES in PC'regedit to point oracle8i to use
oracle9i's listener so that only one listener in oracle9i is used to function
for both. But is there any way you can set this up without TNS_NAMES, such as
modify the listener.ora file or change the NET Manger? Please show me how to do,
I know you can but I am confused with the logic behind it and the way to do it.
Is it the same way you do in UNIX machine?  Thanks a lot.

          Followup:

tnsnames.ora has nothing to do with the listener setup.

tnsnames.ora tells a client what port to connect to on what host and send what
service name.  It tells the listener nothing.

you can ONLY set this up with the listener.ora.  You just have multiple entries
in there (as you probably already do)

      Rating: 2
Need more
      explanation  January 30, 2004
Reviewer:
      davjia@auto-trol.com
Tom, thanks for your prompt answer. Here is why I ask you the question. I have
one Oracle8i and one Oracle9i servers in the same machine. Oracle8i listener is
not on, Oracle9i is on. Here is the listener.ora:
LISTENER =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = republic)(PORT = 1521))
  )
SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (SID_NAME = PLSExtProc)
      (ORACLE_HOME = D:\Oracle\ora92)
      (PROGRAM = extproc)
    )
    (SID_DESC =
      (GLOBAL_DBNAME = REP9R.DEV.CENTRA.AUTO_TROL.COM)
      (ORACLE_HOME = D:\Oracle\ora92)
      (SID_NAME = REP9R)
    )
    (SID_DESC =
      (GLOBAL_DBNAME = REP8B.DEV.CENTRA.AUTO_TROL.COM)
      (ORACLE_HOME = D:\Oracle\ora81)
      (SID_NAME = REP8B)
    )
  )
so now the client can connect both Oracel 8i and 9i if they have the right
connection setting in the tnsnames.ora.
BUt it still works the same after I remove the Oracle8i setting part from
Listener.ora file. Can you tell me WHY or I miss something here? Now the
listener.ora is like below:
LISTENER =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = republic)(PORT = 1521))
  )
SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (SID_NAME = PLSExtProc)
      (ORACLE_HOME = D:\Oracle\ora92)
      (PROGRAM = extproc)
    )
    (SID_DESC =
      (GLOBAL_DBNAME = REP9R.DEV.CENTRA.AUTO_TROL.COM)
      (ORACLE_HOME = D:\Oracle\ora92)
      (SID_NAME = REP9R)
    )
  )

          Followup:

because databases know how to register themselves with a listener.  The database
registers with the listener when the database is configured to do so.

Your listener.ora file can be virtually "empty" if you like.

      Rating: 5
listener.ora
      empty?  January 30, 2004
Reviewer:  Reader

If I have named my listener as listenertest, how does lsnrctl utility know to
start my listener (for ex: lsnrctl start listenertest) if I did not specify it
in listener.ora file? I don't understand how listener.ora can be empty? Thanks.

          Followup:

it is called "defaults".

defaults to "hostname" and port 1521

then, databases come register themselves with it and you can use it to get to
the databases.

      Rating: 2
Question
      regarding the "virtual" Listener  January 30, 2004

Reviewer:  David
Tom,

What you explain here is even confusing. This virtual listener only works if you
have one oracle instance in one machine and you log on from the server. It does
not work if you have multiple instances eg. Oracle8i and Oracle9i in the same
machine, or if you connect from a client via SQLNet. Please give me some
pratical example if I am wrong. No theory please!

          Followup:

what theory?

many servers can register with that listener.  you set it up in the init.ora for
the database.

      Rating: 5
Tom is
      right  January 30, 2004
Reviewer:  reader

Yes, PMON registers all of these info with the listener. No need to specify info
for services and sids in the listener.ora file anymore.

      Rating: 5
Thanks a
      lot.  February 02, 2004
Reviewer:  David

Tom,

Very good and useful information. Thanks a lot.

      Rating: 3
Authenticating
      Users  March 08, 2004
Reviewer:  Jayesh Antony
      Jose  from Chennai,India
Hello Tom,

Which process is responsible for authenticating a user when he is connecting to
oracle server
1) thru listener
2) by IPC

Is it done by the already running server process or the newly spawned server
porcess?

          Followup:

the dedicated/shared server you get connected to does this regardless of the
path you take.

      Rating: 2
Password
      Verfification using Oracle  March 10, 2004

Reviewer:  Jayesh Antony Jose  from Chennai, India

Hello,
      Suppose the user is providing invalid username and password and if the
server is starting a newprocess for validating the user, will it be a
unnecessary operation.
Will it be better if we use the already server processes (background)which are
running on the server to validate the user and start a new one only after
authentication...?

          Followup:

unecessary means "something that was not needed"

in order to authenticate...
you need a physical connection (since the information you need to authenticate
is in the database)...
a physical connection requires a process...

Besides, why optimize for the uncommon - infrequent case here.  You would hope
that 99.999% of your authenticates actually "suceed"

      Rating:
      3
Authentication  March 10, 2004
Reviewer:
      Jayesh Antony Jose  from Chennai, India
What I meant to say is can't we use the already running background server
process(around 8 are there) for this authentication?

          Followup:

and so for the 99.999% of the time you do successfully connect you want to
bother in a serial fashion dbwr or lgwr (who have important jobs to do) to
"authenticate" someone?

No, it would not be wise to make the authentication process a long and winding
road when it almost always results in "success".

March 10, 2004 Nuno -- Thanks for the question regarding
      "Loading Image Problem", version 8.1.7

      You Asked
Hi there,
i'm having a problem trying to upload a .tif image into a blob field from a web
page.

i've used the solution you provided to someone else here but the problem is that
the images i want to upload are in my computer instead of in the DB server.

I've created the virtual directory 'MY_FILES' - create or replace directory
MY_FILES as 'D:\WFF\images'; - ( and the file i want to upload -10687.tif - in
fact exists) but still i keep getting the same error message.

ERROR:
ORA-22285: non-existent directory or file for GETLENGTH operation

Is there any way i can upload a file directly from my computer without the use
of a virtual directory?

Thanks

      and we said...
You cannot use dbms_lob.loadfromfile unless the disk is visible to the database
server.  dbms_lob is plsql, plsql runs on the server, the server can only see
the disks it can see.

You'll need to use a different approach (such as using SQLLDR for example) or
move the images to the server.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:624423639385
for using sqlldr, all you would need is a file of filenames to be loaded.
Review &
      Followup

      Rating: 2
Another
      Way  December 13, 2002
Reviewer:  Nuno
      Oliveira  from Porto - Portugal
I was hoping someone could provide me a diferent answer but it seems like not.

Ok, thanx anyway.

          Followup:

wait -- I see I read too fast.

...
i'm having a problem trying to upload a .tif image into a blob field from a web
page.
.......

Ok, I see you are trying to use dbms_lob.loadfromfile which is wholly
inappropriate and so would be sqlldr.

You just want file upload from a web page.

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4456872422328
the whole bit about the virtual directory -- confused me.  virtual directory =
APP SERVER thingy.  "create or replace directory" = DATABASE thingy.  The two
have *nothing whatsoever to do with eachother.

You do not need a database directory object in order to do file upload.

      Rating: 4
g++
      Issues  March 10, 2004
Reviewer:  Tan Alam

I am running CPP file with the following command. My system is Linux Mandrake.
g++ -o prog2-1.out prog2-1.cpp
The I am getting the error.
 In function `main': undefined reference to `MCB_McbPtr::first()'

Here is the code:
extern "C" {
#include <stdio.h>
}
#include "gdefs.h"
#include "mcb.h"
using namespace std;
int  main()
{

    MCB_McbPtr          mcbPtr;
    // print header
    //
    printf("\n");
    printf("\n");
    printf("Davy Jones' Memory Display Program  (Rev .1) \n");
    printf("\n");
    printf("CHAIN   MCB     PSP    PARAGRAPHS \n");
    printf("----------------------------------\n");

    //
    //  Loop through the MCB's printing out the info they
    //  contain.
    //
    for  (mcbPtr.first(); mcbPtr.more(); mcbPtr.next()) {
printf("%c       %04X     %04X
%6u\n",mcbPtr->chain_status,mcbPtr.segment(),mcbPtr->owner_psp,mcbPtr->size_
paragraphs);

        }
    return 0;
}

          Followup:

You are only kidding right?

      Rating: 4
g++ linux
      compileation error  March 10, 2004
Reviewer:
      Tan alam  from Edwards Airforce base California
I am newbie and this the first time I have asked a question.

I cannot find any topic related to my topic so I just pick one randomly and
entered my question.

May be you can help.

          Followup:

this is a site about Oracle, the database.  I answer things about Oracle, I'm
not really a c++ programmer.

suggest you try groups.google.com - the internet usenet newsgroups.

      Rating: 5
g++
      error  March 10, 2004
Reviewer:
      A reader
Thank you.

      Rating: 5
g++
      error  March 10, 2004
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
You need to use the "-L" command line option to "point" to the library that has
"MCB_McbPtr" info in it.  I am on a Windows machine at the moment, but I believe
"-L" is correct.  Something like:  g++ -L mcblib.a -o prog2-1.out prog2-1.cpp
You will need to adjust that most certainly...

HTH,

Mark

March 10, 2004 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

Review &
      Followup

      Rating: 3
SQL*Loader and
      Parent Record key in Child Records  March 10, 2004

Reviewer:  John  from Sydney, Australia
Hi Tom,

I have a similar problem, except I have multiple parent and child records in the
same file. A child record is associated with the parent record that most
recently preceded it in the file, but the child record has no field to identify
the parent record.

I'm loading this file with SQL*Loader, and generating primary keys for both
tables. Is it possible using SQL*Loader to also populate the foreign key column
of the child record. At the moment I'm doing this with SQL after the load. I
realise this approach relies on all parent records loading successfully.

ORA817> CREATE TABLE TAB1
  2  (
  3    ID   NUMBER,
  4    VAL  VARCHAR2(30),
  5    CONSTRAINT TAB1_PK PRIMARY KEY (ID)
  6  );

Table created.

ORA817> CREATE TABLE TAB2
  2  (
  3    ID       NUMBER,
  4    TAB1_ID  NUMBER,
  5    VAL1     VARCHAR2(10),
  6    VAL2     VARCHAR2(10),
  7    CONSTRAINT TAB2_PK PRIMARY KEY (ID),
  8    CONSTRAINT TAB2_TAB1_FK FOREIGN KEY (TAB1_ID) REFERENCES TAB1 (ID)
  9  );

Table created.

And control file:
LOAD DATA INFILE * REPLACE
INTO TABLE tab1
WHEN (1)='P '
( id RECNUM,
  val POSITION(3:100)
)
INTO TABLE tab2
WHEN (1)='C '
( id     RECNUM,
  val1   POSITION(3:12),
  val2   POSITION(13:100)
)
BEGINDATA
P MasterRecord1
C Detail1   DetailA
C Detail2   DetailB
P MasterRecord2
C Detail3   DetailC
C Detail4   DetailD
C Detail5   DetailE

After loading:
TAB1:
ID VAL
 1 MasterRecord1
 4 MasterRecord2

TAB2:
ID TAB1_ID VAL1    VAL2
 2    null Detail1 DetailA
 3    null Detail2 DetailB
 5    null Detail3 DetailC
 6    null Detail4 DetailD
 7    null Detail5 DetailE

Then I populate TAB2.TAB1_ID
UPDATE TAB2 SET tab1_id = (SELECT MAX(ID) FROM TAB1 WHERE ID < TAB2.ID);

TAB2:
ID TAB1_ID VAL1    VAL2
 2       1 Detail1 DetailA
 3       1 Detail2 DetailB
 5       4 Detail3 DetailC
 6       4 Detail4 DetailD
 7       4 Detail5 DetailE

Is it possible to populate TAB2.TAB1_ID using just SQL*Loader?

          Followup:

if you create sequences S and S2, this will do (albeit without ARRAY inserts)

options(rows=1)
LOAD DATA INFILE *
REPLACE
INTO TABLE tab1
WHEN (1)='P '
( id "s.nextval",
  val POSITION(3:100)
)
INTO TABLE tab2
WHEN (1)='C '
( id     "s2.nextval",
  tab1_id "s.currval",
  val1   POSITION(3:12),
  val2   POSITION(13:100)
)
BEGINDATA

March 10, 2004 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?

      Rating: 3
Oracle for
      Mac?  March 10, 2004
Reviewer:  Jasbir Kular
       from Toronto, Ontario
Can Oracle run on the Mac OS?  If so is there production support for Oracle for
the Mac OS?  I need to design an application with a database component that has
to work on both Windows and Mac OS.  I have heard that people are running the
Oracle Developer edition (how different is developer edition from
standard/enterprise) on the Mac OS X, but when I called Oracle they said Mac OS
X is not supported.  Thanks for all your help, need to make a decision soon on
what database to use...I really want to use Oracle :)

          Followup:

As I understand it, 10g will be supported on mac os x and is to be released in a
couple of weeks -- but, you would need to verify that via support.

      Rating: 5
Oracle for
      Mac  March 10, 2004
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
You can get Oracle9i from the Apple website.  Please see:
http://www.apple.com/downloads/macosx/networking_security/oracle9idatabase.html
for requirements and download.

HTH,

Mark

      Rating: 5
Said too
      much  March 23, 2004
Reviewer:  Tyler D.
      Muth  from Reston, VA
OK,

My comments about performance on Windows vs Linux were not appropriate for this
forum.  This is a community used to answers based on hard evidence and
repeatable benchmarks. Relaying my Windows vs Linux experience opened up a
bigger can of worms than I expected.

The "go with what you know" point that Tom makes is so key here.  Who cares if
your db is x% faster on Linux if you lose critical data because you don't know
how to back it up?

So, I'll stand behind my first paragraph, but ask that you ignore the second.

Tyler


Listing

Recent

Archives

Advanced Search

FAQ by Category

Most Popular

Hot Articles

Your Questions

[ Listing ]

RSS Feed

Subject

Article

Last Updated (East coast time)

Pl/sql table type declaration approaches, which is better.

article only

Mar 11, 2004 09:34 am

Emailing after inserting into database tables.

article only

Mar 11, 2004 11:40 am

Blocks read using index vs. full table scan

article only

Mar 11, 2004 01:25 pm

from file into table

article only

Mar 11, 2004 01:26 pm

How Oracle handles daylight saving time change

article only

Mar 11, 2004 01:28 pm

Collection Variable Types

article only

Mar 11, 2004 01:29 pm

hash partitioning

article only

Mar 11, 2004 01:32 pm

difference between 2 dates

article only

Mar 11, 2004 01:48 pm

Executing AFTER row trigger on failed insert

article only

Mar 11, 2004 02:05 pm

multi-table insert

article only

Mar 11, 2004 02:08 pm

Rows Estimate without executing the query which has joins on many tables?

article only

Mar 11, 2004 02:38 pm

How to use Cursor in Java

article only

Mar 11, 2004 02:47 pm

Manipulating Sort Area Size

article only

Mar 11, 2004 03:49 pm

Loading XML file to a table via DBMS_XMLSave.insertXML

article only

Mar 12, 2004 09:32 am

Data Loading Approach

article only

Mar 12, 2004 09:33 am

Nologging

article only

Mar 12, 2004 09:36 am

database trigger -- same type triggers fire in random order

article only

Mar 12, 2004 09:38 am

Public and private Rollback segments

article only

Mar 12, 2004 09:44 am

sqlplus command line usage

article only

Mar 12, 2004 09:52 am

Serializing Java Objects into the database (and getting them back out)

article only

Mar 12, 2004 09:57 am

ora-4068

article only

Mar 12, 2004 11:00 am

Heterogeneous replication

article only

Mar 12, 2004 11:46 am

sql*plus on unix terminals

article only

Mar 12, 2004 04:55 pm

Sequences

article only

Mar 12, 2004 05:22 pm

<> and !=, yyyy and rrrr

article only

Mar 12, 2004 05:23 pm

Bulk insert of records , using something like a user defined record ???

article only

Mar 12, 2004 07:51 pm

SQL Query

article only

Mar 12, 2004 08:00 pm

OLTP Rollback segment planning

article only

Mar 13, 2004 08:45 am

UPDATE

article only

Mar 13, 2004 10:20 am

How to dynamically update a remote table using 'where current of l_cursor'?

article only

Mar 13, 2004 12:04 pm

Encrytion and Decrytion

article only

Mar 13, 2004 12:05 pm

how to import sequence

article only

Mar 13, 2004 12:05 pm

Primary Key created does not reflect in DBA_INDEXES

article only

Mar 14, 2004 09:46 am

procedure

article only

Mar 14, 2004 10:01 am

freelists higher than 'sessions concurrently inserting'

article only

Mar 14, 2004 10:09 am

SQL advanced querying technics

article only

Mar 14, 2004 07:32 pm

rollback segment

article only

Mar 15, 2004 07:21 am

Read consistency accross cursors in one procedure

article only

Mar 15, 2004 07:36 am

XML data

article only

Mar 15, 2004 08:45 am

pass an array to a cursor, possible?

article only

Mar 15, 2004 09:19 am

ora-28007 password cannot be reused problem

article only

Mar 15, 2004 09:51 am

SQL Tuning

article only

Mar 15, 2004 09:54 am

Question on your HOTSOS presentation, "Developing Efficient ..." Demo002.sql

article only

Mar 15, 2004 09:57 am

how to access data to and from foxpro and oracle simultaneously

article only

Mar 15, 2004 10:32 am

Joins and Normalisation

article only

Mar 15, 2004 10:40 am

IOUG Conference........... Recommendations

article only

Mar 15, 2004 10:41 am

Copying Data ?

article only

Mar 15, 2004 12:54 pm

Rowid or Primary Key

article only

Mar 15, 2004 02:22 pm

'Kill session' priority

article only

Mar 15, 2004 02:24 pm

Your new book : Expert one-on-one performance by design

article only

Mar 15, 2004 02:42 pm

How to load text files into database and read them from database?

article only

Mar 15, 2004 02:51 pm

Partition level export and global indexes

article only

Mar 15, 2004 03:07 pm

Backing up an Oracle db

article only

Mar 15, 2004 03:45 pm

Pfile or SPFile

article only

Mar 15, 2004 03:52 pm

Disabling PK drops Index...

article only

Mar 15, 2004 04:36 pm

External Tables - Backup

article only

Mar 15, 2004 04:54 pm

Truncation of numbers

article only

Mar 15, 2004 05:06 pm

Physical Reads on a DataWarehousing-Solution

article only

Mar 15, 2004 05:09 pm

What is async IO ?

article only

Mar 15, 2004 05:54 pm

Making Oracle Case insensitive

article only

Mar 15, 2004 06:15 pm

Function to concatenate output

article only

Mar 15, 2004 07:53 pm

DBMS_SQL and array initialisation

article only

Mar 15, 2004 08:15 pm

DDL Auditing - automatically including sql alt script file names

article only

Mar 16, 2004 07:37 am

Lots of (Sum) s in a Group By

article only

Mar 16, 2004 07:54 am

Oracle hot backup

article only

Mar 16, 2004 09:18 am

Horizontal tree traversing

article only

Mar 16, 2004 11:34 am

DBMS_UTILITY example

article only

Mar 16, 2004 01:30 pm

Advanced Queues for background Reporting

article only

Mar 16, 2004 01:38 pm

Problem to connect as SYSDBA

article only

Mar 16, 2004 01:40 pm

Timed_statistics & SQL_TRACE parameters

article only

Mar 16, 2004 02:15 pm

Package/Session State Management - Mod PL/SQL

article only

Mar 16, 2004 02:16 pm

Very Slow after restart DB

article only

Mar 16, 2004 02:53 pm

convert values to columns

article only

Mar 16, 2004 03:15 pm

function call cache?

article only

Mar 16, 2004 03:36 pm

TSPITR

article only

Mar 16, 2004 04:03 pm

Loop in a SQL Query

article only

Mar 16, 2004 05:25 pm

lmt and uniform extents

article only

Mar 16, 2004 05:30 pm

Performance - top N query

article only

Mar 16, 2004 06:23 pm

setting size of online redo log members

article only

Mar 16, 2004 06:26 pm

Questions on Locking

article only

Mar 16, 2004 06:37 pm

Parsing a varchar for use in a comparison test.

article only

Mar 16, 2004 08:22 pm

One database or two?

article only

Mar 16, 2004 08:26 pm

fun fun fun -- (you are wrong wrong wrong)

article only

Mar 17, 2004 07:05 am

Difference between V$Process and v$session

article only

Mar 17, 2004 07:07 am

how to get around Dbms_output limitations

article only

Mar 17, 2004 07:10 am

transportable tablespaces

article only

Mar 17, 2004 07:21 am

Is there any command to Unlock a table !

article only

Mar 17, 2004 07:23 am

Implicit Commit

article only

Mar 17, 2004 07:27 am

Checkpoint&logswitch -- what is their relationship.

article only

Mar 17, 2004 07:41 am

Hashing vs. Passwords

article only

Mar 17, 2004 08:09 am

Nologging

article only

Mar 17, 2004 08:12 am

Parallel Query concerns

article only

Mar 17, 2004 08:14 am

Ref Cursor parameter

article only

Mar 17, 2004 08:28 am

Returning a clob into a variable for update Using Dynamic SQL

article only

Mar 17, 2004 08:29 am

A question

article only

Mar 17, 2004 08:32 am

Audit / Replication

article only

Mar 17, 2004 08:34 am

how do we retrieve LOBS from the database using JAVA interface

article only

Mar 17, 2004 08:34 am

query

article only

Mar 17, 2004 08:52 am

Auditing

article only

Mar 17, 2004 11:02 am

snapshot too old error

article only

Mar 17, 2004 11:35 am

Deleting duplicate records

article only

Mar 17, 2004 04:41 pm

Using Interval Datatype

article only

Mar 17, 2004 06:26 pm

Few questions regarding oracle and unix

article only

Mar 17, 2004 06:36 pm

customizing the SQLprompt

article only

Mar 17, 2004 06:49 pm

Pivot a report by date range

article only

Mar 17, 2004 07:46 pm

GET and POST method using MOD PL/SQL

article only

Mar 17, 2004 09:06 pm

row(s) 1 - 106 of 106

March 11, 2004 Arvind -- Thanks for the question regarding "Pl/sql table type declaration approaches, which is better.", version 8.1.7
You Asked
Hi Tom,
If we have some plsql table type o/p parameters in some oracle stored procedure
like as follows:

create table emp( empname varchar2(80));

create or replace package pck1 is
   type tp_varchar2 is table of varchar2(100) index by binary_integer;
   procedure proc( var1  out tp_varchar2);
end;

create or replace package pck2 is
   type tp_varchar2 is table of emp.empname%type index by binary_integer;
   procedure proc( var1  out tp_varchar2);
end;

The only change is this, that in first package I have fixed length of 100 for
plsql table type while in other I have table field length which is dynamic.

Lets assume while returning data o/p variable var1 is returning max data  length
50 in all records in plsql table. So will it use the same amount of memory while
returning data in both approaches or it will be different.
which one is better in your opinion, since by using first approach I can use
only one declared type in other o/p parameters also where returning length is <=
100, but by using second approach I need define as many plsql table types as
many number of plsql table o/p parameters.

Thanks for any help.
Arvind Kapil Sharma
and we said...
Me, I would just use varchar2(some_big_number) once.  The data in the table is
allocated using varying length strings.  varchar2(100), (20), (10000) -- they
will consume the same space if you put the same strings in.

ops$tkyte@ORA9I.WORLD> !cat test.sql
declare
        type array is table of varchar2(&1) index by binary_integer;

        l_data array;
begin
        for i in 1 .. 1000
        loop
                l_data(i) := rpad( '*', 20, '*' );
        end loop;
end;
/
@mystat pga

ops$tkyte@ORA9I.WORLD> @test 100
old   2:        type array is table of varchar2(&1) index by binary_integer;
new   2:        type array is table of varchar2(100) index by binary_integer;

PL/SQL procedure successfully completed.

old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('pga')||'%'

NAME                                VALUE
------------------------------ ----------
session pga memory                 237344
session pga memory max             237344

ops$tkyte@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

Wed Aug 14 13:43:28 EDT 2002
(tkyte@aria-dev) /export/home/tkyte
> plus

SQL*Plus: Release 9.0.1.0.0 - Production on Wed Aug 14 14:57:00 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

ops$tkyte@ORA9I.WORLD> @test 30
old   2:        type array is table of varchar2(&1) index by binary_integer;
new   2:        type array is table of varchar2(30) index by binary_integer;

PL/SQL procedure successfully completed.

old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('pga')||'%'

NAME                                VALUE
------------------------------ ----------
session pga memory                 237344
session pga memory max             237344

ops$tkyte@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

Wed Aug 14 13:43:28 EDT 2002
(tkyte@aria-dev) /export/home/tkyte
> plus

SQL*Plus: Release 9.0.1.0.0 - Production on Wed Aug 14 14:57:06 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

ops$tkyte@ORA9I.WORLD> @test 10000
old   2:        type array is table of varchar2(&1) index by binary_integer;
new   2:        type array is table of varchar2(10000) index by binary_integer;

PL/SQL procedure successfully completed.

old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('pga')||'%'

NAME                                VALUE
------------------------------ ----------
session pga memory                 237344
session pga memory max             237344
Review & Followup

Rating: 5
Thats what I was expecting August 14, 2002
Reviewer: Arvind K Sharma from USA
Thanks tom.
Really it solved some of the doubts in my mind.

Take care
Arvind K Sharma
Rating: 5
Does 'session pga memory' shrink ? November 01, 2002
Reviewer: Adrian Billington from UK
Tom

I was always under the assumption that using the DELETE method on a collection
did not release the memory, whereas assigning an empty collection to a currently
populated one released the memory. However, Steven Fueurstein's latest OPP3 says
that DELETE of all elements of a collection returns all the memory back.

How do you measure this? I had a go as follows, but it doesn't seem to do what I
was expecting it to do:-

920> declare
  2
  3     g_mem     pls_integer;
  4     g_mem_max pls_integer;
  5
  6     type typ_ibt_bin is table of varchar2(30)
  7        index by binary_integer;
  8
  9     ibt_bin       typ_ibt_bin;
 10     ibt_bin_empty typ_ibt_bin;
 11
 12     procedure snap is
 13     begin
 14        select (select value from v$mystat
 15                where statistic# = (select statistic# from v$statname where
name = 'session pga memory'))
 16        ,      (select value from v$mystat
 17                where statistic# = (select statistic# from v$statname where
name = 'session pga memory max'))
 18        into g_mem, g_mem_max
 19        from dual;
 20        --dbms_output.put_line('Memory is '||to_char(g_mem));
 21        --dbms_output.put_line('Memory max is '||to_char(g_mem_max));
 22     end snap;
 23
 24     procedure show_mem is
 25        v_mem pls_integer := g_mem;
 26        v_mem_max pls_integer := g_mem_max;
 27     begin
 28        snap();
 29        dbms_output.put_line('Memory has increased by '||to_char(g_mem -
v_mem)||' bytes.');
 30        dbms_output.put_line('Memory max has increased by
'||to_char(g_mem_max - v_mem_max)||' bytes.');
 31     end show_mem;
 32
 33  begin
 34
 35     /* Now let's start... */
 36     snap();
 37
 38     /* Get memory usage above default session allocation... */
 39     select name bulk collect into ibt_bin from dba_source;
 40     show_mem();
 41
 42     /* Any released ? (should be no)... */
 43     ibt_bin.delete(150);
 44     show_mem();
 45
 46     /* Any released ? (I thought it should be no, OPP3 says it should)... */
 47     ibt_bin.delete;
 48     show_mem();
 49
 50     /* Any released ? (I thought it should be)... */
 51     ibt_bin := ibt_bin_empty;
 52     show_mem();
 53
 54  end;
 55  /
Memory has increased by 5864796 bytes.
Memory max has increased by 6585692 bytes.
Memory has increased by 0 bytes.
Memory max has increased by 0 bytes.
Memory has increased by 0 bytes.
Memory max has increased by 0 bytes.
Memory has increased by 0 bytes.
Memory max has increased by 0 bytes.

PL/SQL procedure successfully completed.

Can v$mystat provide the answer as I was expecting?

Regards

Adrian
Followup:
If you have my book "expert one on one", I have a discussion on this and why pga
memory in general won't go down (it would not be worth the effort -- it won't
really "shrink" at the OS level regardless).  We can "force it" but in general,
you are only making yourself feel better but the system is running slower for
it.

Here is an example, if you want the details, my book goes into why shrinking PGA
memory in dedicated server isn't worth the effort:

ops$tkyte@ORA920.US.ORACLE.COM> declare
  2          type array is table of varchar2(4000) index by binary_integer;
  3
  4          l_data array;
  5
  6          procedure show_pga_usage( p_msg in varchar2 )
  7          is
  8          begin
  9          dbms_output.put_line( p_msg );
 10          for x in (      select a.name, b.value
 11                    from v$statname a, v$mystat b
 12                   where a.statistic# = b.statistic#
 13                     and a.name like '%pga%' )
 14          loop
 15                  dbms_output.put_line( to_char( x.value, '999,999,999' ) ||
' ' || x.name );
 16          end loop;
 17          end;
 18  begin
 19          show_pga_usage( 'starting' );
 20
 21          for i in 1 .. 1000
 22          loop
 23                  l_data(i) := rpad( '*', 4000, '*' );
 24          end loop;
 25          show_pga_usage( 'after filling array: ' );
 26
 27          for i in 1 .. l_data.count
 28          loop
 29                  if ( mod( i, 500 ) = 0 )
 30                  then
 31                          show_pga_usage( 'after deleting ' || i || '
entries' );
 32                  end if;
 33                  l_data.delete(i);
 34          end loop;
 35          show_pga_usage( 'after deleting ALL entries' );
 36  end;
 37  /
starting
401,444 session pga memory
401,444 session pga memory max
after filling array:
5,888,396 session pga memory
5,888,396 session pga memory max
after deleting 500 entries
5,888,396 session pga memory
5,888,396 session pga memory max
after deleting 1000 entries
5,888,396 session pga memory
5,888,396 session pga memory max
after deleting ALL entries
5,888,396 session pga memory
5,888,396 session pga memory max

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> @mystat pga
ops$tkyte@ORA920.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('pga')||'%'

NAME                                VALUE
------------------------------ ----------
session pga memory                5888396
session pga memory max            5888396

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> declare
  2          type array is table of varchar2(4000) index by binary_integer;
  3
  4          l_data array;
  5
  6          procedure show_pga_usage( p_msg in varchar2 )
  7          is
  8          begin
  9          dbms_output.put_line( p_msg );
 10          for x in (      select a.name, b.value
 11                    from v$statname a, v$mystat b
 12                   where a.statistic# = b.statistic#
 13                     and a.name like '%pga%' )
 14          loop
 15                  dbms_output.put_line( to_char( x.value, '999,999,999' ) ||
' ' || x.name );
 16          end loop;
 17          end;
 18  begin
 19          show_pga_usage( 'starting' );
 20
 21          for i in 1 .. 1000
 22          loop
 23                  l_data(i) := rpad( '*', 4000, '*' );
 24          end loop;
 25          show_pga_usage( 'after filling array: ' );
 26
 27          for i in 1 .. l_data.count
 28          loop
 29                  if ( mod( i, 500 ) = 0 )
 30                  then
 31                          show_pga_usage( 'after deleting ' || i || '
entries' );
 32                          dbms_session.FREE_UNUSED_USER_MEMORY;
 33                          show_pga_usage( 'after deleting ' || i || ' entries
and calling "free memory"' );
 34                  end if;
 35                  l_data.delete(i);
 36          end loop;
 37          show_pga_usage( 'after deleting ALL entries' );
 38  end;
 39  /
starting
5,888,396 session pga memory
5,888,396 session pga memory max
after filling array:
5,888,396 session pga memory
5,888,396 session pga memory max
after deleting 500 entries
5,888,396 session pga memory
5,888,396 session pga memory max
after deleting 500 entries and calling "free memory"
3,194,204 session pga memory
5,888,396 session pga memory max
after deleting 1000 entries
3,194,204 session pga memory
5,888,396 session pga memory max
after deleting 1000 entries and calling "free memory"
467,156 session pga memory
5,888,396 session pga memory max
after deleting ALL entries
467,156 session pga memory
5,888,396 session pga memory max

PL/SQL procedure successfully completed.
Rating: 5
That's got it... November 01, 2002
Reviewer: Adrian Billington from UK
Tom

Of course! By freeing the memory with DBMS_SESSION (OK I would never use this in
"real" code), it at least enabled me to verify that the DELETE method (all
elements only) and empty collection assignment both work in the same way.

Thanks.

Adrian
Rating: 3
OK March 11, 2004
Reviewer: Catherine from OH,USA
Dear Tom,
I would like to split an array into parts.Is it possible in
Oracle?For example
sql>declare
      type t is table of number;
      array t := t(1,2,3,4,5,6);
1)How to split the array into 2 or more parts?
2)How to obtain the MEDIAN of the array ROUDED OFF to nearest integer?
Could you please help?
Bye!
Followup:
1) i would do procedurally for sure.  something like

  for i in 1 .. t.count
  loop
     if ( mod(i,2) = 0 ) then
        t2.extend;
        t2(t2.count) := t(i);
     else
        t1.extend .....

2) you would either do procedurally, or if you created the type at the SQL
level, you can use SQL:

ops$tkyte@ORA9IR2> create or replace type array as table of number
  2  /

Type created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2      t array := array(1,2,3,4,5,6);
  3      l_med  number;
  4  begin
  5
  6      SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY column_value DESC)
  7        into l_med
  8        from TABLE( cast( t as array ) );
  9
 10      dbms_output.put_line( round(l_med) );
 11  end;
 12  /
4

PL/SQL procedure successfully completed.
March 11, 2004 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 :)
Rating: 3
Receiving from ,date,subject of email in oracle database March 11, 2004
Reviewer: Muhammad Mustafa from India
  Dear Sir

         Is it possible to store incoming mail date,subject in the oracle
database if yes please give me any example.

 Thanks & Regards
Followup:
well, we have Collaboration Suite which is email in Oracle.

Otherwise, you would load up the javamail API in order to "read" email (i don't
have an example of reading via javamail, only SENDING via javamail.  basically,
you would have to write a program that read mail)
March 11, 2004 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
Rating: 5
DBA_TABLES.USED_BLOCKS Vs # of. Blocks in Buffer January 15, 2004
Reviewer: Sami from NJ,USA
Dear Tom,
SQL> l
  1  select table_name,blocks,
    empty_blocks,avg_row_len,num_rows,
    LAST_ANALYZED
  2  from dba_tables
  3* where table_name in
('COUNTRIES','LANGUAGES','USERSTATUS','USERTIERS','USERTYPES')
SQL> /

TABLE_NAME  BLOCKS EMPTY_BLOCKS AVG_ROW_LEN   NUM_ROWS LAST_ANALYZED
------------------ ------------ ----------- ---------- -----------------
COUNTRIES        5          509          32        242 10/17/03 18:00:40
LANGUAGES        4          510          39        141 01/04/04 12:25:44
USERSTATUS       2          512          74         24 01/04/04 13:15:28
USERTYPES        1          513          51          3 01/04/04 13:15:29
USERTIERS        1          513          52          6 01/04/04 13:15:29
COUNTRIES       10          504          46        242 01/04/04 12:07:29
LANGUAGES

7 rows selected.

The following query is from "Database Performance Tuning Guide and Reference"
manual.(14-15)
under "Determining Which Segments Have Many Buffers in the Pool"

COLUMN object_name FORMAT a40
COLUMN number_of_blocks FORMAT 999,999,999,999

  1  SELECT o.object_name, COUNT(1) number_of_blocks
  2  FROM DBA_OBJECTS o, V$BH bh
  3  WHERE o.object_id = bh.objd
  4  AND o.owner != 'SYS'
  5  AND o.object_name in
('COUNTRIES','LANGUAGES','USERSTATUS','USERTIERS','USERTYPES')
  6  GROUP BY o.object_name
  7* ORDER BY count(1)
SQL> /

OBJECT_NAME                              NUMBER_OF_BLOCKS
---------------------------------------- ----------------
USERTIERS                                               2
USERTYPES                                               2
USERSTATUS                                              3
LANGUAGES                                              22
COUNTRIES                                              53

SQL> select count(*) from languages;

  COUNT(*)
----------
       141

SQL> select count(*) from countries;

  COUNT(*)
----------
       242

Dear Tom,

I have only 5 USED BLOCKS for countries table, 4 USED BLOCKS for languages
table.But in buffer cache  22 BLOCKS for LANGUAGES and 53 BLOCKS for COUNTRIES.

Even if FTS happens it should have have max of 5 BLOCKS(for countries) and 4
BLOCKS(languages),
Isn't it? Or Am i missing something here?

There no DML activity on languages and countries table after ANALYZE.

Kindly need your advise.
Followup:
funny, their query is wrong -- oh well (should use data_object_id, ask the
authors "hey, what happens if we move or truncate that table??)

but.... why don't you actually investigate this?  aren't you curious?  you could
sort of look at the v$bh entries yourself and see what/why they are no?

you'll see different behaviors depending on whether you use assm, non-assm, this
that and the other thing.  but -- if you just take a peek -- you might be able
to see what you see and figure it out (i cannot make every possible guess here!)

ops$tkyte@ORA9IR2> create table abc ( x int ) tablespace assm;

Table created.

ops$tkyte@ORA9IR2> insert into abc values ( 1 );

1 row created.

ops$tkyte@ORA9IR2> commit;

Commit complete.

ops$tkyte@ORA9IR2> analyze table abc compute statistics;

Table analyzed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select * from abc;

         X
----------
         1

ops$tkyte@ORA9IR2> column ext format a15
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> SELECT o.data_object_id,
  2         (select blocks from user_tables where table_name = 'ABC' ) blocks,
  3             (select file_id || ',' || block_id || ',' || (block_id+blocks-1)
from dba_extents where segment_name = 'ABC' and owner = user ) ext,
  4              bh.FILE# ,    bh.BLOCK#,bh.CLASS#, bh.STATUs
  5    FROM (select * from user_objects where object_name = 'ABC'  and rownum >
0) o,
  6             V$BH bh
  7   WHERE o.data_object_id = bh.objd
  8   order by file#, block#
  9  /

DATA_OBJECT_ID     BLOCKS EXT                  FILE#     BLOCK#     CLASS# STATU
-------------- ---------- --------------- ---------- ---------- ---------- -----
         34115          5 9,3217,3224              9       3217          8 xcur
         34115          5 9,3217,3224              9       3218          9 xcur
         34115          5 9,3217,3224              9       3219          4 xcur
         34115          5 9,3217,3224              9       3220          1 xcur
         34115          5 9,3217,3224              9       3221          1 xcur
         34115          5 9,3217,3224              9       3222          1 xcur
         34115          5 9,3217,3224              9       3223          1 xcur
         34115          5 9,3217,3224              9       3224          1 xcur

8 rows selected.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> drop table abc;

Table dropped.

ops$tkyte@ORA9IR2> create table abc ( x int ) tablespace non_assm;

Table created.

ops$tkyte@ORA9IR2> insert into abc values ( 1 );

1 row created.

ops$tkyte@ORA9IR2> commit;

Commit complete.

ops$tkyte@ORA9IR2> analyze table abc compute statistics;

Table analyzed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select * from abc;

         X
----------
         1

ops$tkyte@ORA9IR2> column ext format a15
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> SELECT o.data_object_id,
  2         (select blocks from user_tables where table_name = 'ABC' ) blocks,
  3             (select file_id || ',' || block_id || ',' || (block_id+blocks-1)
from dba_extents where segment_name = 'ABC' and owner = user ) ext,
  4              bh.FILE# ,    bh.BLOCK#,bh.CLASS#, bh.STATUs
  5    FROM (select * from user_objects where object_name = 'ABC'  and rownum >
0) o,
  6             V$BH bh
  7   WHERE o.data_object_id = bh.objd
  8   order by file#, block#
  9  /

DATA_OBJECT_ID     BLOCKS EXT                  FILE#     BLOCK#     CLASS# STATU
-------------- ---------- --------------- ---------- ---------- ---------- -----
         34116          1 1,53473,53480            1      53473          4 xcur
         34116          1 1,53473,53480            1      53474          1 xcur
Rating: 5
More info, since two users have same table names (languages,countries) January 15, 2004
Reviewer: Sami from NJ,USA
Deat Tom,
Included OWNER name in the above query for more clarity.

SQL> SELECT o.owner,o.object_name, COUNT(1) number_of_blocks
FROM DBA_OBJECTS o, V$BH bh
WHERE o.object_id = bh.objd
AND o.owner != 'SYS'
AND o.object_name in
('COUNTRIES','LANGUAGES','USERSTATUS','USERTIERS','USERTYPES')
GROUP BY o.owner,o.object_name
ORDER BY count(1)
  2    3    4    5    6    7    8  /

OWNER   OBJECT_NAME    NUMBER_OF_BLOCKS
------- -------------- ----------------
USER1   LANGUAGES                     2
TEST    USERTIERS                     2
TEST    USERTYPES                     2
TEST    USERSTATUS                    3
USER1   COUNTRIES                     6
TEST    LANGUAGES                    20
TEST    COUNTRIES                    47

7 rows selected.

SQL>

SQL> l
  1  select
owner,table_name,blocks,empty_blocks,avg_row_len,num_rows,LAST_ANALYZED
  2    from dba_tables
  3*   where table_name in
('COUNTRIES','LANGUAGES','USERSTATUS','USERTIERS','USERTYPES')
SQL> /

OWNER   TABLE_NAME  BLOCKS EMPTY_BLOCKS AVG_ROW_LEN   NUM_ROWS LAST_ANALYZED
------- ------------------ ------------ ----------- ---------- -----------------
USER1   COUNTRIES        5          509          32        242 10/17/03 18:00:40
TEST    LANGUAGES        4          510          39        141 01/04/04 12:25:44
TEST    USERSTATUS       2          512          74         24 01/04/04 13:15:28
TEST    USERTYPES        1          513          51          3 01/04/04 13:15:29
TEST    USERTIERS        1          513          52          6 01/04/04 13:15:29
TEST    COUNTRIES       10          504          46        242 01/04/04 12:07:29
USER1   LANGUAGES

7 rows selected.

SQL>
Followup:
(use data object id)
Rating: 5
Evaluation of indexes March 10, 2004
Reviewer: Sanjaya Balasuriya from Columbo, Sri Lanka
Hi Tom,

How can we evaluate the performence of a query with and without indexes ?
I know I can use 'set autotrace traceonly' to check a statement without actually
running it ?

Is there a way that I can say run this statement without using indexes and run
this one using indexes ?

Thanks in advance.
Followup:
HINTs are excellent for testing the performance of different plans.
Rating: 3
max_io_size March 11, 2004
Reviewer: Nadya from Russia
"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..."
Tom, does it means that if, for example,
- db_block_size = 8K
- max_io_size=1M
- locally managed tablespace uniform extent 64K,
then in theory I can set db_file_multiblock_read_count=128,
but in practic I can never  read more than 64K in a time?
If it is true, then must we choose extent  equal or more than max_io_size?
Thanks.
Followup:
correct -- if you have 64k extents, that is the biggest multi-block read.

You do NOT have to (nor want to) make your extents 1m just because that is the
max io size.

Index access = no multi-block reads
Small tables = we would not read 1m anyway even in a full scan.
March 11, 2004 Goran -- Thanks for the question regarding "from file into table", version 2000
You Asked
How to with PL/SQL code import (load) data into an Oracle table
from a delimited text file (from Win95 directory)?
and we said...
If the "Win95" directory is on the "Server" -- we can do this.

If the "Win95" directory is some arbitrary directory on some
arbitrary client -- we can do this with PLSQL in Forms (not
via a stored procedure but via a client side plsql routine) but
not with a stored procedure.

PLSQL runs in either the server (this is normal) or in the
client as client side PLSQL using a runtime engine such as forms
or reports.  when PLSQL is running in the server, it has access
only to files on the server -- it can read and write only what
is on the server and has no abilities to touch the client
machine.  When PLSQL is running on the client under forms -- the
reverse it true -- that PLSQL can only read/write files on the
client, not on the server.

Here is an example of SERVER side PLSQL (replace UTL_FILE with
calls to TEXT_IO if you want client side PLSQL, you would
probably want to use EXEC_SQL instead of DBMS_SQL as well).  The
following is a "mini sqlldr" in plsql.  It loads delimited data
into any table/view from a file.

ops$tkyte@8i> create table badlog( errm varchar2(4000), data
varchar2(4000) );

Table created.

ops$tkyte@8i> column errm format a25
ops$tkyte@8i> column data format a25
ops$tkyte@8i> create or replace
  2  function  load_data( p_table        in varchar2,
  3                       p_cnames    in varchar2,
  4                       p_dir       in varchar2,
  5                       p_filename  in varchar2,
  6                       p_delimiter in varchar2 default '|' )
  7  return number
  8  is
  9      l_input         utl_file.file_type;
 10      l_theCursor     integer default dbms_sql.open_cursor;
 11      l_buffer        varchar2(4000);
 12      l_lastLine      varchar2(4000);
 13      l_status        integer;
 14      l_colCnt        number default 0;
 15      l_cnt           number default 0;
 16      l_sep           char(1) default NULL;
 17      l_errmsg        varchar2(4000);
 18  begin
 19      l_input := utl_file.fopen( p_dir, p_filename, 'r' );
 20
 21      l_buffer := 'insert into ' || p_table || ' values ( ';
 22      l_colCnt := length(p_cnames)-
 23                      length(replace(p_cnames,',',''))+1;
 24
 25      for i in 1 .. l_colCnt
 26      loop
 27          l_buffer := l_buffer || l_sep || ':b'||i;
 28          l_sep    := ',';
 29      end loop;
 30      l_buffer := l_buffer || ')';
 31
 32      dbms_sql.parse(l_theCursor, l_buffer, dbms_sql.native);
 33
 34      loop
 35          begin
 36              utl_file.get_line( l_input, l_lastLine );
 37          exception
 38              when NO_DATA_FOUND then
 39                  exit;
 40          end;
 41          l_buffer := l_lastLine || p_delimiter;
 42
 43
 44          for i in 1 .. l_colCnt
 45          loop
 46              dbms_sql.bind_variable
 47               ( l_theCursor, ':b'||i,
 48                 substr( l_buffer, 1,
 49                 instr(l_buffer,p_delimiter)-1 ) ) ;
 50              l_buffer := substr( l_buffer,
 51                             instr(l_buffer,p_delimiter)+1 );
 52          end loop;
 53
 54          begin
 55              l_status := dbms_sql.execute(l_theCursor);
 56              l_cnt := l_cnt + 1;
 57          exception
 58              when others then
 59                  l_errmsg := sqlerrm;
 60                  insert into badlog ( errm, data )
 61                  values ( l_errmsg, l_lastLine );
 62          end;
 63      end loop;
 64
 65      dbms_sql.close_cursor(l_theCursor);
 66      utl_file.fclose( l_input );
 67      commit;
 68
 69      return l_cnt;
 70  end load_data;
 71  /

Function created.

ops$tkyte@8i>
ops$tkyte@8i> drop table t1;

Table dropped.

ops$tkyte@8i> create table t1 ( x int, y int, z int );

Table created.

ops$tkyte@8i>
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>
ops$tkyte@8i>
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

ops$tkyte@8i>
Review & Followup

Rating: 5
May 03, 2001
Reviewer: Pauline from U.S.A
Rating: 4
Very useful September 27, 2001
Reviewer: Reader from USA
Hi,

What if the number of columns to be loaded is only part of the columns in the
base table, and we also want to load some constant values into the table for
every row, e.g. load SYSDATE, USER etc.?

Is this will affect the general feature of this most suitable procedure?

Thanks
Followup:
Well, if you wanted to load constant values -- I would use DEFAULTS on the
columns.

the little, simple, trivial utility below does not accomidate for defaults and
skipping columns.

In Oracle9i we have a feature called an EXTERNAL table which is sort of a create
table with a sqlldr control file -- this does support all of that.  Instead of
running sqlldr, you'll "create table" and now you just use SQL to process the
input file.
Rating: 4
What if database size is in GBs August 02, 2002
Reviewer: Tony from NJ USA
Hi Tom,

I need to migrate around 4 GB data from ASCII file into Oracle database. I have
two options :

1. Load data from file to oracle using Loader and then write scripts to migrate
the data into live tables.
2. Write script using UTL_FILE and read every record, do some complex processing
and load the record directly into table.

What will be the faster as I told you about the size of data need to loaded?

Regards,
Tony
Followup:
4 gig is tiny today.

1) would most likely be faster.  Especially if you can use bulk operations on
the data (mass inserts/updates).  Use the direct path loader, and a full scan
will be much faster then utl_file.

If you have 9i, just use an EXTERNAL table.
Rating: 4
External Table August 06, 2002
Reviewer: Vish from London
I'm building a datawarehouse, I have to read the files in external table assign
new keys and load the fact table. Will it not cause performance problems if I
use external tables ( I can't create indexes on external tables right ? )

Vish
Followup:
To load -- no, no worries there.  You'll be full scanning these tables anyway -
you want to process every record right?  No indexes would be used anyway.
Rating: 2
error !! August 09, 2002
Reviewer: a from india
at this point, i got an error :

SQL> ed
Wrote file afiedt.buf

  1  begin
  2       dbms_output.put_line(
  3           load_data( 'T1',
  4                      'x,y,z',
  5                      '/tmp',
  6                      't1.dat',
  7                      ',' ) || ' rows loaded' );
  8*   end;
SQL> /
begin
*
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 "QAAR.LOAD_DATA", line 18
ORA-06512: at line 2
Followup:
uh-huh.

you didn't set the utl_file_dir init.ora parameter.  or that file does not
exist.
Rating: 4
Mapping of external_id's to internal_id's January 27, 2003
Reviewer: saj from New york, USA
Thanks for all your examples and explanation's on various topics. I am a regular
visitor to your site and enjoy reading the marvellous solutions you give!

I have one question and it is related to design and performance! We are on
Oracle version 9.i R/2

We have data coming in from various sites and countries and they have their own
ID's or key values and we keep our own ID's or primary key.

Say for example the external table/file comes in as
ext_id, type_name, language
1001    Traditional English

and I want to map it into our database table
as
internal_id, external_id, type_id, lang_id
9999          1001        1         2

where type, language etc are code/ref tables

What would be the best way to massage the data in external tables and map it to
the real tables?

Looking forward to your relpy,
Followup:
do you have a table of type_name to type_id somewhere that has all possible
values?  same with language to lang_id?
Rating: 4
mapping of external id's to internal id's January 27, 2003
Reviewer: saj from New york, USA
Thanks for your prompt response...

Yes I have two table's content_type, and language which have id's and name as
the column. ( a typical reference table)
Followup:
and how do you map their keys to your keys.
Rating: 4
Mapping January 28, 2003
Reviewer: saj
Are you driving at something obvious?

My question is what is the best way to do this? A design issue?

--Insert into another staging table, resolve all id's and merge?
--cursor thru the xternal table and insert/update the real table?
-- or is there any other elegant solution?
Followup:
No, i'm asking "do you have a table that maps all of their keys to yours"

if not, you will be writing procedural row by row code (unless you have 9i, in
which case we may be able to merge)

if yes, we can do a simple insert with select's of select's to convert the
strings to codes.
Rating: 4
Excellent thank you January 28, 2003
Reviewer: Patrick from usa
Bing-O, great script
Rating: 4
Mapping January 28, 2003
Reviewer: saj
Thanks again for your prompt followup. I'm sorry! I was totally thrown off guard
with your reply "and how do you map their keys to your keys."

I had mentioned in the previous queries that I am on 9iv2
and also I have tables to map the external data to our internal data.

We have a table:content_type with
type_id  type_name
1        Tradtional
2        Contemporary

and table:language
lang_id  language_name
2        English
3        Spanish

and this is what I want to design

example the external table/file comes in as

ext_id, type_name, language
1001    Traditional English

and I want to map it into our database table eg: tbl_content
as
internal_id, external_id, type_id, lang_id
9999          1001        1         2

to map external_id to internal_id, I use the sequence object if it does not
exist in tbl_content, if it exists just update the row where external_id = 1001

I hope this is enough information..

looking forward to a very elegent solution....
Followup:
Ok,
ops$tkyte@ORA920> create or replace directory data_dir as '/tmp/'
  2  /

Directory created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table external_table
  2  (ext_id number,
  3   type_name varchar2(20),
  4   language varchar2(30)
  5  )
  6  ORGANIZATION EXTERNAL
  7  ( type oracle_loader
  8    default directory data_dir
  9    access parameters
 10    ( fields terminated by ',' )
 11    location ('test.dat')
 12  )
 13  /

Table created.

ops$tkyte@ORA920> !echo 1001,traditional,english > /tmp/test.dat

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from external_table;

    EXT_ID TYPE_NAME            LANGUAGE
---------- -------------------- ------------------------------
      1001 traditional          english

ops$tkyte@ORA920> create sequence s;

Sequence created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table content_type ( type_id number, type_name
varchar2(20) );

Table created.

ops$tkyte@ORA920> insert into content_type values ( 1, 'traditional' );

1 row created.

ops$tkyte@ORA920> insert into content_type values ( 2, 'contemporary' );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table language( lang_id number, language varchar2(20)
);

Table created.

ops$tkyte@ORA920> insert into language values ( 2, 'english' );

1 row created.

ops$tkyte@ORA920> insert into language values ( 3, 'spanish' );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create table tbl_content
  2  ( internal_id number, external_id number, type_id number, lang_id number );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> merge into tbl_content
  2  using EXTERNAL_TABLE
  3  on ( external_table.ext_id = tbl_content.external_id )
  4  when matched then
  5          update set type_id = (select type_id from content_type where
type_name = external_table.type_name),
  6                     lang_id = (select lang_id from language where language =
external_table.language)
  7  when not matched then
  8          insert ( internal_id, external_id, type_id, lang_id )
  9          values ( s.nextval, external_table.ext_id,
 10                   (select type_id from content_type where type_name =
external_table.type_name),
 11                   (select lang_id from language where language =
external_table.language))
 12  /

1 row merged.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from tbl_content;

INTERNAL_ID EXTERNAL_ID    TYPE_ID    LANG_ID
----------- ----------- ---------- ----------
          1        1001          1          2

ops$tkyte@ORA920>
ops$tkyte@ORA920> !echo 1001,contemporary,english > /tmp/test.dat

ops$tkyte@ORA920> !echo 1002,traditional,spanish >> /tmp/test.dat

ops$tkyte@ORA920>
ops$tkyte@ORA920> merge into tbl_content
  2  using EXTERNAL_TABLE
  3  on ( external_table.ext_id = tbl_content.external_id )
  4  when matched then
  5          update set type_id = (select type_id from content_type where
type_name = external_table.type_name),
  6                     lang_id = (select lang_id from language where language =
external_table.language)
  7  when not matched then
  8          insert ( internal_id, external_id, type_id, lang_id )
  9          values ( s.nextval, external_table.ext_id,
 10                   (select type_id from content_type where type_name =
external_table.type_name),
 11                   (select lang_id from language where language =
external_table.language))
 12  /

2 rows merged.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from tbl_content;

INTERNAL_ID EXTERNAL_ID    TYPE_ID    LANG_ID
----------- ----------- ---------- ----------
          1        1001          2          2
          3        1002          1          3

ops$tkyte@ORA920>

is one way....
Rating: 5
A million Thanks! January 28, 2003
Reviewer: saj
You are the very best!
Rating: 5
column names May 23, 2003
Reviewer: A reader
Tom

so if I have 100 column names in table T, then will I have to hard code all the
100 column names to the load_data routine?

Is there an easier way we can modify this routine, to read all the column names
of a table.

Thx
Followup:
why hard code them?  just loop over user_tab_columns and build it dynamically if
you like.

  for x in ( select column_name from user_tab_columns where table_name = 'x' )
  loop
     l_cnames := l_cnames || ',' || x.column_name;
  end loop;
  l_cnames := ltrim(l_cnames,',');
Rating: 5
just got to your site for the fisrt time...!!!great!!!!!!! June 26, 2003
Reviewer: EKENE KINGS from Nigeria
Hi Tom,
I'm more than impressed with the questions raised on several topics and more
imortantly your cmoprehensive and urgent response to them...
regards ekene...
Rating: 3
I get an error when using the exception November 12, 2003
Reviewer: Srinivas Pothuganti from Jefferson City, MO USA
I am trying to create a procedure to send an email, the message of which is to
be read from an ascii file. I am using utl_file and utl_smtp packages for this.
I am stuck at a point where I am trying to get all the lines from the ascii
file. It gives me a ORA -1403(no data found) if I don't use an exception but
when I use the exception the same way you used it in your procedure above, I get
a different error.
Here is my pl/sql and the errors.
SQL> Create or Replace Procedure Notify_utlfile(subject_line varchar2) As
  2    win_filetype utl_file.file_type;
  3    message varchar2(1000);
  4    con utl_smtp.connection;
  5    the_host varchar2(30);
  6    ip_addr varchar2(30);
  7
  8   PROCEDURE send_header(name IN VARCHAR2, header IN VARCHAR2) AS
  9    BEGIN
 10      utl_smtp.write_data(con, name || ': ' || header || utl_tcp.CRLF);
 11    END;
 12
 13  BEGIN
 14    ip_addr := utl_inaddr.get_host_address;
 15    the_host := utl_inaddr.get_host_name(ip_addr);
 16    con := utl_smtp.open_connection('ghlngwy1');
 17    utl_smtp.helo(con, 'ghlngwy1');
 18    utl_smtp.mail(con, 'raos1@mail.modot.state.mo.us');
 19    utl_smtp.rcpt(con, 'raos1@mail.modot.state.mo.us');
 20    utl_smtp.open_data(con);
 21    send_header('From',    the_host|| '<raos1@mail.modot.state.mo.us>');
 22    send_header('To',      '"DBMS" <raos1@mail.modot.state.mo.us>');
 23    send_header('Subject', subject_line);
 24    win_filetype := utl_file.fopen('/tmp/orafiles','HQT2_raos1_exp.log','r');
 25  loop
 26    utl_file.get_line(win_filetype,message);
 27    EXCEPTION
 28              when no_data_found then
 29              exit;
 30    utl_smtp.write_data(con, utl_tcp.CRLF || message);
 31  end loop;
 32    utl_file.fclose(win_filetype);
 33    utl_smtp.close_data(con);
 34    utl_smtp.quit(con);
 35  EXCEPTION
 36    WHEN utl_file.read_error THEN
 37     BEGIN
 38     utl_file.fclose(win_filetype);
 39     END;
 40    WHEN utl_smtp.transient_error OR utl_smtp.permanent_error THEN
 41      BEGIN
 42        utl_smtp.quit(con);
 43      EXCEPTION
 44        WHEN utl_smtp.transient_error OR utl_smtp.permanent_error THEN
 45          NULL; -- When the SMTP server is down or unavailable, we don't have
 46                -- a connection to the server. The quit call will raise an
 47                -- exception that we can ignore.
 48      END;
 49      raise_application_error(-20000,
 50        'Failed to send mail due to the following error: ' || sqlerrm);
 51  --  WHEN OTHERS THEN
 52  --   utl_file.fclose(win_filetype);
 53  END;
 54  /

Warning: Procedure created with compilation errors.

SQL> sho err
Errors for PROCEDURE NOTIFY_UTLFILE:

LINE/COL ERROR
-------- -----------------------------------------------------------------
27/3     PLS-00103: Encountered the symbol "EXCEPTION" when expecting one
         of the following:
         begin case declare end exit for goto if loop mod null pragma
         raise return select update while with <an identifier>
         <a double-quoted delimited-identifier> <a bind variable> <<
         close current delete fetch lock insert open rollback
         savepoint set sql execute commit forall merge
         <a single-quoted SQL string> pipe

32/3     PLS-00103: Encountered the symbol "UTL_FILE" when expecting one
         of the following:

LINE/COL ERROR
-------- -----------------------------------------------------------------
         end not pragma final instantiable order overriding static
         member constructor map

32/32    PLS-00103: Encountered the symbol ";" when expecting one of the
         following:
         . ( , * % & - + / at mod rem <an identifier>
         <a double-quoted delimited-identifier> <an exponent (**)> as
         from into || bulk
         The symbol ". was inserted before ";" to continue.

33/27    PLS-00103: Encountered the symbol ";" when expecting one of the

LINE/COL ERROR
-------- -----------------------------------------------------------------
         following:
         . ( , * % & - + / at mod rem <an identifier>
         <a double-quoted delimited-identifier> <an exponent (**)> as
         from into || bulk
         The symbol ". was inserted before ";" to continue.

34/21    PLS-00103: Encountered the symbol ";" when expecting one of the
         following:
         . ( , * % & - + / at mod rem <an identifier>
         <a double-quoted delimited-identifier> <an exponent (**)> as
         from into || bulk

LINE/COL ERROR
-------- -----------------------------------------------------------------
Followup:
 25  loop
     BEGIN
 26    utl_file.get_line(win_filetype,message);
 27    EXCEPTION
 28              when no_data_found then
 29              exit;

     end;
Rating: 4
Got it right this time November 12, 2003
Reviewer: Srinivas Pothuganti from Jefferson City, MO USA
Tom,
There were some syntactical errors in my procedure. I corrected them and the
procedure works like a charm now.

Thanks,
Srinivas
Rating: 4
another add up January 03, 2004
Reviewer: Natasha from BH
Tom,

wishing u a happy new year....

I am having a serious problem and getting confused whether to use SQL loader or
UTL_FILE utility.

Before putting my question...pls. clear me.
1) If I use UTL_FILE utility then UTL_FILE_DIR parameter has to be changed. Can
it point a directory on Win-Nt or Win-XP instead of Unix OS.

2) Pls. do let us know how to call SQL-LOADER from JAVa sTORED pROCEDURE.(better
if u give with an example).

My question:
------------
My CSV file looks like this...(assume alphabits enclosed are the data)

START
1
Table1,A,B,C,D,E,F,G,,,,,,,,,,H~
Table2,I,J,,K,L,M,,N,O,P~
       R,M,T,M,D,M,F,M,H,M,J,,U,I,U~
       K,Q,K,J,D,K,A,L,S,J,F,K,S,A,D,J,F~
Table3 W,Q,E,O,R,U,W,E,K,L,W,E,R,K,J,W,E,Q,R,I,O,W,E,Q,R~
       L,E,3,4,3,4,3,4,P,L,3,4,K,0,X,C,K,L,K,3,0,9,4~

2
Table1,A,B,C,D,E,F,G,,,,,,,,,,H~
Table2,I,J,,K,L,M,,N,O,P~
       R,M,T,M,D,M,F,M,H,M,J,,U,I,U~
       K,Q,K,J,D,K,A,L,S,J,F,K,S,A,D,J,F~
Table3 W,Q,E,O,R,U,W,E,K,L,W,E,R,K,J,W,E,Q,R,I,O,W,E,Q,R~
       L,E,3,4,3,4,3,4,P,L,3,4,K,0,X,C,K,L,K,3,0,9,4~

3
Table1,A,B,C,D,E,F,G,,,,,,,,,,H~
Table2,I,J,,K,L,M,,N,O,P~
       R,M,T,M,D,M,F,M,H,M,J,,U,I,U~
       K,Q,K,J,D,K,A,L,S,J,F,K,S,A,D,J,F~
Table3 W,Q,E,O,R,U,W,E,K,L,W,E,R,K,J,W,E,Q,R,I,O,W,E,Q,R~
       L,E,3,4,3,4,3,4,P,L,3,4,K,0,X,C,K,L,K,3,0,9,4~
END

Now...How can I import the data to Oracle Database by using SQL-loader. Even how
can I achieve this task by UTL_FILE utility.

Thxs in advance
Natasha
Followup:
1) utl_file_dir only needs be set in 9iR1 and before. in 9ir2 you should use
create directory (no bounce needed) instead.

yes, the mapped drive can be from win-nt/xp AS LONG AS YOU CAN SEE THE MOUNTED
DIRECTORY IN UNIX of course.  So, if you samba mount or whatever the remote
drive onto a unix mount point, and can "ls" it -- you can utl_file it.

2) that is a simple "host" command.  you do not "call" sqlldr anymore then you
would "call" uptime.  it is a command.

In 9i, you would use external tables (search for that on this site, lots of
examples).  then you can load a file with "create table t as select * from
flat_file" or "insert into t select * from flat_file"

In a java store procedure, you would just be using normal old java to run a host
command.  see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:952229840241
Rating: 3
which way is faster, Big cursor loop or join tables? January 06, 2004
Reviewer: AH from NJ, USA
Tom:

My application loads 8M records to a temp table (T), then open a cursor loop to
compare the imcoming rows against the existing 200M rows partitioned table (P)
to determine I/D/U or ignore the new data. I saw my database performs some
phisical reads and tones of logical reads during this big batch job.

Is this cursor loop efficient? Read key values from input data, retrieves all
rows from T (1 to thousands rows even using indexes), then compare values to
I/D/U or discard.

My concern is the logical reads. Oracle will read the same blocks to retrieve
row(s) for another key on the block. If I join T and P using three sql to
perform I/D/U separately. will this approach be fater than the cursor loop?

Will MERGE be the fasest solution? What happen if I have a "big" where clause?

Thanks.
Followup:
its all about merge -- if not merge, three single MASSIVE statements.

slow by slow processing (i keep doing that, meant row by row) is not a "good
thing"
Rating: 4
Try again January 08, 2004
Reviewer: AH from NJ, USA
Tom:

Assume MERGE cannot be used for a reason. Between cursor loops and join tables,
which one provides better performance? Process 8M to 20M rows daily against 200M
partitioned table.

create table t1 (col1, col2, col3, ..., col20);
create table t2 (f1, f2, f3, ... f15);
create index I1 on t1(col1, col2, col3);
create index I2 on t2(f1, f2, f3);
t2 changes everyday and the goal is to use information in t2 to I/D/U t1 daily.

Opt1: cursor loop
      First cursor loop select all rows from t2;
      Second cursor loops select all rows from t1 where col1 = f1 and col2 = f2
and col3 = f3.
      If fields in t1 matches rule U, then update.
      If fields in t1 matches rule I, then insert.
      end of cursor 2; commit;
      end of cursor 1; commit;

Opt2: join
     update t1.col4, t1.col5, ...
     where col1 = t2.f1 and col2 = t2.f2 and col3 = t2.f3 and col4 = 'X' and
col5 != 'Y';
     commit;
     insert into t1 (...)
     select t2.*
     where col1 = t2.f1 and col2 = t2.f2 and col3 = t2.f3 and col4 = 'A' and
col5 = 'B';
     commit;

Opt1 will use index to fetch data from t1. Am I correct that Oracle will perform
a loop in Opt 2 (Join) if the plain shown index is used? Should I use Oracle's
loop or use my own loop? Is there any performance difference? Which one will be
faster consider the number of rows to be processed?

Thanks
Followup:
SET based processing is ALWAYS my first choice

slow by slow processing is as bad as it sounds (gotta stop doing that.  meant
"row by row")
Rating: 4
A bulk version of the load_data function from start of thread January 21, 2004
Reviewer: Matt from Australia
At the start of the thread you use a function to load CSV data from a flat file
capturing all the errors raised along the way. This appears to be row by row.
Can the rows be bulk loaded and still catch and log each of the errors?

I'm looking at loading 5 or so million CSV records and validating them. SQLLDR
loads the data for me but does not allow me to easily classify the BAD records
in terms of the errors raised.

I was thinking of using an external table and bulk collecting the data using
your load_data function into a table with constraints/triggers and then logging
any errors, the failed data record, and line number from the original CSV file.
I could then have a further process to pull out the data that had errors and
classify them by error into seperate files (for easy resolution).

One problem I have is when a record fails it will fail for one reason, when this
error is fixed there may be additional errors hiding behind it. I want to catch
ALL the error so that from my resulting error files output I am able to solve
ALL data problems and obtain a clean next run.

I have looked at SQLLDR and it does 90% of what I need (and is efficient and
tunable). I really need something that handles BAD records and their errors
better though (for ease of resolution).

Can you briefly comment on the suggested approach? Do you expect it to be more
efficient than say SQLLDR and a routine to parse the BAD and LOG files?

Best Regards.
Followup:
You can use plsql collections/table types and FORALL bulk processing

search for

"save exceptions"

on this site for examples.
Rating: 4
DBMS_SQL & Bulk/Array & SAVE EXCEPTIONS February 18, 2004
Reviewer: Erick from Maryland City, MD USA
Thanks for your previous help concerning SDO.

A related question: I need to load high volumes of data 24/7 and allow user
queries. The data is parsed and loaded from a high volume of flat files. The
data is being inserted into a table which contains a rolling 72 hours of data
and is range partitioned based on 4 hour intervals. As a new partition is
created every 4 hours, the oldest partition is truncated.
I had wanted to use array processing using static SQL to bulk load the data for
the best performance. Well, the truncating of the table partition, requires the
recompilation of the load PL/SQL code since they are dependent on eachother. So
I am left with stopping the loading of data, performing the truncate partition,
resume data loading OR breaking the dependancy between the load PL/SQL code and
the table being loaded. I then utilized 'EXECUTE IMMEDIATE', 'SAVE EXCEPTIONS'
and your trick of using stored procedure global variables to bulk insert and
collect any errors that occurred.
My concern is since the table to be loaded does not change, should I not be
using DBMS_SQL to create the INSERT statement once (1 hard parse) and execute it
a zillion times? If so, then how can I use 'SAVE EXCEPTIONS' with it (or
something like it) so as to efficiently bulk load data and collect errors as
they occur? I have been unable to locate any examples of using DBMS_SQL with
array processing and SAVE EXCEPTIONS.

Thanks.
Followup:
version?
Rating: 4
flat file on network drive March 09, 2004
Reviewer: Chuy
Tom

we have process where in a flat file is loaded at the end of every day into he
'Positions' table.

Currently, the file is loaded from the local host of one of the users.

Obviously, we have text_io doing the upload for us. Now with external tables
coming into picture, we want to use external tables to accomplish this task.

Now the question is when we have create an external table and the flat file is
on a network drive is it possible to access the file for the external table.

1. Can you give me an example of creating a directory referring one of the
folders on your network drive. We are on Windows 2000.

2.While using external tables, can we load bad rows into an exceptions table ?

Thanks in Advance
Followup:
1)
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:241814624807
2) yes, you'll just map that bad file as an external table itself with one
column (varchar2(4000) ).  then you can insert into bad_table select * from
bad_table_external;
Rating: 5
March 10, 2004
Reviewer: A reader
Tom

Does your toutine take care of duplicate rows. That is when you loading your
flat file rows into the table and you encounter duplicate rows( we can either
update or we can discard them).. how can we tackle that issue..

Please explain..
Followup:
as long as you have constraints on these table identifying what duplicate means,
they will be written into the "bad" table (the dups)
Rating: 2
March 11, 2004
Reviewer: A reader
Writing to the bad table is fine, but I want to update my table with the values,
some of the columns might have to be updated for duplicate rows ?
Followup:
You have the code, you can make it do whatever you want?

If you have 9i, it sounds like you want to use an external table and MERGE
(search for

"external table" merge

for examples.
March 11, 2004 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.
Rating: 5
March 11, 2004
Reviewer: A reader
I contacted support and finally got an answer and an update of the oracle files
that were required to be updated. Unfortunatly the response time was not what I
was looking for. Some countries change there DST quite often.

We now have decided to subscribe to recieving a Java class that is updated
regularly and thus the java programmers will there worst.

what i would like to do is keep the java in Oracle as there will be alot of
database activity in the process.  How do I use an external class source with a
oracle java source (9i) ?

thanks
Followup:
you do not, you'd have to load that in.
March 11, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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.
Rating: 4
January 17, 2004
Reviewer: huss
Dear Tom,
in this example
  1  declare
  2  type emp is record (name varchar2(50),id number(3));
  3  type emp_Tab is table of emp index by binary_integer;
  4  emps_list1 emp_tab;
  5  emps_list2 emp_tab;
  begin
  ...
  ...
  ...
-- I can do that
   emps_list1:= emps_list2 ;
-- but it gives me error when try to do that
  if ( emps_list1 = emps_list2) then ....
so , while that not work , how can i compare between the 2 tables of recored
without loop inside them and compare it's fields one by one .
Followup:
well, 10g will let you compare two collections -- but until then -- it is 100%
"diy" (do it yourself)

you would have to procedurally do it element by element.
Rating: 4
what is index by binary integer February 02, 2004
Reviewer: A reader
Hi

What is the difference between

type l_array is table of varchar2(256);

type l_array is table of varchar2(256) index by binary integer;

which is faster? When should index by binary integer used?
Followup:
one is a collection, the other a plsql index by table.

declare
   type collection_type is table of varchar2(256);
   type plsql_table_type is table of carchar2(256) index by binary_integer;

   l_coll   collection_type;
   l_tab    plsql_table_type;
begin

One (collection) needs to be "extended" to allocate space, the other does not.

   l_coll.extend;
   l_coll(1) := 'foo';

   l_tab(1) := 'bar';

One (collection) can be initialized easily, the other -- not:

   l_coll := collection_type( 'hello', 'world', 'foo', 'bar' );

   l_tab(1) := 'hello';
   l_tab(2) := 'world';
   l_tab(3) := 'foo';
   l_tab(4) := 'bar';

Those are the "major" differences -- I find plsql table types generally "easier"
to use since they need not be extended and "contigous" (eg: in order to have
l_coll(100) - I must have 1..99 allocated.  In order to have l_tab(100) i only
need that entry)
Rating: 5
Why is this bulk collect behaving like this in Oracle 9i? February 12, 2004
Reviewer: Raj from India
Dear Tom,
Sorry for posting this here instead of waiting for you to
be ready for taking further questions but can you tell me why the first code
executed without errors but second doesnot?

SQL*Plus: Release 9.0.1.0.1 - Production on Thu Feb 12 14:59:07 2004

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

Enter user-name: scott@testsystem
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> declare
  2  type emparray is table of emp%rowtype index by binary_integer;
  3  cursor c1 is select * from emp;
  4  rowarray emparray;
  5  begin
  6  open c1;
  7  fetch c1 bulk collect into rowarray limit 100;
  8  close c1;
  9  end;
 10  /

PL/SQL procedure successfully completed.

SQL>
SQL> ed
Wrote file afiedt.buf

  1  declare
  2  cursor c1 is select empno,ename from emp;
  3  type emparray is table of c1%rowtype index by binary_integer;
  4  rowarray emparray;
  5  begin
  6  open c1;
  7  fetch c1 bulk collect into rowarray limit 100;
  8  close c1;
  9* end;
SQL> /
fetch c1 bulk collect into rowarray limit 100;
                           *
ERROR at line 7:
ORA-06550: line 7, column 28:
PLS-00597: expression 'ROWARRAY' in the INTO list is of wrong type
ORA-06550: line 7, column 1:
PL/SQL: SQL Statement ignored

SQL>
Followup:
it was an issue that is fixed.

ops$tkyte@ORA9IR2> declare
  2  type emparray is table of emp%rowtype index by binary_integer;
  3  cursor c1 is select * from emp;
  4  rowarray emparray;
  5  begin
  6  open c1;
  7  fetch c1 bulk collect into rowarray limit 100;
  8  close c1;
  9  end;
 10  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2  cursor c1 is select empno,ename from emp;
  3  type emparray is table of c1%rowtype index by binary_integer;
  4  rowarray emparray;
  5  begin
  6  open c1;
  7  fetch c1 bulk collect into rowarray limit 100;
  8  close c1;
  9  end;
 10  /

PL/SQL procedure successfully completed.

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
Rating: 5
March 10, 2004
Reviewer: A reader
Tom,

For index-by-table i have some doubts with the difference regarding index by
binary_integer / index by varchar2(size_limit)

I referred the docs which mention
<doc>
When you reference an element of an associative array that uses a VARCHAR2-based
key, you can use other types, such as DATE or TIMESTAMP, as long as they can be
converted to VARCHAR2 with the TO_CHAR function.
</doc>

I am still not clear as to why one would use index by varchar2.. can you please
give me a example.

Thanks.
Followup:
in other languages it is sometimes called an associative array.

array( 'VA' ) returns 'Virginia'
array( 'NY' ) returns 'New York'

and so on -- it is a lookup table.
Rating: 3
OK March 11, 2004
Reviewer: M.Srinivas from Hyderabad,AP,India
Dear Tom,
I would like to pass an array into a procedure and do some
processing inside the procedure and return the processed
output also as an array.
For example:
 sql> create type nums_va as varying array(5) of number;
 and
 pass this array into a procedure like
 sql>create procedure p(array in out nocopy nums_va) as
 ...
Is it possible to have a structure like this?Could you please help?
Please do reply.
Bye!
Followup:
sure, that works?   what problem are you having exactly....
March 11, 2004 -- Thanks for the question regarding "hash partitioning", version 8.1.7
You Asked
Tom

Frankly, speaking I dont see any particular extraordinary  thing about hash
partitioning..

A good dba should be able to speard about his objects onto different
disks to balance IO contentions..

It hash partitioning does not make any logical sense to the sql queries i.e. I
cannot do partition elimination... then why should I use hash partitioning..

Please explain in what circumstances hash partitioning becomes most
appropriate..

Tom

We are using the partitioning concept for rolling over the data to historical
tables.. right now there are no primary keys or indexes on
the history tables to which the data is being rolled over, or for tables from
whom data is being rolled over..

But I have learnt from my lead that very soon we will be adding indexes and
primary key to the tables from whom the data is being rolled over..

What impact it is going to make on the process of rolling the data over to the
history table..

I have a request..

Can you come up with a small workbook for all the common problems faced by
developers...

NAG
and we said...
Well, partitioning brings about many things for you.  forget about partition
elimination for a moment:

- instead of having a 100 gig tablespace to backup, you have 100, 1 gig
tablespaces. (each tablespace spends less time in backup mode, reduces the
amount of potential extra redo, reduces the amount of manual recovery you need
to do if the instance failes during backup).  same with restores.

- instead of having an index on a 100 gig table, you have say 100 indexes on 1
gig tables.  You can rebuild each one fast.... (and online with minimal
overhead).  rebuilding a single index on a 100 gig table -- that would take
maybe 100 times longer then each individual rebuild (true parallel query might
reduce that).

- you can analyze each partition quickly, instead of running an analyze on a 100
gig table.

- you can reorg each partition independent of any other partition

- you can easily redistribute the load over many disks, you now have evenly
distributed the data into 100 1 gig partitions, move them at will.  If you did
this manually -- you would have a heck of a time moving the stuff around.

- and so on (every single admin option you do to a partition applies to a hash
partition).

- as a bonus, you no longer have to load this data into this file in direct path
mode, that data into that file and so on.  Just let the software do that work
for you.  True, us humans can do lots of stuff -- but -- when the software can
do it for me....  maybe even better then I can... its worth a shot.

Now, lets re-introduce partition elimination.  Say you are an OLTP system.  You
would pick a PARTITION key that is used in most all queries (say a CUSTOMER_ID,
an ORDER_ID, something).  It is very hard to range partition on that (almost
impossible on something like an ORDER_ID) but very trivial to hash partition.

Now, all queries of the form:

  select * from t where customer_id = :x;

will use partition elimitation.  You've just re-introduced all of the
availability features of partitions as well (in addition to never having lost
the admin features).

Say you are a data warehouse and need to do a bulk update/delete.  Well, you can
do that in parallel now since the data is partitioned, if it weren't, you could
not do PDML.

Say you do a join on the hash partition key -- we can do parallel partition wise
elimination on the join....

and so on....

Also, what about when you use range partitioning but the underlying partitions
are still too large -- enter hash partitioning to re-introduce the divide and
conquer features of partitions.

In short:

Hash partitioning -- all of admin features of range partitions and many of the
partition elimination/query features as well.

Use it on data you might have used in a hash cluster.  A census table with data
full of data keyed by SSN.  We either FULL SCAN it or go in by SSN.  Perfect for
a hash partitioned data....

As for the second part -- you NEED TO ENSURE RIGHT NOW that any unique key is
entirely in the partition key.  Otherwise, the only way to enforce uniqueness is
via a GLOBAL INDEX (only if the partition key is in the local index can it be
unique).  A global index will kill your rolling window as it will go totally
invalid every time you add/split/drop a partition.

A small workbook -- actually I can:

o use bind variables, if you do not you will fail.

thats the smallest workbook.  the "real" on is:
http://www.amazon.com/exec/obidos/ASIN/1861004826
but its 1300 pages long.
Review & Followup

Rating: 4
hash cluster v/s has partition August 25, 2002
Reviewer: Yogesh Purabiya from India
Going through your excellent book, I got enough idea of hash-clustered tables.
It is now easy to understand why we have to specify SIZE and HASH-Keys before
hand; and how they are used to     pre-allocate the space needed.

But, I got confused when I reached hash-clustered Partitions. There we do not
specify SIZE and HASH-Keys. Then, how does Oracle pre-allocate space? Or, if it
is not pre-allocated, then why the same facility is not available with clusters?
Followup:
We are not pre-allocating space in hash partitions.

With a harsh partition, we are attempting to achieve an EVEN distribution of
data across all of the partitions while at the same time supporting partition
elimination and other features.

So, we take the key and "hash it" to figure out -- does this row belong in
partition 1, 2, 3, 4, ... N.  Then, we put it there (or get it from there).

So, here, hashing just allows us to evening distribute the data.

A HASH cluster is very very different in nature and goal.
Rating: 2
Hash Cluster v/s Hash Partition August 26, 2002
Reviewer: Yogesh Purabiya from India
Though hash partition does equal distributipn of the rows as per the hash
function, I think that each partition does need to know its size in advance;
otherwise hash partitioning may not be as efficient as hash cluster.
Followup:
You are confusing IDEAS here.

No, you do NOT need to know the size of each partition in a hash partition --
not any more then in a range or list.

You cannot compare in any way shape or form

o hash partitions
o hash clusters

That would be like comparing an apple to a stove.  It just makes ZERO sense.

A hash partition -- method to get data evenly distributed over many partitions.
Used to distribute IO.  Used to allow for partition elimination (partition wise
joins).  Used to ease administration.  Each individual partition is just a
normal old HEAP table.

A hash cluster -- method to force physical organization of individual rows.  A
row goes into the SLOT it hashed to.  Does not distribute IO.  Does not do
partition elimination.  Does not reduce adminstration.  The TABLE is not a HEAP
but a data structure.

They are totally and wholly orthogonal concepts, nothing whatsoever to do with
eachother at all.
Rating: 2
Got clarity from your book August 28, 2002
Reviewer: Yogesh Purabiya from India
I once again went through your book, the chapter on partitioning. Yes, now I
understand the matter. But, that is "Range Partition" where from I could get the
clarity. In fact, that time I just tried to compare Normal Index to Range
partition, and somehow I got the clarity.

Thanks, Tom, for your reply.

Well, one more thing. While giving THIS review, I could see none of my
questions.

Thanks again.

<Yogesh>
Rating: 5
hash partitioning and disk striping July 28, 2003
Reviewer: Peter Tran from Houston, TX USA
Tom,

1)  If you stripe all disks using Raid 0+1, are there any performance benefits
for using hash partition other than the administrative benefits you outlined
above?

For ex.

create table t (
  x number(38))
  partition by HASH(X) (
    partition p1 tablespace ts_dat,
    partition p2 tablespace ts_dat,
    partition p3 tablespace ts_dat,
    partition p4 tablespace ts_dat
  )
/

alter table t
  add (
  constraint t_pk
  primary key (x)
  using index local
  (PARTITION p1_idx tablespace peter,
   PARTITION p2_idx tablespace peter,
   PARTITION P3_idx tablespace peter,
   PARTITION p4_idx tablespace peter)
)
/

Or is the performance better with no partition?

e.g.

create table t2 (
  x number(38))
  tablespace peter
/

alter table t2
  add (
  constraint t2_pk
  primary key (x)
  using index
  tablespace peter
  )
/

2)  From a performance perspective, are there any benefits from partitioning the
data but NOT create the partition on the index?

E.g.

create table t3 (
  x number(38))
  partition by HASH(X) (
    partition p1 tablespace ts_dat,
    partition p2 tablespace ts_dat,
    partition p3 tablespace ts_dat,
    partition p4 tablespace ts_dat
  )
/

alter table t3
  add (
  constraint t3_pk
  primary key (x)
  using index
  tablespace peter
  )
/

If yes, why?

Your help is greatly appreciated.

-Peter

BTW, I have both of your Wrox books and they're great.  I'm looking forward to
your new one - currently on order.
Followup:
1) yes, there are partition wise joins, and there are partition eliminations
that can take place AND there is higher availability possible.

2) there can be -- you can even partition the index in a different MANNER then
the underlying table itself.

there can be performance DETRIMENTS by improperly partitioning an index -- that
implies there can be performance BENEFITS by not partitioning it at all.

The next book is entirely different from "expert one on one Oracle" -- that was
a fairly large reference, this next book is not "expert one on one Oracle
version 2".  Check out my asktom home page and you can see the first three
chapters to get a feel of what it is like.
Rating: 5
Thought of one more question... July 28, 2003
Reviewer: Peter Tran from Houston, TX USA
Can hash partitioning ever be bad or abused?

If you have a table containing unique IDs (e.g. SSN), then using hash
partitioning sounds like the way to go.  Is there any reason not to use it for a
table with SSN# as the PK?

Thanks,
-Peter
Followup:
of course it can be bad and abused.

everything can.

that SSN table -- suppose you DON"T actually access it by SSN -- then
partitioning by SSN would be a bad idea.
Rating: 5
Good point on the SSN question July 28, 2003
Reviewer: Peter Tran from Houston, TX USA
However, if I don't access by SSN, then any type of index on SSN (partition or
not) will be a bad idea.

I'm re-reading chap. 14 (slower) to make sure I grasp all of the concept.

I missed the blurb about "Since global indexes may be partition by range only,
you must use local indexes if you wish to have has or composite partitioned
index."

I spent several futile minutes trying to force Oracle to create a global hash
index.

-Peter
Rating: 3
Partition key & Index key September 29, 2003
Reviewer: David
Tom,

Does the hash partition key have to be the part of index key? For e.g. if I have
a table hash partitioned by deptno and have created partitioned indexes on the
partitioned table, is it necessary to have deptno the part of my index key?

Thanks always!
Followup:
nope.

the hash key (which should be almost unique -- in order to hash nicely) is
idependent of other indexes.
Rating: 4
September 29, 2003
Reviewer: A reader
Tom, thanks for the prompt answer!

"which should be almost unique" -- Do you mean the hash key should be unique in
that partition? Do you mean every partition should have only 1 key value for
better performance?
Followup:
I mean, you want to pick a column that has MANY different values to hash on

Say you have 8 hash partitions
Say you pick a column with 10 different values (like deptno in emp).
Very good chance you have some empty partitions, some very very full partitions
and some skimpy ones.

Now, take same data, hash on empno and you'll find a nice even distribution.

ops$tkyte@ORA920> drop table test1;

Table dropped.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table test1
  2  (
  3    empno  number,
  4    deptno number
  5  )
  6  partition by hash(empno) partitions 8
  7  /

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> drop table test2;

Table dropped.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table test2
  2  (
  3    empno  number,
  4    deptno number
  5  )
  6  partition by hash(deptno) partitions 8
  7  /

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into test1 select object_id, mod(rownum,10) from
all_objects;

31641 rows created.

ops$tkyte@ORA920> insert into test2 select object_id, mod(rownum,10) from
all_objects;

31641 rows created.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          l_cnt number;
  3  begin
  4          for x in ( select partition_name from user_tab_partitions where
table_name = 'TEST1' )
  5          loop
  6                  execute immediate 'select count(*) from test1
partition('||x.partition_name||')'
  7                  into l_cnt;
  8                  dbms_output.put_line( x.partition_name || ' ' || l_cnt );
  9          end loop;
 10  end;
 11  /
SYS_P564 3958
SYS_P565 4047
SYS_P566 3941
SYS_P567 3877
SYS_P568 3901
SYS_P569 4073
SYS_P570 3924
SYS_P563 3920

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> declare
  2          l_cnt number;
  3  begin
  4          for x in ( select partition_name from user_tab_partitions where
table_name = 'TEST2' )
  5          loop
  6                  execute immediate 'select count(*) from test2
partition('||x.partition_name||')'
  7                  into l_cnt;
  8                  dbms_output.put_line( x.partition_name || ' ' || l_cnt );
  9          end loop;
 10  end;
 11  /
SYS_P572 0
SYS_P573 3164
SYS_P574 6328
SYS_P575 0
SYS_P576 6328
SYS_P577 6328
SYS_P578 6329
SYS_P571 3164

PL/SQL procedure successfully completed.
Rating: 4
One more question? September 29, 2003
Reviewer: A reader from pittsbugh,PA
What is the difference between a hash partitioned table with 5 tablespaces(say
with around 20 datafiles) and a normal table with one tablespace that consists
of all those 20 datafiles?
Which is a good option?
Followup:
night and day -- they are as different as you get.

which is "good" -- depends, depends on your needs, what you are trying to do,
your system, your design, everything.
Rating: 4
October 03, 2003
Reviewer: A reader
Tom, is it possible to know, on a hash partition, which partition contains which
keys? Assume a table is hash partitioned by dept, into 6 partitions. Can we find
what dept values are stored in individual 6 partitions?

Thanks in advance!
Followup:
  1* select distinct object_id from big_table_hashed partition(sys_p61)
big_table@ORA920> /

 OBJECT_ID
----------
        55
        74
        81
        97
       111
       146
       245
       252
       262
       281
       294
       297
       306
       310
       325
       326
       338
       341
       373
       400
       401
       425
       455
       458
.....

just use a partition query on the partitions and you can see what is in each.
Rating: 4
how to know in advance October 28, 2003
Reviewer: subbu from MN
how can i say a particular dept_id will go into a particular partition. is there
a way to do this. This will help me in allocating space in advance knowing the
dept_no in advance.
Followup:
hashing an emp table by deptno would be a "bad idea"

hashing is best done on a unique or at least a column with TONS of values

hashing is designed to spread data out evenly.

there should be NO difference in size.

if you thing "i'll have 8gig of data and 8 hash partitions", then it should be
about 1gig/partition.

So, if you follow that advice, you need not know "what partition" it'll go into.
 they will be the SAME

If you need that fine degree of control, you will use LIST partitioning in 9i
and direct specific values to specific partitions
Rating: 3
more details October 29, 2003
Reviewer: subbu from MN,USA
Tom,
I'm having a table where there are going to be millions of rows.
There is a particular id that i'm generating every time i do a dataload into
that table.
Say out of 10 million rows of data, the id which i'm generating is going to be
100 assuming i'm doing only 100 loads.
Is it worth hash partitioning based on those 100 values?

Is so(even if not) then i want to know how to find out how the data will go to
different partition based on this value. What i've seen is all rows having same
id used to go to same partion. how to predict which partition will be used
assuming i've 8 partitions.
Thanks
Subbu
Followup:
not really.  100 values won't spread the data out nicely/evenly.

ops$tkyte@ORA920LAP> create table t ( x int )
  2  partition by hash(x) partitions 8
  3  /

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> insert into t select rownum from all_objects where rownum
<= 100;

100 rows created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> declare
  2      l_cnt    number;
  3  begin
  4      for x in ( select partition_name from user_tab_partitions where
table_name = 'T' )
  5      loop
  6          execute immediate
  7          'select count(*) from t partition('||x.partition_name||')'
  8          into l_cnt;
  9
 10          dbms_output.put_line( to_char(l_cnt,'999') || ' ' ||
x.partition_name );
 11      end loop;
 12  end;
 13  /
  11 SYS_P313
  15 SYS_P314
  13 SYS_P315
  10 SYS_P316
   9 SYS_P317
  13 SYS_P318
  12 SYS_P319
  17 SYS_P320

PL/SQL procedure successfully completed.

see sys_p330 -- with 17% of the data, sys_p317 with 9% of the data.  one of the
partitions would have 2x the data!

you want an ALMOST unique or unique value to hash on -- i keep saying that

with hashing -- all rows with the same key by definition hash to the same
partition -- that is the very essence of hash partitioning.
Rating: 5
Number of partitions November 04, 2003
Reviewer: Hector from UK
Tom

What factors should you take into account when deciding how many partitions to
have in a hash partitioned table.

We will be creating a warehouse table that has over 100 Million rows where the
partition key will not be unique (but very very close).

This table will be joined to many smaller tables based on the same key.  These
smaller tables would typically contain around 100k rows.  Would you recommend
partitioning these as well.

thanks
Followup:
well, 100 million rows isn't that big.

but one would need to know how you use this data.  In a DW, full scans and hash
joins might rule.  That you join based on that key might be "bad", it might be
"good"

you'd have to look at your queries and ask yourself

"will hash partitioning by this key

a) decrease query performance
b) increase it
c) not affect it at all"

for all 3 are possible.

suppose you have this 100m row table T partitioned by t.pk and a lookup L

you query:

select *
  from t, l
 where t.fk = l.pk
   and l.column = 'some value'

that T is partitioned by T.PK isn't going to help anything.  In fact, if you
locally partition the indexes on T and index T(fk) and the optimizer used that
index - you could end up doing N times the LIO's (where N = number of hash
partitions) in a nested loops join.  that is because the T.FK values could be in
ANY or ALL of the N hash partitions of the index.

You have to understand the physics behind partitioning -- what it is doing, what
you want to accomplish.  I've written 2 chapters on this topic -- on in "Expert
One on One Oracle" and another newer one in "Effective Oracle By Design".  You
might be interested in that.  But you want to prototype, understand, benchmark
to see what will work best for you (understand how partitioning works,
understand the implications of partitioning)
Rating: 5
partition elimination November 04, 2003
Reviewer: A reader
Hi

Was reading Jonathan Lewis book a couple of days back. It stated that partition
elimination does not occur in hash partitions... But from your words it seems
that it is possible. Hmmmmm

From optimizer's view how does hash partitioning help to speed up queries? Is
hash partitioning suitable in DWH or OLTP systems? It seems to me in DWH (unless
ubpartitioned) it is useless. But I dont see much use in OLTP neither (let's
forget the administration tasks but focusing on optimizer's view).

Recently I have been in a customer site, they are running a in-house OLTP
application using hash partitions and local hash partitioned index. One of the
huge tables (around 90 million of rows) is partitioned on a date column,
according to customer they partitioned that column because it has quite many
distinct values (so far so good, nice for hash partitions) but also they have
many queries which does range scan such as

x_date between initial_date and end_date

This doest not seem right to me. It seems like they are thinking hash partitions
as they were range partitions? Do you agree? I dont think hash partitioning
would improve this kind of queries since most of the time Oracle would have to
scan all partitions anyway.

Also I have doubts about how Oracle uses indexes to access hash partitions.
Imagine we have local indexes on these hash partitions, if say I want to query
rows between first of June and end of June will Oracle needs to scan all index
partitions? I.E how are the row values distributed? Does Oracle treat all
partitions as a single data structure and scan as the index were B-Tree or it
treats them as four data structures (4 B-Tree)? How does Oracle know which index
partition it has to scan to obtain appropriate values?
Followup:
where did he state THAT?  page references please.

I think you are either taking something out of context or maybe mis-read it.

in OLTP hash partitioning must be carefully used so as to not DAMAGE
performance.  For read queries in that environment, it is HIGHLY UNLIKELY that
it will increase performance.  for modifications -- it may well increase
SCALABILITY (not necessarily single user performance, but multi-user
performance) since you have N table segments and M index segments (reduced
contention) instead of 1.

in DW, hash partitioning can dramatically increase the performance (reduce the
runtime) of full scans via partition elimination (rare with hash partitions,
more common with range or list) or partition wise joins.

If they have a global range partitioned index on X_DATE, that is dandy.  If not,
they have likely increased the IO on their system by a factor of N if the index
on X_DATE is locally partitioned.

If you have access to my new book "Effective Oracle By Design", I actually use
an example almost IDENTICAL to what you describe -- and show you how to show it
is "wrong" and ultimately how to "fix it"

Yes, we MUST range scan all index partitions if you have a locally partitioned
index on a hash partitioned table and use something other then "="
Rating: 5
the table is hash partitioned November 05, 2003
Reviewer: A reader
Hi
Your com ments:

"If they have a global range partitioned index on X_DATE, that is dandy.  If
not, they have likely increased the IO on their system by a factor of N if the
index on X_DATE is locally partitioned."

What does dandy mean? Good?
The table is hash partitioned on X_DATE, I dont think we can create a global
range partitioned index on X_DATE right? In fact they created a local hash
partitioned index. How about creating a plain index, would that have same effect
as global range partitioned index?

"If you have access to my new book "Effective Oracle By Design", I actually use
an example almost IDENTICAL to what you describe -- and show you how to show it
is "wrong" and ultimately how to "fix it"    "

The example you are talking about is similar my customer´s case ? By the way I
ordered the book 3 weeks ago, waiting for it to arrive.

"Yes, we MUST range scan all index partitions if you have a locally partitioned
index on a hash partitioned table and use something other then "="  "

My common sense tells me Oracle has to range scan all partitions if we are not
using "=" operator. My scientific sense however doesnt see how. A small example

I hash partition on a dummy column in 4 partitions, numbered sequentially, if I
query

select *
from test
where dummy between 1 and 4

How does Oracle find the corresponding rows? Does it hash dummy (1, 2, 3,4) then
find the index value thus rowid and then access the table partitions?

Or it scans 4 indexes since dummy is randomly distributed in index partitions?
Followup:
dandy means "good".

you can only create a global range partitioned index on X_DATE if you create a
global index on X_DATE.  Else, it is locally partitioned with the table.

A "plain" index IS a global index with one partition.

The example in the book is virtually identical to the issue you describe.

"where dummy between 1 and 4" -- dummy has an INFINITE set of values!!  1, 1.1,
1.11, 1.111, 1.1111 and so on.

it scans everything.
Rating: 4
why local index increase I/O? November 06, 2003
Reviewer: A reader
Hi

Why the I/O can be increased factor of N on a hash partitioned table if the
index on is locally partitioned?

Imagine we have 8 hash table partitions and 4 hash index partitions (local of
course since there is no such hash partitioned global index concept).
Partitioned by a date column, order_date if we query

order_date = to_date('20031010', 'YYYYMMDD')

we would eliminate unnecesary partitions

if we query

order_date between to_date('20031010', 'YYYYMMDD')
and to_date('20031110', 'YYYYMMDD')

we would scan all 4 index partitions, but where is the factor of N?

Finally which index would you use, a plain index or a range partitioned index on
order_date?
Followup:
you would not be able to have

8 table partitions
4 hash index partitions (local of course)

local of course implies parity -- 8 to 8.  you cannot currently HASH partition
an index differently then the underlying table.

so, the assumptions are wrong -- you cannot have structures as you describe.
Rating: 5
is there a point if.. November 06, 2003
Reviewer: reader from PA
the table being hash partitioned only contains a pk and a LOB?

the LOB is just a pointer, so excluding managability issues, would a query that
looks like select lob from table pk_col=:x have any benefit from partition
pruning on the pk?

I suspect that a traditional B-Tree for this same row might be faster or
negligible with regards to the difference.
I am sure this is why you say that for a query of a transactional nature
partition pruning with hash partitions will provide no benefit.
Followup:
well, the index on the pk would either be:

a) global by pk
b) local by hash(pk)

so, partition pruning would happen regardless.

and yes, it benefits for that.  say the table was (pk,x,lob) and you hashed by
(x) into 8 hash partitions.  you locally partitioned the index on pk (not
possible but assume you did).  then you

select * from t where pk = :x

you have to range scan 8 index partitions.

now, assume it is a global partition on pk (single or multiple-partitions)  --
then select * from t where pk = :x will range scan 1 partition.
Rating: 5
One quick question December 03, 2003
Reviewer: Subbu from MN, USA
Tom, Recently we partitioned few tables by range on column id. After that the
data loading got slower becase the paritioned key is not used in the query which
involves the processing of data load.
I'm thinking of atleast having a partitioned index on the column which they are
using in the processing of data load.
I found that i can't do global hash partition but if i do local then will it
help as the partition is still going to be based on the key i used in table
partition.
Thanks
Followup:
not following you.  you go from "global hash partition" (of something) to "do
local" (of something) and ask will that help?

I don't know, not understanding what you are thinking really -- examples are
worth a trillion words.  (small concise ones that demonstrate what you are
thinking)
Rating: 4
clarifying with eg. December 03, 2003
Reviewer: subbu from MN, USA
CREATE TABLE part.P_UNIT
(
  UV_ID         NUMBER(9)        NOT NULL,
  P_ID               NUMBER(9)             NOT NULL,
  C_ID  NUMBER(9)             NOT NULL,
  U_ID                 NUMBER(9)             NOT NULL
  )
TABLESPACE PART_DEF2
PARTITION BY RANGE (P_ID)
(
partition    p1     values less than    (100)    ,
partition    p2    values less than    (200)    ,
partition    p3    values less than    (300)    ,
partition    p4    values less than    (400)    )
/

create index part.part_test_ind on part.P_UNIT(u_id)
tablespace PART_DEF2
global
/

The above script created the range partitioned table and my index on u_id is
global.

so queries like

select * from part.P_UNIT
where p_id=90;

will run faster.

Will partitioning the index help with queries like

select * from part.P_UNIT where
u_id < 100 ;

Note that i partitioned the table based on p_id

If i create local index will that help? for the above query.

Can i create global hash partitioned index on u_id (i'm on version 8.1.7.4)
which i know is not possible.
So what best can be done in this case where i've created partitioned table and
got 2 queries based on 2 different ids but the partitioning is on only one of
them.

How to make both queries to run efficiently.
Followup:
well, lets look at the statements:

so queries like select * from part.P_UNIT where p_id=90; will run faster.

that is true because we'll FULL SCAN a single partition instead of a FULL SCAN
of a full table.  partition elimination can help that FULL SCAN go faster.

Will partitioning the index help with queries like select * from part.P_UNIT
where
u_id < 100 ; Note that i partitioned the table based on p_id

if you globally partition the index on u_id, you could POSSIBLY descrease the
size of it (the height) and that would lend *some* to the performance.  You
would see the MOST beneift in concurrent inserts/updates/deletes as you'll now
have N index structures and you'll have spread the points of contention for this
modifications out over N structures.

In show, partitioning (globally) the index by u_id probably won't affect queries
(negatively or positively) and may have some affect on modifications (positive
affects)

Partitioning (locally) the index will kill query performance as each index range
scan will really be 4 range scans (as any of the index partitions could have
u_id < 100)

You can only create range partitioned global indexes.

That -- and hashing PRECLUDES partition elimination with <, >!!!  you would
totally defeat the purpose there!

You have already setup structures that allow the object to be queried
efficiently by either of those queries.
Rating: 5
excellent December 04, 2003
Reviewer: kuljeet from india
excellent
Rating: 5
hash partioning key ---> composite January 08, 2004
Reviewer: A reader
Hi

Is it a good idea using a composite key as partiotning key (HASH)?
Followup:
if it lends more "uniqueness" to the hash key and you use all the columns in the
predicate -- sure.
Rating: 4
Index Rebuild is Achilles Heel of Partitioning March 10, 2004
Reviewer: Robert from Memphis, USA.
Tom,

Partitioning is advertised as improving administration by breaking the table up
into manageable chunks.

In our OLTP environment we have a 74G/277M row table.
I plan on partitioning this table into 16, 32, or 64 hash partitions (based on
non-unique account_id) which will improve managing the table...
...but this table has 11 indexes.
Any time we do anything to a partition that would invalidate the indexes we will
have to rebuild at least 10 of the indexes globally which could take days.
This is a bitter-sweet situation for implementing partitioning.

Could you comment on this, please?

Thanks for all your help!

Robert.
Followup:
or do the partition operation with "maintain global indexes" perhaps?

partition operations in 9i can maintain those indexes.

(and if it takes days for you to rebuild 10 indexes on such a small amount of
data, there is something really wrong....)

and, since you are hashing, just what partition level operations are you
planning on doing exactly?
Rating: 5
Partition Level Operations March 10, 2004
Reviewer: Robert from Memphis, USA.
Tom,

Small amount of data!? ... I thought this was a lot in an OLTP environement...
:) ...but anyway...
We have a requirement to archive old data (e.g. 18 months) off of this table
periodically. (insert into archive table, then delete from live table). I
figured that after archiving data in this manner a few times, we would need to
reorg the live table.

What do you think?

Thanks,

Robert.

PS. Will definitely look into 'maintain global indexes'.
Followup:
you would just drop/truncate the oldest partition and if you do this with the
option to maintain the indexes, it'll not impose any sort of rebuild.
Rating: 4
But I'm not partitioning by date March 10, 2004
Reviewer: A reader
Tom,

I am partitioning on 'account_id' but I will be archiving by some date field....
so my deletes will be honeycombed throughout all partitions.
Could you please address this scenario?

Thanks,

Robert.
Followup:
then why are you partitioning by account_id?  (and in your current "scheme", you
would not be doing any partition operations really, you would not need to).

sounds like you want to partition by date (RANGE, not hash)
have global indexes that are range partitioned themselves
and when you want to purge, just drop a partition but maintain the global
indexes.

List why you want to partition (what you most want to gain from it) specifically
-- not "better admin" -- WHAT part of admin exactly do you want to make "better"
Rating: 5
What to be gained by partitioning March 10, 2004
Reviewer: A reader
Tom,

1) We cannot partition by our date field... 70% of the queries against this
table are OLTP direct hits on the account_id... therefore for performance we
*must* partition by this account_id field... to partition by our archiving date
field would cause these online OLTP queries to run poorly... would it not? (I do
plan on benchmarking this, however, in order to prove it).
2) Why do I want to partition? .... To be able better manage the reorging that
must eventually occur after much archiving/deleting from this table.

Is this not sound?

Thanks,

Robert.
Followup:
1) why -- you take the index on account_id and RANGE partition that.

you'll have one index range scan followed by lots of table access by global
index rowids.  no different then now.

2) you won't really have to reorg after a purge -- space is reused in objects.
If you want to make the purge efficient, you would partition to purge.
Rating: 5
Thanks for your help March 11, 2004
Reviewer: Robert from Memphis, USA.
Tom,

You've convinced me to try partitioning by archiving date... however I don't
think we can RANGE partition the account_id index because the distribution is
not even.

1) Would HASH partitioning (or even NOT partitioning) the account_id index work
as well?

2) I plan to test and benchmark whatever I do on a subset of the table which is
approx 10% the size of the real one. Do you think this 10% test table could give
reasonable results?

Thanks,

Robert.
Followup:
1) not until 10g as you cannot hash partition a global index until then.

You can range partition it into a SINGLE partition.  No reason not to.  You want
to optimize the purge, you only need a single index partition.

2) not really, partially.  extrapolation is "really hard".  You'd want to do
perhaps 5, 10, 15% to see how it scales as you add data (does it curve, or is it
a straight line)
March 11, 2004 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)
Rating: 4
Date difference in seconds January 30, 2004
Reviewer: Tony from India
Hi tom,

 Two date columns have date and time stored.
 How to find the date difference in seconds?
 I want day, month, minute and years difference everyting in seconds.
Followup:
select (dt1-dt2) * 24 * 60 * 60 from t;

dt1-dt2 gives diff in days (eg: 1.2323 days)

multiply by 24 = hours, another 60 = minutes, another 60 = seconds
Rating: 4
Negative Seconds March 09, 2004
Reviewer: A reader
Hi, TOm,

In your link:
http://asktom.oracle.com/~tkyte/Misc/DateDiff.html
Some of the results in SECs are negative, is it normal?

THanks,
Followup:
do you have an example?  (you do need to put the "bigger" date in first -- you
can use greatest(a,b) where I used sysdate and least(a,b) where I used created)
Rating: 4
Here is the example March 11, 2004
Reviewer: A reader
Hi,

Did I do something wrong?
 select to_char( created, 'dd-mon-yyyy hh24:mi:ss' ),
       trunc( sysdate-created ) "Dy",
       trunc( mod( (sysdate-created)*24, 24 ) )  "Hr",
       trunc( mod( (sysdate-created)*24*60, 60 ) )  "Mi",
 trunc( mod( to_char(sysdate,'SSSSS')-to_char(created,'SSSSS'), 60 ) ) "Sec",
       to_char( sysdate, 'dd-mon-yyyy hh24:mi:ss' ),
       sysdate-created "Tdy",
       (sysdate-created)*24 "Thr",
       (sysdate-created)*24*60 "Tmi",
       (sysdate-created)*24*60*60 "Tsec"
from all_users
where rownum < 50

And the result:
TO_CHAR(CREATED,'DD-         Dy         Hr         Mi        Sec
TO_CHAR(SYSDATE,'DD-
-------------------- ---------- ---------- ---------- ----------
--------------------
10-jun-2002 13:48:17        639         19          0        -55 11-mar-2004
08:48:22
10-jun-2002 13:48:18        639         19          0        -56 11-mar-2004
08:48:22
10-jun-2002 13:48:38        639         18         59        -16 11-mar-2004
08:48:22
01-may-2003 09:28:45        314         23         19        -23 11-mar-2004
08:48:22
11-jun-2002 08:59:24        638         23         48         -2 11-mar-2004
08:48:22
03-apr-2003 13:59:51        342         18         48        -29 11-mar-2004
08:48:22
10-jun-2002 15:10:09        639         17         38        -47 11-mar-2004
08:48:22
10-jun-2002 15:10:11        639         17         38        -49 11-mar-2004
08:48:22
10-jun-2002 15:10:11        639         17         38        -49 11-mar-2004
08:48:22
10-jun-2002 15:17:16        639         17         31        -54 11-mar-2004
08:48:22
10-jun-2002 16:14:21        639         16         34        -59 11-mar-2004
08:48:22
10-jun-2002 16:14:21        639         16         34        -59 11-mar-2004
08:48:22
10-jun-2002 16:14:21        639         16         34        -59 11-mar-2004
08:48:22
10-jun-2002 16:14:21        639         16         34        -59 11-mar-2004
08:48:22
10-jun-2002 16:14:21        639         16         34        -59 11-mar-2004
08:48:22
10-jun-2002 16:14:21        639         16         34        -59 11-mar-2004
08:48:22
10-jun-2002 16:14:24        639         16         33         -2 11-mar-2004
08:48:22
10-jun-2002 16:14:24        639         16         33         -2 11-mar-2004
08:48:22
10-jun-2002 16:14:25        639         16         33         -3 11-mar-2004
08:48:22
10-jun-2002 16:14:25        639         16         33         -3 11-mar-2004
08:48:22
11-jun-2002 08:59:24        638         23         48         -2 11-mar-2004
08:48:22
11-jun-2002 08:59:24        638         23         48         -2 11-mar-2004
08:48:22
11-jun-2002 08:59:24        638         23         48         -2 11-mar-2004
08:48:22
11-jun-2002 08:59:24        638         23         48         -2 11-mar-2004
08:48:22
11-jun-2002 08:59:24        638         23         48         -2 11-mar-2004
08:48:22
11-jun-2002 08:59:24        638         23         48         -2 11-mar-2004
08:48:22
11-jun-2002 08:59:24        638         23         48         -2 11-mar-2004
08:48:22
11-jun-2002 08:59:24        638         23         48         -2 11-mar-2004
08:48:22
11-jun-2002 08:59:24        638         23         48         -2 11-mar-2004
08:48:22
11-jun-2002 08:59:24        638         23         48         -2 11-mar-2004
08:48:22
11-jun-2002 08:59:24        638         23         48         -2 11-mar-2004
08:48:22
11-jun-2002 08:59:24        638         23         48         -2 11-mar-2004
08:48:22
11-jun-2002 08:59:24        638         23         48         -2 11-mar-2004
08:48:22
11-jun-2002 08:59:24        638         23         48         -2 11-mar-2004
08:48:22
11-jun-2002 08:59:24        638         23         48         -2 11-mar-2004
08:48:22
11-jun-2002 08:59:24        638         23         48         -2 11-mar-2004
08:48:22
11-jun-2002 08:59:24        638         23         48         -2 11-mar-2004
08:48:22
11-jun-2002 08:59:24        638         23         48         -2 11-mar-2004
08:48:22
11-jun-2002 08:59:24        638         23         48         -2 11-mar-2004
08:48:22
11-jun-2002 08:59:24        638         23         48         -2 11-mar-2004
08:48:22
11-jun-2002 08:59:24        638         23         48         -2 11-mar-2004
08:48:22
11-jun-2002 08:59:25        638         23         48         -3 11-mar-2004
08:48:22
11-jun-2002 08:59:25        638         23         48         -3 11-mar-2004
08:48:22
11-jun-2002 08:59:25        638         23         48         -3 11-mar-2004
08:48:22
11-jun-2002 08:59:25        638         23         48         -3 11-mar-2004
08:48:22
11-jun-2002 08:59:25        638         23         48         -3 11-mar-2004
08:48:22
11-jun-2002 08:59:25        638         23         48         -3 11-mar-2004
08:48:22
11-jun-2002 08:59:25        638         23         48         -3 11-mar-2004
08:48:22
11-jun-2002 08:59:25        638         23         48         -3 11-mar-2004
08:48:22
Followup:
looks like its the math

trunc( mod( to_char(sysdate,'SSSSS')-to_char(created,'SSSSS'), 60 ) )

sssss is seconds past midnight.  that'll be negative if sysdate is "early" in
the day

ops$tkyte@ORA9IR2> select to_char( d1, 'dd-mon-yyyy hh24:mi:ss' ),
  2         to_char( d2, 'dd-mon-yyyy hh24:mi:ss' ),
  3         trunc( d1-d2 ) "Dy",
  4         trunc( mod( (d1-d2)*24, 24 ) )  "Hr",
  5         trunc( mod( (d1-d2)*24*60, 60 ) )  "Mi",
  6   trunc( mod( to_char(d1,'SSSSS')-to_char(d2,'SSSSS'), 60 ) ) "Sec"
  7  from (select sysdate d1, sysdate-1+5/24/60/60 d2 from dual )
  8  /

TO_CHAR(D1,'DD-MON-Y TO_CHAR(D2,'DD-MON-Y    Dy      Hr         Mi        Sec
-------------------- -------------------- ----- ------- ---------- ----------
11-mar-2004 12:43:38 10-mar-2004 12:43:43     0      23         59         -5

quick fix, haven't tested all boundary conditions:

ops$tkyte@ORA9IR2> select d1s, d2s,
to_char(d2+"Hr"/24+"Mi"/24/60+"Sec"/24/60/60,'dd-mon-yyyy hh24:mi:ss') new,
  2         "Dy", "Hr", "Mi", "Sec"
  3    from (
  4  select to_char( d1, 'dd-mon-yyyy hh24:mi:ss' ) d1s,
  5         to_char( d2, 'dd-mon-yyyy hh24:mi:ss' ) d2s,
  6             d2,
  7         trunc( d1-d2 ) "Dy",
  8         trunc( mod( (d1-d2)*24, 24 ) )  "Hr",
  9         trunc( mod( (d1-d2)*24*60, 60 ) )  "Mi",
 10         trunc( mod( (d1-d2)*24*60*60, 60 ) ) "Sec"
 11  from (select sysdate d1, sysdate-1+5/24/60/60 d2 from dual )
 12  )
 13  /

D1S                  D2S                  NEW                          Dy
 Hr         Mi        Sec
-------------------- -------------------- -------------------- ----------
---------- ---------- ----------
11-mar-2004 12:48:27 10-mar-2004 12:48:32 11-mar-2004 12:48:27          0
 23         59         55

another would be to use decode( sign("Sec"), -1, 60+"Sec", "Sec" )

(can't believe no one has noticed that before!)
Rating: 3
Once again to_char(date) rules! March 12, 2004
Reviewer: andrew from ca, usa
SQL> column the_diff format a30
SQL> select -- method 1
  2    TO_CHAR (TRUNC (SYSDATE-CREATED), '000') || ' days ' ||
  3    TO_CHAR (TRUNC (SYSDATE) + MOD (SYSDATE-CREATED, 1),
  4          'HH24 "hrs" MI "min" SS "sec"') the_diff,
  5    -- method 2
  6    trunc(sysdate-created ) "Dy",
  7    trunc(mod((sysdate-created)*24, 24 ))  "Hr",
  8    trunc(mod((sysdate-created)*24*60, 60 ))  "Mi",
  9    trunc(mod(to_char(sysdate,'SSSSS')-to_char(created,'SSSSS'), 60)) "Sec"
 10  from all_users
 11  where rownum < 5;

THE_DIFF                               Dy         Hr         Mi        Sec
------------------------------ ---------- ---------- ---------- ----------
 967 days 21 hrs 37 min 16 sec        967         21         37        -44
 967 days 21 hrs 37 min 16 sec        967         21         37        -44
 967 days 21 hrs 36 min 57 sec        967         21         36         -3
 029 days 18 hrs 17 min 15 sec         29         18         17        -45
March 11, 2004 Alla -- Thanks for the question regarding "Executing AFTER row trigger on failed insert", version 8.1.6
You Asked
I always believed that AFTER triggers are being executed after
integrity constraints are checked. For example, if I am
inserting
a row and FK is violated (parent key is not found), then the
statement will fail and "after row" trigger won't be fired.

I just upgraded to 8.1.6. and my application started behaving
weird - the after row triggers are being fired even if the
statement fails (Constraints are immediate and not deferred and
I do get ORA-02291)

Is it something new in 8.1.6? How do I deal with it? How do I
stop these triggers from firing.

You can say, so what if they are firing, the transcation will
be rolled back anyway. This is true, but as a part of the
trigger
I am calling an external procedure to do someting and it's
definitely not reversable, even if the statement rolls back.

Please, help. It's a very urgent problem for me

Thank you very much for giving this your attention.
and we said...
It doesn't have to work like this (the way you describe above)
--the EARLIEST a referential constraint might be able to be
verified would be right after the "before for each row" and
before the "after for each row" but there are many times when it
cannot be verfied at that time.

The documentation actually says:

<quote concepts manual, v7.3, section in "the mechanisms of
constraint checking">

• A multiple row INSERT statement, such as an INSERT statement
with nested SELECT statement, can insert rows that reference
one another. For example, the first row might have EMPNO as
200 and MGR as 300, while the second row might have EMPNO
as 300 and MGR as 200.

Each case reveals something about how and when Oracle performs
constraint checking.

The first case is easy to understand; a null is given for the
foreign key value. Because nulls are allowed in foreign keys,
this row is inserted successfully into the table.

The second case is more interesting. This case reveals when
Oracle effectively performs its constraint checking: after the
statement has been completely executed. To allow a row to be
entered with the same values in the parent key and the foreign
key, Oracle must first execute the statement (that is, insert
the new row) and then check to see if any row in the table has
an EMPNO that corresponds to the new row’s
MGR.
</quote>

the relevant part of this is:

This case reveals when Oracle effectively performs its
constraint checking: after the statement has been completely
executed.

I agree that in earlier releases, it would *sometimes* not fire
the after, for each row trigger but that was never gauranteed or
promised (quite the contrary actually).  Here is an example
showing it could fire the AFTER for each row trigger before
checking constraints in earlier releases:

tkyte@ORA734.WORLD> create table c ( a int primary key,
                    x references c(a) );

Table created.

tkyte@ORA734.WORLD> create or replace trigger c_bi
                    before insert on c
  2  begin
  3          dbms_output.put_line( 'Before insert' );
  4  end;
  5  /
Trigger created.

tkyte@ORA734.WORLD> create or replace trigger c_bifer
                   before insert on c for each row
  2  begin
  3          dbms_output.put_line( 'Before insert FER '
             || :new.a   || ' ' || :new.x );
  4  end;
  5  /
Trigger created.

tkyte@ORA734.WORLD> create or replace trigger c_aifer
                    after insert on c for each row
  2  begin
  3          dbms_output.put_line( 'Before after FER ' ||
                     :new.a || ' ' || :new.x );
  4  end;
  5  /
Trigger created.

tkyte@ORA734.WORLD> create or replace trigger c_ai
                    after insert on c
  2  begin
  3          dbms_output.put_line( 'After insert ' );
  4  end;
  5  /
Trigger created.

tkyte@ORA734.WORLD> set serveroutput on
tkyte@ORA734.WORLD> insert into c
  2  select 1, 2 from dual
  3  union all
  4  select 2, 3 from dual
  5  /
Before insert
Before insert FER 1 2
Before after FER 1 2
Before insert FER 2 3
Before after FER 2 3
insert into c
*
ERROR at line 1:
ORA-02291: integrity constraint (TKYTE.SYS_C00382) violated -
parent key not found

See - it fired the AFTER FOR EACH ROW trigger 2 times before
checking the consraint -- and that was release 7.3.

Back to the root problem -- your extproc.  That is not
transactional (the maintaining of the external data source).
You should be aware that just because something makes it past
the AFTER FOR EACH ROW trigger does not mean it'll be in the
table!!!!  Regardless of trigger firing order/RI or anything.

What about:

insert into T select * from really_big_table;

and you run out of space in the middle?  or the server crashes?
or the end user rolls back?  half of the rows will make it out,
half will not.  the half that made it out will get rolled back
and won't exist -- you have a mess.

Here is what I would do in your case.

1) my after, for each row trigger would queue a job in dbms_job
to be executed right after I commit and the changes are
permanent.

2) this job it queues would simply be a call to the procedure
its calling in real time right now.

3) set up the job queues to check for jobs to run every 15
seconds or so.

that way -- its transactional -- if I commit - the extproc gets
called. If I get rolled back (for WHATEVER reason and there are
dozens that could affect you -- regardless of RI or not), then
the jobs themselves will get removed from the queue.  The rows
will be propagated in the background --probably providing a
better experience for the end user.

how does that sound?  I use dbms_job for lots of stuff to make
it transactional (like sending emails and such).  I queue the
request to perform the operation -- I do not perform the
operation I cannot rollback.  Very safe, very efficient, much
more robust.
Review & Followup

Rating: 4
How can I pass status back from a trigger code?? March 11, 2004
Reviewer: Ravi Kumar from Chicago, IL
Hi Tom,

We have an application in which the user selects a text file from his local
client machine from the client application and makes a procedure call to the
Oracle Database for each line of the file. The procedure inserts those values to
a staging table. There is a "after insert each row" trigger on the staging table
where some validations are done and updates/inserts someother main tables. If
the validations fails, then trigger code raises an exception. Based on the
exception, the procedure retuns the status to the client.

The reason for coding the trigger on the staging table is, the end user wants to
see the progress of the file read and processing of the data.

Now, when the validations fail and the exception is raised, the orginal insert
to the staging table is also not finished. How to handle this situation, if we
want to make the insert into staging table happen and return the fail validation
status back to the procedure and hence to the client? Or any other better
solution to this situation.

Thanks in advance,
Ravi
Followup:
I don't see why this is a trigger.  why is this a trigger?  for the end user to
see progress doesn't need a trigger here.

sounds like procedure wants to

a) insert row REGARDLESS
b) do the code you currently have in trigger to figure out what message to
return to client.

the trigger would only be used to "protect" the table from getting bad data
inserted.
March 11, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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.
Rating: 4
Duplicate exception February 03, 2004
Reviewer: Hien Tran from Reston, VA USA
Using multitable insert to load a row from a flat table to up to 30 rows of a
single normalized table, is there a way to ignore duplicate new rows exceptions.
For instance, 2 rows out of 30 are duplicate to the other 28, is there a way to
multitable insert 28 uniques rows and gracefully ignore the 2 duplicates ? I can
go back to 30 individual inserts but it would be very nice .... Thanks in
advance.
Followup:
in the select statement, use SQL to remove the duplicates -- eg: distinct, group
by, row_number() are techniques to remove the duplicates.
Rating: 5
Multitable insert is slower than individual insert March 09, 2004
Reviewer: Arun Panchal from New York, NY
Tom,

I thought multitable insert will be faster than insert into individual tables.
But to my surprise I found other way. Any idea why ?

CREATE TABLE M1 (M1  NUMBER);
CREATE TABLE M2 (M2  NUMBER);

declare
type a_type is table of number INDEX BY BINARY_INTEGER;
a_arr a_type;

samay number;
begin
for i in 1..10000 loop
     a_arr(i) := i;
end loop;

dbms_output.put_line('Record Count : ' || A_ARR.COUNT);
samay := dbms_utility.get_time;

forall i in 1..a_arr.count
insert
  when 1 = 1 then
  into m1 values (a_arr(i))
  when 2 = 2 then
  into m2 values (a_arr(i))
  select 1 from dual;

dbms_output.put_line('Multitable Inserts : ' || (dbms_utility.get_time-samay));
rollback;

samay := dbms_utility.get_time;
forall i in 1..a_arr.count
        insert into m1(m1) values (a_arr(i));

forall i in 1..a_arr.count
        insert into m2(m2) values (a_arr(i));

dbms_output.put_line('Individual Inserts :  ' || (dbms_utility.get_time-samay));

rollback;

exception
  when others then
  dbms_output.put_line( SQLCODE || '||' || SQLERRM );
end;
/

And I got following DBMS output

Record Count : 10000
Multitable Inserts : 233
Individual Inserts :  53

Thanks,
Arun
Followup:
you added 10,000 "select * from duals" in there that did not exist in the single
table inserts.  10,000 select * from duals add up.

this is not really a valid test of this particular feature.  It is designed to
take the rows from a big query and split them out into 2 or more tables.  it is
not really intended to be used as you did above -- you make it do MORE work this
way by adding the dual query in there 10,000 times.
Rating: 5
Then how to insert data into multiple table at same time ? March 11, 2004
Reviewer: Arun Panchal from New York, NY
Agreed. Then what is the best method to insert data in more than one table in
single statement?

We have single source and we need to insert data into two diff tables(some
fields are loaded in table1 and some are loaded in table2). We are using bulk
binding on Oracle 9.0.2.

Can we insert data in two tables using single forall statement ? Or we need to
write individual forall statement for each table?

Thanks,
Arun
Followup:
you did it.  two forall loops.
March 11, 2004 Jagadish -- Thanks for the question regarding "Rows Estimate without executing the query which has joins on many tables?", version 8.1.6.0.0 Release 2
You Asked
I heard that some of applications on SQLserver shows an estimated or correct
rows without executing the query. I am not sure how that was getting collected
in SQL server. I wonder is there any kind of such mechanism exists in Oracle to
find out the same row estimates without executing the query. I did watch the
execution plan it only provides cost or cardinality. But cardinality is not
correct when you run a query with a joins of many tables.

Source:- One of SQL server Application I saw that provides such kind of metrics
when mouse moves to an estimate kind of button. That returns a quick row
estimates and without executing the query. If we press the button almost all the
same estimated rows data is being displayed through the application. So the
investigation started how to achieve this in Oracle.
and we said...
The explain plan gives this -- as long as you are using the CBO (cost based
optimizer).  Doesn't matter if you have 1 or 50 tables.  The last part of the
plan is the estimate output.

consider:

scott@ORA717DEV.US.ORACLE.COM> set autotrace traceonly explain
scott@ORA717DEV.US.ORACLE.COM> select e1.*, e2.* from emp e1, emp e2;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=15 Card=196 Bytes=12544)
   1    0   MERGE JOIN (CARTESIAN) (Cost=15 Card=196 Bytes=12544)
   2    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=14 Bytes=448)
   3    1     SORT (JOIN) (Cost=14 Card=14 Bytes=448)
   4    3       TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=14 Bytes=448)

196 = 14 * 14 -- thats right....

scott@ORA717DEV.US.ORACLE.COM> select emp.ename, mgr.ename from
  2  emp, emp mgr
  3  where emp.mgr = mgr.empno;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=13 Bytes=208)
   1    0   MERGE JOIN (Cost=5 Card=13 Bytes=208)
   2    1     SORT (JOIN) (Cost=3 Card=14 Bytes=112)
   3    2       TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=14 Bytes=112)
   4    1     SORT (JOIN) (Cost=3 Card=14 Bytes=112)
   5    4       TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=14 Bytes=112)

Here is estimated that about 13 rows would be returned...

scott@ORA717DEV.US.ORACLE.COM> select count(*) from emp;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FULL SCAN) OF 'EMP_PK' (UNIQUE) (Cost=1 Card=14)

one row...

scott@ORA717DEV.US.ORACLE.COM> select ename, dname
  2  from emp, dept 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)

14 rows...

  1  select ename, dname
  2* from emp, dept where emp.deptno = dept.deptno and emp.ename like '%A%'
scott@ORA717DEV.US.ORACLE.COM> /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=18)
   1    0   NESTED LOOPS (Cost=2 Card=1 Bytes=18)
   2    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=1 Bytes=7)
   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'DEPT' (Cost=1 Card=4 Bytes=44)
   4    3       INDEX (RANGE SCAN) OF 'DEPT_IDX' (NON-UNIQUE)

1 rows.....

and this was all done without executing the query.  See EXPLAIN plan and the
PLAN_TABLE.
Review & Followup

Rating: 2
Estimate rows Without query execution November 12, 2001
Reviewer: Jagadish Nalluri from San Diego
I am working with this kind of query ( below ), but I am not getting the correct
Cardinality. The result of cardinality shows 4 and when I execute the same query
I am getting around 264 rows as result set. Why this is happening. You can
tailor the same kind of query on three different tables, Might be the nature of
Query is an issue in this. Anyway I am not getting the same result as
cardinality shows

Query I am using on some of my tables:-
----------------------------------------

SQL> set autotrace traceonly explain;
SQL> select
  2   pi.name_family, ep.code_gender
  3  from eh_person_identity pi,
  4   eh_entity_person ep,
  5   eh_entity_roles er
  6  where pi.name_family = 'SMITH'
  7   and ep.entity_id = pi.entity_id
  8   and ep.code_gender = 'M'
  9   and er.entity_id = pi.entity_id
 10   and er.code_entity_role = 'OFNDR';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=19 Card=4 Bytes=840)
   1    0   NESTED LOOPS (Cost=19 Card=4 Bytes=840)
   2    1     NESTED LOOPS (Cost=11 Card=4 Bytes=496)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'EH_PERSON_IDENTITY'
          (Cost=7 Card=4 Bytes=288)

   4    3         INDEX (RANGE SCAN) OF 'EH_PERSON_IDENT_NAME_FAMILY'
          (NON-UNIQUE) (Cost=3 Card=4)

   5    2       INDEX (UNIQUE SCAN) OF 'PK_EH_ENTITY_ROLES' (UNIQUE) (
          Cost=1 Card=2023 Bytes=105196)

   6    1     TABLE ACCESS (BY INDEX ROWID) OF 'EH_ENTITY_PERSON' (Cos
          t=2 Card=13038 Bytes=1121268)

   7    6       INDEX (UNIQUE SCAN) OF 'PK_EH_ENTITY_PERSON' (UNIQUE)
          (Cost=1 Card=13038)
Followup:
It is called an ESTIMATE, it is not called a promise ;)

It is an ESTIMATE based on the statistics.  If it knew the honest, actual answer
-- it would not even have to run the query.  It is the best guess of the
optimizer based on the data, the statistics.

It is a guess.  It will be RARE to get exactly the right number of rows.
Rating: 3
estimating cardinality fro complex queries November 12, 2001
Reviewer: Mikito Harakiri
I assume that the problem with this query is that if user enters a criteria that
is not selective enough -- for example: pi.name_family like 'S%' then your query
would be spinning for a long time. This is why you want an estimate of the
number of rows returned, right?

I would suggest the following workaround. In your case eh_person_identity is the
driving table, so fire simple query

select count(1) from eh_person_identity
where pi.name_family like <crit>

first. With index on pi.name_family it's guaranteed to be fast. Now, the number
of rows returned is a good approximation of what you primary query must return,
or just make a correction based upon some assumptions about how the result will
be transformed by the joins and additional filters.
Rating: 3
November 12, 2001
Reviewer: Jagadish
That's what exactly my earlier point. Some how Oracle optimizer is unable to
provide at least a near by estimate result when we ran such quieries. But on the
other hand my observation remained on how the things pretty smart with SQL
server database enginge. Might be the same techniques may evolve soon with
Oracle Optimizer too. If we could able to find at least a nearby estimate values
then it could be a great deal of information through the optimizer.

Thanks for the inputs sharing with me. Now I need not search/wait for a long on
this topic. I agreed with your final point(s).
Followup:
You are using a single case.

It can be very accurate, IF it has the proper information.  IF you can look at
the data yourself and figure out how many rows it returns.  Trust me --
SQLServer isn't always accurate on this either.

Lets look at your query again:

SQL> select
  2   pi.name_family, ep.code_gender
  3  from eh_person_identity pi,
  4   eh_entity_person ep,
  5   eh_entity_roles er
  6  where pi.name_family = 'SMITH'
  7   and ep.entity_id = pi.entity_id
  8   and ep.code_gender = 'M'
  9   and er.entity_id = pi.entity_id
 10   and er.code_entity_role = 'OFNDR';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=19 Card=4 Bytes=840)
   1    0   NESTED LOOPS (Cost=19 Card=4 Bytes=840)
   2    1     NESTED LOOPS (Cost=11 Card=4 Bytes=496)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'EH_PERSON_IDENTITY'(Cost=7
Card=4 Bytes=288)
   4    3         INDEX (RANGE SCAN) OF 'EH_PERSON_IDENT_NAME_FAMILY'
(NON-UNIQUE) (Cost=3 Card=4)
   5    2       INDEX (UNIQUE SCAN) OF 'PK_EH_ENTITY_ROLES' (UNIQUE) (Cost=1
Card=2023 Bytes=105196)
   6    1     TABLE ACCESS (BY INDEX ROWID) OF 'EH_ENTITY_PERSON' (Cost=2
Card=13038 Bytes=1121268)
   7    6       INDEX (UNIQUE SCAN) OF 'PK_EH_ENTITY_PERSON' (UNIQUE)(Cost=1
Card=13038)

Here, it goes after EH_PERSON_IDENTITY first by PI.NAME_FAMILY.

It looks at the stats (which you haven't told me HOW they are computed)

It guesses "4 rows", that was wrong, it was closer to 264 rows.  Did you compute
histograms?  Does the optimizer understand the data is skewed?  Did you give it
the information it needs to work with?  Probably not.  You probably just ran
"analyze table EH_PERSON_IDENTITY compute statistics".  Thats not good enough
for what you are doing.

First of all, I PRAY that you do not actually use literals in your code all over
(very bad practice inherited from sqlserver programmers).  Bind variables are
the way to go almost always.

Secondly, if you give it more stats -- it'll do better in this case.  Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t as select object_name from
all_objects;

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select 'SMITH' from t where
rownum <= 196;

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from t;

  COUNT(*)
----------
     17303

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx on t(object_name);

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics;

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain;
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where object_name = 'SMITH';

Execution Plan
----------------------------------------------------------
   0  SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=2 Bytes=48)
   1    0  INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1 Card=2 Bytes=48)

with just "stats", it has no idea about the skew of the columns, lets try
that again an have it compute the histograms

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics for table
for all indexes
  2  for ALL INDEXED COLUMNS size 254;
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where object_name = 'SMITH';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=207 Bytes=4968)
   1    0   INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=207
Bytes=4968)

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where object_name = 'FOO';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=2 Bytes=48)
   1    0   INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1 Card=2 Bytes=48)

So, given the proper amount of information -- it makes the correct assumptions.
Rating: 5
Estimate as pertains to Discoverer November 12, 2001
Reviewer: Connor from Glasgow
Presumably Discoverer uses the same method to present its "This query will take
... seconds to complete".  I was wondering if you have any 'insider' information
on what assumptions its making to transforms "rows" into (predicted) "elapsed
time".

Cheers
Connor
Followup:
Yes, discoverer is using this information in an attempt to predict the runtime
(as does the resource manager feature in 9i which allows you to abort a query
before it even STARTS in the database based on a predicated runtime).

I haven't any "insider" information about the algorithm used to calculate this
value - sorry.
Rating: 3
physical versus logical November 12, 2001
Reviewer: Mikito Harakiri
I wouldn't rely on optimizer to get correct cardinality. You would rely upon
some physical implementation features, that could change (hopefully for the
better;-) between releases.

Look at bug #1846440:
-- Good Cardinality
select a from x where b like 'C%';
-- Good Cardinality
select a from x where b like 'CE%';
-- Bad Cardinality
select a from x where b like 'CEL%';
It isn't even monothonic!
Rating: 5
November 12, 2001
Reviewer: Jagadish
Thanks to make me perfect understanding about use of Histograms and how they
differ with estimated statistics. As you said I simply used analyze table
compute statistics. This  is very useful peace of information.

Thanks for full exploration
Rating: 4
compute statistics November 20, 2001
Reviewer: Ramesh from MA,USA
Is that mean we have to always use "compute statistics for table for all indexes
  for ALL INDEXED COLUMNS size xxx" to enable optimiser to get full statistics ?

Please clarify
Followup:
No, it means in this particular case, because of the nature of the skewed data
in his table we needed to compute histograms to feed the optimizer the proper
information -- not only to get the best plan but to allow him to get a better
estimate of rows that will be returned.

If you want the complete set of stats, you would include these clauses (you can
actually get more, i just got the indexed columns -- you can get all columns,
hidden columns and so on)
Rating: 4
compute statistics November 21, 2001
Reviewer: Ramesh from MA,USA
Is that mean we have to always use "compute statistics for table for all indexes
  for ALL INDEXED COLUMNS size xxx" to enable optimiser to get full statistics ?

Please clarify
Rating: 5
doubts on statistics December 27, 2003
Reviewer: David Rodriguez
Hi,

It´s David from Argentina.
I have question about cardinality shown in explain plan, it seems they are quite
correct when the query is simple but when it gets complex the data arent so
good:

select ZZ.PROFILE_CM_SEQ
  from sysadm.PS_BO_ROLE_CM ZZ
       inner join sysadm.PS_BO_CM_PROFL_DTL Z
               on Z.BO_ID =ZZ.BO_ID
              and Z.PROFILE_CM_SEQ = ZZ.PROFILE_CM_SEQ
where  Z.CM_TYPE_ID in (1, 2)
  and  Z.PRIMARY_IND ='Y'
  and  Z.EFFDT < sysdate    Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=296563 Card=3 Bytes=195)
   1    0   MERGE JOIN (Cost=296563 Card=3 Bytes=195)
   2    1     SORT (JOIN) (Cost=80008 Card=3236059 Bytes=126206301)
   3    2       TABLE ACCESS (FULL) OF 'PS_BO_CM_PROFL_DTL' (Cost=21513
Card=3236059 Bytes=126206301)
   4    1     SORT (JOIN) (Cost=216555 Card=13036685 Bytes=338953810)
   5    4       INDEX (FAST FULL SCAN) OF 'PS_BO_ROLE_CM' (UNIQUE) (Cost=7581
Card=13036685 Bytes=338953810)

select table_name, num_rows
  from dba_tables where table_name in ('PS_BO_ROLE_CM', 'PS_BO_CM_PROFL_DTL')

TABLE_NAME             NUM_ROWS
-------------------- ----------
PS_BO_CM_PROFL_DTL     12944235
PS_BO_ROLE_CM          13036685

The explain plan suggests 3 million rows roughly

TABLE ACCESS (FULL) OF 'PS_BO_CM_PROFL_DTL' (Cost=21513 Card=3236059
Bytes=126206301)

but I have 13 million rows!

Also the

MERGE JOIN (Cost=296563 Card=3 Bytes=195)

suggest that 3 rows will be returned? (the fact is this returns roughly 4
million rows)
Followup:
this query plan is saying:

we will:

TABLE ACCESS (FULL) OF 'PS_BO_CM_PROFL_DTL' (Cost=21513 Card=3236059
Bytes=126206301)

of all of the possible rows in 'PS_BO_CM_PROFL_DTL' (which you say is about
13,000,000) we will return from the full scan 3.2 million.  the predicate:

where  Z.CM_TYPE_ID in (1, 2)
  and  Z.PRIMARY_IND ='Y'
  and  Z.EFFDT < sysdate

is expected to return just 3.2 million records.

Well sort that data.

Then, we'll full scan that index:

INDEX (FAST FULL SCAN) OF 'PS_BO_ROLE_CM' (UNIQUE) (Cost=7581 Card=13036685
Bytes=338953810)

and since there is no predicate that can be applied yet -- we'll get all
13,036,685 rows.

then, based on the join condition:

               on Z.BO_ID =ZZ.BO_ID
              and Z.PROFILE_CM_SEQ = ZZ.PROFILE_CM_SEQ

we expect to take that 3.2 million rows and 13 million rows and after joining,
just get 3.

So, this would be the place where the problem is.  (before this, it looks dead
on).

The question to you would be -- how do you gather statistics exactly, the EXACT
commands used.
Rating: 5
exact command December 27, 2003
Reviewer: David Rodriguez
Hi

I use

DBMS_STATS.GATHER_SCHEMA_STATS (ownname => 'SYSADM',
                               estimate_percent => 20,
                                block_sample => TRUE,
                                method_opt =>NULL,
                                degree => 5,
                   granularity => 'ALL',
                   cascade => TRUE
                   );

anything wrong with that?

Note: PS_BO_ROLE_CM is hash partitioned
Followup:
you have no histograms and block sampling can be not as good as row sampling.

I'd look at the effect histograms might have on this and consider row over block
sampling.
Rating: 5
this is getting worse December 28, 2003
Reviewer: David Rodriguez
Hi

It´s david from Argentina

I followed your advice and analyzed the table with this command

exec dbms_stats.gather_table_stats(ownname=>'SYSADM',-
                                   tabname=>'PS_BO_CM_PROFL_DTL',-
                                   degree=>4,-
                                   method_opt=>'FOR ALL INDEXED COLUMNS SIZE
AUTO',-
                                   estimate_percent=>20,-
                                   granularity=>'ALL',-
                                   cascade=>TRUE)

However I am still getiing bad cardinality as you can see:

  1  select ZZ.PROFILE_CM_SEQ
  2    from sysadm.PS_BO_ROLE_CM ZZ
  3         inner join sysadm.PS_BO_CM_PROFL_DTL Z
  4                 on Z.BO_ID =ZZ.BO_ID
  5                and Z.PROFILE_CM_SEQ = ZZ.PROFILE_CM_SEQ
  6  where  Z.CM_TYPE_ID in (1, 2)
  7    and  Z.PRIMARY_IND ='Y'
  8*   and  Z.EFFDT < sysdate

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=453188 Card=2 Bytes=130)
   1    0   MERGE JOIN (Cost=453188 Card=2 Bytes=130)
   2    1     SORT (JOIN) (Cost=237925 Card=9934470 Bytes=387444330)
   3    2       TABLE ACCESS (FULL) OF 'PS_BO_CM_PROFL_DTL' (Cost=21513
Card=9934470 Bytes=387444330)
   4    1     SORT (JOIN) (Cost=215263 Card=12959460 Bytes=336945960)
   5    4       INDEX (FAST FULL SCAN) OF 'PS_BO_ROLE_CM' (UNIQUE) (Cost=7529
Card=12959460 Bytes=336945960)

getting cardinality of 2 now...?!

  1  select table_name, num_rows, partitioned
  2*   from dba_tables where table_name in ('PS_BO_ROLE_CM',
'PS_BO_CM_PROFL_DTL')

TABLE_NAME                       NUM_ROWS PAR
------------------------------ ---------- ---
PS_BO_CM_PROFL_DTL               12927275 NO
PS_BO_ROLE_CM                    12959460 YES

It should show 9 millions rows though

  1  select count(ZZ.PROFILE_CM_SEQ)
  2    from sysadm.PS_BO_ROLE_CM ZZ
  3         inner join sysadm.PS_BO_CM_PROFL_DTL Z
  4                 on Z.BO_ID =ZZ.BO_ID
  5                and Z.PROFILE_CM_SEQ = ZZ.PROFILE_CM_SEQ
  6  where  Z.CM_TYPE_ID in (1, 2)
  7    and  Z.PRIMARY_IND ='Y';

COUNT(ZZ.PROFILE_CM_SEQ)
------------------------
                 9950707

This is worrying me because in one of big queries these two tables are joined
first then from the rows returned a nested loop join is performed (via local
index scan), that means Oracle has to do 9950707 INDEX UNIQUE SCAN, TABLE ACCESS
BY LOCAL INDEX ROWID then PARTITION HASH ITERATOR!

Any comments is appreciated

TIA
Followup:
you are reading the plan wrong there -- what it is doing is

a) reading 'PS_BO_CM_PROFL_DTL' and keeping some of the records -- sorting them
by the join key.

b) reading 'PS_BO_ROLE_CM' index as if it were a table and sorting them by the
join key

c) merging these two results together -- most likely from temp.

I cannot comment on "another" plan of which I've never ever seen before or the
associcated query.  I cannot make the same leap you did in the last paragraph
there.

(you gathered histograms on indexed columns.  are all of the columns in the join
condition indexed?)
Rating: 5
yes the the join columns are indexed December 28, 2003
Reviewer: David Rodriguez
Hi

There is a composite index on these four columns in PS_BO_CM_PROFL_DTL

BO_ID
CM_TYPE_ID
EFFDT
PROFILE_CM_SEQ

composite index on PS_BO_ROLE_CM

BO_ID
ROLE_TYPE_ID
PROFILE_CM_SEQ

The thing is Oracle doesnt seem to get the right cardinality after merge join,
instead of showing roughly 9 millions rows it shows 2 I dont understand why

Thank you
Followup:
if you like, you can take a 10053 trace, compress it and email it to me.

go into sqlplus and:

set the event on (search for 10053 on this site for details)
set autotrace traceonly explain
do the query here
exit
Rating: 5
What does Cost means ? December 29, 2003
Reviewer: Riaz Shahid from PRAL, Lahore, Pakistan
Very usefull information as usual. I need a little clarification:

SQL>  set autotrace traceonly explain
SQL> select count(*) from test;
Elapsed: 00:00:00.00

Execution Plan
==========================================================
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1121 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'TEST' (Cost=1121 Card=158510)

What is meant by Cost=1121 ? and How can we estimate the running time for a
query ?

Regards
Followup:
the cost is the internal number used by the optimizer to pick the "best plan".

many factors influence the cost -- from system statistics, to hints, to init.ora
parameters (eg: play with your db_file_multiblock_read_count setting using alter
session and watch the cost go up and down)

consider this other example:

big_table@ORA9IR2> set autotrace traceonly explain
big_table@ORA9IR2>
big_table@ORA9IR2> alter session set optimizer_index_cost_adj = 100;

Session altered.

big_table@ORA9IR2> select count(subobject_name) from big_table where id < 5000;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=86 Card=1 Bytes=22)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=86 Card=5039
Bytes=110858)
   3    2       INDEX (RANGE SCAN) OF 'BIG_TABLE_PK' (UNIQUE) (Cost=13
Card=5039)

big_table@ORA9IR2> alter session set optimizer_index_cost_adj = 1;

Session altered.

big_table@ORA9IR2> select count(subobject_name) from big_table where id < 5000;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=22)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=2 Card=5039
Bytes=110858)
   3    2       INDEX (RANGE SCAN) OF 'BIG_TABLE_PK' (UNIQUE) (Cost=13
Card=5039)

big_table@ORA9IR2> set autotrace off

Now, the optimizer index cost adj changes nothing about the runtime performance
of that query.  NOTHING.  But, its setting influences the cost numbers the
optimizer assigns to various phases in the query plan.  Hence, we can influence
the optimizer one way or the other by tweaking these various settings.

But -- this example should also hilight that you cannot really 100% estimate
time from a query given the cost :)   those plans are identical, the runtimes
for both are going to be identical, yet the costs are widely different.

If you have acces to my new book "Effective Oracle By Design", i cover this sort
of stuff in some detail in chapter 6.
Rating: 4
Information on resource manager? December 29, 2003
Reviewer: Steve Mitchell from California USA
Earlier in this thread you said "the resource manager feature in 9i which allows
you to abort a query before it even STARTS in the database based on a predicated
runtime"

I would very much like to add this kind of functionality to my application.
(i.e, "The report you're about to may take 2 hours, do you want to continue?").
But I do not see information about the specifics of using the resource manager
as you describe in my Oracle docs.  Can you elaborate or point me to the docs I
should be reading?
Followup:
the timing information is not currently exposed.  it is not available to you
externally.

the resource manager is documented in the admin guide.
Rating: 5
Interesting December 29, 2003
Reviewer: Sami
Rating: 5
how to return the cardinality to a user? December 29, 2003
Reviewer: Ryan
I want to pass back an 'estimated' number of rows to the user. Per your new
book, you state that you should not get counts for pagination and should use
v$sql_plan.

what does the query look like to get the cardinality of a query i want to run or
just ran. I need one that has the best response time. I have a very strict SLA
Followup:
well, if you uniquely "tag" these queries, you can use this technique:

big_table@ORA9IR2> declare
  2      cursor c1 is select /* unique_tag1 */ * from big_table where
object_name like 'ABC%';
  3      cursor c2 is select /* unique_tag2 */ * from big_table where
object_name like 'jav%';
  4          l_card number;
  5  begin
  6      open c1;
  7      open c2;
  8
  9      select cardinality into l_card
 10        from v$SQL_PLAN_STATISTICS_ALL
 11       where (address,hash_value) = ( select distinct address,hash_value
 12                                       from v$open_cursor
 13                                      where upper(sql_text) like 'SELECT /*
UNIQUE_TAG1 */%'
 14                                        and sid = ( select sid from v$mystat
where rownum=1) )
 15         and child_number = 1
 16         and id = 1
 17         and rownum = 1;
 18
 19      dbms_output.put_line( 'tag1 ' || l_card );
 20
 21      select cardinality into l_card
 22        from v$SQL_PLAN_STATISTICS_ALL
 23       where (address,hash_value) = ( select distinct address,hash_value
 24                                       from v$open_cursor
 25                                      where upper(sql_text) like 'SELECT /*
UNIQUE_TAG2 */%'
 26                                        and sid = ( select sid from v$mystat
where rownum=1) )
 27         and child_number = 1
 28         and id = 1
 29         and rownum = 1;
 30
 31      dbms_output.put_line( 'tag1 ' || l_card );
 32  end;
 33  /
tag1 64
tag1 84444

PL/SQL procedure successfully completed.

big_table@ORA9IR2>
big_table@ORA9IR2> set autotrace traceonly explain
big_table@ORA9IR2> select * from big_table where object_name like 'ABC%';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=66 Card=64 Bytes=6400)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=66 Card=64
Bytes=6400)
   2    1     INDEX (RANGE SCAN) OF 'BIG_TABLE_IDX' (NON-UNIQUE) (Cost=3
Card=64)

big_table@ORA9IR2> select * from big_table where object_name like 'jav%';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1379 Card=84444 Bytes=8444400)
   1    0   TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1379 Card=84444
Bytes=8444400)

big_table@ORA9IR2> set autotrace off

if you are using dbms_sql, we might have a better way (as the cursor handle
returned can be used as a more direct path to the query, obviating the need to
"tag" them)
Rating: 5
latch contention December 30, 2003
Reviewer: Ryan from Tysons Corner, VA
Thanks Tom. This is going to be a very high transaction database. I have read
that hitting the v$views in production repeatedly can cause latch contention.

Do I have any concerns with this method?
Followup:
there is no "free" method to get this information.  this will cost you -- yes.

the cost of this is fairly substantial in terms of latching and a runtime hit
during each parse.

Of course, since the cardinality doesn't change from parse to parse -- you could
easily cache this information.

also, you don't need this to EVERY query -- in fact, it would be the exception
here, not the rule wouldn't it?
Rating: 5
im getting no data returned December 30, 2003
Reviewer: A reader
I have been testing your code. I simplified it for test purposes because it
keeps failing. Not sure why.

I tested it with all your code, then started simplifying it in hopes that I can
figure out what is wrong. Im on Oracle 9.2 and I'm in a DBA privileged account.
The query does return data. 'small_table' is just user_objects.

I also tried commented out child_number=1 and id=1 for test purposes. no data
returned.

  1  declare
  2     cursor c1 is select /* unique_tag1 */ * from user_objects;
  3              l_card number;
  4      begin
  5          open c1;
  6          select cardinality into l_card
  7           from v$SQL_PLAN_STATISTICS_ALL
  8          where (address,hash_value) = ( select distinct address,hash_value
  9                                          from v$open_cursor
 10                                         where --upper(sql_text) like 'SELECT
/* UNIQUE_TAG1 */%'
 11                                            sid = ( select sid from v$mystat
 12                                                        where rownum=1) )
 13            and child_number = 1
 14            and id = 1
 15            and rownum = 1;
 16         dbms_output.put_line( 'tag1 ' || l_card );
 17*    end;
 18  /
declare
*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at line 6
Followup:
looks like not everything is going into there -- it goes into v$sql_plan, but
that is more expensive to query (and it looks like I should have had
child_number = 0 -- not one)
Rating: 3
December 30, 2003
Reviewer: Tanel Poder
You have to use CBO for getting statistics in v$sql_plan_statistics(_all)
Rating: 3
December 30, 2003
Reviewer: Tanel Poder
No, sorry about misinfo, a quick test showed that execution statistics are shown
for RBO as well.

The point is, that v$sql_plan_statistics show execution statistics - for already
executed statements. V$SQL_PLAN is able to predict row amount, but since it's
only CBO estimation based on existing stats, it'll get quite unaccurate for more
complex cases.

This is my understanding, can you comment whether it's correct?
Followup:
the cardinality in both is a guess -- the statistics_all view was cheaper to
query.
Rating: 3
December 30, 2003
Reviewer: A reader
I wasn't precise enough before, actually I was referring to last_output_rows
column in v$sql_plan_statistics(_all) views. This should come directly from
query execution statistics, but of course it's valid only for this single query
with same (bind) variable values.
Rating: 5
update on pagination December 30, 2003
Reviewer: Ryan Gaffuri from Tysons Corner, VA
I do need it for every query on a search screen. We are expecting this to be hit
alot.(no firm estimates yet, we are stress it with a 100 or so selects/second)
Im planning on setting it up similiar to the code from your book 'Effective
Oracle by Design' p. 341-348. I would add the 'row estimate' for just the very
first page. When you users go to the next 'page' they wouldnt get it.

however, it could be alot. Not sure what else to do. The count is required.

I posted this question on ORACLE-L. Wolfgang Breitling made an interesting
point. Id appreciate your take on this.

'v$sql_plan_statistics (and consequently v$sql_plan_statistics_all) only
have data to show if statistics_level is set to ALL. You can set that at
the session level.
Has anyone done measurements on a busy system to evaluate what the impact
is of setting that system-wide. The impression I have is that it is not
something I want to set in production all the time.'
Followup:
there are only two ways I know to get the count.

a) using the explain plan command.  that is a HARD PARSE for each and every
query plus IO to the plan table plus a query to get it out.  ugh.

b) using v$sql_plan or more optimally v$sql_plan_statistics_all (queries better
against the v$sql_plan_statistics_all -- v$sql_plan seems suboptimal, mixture of
lots of x$, and real tables in that one, less so on v$sql_plan_statistics).

if you are doing 100 or so queries/second, they must not be returning large
result sets -- what sizes are you expecting here?
Rating: 5
compute statistics December 30, 2003
Reviewer: David Rodriguez
Hi

It's David from Argentina, I did another test today by computing statistics and
still getting two rows as cardinality after merge join when we are expecting 4
million roughly....

I tried to read the 10053 output but I am totally lost...

TIA
Rating: 3
December 30, 2003
Reviewer: Tanel Poder
In my 9.2.0.4 on W2k, the v$sql_plan_statistics seems to be the simplest view,
querying only from QES view x$qesrstat and v$sql_plan_statistics all is based on
several tables.

Or did you measure the latching & stats?

Thanks.
Followup:
v$sql_plan_statistics doesn't have the cardinality, so we can only compare

v$sql_plan
v$sql_plan_statistics_ALL
Rating: 5
Ryan Gaffuri December 30, 2003
Reviewer: Ryan Gaffuri from Tysons Corner, VA
Thanks Tom...

Not sure how big the result sets will be. I only have development data right
now. Its supposed to be similiar to production data. The largest result sets
have been 2,400 records which is too large to return all at once and meet the
SLA.

I was going to implement something similiar to the 'pagination' procedures in
your second book. I'll return 25 rows at a time via a REF CURSOR. The very first
time the query is issued, I'll get an estimate of the total number of rows as
well.

It's the best way I can think of to do it.
Followup:
the only ways I know to get the estimation are from the v$ tables or explain
plan.

explain plan will be hugely hugely expensive.
v$ will just be expensive
Rating: 5
to David Rodriguez January 03, 2004
Reviewer: A reader
Hello

I suspect you are hitting bug 2109825, INCORRECT CARDINALITY ESTIMATE WITH OR
PREDICATES AND NULL VALUES

Fix, 10g.... I feel your pain sorry
Followup:
with lots of backports to 8iR3, 9iR1 and 9iR2 (2 years ago).  contact support if
you are having pain with this one.
Rating: 4
Can't grab from v$_plan_statistics_all March 11, 2004
Reviewer: J from Vancouver
Hi Tom.

I've taken the above recommendation and modified it to run a test case here -
Alas, I'm having difficulties with v$_plan_statistics_all. I can see it, but get
blown out when selecting from it. Sorry, but I've looked at this for some time,
and I just can't see what's missing. Can you please review this for me?  Many
Thanks!

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 IBM/AIX RISC System/6000: Version 9.2.0.4.0 - Production
NLSRTL Version 9.2.0.4.0 - Production

SQL>
SQL> select count(*)
  2  from   addresses
  3  where  cntry_cd = 'US';

  COUNT(*)
----------
313052

SQL>
SQL> declare
  2      lv_card number;
  3      cursor c1 is
  4         select /* tag1 */ *
  5         from   addresses
  6         where  cntry_cd = 'US';
  7  begin
  8      open c1;
  9
 10      select cardinality
 11      into   lv_card
 12      from   v$sql_plan_statistics_all
 13      where  (address, hash_value) =
 14             (select distinct address, hash_value
 15          from   v$open_cursor
 16          where  upper(sql_text) like 'SELECT /* TAG1 */%'
 17          and    sid =
 18                 (select sid
 19              from   v$mystat
 20              where  rownum = 1)
 21             )
 22      and    child_number = 1
 23      and    id = 1
 24      and    rownum = 1;
 25
 26      dbms_output.put_line('Approx Rowcount: '||lv_card );
 27
 28  end;
 29  /
declare
*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at line 10

SQL>
SQL> select * from v$sql_plan_statistics_all;

no rows selected

SQL>
SQL> select * from v$open_cursor
  2  where sid = (select sid from v$mystat where rownum = 1)
  3  and rownum = 1;

SADDR                   SID USER_NAME                      ADDRESS
---------------- ---------- ------------------------------ ----------------
HASH_VALUE SQL_TEXT
---------- ------------------------------------------------------------
070000000A4CC608         32 ORS                            0700000010602788
1876698668 select * from v$open_cursor where sid = (select sid from v$m
Followup:
what is your statistics level (init.ora)
Rating: 5
Statistics Level: Type = 2, Value = 'TYPICAL' March 11, 2004
Reviewer: J from Vancouver
Wowza, that was a quick response!  What brand of coffee do you drink? (Gotta get
some of THAT stuff)...  =o)

Thanks!
- J
Followup:
ops$tkyte@ORA9IR2> l
  1  SELECT STATISTICS_NAME,
  2         SESSION_STATUS,
  3         SYSTEM_STATUS,
  4         ACTIVATION_LEVEL,
  5         SESSION_SETTABLE
  6    FROM v$statistics_level
  7*  ORDER BY 1
ops$tkyte@ORA9IR2> /

                               Session    System     Activation Session
Statistics Name                Status     Status     Level      Settable
------------------------------ ---------- ---------- ---------- ----------
Buffer Cache Advice            ENABLED    ENABLED    TYPICAL    NO
MTTR Advice                    ENABLED    ENABLED    TYPICAL    NO
PGA Advice                     ENABLED    ENABLED    TYPICAL    NO
Plan Execution Statistics      ENABLED    ENABLED    ALL        YES
Segment Level Statistics       ENABLED    ENABLED    TYPICAL    NO
Shared Pool Advice             ENABLED    ENABLED    TYPICAL    NO
Timed OS Statistics            ENABLED    ENABLED    ALL        YES
Timed Statistics               ENABLED    ENABLED    TYPICAL    YES

8 rows selected.

(black coffee, takes too long to add sugar/milk ;)
Rating: 5
Timed Statistics - Aha! March 11, 2004
Reviewer: J from Vancouver
Thanks for the lesson, coach!  Excuse me, but I've got to discuss things with
the DBA....

SQL> select statistics_name, session_status,
  2         system_status, activation_level
  3         session_settable
  4  from   v$statistics_level
  5* order by 1;

STATISTICS_NAME                 SESSION_ SYSTEM_S SESSION

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

Buffer Cache Advice             ENABLED  ENABLED  TYPICAL

MTTR Advice                     ENABLED  ENABLED  TYPICAL

PGA Advice                      ENABLED  ENABLED  TYPICAL

Plan Execution Statistics       DISABLED DISABLED ALL

Segment Level Statistics        ENABLED  ENABLED  TYPICAL

Shared Pool Advice              ENABLED  ENABLED  TYPICAL

Timed OS Statistics             DISABLED DISABLED ALL

Timed Statistics                ENABLED  ENABLED  TYPICAL
March 11, 2004 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
Rating: 3
username, database name and instance name ... March 11, 2004
Reviewer: A reader
Hi tom,

  thanks for your example;;

/**********************************/
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();
}

}

/***********************************/

 I have a function that takes the connection  as an input
parameter. I don't know the user name and database name.
I just get an active connection (just conneciton object oracle JDBC driver
classes12.zip)

how to find out
1.) what is the useName for this conneciton obj ?
2.) what is the database name for this conneciton obj ?
3.) what is the instance name for this conneciton obj ?

 as we give this things to get the connection using oracle jdbc drivers
(classes12.zip). I would expect that these are the properties of conneciton.
but, I did not see how to retrive that from conneciton

 can you please show me ?

TIA
Followup:
If there aren't any documented properties, then there aren't any properties I
guess (more of a java/jdbc question here -- check the sun site, perhaps it is
part of the standard)

short of that,

ops$tkyte@ORA9IR2> select user, a.name, b.instance_name
  2  from v$database a, v$instance b;

USER                           NAME
------------------------------ ------------------------------
INSTANCE_NAME
----------------
OPS$TKYTE                      ORA9IR2
ora9ir2

that queries does that.
Rating: 3
thanks March 11, 2004
Reviewer: A reader
tom, I am working with oracle JDBC dirves and classes.
so I thought I should Ask oracle ratherthan sun !!
And sun... They ONLY give VERY HIGH LEVEL REFERENCE no easy implementation. Even
if you see javadoc and all those Java API documentation, they give all methods
only. NO EXAMPLE how to use it that is a big big issue with that. Because of
your nature of explaing by exmaple I really find this site
"THE BEST" resource for oracle products.

 yep, I know I can query database but If I already have the data in the
connection object, I don't want to query the same informatio from database right
?

 Thanks, for your reply.
Followup:
JDBC comes from "sun", we'd document extra goodies but not the basic stuff every
jdbc driver would need (and they all take user/pass/url/etc)

I'm just not a java programmer -- don't know if there is such a "property"

digging in the docs -- it seems that

 String     getURL()
          Retrieves the URL for this DBMS.
 String     getUserName()
          Retrieves the user name as known to this database.

from DatabaseMetaData, returned by connection.getMetaData() would tell you what
you want (took a couple of secs after googleing "java.sql.connection" to
discover this........)
http://java.sun.com/j2se/1.4.2/docs/api/java/sql/Connection.html
March 11, 2004 'Abdur-Razzaaq -- Thanks for the question regarding "Manipulating Sort Area Size", version 8.1.6
You Asked
Hi Tom,

The value specified by SORT_AREA_SIZE is assigned on a per user basis.
Therefore, if it is set to 1M, and 25 users connect to the database, each user's
Server Process will consume 1M (25M in total).

The questions are:

1)  The memory consumed for the sort, is it within the SGA memory or the Server
memory (outside SGA)?

2)  What will be the case if 25 physical users (different sessions) are
connected using the same database user?

3)  Let's assume, SORT_AREA_SIZE is set to 1M and temporary tablespace is
locally managed with uniform size of 1M.  If SORT_AREA_RETAINED_SIZE is set to a
higher value other than 1M, what will be the impact on performance?

Thanks,
'Abdur-Razzaaq
and we said...
Well, your basic assumption is wrong.

sort_area_size is an upper bound on the memory we dynamically allocate for a
sort.

A session may have more then one sort going at the same time (more then one sort
area size)

sort_area_sizes are not allocated until they are needed (and GROW to be
sort_area_size bytes in size -- they don't start there) and are freed the second
the sort is over.

So, if it is 1m and there are 25 users connected to the database you will have
somewhere between 0 and many megs of sort space at any point in time (if all 25
do a single sort AT THE SAME exact instant -- you'll have 25m.  If they are not
sorting, you'll have 0 meg)

1) sort_area_size space is allocated in the PGA, outside of the SGA (retained
can be in the UGA with MTS)

2) it is done by the SESSION not by name and a single session can have more then
one going at any time

3) SORT_AREA_RETAINED_SIZE is bounded by sort_area_size, hence this hypothetical
situation won't happen.

See
http://www.ixora.com.au/newsletter/2000_11.htm
for a good discussion on this.

followup to comment one

It is definitely an inaccurate statement on their part (eg: its *wrong* plain
and simple)

See
http://www.sybex.com/erratatracking.nsf/weberrataform?OpenForm&ISBN=2684
there is an errata sheet for this book -- you might want to drop sybex a line
and have them add this to the sheet.

followup to comment two

Nope -- MTS won't affect this, sort_area_size is a per process thingy -- its in
the shared_server under MTS (so using MTS the max sort area memory would be a
function of the number of shared servers you have). The sort area retained comes
from the UGA in MTS which is in the SGA -- and sort area spaces are always freed
after the sort (sort area retained is freed right after the last row is
processed)

sort areas are transient, they come and go -- they are not always there.
Review & Followup

Rating: 4
Reason for wrong basic assumption August 15, 2001
Reviewer: 'Abdur-Razzaaq from Singapore
Tom,
In the book "OCP Oracle8i DBA Performance Tuning and Network Administration
Study Guide" by Joseph C. Johnson and Matthew Weishan from Sybex publishing, it
is quoted on page 263 as follows:
"... The value specified by SORT_AREA_SIZE is assigned on a per user basis.
Therefore, if SORT_AREA_SIZE is set to 1MB, and 20 users connect to the
database, each user's Server Process will consume 1MB of server memory, 20MB
total, whether they are actively sorting or not."  Is this a documentation bug?
Rating: 3
August 16, 2001
Reviewer: AB from London
I think the OCP book might be referring to MTS. It was my understanding that MTS
allocated all SORT_AREA_SIZE to a user at sign-on, whereas non-MTS provides the
SORT_AREA_SIZE as and when needed by a session up to the limit specified.
Rating: 5
Here is what the author of the book has to say August 16, 2001
Reviewer: 'Abdur-Razzaaq from Singapore
Tom Kyte is correct. The amount of memory specified by the
SORT_AREA_SIZE parameter is not allocated to a user's process until they perform
their first sort. This was an erroneous statement and I thank you for bringing
it to my attention.

However, they does seem to be some conflicting information regarding whether the
space is released *after* the user's sort is complete. I think this was the
cause of my own
confusion on this issue. Specifically, page 2-16 of the "Oracle8i Designing and
Tuning for Performance" reference manual states:

"...the user had set SORT_AREA_SIZE to 10MB on a machine with 64 MB of memory
serving 20 users. When users logged on to the system, the first time they
executed a sort, their
sessions were assigned to a sort area. Each session held the sort area for the
duration of the session."

Additionally, page 19-42 of that same manual also states:

"Large sort areas can be used effectively if you combine a large SORT_AREA_SIZE
with a minimal SORT_AREA_RETAINED_SIZE. If memory is not released until the
user disconnects from the database, then large sort work areas could cause
problems."

However, the Oracle8i Reference on page  1-112 seems to contradict the "sort
area is not released until the user disconnects" idea illustrated above by
stating:

"After the sort is complete, but before the rows are returned, Oracle releases
memory down to the size specified by the SORT_AREA_RETAINED_SIZE parameter.
After the last
row is returned, Oracle releases the remainder of the memory."

After further testing and research it appears that the last statement is
correct. Therefore, the corrected statement on page 263 on the Sybex review
guide, section "Make it Bigger", starting at the second sentence should be:

"Care must be taken when increasing this parameter, however. While the amount of
memory specified by SORT_AREA_SIZE is not allocated to a user's session until
they initiate a sort, the server's available memory must be sufficient to
accommodate the user's sort area while the sort is being processed. In
particular, if SORT_AREA_SIZE is set to a large value and many users are sorting
simultaneously, the demands placed on the server's memory may adversely impact
performance until the memory allocated to those sort areas is released when the
sorts are
completed."

Cordially,
Joe Johnson, OCP
Rating: 4
July 18, 2002
Reviewer: A reader
Can you show us some examples.  Used the script provided at
http://www.ixora.com.au/scripts/io_opt.htm#disk_sorts
to monitor disk sorts.

Steps.

1.  Restarted database with Sort_area_size = 64k.

connect as my application user in SQL*Plus and ran a query three times.

checked disk sorts with above script

DISK_SORTS AVERAGE_SIZE PEAK_CONCURRENT
---------- ------------ ---------------
         3          70K               1

did an alter session and set sort_area_size = 1M for my application user and ran
the query 3 more times

checked disk sorts with above script

DISK_SORTS AVERAGE_SIZE PEAK_CONCURRENT
---------- ------------ ---------------
         6          68K               1

I would think that if the user alters his sort area to 1M it would not have a
disk sort.  I tried with 10M sort with the same results.

Can you show an example of this using the alter session?

Thanks
Followup:
not knowing what you queried with -- not having your script from end to end -- I
cannot really comment.  I don't know what you are looking for exactly and how
you were testing.
Rating: 4
July 19, 2002
Reviewer: A reader
Ok,  Let me try to explain a little better what I am asking.
Database 8.1.7.3
DB_Block = 8192
sort_area_size = 65500
sort_area_retained_size = 65500

I spent some more time making up a better test script.

---------------------------------
create table t as (select 'SORTING OF LENGTH 20' TEST from all_objects);

DESC T;

show parameter sort_area

alter session set sort_area_size = 65500;

set autotrace on stat

select count(1) from (select TEST from t
where  rownum < 1331 order by 1);

select count(1) from (select TEST from t
where  rownum < 1332 order by 1);

alter session set sort_area_size = 1024000;

select count(1) from (select TEST from t
where  rownum < 1777 order by 1);

select count(1) from (select TEST from t
where  rownum < 1778 order by 1);

drop table t;

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

I used this query in sql*plus to find the break point

select count(1) from (select TEST from t
where  rownum < &rows order by 1);

Ok, I created a table of char(20) with fixed data.  I set my sort_area_size to
65500.  The most rows I can select and still do a memory sort is 1331.  1332
does a disk sort.

I then set my sort_area_size to 1024000 which is about 15 times larger that
before.  But the max number of rows I can sort in memory is 1777.  1778 does a
disk sort.

I also looked at the sort_area_size and increasing it above 73800 returned same
results and did not allow me to sort more that 1777 rows in memory.

So my questions would be?

1.  Given a fixed about of data, What goes into the sort_area?
2.  If you double your sort_area, shouldn't the amount of data you can sort
double or is there an upper limit of how much sort area you can use?
3.  Can you so an example showing how changing the sort area directly/indirectly
affects how much data will be sorted in memory

Thanks
Followup:
My question to you is -- well, not really a question, rather statement of fact
on my part - I cannot reproduce your findings.  Oh -- I can reproduce your
finding of 1331 to 1332 just fine, but not eh 1700 example.  In fact, I went to
10k rows and didn't do a disk sort:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t as (select 'SORTING OF LENGTH
20' TEST from all_objects);

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> DESC T;
 Name                                                                    Null?
 Type
 -----------------------------------------------------------------------
-------- -------------------------------------------------
 TEST
 CHAR(20)

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> show parameter sort_area

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
sort_area_retained_size              integer 0
sort_area_size                       integer 1024000
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set sort_area_size = 65500;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace on stat
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(1) from (select TEST from t
  2  where  rownum < 1331 order by 1);

  COUNT(1)
----------
      1330

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
          9  consistent gets
         63  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
          1  sorts (memory)
          0  sorts (disk)
          1  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(1) from (select TEST from t
  2  where  rownum < 1332 order by 1);

  COUNT(1)
----------
      1331

Statistics
----------------------------------------------------------
          0  recursive calls
         14  db block gets
          9  consistent gets
          5  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)
          1  sorts (disk)
          1  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set sort_area_size = 1024000;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(1) from (select TEST from t
  2  where  rownum < 1777 order by 1);

  COUNT(1)
----------
      1776

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
         11  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
          1  sorts (memory)
          0  sorts (disk)
          1  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(1) from (select TEST from t
  2  where  rownum < 1778 order by 1);

  COUNT(1)
----------
      1777

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
         11  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
          1  sorts (memory)
          0  sorts (disk)
          1  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(1) from (select TEST from t
  2  where  rownum < 10000 order by 1);

  COUNT(1)
----------
      9999

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
         40  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
          1  sorts (memory)
          0  sorts (disk)
          1  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> drop table t;

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle8i Enterprise Edition Release 8.1.7.3.0 - Production
PL/SQL Release 8.1.7.3.0 - Production
CORE    8.1.7.0.0       Production
TNS for Solaris: Version 8.1.7.3.0 - Production
NLSRTL Version 3.4.1.0.0 - Productio

lets see a cut and paste just like that.
Rating: 4
July 19, 2002
Reviewer: A reader
Here you go.  What else would affect this?
-------------------------------

SQL> show parameter sort_area

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
sort_area_retained_size              integer 65536
sort_area_size                       integer 1024000
SQL>
SQL> create table t as (select 'SORTING OF LENGTH 20' TEST from all_objects);

Table created.

SQL> DESC T
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 TEST                                               CHAR(20)

SQL>
SQL> show parameter sort_area

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
sort_area_retained_size              integer 65536
sort_area_size                       integer 1024000
SQL>
SQL> alter session set sort_area_size = 65500;

Session altered.

SQL>
SQL> set autotrace on stat
SQL>
SQL> select count(1) from (select TEST from t
  2  where  rownum < 1331 order by 1);

  COUNT(1)
----------
      1330

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
          9  consistent gets
          7  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
          1  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL>
SQL>
SQL> select count(1) from (select TEST from t
  2  where  rownum < 1332 order by 1);

  COUNT(1)
----------
      1331

Statistics
----------------------------------------------------------
          0  recursive calls
         14  db block gets
          9  consistent gets
          5  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)
          1  sorts (disk)
          1  rows processed

SQL>
SQL>
SQL> alter session set sort_area_size = 1024000;

Session altered.

SQL>
SQL>
SQL> select count(1) from (select TEST from t
  2  where  rownum < 1777 order by 1);

  COUNT(1)
----------
      1776

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
         11  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
          1  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL>
SQL>
SQL> select count(1) from (select TEST from t
  2  where  rownum < 1778 order by 1);

  COUNT(1)
----------
      1777

Statistics
----------------------------------------------------------
          0  recursive calls
         14  db block gets
         11  consistent gets
          7  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)
          1  sorts (disk)
          1  rows processed

SQL>
SQL>
SQL> drop table t;

Table dropped.

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

Thanks Tom for your time.
Followup:
Interesting,

what platform
what version of that platform
what does select * from v$version show
what is your init.ora (not v$parameter, just show me what you are setting)
Rating: 4
July 19, 2002
Reviewer: A reader
SQL> select * from v$version;

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

OS is Win2000 Workstation SP2 256Meg Ram
Running Oracle in Dedicated Server Mode

init.ora has:

db_files = 1024
open_cursors = 500
max_enabled_roles = 30
db_file_multiblock_read_count = 32
db_block_buffers = 4000
shared_pool_size = 31457280
large_pool_size = 0
JAVA_POOL_SIZE =  0
log_checkpoint_interval = 10000
log_checkpoint_timeout = 1800
processes = 150
parallel_max_servers = 5
log_buffer = 32768
timed_statistics = true
max_dump_file_size = 10240
db_block_size = 8192
remote_login_passwordfile = exclusive
os_authent_prefix = ""
distributed_transactions = 500
compatible = 8.1.0
sort_area_size = 512000
sort_area_retained_size = 65536
job_queue_processes = 1

Thanks
Followup:
DOH -- don't know why I didn't think of that before.

It is your sort_area_retained_size.  It was not big enough.  There were two
things happening here -- the sort and the aggregation.  In between the two, you
give up your sort_area_size and shrink it down to sort_area_retained.  Once we
did that -- the intermediate result was bigger then fit in your
sort_area_retained and we put it to disk.  As soon as we put to disk -- there
you go, sort disk.

If you change your retained size, you'll see the sort disk goes away.  It didn't
really SORT on disk, it sorted and then stuff some results on disk cause they
wouldn't fit in the retained size.
Rating: 5
I spotted it! July 19, 2002
Reviewer: Mark J. Bobak from Ann Arbor, MI
Heh, I'm pretty proud of myself, as I spotted the problem
right away, when the reader posted the first full cut and
paste. ;-)

I just wanted to mention to the reader:

The link to IxOra that Tom has in the reply to the original
question, Steve Adams does a nice discussion of sort_area_size
and sort_area_retained_size, and how they are used.

-Mark

PS  Hey, I just noticed the spell checker icon, pretty cool.
Nice touch!
Rating: 5
Do i have these parameters incorrectly set? July 20, 2002
Reviewer: Yogeeraj from Mauritius
Hello,

Reading this post, i became very curious to see the behaviour of the above test
in my system. So i ran the same steps!

Oops! All these tests give me " 1  sorts (disk)" each time!!

Please advise on how i proceed in fixing the problem (if any).

Thank you for your precious time.

Best Regards
Yogeeraj
============================================================
SQL> show parameter sort_area

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
sort_area_retained_size              integer 65536
sort_area_size                       integer 262144
SQL> create table t as (select 'SORTING OF LENGTH 20' TEST from all_objects);

Table created.

SQL> DESC T
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 TEST                                               CHAR(20)

SQL> alter session set sort_area_size = 65500;

Session altered.

SQL> set autotrace on stat

SQL> SQL> select count(1) from (select TEST from t
  2  where  rownum < 1331 order by 1);

  COUNT(1)
----------
      1330

Statistics
----------------------------------------------------------
          0  recursive calls
         14  db block gets
          9  consistent gets
         12  physical reads
          0  redo size
        296  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)
          1  sorts (disk)
          1  rows processed

SQL> select count(1) from (select TEST from t
  2  where  rownum < 1332 order by 1);

  COUNT(1)
----------
      1331

Statistics
----------------------------------------------------------
          0  recursive calls
         14  db block gets
          9  consistent gets
          5  physical reads
          0  redo size
        296  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)
          1  sorts (disk)
          1  rows processed

SQL>

SQL> alter session set sort_area_size = 1024000;

Session altered.

SQL> select count(1) from (select TEST from t
  2  where  rownum < 1777 order by 1);

  COUNT(1)
----------
      1776

Statistics
----------------------------------------------------------
          0  recursive calls
         14  db block gets
         11  consistent gets
         15  physical reads
          0  redo size
        296  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)
          1  sorts (disk)
          1  rows processed

SQL>

SQL> select count(1) from (select TEST from t
  2  where  rownum < 1778 order by 1);

  COUNT(1)
----------
      1777

Statistics
----------------------------------------------------------
          0  recursive calls
         14  db block gets
         11  consistent gets
          7  physical reads
          0  redo size
        296  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)
          1  sorts (disk)
          1  rows processed

SQL>

SQL> select * from v$version;

BANNER
------------------------------------------------------------
Oracle8i Enterprise Edition Release 8.1.7.4.0 - 64bit Production
PL/SQL Release 8.1.7.4.0 - Production
CORE    8.1.7.0.0       Production
TNS for HPUX: Version 8.1.7.4.0 - Production
NLSRTL Version 3.4.1.0.0 - Production

-------------------------
Parameters in my Init.ora
-------------------------
control_files = ("/d02/oradata/cmtdb/control01.ctl",
"/d02/oradata/cmtdb/control02.ctl", "/d02/oradata/cmtdb/control03.ctl")
open_cursors = 350
max_enabled_roles = 60
db_block_buffers = 10000
shared_pool_size = 157286400
large_pool_size = 20971520
java_pool_size = 20971520
log_checkpoint_interval = 100000
log_checkpoint_timeout = 1800
processes = 275
og_buffer = 262144
timed_statistics = true
log_archive_start = true
log_archive_dest_1 = "location=/d01/app/oracle/admin/cmtdb/arch"
log_archive_format = arch_%t_%s.arc
background_dump_dest = /d01/app/oracle/admin/cmtdb/bdump
core_dump_dest = /d01/app/oracle/admin/cmtdb/cdump
user_dump_dest = /d01/app/oracle/admin/cmtdb/udump
db_block_size = 8192
remote_login_passwordfile = exclusive
remote_os_authent=true
os_authent_prefix = "ops$"
job_queue_processes = 4
job_queue_interval = 60
distributed_transactions = 80
open_links = 4
local_listener = "listener_1"
compatible = "8.1.7.3"
sort_area_size = 262144
sort_area_retained_size = 65536
NLS_DATE_FORMAT=DD/MM/YYYY
utl_file_dir = *
sessions=400
session_cached_cursors=100
_trace_files_public=true
db_file_multiblock_read_count=32
resource_limit = true
============================================================
Followup:
play with it (list the original guy did)

find your "break even" points.
Rating: 4
October 17, 2002
Reviewer: A reader
Tom,
I tried to use the script disk_sorts.sql that is showed in the website that you
recommended, but I am not able to run it because I don't have the table
x_$kcctf,x_$ktfthc and sys.x_$kcftio.
How can I do in order to create these tables?
I checked all the scripts on /oracle/rdbms/admin (grep kcctf *)and none of them
use these objects.
Is there any script that I can use in order to create them?

Thanks
Followup:
that is the problem with x$ tables -- we change them (and they are
undocumented).

You cannot create these tables, they are burned into the kernel itself.  They
are compiled into Oracle -- they are not real tables, they are data structures.
Rating: 4
October 17, 2002
Reviewer: A reader
Tom,
Thanks for answering me.
My db is Oracle8i Enterprise Edition Release 8.1.7.3.0 - 64bit Production. I saw
that the "reader -07/19/02" who run the same script and also you had run the
script on a 8.1.7.3.0 version. So I should be able to run the script too, I mean
how can I do in order to create those table that script need.

Thanks for your help.
Rating: 4
Regarding missing x_$kcctf etc. October 17, 2002
Reviewer: Padders from UK
Regarding missing x_$kcctf etc. I believe Steve Adams typically creates a series
of views over x$ fixed tables and his scripts utilise these views. Script to
create so called 'x_$ views' is also available on his site.
Rating: 5
RE: ...in order to create those table... October 17, 2002
Reviewer: Mark A. Williams from Indianapolis, IN USA
RE: ...in order to create those table...

PMFJI...

Review Tom's answer...

1) The "x$ tables" are not "real" tables, they are structures that are burned
into the Oracle binary.

2) There is no script to create them.

3) They are not publicly documented for a reason...

4) Here's a small hint:  They, in fact, are present, you just don't know how to
access them... BUT you don't need to access them.

5) Ask yourself if there is a better way to get the information needed to tune
disk sorts.  For example, Statspack? Maybe v$sysstat? Or maybe chapters 19 and
20 of the "Oracle8i Designing and Tuning for Perfomance" guide?  There are
sections in those chapters dedicated to this exact topic.

If you really need to tune the sort_area_* parameters, you can get the
information you need from sources other than the "mystical, magic, and
all-powerful x$ tables"...

Anyway, just throwing a suggestion or two out...

- Mark
Rating: 3
OK February 11, 2004
Reviewer: Gerhard from Dusseldorf,Germany
Dear Sir,
Regarding x$views,Is there a way to use them or query them?Please specify some
of the tables you use.
Bye!
Followup:
I do not use x$ views.
Rating: 3
Any way to find out how much memory allocated for sort at any time March 11, 2004
Reviewer: Belinda from USA
Tom,

Is there any way to find out how many users concurrently doing sorting and being
allocate memory for sorting at a particular moment?  we would like to know the
high watermark of the memory allocations for sort.  our sort_area_size =16MB and
sort_area_retained_size =4MB

The V$sort_usage and v$sort_segment only tell us the sorts being carried out in
the temp tablespace, but we want  to know physical memory allocated for sorts at
a particular time.
Followup:
it isn't there -- you'd want to use pga_aggregate_target in 9i and that'll give
you this sort of detailed information.

the only thing you know in 8i is how many were done in memory (less then 16m),
how many were not (> 16m) really....
Rating: 3
March 11, 2004
Reviewer: A reader
Tom,

In 9.2, pga_aggregate_target is the upper bound for sort/hash memory, but how to
find out the amount of memory actually being used at any given point of time by
the processes?
Followup:
look at the statistics that begin with workarea...
March 12, 2004 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
Rating: 5
Loading XML into multiple table also condidering sequence numbers March 11, 2004
Reviewer: mwor from USA
Hi Tom,
Thanks very much for your help as always.

I have a xml and would like to import data into multiple tables.
Following is the sample xml
<A>
 <Name>Tom</Name>
 <B>
  <order>Order1</order>
  <C>
   <part>part1</part>
   <part>part2</part>
  </C>
 </B>
 <B>
  <order>Order2</order>
  <C>
   <part>part3</part>
  </C>
 </B>
</A>
<A>
 <Name>John</Name>
 <B>
  <order>Order3</order>
  <C>
   <part>part4</part>
  </C>
 </B>
</A>

The following is the output after importing data into 3 tables
Table A ( pk = A_ID )

A_ID( seq num )    | Name
________________|_____
A1        |Tom
A2        |John

TABLE B ( pk = B_ID )

B_ID( seq num )    |    A_ID    | oRDER_NAME
________________________________|___________
B1        |    A1    | order1
B2        |    A1    | order2
B3        |    A2    | order3

TABLE C ( pk = B_ID and Part_ID)
B_ID    |    Part_ID        | Part Name
_____________________________________________
B1    |    p1        | Part1
B1    |    p2        | part2
B2    |    p3        | part3
B3    |    p4        | part4

Problem Defination:

Currently I parse the xml in VB and insert 1 record in Table A, then using
select take the sequence num generated ( A1 ), parse xml  then insert another
record in Table B using A1, then using select get sequence number of Table B ,
parse xml and insert 1 record in Table C using sequnce number of Table B.. then
next record in Table A and so on..

Instead of VB program parsing xml and calling Oracle for each Insert then select
seq num again insert in another table , Can something be done on oracle end ?

Can I acheive something called bulk insert or some PL SQL routine or something
in oracle where I just pass the XML and configure which tables to insert (
properly reading the sequence of oe table and inserting into column of another
table ).
My xml can be huge with lots of records around 500,000.

Using VB and looping through each record in xml then insert/select each record
is extremely time consuming.

Thanks in advance
Wor
Followup:

http://otn.oracle.com/pls/db92/db92.docindex?remark=homepage#index-XML
shows you what is available in the DB, yes, you can send the XML straight to
plsql and parse it in the database and do whatever you want with it.
Rating: 5
sorry I forgot to mention I have Oracle 8.1.7 March 11, 2004
Reviewer: mwor from USA
Followup:
then most of what you want is in 9i
March 12, 2004 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.
Rating: 4
Extending this approach to multiple files February 06, 2004
Reviewer: Sue from UK
Hi Tom,

 I have similar requirements as
http://asktom.oracle.com/pls/ask/f?p=4950:8:7050804158237334641::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1443145941123,
 But along with this ... I need to do file validations as well. The file is
fixed width with header-trailer records.
Like this there are round 100 odd files with similar header-trailer but
different columns in detail records.

 Approach I was thinking was ... use UTL_File for file validations, then load
data in temporary tables for each file and update/insert/delete data from Oracle
table. But I am not very sure of how I can create a temporary table at run time
(using dynamic SQL) depending on file passed as input to UTL_FILE. Will you
please suggest how this can be done.

Thanks!!!
Followup:
why not use a single "temporary table"?

but, short of that, you could look at using an external table as well --

execute immediate 'create table .....';
Rating: 4
Extending this approach to multiple files February 06, 2004
Reviewer: Sue from UK
Hi Tom,

 I have similar requirements as
http://asktom.oracle.com/pls/ask/f?p=4950:8:7050804158237334641::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1443145941123,
 But along with this ... I need to do file validations as well. The file is
fixed width with header-trailer records.
Like this there are round 100 odd files with similar header-trailer but
different columns in detail records.

 Approach I was thinking was ... use UTL_File for file validations, then load
data in temporary tables for each file and update/insert/delete data from Oracle

table. But I am not very sure of how I can create a temporary table at run time
(using dynamic SQL) depending on file passed as input to UTL_FILE. Will you
please suggest how this can be done.

Database is Oracle 9.2

Thanks!!!
Rating: 4
Oracle Developer March 11, 2004
Reviewer: Dave from California, USA
Hello Tom,

I am loading data from one table to another table through  script below. Now I
want to insert one by one record/row. I mean not in the bulk. What else I need
to add in this script. Any help will be appreciated. Thanks

DECLARE

V_COUNTER NUMBER(10) :=0;

BEGIN

dbms_output.put_line('Start time -'||to_char(sysdate,'dd-mon-yy HH:MI:SS'));

INSERT INTO NFS_SUITABILITY_WORK_ARCHIVE
(
TRX_LOAD_ID,
PRG_EXEC_ID,
PREFIX,
ACCOUNT_NUM,
LAST_UPDATE_CD,
START_DATE,
INV_OBJ_PRS_OF_CAP_FLG,
INV_OBJ_INCOME_FLG,
INV_OBJ_CAP_APP_FLG,
INV_OBJ_SPEC_FLG,
OTHER_REASON,
OTHER_TEXT,
ANNUAL_INCOME_CD,
ANNUAL_INCOME_CD_DOLLARS,
NET_WORTH_CD,
NET_WORTH_CD_DOLLARS,
LIQUID_ASSETS_CD,
LIQUID_ASSETS_CD_DOLLARS,
PAST_INV_EQUITIES_FLG,
PAST_INV_BONDS_FLG,
PAST_INV_MUTUAL_FUNDS_FLG,
PAST_INV_OPTIONS_FLG,
PAST_INV_ANNUITIES_FLG,
RISK_TOLER_CD,
RISK_TOLER_TEXT,
TAX_BRACKET_NUM,
TIME_HORIZION_CD,
CUST_PREV_INV,
LOADED_FLG,
CREATE_USER_ID,
CREATE_DATE,
UPDATE_USER_ID,
UPDATE_DATE)

select
TRX_LOAD_ID,
PRG_EXEC_ID,
PREFIX,
ACCOUNT_NUM,
LAST_UPDATE_CD,
START_DATE,
INV_OBJ_PRS_OF_CAP_FLG,
INV_OBJ_INCOME_FLG,
INV_OBJ_CAP_APP_FLG,
INV_OBJ_SPEC_FLG,
OTHER_REASON,
OTHER_TEXT,
ANNUAL_INCOME_CD,
ANNUAL_INCOME_CD_DOLLARS,
NET_WORTH_CD,
NET_WORTH_CD_DOLLARS,
LIQUID_ASSETS_CD,
LIQUID_ASSETS_CD_DOLLARS,
PAST_INV_EQUITIES_FLG,
PAST_INV_BONDS_FLG,
PAST_INV_MUTUAL_FUNDS_FLG,
PAST_INV_OPTIONS_FLG,
PAST_INV_ANNUITIES_FLG,
RISK_TOLER_CD,
RISK_TOLER_TEXT,
TAX_BRACKET_NUM,
TIME_HORIZION_CD,
CUST_PREV_INV,
LOADED_FLG,
CREATE_USER_ID,
CREATE_DATE,
UPDATE_USER_ID,
UPDATE_DATE

from NFS_SUITABILITY_WORK
where loaded_flg not in ('N', 'E');
COMMIT;
-----Delete from NFS_SUITABILITY_WORK.
For cur_row_id IN
(SELECT ROWID
from NFS_SUITABILITY_WORK
where loaded_flg not in ('N', 'E'))
LOOP

DELETE FROM NFS_SUITABILITY_WORK
WHERE rowid = cur_row_id .rowid;

IF V_COUNTER = 1000 THEN
COMMIT;
V_COUNTER := 0;

ELSE
V_COUNTER := V_COUNTER+1;
END IF;
END LOOP;
COMMIT;

dbms_output.put_line('End time -'||to_char(sysdate,'dd-mon-yy HH:MI:SS'));

EXCEPTION
WHEN OTHERS THEN

DBMS_OUTPUT.PUT_LINE (SQLCODE|| ''|| SQLERRM);

END;-- nfs_archive_suit_work;

Dave
Followup:
you want to take a super efficient routine and make it process slow by slow
(opps, meant row by row)???

why?
March 12, 2004 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)
Rating: 5
No logging for temp table without index March 11, 2004
Reviewer: Habib from India
Hi..

My production database is running in archieve log mode.

I have one batch process (through pl/sql procedure), which does deletion and
insertion  in one TEMP table (ONE TABLE ONLY) based on group of tables.

This (TEMP table) is required only for reporting purpose. And Pl/sql procedure
runs in scheduler at 3 AM every day.

Since it is only a temporary updation and NO INDEX is available (in that temp
table). "I used delete and insert statement with No_logging option"

Now my question is: If my database is crashed and if I recover data, will I face
any problem OTHER THAN LOOSING THE DATA of temporary (TEMP) table?

Waiting for your reply!
Followup:
well, delete is ALWAYS logged.

insert is only "not logged" if you use APPEND and the table is nologging and
ONLY the table data is not logged (the index data is always logged unless you
drop/set unusable -- then load the table - then build/rebuild with nologging)

Only the data in the non-logged object would be affected, the rest of the
database is "OK"
March 12, 2004 greeshma -- Thanks for the question regarding "database trigger -- same type triggers fire in random order", version oracle 8.0.5
You Asked
hi!tom
i have two t1,t2 row type triggers  after insert
or delete or update firing on a
single table parent table simelteneously.trigger t1 pops up data
into a parent
temp table and t2 when fired goes to the child table
and selects more than one child rec.corresponding
with a common column from the parent and dumps data into
another temp table.
iam passing a sequence number into both the tables.where
parent temp table takes the nextval and child temp table
takes the corrval of the sequence.
but when the triggers are fired i am getting an error saying
ERROR at line 1:
ORA-08002: sequence t_SEQ.CURRVAL is not yet defined in this
session
ORA-06512: at "GL.t2", line 66
ORA-04088: error during execution of trigger 'GL.t2'
kindly help me.

thankx
and we said...
sounds like you are counting on the triggers to fire in some
order but -- they do not.  All same type triggers fire in some
undefined order.  You cannot rely on T1 to fire before T2.  You
should instead write a procedure P1 and a procedure P2 and one
trigger T that calls P1 and then P2 in order if they must be
ordered.

Here is an example showing I can make it work (or not) simply by
dropping and recreating the triggers. Do not count on the
"apparent" order of last created trigger fires first -- it is a
fluke -- it is not guaranteed -- you must procedurally order the
triggers as I described above to make this work correctly.

ops$tkyte@DEV816> create table t ( x int );
Table created.

ops$tkyte@DEV816> create sequence my_seq;
Sequence created.

ops$tkyte@DEV816> create trigger t1
  2  after insert or update or delete on T
  3  for each row
  4  begin
  5          for x in ( select my_seq.nextval from  dual )
  6          loop
  7                  null;
  8          end loop;
  9  end;
 10  /

Trigger created.

ops$tkyte@DEV816> create trigger t2
  2  after insert or update or delete on T
  3  for each row
  4  begin
  5          for x in ( select my_seq.CURRVAL from  dual )
  6          loop
  7                  null;
  8          end loop;
  9  end;
 10  /
Trigger created.

ops$tkyte@DEV816> insert into t values ( 1 );
insert into t values ( 1 )
            *
ERROR at line 1:
ORA-08002: sequence MY_SEQ.CURRVAL is not yet defined in this
session
ORA-06512: at "OPS$TKYTE.T2", line 2
ORA-04088: error during execution of trigger 'OPS$TKYTE.T2'

Here, t2 happened to fire before t1.  dropping and recreating
the triggers will change this behavior (MAYBE, MAYBE NOT -- it
is quite simply "not defined")

ops$tkyte@DEV816> drop trigger t1;
Trigger dropped.

ops$tkyte@DEV816> drop trigger t2;
Trigger dropped.

ops$tkyte@DEV816> create trigger t2
  2  after insert or update or delete on T
  3  for each row
  4  begin
  5          for x in ( select my_seq.CURRVAL from  dual )
  6          loop
  7                  null;
  8          end loop;
  9  end;
 10  /

Trigger created.

ops$tkyte@DEV816> create trigger t1
  2  after insert or update or delete on T
  3  for each row
  4  begin
  5          for x in ( select my_seq.nextval from  dual )
  6          loop
  7                  null;
  8          end loop;
  9  end;
 10  /

Trigger created.

ops$tkyte@DEV816>
ops$tkyte@DEV816> insert into t values ( 1 );

1 row created.

ops$tkyte@DEV816>
Review & Followup

Rating: 2
sequence and child table March 11, 2004
Reviewer: mike from new jersey
Hi Tom,

I think this is related. How can I use my sequence
number in my master table for my child table?
 For example. If let's say it was 5 in my master table I want to be able to use
5 to create various records in my child table.
Followup:
use sequence.currval when processing the child.
Rating: 3
Review March 12, 2004
Reviewer: A reader from India
Sir, but in case of a multi user scenario, the currval may not have the same
value as that inserted in the master table.Is that right?
Followup:
currval/nextval would be *useless* if they did not behave correctly in a
multi-user environment.

Your sessions CURRVAL is the value of YOUR last nextval call.  You'll never
never never get some other sessions "currval"
Rating: 5
Using a sequence for both parent and child March 12, 2004
Reviewer: Mike from Cleveland, OH USA
In addition to the CURRVAL technique, you can use a technique like:

INSERT INTO PARENT_TB
(parent_key)
VALUES (PARENT_SEQ.nextval)
RETURNING parent_key INTO v_parent_key;

INSERT INTO CHILD_TB
(parent_key, child_key, other_child_stuff)
VALUES (v_parent_key, v_child_key, v_other_child_stuff);
March 12, 2004 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.
Rating: 3
Deffered Segments March 11, 2004
Reviewer: Jayesh Antony Jose from Chennai, Cochin
Hello Tom,

Can you tell what is purpose of deffered undo segments? After taking a
tablespace offline without giving any commit, when i take it back online the
transactions are not rolled back unless i sepcify rollback...So what is the
point in saying that deffered undo segments are used to rollback transactions
when a tablespace is put back online..

Also when will i take a tablespace offline "FOR RECOVER"? Is it same as ALTER
TABLESPACE BEGIN BACKUP
Followup:
Ok, you take a tablespace offline -- what happens is we migrate the rollback
data for any outstanding transaction OUT of the "normal" rollback segments into
the system rollback segment.  Why do we do this?  Well, the offlining of the
tablespace indicates "something really bad is going on".  It generally takes "a
while" to recover from something really bad (more then a couple of minutes).
Since we cannot just remove that rollback (we NEED it) we need to keep it
around.  If we kept it around in the "normal" rollback segments -- you would
find they grow and grow and grow cause we use rollback in a circular fashion --
we would not be able to wrap around and reuse the extents these active
transactions were in.  They become like long running transactions at this point
that you cannot commit OR rollback (since their tablespace is offline
currently).

So, in order to avoid the issue that your RBS would grow and grow and grow due
to these long running transactions - we move their rollback data into this
deferred place -- allowing the rbs to reuse the space.

No, a for recover is not like a begin backup.  begin backup simply says "please
log a full block image the first time a block is modified" -- it is for backup
purposes.  We still write to tablespaces (to their datafiles) during a backup --
we never stop writing to the files.
Rating: 3
Deffered Segments March 12, 2004
Reviewer: Jayesh Antony Jose from Chennai, India
Hello Tom,
-------------------------------------------------------------------
| SESSION 1                                        |
-------------------------------------------------------------------
11:59:10 JAYESH@SQL>CONN / AS SYSDBA
Connected.
11:59:22 JAYESH@SQL>CREATE USER DEMO IDENTIFIED BY SQL
11:59:36   2  DEFAULT TABLESPACE USERS
11:59:43   3  QUOTA 5M ON USERS;

User created.

11:59:51 JAYESH@SQL>GRANT CONNECT,RESOURCE TO DEMO;

Grant succeeded.

12:00:22 JAYESH@SQL>CONN DEMO/SQL
Connected.
12:00:52 JAYESH@SQL>CREATE TABLE EMP
12:01:00   2  (EMPNO NUMBER,
12:01:06   3  ENAME VARCHAR2(20));

Table created.

12:02:21 JAYESH@SQL>INSERT INTO EMP VALUES(10,'JAYESH');

1 row created.

12:05:02 JAYESH@SQL>INSERT INTO EMP VALUES(20,'AJESH');

1 row created.

12:05:06 JAYESH@SQL>SELECT * FROM EMP;

     EMPNO ENAME
---------- --------------------
        10 JAYESH
        20 AJESH

-------------------------------------------------------------------
| SESSION 2                                        |
-------------------------------------------------------------------
SQL> CONN / AS SYSDBA
Connected.
SQL> SELECT TABLESPACE_NAME,STATUS FROM DBA_TABLESPACES;

TABLESPACE_NAME                STATUS
------------------------------ ---------
SYSTEM                         ONLINE
UNDOTBS                        ONLINE
TEMP                           ONLINE
USERS                          ONLINE
TEST                           ONLINE
JAY                            ONLINE
PERFSTAT                       ONLINE

7 rows selected.

SQL> ALTER TABLESPACE USERS OFFLINE IMMEDIATE;
ALTER TABLESPACE USERS OFFLINE IMMEDIATE
*
ERROR at line 1:
ORA-01145: offline immediate disallowed unless media recovery enabled

SQL> ALTER TABLESPACE USERS OFFLINE TEMPORARY;

Tablespace altered.

SQL> SELECT TABLESPACE_NAME,STATUS FROM DBA_TABLESPACES;

TABLESPACE_NAME                STATUS
------------------------------ ---------
SYSTEM                         ONLINE
UNDOTBS                        ONLINE
TEMP                           ONLINE
USERS                          OFFLINE
TEST                           ONLINE
JAY                            ONLINE
PERFSTAT                       ONLINE

7 rows selected.

-------------------------------------------------------------------
| SESSION 1                                        |
-------------------------------------------------------------------
12:05:14 JAYESH@SQL>SELECT * FROM EMP;
SELECT * FROM EMP
              *
ERROR at line 1:
ORA-00376: file 3 cannot be read at this time
ORA-01110: data file 3: 'E:\DEMO\DATA\USERS01.DBF'

-------------------------------------------------------------------
| SESSION 2                                        |
-------------------------------------------------------------------

SQL> ALTER TABLESPACE USERS ONLINE;

Tablespace altered.

-------------------------------------------------------------------
| SESSION 1                                        |
-------------------------------------------------------------------
12:09:04 JAYESH@SQL>SELECT * FROM EMP;

     EMPNO ENAME
---------- --------------------
        10 JAYESH
        20 AJESH
############################################################################

Even if i don't commit the data the records are commited to the database..The
same is the case with taking tablespace OFFLINE NORMAL.

So what scenario will you make use of the deffered segments?

Iam able to ROLLBACK my transaction when the tablespace is offline. Then when
the tablespace is brought backonline these transactions are rolled back.
Followup:
No they are not (committed).  committed means they are visible to other
sessions, that they are would be there if the database was shutdown aborted --
but they would not be -- not until you commit.

you didn't issue "rollback" so of course the records are "there".  if you rolled
back -- poof, they would go away.
March 12, 2004 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
Rating: 3
OK February 19, 2004
Reviewer: Carl from TX,USA
Dear Tom,
 when we set formatting commands like
  sql> set pagesize 0
  sql> set linesize 80
  sql> set null missing
in sql*plus, How to cancel them ?Is there any unset or
clear commands for them?please do reply.
Followup:
you just set them "back".  column formatting has a "clear", but things like this
-- you just set back to whatever you want them to be.

if you want to provide a script that changes these but puts them back to
"whatever they were before the script was run", you can use store, eg:

ops$tkyte@ORA920PC> show pagesize
pagesize 9999                            <<<<=== my default from my login.sql
ops$tkyte@ORA920PC> store set x rep      <<<<=== you would do this
Wrote file x
ops$tkyte@ORA920PC> set pagesize 0       <<<<=== before this
ops$tkyte@ORA920PC> show pagesize
pagesize 0
ops$tkyte@ORA920PC> @x                   <<<<=== so that this
ops$tkyte@ORA920PC> show pagesize
pagesize 9999                            <<<<=== will put it back.
Rating: 5
evaluate March 12, 2004
Reviewer: Phil
I am sure this is a no brainer...

what I would like to do is take a .sql file with a paramater ...

@test.sql <<optional param1>>

in test.sql I have
define param2 = 'TEST'

what I want to do is set param2 = param1 if param1 is passed otherwise use the
default text.

can I do this in sqlplus ?
Followup:
You can use ACCEPT with the DEFAULT clause, but the user will have to hit the
enter key.
Rating: 5
evaluate .... March 12, 2004
Reviewer: phil
addition from above ..

ultimatly what I need to do is in my script ( test.sql ) , i will have something
on the lines of the following ...

SQL> create index test_idx on test(status) tablespace &&param2 ;

param2 may be the passed in value if there was one ... otherwise it will be
passed in value.

thanks
Rating: 5
he's the king! March 25, 2004
Reviewer: A reader
HE'S THE KING!!
March 12, 2004 venkat -- Thanks for the question regarding "Serializing Java Objects into the database (and getting them back out)", version version 8.1.6
You Asked
Hi,

How do I serialize Java objects into Oracle database and
vice-versa.

thanks & regards
Venkat Vudithyala
and we said...
For this one, I asked my resident java guru Mark Piermarini
(mbpierma@us.oracle.com) to come up with an example.  Below are
two examples he came up with -- the first with minimal error
checking and code shows an example of

o creating an object
o serializing it into the database
o reading it back out
o printing it.

The second, more robust example with comments is a little more
flexible.  It will serialize a java instance into the database
if passed no inputs.  After it puts it in -- it'll print out the
"id" for that object instance.  Later, you can run this same
java example and pass it that id -- it'll read out that object
instance and print it out, showing how to make this work across
invocations.

Things you will need to run this example in this case are:

create sequence java_obj_seq;

create table java_objects(
   id number,
   classname varchar2(2048),
   bytes blob default empty_blob()
)

We use the sequence to create a "primary key" for out object
instances.  We need this number later to retrieve a specific
instance.  The table is what we use to store the object
instance.  Here we store the classname of the original java
instance in a varchar2 field and in the BLOB we'll store the
bytes of the serialized object.

Here is the first small example:

import java.lang.*;
import java.sql.*;
import java.util.*;
import oracle.jdbc.driver.*;
import oracle.sql.*;

public class serial2 {
   static String genID =
         "select java_obj_seq.nextval from dual";

   static String writeSQL =
       "begin insert into java_objects(id,classname,bytes) "+
             " values (?,?,empty_blob()) " +
             " return bytes into ?; end;";

   static String readSQL =
       "select bytes from java_objects where id = ?";

   public static long write(Connection conn, Object o)
   throws Exception
   {
      long id = nextval(conn);
      String className = o.getClass().getName();
      CallableStatement stmt = conn.prepareCall(writeSQL);
      stmt.setLong(1, id);
      stmt.setString(2, className);
      stmt.registerOutParameter(3, java.sql.Types.BLOB);
      stmt.executeUpdate();
      BLOB blob = (BLOB) stmt.getBlob(3);
      OutputStream os = blob.getBinaryOutputStream();
      ObjectOutputStream oop = new ObjectOutputStream(os);
      oop.writeObject(o);
      oop.flush();
      oop.close();
      os.close();
      stmt.close();
      System.out.println("Done serializing " + className);
      return id;
   }

   public static Object read(Connection conn, long id)
   throws Exception
   {
      PreparedStatement stmt = conn.prepareStatement(readSQL);
      stmt.setLong(1, id);
      ResultSet rs = stmt.executeQuery();
      rs.next();
      InputStream is = rs.getBlob(1).getBinaryStream();
      ObjectInputStream oip = new ObjectInputStream(is);
      Object o = oip.readObject();
      String className = o.getClass().getName();
      oip.close();
      is.close();
      stmt.close();
      System.out.println("Done de-serializing " + className);
      return o;
   }

   private static long nextval(Connection conn)
   throws SQLException
   {
      Statement stmt = conn.createStatement();
      ResultSet rs = stmt.executeQuery(genID);
      rs.next();
      long id = rs.getLong(1);
      rs.close();
      stmt.close();
      return id;
   }

   public static void main(String[] argv)
   throws Exception
   {
      String cs = "jdbc:oracle:oci8:@ora8idev";
      String user = "scott";
      String pass = "tiger";

      DriverManager.registerDriver(new OracleDriver());
      Connection conn =
          DriverManager.getConnection(cs, user, pass);
      conn.setAutoCommit(false);

      LinkedList l = new LinkedList();
      l.add("This");
      l.add("is");
      l.add("a");
      l.add("test");
      l.add(new Long(123123123));
      l.add(new java.util.Date());

      long id = write(conn, l);
      conn.commit();

      System.out.println("ID= " + id);
      System.out.println("Object= " + read(conn, id));
      conn.close();
   }
}

Now, if we run the code:

$ /usr/java1.2/bin/java serial2
Done serializing java.util.LinkedList
ID= 1
Done de-serializing java.util.LinkedList
Object= [This, is, a, test, 123123123, Sun Jan 21 09:45:21 EST
2001]

So, according to that, the object has been serialized and stored
in the database.  In fact, we can log in using sqlplus and see
it if we like:

scott@DEV816> create or replace function clean( p_raw in blob )
              return varchar2
  2  as
  3      l_tmp long default utl_raw.cast_to_varchar2(
                              dbms_lob.substr(p_raw,2000,1));
  4      l_char   char(1);
  5      l_return long;
  6  begin
  7      for i in 1 .. length(l_tmp)
  8      loop
  9          l_char := substr( l_tmp, i, 1 );
 10          if ( ascii(l_char) between 32 and 127 )
 11          then
 12              l_return := l_return || l_char;
 13          else
 14              l_return := l_return || '.';
 15          end if;
 16      end loop;
 17      return l_return;
 18  end;
 19  /

Function created.

scott@DEV816>
scott@DEV816> select id, classname,
  2         dbms_lob.getlength(bytes) len,
  3         clean(bytes) clean
  4    from java_objects
  5  /

        ID CLASSNAME             LEN CLEAN
---------- -------------------- ---- --------------------
         1 java.util.LinkedList  191 ....sr..java.util.Li
                                     nkedList.)S]J`."...x
                                     pw.....t..Thist..ist
                                     ..at..testsr..java.l
                                     ang.Long;.....#....J
                                     ..valuexr..java.lang
                                     .Number...........xp
                                     .....V..sr..java.uti
                                     l.Datehj..KYt....xpw
                                     .....1...xx

So, we see now how to serialize using a BLOB as the input stream
and deserialize using the BLOB again.  We can also review the
contents (sort of) in SQLPlus.

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

Now for the longer, more robust example with error handling and
comments.  You have all you need above -- the following is just
a little more sophisticated.  After you compile this:

$ /usr/java1.2/bin/javac serial.java

we can run:

$ /usr/java1.2/bin/java serial 1
Done de-serializing java.util.LinkedList
Object= [This, is, a, test, 123123123, Sun Jan 21 09:45:21 EST
2001]

(after running the first example serial2.java that is).  All
this did was read out our existing java instance and "rebuild
it" for us and then print it out.  You can run serial without
any arguments to create new instances of the linked list and
read them back out by ID later.  Here is the code:

import java.io.*;
import java.lang.*;
import java.sql.*;
import java.util.*;
import oracle.jdbc.driver.*;
import oracle.sql.*;

public class serial {
   static String genID =
        "select java_obj_seq.nextval from dual";

   static String writeSQL = "begin insert into java_objects "+
           " (id,classname,bytes) values (?,?,empty_blob()) "+
           " return bytes into ?; end;";

   static String readSQL =
       "select bytes from java_objects where id = ?";

   // We attempt to serialize the object to the database table.
   // We use a
   // sequence number to identify the object and return that
   // value to the
   // caller. We also save the class name in case someone wants
   // to search
   // for objects based on that name. If we get any exception,
   // we rethrow
   // it to the caller but we always try to clean up after
   // ourself.

   public static long write(Connection conn, Object o)
   throws Exception
   {
      ObjectOutputStream oop = null;
      CallableStatement stmt = null;
      OutputStream os = null;
      long id = nextval(conn);
      String className = o.getClass().getName();

      try {
         stmt = conn.prepareCall(writeSQL);
         stmt.setLong(1, id);
         stmt.setString(2, className);
         stmt.registerOutParameter(3, java.sql.Types.BLOB);
         stmt.executeUpdate();
         BLOB blob = (BLOB) stmt.getBlob(3);
         os = blob.getBinaryOutputStream();
         oop = new ObjectOutputStream(os);
         oop.writeObject(o);
         oop.flush();
      } catch (Exception e) {
         throw e;
      } finally {
         if (oop  != null) { oop.close(); }
         if (os   != null) { os.close(); }
         if (stmt != null) { stmt.close(); }
         System.out.println("Done serializing " + className);
      }

      return id;
   }

   // We attempt to de-serialize the object from the database
   // table using
   // the given identifier. If we get any exception, we rethrow
   // it to the
   // caller but we always try to clean up after ourself.

   public static Object read(Connection conn, long id)
   throws Exception
   {
      ObjectInputStream oip = null;
      PreparedStatement stmt = null;
      InputStream is = null;
      Object o = null;
      String className = null;

      try {
         stmt = conn.prepareStatement(readSQL);
         stmt.setLong(1, id);
         ResultSet rs = stmt.executeQuery();

         // Even though we expect only one row back, the caller
         // could have
         // passed an invalid identifier so we wrap this in a
         // loop to
         // make sure we don't get null pointer exceptions. In
         // the case
         // where there are 0 rows, we would return a null
         // value. Where
         // there are > 1 rows, we would return only the first
         // one.

         while (rs.next()) {
            is = rs.getBlob(1).getBinaryStream();
            oip = new ObjectInputStream(is);
            o = oip.readObject();
            className = o.getClass().getName();
            break;
         }
      } catch (Exception e) {
         throw e;
      } finally {
         if (oip  != null) { oip.close(); }
         if (is   != null) { is.close(); }
         if (stmt != null) { stmt.close(); }
         System.out.println("Done de-serializing " + className);
      }

      return o;
   }

   // Get the next sequence value. Ideally, this would be better

   // if
   // we just created a prepared statement and repeatedly used
   // that.
   // Obviously, we must assume the same connection is always
   // used.

   private static long nextval(Connection conn)
   throws SQLException {
      long id = -1;
      Statement stmt = null;
      ResultSet rs = null;

      try {
         stmt = conn.createStatement();
         rs = stmt.executeQuery(genID);

         while (rs.next()) {
            id = rs.getLong(1);
         }
      } catch (SQLException e) {
         throw e;
      } finally {
         if (rs != null) { rs.close(); }
         if (stmt != null) { stmt.close(); }
      }

      return id;
   }

   // Here we just simply connect to the database and either
   // serialize or de-serialize an object. If we get an
   // exception then we will rollback whatever we have done.
   // Commit changes as necessary and close the connection.
   public static void main(String[] argv) throws Exception {
      long id;
      Connection conn = null;
      String cs = "jdbc:oracle:oci8:@ora816dev";
      String user = "scott";
      String pass = "tiger";

      try {
         DriverManager.registerDriver(new OracleDriver());
         conn = DriverManager.getConnection(cs, user, pass);
         conn.setAutoCommit(false);

         if (argv.length == 0) {
            id = write(conn, new java.util.Date());
            conn.commit();
            System.out.println("ID= " + id);

            LinkedList l = new LinkedList();
            l.add("This");
            l.add("is");
            l.add("a");
            l.add("test");
            id = write(conn, l);
            conn.commit();
            System.out.println("ID= " + id);
         } else {
            id = Long.parseLong(argv[0]);
            Object o = read(conn, id);
            System.out.println("Object= " + o);
         }
      } catch (Exception e) {
         e.printStackTrace();
         if (conn != null) { conn.rollback(); }
      } finally {
         if (conn != null) { conn.close(); }
      }
   }
}
Review & Followup

Rating: 5
"Serializing Java Objects into the database (and getting them back out)", version version 8.1.6 April 30, 2001
Reviewer: Brad Bittiker from San Francisco, CA USA
Excellent.  This is exactly what I was looking for.
Rating: 5
Very Useful but ..... May 31, 2002
Reviewer: Damien Michalosky from Cambridge, MA USA
It is a great alternative to what I am actually looking for. I wanted to write a
Java Stored Function that returns a Java Object. It looks like that is not
possible for my research but serializing an object created by a Java Stored
Procedure is possible and can be retrieved by an outside class. Any other ideas?
Please post or email me.
Followup:
regardless of the method -- you will have to have a serializable class (we are
going from JVM to JVM after all -- not within a single JVM)

So, given that, I think this would be the most straight forward way.  You can
use a temporary blob instead of a table and have your java stored procedure
return a blob.  Your java client code will

a) connect
b) run the java stored procedure
c) get the blob back to the java client
d) deserialized it
Rating: 4
How about serialized object to stored java procedure? October 18, 2002
Reviewer: Chris Bruun from Vienna VA
This example is great for updating an object table.  How can serialized objects
be passed to and deserialized by a java stored procedure?
Followup:
Well, you could just use this technique -- the caller would serialize into a LOB
(Temporary lob for example) and pass the LOB locator to the java stored proc.
Rating: 5
java object March 12, 2004
Reviewer: Phil
is it possible to

 pass a Java object to an Oracle stored procedure, the object would be compiled
from an XML schema and would hold the content of an XML file. To do something (
eg create a record) a stored procedure would then extract the relevant values
from the object, presumably they would be accessible as properties, then process
the values ????

not possible, totally foolish ?

thanks
Followup:
you could theoretically serialize the java object into a blob, send it to the
database, unserialized it but....

I don't see the tie in between a java "object" (instance) and XML?

If you have XML, just send XML to the db -- lots of capabilities in there to
process it.
http://otn.oracle.com/pls/db92/db92.docindex?remark=homepage#index-XML

Rating: 5
March 12, 2004
Reviewer: phil
I want to validate the the xml against an xml schema first - is that possible in
Oracle?

"read the doco phil!!"

thanks
March 12, 2004 Anthony -- Thanks for the question regarding "ora-4068", version 8.1.7.2.1 EE
You Asked
HI TOM,

Tnx for this oppurtunity again, Can You explain how does the ORA-4068 occurs and
how can I go about it?

How does oracle manages the freelist (8i) on LMT's? And how can we track the
freespace, used space, and fragmentation on LMT's?

What's the difference between creating the index with parallel option and not in
parallel mode ?

TNX
NOTNA
and we said...
That error occurrs when

- you have a package
- someone used the package
- the package maintains a state
- you recreate the package, killing their state.

Freelists on LMT's are no different then freelists on DMT's.  Freelists are
based on SEGMENTS, not tablespaces.  LMT's do not get fragemented, that is one
of their properties.  No need to track that which does not happen.

Search for free.sql on this site to get a script that'll list free space, used
space and more by tablespace.

As for the index -- isn't it obvious?  One creates the index using more then one
thread/process (in parallel).  The other -- doesn't.
Review & Followup

Rating: 3
ora-4068 April 24, 2002
Reviewer: Anthony Velarde from Philippines
Actually here's what's happenning,

    We have a two schema on one DB and two separate folders for the FMX. One for
development and one for QA. When we apply a fix on a certain procedure we
development was updated then after testing on development we update the QA
schema and recompile the forms from QA folder. When we execute the FMX on the QA
folder it's running well, but when we execute the FMX using the QA folder on the
DEVELOPMENT schema (I assure you that nobody updated the DEVELOPMENT schema
after the update on QA schema) we encountered the error ora-4068, but when we
compile the FMX from the QA folder using the DEVELOPMENT environment the problem
disappears. Sometimes I change the FORMS60_PATH from the regedit and the problem
disappears again (again I assure you that nobody is compiling procedures or
forms on both environments.
 I hope you can give me an explaination on this.
TNX.
ANTHONY
Followup:
This is fixed by simply re-executing the package.  The default mode of checking
to see if things have changed is a timestamp mode.  When you compiled the forms
against different databases, the timestamps are different.

You can also look into using remote_dependencies_mode = signature mode instead
of timestamp (database level parameter)
Rating: 5
changing it to SIGNATURE March 12, 2004
Reviewer: Pushparaj Arulappan from NJ, UAS
Tom,

In all our production instances we currently have
remote_dependencies_mode is set to "TIMESTAMP"
and we are planning to change that to "SIGNATURE".

My question is, after changing the parameter to
"SIGNATURE", do we need to re-compile all the
stored programs and the packages.

These programs are called from many other remote databases.

Thanks
Pushparaj
Followup:
no, you do not
March 12, 2004 Rahul -- Thanks for the question regarding "Heterogeneous replication", version 9.2.0.4
You Asked
Tom,

I would like to setup hetrogenous replication from Oracle to MS-SQL,
after visiting websites I noticed many of them mentioning Oracle does not
support hetrogenous replication. I was thinking in the direction of Transparent
Gateways.

Can you please comment on the above if i am going in the right direction if not
then can you please suggest some other solution.

Thanks
Rahul.
and we said...

http://otn.oracle.com/pls/db92/db92.drilldown?remark=&word=heterogeneous+replication&book=&preference=
streams can do that.

specifically:
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96571/repsingdemo.htm#54726
just c/Sybase/Sqlserver/ in the example.
Review & Followup

Rating: 5
March 12, 2004
Reviewer: A reader
Thanks.
March 12, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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.
Rating: 4
setting linesize question February 03, 2004
Reviewer: james su from Toronto CA
hi Tom,

set linesize 300
select lpad('*',200,'1234567890') from dual;

in sqlplus it works well, but in sqlplusw only the first 100 characters are
output. why is that? (sql*plus version 9.2.0.1.0)
Followup:
"shake" the window :)

resize it a bit after the set linesize and see what happens........

(and then just use sqlplus.exe -- ugh, i don't like that sqlplusw.exe and it is
going away in the next release as well)
Rating: 4
doesn't work February 03, 2004
Reviewer: James Su from Toronto CA
I shook and shook and shook -- but nothing happened, the output remained the
same. I am working in 1024*768.

if you try this:

set linesize 101
select lpad('*',200,'1234567890') from dual;

you will get two lines output and the 101st character '1' is missing.
Followup:
sorry -- don't run any software that even allows me to fire up sqlplusw.exe.

I'll have to refer you to support.
Rating: 4
Works for me! February 03, 2004
Reviewer: A Reader from Dallas, TX
SQL*Plus: Release 9.2.0.1.0 - Production on Tue Feb 3 10:22:26 2004

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

SQL> set linesize 101
SQL> select lpad('*',200,'1234567890') from dual;

LPAD('*',200,'1234567890')
---------------------------------------------------------------------------------
--------------------
123456789012345678901234567890123456789012345678901234567890123456789012345678901
23456789012345678901
234567890123456789012345678901234567890123456789012345678901234567890123456789012
34567890123456789*

SQL>
Rating: 4
so what's wrong with my sqlplusw ? February 03, 2004
Reviewer: James Su from Toronto CA
hi Tom,

did you try it in sqlplusw ?
below is what I got in sqlplusw (it's fine in sqlplus):

SQL*Plus: Release 9.2.0.1.0 - Production on Tue Feb 3 12:14:29 2004

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

james@DEV2.US.ORACLE.COM> set linesize 101
james@DEV2.US.ORACLE.COM> select lpad('*',200,'1234567890') from dual;

LPAD('*',200,'1234567890')
---------------------------------------------------------------------------------
-------------------
123456789012345678901234567890123456789012345678901234567890123456789012345678901
2345678901234567890
234567890123456789012345678901234567890123456789012345678901234567890123456789012
34567890123456789*

james@DEV2.US.ORACLE.COM> set linesize 300
james@DEV2.US.ORACLE.COM> select lpad('*',200,'1234567890') from dual;

LPAD('*',200,'1234567890')
---------------------------------------------------------------------------------
-------------------
123456789012345678901234567890123456789012345678901234567890123456789012345678901
2345678901234567890

james@DEV2.US.ORACLE.COM>
Rating: 1
SQL PLUS Command Line interface February 03, 2004
Reviewer: Jeff from Miami
I can't believe no one has mentioned TOAD or SQL Navigator.

I can run circles around a sql_plus user with either of these gui tools.

Why doesn't Oracle come out with nice gui interface like toad?  Perhaps OEM is
such a tool, but in our environment only DBA's can use this tool.

Where is the command history in sql plus?
Where are the object browsers?
Where are the convenient save results as XLS, CSV, clipboard?
Where is the find/replace?

Every command must be typed in. (slow)

sql plus is a very cludgy pre-1990 (i.e. DOS) interface at best.

I always work in a gui interface tool when possible.  It was proven 15 years ago
that gui interface is more productive.

SQL Plus has long learning curve.  Not good for end users, only veteran Oracle
users.

Hope this helps.
Followup:
and I can run circles around your circles with sqlplus ;)

Jdeveloper is the tool you are talking about.

It has the command history.
object browsers
save results as
find replace

full plsql source level debugger
and more...

Where is the "proof" that a gui tool is more efficient.  Have you run your gui
tool over a slow link recently - the gui only people are totally lost without
their gui's.

sqlplus with a long learning curve?  you have got to be kidding me - there ain't
much to it.

Lets see your gui participate in a nightly unload of data at 2am (while you are
asleep).  Or be used over dialup, ISDN, wireless aircards....

Hmmm -- different strokes, for different folks I guess.
Rating: 5
Tastes Great AND Less Filling February 03, 2004
Reviewer: Chuck Jolley from OKC, OK USA
Why would anyone confine themselves to either type of tool?
I run a three monitor PC (XP) and frequently have a copy of toad or two and two
or three copies of sqlplus (cmd line) all running at once.
Cutting and pasteing between all of them.
Why not? The tools all have their strengths and we all have our different styles
(mine is centered around my poor typing skills, if you must know ;) )

As for the OS thing, most DBAs and programmers have absolutely no choice but to
use the OS the shop uses.
I happen to like XP, but if they decided to change to *nix tommorow, it's not
like I'd quit my job over it.
Who would? And why?

And with VPN and all the fine remote desktop software available now, bandwidth
should not be much of a factor in choosing your tool anymore.

chuck

PS You need a new domain for Q&As that get side tracked like this.
I nominate: DiscussWithTom.com
Rating: 4
Jdeveloper February 04, 2004
Reviewer: Robert
I like many others I imagine, use a mixture of toad and sqlplus.  I have tried
jdeveloper and the fact that it is written in java makes it way too slow and
frustrating to use.

I think you should use the tools at your disposal - but also that you must know
sqlplus because as you said before Tom, it is the only tool that every
installation will have.
Followup:
I've found jdev quite snappy really - especially in light of the fact that toad
et. al. do not even run on the machines I use ;)
Rating: 5
Fun experiment February 04, 2004
Reviewer: Chuck Jolley from OKC, OK USA
[quote]
...toad et. al. do not even run on the machines I use ;) ...
[/quote]

Every once and a while I install Linux on a machine at home to see how it's
comming along.
I'll see if I can get TOAD to run in an emulator next time and let you know ;)
chuck
Followup:
Linux has arrived -- totally.  there isn't a tool you need that isn't already
there.

I can even rip and watch my DVD's very nicely...

I actually do run word and ppt -- using crossover office, it could probably run
toad if I really needed to.

vmware comes in handy for those pesky websites that demand IE....  running
windows in a window -- very nice, ironic in a way -- but very safe.
Rating: 5
ok what is the *marvellous* thing February 04, 2004
Reviewer: A reader
Hi

What is the marvellous new tool you were talking about 8 months ago, now that
10g is released?
Followup:
see
http://htmldb.oracle.com/
works with 9iR2 and up -- comes with a nifty sql workshop that I like alot.
Rating: 5
erm sqlplusw is still in 10g February 04, 2004
Reviewer: A reader
I think this sqlplus GUI is in 10g...? I dont think it's deprecated yet am I
wrong?
Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:594023455752#8847527988426
You are right -- it could still be there -- I just haven't touched or looked at
the windows port of 10g at all.

It was announced with 9.0.1 that sqlplusw.exe will go away -- the date of its
demise is not yet firm...
Rating: 4
Shell-like navigating is possible in sqlplus under hp-ux !!! February 05, 2004
Reviewer: Maik Rabe from Germany, Stuttgart
Hi Tom  and all other friends,

With HP-UX there IS a possibility to go though the last commands in sqlplus like
in the ksh.

Set the following alias (in your .profile):
alias sqlplus="ied sqlplus"

and start sqlplus.

Now you can go back/forward in your history with ESC-k/ESC-j, search in your
history with ESC-/ and so on....

Hope, that your are glad now!

Maik
Rating: 3
pfft February 06, 2004
Reviewer: James Blanding from Kingston, RI USA
<quote>I always work in a gui interface tool when possible.  It was proven 15
years ago that gui interface is more productive.</quote>

Ha.  Where is the URL for your so-called proof?  Like Tom said, different
strokes for different folks.  Or to put it another way, *you* use the tool that
*you* find works best for *your* task.

Personally, I find that >90% of the time, I can type a command (or series of
commands) significantly faster than I can direct a 4-pixel-square pointer (the
tip of the arrow, that is) around a maze of icons, menus, and fields.  The only
real hangup is web browsing, since there are so many damn sites out there
nowadays that are flash-based or that have too many links to efficiently tab
through.

But I dont go around defending my personal preferences with undocumented proofs
:P  That's why they're called "personal" preferences.

Anyway, there is a GUI-based SQL-IDE-etc tool out there for linux called TOra.
I've read good things about it, but haven't had a chance (or real desire for
that matter) to try it.
http://www.globecom.se/tora/

Rating: 4
parms to sqlplus March 12, 2004
Reviewer: A reader from madison wi
I am running w2k and oralce 8.1.7.x trying to pass values from dos to a sql
script via sqlplus.  Is it possible?

from the documentation:
"
SQLPLUS [[option] [logon] [start]]
.
.
.
and where start has the following syntax:

@file_name[.ext] [arg ...]
"

Can i pass a text arg that is available to my script?  What is the syntax to
recieve the arg in the .sql script?

Thanks in advance.
Followup:
if you have:

prompt this is input #1 &1
prompt this is input #2 &2
exit

in test.sql, then:

C:\Documents and Settings\Administrator>sqlplus tkyte/tkyte @test hello world

SQL*Plus: Release 8.1.7.0.0 - Production on Fri Mar 12 16:56:28 2004

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

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.4.1 - Production
With the Partitioning option
JServer Release 8.1.7.4.1 - Production

this is input #1 hello
this is input #2 world

Disconnected from Oracle8i Enterprise Edition Release 8.1.7.4.1 - Production
With the Partitioning option
JServer Release 8.1.7.4.1 - Production

C:\Documents and Settings\Administrator>

works...
March 12, 2004 -- 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
Rating: 2
About Sequence... March 12, 2004
Reviewer: reader from NEW JERSEY
Hi Tom,

I think this relevant here. I would like to use a
sequence to in this way. I need to keep the current
value of the sequence in order to populate my
child table. So my question is how can I keep(preserve)
the current value so that I can use it for my child
table?
Followup:
currval returns your sessions last returned value from nextval

insert into parent .... values ( s.nextval, .... )
insert into child ..... values ( ..., s.CURRVAL, .... )
Rating: 5
Auto incremental field in child table March 12, 2004
Reviewer: A reader
Hi Tom,

I have a table:

SQL> desc BUDGETITEMAMOUNTS
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 BUDGETITEMID                              NOT NULL NUMBER(14)
 BUDGETITEMAMTSEQID                        NOT NULL NUMBER(1)
 BUDGETITEMAMTAMT                          NOT NULL NUMBER(20,4)

The requirement is to incremental budgetitemamtseqid field automatically for
each new record of the same budgetitemid.

For example:
BUDGETITEMID           BUDGETITEMAMTSEQID
1                      1
1                      2
2                      1
1                      3
2                      2
2                      3

I write a trigger like this one:

CREATE OR REPLACE TRIGGER budgetitemamounts_seq_trg
BEFORE INSERT ON budgetitemamounts
FOR EACH ROW
BEGIN
  IF (:new.BUDGETITEMAMTSEQID) IS NULL THEN
    SELECT nvl(max(BUDGETITEMAMTSEQID),0) + 1
    INTO :new.BUDGETITEMAMTSEQID
    FROM budgetitemamounts
    WHERE BUDGETITEMID = :NEW.BUDGETITEMID;
  END IF;
END;
/

But I realized that it may have a concurrency problem that multiple users may
try to insert the same seqid. Do you have a better idea on how to do it?

Thanks.
Followup:
you would have to serialize at the budgetitemid.

Why do you need "1,2,3,4", what is wrong with "100,1032213, 2432425353,
5215252153125325" for example?   it gives you the order you want.

(my answer to this is always -- rethink the "requirement", it'll affect your
performance in a miserable way)

besides your trigger is one of those "mutating" ones if you ever attempt to
insert as select
March 12, 2004 rd -- Thanks for the question regarding "<> and !=, yyyy and rrrr", version 8.1.5
You Asked
Tom

is there any difference between using  <> and !=,if so which one is better.

what is the difference between using 'yyyy' and 'rrrr' in the date format for
years?

instead of

AND (a.cc_cd IN ('aus','eng','ind') OR b.sec_cc_cd IN ('aus','eng','ind'))

can I do

AND a.cc_cd or b.sec_cc_cd  in ('aus','eng','ind')

can you discuss a similar example for me, and illustrate

If the optimizer is set to choose, will the optmizer automatically choose cbo
when we use full or first_rows hint, what are the hints which are valid for rbo.
and we said...
they are the same <> and !=

one defaults the century, the other does not:

  1  select 'yyyy 99', to_char( to_date( '99', 'yyyy' ), 'yyyy' ) from dual
  2  union all
  3  select 'rrrr 99', to_char( to_date( '99', 'rrrr' ), 'yyyy' ) from dual
  4  union all
  5  select 'yyyy 01', to_char( to_date( '1', 'yyyy' ), 'yyyy' ) from dual
  6  union all
  7* select 'rrrr 01', to_char( to_date( '1', 'rrrr' ), 'yyyy' ) from dual
ops$tkyte@ORA817.US.ORACLE.COM> /

'YYYY99 TO_C
------- ----
yyyy 99 0099
rrrr 99 1999
yyyy 01 0001
rrrr 01 2001

ops$tkyte@ORA817.US.ORACLE.COM>

No, you cannot do "a or b in (x,y,z)" instead of "a in (x,y,z) or b in (x,y,z)"

Regardless of the optimizer goal set, if you use a HINT you will be using the
CBO -- period.

The only valid "hint" for RBO is /*+ RULE */  -- telling the optimizer to use
RBO.  There are no hints available for RBO.
Review & Followup

Rating: 3
But still some confusion March 12, 2004
Reviewer: Jon from Connecticut
I was having a discussion with someone about "rounded years" and so I came to
the expert.  I guess I don't understand the purpose of RRRR.  The documentation
say, "Accepts either 4-digit or 2-digit input (apparently 1-digit also from your
example). If 2-digit, provides the same return as RR. If you don't want this
functionality, then simply enter the 4-digit year."  Since 2-digit input gives
you the same functionality as 4-digit, why not just enter RR (since the whole
purpose of this seems to be to accommodate lazy people who don't want to enter a
4-digit year.  And if you're going to do it right (IMHO) and enter the full four
digits, who needs RRRR since we have YYYY.

This RRRR thing looks like a Y2.05K problem in the making.  I say enter the 4
digits and use YYYY.  Am I missing something?
Followup:
I say enter 4 digits period.  whether you use rrrr or yyyy at that point is no
longer relevant.
March 12, 2004 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://download-east.oracle.com/docs/cd/A81042_01/DOC/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://download-east.oracle.com/docs/cd/F49540_01/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...
Rating: 2
BULK Inserts with Indexes January 22, 2004
Reviewer: Ivan from USA
Hi Tom,

I have a table (9 million rows) that I have to insert and delete (roughly 50,000
records) a night.  I also have 29 indexes on that table (used for matching
purposes using 39 different algorithms). This update has to be done overnight
and ready for the next morning.

My initial design when the table only had 1 million rows and took 3 hours was:
1) Truncate the table
2) Mark the indexes unusable
3) skip index
4) insert records
5) rebuild indexes
6) analyze the tables

However because of the volume of an additional 8 million records it now takes 11
hours to complete which means that it barely makes the morning deadline.

How would BULK/FORALL be effected by the indexes?

What would your stretegy to this problem be?

Thanks,
Ivan
Followup:
those six steps took 3 hours?????

you'll have to expand on that. I can do that in MINUTES on my laptop.

So, which of the 6 takes the most time and HOW do you do it?
Rating: 1
re BULK Inserts with Indexes January 23, 2004
Reviewer: Ivan from USA
Hi Tom,

Come on - minutes....

Here is my code and roughly the time it takes per process the 1 million records.
 I do not have the exact timing as the database has grown but if you need the
exact timing I can get the databases the right size and run it with timing.

1) Truncate the table ...... seconds
2) Mark the indexes unusable  .... minutes
3) skip index  ...... seconds
4) insert records (1 million)  ....... 1 hour
5) rebuild indexes (29 multi field indexes) ....... 1 hour
6) analyze the tables (including histograms) ......1 hour

PROCEDURE prep_global
  IS

    FUNCTION trunc_table
      (p_table_name     IN VARCHAR2)
    RETURN BOOLEAN IS

      v_check_date   DATE;
      v_check        BOOLEAN DEFAULT FALSE;
      v_name        VARCHAR2(100);
      v_table       VARCHAR2(200);

    BEGIN

      BEGIN
        SELECT TRUNC(last_update_date)
        INTO   v_check_date
        FROM   match_co_2
        WHERE  ROWNUM =1;

      EXCEPTION
        WHEN no_data_found THEN
          v_check_date := SYSDATE - 2;
      END;

      IF v_check_date <> TRUNC(SYSDATE)
      THEN
        v_name  := p_table_name;
        v_table := ('TRUNCATE TABLE ' || v_name);
        -- truncate the table
        EXECUTE IMMEDIATE (v_table);

        -- delete stats
        v_table := ('ANALYZE TABLE '|| v_name || ' DELETE STATISTICS');
        EXECUTE IMMEDIATE (v_table);

        v_check := TRUE;
      END IF;
      RETURN v_check;

    EXCEPTION
     WHEN OTHERS
     THEN
     /*
     || Anonymous block inside the exception handler lets you
     || declare local variables to hold the error code info
     */
     DECLARE
       error_code   NUMBER := SQLCODE;
       error_msg    VARCHAR2(300) := SQLERRM;
     BEGIN
           mev_audit.log_audit(-20058,'E', 'Error in trunc_table function ' || '
error_code: ' || error_code ||' error_msg occurred: ' || error_msg);
       RAISE_APPLICATION_ERROR(-20058,
          'Error in trunc_table function ' || ' error_code: ' || error_code ||'
error_msg occurred: ' || error_msg);
     END;

    END trunc_table;

    FUNCTION unusable_index
      (p_table_name     user_tables.table_name%type)
    RETURN BOOLEAN IS
      CURSOR idx_c
      IS
        SELECT  index_name
        FROM    user_indexes
        WHERE   table_name = p_table_name
        AND     uniqueness <>'UNIQUE';
      idx_rec   idx_c%ROWTYPE;
      v_name        VARCHAR2(100);
      v_index       VARCHAR2(200);
    BEGIN
      OPEN idx_c;
      LOOP
        FETCH idx_c INTO idx_rec;
        EXIT WHEN idx_c%NOTFOUND;
        v_name  := idx_rec.index_name;
        v_index := ('ALTER INDEX ' || v_name || ' UNUSABLE');
        EXECUTE IMMEDIATE (v_index);

      END LOOP;
      CLOSE idx_c;
      RETURN TRUE;

    EXCEPTION
     WHEN OTHERS
     THEN
     /*
     || Anonymous block inside the exception handler lets you
     || declare local variables to hold the error code info
     */
     DECLARE
       error_code   NUMBER := SQLCODE;
       error_msg    VARCHAR2(300) := SQLERRM;
     BEGIN
           mev_audit.log_audit(-20059,'E', 'Error in unusable_index function '
|| ' error_code: ' || error_code ||' error_msg occurred: ' || error_msg);
       RAISE_APPLICATION_ERROR(-20059,
          'Error in unusable_index function ' || ' error_code: ' || error_code
||' error_msg occurred: ' || error_msg);
     END;

    END unusable_index;

    FUNCTION skip_index
    RETURN BOOLEAN IS
      v_statement    VARCHAR2(200);
    BEGIN
      -- alter session to skip the unusable indexes
      v_statement := ('ALTER SESSION SET skip_unusable_indexes=TRUE');
      EXECUTE IMMEDIATE (v_statement);
      RETURN TRUE;

    EXCEPTION
     WHEN OTHERS
     THEN
     /*
     || Anonymous block inside the exception handler lets you
     || declare local variables to hold the error code info
     */
     DECLARE
       error_code   NUMBER := SQLCODE;
       error_msg    VARCHAR2(300) := SQLERRM;
     BEGIN
           mev_audit.log_audit(-20060,'E', 'Error in skip_index function ' || '
error_code: ' || error_code ||' error_msg occurred: ' || error_msg);
       RAISE_APPLICATION_ERROR(-20060,
          'Error in skip_index function ' || ' error_code: ' || error_code ||'
error_msg occurred: ' || error_msg);
     END;
    END skip_index;

    FUNCTION insert_match_companies
    RETURN BOOLEAN IS

      CURSOR comp
      IS
        SELECT company_id
        FROM   companies;

      CURSOR combo (p_company_id companies.company_id%type)
      IS
        SELECT  mc.company_id,
                mca.name AS name_alias,
                osfun.space(TRANSLATE(UPPER(mc.name),'-(),','    ')) AS name,
                mc.address_1,
                mc.address_2,
                mc.city,
                mc.district,
                SUBSTR(TRANSLATE(mc.telephone_number,'0123456789-
/\+()#','0123456789'),1,12) AS telephone_number,
                mc.postal_code,
                LTRIM(RTRIM(osfun.word(mc.postal_code,1))) as postal_1_word,
                mc.parent_id_usage,
                mc.country_id,
                UPPER(SUBSTR(mc.address_1,1,10)) AS address_1_10,
                UPPER(SUBSTR(mc.address_1,1,8)) AS address_1_8,
                UPPER(SUBSTR(mc.address_1,1,5)) AS address_1_5,
                UPPER(SUBSTR(mc.address_2,1,10)) AS address_2_10,
                UPPER(SUBSTR(mc.address_2,1,5)) AS address_2_5,
                UPPER(SUBSTR(mc.city,1,10)) AS city_10,
                UPPER(SUBSTR(mc.city,1,5)) AS city_5,
                UPPER(SUBSTR(mc.city,1,3)) AS city_3,
                UPPER(SUBSTR(mc.district,1,3)) AS district_3,
                UPPER(SUBSTR(mc.district,1,2)) AS district_2,
                SUBSTR(TRANSLATE(mc.telephone_number,'0123456789-
/\+()#','0123456789'),1,15) AS phone_15,
                SUBSTR(TRANSLATE(mc.telephone_number,'0123456789-
/\+()#','0123456789'),1,12) AS phone_12,
                osfun.space(TRANSLATE(UPPER(SUBSTR(mca.name,1,40)),'-(),','
')) AS name_40,
                osfun.space(TRANSLATE(UPPER(SUBSTR(mca.name,1,30)),'-(),','
')) AS name_30,
                osfun.space(TRANSLATE(UPPER(SUBSTR(mca.name,1,20)),'-(),','
')) AS name_20,
                osfun.space(TRANSLATE(UPPER(SUBSTR(mca.name,1,13)),'-(),','
')) AS name_13,
                osfun.space(TRANSLATE(UPPER(SUBSTR(mca.name,1,10)),'-(),','
')) AS name_10,
                osfun.space(TRANSLATE(UPPER(SUBSTR(mca.name,1,7)),'-(),','
')) AS name_7,
                osfun.space(TRANSLATE(UPPER(SUBSTR(mca.name,1,3)),'-(),','
')) AS name_3
        FROM    companies mc,
                company_aliases mca
        WHERE   mc.company_id = mca.company_id
        AND     mc.company_id = p_company_id
                UNION
        SELECT  mc.company_id,
                mc.name AS name_alias,
                osfun.space(TRANSLATE(UPPER(mc.name),'-(),','    ')) AS name,
                mc.address_1,
                mc.address_2,
                mc.city,
                mc.district,
                SUBSTR(TRANSLATE(mc.telephone_number,'0123456789-
/\+()#','0123456789'),1,12) AS telephone_number,
                mc.postal_code,
                LTRIM(RTRIM(osfun.word(mc.postal_code,1))) as postal_1_word,
                mc.parent_id_usage,
                mc.country_id,
                UPPER(SUBSTR(mc.address_1,1,10)) AS address_1_10,
                UPPER(SUBSTR(mc.address_1,1,8)) AS address_1_8,
                UPPER(SUBSTR(mc.address_1,1,5)) AS address_1_5,
                UPPER(SUBSTR(mc.address_2,1,10)) AS address_2_10,
                UPPER(SUBSTR(mc.address_2,1,5)) AS address_2_5,
                UPPER(SUBSTR(mc.city,1,10)) AS city_10,
                UPPER(SUBSTR(mc.city,1,5)) AS city_5,
                UPPER(SUBSTR(mc.city,1,3)) AS city_3,
                UPPER(SUBSTR(mc.district,1,3)) AS district_3,
                UPPER(SUBSTR(mc.district,1,2)) AS district_2,
                SUBSTR(TRANSLATE(mc.telephone_number,'0123456789-
/\+()#','0123456789'),1,15) AS phone_15,
                SUBSTR(TRANSLATE(mc.telephone_number,'0123456789-
/\+()#','0123456789'),1,12) AS phone_12,
                osfun.space(TRANSLATE(UPPER(SUBSTR(mc.name,1,40)),'-(),','
')) AS name_40,
                osfun.space(TRANSLATE(UPPER(SUBSTR(mc.name,1,30)),'-(),','
')) AS name_30,
                osfun.space(TRANSLATE(UPPER(SUBSTR(mc.name,1,20)),'-(),','
')) AS name_20,
                osfun.space(TRANSLATE(UPPER(SUBSTR(mc.name,1,13)),'-(),','
')) AS name_13,
                osfun.space(TRANSLATE(UPPER(SUBSTR(mc.name,1,10)),'-(),','
')) AS name_10,
                osfun.space(TRANSLATE(UPPER(SUBSTR(mc.name,1,7)),'-(),','    '))
AS name_7,
                osfun.space(TRANSLATE(UPPER(SUBSTR(mc.name,1,3)),'-(),','    '))
AS name_3
        FROM    companies mc
        WHERE   mc.company_id = p_company_id
        AND     mc.company_id NOT IN (SELECT /*+ HASH_AJ */ company_id
                                             FROM   company_aliases
                                     WHERE  UPPER(name) = UPPER(mc.name));

      icounter  INTEGER;

    BEGIN

      icounter := 0;
      FOR recco IN comp LOOP

        FOR rec IN combo (recco.company_id) loop

        INSERT /*+ append */ INTO match_co_2
        VALUES (rec.company_id,
                rec.name_alias,
                rec.name,
                rec.address_1,
                rec.address_2,
                rec.city,
                rec.district,
                rec.telephone_number,
                rec.postal_code,
                rec.parent_id_usage,
                rec.country_id,
                rec.address_1_10,
                rec.address_1_8,
                rec.address_1_5,
                rec.address_2_10,
                rec.address_2_5,
                rec.city_10,
                rec.city_5,
                rec.city_3,
                rec.district_3,
                rec.district_2,
                rec.phone_15,
                rec.phone_12,
                rec.name_40,
                rec.name_20,
                rec.name_13,
                rec.name_7,
                rec.name_3,
                SYSDATE,
                rec.postal_1_word,
                rec.name_30,
                rec.name_10);

        IF icounter > 1000 THEN
          COMMIT;
          icounter := 0;
        ELSE
          icounter := icounter + 1;
        END IF;

      END LOOP;
      END LOOP;

      COMMIT;
      RETURN TRUE;

    EXCEPTION
      WHEN OTHERS
      THEN
      /*
      || Anonymous block inside the exception handler lets you
      || declare local variables to hold the error code info
      */
      DECLARE
        error_code   NUMBER := SQLCODE;
        error_msg    VARCHAR2(300) := SQLERRM;
      BEGIN
            mev_audit.log_audit(-20061,'E', 'Error in insert_match_companies
function ' || ' error_code: ' || error_code ||' error_msg occurred: ' ||
error_msg);
        RAISE_APPLICATION_ERROR(-20061,
           'Error in insert_match_companies function ' || ' error_code: ' ||
error_code ||' error_msg occurred: ' || error_msg);
      END;
    END insert_match_companies;

    FUNCTION rebuild_idx
      (p_table_name     user_tables.table_name%type)
    RETURN BOOLEAN IS
      CURSOR idx_c
      IS
        SELECT  index_name
        FROM    user_indexes
        WHERE   table_name = p_table_name
        AND     uniqueness <>'UNIQUE';
      v_name        VARCHAR2(100);
      v_index       VARCHAR2(200);
    BEGIN
      FOR recname in idx_c LOOP
        v_name  := recname.index_name;
        v_index := ('ALTER INDEX ' || v_name || ' REBUILD NOLOGGING');
        EXECUTE IMMEDIATE (v_index);
      END LOOP;
      RETURN TRUE;

    EXCEPTION
     WHEN OTHERS
     THEN
     /*
     || Anonymous block inside the exception handler lets you
     || declare local variables to hold the error code info
     */
     DECLARE
       error_code   NUMBER := SQLCODE;
       error_msg    VARCHAR2(300) := SQLERRM;
     BEGIN
         mev_audit.log_audit(-20061,'E', 'Error in rebuild_idx function ' || '
error_code: ' || error_code ||' error_msg occurred: ' || error_msg);
       RAISE_APPLICATION_ERROR(-20063,
          'Error in rebuild_idx function ' || ' error_code: ' || error_code ||'
error_msg occurred: ' || error_msg);
     END;

    END rebuild_idx;

 BEGIN

   -- analyze the tables

    -- truncate the table
    IF trunc_table('MATCH_CO_2')
    THEN
      -- make the indexes unusable
      IF unusable_index('MATCH_CO_2')
      THEN
        -- alter the system to skip the unusable indexes
        IF skip_index
        THEN
          -- populate the temp match tables
          IF insert_match_companies
          THEN
            -- rebuild the indexe
            IF rebuild_idx('MATCH_CO_2')
            THEN
              -- analyze the tables
              IF analyze_table_stats('MATCH_CO_2')
              THEN
                NULL;
              END IF;
            END IF;
          END IF;
        END IF;
      END IF;
    END IF;

    EXCEPTION
     WHEN OTHERS
     THEN
     /*
     || Anonymous block inside the exception handler lets you
     || declare local variables to hold the error code info
     */
     DECLARE
       error_code   NUMBER := SQLCODE;
       error_msg    VARCHAR2(300) := SQLERRM;
     BEGIN
         mev_audit.log_audit(-20061,'E', 'Error in prep_global ' || '
error_code: ' || error_code ||' error_msg occurred: ' || error_msg);
       RAISE_APPLICATION_ERROR(-20063,
          'Error in prep_global ' || ' error_code: ' || error_code ||' error_msg
occurred: ' || error_msg);
     END;

  END prep_global;
Followup:
come -- YES MINUTES -- MINUTES

your big mistakes are:

a) insert /*+ APPEND */ only works with SELECT

b) you are doing row by row (opps, gotta stop doing that -- i meant "row by row"
processing

change the code to be

insert /*+ APPEND */ into table SELECT......

lose the procedural code.
lose the commits.

and you too can be "minutes".

since you have NO indexes being maintained
AND
you are using append (and letting append work by doing insert as select)

you will generate no undo for the load
HENCE
the incremental commits which are seriously slowing you down in addition to the
slow by slow processing (doh, did it again) will go away!!!!!
Rating: 1
re BULK Inserts with Indexes January 23, 2004
Reviewer: Ivan from USA
Hi Tom,

Before you KILL me on the inserting one company at a time above I just wanted
you to know that the companies and company_aliases tables that I am retrieving
the data from are changing constantly.  This causes snapshot too old errors when
I tried to gather the information at the beginning in one cursor.

Thanks,
Ivan
Followup:
and how does doing it row by row -- which is slower then insert select -- help
for that just make the entire thing run SLOWER (and your commits don't help the
1555 at all!)...

nope, I don't get it.  no way it would 1555 on the much faster insert as select
if it doesn't on the painfully slow insert slow by slow.
Rating: 2
Re bulk Insert January 28, 2004
Reviewer: Ivan from USA
I tried it with your recommendations but I had to struggle with the DBA before
it went through successfully.

The rollback segment had to to increased from 1G to 2G because the insert took
up 650 meg (6 million rows for this insert) which caused a "ORA-01650: unable to
extend rollback segment R03 by 2560 in tablespace RBS".  He claims that a
transaction of 650 Meg is much to big and would instead rather have me do it in
smaller runs (your thoughts please).

As far as timing for the insert of the 6 million rows it took roughly 3 1/2
hours. How the heck do you do it in "minutes" like you stated in your earlier
post?

Thanks,
Ivan
Followup:
My thoughts should be obvious - you need to size rbs for the size of your
transactions -- for your workload.

ps$tkyte@ORA9IR2> set timing on
ops$tkyte@ORA9IR2> l
  1  insert /*+ APPEND */ into test select b.* from big_table.big_table b,
  2    ( select rownum from dual union all select rownum from dual union all
  3    select rownum from dual union all select rownum from dual union all
select
  4*   rownum from dual union all select rownum from dual )
ops$tkyte@ORA9IR2> /

6000000 rows created.

Elapsed: 00:01:13.74
ops$tkyte@ORA9IR2>

like that?   have you run a tkprof to see where your low hanging fruit is?
(that was a tecra m1 laptop, nothing fancy)
Rating: 3
Re Bulk Inserts January 29, 2004
Reviewer: Ivan from USA
You are right about the timing as I have left the primary key index on which is
causing the problem.

So if I read your statement correctly you agree with me in that the rbs should
be able to handle a 650 meg transaction.  If a 650 meg transaction is not too
large then what is the breaking point where you would start creating smaller
transactions?

What is difference between rebuilding the index and drop/recreating the index
after the load.  I read in one of your responses that you always rebuild instead
of drop/create indexes.

Thanks,
Ivan
Followup:
I create transactions based on business requirements only, not based on size.

Now that you have no indexes -- you should be generating almost no undo.

I like "setting unusable" and then "rebuilding" since the index never "goes
away".

Consider:

drop index i1;
drop index i2;
load table
create index i2;
<process fails here and no one noticed>

Now, index i1 is missing and system performance goes down the tubes and you
spend a day trying to figure out "why".

Consider:

set unusable i1;
set unusable i2;
load table
rebuild i2;
<process fails here and no one noticed>

Now, index i1 is unusable and the end users tell you about it since they get an
error message.  You fix the problem immediately.  system performance is not
impacted, you wasted very very little time.
Rating: 3
Bulk collects or insert select from February 03, 2004
Reviewer: Duncan from uk
Tom

I have been tasked with trying to speed up the following procedure. We are using
8.1.7.4. Using tkprof this procedure running time is far too long
(approx 2hours) and mainly consists of the count:

    SELECT     Count(*)
    INTO    V_Check
    FROM    Linked_Mortality_Data2
    WHERE    NHS_Number     = rec_Cur.NHS_NO
    AND    YEAR        = V_Quarter;

and then also the INSERT :

        INSERT INTO MORTALITY_DATA_SET
        SELECT    NHS_NUMBER,
            ANONYMISED_ID,
            DECEASED_FIRST_FORENAME,
            DECEASED_SECOND_FORENAME,
            ...
            ...
        WHERE    NHS_Number     = rec_Cur.NHS_NO
        AND    YEAR        = V_Quarter;

(The insert query with the cursor are large tables with lots of data, they have
been indexed but I'm sure this should be running faster.)

The count is there because the procedure must check that only 1 row will be
inserted into the MDSET table, and if this is not the case then those rows are
logged in an error table.

So to speed up the procedure I was wondering whether or not a BULK COLLECT into
an array then use a FORALL loop
to do the insert (logging failures as i went), would this run quicker? Since I
am on 8.1.7.4 would i have to BULK COLLECT into an object type rather than a
plsql table?

If this method would not be quicker than the INSERT SELECT FROM that is already
used, then how could i speed what i've got up?
Joining the cursor to the INSERT SELECT FROM and maybe losing the count query
would help but then i would need to log which rows did not make it into the
table, how could i achieve this and speed up the query?
(I can think of a decode statement which would calculate the V_Quarter field,
but this wouldn't shave much time off though)

CREATE OR REPLACE PROCEDURE P_M4 (    PO_SUCCESS_OR_FAILURE    OUT    VARCHAR2,
                    P_Batch_ID        IN    MORT_BATCH_RECORDS.BATCH_ID%TYPE)

IS

V_NHS_NO        MBR.NHS_NO%TYPE;
V_Batch_Record_ID    MBR.BATCH_RECORD_ID%TYPE;
V_Field_ID        FIELDS.FIELD_ID%TYPE;
V_Type            MM.TYPE%TYPE;
V_ERROR_Count         NUMBER(10) := 0;
V_Success_Count        NUMBER(10) := 0;
V_Fail_Count         NUMBER(10) := 0;
V_Check         NUMBER(10);
VO_SUCCESS_OR_FAILURE    VARCHAR2(1000);
V_Error            VARCHAR2(1000);
V_APR_02        DATE := '01/04/2002';
V_Month            CHAR(2);
V_Quarter        DATE;

BEGIN

V_Type := 'E';

SELECT     FIELD_ID
INTO    V_Field_ID
FROM     FIELDS
WHERE     COLUMN_NAME    = 'GP_CODE'
AND     TABLE_NAME    = 'LM_DATA'
AND     SYSTEM        = 'O';

FOR rec_Cur IN (SELECT    mbr.NHS_NO,
            mbr.Batch_Record_ID,
            ons.Date_of_Death DOD
        FROM    MBR,
            ONS_DATA_SET ons
        WHERE    mbr.NHS_NO = ons.NHS_Number
        AND    mbr.Batch_ID     = P_Batch_ID
        AND    mbr.Stage_ID    = 3        )     LOOP

    V_Batch_Record_ID := rec_Cur.Batch_Record_ID;

    V_ERROR_Count := 0;

    IF rec_Cur.DOD IS NOT NULL THEN
        IF rec_Cur.DOD < V_APR_02 THEN
            V_Quarter := V_APR_02;

        ELSE
            V_Month := TO_CHAR(rec_Cur.DOD, 'MM');

            IF V_Month IN ('01','02','03') THEN
                V_Month := '01';
            ELSIF V_Month IN ('04','05','06') THEN
                V_Month := '04';
            ELSIF V_Month IN ('07','08','09') THEN
                V_Month := '07';
            ELSIF V_Month IN ('10','11','12') THEN
                V_Month := '10';
            END IF;

            V_Quarter := TO_DATE('01/'||V_Month||TO_CHAR(rec_Cur.DOD, 'RRRR'),
'DD/MM/RRRR');

        END IF;

    END IF; -- DOD null check

    SELECT     Count(*)
    INTO    V_Check
    FROM    Linked_Mortality_Data2
    WHERE    NHS_Number     = rec_Cur.NHS_NO
    AND    YEAR        = V_Quarter;

    IF V_Check = 1 THEN

        INSERT INTO MDSET
        SELECT    NHS_NUMBER,
            ANONYMISED_ID,
            DECEASED_FIRST_FORENAME,
            DECEASED_SECOND_FORENAME,
            DECEASED_THIRD_FORENAME,
            DECEASED_SURNAME,
            DECEASED_ALIASES,
            DECEASED_MAIDEN_NAME,
            DATE_OF_BIRTH,
            DATE_OF_DEATH,
            PLACE_OF_DEATH,
            DECEASED_USUAL_ADDRESS,
            DEC_USUAL_ADDRESS_POSTCODE,
            CAUSE_OF_DEATH_LINE_1,
            CAUSE_OF_DEATH_LINE_2,
            CAUSE_OF_DEATH_LINE_3,
            CAUSE_OF_DEATH_LINE_4,
            CAUSE_OF_DEATH_LINE_5,
            CAUSE_OF_DEATH_LINE_6,
            CAUSE_OF_DEATH_LINE_7,
            CAUSE_OF_DEATH_LINE_8,
            UNDERLYING_CAUSE_OF_DEATH,
            STHA_OF_RESIDENCE,
            PCT_OF_RESIDENCE,
            DATE_OF_REGISTRATION,
            SEX,
            CAUSE_OF_DEATH_ICD_CODE_1,
            CAUSE_OF_DEATH_ICD_CODE_2,
            CAUSE_OF_DEATH_ICD_CODE_3,
            CAUSE_OF_DEATH_ICD_CODE_4,
            CAUSE_OF_DEATH_ICD_CODE_5,
            CAUSE_OF_DEATH_ICD_CODE_6,
            CAUSE_OF_DEATH_ICD_CODE_7,
            CAUSE_OF_DEATH_ICD_CODE_8,
            CERTIFYING_GP,
            PLACE_OF_DEATH_CODE,
            GP_CODE,
            GP_NAME,
            GP_PRACTICE_CODE,
            NULL,
            PCT_CODE,
            HA_CODE,
            SYSDATE,
            P_Batch_ID,
            V_Batch_Record_ID
        FROM    Linked_Mortality_Data2
        WHERE    NHS_Number     = rec_Cur.NHS_NO
        AND    YEAR        = V_Quarter;

        V_Success_Count := V_Success_Count + 1;

    ELSIF V_Check = 0 THEN

        INSERT INTO MM
            (MESSAGE_ID,
            BATCH_RECORD_ID,
            STAGE_ID,
            MESSAGE_TYPE_ID,
            FIELD_ID,
            TYPE,
            DATESTAMP,
            BATCH_ID)
        VALUES    (
            SEQ_MESSAGES.NEXTVAL,
            V_Batch_Record_ID,
            4,
            3003,
            V_Field_ID,
            V_Type,
            SYSDATE,
            P_BATCH_ID);

        V_Fail_Count := V_Fail_Count + 1;

    ELSIF V_Check > 1 THEN

        INSERT INTO MM
            (MESSAGE_ID,
            BATCH_RECORD_ID,
            STAGE_ID,
            MESSAGE_TYPE_ID,
            FIELD_ID,
            TYPE,
            DATESTAMP,
            BATCH_ID)
        VALUES    (
            SEQ_MESSAGES.NEXTVAL,
            V_Batch_Record_ID,
            4,
            3004,
            V_Field_ID,
            V_Type,
            SYSDATE,
            P_BATCH_ID);

        V_Fail_Count := V_Fail_Count + 1;

    END IF;

END LOOP;

PO_SUCCESS_OR_FAILURE := 'SUCCESS';

END P_M4;
/

Please let me know how I can get this to run in a reasonable time.
Followup:
I see AT MOST three inserts and no standalone selects that should be executed
here.

In fact, I believe 2 inserts at most.  No selects.  No procedural code at all.

I have not compiled this code.
I have not run this code.
However, you should be able to read this code and see what it does (your date
conversion for example is a simple sql function!)

Pretty sure this is very close to what you want -- might need a tweak or two but
this is "just a join" -- really.

In 9i -- we'd be able to get this to a SINGLE INSERT as SELECT with multi-table
inserts!!!!

ps -- in 8i, you'll need to use execute immediate and bind in the v_/p_
parameters since plsql won't recognize the analytic functions in there.

CREATE OR REPLACE PROCEDURE P_M4
( PO_SUCCESS_OR_FAILURE    OUT    VARCHAR2,
  P_Batch_ID        IN    MORT_BATCH_RECORDS.BATCH_ID%TYPE)
IS
    V_Field_ID        FIELDS.FIELD_ID%TYPE;
BEGIN
    V_Type := 'E';

    SELECT FIELD_ID
      INTO V_Field_ID
      FROM FIELDS
     WHERE COLUMN_NAME = 'GP_CODE'
       AND TABLE_NAME  = 'LM_DATA'
       AND SYSTEM      = 'O';

    INSERT INTO MDSET
    select NHS_NUMBER, ANONYMISED_ID, DECEASED_FIRST_FORENAME,
           DECEASED_SECOND_FORENAME, DECEASED_THIRD_FORENAME,
           DECEASED_SURNAME, DECEASED_ALIASES, DECEASED_MAIDEN_NAME,
           DATE_OF_BIRTH, DATE_OF_DEATH, PLACE_OF_DEATH,
           DECEASED_USUAL_ADDRESS, DEC_USUAL_ADDRESS_POSTCODE,
           CAUSE_OF_DEATH_LINE_1, CAUSE_OF_DEATH_LINE_2,
           CAUSE_OF_DEATH_LINE_3, CAUSE_OF_DEATH_LINE_4,
           CAUSE_OF_DEATH_LINE_5, CAUSE_OF_DEATH_LINE_6,
           CAUSE_OF_DEATH_LINE_7, CAUSE_OF_DEATH_LINE_8,
           UNDERLYING_CAUSE_OF_DEATH, STHA_OF_RESIDENCE, PCT_OF_RESIDENCE,
           DATE_OF_REGISTRATION, SEX, CAUSE_OF_DEATH_ICD_CODE_1,
           CAUSE_OF_DEATH_ICD_CODE_2,
           CAUSE_OF_DEATH_ICD_CODE_3, CAUSE_OF_DEATH_ICD_CODE_4,
           CAUSE_OF_DEATH_ICD_CODE_5, CAUSE_OF_DEATH_ICD_CODE_6,
           CAUSE_OF_DEATH_ICD_CODE_7, CAUSE_OF_DEATH_ICD_CODE_8,
           CERTIFYING_GP, PLACE_OF_DEATH_CODE, GP_CODE, GP_NAME,
           GP_PRACTICE_CODE, NULL, PCT_CODE, HA_CODE,
           SYSDATE, P_Batch_ID, Batch_Record_ID
      from (
    SELECT count(*) over (partition by nhs_number, year ) cnt,
           NHS_NUMBER, ANONYMISED_ID, DECEASED_FIRST_FORENAME,
           DECEASED_SECOND_FORENAME, DECEASED_THIRD_FORENAME,
           DECEASED_SURNAME, DECEASED_ALIASES, DECEASED_MAIDEN_NAME,
           DATE_OF_BIRTH, DATE_OF_DEATH, PLACE_OF_DEATH,
           DECEASED_USUAL_ADDRESS, DEC_USUAL_ADDRESS_POSTCODE,
           CAUSE_OF_DEATH_LINE_1, CAUSE_OF_DEATH_LINE_2,
           CAUSE_OF_DEATH_LINE_3, CAUSE_OF_DEATH_LINE_4,
           CAUSE_OF_DEATH_LINE_5, CAUSE_OF_DEATH_LINE_6,
           CAUSE_OF_DEATH_LINE_7, CAUSE_OF_DEATH_LINE_8,
           UNDERLYING_CAUSE_OF_DEATH, STHA_OF_RESIDENCE, PCT_OF_RESIDENCE,
           DATE_OF_REGISTRATION, SEX, CAUSE_OF_DEATH_ICD_CODE_1,
           CAUSE_OF_DEATH_ICD_CODE_2,
           CAUSE_OF_DEATH_ICD_CODE_3, CAUSE_OF_DEATH_ICD_CODE_4,
           CAUSE_OF_DEATH_ICD_CODE_5, CAUSE_OF_DEATH_ICD_CODE_6,
           CAUSE_OF_DEATH_ICD_CODE_7, CAUSE_OF_DEATH_ICD_CODE_8,
           CERTIFYING_GP, PLACE_OF_DEATH_CODE, GP_CODE, GP_NAME,
           GP_PRACTICE_CODE, NULL, PCT_CODE, HA_CODE,
           rec_cur.Batch_Record_ID
      FROM Linked_Mortality_Data2
          (SELECT  mbr.NHS_NO,
                   mbr.Batch_Record_ID,
                   greatest( trunc( dt, 'q' ) , to_date( '01-apr-2002',
                              'dd-mon-yyyy' ) ) DOD
              FROM MBR,
                   ONS_DATA_SET ons
             WHERE mbr.NHS_NO = ons.NHS_Number
               AND mbr.Batch_ID     = P_Batch_ID
               AND mbr.Stage_ID    = 3 ) rec_cur
     WHERE NHS_Number = rec_Cur.NHS_NO
       AND YEAR       = rec_cur.DOD
           )
    where cnt = 1;

    INSERT INTO MM
            (MESSAGE_ID, BATCH_RECORD_ID, STAGE_ID, MESSAGE_TYPE_ID,
            FIELD_ID, TYPE, DATESTAMP, BATCH_ID)
    select seq_messages.nextval, batch_record_id, 4,
           decode( cnt, 0, 3003, 3004 ), v_field_id,
           'E', sysdate, p_batch_id
      from (
    SELECT count(*) over (partition by nhs_no, dod ) cnt,
           rec_cur.Batch_Record_ID
      FROM Linked_Mortality_Data2
          (SELECT  mbr.NHS_NO,
                   mbr.Batch_Record_ID,
                   greatest( trunc( dt, 'q' ) , to_date( '01-apr-2002',
                              'dd-mon-yyyy' ) ) DOD
              FROM MBR,
                   ONS_DATA_SET ons
             WHERE mbr.NHS_NO = ons.NHS_Number
               AND mbr.Batch_ID     = P_Batch_ID
               AND mbr.Stage_ID    = 3 ) rec_cur
     WHERE NHS_Number(+) = rec_Cur.NHS_NO
       AND YEAR(+)       = rec_cur.DOD
           )
    where cnt <> 1;

    PO_SUCCESS_OR_FAILURE := 'SUCCESS';

END P_M4;
/
Rating: 2
re BULK Insert February 03, 2004
Reviewer: Ivan from USA
Hi Tom,

I am still struggling even after removing the join of the tables.

Here is my TKPROF of the insert statement and as you can see it is taking hours.
 Your thoughts please on how I can speed this thing up.

TKPROF: Release 9.2.0.1.0 - Production on Tue Feb 3 09:27:12 2004

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Trace file: dev05_ora_1204.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.01          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        1      0.01       0.01          0          0          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 106  (MEV)
********************************************************************************

ALTER SESSION SET skip_unusable_indexes=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: 0
Optimizer goal: CHOOSE
Parsing user id: 106  (MEV)
********************************************************************************

INSERT /*+ APPEND */ INTO match_co_2
SELECT  mc.company_id,
        mc.name AS name_alias,
        osfun.space(TRANSLATE(UPPER(mc.name),'-(),','    ')) AS name,
        mc.address_1,
        mc.address_2,
        mc.city,
        mc.district,
        SUBSTR(TRANSLATE(mc.telephone_number,'0123456789-
/\+()#','0123456789'),1,12) AS telephone_number,
        mc.postal_code,
        mc.parent_id_usage,
        mc.country_id,
        UPPER(SUBSTR(mc.address_1,1,10)) AS address_1_10,
        UPPER(SUBSTR(mc.address_1,1,8)) AS address_1_8,
        UPPER(SUBSTR(mc.address_1,1,5)) AS address_1_5,
        UPPER(SUBSTR(mc.address_2,1,10)) AS address_2_10,
        UPPER(SUBSTR(mc.address_2,1,5)) AS address_2_5,
        UPPER(SUBSTR(mc.city,1,10)) AS city_10,
        UPPER(SUBSTR(mc.city,1,5)) AS city_5,
        UPPER(SUBSTR(mc.city,1,3)) AS city_3,
        UPPER(SUBSTR(mc.district,1,3)) AS district_3,
        UPPER(SUBSTR(mc.district,1,2)) AS district_2,
        SUBSTR(TRANSLATE(mc.telephone_number,'0123456789-
/\+()#','0123456789'),1,15) AS phone_15,
        SUBSTR(TRANSLATE(mc.telephone_number,'0123456789-
/\+()#','0123456789'),1,12) AS phone_12,
        osfun.space(TRANSLATE(UPPER(SUBSTR(mc.name,1,40)),'-(),','    ')) AS
name_40,
        osfun.space(TRANSLATE(UPPER(SUBSTR(mc.name,1,20)),'-(),','    ')) AS
name_20,
        osfun.space(TRANSLATE(UPPER(SUBSTR(mc.name,1,13)),'-(),','    ')) AS
name_13,
        osfun.space(TRANSLATE(UPPER(SUBSTR(mc.name,1,7)),'-(),','    ')) AS
name_7,
        osfun.space(TRANSLATE(UPPER(SUBSTR(mc.name,1,3)),'-(),','    ')) AS
name_3,
        sysdate,
        LTRIM(RTRIM(osfun.word(mc.postal_code,1))) as postal_1_word,
        osfun.space(TRANSLATE(UPPER(SUBSTR(mc.name,1,30)),'-(),','    ')) AS
name_30,
        osfun.space(TRANSLATE(UPPER(SUBSTR(mc.name,1,10)),'-(),','    ')) AS
name_10
FROM    companies mc

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.12       0.17          0          0          0           0
Execute      1   6886.45    8228.53     475289    1088093      18356     3714322
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2   6886.57    8228.70     475289    1088093      18356     3714322

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 106  (MEV)
error during execute of EXPLAIN PLAN statement
ORA-26028: index MEV.XIE9MC2_CTRY_N13_A210 initially in unusable state

parse error offset: 94
********************************************************************************

select file#
from
 file$ where ts#=:1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse     1350      0.23       0.50          0          0          0           0
Execute   1350      0.18       0.26          0          0          0           0
Fetch     2700      0.29       0.24          0       4050          0        1350
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     5400      0.71       1.00          0       4050          0        1350

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 OBJ#(17) (cr=3 r=0 w=0 time=10249 us)
      1   INDEX RANGE SCAN OBJ#(37) (cr=2 r=0 w=0 time=10195 us)(object id 37)

********************************************************************************

update tsq$ set blocks=:3,maxblocks=:4,grantor#=:5,priv1=:6,priv2=:7,priv3=:8
where
 ts#=:1 and user#=:2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse     1350      0.25       0.38          0          0          0           0
Execute   1350      1.32       1.98          0       5400       1350        1350
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2700      1.57       2.36          0       5400       1350        1350

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  UPDATE  (cr=4 r=0 w=0 time=838 us)
      1   TABLE ACCESS CLUSTER OBJ#(55) (cr=4 r=0 w=0 time=443 us)
      1    INDEX UNIQUE SCAN OBJ#(11) (cr=1 r=0 w=0 time=46 us)(object id 11)

********************************************************************************

COMMIT

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.01       0.02          0          0          8           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.01       0.02          0          0          8           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 106  (MEV)
********************************************************************************

ALTER SESSION SET SQL_TRACE = FALSE

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: 106  (MEV)

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        4      0.12       0.19          0          0          0           0
Execute      5   6886.48    8228.57     475289    1088093      18364     3714322
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        9   6886.60    8228.76     475289    1088093      18364     3714322

Misses in library cache during parse: 1

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse     2700      0.48       0.88          0          0          0           0
Execute   2700      1.51       2.24          0       5400       1350        1350
Fetch     2700      0.29       0.24          0       4050          0        1350
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     8100      2.29       3.37          0       9450       1350        2700

Misses in library cache during parse: 2

    5  user  SQL statements in session.
 2700  internal SQL statements in session.
 2705  SQL statements in session.
    0  statements EXPLAINed in this session.
********************************************************************************
Trace file: dev05_ora_1204.trc
Trace file compatibility: 9.00.01
Sort options: default

       1  session in tracefile.
       5  user  SQL statements in trace file.
    2700  internal SQL statements in trace file.
    2705  SQL statements in trace file.
       7  unique SQL statements in trace file.
   25794  lines in trace file.

Thanks,
Ivan
Followup:
I'd be suspicious of osfun.space.

calling a plsql function 7 times for each row and doing it some 3.7 MILLION
times is going to "take a while"

say that function returns in 0.001 second (pretty good that would be).

well, that is 8 hours at 3.7 MILLION times 7 times....

So, lets see osfun.space and see why you think you need to call plsql from sql.
we'll want to make that pure sql.
Rating: 5
Thanks! February 04, 2004
Reviewer: Duncan from UK
That's great thanks.
However can you just explain how the date conversion below works:

"..
greatest( trunc( dt, 'q' ) , to_date( '01-apr-2002',
                              'dd-mon-yyyy' ) ) DOD
..."

what is dt?? internal date?
what is q? a format code?

Thanks
Followup:
DT is the column (sorry, cut and pasted from a test -- dt in your case would be
your ons.Date_of_Death)

trunc( ons.Date_of_Death, 'q' )

will truncate to the quarter, just as you are doing procedurally.

you have the additional requirement that if the trunc'ed date is before april 1
2002, it should be april 1 2002 and greatest does that for us....

yes, 'q' is a documented format code for TRUNC and dates in general.
Rating: 2
Re- BULK INSERT February 04, 2004
Reviewer: Ivan from USA
OSFUN.SPACE FUNCTION

FUNCTION space
************************************************************
-- Function:
--     SPACE(string[,[n][,pad]])
--   Replaces any strings of one or more inter-word blanks in string with n pad
--   characters and returns the result.  The default value of n is 1 and the
--   default value of pad is a blank.  Leading and trailing blanks in string are
--   removed.
--
-- Examples:
--   space(' edge   of night ')          ==> 'edge of night'
--   space(' edge   of night ',0)        ==> 'edgeofnight'
--   space(' edge   of night ',2,'-')    ==> 'edge--of--night'
--   space('                 ')          ==> null
--
-- Note:
--   Same syntax and functionality as REXX function of the same name.
-- ***********************************************************
  (
    i_string                   IN  VARCHAR2,
    i_n                        IN  NATURAL,
    i_pad                      IN  CHAR
  )
  RETURN VARCHAR2
IS
  v_temp                         VARCHAR2(4000);
BEGIN
  v_temp := LTRIM(RTRIM(i_string));
  IF v_temp IS NULL THEN
    RETURN NULL;
  ELSIF i_n = 0 THEN
    RETURN REPLACE(v_temp,' ',NULL);
  ELSE
    WHILE INSTR(v_temp,'  ') > 0 LOOP
      v_temp := REPLACE(v_temp,'  ',' ');
    END LOOP;
    RETURN REPLACE(v_temp,' ',RPAD(i_pad,i_n,i_pad));
  END IF;
END space;

Thanks,
Ivan
Followup:
                              since you are just replacing multiple spaces with
a single space, just use:

ltrim(rtrim( replace( replace( replace(  YOUR_DATA ,' ',' @'),'@ ',''),' @','
')))

and stop calling the space function.  It'll be much much faster.

consider:

ops$tkyte@ORA10G> select '"'|| space( '  edge    of   night   ' ) || '"' from
dual;

'"'||SPACE('EDGEOFNIGHT')||'"'
---------------------------------------------------------------------------------
--------------------------------------------------
"edge of night"

Elapsed: 00:00:00.04
ops$tkyte@ORA10G> select '"'|| ltrim(rtrim( replace( replace( replace(
  2  '  edge    of   night   '
  3  ,' ',' @'),'@ ',''),' @',' '))) || '"' from
  4  dual;

'"'||LTRIM(RTRI
---------------
"edge of night"

Elapsed: 00:00:00.03
ops$tkyte@ORA10G> /*
ops$tkyte@ORA10G> drop table t;
ops$tkyte@ORA10G> create table t as select '    edge   of   night   ' x from
all_objects;
ops$tkyte@ORA10G> */

select space( x )
from
 t

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.02          0          1          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     3142      1.37       1.27          0       3328          0       47107
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     3144      1.39       1.29          0       3329          0       47107

Rows     Row Source Operation
-------  ---------------------------------------------------
  47107  TABLE ACCESS FULL T (cr=3328 pr=0 pw=0 time=47216 us)

select ltrim(rtrim( replace( replace( replace(  x ,' ',' @'),'@ ',''),' @','
')))
from t

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.05          0          1          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     3142      0.22       0.24          0       3328          0       47107
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     3144      0.24       0.30          0       3329          0       47107
Rating: 2
Re BULK INSERT February 04, 2004
Reviewer: Ivan from USA
I changed the code to get away from calling the OSFUN.SPACE function as seen
below and I then ran out of temp space.  I watched as it filled the temp space
up to 2.5 gigs and then it errored out.

ERROR at line 1:
ORA-01652: unable to extend temp segment by 1280 in tablespace TEMP01

INSERT /*+ APPEND */ INTO match_co_2
SELECT  mc.company_id,
        mc.name AS name_alias,
        REPLACE(REPLACE (REPLACE(TRANSLATE(UPPER(mc.name),'-(),','    '),' ','
^'),'^ ',''),' ^',' ') AS name,
        mc.address_1,
        mc.address_2,
        mc.city,
        mc.district,
        SUBSTR(TRANSLATE(mc.telephone_number,'0123456789-
/\+()#','0123456789'),1,12) AS telephone_number,
        mc.postal_code,
        mc.parent_id_usage,
        mc.country_id,
        UPPER(SUBSTR(mc.address_1,1,10)) AS address_1_10,
        UPPER(SUBSTR(mc.address_1,1,8)) AS address_1_8,
        UPPER(SUBSTR(mc.address_1,1,5)) AS address_1_5,
        UPPER(SUBSTR(mc.address_2,1,10)) AS address_2_10,
        UPPER(SUBSTR(mc.address_2,1,5)) AS address_2_5,
        UPPER(SUBSTR(mc.city,1,10)) AS city_10,
        UPPER(SUBSTR(mc.city,1,5)) AS city_5,
        UPPER(SUBSTR(mc.city,1,3)) AS city_3,
        UPPER(SUBSTR(mc.district,1,3)) AS district_3,
        UPPER(SUBSTR(mc.district,1,2)) AS district_2,
        SUBSTR(TRANSLATE(mc.telephone_number,'0123456789-
/\+()#','0123456789'),1,15) AS phone_15,
        SUBSTR(TRANSLATE(mc.telephone_number,'0123456789-
/\+()#','0123456789'),1,12) AS phone_12,
        REPLACE(REPLACE (REPLACE(TRANSLATE(UPPER(SUBSTR(mc.name,1,40)),'-(),','
  '),' ',' ^'),'^ ',''),' ^',' ') AS name_40,
        REPLACE(REPLACE (REPLACE(TRANSLATE(UPPER(SUBSTR(mc.name,1,20)),'-(),','
  '),' ',' ^'),'^ ',''),' ^',' ') AS name_20,
        REPLACE(REPLACE (REPLACE(TRANSLATE(UPPER(SUBSTR(mc.name,1,13)),'-(),','
  '),' ',' ^'),'^ ',''),' ^',' ') AS name_13,
        REPLACE(REPLACE (REPLACE(TRANSLATE(UPPER(SUBSTR(mc.name,1,7)),'-(),','
 '),' ',' ^'),'^ ',''),' ^',' ') AS name_7,
        REPLACE(REPLACE (REPLACE(TRANSLATE(UPPER(SUBSTR(mc.name,1,3)),'-(),','
 '),' ',' ^'),'^ ',''),' ^',' ') AS name_3,
        SYSDATE,
        SUBSTR(LTRIM(postal_code || ' '),1,INSTR(postal_code || ' ',' ')) as
postal_1_word,
        REPLACE(REPLACE (REPLACE(TRANSLATE(UPPER(SUBSTR(mc.name,1,30)),'-(),','
  '),' ',' ^'),'^ ',''),' ^',' ') AS name_30,
        REPLACE(REPLACE (REPLACE(TRANSLATE(UPPER(SUBSTR(mc.name,1,10)),'-(),','
  '),' ',' ^'),'^ ',''),' ^',' ') AS name_10
FROM    companies mc;

Thanks,
Ivan
Followup:
any indexes on this table.  we build the index segments for the appended data
off to the side and then merge them in after the fact.
Rating: 5
March 11, 2004
Reviewer: sonali from waltham, ma
I have a general design question..
say I am doing this insert

Insert into table t1
select * from table t2 where t2.entity in (1,2,3,4......n);

entity (1,2,3,4) are coming from HTML page as constants and the number of
constants in this IN clause can be upto 1000 numbers..may be more.

Is this insert statement right to do this way ? Is there better way to do this ?
Should I create a temp table and put these constants in that table and then read
from there ?
Will there be any locks ?

Whats your suggestion ?
Thanks so much
Followup:
i would put the constants into a global temp table yes.

will there be any locks?  sure, on unique key entries on table T1.
Rating: 5
March 11, 2004
Reviewer: A reader
Hi Tom, is there a way to optimize

insert into t select * from t2;

Every day the whole table must be inserted in a historic table,

something like insert into t bulk select * from t2;

without disabling logging nor indexes in standar edition 9i.

Please :)
Followup:
insert /*+ append */ into t select * from t2;

would avoid UNDO generation for the table (but not the indexes) and would
minimize the redo generation for the indexes.

append does *not* bypass redo
append does maintain indexes
append minimizes undo (and therefore redo) generation for the table and indexes.
Rating: 5
March 12, 2004
Reviewer: A reader
Thanks Tom,

Sorry but I'm not understainding, one question more
my database in archivelog mode
If I do this

insert /*+ append */ into t select * from t2;

I think I'll have to do a full backup
beacuse information will not be saved in archive log files.
Or I'm wrong

Thanks again.
Followup:
thats wrong.

append only bypasses UNDO generation (which is 100% safe)...

if you flip the table into NOLOGGING mode, *then* and only then might you bypass
redo generation as well.

measure the redo generated, check the unrecoverable time, you can see this
easily.
Rating: 5
March 15, 2004
Reviewer: A reader
Thanks Tom
Rating: 3
OK March 21, 2004
Reviewer: Ramanan from India
Dear Tom,
Do PL/SQL records have any built-in attribute to iterate
over each record?I tried the following one and got errors.
How to loop over each record?

SQL>  declare
  2    type t is record(enm emp.ename%type,salary emp.sal%type);
  3    r t;
  4   begin
  5    select ename,sal into r from emp;
  6     for i in 1..r.count loop
  7       dbms_output.put_line(r(i).enm||chr(9)||r(i).salary);
  8     end loop;
  9   end;
 10  /
   for i in 1..r.count loop
                 *
ERROR at line 6:
ORA-06550: line 6, column 18:
PLS-00302: component 'COUNT' must be declared
ORA-06550: line 6, column 4:
PL/SQL: Statement ignored

Please do reply.
March 12, 2004 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
Rating: 4
Sort Explicit Static Cursor at Runtime February 06, 2004
Reviewer: robert from ct
Tom,
Is  this possible. ?
I want to web app user sorting option.

No error raised below but the ORDER BY didn't happen
------------------------
DECLARE
 l_sort INTEGER := 2;

 CURSOR empcur
     IS
     SELECT empno, ename FROM emp ORDER BY l_sort ;

  CURSOR empcur2(psort IN INTEGER)
     IS
     SELECT empno, ename FROM emp ORDER BY psort  ;

BEGIN
  FOR i IN empcur
  LOOP
    dbms_output.put_line(i.ename);
  END LOOP;
  dbms_output.put_line('-----------------');

  FOR i IN empcur2(l_sort)
  LOOP
    dbms_output.put_line(i.ename);
  END LOOP;
END ;

thanks
Followup:
that is alot like:

select * from emp order by '2';

it ordered by a CONSTANT, the constant value "2"

You can either:

a) use dynamic sql
b) use case or decode

eg:

   select empno, ename from emp
     order by decode( psort, 1, to_char(empno,'00000009'), 2, ename );

you have to to_char numbers and dates since you'll be ordering by a character
string -- use YYYYMMDDHH24MISS on dates.  If your numbers go negative -- you'll
have to deal with that somehow as well.
Rating: 3
Nice February 09, 2004
Reviewer: Gerhard from Dusseldorf,Germany
Dear Sir,
Well and wish the same from you.
Is there any other way to put this query?
 sql> select ename,nvl(comm,'Commission is null') from emp
  or
 sql> select ename,decode(comm,null,'Commission is null',comm) from emp
 It is just a matter of eagerness and curiosity.I think
*case* is possible but is there any other way?
Please do reply.
Bye!
Followup:
there are thousands of ways.

select ename, 'commission is null' from emp where comm is null
union all
select ename, comm from emp where comm is not null;

is yet another, case, nvl2, user defined functions -- would all work as well.

NVL() however is the "correct answer" -- but beware -- comm is a number and
'commision is null' is not a number - beware IMPLICIT conversions.  best to use
to_char() on comm to make it a string (since commission is null cannot be made
into a number)
Rating: 5
How to write this sql March 10, 2004
Reviewer: Bole Taro Dol Baje
 CREATE TABLE T ( GP VARCHAR2(10), GPP VARCHAR2(20))
 /

Table created.

 ALTER TABLE T ADD CONSTRAINT PK1 PRIMARY KEY ( GP, GPP);

/

 INSERT INTO T VALUES('A', 'A');

 INSERT INTO T VALUES('A', 'B');

 INSERT INTO T VALUES('Z', 'A');

I want to find out all those GP values which have a common GPP value? How can I
do it, and what are
the multiple ways I can do it. Please help.
Followup:
ops$tkyte@ORA9IR2> select *
  2    from (
  3  select gp, gpp, count(*) over (partition by gpp) cnt
  4    from t
  5         )
  6   where cnt > 1;

GP         GPP                         CNT
---------- -------------------- ----------
A          A                             2
Z          A                             2

first one that popped into my head...
Rating: 4
Is this OK ? March 10, 2004
Reviewer: A reader
select  a.* from t a , t b where a.gpp = b.gpp and a.rowid <> b.rowid
GP         GPP
---------- --------------------
Z          A
A          A
2 rows selected
Followup:
there are potentially an infinite number of ways to do this, yes.

That one would overdo it in the event there are three records:

ops$tkyte@ORA9IR2>  INSERT INTO T VALUES('x', 'A');

1 row created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select *
  2    from (
  3  select gp, gpp, count(*) over (partition by gpp) cnt
  4    from t
  5         )
  6   where cnt > 1;

GP         GPP                         CNT
---------- -------------------- ----------
A          A                             3
Z          A                             3
x          A                             3

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select       a.* from t a , t b where a.gpp = b.gpp and
a.rowid <> b.rowid
  2  /

GP         GPP
---------- --------------------
Z          A
x          A
A          A
x          A
A          A
Z          A

6 rows selected.
Rating: 4
Which of the above two query will be more performant ? March 10, 2004
Reviewer: A reader
I think yours may be.
Followup:
they give different answers :)

not fair to compare.
Rating: 4
Ah missed the distinct March 10, 2004
Reviewer: A reader
select    distinct   a.* from t a , t b where a.gpp = b.gpp and
a.rowid <> b.rowid.

Now they give identical results. Do u agree.
Will the analytic soln outperform the distinct self join soln
Thanx
Followup:
i would prefer analytics over a partial cartesian product
Rating: 3
experts' suggestions March 11, 2004
Reviewer: A reader
hi Tom & all learners

I am a beginner with oracle.
what would be the best suggestion to get a perfect hand in oracle other than
time and practice
really appreciate suggestions/ books name/sites form all learners/experts on my
email.
THANKS in advance to all
Followup:
"other than time and practice" -- well, until we as a human race develop the
ability to imprint knowledge directly on the brain.......

Read the concepts guide (available on otn for all releases) from cover to cover
and if you remember 10% of it, you'll already know 90% more than most people do
about Oracle.
Rating: 3
March 11, 2004
Reviewer: A reader
learnz2004@yahoo.com
Rating: 4
return true only if all deptnos exists in child table ver.9.2 March 12, 2004
Reviewer: A reader
The famous DEPT and EMP tables.
How to write an SQL which returns true only if all depts have employees.

My attempt works but I need tom's Brilliant and simple solution and other
possible solutions.
1)
select count(*) from dual where exists(
select null from dept having count(*) = (select count(distinct deptno) from
emp).
2)select count(*) from emp e,dept d where d.deptno = e.deptno(+) and e.deptno is
 null

The basic emp table has no emps for deptno = 40, once one is added the above
query returns 1 else 0.
Can u please give atleast 3 or 4 or more alternatives ?
Thanx much
Followup:
quiz time?

select decode( count(*), 0, 1, 0 )
  from dept
 where deptno not in ( select deptno from emp );

add where rownum = 1 (and you'll have 2 more, but thats cheating perhaps...)

select decode( count(*), 0, 1, 0 )
  from dept
 where NOT EXISTS ( select null from emp where emp.deptno = dept.deptno )
   and rownum = 1;

select decode( count(*), 0, 1, 0 )
  from ( select deptno from dept
          MINUS
         select deptno from emp );
Rating: 5
U r Great March 12, 2004
Reviewer: A reader
Thanx for the Quiz answers.You have a lot of gray cells.
March 13, 2004 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.
Rating: 5
Number of rollback segments March 12, 2004
Reviewer: Himnish Narang
Hi Tom,

The following is the output from the database dictionary views v$waitstat and
v$sysstat. According to Oracle if any of the undo block is not zero then u need
to add rollback segments.

We are using oracle 8i (8.1.7) on IBM AIX 4.3. Database size is 270GB and we are
having 180 rollback segments. All the rollback segments are being used with
optimal option and optimal is having a value of 10. The application is an OLTP
and concurrent users connected at peak time is more than 800.

Please suggest whether we need to add the rollback segments or not.

Thanx for your time.

SQL> l
  1* select * from v$waitstat where class
  2  in ('undo block','undo header','system undo block','system undo header')
SQL> /

CLASS                   COUNT       TIME
------------------ ---------- ----------
system undo header          0          0
system undo block           0          0
undo header               115         42
undo block               1930        301

SQL>
SQL> l
  1* select name,value from v$sysstat where name in ('consistent gets','db block
gets')
SQL> /

NAME                         VALUE
------------------------ ----------
db block gets              31546735
consistent gets           696922744

SQL>
Followup:
impossible to say.

this is like asking "the last time I took a car ride, i stopped at 100 red
lights.  is that good or bad?"

no matter what you say to that, I'll prove that either the opposite is true OR
that it is neither good or bad.

those stats are since the system was up.  if you were up for 30 seconds, they
might be "alot".  if you were up for a day or two, they are not.
Rating: 4
Rollback segments for OLTP March 13, 2004
Reviewer: Himnish Narang from Gurgaon, India
Hi Tom,

The statistics were taken out when the database has run for 10 hours. The time
these were tken was also the peak time. So according to the statistics given,the
number of users logged on to the application(800) and number of rollback segs
that we have currently, please suggest us how to manage our rollback segments
and also how can we monitor the performance /contention of the same.

Thanx in advance.
Followup:
Well, quick question to you -- are you meeting your performance goals in the
first place and if not, you have already done the application level tuning
right?

But anyway, this is 301 seconds of wait over a 10 hour period.  0.15 seconds of
wait on average per event.  Tell me -- in your system -- is this the low hanging
fruit, something to be concerned about?  (do you really want more than 180 rbs's
is my question).
March 13, 2004 Steve -- Thanks for the question regarding "UPDATE", version 8.1.7
You Asked
tom
i have a procedure like this

cursor c is
select t1.pr_cd from t1,t2
where t1.x =t2.x
and t1.y = t2.y
and  t1.z = t2.z
and t1.dt = v_dt ( receiving as a parameter)
c_cnt number;
begin
c_cnt:= 0
for d in c loop
c_cnt:= c_cnt+1;

update t3
set t3.dt =v_dt
where t3.pr_cd = t1.pr_cd
and t3.dt = 11111111

update t4
set t4.dt =v_dt
where t4.pr_cd = t1.pr_cd
and t4.dt = 11111111

update t5
set t5.dt =v_dt
where t5.pr_cd = t1.pr_cd
and t5.dt = 11111111

..
if c_cnt >1000
commit
c_cnt:=0
end if;
end loop
commit
end

i havent run the above procedure as i have fears of hittin ora-01555 error
t2  has 500krecords
t1,t3,t4 and t5 .. are huge(40 millions rows average )..no problem as they have
good indexes..and planning to run it on night
whats ur suggestion
and we said...
there should be no procedural code, that commit in the for loop massively
increases the probability of ora-1555.

this should be optimally:

update t3 set dt = v_dt where pr_cd in ( that_query ) and dt = 111111;
update t4 set dt = v_dt where pr_cd in ( that_query ) and dt = 111111;
update t5 set dt = v_dt where pr_cd in ( that_query ) and dt = 111111;

and if that_query is "expensive", you would

create global temporary table gtt ( pr_cd .... );

and insert into gtt that_query and use "select * from gtt" in the IN clause.

No code needed, no code desired.  Just three updates.

And if you are updating lots of records -- INDEXES are going to be less then
useful (well, they would help make it go really really slow).  Depending on the
distribution of the data -- if you were to update more than a couple hundred
thousand records (low low 100's), a full scan of T3, T4, T5 would be more
efficient than an index access.  An index on "DT" in particular would be a "bad
thing" for this mass update -- due to the fact that you would have to pay the
high price of maintaining that index as well as using it.
Review & Followup

Rating: 5
March 13, 2004
Reviewer: A reader
hi tom
i fully totally completely agree with you...infact i had a different piece of
code written with 3 updates..but they said the rollback  might me huge..i have
pasted a sample for
your reference:

update table1 T
   set T.dt=v_dt --parameter
 where exists (select NULL
                 from table2 Y
                where T.pr_cd=Y.pr_cd
                      and T.x =Y.x
                      and T.y = Y.y
                      and  T.z = Y.z
                      and Y.dt = v_dt)
   and T.dt=11000001
similarly for other two tables....they refused to adopt this as they feel they
might have problems with rollback
how can i convince them?? they said they did something like this before and they
dint have any problems..the above method is very effecient except for the
rollback
any suggestions?
Followup:
(ar

the problem is -- IF IT DID NOT GENERATE rollback -- and let that rollback stay
there for the duration of your transaction -- you will almost certainly ensure
(ENSURE) your ora-1555

then what?

I'll assume that T.DT is an unindexed column in TABLE1.  Well, if you modify a
million of them:

big_table@ORA9IR2> update big_table set last_ddl_time = created;

1000000 rows updated.

big_table@ORA9IR2> select used_ublk from v$transaction;

 USED_UBLK
----------
     12056

big_table@ORA9IR2> select 12056*8/1024 from dual;

12056*8/1024
------------
     94.1875

You'll use about 100meg of undo (per million or thereabouts).  Is that large?
Well, you would actually generate lots more total rollback if you update a few,
commit, update a few, commit -- you would not have to store it all at the same
time - but thats the DEFECT about that solution (not the advantage!!!!!!).
You'll be setting yourself up directly for the ora-1555.

You can copy your data from t1, t2 into a gtt and cursor over that instead, that
should protect you from the 1555 since the reads from TEMP do not require
consistent reads at all.

So, thats about all I can say -- either be penny wise/pound foolish, or
configure sufficient resources for the smooth, error free operation of your
system.

Also, ask  yourself "is it ok when this blows up 1/2 through to have some data
updated, some data not updated".  "is it ok that while this is running, the data
retrieved by applications will be 'inconsistent'"  (the answers could be yes,
could be no -- that is upto you)
March 13, 2004 -- Thanks for the question regarding "How to dynamically update a remote table using 'where current of l_cursor'?", version 8i
You Asked
Hi Tom,

My objectives are:
1. select * from remotetable@dblink for update (parse & open a cursor l_cursor)
2. update remotetable@dblink set flag='Y' where current of :l_cursor

I try to use dbms_sql.bind_variable (l_cursor2, ':l_cursor', l_cursor);

but it doesn't work. How can I resolve this problem?

Thank you.

Regards,
Jesse
and we said...
It won't work that way.  the for update where current of works with static SQL
in PLSQL only.  There is no concept of "binding" a cursor with either native
dynamic sql or dbms_sql.

You should code:

  'select rowid rid, a.* from remotetable@dblink a for update'

and later:

  'update remotetable@dblink set .... where rowid = :rid'

and bind the rowid in.
Review & Followup

Rating: 5
I'm sure it can be done... March 09, 2004
Reviewer: Dicky from HK
Tom, I think there is a workaround for this but may be wrong? I have a dynamic
cursor - the statement itself is not known until runtime.
Basically, I want to open a remote (just another schema) dynamic cursor, and
loop though the records (some may be locked) writing the unlocked ones to an
separate table when not locked and updating a status on the remote table.
Because some may be locked I don't want the whole thing suspended so I am using
NOWAIT in the cursor. This #almost# works, but I don't know the name of the
cursor to use in the where current of line. Please can you help on this last
hurdle?

And one more issue - this is Oracle 8.0.4 [I know it isn't supported but we have
to live with this because of the client :( ]
l_sql:='SELECT seq_coding_xfer_data.nextval,'||l_controltab.id||','
  || l_controltab.selecttargetcols
  ||' FROM '||l_controltab.extracttargettablename
  ||' WHERE ' ||l_controltab.collectdataclause
  ||' FOR UPDATE NOWAIT';

  handle:= DBMS_SQL.open_cursor;
  DBMS_SQL.parse (handle, l_sql, dbms_sql.v7);
  -- the following is a modified procedure from you!
  define_all(handle);

  l_status := dbms_sql.execute(handle);

  WHILE ( dbms_sql.fetch_rows(handle) > 0 ) LOOP
    BEGIN
    SAVEPOINT startsection;
    dbms_sql.column_value (handle, 1, l_key);
    dbms_sql.column_value (handle, 2, l_key2);
    dbms_sql.column_value (handle, 3, l_key3);
    l_sql := 'INSERT INTO coding_xfer_data (it_id,
it_control_id,'||l_controltab.inserttargetcols||') (SELECT
'||l_key||','||l_key2||',';
    l_sql := l_sql || l_controltab.selecttargetcols||' FROM
'||l_controltab.extracttargettablename||' WHERE '
||l_controltab.collectdataclause||' and ctrecid='''||l_key3||''')';
     sql_execute (l_sql);

    l_sql := 'UPDATE '||l_controltab.extracttargettablename||' SET
'||l_controltab.collectdataupdate||' WHERE CURRENT OF '||handle;
    sql_execute (l_sql);

 EXCEPTION
   -- When a lock is emcountered, we skip the record and rollback
   WHEN TIMEOUT_ON_RESOURCE THEN
     ROLLBACK TO startsection;
   WHEN OTHERS THEN
     Raise;

 END;

END LOOP;
DBMS_SQL.CLOSE_CURSOR(handle);
Followup:
 where current of is simply "a programming nicety added by the programming
language"

it is not really SQL or part of SQL.

so, there is no cursor to be current of in the above.

what current of and select for update do under the covers is

select for update -- silently added ROWID to your query.
where current of -- silently added "where rowid = :x" to your query.

you can do the same, select rowid out, use it in the update

YOU HAD BETTER USE BIND VARIABLES IN YOUR CODE  don't even think of doing this:

' and ctrecid='''||l_key3||''')';

You BETTER parse an insert statement with binds once (outside the loop) and
bind/execute it in the loop

SAME for the update.
Rating: 4
What about not suspending activity with locked rows? March 09, 2004
Reviewer: D from HK
Thanks for the pointers Tom but as I don't know the select or target columns I
can't really know the details of what I am binding? Also, I am trying to
overcome the lock issue so the process does not sit waiting for ages if someone
performs an update (and in the system apparently this is possible). I am not
aware of a way of skipping locked records apart from using a for loop with a
cursor using the NOWAIT option. I guess you may have an approach we haven't
figured?
Followup:
YOU DO know the columns!

    dbms_sql.column_value (handle, 1, l_key);
    dbms_sql.column_value (handle, 2, l_key2);
    dbms_sql.column_value (handle, 3, l_key3);

apparently, you are inserting 3 columns:

 l_sql := 'INSERT INTO coding_xfer_data
          (it_id, it_control_id,'||l_controltab.inserttargetcols||')
          (SELECT :x, :y, ' || l_controltab.selecttargetcols||
           ' FROM  '||l_controltab.extracttargettablename||
          ' WHERE '  ||l_controltab.collectdataclause||
            ' and ctrecid= :z)';

parse that ONCE (outside the loop) and then bind l_key, l_key2, l_key3 and
execute.

    l_sql := 'UPDATE '||l_controltab.extracttargettablename||
               ' SET '||l_controltab.collectdataupdate||
             ' WHERE rowid = :x';

parse that ONCE and then bind the rowid.
Rating: 5
okay you win! March 09, 2004
Reviewer: DP from HK
Yes valid point Tom, sorry for being dim! We know some of the columns yes.
Just as an aside though, how can we tackle the person who makes an uncommited
change though. Using a cursor for update with NOWAIT would allow this to be
handled without an issue as we could raise an exception an handle it moving onto
the next row?
Thanks for this and the past 2 years you've been helping me.
Followup:
if you just want to process "non-locked" rows - you would have to do something
like (this is psuedo code, you'll have to write as dbms_sql code if you want)

   for x in ( select rowid from t where..... )
   loop
       begin
          select * from t where rowid = x.rowid for update nowait;
          insert into....
          update ....
        exception when resource_locked then null; -- skip the row
        end;
   end loop;

select the rows -- lock those you can, process them -- else skip it.
Rating: 4
Missed the main point March 11, 2004
Reviewer: A reader
Tom

I think I have missed the main caveat. We know some columns which will be common
but the majority will not be known. As this is 8.0.5 I think there is a brick
wall in getting past both

for x in ( 'select rowid from test where status=123' )

and

select * from t where rowid = x.rowid for update nowait;

as the first can not be done in 8.0.5 - ok I can use a workaround here using
dbms_sql BUT the 2nd just can not be done as the name of the cursor does not
exist so I can not use where current of ???? in this case.

Would a better option be to open a cursor on non locked rows be available?
Basically the app we are interfacing to allows users to lock a row (not me idea)
and so we have to try and ensure it doesn't suspend things in our code.

Yours ever hopeful,

D
Followup:
what do you mean it won't work???  you are doing the same processing as you are
now?????

run one query that gets rowid
run the other query that gets your columns EXACTLY as you are now.

l_sql:='SELECT seq_coding_xfer_data.nextval,'||l_controltab.id||','
  || l_controltab.selecttargetcols
  ||' FROM '||l_controltab.extracttargettablename
  ||' WHERE ' ||l_controltab.collectdataclause
  ||' FOR UPDATE NOWAIT';

but use where rowid = :x instead

ops$tkyte@ORA806> l
  1  declare
  2     l_rec emp%rowtype;
  3  begin
  4     for x in (select rowid r from emp)
  5     loop
  6             begin
  7                     select * into l_rec from emp where rowid = x.r for
update NOWAIT;
  8                     dbms_output.put_line( x.r || ' success' );
  9             exception
 10                     when others then
 11                             dbms_output.put_line( x.r || ' ' || sqlerrm );
 12             end;
 13     end loop;
 14     commit;
 15* end;
ops$tkyte@ORA806> /
AAAAnEAAHAAAAWoAAA ORA-00054: resource busy and acquire with NOWAIT specified
AAAAnEAAHAAAAWoAAB ORA-00054: resource busy and acquire with NOWAIT specified
AAAAnEAAHAAAAWoAAC success
AAAAnEAAHAAAAWoAAD success
AAAAnEAAHAAAAWoAAE success
AAAAnEAAHAAAAWoAAF success
AAAAnEAAHAAAAWoAAG success
AAAAnEAAHAAAAWoAAH success
AAAAnEAAHAAAAWoAAI success
AAAAnEAAHAAAAWoAAJ success
AAAAnEAAHAAAAWoAAK success
AAAAnEAAHAAAAWoAAL success
AAAAnEAAHAAAAWoAAM success
AAAAnEAAHAAAAWoAAN success

PL/SQL procedure successfully completed.
Rating: 5
I see the light (at last!) March 13, 2004
Reviewer: A reader
Thanks so much Tom. Your patience is truely astounding!

I was under the impression that using a "for update nowait" in cursor x meant
that it was somehow locked to that cursor and the current of had to be used, not
that it was locked in my session and hence could be updated independently. It is
working and I am a happier person.

One last Q related to this dbms_sql though. In my exception block, I presume I
need to close the cursors. Is there a construct available to test if they are
open as in the exception handler the error could occur when one or two are open?
Otherwise I have the messy

            rollback to lockposition;
            -- close cursors if open
            begin
              DBMS_SQL.close_cursor(handle2);
              DBMS_SQL.close_cursor(handle3);
            EXCEPTION

which is fine if 2 and 3 or just 2 is open but if 3 is open and 2 is closed, 3
will be left open.

Kindest regards

Dicky
Followup:
dbms_sql.is_open will tell you that.
Rating: 5
March 16, 2004
Reviewer: A reader
March 13, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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.
Rating: 5
Encrypted data in varchar2 datatype (trying to change) January 19, 2004
Reviewer: Sami from NJ,USA
Dear Tom,

We are storing users encrypted "MEMORABLE ANSWERS" in VARCHAR2 column (I know
you hate this. Me too.. The design happened long time back.)

So for we did not face any issues because of VARCHAR2 datatype. But very soon we
are going to upgrade from 8i to 9i also changing OS from Solaris to Linux.

I did setup a small test case(as you always do) what we will be facing when we
do exp/imp.

Kindly explain how do we resolve this issue?

SQL> desc t
 Name                                      Null?    Type
 ----------------------------------------- -------- -----------------
 ENC_METHOD                                         VARCHAR2(50)
 DATA                                               VARCHAR2(50)
 VARCHAR2_DATA_ENC                                  VARCHAR2(100)
 RAW_DATA_ENC                                       RAW(100)

SQL>
The end result is...

[1]
a)export and import into the same database
b) select data,vc_decrypt(varchar2_data_enc),decrypt(raw_data_enc) from t;

WORKING FINE.

[2]
a)export from DB1 (server1) and import into DB2(server2)
b) select data,vc_decrypt(varchar2_data_enc),decrypt(raw_data_enc) from t;

ERROR at line 1:
ORA-28232: invalid input length for obfuscation toolkit
ORA-06512: at "SYS.DBMS_OBFUSCATION_TOOLKIT_FFI", line 0
ORA-06512: at "SYS.DBMS_OBFUSCATION_TOOLKIT", line 75
ORA-06512: at "fake_userid.VC_DECRYPT", line 8
ORA-06512: at line 1

Note:-
DB character set is UTF8 for both DB1 and DB2
NLS_LANG is set to AMERICAN_AMERICA.UTF8 on both server1 and server2 (while
doing exp and imp)
OS: Sun Solaris

DEMO table to store encryped data
===================================

create table t (enc_method varchar2(50),data varchar2(50), varchar2_data_enc
VARCHAR2(100),raw_data_enc RAW(100));

Function to support RAW datatype
=================================
CREATE OR REPLACE FUNCTION CRYPT( P_STR IN VARCHAR2 )
return raw
as
     l_data  varchar2(255);
begin
    l_data := rpad( p_str, (trunc(length(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('ABCDEFGHIJKLMNOPQRST987123456ABC') );
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('ABCDEFGHIJKLMNOPQRST987123456ABC')
) );
END;
/

Function to support VARCHAR2 datatype
=====================================

CREATE OR REPLACE FUNCTION PADSTR (P_STR IN VARCHAR2)
return varchar2
as
l_len number default length(p_str);
begin
return to_char(l_len,'fm00000009')
||rpad(p_str,(trunc(l_len/8)+sign(mod(l_len,8)))*8,chr(0));
END;
/

CREATE OR REPLACE FUNCTION VC_ENCRYPT (P_STR VARCHAR2)
return varchar2
is
  key_str varchar2(36) := 'ABCDEFGHIJKLMNOPQRST987123456ABC' ;
  return_val varchar2(256) := NULL;
begin
  dbms_obfuscation_toolkit.DESEncrypt(input_string =>p_str, key_string =>
key_str, encrypted_string => return_val );
  return (return_val) ;
END;
/

CREATE OR REPLACE FUNCTION  VC_DECRYPT (P_STR VARCHAR2)
return varchar2
is
  dec_data varchar2(256);
  key_str varchar2(36) := 'ABCDEFGHIJKLMNOPQRST987123456ABC' ;
  return_val varchar2(256) := NULL;
begin
  dbms_obfuscation_toolkit.DESDecrypt(input_string =>p_str, key_string =>
key_str, decrypted_string => dec_data );
  return_val :=substr(dec_data, 9,length(dec_data) -8);
  return trim(return_val);
END;
/

insert into t values('DESEncrypt','Secret Answer1',vc_encrypt(padstr('Secret
Answer1')),crypt('Secret Answer1'));
insert into t values('DESEncrypt','Secret Answer2',vc_encrypt(padstr('Secret
Answer2')),crypt('Secret Answer2'));
insert into t values('DESEncrypt','Secret Answer3',vc_encrypt(padstr('Secret
Answer3')),crypt('Secret Answer3'));
insert into t values('DESEncrypt','Secret Answer4',vc_encrypt(padstr('Secret
Answer4')),crypt('Secret Answer4'));
insert into t values('DESEncrypt','Secret Answer5',vc_encrypt(padstr('Secret
Answer5')),crypt('Secret Answer5'));
insert into t values('DESEncrypt','Secret Answer6',vc_encrypt(padstr('Secret
Answer6')),crypt('Secret Answer6'));

select data,vc_decrypt(varchar2_data_enc),decrypt(raw_data_enc) from t;

SQL> select data,vc_decrypt(varchar2_data_enc) from t;
select data,vc_decrypt(varchar2_data_enc) from t
            *
ERROR at line 1:
ORA-28232: invalid input length for obfuscation toolkit
ORA-06512: at "SYS.DBMS_OBFUSCATION_TOOLKIT_FFI", line 0
ORA-06512: at "SYS.DBMS_OBFUSCATION_TOOLKIT", line 153
ORA-06512: at "fake_userid.VC_DECRYPT", line 8

IMPORT window
==============
$ echo $NLS_LANG
AMERICAN_AMERICA.UTF8

$ imp fake_userid file=enc3.dmp full=y

Import: Release 8.1.7.3.0 - Production on Mon Jan 19 17:33:22 2004

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

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 file created by EXPORT:V08.01.07 via conventional path
import done in US7ASCII character set and UTF8 NCHAR character set
import server uses UTF8 character set (possible charset conversion)
export client uses UTF8 character set (possible charset conversion)
. importing fake_userid's objects into fake_userid
. . importing table                            "T"          6 rows imported
Import terminated successfully without warnings.

fake_server fake_userid idb21 /opt/home/fake_userid
$ sqlplus fake_userid

SQL*Plus: Release 8.1.7.0.0 - Production on Mon Jan 19 17:33:41 2004

(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 data,vc_decrypt(varchar2_data_enc),decrypt(raw_data_enc) from t;
select data,vc_decrypt(varchar2_data_enc),decrypt(raw_data_enc) from t
            *
ERROR at line 1:
ORA-28232: invalid input length for obfuscation toolkit
ORA-06512: at "SYS.DBMS_OBFUSCATION_TOOLKIT_FFI", line 0
ORA-06512: at "SYS.DBMS_OBFUSCATION_TOOLKIT", line 75
ORA-06512: at "fake_userid.VC_DECRYPT", line 8
ORA-06512: at line 1
Followup:
dump the data using the DUMP() function and verify you have the same bits and
bytes in both databases.
Rating: 3
A FIELD INCLUDES BINARY DATA January 23, 2004
Reviewer: reader
Hi Tom
Is there a easy way to check all of the fields in a table
wheather includ bniary data (probably part of some records)

Any ideas

Thanks!
Followup:
depends -- how do YOU define binary data?

is that a newline?  does it include a tab?  a linefeed?

what you would do is setup a string that includes characters you feel are to be
discovered:

declare
   l_string varchar2(1000) := chr(9)||chr(10)||chr(13); -- tab, CR/LF
   l_string2 varchar2(1000) := rpad('a',length(l_string),'a');
begin
   for x in ( select *
                from t
               where translate( c1, l_string, l_string2 ) <> c1
                  or translate( c2, l_string, l_string2 ) <> c2
                  .......... )
   loop
        ......

there I would find all records that included the tab, carriage return, linefeed
characters.

Since the "definition" of what is "binary data" differs by the character set you
use, you'll have to set up the set of binary characters you want to look for.

Alternatively, you could use the set of "safe characters" and

where replace( translate(c1,l_safe,l_string2), 'a', '' ) is not null
   or ......

(turn all safe characters into 'a', replace 'a' with nothing.  if anything is
left over -- then it contains bad characters.
Rating: 5
Comparing Encrypted Values March 02, 2004
Reviewer: Sami from NJ,USA
Dear Tom,

Is it possible to compare encrypted value(without DECRYPT) which is stored in
the database.
For example,
select count(*)
from encrypted_tab T1
where  not exist (
           select 1
           from another_encrypt_tab T2
           where T1.encrypted_customer_id=T2.encrypted_customer_id)
Followup:
sure, it is just data.  as long as the SAME data was encrypted with the SAME
key, they will encrypt to the SAME value.
Rating: 5
password March 13, 2004
Reviewer: reader
If I connected to the database like,

C:>sqlplus scott/tiger@proddb,

is the password encrypted by oracle while transmission? Does this encryption
occur automatically? Thanks.
Followup:
yes and yes.
March 13, 2004 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!
Rating: 4
how to import sequence January 17, 2004
Reviewer: eashwar from Uganda - East Africa
am going to use one of the techniques mentioned to import a db of 3 gb size.
splitting the objects seperately. however just as sequences, do we need to take
the procedures/triggers also from user_source and user_triggers seperately.
Followup:
if you do user level exports, you'll get the procedures and triggers....
Rating: 5
Importing Triggers February 23, 2004
Reviewer: Arun Mathur from Marietta, GA
Tom,

I did an import using the fromuser and touser options.  For the most part, it
worked well. However, several triggers were invalid because the table name each
trigger referred to started with <old user>.table name, and <old user> is no
longer present in the database. Do you have any suggestions on how I can resolve
these types of errors?

Thanks once again.

Arun
Followup:
you have to fix the code -- the code had hard coded references -- not too much
we can do.

you'll need to read the trigger code out of the database, fix it and put it back
in.
Rating: 5
Please disregard my previous post February 23, 2004
Reviewer: Arun Mathur from Marietta,GA
Thanks.
Rating: 5
imp and tablespace creation March 13, 2004
Reviewer: A reader from Houston
Tom,
I have an export dump file of about 15 schemas taken with the option full=y from
the db db_source. Each of these schemas were created in a different tablespace.
On the db I want to do an import (db_destination) I have created all the
schemas, but with the tablespace names not corresponding to db_source)
I do an import logging in as system, with the following options full = y and
ignore = y. I get an error saying that the tablespace does not exist. When I do
the import with the options fromuser, touser, I do not get the error
1) Before doing the import should I be creating the users with the same
tablespace names as in the source db?
2) How can I know the options used when creating the export file?

Thank you Tom
Followup:
when you do the user level imports, import will attempt the CREATE statement and
if it fails -- it will drop the "first" TABLESPACE statement in it and try again
(hence the object gets created in the touser's default tablespace)

For objects that span multiple tablespaces (partitioned tables, tables with
lobs, iot's with overflows and so on) -- this does not work (and you would have
to pre-create the objects, empty tables to be imported into).

I would do user by user exports (and thus imports) or fromuser/touser if the
entire thing was exported.  The users should be pre-created with their default
tablespaces set up and you should expect from time to time to have to pre-create
the target objects in the target database (if the tablespaces do not exist and
you have these multi-tablespace objects).  You can use "imp ....
INDEXFILE=foo.sql" to get the template DDL you need to pre-create these objects
easily.

the options used by export are not recorded, you would have to know what options
were used via the old fashioned "ask the person that did it" method....
Rating: 5
import-tablespace March 13, 2004
Reviewer: A reader from houston
Tom,
When doing the user by user import, in what order will the users be imported?
How Will fk's referencing other schemas be imported
Ex.
fromuser = user1, user2,...
touser = user1, user2
table1 in user1 references table2 in user2.
Q1) Will the FK be created in this case?
Q2) Will public synonyms be imported when doing the import with fromuser ..
touser ...option?

Thank you
Followup:
tables and then fkeys.

if you have fkeys that cross schemas, it'll be up to you to import the objects
in the correct order when using fromuser/touser (or just do constraints=no, imp,
then just import the constraints later)
March 14, 2004 -- Thanks for the question regarding "Primary Key created does not reflect in DBA_INDEXES", version 8.1.7.2.1
You Asked
Hi Tom,

We would like to know more about the DBA_INDEXES and how it works internally.

Our problem here is we have a Primary Key created for a table but does not have
corresponding information reflected in dba_indexes.

SQL> SELECT * FROM dba_constraints WHERE table_name = 'STD_SEM_HIST' and
constraint_type in ('P','U');

OWNER CONSTRAINT_NAME C TABLE_NAME
------------------------------ ------------------------------ -
------------------------------
SEARCH_CONDITION
--------------------------------------------------------------------------------
R_OWNER R_CONSTRAINT_NAME DELETE_RU STATUS
DEFERRABLE
------------------------------ ------------------------------ ---------
-------- --------------
DEFERRED VALIDATED GENERATED BAD RELY LAST_CHANGE
--------- ------------- -------------- --- ---- -----------
IISDBM US_SMH_PK_AYSMMN P STD_SEM_HIST
ENABLED
NOT DEFERRABLE
IMMEDIATE VALIDATED USER NAME 28-JUN-1999
IISDBM UI_SMH_UK_AYSMMNCC U STD_SEM_HIST
ENABLED
NOT DEFERRABLE
IMMEDIATE VALIDATED USER NAME 28-JUN-1999

SQL> select * from dba_indexes
where index_name in (SELECT constraint_name FROM dba_constraints WHERE
table_name = 'STD_SEM_HIST' and constraint_type in ('P','U'));

no rows selected

We tried to insert a duplicate record and we get the following error (which
shows that the Primary Key is taking effect) :

SQL> insert into std_sem_hist
2 (CRSE_C,SEM_ACAD_Y,SEM_C,SEM_DEG_C,SEM_FAC_C,SEM_SEX_C,SEM_STS_C,STD_MATRIC_N)
3 values
4 ('BDG4', '1998/1999', '1', '101', '37', 'F', 'L', '954460M10')
5 ;
insert into std_sem_hist
*
ERROR at line 1:
ORA-00001: unique constraint (IISDBM.US_SMH_PK_AYSMMN) violated

Can you tell us why it's behaving this way ?

PS: We logged a TAR with the Oracle support, unfortunately they are not able to
give us a valid explanation. But they did tell us what we already know...
and we said...
You are making the false assumption that a primary key will ALWAYS create an
index -- it will not.

A primary key will use an existing indexe (be it unique or non-unique) to
enforce itself.

Consider the following example:

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop table t;
Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( a int, b int, c 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 constraint_name, constraint_type from
user_constraints;

CONSTRAINT_NAME                C
------------------------------ -
T_PK                           P

ops$tkyte@ORA817DEV.US.ORACLE.COM> select index_name, uniqueness from
user_indexes;

INDEX_NAME                     UNIQUENES
------------------------------ ---------
T_IDX                          NONUNIQUE

there, we have a primary key constraint and a single NON-UNIQUE index (that is
not even on the primary key alone!!).  In this case the primary key "kidnapped"
the existing index and will utilize it to enforce unqiueness.

Now consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop table t;
Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( a int, b int, c 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 constraint_name, constraint_type from
user_constraints;

CONSTRAINT_NAME                C
------------------------------ -
T_PK                           P

ops$tkyte@ORA817DEV.US.ORACLE.COM> select index_name, uniqueness from
user_indexes;

INDEX_NAME                     UNIQUENES
------------------------------ ---------
T_IDX                          NONUNIQUE
T_PK                           UNIQUE

ops$tkyte@ORA817DEV.US.ORACLE.COM

Here we do have a unique index created.  The add constriant came BEFORE the
create index -- hence there was no suitable index existing and the primary key
created on for us.  Notice that it created a unique index by default -- this is
not always the case as shown below:

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop table t;

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( a int, b int, c int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t add constraint t_pk primary
key(a) deferrable;

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 constraint_name, constraint_type from
user_constraints;

CONSTRAINT_NAME                C
------------------------------ -
T_PK                           P

ops$tkyte@ORA817DEV.US.ORACLE.COM> select index_name, uniqueness from
user_indexes;

INDEX_NAME                     UNIQUENES
------------------------------ ---------
T_IDX                          NONUNIQUE
T_PK                           NONUNIQUE

Here, by using a deferrable constraint, we can see the primary key actually
created a NON-UNIQUE index for us.
Review & Followup

Rating: 5
Woudn't it be beneficial to create a unique index for a primary key -avoid confusions March 14, 2002
Reviewer: Ravi from USA
Hi Tom,
It was very useful. But, for administration/maintenance point-of-view, wouldn't
it be beneficial (ofcourse, that's not the way it is, but..) to have a unique
index automatically when we create a primary key?? This is instead of
"kidnapping" (your word, I like it) an existing index (especially even when it
is a composite one) and making it as a part of unique??
Thanks!
Ravi.
Followup:
No, no it wouldn't.

First of all -- the index NEED NOT BE UNIQUE, we have and support deferrable
constraints.

Secondly, there are cases where having the index by non-unique is EXTREMELY
advantageous (data warehouse where i must disable/enable the constraint
sometimes.  If I use a non-unique index, we can get rid of the constraint
WITHOUT losing the index and put the constraint BACK without having to rebuild
it).

There is nothing, but nothing that says "primary keys MUST build a unique
constraint".  As long as an index exists with the primary key columns in any
order on the leading edge of the index -- that is more then good enough.
Rating: 5
March 14, 2002
Reviewer: A reader
What i understood so far was PRIMARY KEY enforce uniqueness by creating UNIQUE
index on column , But i am now wrong.

 Can you explain how PRIMARY KEY maintain uniqueness WITHOUT unique index ?
Followup:
The constraint is enforced procedurally by the server as always -- the index is
used to speed access to the data that needs to be checked.  The index is a
performance thing with constraints, thats all.  Whether it is unique or not --
it is a performance thing.
Rating: 4
Index disappears when pramary key is disabled March 21, 2002
Reviewer: Ravi Kumar from Chicago, IL
Hi Tom,

There is a primary key on a table with two columns. When I disable the
primarykey and run queries, the index is not used. So, how to use that index
even when I disable the primary key.

Thanks,
Ravi.
Followup:
use a non-unique index.  It is NOT that the index isn't used -- it is that the
(unique) index is DROPPED when you disable the constraint.

This demonstrates one way to achieve that, just create the constraint as
DEFERRABLE:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int );

Table 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>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select index_name, uniqueness from
user_indexes;

INDEX_NAME                     UNIQUENES
------------------------------ ---------
T_PK                           UNIQUE

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 = 1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   INDEX (UNIQUE SCAN) OF 'T_PK' (UNIQUE)

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t disable constraint t_pk;

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where x = 1;

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>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t drop constraint t_pk;

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t add constraint t_pk primary
key(x) DEFERRABLE;

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select index_name, uniqueness from
user_indexes;

INDEX_NAME                     UNIQUENES
------------------------------ ---------
T_PK                           NONUNIQUE

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 = 1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   INDEX (RANGE SCAN) OF 'T_PK' (NON-UNIQUE)

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t disable constraint t_pk;

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where x = 1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   INDEX (RANGE SCAN) OF 'T_PK' (NON-UNIQUE)

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA817DEV.US.ORACLE.COM>
Rating: 5
"Primary Key created does not reflect in DBA_INDEXES", version 8.1.7.2.1 July 12, 2002
Reviewer: asake from London, UK
Extremely useful as always.

I have a problem in a DataW/H with deferrable constraints using Oracle 8.1.5.0 .
After a dataload we ended up with duplicate rows and nulls being inserted in
tables with composite primary keys. The tables all have the primary key as
deferrable novalidate.

I understand that with deferrable constraints, Oracle creates a NON-UNIQUE INDEX
which is what we have on these tables, the issue I have is why we can insert
duplicates and nulls in columns defined as not null during table creation and
are columns in the primary key.

Is this standard behaviour?

Thanks
Followup:
how did you do the load?
what is the status of the constraint in dba_constraints?
Rating: 4
July 12, 2002
Reviewer: A reader
Hi Tom,

The data was loaded using Powermart. The status of all constraints in
dba_constraints is enabled.

Asake
Followup:
I believe that powermart is loading the bad data -- it should be scrubbing it
for you.

They are using some of our data warehousing features that allow you to enable
constraints -- but not validate them -- letting the database "trust" you did the
right thing.  Powermart -- isn't doing the right thing.

consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t add constraint t_pk primary
key(x) deferrable;

Table altered.

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> 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> alter table t enable novalidate constraint
t_pk ;

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

         X
----------
         1
         1

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1 );
insert into t values ( 1 )
*
ERROR at line 1:
ORA-00001: unique constraint (OPS$TKYTE.T_PK) violated

ops$tkyte@ORA817DEV.US.ORACLE.COM> select status from user_constraints where
constraint_name = 'T_PK';

STATUS
--------
ENABLED

ops$tkyte@ORA817DEV.US.ORACLE.COM>

Powermart must be doing the same thing (enabling the constraint with NOVALIDATE)
-- but they didn't scrub the data first.

Time to give them a buzz....
Rating: 4
July 15, 2002
Reviewer: asake
Hi Tom,

Thanks for the update.
Enabling a constraint "novalidate", should not allow new data that violates the
constraint to be entered. If the constraint is also deferred, this means the
data is only checked when the transaction commits. I don't understand how
Powermart comes into play as Oracle is menat to enforce the integrity of the
data. I have done a straight insertion into my test table and I can insert
duplicates and nulls without using Powermart.

Kindly clarify: table created originally with composite primary key and primary
key set as deferrable novalidate allows the insertion of nulls and duplicates?

Thanks
Followup:
Give me a test case including the CREATE TABLE.  What you describe now is
different then before.  Before you said "after a load..." and I showed how that
can happen

Prove to me with a cut and paste from sqlplus that what you say is correct.
show the query that proves the constraint is there and working.  Use my
print_table routine like this:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1035431863958
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec print_table( 'select * from
user_constraints where constraint_name = ''T_PK'' ' )
OWNER                         : OPS$TKYTE
CONSTRAINT_NAME               : T_PK
CONSTRAINT_TYPE               : P
TABLE_NAME                    : T_OLD
SEARCH_CONDITION              :
R_OWNER                       :
R_CONSTRAINT_NAME             :
DELETE_RULE                   :
STATUS                        : ENABLED
DEFERRABLE                    : DEFERRABLE
DEFERRED                      : IMMEDIATE
VALIDATED                     : NOT VALIDATED
GENERATED                     : USER NAME
BAD                           :
RELY                          :
LAST_CHANGE                   : 12-jul-2002 17:33:59
-----------------

PL/SQL procedure successfully completed.

so we can read it.  show us the primary key -- tell us the columns -- insert
some duplicate data, commit and show us the outcome.

show me like I show you in short.
Rating: 5
Excellent insight into Primary Key constraint use of indexes August 01, 2002
Reviewer: Dave Anderson from Rhode Island, USA
This was solid info into how PK constraints use indexes.
Rating: 4
How to determine what index is used to enforce constraint. September 17, 2002
Reviewer: David Fitzwater from Sunny, Portland, OR
On version 8.06, is there a view or method which will determine what index is
enforcing the primary or alternate key uniqueness?

I use the technique a lot (i.e non unique index for enforcing constraint).  It's
really handy when other data is accessed via the primary key, but there must be
other indexes (precluding iot).  It would be handy to quickly access where this
technique is used in those cases where one is considering some maintenance or
changes to the underlying index.
Followup:
In 9i -- there is a column in user_constraints that gives you this but not
before.

Before that, see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1360203050892
for something that will help you
Rating: 5
is this the design? March 13, 2004
Reviewer: reader
Why oracle has to drop the unique index when I disable the PK constraint?
Thanks.
Followup:
it doesn't "have to" but -- if it did not, why bother dropping the constraint?
The unique index would still make the stuff "unique"

ops$tkyte@ORA9IR2> create table t ( x int constraint x_pk primary key );

Table created.

ops$tkyte@ORA9IR2> select object_name, object_name from user_objects;

OBJECT_NAME                    OBJECT_NAME
------------------------------ ------------------------------
T                              T
X_PK                           X_PK

ops$tkyte@ORA9IR2> alter table t drop constraint x_pk keep index;

Table altered.

ops$tkyte@ORA9IR2> select object_name, object_name from user_objects;

OBJECT_NAME                    OBJECT_NAME
------------------------------ ------------------------------
T                              T
X_PK                           X_PK
Rating: 4
PK can not use multi-column unique index March 25, 2004
Reviewer: JW from Denver, CO
  I opened TAR 3585361.994 with a test case because a PK would not use a unique
multi-column index.  It would use the index if it was not-unique.
  I received the response that this is expected behavior.  So keep in mind this
will not work with a unique multi-column index.
March 14, 2004 Ak -- Thanks for the question regarding "procedure", version 81.5
You Asked
Hi tom,
 1)  I have a problem in following procedure ,i need to assign a
dblink name in a variable at the run time and use that variable
to pull the data from diff site.But when i use it,it doesn't seems
to taking the value of that VARIABLE,instead it is taking that varaible(site)
..itself as a dblink.

2)And another question is...say if the network is down for one site  ,hence it
cann't use a dblink to pull the data,what i want here is
it should log the error. and CONTINUE FOR ANOTHER SITE,IF THIS
ALSO NOT AVAILABLE...,log the error and continue for another site.

3)Another question is ,since now we are changing our application
to Use bind varaible,what we are planning now is ....we will have
one DLL WHICH will be written in 'pro *c' and whereever in vb
we have used sql statement,we will call this dll and pass the sql string to be
executed.Because in vb we tested ,it doesn't seems to
be working,but pro*c does it easily(ofcourse we have tested in ).Your comment
please.

create or replace PROCEDURE test IS
  site CHAR(50):='';
  i number;
  ERROR_MSG VARCHAR2(200);
BEGIN

   for i in 1..3  loop
       if i=1 then
        site:='a';
         else
           if i=2 then
        site:='b';
        else
       if i=3 then
         site:='c';
          END IF;
  END IF;
END IF;

INSERT INTO x SELECT a1,a2
 FROM x1@||site WHERE t=t1'

INSERT INTO y SELECT a3,a4 FROM x2@||site
WHERE SER='3';
commit;
End loop;

exception
 when others then
 error_msg := SUBSTR(SQLERRM, 1, 100)|| siteA ;
 insert into error values(error_msg);
END test ;

Awaiting your response.
Thanks,
 Regards
   Ak
and we said...
1) you need to use dynamic sql.

  execute immediate 'insert into x select a1,a2 from x1@' || site || ' where
t=t1';

2) use exception blocks

   begin
     execute immediate ...
     commit;
   exception
     when others then
            rollback;
            log_error;
   end;

   begin
     execute immediate ...
     commit;
   exception
     when others then
            rollback;
            log_error;
   end;

....

3) VB most certainly supports binding.  Please re-read the docs for VB / ODBC or
whatever API you are using.  You don't need to drop down to pro*c.
Review & Followup

Rating: 4
Binding works in VB February 26, 2002
Reviewer: Ravi from NY,NY
Binding works in VB. See the code below where there two methods - one using
command object (binding) and another using dynamic sql. You will see that the
command object results in 2 hard parses, whereas dynamic sql results in 100
*hard* parses. If you are testing over and over, remember to change the 'i = x
to y' with new values of x & y because the old values will be in the oracle
cache.

This works only with the Oracle provider and not with MSDAORA. The best solution
of course is to move to a SP.

Results from program :
Command object Stats
parse count (total)          205
parse count (hard)           2
execute count  104
Dynamic SQL Stats
parse count (total)          205
parse count (hard)           100
execute count  104

... and the code:
Private Sub Form_Load()

    Dim Cn As New ADODB.Connection
    Dim rs As Recordset
    Dim sSQL As String
    Dim cmd As Command
    Dim statsSQL As String

    statsSQL = "select a.name name, b.value " & _
       "from v$statname a, v$mystat b " & _
       "Where a.statistic# = b.statistic# " & _
       "and a.name in " & _
       " ('parse count (total)', " & _
       " 'parse count (hard)', " & _
       " 'execute count') "

    Cn.Open "Provider=OraOLEDB.Oracle.1;Data source=oradb_sh;User
ID=scott;Password=tiger;"

    sSQL = "select * from emp where empno = ?"

    For i = 6001 To 6100
        Set cmd = New ADODB.Command
        Set cmd.ActiveConnection = Cn
        cmd.CommandText = sSQL
        cmd.CommandType = adCmdText
        cmd.Parameters.Append cmd.CreateParameter(, adVarNumeric, adParamInput,
, i)

        Set rs = cmd.Execute
        If rs.EOF And rs.BOF Then
             ' ensure recordset is parsed
        End If
        rs.Close
        Set rs = Nothing: Set cmd = Nothing
    Next i

    Set rs = Cn.Execute(statsSQL)

    Debug.Print "Command object Stats"
    Do While Not rs.EOF
        Debug.Print rs(0), rs(1)
        rs.MoveNext
    Loop
    rs.Close
    Set Cn = Nothing

    Cn.Open "Provider=OraOLEDB.Oracle.1;Data source=oradb_sh;User
ID=scott;Password=tiger;"

    For i = 6101 To 6200
        sSQL = "select * from emp where empno = " & i
        Set rs = Cn.Execute(sSQL)
        If rs.EOF And rs.BOF Then
             ' ensure recordset is parsed
        End If
        Set rs = Nothing
    Next i

    Debug.Print "Dynamic SQL Stats"
    Set rs = Cn.Execute(statsSQL)

    Do While Not rs.EOF
        Debug.Print rs(0), rs(1)
        rs.MoveNext
    Loop
    rs.Close

End Sub
Rating: 3
OraOLEDB validates and fetches the metadata only for SELECT SQL statements January 19, 2004
Reviewer: A reader
"OraOLEDB validates and fetches the metadata only for SELECT SQL statements. "

This statement is killing us.

It is in the "Oracle Provider for OLE DB Developer's Guide" version 9.2.0.4.8
(latest and greatest), Chapter 2 - Features of OraOLEDB under the section
"Preparing Commands".

We have a VB application that utilizes the ADO.Recordset.save method and passing
it the "adPersistXML" parameter to generate XML from the database.

This application was originally written to interface with SQL Server and we are
converting it to Oracle.

When executing a procedure from VB, the METADATA about the tables and columns
involved in the selection of the rowset *is not returned*!
If you simply embed a SQL statement in the VB code, then the adPersistXML
parameter retrieves the metadata properly, and generates the XML just fine. Here
is an outtake of the XML generated:

<s:AttributeType name='EMPLOYEE_ID' rs:number='1' rs:writeunknown='true'
rs:basetable='EMPLOYEES' rs:basecolumn='EMPLOYEE_ID'>
            <s:datatype dt:type='number' rs:dbtype='numeric' dt:maxLength='19'
rs:scale='0' rs:precision='6' rs:fixedlength='true'
             rs:maybenull='false'/>

The stored procedure execution simply leaved out the rs:basetable= and
rs:basecolumn=.

I have exhausted google groups, MS technet, and Metalink knowledgebases.

I am submitting this question to metalink Oracle OLE DB provider forum, and a
TAR, because it is urgent that we figure this out.

I know we can do this lots of different ways, but this is the way it is written
to work, and finding this answer would be the easiest path to success. We are
investigating generating the XML directly from Oracle, (which we know we can do,
but we are also shaping this data using the msdatashape provider, and that makes
it more difficult to duplicate exactly)(We are looking at the XDK to see if this
is feasible)

I have also tried MDAC 2.8 MSDAORA, and Oracle's latest ODBC drivers. Maybe I am
missing a setting from one of these drivers that magically returns the
metadata!?

Of course PLSQLRSet = TRUE in the connection object. Remember, the procedure
returns the data fine to VB, its just that the metadata is gone!

Please help.
Followup:
sorry -- far too many things in there that I just don't work with -- windows
properietary stuff being #1 on the list.

I've never used any of the stuff that runs only on windows -- i use too many
platforms for that to be realistic.  sorry -- won't be able to help on this one.
Rating: 5
no problem January 20, 2004
Reviewer: A reader
I opened a tar, and the support individual thinks it may be a bug.

Thanks for reading it anyway!
Rating: 5
May be a bug? January 20, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
Sorry for jumping in on this...

Just curious about 2 things:

- Why would it be a bug if it is functioning as documented?

- What do you expect to see as far as metadata for a stored procedure?  If you
drop down to OCI and do a describe on a stored procedure, you will get info like
number of parameters, names of parameters, types of parameters, etc... is that
what you are expecting to receive via OleDB?  Doing a describe on a stored
procedure will not tell you what tables/views it uses, etc...

Anyway, just curious,

- Mark
Followup:
I think they are using ref cursors and that is what they mean by a result set
from the stored procedure -- and the ref cursor is not being "described"
Rating: 5
RE: May be a bug January 20, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
...the ref cursor is not being "described"

as in dbms_sql.parse/dbms_sql.describe_columns I guess?

Anyway, I was surprised that Oracle support may consider it a bug since it
appears to be functioning as documented (unless I am totally misreading the
documentation or problem)...

- Mark
Rating: 5
I'll be glad to fill you in on the details January 21, 2004
Reviewer: A reader
Here is *the* response from Oracle support regarding this issue.
**********************************************************
Called customer back. Explained that the full information being returned was
actually due to a typo in the code on this end. When this was corrected, the
basetable and basecolumn information was NOT returned, even when using strongly
typed cursors.

Read to him the response from development when this issue was posed to them:

"Oracle REF Cursors do not provide the base table name and base column name
information. This is more of a OCI/PLSQL limitation rather than OLE DB provider
limitation and that's the reason you see the same behavior with Microsoft as
well as Oracle provider." -- As well as ODBC which isn't mentioned.

Customer's concern: they need the base table and base column metadata. They send
this information to their thin client application, where's it's manipulated and
then used to build insert statements. These insert statements cannot be built
without the table and column information.
**********************************************************

If you read my previous post carefully, you will understand what we are doing.

We are trying to pass a result set from Oracle to a calling VB program. You must
use REF CURSORS to accomplish this, yes? The VB program uses the
"ADORecordset.save" method with a passed of "adPersistXML", such that the result
set *returned in the form of a REF CURSOR from an Oracle stored procedure* is
changed into XML automagically into a predefined format (That is already coded
for). That generated XML lacks base table name and base column names.

If you embed a straight SQL statement into your VB code, the meta-data we are
looking for appears.

And you understand that *no one* wants to embed SQL in their VB code. So - since
REF CURSORS are the only option of returning result sets to a VB program, and
this meta-data is not returned we are in a bit of a quagmire because of the
amount of unexpected work before us, and the limited amount of time we have to
perform it.

It seems to me that REF CURSORs should indeed return this information. I'm an
Oracle bigot, but this is a bummer.

Of course, it could be argued that we should be using java or some other
technology to do this work, but we really don't have a choice as this
application is already written, and we are just converting it to use Oracle.

The painful thing here is I've been an Oracle DBA for over 6 years now (I know
that's not that long, but it's long enough). Coming to this project, everyone is
a SQL Server bigot. I've shown them some of the lackings of SQL Server compared
to Oracle, but of course, they'll have nothing of them. And so I'm constantly
hearing "Oracle stinks", "I'm so furstrated", "WHAaaa Whaaaaaa WHaaaaaaaaaaa".
And now, when I need Oracle software to come through for me to put and end to
these whining baby cries.. .  . .alas. No meta-data from REF CURSORS.

lol

Work Rules!
Rating: 5
to further the conversation January 21, 2004
Reviewer: Justin from PA
Because of the above, we are now going to generate our XML right out of Oracle
and pass it through the system.

We've tried using the Oracle supplied packages to get the XML with the schema,
but found the following:

DBMS_XMLGEN currently only allows:
dtdOrSchema IN number := NONE

and though the Supplied package pdf says right above DBMS_XMLQUERY, not to use
it if you can help it, we tried it with metaType = SCHEMA, but the base table
names and base column names don't come out.

Here is some sample code from our try with DBMS_XMLQUERY, as DBMS_XMLGEN doesn't
return schemas yet.

Sample code:

CREATE OR REPLACE
FUNCTION simplexml
RETURN CLOB
IS
BEGIN

return DBMS_XMLQUERY.GETXML( ' SELECT * FROM emp where rownum < 2',
dbms_xmlquery.schema );

END;
/

select simplexml from dual;

So what do we do if we want schema information to be generated also?

Any help would be more than appriciated.

I have continued my Oracle TAR on the matter, but thought this audience could
benefit.

Thanks for the opportunity Tom.
Followup:
I asked Sean Dillon, our local XML technologist, to take a look at this and
here's what he had to say:
-----------

Hi Justin,

When I do this on 9.2.0.1.0 and above it works swimmingly.  That being said, let
the support analyst working your TAR know that this test case produces an
accurate result on 9.2.0.1, so maybe it's a db version issue.  My results:

SQL> !cat test.sql

create or replace function simplexml
  return clob
is
begin
  return dbms_xmlquery.getxml('select * from scott.emp where rownum < 2',
                              dbms_xmlquery.schema );
end;
/

select simplexml from dual
/

SQL> @test
Function created.

SIMPLEXML
--------------------------------------------------------------------------------
<?xml version = '1.0'?>
<DOCUMENT xmlns:xsd="
">http://www.w3.org/2000/10/XMLSchema">
   <xsd:schema xmlns:xsd="
">http://www.w3.org/2000/10/XMLSchema">
      <xsd:element name="ROWSET">
         <xsd:complexType>
            <xsd:sequence>
               <xsd:element name="ROW" minOccurs="0" maxOccurs="unbounded">
                  <xsd:complexType>
                     <xsd:sequence>
                        <xsd:element name="EMPNO" type="xsd:integer"
minOccurs="0"/>
                        <xsd:element name="ENAME" type="xsd:string"
nullable="true" minOccurs="0"/>
                        <xsd:element name="JOB" type="xsd:string"
nullable="true" minOccurs="0"/>
                        <xsd:element name="MGR" type="xsd:integer"
nullable="true" minOccurs="0"/>
                        <xsd:element name="HIREDATE" type="xsd:string"
nullable="true" minOccurs="0"/>
                        <xsd:element name="SAL" type="xsd:float" nullable="true"
minOccurs="0"/>
                        <xsd:element name="COMM" type="xsd:float"
nullable="true" minOccurs="0"/>
                        <xsd:element name="DEPTNO" type="xsd:integer"
nullable="true" minOccurs="0"/>
                     </xsd:sequence>
                     <xsd:attribute name="num" type="xsd:integer"/>
                  </xsd:complexType>
               </xsd:element>
            </xsd:sequence>
         </xsd:complexType>
      </xsd:element>
   </xsd:schema>
   <ROWSET xmlns:xsi="
http://www.w3.org/2000/10/XMLSchema-instance
xsi:noNamesp
aceSchemaLocation="#/DOCUMENT/xsd:schema[not(@targetNamespace)]">
      <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>
</DOCUMENT>

Hope that helps!

_smd_
Rating: 5
Thanks Justin January 22, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
Justin,

Thanks for all the info...  Though I can do nothing to help you it was
nonetheless interesting to read through it all.  I understand what you are
wanting to do and why, I was just surprised that something that appeared to me
to be working as documented (i.e. only with select statements - no stored
procedures for you!) would be labeled a bug by support.  As you have no doubt
seen there are several similar posts on MetaLink (including your own thread) on
this very topic.

Best of luck from a fellow Oracle bigot!

- Mark
Rating: 3
I should have been more clear with my question January 23, 2004
Reviewer: Justin from PA
I have found a good way to be much more clear in my question. When I mentioned
schema information, I guess I meant "extended schema information".

Thanks very much for your response though!

I have changed the example a bit to express a more real world situation. A
normal complex query will probably use column aliases in it's construction, so I
added some to show what happens.

Here is the code:

***********************************************************
justin@DEV> set lines 150
justin@DEV> CREATE OR REPLACE
  2  FUNCTION simplexml
  3  RETURN CLOB
  4  IS
  5  BEGIN
  6
  7  return DBMS_XMLQUERY.GETXML( ' SELECT empno "emp.empno"
  8                                 , ename "e.ename"
  9                                 , job "e.job"
 10                                 , mgr "e.mgr"
 11                                 , hiredate "e.hiredate"
 12                                 , sal "e.sal"
 13                                 , comm "e.comm"
 14                                 , deptno "e.deptno"
 15                                 FROM emp where rownum < 2'
 16                                 , dbms_xmlquery.schema );
 17
 18  END;
 19  /

Function created.

Elapsed: 00:00:00.00
justin@DEV>
justin@DEV> select simplexml from dual;

SIMPLEXML
=================================================================================
=====================================================================
<?xml version = '1.0'?>
<DOCUMENT xmlns:xsd="
">http://www.w3.org/2000/10/XMLSchema">
   <xsd:schema xmlns:xsd="
">http://www.w3.org/2000/10/XMLSchema">
      <xsd:element name="ROWSET">
         <xsd:complexType>
            <xsd:sequence>
               <xsd:element name="ROW" minOccurs="0" maxOccurs="unbounded">
                  <xsd:complexType>
                     <xsd:sequence>
                        <xsd:element name="e.empno" type="xsd:integer"
minOccurs="0"/>
                        <xsd:element name="e.ename" type="xsd:string"
nullable="true" minOccurs="0"/>
                        <xsd:element name="e.job" type="xsd:string"
nullable="true" minOccurs="0"/>
                        <xsd:element name="e.mgr" type="xsd:integer"
nullable="true" minOccurs="0"/>
                        <xsd:element name="e.hiredate" type="xsd:string"
nullable="true" minOccurs="0"/>
                        <xsd:element name="e.sal" type="xsd:float"
nullable="true" minOccurs="0"/>
                        <xsd:element name="e.comm" type="xsd:float"
nullable="true" minOccurs="0"/>
                        <xsd:element name="e.deptno" type="xsd:integer"
nullable="true" minOccurs="0"/>
                     </xsd:sequence>
                     <xsd:attribute name="num" type="xsd:integer"/>
                  </xsd:complexType>
               </xsd:element>
            </xsd:sequence>
         </xsd:complexType>
      </xsd:element>
   </xsd:schema>
   <ROWSET
xmlns:xsi="
http://www.w3.org/2000/10/XMLSchema-instance
xsi:noNamespaceSchemaLocation="#/DOCUMENT/xsd:schema[not(@targetN
amespace)]">
      <ROW num="1">
         <e.empno>7369</e.empno>
         <e.ename>SMITH</e.ename>
         <e.job>CLERK</e.job>
         <e.mgr>7902</e.mgr>
         <e.hiredate>12/17/1980 0:0:0</e.hiredate>
         <e.sal>800</e.sal>
         <e.deptno>20</e.deptno>
      </ROW>
   </ROWSET>
</DOCUMENT>

***********************************************************

2 things are problems with this output in our situation.

1) The *base table names* and *base column names* are not included in the
output.

We need the base table names and base column names in the XML attributes because
we persist this XML file to a thin-client for them to manipulate. When they
manipulate it, we build insert or update statements from the XML to execute
against the database, and as such, we must know the base table names, and base
column names each of these elements have come from.

Furthermore, we tried using our full table names as aliases so that we could
simply get the base table name and base column name from the <name=> attribute,
but we have run into Oracle's 30 character identifier limit, which makes that
impossible for us.

2) The hiredate column is being expressed as type="xsd:string".

So, the more clear questions are:

1) How do we get base table names and base column names in the output? Like
this:

<?xml version = '1.0'?>
<DOCUMENT xmlns:xsd="
">http://www.w3.org/2000/10/XMLSchema">
   <xsd:schema xmlns:xsd="
">http://www.w3.org/2000/10/XMLSchema">
      <xsd:element name="ROWSET">
         <xsd:complexType>
            <xsd:sequence>
               <xsd:element name="ROW" minOccurs="0" maxOccurs="unbounded">
                  <xsd:complexType>
                     <xsd:sequence>
                        <xsd:element name="e.empno" type="xsd:integer"
basetable='EMP' basecolumn='EMPNO' minOccurs="0"/>
                        <xsd:element name="e.ename" type="xsd:string"
basetable='EMP' basecolumn='ENAME' nullable="true" minOccurs="0"/>
                        <xsd:element name="e.job" type="xsd:string"
basetable='EMP' basecolumn='JOB' nullable="true" minOccurs="0"/>
                        <xsd:element name="e.mgr" type="xsd:integer"
basetable='EMP' basecolumn='MGR' nullable="true" minOccurs="0"/>
                        <xsd:element name="e.hiredate" type="xsd:date"
basetable='EMP' basecolumn='HIREDATE' nullable="true" minOccurs="0"/>
                        <xsd:element name="e.sal" type="xsd:float"
basetable='EMP' basecolumn='SAL' nullable="true" minOccurs="0"/>
                        <xsd:element name="e.comm" type="xsd:float"
basetable='EMP' basecolumn='COMM' nullable="true" minOccurs="0"/>
                        <xsd:element name="e.deptno" type="xsd:integer"
basetable='EMP' basecolumn='DEPTNO' nullable="true" minOccurs="0"/>
                     </xsd:sequence>
                     <xsd:attribute name="num" type="xsd:integer"/>
                  </xsd:complexType>
               </xsd:element>
            </xsd:sequence>
         </xsd:complexType>
      </xsd:element>
   </xsd:schema>
   <ROWSET
xmlns:xsi="
http://www.w3.org/2000/10/XMLSchema-instance
xsi:noNamespaceSchemaLocation="#/DOCUMENT/xsd:schema[not(@targetN
amespace)]">
      <ROW num="1">
         <e.empno>7369</e.empno>
         <e.ename>SMITH</e.ename>
         <e.job>CLERK</e.job>
         <e.mgr>7902</e.mgr>
         <e.hiredate>12/17/1980 0:0:0</emp.hiredate>
         <e.sal>800</e.sal>
         <e.deptno>20</e.deptno>
      </ROW>
   </ROWSET>
</DOCUMENT>

2) And why is a date being shown as a *string* and not a date field? Seems like
that should come out as a Date.
Followup:
Hi Justin, Sean here.

What you're trying to do isn't supported through DBMS_XMLQuery.  You'll have to
use a custom XML Schema generator if you need to do this.

So here's what I came up with.  Seems lenghty but it's actually fairly simple.
I'm using SQLX, a new set of SQL operators in Oracle9iR2 to generate the
results.  The Schema is generated manually (i.e. through SQLX), but it lets you
include whatever attributes you want.  Here's the query:

select xmlelement ("DOCUMENT",
         xmlattributes ('
http://www.w3.org/2000/10/XMLSchema'
as "xmlns:xsd"),
         xmlelement ("xsd:schema",
           xmlattributes ('
http://www.w3.org/2000/10/XMLSchema'
as "xmlns:xsd"),
           xmlelement ("xsd:element",
             xmlattributes ('ROWSET' as "name"),
             xmlelement ("xsd:sequence",
               xmlelement ("xsd:element",
                 xmlattributes ('ROW' as "name",
                                '0' as "minOccurs",
                                'unbounded' as "maxOccurs"),
                 xmlelement ("xsd:complexType",
                   xmlelement ("xsd:sequence",
                     xmlelement ("xsd:element",
                       xmlattributes ('EMPNO' as "name",
                                      'xsd:integer' as "type",
                                      'SCOTT.EMP' as "baseTable",
                                      'EMPNO' as "baseColumn",
                                      '0' as "minOccurs")
                     ),
                     xmlelement ("xsd:element",
                       xmlattributes ('ENAME' as "ename",
                                      'xsd:string' as "type",
                                      'true' as "nullable",
                                      'SCOTT.EMP' as "baseTable",
                                      'ENAME' as "baseColumn",
                                      '0' as "minOccurs")
                     ),
                     xmlelement ("xsd:element",
                       xmlattributes ('JOB' as "ename",
                                      'xsd:string' as "type",
                                      'true' as "nullable",
                                      'SCOTT.EMP' as "baseTable",
                                      'JOB' as "baseColumn",
                                      '0' as "minOccurs")
                     )
                   )
                 )
               )
             )
           )
         ),
         xmlelement ("ROWSET",
           xmlagg (
             xmlelement ("ROW",
               xmlforest (
                 empno as "EMPNO",
                 ename as "ENAME",
                 job as "JOB"
               )
             )
           )
         )
       ) as "DOC"
  from emp
/

...which yields...

<DOCUMENT xmlns:xsd="
">http://www.w3.org/2000/10/XMLSchema">
<xsd:schema xmlns:xsd="
">http://www.w3.org/2000/10/XMLSchema">
  <xsd:element name="ROWSET">
    <xsd:sequence>
      <xsd:element name="ROW" minOccurs="0" maxOccurs="unbounded">
        <xsd:complexType>
          <xsd:sequence>
            <xsd:element name="EMPNO" type="xsd:integer" baseTable="SCOTT.EMP"
baseColumn="EMPNO" minOccurs="0"></xsd:element>
            <xsd:element ename="ENAME" type="xsd:string" nullable="true"
baseTable="SCOTT.EMP" baseColumn="ENAME" minOccurs="0">
</xsd:element>
            <xsd:element ename="JOB" type="xsd:string" nullable="true"
baseTable="SCOTT.EMP" baseColumn="JOB" minOccurs="0"></xsd:element>
          </xsd:sequence>
        </xsd:complexType>
      </xsd:element>
    </xsd:sequence>
  </xsd:element>
  </xsd:schema>
  <ROWSET>
    <ROW>
      <EMPNO>7369</EMPNO>
      <ENAME>SMITH</ENAME>
      <JOB>CLERK</JOB>
    </ROW>
    <ROW>
      <EMPNO>7499</EMPNO>
      <ENAME>ALLEN</ENAME>
      <JOB>SALESMAN</JOB>
    </ROW>
    <ROW>
      <EMPNO>7521</EMPNO>
      <ENAME>WARD</ENAME>
      <JOB>SALESMAN</JOB>
    </ROW>
. . . *some results clipped for the sake of brevity* . . .
  </ROWSET>
</DOCUMENT>

(note your baseColumn and baseTable attributes are included.)

This is obviously not automated, but you could wrap some code around this and
make it a utility for your queries.  It makes it much more granular and
controllable, however, which it sounds like what you need.  Hope that helps!

_smd_
Rating: 5
Very nice answer January 27, 2004
Reviewer: Justin from PA
That has opened my eyes.

Thanks very much for the effort!
Rating: 3
XML help February 17, 2004
Reviewer: Justin from PA
Sean,

I have been on a long road with XML generation out of Oracle.

We were using DBMS_XMLQuery to return our XML. The reason we were using it was
because it seems to be the only option for returning XML *with* schema
information. I was ordered to benchmark Oracle's generation of XML vs. using the
ADO parameter called adPersistXML (which generates XML from a result set
returned to VB) and found that the generation of XML using DBMS_XMLQuery was
significantly slower than if we embed our SQL statements in our VB code.
Like orders of magnitudes slower, and then on top of that, we had to apply XSL
to create our much needed basetable name and basecolumn name information.

Your above response to my dilemma was much appreciated, and you mention I could
wrap the above select in some code to automate it.

I tried this attempt at placing your select into a stored proc, and it bombs
with an ORA-600... not good.

justin@DEV> var x varchar2

justin@DEV> create or replace procedure getvendorlist(p_ans OUT varchar2)
  2
  3  as
  4  v_sql varchar2(10000);
  5  begin
  6
  7  v_sql:='
  8  select xmlelement ("DOCUMENT",
  9           xmlattributes (''
http://www.w3.org/2000/10/XMLSchema''
as "xmlns:xsd"),
 10           xmlelement ("xsd:schema",
 11             xmlattributes (''
http://www.w3.org/2000/10/XMLSchema''
as "xmlns:xsd"),
 12             xmlelement ("xsd:element",
 13               xmlattributes (''ROWSET'' as "name"),
 14               xmlelement ("xsd:sequence",
 15                 xmlelement ("xsd:element",
 16                   xmlattributes (''ROW'' as "name",
 17                                  ''0'' as "minOccurs",
 18                                  ''unbounded'' as "maxOccurs"),
 19                   xmlelement ("xsd:complexType",
 20                     xmlelement ("xsd:sequence",
 21                       xmlelement ("xsd:element",
 22                         xmlattributes (''EMPNO'' as "name",
 23                                        ''xsd:integer'' as "type",
 24                                        ''SCOTT.EMP'' as "baseTable",
 25                                        ''EMPNO'' as "baseColumn",
 26                                        ''0'' as "minOccurs")
 27                       ),
 28                       xmlelement ("xsd:element",
 29                         xmlattributes (''ENAME'' as "ename",
 30                                        ''xsd:string'' as "type",
 31                                        ''true'' as "nullable",
 32                                        ''SCOTT.EMP'' as "baseTable",
 33                                        ''ENAME'' as "baseColumn",
 34                                        ''0'' as "minOccurs")
 35                       ),
 36                       xmlelement ("xsd:element",
 37                         xmlattributes (''JOB'' as "ename",
 38                                        ''xsd:string'' as "type",
 39                                        ''true'' as "nullable",
 40                                        ''SCOTT.EMP'' as "baseTable",
 41                                        ''JOB'' as "baseColumn",
 42                                        ''0'' as "minOccurs")
 43                       )
 44                     )
 45                   )
 46                 )
 47               )
 48             )
 49           ),
 50           xmlelement ("ROWSET",
 51             xmlagg (
 52               xmlelement ("ROW",
 53                 xmlforest (
 54                   empno as "EMPNO",
 55                   ename as "ENAME",
 56                   job as "JOB"
 57                 )
 58               )
 59             )
 60           )
 61         ) as "DOC"
 62    from emp';
 63
 64  execute immediate(v_sql) into p_ans;
 65
 66  end getvendorlist;
 67  /

Procedure created.

justin@DEV> exec getvendorlist(:x)
BEGIN getvendorlist(:x); END;

*
ERROR at line 1:
ORA-00600: internal error code, arguments: [koksghds1], [], [], [], [], [], [],
[]
ORA-06512: at "JUSTIN.GETVENDORLIST", line 64
ORA-06512: at line 1

That's a little messy for my taste.

I have written this code as a function also, and it bombs the same way.

select into a clob for the XSQL and that didn't work either.

How would you write a procedure to return this data.

I should mention I did get it working by returning a REF CURSOR, but I don't
really think that would be the best way to return XML. I would think it should
be returned as a CLOB.

Here is the REF CURSOR one that works:

create or replace procedure getvendorlist(p_rc1 OUT sys_refcursor)

as

begin

open p_rc1 for
select xmlelement ("DOCUMENT",
         xmlattributes ('
http://www.w3.org/2000/10/XMLSchema'
as "xmlns:xsd"),
         xmlelement ("xsd:schema",
           xmlattributes ('
http://www.w3.org/2000/10/XMLSchema'
as "xmlns:xsd"),
           xmlelement ("xsd:element",
             xmlattributes ('ROWSET' as "name"),
             xmlelement ("xsd:sequence",
               xmlelement ("xsd:element",
                 xmlattributes ('ROW' as "name",
                                '0' as "minOccurs",
                                'unbounded' as "maxOccurs"),
                 xmlelement ("xsd:complexType",
                   xmlelement ("xsd:sequence",
                     xmlelement ("xsd:element",
                       xmlattributes ('EMPNO' as "name",
                                      'xsd:integer' as "type",
                                      'SCOTT.EMP' as "baseTable",
                                      'EMPNO' as "baseColumn",
                                      '0' as "minOccurs")
                     ),
                     xmlelement ("xsd:element",
                       xmlattributes ('ENAME' as "ename",
                                      'xsd:string' as "type",
                                      'true' as "nullable",
                                      'SCOTT.EMP' as "baseTable",
                                      'ENAME' as "baseColumn",
                                      '0' as "minOccurs")
                     ),
                     xmlelement ("xsd:element",
                       xmlattributes ('JOB' as "ename",
                                      'xsd:string' as "type",
                                      'true' as "nullable",
                                      'SCOTT.EMP' as "baseTable",
                                      'JOB' as "baseColumn",
                                      '0' as "minOccurs")
                     )
                   )
                 )
               )
             )
           )
         ),
         xmlelement ("ROWSET",
           xmlagg (
             xmlelement ("ROW",
               xmlforest (
                 empno as "EMPNO",
                 ename as "ENAME",
                 job as "JOB"
               )
             )
           )
         )
       ) as "DOC"
  from emp;

end getvendorlist;
/

justin@DEV> var x refcursor
justin@DEV> exec getvendorlist(:x)

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
justin@DEV> print x

DOC
=================================================================================
===================================================
<DOCUMENT xmlns:xsd="
http://www.w3.org/2000/10/XMLSchema">
xmlns:xsd="
http://www.w3.org/2000/10/XMLSchema">
n
ame="ROWSET"><xsd:sequence><xsd:element name="ROW" minOccurs="0"
maxOccurs="unbounded"><xsd:complexType><xsd:sequence><xsd:element n
ame="EMPNO" type="xsd:integer" baseTable="SCOTT.EMP" baseColumn="EMPNO"
minOccurs="0"></xsd:element><xsd:element ename="ENAME" type=
"xsd:string" nullable="true" baseTable="SCOTT.EMP" baseColumn="ENAME"
minOccurs="0"></xsd:element><xsd:element ename="JOB" type="xsd
:string" nullable="true" baseTable="SCOTT.EMP" baseColumn="JOB"
minOccurs="0"></xsd:element></xsd:sequence></xsd:complexType></xsd:e
lement></xsd:sequence></xsd:element></xsd:schema><ROWSET><ROW><EMPNO>7369</EMPNO>
<ENAME>SMITH</ENAME><JOB>CLERK</JOB></ROW><ROW><EMP
NO>7499</EMPNO><ENAME>ALLEN</ENAME><JOB>SALESMAN</JOB></ROW><ROW><EMPNO>7521</EMP
NO><ENAME>WARD</ENAME><JOB>SALESMAN</JOB></ROW><ROW
><EMPNO>7566</EMPNO><ENAME>JONES</ENAME><JOB>MANAGER</JOB></ROW><ROW><EMPNO>7654<
/EMPNO><ENAME>MARTIN</ENAME><JOB>SALESMAN</JOB></RO
W><ROW><EMPNO>7698</EMPNO><ENAME>BLAKE</ENAME><JOB>MANAGER</JOB></ROW><ROW><EMPNO
>7782</EMPNO><ENAME>CLARK</ENAME><JOB>MANAGER</JOB>
</ROW><ROW><EMPNO>7788</EMPNO><ENAME>SCOTT</ENAME><JOB>ANALYST</JOB></ROW><ROW><E
MPNO>7839</EMPNO><ENAME>KING</ENAME><JOB>PRESIDENT<
/JOB></ROW><ROW><EMPNO>7844</EMPNO><ENAME>TURNER</ENAME><JOB>SALESMAN</JOB></ROW>
<ROW><EMPNO>7876</EMPNO><ENAME>ADAMS</ENAME><JOB>CL
ERK</JOB></ROW><ROW><EMPNO>7900</EMPNO><ENAME>JAMES</ENAME><JOB>CLERK</JOB></ROW>
<ROW><EMPNO>7902</EMPNO><ENAME>FORD</ENAME><JOB>ANA
LYST</JOB></ROW><ROW><EMPNO>7934</EMPNO><ENAME>MILLER</ENAME><JOB>CLERK</JOB></RO
W></ROWSET></DOCUMENT>

Please help, and thanks very much for it.
Followup:
I concurr the ora-600 is nasty -- however, it is happening cause of a serious
conversion error.

I took your query and:

ops$tkyte@ORA9IR2> create or replace view v
  2  as
  3  select xmlelement ("DOCUMENT",
...
 56         ) as "DOC"
 57    from emp;

View created.

ops$tkyte@ORA9IR2> desc v
 Name                                Null?    Type
 ----------------------------------- -------- ------------------------
 DOC                                          SYS.XMLTYPE

See -- it returns an XMLTYPE, not a varchar2 -- sooooo, quickie fixie:

ops$tkyte@ORA9IR2> create or replace procedure getvendorlist(p_ans OUT varchar2)
  2  as
  3          v_sql long;
  4          l_ans sys.xmltype;
  5  begin
  6
  7  v_sql:='
  8  select xmlelement ("DOCUMENT",
....
 64    execute immediate(v_sql) into l_ans;
 65    p_ans := l_ans.getStringVal();
 66
 67  end getvendorlist;
 68  /

Procedure created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> variable x varchar2(4000)
ops$tkyte@ORA9IR2> exec getvendorlist( :x )

PL/SQL procedure successfully completed.
ops$tkyte@ORA9IR2> print x

X
-----------------------------------------------------
<DOCUMENT xmlns:xsd="
http://www.w3.org/2000
but you'll probably want to return a clob, not a varchar2, since the bloated XML
stuff will quickly exceed 32k.....
Rating: 5
Super February 18, 2004
Reviewer: Justin from PA
That taught me something.

Thanks much. I'll get to trying it.
Rating: 5
Can you say February 18, 2004
Reviewer: Justin from PA
r_clob := l_ans.getClobVal();

That rules. Thanks so much for your guidance.

Sometimes I have no idea what I would do without the people who make up this
site. (Where do I send donations? ;)
Rating: 3
The big question February 18, 2004
Reviewer: Justin from PA
Ok Tom,

Now that I have this working, I need to sell it to my team as a palatable
solution. The reason the solution is ugly right now, is because all these SQLX
tags are wrapped around our actual SQL statement, rendering it nearly
unreadable. Once one understands what is happening in the select, I agree that
it isn't hard to learn to make modifications to the statement, but I/they would
much rather see just a plain old select statement in my initially called
procedure, and pass the plain old select statement as a literal string to a
conversion procedure which would parse the literal, and spit out the SQLX syntax
with my SQL statement embedded in it.

SO... The question is, how would you parse any given select statement and pull
out column names and table names. Oracle really only has two functions that I
know of to parse text, instr and substr.

In other words I want to:

*****************
PROC getvendorlist
..
v_sql := 'select t093_id from t093_vendor';

parse_query(v_sql);

execute immediate(v_returned_SQLX_stmt) into l_ans;

******************
proc or function parse_query

find the from
find each column name
find each alias name
etc..
spit out the SQLX to the calling proc

*****************

How would you do this?

Keep in mind, the reason I am here is because of performance. These SQLX
statements return instantly as benchmarked with TKPROF. So if you know this type
of text parse processing would be slow, then that would end my search for this
solution.

I am thinking that the parsing procedure/function will be affected by the
complexity of the literal passed to it, but if you could show me how to do a
simple one, maybe I can figure out the more complex ones.

Thanks very much for your time
Followup:
Once one understands what is happening in the select, I agree that
it isn't hard to learn to make modifications to the statement

hmmm.  it all comes back to -- understanding?

do you want this just for simple "single table queries"?  I think we can do
that. I'd rather implement it as a code generator though, not as a generic
runtime thingy.
Rating: 4
<grin> February 19, 2004
Reviewer: Justin from PA
I would much rather it be an industrial strength, and re-usable program, as you
seem to mean by the "code generator".

I can't invision what you mean, so by all means, please show me.

Keep in mind the performance issue. Our times right now fall into 3 categories.

1) Embedding SQL in VB, and letting ADO generate XML for our vendor list query.
-- .18-.30 seconds
2) Using DBMS_XMLQuery to generate the XML for our vendor list query. -- .49
seconds
3) Using SQLX to generate the XML for some our vendor list query. -- .16 seconds

Thank you so much for your help.
Followup:
instead of at run time -- parsing and describing a query and generating a new
query -- you do it ONCE at development time.  that is what I mean.
Rating: 3
February 19, 2004
Reviewer: Justin from PA
So you're saying you would just convert the SQL statements to SQLX statements
during development, and put the SQLX statements in your procs?
Followup:
correct.
Rating: 5
Thanks much! February 19, 2004
Reviewer: Justin from PA
Rating: 4
After much ado March 04, 2004
Reviewer: Justin from PA
My leader is still giving me grief about SQLX being so ugly.

So... We are looking at changing the way our application uses XML.

We need to know the best/fastest way to get XML out of Oracle, and the
best/fastest way read XML into a DOM or SAXX parser and process it.

What package/utility would you use to do this *really fast*. There are so many
Oracle XML packages that's it's almost overwhelmingly confusing as to which one
to use.

It seems that DBMS_XMLDOM doesn't have a straight up "LOAD" procedure that
allows you to read in your XML from the buffer?? We found PLSXML's DBDOM, and it
seems to have it, but it's refered to as a "utility" and that seems
odd/disconcerting to me because it isn't installed by default during
installation.

To brief you on our situation, we are passing an XML "island" to the web client,
and they have the opportunity to make changes to the XML island locally on their
machine. Then the XML island is pushed back to the app/web server, then on to
the DB server. We want to elliminate pushing the schema inline with every set of
XML and just push the data (which reduces the size dramtically for small
datasets). We will be writing separate select, insert and update PL/SQL
procedures and passing the XML to the proper one based on what was done to the
XML.

How would *you* do this - front to back?

I need to know what package/procedure you would use to get the XML out of Oracle
blazingly fast, and then what package/procedure you would use to read the XML in
via a buffer, parse it and process it blazingly fast.

dbms_xmlgen.getxml?
dbms_xmldom.load???

Thanks much for your help!
Followup:
Hi Justin, Sean here.

I hope you're kidding.  You're getting grief because SQLX looks... "ugly". ?!  I
sincerely hope that's not even a relevant factor in what technologies you use to
provide a performant, scalable solution to your partners/customers?!

I'm going to give you a QUICK overview on the solutions Oracle offers in this
area, but I believe you should crack the Oracle documentation on the subject and
do some testing.  You can go to the Oracle Technology Network (
http://otn.oracle.com/
 and go to
"Documentation" (an icon at the top of the screen).  Once you're there, go to
the documentation library for 9i or 10g, and you can read ALL ABOUT Oracle's
approach to generating XML, for storing XML and for manipulationg XML.  Here's a
quick overview with my take:

Generation XML:
- SQLX.  This is the BEST way for you to generate XML, as it's the most natural
way to generate a result.  A QUERY!  What's nice about these?  They generate
query plans and they use the cost-based optimizer and they use indexed reads,
etc.  This isn't to say any of the others don't but SQLX is Oracle's latest and
greatest mechanism for generating XML.
- DBMS_XMLQUERY.  This is another way to query the database to ask for an XML
result set, but the options you have for customizing the way Oracle generates
the final document aren't quite as good as SQLX's options.

Storing/Processing XML:
- PLSXML's DBDOM.  This is a utility written some time ago to parse XML
documents in PL/SQL.  It's not supported, and is considerably dated.  Use at
your own risk.
- DBMS_XMLSAVE.  PL/SQL supplied package for taking an XML document and storing
it into relational tables and/or objects.  It's good, as it removes a lot of the
work from the programmers hands but the XML has to be in a particular
"canonical" format which must match the storage architecture of your tables
and/or objects.  All in all, this works but isn't quite XML DB.
- XML DB.  Using XML DB in 9iR2, there are MANY options you have for storing and
interacting with your XML.  There's structured storage, where the database
automatically breaks down your XML document based on your XML Schema, there's
unstructured storage which places the entire XML document into a CLOB column in
the XML DB schema.  There's the XML Repository which is yet another way to
interface w/ the databases's XML technology.

There are more, but it sounds like you need to do some reading up on what's
available and how Oracle's toolset maps into your requirements.

To answer your question at the bottom...  Use XML DB.  You upload an XML
document via FTP, WebDAV or "SQL".  The XML document is then stored in a CLOB
column or a set of object-relational tables & structures for further
processing/queries.  When you need the XML document, you pull it out via FTP,
HTTP, WebDAV or SQL.  Oracle is then responsible for reconstructing the XML
document for you behind the scenes.

XML DB is the best way to go.  Period.  But of course, you'd need Oracle9i
Release2.

For more information on XML DB, search AskTom for XML DB and XMLType.  You can
view some articles written by various XML technologists in Oracle Magazine at
http://www.oracle.com/oramag/
I hope that helps!
Rating: 4
Thank you March 10, 2004
Reviewer: Justin from PA
I had a lenghty response written, but I think I would have been bored to read
it.

I'll get right to the point. I will check into XMLDB some more, but I don't
think we can use that option for our application.

A question I have now is, can you use multiset inside of SQLX?

Could you give an example with emp,dept?

Thanks much!
Followup:
Justin, I'm not sure I understand what you mean by a "multiset".  If you mean
can I create a SQLX query that nests nodes inside of nodes (inside of nodes
(inside of nodes)) the answer to that is yes.  There are already a bunch of
examples on AskTom, but to keep things simple:

hr@GROOVYDB> select xmlelement("Department",
  2           xmlattributes(d.department_id as "departmentId"),
  3           xmlelement("DepartmentName", d.department_name),
  4           ( select xmlagg(
  5                  xmlelement("Employee",
  6                    xmlattributes(employee_id as "employeeId"),
  7                    xmlforest(
  8                      first_name as "FirstName",
  9                      last_name as "LastName",
 10                      email as "Email",
 11                      phone_number as "PhoneNumber"),
 12                      ( select xmlelement("Manager",
 13                                 xmlattributes(e.manager_id as "managerId"),
 14                                 xmlforest(
 15                                   m.first_name as "FirstName",
 16                                   m.last_name as "LastName",
 17                                   m.email as "Email",
 18                                   m.phone_number as "PhoneNumber"
 19                                 )
 20                               )
 21                          from employees m
 22                         where m.employee_id = e.manager_id
 23                      )
 24                    )
 25                  )
 26               from employees e
 27              where e.department_id = d.department_id ) "Employees"
 28         ) result
 29    from departments d
 30   where department_id = 100
 31  /

RESULT
-------------------------------------------------------------------------------
<Department departmentId="100">
  <DepartmentName>Finance</DepartmentName>
  <Employees>
    <Employee employeeId="108">
      <FirstName>Nancy</FirstName>
      <LastName>Greenberg</LastName>
      <Email>NGREEN BE</Email>
      <PhoneNumber>515.124.4569</PhoneNumber>
      <Manager managerId="101">
        <FirstName>Neena</FirstName>
        <LastName>Kochhar</LastName>
        <Email>NKOCHHAR</Email>
        <PhoneNumber>515.123.4568</PhoneNumber>
      </Manager>
    </Employee>
    <Employee employeeId="109">
      <FirstName>Daniel</FirstName>
      <LastName>Faviet</LastName>
      <Email>DFAVIET</Email>
      <PhoneNumber>515.124.4169</PhoneNumber>
      <Manager managerId="108">
        <FirstName>Nancy</FirstName>
        <LastName>Greenberg</LastName>
        <Email>NGREENBE</Email>
        <PhoneNumber>515.124.4569</PhoneNumber>
      </Manager>
    </Employee>
    <!-- other employees cut out for brevity -->
  </Employees>
</Department>

Hope that helps!

- Sean
Rating: 3
Nice March 12, 2004
Reviewer: Gerhard from Dusseldorf,Germany
Dear Sir,
Can a procedure be declared,defined and used with in a
* Function *?Could you please provide a simpel example for
that?
Please do reply.
Followup:
create function f return number
as
   procedure p
   is
   begin
      null;
   end;
begin
   p;
   return 0;
end;
Rating: 3
Nice March 13, 2004
Reviewer: Donovan from OH,USA
Dear Tom,
Do you have a simple example for the format
 sql> select * from table(func(cursor(..some select stmt)));
Please do reply.
Followup:
ops$tkyte@ORA9IR2> @etl
=================================================================

For external tables, we need to use a directory object -- we'll
start with that, mapping to the temp directory

demo@ORA9IR2>
demo@ORA9IR2> create or replace directory data_dir as '/tmp/'
  2  /

Directory created.

demo@ORA9IR2>
demo@ORA9IR2>
demo@ORA9IR2> set echo off
Enter to continue

=================================================================

Now, we'll create the external table.
part of its definition is what looks like a control file -- it is

demo@ORA9IR2>
demo@ORA9IR2>
demo@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.

demo@ORA9IR2>
demo@ORA9IR2>
demo@ORA9IR2>
demo@ORA9IR2> set echo off
Enter to continue

=================================================================

We'll create a flat file to work with now

demo@ORA9IR2>
demo@ORA9IR2> host flat scott/tiger emp > /tmp/emp.dat

demo@ORA9IR2>
demo@ORA9IR2> set echo off
Enter to continue

=================================================================

And finally, select * from it...

demo@ORA9IR2>
demo@ORA9IR2> select empno, ename, hiredate from external_table
  2  where ename like '%A%'
  3  /

     EMPNO ENAME      HIREDATE
---------- ---------- ---------
      7499 ALLEN      20-FEB-81
      7521 WARD       22-FEB-81
      7654 MARTIN     28-SEP-81
      7698 BLAKE      01-MAY-81
      7782 CLARK      09-JUN-81
      7876 ADAMS      12-JAN-83
      7900 JAMES      03-DEC-81

7 rows selected.

demo@ORA9IR2>
demo@ORA9IR2> pause

demo@ORA9IR2>
demo@ORA9IR2> set echo off
=================================================================

We'll set up a PLSQL "etl" routine to ingest the flat file
and output live data to be merged or inserted

demo@ORA9IR2> pause

demo@ORA9IR2> create or replace type emp_scalar_type 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  )
 11  /

Type created.

demo@ORA9IR2> create or replace type emp_table_type as table of emp_scalar_type
  2  /

Type created.

demo@ORA9IR2>
demo@ORA9IR2>
demo@ORA9IR2> create or replace
  2  function emp_etl( p_cursor in sys_refcursor )
  3  return emp_table_type
  4  PIPELINED
  5  as
  6          l_rec  external_table%rowtype;
  7  begin
  8          loop
  9                  fetch p_cursor into l_rec;
 10                  exit when (p_cursor%notfound);
 11
 12                  -- validation routine
 13                  -- log bad rows elsewhere
 14                  -- lookup some value
 15                  -- perform conversion
 16                  pipe row( emp_scalar_type( l_rec.empno,
 17                            LOWER(l_rec.ename),
 18                            l_rec.job, l_rec.mgr, l_rec.hiredate, l_rec.sal,
 19                                    l_rec.comm, l_rec.deptno ) );
 20          end loop;
 21
 22          return;
 23  end;
 24  /

Function created.

demo@ORA9IR2> set echo off

=================================================================

that routine, our code, works just like a TABLE

demo@ORA9IR2> select empno, ename
  2    from TABLE(emp_etl(cursor( select * from external_table ) ) )
  3   where ename like '%a%';

     EMPNO ENAME
---------- ----------
      7499 allen
      7521 ward
      7654 martin
      7698 blake
      7782 clark
      7876 adams
      7900 james

7 rows selected.

demo@ORA9IR2>
demo@ORA9IR2> set echo off

=================================================================

we'll create an "existing" table of data, we would like to
'refresh' it from the source systems

demo@ORA9IR2> create table emp as select * from scott.emp where mod(empno,2) =
0;

Table created.

demo@ORA9IR2> select empno, ename from emp;

     EMPNO ENAME
---------- ----------
      7566 JONES
      7654 MARTIN
      7698 BLAKE
      7782 CLARK
      7788 SCOTT
      7844 TURNER
      7876 ADAMS
      7900 JAMES
      7902 FORD
      7934 MILLER

10 rows selected.

demo@ORA9IR2>
demo@ORA9IR2>
demo@ORA9IR2> set echo off

=================================================================

and here is the "merge" -- from flat file, through ETL,
straight to the table without hitting the disk with staging
files

demo@ORA9IR2> merge into EMP e1
  2  using ( select *
  3            from TABLE(emp_etl(cursor( select * from external_table ) ) )
  4  ) e2
  5  on ( e2.empno = e1.empno )
  6  when matched then
  7          update set e1.sal = e2.sal, e1.ename = e2.ename
  8  when not matched then
  9          insert ( empno, ename, job, mgr,
 10                   hiredate, sal, comm, deptno )
 11          values ( e2.empno, e2.ename, e2.job, e2.mgr,
 12                   e2.hiredate, e2.sal, e2.comm, e2.deptno )
 13  /

14 rows merged.

demo@ORA9IR2> commit;

Commit complete.

demo@ORA9IR2>
demo@ORA9IR2> pause

demo@ORA9IR2> select empno, ename from emp;

     EMPNO ENAME
---------- ----------
      7566 jones
      7654 martin
      7698 blake
      7782 clark
      7788 scott
      7844 turner
      7876 adams
      7900 james
      7902 ford
      7934 miller
      7369 smith
      7499 allen
      7521 ward
      7839 king

14 rows selected.

demo@ORA9IR2>
demo@ORA9IR2>
demo@ORA9IR2> set echo off
Rating: 3
Nice March 14, 2004
Reviewer: Donovan from OH,USA
Dear Sir,
we learnt a lot from your followup.But some doubts still
persist.They are
1)As you have said in one of your followups that "We use an
external table to query a flat file".Do External tables act
like views?If we delete the flat file, Can the external tables still exist or
their validity gets destroyed after
the flat file is deleted?
2)sometimes when we use object types,we create a nested table type from the base
object type.Is there any predefined rule or terminology to do that?
 For example:
  you create a scalar object type and transform that to a table and in the
Function definition , the return clause
 specifies a table type as a return object and in the function body it returns a
base object type.I could not understand this transformation and could you please
explain
why you are doing so?
Please do reply.
Your answers are excellent,undisputed and touch the hard core.Nice work.
Followup:
1) external tables are TABLES -- in as much as a view is a "table" itself, yes,
they are like views.

if you erase the flat file (assuming that is what you mean by "delete the flat
file") then the data is no longer acessible.  the external table will still
exists (just like a view would still exist even if you drop the underlying
tables) but a query against them will "fail" at runtime

2) pipelined functions "just work that way".  instead of saving up the entire
collection -- you return elements of the collection one by one and the client
recieves them as soon as you send them.

In this fashion, we need not fill up the entire collection AND THEN return it,
we are returning a "row at a time"
Rating: 5
excellent April 02, 2004
Reviewer: V.Vijay Kaushik from Chennai,India
Dear Tom,
          I gained a lot of knowledge from your thoughts.Thanks a lot.

Regards
Vijay
March 14, 2004 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.
Rating: 5
In ASSM? March 13, 2004
Reviewer: reader
In ASSM tablespace, assuming that there are not enough BMBs to support
concurrent processes inserting rows, does oracle allocate another extent to
satisfy the insert from another process?
My understanding is that each extent has a BMB that would guide a user process
to use the "free" blocks within that extent. Now, if all of the BMBs
corresponding to the exisiting extents are used by concurrent inserts, another
transaction may force oracle to allocate a new extent. Is this how oracle moves
the HWM in ASSM? Is this a possible theory behind ASSM? Thanks.
Followup:
You can still have contention for BMB's -- it'll not just allocate a new extent
for that.  BMB's grow in "number" as the segment itself grows and prevent most
of the issues found in systems where pctused/freelists/freelist groups where not
set (typical).

You might be interested in support note <Note:247752.1> available on metalink.
March 14, 2004 Edgar -- Thanks for the question regarding "SQL advanced querying technics", version 9.2
You Asked
Hello Tom,

We all know that you are very good at SQL, can you help me with this question.
Following your rule of tumb I am trying to do as much as possible in single SQL.
Suppose I have a table:

create table data(
   attribute_id integer,
   class_id integer,
   count integer );

insert into data values (1,1,3);
insert into data values (1,3,5);
insert into data values (2,2,3);
insert into data values (2,3,5);
insert into data values (3,1,1);
insert into data values (3,2,2);
insert into data values (3,3,5);

I need to divide this dataset by grouping adjacent attributes into intervals and
than calculate sum of count by class_id per interval, so that result should look
like this:

interval | class_id | count
  1            1       3   (where attribute_id in (1,2))
  1            2       3   (where attribute_id in (1,2))
  1            3       10  (where attribute_id in (1,2))
  2            1       1   (where attribute_id in (2,3))
  2            2       5   (where attribute_id in (2,3))
  2            3       10  (where attribute_id in (2,3))

and so on, the number of distinct values of class_id and attribute_id is
infinite. I'm stuck with this query and thinking about writing this in pl/sql,
but I don't think that it will perform best. Can my requirements be acheived in
SQL?
Thank you in advance!
and we said...
Well, what we would be need to do is

get a set of all intervals.  I'll have to assume (since not stated otherwise)
that the attributes could skip ranges -- eg: the set of distinct attributes
could be

1, 3, 5, 7, 9, 100

with "intervals"

1) 1..3
2) 3..5
3) 5..7

and so on (if that is not the case, if there is a rule that says the intervals
will be 1,2 2,3 3,4 and so on -- that'll make it "easier".  That would be:

  3  (
  4  select attribute_id,
  5         row_number() over (order by attribute_id) interval,
  6         lead(attribute_id) over (order by attribute_id) next_attribute_id
  7    from (select distinct attribute_id from t)
  8  ),

Next, we would need to join this to each distinct class_id (process by class
id).  In 10g, I could use a partitioned outer join but in 9i, we'll have to take
the above query and cartesian product that with:

 11  (
 12  select distinct class_id
 13    from t
 14  ),

that would be:

 15  both
 16  as
 17  ( select interval, class_id, decode(x,1,attribute_id,next_attribute_id)
attribute_id
 18      from (select * from attribute_ranges where next_attribute_id is not
null),
 19               class_ids ,
 20                   (select 1 x from dual UNION ALL select 2 x from dual)
 21  )

The use of DUAL here is to pivot the result set -- to make two rows for each
interval computed above.

And then we join:

ops$tkyte@ORA9IR2> with attribute_ranges
  2  as
  3  (
  4  select attribute_id,
  5         row_number() over (order by attribute_id) interval,
  6         lead(attribute_id) over (order by attribute_id) next_attribute_id
  7    from (select distinct attribute_id from t)
  8  ),
  9  class_ids
 10  as
 11  (
 12  select distinct class_id
 13    from t
 14  ),
 15  both
 16  as
 17  ( select interval, class_id, decode(x,1,attribute_id,next_attribute_id)
attribute_id
 18      from (select * from attribute_ranges where next_attribute_id is not
null),
 19               class_ids ,
 20                   (select 1 x from dual UNION ALL select 2 x from dual)
 21  )
 22  select both.interval,
 23         t.class_id,
 24             sum(t.count) count
 25    from t, both
 26   where t.class_id = both.class_id
 27     and t.attribute_id = both.attribute_id
 28   group by both.interval, t.class_id
 29   order by both.interval, t.class_id
 30  /

  INTERVAL   CLASS_ID      COUNT
---------- ---------- ----------
         1          1          3
         1          2          3
         1          3         10
         2          1          1
         2          2          5
         2          3         10

6 rows selected.

So, I tried it out with 1000 possible attribute_ids with 1000 possible class_ids
and 800,000 rows:

ops$tkyte@ORA9IR2> with attribute_ranges
  2  as
  3  (
  4  select attribute_id,
  5         row_number() over (order by attribute_id) interval,
  6         lead(attribute_id) over (order by attribute_id) next_attribute_id
  7    from (select distinct attribute_id from t)
  8  ),
  9  class_ids
 10  as
 11  (
 12  select distinct class_id
 13    from t
 14  ),
 15  both
 16  as
 17  ( select interval, class_id, decode(x,1,attribute_id,next_attribute_id)
attribute_id
 18      from (select * from attribute_ranges where next_attribute_id is not
null),
 19               class_ids ,
 20                   (select 1 x from dual UNION ALL select 2 x from dual)
 21  )
 22  select both.interval,
 23         t.class_id,
 24             sum(t.count) count
 25    from t, both
 26   where t.class_id = both.class_id
 27     and t.attribute_id = both.attribute_id
 28   group by both.interval, t.class_id
 29   order by both.interval, t.class_id
 30  /

181004 rows selected.

Elapsed: 00:00:07.38

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=64303382 Card=999 Bytes=56943)
   1    0   SORT (GROUP BY) (Cost=64303382 Card=999 Bytes=56943)
   2    1     HASH JOIN (Cost=140212 Card=13068800000 Bytes=744921600000)
   3    2       VIEW (Cost=979 Card=999 Bytes=2997)
   4    3         SORT (UNIQUE) (Cost=979 Card=999 Bytes=2997)
   5    4           TABLE ACCESS (FULL) OF 'T' (Cost=217 Card=800000
Bytes=2400000)
   6    2       HASH JOIN (Cost=36855 Card=13068800000 Bytes=705715200000)
   7    6         TABLE ACCESS (FULL) OF 'T' (Cost=217 Card=800000
Bytes=9600000)
   8    6         MERGE JOIN (CARTESIAN) (Cost=22957 Card=16319664
Bytes=685425888)
   9    8           VIEW (Cost=979 Card=999 Bytes=38961)
  10    9             WINDOW (BUFFER) (Cost=979 Card=999 Bytes=12987)
  11   10               VIEW (Cost=979 Card=999 Bytes=12987)
  12   11                 SORT (UNIQUE) (Cost=979 Card=999 Bytes=2997)
  13   12                   TABLE ACCESS (FULL) OF 'T' (Cost=217 Card=800000
Bytes=2400000)
  14    8           BUFFER (SORT) (Cost=22957 Card=16336 Bytes=49008)
  15   14             VIEW (Cost=22 Card=16336 Bytes=49008)
  16   15               UNION-ALL
  17   16                 TABLE ACCESS (FULL) OF 'DUAL' (Cost=11 Card=8168)
  18   16                 TABLE ACCESS (FULL) OF 'DUAL' (Cost=11 Card=8168)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       6621  consistent gets
       1569  physical reads
       1260  redo size
    4493410  bytes sent via SQL*Net to client
     133225  bytes received via SQL*Net from client
      12068  SQL*Net roundtrips to/from client
          5  sorts (memory)
          0  sorts (disk)
     181004  rows processed

if you do develop a better procedural algorithm, please post the details
that way I can see what other assumptions you made and perhaps "fine tune this",
but so far -- not to bad (for thousands of records this is instantaneous)...
Review & Followup

Rating: 5
just awesome! thank you for help. March 15, 2004
Reviewer: Edgar Chupit
Rating: 5
my 2 CAD cents March 16, 2004
Reviewer: Gabriel
Assuming the schema where the "data" table resides can be augmented ...
Assuming, as in Tom's response, a low ratio of distinct classes and attributes
vis-a-vis of _big_ total #rows ...

One could eliminate two of the FTs on the _big_ table (and their associated
SORTs) by:
1. having one IOT with attribute_id only
2. having one IOT with class_id only
3. row-level insert trigger adding any new attributes and classes to the
corresponding IOT (with "when dup_val_on_index then null;")

Note: one may or may not bother with maintaining the IOTs when performing update
or delete ... doing full joins with "data" would inherently excludes the
_no_longer_existent_ attributes and classes; the synchronization of the IOTs
with the main table could be scheduled as a batch job during the quiet time on
the system.

Replace the "select distinct attribute_id from t" and "select distinct class_id
from t" with "select attribute_id from iot_attr" and "select class_id from
iot_class".

I'll admit I did not benchmark this approach (and it is circumstantial) ... just
another option.
Rating: 5
on second thought ... maybe just 1 cent worth March 16, 2004
Reviewer: Gabriel
The IOT with attributes would have to be maintained when updating/deleting in
order to get the correct intervals ... so, yes, that may be too much to attach
to transactions on "data".
March 15, 2004 ted -- Thanks for the question regarding "rollback segment", version 8.1.6
You Asked
thomas,

1. chap 6 rbs snapshot too old, there are 3 cases of 1555 are listed
A. rbs indeed too small
B. fetch across commits
C.delayed block cleanout.

there two causes as listed by oracle doc
a. rbs itself gotten rewritten
b. transaction slot in tbs transaction table got rewritten

Qestion: How are A.B.C. above related to a.b such as

            A.      B.      C.
           ----     ---     ----
 causes     a       ab       b

2. when rbs wrap around, oracle will wrap to next extend even the next extent
contain active trasaction on it. Is this correct or it will only use space
belongs to inactive transaction?

3. page 190 example :

begin
  for x in (select        ...

  loop
     if
        commit;
        set transaction use
     end if;

     update t

  end loop
  commit;
end;

"At some point in time, we will revist a block with our query
that was modified with our update"

question:
If for loop cursor open one time (got all the qualified rows), when
and why revisiting would taken place ?
Is it true when a cursor is open all qualified rows are in the cursor ?

thnx as always,
ted chyn
and we said...
1) Well, as I said in the book - case b is really a special case of a but one
that gets people all of the time so I treated it special.  A and B are both
caused by "rbs itself got rewritten".  My case C is the transaction slot
overwrite.

2) We can only wrap to an extent that has NO active transaction in it.  If the
extent we want to wrap into has an active transaction, we attempt to EXTEND the
rollback segment adding a new, empty extent between the current and the one we'd
like to wrap into.

3) No, it is not true that the rows are "all in the cursor".  Consider this:

select * from a_one_billion_row_table

We do not "pre-fetch" the data anywhere.  We process and find the data as we go
along.  If you fetched just one row from that query, we do 1 IO to get it.  If
you fetch all billion records, we do lots more IO.  The bottom line is -- we do
NOT pre-answer queries in general (when we don't have to).  Thats why a query
such as "select from really_big_table" can start returning rows immediately.

The OPEN typically does very little work, the FETCH -- it does the work as we go
along.
Review & Followup

Rating: 3
September 30, 2002
Reviewer: Sagi from India
Hi Tom !

One of the reason for ORA-1555 is to small rollback segment. Basically this is
encountered when we try to over right during the wraps's. Right?

My doubt is that say when the the RBS is trying to WRAP and if that extent has
an active transaction then it would try to alloacate one more extent. If it
reaches the maximum extents during this process, then we encounter the error
"ORA-01562:".

So where is the question of over right?

It would be of great help if you could give us an example of it.please.

Thanx in advance.

Regards,
Sagi
Followup:
Syntax error line 2: Tom cannot understand the question -- parse failure.

"so where is the question of over right?"????

1555 happens when we overwrite UNDO (wrap into it -- there are no active
transactions in this example, you've been committing in a cursor for loop
remember) we need for a query.

If we WRAP and you needed the stuff we overwrite -- ora 1555.
Rating: 3
September 30, 2002
Reviewer: Sagi from India.
Hi Tom!

You said

"1555 happens when we overwrite UNDO (wrap into it -- there are no active
transactions in this example, you've been committing in a cursor for loop
remember) we need for a query.  "

I am getting confused. When there was no active transaction
then what UNDO could be there, which we might be needing, which in turn causes
ORA-1555.

I mean in simple:
  when no active transaction then
        no undo information  then
             where is the question of requesting for
             UNDO and encountering the ORA-1555

Thanx in Advance.

Sagi
Followup:
you query:

   for x in ( select rowid, * from t )  <<=== the results of this are FIXED in
time
                                       we will use the RBS to reconstruct the
                                       data in the table as we need to
   loop
       update t set x = y where rowid = x.rowid;
       commit;  <<<=== here you are releasing the undo for table T you just
                       generated.
   end loop

The select you are running may well need the undo you generated however you
released it.

If you have my book "Expert one on one Oracle" I spend a large amount of time on
this very topic and many others.
Rating: 4
fetch across commit March 15, 2004
Reviewer: K.Venkatasubramanian from India
Tom,

In the above example, using commit outside the loop is the best way and the
recommended one?
Followup:
you commit when your transacation is done.  not before, not after.  tell me, if
you have logic like:

  for x in ( select rowid rid from t where ... )
  loop
      update t set sal = sal * 1.02 where rowid = x.rid;
      commit;
  end loop;

and it *fails* part way through, now what?  how do you restart that.  (most
people do not think about that -- they do not make their stuff restartable).
March 15, 2004 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....
Review & Followup

Rating: 4
Read Consistency in cursor_for_loop March 15, 2004
Reviewer: Js from india
Hi,

         Using Redhat 8.0 + Oracle 9.2.0
         -------------------------------

Ref    :   "Expert one-on-one"
Topic  : Multi-versioning + Read Consistent Queries

Chapter 1  page no. 34

 "Bear in mind that oracle does not copy the data anywhere when you
  open a cursor"

 I was trying to understand how oracle multi-versioning scheme works
 and gives the consistent result using rollback segment if required.

SQL> set serveroutput on
SQL>
SQL> create table a as select * from dict where
  2  rownum < 11;

Table created.

SQL> var x refcursor
SQL>
SQL> begin
  2    open :x for select * from a;
  3  end;
  4  /

PL/SQL procedure successfully completed.

SQL> truncate table a;

Table truncated.

SQL> print x
ERROR:
ORA-08103: object no longer exists

no rows selected

          ### Because we didn't genrate the redo...

SQL>
SQL> drop table a;

Table dropped.

SQL> create table a as select * from dict where
  2  rownum < 11;

Table created.

SQL> begin
  2       for i in ( select * from a ) loop
  3
  4         dbms_output.put_line(' -->> '||i.table_name);
  5         execute immediate ( ' Truncate table a ');
  6      end loop;
  7  end;
  8  /
-->> ALL_ALL_TABLES
-->> ALL_APPLY
-->> ALL_APPLY_CONFLICT_COLUMNS
-->> ALL_APPLY_DML_HANDLERS
-->> ALL_APPLY_ERROR
-->> ALL_APPLY_KEY_COLUMNS
-->> ALL_APPLY_PARAMETERS
-->> ALL_APPLY_PROGRESS
-->> ALL_ARGUMENTS
-->> ALL_ASSOCIATIONS

PL/SQL procedure successfully completed.

SQL> select count(*) from a;

  COUNT(*)
----------
         0

I was expecting the same result but .. ??

Sir, Is there any diff.in working logic
     between Ref_cursor or this Cursor_for_loop ?

Thanks,
Js
Followup:
(because you didn't generate the UNDO -- not redo -- but anyway...)

It isn't "ref cursor vs normal cursor" it is

compare a cursor that has been parsed but has not yet touched ANYTHING
(including the segment header to figure out where the table data resides)

to

a cursor that has been parsed and has in fact read the segment header and knows
what data to full scan.

You see, when you truncate or drop a table -- queries that are already accessing
the data may well succeed (as your second case did) since they are reading the
data that is still there (truncate doesn't zero out the data or anything, just
deallocates it).  As long as the data exists, the queries will run.  but your :x
result set never got started.  truncate DID zero out the storage allocation
hence when :x went for the first row -- it failed.

Consider:

ops$tkyte@ORA9IR2> create table t as select table_name from dict where rownum
<11;

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> variable x refcursor
ops$tkyte@ORA9IR2> variable y refcursor
ops$tkyte@ORA9IR2> declare
  2          l_table_name t.table_name%type;
  3  begin
  4          open :x for select * from t;
  5          open :y for select * from t;
  6
  7          fetch :y into l_table_name;
  8  end;
  9  /

PL/SQL procedure successfully completed.

both queries are the "same".  we fetched the first row from :y however (much
like you did in your second example, you fetched a row AND THEN truncated)

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> truncate table t;

Table truncated.

ops$tkyte@ORA9IR2> print x
ERROR:
ORA-08103: object no longer exists

no rows selected

:x doesn't know what data to read but...

ops$tkyte@ORA9IR2> print y

TABLE_NAME
------------------------------
ALL_APPLY
ALL_APPLY_CONFLICT_COLUMNS
ALL_APPLY_DML_HANDLERS
ALL_APPLY_ERROR
ALL_APPLY_KEY_COLUMNS
ALL_APPLY_PARAMETERS
ALL_APPLY_PROGRESS
ALL_ARGUMENTS
ALL_ASSOCIATIONS

9 rows selected.

:y did

Nothing to do with multi-versioning here, it is all about "did we know what
extents to read or not".  :x did not, "for x in" did.

Now, a long running query against a table that was truncated or dropped could
fail partway through if someone comes in and reused the space allocated to the
object as well!!  it is not assured that :y would succeed - only because it is
really small and the database is "single user" so the space would not be reused
did it work this time.
Rating: 5
Re: March 16, 2004
Reviewer: Js from INDIA
 Wow ...  ********

 Good .. Explaination.

Thanks Sir ...
March 15, 2004 rao -- Thanks for the question regarding "XML data", version 9.2
You Asked
Hi Tom,
I have a requirement to concat two varchar2 columns in xml and put it in the
clob.

example like this...

  select empno,dbms_xmlgen.getxml('select ename||'.'||dname from scott.emp
e,scott.dept d where e.deptno=d.deptno and e.empno=7788')
  2* from scott.emp e1 where e1.empno=7788
SQL> /
select empno,dbms_xmlgen.getxml('select ename||'.'||dname from scott.emp
e,scott.dept d where e.deptno=d.deptno and e.empno=7788')
                                                *
ERROR at line 1:
ORA-00907: missing right parenthesis

If I have removed the period then I got the output.
1  select empno,dbms_xmlgen.getxml('select ename||dname from scott.emp
e,scott.dept d where e.deptno=d.deptno and e.empno=7788')
  2* from scott.emp e1 where e1.empno=7788
SQL> /

     EMPNO
DBMS_XMLGEN.GETXML('SELECTENAME||DNAMEFROMSCOTT.EMPE,SCOTT.DEPTDWHEREE.DEPTNO=D.
----------
--------------------------------------------------------------------------------
      7788 <?xml version="1.0"?>
           <ROWSET>
            <ROW>
             <ENAME_x007C__x007C_DNAME>SCOTTRESEARCH</ENAME_x007C__x007C_DNAME>
            </ROW>
           </ROWSET>

But my requirement is to have the period between these two columns.

Am I doing anything wrong....
Please advise me how do I achieve This..
Thanks for your time and support.
rao
and we said...
'select ename || ''.'' || dname from scott.emp

  1  select empno,
  2         dbms_xmlgen.getxml('select ename||''.''||dname ename_dname
  3                               from scott.emp e,
  4                                    scott.dept d
  5                              where e.deptno=d.deptno
  6                                and e.empno=7788')
  7* from emp where empno = 7788
scott@ORA9IR2> /

     EMPNO
DBMS_XMLGEN.GETXML('SELECTENAME||''.''||DNAMEENAME_DNAMEFROMSCOTT.EMPE,SCOTT.DEP
----------
--------------------------------------------------------------------------------
      7788 <?xml version="1.0"?>
           <ROWSET>
            <ROW>
             <ENAME_DNAME>SCOTT.RESEARCH</ENAME_DNAME>
            </ROW>
           </ROWSET>

to get a single tick in string in 9i and before -- you had to put "two" of them
there.
Review & Followup

Rating: 3
February 29, 2004
Reviewer: A reader
you are really great Tom!!
but I have one more issue
Hi Tom, In continuation my previous question I would like to have the
result like this,so that I can join this output with other tables based
on primary key and pass this clob value into target..
My target like this
emp_id  number
edetails clob

If I am using the sqlxml the output looks like this...

 select e.empno,xmlelement("empname",e.ename,xmlelement("job",e.job),
 xmlelement("dept",d.dname||'.'||d.loc)) AS EDETAILS
 from scott.emp e,scott.dept d where e.deptno=d.deptno
     EMPNO ---------- EDETAILS
-----------------------------------------------------------------------
-----------------------------------------------------------------------
-----------------------------------------------------------------------
-----------------------------------------------------------------------
-----------------------------------------------------------------------
-----------------------------------------------------------------------
-----------------------------------------------------------------------
-----------------------------------------------------------------------
-----------------------------------------------------------------------
-----------------------------------------------------------------------
-----------------------------------------------------------------------
-----------------------------------------------------------------------
------------------------------------------------- 7369 <empname>
SMITH<job>CLERK</job><dept>RESEARCH.DALLAS</dept></empname>

      7499 <empname>ALLEN<job>SALESMAN</job><dept>SALES.CHICAGO</dept>
</empname>

      7521 <empname>WARD<job>SALESMAN</job><dept>SALES.CHICAGO</dept>
</empname>

If I am using dbms_xmlgen.getxml function I am getting cartesian product...

  1  select e.empno,dbms_xmlgen.getxml('select ename||''.''||dname ename_dname
  2                                   from scott.emp e,
  3                                        scott.dept d
  4                                  where e.deptno=d.deptno
  5                                    and e.empno in(7788,7902)')
  6*  from scott.emp e where e.empno in(7788,7902)
SQL> /

     EMPNO
DBMS_XMLGEN.GETXML('SELECTENAME||''.''||DNAMEENAME_DNAMEFROMSCOTT.EMPE,SCOTT.DEP
----------
--------------------------------------------------------------------------------
      7902 <?xml version="1.0"?>
           <ROWSET>
            <ROW>
             <ENAME_DNAME>FORD.RESEARCH</ENAME_DNAME>
            </ROW>
            <ROW>
             <ENAME_DNAME>SCOTT.RESEARCH</ENAME_DNAME>
            </ROW>
           </ROWSET>

      7788 <?xml version="1.0"?>

     EMPNO
DBMS_XMLGEN.GETXML('SELECTENAME||''.''||DNAMEENAME_DNAMEFROMSCOTT.EMPE,SCOTT.DEP
----------
--------------------------------------------------------------------------------
           <ROWSET>
            <ROW>
             <ENAME_DNAME>FORD.RESEARCH</ENAME_DNAME>
            </ROW>
            <ROW>
             <ENAME_DNAME>SCOTT.RESEARCH</ENAME_DNAME>
            </ROW>
           </ROWSET>
B'cas I am not able to make a join.. How do I get a single row for each record
so that I can get the required output.Is this possible to get required temp
table.

Also I would like know what would be the difference between these two in terms
of output, why the SQLX do not have <?xml version="1.0"?>..whese as dbms_xmlgen
has it.?

IF both outputs are same then which one is faster in terms performance.

Thanks for your time and support.
Followup:
why would you not use the first approach which is "bind variable friendly"?
Rating: 3
XML data March 01, 2004
Reviewer: A reader
Sorry Tom, I didn't get you.
Actually I have requirement like this ....
I have target table and has couple colmuns as number,varchar2 and a clob.
For this I have to extract the data from different tables..
before doing this I want to pull the clob data first and put in temp table based
on the target primary key.So that I can extract the data from other tables and
make join with this temp table (for clob) and store it in target table...
For this I am approching the two following methods...
If I am using SqlX functions I  am fine .. I am getting the requierd output.But
from my application team is saying that it may not be parser by XML and they are
storing the new data as <?xml version...?> and the clob values...So I thought of
using the DBMS_XMLGEN.GETXML...This is giving the required output but in
cartesian....That is why I have asked about the difference between these two...
As per docs SQLX are java function and DBMS_XML are written under "C" lib's....
SO if you can shed your light about these issues that would be highly
appreciated.
Again thanks for your time and support.
Followup:
it is not "cartesian", you ask for every emp with each record.

why not just add the <?xml ...> part
Rating: 3
March 01, 2004
Reviewer: A reader
How can I add using the XMLELEMENT functions...any examples please...
Followup:
scott@ORA9IR2> l
  1   select e.empno,'<whatever>' ||
xmlelement("empname",e.ename,xmlelement("job",e.job),
  2   xmlelement("dept",d.dname||'.'||d.loc)) AS EDETAILS
  3*  from scott.emp e,scott.dept d where e.deptno=d.deptno
scott@ORA9IR2> /

     EMPNO
----------
EDETAILS
---------------------------------------------------------------------------------
--------------------------------------------------
      7782
<whatever><empname>CLARK<job>MANAGER</job><dept>ACCOUNTING.NEW
YORK</dept></empname>
Rating: 3
March 01, 2004
Reviewer: A reader
Thanks Tom.
Rating: 3
XMLdata March 08, 2004
Reviewer: A reader
Hi Tom,
I would like to load the data as follows...
example:DEPT and emp table...
If I have designed like this
<DEPT>
<dno>deptno</dno>
<dname>deptname</dname>
<alias>Aliasinfo</alias>
This Aliasinfo should hold the emp table info...
How do i achive from the database point of view...Means is there any example
from the DB point of view...
Thanks for your time and support
Followup:
i don't know what you mean?

(but have you looked XML developers guide on otn?)
Rating: 3
XML data March 12, 2004
Reviewer: A reader
Hi Tom,
I have gone thro..

Here is an example to show the master-detail output for SCOTT's emp and dept
tables.
This creates a department element with a list of employees.

SELECT XMLElement("Department",
          XMLForest(deptno "DeptNo", d.dname "DeptName", d.loc "Location"),
             (SELECT XMLAGG(XMLElement("Employee",
                               XMLForest(e.empno "EmployeeId",
                                         e.ename "Name",
                                         e.job "Job",
                                         e.mgr "Manager",
                                         e.hiredate "Hiredate")))
                                         FROM scott.emp e
                                         WHERE e.deptno = d.deptno))
       FROM scott.dept d;

Output:

<Department>
<DeptNo>10</DeptNo>
<DeptName>ACCOUNTING</DeptName>
<Location>NEW YORK</Location>
<Employee>
<EmployeeId>7782</EmployeeId><Name>CLARK</Name><Job>MANAGER</Job><Manager>7839</M
anager><Hiredate>09-
JUN-81</Hiredate></Employee><Employee><EmployeeId>7839</EmployeeId><Name>KING</Na
me><Job>PRESIDENT</Job>
<Hiredate>17-NOV-81</Hiredate></Employee><Employee><EmployeeId>7934</EmployeeId><
Name>MILLER</Name><Job>
CLERK</Job><Manager>7782</Manager><Hiredate>23-JAN-82</Hiredate></Employee>
</Department>
<Department>
<DeptNo>
20</DeptNo>
<DeptName>RESEARCH</DeptName>
<Location>DALLAS</Location>
<Employee><EmployeeId>7369</EmployeeId>
<Name>SMITH</Name><Job>CLERK</Job><Manager>7902</Manager><Hiredate>17-DEC-80</Hir
edate></Employee><Employee>
<EmployeeId>7566</EmployeeId><Name>JONES</Name><Job>MANAGER</Job><Manager>7839</M
anager><Hiredate>02-
APR-81</Hiredate></Employee><Employee><EmployeeId>7788</EmployeeId><Name>SCOTT</N
ame><Job>ANALYST</Job>
<Manager>7566</Manager><Hiredate>19-APR-87</Hiredate></Employee><Employee><Employ
eeId>7876</EmployeeId><Name>
ADAMS</Name><Job>CLERK</Job><Manager>7788</Manager><Hiredate>23-MAY-87</Hiredate>
</Employee><Employee>
<EmployeeId>7902</EmployeeId><Name>FORD</Name><Job>ANALYST</Job><Manager>7566</Ma
nager><Hiredate>03-
DEC-81</Hiredate></Employee>
</Department>
<Department>
<DeptNo>30</DeptNo>
<DeptName>SALES</DeptName>
<Location>
CHICAGO</Location>
<Employee><EmployeeId>7499</EmployeeId><Name>ALLEN</Name><Job>SALESMAN</Job><Mana
ger>
7698</Manager><Hiredate>20-FEB-81</Hiredate></Employee><Employee><EmployeeId>7521
</EmployeeId><Name>
WARD</Name><Job>SALESMAN</Job><Manager>7698</Manager><Hiredate>22-FEB-81</Hiredat
e></Employee><Employee>
<EmployeeId>7654</EmployeeId><Name>MARTIN</Name><Job>SALESMAN</Job><Manager>7698<
/Manager><Hiredate>28-
SEP-81</Hiredate></Employee><Employee><EmployeeId>7698</EmployeeId><Name>BLAKE</N
ame><Job>MANAGER</Job>
<Manager>7839</Manager><Hiredate>01-MAY-81</Hiredate></Employee><Employee><Employ
eeId>7844</EmployeeId><Name>
TURNER</Name><Job>SALESMAN</Job><Manager>7698</Manager><Hiredate>08-SEP-81</Hired
ate></Employee><Employee>
<EmployeeId>7900</EmployeeId><Name>JAMES</Name><Job>CLERK</Job><Manager>7698</Man
ager><Hiredate>03-
DEC-81</Hiredate></Employee></Department>
<Department><DeptNo>40</DeptNo><DeptName>OPERATIONS</DeptName>
<Location>BOSTON</Location></Department>

This is what I am looking for ...But I am getting the following error because of
master record has more than
100+ detail records...
In real case I have master and detail tables .For every master records there
multiple detail records...
but there 40 % of master records has more than 100+ details records.So when it
is doing an aggregation
the buffer is not enough to do this operation..

The total output will store in a clob.
So I need your advise how do I achieve this....

Error:

ERROR:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at line 1

Thanks for you time
Followup:
I asked Sean Dillon, our resident XML technologist, to take a look at this and
here's what he had to say:
--

There are a couple of comments I'll make.

1.  Since you didn't post the code that actually generates that error, it makes
it fairly difficult to fix.
2.  The query you posted returns multiple rows.  Did you want to create a single
XML document with all the departments?  If so, you need another XMLAGG function
call around the "Department" element.
  select xmlelement("Departments",
          (select xmlagg(select xmlelement("Department", ....
3.  It looks like you are doing this operation in PL/SQL, so you can select the
result into an XMLTYPE variable.  I did the operation on DBA_OBJECTS, which has
a considerably larger parent:child ratio (18000+ synonyms, 15000+ java classes,
etc.).

declare
  l_xmlclob clob;
begin
  for i in (
  SELECT XMLElement("Objects",
           XMLForest(o.object_type "ObjectType"),
              (SELECT XMLAGG(
                        XMLElement("Object",
                          XMLForest(o2.object_id "ObjectId",
                                    o2.object_name "ObjectName",
                                    o2.subobject_name "SubObjectName",
                                    o2.created "Created",
                                    o2.status "Status")))
                 FROM dba_objects o2
                WHERE o.object_type = o2.object_type)) result
   FROM (select distinct object_type
           from dba_objects) o )
  loop
    l_xmlclob := i.result.getClobVal();
  end loop;
end;
/

That's just some thoughts, I'm not sure if it actually solves your code problem
as you didn't post it :).  Hope that helps!

Sean
Rating: 3
March 15, 2004
Reviewer: A reader
Hi Sean,

This is the block I am using to populate the temp table...

I am getting the follwing error when ever the customers has multiple details
records....

The reqirement is to avoid the detail tables and put the info in master it self
as clob...

declare
cursor c_xml is
        select c.c_id,'<?xml version="1.0" encoding="UTF-8"?>'||

xmlelement("TProductData",xmlelement("Product",p.product_name||'.'||v.vender_name
),
         xmlelement("type",c.type),
         xmlelement("alias",(select xmlagg(xmlforest(pl.sub_product_name
"aliasName",
                                           pl.sub_type_id "type")
                                         ) AS "AliasInfo"
                         from phar.product_log pl
                         where pl.c_id=c.c_id
                          and pl.status_id=1
                         ))).getStringVal() AS "CINFO"
                         from phar.customer c,phar.product p,phar.vendor v
                         where c.product_id=p.product_id
                         and p.v_id=v.v_id
                        and c.c_id>=1 and c.c_id<2000;
                        begin
                                for c1 in c_xml loop
                                exit when c_xml%notfound;
                                insert into phar.temp_customer
values(c1.c_id,c1.cinfo);
                                end loop;
                                commit;
                        end;
/

I hope this might give you a clear picture and what I am planning to achieve..

Thanks for your time and support.
March 15, 2004 -- 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.
Rating: 3
Nice March 15, 2004
Reviewer: Gerhard from Dusseldorf,Germany
Hi Tom,
In the "Create type " statement, Functions and Procedures
can be defined with "static" keyword.What role does "static" play in the
function or procedure declaration?
Please do reply.
Followup:
static just means the the method does not need an "instance" to work on -- it is
not tied to a single "instance", it does not have a "self" parameter.

it is just like static in java for example -- there you can have a class that
need not be instantiated in order to call the subroutines contained therein.

Same with static in a type, makes it more like a package then a type body at
that point.

In the past, they mostly were used for developing non-default constructors, but
the language was evolved recently to support the direct implementation of
non-default constructors natively.
March 15, 2004 Curtis -- Thanks for the question regarding "ora-28007 password cannot be reused problem", version 9.2.0
You Asked
I have created an application and assigned a profile to the users who connect to
the database with the Keep Password History entries as follows:
Keep 1 password(s)
Keep for Unlimited day(s)

With this, I am having the users enter a password.  Upon expiration/account lock
I am having an administrator reset the account to a default password
(Reset_Pass1) which then forces the user to enter a new password.  However, upon
attempting to reset the account to the default password, I am getting the
"ora-28007 password cannot be reused" error...even if I go in and change the
password to several different passwords, it still will not accept my default
once it has been used.  I would think that the settings above would only store
the last password for an unlimited time, but it seems that it is storing all the
passwords that have ever been used.  Is there any way to clear the password
history?
and we said...
please see metalink support note  <Note:228991.1>

the documentation has a "bug" in that regard, setting to unlimited makes it
impossible to reuse the password ever.

the 10g docs have it right:
http://download-west.oracle.com/docs/cd/B12037_01/network.101/b10773/policies.htm#1006720
Review & Followup

Rating: 5
ora-28007 password cannot be reused problem, version 9.2.0 March 15, 2004
Reviewer: Curtis Bell from San Antonio, TX USA
Thank you, it did work.

One note, using the OEM, once a value is selected for keeping either the Keep
(value) password(s) or Keep for (value) day(s) the opposite changes to
"Unlimited"--which is the Oracle 8i rule (ie: I set the Keep (value) password(s)
to "30" and the OEM sets Keep for (value) day(s) to "unlimited"; I change Keep
for (value) day(s) to "30" and the OEM changes the Keep (value) password(s) to
"unlimited").  However, I had no problems setting both of the values in
SQL*plus.
Rating: 4
workaround March 15, 2004
Reviewer: eric givler from harrisburg,pa
We had a similar experience.

To reset the password to a "pre-established" default, we just issued:

alter user test_account profile none;
alter user test_account identified by dummy;
alter user test_account profile pwd_rules;
alter user test_account password expire; -- force change
March 15, 2004 Rakesh -- Thanks for the question regarding "SQL Tuning", version 8.0.5
You Asked
I am new in tuning sql statements. Can u give a methodology of tuning
the sql statements.
and we said...
Here is a short extract from a book I am working on.  The short answer is

if you want a 10 step guide to tuning a query, buy a piece of software.  You are
not needed in this process, anyone can put a query in, get a query out and run
it to see if it is faster.  There are tons of these tools on the market.  They
work using rules (heuristics) and can tune maybe 1% of the problem queries out
there.  They APPEAR to be able to tune a much larger percent but that is only
because the people using these tools never look at the outcome -- hence they
continue to make the same basic mistakes over and over and over.

If you want to really be aboe to tune the other 99% of the queries out there,
knowledge of lots of stuff -- physical storage mechanisms, access paths, how the
optimizer works - thats the only way.

of course, read:
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76965/toc.htm
from cover to cover and
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76992/toc.htm
as well

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:

o    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)

o    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.

o    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.

o    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.
.....
Review & Followup

Rating: 5
SQL Tuning March 24, 2003
Reviewer: Rakesh Dudhia from Thousand Oaks, CA USA
This information and guidence will help me a lot and gives me the direction for
SQL Tuning. Thank you so much, Tom.
Rating: 4
Difference in SQL's August 21, 2003
Reviewer: Vivek Sharma from Bombay, India
Dear Tom,

Can you please tell me the difference between the following 2 queries in terms
of resources and performance.

select object_name, cnt from vivek_temp a,
       (select object_id, count(*) cnt from vivek_temp2
        group by object_id) b
where a.object_id = b.object_id

and

select object_name, count(*)
from vivek_temp a, vivek_temp2 b
where a.object_id = b.object_id
group by object_name;

Which query will be better. At present there are no indexe on both the tables
and the count of no. of records is
vivek_temp 29616 record and vivek_temp2 917 records.

I have read in a book that the first query will be faster but when I genereted
the output using set autotrace traceonly, I could not find any difference.

Thanks and Regards
Followup:
nope.

did you try benchmarking them.  I could, but it seems you already have
everything setup?

use tkprof
Rating: 4
sql re-write suggestion October 10, 2003
Reviewer: Baqir Hussain from Emeryville, CA USA
Tom,
I would very much appreciate if you please give some suggestions that how the
following query can be rewritten in robust fashion:

select count(personnel_id) from personnel
where personnel_id in (select personnel_id from personnel_role where
department_id = 12)
and personnel_id in (select personnel_id from personnel_role where district_id
is not null)
and personnel_id in (select personnel_id from personnel_role where end_date is
null);

Thanks in advance
Followup:
well, to generate the set of personnel ids we are interested int, we could:

select personnel_id,
       max(decode(department_id,12,1)) got_dept,
       max(decode(district_id,null,to_number(null),1)) got_district,
       max(decode(end_date,null,1)) got_end_date
  from personnel_role
 group by personnel_id
/

(I'm assuming that there are many rows in personnel role for each person and we
need to "look across them"

Now, if personnel_id in personnel_role MUST be in personnel (eg: parent child
relationship) all we need to do is:

select count(*)
  from ( THAT_QUERY )
/

else we can

select count(*)
  from personnel a, (THAT_QUERY) b
 where a.personnel_id = b.personnel_id
/
Rating: 5
excellent explanation November 03, 2003
Reviewer: austin from ohio
Hi Tom

I am trying to fine tune this query .This takes almost 50 seconds need to get
that down.

Select distinct PA.PersonAddress_IDX, AT.Name AddressType,
   A.Line1 Address1, A.Line2 Address2, A.City, A.State,
   A.County, A.Country, A.PostalCode, A.AllowPostalSoftYN,
PA.ChangedBy,
   PA.ChangedDT, PA.DeletedYN ,PA.Person_Key, PA.Address_Key,
   PA.AddressType_Key
FROM       PersonAddress_h PA,Address_h A,AddressType_h AT,
   (select max(CHANGEDDT)
maxchdt,Person_key,AddressType_Key,Address_Key
           from PersonAddress_h
           group by Person_key,AddressType_Key,Address_Key) X
where   PA.AddressType_Key IN (1,2,3) AND AT.AddressType_IDX =
PA.AddressType_Key
   And A.Address_IDX = PA.Address_Key and PA.DeletedYN = 0
   and PA.Person_KEY in (SELECT PERSON_KEY FROM INSURED_h I where
I.insured_idx=592374 )
   and PA.CHANGEDDT=X.maxchdt
   and PA.AddressType_Key=X.AddressType_Key
   and PA.Address_Key=X.Address_Key
   and AT.CHANGEDDT=(select max(CHANGEDDT) from AddressType_h
                           where AddressType_IDX = PA.AddressType_Key)
   and     A.CHANGEDDT= (Select max(CHANGEDDT) from Address_h
                   where Address_IDX = PA.Address_Key and
                   (CHANGEDDT-to_date('10/22/2003
18:02:30','mm/dd/yyyy hh24:mi:ss'))<=0.001 )

The exaplain plan now is

Rows     Row Source Operation
-------  ---------------------------------------------------
      3  SORT UNIQUE
      8   FILTER
     20    SORT GROUP BY
   4256     TABLE ACCESS BY INDEX ROWID ADDRESS_H
   8513      NESTED LOOPS
   4256       NESTED LOOPS
   1120        HASH JOIN
   1120         HASH JOIN
    560          HASH JOIN
    560           TABLE ACCESS BY INDEX ROWID PERSONADDRESS_H
    617            NESTED LOOPS
     56             TABLE ACCESS BY INDEX ROWID INSURED_H
     56              INDEX RANGE SCAN INDX_INSURED_H_IDX_EDATE_CDATE
(object id 35548)
    560             INDEX RANGE SCAN INDX_PRSNADDR_PRSN_ADDR_H (object
id 56328)
      3           VIEW
      3            SORT GROUP BY
      6             INDEX FAST FULL SCAN CI_ADDRESSTYPE_H (object id
34443)
      6          TABLE ACCESS FULL ADDRESSTYPE_H
 459380         VIEW
 459380          SORT GROUP BY
 462919           TABLE ACCESS FULL ADDRESS_H
   4256        INDEX RANGE SCAN INDX_PRSNADDR_ALL (object id 56331)
   4256       INDEX RANGE SCAN CI_ADDRESS_H (object id 34445)

what baffles me is why the full table scans on ADDRESSTYPE_H and
ADDRESS_H

The tables ADDRESSTYPE_H and ADDRESS_H contain 464080 and 8 records
respectively

Is ther a better way to rewrite this query.A hint or a pointer would be enough
to set of the direction.Also i tried the manuals to understand the plan..the
manuals are way to simplistic..was just wunderin if you can take some time out
to explain what the query id tryin to do in tom speak :-)
Followup:
lets see the autotrace traceonly explain output as well from sqlplus

and do you have access to my latest book "effective Oracle by design", in there
I walk thru reading explain plans. i have an excerpt here
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:231814117467#7344298017927
Rating: 5
heres my set autotrace on November 07, 2003
Reviewer: austin from ohio
Hi Tom

Select distinct PA.PersonAddress_IDX, AT.Name AddressType,
    A.Line1 Address1, A.Line2 Address2, A.City, A.State,
     A.County, A.Country, A.PostalCode, A.AllowPostalSoftYN, PA.ChangedBy,
     PA.ChangedDT, PA.DeletedYN ,PA.Person_Key, PA.Address_Key,
     PA.AddressType_Key
  FROM       PersonAddress_h PA,Address_h A,AddressType_h AT,
     (select max(CHANGEDDT) maxchdt,Person_key,AddressType_Key,Address_Key
             from PersonAddress_h
             group by Person_key,AddressType_Key,Address_Key) X
  where   PA.AddressType_Key IN (1,2,3) AND AT.AddressType_IDX =
PA.AddressType_Key
     And A.Address_IDX = PA.Address_Key and PA.DeletedYN = 0
     and PA.Person_KEY in (SELECT PERSON_KEY FROM INSURED_h I where
I.insured_idx=592374 )
    and PA.CHANGEDDT=X.maxchdt
     and PA.AddressType_Key=X.AddressType_Key
     and PA.Address_Key=X.Address_Key
     and AT.CHANGEDDT=(select max(CHANGEDDT) from AddressType_h
                             where AddressType_IDX = PA.AddressType_Key)
     and     A.CHANGEDDT= (Select max(CHANGEDDT) from Address_h
                     where Address_IDX = PA.Address_Key and
                     (CHANGEDDT-to_date('10/22/2003 18:02:30','mm/dd/yyyy
hh24:mi:ss'))<=0.001 )

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3802 Card=1 Bytes=19
          6)

   1    0   SORT (UNIQUE) (Cost=3802 Card=1 Bytes=196)
   2    1     FILTER
   3    2       SORT (GROUP BY) (Cost=3802 Card=1 Bytes=196)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'ADDRESS_H' (Cost=3
           Card=1 Bytes=74)

   5    4           NESTED LOOPS (Cost=3791 Card=1 Bytes=196)
   6    5             NESTED LOOPS (Cost=3788 Card=1 Bytes=122)
   7    6               NESTED LOOPS (Cost=3785 Card=1 Bytes=101)
   8    7                 HASH JOIN (Cost=3783 Card=1 Bytes=79)
   9    8                   HASH JOIN (Cost=42 Card=13 Bytes=845)
  10    9                     TABLE ACCESS (BY INDEX ROWID) OF 'PERSON
          ADDRESS_H' (Cost=6 Card=89634 Bytes=3854262)

  11   10                       NESTED LOOPS (Cost=40 Card=16 Bytes=84
          8)

  12   11                         TABLE ACCESS (BY INDEX ROWID) OF 'IN
          SURED_H' (Cost=10 Card=5 Bytes=50)

  13   12                           INDEX (RANGE SCAN) OF 'INDX_INSURE
          D_H_IDX_EDATE_CDATE' (NON-UNIQUE) (Cost=4 Card=5)

  14   11                         INDEX (RANGE SCAN) OF 'INDX_PRSNADDR
          _PRSN_ADDR_H' (NON-UNIQUE) (Cost=3 Card=3)

  15    9                     VIEW OF 'VW_SQ_1' (Cost=1 Card=3 Bytes=3
          6)

  16   15                       SORT (GROUP BY) (Cost=1 Card=3 Bytes=3
          3)

  17   16                         INLIST ITERATOR
  18   17                           INDEX (RANGE SCAN) OF 'CI_ADDRESST
          YPE_H' (NON-UNIQUE) (Cost=1 Card=6 Bytes=66)

  19    8                   VIEW OF 'VW_SQ_2' (Cost=3740 Card=23212 By
          tes=324968)

  20   19                     SORT (GROUP BY) (Cost=3740 Card=23212 By
          tes=301756)

  21   20                       TABLE ACCESS (FULL) OF 'ADDRESS_H' (Co
          st=3430 Card=23218 Bytes=301834)

  22    7                 INLIST ITERATOR
  23   22                   TABLE ACCESS (BY INDEX ROWID) OF 'ADDRESST
          YPE_H' (Cost=2 Card=1 Bytes=22)

  24   23                     INDEX (RANGE SCAN) OF 'CI_ADDRESSTYPE_H'
           (NON-UNIQUE) (Cost=1 Card=1)

  25    6               INDEX (RANGE SCAN) OF 'INDX_PRSNADDR_ALL' (NON
          -UNIQUE) (Cost=3 Card=1 Bytes=21)

  26    5             INDEX (RANGE SCAN) OF 'CI_ADDRESS_H' (NON-UNIQUE
          ) (Cost=2 Card=1)

Statistics
----------------------------------------------------------
        103  recursive calls
         31  db block gets
      38654  consistent gets
      32404  physical reads
          0  redo size
        854  bytes sent via SQL*Net to client
        253  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          7  sorts (memory)
          1  sorts (disk)
          3  rows processed

I managed to get expert one on one by you.Its a great book on oracle...and suits
my rqeuirements esp loved the chapter where you compare oracle with sybase and
DB2.

The other book is not available will try to get it too..

I understand that you want to look at the cardinalites .Just curious why you are
askin for a autotrace output when we already have tkprof ..
Followup:
reason i wanted both - i want to see what we EXPECTED, vs what we GOT

for example, look at the view on ADDRESS_H, we EXPECTED 23,212 rows. we GOT
459,380.  This mistake made the cards go from an expected 1 in the hash/nested
loops joins to thousands.

So, why?  are the stats upto date? how do you gather them (exact command, please
don't say "we compute", say "we run this command")
Rating: 5
November 08, 2003
Reviewer: austin from ohio
Hi Tom

Thanks for taking your precious time out to look into this problem.I am learning
a lot from your comments.The stats are collected everynight.Heres the exact
command we use

exec dbms_stats.gather_schema_statsownname=>'AUSTIN',METHOD_OPT=>'for
all columns ',CASCADE=>true);

The optimizer expects 23,212 rows but finds 459,380 and the reason might be
attributed to missin or stale stats.Have you explained such scenarios in your
book Performance by design ?
Followup:
what happens to the autotrace if you change:

A.CHANGEDDT= (Select max(CHANGEDDT) from Address_h
                     where Address_IDX = PA.Address_Key and
                     (CHANGEDDT-to_date('10/22/2003 18:02:30','mm/dd/yyyy
hh24:mi:ss'))<=0.001 )

to

A.CHANGEDDT=
(Select max(CHANGEDDT)
   from Address_h
  where Address_IDX = PA.Address_Key
    and CHANGEDDT <= to_date('10/22/2003 18:02:30','mm/dd/yyyy
hh24:mi:ss')+0.001 )

which seems like a "strange" constraint in the first place, why not just use
18:03:56 for the time and lose the 0.001?
Rating: 5
excellent response I did not notice this November 12, 2003
Reviewer: austin from Ohio
Hi Tom

Thank you very much..You have spotted the right thing.

This time we have a slightly better plan

SQL> Select distinct PA.PersonAddress_IDX, AT.Name AddressType,
  2     A.Line1 Address1, A.Line2 Address2, A.City, A.State,
  3     A.County, A.Country, A.PostalCode, A.AllowPostalSoftYN, PA.ChangedBy,
  4     PA.ChangedDT, PA.DeletedYN ,PA.Person_Key, PA.Address_Key,
  5     PA.AddressType_Key
  6  FROM       PersonAddress_h PA,Address_h A,AddressType_h AT,
  7     (select max(CHANGEDDT) maxchdt,Person_key,AddressType_Key,Address_Key
  8             from PersonAddress_h
  9             group by Person_key,AddressType_Key,Address_Key) X
 10  where   PA.AddressType_Key IN (1,2,3) AND AT.AddressType_IDX =
PA.AddressType_Key
 11     And A.Address_IDX = PA.Address_Key and PA.DeletedYN = 0
 12     and PA.Person_KEY in (SELECT PERSON_KEY FROM INSURED_h I where
I.insured_idx=592374 )
 13     and PA.CHANGEDDT=X.maxchdt
 14     and PA.AddressType_Key=X.AddressType_Key
 15     and PA.Address_Key=X.Address_Key
 16     and AT.CHANGEDDT=(select max(CHANGEDDT) from AddressType_h
 17                             where AddressType_IDX = PA.AddressType_Key)
 18     and     A.CHANGEDDT= (Select max(CHANGEDDT) from Address_h
 19                     where Address_IDX = PA.Address_Key and
 20                     (CHANGEDDT-to_date('10/22/2003 18:02:30','mm/dd/yyyy
hh24:mi:ss'))<=0.001 )
 21

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3779 Card=1 Bytes=18
          7)

   1    0   SORT (UNIQUE) (Cost=3779 Card=1 Bytes=187)
   2    1     FILTER
   3    2       SORT (GROUP BY) (Cost=3779 Card=1 Bytes=187)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'ADDRESS_H' (Cost=3
           Card=1 Bytes=66)

   5    4           NESTED LOOPS (Cost=3768 Card=1 Bytes=187)
   6    5             NESTED LOOPS (Cost=3765 Card=1 Bytes=121)
   7    6               NESTED LOOPS (Cost=3762 Card=1 Bytes=100)
   8    7                 HASH JOIN (Cost=3760 Card=1 Bytes=78)
   9    8                   HASH JOIN (Cost=42 Card=16 Bytes=1040)
  10    9                     TABLE ACCESS (BY INDEX ROWID) OF 'PERSON
          ADDRESS_H' (Cost=6 Card=101955 Bytes=4384065)

  11   10                       NESTED LOOPS (Cost=40 Card=16 Bytes=84
          8)

  12   11                         TABLE ACCESS (BY INDEX ROWID) OF 'IN
          SURED_H' (Cost=10 Card=5 Bytes=50)

  13   12                           INDEX (RANGE SCAN) OF 'INDX_INSURE
          D_H_IDX_EDATE_CDATE' (NON-UNIQUE) (Cost=4 Card=5)

  14   11                         INDEX (RANGE SCAN) OF 'INDX_PRSNADDR
          _PRSN_ADDR_H' (NON-UNIQUE) (Cost=3 Card=3)

  15    9                     VIEW OF 'VW_SQ_1' (Cost=1 Card=3 Bytes=3
          6)

  16   15                       SORT (GROUP BY) (Cost=1 Card=3 Bytes=3
          3)

  17   16                         INLIST ITERATOR
  18   17                           INDEX (RANGE SCAN) OF 'CI_ADDRESST
          YPE_H' (NON-UNIQUE) (Cost=1 Card=6 Bytes=66)

  19    8                   VIEW OF 'VW_SQ_2' (Cost=3717 Card=23212 By
          tes=301756)

  20   19                     SORT (GROUP BY) (Cost=3717 Card=23212 By
          tes=255332)

  21   20                       TABLE ACCESS (FULL) OF 'ADDRESS_H' (Co
          st=3430 Card=23218 Bytes=255398)

  22    7                 INLIST ITERATOR
  23   22                   TABLE ACCESS (BY INDEX ROWID) OF 'ADDRESST
          YPE_H' (Cost=2 Card=1 Bytes=22)

  24   23                     INDEX (RANGE SCAN) OF 'CI_ADDRESSTYPE_H'
           (NON-UNIQUE) (Cost=1 Card=1)

  25    6               INDEX (RANGE SCAN) OF 'INDX_PRSNADDR_ALL' (NON
          -UNIQUE) (Cost=3 Card=1 Bytes=21)

  26    5             INDEX (RANGE SCAN) OF 'CI_ADDRESS_H' (NON-UNIQUE
          ) (Cost=2 Card=1)

Statistics
----------------------------------------------------------
          0  recursive calls
         31  db block gets
      38633  consistent gets
      31897  physical reads
          0  redo size
        865  bytes sent via SQL*Net to client
        253  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          3  sorts (memory)
          1  sorts (disk)
          3  rows processed

But can we improve the query performance at all ?

I also notice a very strange thing

If in the qury on line 10 if we use

where   PA.AddressType_Key IN (1) instead of
PA.AddressType_Key IN (1,2,3) or any single number then the result is
instanttaneous any ideas why this happening.
Followup:
well, in (1) processes less rows then in (1,2,3) would no?

you can see about using analytics instead of subqueries.  for example, one
approach to find the emp(s) with the max(hiredate) by dept could be:

select *
  from emp
 where hiredate = ( select max(hiredate) from emp e2 where e2.deptno =
emp.deptno)

that is what you have coded, another way is:

select *
  from ( select emp.*, max(hiredate) over ( partition by deptno ) max_hd
           from emp
       )
 where hiredate = max_hd;
Rating: 5
Optimal Schema Design December 04, 2003
Reviewer: A reader
Hi Tom,

I have an impression that no matter how hard I spend time designing the schema,
choosing table types, creating indexes, there is no such thing as "the most
optimally designed schema".

For example, a table designed for small data volume may become very non-optimal
if the data volume increases; a well-picked index may slow down the performance
if there is ad-hoc query not being expected.

In your opinion, is there an "optimally designed schema" or not? Please comment.
Followup:
there are optimally designed functions

today with huge schemas - probably not optimally designed schemas for the entire
system.

You pick the hard parts -- optimize for them at the expense of others.

I'll gladly denormalize in a transactional system if

a) the inserts are relatively few compared to
b) the reads, which if I didn't optimize for -- would kill me

I did sort of a case study on that in my book "effective oracle by design".  it
was a palm pilot sort of application (well, it was a web
calendard/scheduler/contact list to be synced with a palm)

we could have used the palm model, very terse, compact -- very fast for inserts

but -- dog dog dog slow for queries and impossible to query using sql (eg: had
to read data out and do the query in the middle tier)

we realized (based on study) that well over 90% of the access was read.  inserts
-- few (you schedule an appointment once, you look at it dozens/hundreds of
times as do others)

we optimized for reads -- took more storage, much harder to update, but the
performance -- outstanding.

Take your low hanging fruit -- go for it.  Make it good and the rest good
enough.  concentrate on the things that if they do not work would kill you.
make them go really good.

my 2cents worth.
Rating: 5
Sql's across db links. December 05, 2003
Reviewer: Sai from San Jose, CA
Hi Tom,

Tuning sql statments across db links driving me crazy, absolutely no consistency
in Oracle optimizer plans. For example.. I want to write following pl/sql block
in to single sql statment, but it was always doing full table scan with one
single statment( no matter whatever analyze, hints...etc...I do). I have to use
Index range scan here, since it selects very few rows out of a 20G table, and
hence I was forced to choose pl/sql.

for i in ( select * from prod.poof_model_login@ctoc
           where time_created between v_time and v_time_end) loop
insert into ctoc.poof_model_login values (i.id,i.account_number,i.time_created
,i.flags,i.spoof_bucket,i.login_ip,i.susp_activity_level,i.geography);
end loop;

What I have observed was, Oracle will use efficient optimizer plans across db
links only in a plain select statment, but not with CTAS, insert/update/delete
statments.

Your thoughts on this is very much appreciated.

Thanks
Rating: 5
SQL tuning the scientific way January 01, 2004
Reviewer: A reader
Hi

I bought a SQL tuning book recently from OReilly, the title is called "SQL
Tuning" by Dan Tow. I read a few chapters and noticed he is base many things
scientifically, using filter ratios, heuristic join orderes, nodes, links
between tabla joins, full query diagrams etc. Are these the basics behind Oracle
Optimizer? Do you tune a query this way or using common sense such as reducing
the query result sets as small as possible (the Oracle documentation way). The
book seems reasonably good just that if we are using CBO I think it's quite
useless :-)
Followup:
I agree with your last sentence :)

that is the work of software, our job is in the design.  If you have a bad
design for the questions you frequently ask -- no amount of query tuning is
going to help.
Rating: 4
Reducing I/O January 03, 2004
Reviewer: Vivek Sharma from Bombay, India
Dear Tom,

I have a query which is scanning table Nbfc_Pmnt_dtl table twice via Index. This
table has around 2.3 Crore Rows. How can I change this query so that this table
is scanned only once so as to perform single I/O.

SELECT a.Chequeid,a.AllocatedAmt,
       nvl(a.intcomp_recd/*used this as chargecodeid 22 was used earlier*/,0)
allocated_prin
FROM finnonelea.Nbfc_Pmnt_Dtl a
WHERE TxnAdviceID = :txnadviceid
AND NVL(a.Status,'Z') = 'Z'
UNION all
SELECT a.Chequeid,a.AllocatedAmt,0 allocated_prin
FROM finnonelea.Nbfc_Pmnt_Dtl a,
finnonelea.Nbfc_txn_advice_dtl b
WHERE a.TxnAdviceID = :txnadviceid
AND NVL(a.Status,'Z') = 'Z'
AND a.txnadviceid = b.txnadviceid
AND b.chargeid in ( select chargeid from finnonelea.nbfc_charges_m where
chargecodeid in ( 82,261 ) )
/

Regards
Vivek Sharma
Followup:
can't realistically  (well, we could but it would probably be worse)

You have rows coming out of finnonelea.Nbfc_Pmnt_Dtl TWICE in that query.

once when not joined to B and again when joined to B.

If you are willing to change the output result set "shape", we can do this.
Instead of outputting the row 2times, we'll use 2 columns on a single row.

SELECT a.Chequeid,
       a.AllocatedAmt,
       nvl(a.intcomp_recd/*used this as chargecodeid 22 was used earlier*/,0)
c1,
       decode( b.txnadviceid, NULL, NULL, 0 ) c2
  FROM finnonelea.Nbfc_Pmnt_Dtl a,
       (select txnadvicedid
          from finnonelea.Nbfc_txn_advice_dtl
         where txnadviceid = :txnadviceid
           and chargeid in ( select chargeid
                               from finnonelea.nbfc_charges_m
                              where chargecodeid in ( 82,261 ) )
       ) b
 WHERE a.TxnAdviceID = :txnadviceid
   AND NVL(a.Status,'Z') = 'Z'
   AND a.txnadviceid = b.txnadviceid(+)
/

Now, C1 will be filled in for all rows and if C1 is NULL, then the "second
record" from A would not have existed.  If C2 is 0, it would have existed (would
have come out in the union all query)
Rating: 5
Tuning SQL Query January 07, 2004
Reviewer: Tarun from India
Hi tom,
  I tried to post question several times but, its showing status busy always.
Please help me to write the following query in a better way.

INSERT INTO drehr_addr_errors
         SELECT bu_name -- check for existence of town or city
               ,system_location
               ,system_name
               ,employee_number
               ,national_identifier
               ,'TOWN_OR_CITY'
               ,town_or_city
               ,'Town or City is Null'
               ,SYSDATE
               ,0
               ,SYSDATE
               ,0
               ,NULL
               ,NULL
           FROM drehr_addr
          WHERE town_or_city IS NULL
         UNION
         SELECT bu_name -- check for existence of State
               ,system_location
               ,system_name
               ,employee_number
               ,national_identifier
               ,'REGION2'
               ,region2
               ,'State is Null'
               ,SYSDATE
               ,0
               ,SYSDATE
               ,0
               ,NULL
               ,NULL
           FROM drehr_addr
          WHERE region2 IS NULL
         UNION
         SELECT bu_name -- check for existence of County
               ,system_location
               ,system_name
               ,employee_number
               ,national_identifier
               ,'REGION1'
               ,region1
               ,'County is Null'
               ,SYSDATE
               ,0
               ,SYSDATE
               ,0
               ,NULL
               ,NULL
           FROM drehr_addr
          WHERE region1 IS NULL
         UNION
         SELECT bu_name -- check for existence of ZIP
               ,system_location
               ,system_name
               ,employee_number
               ,national_identifier
               ,'POSTAL_CODE'
               ,postal_code
               ,'Postal Code is Null'
               ,SYSDATE
               ,0
               ,SYSDATE
               ,0
               ,NULL
               ,NULL
           FROM drehr_addr
          WHERE postal_code IS NULL
         UNION
         SELECT bu_name -- check for valid Primary Flag
               ,system_location
               ,system_name
               ,employee_number
               ,national_identifier
               ,'PRIMARY_FLAG'
               ,primary_flag
               ,'Invalid Primary Flag'
               ,SYSDATE
               ,0
               ,SYSDATE
               ,0
               ,NULL
               ,NULL
           FROM drehr_addr
          WHERE UPPER (primary_flag) <> 'N'
            AND UPPER (primary_flag) <> 'Y'
            AND UPPER (primary_flag) <> 'YES'
            AND UPPER (primary_flag) <> 'NO'
         UNION
         SELECT bu_name -- Chk for the Duplicate primary flag enabled for the
employee
               ,system_location
               ,system_name
               ,employee_number
               ,national_identifier
               ,'PRIMARY_FLAG'
               ,primary_flag
               ,'Duplicate Primary Flag'
               ,SYSDATE
               ,0
               ,SYSDATE
               ,0
               ,NULL
               ,NULL
           FROM drehr_addr a
          WHERE 1 <
               (SELECT   COUNT (primary_flag)
                FROM drehr_addr b
                   WHERE (   UPPER (primary_flag) = 'Y'
                      OR UPPER (primary_flag) = 'YES'
                     )
                 AND a.employee_number = b.employee_number
                 AND a.bu_name = b.bu_name
                 AND a.system_location = b.system_location
                 AND a.system_name = b.system_name
                 AND a.national_identifier = b.national_identifier
                GROUP BY bu_name
                    ,system_name
                    ,system_location
                    ,national_identifier
                    ,employee_number)
         UNION
         SELECT bu_name -- check for date_from less than hiredate of employee
               ,system_location
               ,system_name
               ,employee_number
               ,national_identifier
               ,'DATE_FROM'
               ,TO_CHAR (date_from)
               ,'Date From is Less Than Hiredate of Employee'
               ,SYSDATE
               ,0
               ,SYSDATE
               ,0
               ,NULL
               ,NULL
           FROM drehr_addr a
          WHERE TRUNC (date_from) <
               (SELECT DISTINCT TRUNC (original_date_of_hire)
                       FROM per_all_people_f p
                      WHERE a.employee_number= p.attribute3
                    AND NVL (a.national_identifier, '~') =
                            NVL (p.national_identifier, '~')
                    AND a.effective_date
                           BETWEEN p.effective_start_date
                           AND p.effective_end_date)
     UNION
         SELECT bu_name -- check for existence of national_identifier and it
length for US addresses
               ,system_location
               ,system_name
               ,employee_number
               ,national_identifier
               ,'NATIONAL_IDENTIFIER'
               ,national_identifier
               ,'National Udentifier Existence/Length Validation failed for US
Employee'
               ,SYSDATE
               ,0
               ,SYSDATE
               ,0
               ,NULL
               ,NULL
           FROM drehr_addr
          WHERE UPPER (style) = 'US'
            AND UPPER (country) = 'US'
            AND (   national_identifier IS NULL
             OR LENGTH (national_identifier) <> 11
            )
         UNION
         SELECT   bu_name -- Check for equality of Effective date and date from
when prior addresses are not there
             ,system_location
             ,system_name
             ,employee_number
             ,national_identifier
             ,'EFFECTIVE_DATE'
             ,TO_CHAR (effective_date)
             ,'Effective_Date and Date_From not same for Single Address Record'
             ,SYSDATE
             ,0
             ,SYSDATE
             ,0
             ,NULL
             ,NULL
             FROM drehr_addr
            WHERE TRUNC (effective_date) <> TRUNC (date_from)
         GROUP BY bu_name
             ,system_name
             ,system_location
             ,national_identifier
             ,employee_number
             ,effective_date
           HAVING COUNT (DISTINCT effective_date) = 1
/

Execution Plan
----------------------------------------------------------
   0      INSERT STATEMENT Optimizer=CHOOSE (Cost=383 Card=30 Bytes=34
          10)

   1    0   SORT (UNIQUE) (Cost=383 Card=30 Bytes=3410)
   2    1     UNION-ALL
   3    2       TABLE ACCESS (FULL) OF 'DREHR_ADDR' (Cost=2 Card=4 Byt
          es=428)

   4    2       TABLE ACCESS (FULL) OF 'DREHR_ADDR' (Cost=2 Card=4 Byt
          es=508)

   5    2       TABLE ACCESS (FULL) OF 'DREHR_ADDR' (Cost=2 Card=4 Byt
          es=508)

   6    2       TABLE ACCESS (FULL) OF 'DREHR_ADDR' (Cost=2 Card=4 Byt
          es=428)

   7    2       TABLE ACCESS (FULL) OF 'DREHR_ADDR' (Cost=2 Card=1 Byt
          es=107)

   8    2       FILTER
   9    8         TABLE ACCESS (FULL) OF 'DREHR_ADDR' (Cost=2 Card=4 B
          ytes=428)

  10    8         SORT (GROUP BY NOSORT) (Cost=2 Card=1 Bytes=107)
  11   10           TABLE ACCESS (FULL) OF 'DREHR_ADDR' (Cost=2 Card=1
           Bytes=107)

  12    2       FILTER
  13   12         TABLE ACCESS (FULL) OF 'DREHR_ADDR' (Cost=2 Card=4 B
          ytes=432)

  14   12         SORT (UNIQUE) (Cost=57 Card=1 Bytes=30)
  15   14           TABLE ACCESS (FULL) OF 'PER_ALL_PEOPLE_F' (Cost=44
           Card=1 Bytes=30)

  16    2       TABLE ACCESS (FULL) OF 'DREHR_ADDR' (Cost=2 Card=1 Byt
          es=139)

  17    2       FILTER
  18   17         SORT (GROUP BY) (Cost=27 Card=4 Bytes=432)
  19   18           TABLE ACCESS (FULL) OF 'DREHR_ADDR' (Cost=2 Card=4
           Bytes=432)
Followup:
well, i am just one guy -- i can take maybe 10/20 questions a day with people
asking questions here.  so, it is purely catch as catch can.

Actually, if the stats are accurate, this query should run in a fraction of a
millisecond.  no data in there... but anyway

take all of the queries of this form:

SELECT bu_name -- check for existence of town or city
...
               ,'TOWN_OR_CITY'
               ,town_or_city
               ,'Town or City is Null'
....
           FROM drehr_addr
          WHERE town_or_city IS NULL
         UNION
         SELECT bu_name -- check for existence of State
...
               ,'REGION2'
               ,region2
               ,'State is Null'
....
           FROM drehr_addr
          WHERE region2 IS NULL

and recode as a single statement:

SELECT bu_name -- check for existence of town or city
...
               ,decode( town_or_city, null, 'TOWN_OR_CITY') c1a,
               ,town_or_city                                c1b,
               ,decode( town_or_city, 'Town or City is Null' ) c1c,
               ,decode( region2, null, 'REGION2') c2a
               ,region2                                c2b
               ,decode( region2, 'State is null' ) c2c

....
           FROM drehr_addr
          WHERE town_or_city IS NULL OR region2 is NULL

change the code that processes this to look at the c1x, c2x, c3x, ... columns
instead of looking for row by row (yes, this will require a slight change to the
client code.

start there and apply the same technique to the rest of the query.

You have things like a case statement, so this query:

SELECT bu_name -- check for valid Primary Flag
               ,system_location
               ,system_name
               ,employee_number
               ,national_identifier
               ,'PRIMARY_FLAG'
               ,primary_flag
               ,'Invalid Primary Flag'
               ,SYSDATE
               ,0
               ,SYSDATE
               ,0
               ,NULL
               ,NULL
           FROM drehr_addr
          WHERE UPPER (primary_flag) <> 'N'
            AND UPPER (primary_flag) <> 'Y'
            AND UPPER (primary_flag) <> 'YES'
            AND UPPER (primary_flag) <> 'NO'

can become

 select bu_name, ......,  case when upper(primary_flag) not in ( 'N', 'Y',
'YES', 'NO' ) then 'Invalid Primary Flag' end, .....

your goal -- process ALL of the exceptions for a record in a single record!  if
a record has 5 errors, instead of getting 5 records, you'll get 1 record with 5
sets of columns populated describing the errors.
Rating: 5
index range scan related issue January 07, 2004
Reviewer: Reader
12:25:45 SQL> @tc1
12:25:46 SQL> spool tc1;
12:25:46 SQL> set echo on timing on
12:25:46 SQL> column plan_plus_exp format a100
12:25:46 SQL> set linesize 150
12:25:46 SQL> set trimspool on
12:25:46 SQL> drop table t;

Table dropped.

Elapsed: 00:00:00.09
12:25:46 SQL> create table t nologging as select * from dba_objects;

Table created.

Elapsed: 00:00:00.45
12:25:47 SQL> create index t_idx on t(owner,object_id) nologging compute
statistics;

Index created.

Elapsed: 00:00:00.28
12:25:47 SQL> set autot on
12:25:47 SQL> select rownum||' '||owner||' '||object_id from t where owner='SYS'
and rownum<11;

ROWNUM||''||OWNER||''||OBJECT_ID
---------------------------------------------------------------------------------
-------------------------------
1 SYS 98
2 SYS 99
3 SYS 100
4 SYS 101
5 SYS 107
6 SYS 121
7 SYS 125
8 SYS 142
9 SYS 900
10 SYS 902

10 rows selected.

Elapsed: 00:00:00.64

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=10 Bytes=180)
   1    0   COUNT (STOPKEY)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=218
Bytes=3924)

Statistics
----------------------------------------------------------
         22  recursive calls
          0  db block gets
          5  consistent gets
          1  physical reads
          0  redo size
        703  bytes sent via SQL*Net to client
        652  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         10  rows processed

12:25:48 SQL> select * from (select rownum||' '||owner||' '||object_id from t
12:25:48   2  where owner='SYS'
12:25:48   3  order by owner,object_id ) where rownum<11;

ROWNUM||''||OWNER||''||OBJECT_ID
---------------------------------------------------------------------------------
-------------------------------
1 SYS 98
2 SYS 99
3 SYS 100
4 SYS 101
5 SYS 107
6 SYS 121
7 SYS 125
8 SYS 142
9 SYS 900
10 SYS 902

10 rows selected.

Elapsed: 00:00:00.65

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=10 Bytes=580)
   1    0   COUNT (STOPKEY)
   2    1     VIEW (Cost=2 Card=218 Bytes=12644)
   3    2       COUNT
   4    3         INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=218
Bytes=3924)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          3  consistent gets
          0  physical reads
          0  redo size
        703  bytes sent via SQL*Net to client
        652  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         10  rows processed

12:25:48 SQL>

Question: Is there any risk first SQL may not work as expected in future (if I
drop index obviously it will not), but apart from that. Thus is the 2nd SQL
really needed?

Somehow I have a feeling you will say don't use first and only use 2nd?

Thanks for your help!
Followup:
RULES OF THE GAME:

rule #1: if you want, expect, or need sorted data, there is exactly one way to
achieve that in a relational database.

you must use order by

rule #2: please re-read #1 until you believe it.

the first query says "give me a random set of 10 rows for the SYS owner"

the second says "give me 10 rows for the sys owner starting at the smallest
object_id in sorted order"
Rating: 5
follow up to earlier January 08, 2004
Reviewer: A reader
Hi Tom,

Thanks for the answer.
However, "INDEX (RANGE SCAN) " vs full scan
Doesn't one goes from small -> big (or big->small) vs
scan all the blocks and present data as it presents (i.e no particular order).

If so, have you ever encountered data which says using "INDEX (RANGE SCAN) " AND
is out of order?
Also, can you take a approach like below,

select * from (select rownum rn,owner,object_id
from t where rownum<6)
order by rn;

Now the client reading the data will ensure if next rows rn value is not rn+1
compared to current one, there is a problem.

I am trying to understand "INDEX (RANGE SCAN) " does mean go from node to node
in certain order or not?

Again, thanks for your explanations
Followup:
if you want SORTED DATA, you need to order by.

select * from (select rownum rn,owner,object_id
from t where rownum<6)
order by rn;

is not any different than

select rownum rn, owner, object_id from t where rownum < 6;

then are the same.

if you need SORTED DATA by all means ORDER BY it -- else you can have NO
expectations as to the returned order of the data.  NONE.
Rating: 5
one correction January 08, 2004
Reviewer: A reader
my earlier question:

Actually no need to order for this test

select * from (select owner,object_id
from t where rownum<6)
;

Now the client reading the data will ensure owner+object_id of next row is in
order (< or >) than current owner+object. If not rollback and retry
Followup:
that is called a "random code path"

it is meaningless.  it is "not sensible".  it makes no sense.

retry what?  you'll just get the same data (i guess you could call this an
infinite loop)
Rating: 5
Oops January 08, 2004
Reviewer: A reader
select owner,object_id from t where owner='SYS' and rownum<6
should be the sql I was saying in prior two posts.

Sorry for that.

Thanks
Rating: 5
SQL Tuning January 08, 2004
Reviewer: Gerry Jurrens from Kingston, NJ
I teach SQL Tuning and found this thread EXTREMELY helpful!  I try to tell my
students that if I had a magic wand, I'd put the code in the kernel of Oracle
and no one would have to do anything to tune their SQL.

Tom's book is great!  Buy the book!

GJ
Rating: 3
Kiran from India January 28, 2004
Reviewer: Kiran from India
Hi,

Tom Could u pls suggest me a good bood for SQL Tuning.
Followup:
how about the oracle documentation set?

have you read the concepts guide?  the application developers guide?  the
performance guide?

they are all pretty solid.

after that -- it is practice, practice, practice.  for you see -- the stuff that
can be "taught" as a list (eg: do this, do that, do the other thing) well -- we
put that into software.  so the stuff you can find in a book (tips and tricks
sort of stuff) -- it's already in the software.

The stuff that is NOT in the software -- how to take a slow by slow algorithm
(row by row) and turn it into a single sql statement.  You can tune a query to
death but if you execute it a million times, it'll still take a really long
time.  If you take that procedural process and turn it into a single sql
statement -- you'll find it goes much much faster.
Rating: 5
tuning tools are much more clever than CBO! January 29, 2004
Reviewer: A reader
I never thought these tuning tools such as Quest SQL LAB can make queries run
much better.

Was evaluating the tool and passed queries from one of slow batch jobs running
in HP-UX Oracle 9.2.0.4 (latest patch)... Guess what, SQL LAB reduced by 10
times (yes 10!!!) the query execution times!!! Our batch job used to take 6
hours now takes 35 minutes wowowowo!!!

I dont understand how these tools work but they seem to be much better than
CBO?!
Rating: 5
Oracle should hire Quest to write their CBO January 30, 2004
Reviewer: A reader
I think so, Quest SQL LAB seems much better than CBO!
Followup:
you should give OEM a try as well -- it does the same thing.
Rating: 5
Oracle should hire Quest? January 30, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
Funny.  Did the tool suggest a change to the query?

- Mark
Rating: 5
Quest Lab Better Than CBO ??? January 30, 2004
Reviewer: Kevin Balfe from Laurel, MD USA
I've used Quest Lab in the past.  It's pretty good, but:

1. It can take a half-hour to test hundreds of different scenarios.  Do you
REALLY want the CBO to take that much time to evaluate alternatives every time
you run a query?

2. It often uses HINTS which optimize the query under the particular
circumstances tested, and DE-optimize it under other (untested) circumstances.
This inflexible approach more or less assumes that circumstances (data,
hardware, config) will not change much.

3. It sometimes re-writes your SQL, even warning you that the results may not be
the same.

CBO does pretty well all things considered.  Quest Lab is a useful tool -- but
you have to live with IT's limitations too.
Followup:
And in 10g -- we do have the option of letting the optimizer take as long as we
want to optimize a query like that.  It is like gathering statistics on a query
-- and it uses extended stats in the data dictionary (not hints) to help the
optimizer out in real time later on.

#3 is the really bad thing -- especially if the person asking quest to do this
isn't SQL savvy enough to do it themselves -- how can they tell in some cases?
Rating: 4
any thoughts on this query March 12, 2004
Reviewer: john
Hi Tom,

This query is taking about 10 min. to execute.
Any thoughts on how can I improve it?

select t.tcode, sum(tp.percentage), t.FSTPERFYEARQTR
from title t,
(select b.TCODE, b.ICODE, b.percentage
from title_party b, (select a.TCODE, a.ICODE, a.PARTYID, a.RECORDCODE,
a.TERRITORY,
                       max(a.EFFDTE) as effdte
                    from title_party a
                    group by a.tcode, a.icode, a.partyid, a.recordcode,
a.territory) c
where b.tcode = c.tcode
and b.icode = c.icode
and b.partyid = c.partyid
and b.recordcode = c.recordcode
and b.territory = c.territory
and b.effdte = c.effdte
and b.recordcode = 1) tp
where t.ICODE = tp.ICODE
group by t.tcode, t.FSTPERFYEARQTR
having sum(tp.percentage) < 199
Followup:
seems to me that:

    (select b.TCODE, b.ICODE, b.percentage
       from title_party b,
            (select a.TCODE, a.ICODE, a.PARTYID, a.RECORDCODE,
                    a.TERRITORY, max(a.EFFDTE) as effdte
               from title_party a
              group by a.tcode, a.icode, a.partyid, a.recordcode, a.territory) c
      where b.tcode = c.tcode
        and b.icode = c.icode
        and b.partyid = c.partyid
        and b.recordcode = c.recordcode
        and b.territory = c.territory
        and b.effdte = c.effdte
        and b.recordcode = 1) tp

Is just trying to get the ICODE/PERCENTAGE for each record such that the EFFDTE
is the MAX(EFFDTE) by tcode,icode,partyid,territory for recordcode = 1

that is what it works out to in my head anyway -- since you generate the set of
max_effdtes by that and join.  If that agrees with your take on it, then I
believe that is more simply said like this:

(
select icode, percentage
  from ( (select icode, percentage, effdte,
                  max(effdte) over(partition by tcode, icode, partyid,
                                   territory) max_effdte
            from title
           where recordcode = 1)
 where effdte = max_effdte
) tp

so, you should be able to replace your TP with that one and make a single pass
on the title table and only pick up the recordcode=1 records

You might have to throw in some "and tcode is not null and icode is not null
..." as well if they are nullable (as your JOIN removes them)
Rating: 5
Thank You very much !! March 15, 2004
Reviewer: mike
Thanks, Tom. But I am getting this:

ERROR at line 9:
ORA-00907: missing right parenthesis

select t.tcode, sum(tp.percentage) , t.FSTPERFYEARQTR
from title t,
 (select icode, percentage
  from ( (select icode, percentage, effdte,
                  max(effdte) over(partition by tcode, icode, partyid,
                                   territory) max_effdte
            from title
           where recordcode = 1)
where effdte = max_effdte ) tp
where t.ICODE = tp.ICODE
group by t.tcode, , t.FSTPERFYEARQTR
having sum(tp.percentage) < 199
Followup:
from ( (selec
       ^^ remove
March 15, 2004 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.
Rating: 5
March 15, 2004
Reviewer: A reader
will your presentation from this years hotsos conference be available for
download
thanks
Followup:
it is available on the hotsos website
Rating: 5
March 15, 2004
Reviewer: Mirjana from Barcelona
Hi Tom,

Can we have a link to download your presentation?
I can't find it on the hotsos site...

Thank you very much!
Followup:
The "techie" presentation is the same as I gave at a DBA sig:

see
http://asktom.oracle.com/~tkyte/
and look for "Slides presented in Cincinnati on Feb 6th, 2004 (dba sig)"

for the "keynote" one -- I'm going to hold onto that one until I've given it in
Georgia next week.  It has a "quiz" in it and the punchlines would be given
away.  If you were a registered hotsos attendee, you can download it from their
site.
March 15, 2004 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.
Review & Followup

Rating: 4
Problem with FoxPro logical fields in generic connectivity March 15, 2004
Reviewer: Amid from Russia
Sir,
When use generic connectivity from odbc to foxpro in Data Dictionary
Translations not containing information about FoxPro logical fields, however
this fields containing in Oracle ODBC Test answer as numeric field.
How i can have access to this fields without editing data dictionary tables.
thanks in advance
Followup:
please contact support for that one -- i don't really have any sort of access to
foxpro (and the answer is most certainly NOT editing data dictionary tables!)
Rating: 3
OK March 15, 2004
Reviewer: Kumar from Pune,India
Dear Tom,
Do you know the names of programming languages used by the
following relational databases?
 1)Ingress
 2)Informix
 3)Sybase
 4)Mysql
 5)Borland dBASE,Paradox
 6)ADABAS
Please let me know if you know.Any reader's input is appreciated
Followup:
they all are accessible by the programming language C.

so "C" is an answer to 1..6
Rating: 3
OK March 15, 2004
Reviewer: Kumar from Pune,India
Dear Sir,
Not what you mean.I mean "Since we use PL/SQL for Oracle as
a procedural language,I wanted to know the procedural languages for the above
listed RDBMSs."
Please do reply.
Followup:
2) spl or i4gl
3) transact sql (tsql)
5) dbase is in fact the language as is paradox
6) natural
March 15, 2004 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???
Rating: 3
OK March 15, 2004
Reviewer: Philip from AR,USA
Dear Tom,
Any simple examples you have for *anti joins and semi joins"?.Please do reply.
Thanks in advance
Bye!
Followup:
I try to explain all of the methods in more detail in my book "Effective Oracle
by Design" if you are interested but...

anti-join = not in for example.

ops$tkyte@ORA9IR2> create table t1 as select * from all_objects where rownum <=
10000;

Table created.

ops$tkyte@ORA9IR2> create table t2 as select * from all_objects where rownum <=
9950;

Table created.

ops$tkyte@ORA9IR2> create index t2_idx on t2(object_id);

Index created.
ops$tkyte@ORA9IR2> analyze table t1 compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns;

Table analyzed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> analyze table t2 compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns;

Table analyzed.

select count(*)
  from t1 cbo
 where object_id not in ( select object_id from t2 where object_id is not 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.08       0.10          0        165          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.08       0.10          0        165          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 105

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=165 r=0 w=0 time=100438 us)
     50   HASH JOIN ANTI (cr=165 r=0 w=0 time=100399 us)
  10000    TABLE ACCESS FULL T1 (cr=138 r=0 w=0 time=7977 us)
   9950    INDEX FAST FULL SCAN T2_IDX (cr=27 r=0 w=0 time=7123 us)(object id
35456)

It "anti-joins" in a manner similar to this 'manual' method

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.07       0.08          0        165          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.07       0.08          0        165          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 105

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=165 r=0 w=0 time=82849 us)
     50   FILTER  (cr=165 r=0 w=0 time=82807 us)
  10000    HASH JOIN OUTER (cr=165 r=0 w=0 time=76006 us)
  10000     TABLE ACCESS FULL OBJ#(35454) (cr=138 r=0 w=0 time=8070 us)
   9950     INDEX FAST FULL SCAN OBJ#(35456) (cr=27 r=0 w=0 time=7270 us)(object
id 35456)

Semi-join = IN as an example.  Here it "partially" joins to the set.  In this
example, it is not known that object_id in T2 is unique so it "semi joins" t1 to
t2 -- joining to at most ONE row in t2 (if it just joined, it would affect the
answer -- making it wrong)

  1* select count(*) from t1 where object_id in ( select object_id from t2 )
ops$tkyte@ORA9IR2> /

  COUNT(*)
----------
      9950

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=23 Card=1 Bytes=17)
   1    0   SORT (AGGREGATE)
   2    1     HASH JOIN (SEMI) (Cost=23 Card=10000 Bytes=170000)
   3    2       TABLE ACCESS (FULL) OF 'T1' (Cost=16 Card=10000 Bytes=130000)
   4    2       INDEX (FAST FULL SCAN) OF 'T2_IDX' (NON-UNIQUE) (Cost=4
Card=9950 Bytes=39800)
March 15, 2004 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.
Rating: 5
IOUG 2004 Website doesn't show you as a speaker March 15, 2004
Reviewer: Robert from Memphis, USA.
Tom,

I just saw your announcement that you will be at IOUG 2004.
Great news!
...but when I searched the daily schedules for 'Kyte' you were not listed !?

Thanks,

Robert.
Followup:
They are slotting my times in - just talked to them this morning.  It'll be
updated when we get that nailed down.  It'll be on monday or tuesday.
March 15, 2004 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).
Rating: 5
Generate insert script for data January 13, 2004
Reviewer: A reader
Hi Tom,

Assume all table columns are simple scalar type, such as varchar2, number, and
date. Is there a script that I can use to generate the SQL insert script for all
data?

For example, if a table contains:

TAB1
COL1  COL2  COL3
   1    AB  13-JAN-04

Then I want to generate a insert statement for it:

INSERT INTO TAB1 (COL1, COL2, COL3)
VALUES (1, 'AB', TO_DATE('13-JAN-04', 'DD-MON-YY');

Thanks.
Followup:
you'll have to write it yourself as I'm 1000% against such an idea for a whole
lot of reasons.

if you'd like a comma delmited file that tools like sqlldr or bcp for sqlserver
or whatever can use -- i've got those.  insert statements -- nope.
Rating: 4
1000% against! January 14, 2004
Reviewer: Paul from UK
I guess that's "very much against" then? ;-)

I'm curious about this because I've written a script to generate insert
statements just was requested and I use it a lot (almost exclusively when
migrating data from development/test to production, where the implementation
procedures in my company specify that a script is required).

Could you let me know what are the main objections you have, please?
Followup:
(it was not a typo, 1000% is what I meant....)

bind variables...............

parsing.............

sqlldr avoids both issues...
Rating: 4
INSERTS via an anonymous PL/SQL block January 14, 2004
Reviewer: Gary from Sydney, Aus
Presumably a script which generates a file like :

DECLARE
  procedure INS (p_col1 IN VARCHAR2, p_col2 IN ...) IS
  BEGIN
    INSERT INTO table_a (col1, col2...)
    VALUES (p_col1,
           to_date(p_col2,'DD-MON-YYYY HH24:MI:SS'... );
  END;
BEGIN
  INS('ABC','11-MAR-2004 12:13:14',...);
  INS('XYZ','11-MAR-2004 12:13:14',...);
  ...
END;

would be an improvement over a series of INSERTs.
I've used something similar to this lots of times (when we are talking about
dozens of rows rather than thousands/millions).
It's also easier to add exception handling (constraint violations) or if you
need to merge data
Followup:
NO NO NO NO....

no BIND variables!!!!!!!!!!!!!!
Rating: 4
1000% against! January 15, 2004
Reviewer: Paul from UK
I didn't think it _was_ a typo!  It just goes against my mathematical
upbringing!  It reminds me of the football (soccer) cliche we have here in
England when the player says the team "tried 110%".  Hmmm ...

Anyway, personally I would be 95% against using this method (obviously I agree
100% with your comments on bind variable and parsing).  I use my little script
when transferring tiny (< 200 rows) amounts of lookup data from development to
production when the users/managers/dba/whoever want to check the data before
implementation.
Followup:
I use this:

[tkyte@localhost tkyte]$ sqlldr_exp scott/tiger dept
LOAD DATA
INFILE *
INTO TABLE dept
REPLACE
FIELDS TERMINATED BY '|'
(
deptno
,dname
,loc
)
BEGINDATA
10|Accounting|NEW YORK
20|RESEARCH|DALLAS
30|SALES|CHICAGO
40|OPERATIONS|BOSTON

(with redirection of course).  see
http://asktom.oracle.com/~tkyte/flat/index.html
if you are interested..... (and in 10g, it'll look like this!)

scott@ORA10G> !ls -l /tmp/dept_unload.dat
ls: /tmp/dept_unload.dat: No such file or directory

scott@ORA10G>
scott@ORA10G> create table dept_unload
  2  organization external
  3  ( type oracle_datapump
  4    default directory TMP
  5    location( 'dept_unload.dat' )
  6  )
  7  as
  8  select * from dept
  9  /

Table created.

scott@ORA10G>
scott@ORA10G> !ls -l /tmp/dept_unload.dat
-rw-r--r--    1 ora10g   ora10g       1520 Jan 15 14:45 /tmp/dept_unload.dat
Rating: 5
Export table procedure - performance January 22, 2004
Reviewer: A reader
Hi Tom,

I created a procedure for exporting the data of any simple table into a .sql
script. It works fine, but seems a little slow. Is there anything you can tell
that can be easily tuned? Thanks.

create or replace procedure export_table (
   p_table varchar2,
   p_file utl_file.file_type
)
as
   no_such_table EXCEPTION;
   PRAGMA EXCEPTION_INIT(no_such_table, -942);
   l_col_list varchar2(1000);
   l_val_list varchar2(32767);
   l_rc1 sys_refcursor;
   l_rc2 sys_refcursor;
   l_rowid ROWID;
   l_value varchar2(2000);
   l_col_count pls_integer;
begin
   select stringAgg(column_name), count(column_name)
   into l_col_list, l_col_count
   from user_tab_columns
   where table_name = p_table
   order by column_id;

   open l_rc1 for 'select rowid rrowid from ' || p_table;

   utl_file.put_line(p_file, '-- Insert data into ' || p_table);
   loop

      fetch l_rc1 into l_rowid;
      exit when l_rc1%notfound;

      utl_file.put(p_file, 'INSERT INTO ' || p_table || '(' || l_col_list || ')'
|| chr(10));
      utl_file.put(p_file, 'VALUES(');

      for d in (select * from user_tab_columns where table_name = p_table order
by column_id) loop
         open l_rc2 for 'select ' || d.column_name || ' val from ' || p_table ||
' where rowid = :1 ' using l_rowid;
         loop
            fetch l_rc2 into l_value;
            exit when l_rc2%notfound;
            if l_value is null then
               utl_file.put(p_file, 'NULL');
            else
            case d.data_type
            when 'NUMBER' then
               utl_file.put(p_file, l_value);
            when 'CHAR' then
               utl_file.put(p_file, '''' || replace(l_value, '''', '''''') ||
'''');
            when 'VARCHAR2' then
               utl_file.put(p_file, '''' || replace(l_value, '''', '''''') ||
'''');
            when 'LONG' then
               utl_file.put(p_file, '''' || replace(l_value, '''', '''''') ||
'''');
            when 'LONG RAW' then
               utl_file.put(p_file, '''' || l_value || '''');
            when 'ROWID' then
               utl_file.put(p_file, '''' || l_value || '''');
            when 'DATE' then
               utl_file.put(p_file, '''' || l_value || '''');
            else
               utl_file.put(p_file, '''' || l_value || '''');
            end case;
            end if;
            if d.column_id < l_col_count then
              utl_file.put(p_file, ',');
            else
              utl_file.put(p_file, ')'||CHR(10)||'go'||CHR(10));
            end if;
         end loop;
         utl_file.fflush(p_file);
         close l_rc2;
      end loop;
   end loop;
   close l_rc1;
exception
   when no_such_table then
      if l_rc1%isopen then close l_rc1; end if;
      if l_rc2%isopen then close l_rc2; end if;
   when others then
      if l_rc1%isopen then close l_rc1; end if;
      if l_rc2%isopen then close l_rc2; end if;
      raise;
end;
/
Followup:
ugh, why why why would you build insert statements???????

just unload to a flat file:
http://asktom.oracle.com/~tkyte/flat/index.html
no inserts, just no inserts.
Rating: 5
Timestamps? February 10, 2004
Reviewer: A reader
It seems that SQL*Plus copy doesnt support timestamps:

--copy command of table with timestamp datatypes.

Array fetch/bind size is 15. (arraysize is
Will commit when done. (copycommit is 0)
Maximum long size is 80. (long is 80)

CPY0012: Object datatypes cannot be copied

SQL>
Rating: 3
Remote LONGs/LOBs .. not so easy February 25, 2004
Reviewer: Paul Gallagher from Singapore
Mikito Harakiri posted that the elegant "insert into t2 select * from
t1@remote_db_link;" doesn't work with LOBs, but you responded with a "proof" to
blow away this objection:

scott@ORA815.US.ORACLE.COM> insert into t select x, y from t@ora8i.world;

But I think your "proof" is deceptive. The 9i SQL Reference A96540-01 is pretty
explicit about the limitations on remote LONGs and LOBs, and same applies in
10.1. As I understand it, a single SQL statement cannot contain multiple
distributed references to LONGs/LOBs. i.e. "insert into t select x, y from
t@ora8i.world;" will NOT work if this is between 2 remote systems (you will get
an "ORA-00997: illegal use of LONG datatype" or similar error)

I suspect "ORA815.US.ORACLE.COM" and "ora8i.world" are not truely "remote"? In
which, this statement will work;-)

These restrictions on LONGs and LOBs in distributed scenarios have gotta go!
When will they be treated like other built-in types? As a db user I don't want
or need to care about internal issues of representation etc .. just that "if I
can do it with an INT or VARCHAR, why not a LONG/LOB?". Increasingly we are
dealing with large data objects - images, XML documents, video etc. But at the
same time we are also getting more "distributed" - and these systems are not
necessary all Oracle (may be an HS dblink). Already I see many people "solving"
this problem outside the database - writing custom import/export/loader programs
to deal with "non-simple" data types and structures etc

Got any insights on when we might see these limitations fall, or ways to work
around them elegantly?
Followup:
they were two different databases -- I was logged into 815, I was selecting from
8i (another database, another machine).

they were "truly remote".  Not sure what you mean there.  They were two separate
instances.

If you mean:

I want to log into A and copy LOB data from B to C, then you are correct, you
cannot insert into t@b select lob from t@c however -- i would also say that you
wouldn't really want to do that -- you really want to pull to A the lob data
from B to squirt it up to C?

But anyway, yes, that limit is there -- but the "cannot use them at all" is not
there.
Rating: 3
RE: Remote LONGs/LOBs .. not so easy February 26, 2004
Reviewer: Paul from Singapore
Thanks for the extra input, but no the "A to do B to C LOB transfer" is not what
I had in mind. Actually, it is the issue of HS remote connections and the use of
LONG types that is my immediate concern.

I've tabulated my findings below.
(o) "ora-ora DBLINK" means an Oracle to Oracle database link
(o) "ora-ora via HSODBC" means an Oracle to Oracle database link via
Heterogeneous Services and the Oracle ODBC driver
(o) the "select" test is a simple select statement on the remote table object
(o) the "insert/select" test is a single sql command cobiming local and remote
objects like "insert into t0 select a,b from t0@remote1;"

Looking at the findings, a clear conclusion is that its the LONGs that are the
problem in a remote connection scenario. This is exacerbated by the fact that
when using Heterogenous Services most large object types are translated as LONGs
not LOBs. So from that point on you are pretty much stuffed.

        +--------------------+---------------------+
        | ora-ora DBLINK     | ora-ora via HSODBC  |
        +------+-------------+-------+-------------+
Type    |select|insert/select|select |insert/select|
--------+------+-------------+-------+-------------+
int etc | ok   | ok          | ok    | ok          |
--------+------+-------------+-------+-------------+
LOBs    | ok** | ok          | ok*** | ORA-997*    |
--------+------+-------------+-------+-------------+
LONG    | ok   | ORA-997*    | ok*** | ORA-997*    |
--------+------+-------------+-------+-------------+
* ORA-00997: illegal use of LONG datatype
** using dbms_lob procedures can result in error ORA-22992: cannot use LOB
locators selected from remote tables
*** note that LOBs are translated to LONG/LONG RAW via HS ODBC and you are very
limited in what you can do before running into either
ORA-22992: cannot use LOB locators selected from remote tables,
ORA-02070: database REMOTE2 does not support operator PLSFUN in this context, or
ORA-00997: illegal use of LONG datatype
Followup:
insert as select LONG doesn't even work on a single instance, never has.

ops$tkyte@ORA920PC> create table t1 ( x long );
Table created.

ops$tkyte@ORA920PC> create table t2 ( x long );
Table created.

ops$tkyte@ORA920PC> insert into t1 values ( 'hello world' );
1 row created.

ops$tkyte@ORA920PC> insert into t2 select * from t1;
insert into t2 select * from t1
                      *
ERROR at line 1:
ORA-00997: illegal use of LONG datatype
Rating: 5
March 15, 2004
Reviewer: A reader
Tom,

We are planning to move our couple of databases (9.2.0.4) from Unix(Sol) to
Linux(Red Hat).

Database sizes are 800Gb and 1.5TB.

Since the OS are different cannot use TTS,what do you suggest would be the best
approach to move data.

Thanks.
Followup:
any possibility of 10g in the near future?  then you can use TTS to move the
data.  Or at least high speed data dumps (new export/import processes)

Else, you'll be doing a bit of manual work to migrate - typically using table or
schema level exports in parallel and likewise on the back end to reload with
import and doing indexes "by hand" in parallel nologging and adding constraints
with enable/rely (to make them go on "real fast" since you know the data is
clean)
March 15, 2004 -- Thanks for the question regarding "Rowid or Primary Key", version 816
You Asked
Hello Sir,
Environment : 3 tier ,Oracle 816,IIS,ASP.
I have to Implement Concurrency Checks for which I am using Optimistic Locking
Strategy.
Q1)When the user Queries the record in edit mode, Do i supply Rowid or the
Primary key. This is then re-queried by me to check If the record has modified.
What is more efficient.
Q2) Can I store the Rowid in a varchar variable. What is the max length of
Rowid. Because ASP does not have any rowid datatype. As the Front end has to
store this value to be returned to me later when I check for record status
change, i was thinking of letting the front end store it as as varchar for which
i need to know the max length of rowid.Or even can i still accept that front end
varchar type as a rowid in my oracle procedure :input parameter.
Q3)To check record status change what is a better approach. Currently i use
AUDIT columns like update_date in all my tables. This gives concurreny check for
up to a second.
Can using a sequence number be a much better option in place of update date.
Or use dbms_utility.get_time to get and store the elapsed time.
Q4)How can i implement this fully thru triggers so that even if someone is using
sql plus he should be stopped. I think having this logic in Packages developed
for the application will not prevent other tools.What are your suggestions. My
weak ideas for this is have a package variable defined to accept the value of
update_date from the fornt end.
But how will the before update trigger know what is the row in question. I am
somewhat weak in Triggers.Any examples or directions will greatly help

Thank U.
and we said...
If you are interested, I have a large section on this in my book (amongst lots
of other stuff)

q1) rowid is generally faster, it is the address of the row.  Just make sure to
"escape it" properly -- it can have + signs in it (just like you escape all text
fields to replace things like &, <, > and so on)

q2) yes, 18 characters.  there is rowidtochar and chartorowid to do the explict
conversions.

q3) or to broaden your choices:

- keep the old values in hidden fields on your form, the new values in text
fields the user will use.  when you goto update, do the udpate as:

   update T
      set c1 = new_c1, c2 = new_c2 ....
    where c1 = old_c1 and c2 = old_c2 and .... AND rowid = old_rowid;

   if ( sql%rowcount = 0 ) then
           error, someone changed your record
   else
           success
   end if

that way, your model doesn't have to reflect your concurrency scheme.

- use checksums (there is an owa_opt_lock package you can use to do this.
Again, you don't have to have your model reflect your scheme

I personally use OLD values in hidden fields -- that way if one screen has
fields c1, c2, c3 and another has c4, c5, c6 -- they can each update the same
record without stomping on eachother.  A row level flag would prevent them from
updating the same row.

q4) you cannot.  sqlplus doesn't need it.  If they select it out with the
intention of updating it -- they should use a select for update (as forms does,
as other CLIENT SERVER tools should).  You see -- in forms -- there is nothing
saying the user ever selected the data out in the first place!  I would let the
various tools do concurrency controls as best fits them.  client server =
pessimistic locking, web (stateless) = optimistics, web (stateful) = pessimistic
again.

I would avoid a trigger to implement concurrency controls.  it'll give you a
false sense of security and just add unnecessary overhead.
Review & Followup

Rating: 4
Clarifications June 03, 2002
Reviewer: A reader
Thanx for your great Answers.
Regarding
q1) escape it properly -- it can have + signs in it (just like you escape all
text
fields to replace things like &, <, > and so on)

:--Is the + sign i only need to escape or there are other unknowns also.
Should i use the escape command we usually use in the like operator.
Will escaping not create problems when i re- query for that particular rowid,
the front will not return me the original rowid ( it will be rowid minus any +
sign).
Do I need convert it to char using rowidtochar when supplying the front end. It
would make sense using chartorowid when I accept the value back from front-end.

q3) Just a thought : if we use your scheme or the checksum or the audit
update_date method. The moment another user commits changes to some column of
our record which we may not be interested in our update will fail.
With one exception that in your method and the checksum method if another user
updates records to its original value then I can still go ahead with my update.
Correct ?
Followup:
q1) escaping will NOT cause a problem.  It can contain all of the characters in
base64 encoded sets of data. We escaping it so the HTML can deal with it.  When
we get it back, it is "unescaped" for us.  The problem is -- + will be
considered a BLANK in a url encoded string, so you must url encode it when using
it in a URL.  I use this function to URL encode strings before using them in
hrefs and other targets:

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;
/

(look at the asktom home page for example, on there -- today -- i have:

<href="z?p_url=http%3A%2F%2Fwww.wrox.com%2FACON7.asp%3Fart_type%3D2%26id%3D790&p_
cat=BEG_ORA_INTERVIEW&p_company=10">^M
interview </a> with the team behind Beginning Oracle Programming</b>

the action of the link is to run a procedure called Z and pass it a URL.  Well,
the URL can contain all kinds of stuff that would break a URL so we have to
encode it.....

You do not *need* to rowidtochar/chartorowid but it sure does make sense.  I
like to avoid implicit conversions where ever possible.  It is a little more
typing but it'll make it obvious that you are doing it.

q3) I think that is correct, maybe.

If you use my approach, I keep the old field values of the columns you are
interested in, or checksum just them.  If two sessions update DIFFERENT vertical
slices of the table -- we will not conflict.

Under your approach -- one of them would lose, you do it at the row level
always.
Rating: 4
Locking September 21, 2002
Reviewer: Vikas Sharma from Delhi - India
Hi Tom,
I read somewhere that oracle forms uses optimistic locking. In web form I know
we can store the old values  and perform updates like
update T
      set c1 = new_c1, c2 = new_c2 ....
      where c1 = old_c1 and c2 = old_c2 and .... AND rowid = old_rowid;
As discussed in the review posted under ‘Clarification’ subject. If a user
update to the same values then we go ahead with update.

I want to know how does oracle form controls it? Because it is not possible with
oracle forms. For eg. In oracle form if create a form base on say emp table open
two sessions in one session say S1 update the ename and other session say S2
update the sal for the same row.(both queried at same time for a same row with
delayed locking mode). S1 issues commit. Now when S2 tries to commit it will not
allow to commit and ask you to requery the data as it is changed. Even if S2
updates ename with it present database value then also Form will not allow. But
in our case with following update
      update T
      set c1 = new_c1, c2 = new_c2 ....
      where c1 = old_c1 and c2 = old_c2 and .... AND rowid = old_rowid;

the update will be done.

I want to know this, because I am creating a procedure for performing the DML on
a table. This procedure can be called  from any front end to perform the dml. In
DML I don’t have any problem with insert and delete. But with update I am stuck
in to the problem.

After reading you book on the locking and other issues and to prevent the Lost
Update. I am going for optimistic locking. Which will be perfect in the case of
web form where I will store values in hidden field.

But in the case of oracle form where shall I store the old values, as I will
create a block, which will base on the procedure with ref cursor as out
parameter and I need to opt my locking model. Then one way what I can think is
create non database item in the same block which will hold the old values like
for ename I will create a ename_old to stored the old value. But that way I have
to create items for all the items in the block to hold the old_values, then pass
the values from those items to procedure. The other way will be to create a
record group, then pass the values from rg to procedure.

Kindly suggest a better way. And how oracle form does it when a block base on a
table.

Regards,
Vikas Sharma
Followup:
No, forms uses PESSIMISTIC locking (if you have my book -- i go into how these
"optimistic/pessimistic" locking schemes work in detail)

see page 105 for what forms does.

Your scenario described above (S1 and S2) isn't the way forms does it -- S1
would update the ename and the very act of typing over the ename would LOCK the
row.  The other session, S2 would be prevented from changing the salary on
screen at all.

In forms, if you are not using a default block, you should code logic in an
ON-LOCK trigger which forms will call asking you to lock the row.
Rating: 5
Just a small addition to the Oracle 9i users September 21, 2002
Reviewer: PrashanthBS from Singapore
For q1 on oracle 9i you may use the UTL_URL.ESCAPE and UTL_URL.UNESCAPE to
escape special characters when passing as parameters in the URL's. I feel the
UTL packages would perform more efficiently as they would have built it
considering the internet standards. So this eases the maintenance at the
developers end.
Rating: 3
Need more info September 22, 2002
Reviewer: Vikas Sharma from Delhi - India
Hi Tom,

As you say  "S1 would update the ename and the very act of typing over the ename
would LOCK the row.  The other session, S2 would be prevented from changing the
salary on screen at all." This is true in the case of Automatic locking mode or
Immediate locking mode of the block property. As I describe above in my scenario
the mode is set to Delayed. So s2 will not be prevented by changing the salary
at the moment he tries to type something but will be notify when saving the
records. I think when the mode is set to Delayed then form issues the select for
UPDATE, just before Update.

I am sorry, I should have taken your book example for clearing my doubts. Now
let me take that.  On pg 105 take the last point (paragraph), In which you talk
about the preventing the lost updates in case of Pessimistic locking.

When user2 (Ref. your book) attempts to update the telephone field then
application (oracle form) recognize that the address field is changed and
prevent you to update and ask to  re-query. Suppose if user2 also updates the
address field to a value, which is current in the database (set by user1) then
also oracle form prevent you to update and ask you to re-query. So when the
address field is also updated to value same as current in the database then when
application re-query we Should Not get zero row return and should be able to
lock the row before update of telephone field.

So my opinion is that it is Not Just the Value of the address field which form
takes to re-query the data, there is something else also involve when form
re-query to lock.

Please comment.

Thanks,

Regards
Vikas Sharma
Followup:
I've never used delayed mode, seems pointless to me (let them do the work and
THEN fail the transaction later -- why bother?)

turn on sql_trace and see what forms does.
Rating: 4
Thanks for sql_tracing idea September 22, 2002
Reviewer: Vikas Sharma from Delhi - India
Hi tom,

Thanks lot for sql_tracing idea. It just slipped from my mind.

Thanks again.

Vikas Sharma,
Rating: 4
Updating via Package April 01, 2003
Reviewer: Robert Boyle from Ireland
Hi Tom,

So when you say:

"If you use my approach, I keep the old field values of the columns you are
interested in, or checksum just them.  If two sessions update DIFFERENT vertical
slices of the table -- we will not conflict."

Would you pass the old values and the new values to some procedure in a package
and let that check the old values against the current table and raise an
application error to let the web user know that the row had been updated?

Or would you be better just using the checksum for a stored proc?

Cheers
Robert
Followup:
yes.

Either compare column by column OR use a checksum -- it is upto you.  checksum =
computationally expensive.  column by column = more data to transfer.  it is a
tradeoff.

checksum also has small chance of missing a change as a checksum of two entirely
rows could be the same -- very small, but there.
Rating: 5
Thanks! April 02, 2003
Reviewer: A reader
Rating: 5
How to check if old parameters have changed? September 01, 2003
Reviewer: David Piazza from San Mateo, CA
I'm supporting a web application that is using JAVA and websphere, and
connection pooling, so I'll be using optimistic locking.  What's the best way to
check if the old values have changed?  I can use a before update trigger, or I
can have a packaage with procedures for updating.  Would I have one procedure
for each table, or one procedure with code to update the table based on the
table name passed in.  How did you implement this?  Thanks...
Followup:
we do one of two things (we being me and people I work with)

a) save the old values in the form itself.  so, when you post it back to the
database - you have the OLD and NEW values and you can simply:

  update T
     set c1 = :new_c1, c2 = :new_c2, ....
   where primary_key = :PRIMARY_KEY
     and c1 = :old_c1 and c2 = :old_c2 and ......;

if that gets a row -- you win, if not, you lose -- someone else modified it.

b) you compute a checksum (owa_opt_lock is available for this, even if you are
using java and not plsql) or a hash (dbms_obfuscation_toolkit) of the data.
Then, when you come back -- you do the same again (select for update the data,
compute checksum), compare them and update if same.  else you lose.
Rating: 3
Update through application code or through Oracle via a package? September 01, 2003
Reviewer: David Piazza from San Mateo, CA
I understand the two methods from reading all the notes in optimistic locking.
I was wondering what was better, to let the code perform the update, or have the
code call a package and let Oracle do the update.  I thought I read in these
notes that it was recommended to let the database do the update because the
application technology, or vendor may change.  And if it was through a package
how that was done, i.e., calling a specific procedure to update a table, or pass
all the parameters to one procedure and let the code determine which table to
update.

Thanks!!
Followup:
well, you should use static SQL -- so "...all of the parameters to one procedure
and let the code determine which table to update..." is totally out in my book.

There are advantages to wrapping the modifications in a package and not allowing
mods directly from the application.  You can audit very easly.  You can add
checks for security -- very easily (and in one place -- not in every application
over and over).

It is the way I do it -- using the framework that built the asktom site here
(HTML DB), we setup an API package to do all CRUD (create, read, update, delete)
and the framework uses those api calls to perform all of its work.  perfect
separation -- allows us to "re-interface" the application at will -- since the
application is really this API and the interface is just sugar coating on top of
it.
Rating: 5
Application = API September 02, 2003
Reviewer: Tony Andrews from London, UK
> since the application is really this API and the interface is just sugar
coating on top of it.

That's the way I like it.  As far as I am concerned, a robust business
application could be run entirely from the SQL Plus prompt, with all appropriate
functions available, security and integrity applied.

The rest of the application (outside the server) is just there to present a more
user-friendly way of accessing it.  And that part can be thrown away annually
and rebuilt with the latest funky tools, if the business so wishes - without
compromising the data.

Am I being unreasonable?
Followup:
not from where I'm sitting.

you know, it's funny.  the best systems have been "n-tier" from day one.  It is
not a physical thing, it is a design philosophy.  You can do "n-tier" on a
single computer (we called it "green screen").  There was a design philosophy
(we used to call it modular coding) whereby the system would be built in chunks.
 data processing, application logic, user interface.  You would compile and link
edit them together to build a program.

Problem is, it took (takes) rigor to enforce such a thing.  What you generally
end up with is a monolithic thing with data/logic/ui all melded together in one
really big subroutine (i shudder at the size of what some people call a
subroutine).

When that happens, you have this big immutable "rock".  Along comes some new UI
and you are dead -- you cannot re-UI your app cause the UI and all are so
intertwined.  So, you end up screen scraping -- you cannot repurpose your
system.

The web is really a big screen scraper.  Anyone else wonder why 99% of the time
when you order something on saturday -- you don't get an email confirming your
order for real until Monday morning - in the timezone of the company you ordered
from (i do most of my shopping on saturday morning on the web.  I never get the
PO until monday).  The reason I theorize is their new wizz bang systems on the
web have to ultimately be re-keyed into a green screen (or orange screen)
somewhere -- either by hand or via screen scrapers.

If they did it right in the first place, there would be an API to invoke.  This
is the theoretical promise of web services for example.  In the next release of
the database, what will be cool is that stored procedures for example can be
exposed as a web service easily (nothing says web service must be java, in fact
-- most probably should not be).

but the problem with that is -- people will build systems where by they use web
services to "talk to themselves" (eg: they take the concept to illogical
extremes).  Instead of a static subroutine call, or stored procedure invoke --
they'll web service themselves.  That'll be really "fast" :)

The nice thing could be -- if you your API is an efficient stored procedure that
CAN be called as a webservice but does NOT NEED TO BE called that way -- you can
get high speed (direct call) and openess (webservice call)
Rating: 4
Rowid parameter problem January 09, 2004
Reviewer: robert from ct
8.1.7/OAS 4

Tom,
I have this rowid => "AAABjvAADAAAAP+ABu"
I urlencoded it to => "AAABjvAADAAAAP%2BABu"
as shown in the mod_plsql call below:

webpkg.show_tran_note?p_itemid=AAABjvAADAAAAP%2BABu&p_source=v_client_transaction
&p_code=GECAP')

But the receiving procedure kept erroring out with no_data_found and this ---
"AAABjvAADAAAAP ABu" --- is the value it receives.

I tried, rowidtochar/chartorowid - no luck, what's going on ?

Thanks
Followup:
I've never had an issue -- see this url:
http://asktom.oracle.com/pls/ask/ask_tom.test?p_rowid=AAAMcfAALAAAy%2FKAD%2B
the code is just this:

create or replace procedure test( p_rowid in varchar2 )
as
begin
    htp.p( 'Rowid = "' || p_rowid || '"' );
    htp.br;
    for x in ( select * from test_table where rowid = p_rowid )
    loop
        htp.p( 'X = ' || x.x );
        htp.br;
    end loop;

    htp.bold( 'Success' );
end;
/

and it works just fine.
Rating: 5
Rowid after undo on partitioned table March 15, 2004
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom,
I update the partition key column of a row so it moves from one partition to
another and it's rowid changes. If I rollback the update operation, the rowid
also reverts back to what it was before update. How is this achieved?
Thanks
Followup:
well, a rollback puts things "back the way they were".  It undoes block level
changes -- changing things back.  The row would go back where it was supposed to
go (where it came from)
March 15, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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.
Rating: 3
What happens in the Background? Is V$SESSION_LONGOPS useful? January 13, 2004
Reviewer: Olga from Vienna
Hello Tom,

I can't see what happens in V$TRANSACTION (I have no DBA rights) but i could not
imagine that a rollback is the reason for my KILLED session state.

When I do an INSERT /*+APPEND*/ INTO nologging_table FROM big_query I could
watch the following steps in V$SESSION_LONGOPS:

1. Table Scan
2. Hash Join
3. Sort Merge
4. Sort Output

Ok, I cancel my session and let the DBA kill the session. When I cancel it at
the "Table Scan"-Level the Session will be killed immediate, but when I cancel
it at the "Hash Join"-Level then the session runs further (TIME_REMAINING in
V$SESSION_LONGOPS goes down) in the background and my session is marked as
KILLED for a long time.

What happens there?

Many thanks for your great site!
Followup:
I'll guess (your dba should be able to verify this -- watching used_ublk in
v$transaction)

has used_ublk started going up when you get to the hash -- perhaps you have
started inserting during that phase and we are rolling back modifications to an
index.
Rating: 3
Kill a CREATE AS SELECT March 15, 2004
Reviewer: Olga from Vienna
Hi Tom,

it's me again. I have a running CREATE MATERIALIZED VIEW-Statement in a SQL*Plus
session. Because I forgot a join condition I canceled the SQL*Plus Session by
closing the Window and then I killed the session. But the session is only marked
as "KILLED" and I can see a enormous running
Hash-Operation in v$session_longops.

What "cleanup" happens there in the background? I thought I could kill a running
CTAS or Create Matview-Statement immediately because it would not generate
rollback data.

Bye,

Olga
Followup:
by closing the window, the server has no idea the connection is broken, that
there is no client (windows tcp/ip doesn't tell the server "i've gone away")

It'll run till the call completes and then undo what it did.  In the future, a
simple "ctl-c" is what you want.  But now, you can ask the DBA to help you out
and kill the process.
March 15, 2004 Tarry -- Thanks for the question regarding "Your new book : Expert one-on-one performance by design", version 9.2.0
You Asked
Tom ,
I've got a few questions about you new book.
1.How is your new book different than the older one?
After reading the drafts I kind of figure(don't get me wrong here) might be
getting a little repetitive there(wrt your older book--although I'm looking
forward to reading it).

2. How version dependent or independent is it?

Please throw some light here....
and we said...
if you consider bind variables repetitive sure, I mention them a couple of times
;)

Tell you what, visit:
http://asktom.oracle.com/pls/ask/z?p_url=http%3A%2F%2Fwww.expert-oracle.com%2F&p_cat=NEW_BOOK_REVIEW&p_company=10
and give some feedback.  Bear in mind, it is DRAFT, not done the technical
review -- I can assure you the finished product will look a tad different -- but
you'll get the point.

Consider the Expert One on One outline:

Introduction
Setting Up
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
Chapter 8: Import and Export
Chapter 9: Data Loading
Chapter 10: Tuning Strategies and Tools
Chapter 11: Optimizer Plan Stability
Chapter 12: Analytic Functions
Chapter 13: Materialized Views
Chapter 14: Partitioning
Chapter 15: Autonomous Transactions
Chapter 16: Dynamic SQL
Chapter 17: interMedia
Chapter 18: C-Based External Procedures
Chapter 19: Java Stored Procedures
Chapter 20: Using Object Relational Features
Chapter 21: Fine Grained Access Control
Chapter 22: n-Tier Authentication
Chapter 23: Invoker and Definer Rights

and this one:

1 The Right Approach to Building Applications (67pp)
2 Your Performance Toolkit (58 pp)
3 Scalable Oracle Architecture (49 pp)
4 Optimizing Database Processes (32 pp)
5 Effective Administration (24 pp)
6 Getting the Most out of the Cost Based Optimizer (44 pp)
7 Statement Processing
8 Defining an Efficient Schema
9 Writing Efficient SQL
10 Writing Efficient PLSQL
11 So you had an Accident

They are definitely different in size and overall scope.  Whereas the first book
was pretty much "this is how is works", this one is more "this is what I think
you should do".  This one has slightly more "opinion" (backed up by facts of
course).

I actually see them as being quite complimentary.  In fact, electronic copies of
large portions of "Expert One on One Oracle" will be made available as a
companion to this one -- when talking about defining an efficient schema for
example, I needed all of the information in

Chapter 6: Database Tables
Chapter 7: Indexes

from "Expert One on One Oracle".  Those chapters (and others) will be available
to people who get this book (as I say "read that over there", that wouldn't be
fair otherwise).

This book will be repetitive in that I keep saying the same thing over and over
-- it is all about how you use the database, period.  Same sorts of examples,
same sorts of principles.  Many of the examples/cases in the book come right
from this sight or variations thereof.

I think they are very different books -- that is something that scares me a
little here.  Anyone expecting a new "Expert One on One Oracle" will not get
what they are expecting.  I don't teach the architecture, I tell you when to use
a specific architecture (eg: use dedicated server when... use shared server
when...).  I don't teach all about tables, high water marks, pctfree, pctused --
I say use LMTs, do this, do that.

It is very version independent -- sure, some of the structures like IOT's
require 8.0 and up -- but others like hash clusters do not.  Array processing --
thats been there for years, new in plsql though.  Does it contain stuff about
the next release of Oracle (after 9iR2) -- nope, but will it all apply ?  Yup.
Review & Followup

Rating: 5
Allright Chief! March 03, 2003
Reviewer: Tarry from Netherlands
And yes I will give some feedback there.....
Rating: 5
Eagerly waiting for release of your Book March 03, 2003
Reviewer: Vijay Sehgal from India
Hey Tom,
       I read in one of your response to the new coming book that you won't be
shooting for too many pages but I think (most of us coming to your site) won't
disagree with me when I say please do not confine yourself from what is coming
from you, It's really great to have you helping out people and passing on the
knowledge... Great work....

Best Regards,
Vijay.
Rating: 5
This response was definitely "Most Useful" March 03, 2003
Reviewer: Tom Best from PA
You are apparently at least as interested in sharing your expertise with the
Oracle community as you are in making a living at it.
Rating: 5
Regarding New Book. March 03, 2003
Reviewer: Ranajit Ganguli from MI USA
As a DBA I often come across such issues like how to take advantage of big
Storage Area Networks (SAN),how do RAID come into play when our mount points are
distributed over various physical volumes, how to best distribute IO on a SAN
etc.

The above mentioned issues needs to be answered during design and capacity
planning. Are you planning to discuss these issues in the design section of your
new book.

Thanks

Ranjit
Followup:
Nope, not at all.  Getting outside of my area of expertise. In fact, the stuff
is so far removed these days, that well, I don't even really think about it at
the database level as much anymore.  If the hardware guys get it wrong, they
just shuffle it about behind the big cabinet while I'm at lunch.
Rating: 5
You can preorder it at Amazon March 03, 2003
Reviewer: Cristian Perez from Dominican Republic
This morning I saw that you can pre-order it at Amazon. I already have expert
one on one and I'm waiting for this one to come out.
Rating: 5
Good Work Mr. Tom March 03, 2003
Reviewer: A reader
I can only say that. Is there any chance you consider adding a chapter on tips
for Query Tuning. I know there are few books available in market like one from
GUY HARRISON, but if it comes from you , I think that will be something more
than real. Thanks for all your support Tom.
Rating: 5
clarification March 04, 2003
Reviewer: Ram from India
Its a pleasure to read the draft of your forthcoming book.
I am now reading the 3rd chapter and one particular thing is not clear to me.
this is about the disadvantages of Shared Server, more specifically about the
"artificial deadlock" scenario (point no 6).
according to the draft
<quote>
the session holding the lock attempts to commit or rollback it will hang as
there are no more free shared servers.
<unquote>
Q. lets say there are 5 shared servers. 3 are busy with their own work. 4th is
trying to update a row which is already locked by the last and only remaining
5th server. now 5 is the blocker and 4 is the blockee. what happens when 5
commits/rollsback ? why does it need more free servers ?
if the session currently using 5 does not have any more work then 5 is free to
be used again (by any other session). even if not, it can still use 5, isnt it ?
its not clear to me how this is a problem in the context of a deadlock. if its a
question of more sessions then that will come under resource monopoly (point no
7 in the draft).
pls clarify.

not sure whether to post it here or not. (for some strange reason my login
always fails at expert-exchange when i try to post it there. )
Followup:
The artificial deadlock would happen when

o you have N shared servers.
o you have N+1 sessions
o session 1 holds a lock "Z" and goes "idle"
o sessions 2..N+1 each attempt to get lock "Z" and hence all N shared servers
are busy
o session 1 cannot release the lock since there are no shared servers available
Rating: 5
your first book March 04, 2003
Reviewer: mo
Tom:

Just curious, do you have a count of how many copies of your first book were
sold.  is it a best seller yet?
Followup:
JK Rowling doesn't have anything to worry about yet no.
Rating: 5
Best seller March 04, 2003
Reviewer: A reader
Looking for forward to the movie ;)

I think the Tom Kyte Action Figure
will be a popular collectable as well
Rating: 3
Books on RAID March 04, 2003
Reviewer: Tony from INDIA
Tom, can you recommand some books on RAID?
Followup:
nope.
Rating: 5
Books on RAID March 05, 2003
Reviewer: Jim
Tony

Search Tom's site for RAID

I think you will find nearly all you need to know
Rating: 5
Free pdf? March 05, 2003
Reviewer: reader
Hi,Tom ,I read your sample chapter of the book,
in the book ,you said that reader can get free pdf on www.wrox.com (Expert
one-on-one (1861004826)),where is it ?
Followup:
after you buy the book and get the book, you'll be able to use information from
the book to get access to selected chapters (ones that I refer to heavily for
reference) in pdf format.
Rating: 5
Regarding books on RAID March 05, 2003
Reviewer: Mark J. Bobak from Ann Arbor, MI
It may be worth picking up Jonathan Lewis's Practical Oracle
which covers various RAID strategies, 1+0 vs. 0+1, which
is better and why, how bad is RAID5, and all with the
consideration of how Oracle does I/O.
Rating: 5
Thank you! March 06, 2003
Reviewer: Fenng
Free pdf ?sorry ,I'm a reader from China ,and the book is a chinese book,so I
can't get the pdf.
:(
Followup:
But you apparently can read some other languages that are pdf-able?

why isn't chinese pdf-able?
Rating: 5
Thank you ,anyway March 06, 2003
Reviewer: a reader
First,I'am sorry for my poor English.

I mean I can only get the book's smaple chapter,
but can not get the **other** free pdf about the book .

In your new book,you said reader can get the free pdf from wrox.com.Maybe it's a
mistake.
Followup:
What it means is that "selected chapters of expert one on one to which I refer
heavily in the new book will be made available to purchasers of the new book in
pdf format"
Rating: 5
Here March 06, 2003
Reviewer: Fenng
Here:in your new books ,chapter 3,1.1 assumed knowledge,you said :EXPERT
ONE-ON-ONE ORACLE(1861004826):chapter 2 architecture(Available as free pdf on
www.wrox.com)

I download it(chapter 3's pdf) from www.expert-oracle.com.

Thank you!
Followup:
right -- when the book is published -- you would (as an owner of the book) have
access to that pdf.
Rating: 3
I think what Feeng is asking... March 06, 2003
Reviewer: Jeremy Smith from Salt Lake City, UT
If you purchase the new book (either in english or another language) are the
chapters from Expert-One-on-One that you'll be able to reference online going to
be available languages other than english?
Rating: 5
Sorry, March 06, 2003
Reviewer: Fenng
If I purchase the new book (in another language, but not English) ,are the
chapters from Expert-One-on-One that can reference online in  **english**?

I love the great book ,but hate the damned translation.

Thank you ,Tom.
Followup:
Ahh -- the chapters online will be available in english, yes, I'm finding out
whether they will be in other languages as well.
Rating: 3
Please Include PL/SQL best practices March 07, 2003
Reviewer: Tony from India
Hi,
    I went through your new books sample chapters. Please extensively include
PL/SQL best practices as well as Exception handling in web/client server apps as
they are very important.
Followup:
I'm not doing programming style like exception handling (that is a matter of
programming style).

I'm doing the top 10 things I see for plsql (with the "why" and "proof of why"),
namely:

o write as little as you can
o only use packages
o use static sql
o bulk processing
o how to return data to the client
o using %type/rowtype
o properly using authid current user
o how to lookup data correctly
o properly using autonomous transactions
o implicit/explicit cursors

The only coding style thing I have in there is "make the darn procedures fit on
a screen, If I have to page up and down to read a subroutine, it is too long"
Rating: 4
March 07, 2003
Reviewer: A reader
Is Expert one-on-one: Oracle available as online version? If yes, could you
please post the link?
Followup:
sigh...

ok -- here is the scoop.

My next book "expert one on one Oracle performance by design" relies heavily on
the material in "expert one on one Oracle".   Where I put in references to
"expert one on one Oracle" -- we will make that material available online (so
you are not "forced" to buy "expert one on one Oracle" to make sense of this new
book).  That material will be available AFTER the book is published, to those
people that purchase "expert one on one Oracle performance by design" as
reference material.
Rating: 5
will there be an expert one on one oracle, 2nd edition March 07, 2003
Reviewer: mary from dover de
Tom,

Is there going to be a 2nd edition of the 2001 expert one on one book?  I guess
my main question is does everything in the 2001 book apply to 9i too?
Followup:
i would say well over 90% of the material does apply.  there are little nuances
like the job queue process architecture is a little different, UTL_HTTP is so
much better that my reimplementation isn't needed -- but page for page I would
say it all applies and is still accurate for 9i.

There may some day be a 2cnd edition but it will include some yet to be released
version of the database back to 9i and before ;)
Rating: 4
Regarding - Defining an Efficient Schema March 07, 2003
Reviewer: A reader
Tom,
What are you going to cover in this topic? Does it also include your opinion
about designing for performance features for data warehousing applications? if
not, it will be nice to include them, as performance issues in data warehousing
applications sometimes becomes visible after the growth of data (which can be
sometimes after few months/years), and by that time it is too late to change the
design.
Thanks
Followup:
well, I would beg to differ -- it is never too late to change the design.  If
something ain't broke, don't fix it. Conversely - if something ain't working,
what are you going to do?  Just keep dumping money into it (money for the dba's,
developers and end users that use it) throwing it away or -- gasp -- do you fix
it.

If you design in a database centric approach, with lots of centralized controls,
you'll actually find that evolving a schema is workable.

The outline for efficient schemas is:

Why the schema matters
Let the database do your work (Ref Integrity)
If you are only using a heap..
    clusters
    single table hash clusters
    IOT
    External tables
If you are only using B*Trees
    FBI's
    Domain Indexes
Using the correct datatype
Compression
Optimize to your most frequently asked questions
Rating: 5
ashraf March 09, 2003
Reviewer: ashraf from kuwait
i belive that any developer or DBA must get this book my mind is change in
understanding lock after i read tom expert book
Rating: 4
Regarding - Defining an Efficient Schema March 09, 2003
Reviewer: A reader
Tom,
Thanks for your views. Actually, I was referring to your 'expert one-on-one'
page 432
>>>>
Design for performance
A database system is not something that can be approached with the 'build it now
and tune it later'......
It must be designed this way from day one.
<<<<
in relation to data warehousing applications (DB centric approach) features like
MVs (instead of manually creating summerised tables), partitioning key, bitmap
indexes, loader vs external tables etc. etc.
Thanks
Followup:
Ok, let me extend that:

Design for performance
A database system is not something that can be approached with the 'build it now

and tune it later'......It must be designed this way from day one.  Or you
must be willing to throw out and redesign in order to obtain performance
....

I do talk about partitioning, indexing in general, external tables.  Don't get
too much into MV's (covered them pretty well in the last book)
Rating: 5
Copy of first book. March 09, 2003
Reviewer: Rory Concepcion from Philippines
Hi TOm,

   First of all I would like to congratulate you on your upcoming book. I have
read the drafts of the sample chapters. They were very informative and useful.
I'll await the day that the book comes out. When would it be coming out?

   Do you mean to say that if we purchase this new book, we would have a sort of
access to wrox website and be able to view a copy of your first book? Maybe
we'll be given a password or account in the site to access the online copy of
the first book? If that's the case, wouldn't it be unfair to those who bought
the first book? Coz this time all we need to do is just purchase the 2nd book
and be able to download the first one?
Followup:
Book is out at the very beginning of May.

You would have electronic access to portions of the first book (chapters).  You
will not get the first book in its entirety.  You will only get electronic
copies.

Is it unfair? I hardly think so (me, I wanted to have the entire first book
included as a CD-Rom with this one, but that didn't fly).

The first book has been out for going on 2 years -- there is something to be
said for having the book for the last 2 years.  And to have the hard copy.  You
know -- there isn't much in the first book that you cannot put together yourself
from this site, the concepts guide.  Is that fair?  People bought that which
they could get for free :)
Rating: 5
March 12, 2003
Reviewer: Rory Concepcion from Philippines
Ok... Ok. it's not unfair. Like you said, the first book could have been gotten
for free coz all the information can be found here.... that's a good one..

Congrats again. If the book will be released by May, I hope our bookstores here
would purchase it so that I could buy one. This will be another hit. I loved the
drafts. I learned more from them.

God bless you!
Rating: 3
Publisher going under.. March 14, 2003
Reviewer: robert from PA
you said >> Funny thing about my book My publisher went under...

WROX is going under ?
Isn't "Oracle Press" a publishing unit as well ?
Why didn't you go with it ? Larry wants bigger cut ?
Followup:
WROX is out of business (no more expert one on one Oracle, what is on the shelf
will be the last of them).

I'm looking around now ;)
Rating: 5
Who will publish? March 17, 2003
Reviewer: Rory Concepcion from Philippines
Hi Tom,

    How about ur new book then? If Wrox is out of business then who would
publish it? Well, if you'd like you can finish the whole book, post a link here
to it's electronic copy... then it's no sweat for all of us. hehehe. Just
kidding. I know that would be impossible. But if you'd like to bite my joke then
good for all of your fans.

    Have a good day.
Followup:
I'm shopping around for a publisher now.  The book will obviously be quite
delayed.  I was relying on the fact that Expert One On One Oracle existed and
would be in electronic form for people who bought this book.  So, there are
sections that say "for all of the details refer to chapter such and such".  As
that won't be possible -- Expert One on One Oracle no longer "exists" (no more
printed copies will be made) and I cannot as yet transfer the digital rights....
 It'll take some re-working.
Rating: 3
your survey... March 18, 2003
Reviewer: Robert from PA
tom, you asked
> What publishing company are you comfortable enough with to >be able to
purchase a book from them without worrying >about the quality of the content?

And I said:
Personally Im not sure how to give a reasonable answer...
for I thought "quality of the content" has to do with the author, No ? what does
the publisher have anything to do with this important aspect ? I mean who's to
stop you as an tech. author to put out hard-to-understand, bad/wrong info (like
a WHOLE lot of them out there)
I'd think they have to do with fair pricing (textbook publishers are criminals
!), quality of layout, print, paper...
Followup:
I've changed the question to

"Tell me the name of the publishing company you most respect technically, the
one that you would trust to deliver you high quality material worth the price
you paid every time.  One you might buy a book by an author you never heard of
-- based on the company they keep"

What I'm trying to find out is -- who do you all really like, really respect as
a publisher...
Rating: 4
One would think... March 18, 2003
Reviewer: Jer Smith from Salt Lake City, UT
That one of the assets that any liquidator would be able to squeeze out of WROX
would be the rights to those books they've published that have actually made a
profit.  Maybe we'll get lucky and a decent company will snatch it up.

I feel more sorry for some pretty great books WROX has just published that
haven't had a chance to really get "out there" yet.  There have been quite a few
in the last six months.  In all liklihood those authors (and us!) are basically
SOL.
Followup:
if they are not sold, wrox's contracts appear to revert the rights back to us
(man, never thought I'd have to get a lawyer for something like this).   I'm
actually hoping it doesn't get picked up, so I can choose who gets "2cnd
edition" with me.

Same for those other books -- when they revert back to the authors, they would
have another chance.
Rating: 3
let us know March 18, 2003
Reviewer: A reader
Can you let us know which publisher got the highest votes and which one you
chose ?

Thanks,
Rating: 5
Choose a global publisher March 18, 2003
Reviewer: Logan Palanisamy from Logan Palanisamy, CA USA
Tom,

You are providing a wonderful service to a "worldwide" Oracle audience. When all
the other factors are same, you should choose a publisher who has presence in
all the continents.  (like offices in Boston, San Francisco, New York, Toronto,
Montreal, London, Munich, Paris, Madrid, Cape Town, Sydney, Tokyo, Singapore,
Mexico City, Auckland, New Delhi, Sao Paulo)

That way it is easy for every one to get a copy of your new book, preferably in
a local English edition at a much a cheaper price than the US edition. Remember,
how many people were asking you when they are going to get the 'Expert one on
one' in their country.

WROX doesn't/didn't seem to have any wordwide offices. May be it is a blessing
in disguise. You can choose a better publisher now. Let's hope you get the
digital copy rights in your favor without much hassle.

Best wishes to you from all of us.
Followup:
well, actually WROX was worldwide.  It was released in places from china to
korea, india to france, russia to spain and all over the place.  It just takes
time to propagate and in most cases -- it was a stocking issue, if they just
take the ISBN into a book store, they would get it right away (the bookstore
would gladly get it)

I hear you though...
Rating: 5
We're with you... March 18, 2003
Reviewer: Rory Concepcion from Philippines
I think the last comment is right. Maybe this really is a blessing in disguise
for you or maybe for all of us. I just hope you find the right publisher for
you. I'm sorry if I didn't vote.... dont really know much information on any
publisher. I could give names of our local publishers coz I used to write
children's magazines here. :-)
Anyway, please inform us when you've chosen a publisher. I do hope that
publisher has a wide market here in my side of the world. And I hope your book
gets published soon. We're all very excited to see it.... and I also hope it
comes out cheaper. hehehe. but if not, it's still ok coz it's worth the amount.

    Good luck Tom. You'll always have our support. Coz after all, you've always
supported us in different ways.

    God bless you!
Rating: 5
New publisher March 18, 2003
Reviewer: Alex from New York, NY USA
Tom,

I hope that you will find a new publisher fast and easy. Best wishes and good
luck.

Regards,
Alex
Rating: 5
About Expert One on One March 19, 2003
Reviewer: Ashiq Shamsudeen from Trichy, Tamil Nadu, India
   You mean to say that getting Expert One on One now is impossible ??!! (since
WROX is no more ). It'll be good (for the guys who  doesn't have ) if u get
published with someother publisher or you make a electronic version of it.I
being lucky(b'cos i have your book )

     Regarding  your second book i went some chapters ,its really great .It'll
be great if u provide the book with electronic version. Please make sure book
should've bigger fonts .Becuase if read Expert one on one for about 1
contieously eyes are getting strained.
Rating: 3
Publisher March 19, 2003
Reviewer: Steven Cooper from England
Tom,

Why not try the Publishers Oracle use... Osborne / McGraw Hill...just don't
attach the big price tag like Oracle tend to for their Oracle Press books.

Best wishes
Steven
Followup:
I'm trying to find out who you all "respect", who is the best...

the authors have little (eg: ZERO) control over the cost of a book -- the
publishers pretty much do that.
Rating: 3
Please see to it that your book is available World wide March 19, 2003
Reviewer: Vijay Sehgal from India
Hi Tom,
      As most of them have already said it please see to it that the publisher
you choose for your book makes it available globally so no one is left out,
don't want to be the last few who get the book.

Best Regards,
Vijay.
Rating: 5
Your new book : Expert One-on-One Performance by Design March 19, 2003
Reviewer: Rich from NJ
I second Alex's comments above, best of luck with finding a new publisher. There
were a lot of us who were looking forward to it coming out soon.
Rating: 5
So (OReilly), Tom is (OReilly) looking for (OReilly) a new (OReilly) publisher (OReilly) March 19, 2003
Reviewer: Mark . Bobak from Ann Arbor, MI
Hmm....(OReilly), I wonder (OReilly) who has a great (OReilly)
reputation (OREilly) in the technical market (OReilly.)

If you keep reading this reply, I suspect the name may just
pop into your head, almost like it was subliminal....;-)

Quick question regarding the artifical deadlock in the MTS
case from a while ago:  I get it, I can see how it happens,
now, what can or will Oracle do?  Can it raise an ORA-0060
or ORA-4020?  (Acutally, of those two, I would expect ORA-0060,
but, can/will Oracle do anything, or does the instance
just hang?)

-Mark
Followup:
hang...  sysdba can get in to correct.
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76965/c07procs.htm#4277
...When Oracle detects an artificial deadlock, new shared server processes are
automatically created as needed until the original user submits a request that
releases the locked resources causing the artificial deadlocks. If the maximum
number of shared server processes (as specified by the MTS_MAX_SERVERS
parameter) have been started, the database administrator must manually resolve
the deadlock by disconnecting a user. This releases a shared server process,
resolving the artificial deadlock....
Rating: 4
My 2.5 cents... March 19, 2003
Reviewer: Kashif from Houston, TX
Hi Tom,

So when should we anticipate the book coming out now? I've been fibbing to my
wife that I'll be finishing up the Expert One on One really soon so that I can
justify my purchase of your new book, but now this setback. Personally, I've
never really cared much about who the publisher is, although now that I've
briefly glanced my set of technical books, specifically the ones that I like,
O'Reilly seems to be the popular choice. Upon further ponderance, I think I like
their Oracle related books most among the choices available. Either that or
Oracle Press (Osborne). I don't think us readers care much about who the
publisher is though, so long as the author is a quality writer, and the subject
is one we're interested in. And by that token, I don't think I personally would
buy a book simply based on who published it. Besides, I don't think you need a
measly publisher to establish your reputation as a first class educator, and
most of us (bar the ones who've been chided occassionally perhaps ;))
subscribing to this forum will second that. Though, being a business student by
training, I can't help but think that the bigger issue is advertising and
marketing of your asktom forum and the new book. I frequently read about how
people just come across this website by accident. Maybe they should come here by
design, i.e. by some carefully targetted marketing. The defense rests now...

Anyway, good luck with your search, and please let us know when we can expect
the new book to come out now.

Kashif
Followup:
Thanks for the feedback -- yes, I plan on publishing it for sure.

I don't know why -- but i never got around to signing my contract with WROX on
this book...  I wanted to be able to control the publication date (which I made
them slip) cause I was feeling rushed.  without a contract -- it was quite easy.
 So, i have that book totally unencumbered, which makes it much easier for me.

Expert One on One Oracle is the one I'm worried about now -- trying to get that
back so I can do it again.

My goal with the "survey" is to find out who everyone respects -- not so much so
you all who already come here buy it -- but so that people who don't know me
from Adam are tempted to pick it up as well.
Rating: 4
List of publishers March 19, 2003
Reviewer: Jer Smith from Salt Lake City
If you don't really pay attention to who publishes a book, here's a good list
for your comparison:
http://www.bookpool.com/.x/zy9hb31yl4/pl

Rating: 3
List of Publishers.... March 19, 2003
Reviewer: robert from PA
"M****soft Press" !!!
Hey ! Jer, you trying to give us a heart attack or something ?

But Springer-Verlag....I have high respect for their textbooks back in college
days. High-quality stuff they got...
Rating: 4
got lucky... i got the first one ;) March 20, 2003
Reviewer: aries from Ph
i have some books from o'reilly and they're good. i voted for it acually.
publish...publish...publissshhh...
;)
Rating: 5
Author rules whatever the publisher March 20, 2003
Reviewer: Tarry from Netherlands
I second kashif there..
As an author, teacher, promoter I find Tom as one of the best oracle
professionals on he planet. Had he written his own pdf's and sold it, I'd buy
it.

I'd go to binding store and get it bound. I've done that for several of my key
oracle pdf's. I've done that with bruce eckel's book..

A lot of these companies ,with the self-serving managements are carrying a lot
of extra baggage and it's only a matter of time that they'll succumb to theirown
undoings.

I only feel bad for all the good and dedicated folks in wrox who will have to
get laid off.
Rating: 5
Any Publisher But Sams March 20, 2003
Reviewer: Jerry from Washington, DC USA
I cringe at the thought of "Teach Yourself Expert One-on-One Performance By
Design in 21 Days"  :)
Rating: 4
Oreilly -- March 20, 2003
Reviewer: Govind from Novota, CA USA
Hi Tom,

  I have been buying technical books as far back as I can recollect.Honestly I
do not make my decision based on the publisher, however the Font and the  paper
quality would definetly be a choosing factor,they tend to make an unconscious
effort to hold the attention while you read. O'Reilly has been my favourite till
now.They have a way to present a "Serious Material" attitude and is very cursive
at the same time. This is strictly me, and I hope everyone  has their own
friendly format.
I wouldn't mind reading a page or two from O'Reilly when I refer some thing but
definetly cannot read from Osborne or Others. Wrox seems to give an impression
of cramminess. Too many words in a page.

Having said all of the above
I intend to complete and comprehend your book back to back before your second
book.
So Its all about the author and not the publisher. O'Reilly or NOT O'Reilly ...

Thanks
Govind.
Rating: 5
Oracle Press or O'Reilly March 20, 2003
Reviewer: Chip from Denver, CO USA
Looking at my Baker's dozen bookshelves filled with computer books, I find
McGraw Hill/Osborne # 1 and O'Reilly #2 overall (with a wide variety of other
publishers).  Personally, I am more inclined to buy an Oracle Press or O'Reilly
book when shopping since their quality is consistently high and I know I can
learn from reading the book.

Possibly a publisher could purchase the digital rights to "Expert One-on-One
Oracle" and include an electronic copy on CD with Tom's new book.

If O'Reilly publishes Tom's new book, I wonder what the cover would feature
(goofy idea: a soaring Kite).

Very thankful and grateful for Tom's numerous examples and explanations :)
Rating: 4
Just an Idea... March 21, 2003
Reviewer: Richard from Oxfordshire, United Kingdom
Tom,

I am sure I speak for many when I say that technical publications would be much
easier to use in electronic format.

By publishing in electronic format, you could bypass publishing houses entirely.

I am completely ignorant of the means of preventing electronic publications
being copyied etc., but perhaps you could create a downloadable .exe which
created the publication. You could incorporate a passsord that expired
periodically, with a replacement password sent to the purchaser by email?

Anyway, I look forward to your publication, whatever format it's in!
Followup:
A .exe?  what is that ;)  is that something you need to run software on legacy
windows machines?
Rating: 2
Re: My Last Posting. March 21, 2003
Reviewer: Richard from Oxfordshire, United Kingdom
OK, point taken! Nonetheless, would you consider electronic publication? It'd
help with errata update, too.

Regards.
Followup:
It would be totally up to the publisher.
Rating: 3
Book Cover... March 21, 2003
Reviewer: robert from PA
>>If O'Reilly publishes Tom's new book, I wonder what the cover would feature
(goofy idea: a soaring Kite).

I rather like the portrait sketch you used to see on Tom's article in Oracle
magazine in the past. That was a really good drawing....kinda like some in MAD
magazine....
Rating: 5
Re: Book Cover March 21, 2003
Reviewer: A reader
As long as it's not the same as the other book. Tom's eyes following me around
the room is not a comfort to me. :-;
Rating: 5
Please don't limit March 21, 2003
Reviewer: Vijay Patel from Vancouver
Hi Tom, Well I know you will try level best to make others to understand your
opinion about database, but here is my point. Please don't limit number of pages
on your book. I would like to read more and more facts justified with reasonable
number of examples. In reality examples make things very clear.

Thanks for your good work.
Rating: 5
Anything close to Wrox quality March 24, 2003
Reviewer: Sam from Dallas, TX
Are you sure Wrox is going out of business? Amazon is still taking pre-orders of
your book.

For your new publisher, I don't have any preference. Personally, I love tech
books from Wrox. Unique cover page, proper font styles/sizes (one font style in
description part and other style in coding parts), page layout, paper quality
make my reading easier. If you have a full control over those book attributes,
please remain it the same. Anyway, I like this cover page
http://www.wrox.com/news/903.htm
If you can make it by May 14, it'll be great. I have to go back Thailand for
good on that day.

Thanks for your dedication to asktom and Oracle Expert One-on-One.
Followup:
Umm, yes, WROX and all related companies are *gone*, being liquidated.

There is about 0% chance of May for a release date.  I have to

a) find a new publisher
b) finish the book

you see -- i stopped, I have no review team in place, no contract -- just a
bunch of doc files.
Rating: 5
Oracle Press March 24, 2003
Reviewer: David Schwartz from NJ, USA
What about Oracle Press?
Rating: 3
If not May, then when........ March 24, 2003
Reviewer: Tarry from Netherlands
Well Tom,

Responding to what you said.......
--Aren't you being approached by publishers(given the success of the last book)?
--May , ofcourse it won't be, but when then?
--I though you got the book reviewed by jonathan, steve, and others, what does
that got to do with the wrox's demise(if you will)
Followup:
It'll take a while to set up a contact (not too long but May is very close)..

It takes a while to go through their edit and review process...

It takes a while to "fix" the book -- I won't be able to say "using your
electronic copy of Expert one on one Oracle" as liberally as I did in this
book...

Then it takes a while to lay it out...

Then it takes a while to index it...  Proof it.... and so on.

Then it takes a while to print it, ship it, stock it.

It is not as simple as putting a pdf together
Rating: 3
my two cents March 24, 2003
Reviewer: Jiu from Rochester, NY
How about put the chapters from "Expert one on one Oracle" that you want to
reference at the end of the new book as appendix. Then you just replace the
reference to the on-line pdf with appendix number. In this way, you don't need
to rewrite, incorporate, and index them into the actual content of the new book.
Or, if it takes too much paper, put them on a CD as supplement.
Followup:
I don't "own" Expert One on One Oracle (yet).  WROX does, they are selling
themselves (liquidating).  Some company may or may not purchase it.

It is all in big "limbo town".

If we could put them on a CD, we could have put them on the web in pdf as
planned...
Rating: 5
Curlingstone Publishing March 24, 2003
Reviewer: Sam from Dallas, TX
I just went to Barnes and Noble today and found some new database books printed
by a new publisher called Curlingstone Publishing,
http://www.curlingstone.com/
They specialize in printing database books. The one that I browsed was "SQL
Server 2000 Fast Answers for DBAs and Developers". Compared to Wrox's book
side-by-side (regardless of content), this book creates the same feel as Wrox's.
You might wanna check at your local bookstore.
Followup:
he he, no wonder curlingston reminded you of WROX:

    Peer Information Limited
    Active Path Limited
    Friends of Ed Limited
    Wrox Press Limited
        Glasshaus
    Curlingstone
    Tect

    Online journals include:
    ASPToday
    C#Today

all one in the same. hang onto that curlingstone book -- It just became a
collectors edition.
Rating: 3
X Curlingstone.... March 24, 2003
Reviewer: robert from PA
Yuck...so sql server-ish...
Followup:
Oh, are they going out of business too ?
Rating: 5
wait a minute! March 24, 2003
Reviewer: J. from MA
Quote/
"Followup:
Umm, yes, WROX and all related companies are *gone*, being liquidated.

There is about 0% chance of May for a release date.  I have to

a) find a new publisher
b) finish the book

you see -- i stopped,
b1) I have no review team in place,
b2) no contract --
b3) just a bunch of doc files. "
/Quote

I can help with a)
I can't wait for b)
I can do b1) (just like reading right? I won't charge nuttin either:)  )
don't need no stinkin b2 with me brother! I trust you

finally, I've got Word on my windows box, I can read those doc......

HEY!! WAIT A MINUTE!!!
Rating: 5
publishers for the common man March 24, 2003
Reviewer: dharma from CA, USA
Hi Tom,
O'reilly has been in its own class for techno books, well steve adams, steven
fstein authors we know of ..and they seem to do good ;)

well the other choice would be apress books eventho a whole lot of their books
tend to be MS, a few java they are agood publishing house.

cant wait for your new book..
Rating: 5
March 26, 2003
Reviewer: chaim katz from Quebec
I think O'Reilly is the natural choice. Their book Oracle Performance Tuning
came out in 1996! I can't imagine that they're still selling any copies. They
need a performance tuning book and you've got it. All the best.
Rating: 3
Your New Book March 26, 2003
Reviewer: Trevor Williams from Perth, Western Australia
Tom
I hope that you are seriously considering making your new book an e-book. Now
that Wrox is gone. I for one am hanging out for a copy.
Come to think of it, "Implications of Adding Creditcard Facilities to a Website"
may be a useful topic.
t
Rating: 4
Are you covering RAC in your new book ! March 28, 2003
Reviewer: Shankar Govindan from Portland, Oregon
Would like to know if you are covering RAC related stuff in your new book ?

thanks,

shankar
Followup:
a teeny tiny bit -- nothing in depth.
Rating: 5
http://www.expert-oracle.com/ April 08, 2003
Reviewer: Alberto Dell'Era from Milan, Italy
Tom,

has the site
http://www.expert-oracle.com/
"gone under" too ?

If yes, are you planning any replacement ?

Alberto
Followup:
their servers at wrox are just running -- I would assume the first time they
crash, it's over.

Not planning a replacement as yet -- that'll be up to my new publisher.
Rating: 5
Your Book April 09, 2003
Reviewer: A reader
If i had lots of money, i would have opened an international publishing house
just for you. :)
Rating: 5
New Book ... April 10, 2003
Reviewer: ranajit from US
Why not make the performance book available online from oracle's website, like
the technet.If someone is trying to get into Oracle I hope they will start with
oracle's website.

Also you can work with Amazon if they can make it available online. I always
read the reviews there before buying any book even from authors I don't know.

Like someone mentioned above , we can download in pdf , pay you though paypal or
something.
Followup:
I've signed with Oracle Press -- It'll be out this summer later.
Rating: 5
so will it be on tech net ? April 10, 2003
Reviewer: A reader
as you going with oracle press will it be on technet ?

 or do they have any online forum or online books or something ?
Followup:
it'll be an old fashioned book on paper with ink.
Rating: 5
Oracle Press ??? Yuck ! April 10, 2003
Reviewer: A reader
Sorry, but I -- and several people whose opinions and Oracle skills I admire --
have a thing about Oracle Press.

Most books are of low quality and I would far rather look up Oracle online docs
or Tom's site.

I can send you a list of grotesque errors and misconceptions about Oracle they
put down on those... hmmmm "books" -- a lot of those myths you talk about.

Except for a couple of reasonably well-written ones. They are exceptions. I hope
yours is one of them.

Cheers !
Followup:
I can do the same for any set of books tho.  Any set.

What I discovered from the "survey" I took is that people didn't really care,
they looked at the author more than the publisher.  I found McGraw Hill (Oracle
Press) to be the most "stable" out there -- that was a driving factor for me.
Rating: 3
So... April 10, 2003
Reviewer: Kashif from Houston, TX
I guess you can remove/modify the informational message on your homepage which
mentions your publisher went under... Can't wait for the new one to come out.
Thanks for all the work you put in.

Kashif

P.S. What made you choose Oracle Press in the end?
Followup:
stability.  don't want to be in the same boat 2 years from now..
Rating: 5
Congratulations April 10, 2003
Reviewer: A reader
Hello
I'm really happy you found a publisher.
I don't see why not Oracle Press
After all it is not a dress I'm going to wear
It work
As long as the book containts excellant information
as you always gave us
there is nothing wrong with the book.
Keep up the marvelous work
Bless you
Rating: 4
Great News April 10, 2003
Reviewer: Sriram Kumar from India
Hi Tom,

It was great to hear the news that your book will be published thro Oracle
Press.

Keep up the good work.

Tom is Best
Rating: 5
yes this is a great news April 10, 2003
Reviewer: Ajeet
yes -- Tom this is a great news that your book is coming in summer -- should i
say it will come in June this year.
Followup:
hows about august.  I'll be done with my part around the middle of july.
Rating: 5
In India I would blindly books published by Oracle Press April 10, 2003
Reviewer: umesh from bangalore india
Rating: 5
Great News indeed! April 11, 2003
Reviewer: Yogeeraj from Mauritius
You couldn't have found a better publisher!

Everyone is eager to get hold of it asap.
Hopefully, it will be available everywhere in this world asap.

best regards
Rating: 5
It is really a good news! April 11, 2003
Reviewer: A reader
Tom,
The help I got from your site (I am sure others too) and previous book is really
great.
You are an excellent example of real help especially at the time one needs it. I
wish you all the best for your new book, and looking forward to it.
Rating: 3
At last the wait is going to pay off April 11, 2003
Reviewer: Vijay Sehgal from India
Hey Tom,
       Good to hear that finally the book is going to arrive soon, simply was
waiting for that to happen. One request though please see if it can be made
available globally so that we are not the left out ones
Rating: 5
New publisher: Great news. April 11, 2003
Reviewer: Georg B. from Germany
Great news !

Regards,
Georg
Rating: 5
Excellent News !!!!!!!!!!!!!!!! April 11, 2003
Reviewer: Cristian Perez from Dominican Republic
Rating: 4
Great news April 11, 2003
Reviewer: Marcio from Brazil
Hi Tom,
Great news to us.
But, I hope this time the translation to another language like portuguese be
better than last one. Hard to say that in English...
They translation
INSTANCE -> COPY, TABLESPACE -> table's space word by word, and so on. In this
time I'll buy English version.
Congratulation.

Cheers,
Marcio.
Rating: 5
In other countries April 12, 2003
Reviewer: A reader
When will it come in India?
Rating: 5
performance toolkit April 21, 2003
Reviewer: Hrishy from PA
Hi

Tom in expert one to one..i feel the amount of importance given to statspack is
very less..i find myself this has got much better examples of finding problems
with statspack..Was interested in knowing wheather in your new book..would you
show us some case studies wherein you would discover the instance problem with
statspack..

regards
Hrishy
Followup:
yes I do
Rating: 5
topic proposal April 22, 2003
Reviewer: Alberto Dell'Era from Milan, Italy
Tom,

i would suggest to expand on the subject of cursors,
continuing from what it was left on the botton of
page 440 of your book when you said "this statement
will be parsed once per session if in a procedure,
and once per pl/sql block ...".
I think it would be useful to cover things like
implicit pl/sql caching, session_cached_cursors,
parsing of ref cursors and so on.
Those are all things that i later found on your site, but
of course a brief organized dissertation (note the
academic-like world ;-) ) from you may clarify
things further, and it would be VERY useful for
the general audience IMHO.

I don't know whether your new book may be considered
a continuation of Expert One-On-One, so this request
may be inappropriate ... sorry if it is.

BTW: why don't you open a thread or something like that
for requests ? Like the old site www.expert-oracle.com
at defunct Wrox ... perhaps here on asktom.

Congratulations for having found a new home!
Bye
Alberto
Rating: 4
Your new book : Expert one-on-one performance by design April 24, 2003
Reviewer: Reader from NJ,USA
Tom,
It is great news Tom you have finally found a publisher. I back ordered your
book in March 2003.  Little did I know that Wrox is going out of business. You
have been an excellent source of info for me along with Steve Adams. Keep up the
good work
Rating: 5
Great book May 01, 2003
Reviewer: John from Canada
Hi Tom,

Is it avaliable now? Or could you please let me know when it will be released?

Thanks.
Followup:
july 15 is my deadline, say august
Rating: 5
buy this book for this chapter alone (so you had an accident) May 02, 2003
Reviewer: Hrishy from PA
Hi Tom

Was browisng through the detail specification of this book..and was really
excited by the contents of this chapter..I have not seen the book but would
certainly by this book for this chapter..as that chapter seems to be thoughts of
an seasoned professional penned down :-)

But for the first time perhaps i have a complaint :-)..the book is supposed to
have been titled..export one on one performance shouldnt all experts know about
the oracle myths before reading this book..:-D.wish Tom had expanded the chapter
So you had an accident and totally skipped oracle myths .
Rating: 5
why is august taking so long ? June 03, 2003
Reviewer: Alvin from Philippines
why ?

can't we just....

select to_date(sysdate+60) from dual;

? after all it did work on my db....
Rating: 3
when will the book be released? June 04, 2003
Reviewer: Jianhui Zhang from DC
Tom,

I have heard it would be released in early May. But i checked the search engine
and didnt find anything. Has it been postponed?
Followup:
August, the publisher (WROX) went out of business.
Rating: 3
feedback after reading ch01 online June 04, 2003
Reviewer: Jianhui Zhang from DC
Tom,
The ch01 was interesting with lots of real world examples. If possible, you may
change the tone when you say do this, not to do that, so some readers may feel
not so offended. Why i said that is because you mentioned some rules, like

It's a team effort
Read the concepts guide
...

Themselves are great, just the tone in your book may not sound so friendly, like
you are a DBA and DEVELOPER's manager, telling them rule 1, 2, 3... Because i
think writing a book is little bit different than answering questions in this
site, why not make it more friendly and still let the reader get what you want
to say.

As for the technical topics, i 'm an advactor of the concept that it's design
for performance instead of tuning for performance. I had some clients who wanted
the developers to write effecient SQL in order for better performance, but when
i asked how they took the performance into account during the design phase, they
simplely couldnt tell me anything!!! They can have top nouch developers but if
they design the bad system, it's gonna have performance problems. Most of
designers of the real world are business experts instead of the technology
specilists i have seen especially in the home grown projects, then they hire
consultants to do the coding, not a good approach i think.

Personally, i summarize the performance related issues into two catagories,
which are I/O and contensions.

SQL tuning, memory tuning, physical design, etc, all their goals are the same,
to minize the IO, both physically and logically.

Scalibility, concurrency are all about contensions, since database is an open
shared environment.

So better performance = minized i/o + high concurrency

let's say the I/O speed is given(base on your hardware), then:
 the number of transactions(performance of single user) = IO speed / io
consumption per transaction under single user mode.

Total throughput (performance of the overall system) = number of transactions *
concurrency, so if the concurrency is close to linear function of the number of
users, i give this system thumbs up! This system has high scalibility and
concurrency.

This is the formular i use to convince my clients to choose the good approach to
gain the performance, instead of using hit ratios, parameter settings, etc which
are trivial and dont provide you a higher level view of your system.

I havent found any DB related performance problems fall out of these two
catagories. As for the CPU usage, since most of DB applications by nature are
not scientific caculation intensive, it's really hard for some "bad" developers
to write bad programs and algerithm to suck the CPU up before the I/O and
contension problems are raised.

So if you can make it clearly why we need to do this (minimize the IO or
contension) in your new book, i think it will give us a very clear goal and know
why we are doing that.

Regards,
Rating: 4
How about some "Read for Fun" Material Too ? June 04, 2003
Reviewer: Robert from PA
Hey Tom, maybe you should make chapter out of this:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3512484632553
or put in as an apendix.
All technical stuff and no fun make us a dull boy, you know...
Rating: 5
We can wait till Augest June 04, 2003
Reviewer: Ajeet from oh,usa
Hi Tom,
No doubt --you taught us that oracle is not only a database but it is a rational
and science..we can wait till Augest for your book...we will learn many things
from this book again.

Thanks
Ajeet
Rating: 5
expert one-on-one Oracle June 11, 2003
Reviewer: Valter Rogério from Curitiba, Paraná - Brazil
Hi, i buy your book.  It´s a translation for Portuguese from Brazil.  I´m in the
begining and it have very, very mystakes in the codes.  There was a bad
translation, mabe for a dentist or a lawyer.  Finaly, where i can find an errata
?   The Wrox don´t have.   The "ciencia moderna"
http://www.lcm.com.br/
 don´t have too.
Followup:
sorry, wrox no longer exists, there is no "errata site" anymore, there is no
"book" anymore (expert one on one Oracle from wrox, out of print)
Rating: 4
errata June 11, 2003
Reviewer: Dmitry from Russia
Why don't you move/create errata site somewhere here?
Followup:
because I don't have all of the prior errata.... and I'm in the midst of a new
book (out in august).

if the expert one on one gets printed again, there will be an errata site
Rating: 5
Who picked up the right to Expert-One-on-One Oracle? June 11, 2003
Reviewer: Eric Ma from New Jersey, USA
Does APress have the book right?  Will they run reprints?  Or did Oracle Press
buy it from Wrox?  It is such a mistake for Wiley not be buy your book.  Maybe
it is not in their stratigic plan to publish database books.  In my opinion,
half of the 30 or so books they bough from Wrox are not that great and they
missed a few gems.
Followup:
Yes, apress currently does.

Wiley tool MS only titles, nothing else.

We are waiting and seeing what we are going to do with it.
Rating: 5
When it'll be released ????? June 11, 2003
Reviewer: Ashiq Shamsudeen from Trichy, Tamil Nadu, India
Hi Tom,

 When your second book will be released(any dates in near future)  ??
  Another thing It will be great if you give us supplement copy of Expert One On
One(in CD) along with your second book ;)

thanx
Followup:
August for the new book.

Not quite possible for Expert one on one, you see, I don't own it.
Rating: 5
Expert one-on-one insurance program June 11, 2003
Reviewer: Worried from the good 'ol USA
Perhaps we need to all take out insurance policies against our expert
one-on-one!  What if mine is destroyed by fire, flood or other natural (or
unnatural for that matter) calamity?!?!?!?!  Hey this could be a great business
opportunity:  out-of-print tech book insurance.
Rating: 5
Expert one-on-one insurance program June 12, 2003
Reviewer: Kalita from Bromley,UK
Excellent idea :-)
Rating: 1
Copyright of the new book June 12, 2003
Reviewer: Harish from London, England
Tom

Once your new book is published, who will hold the copyright on the material,
you or McGraw/Hill?

Have you considered using the GNU or even the LGPL licence for your new book?
If I remember correctly, Havoc Pennington of GTK fame used the GNU licence for
his GTK programming book.  His book is also available on the web, last time I
checked!

Harish
Followup:
McGraw Hill will.

Sorry, bit old fashioned, you see -- they do a ton of stuff for me that I don't
have to deal with.  It is a business relationship from which we both derive
benefit.
Rating: 5
June 12, 2003
Reviewer: Harish from London, England
Rating: 3
Errata and code (examples) are still available June 12, 2003
Reviewer: tobias from Victoria, Canada

http://www.wrox.com/dynamic/books/errata.aspx?isbn=1861004826
for both (code download via button at the top of the page works)
Followup:
thanks, don't know how long it'll be there...
Rating: 5
Can't access the link to your new book (DRAFT) June 13, 2003
Reviewer: Deepak from London, UK
Can't acess:
http://asktom.oracle.com/pls/ask/z?p_url=http%3A%2F%2Fwww.expert-oracle.com%2F&p_cat=NEW_BOOK_REVIEW&p_company=10
Any other links ? or mirror ? ..Please...
Followup:
company (WROX) went out of business in march.

new company i'm working with (Oracle Press) is setting up a "beta books" site
that will be available soon.
Rating: 5
Tom's New Book June 13, 2003
Reviewer: Paul from UK
Barnes and Noble are already offering 20% off!:
http://search.barnesandnoble.com/booksearch/isbninquiry.asp?ISBN=0072230657
This site says the book's available on August 25 (Amazon says August 28).  Are
the bookshops opening at midnight for this one? ;-)
Rating: 5
Aha..So it's there finally! June 29, 2003
Reviewer: Tarry from Netherlands
Tom,

The book turned out more thicker(828) than expected(I like that).

I see the title also changed.

Hopefully soon it'll be in my bookshelf.

Cheers!
Rating: 5
30% off on amazon.com July 01, 2003
Reviewer: A reader
Tom,
Do you know how many copies of your new book have already been reserved?
Followup:
nope
Rating: 5
Pre-Orders July 01, 2003
Reviewer: A reader
Hmmm. I would have thought you'd hear a little "ka-ching" every time one was
ordered. :-)
Rating: 5
Excellent 2nd Chapter! July 01, 2003
Reviewer: Brian from Phoenix
Just read the preview of the 2nd Chapter, and I thought it was awesome.  Of
course, I don't understand the transition from chapter 2 to chapter 3.

Tom, your writing ability has improved significantly since your last book.

Brian
Followup:
think of the chapters as "books unto themselves"

(and thanks)
Rating: 4
mistake on page 17-18 of chapter 1 July 03, 2003
Reviewer: freek from Belgium
Tom,

I think your transaction sequence on page 17-18 of chapter one is wrong.

If you run it in two sessions, you would get the correct answer.
The mistake is made (i think) in the script with the autonomous transaction.
There your last statement is updating sum_of_salary for department 1, while it
should have been department 2 (where session 1 added the new employee)

greetings

Freek
Followup:
you are correct, I need to move the commit and cannot use the autonomous
transaction.

thanks for reading so close ;)  no one else caught that yet!  the example is
"sound", I was trying to make it easy to see in a single session.  need two
sessions - each does their work AND THEN they commit.

it makes the example more "interesting"
Rating: 5
Is USER a function or pseudo column July 03, 2003
Reviewer: Kalita from Bromley,UK
I remember you saying somewhere in asktom that USER is not a actually a pseudo
column but a function. But you refer to USER as a pseudo column in Chapter 2
page 25 :-) Tried to find out where you said that but could not.
Regards,
Kalita
Followup:
thank you, that'll be corrected
Rating: 5
Mistake found on pp.17-18 July 03, 2003
Reviewer: A reader
Would it be a problem to copy and paste the script that had the error so
everyone can follow?

Thanks,
Arun
Followup:
it is on the website there -- goto the homepage of asktom.oracle.com and click
thru to the site to read the samples online.  I followed up there with the fix.
Rating: 5
need to upload different chapters July 04, 2003
Reviewer: hrishy from PA
Hi Tom

I had a chance to look at some of the chapters of your new book all the three
chapters uploaded there talked about how to build the performnace into the
design..(which i belive is the books theme)but as a DBA/developer i would like
to know about this .but the practical world is all about troubleshooting
performance (and we dont get a chance to be involved in design) after the system
goes into production..it would be great if you could upload the chapter on case
studies with statspack..rather than just design design & design.

regards
Hrishy

P.S:how about a book called best of asktom ;-) where you publish a assorted
questions here as casestudies

regards
Hrishy
Followup:
well, you only have a month to wait to get the whole thing.

And I disagree about your "practical world", that is not my world.  And we'll
only get the world to be in the right place by working against "your world" in
the end.

I don't have a chapter on case studies with statspack.  Everyone is a new and
exciting mystery.  I wail against the "hey make this go faster" throughout.

You did notice the "title" of the book?

Effective Oracle by design

there is a theme to that title.
Rating: 3
Your books July 10, 2003
Reviewer: Erik from Belgium
Hello Tom,

Today I would order your books, but is it true your publisher doesn't excist
anymore  ?

Your books, expert one on one , would they be released by another publischer ?

Kind regards
Followup:
apress is re-releasing it.
http://www.apress.com/book/bookDisplay.html?bID=220

Rating: 4
What about... July 11, 2003
Reviewer: Kashif from Reston, VA
Hi Tom,

What about the "Oracle 9i Java Programming: Solutions for Developers Using
PL/SQL and Java" book (on which you're a technical contributor) and the
"Professional Oracle 8i Application Programming with Java, PL/SQL and XML"; are
they being re-released by apress or someone else?

Kashif
Followup:
they may well be.  I was paid by the page for those -- no royalties -- so I
don't really "follow them" ;)
Rating: 5
Excellent July 11, 2003
Reviewer: Brian
It's good that your book is being re-released.  I picked up a copy (WROX) from
www.bookpool.com when you had the link up.

Anyway, two questions:

1) Have you ever considered writing anything about Oracle
   security?  I haven't seen much out there that addresses
   security around Oracle databases.  And I would imagine
   that folks would love to steal clob and blob space.

2) Will the code be up for your previous book at Apress?

Thanks
Followup:
1) A guy I work with -- David Knox -- is doing such a book with Oracle Press
right now.

2) should be
Rating: 5
no eyes watching! July 11, 2003
Reviewer: J. from MA
(0)(0)
  /\
-____-
 \__/

(had I waited for the Apress release you wouldn't be staring at me about lack of
bind variables all the time)
Rating: 5
July 15, 2003
Reviewer: A reader
I have your Export one-on-one book. I am going to order Effective oracle by
design soon.

Probably it’s a strange question :)
Are you planning to update your Export one_on_one book to cover 10i topics? Or
Are you planning to write any other books?
Followup:
I am planning on finishing Effective oracle by design tonight.... (15-jul-2003)
the last of the tech edits.

after that -- vacation....

beyond that, well, I haven't thought it out yet ;)
Rating: 5
new book July 16, 2003
Reviewer: A reader
so are you done - now that we are on july 16th?;)

Anyways, I am eagerly awaiting the release. Loved your
three sample chapters - your writing ability (which was
anyways excellent) has shown remarkable improvement
since the first book - it is even better plus the wealth
of knowledge you have makes it a cool combination!

Please keep up the good work - always wonder how you
find time to do all this *and* run this awesome site!!

Regards
Menon:)
Rating: 5
July 17, 2003
Reviewer: A reader
Good to know that you have finished Oracle by Design book before your vacation
:)

>>beyond that, well, I haven't thought it out yet ;)

Ok I will ask the same question after your vacation :) :)

Have a wonderful vacation!
Rating: 5
Release the book in India at the same time July 25, 2003
Reviewer: A reader from India
I request you to make sure that you release the book at about the same time its
being released in US.
Thanks
Followup:
when I open "kyte publishing" -- I'll make sure to do that :)

right now, it is all up to McGraw-Hill and their schedules, over which I have
zero control.
Rating: 3
Expert One on One Oracle is still out of stock July 30, 2003
Reviewer: A reader
Hi Tom,

 I was thinking of having this book as my birthday gift and had placed one order
on Amazon.co.uk. But recently they replied back saying the book is out of stock.
Almost three weeks now. Will you please suggest some other site on which the
book is available? I checked on wrox, but couldn't find the title there :(

Regards,
A reader who wants to learn from this book!!!
Followup:
wrox no longer exists really -- they went out of business.

apress bought the book:
http://www.apress.com/book/bookDisplay.html?bID=220
and they will be reprinting it soon.
Rating: 3
Professional Oracle 8i Application Programming.... August 04, 2003
Reviewer: robert from PA
Tom, what chapters did you write in this book ?
"Professional Oracle 8i Application Programming with Java, PL/SQL and XML ".
Your writing in this is after or before
"Expert 1-1" ?
thanks

PS:many of those other authors can use your hair-stylist ;)
Followup:
I wrote just one -- chapter 16

easily identifiable by the trademark sqlplus prompt :)

I always get a good smile when I look at the guy to my right on that cover.
"nice hat"
Rating: 3
this other book... August 04, 2003
Reviewer: robert from PA
>> I wrote just one -- chapter 16

BTW, can I assume that by virtue of the fact of your contribution
to this book that it's got your blessing...like you reviewed it ?

Thanks
Followup:
I'm listed as a technical reviewer, that does not mean i read the entire book --
nor that I necessarily agree with 100% of everything that is in there.  As a
technical reviewer, we simply raise red flags, ask questions, pointer out
errors.

whether they get fixed or not is beyond me.

Also, bear in mind, I was working on my own book and doing my job at Oracle and
doing this at the same time, I reviewed a small section of that book only -- the
core database related stuff (don't even ask me what chapters, i forget)
Rating: 5
out of curiosity August 04, 2003
Reviewer: Dave from UK
just out of interest, what is your Job at Oracle?  Are you purely a manager of
people or do you develop as well.  Also what kind of people are your clients?
Do you develop their applications for them or just advise?

Thanks
Followup:
well,

o I have a team of 12 right now.  specialize in various technologies.  for
example Cameron O'Rourke (
http://devtrends.oracle.com/
) does java architecture.  Sean Dillon does XML
technologies (he did my rss feed for example).  Kimberly Better does
portal/forms/reports/disco.  Another person does high availability/disaster
recovery.  Another ias.  Another capacity planning/mgmt.  Another collab suite
and so on.

o I still write code, build systems.  Not as much as I used to, but still do
that alot.

o I do work with development at HQ from time to time but more in a "this is what
people want" role then in "here is some code" these days.

o I work in Oracle GEH -- Government, Education and Healthcare.  So one day I'm
with NASA, the next with a hospital, then a university and then the Pentagon.
We are about 10% of Oracle's customer base.  I normally provide 411 (info)/ 911
(help!) services for them.  Benchmarking, Root cause analysis (i hate that),
tuning, architecture, program review sort of stuff.
Rating: 2
Good to know about you!!! August 05, 2003
Reviewer: Reader from Manchester, UK
Rating: 5
Tom's Job... August 05, 2003
Reviewer: Tarry from Holland
Tom's job as a VP is excellent in terms of what a techie would want. A more
managerial but still a techie job where you can/would jump in to code. It's
amazing the way he can do this(which is a lot of work when you're solving
problems of other people who call for all over the world) and then also fly all
over the place to the ailing ;) clients...

I think it's more like when you get sick and tired of seeing too many people ,
you can always come back and sit behind asktom.com and talk to your machine, who
politely listens and does all you want, right Tom?

Keep up the excellent job!

Cheers!

Tarry
Rating: 5
August 05, 2003
Reviewer: A reader
even though your team members specialize in specific do they share knowledge and
how
Followup:
yes, of course.  that is their job.

we are in a technical pre-sales organization.  We provide 411/911 help for our
division.
Rating: 5
August 05, 2003
Reviewer: A reader
sorry for not making myself clear
I was thinking about the sharing knowledge within your team
Followup:
definitely... yes.
Rating: 5
Soon means how soon??? August 05, 2003
Reviewer: Reader from Manchester, UK
Hi Tom,

 I have pre-ordered Effective Oracle by design book and I see dispatch date to
be May-2004 on amazon.co.uk site. I hope the book will be available in a month
or so. Let us know your thoughts/views :-)
Followup:
I asked the publisher and this is what they say:

The US Edition (USE) is available for sale internationally as soon as it is
published. The UK and our other international subsidiaries already have the book
on order. It will be available immediately after release. Conditions in India
require Tata/McGraw-Hill to license English language translations in order to
price to the market. That causes a bit of a delay, but I'm sure they'll get your
book out as soon as possible there. Foreign subsidiaries and third party
publishers usually wait until the book is actually published before they
consider it for translation into other languages. However, I know that we've had
some interest on this front already. Any feedback from readers that will help
show the demand for specific languages is alway helpful.
Rating: 3
Got few more months... August 05, 2003
Reviewer: fan from PA
>>and I see dispatch date to be May-2004
Hmmm so I got 9 more months to finish up the first book....
Rating: 5
Found Errata and Code Samples August 05, 2003
Reviewer: Fred from irvine, ca
This site has the errata and code samples for Expert: One-On-One.  Tom, maybe
you can download these and make them available to your website?
http://support.apress.com/code_listing.asp?s=0&view=all

Followup:
they belong there -- with the book!  apress "owns" that book now
Rating: 5
How do you manage all this.... August 05, 2003
Reviewer: Ajeet from oh,usa
Tom -- What is your secret..of handling :
1.Answering 10-20 questiones (sometime you write all the code as an answer ,you
have done more than once in reply of my question)...
and then
2.work as a VP of Oracle..
and
3.Write such great books
and last but not least--always eager to help unknown oracle developers like us..
We too work somewhere in the world ,of course in oracle realted technology but
most of the time --we find 12 hours insuffiecinet to complete our project or
assigned work and come to you for a solution,helping others is a distant
things....
Just curious--how you do all this with such a ease...

Thanks Tom ---looking forward to read your new book and hoping that i will learn
from it,

Ajeet
Rating: 3
August 06, 2003
Reviewer: Anthony Reddy from KL, Malaysia
hai Mr. Tom,
thanks for your great books expert One on One Oracle
and the coming one.
Rating: 3
Book Signing August 06, 2003
Reviewer: David from Lexington, MA
Tom -

When the new book comes out, where will the book signing be? ;)
Followup:
OracleWorld of course!  The bookstore there.
Rating: 5
any particular day you will be at OracleWorld? August 06, 2003
Reviewer: A reader
"OracleWorld of course!  The bookstore there. "

Do you know what days of OracleWorld - I would love to
just meet you up there!

Menon:)
Rating: 5
New Release August 10, 2003
Reviewer: David from Lexington, MA
Tom -

If Wrox no longer has your book, where can we download the source code examples?

Thanks,
Dave
Followup:

http://www.apress.com/book/supplementDownload.html?bID=220&sID=1086
apress bought it.
Rating: 5
August 11, 2003
Reviewer: Kalita from Bromley,UK
Hi Tom,
   Why don't you put a link to new Expert One-on-One Oracle in your home page or
include it in the messages? I think it will be helpful to people who did not buy
the original copy (not me :-)).

Regards,
Kalita
Followup:
it is on amazon now -- they got my name wrong so the click on "all of my books"
doesn't show it yet.  they are working on that.
Rating: 3
Can't access: asktom.oracle.com/~tkyte/OracleWorld.html August 11, 2003
Reviewer: fan from PA
Tom the link you provided below can only be accessed by
registered attendees, is that what you meant it to be ?
Thanks

>> the new SQL Model Clause (if you thought analytics were >> cool -- wait till
you see this!) Click here to read
>> about it: asktom.oracle.com/~tkyte/OracleWorld.html.
Followup:
goto
http://www.oracle.com/oracleworld/sanfrancisco/conference/
  -> conference -> session catalog

and search for "kyte" in the "speakers last name", that should get you in.  I
don't seem to be able to set up a straight bookmark.
Rating: 5
Oracle 10G August 11, 2003
Reviewer: David
Tom, going through the conference schedule, I found the next Oracle version is
named Oracle 10G. I was checking out the new features, and some of them seem
interesting -- like "your" new SQL model.

Are you going to launch Oracle 10G at Oracle World ? When will it be finally
available ?

Every new Oracle version brings some (r)evolutionary stuff.

Can you name some of the features you find interesting in it ?
Followup:
Sept 8th is the launch date for 10, yes.  Larry (not me :) will be announcing it
and its features.

cannot say much more then that for until, until the launch anyway...
Rating: 3
Just one show by Tom at OracleWorld ? August 11, 2003
Reviewer: fan from PA
>> 40166   Oracle Database 10G SQL Model Clause
>> Product Area:   Database
>> Length:   1 Hour
>> Level:   Introductory

man ! you only got an one-hr show at this 3-day event ??!!
..not fair...to me the value of the event just took a big dip...
Are you gonna get a kiosk or something with a big AskTom logo ?
Followup:
I'll be in the bookstore once or twice for at least an hour as well.  will let
you know the dates/times when I know them :)

You'll find me at the HTML/DB booths as well lots.  (aka project marvel -- new
10 feature)
Rating: 5
Tee Shirts August 11, 2003
Reviewer: Tom Best from PA
I would pay money for a tee shirt that says "I AskTom"

:-)
Rating: 5
tee shirt that says "I AskTom" - that's cool idea August 11, 2003
Reviewer: Alex from NY
I think that the idea for the t-shirt "I AskTom" is good. Tom any plans in that
direction ?
Rating: 5
I would go for a T shirt August 11, 2003
Reviewer: Jim from Portland, OR USA
I would pay good money.  Got it in a 2xl?
Rating: 5
August 12, 2003
Reviewer: A reader
As to the T-shirt, you can count me in.

Who is this other person who is going to co-present your lecture ? I see no
description regarding him whatsoever.

Is he your "ghost writer" by the way ?

;)
Followup:
Andy Witkowski is the original presenter, I'm co-presenting with him.  He is a
very Sr architect in the SQL languages group and one of the inventors of the
MODEL clause we'll be talking about.
Rating: 5
presentation August 12, 2003
Reviewer: Dave from UK
Will you be posting the slides you use after the event, us Brits who won't be
going need to see :-)
Followup:
All OracleWorld presentations are archived and made available on otn
Rating: 3
t-shirt do it yourself August 12, 2003
Reviewer: fan from PA
> I think that the idea for the t-shirt "I AskTom" is good. > Tom any plans in
that direction ?
> I would pay good money.  Got it in a 2xl?
> As to the T-shirt, you can count me in.

yeah, right...i am trying to picture Tom in a booth behind a pile of t-shirts...
But there is this shirt-print kit if you can't wait.
Rating: 5
T-shirt August 12, 2003
Reviewer: T-shirt salesman
Wow... great idea ! I'd love to wear Tom's shirt !

Anyway... if he won't care,  I'll be making them myself.

Who wants to buy ?  ;)
Rating: 5
I will buy August 12, 2003
Reviewer: A reader
Rating: 4
About Model Clause of SQL. August 14, 2003
Reviewer: Asim Naveed from Pakistan
Its very very good to know that SQL has a new MODEL clause.
I was eagrly waiting for it.

I have read it at that site.

Please answer the following.

1- Will MODEL clasue provides the functionality like the
  MDX(Multidimensional Expressions/Queries).by Microsoft.

2- Can SQL return variable number of COLUMNS now.
   i.e no. of columns of the output depend on the rows
   queried. We do this by DECODE right now, but its still
   not variabe.

Thanks

Asim Naveed
Followup:
1) no, it is a sql extension -- but that does not mean 10 won't have what you
ask for...

2) sql will never be able to do that -- consider the IMPLICATION on the client
for a moment.

you can use collections for this.
you can use cursor variables for this.

else you are using DECODE/case for a FIXED NUMBER of columns.

even with collections and cursor variables you have a number of FIXED columns.
Rating: 4
project marvel -- new 10 feature August 14, 2003
Reviewer: robert from PA
>>You'll find me at the HTML/DB booths as well lots.  (aka >>project marvel --
new 10 feature)

Tom, you mean Marvel is "integrated" (not sure if this right word) in 10i ?
thanks
Followup:
it is a feature of the database, install 10 and you'll have it.
Rating: 5
August 14, 2003
Reviewer: A reader
Tom,

You should oraganize a conference in which you will be the only speaker, I'm
sure your fans will love it..
Rating: 2
Neither of your books available in the UK :(( August 15, 2003
Reviewer: SP from Warrington, UK
Hi Tom,

 It is mid August now and neither 'Expert One on One Oracle' nor 'Effective
Oracle by Design' are available. Please let us know by when will either of the
book will be available.

Regards,
SP
Followup:
read
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:8145042756282#11551243925689
Rating: 5
Oracle Press says that your new book is available now August 15, 2003
Reviewer: A reader
If you go to the Oracle Press web site, they state:
"Effective Oracle by Design
ISBN: 0072230657, $59.99 US, Softcover, 45 illus. , 688 pages., Available Now"

However, amazon.com still says it's not released yet. Personally, I'll wait for
the amazon discounted version. :-)
Followup:
I would too -- it is orderable right now (august 15th) but won't be shipping
until about the 28th.
Rating: 5
Honey, I Blew up the Book August 15, 2003
Reviewer: Mark A. Williams from Indianapolis, IN USA
From the Amazon website:

"Dimensions (in inches): 18.00 x 38.00"

That book is a foot and a half wide and over 3 feet high! :)

I am gonna need a new bookshelf.
Followup:
i sent that to the publisher :)

LOL
Rating: 5
that was funny!! August 15, 2003
Reviewer: A reader
Anyways - for the cost conscious bookpool is giving
slightly better discount..check out
http://www.bookpool.com/.x/pcyyyykj56/sm/0072230657
Followup:
well, that does *nothing* for my amazon.com ranking!

:)
Rating: 5
ranking?!! August 15, 2003
Reviewer: A reader
hmm...did not think of that! You should perhaps not
publish these kind of suggestions then, eh?!;)
Rating: 5
you need to spend $40 at bookpool for free shipping August 15, 2003
Reviewer: A reader
Rating: 5
Don't forget their shipping costs! August 15, 2003
Reviewer: A reader
Don't forget their shipping costs!
Rating: 3
New version of Oracle 10g August 15, 2003
Reviewer: Asim Naveed from Pakistan
Hi,

MDX is a standard for multidimensional databases as
SQL is for relational databases. Many multidimensional
databases support MDX.

My question is that why doesnt ORACLE support MDX. Why
are they not following the standard as they followed in
SQL. Even if Oracle provides a better solution than
 MDX, it will be difficult to write database independent client side
applications.
Followup:
point me to the site that talks of this ISO, ANSI, or any open standard owned by
standards bodies called MDX?

i'm very aware of it -- but how did you turn it into a standard?

anyway, cannot say much more until around 9am pst on september 8th
Rating: 3
What is G stands for in 10G? August 18, 2003
Reviewer: Tony from India
What is "G" stands for in 10G? Is there any URL to check out 10G new features?
Followup:
g is for grid.

there will be right after the product is announced at OracleWorld on sept 8,
2003
Rating: 3
Link for Expert One on One Oracle August 18, 2003
Reviewer: A reader
Hi Tom,

 Will you please put a link for 'Expert One on One Oracle' on asktom home page
... the way there is link for 'Effective Oracle By Design'
Rating: 5
RE: Link for Expert One on One Oracle August 18, 2003
Reviewer: Mark A. Williams from Indianapolis, IN USA
> Link for Expert One on One Oracle

You can use the 'quick link to all books' and E:1-1 is right there - only 1
click away...

- Mark
Rating: 5
Here's a "straight" bookmark to Tom's Oracle Openworld session August 19, 2003
Reviewer: A reader

http://www.oracleworld2003.com/catalog/catalog/sessionDetail.jsp?SESSION_ID=40166&form=searchform

Rating: 3
related to MDX question earlier in this discussion August 20, 2003
Reviewer: Asim Naveed from Pakistan
Hi,

I cannot find any site of MDX standard, but I have
read something on internet that lead me to believe that
MDX is standard. May be its a defacto standard.

Please go to the following link
http://www.microstrategy.com/Software/Products/MDX_Adapter/FAQs.asp#7
and read the question no. 7
here is a cut/paste of Q.7

7. Can you give a couple of rough analogies explaining how ODBO and MDX relate
to each other and to multidimensional and relational data sources?

Answer.

ODBO is to multi-dimensional data sources as ODBC is to relational databases.

MDX is to multi-dimensional data sources as SQL is to relational databases.

Also I have read on the following site about OLE DB for
OLAP.
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnoledb/html/msdn_oledbfaq.asp
On the above link , specially see the following question.

Q. Why should I use OLE DB for OLAP?

A. OLE DB for OLAP allows you to access OLAP data through a set of
industry-standard interfaces. ISVs and corporate application developers can
depend on a single interface for consuming OLAP data, regardless of the vendor
or source. Providers of OLAP data can implement a single set of interfaces that
allow all OLAP clients to access their data. Consumers can use the COM query
interface and data source properties to dynamically adapt to provider
capabilities. We expect OLE DB for OLAP to spawn a new market for interoperable
OLAP tools.

Thanks
Followup:
MDX is not a "standard" in the way that SQL, C, Java, SMTP, FTP and other open
standards are.

MDX is a proprietary API owned, governed, made up by Microsoft.  It is as "open"
as ODBC.

You are reading the MS marketing pages.  When was the last time they made an
open standard?
Rating: 3
One more thing August 20, 2003
Reviewer: Asim Naveed from Pakistan
Regarding "variable no of columns in SQL" you said that:-

"
2) sql will never be able to do that -- consider the IMPLICATION on the client
for a moment."

I am unable to understand this please can you ellaborate
this.

Thanks
Followup:
what would the client do if the first row contains 100 columns
row 2 has 2
row 3 has 50
row 4 has 10

what the heck would that mean?
how could you array fetch?
imagine the CODE you would have?
think about it -- just doesn't make SQL sense.
Rating: 3
I don't suggest you to do an ebook version of your book. August 21, 2003
Reviewer: juancarlosreyesp@yahoo.com from Bolivia
Hi Tom,
I don't suggest you to do an ebook, why?
because this will become available through edonkey, an other file sharing
software.

For example in the bestdown oracle collection available at edonkey, I found your
book "Oracle 8i Application Programming"
1. Personally I wouldn't suggest it to put ALL as ebook.
2. You can put as ebook, only the examples
2.  You could put online the book if it has a good security or license control.

This is only my opinion
Rating: 3
variable no. of columns August 22, 2003
Reviewer: Asim Naveed from Pakistan
Yes I understand that this kind of "variable no. of columns" does
not make sense.

But what I mean by "variable no. of columns" is not this.

I am not saying that each row have different no. of columns.

What I mean is that for a specific set of data the SQL statement
will return FIX no. of columns. For another data set
the same sql will return  different FIX no. of columns.

For e.g
Create table T (region varchar2(10), product(20), sales number(10));

Now I insert 4 regions, and 3 products, each region have
every product, so there will be 12 rows in the table.

Now I want to see sales of each product in each region.I want
regions on rows and products on columns. I will simply
write SQL statement that will contain 3 DECODEs (i.e the no. of products)

Now if I add 8 more products in the table, I will
have to change my SQL statement and add 8 more DECODES.

I want SQL to return 11 columns because I have 11 products now. That is the
no. of output columns change on the state of data not on each row.
If a region (i.e a row) have only 1 product then we will
show NULL or 0 under every other product.

Will 10g have something in this regard.

Will SQL ever be able to do this kind of "variable no. of columns"

Thanks

Asim Naveed.
Followup:
six one way

1/2 dozen the other.

there are no plans I am aware of to make the SAME query return DIFFERENT
numbers/types of columns from execution to execution.
Rating: 3
Arrgh...they're all out of stock... August 23, 2003
Reviewer: Robert from CT
I was just curious if there's anyone out there selling used "Expert O-O-O
Oracle". Search on eBay using the term "expert one on one"...this is in the
small list that came up :

Mediterranean Cattle and Sheep in Cross-Breeding
» Paperback, 1977  - Not in stock. Add to Wish List
Followup:
it is almost shipping from amazon now.  it has been reprinted.
Rating: 5
difference between reprinted book and original August 23, 2003
Reviewer: Ishaque Hussain from Fords, NJ USA
Hi Tom,
  I was wondering if there were any differences between the reprinted Expert one
on one Oracle and the original Expert one on one Oracle book?  Does it include
additional 9i features?
Followup:
nope, same exact book.

they just bought it and are printing it.  that'll be the end of it.
Rating: 3
MDX again sorry. August 24, 2003
Reviewer: Asim Naveed from Pakistan
Ok , i got it.

But i am unable to understand this

six one one
1/2 dozen the other.

Actually, my problem is that I am responsible here
for developing a front end application for OLAP using
Oracle. In this application there are spreadsheet like
reports that are generated dynamically. So I installed
SQL SERVER's analysis services. Analysis services will
bring all data from ORACLE to its own multidimensional
database and store it in cube. And on that cube I can
write MDX. MDX is so easy to write for the kind of
reports i want.

I want a complete ORACLE solution, I know that oracle 9i
also have OLAP built in, but the additional cost is too
much for our client, so we were left with only one option
if we want to do it complete ORACLE and not microsoft, and
that option was to use SQL to retrieve the datawarehouse
and generate dynamic reports. But because I was used to with
MDX for these reports (which were very easy) I find it
very difficult to write equivalent SQL statements,
specially when making metrices (calculated members).

Does this mean that I can say to my boss that in Oracle 9i
release 2 (without OLAP) this is not possible, or can you
give me some alternative solutions. I already know the
MVs and analytical functions.

Thanks
Followup:
sorry -- should have been

6 one way.
1/2 dozen the other.

Have you looked at Oracles OLAP extensions -- you may very very will find them
more powerful, infinitely faster and maybe even "easy".

I find 99.999% of what people try to do -- and believe they need "cubes" for are
done 100% using analytic functions.  don't know how many times I've taken a
"dump from oracle into sqlserver so we can 'cube' it" and turned it into a
simple "group by rollup" or "group by cube" or used lag/lead, first/last, etc
straight from sqlplus to get the answer in an extremely small fraction of the
time.
Rating: 5
reviews on amazon for "Expert on-on-one" August 25, 2003
Reviewer: Alberto Dell'Era from Milan, Italy
Why don't you call Amazon and ask them to copy the online reviews of "Expert
on-on-one" from the Wrox to the Apress edition ?

Should boost the sells a bit since they averaged 5 stars - they win, you win ;-)
Followup:
i'll ask and see...

i of course have them permanently archived on my machines :)
Rating: 3
Expert one- on -one August 26, 2003
Reviewer: vivek from UK
Tom,

Why can't i buy your book from amazon.co.uk.
I have received mail from amazon
----------------------------------
From: Amazon.co.uk [mailto:auto-response@amazon.co.uk]
Sent: 22 August 2003 17:16
To: Andrew Taylor
Subject: Expert One-On-One: Oracle will not become available

Greetings from Amazon.co.uk

We've recently learned from our supplier that the item you requested to be
notified about, Expert One-On-One: Oracle, will not be available in the
foreseeable future.

-------------------------------------------------------
I am waiting for this book for long time and on your home page it says it will
be available 'september 2003', but this mail from azazon has shattered the hope.

Book seems to be availbale from wrox, but thats a quite old release. Any reasons
why new release is not available from Amazon when you say its available.

Thanks

Vivek
Followup:
i'll send this to the publisher, nothing I can do.
Rating: 5
just fyi bookpool has the book and is shipping now! August 28, 2003
Reviewer: A reader
mine is on the way!
Rating: 5
amazon.fr September 02, 2003
Reviewer: Fred from France
Hi Tom,

Do you know when your book will be avalaible on amazon.fr ?

Thx by advance,

Fred
Rating: 5
amazon.fr September 02, 2003
Reviewer: Fred from France
(i mean your NEW book)
Followup:
that i have no control over (nor info about).  i don't do the distribution :)
Rating: 5
Collector's item September 02, 2003
Reviewer: sPh from Saint Louis, MO USA
>>  i was wondering if there were any differences
>>  between the reprinted Expert one on one Oracle
>>  and the original Expert one on one Oracle book?
>>  Does it include additional 9i features?

> Followup:
>
> nope, same exact book.

However, just as people try to collect every edition of _Harry Potter_ ever
printed, I am sure that many will be rushing to round out their collection with
the new printing.  Another techno-sport:  collect every edition of every Tom
Kyte book ever published!

;-)  Only half joking though - I may order the reprint just for that reason.  I
must be sick!

sPh
Rating: 5
Which one should be read first? September 06, 2003
Reviewer: Sam from Bangkok, Thailand
In order to effectively learn advanced Oracle from your books, which one should
I start first? (expert one-on-one, or oracle by design)
Followup:
well, they are very very different books.

expert one on one is three books in one....

architecture/implementation/explanation -- how the server works, how locking
works, how transactions work, etc.  knowledge you need regardless of who you
are.

developer topics -- external procedures, java stored procedures, analytic
functions, etc.

dba topics -- partitioning, materialized views, etc.

but - even with the dba/developer stuff -- they apply to the other equally as
well.  A developer might want to use an external procedure, but the dba has to
set them up.  A dba might want to have partitioning implemented -- but the
developers will have to participate in that design.....

Effective Oracle By Design isn't a reference/tutorial like that.  It is more of
"how to do it right" or "things you need to think about before doing
something"...

It depends on whether you haven't the concepts guide yet!!  if you haven't, THAT
is where you need to start.
Rating: 4
where is the pdf September 08, 2003
Reviewer: Ringo Li from Hong Kong
Followup:
after you buy the book and get the book, you'll be able to use information from
the book to get access to selected chapters (ones that I refer to heavily for
reference) in pdf format.

Tom,

I got your book on hand but couldn't find the related info as mentioned
above..... Am I missing something ?
Followup:
ok, long story short

expert one on one -- written with WROX press.
effective oracle by design -- started with WROX press (was actually like a month
away from being done)

WROX press goes out of business.  All prior plans go out the window with that.

Oracle Press is who I decided to work with on effective Oracle by design.

Apress bought Expert one on one.

So, I removed the references to expert one on one from effective oracle --
backfilled the necessary material instead of just pointing to it.

There cannot be any pdf's since there are two totally differently publishers
Rating: 3
Reply to Vivek September 08, 2003
Reviewer: Suvarna from Warrington, UK
Hi,

 Both 'Expert One on One Oracle' and 'Effective Oracle by Design' are available
on Amazon.co.uk. I have recently received my copy of 'Expert One on One Oracle'
reprinted by Apress!!!
Rating: 5
6 months to this thread... September 09, 2003
Reviewer: Tarry Singh from Assen, Netherlands
Tom,

Six months ago I started this thread and today after struggling with a lock
escalation issue on a sql server I was pleasently surprised to find the
"effective oracle.." on my computer desk(at home).

I will naturally come back with my questions but for now it's reading time ;-)

Keep up the great work.

Cheers,

Tarry
Rating: 5
your new book - source code download? September 13, 2003
Reviewer: A reader
Hi tom
Could not find source code download on the net for your
new book. I hope there is one:)
Followup:
yeah, i guess that would be useful.

i ripped all of the code out of the book and put it here:
http://asktom.oracle.com/~tkyte/EffectiveOracleByDesign/code.zip
now, i haven't actually re-run the code.  I'm sure there is an issue or two.  My
email address is in the readme, just drop me a line and I'll fix it up.  But, it
should save on some amount of typing for you....
Rating: 5
thanx Tom! September 14, 2003
Reviewer: A reader
I would definitely let you know if there are any issues!

Please keep up the fantastic work!:)

And did I say - I love your new book too!! It is full of
amazing information - I especially liked the chapters
on optimizer, schema design and effective sql (I confess I have not understood
each and every point - i will be doing multiple scans) The PL/sql chapter also
looks very good -
haven't finished it yet.
Rating: 5
Your book .... September 17, 2003
Reviewer: reader
Tom, I have bought your book 'Effective Oracle by Design". I was wondering is
there a site where I can download the scripts that you use in the book (that
would save me typing time). For Expert one on one, I could download the scripts
from wrox web site. Is there a similar web site for your new book. Thanks.
Rating: 5
Read a couple responses up.... September 18, 2003
Reviewer: john
Tom gives the link there for the .zip file of scripts from the new book...
Rating: 5
Yes, I have downloaded the scripts. THANKS. September 18, 2003
Reviewer: reader
Rating: 5
Terrabyte Databases in Oracle September 19, 2003
Reviewer: Shouvik from Kolkata, India
Tom,

In your book there are a couple of mentions about database sizes going to
Terrabytes. I was just curious about the domains in which Oracle database sizes
can go to Terrabyte.

Is it advisable to have single terrabyte warehouse repository of data or split
it into several subdomains of smaller sizes (if possible of course) from
performance and manageability standpoint.

Just expecting another chapter on Oracle VLDBs, in the next book. :-)
Thank you,
Shouvik
Followup:
all domains.

we have many "oltp" systems in terabytes
we have many "dw" systems in terabytes
we have many "mixed workload" systems in terabytes

terabyte sized databases are not uncommon anymore -- they used to be, but they
are now becoming fairly common place.  everything from apps (HR/finapps/ERP/CRM)
to custom apps -- they are all getting really big.
Rating: 5
Where's the picture? September 22, 2003
Reviewer: A reader from UK
Just a quick gripe, over here in the UK, our version of the new book doesn't
have your picture on the front?

It's not fair, why do we always lose out over here? ;-)
Followup:
IMHO, that is a bonus!
Rating: 5
For readers in India September 23, 2003
Reviewer: A reader
I enquired with McGraw Hill about the book and this is what they say -

The title Effective Oracle by Design by THOMAS KYTE will be reprinted in
India by us in the month of October, 2003 and will be priced at Rs.495.00
(Price subject to change without notice). Our titles are available at almost
all the retail outlets across the country. You may contact your nearest
dealer or you can purchase it from TMH Direct, our Direct Marketing
department.
Rating: 3
Oracle Versions September 23, 2003
Reviewer: Asim Naveed from Pakistan
1-
What is the latest version of Oracle for which
the information in the book "Effective Oracle By design"
is valid.

2-
What is the latest version of Oracle for which
the information in the book "Expert One on One Oracle"
is valid.

If possible please specify the exact Product and its
exact version
for e.g  Oracle 9i Database Release 2 9.2.0.2
         Oracle 9i Application server rel 2 9.2.0.2

Thanks
Followup:
1) all
2) all

the books are mostly filled with "advice" and the advice applies to 7.0 as much
as 10g.  It is true there might be some features described that did not apply to
7.0 (eg: materialized views were added in 8.1.5 for example) but the material is
'valid' for all releases.

When Expert One on One Oracle was released -- the production release of Oracle
was 8.1.7.  Hence that book has no new 9iR1 and up "features" -- but the
architecture, locking, concurrency control, transactions, etc etc etc -- all of
that stuff is 100% valid for everything before and after 817.

When Effect Oracle Be Design was released -- the production release of Oracle
was 9iR2.  Hence that book has no new 10g and up "features" but everything in it
-- is relevant for all releases of Oracle before and after 9iR2.  Sure, I'll
discussion features only available in 9iR2 (eg: gathering system stats for
example)...
Rating: 4
Thanks but what about different behaviours September 23, 2003
Reviewer: Asim Naveed from Pakistan
Thanks for your detailed answer.

I am not only concerned about features, but behavior
changes as well. for example there might be some
rules to follow to make a specific query run
fast in earlier versions, but these rule are not
true in later versions and possibly may have reverse
effect.

Or do you specify the version no. also with your
advice in the books.

Actually i mean that can I read something from these
books and apply it equally to every version.
Actualy for some reasons we have several versions of
Oracle in production. What is the minimum version valid
for these books ? 7.0??
Followup:
i'd like to think they are universal version wise for the simple reason that i
repeat (and myself do) over and over again:

here is a list of things to consider
here is how to see what works best for you
here is how to test that
here is how to collect metrics for that

it is never "do it like this", it is "follow these steps", "understand how this
works", "apply your knowledge in this fashion"

any release, really.  there will be goodies in there you cannot use on really
old stuff, but most of the content is version independent.
Rating: 5
New book on sql! September 24, 2003
Reviewer: A reader
I would LOVE it if you wrote a book on sql itself
explaining all the concepts - neat tricks that you
use etc. I am not aware of any good sql books.
(Your chapter on sql and pl/sql are excellent in your
new book).
Do you have any such plans? May be you could write one
book on sql/plsql (assuming basic knowledge from the
reader on both topics.)

What do you think, Tom?
Followup:
not currently on the drawing board -- was thinking about doing the supplied
packages in detail.  HOW to use them, WHEN to use them, rather then just listing
the apis in them.
Rating: 5
if you meant plsql supplied packages September 24, 2003
Reviewer: A reader
there is already one by O'reily.
(though I am sure yours would be a class above.)

I feel that an sql book by you would be just wonderful
- don;t know what others feel about it.

Thanx for an awesome site, amazing sharing of first class technical knowledge
and even more amazing generosity!
Rating: 5
why not just expanding "expert one-on-one" ? September 24, 2003
Reviewer: Alberto Dell'Era from Milan, Italy
If you are going to discuss the supplied pl/sql packages in details, why not
just expanding the section on expert-on-one about them  - and while you're
there, expanding the whole book  and bringing it to the 2nd edition ... i.e.
aren't you planning any 2nd edition of "expert" ?
Followup:
it would be in the same vein as the appendix of expert one on one.

I do not "own" that book.

when WROX went under, it was sold to a company Apress.

I am working with Oracle Press -- hence, there will probably never be a 2cnd
edition to Expert One on One.  I'd have to rewrite it.
Rating: 5
Sql + Plsql + expert One-on-One 2nd edition September 24, 2003
Reviewer: Pieraldo Antonello from Italy
Yes, 3/4 more chapters written by Tom about sql & plsql
Rating: 5
New Book September 25, 2003
Reviewer: A reader
Hi Tom,
Please tell us about the new book you're working on or are planning to work on.
Thanks
Followup:
just look up a couple of reviews -- short blurb on what I'm thinking about
there.
Rating: 5
dbms_aq September 25, 2003
Reviewer: Alberto Dell'Era from Milan, Italy
Are you planning to cover it - and so, AQ ?

Your coverage of DBMS_PIPE and DBMS_ALERT was so useful, that I sorely missed a
chapter about DBMS_AQ in "Expert" ...
Followup:
i would in such a book, should I decide to actually commit to doing it :)

i would be hitting all of the dbms_ and utl_ packages.
Rating: 5
Pictures September 26, 2003
Reviewer: Dave from UK
I just got my copy in England from Amazon and Tom's picture is on the front AND
the back - double the pleasure
Rating: 5
Indian Edition September 29, 2003
Reviewer: A reader
Hi Tom,
Do you know when would an Indian Edition of your new book be available?
Thanks
Followup:
i hear that the end of october is the target date.
Rating: 5
Sea Biscuit September 29, 2003
Reviewer: A reader
I notice on the back of the book, it says "Oracle Guru", and thats definitely
true.  I rate this book as high as any of those done by the other Oracle guru,
Don Burleson.
Rating: 5
September 29, 2003
Reviewer: A reader
Please don't compare Tom to Don... Don Burleson's books stink !!! Many of the
concepts discussed here are overlooked by him. He insists on cache hit ratios,
rebuilding indexes, etc. Plain humbug !
Rating: 5
RE: Sea Biscuit September 29, 2003
Reviewer: Mark A. Williams from Indianapolis, IN USA
LOL!

(It is an inside joke)

- Mark
Rating: 5
September 29, 2003
Reviewer: A reader
I totally agree, TOM is the best!!
Rating: 5
Writing is an art September 29, 2003
Reviewer: Tarry from Assen, Netherlands
The book is nice to read(I'm already done reading 3 chapters..must admit I'm a
slow reader).Only they've pasted Tom's face all over the place. I mean ok he's
got the cute looks but I guess the oracle press guys got carried away with
it.;-)

Funny thing about Don's website was that I see him recommending lot's of authors
but not a single book of Tom. ;-)

I bought Steven's book(pl/sql programming) as well but there's no author(besides
someone like jonathan, harrison, henderson..to name a few)like Tom with
excellent conversational style of writing. Writing a book is an art , not
everyone can write. A lot of people can do wonderful things with a computer but
are hopeless when it comes to conversing/documenting/explaining/sharing.
Rating: 5
I agree with Tarry... October 02, 2003
Reviewer: Franco from Denmark
I'm reading Tom´s books like I can read a spy story: the killer is hidden, and I
constantly look for him; but at the end.... I always discover that the killer is
a lack of knowledge in my mind, some missed Ora doc, some too complicated idea.
Tom's books rating: 20 * !!!!
Rating: 5
I need that October 07, 2003
Reviewer: Praveen from Bangalore
Tom,

I badly need your second book too. And I'm in Bangalore...:)

Thanks

Praveen
Rating: 5
Your new book : Expert One-on-One Performance by Design October 09, 2003
Reviewer: Rich from NJ
Just a note of appreciation for your site and your new book.

Thanks
Rating: 5
New Book October 17, 2003
Reviewer: A reader from San Diego, Ca
Excellent! Chapter 7: Effective schema design is brilliant. Simple and helpful.

Thanks Tom!
Rating: 3
Please verify October 18, 2003
Reviewer: muhammad from NJ
I got  expert one-on-one book.it is published by apress. it has 1997 pages with
23 chapter. its isbn no is 1590592433.
is it the new edison or the reprint of the previous book.
Followup:
WROX went out of business in march 2003.

apress bought expert one on one Oracle

they reprinted it in Aug 2003.

It is word for word for word the same book as WROX sold.

My new book is title "Effective Oracle By Design" published by Oracle Press in
Aug 2003.

sorry for the unavoidable confusion -- all of which is so far outside my realm
of control.
Rating: 5
Indian edition pleaseeeeeeeee October 20, 2003
Reviewer: Sameer from Pune, India
Hi Tom,

My patience is almost dying out !! But I'm a DBA by profession, cannot show my
emotions so easily :-)

Why Indian edition is getting sooooo delayed ?
(I don't really expect you to answer this..)

Just a thought... do you have any data of your last book which show's which part
of the world bought your last book the most? It could be helpful to prove that
India should be one of the first countries where Mr Kyte's book should be
published first..!!
You got huge fan following here sir.
Followup:
it is not "delayed", it is that the indian reprints are done after the first
print.  I hear that it should be out this months.

It is not about "quantity shipped" really -- but rather quantity*price that
drives it.  The reprints in india and some other countries can cost sustantially
less.
Rating: 5
quantity*price October 20, 2003
Reviewer: A reader
Three copied can be bought in India for the price of one :-)
Buy two for yourself and present the third to a person who says Explicit cursors
are faster than Implicit :-)
Rating: 3
can I get ISBN no. of your books October 23, 2003
Reviewer: Anurag from INDIA
Tom ,

Can I get an ISBN nos. and name of publishers of both of your books, As my
bookseller is unable to find and has asked for same.

Thanks
Followup:
see my home page -- click thru to amazon.com.  they are there.
Rating: 5
Now in India October 28, 2003
Reviewer: A reader
Hi everyone,

Just got a mail from Tata McGraw Hill,
Effective Oracle by Design has finally launched in india. Hope to rush to my
bookstore after office to get my copy.

Cheers
Rating: 4
Books24x7 October 30, 2003
Reviewer: Kimathi from Los Angeles, CA
Any chance you might publish on Books24x7.
It serves as great reference material to have online versions.

Other authors with the same series i.e 'Expert One-on-One' seem to have done so.

e.g
Expert One-on-One J2EE Design and Development by Rod Johnson
Followup:
never heard of it, i don't work on the distribution, just the "development of"
material.

Isn't that the book that says "don't do stuff in the database".  hmmm, not sure
it should be 24x7
Rating: 5
Just got your book... October 31, 2003
Reviewer: Piotr Jarmuz from Poznan, Poland
Can't wait the evening to start the lecture :)

To Srinivas M:
The book is maybe expensive but think of it like an investment in yourself.
Believe me, the knowledge you will acquire is worth thousendfold the price and
eventually your salary will be improved too just like it happened to myself
after Tom's first book. Even better it helped me get rid of
"database-is-slow-trauma" after a year of work with some other commercial
database :)

This means a lot to me and alone is definitely worth the price.
Rating: 3
November 06, 2003
Reviewer: A reader
Congratulations for your book, I'll see if this time get time to read the whole
book.

PD
The next time rest a little before taking the photo for your books. ;)
Rating: 5
Book on 10g November 06, 2003
Reviewer: Arun from New York, NY
1. My friend brought your new book and he says it is summary/collection of info
on your site (asktom.oracle.com). Mo need to buy if you have access to site.
Your comments ?

2. Are you planning to publish book on 10g in near future ?
Followup:
will, if you read the 9,000+ published things here, sure, everything is already
here.  some of the examples in the book come from here.

but, there is a big difference between a quick answer here and an in depth "lets
look at this in a book" answer.

is there overlap -- sure, obviously (same with expert one on one - this site was
the genisis for that book -- everything in that book is there as well).

books are just a different medium from a website.  I'm able to be a little more
coherent, organized in a book then here.

Yes, planning on a 10g book sure -- but "near future" is relative.  I like to
USE the produce before writing about it :)
Rating: 5
Web Seminar November 07, 2003
Reviewer: A reader
Tried to access your Web Seminar from Nov. 5th, it said that it has not yet
started.

By the way, what is OGEH?
Followup:
i'll ask them why....

OGEH is Oracle Government, Education and Healthcare, the vertical I work in.
Rating: 5
Book is now available in India November 08, 2003
Reviewer: A reader from India
The book is out in India as well -
http://www.tatamcgrawhill.com/catalog/bsearchdetails.asp?author=kyte&title=&isbn=&year=&keywords=&publisher=&sub=Find+It
Rating: 1
Not yet released in India November 10, 2003
Reviewer: A reader
Rating: 5
Outer table... November 19, 2003
Reviewer: Scott Watson
Tom,

I was going through your new book and either I need some clarification on how
the hash outer join (p470) works or there may be some errata on this page.

Which table would be the outer table in this example?  Normally I would have
said EMP since it comes second in the explain plan, however, is this still the
case when dealing with an outer join?  The reason I ask is because on the
previous page you wrote "The outer table (whose rows are being preserved) is
used to build the hash table".  However, in the section on how this is processed
you have the DEPT table as being scanned and hashed and not the EMP table.

Did I miss something or is this an error.

Thanks,
Scott.

PS. Good job on the book.
Followup:
in the example in the book -- DEPT is the table with the rows being preserved.

Normally, the EP is read bottom up, inside out -- but with hash joins, the order
of the tables can be reversed at will.

in the example, the dept table was scanned and hashed.
Rating: 5
Effective Oracle by Design November 25, 2003
Reviewer: Justin from PA
I can't ever seem to instantiate a new thread, so I'll
/*+ append */ my comment. (We need more Toms.. when will Alan and Megan be
ready??)

I'm reading your new book, and I must say, I haven’t ever laughed this much
reading a technical book!

It's practically hilarious in parts Tom.

Thanks .. I'm thoroughly enjoying it.
Rating: 5
will you be putting a URL to your talk in sweden on asktom? January 09, 2004
Reviewer: A reader
Thanx and have a happy new year!
Followup:
the url is already there on the home page.  i'll be taking it down after its
over of course, but it is there right "now"
Rating: 5
what I meant was January 09, 2004
Reviewer: A reader
would you be putting the url for contents of your
presentation that you will do in Sweden. Current url
points to a pdf detailing the event itself..
Followup:
it'll be the "day3" stuff from the master class in denmark that i'm doing next
week.  I'll be posting those when I get back, yes.
Rating: 3
A reader January 11, 2004
Reviewer: alex from uk
Tom

Can I request you to tell me what tool can be used to performance test for
Oracle Forms 6i & Reports 6i.
Is it discoverer, win runner or any good tool.

Thanks
Followup:
I know load runner is a workable solution for sure. (by mercury interactive)
Rating: 5
Code Download January 15, 2004
Reviewer: Iain from uk
Tom

Do you know if it's possible to download the code from your Expert One-on_One
book from any other source now that Wrox have gone out of business.

A link on this site maybe
Followup:
www.apress.com

they bought the book
Rating: 5
your next new book ? January 21, 2004
Reviewer: Alberto Dell'Era from Milan, Italy
Still planning a book on dbms_* and utl_* ?
Followup:
not sure.
Rating: 5
Your new book : Expert one-on-one performance by design January 28, 2004
Reviewer: Subodh Deshpnade from India
Hi Tom,

just now i checked, the web site amezon, it stil shows that it covers upto 8.x.
I ALSO ASKED THE SHOPKEEPER, HE SAID ME THIS IS THE ONLY LATEST VERSION.

HOW CAN I GET YR NEW BOOK...9.X RELEASE..

PLEASE REPLY...
Followup:
there is "effective oracle by design", that is the new book (as of august 2003).

the other book is "expert one on one Oracle"

the book "expert one on one performance by design" was never published -- it is
called "effective oracle by design"
Rating: 5
system stats January 28, 2004
Reviewer: Ferenc Palyi from Hungary
Just one little note about your book.

On Page 319, System Statistics Wrap-up you say:
"The important consideration here, however, is that you must gather these system
statistics when the system is running a representative load."

In my oppinion, representative load is not enough, I will share my experiences
with you.

The problem is that, Oracle collects the stats using the current execution
plans. In my case, the database was full table scan happy. So during the
collection there were a lot's off executions doing full scans, there were no
index blocks in the buffer cache. The collected results indicated that full
scans are quiet cheap. I loaded the stats, and everything remained the same, but
I wasn't convinced, so I picked some queries and started to hint them, to use
indexes. I experienced, that the queirs are running much quicker.

I created artificial stats and loaded them, then I started to collect system
stats again. This time the stats said single block access is not so expensive,
and the overall performance of the system greately increased.

Creating artificial stats is the same like playing with
OPTIMIZER_INDEX_COST_ADJ. It's the same game, you discussed in the following
topic:
http://asktom.oracle.com/pls/ask/f?p=4950:8:346114510039635682::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:6601251003901,
Well, system stats doesn't make optimizer_index_cost_adj obsolote, you still
have to "push" the db to the right direction. If you have no clue what's going
on, you will just generate "wrong or misleading set of statistics" even using
representative workload.
Rating: 5
January 28, 2004
Reviewer: A reader
Tom,

Can you please give some feedback regarding connor mcdonald's book "Mastering
Oracle PL/SQL Practical Solutions"

Thanks.
Followup:
I could, but Connor is a friend and my mother always said "don't say anything
unless you have something nice to say"

ONLY kidding.  Only kidding.

If you are doing PLSQL development and would like something beyond syntax
diagrams and would like to hear about how to make it perform (bulking up,
avoiding parses, making it scale), this book might well be for you.

It is not an entry level book.

It is not a "how to write pretty plsql that indents really cool" book.

Maybe if Connor sees this he can post the outline and a little bit of what its
about.
Rating: 5
January 28, 2004
Reviewer: A reader
Thanks Tom, that was a good one :-)

So Mr. Connor can we hear from you.
Rating: 5
Book info January 28, 2004
Reviewer: Connor from Perth, Australia
One thing I will say is that the book I've been involved with doesn't have
pictures of the author on the front, side, back, top, bottom...man talk about
ego....hee hee, just kidding Tom :-)

Seriously though, this from the back cover:

"Dear Reader,

If you need more from a PL/SQL book than just the correct keywords and some
pretty syntax diagrams, then this is the book for you. If you’ve been searching
in vain for real-world examples and genuine strategies for maximizing the
benefits of PL/SQL within your organization, you’ll find them in Mastering
Oracle PL/SQL: Practical Solutions, the inaugural title of the pioneering
OakTable Press series. Mastering Oracle PL/SQL isn’t a tutorial on how to code
PL/SQL. It’s designed to show you how to code PL/SQL *well*. It will show you
how to write code that will run quickly and won’t break in high load, multiuser
environments. It covers the vast array of functionality that PL/SQL provides,
including effective handling of relational and abstract data, security,
triggers, dynamic web content presentation from within the database, creation of
a DBA toolkit, and effective debugging techniques.

This book sets out to redress the unjust criticism leveled at PL/SQL in years
gone by. It gives you the tools and techniques to ensure that, whatever your
needs are, the PL/SQL you build will run as efficiently as possible. I am
confident the practical solutions provided in this book will help you to realize
the true power and functionality PL/SQL can offer your projects."

The chapters are (if memory serves)
1:  Efficient PL/SQL (performance, scalability, concurrency)
2.  Why Packages
3.  Cursors (explict vs implicit, etc)
4.  Smart data handling (bulking, record based dml etc)
5.  Odds and ends (hints & tips etc)
6.  Triggers
7.  Security
8.  DBA tools in PL/SQL
9.  Debugging and instrumentation techniques
10. The web packages (OWA, HTP et al)

I should stress, this is not a book for *learning* PL/SQL.

Cheers
Connor
Rating: 5
Pictures January 28, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
Maybe the next book one will have Seabiscuit on it instead :)
Followup:
i did finally see that movie, it was a good movie
Rating: 5
January 28, 2004
Reviewer: A reader
Thanks Connor.. Am placing an order. :-)
Rating: 5
Re: Your new book : Expert one-on-one performance by design" January 29, 2004
Reviewer: diprey from NYC
Just finished reading this book: took me almost 5 months. It took me some time
to understand the details.

My opinion is it's a fine book, a subtle one, regardless of whether you are an
Oracle developer or not. It's only second to ORA 1-on-1, by the same author. I,
myself, work with various databases, Oracle being one of them, and I do this
stuff for a living.

There are quite a few RDBMS books on the market, but this one stands out.  For
all you SQL Server aficionados out there: buy this book, take your time to
understand it.  It is inspirational, it bears thought, it makes your creative
juices flow.

Thank you Tom for your tireless work and for your insight.  You are the best.
Rating: 3
Link for 'Efficient Schema Design' presentation January 29, 2004
Reviewer: A reader
Hi Tom,

 Will you please add link for 'Efficient Schema Design' presentation again on
asktom homepage?

Cheers!!!
Followup:

http://asktom.oracle.com/~tkyte/
I moved it to there.
Rating: 4
I want to know which is your book on 9.x and 9ids January 31, 2004
Reviewer: Subodh Deshpande from India
Thanx TOM,

Whcih one is about 9i,

Expert expert one on one Oracle

Or effective oracle by design

and lastly are you going to write anything on 9ids..pl do, i fall in love just
by reading sample lessons from 'one on one'
Followup:
Effective Oracle by Design is upto 9ir2

Expert One on One Oracle goes upto 8iR3 (but is pretty much 99% applicable in
whole to 9i, 10g and beyond)

IDS is covering forms, reports, disco, jdev -- things I'm just not "expert in".
I do not use those tools personally.  I'd be the wrong person to write about
them.
Rating: 5
your latest book February 03, 2004
Reviewer: A reader

http://www.apress.com/book/bookDisplay.html?bID=314
when is this goin to be available on amazon?
Followup:
after its been written :)
Rating: 5
your latest book / Oracle Insights February 03, 2004
Reviewer: Alberto Dell'Era from Milano, Italy
Already planned an outline of your contribution ?

"My fight - Save the Binds" seems mandatory ;-)
Rating: 5
ego boost for you February 17, 2004
Reviewer: Dave
Tom, take a look at this thread - bit of an ego boost for you :)
http://www.dbasupport.com/forums/showthread.php?s=2be256cc5dbc6618b8b1305e5c091022&threadid=41402

Rating: 4
Expert one-on-one performance by design", version 9.2.0 February 23, 2004
Reviewer: Leonard Anukam from Finland
Hey Tom, it could be a good idea to write a book from the questions and answer
from this site into a book. You could call

Problems and Solutions. How about that or A CD-Rom
Rating: 4
your book expert one on one.. March 15, 2004
Reviewer: A reader
hi tom good afternoon,

  I was reading your book page 610 (old ed. from wrox).
you have commit inside the for loop .

  can you tell me what is the reason/ benifit/ no effect ?
of that ? I thought I shoul always write commit out side the loop ?

Thanks,
Followup:
on that page -- i am

insert /*+ append */ into SALES
select ..... from SALES;
commit;

in a loop just to generate data.  If I did not commit (due to the direct path
write in this case, the append hint) the second "insert as select" would fail
since you cannot read/write to a table in the same transaction after doing a
direct path operation on it.
Rating: 4
very interesting.... March 15, 2004
Reviewer: A reader
Thanks for your replay. I will test further. This is good.

so  I can not issue

 insert /*+ Append */into t(id,name)
 select id_seq.nextval,to_char(sysdate) from all_objects
 where rownum < 2;

 insert /*+ Append */into t(id,name)
 select id_seq.nextval,to_char(sysdate) from all_objects
 where rownum < 2;

ERROR at line 2:
ORA-12838: cannot read/modify an object after modifying it in parallel

 cool I tested it my self too.  great. I didn't know that.
Followup:
(and it is a good thing you cannot! it would waste huge amounts of space as only
a single row would get onto a block!!!)
Rating: 3
Effective Oracle by Design..... April 06, 2004
Reviewer: denni50 from NA
Hi Tom....
Hope you (and family) are enjoying a much deserved splendid
vacation.

Finally got your new book...one of the online bookstores
was having a 45% discount on techy books and yours was one
of them....couldn't pass on that deal!(bought some others
too)

Started reading through chapter 1 and the more I read
the more frustrated I found myself becoming.

Showed my boss your two examples of using analytics vs
generic sql(page 13)..and then the performance analysis
between the two. Needless to say..she was astounded.

I told her that I cannot continue to work in an environment
that is stagnant and does not afford me the tools to advance
the goals of the organization and performance of the database.
I am seriously considering asking our network administrator to build me a high
end PC with a SCSI hard drive(found a good deal on a 73gig) and tons of CPU.
Then install OS, 9iR2 and import the database.

I will be "FREE" from the confines and limitations of the
software vendor and can start using 9i features especially
"analytics".

At the moment(with 817 SE)I have no:
OLAP
MV
Partitioning
Analytics....ad-nauseum.

Anyhow...just thought I'd let you know what's up!

any comments,recommendations would be most welcomed and
appreciated.

happy spring!
;~)
March 15, 2004 Jesse -- Thanks for the question regarding "How to load text files into database and read them from database?", version Oracle 8.0.5
You Asked
Hi Tom,

How can I load text files into database in a batch? And how can
I retrieve the files just like a select statement to select a
varchar2 column from a table and pass it back to the caller? My
intention is to call a stored procedure to retreive a text file
from the db based on an ID passed in (via JDBC) and displays the
texts in a html page. Is it possible to achieve that?
Thanks.
Regards,
Jesse
and we said...
couple of questions there....

1) load text files into database.  probably the easiest way is
to create a stored procedure such as:

ops$tkyte@8i> create table demo
  2  ( id        int primary key,
  3    theClob    clob
  4  )
  5  /
Table created.

ops$tkyte@8i> create or replace directory
  2  MY_FILES as '/export/home/tkyte/public_html';
Directory created.

ops$tkyte@8i> create or replace
  2  procedure load_a_file( p_id in number,
  3                         p_filename in varchar2 )
  4  as
  5      l_clob    clob;
  6      l_bfile    bfile;
  7  begin
  8      insert into demo values ( p_id, empty_clob() )
  9      returning theClob into l_clob;
 10      l_bfile := bfilename( 'MY_FILES', p_filename );
 11      dbms_lob.fileopen( l_bfile );
 12      dbms_lob.loadfromfile( l_clob, l_bfile,
 13                             dbms_lob.getlength( l_bfile ) );
 14      dbms_lob.fileclose( l_bfile );
 15  end;
 16  /

Procedure created.

ops$tkyte@8i> exec load_a_file( 1, 'index.html' );
PL/SQL procedure successfully completed.

ops$tkyte@8i> select * from demo;

        ID THECLOB
---------- --------------------------------------------------
         1 <html>
           <head><title>Tom's Page</title></head>

           <body>
           <h1>Welcome to my home page</h1>

           blah blah blah......

           </body>
           </html>

so, now you can call load_a_file over and over for each file you
need to load....

2) how can I retrieve the files just like a select statement to
select a varchar2 column ....  Well, if the size of the files is
LESS THEN OR EQUAL TO 4000 bytes, you can simply:

  select dbms_lob.substr( theclob, 4000, 1 ) from T

If it is >4000, you must select the lob locator out and then
process the lob locators either using dbms_lob.read OR using
streams in java.

I have an example of reading/writing via streams at:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:238014476571
but you'll definitely want to read:
http://download-east.oracle.com/docs/cd/F49540_01/DOC/java.815/a64685/oraext4.htm#1003223
as well.
Review & Followup

Rating: 3
Beware of the case sensitive in the directory creation April 16, 2002
Reviewer: Ringo Li from Hong Kong
Tom,

You should have warn us re the upper case (by default) on directory created.

Not until I check out your book (page 410), I will lost on what happened.

Anyhow, your book give me great explaination.

Thanks
Rating: 4
error January 01, 2004
Reviewer: Eugene from Seattle
Hi Tom,
I have a project where I have to store documents into the d/base. We have one
9.2 d/base that I use.
I was able to load two files: one .doc and one .txt, but when I try to view
them, both of them come out garbage-looking.
I wonder if you can help me to pinpoint what I may have done wrong.
Here are the steps that I take:
===============================

create table ep_doc_demo
(id     int primary key,
theClob    clob);

create or replace directory DOC_DIR AS 'c:\temp\doc_dir';

Placed two files in that directory: doc1.doc and doc2.txt

CREATE OR REPLACE PROCEDURE Load_Doc_P (p_id IN NUMBER, p_filename IN VARCHAR2)
AS

l_clob    CLOB;
l_bfile      BFILE;

BEGIN
      INSERT INTO EP_DOC_DEMO
      VALUES(p_id,EMPTY_CLOB())
      returning theClob INTO l_clob;

      l_bfile := BFILENAME('DOC_DIR',p_filename);
      dbms_lob.fileopen(l_bfile);
      dbms_lob.loadfromfile(l_clob, l_bfile,
         dbms_lob.getlength(l_bfile) );
      dbms_lob.fileclose(l_bfile);

END Load_Doc_P;
/

select *
from ep_doc_demo;

ID      THECLOB
--      ----------------------------------------------
1    ¿¿¿¿
2    ¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿

Why is there a garbage in them?

Thanks for your help
Followup:
well, I would expect the ".doc" to be garbage -- it is a proprietary, binary
file format.  It is not TXT.

as for the text, well, show us what is in there -- try something like a script;

host echo Hello World > /tmp/data.txt
then load using your stored procedure
then query using sql

and cut and paste the results.
Rating: 2
A similar situation January 02, 2004
Reviewer: Muhammad Ahmad Imran from Pakistan
Hi Tom,

I want to insert the data in the database which are currently in the txt files
and look like this(an example):

      empno: 7369
      ename: smith
      jobid: clerk
      manag: 7902
      hired: 17-dec-80
      salar: 800
      commi:
      depno: 20
      empno: 7499
      ename: allen
      jobid: salesman
      manag: 7698
      hired: 20-feb-81
      salar: 1600
      commi: 300
      depno: 30
      empno: 7521
      ename: ward
      jobid: salesman
      manag: 7698
      hired: 22-feb-81
      salar: 1250
      commi: 500
      depno: 30
      empno: 7934
      ename: millar
      jobid: clerk
      manag: 7782
      hired: 23-jan-82
      salar: 1300
      commi:
      depno: 10

And then I want to use "Select * from Emp_tab" to query this.(The results should
be simiar to normal emp table)

Some times ago when I have the similar situation then i use this technique

This is the sample data then

  1.  2.  All praise is due to ALLAH alone, Lord of all the worlds.
  1.  3.  The Gracious, the Merciful.
  1.  4.  Master of the Day of Judgment.
  1.  5.  THEE alone do we worship and THEE alone do we implore for help.
  1  6.  Guide us in the straight path,
  1 7.  The path of those on whom THOU hast bestowed THY favours, those who have
not incurred THY displeasure and those who have not gone astray.

And I use database as:

SQL> create table ch1(cno number,ano number,
                       ayyat varchar2(100))
  9  organization external
 10  (type oracle_loader
 11  default directory md2
 12  access parameters
 13  (fields terminated by '.'
 14  )
 15  location ('1.txt')
 16  )
 17  /

Table created.

SQL> select * from ch1;

And then easily get the required results but this time I really need your help.
Followup:
create table t ( field varchar2(30), val varchar(30) )
organization external
(type oracle_loader
 default directory tmp
 access parameters
 (fields terminated by ':'
)
location ('test.txt')
)
/
select * from t;

select r,
       max(decode(field,'empno', val )) empno,
       max(decode(field,'ename', val )) ename,
       max(decode(field,'jobid', val )) jobid,
       max(decode(field,'manag', val )) manag,
       max(decode(field,'hired', val )) hired,
       max(decode(field,'salar', val )) salar,
       max(decode(field,'commi', val )) commi,
       max(decode(field,'depno', val )) depno
  from (
select trunc(rownum/8-0.1) r,
       trim( both from field ) field,
       trim( both from val ) val
  from t
       )
 group by r
/
Rating: 2
To the last review: January 11, 2004
Reviewer: Muhammad Ahmad Imran from Pakistan
I want a database for this type of data....

I put here the actual data file but bear in mind that this is only a part of the
whole file(And I have hundreds of such files) so please some dynamic code writen
by you is really helpful for me(If you not seen the data in proper format kindly
copy to some text file and then examine it)

BA: 20023146007
      ET: Myiasis. A problem which gets under the skin.
      FT: Fliegenmadenbefall - ein Problem, das unter die Haut geht.
      AU: Hertzberg, H.
      SO: Forum Kleinwiederkauer/Petits Ruminants, 2002, No.9, pp.15-22
      AB: The most important cause of skin myiasis in sheep in
          Switzerland is Lucilia sericata. Its development, the lesions
          it causes and symptoms are described and illustrated. Control
          measures suggested are: prevention by tail docking and spraying
          with insecticides or insect growth regulators; therapy; and
          biological control.
      DE: biological control; insect growth regulators; insecticides;
          myiasis; pest control; skin diseases
      OD: Lucilia sericata; sheep
      GL: Switzerland
      UP: Ovis; Bovidae; ruminants; Artiodactyla; mammals; vertebrates;
          Chordata; animals; ungulates; Western Europe; Europe; Developed
          Countries; EFTA; OECD Countries; Lucilia; Calliphoridae;
          Diptera; insects; arthropods; invertebrates
      PU: Forum Kleinwiederkauer (Forum Petits Ruminants), Niederonz,
          Switzerland
      CC: LL823;
          HH405;
          HH100
      LA: German; French
      DT: Journal article
      SC: Biological Control; Medical and Veterinary Entomology;
          Veterinary Medicine

  BA: 20023123730
      ET: Synanthropy of the Calliphoridae (Diptera) from Valdivia,
          Chile.
      FT: Sinantropia de los Calliphoridae (Diptera) de Valdivia, Chile.
      AU: Figueroa-Roa, L.; Linhares, A. X.
      AA: Instituto de Parasitologia, Universidad Austral de Chile,
          Valdivia, Chile.
      SO: Neotropical Entomology, 2002, Vol.31, No.2, pp.233-239, 40 ref.
      AB: The objective of this study was to determine synathropic index
          of adult Calliphoridae (Diptera) collected in the city of
          Valdivia, Chile (39 deg 48 S; 73 deg 15' W). For this, samples
          were taken monthly between September 1996 and August 1997 in
          three different localities representing urban, rural and
          natural environments. Rat carcass, fish, and chicken viscera
          were used as baits. Four blowfly species were collected:
          Calliphora vicina, the dominant species in all three baits and
          sites of capture, followed by Lucilia sericata, both species
          being eusynanthropic. The other two species, Compsomyiops
          fulvicrura and Sarconesia magellanica, were collected in
          smaller numbers and were characterized respectively as
          hemysinanthropic and asynanthropic. In addition to the
          synanthropic index, the monthly abundance and the efficiency of
          the baits as attractants to the flies as well as their medical
          and sanitary importance were also determined and discussed.
      DE: ecology; synanthropy
      OD: Calliphora vicina; Calliphoridae; Lucilia sericata
      GL: Chile
      ID: Compsomyiops fulvicrura; Sarconesia; Sarconesia magellanica
      UP: Calliphora; Calliphoridae; Diptera; insects; arthropods;
          invertebrates; animals; South America; America; Developing
          Countries; Threshold Countries; Latin America; Lucilia
      PU: Sociedade Entomologica do Brasil (SEB), Londrina, Brazil
      IS: 1519-566x
      CC: YY000;
          ZZ332
      LA: Spanish; English
      DT: Journal article
      SC: Medical and Veterinary Entomology

   BA: 20023173425
      ET: Molecular approaches to the study of myiasis-causing larvae.
      AU: Otranto, D.; Stevens, J. R.
      AA: Faculty of Veterinary Medicine, University of Bari, P.O. Box 7,
          70010, Valenzano, Bari, Italy.
      SO: International Journal for Parasitology, 2002, Vol.32, No.11,
          pp.1345-1360, many ref.
      AB: Among arthropod diseases affecting animals, larval infections -
          myiases - of domestic and wild animals have been considered
          important since ancient times. Besides the significant economic
          losses to livestock worldwide, myiasis-causing larvae have
          attracted the attention of scientists because some parasitize
          humans and are of interest in forensic entomology. In the past
          two decades, the biology, epidemiology, immunology,
          immunodiagnosis and control methods of myiasis-causing larvae
          have been focused on and more recently the number of molecular
          studies have also begun to increase. The 'new technologies'
          (i.e. molecular biology) are being used to study taxonomy,
          phylogenesis, molecular identification, diagnosis (recombinant
          antigens) and vaccination strategies. In particular, more in
          depth molecular studies have now been performed on
          Sarcophagidae, Calliphoridae and flies of the Oestridae sister
          group. This review discusses the most topical issues and recent
          studies on myiasis-causing larvae using molecular approaches.
          In the first part, PCR-based techniques and the genes that have
          already been analysed, or are potentially useful for the
          molecular phylogenesis and identification of myiasis-causing
          larvae, are described. The second section deals with the more
          recent advances concerning taxonomy, phylogenetics, population
          studies, molecular identification, diagnosis and vaccination.
      DE: animal diseases; diagnosis; ectoparasitoses; genes;
          identification; phylogenetics; polymerase chain reaction;
          reviews; taxonomy; vaccination
      OD: Calliphoridae; Oestridae; Sarcophagidae
      UP: Diptera; insects; arthropods; invertebrates; animals
      PU: Elsevier Science Ltd, Oxford, UK
      IS: 0020-7519
      CC: LL822;
          WW700;
          YY300;
          YY700;
          ZZ360;
          ZZ900
      LA: English
      DT: Journal article
      SC: Veterinary Medicine; Medical and Veterinary Entomology
Followup:
happy early aprils fools to you too :)
Rating: 1
Reminder January 12, 2004
Reviewer: Muhammad Ahmad Imran from Pakistan
I want to discuss the very last comment posted by me and above the irrelevant
followup.

Please read my post and evaluate it.
Followup:
i evaluated it -- I'm not going to write your system.  give me a break.
Rating: 5
Output? January 12, 2004
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
Please provide the desired output for your last post?
Rating: 5
Dear Tom, "Remove/Ignore guys like Muhammad Ahmad Imran" January 13, 2004
Reviewer: A reader from NJ,USA
He does not know your technical ability. You sould have someone(to help you out
from oracle) to keep this GREAT forum very clean.

Muhammad, Please read Oracle Concept manual and Expert one-on-one Oracle by Tom
Kyte.
Rating: 5
For the "A Reader" January 15, 2004
Reviewer: M Ahmad Imran from Pakistan
I never mind for the Tom's follow up whatever he post but who are you to call me
out from this forum. Do you understand what is a forum "A Reader".

********Sorry Mr. Reader if you don't like this*******

Sorry Tom, your query is still not what I required so i post the sample data. If
you don't like it I never send a post agian. I know what's your postion but
doesn't know that there are some people who thinks that Tom is THEIR.

Dear Sir Sikander, I personally mail you to solve my problem
Rating: 4
bad link? January 16, 2004
Reviewer: Harrison Picot from Haymarket "Dog food capital of Virginia" USA
seems to be a bad link:

"but you'll definitely want to read:
http://download-east.oracle.com/docs/cd/F49540_01/DOC/java.815/a64685/oraext4.htm#1003223

Rating: 3
External Table based on CLOB inside database January 22, 2004
Reviewer: Dave from Lexington, MA
Tom -

Is there a way to base an external table on a row of a table with a CLOB column?

The name "external table" suggests not. If not, I'm guessing I'd have to write
the file to the file system, then use the external table with that.

Any other suggestions?

If it is not clear, the desired function is this...
"Generic function to upload file into CLOB" -> "External table based on CLOB
used to load data into relational table." (The file structure would be assumed.)

Thanks,
Dave
Followup:
ops$tkyte@ORA9IR2> create table t ( id int, x clob );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t values (
  2  1,
  3  'this is line one
  4  this is line two
  5  this is line three
  6  ' );

1 row created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select * from t;

        ID X
----------
--------------------------------------------------------------------------------
         1 this is line one
           this is line two
           this is line three

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace type row_type as table of varchar2(4000)
  2  /

Type created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace function clob_to_table( p_cursor in
sys_refcursor ) return row_type
  2  pipelined
  3  as
  4      l_clob clob;
  5      l_last number := 1;
  6      l_current number;
  7  begin
  8      loop
  9          fetch p_cursor into l_clob;
 10          exit when p_cursor%notfound;
 11
 12          for l_line in 1 .. 50000
 13          loop
 14              l_current := dbms_lob.instr( l_clob, chr(10), l_last, 1 );
 15              exit when (nvl(l_current,0) = 0);
 16              pipe row( dbms_lob.substr( l_clob, l_current-l_last+1, l_last )
);
 17              l_last := l_current+1;
 18          end loop;
 19      end loop;
 20      close p_cursor;
 21      return;
 22  end;
 23  /

Function created.

ops$tkyte@ORA9IR2> column column_value format a40
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select rownum, column_value
  2    from TABLE( clob_to_table( cursor( select x from t where id = 1 ) ) )
  3  /

    ROWNUM COLUMN_VALUE
---------- ----------------------------------------
         1 this is line one
         2 this is line two
         3 this is line three
Rating: 4
Thanks January 22, 2004
Reviewer: Dave from Lexington, MA
Thanks Tom. Believe it or not, I actually started to come to this conclusion,
but your example confirms with me that this is the best way.

Thanks again,
Dave
Rating: 4
Follow up January 22, 2004
Reviewer: Dave from Lexington, MA
I realized my original question was not correct (and somewhat foolish to
consider using an external table.) I am actually generically uploading a text
file but it is going into a BLOB column NOT a CLOB column.

But based on Tom's reply and poking around other postings on askTom, I came up
with the code I needed. It is not production code, but in case anyone finds it
useful, it seems to be working fine...

CREATE TYPE ROW_TYPE AS TABLE OF VARCHAR2(4000);

CREATE OR REPLACE FUNCTION BLOB_TO_VARCHAR2( p_blob in blob ) return row_type
  pipelined
  as
      l_str1      varchar2(4000);
      l_str2      varchar2(4000);
      l_leftover  varchar2(2000);
      l_chunksize number := 2000;
      l_offset    number := 1;
      l_linebreak varchar2(2) := chr(13)||chr(10);
      l_length    number;
  begin
      l_length := dbms_lob.getlength(p_blob);

      while l_offset < l_length loop

         l_str1 := l_leftover ||
utl_raw.cast_to_varchar2(dbms_lob.substr(p_blob, l_chunksize, l_offset));
         l_leftover := null;
         l_str2 := l_str1;

         while l_str2 is not null loop

          if instr(l_str2, l_linebreak) <= 0 then
             l_leftover := l_str2;
             l_str2 := null;
          else
             pipe row ( substr(l_str2, 1, instr(l_str2, l_linebreak)-1) );
             l_str2 := substr(l_str2, instr(l_str2, l_linebreak)+2);
          end if;

         end loop;

         l_offset := l_offset + l_chunksize;

      end loop;

      if l_leftover is not null then
       pipe row ( l_leftover );
      end if;

      return;
  end;
/

select * from table(select blob_to_varchar2(blob_col) from blob_table where pk =
1);
Rating: 4
Textfile insert and retrieval February 06, 2004
Reviewer: Djecal
Hi Tom,
I followed your steps in insertind textfile in the database, but for some
reason, when I execute the procedure, I received the following error message:
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 "MASTERDB.LOAD_FILE", line 11
ORA-06512: at line 1
Here is the entire code:
 create or replace procedure load_file (p_id in number,
     p_filename in varchar2)
    as

    l_resp  clob;
    l_bfile  bfile;
    begin
    insert into narrat_tb values (p_id, empty_clob() )
    returning resp into l_resp;
    l_bfile := bfilename ( my_resp,p_filename);
    dbms_lob.fileopen (l_bfile);
    dbms_lob.loadfromfile (l_resp,l_bfile, dbms_lob.getlength(l_bfile));
    dbms_lob.fileclose( l_bfile );
    end;
Before the procedure, I have created a table and a directory . I copied a text
file into the location referenced by the directory on the same machine as the
Oracle database. Please tell me what I'm doing wrong for the procedure not to
find the directory.
Followup:
for some reason?  or for the reason that either the directory or file does not
actually exist :)

that is what it is telling you.

You have not given us a complete example, so I cannot explain further other than
to say "you didn't do what you said"

Make sure the case of the directory object is correct...  if you

create directory my_files as ...

then make sure you used 'MY_FILES' in uppercase.  Also, make sure the directory
is in fact ON THE SERVER.  Also make sure the oracle account can actually read
that directory.  And likewise for the file.
Rating: 4
Textfile insert and retrieval February 06, 2004
Reviewer: Djecal
Hi Tom,
I followed your steps in inserting textfile in the database, but for some
reason, when I execute the procedure, I receive the following error message:
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 "MASTERDB.LOAD_FILE", line 11
ORA-06512: at line 1
Here is the entire code:
 create or replace procedure load_file (p_id in number,
     p_filename in varchar2)
    as

    l_resp  clob;
    l_bfile  bfile;
    begin
    insert into narrat_tb values (p_id, empty_clob() )
    returning resp into l_resp;
    l_bfile := bfilename ( my_resp,p_filename);
    dbms_lob.fileopen (l_bfile);
    dbms_lob.loadfromfile (l_resp,l_bfile, dbms_lob.getlength(l_bfile));
    dbms_lob.fileclose( l_bfile );
    end;
Before the procedure, I have created a table and a directory . I copied a text
file into the location referenced by the directory on the same machine as the
Oracle database. Please tell me what I'm doing wrong for the procedure not to
find the directory.
Rating: 4
clob insert and retrieval February 10, 2004
Reviewer: djecal
Thanks Tom,
I decreate table narrat_tb (
id int primary key,
resp  clob)
tablespace users;

create or replace directory
MY_FILES as 'd:\narrative\textfiles\resp';

create public sysnonym MY_FILES for MY_FILES;

create or replace procedure load_file (p_id in number,
             p_filename in varchar2)
    as
 l_resp clob;
 l_bfile bfile;
begin
insert into narrat_tb values (p_id, empty_clob() )
returning resp into l_resp;
 l_bfile := bfilename ( 'MY_FILES',p_filename);
 dbms_lob.fileopen (l_bfile);
dbms_lob.loadfromfile (l_resp,l_bfile, dbms_lob.getlength(l_bfile));
dbms_lob.fileclose( l_bfile );
 end;
***** Then, I went to the physical drive D and created the following path
D:\narrative\textfiles\resp to which I copied a file called resp04.txt
Finally, when I execute the procedure using the following statement
exec load_file (1,'resp04.txt');
I received the following error message:
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 "MASTERDB.LOAD_FILE", line 11
ORA-06512: at line 1

cided to provide you with the entire code and the error messages.
Followup:
sorry - but once again, the only thing I can say is

a) d: is not on the server itself or
b) the lowly privileged account that is 'oracle' does not have access to this
path or
c) the file really does not exist

Here is your script, cut and pasted, showing this works (when run ON THE SERVER
so the file actually exists ON THE DATABASE SERVER)

ops$tkyte@ORA9IR2> create table narrat_tb (
  2  id int primary key,
  3  resp  clob)
  4  /

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace directory MY_FILES as '/tmp/';

Directory created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create public sysnonym MY_FILES for MY_FILES;
create public sysnonym MY_FILES for MY_FILES
              *
ERROR at line 1:
ORA-00901: invalid CREATE command

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace procedure load_file
  2  ( p_id in number,
  3    p_filename in varchar2)
  4  as
  5   l_resp clob;
  6   l_bfile bfile;
  7  begin
  8  insert into narrat_tb values (p_id, empty_clob() )
  9  returning resp into l_resp;
 10   l_bfile := bfilename ( 'MY_FILES',p_filename);
 11   dbms_lob.fileopen (l_bfile);
 12  dbms_lob.loadfromfile (l_resp,l_bfile, dbms_lob.getlength(l_bfile));
 13  dbms_lob.fileclose( l_bfile );
 14   end;
 15  /

Procedure created.

ops$tkyte@ORA9IR2> host rm -f /tmp/resp04.txt

ops$tkyte@ORA9IR2> exec load_file (1,'resp04.txt');
BEGIN load_file (1,'resp04.txt'); END;

*
ERROR at line 1:
ORA-22288: file or LOB operation FILEOPEN failed
No such file or directory
ORA-06512: at "SYS.DBMS_LOB", line 504
ORA-06512: at "OPS$TKYTE.LOAD_FILE", line 11
ORA-06512: at line 1

ops$tkyte@ORA9IR2> host echo hello world > /tmp/resp04.txt

ops$tkyte@ORA9IR2> exec load_file (1,'resp04.txt');

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> select * from narrat_tb;

        ID RESP
----------
--------------------------------------------------------------------------------
         1 hello world
Rating: 4
clodb data type insert and retrieval February 10, 2004
Reviewer: A reader
Sorry, here is the entiere script:
create table narrat_tb (
id int primary key,
resp  clob)
tablespace users;

create or replace directory
MY_FILES as 'd:\narrative\textfiles\resp';

create public sysnonym MY_FILES for MY_FILES;

create or replace procedure load_file (p_id in number,
             p_filename in varchar2)
    as
 l_resp clob;
 l_bfile bfile;
begin
insert into narrat_tb values (p_id, empty_clob() )
returning resp into l_resp;
 l_bfile := bfilename ( 'MY_FILES',p_filename);
 dbms_lob.fileopen (l_bfile);
dbms_lob.loadfromfile (l_resp,l_bfile, dbms_lob.getlength(l_bfile));
dbms_lob.fileclose( l_bfile );
 end;
***** Then, I went to the physical drive D and created the following path
D:\narrative\textfiles\resp to which I copied a file called resp04.txt
Finally, when I execute the procedure using the following statement
exec load_file (1,'resp04.txt');
I received the following error message:
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 "MASTERDB.LOAD_FILE", line 11
ORA-06512: at line 1

I am still having problems with the execution.  Thanks!!
Rating: 5
load text files March 15, 2004
Reviewer: A reader
Hi

I have around 100 shell scripts which I would like to store in a table to
centralize administration. I was wondering if the procedure is the only way or
is there anyway with SQL*Loader to load 100 text files to the database?
Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:627223659651
March 15, 2004 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.
Rating: 4
Global non partitioned vs global partition index March 15, 2004
Reviewer: sam from NJ USA
Tom,

Do u know any real life case where partitioned global index has advantage over
non partitioned global index ??

Thanks
Followup:
absolutely...

biggest is decreased concurrency issues in a high modification environments
(more root blocks, more right hand sides).

increased maintainability (divide and conquer)

potential decreased LIO due to a height of 3 instead of 4 (possible, not
promised)

more parallel options (in earlier releases the ONLY way to get parallel index
operations)
March 15, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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://download-west.oracle.com/docs/cd/B10501_01/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.
Rating: 5
deletion of archive logs with backup. February 06, 2004
Reviewer: concerned rman man from TX
Tom,

Is it really a good idea to allow RMAN to delete archive logs once they are
backed up?  (delete all input).
I see this as a recipe for disaster.  In the event my backup is taken, and not
put to tape immediately, and the drive the backups are on is lost, I now can
only restore from my prior backup (that was put to tape), and now I have no
archive logs to apply to that old backup since they were deleted with the backup
that is gone.
Followup:
it is up to you.  most people would use rman to put the archives to tape in the
first place.

How's about this -- what if you lose the archives you were GOING to backup
before you backed them up?  Hmm - same situation as you describe above.

You want to multi-plex your archives probably.
You want to get them to tape ASAP.

I see no need to back them up to yet another disk -- just multiplex them and get
them to tape ASAP (be nice to have redundant tapes at this point) and remove the
disk based ones when you feel comfortable doing so.  I personally like to keep
the archives on disk until starved for space -- to facilitate speedy recovery
when needed.
Rating: 4
Backup / Recovery February 09, 2004
Reviewer: Don Morrison from Indianapolis, IN USA
I guess I consider myself spoiled, with a SAN setup to be able to put the
tablespaces in backup mode, break off a copy of the database and plop it
anywhere I need to within 45 minutes using simple Oracle/UNIX scripts.  I've
used this rebuild from a catastrophe, and to update a development server at a
moments notice.

My Unix Admin guy and I have though this thing out and all of our production
servers use this technology.  Side effect is that a SAN is PRICEY! and you loose
a lot of disk to make such a thing happen, but with what it can do it has paid
for itself many times over
Rating: 5
IMAGE COPIES to tape not possible with RMAN March 15, 2004
Reviewer: reader
Why RMAN is designed to write IMAGE COPIES only to disk? Why can't it write to
tape? Is there any special reason? Thanks.
Followup:
writing to tape is "difficult" at best.  It is highly OS dependent.  It wouldn't
be excessively useful to have individual file images on a tape (you'd have to
rewind, seek forward, looking for files -- sort of like having multiple tars on
a single tape image, not very useful).

You need a tape management system to effectively store and use gigabytes of
information on tape.
March 15, 2004 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.
Rating: 3
Interesting Scenario on 9i... March 15, 2004
Reviewer: denni50 from NA
Hi Tom

I mentioned, these past weeks, about not having the
tkprof tool in version 9.0.1.1.1.

Been trying to determine cause of slow performance,found
documentation regarding optimizing 9i on NT and performed
all the procedures outlined for efficient use of system
resources....configuring NT to be an App Server rather
than file server....etc

I found the Tuning/SQL Analyzer Tool that came with the 9.0.1.1.1 installation
so installed that instead.

It's a pretty neat gui tool that does trace/tkprof and
statistics all in one.

I came upon a curious situation when running a sql statement through the
analyzer tool.

The optimizer parameter in the pfile is set to RULE...
however in the SQL Analyzer it showed optimizer mode
chosen was "CHOOSE First Rows"?

I am not using a spfile:
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> select isspecified, count(*) from v$spparameter group by isspecified;

ISSPEC   COUNT(*)
------ ----------
FALSE         250

SQL>

I prefer to stick with pfile so I can modify parameters
as needed.

Why is the SQL Analyzer choosing an optimizer mode that is
not specified in the pfile.

downloaded 9ir2 haven't had the chance to upgrade.

thanks
Followup:
the sql analyzer is reporting WHAT HAPPENED. it (sql analyzer) did not choose
the optimizer mode.

sessions can change their optimizer goal -- but, i'm not sure what "CHOOSE First
Rows" means really as CHOOSE is a mode and FIRST_ROWS is a mode but choose first
rows isn't...

Not sure of the context there.
Rating: 4
thanks Tom... March 15, 2004
Reviewer: denni50 from NA
for always catching my blunders...
I meant "COST First Rows"....got so many parameters
flowing through my synapses I took the first "C" that
triggered an electrical impulse.

As I fiddled with the Analyzer I saw the menu where you
choose the optimizer mode you want explained...i.e.:
Choose
First_Rows
All_Rows
Rule......etc...so that explains the First_Rows scenario.

which brings me to another tangent....
the optimizer_index_cost_adj has a default value of 100.
I understand this value can be just as expensive as a full
table scan and should be lowered.
Is there a ball park value that I should try and test?

I read 10g does away with RULE Optimizer altogether....
it'll be "light years" before we ever upgrade to 10g.

hey Tom...was going through some of your archived stuff
and found the picture of the galaxy with the database at
the center...like a "black-hole"(blackbox)...maybe
Hubble will send back some snapshots!!(chuckle).
Followup:
if you have my book "Effective Oracle by Design", I go over
optimizer_index_cost_adjust/caching and what they do.  There is an excellent
white paper as well
http://www.evdbt.com/SearchIntelligenceCBO.doc
out there
Rating: 4
forgot to mention... March 15, 2004
Reviewer: denni50 from NA
I know the optimizer_index_cost_adj only works with CBO..
I plan to change the Optimizer to CHOOSE since I've
Analyzed my tables and Indexes.

see Tom i'm learning this stuff!
Rating: 5
Wow!!!! March 15, 2004
Reviewer: denni50 from NA
thanks Tom....

printed out the CBO.doc link you provided.
Clearly and simply illustrated and explained.
Anyone wanting to learn and understand the differences
between RBO and CBO should most definitely read this paper.
March 15, 2004 Jan -- Thanks for the question regarding "Disabling PK drops Index...", version 9.1.2
You Asked
If I disable PK on a table, the Oracle will drop an index used for enforcing
uniqueness, all information about that index will disappear from dba_indexes,
dba_ind_columns. If I enable the PK, it will recreate the index with the same
name and parameters as it was before. my question is, where is the information
about an index stored during the PK is disabled?

Thanks, Jan
and we said...
It uses the defaults - it is not "keeping" the original information anywhere.
That -- and it doesn't "have" to drop the index either.  Consider:

ops$tkyte@ORA9IR2> create table t ( x int );
Table created.

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

ops$tkyte@ORA9IR2> alter table t add constraint t_pk primary key(x);
Table altered.

here the index is named t_idx and the constraint t_pk, we know t_idx is the
constraint index because:

ops$tkyte@ORA9IR2> drop index t_idx;
drop index t_idx
           *
ERROR at line 1:
ORA-02429: cannot drop index used for enforcement of unique/primary key

ops$tkyte@ORA9IR2> alter table t disable constraint t_pk KEEP INDEX;
Table altered.

ops$tkyte@ORA9IR2> select index_name from user_indexes;

INDEX_NAME
------------------------------
T_IDX

so that shows the index doesn't have to disappear, but we can make it go away
now:

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

ops$tkyte@ORA9IR2> alter table t enable constraint t_pk;
Table altered.

ops$tkyte@ORA9IR2> select index_name from user_indexes;

INDEX_NAME
------------------------------
T_PK

and when we re-enable, it named the index by the default name...
March 15, 2004 -- Thanks for the question regarding "External Tables - Backup", version 9.2
You Asked
Hi Tom,

External tables are a real boom, but i have a doubt about it. With the external
tables how should one proceed with Backup and recovery. Is it too much of work
for DBA in terms of Maintainance, are there any special things to be taken care

Regards
RK
and we said...
you use external tables to load data.

you do not use external tables to hold live data you are going to query over and
over and over.

there is no need therefore to backup the files behind external tables -- the
data is quite transitory and makes it way ASAP into the real tables.  they are a
much easier "sqlldr", not something to query in applications.
March 15, 2004 -- Thanks for the question regarding "Truncation of numbers", version 8.1.7
You Asked
Hello Tom,

I have this problem in SQL and PL/SQL:

select floor(ln(power(2,32))/ln(2)) as result from dual;

RESULT
------
    31

Can I change something in the SQL to get the right answer? Or is there another
datatype to use to prefend truncation?

Thnx Hans.
and we said...
you are getting the "right" answer.  the right answer is 31 because the result
of that math is 31.9999999999999999999999999999999999999999<something.....>

you have 38 digits of precision here.  that is not 32 given a finite precision.

the problem lies in the fact that ln(2) is not a rational number (in fact, it is
a super special kind of number -- a transcendental number), it cannot be
accurately stored in any computer system with finite precision.  In fact, it is
said that using a fixed-size font (as I do here on this site), it can't be
written on a piece of paper as big as the universe.

ops$tkyte@ORA9IR2> select floor(ln(power(2,32))/ln(2)) as result from dual;

    RESULT
----------
        31

ops$tkyte@ORA9IR2> select 32-(ln(power(2,32))/ln(2))*100000000000000 as result f
rom dual
  2  where 32-(ln(power(2,32))/ln(2)) <> 0
  3  /

    RESULT
----------
-3.200E+15

see, there is some minute difference between the two....
March 15, 2004 chaitanya -- Thanks for the question regarding "Physical Reads on a DataWarehousing-Solution", version 8.1.7
You Asked
Hi Tom,

We have a Datawarehousing type of application,  the DBA's are complaining that
the application is doing a lot of physical reads.

The scenario is as below:
1.  We have a load program which loads data to the target database(oracle).
Before the load, lookup caches are created by quering on the target database.
Before the load the input data is compared against the lookup to decide the
update strategy. Building the lookup cache eats up about 85% of the time.
Volume stats:
input : 50,000 records on an average
lookup record count: 20 million [5 columns]
target table: 35 million records [25 columns]

I think in datawarehousing application, this is the way to go about and the
reads are necessary I would like to know your views on the same ?

As an alternative, instead of building the lookup cache the target database has
to be queried for every incoming record, this i think would be an overhead.
What is your view on this alternative ?

2nd Question
------------
We have a situation wherein the application (same scenario described above) used
to take 30 minutes daily,  now this is taking double the time.  We checked that
there is no change in oracle paramters, all the indexes are alalyzed, execution
plan are the same for the queries.
During the weekend a cold backup of the database was taken and the same was
restored (this was done to do some application testing), could this have caused
the same application to perform poorly ?

What are the items that needs to be checked ?

Waiting to hear your expert views.

Thanks and Regards
Chaitanya
and we said...
I use constraints and let the database do the work.  why are you replicating
inherit database functionality???

You load 50,000 records...
Yet you must load up 20,000,000 records first???

Use the database -- databases were BORN TO JOIN, as tigger likes to say "it is
what they do best".

No, backing up a database won't cause it to "run slower".  You need to employ
set based processing, let the database do what it does best and erase as much
procedural code as humanly possible!

Use tkprof to see what your low hanging fruit is right now but the only way to
make this "go fast" is to let the database *do its job*.  Don't second guess it,
it is rather good at doing "database things"
March 15, 2004 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.
Rating: 5
direct io February 19, 2004
Reviewer: reader
(1) If I have asynch io turned on for the server, is it possible to to mount a
particular file system on direct io as other file system can continue to be
asynch io? Thanks.

(2) If above is possible, do you recommend the file system containing redo logs
be on direct io?
Followup:
1) async IO and direct IO are two entirely different concepts.  Yes, you can use
async IO with or without direct IO.  They really have nothing to do with
eachother.

2) sure -- especially that one.
Rating: 5
direct io February 19, 2004
Reviewer: David
Does direct io mean "write through cache"? I thought LGWR already does "write
through cache" and if so why do we need to mount the file system containing
redologs on direct io? May be I am confused with direct io concept? could you
help me to understand? Thanks.
Followup:
lgwr does "sync IO", it waits for the write to be confirmed "on disk".  It still
would go through buffer cache mechanics at the OS level.

lgwr just uses the OS, the OS will either buffer and flush or just buffer and
flush later.  directio just turns off that cache.
Rating: 5
Interesting concept. Thanks. February 19, 2004
Reviewer: David
Is there a way DBA can find out if the file system is mounted with direct IO or
not using some "special" unix command? Thanks again.
Followup:
tell them to look in their fstab (on solaris).  it is an option to "mount"
Rating: 4
Sync / Async IO February 20, 2004
Reviewer: noel seq from mumbai, India.
Very Interesting information.

How do I know if my operating system is using Sync IO or Async IO. Is there a
command in Unix. How do we check in Windows NT.

noel.
Followup:
depends on the OS, ask your system admin, they would be the ones that would have
configured it (or not)
Rating: 5
But.... February 20, 2004
Reviewer: Gururaj Kulkarni from Bangalore,India
Thanks to Ravi for bringing this up in this forum. Thanks to you Tom for your
explanation with an example.

I am referring to your answer here.

"   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."

From Database how would I know the cause of slow ?

Thanks
-Gururaj
Followup:
you'd see waits for checkpoints most likely as dbwr would not be able to keep up
with the load.
Rating: 3
Asynch I/O March 15, 2004
Reviewer: A reader from NJ, USA
Tom,

Is it what some books call as blocking and non-blocking I/O. ie.

Synch I/O = Blocking I/O where a process waits (does not return) till the O/S
completes the I/O
Asynch I/O = Non-Blocking I/O where a process does not wait for the O/S to
complete the I/O, but rather continues with its processing and the O/s sends
back a signal to the process to indicate the I/O completion.

Please confirm
Followup:
correct
Rating: 5
File_systemio_options March 15, 2004
Reviewer: Shankar from Houston, TX
I am confused about this parameter. What should a dba set this parameter to
achieve asynch io or not? Does a dBA need to be concerned about this parameter?
It was not there prior to 9i. why it is so now in 9i? Thanks.
Followup:
it is highly OS dependent.  you should not set it unless directed to by support
in the tracking down of a bug really.
March 15, 2004 Getachew -- Thanks for the question regarding "Making Oracle Case insensitive ", version 9.2.0.4
You Asked
Tom:
    First and foremost, thank you for the great work you do for
the Oracle community.

My question is:

- We have a third party application we use for selecting specific
values from several tables. The character values stored in a field could be in
any of the following formats:
"Bell", "bell" , "BELL" , "beLL" etc..
1) Is there a way to force (change) values (either to UPPER or Lower case)
during a DML other than using triggers?
if not,
2) Is there a way to force a select query (other than using functions UPPER or
LOWER) to return all the values regardless of the case that a user enters?

Thank you.
and we said...
1) no

2) in 10g, yes.

ops$tkyte@ORA10G> create table t ( data varchar2(20) );

Table created.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> insert into t values ( 'Hello' );

1 row created.

ops$tkyte@ORA10G> insert into t values ( 'HeLlO' );

1 row created.

ops$tkyte@ORA10G> insert into t values ( 'HELLO' );

1 row created.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> create index t_idx on
  2  t( nlssort( data, 'NLS_SORT=BINARY_CI' ) );

Index created.

ops$tkyte@ORA10G> pause

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> variable x varchar2(25)
ops$tkyte@ORA10G> exec :x := 'hello';

PL/SQL procedure successfully completed.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> select * from t where data = :x;

no rows selected

ops$tkyte@ORA10G> pause

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> alter session set nls_comp=ansi;

Session altered.

ops$tkyte@ORA10G> alter session set nls_sort=binary_ci;

Session altered.

ops$tkyte@ORA10G> select * from t where data = :x;

DATA
--------------------
Hello
HeLlO
HELLO

ops$tkyte@ORA10G> pause

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> set autotrace on
ops$tkyte@ORA10G> select /*+ first_rows */ * from t where data = :x;

DATA
--------------------
Hello
HeLlO
HELLO

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=2 Card=1 Bytes=12)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (TABLE) (Cost=2 Card=1
Bytes=12)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (INDEX) (Cost=1 Card=1)
Review & Followup

Rating: 5
Making Oracle Case insensitive March 13, 2004
Reviewer: Getachew G. from Minneapolis, MN USA
Thank you again.

I will test and implement this in 10G in a few months.
I am not sure what the impact will be if I set the
two parameters(nls_comp and nls_sort) at the system
level.  But, thank you for providing me with a good information.
Followup:
it'll make everything case insensitive will be the impact.  Not sure I'd do it
system wide, but via alter sessions in the app or via an on logon trigger --
sure.
Rating: 4
no caveat ?! March 14, 2004
Reviewer: Gabe
Regarding #2 and the answer "in 10g, yes" ...

My observations/questions:
1. I'm assuming statistics were gathered automatically (10g feature)
2. Is RBO still available in 10g?
3. I know Cardinality is an estimation ... but why wouldn't the optimizer
estimate on the side of caution with "Card=<#rows in table>" (or some function
of it) rather than "Card=1"?
4. What was the point of first_rows? For the method to be deterministic, the FBI
has to be used (hints are just ... hints). In fact, any query against a table
with 3 rows should probably result in a FT ... here the FT would translate in a
bug in the application.

I'm highly suspicious of relying on indexes for business logic when querying
Oracle (for DML? ... yes, FTIs are very useful as you show in your last book)
... is my suspicion unfounded? Can Oracle really be forced to always use an
index (FBI or not)?

Thanks.
Followup:
1) no, there weren't any

2) yes, but not supported and not by default

3) small table.  forced the cbo to guess with the hint so the index would be
used.

4) to bias the index, table was too small to use an index "for real" otherwise.
I was trying to demonstrate that "yes, this case insensitive stuff can be as
efficient as case sensitive, given the correct indexes are in place"

I'm not relying on the index for anything here.  I was merely demonstrating that
the index can in fact be used transparently.

ops$tkyte@ORA10G> select /*+ FULL(t) */ * from t where data = :x;

DATA
--------------------
Hello
HeLlO
HELLO

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=3 Card=1 Bytes=12)
   1    0   TABLE ACCESS (FULL) OF 'T' (TABLE) (Cost=3 Card=1 Bytes=12)

Full scans work just as well.  I would never suggest on relying on a query plan
for getting data "just so".  Either plan works the same (although they could
return the data in very different order of course! but if you need a certain
order, that is what order by is all about)
Rating: 5
indeed ... March 14, 2004
Reviewer: Gabe
"I'm not relying on the index for anything here"
I can see now you don't.

So it is all about these:
  alter session set nls_comp=ansi;  --works in 9i
  alter session set nls_sort=binary_ci;

Guess the "binary_ci" is the thing that makes this a 10g solution only. I'll
look it up in the docs.

Thanks.
Followup:
binary_CaseInsensitive
       ^   ^
Rating: 5
March 14, 2004
Reviewer: Bhagat Singh from India
Hello Tom,
may be most of the peoply might not be having 10g right now but I was astonished
to see the answer, I was expecting some function based indexes and all any
special reason for this.

regards
Bhagat Singh
Followup:
curious -- you did see a function based index:

ops$tkyte@ORA10G> create index t_idx on
  2  t( nlssort( data, 'NLS_SORT=BINARY_CI' ) );

but any special reason for what exactly?
Rating: 4
LIKE operator for case-insensitive queries March 15, 2004
Reviewer: Gary from Sydney, Australia
Haven't got 10g, so cannot verify this, but it has been reported on another
website
(
http://www.quest-pipelines.com/pipelines/dba/index.asp
, that while = (and presumably <, > etc) all pick up on this
case-insensitive setting, the LIKE operator does not.

Also changing the nls_sort parameter means you cannot mix case-sensitive and
case-insensitive predicates in a query.
This means you have a choice of
1) Have both case-sensitive and case-insensitive indexes
2) Risk some queries not have the correct sensitivity index available on some
predicates
3) Choose whether the database as a whole should be case-sensitive or
insensitive, and design your indexes accordingly

Personally, I still prefer the 'old-tech' UPPER(col)='...' with a function based
index on the UPPER(col).
That allows the case-insensitivity to be applied at the lowest level so has more
flexibility.
For example, with a table with Address, City, State and Zip Code, you could
decide to be case-insensitve for Address, but case-sensitive for state because
you store them all in upper case.
Followup:
correct but an Oracle Text index can (even in 9i, 8i) do that.
March 15, 2004 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://

Rating: 3
Please type PL/SQL in text editor. January 21, 2004
Reviewer: Antony Paul from India
I am finding this site very useful. Usually I ask questions at OTN forums and
people provide links to this page. I have a request to you if possible type in
the PL/SQL code in text editor and copy this to the site instead of that from
SQL Plus which contains line numbers and causes difficulty incopy & paste.
Followup:
sorry -- cut and paste is the ONLY way to do it properly.  It is the only way to
avoid mistakes.

I use this all of the time myself.  using my favorite editor vi, it takes
exactly two commands to "fix the code"

:1,$s/.*WORLD>/xxxx/
:1,$s/.....//

You should have some equivalent in your text editor.  It takes me about 2
seconds to go from this page to something I can re-run in sqlplus.

Without using a cut and paste from sqlplus, you would not get my narrative
inlined with the output -- you'd see a script, followed by a ton of output and
have to page up and down.  i don't think it is too hard to perform two simple
edits to re-run the code.
Rating: 4
Please type PL/SQL in text editor January 22, 2004
Reviewer: Antony Paul
 I learned regular expressions to remove numberings and now using it in a text
editor to remove numberings and then copy to SQL Plus.

Antony Paul
Rating: 3
Tom doesn't listen to his users January 23, 2004
Reviewer: Mikito Harakiri
1. Not everybody uses vi.
2. Not every editor has regular expressions in it.
3. SQLPlus is the client that gives a bad reputation to oracle and oracle GUI
development abilities, in particular. See
http://troels.arvin.dk/db/rdbms/links/
"Annoying Oracle".
Followup:
Mikito -- the contrarian.

anyway -- assume I didn't listen to them.  Hmmm, would this place even exist?
Rhetorical question, of course it wouldn't.  I listen to them all day, pretty
much all day.

Maybe you didn't listen to me -- the answers would NOT make sense unless they
were in fact cut and pastes.  Think about it.  it would just be a bunch of code
-- no results, no output from sqlplus.

If -- IF -- you cannot figure out how to get your editor to do those two global
changes -- THEN -- well, I feel somewhat "sorry for you".

You either have the worst editor on the planet or.....  well, I won't say the or
outloud.

Tell me this Mr. Harakiri -- if it were not for sqlplus -- how, how the heck
would this site even exist.  Tell me that.  I should have 5 meg pages with
screen shots of CHARACTER MODE DATA???

funny thing is, people keep emailing me junk like that.  screen shots of their
windoze screen -- 1, 2, 3,... N megabytes big -- think nothing of it.  Kills me
when they do that (not that I asked them to, they just send it)

I find sqlplus to be the most enjoyable of environments.

If it were not for the character mode sqlplus -- this site would not exist.

If it were not for the character mode sqlplus -- my books would not be very
readable.  full of "pretty pictures" no doubt but not as good.

If you don't like it -- well, guess you don't have to come here and read.

Think about it -- if I used a gui -- you wouldn't even be able to cut and paste
it would you.  Nope, not really.

You should be saying "thank goodness for this simple, yet complete tool"
Rating: 5
Tom's Miracle Ear January 23, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
> 1. Not everybody uses vi.
True, but I found no place where Tom claimed this to be true - only that it was
his favorite editor.

> 2. Not every editor has regular expressions in it.
True, including the "alternative" from the link you provided.  But surely line
numbers can be removed without using regular expressions?  It may be easier to
do with regular expressions, but not required.  Besides, most of Tom's examples
are not 500 lines long.

> 3. SQLPlus is the client that gives a bad reputation to oracle...
http://troels.arvin.dk/db/rdbms/links/
A link to a page on the site of a guy who is a student at "IT University of
Copenhagen", says "PostgreSQL - my favorite, because of the nice feature set",
and doesn't like sql plus (listing a java/jdbc utility as an alternative)...  I
have to say that my experience with Oracle has been (apparently) radically
different from that of Mr. Arvins.  Different strokes for different folks I
guess.  However, saying that Tom doesn't listen to his users is plain silly in
my opinion.  I believe he has been rather indulgent in your case in fact.  I do
find myself wondering what keeps bringing you back, but, like I said earlier,
different strokes for different folks.

- Mark
Rating: 3
sql*plus from my point of view ... January 24, 2004
Reviewer: j.
... after reading tom's books and customizing sql*plus the way he described it
became my tool of choice whenever i've to do things like spooling and tracing
and such.

i prefer it to sql navigator for instance since sql*plus does *NOT* parse a
statement *TWICE* for executing it just *ONCE* ... ;o)
Rating: 3
Re: Tom's Miracle Ear January 24, 2004
Reviewer: Troels Arvin from Copenhagen, Denmark
Mark, what are you getting at?
Rating: 5
What am i getting at... January 24, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
Hi Troels,

I did not do a very good job of just trying to point out that you have a certain
viewpoint or perspective that may (or probably) is different from the "typical"
Oracle user or reader of AskTom.  I felt that your perspective should be taken
into consideration when evaluating your statements, etc., just as mine should,
Tom's should, etc.

I guess my main confusion was that I don't see how your feelings about sql plus
really substantiate mikito's claim that "SQLPlus is the client that gives a bad
reputation to oracle and oracle GUI development abilities, in particular."  Some
of the things you mention like command line completion are not important to me,
but perhaps they are to other people.  So, for those people, they may be biased
against sql plus from the start is my point stated differently perhaps.  If I
provided a link to a page on a person's site that said sql plus is the greatest
tool in the whole world, would that be effective?  I see that as being no more
effective than a link to a page on a site that says sql plus is terrible.

As I said in my other post, I believe your experiences with Oracle have been
different from mine.  As a very pro-Oracle person, I do realize that not
everyone has the same perspective as me.  That should be taken into account as
well.

In any case, I do hope that I did not cause any offense as certainly none was
intended.  I apoligize for my lack of clarity which probably has not been
further focused by this post.

- Mark
Rating: 3
What century do you guys live? January 26, 2004
Reviewer: Mikito Harakiri
I don't know what to say. A relational DBMS manages data in the relational data
model, whose primary construct is the table. And you advocate the client that
cannot even properly render a table???
Followup:
huh?

I don't know what to say to your not knowing what to say since I've not any clue
what you mean by "render a table".

If you want a spreadsheet -- have fun with excel.

If you cannot figure out how to cut and paste and use your editor to fix up
spoon fed, complete examples -- well, all I can say is "sorry, guess the gui has
made the brain goto mush".

Just think, I could post pictures of a really pretty gui.  Would that not be
sooooo 2004 and hip.  It would also make each page 50meg in size and sort of
obviate this entire discussion since cutting and pasting would just be "so not
happening"

I advocate people use the tools they like.

I like sqlplus

You do not have to like sqlplus.  (hey -- you don't even like analytics, not
"pure enough" for you - circular arguments here Mikito, you like what you like
-- i obviously like what I like)

whatever, last followup i'll make on this.  have a nice day.
Rating: 5
ordering stringagg output March 15, 2004
Reviewer: bob from PA
Tom,

I hope you still have energy to follow up on a real question/comment.  Using
StringAgg, how can I order the concatenation so that it occurs alphabetically?
Followup:
You'd have to "save up" the inputs and then sort them and then concatenate them.
 they come in random order to stragg...
March 15, 2004 ALWYN -- Thanks for the question regarding "DBMS_SQL and array initialisation", version 8.1.7
You Asked
Hello Tom,

When DBMS_SQL is used with collections in a loop,  I discovered that the first
index in each iteration is not necessarily 1.  The following scenario
demonstrates what I mean:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CREATE TABLE locations (
   locn_code    VARCHAR2(03)
  ,locn_name    VARCHAR2(100)
)
/

INSERT INTO locations VALUES ('PER','PERTH')
/
INSERT INTO locations VALUES ('FRE','FREMANTLE')
/
INSERT INTO locations VALUES ('BRO','BROOME')
/
INSERT INTO locations VALUES ('GER','GERALDTON')
/
INSERT INTO locations VALUES ('BUN','BUNBURY')
/
INSERT INTO locations VALUES ('DEN','DENMARK')
/
INSERT INTO locations VALUES ('ESP','ESPERANCE')
/

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
DECLARE
   C_BATCH_LIMIT         CONSTANT INTEGER := 1;
   g_sql                 VARCHAR2(200);
   g_cursor_id           PLS_INTEGER;
   g_coll_locn_name      DBMS_SQL.VARCHAR2_TABLE;
   g_result              PLS_INTEGER;
BEGIN
   DBMS_OUTPUT.PUT_LINE('max fetch size='||C_BATCH_LIMIT);
   g_sql := 'SELECT locn_name FROM locations';
   g_cursor_id := DBMS_SQL.OPEN_CURSOR;
   DBMS_SQL.PARSE(g_cursor_id, g_sql, DBMS_SQL.NATIVE);
   DBMS_SQL.DEFINE_ARRAY(g_cursor_id, 1, g_coll_locn_name, C_BATCH_LIMIT, 1);
   g_result := DBMS_SQL.EXECUTE(g_cursor_id);

   <<outer_loop>>
   LOOP
      g_coll_locn_name.DELETE;
      g_result := DBMS_SQL.FETCH_ROWS(g_cursor_id);
      DBMS_SQL.COLUMN_VALUE(g_cursor_id, 1, g_coll_locn_name);
      EXIT outer_loop WHEN (g_result = 0);

      <<for_loop>>
      FOR i IN g_coll_locn_name.FIRST..g_coll_locn_name.LAST LOOP
         DBMS_OUTPUT.PUT_LINE('i='||i||' locn_name='||g_coll_locn_name(i));
      END LOOP for_loop;

      EXIT outer_loop WHEN (g_result < C_BATCH_LIMIT);

   END LOOP outer_loop;

   DBMS_SQL.CLOSE_CURSOR(g_cursor_id);

END;

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. Why is g_coll_locn_name.FIRST not 1 every time as the array has been cleared?
2. If a few million records are being processed,  does it matter that FIRST is
not 1 every loop?
3. Can FIRST be forced to 1 every loop?

Alwyn
and we said...
that is just the way dbms_sql works.  it'll put N rows at the "rowcount" offset.

if you "clear out" the array each time, it'll not consume any undo memory.

But the first 100 rows will go into 1..100 the next 100 rows into 101..200 and
so on.

Just

DECLARE
   C_BATCH_LIMIT         CONSTANT INTEGER := 1;
   g_sql                 VARCHAR2(200);
   g_cursor_id           PLS_INTEGER;
   g_coll_locn_name      DBMS_SQL.VARCHAR2_TABLE;
   g_empty               dbms_sql.varchar2_table;
   g_result              PLS_INTEGER;
BEGIN
   ....

           -- at top of loop, before the fetch
           g_coll_locn_name := g_empty;
Review & Followup

Rating: 5
DBMS_SQL and array initialisation March 15, 2004
Reviewer: Alwyn from WA, Australia
Thanks for the simple explanation and clarifying that no extra memory is
consumed.  Is there any difference between using

g_coll_locn_name := g_empty
and
g_coll_locn_name.DELETE ?

Alwyn
Followup:
they are "the same" for memory.

however, coll.delete is "faster" (guess i'm showing my 'age', .delete was "new"
at one time -- it is very "old" now - .delete would be

a) faster
b) more appropriate

:)
March 16, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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%'
Rating: 3
OK March 14, 2004
Reviewer: Stella from Perth,Australia
Hi Tom,
Till I am not able to understand the concepts "application info and setting
context namespaces".
I see things like
 sql > set appinfo on
 and other related information.They seem to be scary for me.Could you please
explain them if you have time?
Please do write a followup.
Followup:
why are they "scary"?

application info (a very useful tool for instrumenting your code) and "context
namespaces" (a very useful tool for supplying bind variables to queries when you
cannot otherwise bind) are really "not related"

set appinfo on

just lets sqlplus tell other sessions what your session is doing.  appinfo on is
a sqlplus command to tell sqlplus "please use dbms_application_info to set the
client_info/action/module columns in v$session so others can see what script we
are running and so on".  It is useful so you can monitor the execution of a
script from another session.

Things are less scary if you read about them -- suggest you start with the
Oracle Concepts guide -- if you read that, retain 10% of it, you'll already know
90% more than most people do!
Rating: 3
Any Example Please March 16, 2004
Reviewer: Amir Ali from Chittagong,Bangladesh
Dear Sir,
Do you have any simple demos for "dbms_application_info.
 set_client_info".Can it affect other sessions? * We don't
have access to your book *.Kindly bear with us.
Please do write a followup.
Followup:
sure you have *access to my book*.  its for sale.

But anyway, you certainly have access *to the documentation*, otn.oracle.com has
it and we document stuff like this.

search this site for set_client_info, you'll see examples.  it sets the value
only in your session.

see the supplied packages guide (otn.oracle.com -> documentation) for all of the
details on dbms_application_info.
March 16, 2004 John -- Thanks for the question regarding "Lots of (Sum) s in a Group By", version 8.0.6
You Asked
Hi, Tom

I've got a problem creating a temporary table from a large table with 1.5
million records.
The temp table should have about 40000 records

Create table temp as
(select loc, team, year,
  sum(a) sales1,
  sum(b) sales2,
      ....
  sum(z) sales12,
  sum(a) - sum(aa) - sum(aaa) margin1,
      .....
  sum(z) - sum(zz) - sum(zzz) margin12
from master
group by loc, team, year)

The problem looks like a workspace problem as it doesn't even finish the query
in 4 hours, and that's all the spare time I have overnight to do housekeping
before the applications restart.
Is the problem the 24 (Sum)s or am I missing something

Thanks

John
and we said...
could be indicative of being ultra conservative on your sort_area_size.  Here is
an example on a modest Solaris box

two CPU:

Oct 24 15:37:33 aria-dev unix: cpu1: SUNW,UltraSPARC-II (upaid 1 impl 0x11 ver
0xa0 clock 400 MHz)
Oct 24 15:37:33 aria-dev unix: cpu3: SUNW,UltraSPARC-II (upaid 3 impl 0x11 ver
0xa0 clock 400 MHz)

and 2gig of ram (quite a few years old, definitely not state of the art, has
about 6 instances of Oracle running on it -- nothing fancy with the disks --
just "plain old disks"

So, I created a table T:

drop table t;

create table t ( loc varchar2(10), team varchar2(10), year int,
a int, b int, c int, d int, e int, f int, g int, h int, i int, j int, k int, l
int, m int,
n int, o int, p int, q int, r int, s int, t int, u int, v int, w int, x int, y
int, z int );

begin
    for l_loc in 1..200
    loop
        for l_team in 1..40
        loop
            for l_year in 1998..2002
            loop
                insert into t values ( 'loc' || l_loc, 'team' || l_team, l_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,26);
            end loop;
        end loop;
    end loop;
end;
/

insert /*+ append */ into t select * from t;
commit;
insert /*+ append */ into t select * from t;
commit;
insert /*+ append */ into t select * from t;
commit;
insert /*+ append */ into t select * from t;
commit;
insert /*+ append */ into t select * from t;
commit;

select count(*) from t;

1,280,000 records...

and then:

ops$tkyte@ORA806.WORLD> set timing on
ops$tkyte@ORA806.WORLD> alter session set sort_area_size = 104857600
  2  /

Session altered.

Elapsed: 00:00:00.00
ops$tkyte@ORA806.WORLD> create table t2
  2  as
  3  select loc, team, year,
  4             sum(a) sales1, sum(b) sales2, sum(c) sales3,
  5             sum(d) sales4, sum(e) sales5, sum(f) sales6,
  6             sum(g) sales7, sum(h) sales8, sum(i) sales9,
  7             sum(j) sales10, sum(k) sales11, sum(l) sales12,
  8             sum(m) sales13,
  9             sum(q)-sum(r)-sum(q) margin1,
 10             sum(a)-sum(f)-sum(w) margin2,
 11             sum(k)-sum(d)-sum(e) margin3,
 12             sum(c)-sum(s)-sum(r) margin4,
 13             sum(e)-sum(z)-sum(t) margin5,
 14             sum(j)-sum(y)-sum(y) margin6,
 15             sum(l)-sum(x)-sum(u) margin7,
 16             sum(o)-sum(w)-sum(i) margin8,
 17             sum(n)-sum(v)-sum(o) margin9,
 18             sum(j)-sum(u)-sum(p) margin10,
 19             sum(g)-sum(t)-sum(g) margin11,
 20             sum(n)-sum(g)-sum(h) margin12,
 21             sum(w)-sum(f)-sum(j) margin13
 22   from t
 23   group by loc, team, year
 24  /

Table created.

Elapsed: 00:02:28.50
ops$tkyte@ORA806.WORLD>

took about 2 1/2 minutes.  Normally, the sort area size is 102,400 bytes
(100k).  For this test, using a temp tablespace with 1m extents (that never
actually got used!) and a 100m sort area size.

Now, trying the same thing with a 10m sort area size, it is very different, I
let it run for 1/2 hour -- it took up about 1.4 gig of temp and I stopped it
(got bored, proved my point)...

So suggestion: increase sort_area_size in your batch session to some large
number to improve the performance of your very very large sort.  The smaller the
sort area size -- the smaller the swap pieces - the more temp you need -- the
longer it will take (lots of extra IO and all).

People tend to way undersize this and never change at the session level for
batch jobs.  It can make a TREMENDOUSLY huge difference though!
Review & Followup

Rating: 5
Illustrative; shows up with materialized views, too November 16, 2002
Reviewer: Daryl
Thanks for the nice example. I've seen this with refreshing materialized views
as well. With a default sort_area_size, it takes *forever*. Upping it to 100m
cut it to under 30 seconds.
Rating: 5
How about 9i December 06, 2002
Reviewer: A reader from India
Hi Tom

If I am using 9i with workarea size policy = AUTO. Then would Oracle take care
of dynamically sizing the sort_area_size according to the need of the session?

Thanks
Followup:
yes, according to the need of the session AND the currently available resources.
Rating: 5
Other operations March 16, 2004
Reviewer: Sanjaya Balasuriya from Colombo, Sri Lanka
Hi Tom,

You have proved the effect of "sort area size" for SELECTs.
But what about the effect "sort area size" on inserts and updates ?

From where "sort area size" is allocated ? From SGA or PGA or from the OS ?
And when is the "sort area size" used for query released ?
If I have set "sort area retained size" 0, the memory used for a query is
released immediately after the query ?

Thanks in advance.
Followup:
inserts can have selects

insert into t select ......

updates can have a query portion as well.

same with deletes

and merge

anything that "sorts".

The sort_area_size is allocated from the UGA.  The UGA is in the PGA in
dedicated server mode.  The UGA is in the SGA in shared server mode
(specifically the large_pool if one is allocated, else the shared_pool)

The sort area size is immediately shrunk down to sort area retained when the
sort operation is completed.  If sort area retained is not set (and would show
as "0") then sort area retained = sort area size.

If you have my book "Expert one on one Oracle" -- i cover memory allocations and
how/when they work in Oracle in some detail in the architecture chapter.
March 16, 2004 Mahomed -- Thanks for the question regarding "Oracle hot backup", version 7.3.4
You Asked
Hi Tom,

I am using the oracle hot backup facilty in ARCHIVELOG mode. I
would like some advice/guidance on a particular matter .

Currently I am performing a hot backup every day as follows :

1. Copy each tablespace in turn by setting tablespace into hot
backup mode , copy the tablespace files using unix copy command
and then finally releasing the tablespace from backup mode.
2. Force a log switch .
3. copy all the archived log files.
4. Backup the control file.

My database has the following tablespaces :

LIVE_DATA  - Has all tables for 1 owner
LIVE_INDEX - Has all indexes for the same owner
LIVE_TEMP  - Temp tablespace
RBS
SYSTEM
TEMP
TOOLS
USERS

My application consists of 1 owner/user and relies on all
objects from LIVE_DATA and LIVE_INDEX. Currently the backup
takes about 2.5 hours and I feel I am backing up too much data.
I would like to know If I can miss out backing up of the
following :

1. All the archived log files that have accumulated before the
start of the backup procedure.
2. The RBS, TEMP and LIVE_TEMP tablespaces. ( These take about
20 minutes each to backup).

The problem is I cannot easily try any new backup procedures on
my system as it has gone live and is a 24 hour operation with
very occassional down time. There is a down time due this
weekend for 8 hours but I would like to get some advice/guidance
from yourself before I try anything.

Regards,
Mahomed
and we said...
1) Well, hopefully you keep at least 2 backups -- the current
one you are making AND the last successful one you made.
Therefore, you would need all of the archive -- in the event the
backup you are making right now fails.

True story:  someone is making a hot backup.  only keeps the
current backup -- overwrites the last backup on the tape drive.

Disk fails in the middle of the backup.  They now have no
backups and a media failure!!!  They are hosed.  They cannot
recover the datafile that failed and was being backed up because
the backup they were making did not complete AND the backup they
had was destroyed.

I would archive all of the log files before beginning the backup
and copy those log files to where ever my last successful backup
was.  You would then take that tape out of the machine and put a
new one in.

Now, hot backup all of the tablespaces, after you do that,
archive all online redo logs and copy them to tape as well.  You
now have on tape something that can be recovered to a consistent
state with.  During the week, keep adding your archives to this
tape -- next week do the whole thing over again -- adding the
last archives to this tape and using the other tape you popped
out last week.

2) you had better backup rollback !!!! that is so critical, so
crucial. A backup without rollback is totally useless.

In 7.x as you have - before true tempfiles, you should backup
TEMP.  You could bypass it but it complicates the recovery
process and when you need to recover -- you want things to go
smoothly -- easily.  You don't want glitches and gotchas.  You
could recover without them (if they are true temporary
tablespaces without data).  In 8 with tempfiles (not datafiles,
tempfiles) you won't be backing them up (cannot back them up
actually).

2.5 hours is OK to run a backup... It isn't affecting the
performance of anything.  You might be able to make it go faster
by copying >1 datafile at a time if you have enough physical
devices....
Review & Followup

Rating: 4
Same situation... October 22, 2001
Reviewer: Sven Bleckwedel from Santos, SP - Brazil
Hi,

I had the same situation on my site and changed the backup  of the tablespaces
(datafiles and also the archived redologs, configuration files like
init<sid>.ora, password files and others...) to disk-to-disk.  The new backup
strategy had decreased the backup time (from 1 hour to 5 minutes) and the
possibility of instance problems (redo log increasing with the tablespaces in
backup mode and so on...).  Later, this backup area is sended to tape, and the
two copies of the same backup are guaranteed.  This is a good approach for
faster recovery, too...

Rgds,
Sven Bleckwedel
Rating: 4
Oracle hot backup May 31, 2002
Reviewer: Anthony, Cheuk Tung, LAI from Hong Kong SAR, China
Most of us would like to save time and effort, however, when disaster comes,
there is no shortcut to save.
This is a great approach to remind ourselves:
Keep 2 Backup copies and don't shutdown the database for backup indeed.

By the way, I believe that it is necessary to set up a staging machine for
backup and recovery plan rehearsal regularly. It likes a firefighting rehersal,
am i correct?
so as to keep our plan fresh in mind and keep people/team updated.
Followup:
Yes it is correct.

Here is a fun thing as well.  On this backup/staging machine -- do a restore.
Now, split your DBA team in half (assuming you have more then one DBA).  One DBA
is the "evil" DBA, the other the "good" DBA.  Evil DBA will do something to the
test machine to simulate a failure -- anything they want.  Good DBA must fix it.
 Good DBA wins if they can -- Evil DBA wins if they can fix it when good DBA
cannot.  Both lose if neither can recover from it (but they should learn from
that and be able to recover next time).  Now switch posititions.

You'll cover lots of ground that way (you'll find out how to recover from
anything)
Rating: 5
Hot backup question October 04, 2002
Reviewer: atul from india
Sir,

When in my system hot backup is on...I checked status colum from v$backup view
it shows active..
so when i query v$recover_file and v$recover_logs it shows no rows..
I want to know why?
because when you are doing hot backup some files are in recovery stage..
so we should get this info in v$recovery_file and v$recover_logs views..
or
we get this info whenever our database is mount but not open

Thanks.
atul
Followup:
the files are not in recovery mode, they are in hot backup mode.  huge, big,
total difference.

The files are NOT in need of recovery.
Rating: 5
Great Game Idea! January 23, 2003
Reviewer: Robert from Memphis, USA
Tom,

Thanks for the idea of the game!
I think playing this regularly would go far in making a crack DBA team, not to
mention fun.

Thanks,

Robert.
Rating: 5
tempfiles LMTs? January 24, 2004
Reviewer: Reader
Tom,

<quote> In 8 with tempfiles (not datafiles, tempfiles) you won't be backing them
up (cannot back them up actually).<quote>

I thought true temp tablespace is when we do with syntax
create temporary tablespace tempfile and it is possible only with a LMT? If so,
how is a true temp tablespace is possible with version 8 as we did not have LMT
then? Please correct me if mu understanding is wrong. Thanks for your valuable
time.
Followup:
c/8/8i/g
Rating: 4
Here goes... March 16, 2004
Reviewer: Invisible from UK
Appologies for waking this thread up again. (Such a simple question, it's
supprising the Search page didn't answer it. But anyway...)

We have an Oracle 8i database in ARCHIVELOG mode. Currently I perform a cold
backup (almost) every Friday. (And yes, it's on multiple tapes & all the
rest...)

This DB used to be almst totally unused, but as people get the training it's
becoming more utilised. Thus, it's now becoming harder to find a window where no
one is using it so I can shut it down. Anyway, I'd like to start doing HOT
backups instead.

So, the other week, I tried to take a hot backup of our production database. It
seemed to work perfectly. However, all that means is that I have a set of files
on tape and Oracle didn't give me any errors. ( = doesn't mean much.)

So I immediately decided to attempt to restore this backup to a totally seperate
PC, using only the hot backup I just made. (Seems like a good test to me.) And
guess what? I can't make it work! [*SO* glad I found this out NOW...]

All of our archived redo logs are always automatically put on tape anyway,
without human intervention. So all I did was to put each datafile into hot
backup mode, copy it, and take it out of hot backup mode again. Oh, and I asked
for a textual controlfile.

So, on my tape, I have:
 * Parameter file.
 * Trace file with the CREATE CONTROLFILE command in it.
 * All datafiles.
 * All archived redo logs (for the entire week since the backup, and also
predating the backup).

I put these files on my test PC, and edited the parameter file to reflect the
new file locations. I created an instance with ORADIM and tried to start it up.
No problem.

I edited the CREATE CONTROLFILE command to reflect the new file locations, and
tried to execute it.

BAM! No-go.

ORA-01503: CREATE CONTROLFILE failed.
ORA-01565: Error in identifying file <Redo01>
ORA-27041: Unable to open file.
OSD-04002: Unable to open file.

Of course, I never backed up the ONLINE redo - only the ARCHIVED redo. So *of
course* it can't find it - it isn't there!

I found the instructions for recreating the online redo logs... guess what? The
database has to be mounted first. And guess what you need to mount a database?
Yup - a controlfile!

After much reading of the Oracle documentation (and my magic Oracle book), it
appears that

-> you can recreate the controlfile - but only if you have the online redo logs.

-> you can recreate the online redo logs - but only if you have the controlfile.

How the heck do I get OUT of this catch-22??? (Surely this is something reeeealy
simple... but I can't figure it out!)

Thanks.
Followup:
You need to use "RESETLOGS" on the create controlfile statement.

What you've simulated is the total loss of production -- no current
controlfiles, no access to online redo logs.  It is as if your data center blew
up (worst situation).  Normally you have access to at least one copy of online
redo and a controlfile.

In this case what you do is

o restore the datafiles
o use the CREATE CONTROLFILE statement but use RESETLOGS
o use the RECOVER DATABASE USING BACKUP CONTROLFILE command
o when you've applied all of the archives -- alter database open resetlogs
  (that'll create the new redo files for you)
Rating: 5
Thanks. March 16, 2004
Reviewer: Invisible from UK
>What you've simulated is the total loss of production.

Yups. If I can recover from this then (presumably) I can recover from anything
less serious.

>You need to use "RESETLOGS" on the create controlfile statement.

Aahhh... I see.

I was worried that this would prevent me then applying the archived redo logs.
(Which would obviously be BAD.) But since you only actually "reset" them as you
open the database... that's not a problem.

Heh - I _knew_ it would be something simple!

Thankyou for your time.
March 16, 2004 BHUPENDRA -- Thanks for the question regarding "Horizontal tree traversing", version 8.1.7.4.0
You Asked
Tom,

My question is very simple. I have one table where I have parent_id and
child_id, in my process I know one Child_id but I have to find all children of
the parent of this Child.

I can do same query selecting same table twice. But I was wondering, whether I
can achieve same result without it or not ? I tried with tree walking but I
couldn't make it.

My table is somewhat like this

Parent_id         child_id
----------       ----------
   1                  2
   1                  3
   1                  4
   1                  5
   0                  1
   0                  10
   0                  11

Now if my process supplies parameter value (child_id = '2'), then query result
should be

Child_id
---------
    2
    3
    4
    5

Only thing I'm trying, is to avoid selecting same table twice.

Thank you.

Bhupendra.
and we said...
start with

tkyte@ORA8I> select * from t
  2   start with parent_id = ( select parent_id from t where child_id = 2 )
  3  connect by prior child_id = parent_id
  4  /

 PARENT_ID   CHILD_ID
---------- ----------
         1          2
         1          3
         1          4
         1          5
Review & Followup

Rating: 3
fetched twice? March 12, 2004
Reviewer: CA from Vancouver
I don't think it's possible without selecting from table t twice. Tom, your
example is selecting twice too and not really different than

Select * from t
where parent_id = (select parent_id from t where child_id=2)

 PARENT_ID   CHILD_ID
---------- ----------
         1          2
         1          3
         1          4
         1          5
Followup:
Umm, it is "selecting" from the table once (one query).  the plan simply gets
the parent and then goes.  In  light of proper indexes -- this is the "correct
thing to do".

Who cares if you

a) started at child
b) went up a level
c) went down the levels (eg: the hypothetical "i hit the table 'once'")

versus

a) start at child
b) went up a level (a and b are the subquery)
c) went down the levels

they are they "same".  It is not "fetching twice", it is just doing the least
amount of work possible to answer your question "show me the hierarchy for this
child".  there are no round trips here, one query over, one result set back
Rating: 2
March 13, 2004
Reviewer: SR from Delhi, India
A genius sometimes overlooks the simplest possible solution.  Tom’s answer
reminded me of the old story of Sir Newton - two separate doors for his big cat
and the small one ;-)
Followup:
er?  didn't quite follow
Rating: 2
Known answer. March 16, 2004
Reviewer: BHUPENDRA SHAH from USA
Answer given was known one. In my question, I specifically insisted on using
table only once. But the answer given had the solution, which I already know.

There is this guy commenting from Delhi, but it's vague. He didn't clarify how
to do it.

Anyway, my special thanks to Tom, for his time.

Tom, I have one more question to you. How to become a reviewer on your site?

In future if anybody has any idea about this, please let me know.
Followup:
you are using the table once.  sigh.  oh well.  cake, eating it, lots of
metaphors popping into me head here.

you are are "reviewer" on this site right now, you just made a "review"
Rating: 5
When in doubt- Analytics! March 16, 2004
Reviewer: Chuck Jolley from OKC, OK USA
chajol@tax.experior>select * from t order by 2, 1;

    PARENT      CHILD
---------- ----------
         2          1
         3          2
         1          3
         2          4
         3          5
         1          6
         2          7
         3          8
         1          9

9 rows selected.

Elapsed: 00:00:00.00
chajol@tax.experior>select tt.parent, tt.child
  2    from (select parent,
  3                 child,
  4                 first_value(parent) over (order by decode(child, &ar_child,
parent, -1) desc) our_parent
  5            from t ) tt
  6   where parent = our_parent;

Enter value for ar_child: 2
old   4:                first_value(parent) over (order by decode(child,
&ar_child, parent, -1) desc) our_parent
new   4:                first_value(parent) over (order by decode(child, 2,
parent, -1) desc) our_parent

    PARENT      CHILD
---------- ----------
         3          2
         3          5
         3          8

Elapsed: 00:00:00.00
chajol@tax.experior>

Actually I think it's silly, but there it is: only one select.
chuck
Rating: 5
March 16, 2004
Reviewer: Chuck
I should have said only 1 "select from t"
chuck
Rating: 3
OK March 17, 2004
Reviewer: Kumar from Pune,India
Dear Tom,

 How does the following query proceed?Any explanation or
pseudocode you may provide?Particularly I am not clear with
"connect by " clause.

tkyte@ORA8I> select * from t
  2   start with parent_id = ( select parent_id from t where child_id = 2 )
  3  connect by prior child_id = parent_id
  4  /

 PARENT_ID   CHILD_ID
---------- ----------
         1          2
         1          3
         1          4
         1          5
March 16, 2004 Leah -- Thanks for the question regarding "DBMS_UTILITY example", version 8.1.5
You Asked
During our software build, some database objects
such as packages, views, etc. become invalid after compilation.
You've suggested in one of your q&a's to use
dbms_utility.compile_schema to recompile invalid objects in the
order of their dependencies. Can you please give an example
using the utility for this purpose?
and we said...
The goal of the compile schema procedure is to attempt to make
valid all INVALID procedures, packages, triggers, views, types,
and so on in a schema.  This procedure works (in 8.1.6 and
before but not 8.1.7) by using the
SYS.ORDER_OBJECT_BY_DEPENDENCY view which is supposed to return
objects in the order they depend on eachother.  If we compile
the objects in the order this view returns the data ¡V at the
end, all objects should be valid.  This procedure runs the ALTER
COMPILE command as the user who invoked the procedure (invokers
rights).

It should be noted that COMPILE_SCHEMA demands you pass in a
case sensitive username ¡V if you call:

scott@TKYTE816> exec dbms_utility.compile_schema( ¡¥scott¡¦ );

It is probable that nothing will happen (unless you have a lower
case user named scott.  You must pass in SCOTT.

There is however another issue with compile_schema in versions
of the database between 8.1.5 to 8.1.6.2 (that is all 8.1.5 and
8.1.6.0 and 8.1.6.1 versions).  If you have a Java enabled
database, this will introduce some recursive dependencies into
your system.  This will cause COMPILE_SCHEMA to raise the error:

scott@TKYTE816> exec dbms_utility.compile_schema( user );
BEGIN dbms_utility.compile_schema( user ); END;

*
ERROR at line 1:
ORA-01436: CONNECT BY loop in user data
ORA-06512: at "SYS.DBMS_UTILITY", line 195
ORA-06512: at line 1

If you encoutner this error, we can create our own
COMPILE_SCHEMA procedure that behaves exactly as the real
COMPILE_SCHEMA.  We can do this by compiling the objects in any
order we feel like it.  It is a common misconception that we
must compile objects in some specific order ¡V we can in fact do
them in any old order and end up with the same outcome we would
have had ordered by dependency.  The logic is:

ƒá Pick any invalid object from a schema that we have not yet
tried to compile
ƒá Compile it
ƒá Goto step one until there are no more invalid objects that we
did not try to compile once

It is that simple ¡V we need no special ordering.  That is
because a side effect of compiling an invalid object is that all
invalid objects it depends on will be compiled in order to
validate this one.  We just have to keep compiling objects until
we have no more invalid ones (well, we might have invalid ones
but that would be because they cannot be successfully compiled
no matter what).   What we might discover is that we need only
to compile a SINGLE procedure to have 10 or 20 other objects get
compiled.  As long as we don¡¦t attempt to manually recompile
those 10 or 20 other objects (that would invalidate the first
object again) we are OK.

Since the implementation of this procedure is somewhat
interesting, we¡¦ll demonstrate it here.  We need to rely on an
invokers rights routine to do the actual ¡§alter compile¡¨
command ¡V however we need access to the DBA_OBJECTS table to
find the ¡§next¡¨ invalid object and report on the status of the
just compiled object.  We do not want the invoker of the routine
necessarily to have to have access to DBA_OBJECTS.  In order to
achieve this, we will use a mixture of Invokers rights routines
and Definers rights routines.  We need to make sure that the top
level routine, the one called by the end user, is the invokers
rights routine however to ensure that roles are enabled and all.

Here is my implementation of a COMPILE_SCHEMA.  The person who
runs this script must have had SELECT granted to them on the
SYS.DBA_OBJECTS view directly (refer to the Invokers Rights
chapter for details on why this is).  Since this is a SQLPlus
script, with some SQLPlus directives in it ¡V I¡¦ll put the
script in this time, not the results of actually running the
script.  I am using a SQLPlus macro variable to fill in the
schema name as we compile objects.  I am doing this because of
the invokers rights routine (need to fully qualify objects if
they should always access the SAME table regardless of who is
running it) and the fact that I personally do not like to rely
on public synonyms.  The script will be given to you in pieces
below with commentary in between:

column u new_val uname
select user u from dual;

drop table compile_schema_tmp
/

create global temporary table compile_schema_tmp
( object_name varchar2(30),
  object_type varchar2(30),
  constraint compile_schema_tmp_pk
  primary key(object_name,object_type)
)
on commit preserve rows
/
grant all on compile_schema_tmp to public
/

We start the script by getting the currently logged in users
username into a SQLPlus macro variable.  We will use this later
in our create or replace procedures below.  The temporary table
is used by our procedures to ¡§remember¡¨ what objects we have
attempted to compile.  We need to use ¡§on commit preserve
rows¡¨ because of the fact that we are going to do DDL in our
procedure (the alter compile command is DDL) and DDL commits.
Next, we can start in on the procedures we need:

create or replace
procedure get_next_object_to_compile( p_username in varchar2,
                                      p_cmd out varchar2,
                                      p_obj out varchar2,
                                      p_typ out varchar2 )
as
begin
    select 'alter ' || object_type || ' '
           || p_username || '.' || object_name ||
          decode( object_type, 'PACKAGE BODY', ' compile body',
                  ' compile' ), object_name, object_type
      into p_cmd, p_obj, p_typ
      from dba_objects a
     where owner = upper(p_username)
       and status = 'INVALID'
       and object_type <> 'UNDEFINED'
       and not exists ( select null
                          from compile_schema_tmp b
                         where a.object_name = b.object_name
                           and a.object_type = b.object_type
                      )
       and rownum = 1;

    insert into compile_schema_tmp
    ( object_name, object_type )
    values
    ( p_obj, p_typ );
end;
/

This is a definers rights procedure that accesses the
DBA_OBJECTS view for us.  This will return ¡§some¡¨ invalid
object to be compiled ¡V as long as we have not yet attempted to
compile it.  It just finds the first one.  As we retrieve them,
we ¡§remember¡¨ them in our temporary table.  Next we have our
invokers rights routine that will actually do the compilation.
This also shows why we needed the ¡§column u new_val uname¡¨
directive above ¡V we need to physically insert the OWNER of the
temporary table in here to avoid having to use a synonym.  Since
we do this dynamically upon install ¡V it makes it better then a
synonym:

create or replace procedure compile_schema( p_username in
varchar2 )
authid current_user
as
    l_cmd  varchar2(512);
    l_obj  dba_objects.object_name%type;
    l_typ  dba_objects.object_type%type;
begin
    delete from &uname..compile_schema_tmp;

    loop
        get_next_object_to_compile( p_username, l_cmd, l_obj,
l_typ );

        dbms_output.put_line( l_cmd );
        begin
            execute immediate l_cmd;
            dbms_output.put_line( 'Successful' );
        exception
            when others then
                dbms_output.put_line( sqlerrm );
        end;
        dbms_output.put_line( chr(9) );
    end loop;

exception
    when no_data_found then NULL;
end;
/
grant execute on compile_schema to public
/

And that is it ¡V now you can go into any schema that is able to
compile some objects and execute:

scott@TKYTE816> exec tkyte.compile_schema('scott')
alter PROCEDURE scott.ANALYZE_MY_TABLES compile
Successful

alter PROCEDURE scott.CUST_LIST compile
ORA-24344: success with compilation error

alter TYPE scott.EMP_MASTER compile
ORA-24344: success with compilation error

alter PROCEDURE scott.FOO compile
Successful

alter PACKAGE scott.LOADLOBS compile
Successful

alter PROCEDURE scott.P compile
Successful

alter PROCEDURE scott.RUN_BY_JOBS compile
Successful

PL/SQL procedure successfully completed.

So, that shows me the objects it attempted to compile and the
outcome.  According to the above ¡V we compile 7 objects, 2 of
which failed and 5 of which succeeded.  We compiled them in any
order, the order was simply not relevant.  This procedure should
work in all situations.
Review & Followup

Rating: 5
Questions August 19, 2002
Reviewer: Jeff Yuan from AZ USA
After installing 9.2.0.1, I got more than 10 hundred invalid objects in the SYS.
I used your method in your wonderful book (and here) to compile the invalid
objects, and still got about 150 invalid objects in SYS. Three kinds of errors
in my SqlPlus screen:

1. alter TYPE SYS.ODCIARGDESC compile
ORA-02311: cannot alter with COMPILE option a valid type with type or table
dependents

Question: How can I manually compile this object? and what cause the problem?

2. alter PACKAGE BODY SYS.DBMS_AQ compile body
ORA-00922: missing or invalid option

Action: "alter package DBMS_AQ compile body;" compiled it.
Question: Is there a bug in your procedure?

3.alter VIEW SYS._ALL_REPCOLUMN_GROUP compile
ORA-00911: invalid character

Action: "alter VIEW "_ALL_REPCOLUMN_GROUP" compile;" compiled it.
Question: Need "" on the name with special chars?

I am more interested in solving the problem in Question 1. Could you please take
a look on it? I use 9i rel 2.

Thank you very much.

Jeff Yuan
Followup:
> oerr ora 2311
02311, 00000, "cannot alter with COMPILE option a valid type with type or table
dependents"
// *Cause:  An attempt was made to ALTER with COMPILE option a type that is
//          valid and has type or table dependents.
// *Action: No need to perform this operation.

I love that action.  I've always wondered why people are so gung ho to have this
"perfect" no invalid objects state.  I've always just let it fix itself.

Let that type be, it'll fix itself IF and WHEN you need it.

#2 -- looks like there is:

create or replace
procedure get_next_object_to_compile( p_username in varchar2,
                                      p_cmd out varchar2,
                                      p_obj out varchar2,
                                      p_typ out varchar2 )
as
begin
    select 'alter ' || object_type || ' '
           || p_username || '.' || object_name ||
          decode( object_type, 'PACKAGE BODY', ' compile body',
                  ' compile' ), object_name, object_type
      into p_cmd, p_obj, p_typ
      from dba_objects a

should be

create or replace
procedure get_next_object_to_compile( p_username in varchar2,
                                      p_cmd out varchar2,
                                      p_obj out varchar2,
                                      p_typ out varchar2 )
as
begin
    select 'alter ' ||
          decode( object_type, 'PACKAGE BODY', 'PACKAGE', object_type ) || ' '
           || p_username || '.' || object_name ||
          decode( object_type, 'PACKAGE BODY', ' compile body',
                  ' compile' ), object_name, object_type
      into p_cmd, p_obj, p_typ
      from dba_objects a

3) another fix -- need quoted identifiers:

create or replace
procedure get_next_object_to_compile( p_username in varchar2,
                                      p_cmd out varchar2,
                                      p_obj out varchar2,
                                      p_typ out varchar2 )
as
begin
    select 'alter ' ||
          decode( object_type, 'PACKAGE BODY', 'PACKAGE', object_type ) || ' "'
           || p_username || '"."' || object_name || '" ' ||
          decode( object_type, 'PACKAGE BODY', ' compile body',
                  ' compile' ), object_name, object_type
      into p_cmd, p_obj, p_typ
      from dba_objects a

will do that.
Rating: 5
alter type body --> alter type August 19, 2002
Reviewer: Jeff Yuan from AZ USA
Hi Tom,

Thanks a lot again for your help.

I just found an issue with TYPE BODY. "alter type body xxx compile body;" should
be "alter type xxx compile body;".

One example is "alter type XMLGENFORMATTYPE compile body;"

Regards,

Jeff
Rating: 3
Another implementation not using temp-tables March 16, 2004
Reviewer: Matthias Rogel from Kaiserslautern, Germany
hello tom,

just saw your - nice as always - implementation.

a brute attack not needing get_next_object_to_compile
does the stuff for me since over 3 years now -
notice I just compile my own schema and assume
dependent objects in other schemata don't need to recompile,
but it should be not difficult to overcome this:

create or replace
  procedure recompile_invalid_objects is
  cursor inv is
    SELECT object_type, object_name
    FROM user_objects
    WHERE STATUS = 'INVALID';
    anz integer;
    anz_pref integer;
  begin
     select count(*) into anz
     FROM user_objects
     WHERE STATUS = 'INVALID';

     if anz = 0 then return; end if;
     anz_pref := anz + 1;

     while anz_pref > anz and anz > 0 loop
        for inv_rec in inv loop
           if inv_rec.object_type like '% BODY' then
              begin
                 execute immediate 'alter ' || substr(inv_rec.object_type, 1,
instr(inv_rec.object_type, ' BODY') - 1) ||
                                   ' "' || inv_rec.object_name || '" compile
body';
              exception when others then null;
              end;
           else
              begin
                 execute immediate 'alter ' || inv_rec.object_type || ' "' ||
inv_rec.object_name || '" compile';
              exception when others then null;
              end;
           end if;
        end loop;

        anz_pref := anz;

        select count(*) into anz
        FROM user_objects
        WHERE STATUS = 'INVALID';

     end loop;

     if anz > 0 then
        declare errm varchar2(2048);
        begin
           errm := 'The following ' || anz || ' Objects compiled with error:';
           for inv_rec in inv loop
              if length(errm) < 1950 then
                 errm := errm || chr(10) || inv_rec.object_name || ' (' ||
inv_rec.object_type || ')';
              else
                 errm := errm || chr(10) || '...';
                 exit;
              end if;
           end loop;
           raise_application_error(-20001, errm);
        end;
     end if;

  end recompile_invalid_objects;
Followup:
it does more work then it needs to (the very first COMPILE very well may have
compiled EVERYTHING for you)

And it may well end up with invalid stuff as the very act of compiling something
that isn't invalid (because of an implicit compile) will invalidate the things
that validated it!!!  (catch 22).

I'll stick with

a) pick off the first invalid object you haven't tried to compile yet
b) compile it
c) remember you compiled it (i found a temp table easiest, but an array would
work -- especially in 9i with associative arrays)
d) goto a until you get no more.

You might find in a schema with 1000 invalid objects -- you loop once due to the
implicit compiles.
Rating: 3
can't see 22 March 16, 2004
Reviewer: Matthias Rogel from Kaiserslautern, Germany
> it does more work then it needs to (the very first COMPILE > very well may
have
> compiled EVERYTHING for you)

true of course, but the procedure is used once a month maybe
and hence, I don't care looking at performance here

> And it may well end up with invalid stuff as the very act of > compiling
something
> that isn't invalid (because of an implicit compile) will > invalidate the
things
> that validated it!!!  (catch 22).

I can't see that (and never experienced it - I really use
the procedure since years )
Could you give me an example ?

SQL> create table temp(t integer);

Tabelle wurde angelegt.
SQL> create view vtemp as select * from temp;

View wurde angelegt.

SQL> create view vvtemp as select * from vtemp;

View wurde angelegt.

SQL> drop table temp;

Tabelle wurde gel÷scht.

SQL> create table temp(t integer);

Tabelle wurde angelegt.

SQL> select object_type,object_name from user_objects where status='INVALID';

OBJECT_TYPE        OBJECT_NAME
------------------
------------------------------------------------------------------------------
VIEW               VTEMP
VIEW               VVTEMP

SQL> alter view vvtemp compile;

View wurde geõndert.

SQL> select object_type,object_name from user_objects where status='INVALID';

Es wurden keine Zeilen ausgewõhlt

SQL> alter view vtemp compile;

View wurde geõndert.

SQL> select object_type,object_name from user_objects where status='INVALID';

Es wurden keine Zeilen ausgewõhlt

So the last "alter view vtemp compile;" didn't invalidate vvtemp

What do you mean by
"catch 22" ?

Thank you
Followup:
i see yours won't but only because it compiles everything over and over.

The last thing DID invalidate it -- you just went back and redid the work again.

I put a dbms_output in there and set up a test case.

V is a view, B depends on V, A depends on B.  I recreate the view V and B and A
go invalid, you will:

ops$tkyte@ORA9IR2> exec recompile_invalid_objects;
compiling A
compiling B
compiling A

PL/SQL procedure successfully completed.

Now in my method, I would get A, compile it -- which in turn compiles B and so I
stop.

You've already said "don't care if it is inefficient" so that is all well and
done.  I went with "I'd rather have the stuff fix itself ASAP".

So yes, you can get rid of the table (or an array) -- at a price, too high a
price for me.
Rating: 5
agree March 17, 2004
Reviewer: Matthias Rogel from Kaiserslautern, Germany
it just depends on what's more expensive -
temp tables or efficiency
just wanted to show you *can* do it without temp tables
March 16, 2004 -- Thanks for the question regarding "Advanced Queues for background Reporting", version 9.2
You Asked
In a web based reporting app. can we use advanced queues for running background
long running reports? Only thing i dont understand is how would i notify the
user (few thousand users) that report is completed or how to notify?
Web application would be using connection pooling.

Am i on the right track or completely off by thinking of using advanced queues?

Thanks
and we said...
Yes you can -- and yes, it makes total sense.

The notification methods are limitless.

My phone accepts SMS messages by emailing my phonenumber@service.provider.  You
could use utl_smtp or just send mail upon completion to let them know.

You could just send email to their accounts.  Email them a link "click here to
see your report"

You could integrate it in whatever portal you are using -- having a little area
on "their" home page show them the status of their reports.

You could let them pick up the status of the report at a web page anytime they
wanted.

I myself use the "email them a link" option -- although the SMS page is
excellent as well (I have my system email me an SMS page when I get voice mail
for example -- so my one phone tells me "all")
Review & Followup

Rating: 4
Excellent reply, but what about scaling? March 15, 2004
Reviewer: Mohit Dubey from London
Hi Tom,

This is one of the very few questions I have found on this site which deals with
AQs and performance. I am therefore going to avail this opportunity to focus
this thread more closely towards performance, but from a different perspective.

I think my situation is somewhat similar to the original post... I have a
multiconsumer queue which accepts payloads targeted to one or more consumers
(Oracle 9.2.0.4). The number of consumers is quite large and they are constantly
polling for "work" -- which I get using a simple PL/SQL routine.

The routine is basically called by each consumer, tries a dequeue in a
"wait-forever" mode, and "blocks" till work becomes available. The cycle
continues after the consumer has finished the dequeued work and wants more.

The trouble I am facing when scaling up is that the Queue itself becomes a point
of contention at high volumes (60,000+ messages and 900+ consumers). I have
tried the usual tricks of manipulating PCTFREE/USED to "spread" data more
thinly, but am wondering if there is something else I can do to help boost the
performance?

One thing that comes to mind is partitioning the queue table somehow, but I
could find no pointers on this in the manuals or the web. BTW, partitioning
worked wonders when the AQ was a "normal" table (I think it distributed the rows
across different objetcs, thus spreading them across multiple hash-chains in the
SGA -- which reduced contention). I may be barking up the wrong tree here if the
contention is for the "head" of the queue...

I also see a lot of enqueue waits and dequeue message waits which I am not sure
how to tune.

Any pointers would be very appreciated.

As tons of people before me have said, thanks for this *wonderful* site... It's
a veritable treasure-trove of Oracle knowledge, tips, facts and tricks!

Mohit.
Followup:
well, the dequeue message waits are going to be hard to consider here - you will
be getting them as people just naturally "wait" for a new message to come along.

have you identified (by tracing a single session with 10046 trace at level 12)
what the enqueues are on?

data is cached in the SGA based on a hash of the DBA (data block address) -- we
need not have things in different segments to achieve what you describe with the
hashing (the block number/file number differences are more than sufficient)

900 dequeue processes -- they are not all running on the same machine are they?
Rating: 3
Here are some more details..... March 16, 2004
Reviewer: Mohit from London
Hi Tom,

Apologies for the delayed reply, but here's some data (derived from the
StatsPack report) to help nail the waits down. Some salient points first:

o The "consumers" mentioned in my previous post were running on multiple servers
different from where DB is hosted.

o The "enqueues" I mentioned earlier were found to be mainly undo segment
related -- this was fixed (Thanks for the tip).

o In the snapshot below, SYS_IOT_TOP_16272 is an "overflow" segment created as
part of the AQ table, specifically for an IOT called AQ$_<QueueName>_NR. I have
no idea what this does.

o I have ignored log file sync waits as (I believe) that can be improved by
moving the redos to a RAID0 device. Redo-allocation has already been tuned, so
the "physical" write time (and the fact that the system transactions are very
small) is the main culprit here.

Selected sections from the report are as below.

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:          2,982,933.70              6,975.28
              Logical reads:             35,519.57                 83.06
              Block changes:             21,779.11                 50.93
             Physical reads:                  7.61                  0.02
            Physical writes:                209.50                  0.49
                 User calls:              3,799.58                  8.88
                     Parses:                261.96                  0.61
                Hard parses:                  0.01                  0.00
                      Sorts:                 45.95                  0.11
                     Logons:                  0.02                  0.00
                   Executes:              7,173.91                 16.78
               Transactions:                427.64

  % Blocks changed per Read:   61.32    Recursive Call %:                59.17
 Rollback per transaction %:    0.01       Rows per Sort:                 3.34

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:   98.21       Redo NoWait %:               99.99
            Buffer  Hit   %:   99.98    In-memory Sort %:              100.00
            Library Hit   %:  100.01        Soft Parse %:              100.00
         Execute to Parse %:   96.35         Latch Hit %:               97.69
Parse CPU to Parse Elapsd %:   81.11     % Non-Parse CPU:               99.70

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   73.46   73.46
    % SQL with executions>1:   89.94   93.85
  % Memory for SQL w/exec>1:   78.64   93.24

Top 5 Timed Events
~~~~~~~~~~~~~~~~~~                                                     % Total
Event                                               Waits    Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
log file sync                                      48,233       2,166    33.33
buffer busy waits                                  71,799       1,995    30.70
latch free                                        123,801       1,452    22.35
CPU time                                                          726    11.17
.
.
.

Wait Events for DB: MEMX9ID0  Instance: MEMX9ID0  Snaps: 234 -235
-> 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
---------------------------- ------------ ---------- ---------- ------ --------
log file sync                      48,233        165      2,166     45      1.0
buffer busy waits                  71,799          2      1,995     28      1.5
latch free                        123,801    114,389      1,452     12      2.5
db file parallel write                342          0         59    173      0.0
enqueue                             1,976          0         46     23      0.0
db file sequential read               785          0         24     31      0.0
.
.
.

Latch Sleep breakdown for DB: MEMX9ID0  Instance: MEMX9ID0  Snaps: 234 -235
-> ordered by misses desc
                                      Get                            Spin &
Latch Name                       Requests      Misses      Sleeps Sleeps 1->4
-------------------------- -------------- ----------- ----------- ------------
cache buffers chains           13,237,415     157,268     118,235 0/0/0/0/0
redo allocation                 1,355,068     141,333       1,836 139508/1814/
                                                                  11/0/0
library cache                   2,838,152     114,317       2,715 111647/2625/
                                                                  45/0/0
dml lock allocation               817,347      64,033         950 63088/940/5/
                                                                  0/0
shared pool                       791,255      60,891         253 60640/249/2/
                                                                  0/0
.
.
.

Top 10 Logical Reads per Segment for DB: MEMX9ID0  Instance: MEMX9ID0  Snaps: 23
-> End Segment Logical Reads Threshold:     10000
                                           Subobject  Obj.       Logical
Owner      Tablespace Object Name          Name       Type         Reads  %Total
---------- ---------- -------------------- ---------- ----- ------------ -------
BLADE01    BOM_DATA   SYS_IOT_TOP_16272               INDEX      223,632    8.10
SYS        SYSTEM     DUAL                            TABLE      170,016    6.16
BLADE01    INFRA_DATA ITAUDIT0             TAUDIT0_P3 INDEX      158,832    5.76
BLADE01    ARC_DATA   ITAUDIT0             TAUDIT0_P1 INDEX      144,224    5.23
BLADE01    INFRA_INDE ITAUDIT0             TAUDIT0_P4 INDEX      143,440    5.20
BLADE01    ARC_INDEX  ITAUDIT0             TAUDIT0_P2 INDEX      135,728    4.92
BLADE01    INFRA_DATA ITAUDIT              ITAUDIT_P3 TABLE      119,936    4.35
BLADE01    ARC_DATA   ITAUDIT              ITAUDIT_P1 TABLE      108,736    3.94
BLADE01    INFRA_INDE ITAUDIT              ITAUDIT_P4 TABLE      106,992    3.88
BLADE01    ARC_INDEX  ITAUDIT              ITAUDIT_P2 TABLE      100,336    3.64
          -------------------------------------------------------------

Top 10 Physical Reads per Segment for DB: MEMX9ID0  Instance: MEMX9ID0  Snaps: 2
-> End Segment Physical Reads Threshold:    1000
                                           Subobject  Obj.      Physical
Owner      Tablespace Object Name          Name       Type         Reads  %Total
---------- ---------- -------------------- ---------- ----- ------------ -------
BLADE01    ARC_INDEX  ITWORKFLOWQUEUEOBJEC EOBJECT_P4 TABLE          101   27.08
BLADE01    ARC_DATA   ITWORKFLOWQUEUEOBJEC EOBJECT_P3 TABLE           62   16.62
BLADE01    BOM_DATA   ITWORKFLOWQUEUEOBJEC EOBJECT_P1 TABLE           51   13.67
BLADE01    BOM_INDEX  ITWORKFLOWQUEUEOBJEC EOBJECT_P2 TABLE           49   13.14
BLADE01    ARC_INDEX  ITWORKFLOWQUEUEOBJEC OBJECT1_P4 INDEX           11    2.95
BLADE01    BOM_DATA   ITWORKFLOWOBJECT                TABLE           10    2.68
BLADE01    BOM_DATA   SYS_IOT_TOP_16272               INDEX            9    2.41
BLADE01    BOM_INDEX  ITWORKFLOWOBJECT0               INDEX            9    2.41
BLADE01    INFRA_DATA ITWORKFLOWCREATIONAD            TABLE            8    2.14
BLADE01    ARC_DATA   ITWORKFLOWQUEUEOBJEC OBJECT0_P3 INDEX            8    2.14
          -------------------------------------------------------------

Top 10 Buf. Busy Waits per Segment for DB: MEMX9ID0  Instance: MEMX9ID0  Snaps:
-> End Segment Buffer Busy Waits Threshold:     100

                                                                  Buffer
                                           Subobject  Obj.          Busy
Owner      Tablespace Object Name          Name       Type         Waits  %Total
---------- ---------- -------------------- ---------- ----- ------------ -------
BLADE01    BOM_DATA   SYS_IOT_TOP_16272               INDEX       17,771   30.38
BLADE01    ARC_DATA   ITAUDIT0             TAUDIT0_P1 INDEX        3,509    6.00
BLADE01    INFRA_DATA ITAUDIT0             TAUDIT0_P3 INDEX        3,442    5.88
BLADE01    INFRA_INDE ITAUDIT0             TAUDIT0_P4 INDEX        3,394    5.80
BLADE01    BOM_INDEX  ITWFPINFRASTACK0                INDEX        3,148    5.38
BLADE01    ARC_INDEX  ITAUDIT0             TAUDIT0_P2 INDEX        2,874    4.91
BLADE01    ARC_DATA   ITWORKFLOWQUEUEOBJEC OBJECT0_P3 INDEX        2,349    4.02
BLADE01    BOM_DATA   ITWORKFLOWQUEUEOBJEC OBJECT0_P1 INDEX        1,538    2.63
BLADE01    ARC_INDEX  ITWORKFLOWQUEUEOBJEC OBJECT0_P4 INDEX        1,523    2.60
BLADE01    ARC_DATA   ITWORKFLOWQUEUEOBJEC EOBJECT_P3 TABLE        1,511    2.58
          -------------------------------------------------------------

Top 10 Row Lock Waits per Segment for DB: MEMX9ID0  Instance: MEMX9ID0  Snaps: 2
-> End Segment Row Lock Waits Threshold:     100
                                                                     Row
                                           Subobject  Obj.          Lock
Owner      Tablespace Object Name          Name       Type         Waits  %Total
---------- ---------- -------------------- ---------- ----- ------------ -------
BLADE01    BOM_DATA   SYS_IOT_TOP_16272               INDEX        7,666   89.27
BLADE01    INFRA_DATA ITAUDIT0             TAUDIT0_P3 INDEX          258    3.00
BLADE01    INFRA_INDE ITAUDIT0             TAUDIT0_P4 INDEX          210    2.45
BLADE01    ARC_DATA   ITAUDIT0             TAUDIT0_P1 INDEX          202    2.35
BLADE01    ARC_INDEX  ITAUDIT0             TAUDIT0_P2 INDEX          157    1.83
BLADE01    ARC_INDEX  ITWORKFLOWQUEUEOBJEC OBJECT1_P4 INDEX           28     .33
BLADE01    BOM_DATA   ITWORKFLOWQUEUEOBJEC OBJECT1_P1 INDEX           13     .15
BLADE01    BOM_INDEX  ITWORKFLOWOBJECT0               INDEX           10     .12
BLADE01    ARC_INDEX  ITAUDITARCHIVE0      RCHIVE0_P4 INDEX            7     .08
BLADE01    ARC_DATA   ITAUDITARCHIVE0      RCHIVE0_P3 INDEX            5     .06
          -------------------------------------------------------------

Your comments are most welcome!

Mohit.
Followup:
zero in on the buffer busy waits (if you believe you can fix the log file sync
already).

find out what they are about.  a level 12 10046 trace for an application
suffering from them would be relevant (actually -- I'd prefer not seeing
statspacks, but TKPROFS!  much more relevant to tuning *an application*.  this
system level stuff contains tons of noise that just isn't relevant to tuning
your application)
March 16, 2004 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://download-east.oracle.com/docs/cd/A81042_01/DOC/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://download-west.oracle.com/docs/cd/A87860_01/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.
Rating: 5
March 01, 2004
Reviewer: Randall Richardson from St. Louis, MO
Thanks Tom..

One follow-up question.

We are in a Windows cluster env and I have the spfile along with all
admin\ORACLE_SID\ type stuff on the cluster drive.  The ORACLE_HOME has to be on
a local drive.

Can I change the location where the password file from ORACLE_HOME\database to a
different directory like I do with the spfile?

Thanks again.
Followup:
not that I'm aware of -- each instance needs it's own.

how are you sharing an spfile amongst instances?
Rating: 5
March 01, 2004
Reviewer: Randall RIchardson from St. Louis, MO
Its a Failsafe cluster, Database is Active/Passive

Each node has a text file "init<SID>.ora" in the Local "ORACLE_HOME\database"
directory that point to the shared drive init file.

------ initPROD.ora ------------
spfile=G:\oracle\admin\PROD\spfile\PROD.ORA
--------------------------------

I just wanted to try and make sys password changes on the cluster a little more
stream lined.  Oh well..

Thanks..
Rating: 4
Connect as sysdba doesn't work March 16, 2004
Reviewer: Michael from Germany
Hi Tom!

I have the following problem with the "connect as sysdba":

Only service and listener on NT is started (with: oradim -startup -sid ora81db
-starttype srvc).

Then in SQL*Plus:

E:\Apps\oracle\ora81\bin>set ORACLE_SID=ora81db

E:\Apps\oracle\ora81\bin>set ORACLE_HOME=E:\Apps\oracle\ora81

E:\Apps\oracle\ora81\bin>sqlplus /nolog

SQL*Plus: Release 8.1.7.0.0 - Production on Di Mõr 16 12:49:32 2004

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

SQL> conn sys as sysdba
Kennwort eingeben: (The correct password from the orapwd tool)
ERROR:
ORA-01034: ORACLE not available
ORA-27101: shared memory realm does not exist

SQL> conn internal
Bei einer nicht hochgefahrenen Instanz angemeldet.

SQL> startup
ORACLE-Instanz hochgefahren.

Total System Global Area   99227676 bytes
Fixed Size                    75804 bytes
Variable Size              82296832 bytes
Database Buffers           16777216 bytes
Redo Buffers                  77824 bytes
Datenbank mit MOUNT angeschlossen.
Datenbank ge÷ffnet.

SQL> select * from v$pwfile_users;

USERNAME                       SYSDB SYSOP
------------------------------ ----- -----
INTERNAL                       TRUE  TRUE
SYS                            TRUE  TRUE

SQL> conn sys as sysdba
Kennwort eingeben: (Same password as above)
Connect durchgef³hrt.
SQL> shutdown immediate
ORA-01031: Unzureichende Berechtigungen
SQL> conn sys as sysoper
Kennwort eingeben:
Connect durchgef³hrt.
SQL> shutdown immediate
ORA-01031: Unzureichende Berechtigungen
SQL> conn internal
Connect durchgef³hrt.
SQL> show user
USER ist "SYS"
SQL> shutdown immediate
Datenbank geschlossen.
Datenbank abgehõngt.
ORACLE-Instanz heruntergefahren.

I don't know what's wrong. Any idea?
Followup:
the

connect sys as sysdba

isn't "working" right -- it is just connecting as "sys", not "sys as sysdba",
the "as sysdba" is getting lost by sqlplus

you can use "connect / as sysdba" in sqlplus or use svrmgrl to

connect sys as sydba
Rating: 5
iSQLPlus March 16, 2004
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
I am unable to connect as sysdba from the isqlplus window. I am using Oracle 9i
R2 on Windows box. Please guide me how to configure it to login as sysdba.
Followup:
did you read and follow all of the needed setup in the sqlplus guide?

(i would not recommend using sysdba like this, sysdba is pretty darn powerful,
I'd pretty much limit it to real DBA tools -- if even there (i use it in sqlplus
command line and no where else myself)
Rating: 4
Interesting ... March 18, 2004
Reviewer: Michael from Germany
Hi Tom!

With the SVRMGR the "connect as sysdba" worked immediatly. Thanks!

Then i tried in SQL*Plus the following:
(The user MIKE has the password "mike" and is sysdba)

E:\Apps\oracle\ora81\bin>sqlplus /nolog

SQL*Plus: Release 8.1.7.0.0 - Production on Do Mõr 18 08:07:29 2

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

SQL> conn sys as sysdba
Kennwort eingeben:
ERROR:
ORA-01034: ORACLE not available
ORA-27101: shared memory realm does not exist

SQL> conn sys as sysdba;
Kennwort eingeben:
ERROR:
ORA-01034: ORACLE not available
ORA-27101: shared memory realm does not exist

SQL> conn sys/sys as sysdba
Bei einer nicht hochgefahrenen Instanz angemeldet.
SQL> conn mike as sysdba
Kennwort eingeben: (i entered "mike")
ERROR:
ORA-01034: ORACLE not available
ORA-27101: shared memory realm does not exist

Achtung: Sie sind nicht mehr mit ORACLE verbunden.
SQL> conn mike/mike as sysdba
Bei einer nicht hochgefahrenen Instanz angemeldet.
SQL> startup
ORACLE-Instanz hochgefahren.

Total System Global Area   99227676 bytes
Fixed Size                    75804 bytes
Variable Size              82296832 bytes
Database Buffers           16777216 bytes
Redo Buffers                  77824 bytes
Datenbank mit MOUNT angeschlossen.
Datenbank ge÷ffnet.
SQL> conn mike as sysdba
Kennwort eingeben: (i entered "mike")
Connect durchgef³hrt.
SQL> shutdown immediate
ORA-01031: Unzureichende Berechtigungen
SQL> conn mike/mike as sysdba
Connect durchgef³hrt.
SQL> shutdown immediate
Datenbank geschlossen.
Datenbank abgehõngt.
ORACLE-Instanz heruntergefahren.

It looks like, if you use the syntax user/password, then SQL*Plus pays attention
to the "as sysdba". Otherwise (only username) it is ignored.

IMHO, i would call this behaviour of SQL*Plus a bug. (It seems to be fixed in 9i
:-).
March 16, 2004 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.
Rating: 3
SQL_Trace parameter in 9i March 16, 2004
Reviewer: denni50 from NA
Tom...

I noticed 9i doesn't have the sql_trace initialization parameter. When I type
"alter session set sql_trace=true"
I get an ora-02095 error stating the parameter cannot be modified.

I read through the 9i online Reference Guide which provided
a list of dynamic IP's that can be changed using Alter Session or Alter
System....sql_trace was not there like it is in 8i.
There is a "trace_enabled" parameter in 9i, do
I need to set that to true then execute any trace through
the dbms_system or dbms_session packages?

I guess that's why they didn't include tkprof...there's no
sql_trace(kidding!)

thanks
Followup:
you got that wrong I believe.

alter session <<== works dandy
alter system <<=== never worked, unless you add "scope=spfile" for the next
restart

Oracle9i Enterprise Edition Release 9.0.1.0.0 - Production
With the Partitioning option
JServer Release 9.0.1.0.0 - Production

ops$tkyte@ORA9IR1> alter SESSION set sql_trace=true;

Session altered.

ops$tkyte@ORA9IR1> alter SYSTEM set sql_trace=true;
alter SYSTEM set sql_trace=true
                 *
ERROR at line 1:
ORA-02095: specified initialization parameter cannot be modified

ops$tkyte@ORA9IR1> a  scope = spfile
  1* alter SYSTEM set sql_trace=true scope = spfile
ops$tkyte@ORA9IR1> /

System altered.
Rating: 3
Tom... March 16, 2004
Reviewer: denni50 from NA
yep...you got me again...I was using the Alter System
instead of Alter Session command.

I also gave my username the Alter Session and System
priviledges as I was unable to show the parameter in
SQL PLus.

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> alter session set sql_trace=true;

Session altered.

SQL> show parameter sql_trace;

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
sql_trace                            boolean     FALSE

Sql_trace shows FALSE at the parameter level, which is
technically the SYSTEM level...therefore turning sql_trace
on for a session has no effect on the value at the
parameter level...correct?
Followup:
correct....
March 16, 2004 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.
Rating: 3
Other leftover session "state"? January 23, 2004
Reviewer: andrew from ca, use
Tom

8.1.7 database, 9.0.2 9iAS.

The "reset state" property is achieved by running DBMS_SESSION.reset_package
after the client call is - right? So package state is destroyed, but other
residual session "state"/configuration is left like:
1.) Modified NLS settings
2.) Modified optimizer mode
3.) Open DB links etc...

I have more than one App using the same DAD. My problem is that some
transactions open DB links and then subsequent transactions having autonomous
transactions run into "ORA-00164: autonomous transaction disallowed within
distributed transaction".

Can I determine the current settings for any of the above (specifically #3)? I
know that I could try to ensure that the transaction which opens the DB link
could manually close it too, but in my case it may be more efficient to simply
close any open links before I run the autonomous transaction.
Followup:
v$dblink will show you all of the open dblinks in your session.

for safety sake, you would issue the alter sessions needed in all cases.
Rating: 3
connection -pooling March 15, 2004
Reviewer: robert from CT
Tom , my app is "breaking" under Apache/mod_plsql !

The app was running against OAS (4.0.8). I use global temp table (on delete
PRESERVE rows) every page  to transfer the static html stuff over from regular
table.

The problem I am facing:
Obviously under mod_plsql, with connection-pooling ON, the GTT is NOT cleared
from page to page as under OAS.

How can I deal with this ? make GTT "on commit delete rows" and use "Autonomous
Transactions" ?

thanks
Followup:
make the gtt on commit delete rows -- for sure.  IF you need a gtt at all even.

do not use autonomous transactions.
Rating: 4
DBMS_SESSION.UNIQUE_SESSION_ID - SAME value in 2 browsers March 16, 2004
Reviewer: robert from ct
>>
make the gtt on commit delete rows -- for sure.  IF you need a gtt at all even.
do not use autonomous transactions.
<<
hi tom,

8.1.7
Well, how else can I avoid locking table in which I store static HTML ?
I transfer to GTT & do updates/substitution replace at run-time.

Now I made the GTT "on commit delete" and put a COMMIT in pkg init section.  and
the app seems to work fine.

With connection pooling on, one thing I discoverd is that I htp.p out
DBMS_SESSION.UNIQUE_SESSION_ID to the page and the
values are the SAME in 2 separate browser(sessions) (SAME Basic Auth.
login)...Is this supposed to be like this ?

thanks
Followup:
yes, it is the same session after all.

sys_guid() or a sequence will be something to look into.
March 16, 2004 billy -- Thanks for the question regarding "Very Slow after restart DB", version 8.1.7
You Asked
I have shutdown and startup after offline backup, and the performance is very
very slow after shutdown and restart the DB, the following log capture from the
alert.log :-

Tue Mar 16 23:27:50 2004
kccrsz: denied expansion of controlfile section 9 by 65535 record(s)
  the number of records is already at maximum value (65535)
krcpwnc: following controlfile record written over:
 RECID #31164695 Recno 35570 Record timestamp
 03/15/04 10:44:55
 Thread=1 Seq#=31164695 Link-Recid=31164694
  Low
scn: 0x0000.93da07dc
 03/15/04 10:44:53
 Next
scn: 0x0000.93da0827
Thread 1 advanced to log sequence 31230231
  Current log# 3 seq# 31230231 mem#0: /dwhapp/ora01/oradata/dwh/redo03.log

I don't know what the database is doing and what can I do now?

Thanks, Tom
and we said...
your database is warming up -- it has nothing really to do with that message
above, it is because you emptied your cache, you flushed your shared pool.

It is never necessary or even desirable to shutdown the database to back it up.
Please take a look at hot backups and you'll avoid losing all of the work your
database did to get warmed up.
March 16, 2004 Florin -- Thanks for the question regarding "convert values to columns", version 8.1.7
You Asked
Hi Tom,

I have the following query:

select to_char(LOGICAL_DATE,'Dy Dd-MON-yyyy') "Date", PROCESS_NAME,
sum(nvl(NUM_OF_REC,0)) "Total" from PRM51_PROCESS_ACTIVITY where
LOGICAL_DATE like '15-MAR-04' group by LOGICAL_DATE,PROCESS_NAME order by
LOGICAL_DATE;

The output is:

Date            PROCESS_NAME                        Total
--------------- ------------------------------ ----------
Mon 15-MAR-2004 AF_Err_Loader                          30
Mon 15-MAR-2004 AF_Err_Resolution                       0
Mon 15-MAR-2004 AF_main_Driver                         34
Mon 15-MAR-2004 DataWareHouse_Extract                  12
Mon 15-MAR-2004 PRM_Err_Loader                         88
Mon 15-MAR-2004 Rated_Usage_Extract                     0
Mon 15-MAR-2004 Rating                                 34
Mon 15-MAR-2004 Split_Rated_Usage                      56
Mon 15-MAR-2004 Splitter                               34
Mon 15-MAR-2004 Usage_Manager                          79

I want to have the values of PROCESS_NAME as columns. Ex:

Date         AF_Err_Loader AF_Err_Resolution  AF_main_Driver
-----------  ------------- -----------------  --------------
15-MAR-2004    30           0          34

Many thanks in advance,

Florin
and we said...
"where logical_date like '15-MAR-04'"  -- that sends shivers down my spine!!
scary stuff.

using dates like strings.
using a like without % or _
relying on implicit date formats

all scary stuff.

But in any case -- you'll need to know the set of PROCESS_NAME values (for sql
needs to have N columns when the query is prepared for execution).  If you do
not know them all -- you'll have to "two step" this -- one query to get the set
of distinct names and then a dynamically built query that looks like this:

select to_char(logical_date, 'Dy DD-MON-YYYY'),
       sum(decode(process_name,'AF_Err_Loader',Num_of_Rec)) af_err_loader,
.....
       sum(decode(process_name,'AF_main_Driver',Num_of_Rec)) af_main_driver
  from PRM51_PROCESS_ACTIVITY
 where logical_date >= to_date('15-mar-2004','dd-mon-yyyy')
   and logical_date <  to_date('16-mar-2000','dd-mon-yyyy')
 group by to_char(logical_date, 'Dy DD-MON-YYYY');
Review & Followup

Rating: 5
Many thanks for your brilliant solution! March 25, 2004
Reviewer: Florin from The Netherlands
March 16, 2004 Denis -- Thanks for the question regarding "function call cache?", version 9.2
You Asked
Will you explain why in the following script
f_tmp function is called different times in Query1 and Query2.

create or replace function f_tmp(p_ignored number) return number is
  l_ignored number;
begin
  select count(*) into l_ignored from user_objects;
  return 1;
end;
/

create table t_tmp (n number);
insert into t_tmp values (1);
insert into t_tmp values (1);
insert into t_tmp values (2);
insert into t_tmp values (2);
insert into t_tmp values (1);
insert into t_tmp values (1);
insert into t_tmp values (3);
insert into t_tmp values (2);
insert into t_tmp values (2);

select * from t_tmp;

set timing on;
--alter session set sql_trace=true;

Query1

select count(*) from t_tmp t where 1=f_tmp(t.n);

In trace the file I see (function's query):

SELECT count(*) from user_objects

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      9      0.00       0.00          0          0          0           0
Fetch        9      0.05       0.04          0       4428          0           9
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       19      0.05       0.05          0       4428          0           9

Query2

select count(*) from t_tmp t where 1=(select f_tmp(t.n) from dual);

The trace file:

SELECT count(*) from user_objects

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.02       0.01          0       1476          0           3
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        7      0.02       0.01          0       1476          0           3

--alter session set sql_trace=false;

It looks as if in the first case f_tmp is called for every row
in t_tmp table and in the second case the subquery results
(select f_tmp(t.n) from dual) are cached for every value of the function's
parameter (t.n in the statement).
What's the rule in here?

Thanks.
Denis
and we said...
thats an interesting suboptimization.

but the bottom line is -- there are in fact "no rules" governing the order of
operation, how many times things are called.  It all depends on the "plan".
Here you have introduced a correlated scalar subquery and it is doing something
"fancy" with it.

the change can be seen using this technique:

ops$tkyte@ORA9IR2> create or replace function f_tmp(p_ignored number) return
number
  2  is
  3  begin
  4    dbms_application_info.set_client_info( userenv('client_info')+1 );
  5    return 1;
  6  end;
  7  /

Function created.

using that, we can:

ops$tkyte@ORA9IR2> exec dbms_application_info.set_client_info(0);

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> select count(*) from t_tmp t where 1=f_tmp(t.n);

  COUNT(*)
----------
         9

ops$tkyte@ORA9IR2> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
9

that shows our function was called 9 times...

ops$tkyte@ORA9IR2> delete from plan_table;

5 rows deleted.

ops$tkyte@ORA9IR2> explain plan for select count(*) from t_tmp t where
1=f_tmp(t.n);

Explained.

ops$tkyte@ORA9IR2> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
--------------------------------------------------

--------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |
--------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             |       |       |       |
|   1 |  SORT AGGREGATE      |             |       |       |       |
|*  2 |   TABLE ACCESS FULL  | T_TMP       |       |       |       |
--------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("OPS$TKYTE"."F_TMP"("T"."N")=1)

Note: rule based optimization

15 rows selected.

and that the filter was done row by row by row.   Now:

ops$tkyte@ORA9IR2> exec dbms_application_info.set_client_info(0);

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> set autotrace on explain
ops$tkyte@ORA9IR2> select count(*) from t_tmp t where 1=(select f_tmp(t.n) from
dual);

  COUNT(*)
----------
         9

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     FILTER
   3    2       TABLE ACCESS (FULL) OF 'T_TMP'
   4    2       TABLE ACCESS (FULL) OF 'DUAL'

ops$tkyte@ORA9IR2> set autotrace off
ops$tkyte@ORA9IR2> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
3

that shows it was called three times -- once each for 1,2,3.  the plan is very
different:

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> delete from plan_table;

3 rows deleted.

ops$tkyte@ORA9IR2> explain plan for select count(*) from t_tmp t where 1=(select
f_tmp(t.n) from dual);

Explained.

ops$tkyte@ORA9IR2> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
--------------------------------------------------

--------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |
--------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             |       |       |       |
|   1 |  SORT AGGREGATE      |             |       |       |       |
|*  2 |   FILTER             |             |       |       |       |
|   3 |    TABLE ACCESS FULL | T_TMP       |       |       |       |
|   4 |    TABLE ACCESS FULL | DUAL        |       |       |       |
--------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter( (SELECT "OPS$TKYTE"."F_TMP"(:B1) FROM "SYS"."DUAL"
               "DUAL")=1)

Note: rule based optimization

18 rows selected.

the filter was happening at a much higher level.  However, this
suboptimization doesn't "scale" if you will -- consider:

ops$tkyte@ORA9IR2> insert into t_tmp select object_id from all_objects;

30601 rows created.

ops$tkyte@ORA9IR2> insert into t_tmp select object_id from all_objects;

30601 rows created.

ops$tkyte@ORA9IR2> insert into t_tmp select object_id from all_objects;

30601 rows created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec dbms_application_info.set_client_info(0);

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> select count(*) from t_tmp t where 1=(select f_tmp(t.n) from
dual);

  COUNT(*)
----------
     91812

ops$tkyte@ORA9IR2> set autotrace off
ops$tkyte@ORA9IR2> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
91296

they skipped calling it a couple of times -- but there was just too many
values to do whatever caching they were doing and it is sort of "obviated" here.
 They called the function virtually once per row in this case

But basically -- you *cannot* rely on a function getting called once per row,
once per unique value -- whatever.  Consider:

ops$tkyte@ORA9IR2> create table t as select * from t_tmp order by n;

Table created.

ops$tkyte@ORA9IR2> exec dbms_application_info.set_client_info(0);

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> select count(*) from t where 1=(select f_tmp(t.n) from dual);

  COUNT(*)
----------
     91812

ops$tkyte@ORA9IR2> set autotrace off
ops$tkyte@ORA9IR2> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
30603

the order of the data was really relevant here.  they must be caching the last
few filter values and reusing them -- due to the filter step being "higher up".

Interesting...
Review & Followup

Rating: 5
caching the last value March 16, 2004
Reviewer: Alberto Dell'Era from Milan, Italy
I think they are caching the last value, reusing it until "n" changes (i'm on
9.2.0.4):

Only one distinct value:

SQL> insert into t_tmp select 1 from all_objects where rownum <= 100;

100 rows created.

SQL> exec dbms_application_info.set_client_info(0);

PL/SQL procedure successfully completed.

SQL> select count(*) from t_tmp t where 1=(select f_tmp(t.n) from dual);

  COUNT(*)
----------
       100

SQL>  select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
-------------------------------------------------------------------------
1

Two distinct values:

SQL> delete from t_tmp;

100 rows deleted.

SQL> insert into t_tmp select 1  from all_objects where rownum <= 50;

50 rows created.

SQL> insert into t_tmp select 2  from all_objects where rownum <= 50;

50 rows created.

SQL>  exec dbms_application_info.set_client_info(0);

PL/SQL procedure successfully completed.

SQL> select count(*) from t_tmp t where 1=(select f_tmp(t.n) from dual);

  COUNT(*)
----------
       100

SQL> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
--------------------------------------------------------------------------------
2

Three distinct values:

SQL> delete from t_tmp;

100 rows deleted.

SQL> insert into t_tmp select 1  from all_objects where rownum <= 33;

33 rows created.

SQL> insert into t_tmp select 2  from all_objects where rownum <= 33;

33 rows created.

SQL> insert into t_tmp select 3  from all_objects where rownum <= 34;

34 rows created.

SQL>  exec dbms_application_info.set_client_info(0);

PL/SQL procedure successfully completed.

SQL>  select count(*) from t_tmp t where 1=(select f_tmp(t.n) from dual);

  COUNT(*)
----------
       100

SQL>  select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------------------
3

Eleven distinct values:

SQL> delete from t_tmp;

100 rows deleted.

SQL> insert into t_tmp select trunc (rownum / 10) from all_objects where rownum
<= 100;

100 rows created.

SQL> select count (distinct n) from t_tmp;

COUNT(DISTINCTN)
----------------
              11

NB: count (distinct) is equal to the number of "changes" of n ONLY IN THIS CASE,
since we know that they will full-scan, fetching the rows in the same order we
inserted them (but only in this "dust clean" experiment).

SQL>  exec dbms_application_info.set_client_info(0);

PL/SQL procedure successfully completed.

SQL> select count(*) from t_tmp t where 1=(select f_tmp(t.n) from dual);

  COUNT(*)
----------
       100

SQL> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
--------------------------------------------------------------------------------
11

bye
Alberto
Rating: 5
essentially improves performance March 17, 2004
Reviewer: Denis Kolesnikov
Tom, thank you for the exhaustive answer.
I use this feature in my FGAC-like views and that essentially improves
performance.
March 16, 2004 -- 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.
Rating: 5
TSPIR with RMAN March 16, 2004
Reviewer: reader
Following is an extract from 10g online doc
http://download-west.oracle.com/docs/cd/B12037_01/server.101/b10734/rcmtspit.htm#1014658
It is extremely important that you choose the right target time or SCN for your
TSPITR. As noted already, once you bring a tablespace online after TSPITR, you
cannot use any backup from a time earlier than the moment you brought the
tablespace online. In practice, this means that you cannot make a second attempt
at TSPITR if you choose the wrong target time the first time, unless you are
using a recovery catalog. (If you have a recovery catalog, however, you can
perform repeated TSPITRs to different target times.)

For example, assume that you run TSPITR on a tablespace, and then bring the
tablespace online at 5PM on Friday. Backups of the tablespace created before 5PM
Friday are no longer usable for recovery with a current control file. You cannot
run TSPITR again on this tablespace with a target time earlier than 5PM Friday,
nor can you use the current control file to recover the database to any time
earlier than 5PM Friday. Your only option will be point-in-time recovery of your
entire database using a restored control file.

Tom, what information in the current controlfile is preventing me from using the
backups earlier than 5PM? I am not able to understand why can't I use the prior
backups? Thanks.
Followup:
it is because you have two logical branches now -- it is sort of like you did a
partial "open database resetlogs" -- you've broken the redo chain.

The TSPITR starts a mini instance (aux instance it calls it) and does a PITR of
the affected tablespace.  It then "transports" this to the real instance and
attaches it.

The caveat above has to do with RMAN here.  it is saying "if you don't use me
right -- with a recovery catalog -- once you do this, I cannot do it again, I
won't have data I feel I need to do it again"

it is not that it could not be done again (you can do a manual TSPITR anytime
you want!  just restore the database to another server, PITR it, transport
away....) it is that RMAN cannot do it again without a recovery catalog and all
of the details it wants to keep.  It is a bookeeping thing more than anything
else here.

the "technical" reason is up one paragraph from where you copied:

...
When not using a recovery catalog, the current control file has no record of
the older incarnation of the recovered tablespace. Thus, recovery with a current
control file that involves this tablespace can no longer use a backup taken
prior to time t. You can, however, perform incomplete recovery of the whole
database to any time less than or equal to t, if you can restore a backup
control file from before time t.
........
Rating: 5
Thanks for clarifying this. March 16, 2004
Reviewer: reader
March 16, 2004 Bhavesh -- Thanks for the question regarding "Loop in a SQL Query", version 9i
You Asked
I would like to achieve the following. If my parameters are Valid From =
10/12/2002 and Valid To=15/12/2002, a 'SQL Query' should give me output from
DUAL table or otherwise, as follows

o/p
==========
10/12/2002
11/12/2002
12/12/2002
13/12/2002
14/12/2002
15/12/2002

Please note it has to be a SQL Query, not PL/SQL.
Basically I want to loop through all the days(like a dynamic cursor) entered by
the user and use the output for another Query. I shall be using this in a Java
Server Side code.

Thanks
and we said...
In this example (sysdate-10) is your from date and sysdate is your to date.  You
would use bind variables and supply these values at runtime of course.

ops$tkyte@ORA920> create or replace type dateCollection as table of date
  2  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace function all_dates( p_start in date, p_stop
in date )
  2  return dateCollection
  3  PIPELINED
  4  as
  5  begin
  6          for i in 0 .. (p_stop-p_start)
  7          loop
  8                  pipe row( p_start+i );
  9          end loop;
 10          return;
 11  end;
 12  /

Function created.

the java routines will use this query then:

ops$tkyte@ORA920>
ops$tkyte@ORA920> select *
  2    from TABLE( all_dates( sysdate-10, sysdate ) )
  3  /

COLUMN_VA
---------
12-DEC-02
13-DEC-02
14-DEC-02
15-DEC-02
16-DEC-02
17-DEC-02
18-DEC-02
19-DEC-02
20-DEC-02
21-DEC-02
22-DEC-02

11 rows selected.

Is the way to do that.

You can also:

ops$tkyte@ORA920> select (sysdate-10)+r
  2    from ( select rownum-1 r
  3             from all_objects
  4                    where rownum <= (sysdate)-(sysdate-10)+1
  5             )
  6  /

(SYSDATE-
---------
12-DEC-02
13-DEC-02
14-DEC-02
15-DEC-02
16-DEC-02
17-DEC-02
18-DEC-02
19-DEC-02
20-DEC-02
21-DEC-02
22-DEC-02

11 rows selected.

assuming all_objects is big enough (if not, create a big enough table elsewhere
and use that)
Review & Followup

Rating: 4
Loop in a SQL Query December 22, 2002
Reviewer: Bhavesh Gandhi from India, Bombay
Thanks a lot for the solution.

Parellely I also reached the second solution as suggested here.

I am really happy that this website replies promptly. This is the second time I
have asked a question and I have received a solution on both occasions.
Rating: 5
loop in sql March 16, 2004
Reviewer: A reader from NYC, USA
Hi Tom,

I have 2 tables

group   prodId    seq     qty
-------------------------------
1       p1        1      1
1       p2        2      0
1       p3        3      0
1       p4        4      2
1       p8        5      0
...
2       p5        1      1
2       p6        2      0
2       p7        3      1
2       p9        4      0
...

and

Group   last_order_seq   order_qty_limit  group size
----------------------------------------------------
1         3                2                    5
2         1                2                    4
...

I'd like to have a query to give all products need re-ordering based on
last_order_seq and order_qty_limit
e.g.
for group 1, sinece last_order_seq = 3, so we should start from seq 4, then 5,
then 1... [ mod(last_order_seq-1, 5)] and find p8 and p2 since order_qty_limit
is 2 so I won't pick p3.
and for group 2, we start from seq 2 and we get p6 and p9

result
------
p8
p2
p6
p9

I don't know if it can be done with one sql.

Thanks!

Steve
Followup:
remember -- take the innermost inline view, run it and then wrap the other
layers one by one to see "how it works" (that is how i build them, from the
inside out)

ops$tkyte@ORA9IR2> select grp, prodid
  2    from (
  3  select t.*,
  4         row_number() over (partition by grp order by oseq) rn
  5    from (
  6  select t1.*,
  7         decode( sign(t2.last_order_seq-t1.seq),
                     -1, t1.seq, t1.seq+t2.grp_size+1) oseq,
  8             t2.order_qty_limit
  9    from t1, t2
 10   where t1.grp = t2.grp
 11     and t1.qty = 0
 12         ) t
 13             )
 14   where rn <= order_qty_limit
 15   order by grp, oseq
 16  /

       GRP PR
---------- --
         1 p8
         1 p2
         2 p6
         2 p9
March 16, 2004 t -- Thanks for the question regarding "lmt and uniform extents", version 9.2.0.4
You Asked
In one of oracle's pre 9.x white papers on fragmentation, "How to stop
defragmenting and start living" (paper 711) it suggested creating tablespaces
small,medium,large, and extra large and in these tablespaces using extent sizes:

"Follow these three simple rules when assigning a segment to a tablespace.
1) Segments smaller than 160M should be placed in 160K extent tablespaces.
2) Segments between 160M and 5120M should be placed in 5120K extent
tablespaces.
3) Segments larger than 5120M should be placed in 160M extent tablespaces.
These extent sizes are chosen to be a multiple of five blocks since Oracle7 will
round all extent sizes to a multiple of five
blocks. In Oracle8 extents won’t be rounded to a multiple of five blocks if they
are a multiple of the minimum extent size
for the tablespace. For Oracle8 databases, choosing the following extent sizes
is a little simpler.
1) Segments smaller than 128M should be placed in 128K extent tablespaces.
2) Segments between 128M and 4G should be placed in 4M extent tablespaces.
3) Segments larger than 4G should be placed in 128M extent tablespaces"

According to some document I read on LMT.
"When creating Locally Managed Tablespaces(lmt) if you don't specific uniform
extents it will allocate 64k extents to an object until the object reaches 1mb
total size, then allocate 1mb until the object reaches 64mb total size, then
allocate 8mb extents until the object reaches 1gb ins size, after this it will
create 64mb extents."

My question:
Is there any point in allocating uniform extents like the white paper suggests
for lmt because won't the autoallocation of extents used by lmt keep
fragmentation very low anyway?
I am unsure of whether to create lmt uniform extents where extent size is based
on the white paper suggestions, or just leave
lmt to create it's extents by default, because I think lmt using it's default
extents will keep fragmentation low.

Thank you.
and we said...
autoallocation of extents will remove fragmentation pretty much, yes.

I've gotten into the habit of

a) an application gets a tablespace
b) the tablespace is an LMT with auto-allocated extents
c) the use of "initial", "next", "pctincrease" is outlawed

if you have access to my book "Effective Oracle by Design", i describe this sort
of layout and how it works.
Review & Followup

Rating: 5
autoallocation March 16, 2004
Reviewer: reader
However, if you chose autoallocation there is a possibility of different extent
sizes for different objects that can exist at any point of time in the LMT. I
was wondering, if I drop a table say that had extents created with 128k extent
sizes, can another table that needs 1M extent size use these freed up 128
extents? Thanks.
Followup:
well, there are two cases to consider:

a) you drop a table and don't recreate new ones (just free up space)

b) you drop a table and create a new one

In case (b), there are no issues -- all of the extents, everyone, that you just
freed up is available for reuse.

It only gets sticky in case (a), but really -- not so much.  The freed extents
are all nice powers of two and happen with large frequencies.  It is true if I
just dropped a table that had a bunch of 64k extents and then a 128k -- and
someone else needed 1m, i *might* not have sufficient free contigous extents --
but odds are the 64k's were all contigous (it tries to do that) and would be the
1m you were looking for.

It does not remove the possibility that you have free space of Xm and you do an
allocation of an extent of Ym and Y < X that it could fail -- just makes it
highly unlikely (by having well sized extents that fit into eachother nicely and
having very very few of them -- few distinct sizes that is)

Beats DMTs hands down and for tables where you "just don't know" or for
applications where "tables won't really be dropped" (that is many i would
presume as it is few and far between I see tables actually getting dropped) they
are perfect.
March 16, 2004 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.
Rating: 3
remove nulls from tile? February 09, 2004
Reviewer: Rob from MB, Canada
Is there a way to ntile without the nulls or place nulls in tile 0?

IE
select cust_no,
sum(case when to_char(ord_date,'YYYY')='1999' then net else 0 end) sales_1999,
sum(case when to_char(ord_date,'YYYY')='2000' then net else 0 end) sales_2000,
ntile(10) over(order by sum(case when to_char(ord_date,'YYYY')='1999' then net
else 0 end) desc) decile_1999,
ntile(10) over(order by sum(case when to_char(ord_date,'YYYY')='2000' then net
else 0 end) desc) decile_2000
from sales_data
group by cust_no

the problem is that the decile_2000 deciles all customers, we would like to
exclude customers with no purchases from the decile.  I tried caseing the net>0
but then I'm told I need to group by net (ora-00979) and thats not correct.
Followup:
Pretty sure this does it -- note, used an inline view to make "more readable" --
you can undo that but I would keep my use of trunc instead of to_char, consumes
less cpu on big result sets.

ops$tkyte@ORA9IR2> create table t ( cust_no number, ord_date date, net number );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t
  2  select trunc(rownum/2),
  3         add_months( to_date('01-jun-1999'), mod(rownum,2)*12 ),
  4             user_id+1
  5    from all_users
  6  /

48 rows created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select cust_no,
  2         sales_1999,
  3         decode(sales_1999,null,to_number(null),
  4             ntile(10) over (partition by decode(sales_1999,null,null,'x')
  5                             order by sales_1999)) decile_1999,
  6         sales_2000,
  7         decode(sales_2000,null,to_number(null),
  8             ntile(10) over (partition by decode(sales_2000,null,null,'x')
  9                             order by sales_2000)) decile_2000
 10    from (
 11  select cust_no,
 12         sum(decode(trunc(ord_date,'y'),to_date('01-jan-1999'),net))
sales_1999,
 13         sum(decode(trunc(ord_date,'y'),to_date('01-jan-2000'),net))
sales_2000
 14    from t
 15   group by cust_no
 16         )
 17   order by sales_1999 NULLS LAST
 18  /

   CUST_NO SALES_1999 DECILE_1999 SALES_2000 DECILE_2000
---------- ---------- ----------- ---------- -----------
         1          6           1         12           1
         2         20           1         22           1
         3         31           1         32           2
         4         33           2         34           2
         5         36           2         37           2
         6         40           2         41           3
         8         43           3         47           3
         7         45           3         46           3
         9         49           3         50           4
        10         51           4         52           4
        11         54           4         55           4
        12         56           4         57           5
        13         58           5         59           5
        14         60           5         61           6
        16         65           6         82           8
        18         71           6         72           7
        19         73           7         74           7
        20         75           7         83           8
        21         84           8         85           9
        22         86           8         87           9
        23         88           9        100          10
        24         93           9
        15         96          10         64           6
        17        102          10        103          10
         0                                 1           1

25 rows selected.
ops$tkyte@ORA9IR2> select cust_no,
  2         sales_1999,
  3         decode(sales_1999,null,to_number(null),
  4             ntile(10) over (partition by decode(sales_1999,null,null,'x')
  5                             order by sales_1999)) decile_1999,
  6         sales_2000,
  7         decode(sales_2000,null,to_number(null),
  8             ntile(10) over (partition by decode(sales_2000,null,null,'x')
  9                             order by sales_2000)) decile_2000
 10    from (
 11  select cust_no,
 12         sum(decode(trunc(ord_date,'y'),to_date('01-jan-1999'),net))
sales_1999,
 13         sum(decode(trunc(ord_date,'y'),to_date('01-jan-2000'),net))
sales_2000
 14    from t
 15   group by cust_no
 16         )
 17   order by sales_2000 NULLS LAST
 18  /

   CUST_NO SALES_1999 DECILE_1999 SALES_2000 DECILE_2000
---------- ---------- ----------- ---------- -----------
         0                                 1           1
         1          6           1         12           1
         2         20           1         22           1
         3         31           1         32           2
         4         33           2         34           2
         5         36           2         37           2
         6         40           2         41           3
         7         45           3         46           3
         8         43           3         47           3
         9         49           3         50           4
        10         51           4         52           4
        11         54           4         55           4
        12         56           4         57           5
        13         58           5         59           5
        14         60           5         61           6
        15         96          10         64           6
        18         71           6         72           7
        19         73           7         74           7
        16         65           6         82           8
        20         75           7         83           8
        21         84           8         85           9
        22         86           8         87           9
        23         88           9        100          10
        17        102          10        103          10
        24         93           9

25 rows selected.
Rating: 5
Perfect February 10, 2004
Reviewer: Rob from MB, Canada
Thats exactly what I was looking for.  I'm surpised that a trunc and to_date
comparison is faster than a to_char comparison, but I'll take your word on it.

Just for refernece here's our decile, sales-data cube.

select
 cust_no,
 store_count,
 sales_ltd,
 min_sale,
 max_sale,
 first_purchase,
 last_purchase,
 sales_1999,
 sales_2000,
 sales_2001,
 sales_2002,
 sales_2003,
 sales_2004,
 decode(sales_1999,null,to_number(null),
               ntile(10) over (partition by decode(sales_1999,null,null,'x')
                               order by sales_1999 desc nulls last))
decile_1999,
 decode(sales_2000,null,to_number(null),
               ntile(10) over (partition by decode(sales_2000,null,null,'x')
                               order by sales_2000 desc nulls last))
decile_2000,
 decode(sales_2001,null,to_number(null),
               ntile(10) over (partition by decode(sales_2001,null,null,'x')
                               order by sales_2001 desc nulls last))
decile_2001,
 decode(sales_2002,null,to_number(null),
               ntile(10) over (partition by decode(sales_2002,null,null,'x')
                               order by sales_2002 desc nulls last))
decile_2002,
 decode(sales_2003,null,to_number(null),
               ntile(10) over (partition by decode(sales_2003,null,null,'x')
                               order by sales_2003 desc nulls last))
decile_2003,

decode(decode(nvl(sales_2000,0)+nvl(sales_2001,0),0,null,nvl(sales_2000,0)+nvl(sa
les_2001,0)),null,to_number(null),
               ntile(10) over (partition by
decode(decode(nvl(sales_2000,0)+nvl(sales_2001,0),0,null,nvl(sales_2000,0)+nvl(sa
les_2001,0) ),null,null,'x')
                               order by
decode(nvl(sales_2000,0)+nvl(sales_2001,0),0,null,nvl(sales_2000,0)+nvl(sales_200
1,0) ) desc nulls last)) decile_00_01,

decode(decode(nvl(sales_2002,0)+nvl(sales_2003,0),0,null,nvl(sales_2002,0)+nvl(sa
les_2003,0)),null,to_number(null),
               ntile(10) over (partition by
decode(decode(nvl(sales_2002,0)+nvl(sales_2002,0),0,null,nvl(sales_2002,0)+nvl(sa
les_2003,0)),null,null,'x')
                               order by
decode(nvl(sales_2002,0)+nvl(sales_2003,0),0,null,nvl(sales_2002,0)+nvl(sales_200
3,0)) desc nulls last)) decile_02_03
 from (select cust_no,
               count(distinct br_no) store_count,
               sum(net) sales_ltd,
               min(net) min_sale,
               max(net) max_sale,
               min(ord_date) first_purchase,
               max(ord_date) last_purchase,
               sum(decode(trunc(ord_date,'y'),to_date('01-jan-1999'),net))
sales_1999,
               sum(decode(trunc(ord_date,'y'),to_date('01-jan-2000'),net))
sales_2000,
               sum(decode(trunc(ord_date,'y'),to_date('01-jan-2001'),net))
sales_2001,
               sum(decode(trunc(ord_date,'y'),to_date('01-jan-2002'),net))
sales_2002,
               sum(decode(trunc(ord_date,'y'),to_date('01-jan-2003'),net))
sales_2003,
               sum(decode(trunc(ord_date,'y'),to_date('01-jan-2004'),net))
sales_2004
       from
         sales_data
       group by cust_no )
Followup:
to_char = lots of NLS stuff, conversion from 7 byte binary to string.
trunc = set some bytes to "0"
Rating: 3
ntile on value, not row? February 17, 2004
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
Hello Tom,

I would like to divide a number of rows into N buckets, but not equal number of
rows, i would like a equal number based on summing the "value" column.

Example with 4 buckets:

Name     Val bucket
------ ----- ------
Ele1     100      1  (1 -> 100)
Ele2      20      2  (2 -> 20)
Ele3      30      3  (3 -> 30)
Ele4      30      4  (4 -> 30)
Ele5      30      2  (2 -> 50)
Ele6      80      3  (3 -> 110)
Ele7      15      4  (4 -> 45)
Ele8      50      2  (2 -> 100)
Ele9      70      4  (4 -> 115)
Ele10     20      1  (1 -> 120)
...

You get the idea. The idea is at the end to have a equal "sum" of value for each
bucket, or at least as close as possible.

Is it possible to do that with a single query ?
Followup:
absolutely, it is the technique I use to "bin" extents to divide a table up.
ntile doesn't work but a "group" column based on running_total/(total/ins)
will.

see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:10498431232211
Now, you seem to have some "algorithm" going there as the buckets are
1,2,3,4,2,3,4,2,4,1 -- and so on.  Mine algorithm will just take them as they
come -- getting "as close as it can" to the "perfect value", but it won't
"optimize" it in any fashion.
Rating: 4
Follow up February 17, 2004
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
Interesting approach,

However there are a few problems. For example, if the size of a single item is
over the "optimal" size per bucket, then a bucket is missing.

The algoritm above is not accurate as I typed the values manualy.

The algorithm should be:
"put the next value into the bucket with the smallest sum" and values are
ordered by value, descending.

That way, when one (or more) item is bigger then the optimal value, it will
simply be alone in a bucket, and all the remaining items will be equally
distributed among the rest of the buckets.

I did try the ordering with your approach, and here's the result:

select grp, sof, opt_chunk, name, sum(size_mb)
from (
select
 trunc( (sum(bytes) over (order by bytes desc)-0.01) /
              (sum(bytes) over ()/5) ) grp,
  round(sum(bytes) over (order by bytes desc)/1024/1024) as sof,
  round(sum(bytes) over ()/5/1024/1024) as opt_chunk,
segment_name name, round(bytes/1024/1024) as size_mb
from user_segments u
where segment_type = 'TABLE' and bytes > 1000000000
) t
group by grouping sets ((sof, opt_chunk, name,grp),grp)

Here's my result:

GRP    SOF    OPT_CHUNK    NAME    SUM(SIZE_MB)
------- ------- --------------- ------- -----------------
1    15489    8634        T1    15489
1                    15489
3    29114    8634        T2    13625
3    31162    8634        T3    2048
3    32698    8634        T4    1536
3    34120    8634        T5    1422
3                    18631
4    35530    8634        T6    1410
4    36814    8634        T7    1284
4    37966    8634        T8    1152
4    39060    8634        T9    1094
4    40110    8634        T10    1050
4    41130    8634        T11    1020
4    42150    8634        T12    1020
4    43170    8634        T13    1020
4                    9050

P.S.
How do I preserve formatting in follow ups? I tried pressing the Help in top
right corner, no info.
Followup:
to preserve formatting, just use "regular characters and spaces" (eg: no tabs)

the binning problem is an optimization problem really -- it is a procedural
algorithm.
Rating: 4
February 20, 2004
Reviewer: VKOUL from Lacey, WA USA
Excerpt :
"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."

I have tried to do it in following way, but the query takes long time of the
order of 15 to 20 minutes on 24K records.

SQL> drop table abc;

Table dropped.

SQL>
SQL> ed
Wrote file afiedt.buf

  1  CREATE TABLE abc (object_name, bucket)
  2  AS
  3  SELECT object_name, ntile(2000) OVER (ORDER BY object_name)
  4* FROM   all_objects
SQL>
SQL> /

Table created.

SQL> select count(*) from abc;
Press Enter ...

  COUNT(*)
----------
     24578

SQL> alter table abc add new_bucket number;

Table altered.

SQL> desc abc
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 OBJECT_NAME                               NOT NULL VARCHAR2(30)
 BUCKET                                             NUMBER
 NEW_BUCKET                                         NUMBER

SQL>
SQL> select count(object_name), count(bucket), count(new_bucket)
  2  from   abc;
Press Enter ...

COUNT(OBJECT_NAME) COUNT(BUCKET) COUNT(NEW_BUCKET)
------------------ ------------- -----------------
             24578         24578                 0

SQL>
SQL> UPDATE abc t1
  2  SET    new_bucket = (SELECT t2.new_ntile
  3                       FROM  (SELECT rowid row_id, ntile(2000) OVER (ORDER BY
object_name) new_ntile
  4                              FROM   abc
  5                             ) t2
  6                       WHERE  t1.rowid = t2.row_id
  7                      )
  8  /

24578 rows updated. (*** Very Slow ***)

SQL> commit;

Commit complete.

SQL>
SQL>
SQL> select count(*) from abc where bucket <> new_bucket;
Press Enter ...

  COUNT(*)
----------
         0

SQL> select count(object_name), count(bucket), count(new_bucket)
  2  from   abc;
Press Enter ...

COUNT(OBJECT_NAME) COUNT(BUCKET) COUNT(NEW_BUCKET)
------------------ ------------- -----------------
             24578         24578             24578

SQL> select count(*) from abc;
Press Enter ...

  COUNT(*)
----------
     24578

SQL>
SQL> alter table abc add count_bucket number;

Table altered.

SQL> ed
Wrote file afiedt.buf

  1  update abc
  2* set    count_bucket = trunc( rownum / (select count(*)/2000 from abc ) -
0.0001 ) + 1
SQL>
SQL>
SQL> /

24578 rows updated.

SQL> commit;

Commit complete.

SQL> select count(object_name), count(bucket), count(new_bucket),
count(count_bucket)
  2  from   abc;
Press Enter ...

COUNT(OBJECT_NAME) COUNT(BUCKET) COUNT(NEW_BUCKET) COUNT(COUNT_BUCKET)
------------------ ------------- ----------------- -------------------
             24578         24578             24578               24578

SQL>
SQL> select count(*)
  2  from   abc
  3  where  bucket <> count_bucket;
Press Enter ...

  COUNT(*)
----------
     24264

SQL>

Why it is so slow ???
Followup:
it is most likely executing the correlated subquery once per row.  so, it is
building the NTILE result 24,578 times -- scanning it to find the right rowid
and updating a single row at a time.

In 9i, we can do this via:

ops$tkyte@ORA920PC> merge into abc t1
  2  using ( select rowid rid, ntile(2000) over (order by object_name) new_ntile
  3            from abc ) t2
  4  on (t1.rowid = t2.rowid)
  5  when matched then update set new_bucket = new_ntile
  6  when not matched then insert (object_name) values(null);

30218 rows merged.

Elapsed: 00:00:03.10
ops$tkyte@ORA920PC> select * from abc where bucket <> new_bucket;

no rows selected
Rating: 4
Ntile February 20, 2004
Reviewer: VKOUL from Lacey, WA USA
Thanks, a Lot.

Very helpful
Rating: 3
Mystifying March 16, 2004
Reviewer: Raj from Bangalore,India
Dear Sir,
 The query
 "sql> 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;"
 is not obvious to me.Could you please demystify it?
How does it procede?
Bye!
Followup:
think of it like this:

select sal, deptno
  from emp
 where some_function(deptno,sal) < 2
 order by deptno, sal

that would make sense right?  if you had a function some_function that when
given a particular deptno, sal would return the count of records in that deptno
that had a sal greater than the input.

That correlated subquery:

(select count(sal)
               from emp
               where deptno=a.deptno
           and sal>a.sal)

is that function.  It is as if that query is run for each row.
Rating: 5
your 3rd question March 16, 2004
Reviewer: Mathan from Chennai, India
Tom,

     will this sql answer ur 3rd interview question?

select * from
(select month, count(1) from tab group by month order by 2 desc)
where rownum =1

     pls provide ur answer also (using a having & all)

advance thanks
Followup:
Nope.  that gets the wrong answer

ask yourself "what happens when the table has two rows - one with 1 and one with
2"

the problem is the answer is a SET, not a single ROW.
Rating: 5
Your 3rd question March 16, 2004
Reviewer: Nic from Dearborn, MI
Is this the answer to your 3rd question?

select month, count(*)
from t
group by month
having count(*) >=
    (select max(cnt) from
        (select month, count(*) cnt
        from t
        group by month))

It seems that there may be a better solution than this.  It queried the table
twice doing the same group by.
Followup:
that is one way

select month
  from t
 group by month
having count(*) >= ALL( select count(*) from t group by month)

is another, there are many many variations on this.  analytics open a whole new
realm of solutions.
March 16, 2004 wallacel -- Thanks for the question regarding "setting size of online redo log members", version 8.1.6
You Asked
Tom:

How to set the size of online redo log members?
bigger is better or smaller is better(or it doesn't matter)?

I don't think there are performace issue regarding the size
of online redo log members because i think
the amount of redo information server generated is indepedent
on the size of redo log member, am i right?

(both in archivelog mode and noarchivelog mode)

if this is true ,in archivelog mode, what i need to do is setup
enough redo log group so archive won't wait for lgwr to finish
its job. am i right?
and we said...
Redo sizing does impact performance.

Every time we log switch, we initiate a checkpoint.  If your
logs are very very small -- we'll checkpoint frequently.  This
will decrease performance.

On the other hand, if your redo logs are very very large -- we
won't checkpoint frequently (unless you have some other
checkpoint init.ora parameters set up) and hence if we crash, it
could take a long time to recover (lots of redo to reapply).

So, you need to trade those two things off -- there is no "one
size is right for everyone".  I've used sizes from 25meg to
1gig.  It just depends.  For everyday use -- I tend to use 25meg
redo log files.  You want them to switch every 20-30 minutes at
most in a "medium" use database (more frequently in a less used
database, less frequently in a mostly read only database).
Review & Followup

Rating: 5
Does this indicate that our log files are way to small??? March 16, 2004
Reviewer: Alan Rosenthal from San Diego, CA USA
Hi Tom,

In our Oracle 11i Apps system we use 10meg redo log files.  Below is a part of a
typical directory listing from our archive log directory.  Look at the
timestamps in the first column, please.  Doesn't this indicate that our log
files are too small?

Thanks,

Alan

00:00 arch_OAPROD_134907.arc
00:00 arch_OAPROD_134908.arc
00:00 arch_OAPROD_134909.arc
00:00 arch_OAPROD_134910.arc
00:00 arch_OAPROD_134911.arc
00:01 arch_OAPROD_134912.arc
00:02 arch_OAPROD_134913.arc
00:02 arch_OAPROD_134914.arc
00:02 arch_OAPROD_134915.arc
00:02 arch_OAPROD_134916.arc
00:02 arch_OAPROD_134917.arc
00:15 arch_OAPROD_134918.arc
00:44 arch_OAPROD_134919.arc
01:09 arch_OAPROD_134920.arc
01:34 arch_OAPROD_134921.arc
01:43 arch_OAPROD_134922.arc
01:45 arch_OAPROD_134923.arc
02:00 arch_OAPROD_134924.arc
02:00 arch_OAPROD_134925.arc
02:00 arch_OAPROD_134926.arc
02:00 arch_OAPROD_134927.arc
02:00 arch_OAPROD_134928.arc
02:01 arch_OAPROD_134929.arc
02:01 arch_OAPROD_134930.arc
02:02 arch_OAPROD_134931.arc
02:02 arch_OAPROD_134932.arc
02:02 arch_OAPROD_134933.arc
02:02 arch_OAPROD_134934.arc
02:02 arch_OAPROD_134935.arc
02:03 arch_OAPROD_134936.arc
02:17 arch_OAPROD_134937.arc
02:41 arch_OAPROD_134938.arc
03:02 arch_OAPROD_134939.arc
03:19 arch_OAPROD_134940.arc
03:47 arch_OAPROD_134941.arc
04:00 arch_OAPROD_134942.arc
04:00 arch_OAPROD_134943.arc
04:00 arch_OAPROD_134944.arc
04:00 arch_OAPROD_134945.arc
04:01 arch_OAPROD_134946.arc
04:02 arch_OAPROD_134947.arc
04:02 arch_OAPROD_134948.arc
04:02 arch_OAPROD_134949.arc
04:02 arch_OAPROD_134950.arc
04:02 arch_OAPROD_134951.arc
04:07 arch_OAPROD_134952.arc
04:33 arch_OAPROD_134953.arc
05:01 arch_OAPROD_134954.arc
05:04 arch_OAPROD_134955.arc
05:05 arch_OAPROD_134956.arc
05:27 arch_OAPROD_134957.arc
05:34 arch_OAPROD_134958.arc
05:38 arch_OAPROD_134959.arc
05:57 arch_OAPROD_134960.arc
05:58 arch_OAPROD_134961.arc
06:00 arch_OAPROD_134962.arc
06:00 arch_OAPROD_134963.arc
06:00 arch_OAPROD_134964.arc
06:00 arch_OAPROD_134965.arc
06:01 arch_OAPROD_134966.arc
06:01 arch_OAPROD_134967.arc
06:01 arch_OAPROD_134968.arc
06:01 arch_OAPROD_134969.arc
06:02 arch_OAPROD_134970.arc
06:02 arch_OAPROD_134971.arc
06:02 arch_OAPROD_134972.arc
06:02 arch_OAPROD_134973.arc
06:03 arch_OAPROD_134974.arc
06:05 arch_OAPROD_134975.arc
06:27 arch_OAPROD_134976.arc
06:53 arch_OAPROD_134977.arc
07:16 arch_OAPROD_134978.arc
07:36 arch_OAPROD_134979.arc
07:44 arch_OAPROD_134980.arc
07:49 arch_OAPROD_134981.arc
08:00 arch_OAPROD_134982.arc
08:00 arch_OAPROD_134983.arc
08:00 arch_OAPROD_134984.arc
08:00 arch_OAPROD_134985.arc
08:00 arch_OAPROD_134986.arc
08:00 arch_OAPROD_134987.arc
08:02 arch_OAPROD_134988.arc
08:02 arch_OAPROD_134989.arc
08:02 arch_OAPROD_134990.arc
08:02 arch_OAPROD_134991.arc
08:02 arch_OAPROD_134992.arc
08:13 arch_OAPROD_134993.arc
08:13 arch_OAPROD_134994.arc
08:17 arch_OAPROD_134995.arc
08:39 arch_OAPROD_134996.arc
08:55 arch_OAPROD_134997.arc
09:05 arch_OAPROD_134998.arc
09:08 arch_OAPROD_134999.arc
09:10 arch_OAPROD_135000.arc
09:12 arch_OAPROD_135001.arc
09:12 arch_OAPROD_135002.arc
09:12 arch_OAPROD_135003.arc
09:13 arch_OAPROD_135004.arc
09:13 arch_OAPROD_135005.arc
09:15 arch_OAPROD_135006.arc
09:28 arch_OAPROD_135007.arc
09:51 arch_OAPROD_135008.arc
10:00 arch_OAPROD_135009.arc
10:00 arch_OAPROD_135010.arc
10:00 arch_OAPROD_135011.arc
10:00 arch_OAPROD_135012.arc
10:00 arch_OAPROD_135013.arc
10:01 arch_OAPROD_135014.arc
10:01 arch_OAPROD_135015.arc
10:02 arch_OAPROD_135016.arc
10:02 arch_OAPROD_135017.arc
10:02 arch_OAPROD_135018.arc
10:02 arch_OAPROD_135019.arc
10:02 arch_OAPROD_135020.arc
10:07 arch_OAPROD_135021.arc
10:31 arch_OAPROD_135022.arc
10:57 arch_OAPROD_135023.arc
11:12 arch_OAPROD_135024.arc
11:12 arch_OAPROD_135025.arc
11:17 arch_OAPROD_135026.arc
11:33 arch_OAPROD_135027.arc
11:52 arch_OAPROD_135028.arc
12:00 arch_OAPROD_135029.arc
12:00 arch_OAPROD_135030.arc
12:00 arch_OAPROD_135031.arc
12:00 arch_OAPROD_135032.arc
12:00 arch_OAPROD_135033.arc
12:01 arch_OAPROD_135034.arc
12:02 arch_OAPROD_135035.arc
12:02 arch_OAPROD_135036.arc
12:02 arch_OAPROD_135037.arc
12:02 arch_OAPROD_135038.arc
12:02 arch_OAPROD_135039.arc
12:24 arch_OAPROD_135040.arc
12:48 arch_OAPROD_135041.arc
13:13 arch_OAPROD_135042.arc
13:16 arch_OAPROD_135043.arc
13:17 arch_OAPROD_135044.arc
13:36 arch_OAPROD_135045.arc
13:58 arch_OAPROD_135046.arc
14:00 arch_OAPROD_135047.arc
14:00 arch_OAPROD_135048.arc
14:00 arch_OAPROD_135049.arc
14:00 arch_OAPROD_135050.arc
14:00 arch_OAPROD_135051.arc
14:00 arch_OAPROD_135052.arc
14:02 arch_OAPROD_135053.arc
14:02 arch_OAPROD_135054.arc
14:02 arch_OAPROD_135055.arc
14:02 arch_OAPROD_135056.arc
14:02 arch_OAPROD_135057.arc
14:02 arch_OAPROD_135058.arc
14:03 arch_OAPROD_135059.arc
14:20 arch_OAPROD_135060.arc
14:24 arch_OAPROD_135061.arc
14:26 arch_OAPROD_135062.arc
14:45 arch_OAPROD_135063.arc
Followup:
no, it doesn't :)  not necessarily.

checkpoint not complete messages could indicate there are too few or the one you
have are too small...

I would tend to be on the side of "they might be a tad small", but without all
of the facts -- it is hard to say.  If you make them really big and don't play
around with fast_start_mttr/io_target and other checkpoint controlling things --
perhaps if you make them really big you cannot checkpoint fast enough anymore.
You could start running into massive buffer busy waits.

But on the other hand, by switching so fast, you are checkpointing all of the
time which *might* not be good as well.

So, one data point does not a case make.
March 16, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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.
Rating: 5
What is exchange deadlock January 20, 2004
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom,
Recently, in a statspack report we saw exchange deadlock. From Metalink, we
found that exchange deadlock is same as buffer deadlock. Is that correct? What
exactly is an exchange deadlock?
Thanks
Followup:
defined: "exchange deadlocks" is the number of times that a process detected
potential deadlock when exchanging two buffers and therefore raised an internal,
restartable error. Index scans are currently the only operation which perform
exchanges.
Rating: 5
Multiple SQLs slower the response January 29, 2004
Reviewer: Wor from USA
Hello Tom,
I am running on Win 2000 , Oracle 8.1.7.
I have one SQL query which has around 7 joins ( including some outer joins )
returns 65K rows, 12 columns , It takes 3 minutes to execute from one machine
one at a time.
But If I execute the same SQL query simultaneously from 10 different client
machines on the same Oracle server after 15 minutes the results are displayed on
all 10 machines.
I am sure Oracle should be much more powerful than this.
On just 10 users that same SQl takes three times more time to execute. What will
happen if 1000 users request the SQL execution, with this rate the same SQL will
take Hours.
Can some thing be done to increase the performance ?

In ideal case I will have different SQL's running simultaneosly, more or less
the Tables will be the same, thats why here for this example I used the same
query 10 times.
Thanks in advance,
Wor
Followup:
Umm, you haven't told anyone about anything here that could let them say
anything useful.

how big is this win 2k machine.  If I hit my laptop with 10 concurrent users --
guess what happens.

You size your hardware based on your desired load.  Oracle cannot makeup CPU and
disk speed out of thin air.  You have to have hardware able to accomodate your
load.
Rating: 4
Very good information February 17, 2004
Reviewer: Emmett Cleveland from Houston, TX USA
Tom, I have learned a lot from your site and your book.
I am not sure if this is the proper thread but I am looking at a lock type,
under v$lock, of "TO".
I can not find this lock type on metalink or in any Oracle Doc.
Could you please explain what type of lock this is?
Followup:
The TO enqueue protects concurrent DDL operations on temporary
tables. It is acquired in exclusive(X) mode during ALTER, DROP and CREATE
operations on a temporary table or its indexes.

Acquired in SX mode when performing DML against a temporary object
Acquired in S mode for DDL index operations.

The purpose of the lock is to prevent DDL from being issued against an
object while DML is occurring.
Rating: 4
Answered my own question. February 17, 2004
Reviewer: Emmett Cleveland from Houston, TX USA
Sorry to waste your time, I found the answer to my post about TO lock type,
It is a lock that was introduced in 8i that controls DDL for temporary tables.
Rating: 3
February 18, 2004
Reviewer: A reader
very useful
Rating: 5
about maxtrans February 23, 2004
Reviewer: lizhuohua from China
Dear tom,

This puzzles me:

create table p(x int) maxtrans 1;
insert into p values(1);
insert into p values(2);
commit;
update p set x=3 where x=1;
1 row updated.

in another sql/plus:

update p set x=4 where x=2;
1 row updated.

 select sid, type, id1, id2, lmode, block, request
 from v$lock where type='TX';

       SID TY        ID1        ID2      LMODE      BLOCK    REQUEST
---------- -- ---------- ---------- ---------- ---------- ----------
        10 TX     327721       2709          6          0          0
        15 TX     655390       2699          6          0          0

Why the "maxtrans" doesn't work?

(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)
Followup:
maxtrans is silently now 2 regardless of what you set it to in 9i and above.
Rating: 5
maxtrans February 23, 2004
Reviewer: Alberto Dell'Era from Milan, Italy
> maxtrans is silently now 2 regardless of what you set it to in 9i and above.

You mean "AT LEAST 2", i suppose ... ?
Followup:
ahh, yes, of course :)

thanks for pointing that out.
Rating: 5
Thanks February 24, 2004
Reviewer: A reader
Thanks a lot Tom for your help!
Rating: 5
INSERT NOWAIT March 02, 2004
Reviewer: Cefers.br from Brasil
Tom,

If a session inserts into a table that has a PRIMARY KEY and doesn´t COMMIT, any
other sessions that try to insert records with the same PRIMARY KEY will be
locked until the first session commits.

In this case, I want the second session to receive an error message instead of
being locked until COMMIT. It would be something like NOWAIT clause on SELECT
FOR UPDATE.

Is it possible?

(I´m using 9iR2)
Followup:
I'd really want to look at why I was generating the same primary key first and
foremost but....
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:340617419132
shows how you can accomplish this.
Rating: 4
dba_dml_locks & dba_ddl_locks March 02, 2004
Reviewer: Sami from NJ,USA
Dear Tom,

What is the use of "dba_dml_locks" and "dba_ddl_locks"

Select count(*) from dba_dml_locks;
0 record.

Select count(*) from dba_dml_locks;
35,000 records.
Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96536/ch2228.htm#1308691

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96536/ch2238.htm#1308850
35k in dba_dml_locks?  impressively large.
Rating: 4
DBA_DDL_LOCKS records are not deleted like DBA_DML_LOCKS? March 03, 2004
Reviewer: Sami
Sorry Tom,

In my previous post,the second select should be read as "DBA_DDL_LOCKS".

Question,
Why do I have  27683 DDL_LOCKS? Looks like it got accomulaed over the period.
The record stays in DBA_DDL_LOCKS, even after releasing the DDL lock?

Am I corerct?

Below query is from diffenent environment (than previous posting)

SQL> select count(*) from dba_dml_locks;

  COUNT(*)
----------
         2

SQL> select count(*) from dba_ddl_locks;

  COUNT(*)
----------
     27683

SQL>
Followup:
that view is "dynamic", you must have 27k dba_ddl_locks in action right now:

create or replace view dba_ddl_locks (
SESSION_ID
,OWNER
,NAME
,TYPE
,MODE_HELD
,MODE_REQUESTED
) as
select  s.sid session_id,
          substr(ob.kglnaown,1,30) owner,
          substr(ob.kglnaobj,1,30) name,
    decode(ob.kglhdnsp, 0, 'Cursor', 1, 'Table/Procedure/Type', 2, 'Body',
           3, 'Trigger', 4, 'Index', 5, 'Cluster', 13, 'Java Source',
             14, 'Java Resource', 32, 'Java Data', to_char(ob.kglhdnsp)) type,
    decode(lk.kgllkmod, 0, 'None', 1, 'Null', 2, 'Share', 3, 'Exclusive',
       'Unknown') mode_held,
    decode(lk.kgllkreq,  0, 'None', 1, 'Null', 2, 'Share', 3, 'Exclusive',
       'Unknown') mode_requested
   from v$session s, x$kglob ob, x$kgllk lk
   where lk.kgllkhdl = ob.kglhdadr
   and   lk.kgllkuse = s.saddr
   and   ob.kglhdnsp != 0

/

it is built off of v$ and x$ tables that do not persist
Rating: 4
Is it really strange to have 20K+ DDL Locks March 03, 2004
Reviewer: A reader
Dear Tom,

Why these many DDL locks? Is something wrong in this environment? Or it is
normal to have 20k+ locks?

If it is not okay where should I start investigating this issue?

I checked the same environment again, now it got reduced from 27683 to 21318.

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 count(*) from dba_ddl_locks;

  COUNT(*)
----------
     21318

SQL> select count(*) from dba_dml_locks;

  COUNT(*)
----------
         3

SQL>
Followup:
why don't you investigate what they are rather than counting them? (eg: look at
the values therein).

could be perfectly normal -- if you have lots of users, doing lots of stuff.
You'll see entries for sequences, tables, packages, procedures and the like --
stuff people are "using".

Could be just fine and dandy if you have lots of connected users doing stuff.
Rating: 4
DBA_DDL_LOCKS break-up March 03, 2004
Reviewer: Sami
18:18:07 SQL>
SELECT TYPE,COUNT(*) FROM DBA_DDL_LOCKS GROUP BY TYPE;18:18:09 SQL>

TYPE                                       COUNT(*)
---------------------------------------- ----------
10                                                1
11                                                1
16                                                2
18                                             1956
Body                                           1142
Java Data                                       169
Java Resource                                     3
Table/Procedure/Type                          24494
Trigger                                         765

9 rows selected.

SQL> set time on
18:09:03 SQL> /

  COUNT(*)
----------
     27054

18:09:08 SQL> /

  COUNT(*)
----------
     27051

18:09:14 SQL> /

  COUNT(*)
----------
     27053

18:09:20 SQL> /

  COUNT(*)
----------
     27607

18:10:02 SQL> /

  COUNT(*)
----------
     27621
Followup:
looks just fine, you just have lots of people doing lots of stuff.  nothing to
worry about here -- you have an active database.
Rating: 4
(dba_ddl_locks)harmless but trying to understand the logic.. March 04, 2004
Reviewer: Sami from NJ,USA
Dear Tom,

Sorry for bothering you again.

I went thur manuals and books but still I could not understand the
meaning/purpose of DDL locks.

Please enlighten me with your valuable comments on this.

  1  SELECT name,COUNT(*) FROM DBA_DDL_LOCKS
  2  WHERE mode_held IS NOT NULL AND TYPE='Trigger'
  3  GROUP BY name
  4* order by 2 desc
SQL> /

NAME                                       COUNT(*)
---------------------------------------- ----------
PROFILEDUSER_TR01                               185
PROFILEDUSER_TR02                               184
LOGONDETAILS_TR01                                90
LOGONCAMATTRIBUTES_TR01                          72
:
:

SQL> SELECT session_id,mode_held,mode_requested
FROM DBA_DDL_LOCKS
WHERE mode_held IS NOT NULL AND TYPE='Trigger'
AND  name='PROFILEDUSER_TR01'  2    3    4
  5  /

SESSION_ID MODE_HELD MODE_REQU
---------- --------- ---------
        25 Null      None
        36 Null      None
        36 Null      None
        36 Null      None
        36 Null      None
        36 Null      None
        39 Null      None
        41 Null      None
:
:
:

What does it mean by having 185 DDL locks on PROFILEDUSER_TR01?

Why DDL locks are higher than DML locks?

My understanding is that DDL lock should occur while some one is performing DDL
operations on an object.
Followup:
do you have access to my book Expert one on one Oracle?  I write about it in
there

it just means "people are using that, if you do something to profileduser_tr01,
there are 185 people using it that may be affected (or may prevent you from
doing the change)"
Rating: 3
Disable DDL March 16, 2004
Reviewer: A reader from NJ, USA
Tom,

I remember reading somewhere (your article or the Expert one-on-one book, I
don't remember exactly) that doing an ALTER TABLE DISABLE TABLE LOCK will
prevent any DDLs from being performed on the table. I searched your web-site but
couldn't find it. I would appreciate if you could point me to the link or the
chapter in your book where I could find this.

Thanks much
Followup:
It is in the chapter on locking and concurrency

...
An interesting side note to the TM lock. The total number of TM locks allowed in
the system is configurable by you (see the DML_LOCKS init.ora parameter
definition in the Oracle8I Server Reference manual for details). It may in fact
be set to 0. This does not mean that your database becomes a read-only database
(no locks), but rather that DDL is not permitted. This is useful in very
specialized application, such as OPS, to reduce the amount of intra-instance
coordination that would otherwise take place.  You can also remove the ability
to gain TM locks on an object by object basis using the ALTER TABLE tablename
DISABLE TABLE LOCK.

.........
Rating: 3
Thanks March 16, 2004
Reviewer: A reader from NJ, USA
Tom,

Thanks much for pointing me to the article. In the same paragragh, you mention
that disabling table lock removes the ability to perform a DDL on the table, but
allows DMLs to be performed (ie. database does not become read-only). Why does
disabling table lock using ALTER TABLE ... DISABLE TABLE LOCK prevents only DDL
from happening on that table. The reason I have this question a DML on a table,
takes 2 locks :
 a) a TM lock on the table (shared lock) so that no other session/session can
take an exclusive lock on the table
 b) a TX lock on the table to make sure that the affected rows are not changed
by another transaction/session

If the ALTER TABLE .. DISABLE TABLE LOCK removes the ability to gain a TM lock
on the table(s), then how can we perform a DML on that (since to perform a DML,
we need to have a TX lock on the rows which in turn requires a TM lock taken on
the table).

Please explain

Thanks
Followup:
the TM lock is not a "table lock" like "lock table in X mode", it is a lock
registered against that segment saying "hey, I've got some locking going on this
table".

It would prevent someone from dropping a column you are updating for example.
It is lightweight.

the TX lock isn't on the "table", you get one -- not one per table:

ops$tkyte@ORA9IR2> update emp set ename=ename;

14 rows updated.

ops$tkyte@ORA9IR2>  select * from v$lock where sid = (select sid from v$mystat
where rownum=1);

ADDR     KADDR           SID TY        ID1        ID2      LMODE    REQUEST

-------- -------- ---------- -- ---------- ---------- ---------- ----------
56B41D8C 56B41E98         10 TX     393258       9816          6          0

56AE9D48 56AE9D5C         10 TM      35514          0          3          0

ops$tkyte@ORA9IR2> ops$tkyte@ORA9IR2> update dept set dname=dname;

4 rows updated.

ops$tkyte@ORA9IR2>  select * from v$lock where sid = (select sid from v$mystat
where rownum=1);

ADDR     KADDR           SID TY        ID1        ID2      LMODE    REQUEST

-------- -------- ---------- -- ---------- ---------- ---------- ----------
56B41D8C 56B41E98         10 TX     393258       9816          6          0

56AE9DCC 56AE9DE0         10 TM      35515          0          3          0

56AE9D48 56AE9D5C         10 TM      35514          0          3          0

A TM per segment, a TX per transaction.

Basically the alter table disable table lock is preventing the "high mode" locks
-- the ones that prevent other concurrent activity.

An update for example takes a "row exclusive" (mode=3) 'table lock'.  It means
"hey, I've got some rows locked in exclusive mode in this table (not really a
table lock truly).  as long as you don't try to lock the same rows -- you can
have at this table"

A locak table in exclusive mode takes an "exclusive" lock -- it gets them all --
it does this at the table level.  It is a true "table lock"

Consider:

ops$tkyte@ORA9IR2> alter table emp disable table lock;
Table altered.

ops$tkyte@ORA9IR2> lock table emp in row share mode;
Table(s) Locked.

that is like SELECT FOR UPDATE does.

ops$tkyte@ORA9IR2> lock table emp in row exclusive mode;
Table(s) Locked.

That is like UPDATE does

ops$tkyte@ORA9IR2> lock table emp in share mode;
lock table emp in share mode
           *
ERROR at line 1:
ORA-00069: cannot acquire lock -- table locks disabled for EMP

ops$tkyte@ORA9IR2> lock table emp in share row exclusive mode;
lock table emp in share row exclusive mode
           *
ERROR at line 1:
ORA-00069: cannot acquire lock -- table locks disabled for EMP

ops$tkyte@ORA9IR2> lock table emp in exclusive mode;
lock table emp in exclusive mode
           *
ERROR at line 1:
ORA-00069: cannot acquire lock -- table locks disabled for EMP

those last three are true TABLE LEVEL locks -- locked the entire table.  They
are what are prohibited

guess the command should be

alter table t disable "most" table lock;
Rating: 4
March 16, 2004
Reviewer: A reader
Great
Rating: 3
Row Share mode March 16, 2004
Reviewer: A reader from NJ, USA
Tom,

My understanding with SELECT FOR UPDATE (without NOWAIT) is that the statement
will take an
exclusive lock on the selected rows (provided no other session is holding an
exclusive lock
on those rows). However, in your earlier discussion you mention :
"
   ops$tkyte@ORA9IR2> lock table emp in row share mode;
   Table(s) Locked.

   that is like SELECT FOR UPDATE does.

   ops$tkyte@ORA9IR2> lock table emp in row exclusive mode;
   Table(s) Locked.

   That is like UPDATE does
"

But I thought both of the above would hold an exclusive lock on the affected
rows. Why does
SELECT FOR UPDATE take a lock in row share mode? Does that mean that other
session can still
be able to change the same row. If so, what is the use of pessimistic locking?
I am getting confused

Please explain

Thanks
Followup:
No, other sessions cannot lock that row.  It is a row share table lock and
defined as:

<quote>
Row Share Table Locks (RS) A row share table lock (also sometimes called a
subshare table lock, SS) indicates that the transaction holding the lock on the
table has locked rows in the table and intends to update them. A row share
table lock is automatically acquired for a table when one of the following SQL
statements is executed: SELECT ... FROM table ... FOR UPDATE OF ... ; LOCK TABLE
table IN ROW SHARE MODE; A row share table lock is the least restrictive mode of
table lock, offering the highest degree of concurrency for a table.

Permitted Operations: A row share table lock held by a transaction allows
other transactions to query, insert, update, delete, or lock rows concurrently
in the same table. Therefore, other transactions can obtain simultaneous row
share, row exclusive, share, and share row exclusive table locks for the same
table.

Prohibited Operations: A row share table lock held by a transaction prevents
other transactions from exclusive write access to the same table using only the
following statement: LOCK TABLE table IN EXCLUSIVE MODE;

You might want to glance at the concepts guide -- the section on "how oracle
locks data" to get a description of all of the locks used and what other locks
can coexist with it
Rating: 4
What about regular UPDATE March 16, 2004
Reviewer: A reader from NJ, USA
Tom,

What ever applies to RS/SS lock obtained by SELECT .. FOR UPDATE applies to
regular UPDATE statement also right? If not what is the difference between
SELECT .. FOR .. UPDATE and the regular UPDATE statement except that the former
indicates an intention for the transaction to update the specified rows. Is my
understanding not correct. If so, please explain the difference between SELECT
.. FOR ..UPDATE that takes a SS/RS lock and the regular UPDATE that takes a
Exclusive row lock
Followup:
update is a little "stronger", consider:

ops$tkyte@ORA9IR2> create table emp as select * from scott.emp;

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> lock table emp in share mode;

Table(s) Locked.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2          pragma autonomous_transaction;
  3  begin
  4          for x in ( select ename from emp where rownum = 1 for update )
  5          loop
  6                  dbms_output.put_line( 'locked ' || x.ename );
  7          end loop;
  8          commit;
  9  end;
 10  /
locked SMITH

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2          pragma autonomous_transaction;
  3  begin
  4          for x in ( select ename from emp where rownum = 1 for update )
  5          loop
  6                  dbms_output.put_line( 'locked ' || x.ename );
  7                  update emp set ename = ename where ename = x.ename;
  8                  dbms_output.put_line( 'updated ' || x.ename );
  9          end loop;
 10          commit;
 11  end;
 12  /
locked SMITH
declare
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
ORA-06512: at line 7

we are able to "select for update" SMITH, but we are not able to actually
update SMITH because of the outstanding table level share lock

(again -- concepts guide, lists all locks, describes all locks, tells us what
locks coexist with other locks.  check it out!)
March 16, 2004 Peter -- Thanks for the question regarding "Parsing a varchar for use in a comparison test.", version 8.1.7
You Asked
Hello Tom
I want to compare a value is a table with a certain threshold.

I have a table as follows

Name VARCHAR2(32);
Value NUMBER;
Threshold VARCHAR2(10);

The threshold is a number plus the character > or < or =.

One entry in this table could be:

Name  = 'People';
Value = 101;
Threshold = '>100';

I want to test the Value against the Threshold and have tried
to use execute immediate but do not get the result I expect.

string1 := 'Too many people';
execute immediate 'begin if ('||Value Threshold||') then
dbms_output.put_line('||string1||'); end if; end;';

Thanks for any help on this
and we said...
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 'People', 101, '>100'
);

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 'Dogs', 11, '>1000' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace
  2  function eval( p_value in number,
  3                 p_threshold in varchar2,
  4                 p_when_true in varchar2,
  5                 p_when_false in varchar2 ) return varchar2
  6  as
  7      l_return varchar2(4000);
  8  begin
  9      execute immediate
 10      'select decode( count(*), 0, :when_false, 1, :when_true )
 11         from dual
 12        where :val ' || p_threshold
 13      into l_return
 14      using p_when_false, p_when_true, p_value;
 15
 16      return l_return;
 17  end;
 18  /

Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> column outcome format a30
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select t.*, eval( value, threshold, 'Too many
people', 'Ok' ) outcome
  2    from t
  3  /

NAME            VALUE THRESHOLD  OUTCOME
---------- ---------- ---------- ------------------------------
People            101 >100       Too many people
Dogs               11 >1000      Ok

ops$tkyte@ORA817DEV.US.ORACLE.COM>
Review & Followup

Rating: 5
Execute immediate - creating the statement from bind vars July 23, 2002
Reviewer: Peter Jones from Switzerland
Thanks for your help. I have identidified a common statement type and rather
than creating many procedures or sql statements I want to have one and give it 3
variables.
ObjectName, Operator (<,= or >) and Threshold.
I have checks like
NumberUsers < 5
Load > 12
State = 0 and there could be 100s like this, so I want
to manage one piece of common code only.

The following
procedure compiles but it gives an error when I run it probably because the
operator can not be a bind variable. However you managed to make this work with
execute immediate. I failed to make execute immediate work with a cursor so once
again I need your valuable help.

Many thanks in advance

create or replace procedure rule_check (RULE_NUMBER     in NUMBER ,
                        RULE_METRICNAME in VARCHAR2 ,
                        RULE_COMPARATOR in VARCHAR2 ,
                                        RULE_THRESHOLD  in NUMBER
                        )
is
type rc is ref cursor;
alarm_cur rc;
rec       perfmon_values%ROWTYPE;
begin
  open alarm_cur for 'select machine,timestamp,metricname,metricvalue
                     from perfmon_values p
                     where metricname=:RULE_METRICNAME and metricvalue
                     :RULE_COMPARATOR :RULE_THRESHOLD
                     group by machine,metricname,metricvalue,timestamp
                     having max(timestamp) = (select max(timestamp)
                                              from perfmon_values p2
                                              where p.machine=p2.machine
                                              and metricname=:RULE_METRICNAME);'

            USING
RULE_METRICNAME,RULE_COMPARATOR,RULE_THRESHOLD,RULE_METRICNAME;
 fetch alarm_cur into rec;
 loop
     BEGIN
        insert ....
    dbms_output.put_line('OK');
     EXCEPTION
              WHEN DUP_VAL_ON_INDEX THEN dbms_output.put_line('DUPLICATE');
              WHEN OTHERS THEN dbms_output.put_line('OTHER');
     END;
     fetch  alarm_cur into rec;
     exit when alarm_cur%NOTFOUND;
 end loop;
END;
/
Followup:
  open alarm_cur for 'select machine,timestamp,metricname,metricvalue
                     from perfmon_values p
                     where metricname=:RULE_METRICNAME and metricvalue  ' ||
                     RULE_COMPARATOR || ' :RULE_THRESHOLD
                     group by machine,metricname,metricvalue,timestamp
Rating: 4
Parsing a varchar for use in a comparison test July 25, 2002
Reviewer: Peter Jones from Switzerland
Hi
Thanks fo your reply. I tried the changes you suggest but the code still gives
errors that I dont understand on the select statement.

Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production

SQL> @pete.sql

Procedure created.

SQL> execute pete
BEGIN pete; END;

*
ERROR at line 1:
ORA-00920: invalid relational operator
ORA-06512: at "PETE.PETE", line 11
ORA-06512: at line 1

SQL> l 11
 11*  open alarm_cur for 'select machine,timestamp,metricname,metricvalue
SQL>

create or replace procedure pete (RULE_NUMBER     in NUMBER default 1,
                                  RULE_METRICNAME in VARCHAR2 default 'l',
                                  RULE_COMPARATOR in VARCHAR2 default 'l',

                                  RULE_THRESHOLD  in NUMBER default 1
                                  )
is
type rc is ref cursor;
alarm_cur rc;
rec       perfmon_values%ROWTYPE;
begin
 open alarm_cur for 'select machine,timestamp,metricname,metricvalue
                     from perfmon_values p
                     where metricname=:RULE_METRICNAME and metricvalue '
                     || RULE_COMPARATOR || ' :RULE_THRESHOLD
                     group by machine,metricname,metricvalue,timestamp
                     having max(timestamp) = (select max(timestamp)
                                              from perfmon_values p2
                                              where p.machine=p2.machine
                                              and metricname=:RULE_METRICNAME);'
                    USING 'CpuIdle',90,'CpuIdle';
                    --USING RULE_METRICNAME,RULE_COMPARATOR,RULE_THRESHOLD,RULE_
METRICNAME;
 fetch alarm_cur into rec;
 loop
     BEGIN
             dbms_output.put_line('OK');

     EXCEPTION
                      WHEN DUP_VAL_ON_INDEX THEN dbms_output.put_line('DUPLICATE
');
                      WHEN OTHERS THEN dbms_output.put_line('OTHER');
     END;
     fetch  alarm_cur into rec;
     exit when alarm_cur%NOTFOUND;
 end loop;
END;
Followup:
Look at the SQL you generate.

You are not passing a "comparator" -- you are just passing an expression.  The
sql you generate has a predicate like:

   .... and metricvalue 1 :rule_threshold ....

'1' is not a comparision operation -- I think rule_comparator should be
defaulted to '=' perhaps so that would be:

   .... and metricvalue = :rule_threshold ....

for example
Rating: 2
Parsing a varch for ue in a comparison test July 25, 2002
Reviewer: Peter Jones from Switzerland
Hello
I'm sorry, maybe I'm missing something. I have changed
the code as advised but I still get the error
SQL> execute pete
BEGIN pete; END;

*
ERROR at line 1:
ORA-00911: invalid character
ORA-06512: at "PETE.PETE", line 12
ORA-06512: at line 1
SQL> l 12
 12*  open alarm_cur for 'select machine,timestamp,metricname,metricvalue
Followup:
SQL> set serveroutput on

and add some dbms_output.put_lines to your code to actually print out the sql
statement and debug your code!
Rating: 5
pete,pete,pete your default = 1 July 25, 2002
Reviewer: J from ma
procedure pete (RULE_NUMBER  in NUMBER default 1,
            RULE_METRICNAME in VARCHAR2 default 'l',
RULE_COMPARATOR in VARCHAR2 default 'l',      <<<<<<<<<<<<< default to '=' or
something                                                     RULE_THRESHOLD  in
NUMBER default 1
                                  )
Rating: 3
Parsing a varchar for use in a comparison test July 26, 2002
Reviewer: Peter Jones from Switzerland
Hello
I fear the problem is more fundamental. I have trimmed down the code in order to
debug and I cant use the dynamic cursor correctly. The syntax seems ok and I
have checked other examples for this.
Can you see where the parsing of the variable :x goe wrong?

create or replace procedure pete2
is
x number :=  9;
type rc is ref cursor;
alarm_cur rc;
rec       perfmon_values%ROWTYPE;
begin
 dbms_output.put_line('OK');
 open alarm_cur for 'select machine,timestamp,metricname,metricvalue from
perfmon_values where metricname = :x' using x;
 loop
     fetch  alarm_cur into rec;
     exit when alarm_cur%NOTFOUND;
     dbms_output.put_line('OK');
 end loop;
 close alarm_cur;
END;
/

SQL> exec pete2
OK
BEGIN pete2; END;

*
ERROR at line 1:
ORA-01722: invalid number
ORA-06512: at "PETE.PETE2", line 12
ORA-06512: at line 1
Followup:
and is metricname a NUMBER???  I suspect not and you have fallen into the age
old trap of "i'm comparing a number to a string, or a date to a string or a
number to a date and getting an error, why?"  The error is you are converting
the string metricname to a number in order to compare to the number 9 and for
some row in the table, it is failing.

Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table perfmon_values
  2  ( machine varchar2(1), timestamp date, metricname varchar2(1), metricvalue
varchar2(1) );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into perfmon_values values ( '1',
sysdate, '9', '9' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure pete2
  2  is
  3  x number :=  9;
  4  type rc is ref cursor;
  5  alarm_cur rc;
  6  rec          perfmon_values%ROWTYPE;
  7  begin
  8   dbms_output.put_line('OK');
  9   open alarm_cur for 'select machine,timestamp,metricname,metricvalue from
 10  perfmon_values where metricname = :x' using x;
 11   loop
 12       fetch  alarm_cur into rec;
 13       exit when alarm_cur%NOTFOUND;
 14       dbms_output.put_line('OK');
 15   end loop;
 16   close alarm_cur;
 17  END;
 18  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec pete2
OK
OK

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into perfmon_values values ( '1',
sysdate, 'A', '9' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec pete2
OK
OK
BEGIN pete2; END;

*
ERROR at line 1:
ORA-01722: invalid number
ORA-06512: at "OPS$TKYTE.PETE2", line 12
ORA-06512: at line 1

always compare dates to dates, numbers to numbers, strings to strings and
never any other way!!!

change:

  3  x number :=  9;

to

  3  x varchar2(20) :=  '9'

and try again.
Rating: 2
Problem solved July 27, 2002
Reviewer: Peter Jones from Switzerland
Hello Tom
Although I dont know why my last example fails I have managed to correct the
original problem. I had a ';' in my
select statement which is not needed.
Rating: 4
string comparision March 16, 2004
Reviewer: A reader
My database column has value for first name like 'John Dow Evert', but I have
got to compare it to string like 'JOHNDOW EVERT' or 'JOHN dow     EVERT' or JOHN
D OW E ver T'. Further the string given to me for comparision cal also have
commas and semi colons.

What is the best way to do this comparision, based on the comparision I have to
select the name key.

Thanks a lot
Followup:
i would translate all non-relevant characters (;, tabs, etc) into a space.

then replace space with "nothing"

then upper it..

  1  select upper(replace( translate( 'john;dow e v e r et', ';,', '  ' ), ' ',
  2  '' ))
  3* from dual
ops$tkyte@ORA9IR2> /

UPPER(REPLACE
-------------
JOHNDOWEVERET

compare those.
March 16, 2004 Greg -- Thanks for the question regarding "One database or two?", version 8.1.6
You Asked
Hi Tom,

We have a 24GB database that some people want to divide into two.

22GB of the database consists of two large tables and indexes on those tables.
The data comes from CD's we buy from the US Government.  Once a year we get new
CD's and replace everything.  During the year we update a few rows every week
when we find errors in the data, but other than that our application only does
SELECT statements which look for a few rows at a time.

The other 2GB is a standard set of tables for customers, sales transactions and
the like.  Standard OLTP stuff.

This is the only database on our server, and like I said, most people want to
make it into two databases, with the two large tables and their indexes in one
database, and the rest in another database.  I have to admit that at present our
single database does seem to be composed of two parts, and there would be no
application problems if we split it into two, since the programs work with the
two parts separately (the SQL either goes after something in the 22GB part, or
works on the 2GB part, but never both).

When I ask why we should make our single database into two, the others talk
vaguely about potential problems, saying that if we had two databases and
something terrible happened to the large one, we could always recreate it and
reload the data from the CD's (which is true) and that it would not be a
significant problem if we lost the few updates we make each week (which is also
true).  I can't get them to explain their reasoning any more than this.

Tom, I wonder if you could comment on this, and in general talk about decisions
on how many databases to create.

1. When we are designing systems, it seems natural to create a single database
for a single application, but are there times when it would be smarter to create
two?

2. And how about the reverse?  When we create three applications, it seems
natural to create three databases, but are there times when it would be better
to create just one?

Thanks very much.
and we said...
I do not see any benefit in segregating these two instances unless you put them
onto two different machines.

Two instances will consume more overall ram.
Two instances will be twice the monitoring.
Two instances will be twice the admin (two new user accounts, not one, etc).
Two instances on a box will be harder to tune.

and so on.  I see no restore advantage by breaking this up -- anything you could
do in a pair of instances, you would just as easily be able to do in one.

My rule of thumb on this (i have very very few rules of thumb but this is one)

o if you are doing the development in house, use a single instance
o if you have 3rd party off the shelf apps -- use an instance for the
application as that application will typically have very specific version and
patch level requirements.  No two 3rd party apps will have the same specific
requirements.
Review & Followup

Rating: 4
RAM June 24, 2002
Reviewer: Gareth
Tom,

You say two instances use more RAM.  Can you give us a feeling of how much more,
please?  What about more than two instances (we have five on our production
machine, over 12 on development/testing).
Followup:
look at what pmon, smon, lgwr, dbwr, etc take.

consider the redundant dictionary caches.

consider the redundant "dbms_output" packages cached in the shared pool (and the
hundreds of other packages).

Each one takes more then a single instance would.
Rating: 4
Re: RAM June 24, 2002
Reviewer: Gareth
Thanks, Tom.

I think I'd better have a word with my DBA!
Rating: 5
June 24, 2002
Reviewer: A reader
Rating: 4
But..... June 24, 2002
Reviewer: A reader
I understand your point Tom, but don't you think there could be some possible
benefits of splitting them too? The larger tables are getting updated once in a
year, while the smaller set of tables represent an OLTP system. If they are
seperated into two databases we can tune them more specifically (like may be we
could use MTS on the OLTP system or tune the SGAs more specifically to the
usage). I think tuning efforts within the database could be easier if you have a
specific OLTP type or DSS type system rather than a general system. Ofcourse
there could be some overheads in resouces as both systems reside in the box. I
think it all depends on the application/usage and the DBA has to take the
decision keeping in mind his requirements.

You have the last word Tom......
Followup:
You can use MTS (shared server) for the OLTP users and dedicated server for the
DSS users in a single instance (no advantage there)

You can use buffer pools to tune the SGA individually.  Set up a keep and
recycle pool and assign tables to it as needed (if needed)

Heck, in 9i -- you can even use different block sizes for each (the 2 big tables
could use a 16k block size while the OLTP tables could use a 2k blocksize)

I don't want the last word ;) In the end, it is up to you and your
implementation.  I don't see any huge advantage in 2 instances, I see downsides.
 Unless and until someone presents a really good reason, I would categorically
be against splitting them out personally (I hate "work for works sake" -- give
me a reason and I would consider it)
Rating: 5
Thanks June 24, 2002
Reviewer: Greg from SF, California
Tom,

Thanks.  The specific disadvantages that would happen if we split one database
into two (more memory, more monitoring, more admin work, etc.) are good points.
Your answer is very helpful.  Thanks for the benefit of your experience.
Rating: 4
But ... June 27, 2002
Reviewer: Gareth
I did have a word with my DBA.  His very reason for having multiple databases
was easier management.

The example he gave was that one of the production applications had a whole load
of test data applied to it by mistake a few days ago.  He was able to take the
database down and restore the backup without affecting other systems.

Food for thought.
Followup:
Food for thought from me:

restores take place at the tablespace level.

You can do tablespace point in time recoveries.

You can do this without affecting other tablespaces.

Hence, your DBA could have done the same in a single instance without any
downtime imposed on the other applications just as easily.
Rating: 5
Thanks again! June 27, 2002
Reviewer: Gareth
Well I never knew that (but I am a mere developer, not a DBA, so I don't really
need to know).

Thank you YET AGAIN for teaching me something new.

Now I just need to find out a way to inform my DBA diplomatically.
Followup:
send him here:
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76990/rmantspi.htm#443831
...
Rating: 5
MTS and DSS June 27, 2002
Reviewer: Leo from Boston, MA
Hi Tom,

From your answer...
   You can use MTS (shared server) for the OLTP users and dedicated server for
the DSS users in a single instance (no advantage there).

  Could you please explain to me that how can i do this?
Thanks
Followup:
setup a tns connect string "dedicated_connect" with the server=dedicated option
and one "shared_connect" with server=shared.

It is part of the connect_data section.
Rating: 5
Thanks June 27, 2002
Reviewer: Leo
Rating: 3
Where to draw the line? August 06, 2003
Reviewer: Matt from Australia
How about three applications and one database instance?

At what point might the "profile" of the application (OLTP/DSS/Hybrid) mean that
you would consider separate multiple database instances?

Would you be happy to run a 24/7 OLTP system with many users and a data
warehouse summarising this OLTP data in the same instance?

I like the simplified management of one instance, but feel a little
uncomfortable with the concept.

THanks in advance
Followup:
I would not want a machine with more then one instance.

Since all of the relevant settings I might want to make are session setable,
running a mix is just dandy.

I would need two machines before I thought about breaking them out and even
then, if they shared data -- I would give serious thought to clustering so I
have two instances but still only one database ( to remove the baggage and code
related to replicating and cleansing and syncronizing the databases )....

As for the 27x7 OLTP and DW -- the question should really be "do YOU feel
comfortable running both of those on the same MACHINE".  If the answer is "yes",
then by all means -- single instance.  if the answer is "no", well then this
question is moot :)

One machine, one instance.

If you have that DW instance and OLTP instance on the same machine (separate
instances) what tools do you have to make it so the DW users don't totally
squash the OLTP and vice versa?  If they are in the same instance, you have
some.
Rating: 5
How many database instances can we run on same machine. August 06, 2003
Reviewer: Dilip Patel from MD, USA
Not sure if I should be asking this as a seperate question. My application
database is 8.1.7, NOARCHIVELOG, WIN200,
total size 4 GB, more of single user OLTP client-server application.

Now the customer wants to give training on this application to hundred trainees
at a time. For this he wants to install hundred database instances on same
server machine,  which *each* will be accessed simultaneously from 100 different
client workstations.

The reasons for installing all instances on same machine are
- to avoid re-installing databases on 100 workstations after each round of
training.
- No user should see any other user's data.

Please suggest if this approach is feasible. I tested this with 5 instances, and
it seems to work. The customer is willing to upgrade to any hardware needed for
this setup.

Thanks in advance for your time.
Followup:
buy stock in a hardware company -- cause you will need it.

100 instances would be "not smart"

a single instance with 100 schemas would be "smart"
Rating: 5
To Dilip August 06, 2003
Reviewer: A reader
Tom is gone till the weekend ( see the asktom home page message - "FYI... I'll
be deep in the woods Wed-Fri August 6-8th. I doubt my network cables will reach
far enough to stay in touch. So, you won't see any activity from me till the
weekend :)"

Anyways, in your case, is not it possible to have multiple
users (with appropriate privileges so they cant see each others data) on one
instance for training purposes?

Regards
Menon:)
Rating: 3
August 07, 2003
Reviewer: Anthony Reddy from KL, Malaysia
Rating: 5
To Menon August 07, 2003
Reviewer: Dilip Patel from Maryland, USA
Yeah, I noticed Tom is camping out somewhere only after posting this question.
Anyways, The application is already developed and I cannot change the code. Just
wanted to know if someone out here really implemented this kind of "hundreds" of
database install on single server machine. Or is it really worth doing all this.
Thanks.
Rating: 4
August 07, 2003
Reviewer: Vipin
Dilip ,
       I also agree with the suggestion that u should go with multiple user with
proper privileges approach rather then creating 'hundreds' of databases on the
same machine . More so when the training is to be provided for the application
only and not on the dba activities . Creating that many databases , even by
using the minimum of resources allowed for each database , will be a big burden
on ur server .Then remembering their different instance_ids,initialization files
location etc. will be a tedious task as well.So create multiple users , copy
data across all of them and set the privileges so that they can not see each
others data and u r set to go.
As always ,Tom can give a more expert opinion on this.
Thanks.
Rating: 5
To Vipin August 07, 2003
Reviewer: Dilip Patel from Maryland, USA
Vipin,
The way the application works now is, logged-in user can access and modify the
data in the database owned by one single schema. So any user who logs in the
application can see all of others data. Pardon me for my ignorance here, but I
am not able to think how user privileges can restrict user to see someone else
schema. If you mean that all the logged in users use thier own individual
schemas to store their data, sorry that is not an option for me. Like I said,
the application is already developed and by no means I can change the code
within it. Think of it as some black
box given to me.

I know the training is to be provided on application and not on DBA activities.
Here the main requirement is to allow 100 trainees to work on application at
same time, so that they dont overlap on each other's data. The option to install
database in each of those 100 workstations is
ruled out for now because they dont want to goto each of those 100 machines to
drop/delete data after each set of training. If there is only machine where all
these database can reside, they can just re-ghost the machine with before image
after the training session is over.

I also know that this setup looks crazy, and that is why I am here. I think, I
will wait until Tom comes back. Thanks anyways.
Rating: 5
I guess the application uses "absolute" reference to database objects... August 07, 2003
Reviewer: A reader
If the SQL code in the application uses owner qualified table names, then there
is no way around, no matter how hard you fiddle with the privileges, and/or
synonyms.

For example, if the single schema owner is called "abc" and that's every user
(user1, user2, ...) will have to access, AND, in the code there is something
like "select col from abc.tab", then you must have separate physical databases
for every user so that everyone will have his or her own abc's data.
Rating: 2
Regarding the 100 instances August 08, 2003
Reviewer: Glenn A. Santa Cruz from Mobile, AL USA
Provided that the application may directly reference schema objects, and that
the clients must not interfere with each other in any way, why not use Personal
Oracle on each of the clients and be done with it?  Scripts could be in place on
each client that will allow for startup/shutdown of the instances (with very
little if any user interaction) and you'll be certain that no users can
see/modify other user data.  To "reset" the 100+ user machines, you could use a
client-side script to restore each from backup files.

At least this may help prevent a maintenance headache on the primary server,
having to maintain/start/stop/refresh/etc 100+ instances on a single box.  Seems
to me that using some network tools to initiate client-side scripts
programmatically would be simpler.  Besides, what happens when 100 clients
becomes 300?  Having a ghosted client image sounds a little nicer, methinks.

By the way, if the person requesting this is willing to absorb any cost
required, can you please give me his/her number?  ;-)
Rating: 4
regarding 100 instances. August 08, 2003
Reviewer: Dilip Patel from Maryland, USA
Thanks for the followup. To a question whether application uses qualified
tablenames, it does not. All the synonyms are in place for each user accessing
the database objects.

Someone mentioned about using Fine Grained Auditing wherein Was told that I dont
need to touch the packaged Application. Maybe Its time to read about FGAC.
Followup:
that is fine grained ACCESS control, not auditing.

but if they use private synonyms -- all you need are 100 schemas -- back to
square one again.
Rating: 5
Dilip's problem August 08, 2003
Reviewer: A reader
FGAC could be one solution - but does not the application
need some sort of insfrastructure to enable it? For
example, the tables (or at least some of them) need to
have the "user" column to  be used in the policies. If
you don't want to "touch" the application, FGAC may not
be your solution...

Menon:)
Rating: 5
If the code does not use qualified table name, you are in luck August 08, 2003
Reviewer: A reader
Then you can simply let each user's set of (private) synonyms pointing to
separate schemas who own actual data. Or better still, simply let every user
"have" its own data. You will simply need to setup one instance with hundreds of
same structured schemas.

The whole point is whether the application code (which you CANNOT change) uses
absolute reference (i.e. owner-qualified names) to tables/views/packages or not.
Rating: 4
Excellent August 08, 2003
Reviewer: fan from CT
Rating: 5
FGAC August 11, 2003
Reviewer: Dilip Patel from Maryland, USA
Right, that is Fine Grained Access Control and not auditing as you pointed out.
Hasty mistake on my part. I would go with your suggestion of using 100 schemas
and one instance.

Thanks Tom.
Rating: 4
my database dss or oltp ? August 25, 2003
Reviewer: A reader
tom,
how to find out if my databas is configured for dss or oltp. which data
dictionary view shows that.

thanks
Followup:
v$parameter

dss and oltp are just "different" parameter settings is all.
Rating: 5
Thanks ... August 26, 2003
Reviewer: A reader
Rating: 5
Multiple DB's into one October 23, 2003
Reviewer: Will from Texas
I agree that a single database hosting multiple in-house apps (in separate
schemas) is probably the way to go for various reasons:

o Reduced database administration (backups/monitoring/users, etc.)
o Reduced hardware overhead (processor/disk/memory, etc.)
o Etc, etc.

However a colleague, who prefers to take cold backups prior to making any schema
changes, is concerned about the downtime of unrelated applications in the same
database during the backup, upgrade and potential recovery.

In order to recover from a failed update, I have suggested tablespace
point-in-time recovery, as long as the apps live in their own tablespaces, but
TSPITR is somewhat involved.

Another way to be able to recover may be to ensure all users are out of the
particular app and prior to the upgrade process, do an export as the apps are
pretty small.

Questions:

What are your preferred methods for dealing with schema mods to databases
containing other, unrelated schemas?

What is the best way to tell which app (within a database with multiple apps)
that a user is running?
Followup:
colleague doesn't understand oracle or databases then -- a hot backup is as
good, if not better then cold.  cold backups just are silly.

tell them to fix their preference and problem is solved.  cold isn't better then
hot.  hot IS better then cold.  problem just disappears!

tspitr is not "involved", it is just a process.  just a process.  very simple
actually (conceptually).

my methods for dealing with this?  TEST ENVIRONMENTS -- then there isn't
anything to backout.

if the applications used dbms_application_info to register themselves right
after login -- this is really really easy.
Rating: 2
Test and production in the same instance? October 24, 2003
Reviewer: Matt from Australia
just something I took from reading the above discussion. Surely no-one is
suggesting placing test and production in the same instance?

Are they?
Followup:
i don't think so?  where do you think you saw that here?

we are talking about "how many instances to run on a machine"

since you surely would never even consider running test and prod on the same
physical hardware, it cannot come into play in this discussion.
Rating: 4
Why 90%/10%? November 21, 2003
Reviewer: mesarunner from Fla
Tom,

Early on in this thread you mentioned running both MTS and dedicated servers if
you have 90% OLTP and 10% "not quite oltp".  Why 90/10?    Why not 50/50 or
30/70?

We have mixed use databases - transactions from many remote sites are fed into
the system at night, reporting kicks off when that is done and runs throughout
the day until 6 pm when data loading starts again.    Data entry at corporate
headquarters is done during the day, while reports are running.   (It seems that
for the most part the concurrent data entry and reporting do not address the
same data.)

So, why not use dedicated servers for the reporting system, which makes up a lot
more than 10% of the load during the  day,  MTS for the daytime data entry which
involves a lot less than 90% of the load,  and dedicated server for the data
loading at night?

Thanks.
Followup:
first occurrence of 90 in this thread is your mention of it?

I will say this -- if you

a) have a need for shared server
b) run batch/long running things

you will have both shared AND dedicated server connects since a batch/long
running thing best not ever use (eg: monopolize) a shared connection!

90/10
50/10
10/90
doesn't matter  as long as it is

x/y and x>0 and y>0
Rating: 5
Book, not thread November 25, 2003
Reviewer: mesarunner
Tom,

Thanks for the clarification.  The 90/10 thing was in Expert One-on-one Oracle.
I must have had the book open while I was reading the thread.  Kind of scary,
actually, I don't recall mistaking a book for a computer monitor in the past.
Anyhow,  the top of page 88 of the red-covered book is where I got the 90/10
ratio.
Followup:
well that says

"IF you have a system that is 90 percent..."

it was a "for example"

not a "rule of thumb" :)
Rating: 4
Test and production data in the same instance? January 08, 2004
Reviewer: Le from VA USA
Thank you for helping us and your patience!

Can you discuss more about the pros and cons between having separate prod and
test databases (one instance for each database) and having one database but
different schemas for prod and test data.  I prefer having multiple databases to
avoid messing up the live data.  Do you see more advantages in having one
database and multiple schemas even though the system has enough resources to run
multiple instances.  What would you recommend?  Thanks!
Followup:
you need two machines for test and prod, not just two databases!

You *cannot* have test and prod in a single database (unless you are in a shop
where downtime is allowed, expected and frequent and can be of an unknown
duration)

Same applies for a single machine.
Rating: 4
One Database or Two. January 08, 2004
Reviewer: NOTNA from Philippines
Hi Tom,

As always thank you for sharing your knowledge with us, I have a comment when
creating a 1 database or two.

Say we are developing an application and this application runs a batch and the
test data is about 5GB and say the development/test divided this application
into 3 modules  (ModA, ModB, ModC). ModA is assigned to a group (which is
dedicated to develop/test batch runs), ModB is assigned to a group to create
users/roles/grants and security, and ModC is assigned to another group which is
dedicated to develop/test data capture.

if we put this on a single database we would be having lock problems, like when
the other module is testing their batch run and its using a dba_job and the
other module test the creation of the user and it needs a grant to the dba_job
or other packages needed, they will be having a lock problems.

Do you agree with me that we need a separate dbs for this case????
Followup:
no, i do not agree.

Sounds like a single database to me.  Unless moda modb modc are really not
"modules" but three separate entities that share NO DATA.  then you might have a
case for three separate servers -- but I doubt it.
Rating: 4
NOTNA January 10, 2004
Reviewer: NOTNA from Philippines.....
but what if I they are testing simultaneously, would'nt there be a locking
problem when they drop/create/grant to a user while doing a batch (since
packages are being executed by the batch and will hold an exclusive lock for the
particular package, the grant and revoke cannot be  performed)?
Followup:
size your system for what you do, plain and simple.

use the appropriate tools to do what you do.

You have tools such as AQ.  You can use AQ instead of dbms_jobs to get queues
set up for each kind of work.  Say

1 queue for user drop/create/grant.

5 queues for batch

2 queues for this

3 queues for that

Now, they do not conflict. They each have their own resources.  The target of an
AQ can be a plsql routine that is run automagically:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:8760267539329
or your can create custom processes that run either on that database server or
elsewhere in the network that dequeue the messages and process them.
Rating: 5
one db per machine? March 16, 2004
Reviewer: bob from PA
you say this so matter of factly.  can you please share some your lessons
learned that reinforce this methodology?

i work with people who have the "buy an E10K server" mentality and convincing
them that dev and test should be on separate maybe more commodity type machines
than prod will need some good lessons learned from someone more experienced than
me.  Assuming the resources are available for the multiple instances, can you
provide some additional reasons for not doing this?

I would like to hear some more of the worst case scenarios that justify the
approach.  Maybe something as simple as the dev instance runs amok and crashes
the entire box?

on another note, can I have two RAC databases with only two machines? (each
machine functions as a node for each RAC database)
Followup:
i say it matter of factly cause I say it so often.

However, maybe I need to clarify

I do not see any benefit in segregating these two instances unless you put them
onto two different (virtual) machines.

You have to wall them off.  Using the OS partioning on an e10k is one
(expensive) way.

It is on a single OS "image" that you want a single instance.  Else that list
above holds true.

But on a big old e10k -- you would partition it (so OS instance 1 gets 4 cpus --
6gig ram, OS instance 2 gets 8 cpus -- 12 gig ram, and so on) as if it were
separate PHYSICAL machines.

You would have 2 RAC instances and a single database with 2 machines.  One
"instance" per machine -- one database (one set of shared disk)
March 17, 2004 Antonio -- Thanks for the question regarding "fun fun fun -- (you are wrong wrong wrong)", version 7.3, 7.4,8.04, 8.5
You Asked
i'm a simple programmer with
an account in a DB with this grants on an NT machine
- connect
- create procedure

--So I can program  this :
procedure fun
begin
  hosts('format c: --siwtches_for_format_come_here');
end;

-- I compile and exec the procedure

I think the DB administrator is going to have a lot of fun....
and we said...
hosts (or host) is not a PLSQL command.

There are no built in facilities in PLSQL stored in the server
to execute a host command.  Special care is taken and
facilities provided to the DBA to ensure nothing like the above
could ever happen on the server.

For example, given the appropriate priveleges, you could write
an External Procedure in C.  This external procedure could in
fact do a format since C can pretty do as it likes.  However,
the DBA should set up external procedures to run as some "safe"
user (not Oracle, not Root, not Administrator) thus preventing
you from doing this at the OS level.

The HOST() builtin command is a feature of PLSQL in Oracle FORMS
only.  It executes a HOST() command on the client machine.  Here
you could do that (run the format) if the client is priveleged
enough (not that it would actually work however since you cannot
format a drive that is being used and C: is mostly likely USED).
 This is no different then you writing a C program, of a VB
program, or a program in any language that does that.  It is
called a trojan horse -- its supposed to do something useful but
does something damaging.

The server is safe.
Review & Followup

Rating: 3
It is funny August 08, 2001
Reviewer: Anthony Lai Cheuk Tung from Ma On Shan, Hong Kong SAR
Tom, your answer is good and helpful.
Simple programmer, your question is good but you are too simple ^_^//.
Rating: 5
A related question July 04, 2002
Reviewer: A reader
Dear Tom,
With reference to your comment:
"DBA should set up external procedures to run as some "safe" user (not Oracle,
not Root, not Administrator) "
would it be possible to read the environment variables set for this other user
in an external procedure. I think it is possible using OCI ( using funtions like
GetOCIEnv or createOCIEnv etc. to get the environment handle ), but without
using OCI?
Thanks.
Followup:
environment variables are an OS thing, not an OCI thing.  or I'm not
understanding what you are asking for.  Do you mean environment variables like
ORACLE_HOME or what?
Rating: 2
Explaing July 05, 2002
Reviewer: A reader
Dear Tom,
These are some user defined environment varaibles. Just like ORACLE_HOME, on
UNIX say $ export PARAMETER1="abc"
Thanks
Followup:
the environment in the external procedure will be the environment inherited from
the listener that spawns the extproc process.  No more, no less.

You can only see the environment that was in place when the listener itself was
started....
Rating: 4
another wonder: March 16, 2004
Reviewer: Li YS from CHINA
set define on;
set serveroutput on
var sqlpath varchar2(200);
begin
    :sqlpath:=rtrim(ltrim('&1'));
end;
/

begin
dbms_output.put_line('Value is ' || :sqlpath);
end;
/

host md :sqlpath;

if my test.sql like this,and type @e:\test.sql e:\sql in SQL*Plus,I found the
host md :sqlpath is no effect,may be the 'sqlpath' is a variable of SQL*Plus,not
a variable of OS,but how can I realization it?Thanks!
Followup:
how about boiling the entire script down to

host md &1

you need no "trim", whitespace isn't going to affect anything.

:sqlpath is a bind variable, binds are ONLY useful in SQL and PLSQL -- not in
sqlplus commands like "host"
Rating: 4
Thank you! and: March 16, 2004
Reviewer: YS Li from CHINA
  And I'm sorry that I'm not express what I want yesterday.

  In fact I want to change the context of &1,such as If &1='e:/**',I'll change
it to 'e:\**' etc.

  So I think may be :'host md &1' cann't realize it,and must be use bind
varible?

  Thank you!
Followup:
no binds -- binds, as stated won't work in a sqlplus command like host -- only
in SQL/PLSQL

Use "new_val" and select it from dual.

ops$tkyte@ORA9IR2> set echo on
ops$tkyte@ORA9IR2> @test c:/abc
ops$tkyte@ORA9IR2> column X new_val X
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select replace( '&1', '/', '\' ) X from dual;
old   1: select replace( '&1', '/', '\' ) X from dual
new   1: select replace( 'c:/abc', '/', '\' ) X from dual

X
------
c:\abc

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> prompt &x
c:\abc
ops$tkyte@ORA9IR2>
March 17, 2004 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....
Rating: 5
January 29, 2004
Reviewer: olaf from germany
Tom,

what means NONE for server in v$session:

SQL> select username, server from v$session;

                               DEDICATED
                               DEDICATED
                               DEDICATED
                               DEDICATED
                               DEDICATED
                               DEDICATED
                               DEDICATED
                               DEDICATED
                               DEDICATED
                               DEDICATED
CITRIX                         NONE
CITRIX                         NONE
SYSTEM                         SHARE
Followup:
it is a shared server connection that is not currently active.
Rating: 2
Multiplexing with Dedicated Server? February 03, 2004
Reviewer: Jan from Europe
You wrote:

"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."

What is "Connection Multi-plexing"? Is it "Session Multiplexing"? If yes, then
as I understood from docs:

Oracle9i Net Services
part "Session Multiplexing"

this option is available only for Shared Server mode. What is the true?
Followup:
multi-plexing is just a "computer term"
http://www.google.com/search?hl=en&lr=&ie=UTF-8&oe=utf-8&q=define%3A+multiplexing&btnG=Google+Search
in my context, it means I can have a single physical connection to a dedicated
server but have many sessions active at once.  I'm multiplexing my own personal
sessions over a single physical circut (connection)

the net guides are talking about multi-plexing ANY session from ANY user through
a single server.  a  much more general case.
Rating: 5
multiplexing February 03, 2004
Reviewer: Reader
Tom, as one may have many sessions using a single dedicated server connection in
multiplexing, is PGA shared by all of the sessions in multiplexing? Thanks.
Followup:
PGA is a process thing, not a session thing.

So yes, the pga is shared by any session using that process.
Rating: 4
Shall I kill the extra process in v$process February 19, 2004
Reviewer: Ramesh from Atlanta GA
the counts are not matching in V$process and V$session

Shall I kill the extra session which is available in v$process  but not
available in v$session... Is there any harm to do that ?

Thanks for the Excellant work..
Followup:
no, they do not need to match.  don't kill anything willy nilly like that.
Rating: 3
March 16, 2004
Reviewer: zhigang wang from Beijing CHINA
the following data is query rusult by :
select p.spid,s.sid,t.value/1024/1024 PGA_Mb
from v$process p,v$session s,v$sesstat t
where p.addr=s.addr
and s.sid=t.sid
and t.statstic#=20
adn p.spid=9770;

p.spid  s.sid   PGA_Mb
9770    107    39.43
9770    138    38.18
9770    432    39.99
9770    392    39.78

==========================
i caught above lines from our Oracle 11i system on Oracle 9.2.0.3, it's not a
shared server/MTS, but one shadow process had 4 related session id which
statistics of 'pga memory' were different.

it's really confusing me.
Followup:
well, thats not the query (typos, wrong column names, etc)

but, add s.server to the query.

and remember a single client can open many sessions on the same connection. (see
original answer above!)

In fact, we can see this with a tool as simplistic as SQL plus!  Watch:

ops$tkyte@ORA9IR2> select p.spid,p.addr,s.sid,s.server,t.value/1024/1024 PGA_Mb
  2    from v$process p,v$session s,v$sesstat t
  3   where p.addr=s.paddr
  4     and s.sid=t.sid
  5     and t.statistic#=20
  6     and s.username = user
  7  /

SPID         ADDR            SID SERVER        PGA_MB
------------ -------- ---------- --------- ----------
17244        5628FF88          8 DEDICATED .382892609

so, apparently I have one process, one session... but:

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> set autotrace on  statistics
ops$tkyte@ORA9IR2> select p.spid,p.addr,s.sid,s.server,t.value/1024/1024 PGA_Mb
  2    from v$process p,v$session s,v$sesstat t
  3   where p.addr=s.paddr
  4     and s.sid=t.sid
  5     and t.statistic#=20
  6     and s.username = user
  7  /

SPID         ADDR            SID SERVER        PGA_MB
------------ -------- ---------- --------- ----------
17244        5628FF88          8 DEDICATED .382892609
17244        5628FF88          9 DEDICATED .507892609

whats that?  where did that session come from?  SQLPlus opened the session for
me in order to autotrace me.  It uses another session in order to NOT affect the
session it is watching (eg: autotrace runs sql and such -- if it did its work in
our session -- autotrace would affect the measurments it is trying to show us.
therefore, it just starts a new session on our connection)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          0  consistent gets
          0  physical reads
          0  redo size
        664  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

ops$tkyte@ORA9IR2> set autotrace off
Rating: 5
More Processes than Sessions? March 16, 2004
Reviewer: David from Houston, TX
I understand why I have more sessions than processes, but why would I have a
database with more processes than sessions?

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production

ops$oracle@GPDPROD> select count(*) from v$process;

  COUNT(*)
----------
        52

ops$oracle@GPDPROD> select count(*) from v$session;

  COUNT(*)
----------
        16

Thanks.
Followup:
There is a connection -- a physical circut from the client to the server.

Then, there are sessions.

You can be connected to Oracle but not have any sessions.

You can have 10 sessions -- but not have ANY processes associated with you.

It is a 1-1, 1-m, m-1, m-m possibility here.

In shared server mode, you could easily have started N sNNN processes (shared
servers) but have N-M sessions (set shared servers to 50, have 10 people log in.
 10 sessions, 50 processes)

Job queues will be processes

Aq processes will be processes

DBWn is a process

Look at the PROGRAM column in v$process and you'll see what I mean....
Rating: 5
March 16, 2004
Reviewer: Sven Bleckwedel from Santos/SP - Brasil
Hi Tom,

Could this query be modified to show the PGA consumption for all other
sessions/processes ?

Tks in Advance,
Sven
Followup:
sure, just drop the "s.username = user"
Rating: 4
Multi-Session used in single physical connection by oracle forms March 16, 2004
Reviewer: zhigang wang
thanks, Tom. as u told us, one single physical connection could start 2 or more
sessions. the PGA of the process is summary of all sessions, right? and could u
talk about something about how to implement that besides set autotrace, for
example, how is it used in Oracle Forms.
Followup:
PGA is process global area and there is a single PGA per process, so yes, that
is the summary for all sessions using that process (there wasn't 2 separate
pga's in my example really)

Forms uses it for creating new sessions separate from the main session (like
with run_product) and debugging.
March 17, 2004 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).
Rating: 5
Lifesaver March 01, 2004
Reviewer: Arun Mathur from Marietta, GA
Tom,

The package you provided works great. For some reason, I'm getting a
java.lang.OutOfMemory exception when using the package to run a report.  The
report itself runs to completion, but I'm curious as to why I'm getting this
exception. Any thoughts on where I can troubleshoot?

Thanks.
Arun
Followup:
you are getting a java exception running plsql?  can you clarify the environment
here and exactly what is happening?  (this page doesn't do java)
Rating: 5
Forgot to paste the error March 01, 2004
Reviewer: Arun Mathur from Marietta, GA
ERROR at line 1:
ORA-29532: Java call terminated by uncaught Java exception: java.lang.OutOfMemor
yError
ORA-06512: at "SYS.XMLPARSERCOVER", line 0
ORA-06512: at "SYS.XMLPARSER", line 101
ORA-06512: at line 40

I'm running Oracle 9.2.0.1.0.

Thanks again.
Arun
Followup:
oh, that is nothing to do with dbms_output'ing then.

you are running out of memory doing a huge parse of some really big xml
document.  You "ran out of memory available"
Rating: 5
My mistake March 01, 2004
Reviewer: Arun Mathur from Marietta, GA
It would've helped if I actually read the error message a little more carefully.
The exception has nothing to do with your package, just with my brain. Sorry to
take extra space from this discussion.

Regards,
Arun
Followup:
no worries :)
Rating: 5
Word-wrapping version of p March 08, 2004
Reviewer: Paul from UK
CREATE OR REPLACE PROCEDURE p1 (p_str IN VARCHAR2)
  IS
   l_str     LONG   := p_str;
   l_instr   NUMBER;
BEGIN
   LOOP
      EXIT WHEN l_str IS NULL;
      l_instr :=
INSTR(TRANSLATE(SUBSTR(l_str,1,250),chr(10)||chr(32),chr(44)||chr(44)),chr(44),-1
);
      IF l_instr = 0 OR LENGTH(l_str) <= 250 THEN
         l_instr := 250;
      END IF;
      DBMS_OUTPUT.put_line(SUBSTR(l_str,1,l_instr));
      l_str := SUBSTR(l_str,l_instr+1);
   END LOOP;
END;
/

Could do with the odd tweak to make it perfect, though :-)
Rating: 4
Peculiar Problem March 16, 2004
Reviewer: Tony from India
I'm writting shell progam embedded with PL/SQL block and use
DBMS_OUTPUT.PUT_LINE to output messages. Only the first word of the message is
displayed and 'not found' error message is also displayed. what could be the
reason?
For example: If I've
 DBMS_OUTPUT.PUT_LINE('Record Succesfully inserted.'), only the first word of
the message is displayed with 'not found' error. This happens when the PL/SQL
block is embedded with shell script. If I run the PL/SQL block alone in SQL
Plus, complete message is displayed without any error.
Am I doing anything wrong?
Please help me Tom.
Followup:
example please?  (small, concise, yet 100% complete)

Here is mine:

$ cat test.sh
#!/bin/sh

sqlplus -s / <<EOF
exec dbms_output.put_line( 'Record Successfully Inserted.' );
EOF
$ ./test.sh
Record Successfully Inserted.

PL/SQL procedure successfully completed.
Rating: 4
Peculiar Problem - Code March 16, 2004
Reviewer: Tony from India
Since the original fuction is big, I tailored that function.

function LoadData
{
   g_UserName="scott/tiger"

   # Connect to DataBase and Start Script
   `$SQLPLUS -s << EOF
   $g_UserName
      WHENEVER OSERROR  EXIT OSCODE
      WHENEVER SQLERROR EXIT SQL.SQLCODE
      SET SERVEROUTPUT ON SIZE 200000
      SET HEADING OFF
      SET VERIFY OFF
      SET FLUSH OFF
      SET ECHO OFF
      SET PAGES 0
      SET FEEDBACK OFF
      SET TERM OFF
      SET TAB OFF
      SET SPACE 0
      SET WRAP OFF
      SET TRIM OFF

   DECLARE

         g_PgmId      NUMBER:= 123;

       PROCEDURE Insert
       IS
       BEGIN
             DBMS_OUTPUT.PUT_LINE('Rows are inserted.');

       EXCEPTION
         WHEN OTHERS THEN
                DBMS_OUTPUT.PUT_LINE('Rows not are inserted.');

       END Insert;

   BEGIN

           Insert;

   END;
/
EXIT;
EOF`
   return 0

}
Followup:
hows about a complete example -- as I asked for:

example please?  (small, concise, yet 100% complete)

I don't even know what shell you are using, I don't have a $SQLPLUS environment
variable and so on.

small
concise
yet 100% complete <<<======== very important.

Make it as small as humanly possible (eg: do you really need an insert in there
to reproduce your issue?  start with an EMPTY script and add just that which is
necessary to reproduce)
March 17, 2004 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.
Rating: 4
Transportable Tablespaces January 14, 2004
Reviewer: A reader from NJ, USA
Tom,

1.My understanding is that until 8i, the target and source databases should have
the same database block size. But in 9i this restriction has been removed and we
could have different block sizes between source and target databases. Is my
understanding correct? If not pls. explain
2. Also, I have a production database with 500 GB and a new benchmark database.
Can the tablespaces in the new database be created with a different name and
different storage parameters than the production database and still use
transportable tablespace feature. Pls. explain. I keep getting these
questions/doubts.
3. What would be the easiest way to make a copy of these large databases. use
transportable tablespace or use database cloning.

Thanks
Followup:
1) in 9i, a database can have 2, 4, 8, 16, and even 32 (if supported) block size
tablespaces. so yes.

2) a tablespace "ABC" transported from a database will have the name "ABC" on
the new database.  in 10g there is a rename tablespace -- but not before.

3) backup, restore (eg: clone)
Rating: 3
Qn 2 January 15, 2004
Reviewer: A reader from NJ, USA
Tom,

Per Qn 2 above, what would be the case if the tablespace in the two databases
have different storage parameters. Can the database have different storage
parameters (however with same name)?
Followup:
storage parameters are not really meaningful here.

they are "defaults" for objects created in a tablespace, thats all.

databases do not have "storage parameters"

tablespaces have "default storage parameters"

each segment has their own (perhaps unique) storage parameters.

storage parameters do not affect transporting.
Rating: 5
Merge two databases February 19, 2004
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom,
Referring to Metalink article 100693.1, in the Restrictions section, it says:
- The tablespace must be self-contained ( no reference pointing outside    this
tablespace like index, constraint, partition, LOB...)

Does this mean that if I have tables in tablespace TAB_DATA and the indexes on
these tables are in tablespace TAB_IDX, then I cannot use transportable
tablespace feature to move both these tablespaces to another database? We are on
9iR2.

Thanks
Followup:
It means you would have to transport both as a set.

You can transport them.  You cannot transport just ONE of them.
Rating: 3
Import March 17, 2004
Reviewer: Dilip
Dear Tom,

I have 2 Databases,
Database "A" having user "UA" and tablesapace "TA",
Database "B" (other database) also having user "UA" and tablespace "TA".

Now i want to import the user from Database A into Database B,
but the objects of user in Database A should be into a
 different (NEW) tablespace; say tablespace "TZ" of Database B,
how do i specify the parameters.

how also, if i want to import into Database B with New User and New Tablespace;
(User "NA", Tablepsace "TZ").

Please, please help me Tom, this question hase been on since long with me, but
now I have a urgent requirement, be my saviour

Regards
Dilip
Followup:
make sure user UA does not have "unlimited tablespace" in database b

alter user UA quota 0k on TA;
alter user UA default tablespace TZ;

imp .......

(see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:47812348053
for more details.  with the "new user", do the same as above (no unlimited
tablespace, 0k quotas on tablespaces they should not import into and use
fromuser, touser on the import)

bear in mind that this works only for "simple" single segment objects.  If you
have tables with lobs (multi-segment objects - a table segment, lob segment and
lob index segment), partitioned tables, IOTs with overflows and such -- you will
have to manually edit the DDL fixing the tablespaces and precreate the objects.
You can get the DDL easily using

imp userid=u/p full=y indexfile=foo.sql

foo.sql will have the DDL, you can precreate the tables/whatever and then

imp .... IGNORE=YES

to load the data.
Rating: 4
Import March 18, 2004
Reviewer: A reader
Great!!! thank you very much dear Tom, God Bless You and keep you in good health
to help others!!
March 17, 2004 OP -- Thanks for the question regarding "Is there any command to Unlock a table !", version 8.1.6.0.0
You Asked
Dear Tom,
Hi !

Thanks very much for the very useful tips on tuning process.

My Quest to you is

When I am trying to delete some rows from a table T1
it says "TABLE LOCKED".It doesn't allow me to make
deletion possible.

I am logged in to oracle as user "TEST/TEST".There are
so many developers who are also logged in as TEST/TEST.

1) I there any way to unlock this table ?

2) Is there any way to find out how many users are using
T1 table and for what purpose.

3) How to kill sessions in ORACLE for a USER !

Kindly answer !

Thanks and Regards
OP
and we said...
1) We don't have an error message "table locked" -- a delete
would wait for rows to become unlocked to proceed.  I'd really
need more information to help you out such as the exact error
you are getting and the environment in which you get it
(sqlplus, custom app, forms, etc etc etc)

2) select a.username us, a.osuser os, a.sid, a.serial#
from   v$session a, v$locked_object b, dba_objects c
where  upper(c.object_name) = upper('&tbl_name')
and    b.object_id   = c.object_id
and    a.sid         = b.session_id;

will show you who has locks on the object

3) alter system kill session 'sid,serial#'

where you get the sid and serial# from v$session.
Review & Followup

Rating: 2
unlock table March 17, 2004
Reviewer: anand from Chennai
how do i release lock on a table without killing that session
Followup:
you get the person who owns the session to type in "commit" or "rollback"

that is the only (other) way.
March 17, 2004 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.
Rating: 2
commit time for 1 million records February 26, 2004
Reviewer: Rangadham from India (mumbai)
hi Tom,

i would like to know the time taken to commit 100 records and a million records.

Thanks
Rangadham
Followup:
the same.

unless you commit 1,000,000 records 100 at a time -- then, it'll be 1,000 times
larger or more.

if you have "Expert one on one Oracle" -- i actually cover this topic and give
examples as well.
Rating: 5
convertable function from number to bit March 17, 2004
Reviewer: T.V.Rangadham from INDIA
hi Tom,

Thanks for replying to my previous question.

please tell me how do i convert a number to bit mode.

Thanks
Rangadham
Followup:
if by "bit mode" you mean "binary", see
http://asktom.oracle.com/~tkyte/hexdec/index.html

March 17, 2004 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.
Rating: 3
Chekpoints at logswitches only March 16, 2004
Reviewer: Hatem
Hi Tom
i want to achive checkpoints only at logswitch and eliminate the time and
interval checkpoint so i've set log_checkpoint_interval to 999999 and
log_checkpoint_timeout to zero but from my trace file it shows that the
chekpoint takes about 3 hours to complete and once it completes it starts a new
logswitch
i've 3 log groubs with 2 members each, each log size = 82
Tue Mar 16 00:38:40 2004
Beginning log switch checkpoint up to RBA [0x4915.2.10], SCN: 0x0000.0e23b831
Thread 1 advanced to log sequence 18709
  Current log# 1 seq# 18709 mem# 0: E:\ORACLE\PRODDATA\LOG01A.DBF
  Current log# 1 seq# 18709 mem# 1: E:\ORACLE\PRODDATA\LOG01B.DBF
Tue Mar 16 05:22:38 2004
Completed checkpoint up to RBA [0x4915.2.10], SCN: 0x0000.0e23b831
Tue Mar 16 06:15:02 2004
Beginning log switch checkpoint up to RBA [0x4916.2.10], SCN: 0x0000.0e2504bd
Thread 1 advanced to log sequence 18710
  Current log# 2 seq# 18710 mem# 0: E:\ORACLE\PRODDATA\LOG02A.DBF
  Current log# 2 seq# 18710 mem# 1: E:\ORACLE\PRODDATA\LOG02B.DBF
Tue Mar 16 09:55:42 2004
Completed checkpoint up to RBA [0x4916.2.10], SCN: 0x0000.0e2504bd
Tue Mar 16 10:08:14 2004
Beginning log switch checkpoint up to RBA [0x4917.2.10], SCN: 0x0000.0e2604ec
Thread 1 advanced to log sequence 18711
  Current log# 3 seq# 18711 mem# 0: E:\ORACLE\PRODDATA\LOG03A.DBF
  Current log# 3 seq# 18711 mem# 1: E:\ORACLE\PRODDATA\LOG03B.DBF

am i on the right way?
Followup:
why do you *think* you want to do this?

and where do you see it starting a logswitch as soon as the checkpoint
completed?  I see a logswitch starting sometime AFTER, but not triggered by the
checkpoint completion?
Rating: 3
March 17, 2004
Reviewer: Hatem
Hi Tom
in peak time my db chekpoint every 2 min so i'm trying to minimize chekpoints,my
prod db is about 4o gb size i think chekpointing every 2 min point cause degrade
to the db
so why chekpoint every 1800 sec or every 1000 block?
anyway db checkpoint every logswitch.so i'm trying to disable timing and
interval chekpoint.
i've resized my log file from 60 to 80 mb ,what i've realized is that chekpoint
takes 3 hours to complete and 10 min to begin another logswitch after the last
one complete
what i cant understand why it takes this time,are there any other factor cause
this behaviour?
waiting your valuable comments
best regards
Hatem
Followup:
"i think" = "red flag"

how do you know it is checkpointing every 2 minutes.
how did you identify it as being a performance issue (what metrics did you use)

checkpoints happen lazily in the background, it is not like "oh - hey, quick,
hurry up, flood the IO system -- get those blocks to disk NOW".  It is more like
-- 'hey, we just flipped logs, dbwr -- start getting those blocks to disk
please.  try to get it finished before we need to reuse this logfile'.

but, I'm not quite sure you've measured things correctly here.
March 17, 2004 Josh -- Thanks for the question regarding "Hashing vs. Passwords", version 9.2.0.1.0
You Asked
Tom,
  First of all I want to thank you for the wonderful service that you provide.
You examples have been a great source of information.

   I am currently starting a project to "encrypt" passwords and store them in
the database.  You mention on your website this should never be done and hashing
is the way to go.

   What benefit does hashing have over encrypting?  How is hashing safer by not
using a "key"?  Can the hash just be reverse engineered?

   Thanks for your help as always.
        Josh
and we said...
hashes cannot be reversed.

if you store encrypted passwords -- you have to do key mgmt.  if the key gets
compromised, you have just given away all passwords.

since you do NOT need to know what the password is -- only verify if the correct
one is given -- you should use a hash.

hashed_pw = dbms_obfuscation_toolkit.md5( username || password );

save that -- then when someone presents you a user/password -- all you need to
do is hash it, compare it and let them in (or not).

It is the way Unix and Oracle work.
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:95412348059
Review & Followup

Rating: 5
Does it mean.... February 17, 2004
Reviewer: Henry Yick from Hong Kong
that this is a complex version of checksum/CRC. And there is chance to have
different username/password with the same hash result, but the possibility is so
little that can be ignored? Just want to calify.
Followup:
md5 checksums are cryptographic checksums.  google it to find out about it.

yes,

scott with password tiger

could be the same as

bob with password monster

but the odds are pretty darn small and not really relevant.  Having access to
the hash is useless.  You need a pair of strings that get to the hash.  even if
everyone has the SAME hash -- it would be "perfectly acceptable and OK" -- since
all you know is the username and it takes

f(username,password) = hash

having the hash doesn't tell you squat.
Rating: 4
APPS 11i use hashing? February 18, 2004
Reviewer: RK from CA
Hi Tom,

Thanks for the above info.

Oracle apps 11i users have ids and pwds. Are these pwds hashed or encrypted and
stored in the database? I had read somewhere that they're encrypted.

Thanks,
RK
Followup:
you know, i don't know.  i don't really touch apps myself.  never really looked
into it.
Rating: 5
February 18, 2004
Reviewer: Jack
I suppose md5 is an open standard with a public known algorithm, and no key
required, right?

And second, it's possible that hash(scott, tiger) = hash(scott, cookies), there
should have more than 1 (and much more) password for the same username to have
the same hash result. That's mean either scott/tiger or scott/cookies can
successfully logon to oracle. Is that true?

If so, is it possible for somebody who know the md5 algorithm to reverse the
logic and generate a 'password' from the hash and username. This is not
necessary the real password of that user, just one of the possible password.
Then I can use this 'password' to login to that user....

I know that shouldn't be the fact. But where is the trick?
Followup:
first - yup

second the hash function maps into a pretty darn large space, the odds are 1 in
millions or billions..  yes, there is a chance even in oracle, unix, others that
use this technique -- but it is so small as to be "not realistic".

knowledge of the hashing algorith won't help you (unless the hash is "trivial"
which md5 is not)
Rating: 5
Unlikely ;-) February 19, 2004
Reviewer: Invisible from UK
Just to add to what Tom already said...

Hash functions are SPECIALLY DESIGNED to be very very VERY hard to reverse. This
is design goal #1. (Design goal #2 is to make it really unlikely that any two
things will ever have the same hash.)

The MD5 algorithm is here:
http://www.faqs.org/rfcs/rfc1321.html
MD5 produces a 128 bit hash value. That is, there are 2^128 different possible
output values.

Is 2^128 big?

Look at this:
2^10 = one thousand (approx)
2^20 = one million
2^30 = one thousand million
2^40 = one billion (1 + 12 zeros)
2^50 = one thousand billion (1 + 15 zeros)
2^60 = one million billion (1 + 18 zeros)
...
2^120 = (2^60) x (2^60) = (one million billion) x (one million billion) = one
billion billion billion. (1 + 36 zeros)

2^121 is TWICE AS BIG as 2^120.
2^122 is TWICE AS BIG as 2^121.

Get the picture? It's a BLOODY BIG NUMBER! :-)

In fact, 2^128 is a 38 digit number! To be exact, it is approximately

  340,282,366,920,938,463,463,374,607,430,000,000,000

(as near as the standard Windows Calc.exe program will tell me)

To give you some idea of the vastness of this number, imagine 12 grams of carbon
(i.e., a small lump of coal). The number of individual atoms in that is 6.022 x
10^23. In other words, roughly 6 followed by 23 zeros.

Using this, you can calculate that 2^128 carbon atoms would weigh

  565,065,372 tonnes (!!!)

Think about how unimaginably TINY a single atom is, and how many trillions of
them fit on a pinhead. NOW think about how HUGE 500 megatonnes is...

It's so huge it's beyond imagination... beyond comprehension!

SO, the number of possible MD5 hash values is equal to the number of atoms in
500 megatonnes of carbon. Finding two things with the same hash isn't so much
"finding a needle in a haystack" as "finding a single atom in [literally] a
MOUNTAIN of carbon"!

In practice, the only way somebody can get from a hash value back to the
original password is to just try every password you might have used, put it
through the same hash function, and see if the result matches your password
hash. It takes a LONG time.

Of course, with the system Tom suggests, it doesn't MATTER if the password they
find really is your password or just hashes to the same thing. However, we
already said a hash function is designed so that it's highly unlikely (virtually
impossible) to find anything else with the same hash!

In practice, you will usually find that any OTHER text that hashes to the same
thing as your password would be
-> very very much longer
and / or
-> not printable ASCII
Chances of two SHORT, PRINTABLE text strings giving the same hash value are
TINY. It's one of the design goals of the hash function. And MD5 has been around
long enough to have been analysed and tested by SO many experts, I think we can
safely say it does that it's supposed to very well indeed.

Just my comments :-)

[Tom - you're welcome to add all or some of this to
http://asktom.oracle.com/~tkyte/Misc/Passwords.html
if you feel inclined.]

PS. Guess what MY hobby is. ;-)

PPS. It took me all morning to reasurch this. It would not have been possible
without the Google search engine - www.google.com

PPPS. Tom is great!
Followup:
forget windows "calc" ;)

ops$tkyte@ORA920PC> set numformat 999999999999999999999999999999999999999999999

ops$tkyte@ORA920PC> select power(2,128) from dual;

                                  POWER(2,128)
----------------------------------------------
       340282366920938463463374607431768211456
Rating: 5
Wow February 19, 2004
Reviewer: Invisible from UK
Wow... you can do that??? :-D

Now make it print a comma every 3 digits :-P

(Sheesh... I wonder what the hell else Oracle can do... I assumed it would have
limited precition arithmatic only. Certainly had enough weird rounding errors
last time I tried to use it...)
Followup:
you won't have any wierd rounding errors with Oracle numbers -- they are built
to be extremely precise in that fashion -- 38 digits precise in fact.  they are
like BCD or packed decimal.  Much more precise than a float or double.

they were built to count pennies accurately.
Rating: 4
please add salt February 19, 2004
Reviewer: A. Reader
In order to prevent dictionary based attacks, add salt to it:

    salt = random_number()
    hash = md5(salt || user || password)

and store salt and hash in the database.
Followup:
hmm, interesting.

but -- tell me -- how do you validate an input username/password pair later?
You don't know what the salt was.

The salt has to be a fixed (semi-secret) value.  even if not secret, it's OK,
attacking a hash is a computationally "big" thing to do.
Rating: 5
"they were built to count pennies accurately. " February 20, 2004
Reviewer: Connor from Aus
Yes I do believe "NUMBER(38)" was built by Oracle as a response to their license
costs

:-)
Followup:
dude, thats so harsh ;)
Rating: 5
Indeed February 20, 2004
Reviewer: Invisible from UK
...and 38 digits is EXACTLY how many there are in 2^128 :-D

(Flukey or what? ;-)

BCD eh? Interesting... Oh, wait a sec - 1/10 is a recurring sequence as in
binary, so can't be exactly represented. Mmm, a datbase that can't store 10p
exactly wouldn't be terribly good. I guess that's why it's BCD then! :-)

As for my comment about rounding errors... don't want to get into a huge debate.
I don't have the source code that experienced the problem, so it would be a
rather factless ( = pointless) debate.

Basically for a project I had to develop a database that could store some 2D
lines (easy), and tell me which lines cross each other (harder). In one of my
test cases, two lines that overlapped were reported as NOT intersecting; but if
I translated both back towards the origin by the same amount, suddenly the
software said they DID overlap!

Of course, now I don't have the code any more, no way of telling whether Oracle
did that or whether I fluffed up the code, so let's leave it there.

Coming back round to the original topic of this thread... the other poster's
suggestion to add salt is a good one.

Basically someone might build a great long list of plausible ASCII sequences
that someone might use for a password. (i.e., all possible combinations of
common printable characters). They could then calculate the hash of each such
sequence, and store these all in a massive [Oracle? ;-)] database.

Then when it comes to figuring out a user's password from the hash, they just
compare the hash to all the values in their database. Pointless to do it this
way for just one password - the amount of effort is the same. But for groups of
passwords, this approach can make it a lot faster to break some of them.

Of course, this little "short cut" totally masses up if the thing that was
hashed isn't in the attacker's database. So if you add random (non-printable
ASCII) salt to the user's password, then the complete thing won't be in the
attackers database - unless they include ALL possible strings, not just
printable ones. And that makes the database several times larger, increases the
time needed to compute all the hashes several times over, increases the time
needed to find things several times over, etc.

In short, adding salt makes it that little bit harder to anyone wanting to break
in. (Well, actually quite a lot harder...) It also makes it very slightly more
complicated for you to code - but it's probably worth it! :-)
Rating: 4
February 20, 2004
Reviewer: A reader
"You don't know what the salt was.

The salt has to be a fixed (semi-secret) value."

So you just embed your salt value into your PL/SQL hash function and then use
that "magical PL/SQL wrapping thing" to obfuscate the function itself. :)
Followup:
correct -- but in 9i and before make sure to obscure the code a bit -- else a
variable define in code is very plain in the wrapped code.

use

  salt := chr(23) || chr(54) || ....;

don't use

  salt := 'some magic salt';
Rating: 4
Very well explained February 20, 2004
Reviewer: Beginner from New Jersey
But when I try to execute it, I get following error:

[1]: (Error): ORA-06553: PLS-307: too many declarations of 'MD5' match this call
Followup:
yeah, they have a 'bad' overload in there (raw and varchar2).

  dbms_obfuscation_toolkit.md5( INPUT_STRING => 'blah' );
                                ^^^^^^^^^^^^^^^

will help it out.
Rating: 4
But listener password hashes are not so secure March 16, 2004
Reviewer: David Taitano from Washington, DC
If you don't have the password for the TNS listener, just get the hash from
listener.ora and use it, instead -- oracle accepts it as if it were the
password!  I have noticed this behavior in multiple versions of Oracle.

The only apparent way to protect the listener password is to secure the
permissions on the listener.ora file, but this seems pretty weak.
Followup:
they have changed this behaviour in 10g - but, it is all about access (think
"/etc/shadow" for example, to prevent offline cracks).  that file needs to be
locked down regardless.
Rating: 3
about salt :) March 16, 2004
Reviewer: Zsolt Müller from Hungary
Salt ... no meal can be made without it. ;))
That's for the joke. :)

"Salt" is used when it comes to store password hashes in a more "secure" way.
It's purpose is not to make the hash more "secret", but to make it harder for an
attacker to mass-decode passwords. :)

Look at it like this:
- user passwords are usually stored "together" ... in "one place" (in Unix/Linux
it is a single file, in Oracle it is a single table, etc. ... the point is that
an attacker usually gains access to a big collection of stored password hashes
and not only to a single one)
- if you take the username+plain_password as the input of the hash function, one
could "easily" make a big list with all the common (username+password/hash)
pairs and decode a significant part of the passwords in a quite short time
(remember: users _tend_ to choose weak passwords ...)
- if you take some random data, concatenate it with the password, compute the
hash, then store the random data with the hash ... the attacker's hands are
bound ... she/he cannot use any precomputed list of input+hash values, since the
salt multiplies the input space by millions/billions. :)

Here's how I store a password:
- generate 16 semi-random bytes (there's quite a big literature only on this
part ... I mean generating random data :)) )
- pad the cleartext password with the salt to 64 bytes (MD5() is defined to run
on an 512 bits long input, so I make it 512 bits long ;) ... still better if I
do the padding than if Oracle's md5() does it :D )
- store the salt together with the MD5 hash value of the "padded" cleartext
password in the database

The stored hash is made up like this:
  stored_password := salt || md5(LPAD(cleartext_password, 64, salt));
Every stored value contains the salt in the first 16 bytes and an md5 hash in
the second 16 bytes.

The password-check looks something like this:

IF md5(LPAD(incoming_cleartext_password, 64, first_half_of_stored_password)) =
second_half_of_stored_password THEN
  -- passwords match
  RETURN true;
ELSE
  -- different passwords
  RETURN false;
END IF;
Followup:
you've put the horse before the cart here.  the magic salt is sitting right
there?  whats the point of that salt.

the attackers hands are not tied, they need only add the same dash of salt --
the value of which you have provided?
Rating: 5
Guess Tom means Cart before Horse March 17, 2004
Reviewer: A reader
Followup:
doh!  of course ;)
Rating: 3
NaCl? March 17, 2004
Reviewer: Invisible from UK
The "point" is that the attacker can't precompute the hashes before hand -
because the salt makes the search space many millions of times bigger (depending
on how much you add) and it's truely random. (The passwords aren't really.
Should be, but aren't.)

You (the attacker) now can't just compute the hash of every imaginable password
and match the hashes against what you've stolen; you'll get no matches, because
you didn't include the salt.

Of course the salt is right there next to the password. It just means you'll
have to redo all those hash calculations for EACH user you want to crack, not
just once for all of them.

If the salt is right next to the hash, this is the ONLY adventage you gain.
Cracking individual passwords won't be any harder, but you won't be able to take
a big "short cut" to crack whole groups of passwords, that's all.

Now, if there were some way to hide the salt away somehow... that would add even
more benefit. (However, if your password manager can find it, presumably it's
gotta be somewhere - so an attacker can probably find it too.)

Hope that clarifies it...
Followup:
that point was not clear -- it doesn't make it "harder" (it is just a program),
it makes it "take longer".

The normal way is to take the USERNAME/PASSWORD -- hash that.  In that fashion
you still cannot compute the hash of every imaginable password since the
username is part of it (the username in this case is the 'salt' but it is 'salt'
in the clear meaning the attacker must take a dictionary of well known passwords
and hash it for each user -- increasing the time of attack measurably).
Rating: 4
True March 17, 2004
Reviewer: Invisible from UK
Well, you could argue that true salt would be random non-ASCII symbols, possible
of very great length, increasing the search space even more - but yes, simply
using the username achieves something comparable.

(It depends how paraniod you are - and when talking about security, people tend
to get that way I guess.)

Certainly either way is better than just hasing the password alone... It also
helps if you use strong passwords. Oh, and better still - if the attacher can't
get the hashes in the first place! 8-D (If only it were that easy...)
March 17, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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.
Rating: 5
logging and archiving of objects (!!??) February 25, 2004
Reviewer: Subodh Deshpande from India
Hi Tom,

Archiving is done at database level, even then why it is asked many times that
if i giving logging option while creating the objects am achving archiving of
the objects also ?

bye the way, i got your book Expert-one-on-one as my birthday gift !!!
Followup:
huh? not sure what you mean.

archiving is the act of copying the online redo log to another location so we
can use it for media recovery (to catch a backup "up").

Not sure what the question here is?
Rating: 5
question on backup February 25, 2004
Reviewer: Jerry from US.
Tom,

A question on 'both tables are nuked'.  Why the first table can not be restored
from the backup?  How should I protect data for nologging operations?  Thanks!
Followup:
you either

a) do not use nologging
b) schedule an online backup of the affected datafiles IMMEDIATELY after the
nologging operations.
Rating: 4
doubt? February 25, 2004
Reviewer: Jerry from US
Tom,

Sorry about the repeated question.  I am still not very clear on why in your
example the T1 table can not be restored?  You backed up the tablespace
immediately.
Followup:
I added more non-logged data to t1 after backing it up and did not subsequently
back it up again:

.....
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...

....
Rating: 4
how to minimize redo generation February 25, 2004
Reviewer: Jianhui from DC
Tom,
I want to generate redo as little as possible since i dont care the recovery of
these data i'm populating. Do you have any suggestion? I frequently do insert
and delete, no update.
1)I know insert with /*+ append */, but how about delete? I can NOT use truncate
since I need to control what data i delete.
2)Does TEMPORARY table generate REDO? I did a test it seemed it did generate
REDO and even more than normal table. see below Ctrl_C&Ctrl_V

SQL> create global temporary table test as (select * from dba_objects where
0=1);

Table created.

SQL> select * from v$sesstat
  2  where statistic#=115
  3  and sid = (select sid from v$session where username=USER);

       SID STATISTIC#      VALUE
---------- ---------- ----------
        46        115    1673712

SQL> insert into test (select * from dba_objects);

6758 rows created.

SQL> select * from v$sesstat
  2  where statistic#=115
  3  and sid = (select sid from v$session where username=USER);

       SID STATISTIC#      VALUE
---------- ---------- ----------
        46        115    2543500

SQL> create table test2 as (select * from dba_objects where 0=1);

Table created.

SQL> select * from v$sesstat
  2  where statistic#=115
  3  and sid = (select sid from v$session where username=USER);

       SID STATISTIC#      VALUE
---------- ---------- ----------
        46        115    2573744

SQL> insert into test2 (select * from dba_objects);

6759 rows created.

SQL> select * from v$sesstat
  2  where statistic#=115
  3  and sid = (select sid from v$session where username=USER);

       SID STATISTIC#      VALUE
---------- ---------- ----------
        46        115    3253252

SQL>
Followup:
to delete with minimum redo you don't delete (cannot modify the "internal
structure" and NOT generate REDO!!!!

to delete a large % of the records with minimum redo you

CTAS (create table as select) the records to keep
drop old
rename new
index with nologging.

or

CTAS the records to keep
truncate old
insert /*+ append */ into old select * from new
drop new
Rating: 3
How should I protect data for nologging operations? FORCE LOGGING February 25, 2004
Reviewer: Raj from VA. USA
In regards to your comments...on..

" How should I protect data for nologging operations?  Thanks!

Followup:

you either
a) do not use nologging
b) schedule an online backup of the affected datafiles IMMEDIATELY after the
nologging operations. "

Is FORCE LOGGING option of Create database Statement and Create/alter Tablespace
statement...can be used to protect data from all nologging operations ?..
Followup:
that would fall into the a) category.

yes, with 9ir2 you can "force logging" via alter database as well.
Rating: 5
logging and archiving of objects (!!??) February 26, 2004
Reviewer: Subodh Deshpande from India
Hi Tom,

I mean, while creating the object A, if i mention nologging then redos for
object A will not be genrated, suppose if my db crashes and apply the archives,
i will get my other objects and will not get A, does it mean I have archieved my
other objects and not A.

Please explain..
Followup:
if you create object A, using a non-logged operation
AND
all other operations against all other objects have been logged
THEN
when you restore, A will most likely "not be usuable" but everything else will
be.
Rating: 5
logging and archiving of objects (!!??) February 26, 2004
Reviewer: Subodh Deshpande from India
Hi Tom,

I mean, while creating the object A, if i mention nologging then redos for
object A will not be genrated, suppose if my db crashes and apply the archives,
i will get my other objects and will not get A, does it mean I have archieved my
other objects and not A..

OR Archieving is of REDOs (copying redos at diff locn) hence the question of
archieving of objects etc is just playing with words..

Please explain..
Followup:
archives are just copies of online redo log files that filled.  if something was
not logged, it won't be "archived" either.
Rating: 5
Why put a tablespace in nologging? March 10, 2004
Reviewer: A reader
I am confused about why you would put a tablespace in nologging mode. In my
reading on your site, it doesn't seem that the tablespace needs to be in
nologging to perform nologging operations on tables or indexes.

Is that correct? And if so, why would you put a tablespace in nologging?
Followup:
correct -- it does not need be.

setting the tablespace attribute of a tablespace only affects the defaults for
newly created objects in that tablespace (just like setting "initial" would
affect the default initial size for objects created in that tablespace AFTER you
changed the setting)

it does not affect existing objects, only newly created ones.

You might use it on a tablespace that an application created "scratch tables" in
and used direct path operations to manipulate them (insert /*+ APPEND */ for
example) if the application did not set "nologging" on the create table itself.
Rating: 3
Tables nologged March 16, 2004
Reviewer: Ferran from Barcelona
Hi,

Its possible to select on the dictionary what tables are not recoberable if the
instance crashes?

Thanks!
Followup:
it is not "tables" in as much as "files"

query v$datafile for unrecoverable_time.

You can query what tables are non-logged from the data dictionary yes, there is
a column right there in dba_tables (called LOGGING)
Rating: 3
more details March 17, 2004
Reviewer: Ferran
I have one table (table A) which constantly have direct loads. The content of
tha table is not important and can be lost.

When I recover a copy of the database, an ORA-26040 is raised quering table A.
That's right. But the error is not raised for other tables in the same
tablespace or datafile.

Its possible to know what tables/indexes will raise that errors?
Followup:
any and all non-logged tables in that tablespace -- which is available in
dba_tables (you have tablespace_name and logging in that view)

recommendation:  put these objects into their very own special tablespace for
managability reasons
March 17, 2004 Nag -- Thanks for the question regarding "Parallel Query concerns", version 8.1.7
You Asked
Tom

We have 10 cpu's on our production server, we have the following concerns, and
missing pieces of knowledge before we start using parallel query on some select
tables.(I have got the go ahead for using PQ ,but only after doing the research
and proving the results.)

1. What is the max degree of parallelism for a single cpu.
2. How do we arrive at the ideal set up values for
parallel_max_servers
parallel_min_servers
3.What are the max values for the above parameters.
4.I have 10 cpu's and my parallel max servers is set at 40. Now if I give a
parallel degree of 10 for table t( with full hint), how many cpu's will be used
to privide the parallelism of 10.
5.How can we find out how many cpu's are being for a particular query.
6.One of the concerns is that if we start using parallel query, some of the
other processes and applications running, will not get any cpu time. Iam not
convinced, how can this be true or false.
7.How can I arrive at a degree of parallelism, where in my process uses only 4
cpu's out of 10 , and leave the other 6 alone.
8. 'A PQ degree of 1 will use 1 cpu, a pq degree of 2 will use 2 cpu'
this statement is false , can you explain why.

The answers to the above will be very crucial to us.

Thank you very much.
and we said...
1) 3,599

2) How many processes do you want going at the same time?  Probably no more then
100.  Min servers -- upto you totally.  start at zero and let them go up from
there.

3) max, 0..3599
   min, 0..max

4) it'll use 10 processes -- the OPERATING system is responsible for scheduling
the processes.  It'll use whatever it feels like.  Don't forget, when they are
doing IO, they are not on a CPU -- a full scan is not a CPU bound thing, its IO
bound.  For many operations, don't expect your CPU monitors to light up like a
christmas tree or anything.

5) system management tools.  You can see in the database how many PROCESSES
(v$session) but they aren't all active at the same time.

6) this can be true.  You might want to get Jonathan Lewis's book "practical
Oracle8i -- building efficient databases".  I think he put it beautifully when
he described parallel query as a "non scalable operation".  It does not scale up
with users (more users, less scalability).  It gives you the ability to consume
the ENTIRE machine with your one query.  It is designed to use EVERYTHING, it is
very selfish.

7) use a parallel hint with degree = 4.  Again, you won't starve them on CPU as
much as you'll hit the disks and light them up like crazy.  You might be looking
at the wrong resource.

8) See the above answers.  While one process is reading the other could be
reading as well (we are using 0 cpu at that point).  We could be using 0, 1 or 2
-- its up to the SCHEDULER in the OS.
Review & Followup

Rating: 4
Parallel Query October 18, 2001
Reviewer: George Spears from Dayton, OH
Tom,

Just a general comment on question 4.  Assuming that the table degree of
parallelism is set to 10, either 10 or 20 processes / threads may be used, 10
for the reading, and an additional 10 if the query is doing a sort operation.  I
would assume the behaviour in this circumstance is the same, regardless of the
degree of parallelism being set at the table or in a hint.

Comments?
Followup:
Ok, just to be pedantic, it could use more then 20 (i was keeping it simple and
trying to point out a process != CPU)

Each step in the query that feeds another step can be done at the same time and
in parallel as well.  if the query were a full scan AND a sort, it could be the
10 scanners feed to the 10 sorters feed to the one coordinator.

If you look at:
http://download-east.oracle.com/docs/cd/A81042_01/DOC/server.816/a76965/c22paral.htm#12937
you can see that if I did a join of two tables each parallel 10, I might have
upto 40 parallel query "slaves" and one coordinator (in that example).
Rating: 5
October 18, 2001
Reviewer: A reader
Tom, kindly distinguish between

"a full scan is not a CPU bound thing, its IO
bound"

CPU bound and IO bound

1.Give examples of CPU bound operations and IO bound operations.
2.( how come an operation which is IO bound i.e. reading disks, does not use
CPU).

3.When you say process
 is 1 process= 1 CPU

4.Can you explain the role of SCHEDULER in the OS, how does it distribute cpu
between queries.
Followup:
1) when you SORT data in memory, that eats a ton of CPU.
   when you read 5 gig of data from disk, that'll spin your disks.

(i'm at a loss to come up with "simpilier" examples -- things that read the disk
= IO bound.  things that process data = CPU)

2) the storage device does that, your SCSI controller gets the read request and
does the work.  There are drive technologies where this is not 100% true (like
IDE drives) but you wouldn't use them on a database machine.

3) no, i thought that would be clear.  A process is a process is a process.  On
a single CPU machine, you can have hundreds of processes running.  On a 10 cpu
machine you can have hundreds of processes.  A cpu is a resource (like a
printer, or disk space) that a process will reques an the OS will coordinate
access to.

4) beyond the scope of asktom -- thats a whole class in computer science ;)
Rating: 4
Linear performance gain May 14, 2002
Reviewer: john from ukraine
Tom,

Many people from Oracle in their books say that one can get almost linear
performance gain out of OPQ.
I've used every single thought that came on my mind
and couldn't receive even 50% gain.
I used one tablespace with 2 datafiles in 3 configurations, and those are:
1. different disks
2. different disks and on different controllers
3. different raid storages through one scsi controller

What am i missing to gain the result?
Followup:
operative word "CAN get".

You would optimally have separate devices (to reduce contention).
You would be doing something that can easily be sped up (full scans, index
builds).
You would have more then 1 cpu.You would have sufficient ram.
You would have sufficient spare resources to begin with (eg: if the machine is
overloaded doing serial processing -- PQ will make it run slower)

You will make sure that the PQ slaves do different types of work at the same
time.  For example -- I see people do index rebuilds in parallel with HUGE sort
area sizes.  Well -- each PQ slave tends to read a ton, sort a ton, read a ton,
sort a ton.  A smaller sort area size would help them get out of "sync" --
instead of all of them reading together then sorting then reading then sorting
-- they will start to interleave so that some are reading, some are sorting
(some using disk, some using cpu at the same time).
Rating: 5
May 14, 2002
Reviewer: A reader
We are using RULE base optimizer.

Can we take advantage of parellel query option in oracle.

Thanks.
Followup:
Yes, but it'll be using CBO *not* RBO as soon as you start using it. (hence
you'll want statistics).
Rating: 5
November 22, 2002
Reviewer: A reader
Hi Tom,

What do you infer from the following info?

SQL> select name, value from v$sysstat where name like 'Parallel%';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
Parallel operations not downgraded                                     4321
Parallel operations downgraded to serial                               2163
Parallel operations downgraded 75 to 99 pct                             544
Parallel operations downgraded 50 to 75 pct                            2063
Parallel operations downgraded 25 to 50 pct                            3960
Parallel operations downgraded 1 to 25 pct                             2006

6 rows selected.

SQL> sho parameter parallel

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
fast_start_parallel_rollback         string  LOW
optimizer_percent_parallel           integer 0
parallel_adaptive_multi_user         boolean TRUE
parallel_automatic_tuning            boolean TRUE
parallel_broadcast_enabled           boolean FALSE
parallel_execution_message_size      integer 16384
parallel_instance_group              string
parallel_max_servers                 integer 200
parallel_min_percent                 integer 0
parallel_min_servers                 integer 0
parallel_server                      boolean FALSE
parallel_server_instances            integer 1
parallel_threads_per_cpu             integer 4
recovery_parallelism                 integer 0
SQL>

Thanks.
Followup:
such a large downgraded to serial indicates that lots of your operations were
done serially instead of in parallel.  That is, your system was maxed out --
tons of people trying to do parallel query that couldn't.

given that you have max server at 200 i would say that you have a database with
lots of concurrent DW queries and are maxing it out.
Rating: 5
November 22, 2002
Reviewer: A reader
Thanks. Now what would you say if my DBA or manager does not really care when I
pointed this out?

This is a warhouse with about 800 GB data on a Sun10K domain (24 CPU). There are
about 20-50 concurrent users all the time. We are using
parallel_automatic_tuning. But the tables still have a DOP set. Will it help if
we keep the DOP on tables to default and let oracle decide? Also, do you think
the parallel_max_servers can be higher (to something like 800-1000) or since we
have parallel_threads_per_cpu at 4, will the actual max_servers be 4*200=800?

How can I find more info on the details of parallel query? (what to look for in
v$pq_systat etc..). Thank you for your time.
Followup:
50 users -- parallel 4 (little) -- you are out of power.

I would say -- if performance is acceptable - it is doing what it is supposed to
be doing (backing off on resources as resources become scarce).

I might remove the DOP on the objects and just make the parallel.  Be prepared
to put them back as they are (just in case).

200 parallel, active processes on a 24 cpu machine is already getting up there,
I would not think HIGHER no.
Rating: 5
November 23, 2002
Reviewer: A reader
Oracle suggests parallel_max_servers as 2xDOPxmax_concurrent_users. How do you
relate the no. of cpus to parallel_max_servers? (ie how do you say that a
parallel_max_servers of 200 is high for a 24 CPU box? I could not find a
relation between no of cpus and parallel max servers in Oracle Doc). How do we
monitor the system load when we have 200 parallel slave processes running (at
o/s level what to look for? Its solaris 2.8).

You said if performance is ok then it is doing the right thing. But performance
is not OK. If we have more than 20 users, the system slows down. It has a
scalability limitation which we want to identify and fix. Thank you very much
for your kind help on this.
Followup:
(where is that guidance, I can't look at things out of context -- I would bet
there is a caveat lurking somewhere near that sentence that says "of course, the
number of CPU's had better be considered")

If you have 200 processes all going active on a 24 cpu box -- tell me -- what is
going to happen?  can you spell "backed up run queue".  You can use vmstat, sar,
top -- any number of OS utilities to monitor this.

As for the last paragraph -- I would expect it to slow down.  You only have but
24 CPU's.  You let users use lots more then one at a time.  Parallel query is
NOT A SCALABLE solution (i believe Jonathan Lewis said it best in his book
Practical Oracle8i).  PQ was designed to let a few users totally consume a
machine.  As you add more and more users -- you have less and less resources --
hence by definition I would expect things to run slower and slower.

Now the question is -- given the resources you have (24 cpus) -- and more then
20 users -- what is the fair and equitable ratio of USERS to CPUS.  It is
getting nearer and nearer to 1:1 as the number of users approaches and then
exceeds the number of CPUs.

Think what would happen if you got 50 users doing parallel 10?  500 concurrently
execution processes doing CPU grabbing and context switching all over the place.

This is exactly why connection pooling, connection concentration, connection
multiplexing was invented -- to reduce the amount of concurrently executing
stuff as the machine gets swamped.

20 users doing parallel query on a 24 cpu machine cannot reasonably expect the
performance that same machine gives 1 user, 5 users.

What you should be looking for is a predicable, near linear change in response
times as concurrency goes up and demand for these shared resources increases.
Rating: 5
November 23, 2002
Reviewer: A reader
Thank you for the fast response.

We have plans of moving out to a bigger machine. But till then, we have to
somehow improve things on this machine. We are planning to use
dbms_resource_manager to limit unimportant users from hogging the resources.
Isn't it the right thing to do? Any caveats?

Also I am just wondering, to support a DW system with about 1TB of data and
around 30-50 concurrent users, what would be the ideal system config? Should
something like 48 cpus and 96GB memory (that is what we are planning to upgrade
to) would be enough?. It sounded like an overkill to me first, but I realise now
may be I am wrong. As you said parallel query is not a scalable solution.
Curious to know first hand experience from anyone running a really busy DWH out
there!

Your valuable insights are apprecited as always!
Followup:
using the dbms_resource manager would be a way to prioritize yes, you could
limit the DOP by user groups -- making some users parallizable and others --
not.

You SHOULD also look at just POT -- plain old tuning.  Parallel query is great
at "big things" -- well, are there frequently asked questions of your DW that
you could pre-answer via materialized views and using query rewrite knock off
the need for parallel query all over the place (rhetorical question, I'm
confident the answer is YES).

Have you read the data warehousing guide?
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76994/toc.htm
look at MVs, partitioning, star transformations and other traditional DW
features in order to reduce or entirely REMOVE the need for parallel query.

If by 30-50 concurrent users you really mean 30-50 concurrent DW Queries all
wanting to use parallel operations for "fast=true", you will need an truly big
machine.  You could consider that instead of a multi-million dollar piece of
hardware, you look at commidity servers and use something like 9iRAC to spread
the load out.
Rating: 5
November 24, 2002
Reviewer: A reader
You are very correct in your observations... No doubt, a lot can be achived by
making use of the good old tuning techniques. We are infact using partitioning,
star schema and MVs. But we can sure do a lot more.

A very interesting point you have made regarding RAC. If I were the one to make
the decision, I would have considered it seriously. But I feel, it will take few
more years before it will be accepted across corporate america as the platform
of choice for deploying critical applications.

Thanks again for your time.
Followup:
just like SMP in the early 90's -- I remember the project I was working on,
having heated discussions over whether to go with a 2 cpu vs 1 cpu machine.  Not
even a consideration anymore....
Rating: 5
PQ is a big headache April 01, 2003
Reviewer: Peter Tran from Houston, TX USA
We had a situation where we installed oracle 9i and it had as default:

parallel_max_servers=40
parallel_min_servers=0

We used the same base code that we had been developing against our 8.1.7
database to test against this new 9i instance.  Performance was abysmal.

We couldn't figure out why until we reset the values to 0.  When we disabled the
PQ parameters, performance returned to acceptable (i.e. 8i) level.

Does ORACLE documentation even mention that PQ is a non-scalable solution?

If you set both parallel_max_servers and parallel_min_servers to 0, does that
mean PQ is forever turned off?  Can you enable PQ with hints when these two
values are set to 0?

Other than the trial and error with the init.ora parameters, what other methods
can be used to determine that enabling PQ is not appropriate?  We looked at a
bunch of reports, but nothing said "Disable PQ!"  Is there any reports or query
you can recommend running against the V$ tables that would indicate that
enabling PQ is bad?

Thanks and keep up the good work!
Followup:
Umm, you had to have had parallel on your tables there then.  You ASKED us to do
this.  You changed the init.ora settings, after having set all or some of your
tables to parallel -- and well, the system performed differently.

And yes, parallel query (unless you are using the parallel automatic stuff) is
by design "non scalable".  It's very design allows -- is supposed to -- let a
single user completely nail the machine.

If you have max set to zero, its off.  You have parallel set on your tables.  If
you want to use it with hints -- you would put the default DOP (degree of
parallelism) back on the tables -- set max to whatever and hint when you wanted
to use it.

Benchmarking, understanding how it works, understanding what you are asking the
system to do and for how many users.

PQ is not a big headache -- no more then a hammer is to a carpenter.  It is a
tool and all tools, when inappropriately applied cause headaches.  Ever try to
put a nail in with a stapler?
Rating: 5
You're right April 09, 2003
Reviewer: Peter Tran from Houston, TX USA
Okay, I stand corrected.  PQ isn't a headache.  It only is when you don't
understand what's going on underneath the hood.

When thing that puzzled us is we had a situation where the parallel_min_servers
and parallel_max_servers were set to the same values (e.g. min=0 and max=40) on
both 8i and 9i instances (assuming all other configuration parameters being
equal), we noticed using the same table/index creation script (with the parallel
keyword), the performance on the 9i instance was much worse.  Using the same
code base to benchmark the difference, we noticed a 5x performance degradation
with the 9i instance.  If we set the parallel_min_servers=0 (affectively
disabling PQ), the 9i instance performance matched that of the 8i instance.

From your reply above, we could achieve the same result by removing the parallel
keyword from our table/index creation script without having to disable PQ for
the 9i instance.

I noticed that 8i has the optimizer_percent_parallel parameter that has been
deprecated in 9.0.1.  If this setting is left at the default 0 on the 8i
instance, does this mean that this affectively disables PQ on an 8i instance
even when the parallel_min_servers > 0?

From the ORACLE 8i documentation:
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76961/ch1125.htm#66146
<quote>
OPTIMIZER_PERCENT_PARALLEL specifies the amount of parallelism that the
optimizer uses in its cost functions.

The default of 0 means that the optimizer chooses the best serial plan. A value
of 100 means that the optimizer uses each object's degree of parallelism in
computing the cost of a full table scan operation. Low values favor indexes, and
high values favor table scans.
</quote>

Thank you for your help.
Rating: 5
July 02, 2003
Reviewer: A reader
Hi Tom,

  We are running RULE base optimizer. Can we use Parallel Query or any other
parallel stuff in Rule base Optimizer?

Thanks
Followup:
parallel requires the use of the CBO.
Rating: 5
according to James Morle´s book July 03, 2003
Reviewer: pingu_san
Hi

I am reading James Morle´s Scaling Oracle 8i book he states that

a CPU can only run a process

This was true before several years ago, is it still true now days???
Followup:
add "at a time"

a cpu can only run a process at a time.  true they are getting into pipelining
and such but in general "a cpu can only run a process at a time".

the registers and all have to be setup for that process, then a context switch
to another process and so on.
Rating: 5
Why parallel query ? January 05, 2004
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom,
We have a production OLTP database. We have set parallel_max_servers=5 to speed
up some DBA operations on staging tables. However, all the OLTP tables and
indexes have the NOPARALLEL option specified. During the day, we see lot of
application user's queries being parallelized even though they are being fired
against noparallel tables/indexes. We do not understand what is causing this.
There is no parallel hint in any of the queries. The only objects with degree>1
are in the staging area. Application users do not have access to this area. We
do not want OLTP queries to be parallelized.
Thanks.
Followup:
versions?

show parameter parallel output?

select table_name, degree from user_tables where degree <> 1 or instances <> 1

and please verify NONE of your tables pop up.
Rating: 5
Why parallel query ? January 06, 2004
Reviewer: Arun Gupta from Harrisburg, PA USA
We are on Oracle version 9.2.0.4. The outputs are:
SQL> show parameter parallel;

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
fast_start_parallel_rollback         string      LOW
log_parallelism                      integer     1
parallel_adaptive_multi_user         boolean     FALSE
parallel_automatic_tuning            boolean     FALSE
parallel_execution_message_size      integer     2152
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

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
parallel_threads_per_cpu             integer     2
recovery_parallelism                 integer     0
SQL>

SQL> select table_name, degree from user_tables where degree <> 1 or instances
<> 1
  2  ;

no rows selected

SQL>

SQL> ed
Wrote file afiedt.buf

  1* select table_name, degree from dba_tables where degree <> 1 or instances <>
1
  2  /

no rows selected

SQL>

Thanks
Followup:
well, you said SOME of the tables were parallel no? where are they?

I have (given the information I have) nothing postive for you here -- All I can
say is "something is amiss"...

can you tell me how you are seeing that the queries are being done in parallel
during the day -- give me an example output that you are seeing that leads you
down that path.
Rating: 5
Why parallel query ? January 06, 2004
Reviewer: Arun Gupta from Harrisburg, PA USA
Between the time I posted the question and generated the output, my colleague
DBA switched all tables to noparallel to eliminate the problem. We are still
seeing the parallel queries. I ran the following script from Metalink:
SELECT
  decode (px.qcinst_id,
  null,
  username,
  ' - '||lower(substr(s.program,
  length(s.program)-4,
  4) ) ) "Username",
  decode(px.qcinst_id,
  null,
  'QC',
  '(Slave)') "QC/Slave",
  to_char(px.server_set) "Slave Set",
  to_char(s.sid) "SID",
  decode(px.qcinst_id,
  null,
  to_char(s.sid),
  px.qcsid) "QC SID",
  px.req_degree "Requested DOP",
  px.degree "Actual DOP"
FROM
  v$px_session px,
  v$session s
WHERE
  px.sid=s.sid(+)  and
  px.serial#=s.serial#
  order by 5,1 desc;

The results show us that the Requested DOP is 8 but the Actual DOP is 5, which
is correct since we have 5 parallel servers. The only place 8 appears is that
one of tables referenced in the query has 8 partitions. Though not likely, is it
possible that presence of partitions is causing parallelism?
Thanks
Followup:
WHAT SQL is actually being done in parallel?
Rating: 5
January 07, 2004
Reviewer: Arun Gupta from Harrisburg, PA USA
SELECT
  count(1)
FROM
  hcsis.t_sc_mntrng_doc_vers scm,
  hcsis.t_resp r
WHERE
  scm.idn_case=:b4   AND
  scm.nbr_ctl_doc=r.nbr_ctl_doc   AND
  r.idn_prompt=:b3   AND
  scm.cde_status_mntrng = :b2   AND
  SUBSTR(scm.cde_type_cntc,2,1) = :b1   AND
  scm.tme_dte_visit IN    (SELECT MAX(scm.tme_dte_visit)
                           FROM hcsis.t_sc_mntrng_doc_vers scm, hcsis.t_resp r

                           WHERE scm.idn_case=:b4
                           AND  scm.nbr_ctl_doc=r.nbr_ctl_doc
                           AND  r.idn_prompt=:b3
                           AND  scm.cde_status_mntrng = :b2
                           AND  SUBSTR(scm.cde_type_cntc,2,1) = :b1);

The parallel query processes are executing:
p000
SELECT
  /*+ ORDERED NO_EXPAND USE_NL(A2) */ A1.C0,
  A1.C1,
  A1.C2,
  A1.C3,
  A1.C4,
  A1.C5,
  A1.C6,
  A2.C0,
  A2.C1,
  A2.C2
FROM
  :Q6140000 A1,
  (SELECT /*+ NO_EXPAND INDEX(A3 "T_RESP_PR_DCN_INDEX") */ A3.ROWID C0,
  A3."IDN_PROMPT" C1,
  A3."NBR_CTL_DOC" C2 FROM "HCSIS"."T_RESP" PX_GRANULE(0,
  PARTITION,
  DYNAMIC)  A3) A2
WHERE
  A2.C1=:B1 AND
  A1.C1=A2.C2;

p001:
SELECT
  /*+ ORDERED NO_EXPAND USE_NL(A2) */ A1.C0,
  A1.C1,
  A1.C2,
  A1.C3,
  A1.C4,
  A1.C5,
  A1.C6,
  A2.C0,
  A2.C1,
  A2.C2
FROM
  :Q6140000 A1,
  (SELECT /*+ NO_EXPAND INDEX(A3 "T_RESP_PR_DCN_INDEX") */ A3.ROWID C0,
  A3."IDN_PROMPT" C1,
  A3."NBR_CTL_DOC" C2 FROM "HCSIS"."T_RESP" PX_GRANULE(0,
  PARTITION,
  DYNAMIC)  A3) A2
WHERE
  A2.C1=:B1 AND
  A1.C1=A2.C2;

p002:
SELECT
  /*+ ORDERED NO_EXPAND USE_NL(A2) */ A1.C0,
  A1.C1,
  A1.C2,
  A1.C3,
  A1.C4,
  A1.C5,
  A1.C6,
  A2.C0,
  A2.C1,
  A2.C2
FROM
  :Q6140000 A1,
  (SELECT /*+ NO_EXPAND INDEX(A3 "T_RESP_PR_DCN_INDEX") */ A3.ROWID C0,
  A3."IDN_PROMPT" C1,
  A3."NBR_CTL_DOC" C2 FROM "HCSIS"."T_RESP" PX_GRANULE(0,
  PARTITION,
  DYNAMIC)  A3) A2
WHERE
  A2.C1=:B1 AND
  A1.C1=A2.C2;

p003:
SELECT
  /*+ ORDERED NO_EXPAND USE_NL(A2) */ A1.C0,
  A1.C1,
  A1.C2,
  A1.C3,
  A1.C4,
  A1.C5,
  A1.C6,
  A2.C0,
  A2.C1,
  A2.C2
FROM
  :Q6140000 A1,
  (SELECT /*+ NO_EXPAND INDEX(A3 "T_RESP_PR_DCN_INDEX") */ A3.ROWID C0,
  A3."IDN_PROMPT" C1,
  A3."NBR_CTL_DOC" C2 FROM "HCSIS"."T_RESP" PX_GRANULE(0,
  PARTITION,
  DYNAMIC)  A3) A2
WHERE
  A2.C1=:B1 AND
  A1.C1=A2.C2;

p004:
SELECT
  /*+ ORDERED NO_EXPAND USE_NL(A2) */ A1.C0,
  A1.C1,
  A1.C2,
  A1.C3,
  A1.C4,
  A1.C5,
  A1.C6,
  A2.C0,
  A2.C1,
  A2.C2
FROM
  :Q6140000 A1,
  (SELECT /*+ NO_EXPAND INDEX(A3 "T_RESP_PR_DCN_INDEX") */ A3.ROWID C0,
  A3."IDN_PROMPT" C1,
  A3."NBR_CTL_DOC" C2 FROM "HCSIS"."T_RESP" PX_GRANULE(0,
  PARTITION,
  DYNAMIC)  A3) A2
WHERE
  A2.C1=:B1 AND
  A1.C1=A2.C2;
Followup:
what is the DOP on the indexes?
Rating: 5
January 08, 2004
Reviewer: Arun Gupta from Harrisburg, PA USA
The DOP on all indexes is 1. The difference is that for two indexes, the value
of instances is DEFAULT. For all other indexes, the value of instances=1.

Table   Index                           Degree  Instances
T_RESP    T_RESP_NBR_CTL_DOC_INDEX    1    DEFAULT
T_RESP    T_RESP_PR_DCN_INDEX            1    DEFAULT
Followup:
that is what is doing it then.

consider:

ops$tkyte@ORA817DEV> create table t ( x int not null );

Table created.

ops$tkyte@ORA817DEV> create index t_idx on t(x);

Index created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> exec dbms_stats.set_table_stats( user, 'T', numrows =>
1000000, numblks => 100000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> set autotrace traceonly explain
ops$tkyte@ORA817DEV> select count(*) from t where x > 0;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=13)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FAST FULL SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1 Card=50000
Bytes=650000)

ops$tkyte@ORA817DEV> alter index t_idx parallel ( degree 1 instances default );

Index altered.

ops$tkyte@ORA817DEV> select count(*) from t where x > 0;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=13)
   1    0   SORT (AGGREGATE)
   2    1     SORT* (AGGREGATE) :Q357000
   3    2       INDEX* (FAST FULL SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1
Card=50000 Bytes=650000) :Q357000

   2 PARALLEL_TO_SERIAL            SELECT /*+ PIV_SSF */ SYS_OP_MSR(COUNT(*)) F
                                   ROM (SELECT 0 FROM :I."T"."T_IDX"  PX_GRANUL
                                   E(0, BLOCK_RANGE, DYNAMIC) A2 WHERE A2."X">0
                                   ) A1

   3 PARALLEL_COMBINED_WITH_PARENT

ops$tkyte@ORA817DEV> alter index t_idx parallel ( degree 1 instances 1 );

Index altered.

ops$tkyte@ORA817DEV> select count(*) from t where x > 0;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=13)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FAST FULL SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1 Card=50000
Bytes=650000)

ops$tkyte@ORA817DEV> set autotrace off
Rating: 4
parallel automatic tuning parameter January 08, 2004
Reviewer: Vinnie from Orlando
Tom,

If I set the automatic tuning parameter=TRUE
& create a table using the parallel clause
what can I except the effect on other users when
a single user is quering this table constantly?

Does is make a difference if that table has a BLOB?

thanks
Followup:
you can expect to see the DOP go up and down on this table as the load goes up
and down (this is the purpose of the auto part -- to increase/decrease the
resources used by PQ in reaction to the current load)

I cannot "quantify" what will happen to the other users -- not quite possible.

blobs over 4k are stored out of line, so they will not necessarily impact full
table scans.
Rating: 5
January 08, 2004
Reviewer: Arun Gupta from Harrisburg, PA USA
What is "instances" and why a value of default causes parallel query? We have
the database mounted by single instance only, no RAC.
Thanks...
Followup:
instances = number of instances to parallelize the query over.

In Oracle -- you can have more than one database instance open and mount a
single database (set of files).

So, you could have 4 computers in a cluster -- each running Oracle -- against a
single database.

Now, along comes query -- we can parallelize the query in one of three ways:

a) on a single machine, in parallel
b) across ALL FOUR machines -- each machine doing serial
c) across ALL FOUR machines -- each machine further doing its part in parallel

Someone at your site explicitly set this, explicitly set the instances.  This
trips off parallel query capabilities and with parallel automatic tuning, well
-- the rest as they say is history

Just like default degree kicks in PQ, default instances does as well.  The query
plan was what keyed me to ask "and what about your indexes" since it is showing
a parallel index scan -- not a parallel full scan of a table.
Rating: 5
January 08, 2004
Reviewer: Arun Gupta from Harrisburg, PA USA
It sure looks like someone set the wrong parameters. It was the index in the
plan which gave things away.

I was told that the indexes were altered to noparallel, but obviously that
information was incorrect. I just set both the indexes to noparallel and the
degree and instances were both set to 1. The parallel queries disappeared.

Though of academic interest now, I cannot reproduce the example you worked out.
I always get INDEX(RANGE SCAN) and unless I set both table and index to parallel
with degree>1, the parallel query just does not appear in the plan. I am working
on some more combinations to see if I can reproduce the problem.

Thanks a lot for your help. I was sure that if anyone can help, it would be you.
Rating: 5
Is it true that Parallel query is meaningfull only if table is partitioned? March 11, 2004
Reviewer: Tony from India
Followup:
No, not at all.  Parallel query predates partitioning by many releases

PQ = 7.1.6
Partitioning = 8.0
Rating: 4
find the process which spawned p00* processes March 17, 2004
Reviewer: A reader
Hi

How can I find the parent process which spawned parallel slave processes? Can I
rely on v$session.audsid? It seems that the PQ coordinator and slave processes
share same audsid
Followup:
v$px_ views

v$px_session in particular has the "session identifier of the parallel
coordinator"
March 17, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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.
Rating: 3
passing a refcursor to a stored procedure from a stored procedure February 11, 2004
Reviewer: dwl from uk
Tom

i would like to pass a resultset from a stored procedure to a calling procedure.
Are refcursors the best way? I tried to code it like this:

CREATE OR REPLACE TYPE rec_Table1 IS OBJECT (    Org_ID         int,
                        ST_Count     int);
/

CREATE OR REPLACE TYPE rec_Table1_TABLE IS TABLE OF rec_Table1  INDEX BY
BINARY_INTEGER;
/

CREATE OR REPLACE PACKAGE PKG_SBE515 AS

TYPE C_Return_Cursor IS REF CURSOR;

PROCEDURE P_SBETable_1a (    P_Cur IN OUT C_Return_Cursor,
                V_Start_Date     IN DATE,
                V_End_Date     IN DATE        );

PROCEDURE P_SBETable_1;

END PKG_SBE515;
/

CREATE OR REPLACE PACKAGE BODY PKG_SBE515 AS

PROCEDURE P_SBETable_1a (    P_Cur         IN OUT C_Return_Cursor,
                V_Start_Date     IN DATE,
                V_End_Date     IN DATE        )
IS

BEGIN

    OPEN P_Cur FOR

'SELECT ORG_ID,
    ST_COUNT
FROM    (
        SELECT     ORG_ID,
            NVL(SUM(THECOUNT), 0) AS ST_COUNT
        FROM    (
            SELECT
                PAYMENT_ORG_ID,
                COUNT(*) AS THECOUNT
            FROM     PAID_CLAIM
            WHERE     PAYMENT_ORG_ID IN (SELECT Org_ID FROM Organisation WHERE
Type_Id IN (5, 7, 10))
            AND     PAYMENT_PERIOD_TO >= ''1/Oct/2003'' AND PAYMENT_PERIOD_TO <
''1/Apr/2004''
            AND     CLAIM_TYPE=''GOS1''
            GROUP BY PAYMENT_ORG_ID
            UNION ALL
            SELECT     PAYMENT_ORG_ID,
                SUM(ST_TOTAL_NUMBER) AS THECOUNT
            FROM     PAID_NHS_SIGHT_TESTS
            WHERE     PAYMENT_ORG_ID IN (SELECT Org_ID FROM Organisation WHERE
Type_Id IN (5, 7, 10))
            AND     PAYMENT_PERIOD_TO >= ''1/Oct/2003'' AND PAYMENT_PERIOD_TO <
''1/Apr/2004''
            GROUP BY PAYMENT_ORG_ID
            UNION ALL
            SELECT     PAYMENT_ORG_ID,
                SUM(NVL(ST_BEFORE_NUMBER, 0) + NVL(ST_AFTER_NUMBER, 0)) AS
THECOUNT
            FROM     PAID_OMPS_STATEMENTS
            WHERE     PAYMENT_ORG_ID IN (SELECT Org_ID FROM Organisation WHERE
Type_Id IN (5, 7, 10))
            AND     PAYMENT_PERIOD_TO >= ''1/Oct/2003'' AND PAYMENT_PERIOD_TO <
''1/Apr/2004''
            GROUP BY PAYMENT_ORG_ID
            ),
            (SELECT Org_ID FROM Organisation WHERE Type_Id IN (5, 7, 10))
        WHERE     PAYMENT_ORG_ID(+) = ORG_ID
        GROUP BY ORG_ID
    )';

END P_SBETable_1a;

PROCEDURE P_SBETable_1

IS

l1_data rec_Table1_TABLE;
t1      C_Return_Cursor;

BEGIN

P_SBETable_1a (    t1,
        '1/Oct/2003',
        '1/Apr/2004');

      loop
            l1_data := rec_Table1_TABLE();
            fetch t1 bulk collect into l1_data;
        dbms_output.put_line( 'Fetched ' || l1_data.count || ' rows' );

           exit when t1%notfound;
      end loop;
      close t1;

      insert into tble1
      select * from TABLE( cast (l1_data as rec_Table1_TABLE) );

end P_SBETable_1 ;

END PKG_SBE515;
/

This compiles but when run i get:

BEGIN PKG_SBE515.P_SBETable_1; END;

*
ERROR at line 1:
ORA-01001: invalid cursor
ORA-06512: at "OPT1.PKG_SBE515", line 122
ORA-06512: at line 1

What am i doing wrong?
If this isn't the best way, then how else should i do it, pass arrays of
objects, pl/sql tables??

I want to fetch the cursor into an array of a complex object type so i can cast
the table and do a bulk insert into final table.

I am using 8.1.7.4.

Thanks
Followup:
simple -- small -- concise, yet 100% complete.  that is what examples should be
(and runnable by anyone as well!)

anyway.... the problem is in 8i (not so in 9i) you cannot array fetch from the
dynamically opened ref cursor:

ops$tkyte@ORA817DEV> create or replace package demo_pkg
  2  as
  3          type rc is ref cursor;
  4
  5          procedure p1;
  6          procedure p2;
  7          procedure p3( p_cur in out rc );
  8  end;
  9  /

Package created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace package body demo_pkg
  2  as
  3
  4  procedure p1
  5  is
  6          l_cursor rc;
  7          l_dummy  dual.dummy%type;
  8  begin
  9          p3( l_cursor );
 10          loop
 11                  fetch l_cursor into l_dummy;
 12                  exit when l_cursor%notfound;
 13          end loop;
 14          close l_cursor;
 15  end;
 16
 17
 18  procedure p2
 19  is
 20          l_cursor rc;
 21          type tabType is table of dual.dummy%type index by binary_integer;
 22          l_dummy  tabType;
 23  begin
 24          p3( l_cursor );
 25      fetch l_cursor BULK COLLECT into l_dummy;
 26          close l_cursor;
 27  end;
 28
 29  procedure p3( p_cur in out rc )
 30  is
 31  begin
 32          open p_cur for 'select * from dual';
 33  end;
 34
 35  end;
 36  /

Package body created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> exec demo_pkg.p1

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> exec demo_pkg.p2
BEGIN demo_pkg.p2; END;

*
ERROR at line 1:
ORA-01001: invalid cursor
ORA-06512: at "OPS$TKYTE.DEMO_PKG", line 25
ORA-06512: at line 1

Only difference between p1 and p2 is the "bulk collect".

if you use static sql:

 29  procedure p3( p_cur in out rc )
 30  is
 31  begin
 32          open p_cur for select * from dual;
 33  end;
 34
 35  end;
 36  /

Package body created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> exec demo_pkg.p1

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> exec demo_pkg.p2

PL/SQL procedure successfully completed.

no such issues.

AND you have to use binds (please please please) not those literals in your
dynamic sql!!!!!!
Rating: 5
cant quite get iti to work yet! February 12, 2004
Reviewer: dwl from uk
Tom

Thanks for the information regarding bulk collect into arrays.

However i am still having trouble with this my new procedure now looks like:

PROCEDURE P_SBETable_1

IS

t1      C_Return_Cursor;

--l1_data t1%TYPE;

l1_data_element rec_Table1;
l1_data rec_Table1_TABLE;

i int := 0;

BEGIN

null;

P_SBETable_1a (    t1,
        '1/Oct/2003',
        '1/Apr/2004');

        loop

        i := i + 1;

            fetch t1 into l1_data_element;
        l1_data(i) := l1_data_element;

           exit when t1%notfound;
      end loop;
      close t1;

     insert into tble1
     select org_id from TABLE( cast (l1_data as rec_Table1_TABLE) );

end P_SBETable_1 ;

10:48:30 OPT1@OP5>exec PKG_SBE515.P_SBETable_1;
BEGIN PKG_SBE515.P_SBETable_1; END;

*
ERROR at line 1:
ORA-00932: inconsistent datatypes
ORA-06512: at "OPT1.PKG_SBE515", line 131
ORA-06512: at line 1

I still can't fetch into the object type. What am i doing wrong??

my object and table types are:

CREATE OR REPLACE TYPE rec_Table1 IS OBJECT (    Org_ID         number(38),
                        ST_Count     number(38));
/

CREATE OR REPLACE TYPE rec_Table1_TABLE IS TABLE OF rec_Table1;
/

I couldn't use INDEX BY BINARY_INTEGER plsql table as it wouldn't compile. What
is the best way to get my cursor resultset into my oracle object type table??
I've also played around with using different datatypes in the object eg int.

Nothing works though, what am i doing wrong??

Seeing as i cannot BULK COLLECT anymore then am i using the best method for
this? would passing arrays of objects instead of refcursors to the calling
procedure be faster??

(By the way when you asked about literals in the data instead of binds were you
referring to the dates 01/Apr/2003 ?? If so i was planning on using variables in
the final procedure!)

thanks again
Followup:
can you give me a

concise
yet 100% complete
small
yet everything there

test case to work with.  You see, the error is reported on line 131, no idea
what line 131 is.  I cannot run your code, I do not have your tables, your
stuff.

set up a small, tiny example using emp and dept or something -- provide
everything one would need to reproduce.

I find that 999 times out of 1,000 in the generation of such a test case -- I
discover whats wrong (and you might too)

this is what I mean -- i took your example and tried to reproduce your issue --
but I cannot.  But see how the test case is sort of "self contained" and line
numbers and all are available:

ops$tkyte@ORA9IR2> CREATE OR REPLACE TYPE rec_Table1 as OBJECT
  2  (    Org_ID         number(38),
  3       ST_Count     number(38)
  4  );
  5  /

Type created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> CREATE OR REPLACE TYPE rec_Table1_TABLE as TABLE OF
rec_Table1;
  2  /

Type created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create table tble1( x int );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace PROCEDURE P
  2  as
  3     type c_return_cursor is ref cursor;
  4     t1      C_Return_Cursor;
  5     l1_data_element rec_Table1;
  6     l1_data rec_Table1_TABLE := rec_table1_table();
  7  BEGIN
  8     open t1 for select rec_table1( 55, 100 ) data from dual;
  9     loop
 10          fetch t1 into l1_data_element;
 11          exit when t1%notfound;
 12          l1_data.extend;
 13          l1_data(l1_data.count) := l1_data_element;
 14     end loop;
 15     close t1;
 16
 17     insert into tble1
 18     select org_id
 19       from TABLE( cast (l1_data as rec_Table1_TABLE) );
 20
 21  end P;
 22  /

Procedure created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec p

PL/SQL procedure successfully completed.

Now, a comment on your code -- it cannot run.

       l1_data(i) := l1_data_element;

that would fail with

ERROR at line 1:
ORA-06531: Reference to uninitialized collection
ORA-06512: at "OPS$TKYTE.P", line 13

Your loop:

    loop

        i := i + 1;

            fetch t1 into l1_data_element;
        l1_data(i) := l1_data_element;

           exit when t1%notfound;
      end loop;

if it worked would end up putting the last row in the table TWICE.  the exit
needs to be right after the fetch else you'll move that last row in there two
times.

"i" as a variable should not be used, you'll be using .extend and l1_data.count
to see how big your array is, no need for "i"

so, without a FULL, self contained test case from you -- something simple I can
run -- we won't get anywhere.
Rating: 4
sorry! February 12, 2004
Reviewer: dwl from uk
Here is the test case you require (this should compile)

drop type rec_test ;
drop type rec_test_TABLE ;

CREATE OR REPLACE TYPE rec_test IS OBJECT (    empno         number(4),
                        mgr         number(4));
/

CREATE OR REPLACE TYPE rec_test_TABLE IS TABLE OF rec_test;
/

CREATE OR REPLACE PACKAGE package1 AS

    TYPE C_Return_Cursor IS REF CURSOR;

    PROCEDURE proc1 (    P_Cur IN OUT C_Return_Cursor        );
    PROCEDURE proc2;

END package1;
/

CREATE OR REPLACE PACKAGE BODY package1 AS

PROCEDURE proc1 (    P_Cur IN OUT C_Return_Cursor        )
IS

BEGIN

    OPEN P_Cur FOR

    'SELECT empno,
        mgr
    from emp';

END proc1;

PROCEDURE proc2

IS

l1_data_element    rec_test;
l1_data     rec_test_Table;
t1         C_Return_Cursor;

BEGIN

    proc1 (    t1);

--    open t1 for select rec_test( empno, mgr ) data from dual;

        loop
                fetch t1 into l1_data_element;
            exit when t1%notfound;
        l1_data.extend;
        l1_data(l1_data.count) := l1_data_element;
    end loop;
      close t1;

--       insert into tble1
--          select * from TABLE( cast (l1_data as rec_test_TABLE) );

end proc2;

END package1;
/

exec package1.proc2;

BEGIN package1.proc2; END;

*
ERROR at line 1:
ORA-00932: inconsistent datatypes
ORA-06512: at "OPT1.PACKAGE1", line 41
ORA-06512: at line 1

That is what i get.
The line it doesn't like is

                fetch t1 into l1_data_element;

And i can kinda see why, it is trying to fetch the results of the cursor into my
object type which doesn't get "cast" directly as the table record??

So how can i do this correctly. Your example shows it working when the cursor is
opened in the same procedure and using the data element to select from dual
which works, but i specifically want to use the cursor returned from another
procedure.

I am still questioning with this is the best thing to do anyway. I mean maybe i
should populate an array in proc1 using bulk collect and then pass that back to
proc2 instead of the refcursor, would this be more efficient??

Maybe it would help if i gave you some more context.
Basically once i have the resultset in proc 2 with the data in an array then i
need to select from that array and process each element into a new larger array.
 I will be doing this lots of times with several other different sized arrays,
all loading into one parent array which will do an insert into the final table.
I will effectively be flattening lots of resultsets into one big flat array. I
guess this can only be done procedurally stepping through each array element in
turn.
I know this is not ideal and i should probably be using nested tables to store
this type of data structure?? But the legacy application expects the data in the
flat table.
How could i select from the nested table to populate a flat table though?? it
would have to iterate through each nested table wouldn't it?

Hope this helps see what i am trying to do!

Many thanks
Followup:
it is easy to see now.  I was fetching an object type from dual:

 8     open t1 for select rec_table1( 55, 100 ) data from dual;

You are not:

    OPEN P_Cur FOR

    'SELECT empno,
        mgr
    from emp';

you are fetching SCALARS, not object types.  You have an obvious type mismatch
here.

You are selecting two columns -- you are fetching one.

anyway, you can code this:

 13  PROCEDURE proc2
 14  IS
 15  l1_data_element    rec_test;
 16  l1_data            rec_test_Table := rec_test_table( null, null );
 17  t1                 C_Return_Cursor;
 18  BEGIN
 19      proc1 (    t1);
 20
 21      loop
 22         fetch t1 into  l1_data_element.empno, l1_data_element.mgr;
 23         exit when t1%notfound;
 24         l1_data.extend;
 25         l1_data(l1_data.count) := l1_data_element;
 26      end loop;
 27      close t1;
 28
 29  end proc2;

and now you are fetching scalars into scalars -- and the scalars you fetch into
are just attributes of the UDT.
Rating: 4
What about forms or reports as front end February 12, 2004
Reviewer: eric givler from harrisburg, pa
Suppose you had a form and you wanted to base a block on a stored procedure.
The options are to base it on table, a view, a from-clause query, and then a
stored procedure that either returns a refcursor or a pl/sql table.  I would
think if you wanted to REMOVE all the front-end logic for building the where
clause on the client, you could simply pass the prms to the package which would
return a ref cursor.  Either that, or base it on a view.

If it is based on a view or a stored proc and the join is mucked up, you could
avoid opening the front end and just fix the query.  Also, you could interrogate
the function and examine the resultset in sql*plus.  Also, views aren't always
the easiest to query against - based on the inputs, I could open up different
cursors that perform the best for what was requested.

I can't say:
select *
from (view with 8 way-join unioned to another 8 way join)
where employee_id in (select employee_id from persons where
phone_num=:user_phone)

However, I could send the phone to a stored procedure and write a query that
performs more efficiently than the previous note.  (this example is somewhat
contrived, but we do have forms with blocks based on views this complex)

I would think where you have 3 reports that all use similar queries, that you
could consolidate them more easily with a report based on a package returning a
refcursor, than building the report on the client-side static sql interspersed
with lexicals to modify the query for the report conditions, right?

That is, it would seem prudent to shy away from hardcoding all these queries in
the front-end, in that if they were all defined in a few re-usable components,
then you wouldn't keep seeing the same code cut&paste everywhere, and could just
re-use these "resultsets" and change your underlying structures as needed with
no front-end change at all.
Followup:
"prms"?  what is "prms"

"join is mucked up".  how could a join be "mucked up"

why cannot you say "select * from ( view with .... ) where employee_id in (
...)"?
if it were simple joins, sure you could.

It depends on what toolset you are using as to which approach you would most
likely take.  A 4gl like forms and reports -- you'll find it "hard not to use
sql", "easiest to use sql".  A 3gl like java, vb, c -- you'll find it "easiest
to use plsql", "harder to do straight sql" (not just from a programming
perspective but from tuning and such as well)
Rating: 5
Thanks it works now! February 13, 2004
Reviewer: dwl from uk
Tom

Thanks very much i have it working now.

Two more things though:

1) To get it to compile i had to initialise the scalartype and the arraytype.

This worked except that the collection has initially got 2 empty null rows in
it. As you can see from the results below:

My new proc2 is:

PROCEDURE proc2

IS

l1_data_element        rec_test;
l1_data             rec_test_Table := rec_test_TABLE(null, null);
t1                 C_Return_Cursor;

BEGIN

--    l1_data.delete;
    proc1 (    t1);

   l1_data_element := rec_test(null, null);

        loop
            fetch t1 into l1_data_element.empno, l1_data_element.mgr;
            exit when t1%notfound;

           dbms_output.put_line('l1_data.count = '||l1_data.count);

          l1_data.extend;
          l1_data(l1_data.count) := l1_data_element;
        end loop;
        close t1;

--       insert into tble1
--        select * from TABLE( cast (l1_data as rec_test_TABLE) );

end proc2;

11:47:30 OPT1@OP5>exec package1.proc2;
l1_data.count = 2
l1_data.count = 3
l1_data.count = 4
l1_data.count = 5
l1_data.count = 6
l1_data.count = 7
l1_data.count = 8
l1_data.count = 9
l1_data.count = 10
l1_data.count = 11
l1_data.count = 12
l1_data.count = 13
l1_data.count = 14
l1_data.count = 15

What i have to do is uncomment the line

    l1_data.delete;

To remove the initial rows, then it works ok:

11:47:58 OPT1@OP5>exec package1.proc2;
l1_data.count = 0
l1_data.count = 1
l1_data.count = 2
l1_data.count = 3
l1_data.count = 4
l1_data.count = 5
l1_data.count = 6
l1_data.count = 7
l1_data.count = 8
l1_data.count = 9
l1_data.count = 10
l1_data.count = 11
l1_data.count = 12
l1_data.count = 13

Why is this? Is it to do with initialising the objects to null first?

2) Now i have this working i will write the whole procedure like this. I will
also write it without using refcursors but build arrays or objects to pass
between procedures instead. Then see which is quicker. Which one do you think
will perform better?
Followup:
1) you initialized wrong.

l1_data_element        rec_test := rec_test();

thats all the initialization you need.

2) what would be fastest and most correct?  not your approach.  it would be:

  insert into tbl1
  select ......;

period, NO procedural code whatsoever.  the routine that is returning a ref
cursor, should not.  it should just execute the insert into statement.
Rating: 3
strange February 13, 2004
Reviewer: dwl from uk
This is not exactly what i have found:

First if i only initialise the l1_data_element i get:

15:34:12 OPT1@OP5>drop type rec_test_TABLE ;

Type dropped.

Elapsed: 00:00:00.00
15:34:26 OPT1@OP5>drop type rec_test ;

Type dropped.

Elapsed: 00:00:00.00
15:34:26 OPT1@OP5>
15:34:26 OPT1@OP5>
15:34:27 OPT1@OP5>CREATE OR REPLACE TYPE rec_test IS OBJECT (    empno
number(4),
15:34:27   2                          mgr         number(4));
15:34:27   3  /

Type created.

Elapsed: 00:00:00.00
15:34:27 OPT1@OP5>
15:34:27 OPT1@OP5>
15:34:27 OPT1@OP5>
15:34:27 OPT1@OP5>CREATE OR REPLACE TYPE rec_test_TABLE IS TABLE OF rec_test;
15:34:27   2  /

Type created.

Elapsed: 00:00:00.00
15:34:27 OPT1@OP5>
15:34:27 OPT1@OP5>
15:34:27 OPT1@OP5>
15:34:27 OPT1@OP5>
15:34:27 OPT1@OP5>CREATE OR REPLACE PACKAGE package1 AS
15:34:27   2
15:34:27   3      TYPE C_Return_Cursor IS REF CURSOR;
15:34:27   4
15:34:27   5      PROCEDURE proc1 (    P_Cur IN OUT C_Return_Cursor        );
15:34:27   6      PROCEDURE proc2;
15:34:27   7
15:34:27   8  END package1;
15:34:27   9  /

Package created.

Elapsed: 00:00:00.00
15:34:27 OPT1@OP5>
15:34:27 OPT1@OP5>
15:34:27 OPT1@OP5>CREATE OR REPLACE PACKAGE BODY package1 AS
15:34:27   2
15:34:27   3
15:34:27   4
15:34:27   5
15:34:27   6  PROCEDURE proc1 (    P_Cur IN OUT C_Return_Cursor        )
15:34:27   7  IS
15:34:27   8
15:34:27   9  BEGIN
15:34:27  10
15:34:27  11
15:34:27  12      OPEN P_Cur FOR
15:34:27  13
15:34:27  14      'SELECT empno,
15:34:27  15          mgr
15:34:28  16      from emp';
15:34:28  17
15:34:28  18  END proc1;
15:34:28  19
15:34:28  20
15:34:28  21
15:34:28  22
15:34:28  23  PROCEDURE proc2
15:34:28  24
15:34:28  25  IS
15:34:28  26
15:34:28  27  l1_data_element        rec_test := rec_test(null, null);
15:34:28  28  --l1_data_element     rec_test;
15:34:28  29  --l1_data       rec_test_Table := rec_test_TABLE(null, null);
15:34:28  30  l1_data       rec_test_Table;
15:34:28  31  t1           C_Return_Cursor;
15:34:28  32
15:34:28  33  BEGIN
15:34:28  34
15:34:28  35  --    l1_data.delete;
15:34:28  36      proc1 (    t1);
15:34:28  37
15:34:28  38  --   l1_data_element := rec_test(null, null);
15:34:28  39
15:34:28  40          loop
15:34:28  41              fetch t1 into l1_data_element.empno,
l1_data_element.mgr;
15:34:28  42              exit when t1%notfound;
15:34:28  43
15:34:28  44             dbms_output.put_line('l1_data.count =
'||l1_data.count);
15:34:28  45
15:34:28  46            l1_data.extend;
15:34:28  47            l1_data(l1_data.count) := l1_data_element;
15:34:28  48          end loop;
15:34:28  49          close t1;
15:34:28  50
15:34:28  51
15:34:28  52  --       insert into tble1
15:34:28  53  --        select * from TABLE( cast (l1_data as rec_test_TABLE) );
15:34:28  54
15:34:28  55
15:34:28  56
15:34:28  57  end proc2;
15:34:28  58
15:34:28  59
15:34:29  60  END package1;
15:34:29  61  /

Package body created.

Elapsed: 00:00:00.01
15:34:29 OPT1@OP5>
15:34:29 OPT1@OP5>exec package1.proc2;
BEGIN package1.proc2; END;

*
ERROR at line 1:
ORA-06531: Reference to uninitialized collection
ORA-06512: at "OPT1.PACKAGE1", line 44
ORA-06512: at line 1

So this time i initialise the l1_data too:

Elapsed: 00:00:00.01
15:34:29 OPT1@OP5>drop type rec_test_TABLE ;

Type dropped.

Elapsed: 00:00:00.00
15:34:55 OPT1@OP5>drop type rec_test ;

Type dropped.

Elapsed: 00:00:00.00
15:34:55 OPT1@OP5>
15:34:56 OPT1@OP5>
15:34:56 OPT1@OP5>CREATE OR REPLACE TYPE rec_test IS OBJECT (    empno
number(4),
15:34:56   2                          mgr         number(4));
15:34:56   3  /

Type created.

Elapsed: 00:00:00.00
15:34:56 OPT1@OP5>
15:34:56 OPT1@OP5>
15:34:56 OPT1@OP5>
15:34:56 OPT1@OP5>CREATE OR REPLACE TYPE rec_test_TABLE IS TABLE OF rec_test;
15:34:56   2  /

Type created.

Elapsed: 00:00:00.00
15:34:56 OPT1@OP5>
15:34:56 OPT1@OP5>
15:34:56 OPT1@OP5>
15:34:56 OPT1@OP5>
15:34:56 OPT1@OP5>CREATE OR REPLACE PACKAGE package1 AS
15:34:56   2
15:34:56   3      TYPE C_Return_Cursor IS REF CURSOR;
15:34:56   4
15:34:56   5      PROCEDURE proc1 (    P_Cur IN OUT C_Return_Cursor        );
15:34:56   6      PROCEDURE proc2;
15:34:56   7
15:34:56   8  END package1;
15:34:56   9  /

Package created.

Elapsed: 00:00:00.00
15:34:56 OPT1@OP5>
15:34:56 OPT1@OP5>
15:34:56 OPT1@OP5>CREATE OR REPLACE PACKAGE BODY package1 AS
15:34:56   2
15:34:56   3
15:34:56   4
15:34:56   5
15:34:56   6  PROCEDURE proc1 (    P_Cur IN OUT C_Return_Cursor        )
15:34:56   7  IS
15:34:56   8
15:34:56   9  BEGIN
15:34:56  10
15:34:56  11
15:34:56  12      OPEN P_Cur FOR
15:34:56  13
15:34:56  14      'SELECT empno,
15:34:57  15          mgr
15:34:57  16      from emp';
15:34:57  17
15:34:57  18  END proc1;
15:34:57  19
15:34:57  20
15:34:57  21
15:34:57  22
15:34:57  23  PROCEDURE proc2
15:34:57  24
15:34:57  25  IS
15:34:57  26
15:34:57  27  l1_data_element        rec_test := rec_test(null, null);
15:34:57  28  --l1_data_element     rec_test;
15:34:57  29  l1_data       rec_test_Table := rec_test_TABLE(null, null);  <--
INITIALISE THIS ASWELL THIS TIME
15:34:57  30  --l1_data       rec_test_Table;
15:34:57  31  t1           C_Return_Cursor;
15:34:57  32
15:34:57  33  BEGIN
15:34:57  34
15:34:57  35  --    l1_data.delete;
15:34:57  36      proc1 (    t1);
15:34:57  37
15:34:57  38  --   l1_data_element := rec_test(null, null);
15:34:57  39
15:34:57  40          loop
15:34:57  41              fetch t1 into l1_data_element.empno,
l1_data_element.mgr;
15:34:57  42              exit when t1%notfound;
15:34:57  43
15:34:57  44             dbms_output.put_line('l1_data.count =
'||l1_data.count);
15:34:57  45
15:34:57  46            l1_data.extend;
15:34:57  47            l1_data(l1_data.count) := l1_data_element;
15:34:57  48          end loop;
15:34:57  49          close t1;
15:34:57  50
15:34:57  51
15:34:57  52  --       insert into tble1
15:34:57  53  --        select * from TABLE( cast (l1_data as rec_test_TABLE) );
15:34:57  54
15:34:57  55
15:34:57  56
15:34:58  57  end proc2;
15:34:58  58
15:34:58  59
15:34:58  60  END package1;
15:34:58  61  /

Package body created.

Elapsed: 00:00:00.01
15:34:58 OPT1@OP5>
15:34:58 OPT1@OP5>exec package1.proc2;
l1_data.count = 2
l1_data.count = 3
l1_data.count = 4
l1_data.count = 5
l1_data.count = 6
l1_data.count = 7
l1_data.count = 8
l1_data.count = 9
l1_data.count = 10
l1_data.count = 11
l1_data.count = 12
l1_data.count = 13
l1_data.count = 14
l1_data.count = 15

PL/SQL procedure successfully completed.

ie it starts at element number 2.
It was necessary for me to use

l1_data_element := rec_test(null, null);

instead of just

l1_data_element := rec_test();

otherwise i get this:

Elapsed: 00:00:00.02
15:34:58 OPT1@OP5>drop type rec_test_TABLE ;

Type dropped.

Elapsed: 00:00:00.00
15:35:34 OPT1@OP5>drop type rec_test ;

Type dropped.

Elapsed: 00:00:00.00
15:35:34 OPT1@OP5>
15:35:34 OPT1@OP5>
15:35:34 OPT1@OP5>CREATE OR REPLACE TYPE rec_test IS OBJECT (    empno
number(4),
15:35:34   2                          mgr         number(4));
15:35:34   3  /

Type created.

Elapsed: 00:00:00.00
15:35:35 OPT1@OP5>
15:35:35 OPT1@OP5>
15:35:35 OPT1@OP5>
15:35:35 OPT1@OP5>CREATE OR REPLACE TYPE rec_test_TABLE IS TABLE OF rec_test;
15:35:35   2  /

Type created.

Elapsed: 00:00:00.00
15:35:35 OPT1@OP5>
15:35:35 OPT1@OP5>
15:35:35 OPT1@OP5>
15:35:35 OPT1@OP5>
15:35:35 OPT1@OP5>CREATE OR REPLACE PACKAGE package1 AS
15:35:35   2
15:35:35   3      TYPE C_Return_Cursor IS REF CURSOR;
15:35:35   4
15:35:35   5      PROCEDURE proc1 (    P_Cur IN OUT C_Return_Cursor        );
15:35:35   6      PROCEDURE proc2;
15:35:35   7
15:35:35   8  END package1;
15:35:35   9  /

Package created.

Elapsed: 00:00:00.00
15:35:35 OPT1@OP5>
15:35:35 OPT1@OP5>
15:35:35 OPT1@OP5>CREATE OR REPLACE PACKAGE BODY package1 AS
15:35:35   2
15:35:35   3
15:35:35   4
15:35:35   5
15:35:35   6  PROCEDURE proc1 (    P_Cur IN OUT C_Return_Cursor        )
15:35:35   7  IS
15:35:35   8
15:35:35   9  BEGIN
15:35:35  10
15:35:35  11
15:35:35  12      OPEN P_Cur FOR
15:35:35  13
15:35:35  14      'SELECT empno,
15:35:35  15          mgr
15:35:35  16      from emp';
15:35:35  17
15:35:35  18  END proc1;
15:35:35  19
15:35:35  20
15:35:35  21
15:35:35  22
15:35:35  23  PROCEDURE proc2
15:35:35  24
15:35:35  25  IS
15:35:35  26
15:35:36  27  --l1_data_element        rec_test := rec_test(null, null);
15:35:36  28  l1_data_element     rec_test;
15:35:36  29  l1_data       rec_test_Table := rec_test_TABLE(null, null);
15:35:36  30  --l1_data       rec_test_Table;
15:35:36  31  t1           C_Return_Cursor;
15:35:36  32
15:35:36  33  BEGIN
15:35:36  34
15:35:36  35  --    l1_data.delete;
15:35:36  36      proc1 (    t1);
15:35:36  37
15:35:36  38  --   l1_data_element := rec_test(null, null);
15:35:36  39
15:35:36  40          loop
15:35:36  41              fetch t1 into l1_data_element.empno,
l1_data_element.mgr;
15:35:36  42              exit when t1%notfound;
15:35:36  43
15:35:36  44             dbms_output.put_line('l1_data.count =
'||l1_data.count);
15:35:36  45
15:35:36  46            l1_data.extend;
15:35:36  47            l1_data(l1_data.count) := l1_data_element;
15:35:36  48          end loop;
15:35:36  49          close t1;
15:35:36  50
15:35:36  51
15:35:36  52  --       insert into tble1
15:35:36  53  --        select * from TABLE( cast (l1_data as rec_test_TABLE) );
15:35:36  54
15:35:36  55
15:35:36  56
15:35:36  57  end proc2;
15:35:36  58
15:35:36  59
15:35:36  60  END package1;
15:35:36  61  /

Package body created.

Elapsed: 00:00:00.01
15:35:37 OPT1@OP5>
15:35:37 OPT1@OP5>exec package1.proc2;
BEGIN package1.proc2; END;

*
ERROR at line 1:
ORA-06530: Reference to uninitialized composite
ORA-06512: at "OPT1.PACKAGE1", line 41
ORA-06512: at line 1

Elapsed: 00:00:00.01
15:35:37 OPT1@OP5>

Can you recreate this behaviour?
Why can i only get it to compile when i initialise both the scalar and the
array, and then when it does compile i get element 2 as the first array preceded
by null arrays??
Followup:
just do this please (it is what i stated above)

 11   PROCEDURE proc2
 12   IS
 13
 14      l1_data_element rec_test := rec_test(null, null);
 15      l1_data         rec_test_Table := rec_test_table();
 16      t1              C_Return_Cursor;
 17   BEGIN
 18
 19       proc1 (    t1);
 20       loop
 21          fetch t1 into l1_data_element.empno, l1_data_element.mgr;
 22          exit when t1%notfound;
 23
 24          dbms_output.put_line('l1_data.count = '||l1_data.count);
 25
 26          l1_data.extend;
 27          l1_data(l1_data.count) := l1_data_element;
 28       end loop;
 29       close t1;
 30   end proc2;
 31  END;
 32  /

Package body created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec package1.proc2
l1_data.count = 0
l1_data.count = 1
l1_data.count = 2
l1_data.count = 3
l1_data.count = 4
l1_data.count = 5
l1_data.count = 6
l1_data.count = 7
l1_data.count = 8
l1_data.count = 9
l1_data.count = 10
l1_data.count = 11
l1_data.count = 12
l1_data.count = 13

PL/SQL procedure successfully completed.

You want an empty object:

 14      l1_data_element rec_test := rec_test(null, null);

and an empty array:

 15      l1_data       rec_test_Table := rec_test_table();

that is all.
Rating: 4
follow-up to your comments February 18, 2004
Reviewer: eric givler from harrisburg, pa
prms = parameters.  All that other typing, and I didn't type parameters.

"Mucked up" means that the join was not written correctly for the view.  For
example, in our system, a person can have one or more addresses of an address
type.  Each address is stored in an addresses table for a person along with a
date of effectivity.  When an address is changed, the stop_date is now filled
in, and a new entry is created with a NULL stop_date column.  If the join to
this table does not reference stop_Date is null, then you will see duplicate
entries on screens based on this invalid join.  Additionally, suppose the person
has NO address at all. In this case, the join would be an outer join, but maybe
it was miscoded as:
persons.person_eid=addresses.person_eid(+)
and 'HOME' = ADDRESSES.ADDRESS_TYPE_CODE
AND ADDRESSES.STOP_DATE IS NULL

In this case, if no home address is on file, or there is one but it has a stop
date, the person cannot be retrieved properly.

Maybe this is more of a design issue but it's nice for me to simply say in plus:

variable x refcursor
exec :x := pkg.refcur(prm1,prm2,prm3);
print x

Then, I examine the results.  If they are ok, I base the report or forms block
on these.  Then, if I eventually drop forms from the front end, I have all the
query logic self-contained in the backend as opposed to trying to dig it back
out of the front end.

I would think that there are only a limited number of ways in our application
that data is queried, but when I look at the application it is riddled with
hundreds and hundreds of sql statements pasted in every form and report.  It
makes maintenance a nightmare.

How do you tackle something like this?  I've tried to move all hardcoded sql out
of the front end,making packages and functions for everything (as much as
possible).  SAme with business logic.  Blocks based on resultsets (refcursors)
seemed like the natural thing to do.  Did I go the wrong route?
Followup:
by mucked up you mean "we have a bug in the developed code".

I would (do) use views.

You can use refcursors -- but didn't you find it "awkward" in some respects?
Rating: 3
ref cursor + db link February 23, 2004
Reviewer: A reader
How can i Or Can I ? Pass the ref cursor over a dblink ?
Followup:
you cannot, documented restriction.
Rating: 3
Please have a look March 17, 2004
Reviewer: Ram from Bangalore,India
Dear Sir,
Please have a look at the following code
1)
  SQL> create or replace procedure p(x in emp.ename%type,y out emp%rowtype) as
  2   c emp%rowtype;
  3  begin
  4   select * into c from emp where ename = x;
  5   y := c;
  6  end;
  7  /

Procedure created.

SQL> VAR A REFCURSOR
SQL> SET AUTOP ON
SQL> exec p('WARD',:A)<< --- Can't a refcursor be used here

BEGIN p('WARD',:A); END;

      *
ERROR at line 1:
ORA-06550: line 1, column 7:
PLS-00306: wrong number or types of arguments in call to 'P'
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

ERROR:
ORA-24338: statement handle not executed

SP2-0625: Error printing variable "a"

2)

SQL> create synonym op for dbms_output
  2  /

Synonym created.

SQL> exec op.put_line('Hello')<<-- Is it not possible to
                             <<create a syn. for
                              <<dbms_output?

BEGIN op.put_line('Hello'); END;

      *
ERROR at line 1:
ORA-06550: line 1, column 7:
PLS-00201: identifier 'OP.PUT_LINE' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

Sorry for disturbing you.May I seek your prompt reply?
Thanks in advance.
Bye!
Followup:
a ref cursor is not a record, emp%rowtype is a record, so no, a ref cursor
cannot be used as you have it coded.

Now, if you code:

create or replace procedure p(x in emp.ename%type,y out sys_refcursor) as
begin
  open y for select * into c from emp where ename = x;
end;
/

it'll work great.

In the second case, you created a synonym to a synonym -- not good.  create the
synynom to the "real thing"

ops$tkyte@ORA9IR2> create or replace synonym op for SYS.dbms_output;

Synonym created.

ops$tkyte@ORA9IR2> exec op.put_line('hello');
hello

PL/SQL procedure successfully completed.
March 17, 2004 Mohamad -- Thanks for the question regarding "Returning a clob into a variable for update Using Dynamic SQL", version 8.1.7
You Asked
hi
i am using dynamic SQL to update a clob Feild in a table. And as i need to call
this procedure many times. i need to write it using dynamic SQL. The procedure
as it is in static sql gives no error. But when i am using the dynamic SQL it
stops when it reaches the returning statement.

The static SQL :

PROCEDURE "INSERLONGROW" (corps1 IN varchar2, newsID in Number) IS
 l_clob clob;
BEGIN
  UPDATE news
        SET  corpsF_news = empty_clob()
    WHERE  id_news = newsID
    returning corpsF_news into l_clob;

  dbms_lob.writeappend( l_clob, length(corps1), corps1 );
  COMMIT;
END;

The Dynamic SQL:

PROCEDURE "UPDATELONGROW"(NomTable IN varchar2,Nomchamp IN varchar2,NomID IN
varchar2,ValChamp IN varchar2, ValID in Number) IS
 update_st varchar2(100);
 set_st varchar2(100);
 where_st varchar2(100);
 return_st varchar2(100);
 sql_ST VARCHAR2(200);
 l_clob clob;
 len number;
 v_cursor number;
 v_num number;

BEGIN
  update_st := ' UPDATE ' || NomTable ;
  set_st := ' SET ' || Nomchamp || ' = empty_clob() ';
  where_st := ' WHERE ' || NomID ||' = ' ||ValID;
  return_st := ' returning ' || Nomchamp || ' into l_clob';
  sql_st := update_st || set_st || where_st || return_st;

  v_cursor := dbms_sql.open_cursor;
  dbms_sql.parse(v_cursor, sql_st, dbms_sql.native);
  v_num := dbms_sql.execute(v_cursor);
  dbms_sql.close_cursor(v_cursor);
  len:= length(ValChamp);
  dbms_lob.writeappend( l_clob, len , ValChamp );
   COMMIT;
END;

Thank you for your support
and we said...
Using Native Dynamic SQL this can be accomplished:

ops$tkyte@ORA817.US.ORACLE.COM> create table t
  2  ( x int primary key, y clob )
  3  /

Table created.

ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> insert into t values ( 1, null );

1 row created.

ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> create or replace
  2  procedure update_clob( p_tname       in varchar2,
  3                         p_pkname   in varchar2,
  4                         p_pkval    in int,
  5                         p_clobName in varchar2,
  6                         p_clobVal  in varchar2 )
  7  is
  8      l_clob clob;
  9  begin
 10      execute immediate
 11      'begin
 12           update ' || p_tname || '
 13              set ' || p_clobName || ' = empty_clob()
 14            where ' || p_pkname || ' = :x
 15        returning ' || p_clobName || ' into :y;
 16       end;'
 17      USING IN p_pkVal, OUT l_clob;
 18
 19      dbms_lob.writeappend( l_clob, length(p_clobVal), p_clobVal );
 20  end;
 21  /

Procedure created.

ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> exec update_clob( 't', 'x', 1, 'y', 'Hello
World' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817.US.ORACLE.COM> select * from t;

         X Y
----------
--------------------------------------------------------------------------------
         1 Hello World

Make sure to use BIND VARIABLES where ever possible!!!
Review & Followup

Rating: 5
June 22, 2001
Reviewer: A reader from france
Rating: 4
can a clob variable be used in 'EXECUTE IMMEDIATE statement? January 30, 2002
Reviewer: mins from nj usa
Can a clob variable be used in the dynamic sql(because the
sql statment is greater than 40000 characters)
Followup:
No (a sql statement that is 40,000 characters -- I would be seriously rethinking
that one).

You'll need to use dbms_sql.  there is a parse routine that will do this for
you:

procedure parse(c in integer, statement in varchar2s,
                  lb in integer, ub in integer,
                  lfflg in boolean, language_flag in integer);
  --  Parse the given statement in the given cursor.  The statement is not in
  --  one piece but resides in little pieces in the PL/SQL table "statement".
  --  Conceptually what happens is that the SQL string is put together as
  --  follows:
  --  String := statement(lb) || statement(lb + 1) || ... || statement(ub);
  --  Then a regular parse follows.
  --  If "lfflg" is TRUE then a newline is inserted after each piece.
  --  For further information and for documentation on the rest of the
  --  arguments see the regular parse procedure below.
Rating: 4
the varchar2s ... January 31, 2002
Reviewer: mins from nj usa
where could I find an example of this ...
how to pass the the sql to the statement??

procedure parse(c in integer, statement in varchar2s,
                  lb in integer, ub in integer,
                  lfflg in boolean, language_flag in integer);

Could you point me to a site where I could look at an exmaple.

Thanks
Followup:
declare
    l_stmt          dbms_sql.varchar2s;
    l_cursor        integer default dbms_sql.open_cursor;
    l_rows          number  default 0;
begin
    l_stmt(1) := 'insert';
    l_stmt(2) := 'into foo';
    l_stmt(3) := 'values';
    l_stmt(4) := '( 1 )';

    dbms_sql.parse( c             =>   l_cursor,
                    statement     => l_stmt,
                    lb            => l_stmt.first,
                    ub            => l_stmt.last,
                    lfflg         => TRUE,
                    language_flag => dbms_sql.native );

    l_rows := dbms_sql.execute(l_cursor);

    dbms_sql.close_cursor( l_cursor );
end;
/
Rating: 4
Using Native Dynamic SQL this can be accomplished June 04, 2003
Reviewer: Andri Kurniawan from bandung, Indonesia
helpful example on dbms_sql.parse
Rating: 4
DBMS_SQL & DBMS_LOB ? February 26, 2004
Reviewer: Vinnie from ORlando
Tom,

Can I use DBMS_SQL to get the length of a BLOB?

ex.
query = 'SELECT dbms_lob.getlength(event) FROM test'

dbms_sql.parse (l_cursor, query, dbms_sql.native);
dbms_sql.describe_columns(l_cusor, l_colCnt, l_descTbl);

I get the following after the dbms_sql.describe_columns:
dbms_sql.describe_columns overflow, col_Name_len=38. Use describe_columns2

?
Followup:
SELECT dbms_lob.getlength(event) event_len FROM test
                                 ^^^^^^^^^

or, use describe_columns2 as it says :)
Rating: 5
update CLOBs March 17, 2004
Reviewer: A reader
Hi

We have some plain text files stored as CLOB inside database. Until now whenever
we modify these files using notepad what we do is truncate the table and reload
the data to the table... I was wondering if we can do this using plain UPDATEs?
The example you provided pass string to the procedure, how can I pass the text
file to this procedure?
Followup:

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96591/toc.htm
describes how to program using clobs/blobs.

truncating a table just to update is sort of drastic, yes, you can just update
the data.
March 17, 2004 Vinh -- Thanks for the question regarding "A question", version 8.0.4
You Asked
Hi Tom,
I want you give me an advice for a following problem:
- We intend developing a system for query words or phrases that
have same meaning. For example: When i submit a query criterion
as "advice", the system can find out some words have same
meaning like " advice, recommendation, consultation...". Of
course, these words are in Vietnamese, not English.
- Now, i don't know whethe Oracle supports these feature. Please
tell me some information about the problem.
Thanks a lot.
Vinh
and we said...
This sounds like a job for interMedia text (aka "context").

This will do a keyword full text index that allows for
searching.  As well, you can setup a thesaurus so that a search
for advice will find all of those words above as well.  The
searching works with all languages.

Also, in addition to using a thesaurus for synonym-searching (as
the you requested), you can also exploit a thesaurus to search
for related search terms, broader search terms, or even narrower
search terms.

Note that interMedia Text comes with a sample thesaurus in
English only.

See
http://download-east.oracle.com/docs/cd/A81042_01/DOC/inter.816/index.htm
for more details.
Review & Followup

Rating: 4
help me in providing a better approach March 17, 2004
Reviewer: A reader
Tom,
I need your advice on this. we are planning to have a table with 2 columns. Col1
beign the file location and Col2 being the usernames who have access to this
files as comma separated. Similliar to one i have showm below

c:/test/test.txt     nhtg2wt,hntg33,mkehtm2,k3jk31

For a single file, there are chances there might be 500 to 1000 users. And there
are the around 300,000 files.

Currently what i have in my thought is ..We need to search on, pick up, files
that are being used by the username- for example.

select filename from table where userid like '%"nhtg2wt"%';

Hope this will have a performance impact on 300,000 records. Please help me in
providing a better approach and getting better performance.

Thanks as always.
Followup:
you did the data model wrong.

do it over.  do NOT store a comma delimited list.

do store

filename         username
----------       -----------
c:\test\test.txt bob
c:\test\test.txt mary
c:\test\test.txt george
.......
Rating: 5
I agree .... March 17, 2004
Reviewer: A reader
Tom,

your approach here is simlpe and best ... but when i have 500 users for 1 file
and for 300,000 files i will have so many records...is that ok?

and one more , i tried using intermedia text search and it was faster ..faster
by 60% ....If i had to go with the design of comma separated values ... Do you
think, this is the best approach...

Thanks for your advice and time
Followup:
yes, it is ok.

if you do it as a normal table with normal indexes, you'll find it yet again
faster then text.  text is good for indexing free form text.  You have
structured data -- store it structured and you'll get the best
performance/maintainability.
March 17, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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.
Rating: 4
Problem when trying to update PO to check Workspace Manager... March 17, 2004
Reviewer: Sven Bleckwedel from Santos, SP - Brasil
Hi Tom,

I Noted some warnings before trying to use the "Workspace Manager", in one test
environment:

>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.

I use an Personal Oracle 8i Release 3 (downloaded from OTN, for testing
pourposes, like this one) in one specific test environment, as showed below:

SQL*Plus: Release 8.1.7.0.0 - Production on Qua Mar 17 09:52:16 2004

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

SQL> connect system
Enter password: *******
Connected.
SQL> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle8i Personal 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

I downloaded in the past (when having access to Metalink), the patchset
"p2376472_8174_WINNT.zip".  The readme showed me that was possible to use it
with Personal release of Oracle, for Windows NT or 2000.  But couldn´t find any
specific reference to Personal edition for Windows 98.  Is there any possibility
to apply this patchset in this test environment or exist any workaround to be
possible to update Personal Oracle to test Workspace Manager ?

Tks in Adv,
Sven
Followup:
please contact support for this sort of information.
March 17, 2004 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://download-east.oracle.com/docs/cd/A81042_01/DOC/appdev.816/index.htm
or
http://download-east.oracle.com/docs/cd/F49540_01/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
Rating: 5
Java code to save BLOB to disk February 25, 2004
Reviewer: A reader
Hi Tom, could you please include here the java code to save a blob to a file,
for people who has no idea about java.

like
create aaa ...
end/

run aaa( blob, filename)
:) :) please please if possible
I searched the code but I can't find
Followup:
well, if I were a hard core java programmer :)

anyone out there have a java routine to save a blob to a file (the reverse of
the above code....)
Rating: 3
Oracle JDBC samples February 25, 2004
Reviewer: Alex from Montreal

http://otn.oracle.com/sample_code/tech/java/sqlj_jdbc/files/advanced/LOBSample/LOBSample.java.html
http://otn.oracle.com/sample_code/tech/java/sqlj_jdbc/files/9i_jdbc/NewLOBAPISample/NewLOBAPISample.java.html
http://otn.oracle.com/sample_code/tech/java/sqlj_jdbc/files/advanced/advanced.html
http://otn.oracle.com/sample_code/tech/java/sqlj_jdbc/index.html

Rating: 3
February 25, 2004
Reviewer: Alex from Montreal
Nice collection of code fragments:
http://otn.oracle.com/sample_code/tech/java/sqlj_jdbc/htdocs/templates.htm
(free OTN registration required)
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96591/adl10pst.htm
http://download-west.oracle.com/docs/cd/B10501_01/java.920/a96654/oralob.htm#1043220
Streams can also be used with LONGs and LOBs
http://download-west.oracle.com/docs/cd/B10501_01/java.920/a96654/basic.htm#1009860

Rating: 5
Java/BLOB/Filesystem Copy and Paste Fun February 25, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
Adjust for your environment and line wrapping may need to be undone...

connect / as sysdba;

grant javauserpriv to scott;

begin
  dbms_java.grant_permission('SCOTT',
'java.io.FilePermission','c:\temp\blob.txt', 'write');
end;
/

connect scott/tiger;

create or replace java source named "exportBLOB" as
  import java.lang.*;
  import java.io.*;
  import java.sql.*;
  import oracle.sql.*;
public class exportBLOB
{
  public static void do_export(BLOB p_blob, String p_file) throws Exception
  {
    // create file output stream
    File l_file = new File(p_file);
    FileOutputStream l_out = new FileOutputStream(l_file);

    // get an input stream from the blob
    InputStream l_in = p_blob.getBinaryStream();

    // get buffer size from blob and use this to create buffer for stream
    int l_size = p_blob.getBufferSize();
    byte[] l_buffer = new byte[l_size];
    int l_length = -1;

    // write the blob data to the output stream
    while ((l_length = l_in.read(l_buffer)) != -1)
    {
      l_out.write(l_buffer, 0, l_length);
      l_out.flush();
    }

    // close the streams
    l_in.close();
    l_out.close();
    }
};
/

-- the requisite plsql wrapper
create or replace procedure exportBLOB (p_blob in blob, p_file in varchar2) as
language java name 'exportBLOB.do_export(oracle.sql.BLOB, java.lang.String)';
/

-- a small test of the above
declare
  v_blob blob;
  v_raw  raw(32);
  v_len  number;
begin
  -- create a raw to use in test
  v_raw := utl_raw.cast_to_raw('Oracle!');
  v_len := utl_raw.length(v_raw);

  -- create a temporary lob to test
  dbms_lob.createtemporary(v_blob, true);
  dbms_lob.write(v_blob, v_len, 1, v_raw);

  -- export it
  exportBLOB (v_blob, 'c:\temp\blob.txt');
end;
/
Rating: 5
February 26, 2004
Reviewer: A reader
Thanks a lot
Mark, Alex and Tom
Rating: 5
February 26, 2004
Reviewer: A reader
Mark, if you read again this thread, could you please if possible, to write in
that nice fashion the java procedure to read a archive from disk, because using
utl_file you have to create a directory object for every directory.

:) Thank you again.

P.D. Java don't have utl_file bug replacing 0a with 0a0d in windows 2000
Rating: 5
February 27, 2004
Reviewer: A reader
Hi Tom, I'm trying to do that read java function, but the problem I have is I
must stablish a conection, and the user password can be changed, and there is no
way to know that.
The question is, I tried in plsql to read a archive directly to a blob variable
but I could'nt is there a way to do this?

To read an archive directly to a variable (no table insert) and return it in a
function.

FUNCTION LeeArchivo( cFile VARCHAR2, cDirectory VARCHAR2, bArchivo IN OUT BLOB)
RETURN VARCHAR2 IS
    b_blob     BLOB;
    b_bfile    BFILE;
    cPath      VARCHAR2(150);
    cReturn    VARCHAR2(2000);
begin
    cReturn := 'No existe directorio.'||cDirectory;
    SELECT DIRECTORY_PATH INTO cPath
         FROM DBA_DIRECTORIES WHERE DIRECTORY_NAME = cDirectory;
    b_bfile := bfilename( cDirectory , cFile );
    cReturn := 'Open File, debe darse GRANT READ/WRITE ON DIRECTORY TO DAZ.';
    DBMS_LOB.FILEOPEN( b_bfile );
    cReturn := 'Load File.';
--trying to avoid    INSERT INTO ADM.DEMO VALUES( 1, EMPTY_BLOB()) RETURNING
THEBLOB INTO b_blob;
    DBMS_LOB.LOADFROMFILE( b_blob, b_bfile, DBMS_LOB.GETLENGTH( b_bfile ) );
    cReturn := 'Close File.';
    DBMS_LOB.FILECLOSE( b_bfile );
    bArchivo := b_blob;
    RETURN 'T';
EXCEPTION WHEN OTHERS THEN
 RETURN 'F-'||cReturn || SQLERRM;
END;

Thanks
Followup:
why couldn't you?  using dbms_lob.loadfromfile was designed to let plsql load  a
file on the SERVER into a blob.
Rating: 5
February 27, 2004
Reviewer: A reader
If I try to load directly to the variable bArchivo
    DBMS_LOB.LOADFROMFILE( bArchivo, b_bfile, DBMS_LOB.
GETLENGTH( b_bfile ) );

 I get the following error
"Return Value" = F-Load File.ORA-06502: PL/SQL: error : invalid LOB locator
specified: ORA-22275 numérico o de valor

If I initialize previously the variable
bArchivo :=EMPTY_BLOB();

I get this other
"Return Value" = F-Load File.ORA-22275: se ha especificado un localizador LOB no
válido

Sorry if I'm doing a stupid mistake, but I can't, and I didn't found how to do
it.
Followup:
you'd need a dbms_lob.createTemporary call.

empty_blob() is only for "insert" or "update"
Rating: 5
February 27, 2004
Reviewer: A reader
Thanks Tom I did previously, but I got a
"Return Value" = F-6ORA-06502: PL/SQL: error  numérico o de
valor
from DBMS_LOB.WRITEAPPEND(Lob_loc, Amount, Buffer);
Seems the document (about 100k) was too big for the temporal lob.

FUNCTION LeeArchivo( cFile VARCHAR2, cDirectory VARCHAR2, bArchivo IN OUT BLOB)
RETURN VARCHAR2 IS
    b_blob     BLOB;
    b_bfile    BFILE;
    cPath      VARCHAR2(150);
    cReturn    VARCHAR2(2000);

Lob_loc BLOB;
Buffer RAW(32767);
Src_loc BFILE ;
Amount Binary_integer := 32767;
Position Binary_integer := 128;
BEGIN

    cReturn := '1';
Src_loc := BFILENAME( cDirectory , cFile );
    cReturn := '2';
DBMS_LOB.CREATETEMPORARY(Lob_loc,TRUE);
    cReturn := '3';
DBMS_LOB.OPEN(Lob_loc,DBMS_LOB.LOB_READWRITE);
    cReturn := '4';
DBMS_LOB.OPEN(Src_loc, DBMS_LOB.LOB_READONLY);
    cReturn := '5';
DBMS_LOB.LOADFROMFILE (Lob_loc,Src_loc, Amount);
    cReturn := '6';
DBMS_LOB.WRITEAPPEND(Lob_loc, Amount, Buffer);
    cReturn := '7';
DBMS_LOB.CLOSE(Src_loc);
    cReturn := '8';
DBMS_LOB.CLOSE(Lob_loc);
    cReturn := '9';
bArchivo :=Lob_loc;
    cReturn := '10';
DBMS_LOB.FREETEMPORARY(Lob_loc);
Followup:
it is not "too big", you tried to write an empty buffer there (you asked to
write 32k of 0k of data).

you just need to dbms_lob.loadfromfile the bfile into the temporary lob using a
length of dbms_lob.getlength( bfile )
Rating: 5
:) Thanks Tom solved, documentation example was wrong February 27, 2004
Reviewer: A reader
Sorry rebuild incorrectly the scenarioI forgot, and put as I tested
I tested so (including getlenght)
And got the error, please if you can note the mistake
"Return Value" = F-6-*ORA-06502: PL/SQL: error  numérico o de valor

Now I checked the values and buffer is 0, file is 8799373,
Lob_loc is 8799373
So I tried to deleted
DBMS_LOB.WRITEAPPEND(Lob_loc, DBMS_LOB.GETLENGTH(Buffer), Buffer); (because it
does nothing)
and is working fine, I think this is an error in the example.

FUNCTION LeeArchivo( cFile VARCHAR2, cDirectory VARCHAR2, bArchivo IN OUT BLOB)
RETURN VARCHAR2 IS
    b_blob     BLOB;
    b_bfile    BFILE;
    cPath      VARCHAR2(150);
    cReturn    VARCHAR2(2000);

Lob_loc BLOB;
Buffer RAW(32767);
Src_loc BFILE ;
Amount Binary_integer := 32767;
Position Binary_integer := 128;
BEGIN

    cReturn := '1';
Src_loc := BFILENAME( cDirectory , cFile );
    cReturn := '2';
DBMS_LOB.CREATETEMPORARY(Lob_loc,TRUE);
    cReturn := '3';
DBMS_LOB.OPEN(Lob_loc,DBMS_LOB.LOB_READWRITE);
    cReturn := '4';
DBMS_LOB.OPEN(Src_loc, DBMS_LOB.LOB_READONLY);
    cReturn := '5';
DBMS_LOB.LOADFROMFILE (Lob_loc,Src_loc, DBMS_LOB.GETLENGTH( Src_loc ));
    cReturn := '6-* file:' ||DBMS_LOB.GETLENGTH( Src_loc )||
    ' Buffer:'||nvl(DBMS_LOB.GETLENGTH(Buffer),0);
DBMS_LOB.WRITEAPPEND(Lob_loc, DBMS_LOB.GETLENGTH(Buffer), Buffer);
    cReturn := '7';
DBMS_LOB.CLOSE(Src_loc);
    cReturn := '8';
DBMS_LOB.CLOSE(Lob_loc);
    cReturn := '9';
bArchivo :=Lob_loc;
    cReturn := '10';
DBMS_LOB.FREETEMPORARY(Lob_loc);
Rating: 5
RE: Mark, if you read again this thread February 27, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
Well, I am no Java expert, but this may help you out some.  Again, line wrapping
may need to be undone, etc...

connect sys as sysdba;

grant javauserpriv to scott;

begin
  dbms_java.grant_permission('SCOTT',
'java.io.FilePermission','c:\temp\blob.txt', 'read');
end;
/

connect scott/tiger;

create table blob_test
(
  short_desc varchar2(32),
  the_blob blob,
  constraint blob_test_pk primary key (short_desc)
);

create or replace and compile java source named "importBLOB" as
import java.lang.*;
import java.io.*;
import java.sql.*;
import oracle.sql.*;
import oracle.jdbc.driver.*;

public class importBLOB
{
  public static void do_import(String p_file) throws Exception
  {
    PreparedStatement l_pstmt = null;
    BLOB l_blob = null;

    // get a connection using Oracle's internal server-side jdbc driver
    Connection l_conn = new OracleDriver().defaultConnection();

    // turn off autocommit - required for this example but something I do anyway
    l_conn.setAutoCommit(false);

    // put an 'empty' row in the table
    l_pstmt = l_conn.prepareStatement("insert into blob_test(short_desc,
the_blob) values (?, empty_blob())");

    l_pstmt.setString(1, "TestBlob");

    l_pstmt.execute();

    l_conn.commit();

    // select the 'empty' row from the table for update
    // this requires autocommit to be false
    l_pstmt = l_conn.prepareStatement("select short_desc, the_blob from
blob_test where short_desc = ? for update");

    l_pstmt.setString(1, "TestBlob");

    ResultSet l_rs = l_pstmt.executeQuery();

    // get the blob from the resultset
    if (l_rs.next())
    {
      l_blob = (BLOB) l_rs.getBlob(2);
    }

    // open the file to load into the blob
    File l_inputFile = new File(p_file);
    FileInputStream l_inputStream = new FileInputStream(l_inputFile);

    // get output stream from the blob
    OutputStream l_outputStream = l_blob.getBinaryOutputStream();

    // create a buffer of the appropriate size
    int l_chunkSize = l_blob.getChunkSize();

    byte[] l_buffer = new byte[l_chunkSize];

    // read the file into the blob
    int l_readLength = -1;

    while ((l_readLength = l_inputStream.read(l_buffer)) != -1)
    {
      l_outputStream.write(l_buffer, 0, l_readLength);
    }

    // close the streams
    l_outputStream.close();
    l_inputStream.close();
  }
};
/

create or replace procedure importBLOB (p_file in varchar2) as
language java name 'importBLOB.do_import(java.lang.String)';
/

-- a small test of the above
begin
  importBLOB('c:\temp\blob.txt');
end;
/

HTH,

Mark
Rating: 5
March 01, 2004
Reviewer: A reader
Thanks Mark you are the man.
Rating: 5
autocommit and BLOB with JDBC March 12, 2004
Reviewer: syed from UK
Tom

am I right that we cant effectivly work with BLOBS via JDBC
if autocommit is TRUE ? I think this because I believe you have to do a select
for update prior to working with the lob - is this true ?

Thank you

Syed
Followup:
I do not think you can effectively work with JDBC if autocommit is true period.

But yes, you would have to do everything in a a stored procedure call (not a bad
idea) so that you can select the lob for update, write to it and return (and
then be autocommitted) in a single call.
Rating: 5
... comment on links ... March 12, 2004
Reviewer: Steve Mugiri from Marlboro, MA
Thank you for making this site and yourself available to us as a resource. I
frequently search your site to get infomation that I need and appreciate the
help that you offer.

This is just a comment not directly connected to this page but more to the site
in general: I could not find a general board to post it on.

I am not sure if it just me but I do not have access to some of the links that
you give to Technet documentation or Oracle documentation:

I find that links that you give to pages at the site
http://technet.oracle.com/doc/
.... always come back
404 at the link
http://otn.oracle.com/errors/404.html
.However, links to
http://download-west.oracle.com/docs/
... or
http://otn.oracle.com/
.... are OK.

I frequently find that I can retrieve the documents that you are pointing to by
going to
http://otn.oracle.com/documentation/oracle8i.html
and looking for the corresponding path of the page of the form
http://download.oracle.com/docs/cd/AXXXXX_XX/XX817XX/index.htm
by doing a mouse over on the corresponding link and checking out the name
on the status bar since the final file name remains the same: tedious but worth
the effort.

regards,
Steve
Followup:
I gotta write the 10g regular expression update to fix them all -- maybe that's
what I'll do this weekend, it would be an interesting update (of my entire set
of Q&A's !  arg...)
Rating: 5
thats one way ... but ... March 13, 2004
Reviewer: Steve from Marboro, MA
Thanks Tom, we will really appreciate the fix - the mouse over thing is getting
to be a drag ...

just a thought however.... I think that this will be a patch rather than a fix,
I remember when I used to use 7.3, documentation was at docs.oracle.com or
something like that then it went to technet.oracle.com with v8 and now, it has
moved once again to download.oracle.com with 9

It seems to me that you will always be trying to hit a moving target as long as
someone else is making the decision about where the docs will be..

a suggestion if I may make one without being presumptive: you keep telling us
that disk is cheap ( relative to the cost of having an expert take the time to
fix a problem, in this case, you! )
http://asktom.oracle.com/pls/ask/f?p=4950:8:13816279803321301532::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:12391552754715,
 since the documentation that you are
pointing us to for Oracle 8/9 is static now, why not mirror it all at
asktom.oracle.com ? I think it adds up to app. 1.8GB of html all told. That way,
you can always be sure that the links that you give us are OK
Rating: 4
I do not think you can effectively work with JDBC if autocommit is true period. March 15, 2004
Reviewer: syed
Tom

i'd love to hear your reasons for this. Anything that will help me in my
struggle with the Java people would be appreciated !!

Syed
Followup:
ask yourself

"are all of my transactions so simplistic that they take but one statement?"

do your developers even know that if they:

prepare( update accounts set bal = bal + ? where account = ? );

bind 55, 100
execute
bind -55, 101
execute

(to transfer 55$ from account 100 to 101) and if fails between the two executes
-- there is not much they can do, the database has bad data.

Do the developers even understand what a "transaction" is?  They are typically
never so trivial as to be a single statement (unless of course they have been
done properly and the transaction is really:

   begin
       invoke_tranaction_foobar( ?, ?, ? .... );
   end;

:)
Rating: 5
Thanks a lot !! March 15, 2004
Reviewer: Syed
Rating: 5
Fixed? March 17, 2004
Reviewer: Jeff from Atlanta
Looks like you did fix all the doc links. Thanks!!!
Followup:
yup, if you find dead links, please leave a "review" on that page.  I might have
missed a translation or two.
March 17, 2004 -- Thanks for the question regarding "query ", version 9.02
You Asked
i have a bad luk i submit a question and i put a wrong e-mail
any how i have now  no sorry windows

i have that table like below:

unit    region  activity        full_emp          activd
-----   ------- ---------      ----------        --------
xyz      EUR     DCHE             E              31-jan-03
xyz      EUR     LODE             E              25-FEB-03
xyz      EUR     DCHF             E              01-MAR-03
xyz      EUR     DCHE             F              20-MAR-03
xyz      EUR     DCHE             E              30-MAR-03
xyz      EUR     DCHE             E              10-APR-03
xyz      EUR     DCHE             F              25-APR-03
xyz      FER     DCHE             E              29-APR-03
xyz      FER     DCHE             E              01-MAY-03
xyz      FER     DCHE             F              12-MAY-03
xyz      FER     DCHE             E              25-MAY-03
xyz      EUR     DCHE             E              30-MAY-03
xyz      EUR     LODE             E              10-JUN-03
xyz      EUR     DCHF             F              20-JUN-03
xyz      EUR     DCHE             F              23-JUN-03
xyz      EUR     DCHE             E              25-JUN-03
xyz      EUR     DCHE             E              01-OCT-03

AND ineed to get:
1- avarage days that units is empty PER REGION (full_emp =E )
2- the total days the units will be empty PER REGION

what i need is to get first date the container is empty in EUR which is
(31-jan-03) and last date empty which is (1-mar-03) and then (30-mar-03) and
(10-apr-03) and so on for each region (EUR,FER,MER). i have many units put in
the sample above contain one unit (xyz)
and we said...
basically a variation on this theme:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:13946369553642
(remember, to figure out what I did, take the innermost inline view and run it
and then add the layers one by one on top to see it "build".  that is the way I
write them -- that is the best way to understand them -- bit by bit)

analytics rock
analytics roll....

ops$tkyte@ORA9IR2> select unit, region, min(activd), max(activd)
  2    from (
  3  select a.*, max(x) over (partition by unit, region order by activd) max_x
  4    from (
  5  select unit, region, activity, full_emp, activd,
  6         case when lag(full_emp)
                          over (partition by unit, region
                                    order by activd) <> full_emp
  7              then row_number()
                            over (partition by unit, region order by activd)
  8                  end X
  9    from t
 10         ) a
 11   where full_emp = 'E'
 12         )
 13   group by unit, region, max_x
 14   order by 1,2,3
 15  /

UNIT  REG MIN(ACTIV MAX(ACTIV
----- --- --------- ---------
xyx   EUR 31-JAN-03 01-MAR-03
xyx       30-MAR-03 10-APR-03
xyx       30-MAY-03 10-JUN-03
xyx       25-JUN-03 01-OCT-03

xyx   FER 29-APR-03 01-MAY-03
xyx       25-MAY-03 25-MAY-03

6 rows selected.
Review & Followup

Rating: 5
GOOOOOOOOOOOOOOOD March 18, 2004
Reviewer: ashraf from kuwait
it is like log 10 * sin(90) /cos(180) i now analytic function but can you give
small hint about the query
March 17, 2004 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
Rating: 4
audit_file_dest March 17, 2004
Reviewer: jas from india
hi tom

platform Windows NT

I set
 audit_trial =DB in init.ora

and audit_file_dest=$oracle_home/rdbms/audit
which is also default
i have enabled auditing.
but nothing appears in audit directory.
Followup:
connect / as sysdba and see. the audit file dest is where "sysdba" connection
auditing goes.

by setting audit-trail to db, you've said "put the other stuff into the db".

if you set to OS it'll use the OS audit trail facility (eg: the windows "event"
viewer)
March 17, 2004 Jane -- Thanks for the question regarding "snapshot too old error", version oracle 8i
You Asked
Tom,
Would you tell me what snapshot too old error. When does it
happen? What's the possible causes? How to fix it?

Thank you very much.

Jane
and we said...
I think support note <Note:40689.1> covers this topic very well:

ORA-01555 "Snapshot too old" - Detailed Explanation
===================================================

Overview
~~~~~~~~

This article will discuss the circumstances under which a query
can return the Oracle error ORA-01555 "snapshot too old
(rollback segment too small)". The article will then proceed to
discuss actions that can be taken to avoid the error and finally
will provide some simple PL/SQL scripts that illustrate the
issues discussed.

Terminology
~~~~~~~~~~~

It is assumed that the reader is familiar with standard Oracle
terminology such as 'rollback segment' and 'SCN'. If not, the
reader should first read the Oracle Server Concepts manual and
related Oracle documentation.

In addition to this, two key concepts are briefly covered below
which help in the understanding of ORA-01555:

1. READ CONSISTENCY:
====================

This is documented in the Oracle Server Concepts manual and so
will not be discussed further. However, for the purposes of this
article this should be read and understood if not understood
already.

Oracle Server has the ability to have multi-version read
consistency which is invaluable to you because it guarantees
that you are seeing a consistent view of the data (no 'dirty
reads').

2. DELAYED BLOCK CLEANOUT:
==========================

This is best illustrated with an example: Consider a transaction
that updates a million row table. This obviously visits a large
number of database blocks to make the change to the data. When
the user commits the transaction Oracle does NOT go back and
revisit these blocks to make the change permanent. It is left
for the next transaction that visits any block affected by the
update to 'tidy up' the block (hence the term 'delayed block
cleanout').

Whenever Oracle changes a database block (index, table, cluster)
it stores a pointer in the header of the data block which
identifies the rollback segment used to hold the rollback
information for the changes made by the transaction. (This is
required if the user later elects to not commit the changes and
wishes to 'undo' the changes made.)

Upon commit, the database simply marks the relevant rollback
segment header entry as committed. Now, when one of the changed
blocks is revisited Oracle examines the header of the data block
which indicates that it has been changed at some point. The
database needs to confirm whether the change has been committed
or whether it is currently uncommitted. To do this, 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.

If it is found  that the block is committed then the header of
the data block is updated so that subsequent accesses to the
block do not incur this processing.

This behaviour is illustrated in a very simplified way below.
Here we walk through the stages involved in updating a data
block.

 STAGE 1 - No changes made

 Description: This is the starting point. At the top of the
              data block we have an area used to link active
              transactions to a rollback
              segment (the 'tx' part), and the rollback segment
              header has a table that stores information upon
              all the latest transactions
              that have used that rollback segment.

              In our example, we have two active transaction
              slots (01 and 02)
              and the next free slot is slot 03. (Since we are
              free to overwrite committed transactions.)

Data Block 500             Rollback Segment Header 5
+----+--------------+      +----------------------+---------+
| tx | None         |      | transaction entry 01 |ACTIVE   |
+----+--------------+      | transaction entry 02 |ACTIVE   |
| row 1             |      | transaction entry 03 |COMMITTED|
| row 2             |      | transaction entry 04 |COMMITTED|
| ... ..            |      |     ...     ...   .. |  ...    |
| row n             |      | transaction entry nn |COMMITTED|
+-------------------+       +--------------------------------+

 STAGE 2 - Row 2 is updated

 Description: We have now updated row 2 of block 500. Note that
              the data block header is updated to point to the
              rollback segment 5, transaction
              slot 3 (5.3) and that it is marked uncommitted
             (Active).

Data Block 500             Rollback Segment Header 5
+----+--------------+     +----------------------+---------+
| tx |5.3uncommitted|-+   | transaction entry 01 |ACTIVE   |
+----+--------------+ |   | transaction entry 02 |ACTIVE   |
| row 1             | +-->| transaction entry 03 |ACTIVE   |
| row 2 *changed*   |     | transaction entry 04 |COMMITTED|
| ... ..            |     |     ...     ...   .. |  ...    |
| row n             |     | transaction entry nn |COMMITTED|
+------------------+      +--------------------------------+

 STAGE 3 - The user issues a commit

 Description: Next the user hits commit. Note that all that
              this does is it
              updates the rollback segment header's
              corresponding transaction
              slot as committed. It does *nothing* to the data
              block.

Data Block 500                   Rollback Segment Header 5
+----+--------------+       +----------------------+---------+
| tx |5.3uncommitted|--+    | transaction entry 01 |ACTIVE   |
+----+--------------+  |    | transaction entry 02 |ACTIVE   |
| row 1             |  +--->| transaction entry 03 |COMMITTED|
| row 2 *changed*   |       | transaction entry 04 |COMMITTED|
| ... ..            |       |     ...     ...   .. |  ...    |
| row n             |       | transaction entry nn |COMMITTED|
+------------------+        +--------------------------------+

 STAGE 4 - Another user selects data block 500

 Description: Some time later another user (or the same user)
              revisits data block 500. We can see that there
              is an uncommitted change in the
              data block according to the data block's header.

              Oracle then uses the data block header to look up
              the corresponding rollback segment transaction
              table slot, sees that it has been committed, and
              changes data block 500 to reflect the
              true state of the datablock. (i.e. it performs
              delayed cleanout).

Data Block 500                   Rollback Segment Header 5
+----+--------------+      +----------------------+---------+
| tx | None         |      | transaction entry 01 |ACTIVE   |
+----+--------------+      | transaction entry 02 |ACTIVE   |
| row 1             |      | transaction entry 03 |COMMITTED|
| row 2             |      | transaction entry 04 |COMMITTED|
| ... ..            |      |     ...     ...   .. |  ...   |
| row n             |      | transaction entry nn |COMMITTED|
+------------------+       +--------------------------------+

ORA-01555 Explanation
~~~~~~~~~~~~~~~~~~~~~

There are two fundamental causes of the error ORA-01555 that are
a result of Oracle trying to attain a 'read consistent' image.
These are :

  o The rollback information itself is overwritten so that
Oracle is unable to rollback the (committed) transaction entries
to attain a sufficiently old enough version of the block.

  o The transaction slot in the rollback segment's transaction
table (stored in the rollback segment's header) is overwritten,
and Oracle cannot rollback the transaction header sufficiently
to derive the original rollback segment transaction slot.

Both of these situations are discussed below with the series of
steps that cause the ORA-01555. In the steps, reference is made
to 'QENV'. 'QENV' is short for 'Query Environment', which can be
thought of as the environment that existed when a query is first
started and to which Oracle is trying to attain a read
consistent image. Associated with this environment is the SCN
(System Change Number) at that time and hence, QENV 50 is the
query environment with SCN 50.

 CASE 1 - ROLLBACK OVERWRITTEN

 This breaks down into two cases: another session overwriting
the rollback that the current session requires or the case where
the current session  overwrites the rollback information that it
requires. The latter is discussed in this article because this
is usually the harder one to understand.

  Steps:

    1. Session 1 starts query at time T1 and QENV 50

    2. Session 1 selects block B1 during this query

    3. Session 1 updates the block at SCN 51

    4. Session 1 does some other work that generates rollback
information.

    5. Session 1 commits the changes made in steps '3' and '4'.
       (Now other transactions are free to overwrite this
rollback information)

    6. Session 1 revisits the same block B1 (perhaps for a
different row).

       Now, Oracle can see from the block's header that it has
been changed and it is later than the required QENV (which was
50). Therefore we need to get an image of the block as of this
QENV.

       If an old enough version of the block can be found in the
buffer cache then we will use this, otherwise we need to
rollback the current block to generate another version of the
block as at the required QENV.

       It is under this condition that Oracle may not be able to
get the required rollback information because Session 1's
changes have generated rollback information that has overwritten
it and returns the ORA-1555 error.

 CASE 2 - ROLLBACK TRANSACTION SLOT OVERWRITTEN

    1. Session 1 starts query at time T1 and QENV 50

    2. Session 1 selects block B1 during this query

    3. Session 1 updates the block at SCN 51

    4. Session 1 commits the changes
       (Now other transactions are free to overwrite this
rollback information)

    5. A session (Session 1, another session or a number of
other sessions) then use the same rollback segment for a series
of committed transactions.

       These transactions each consume a slot in the rollback
segment transaction table such that it eventually wraps around
(the slots are written to in a circular fashion) and overwrites
all the slots. Note that Oracle is free to reuse these slots
since all transactions are committed.

    6. Session 1's query then visits a block that has been
changed since the initial QENV was established. Oracle therefore
needs to derive an image of the block as at that point in time.

       Next Oracle attempts to lookup the rollback segment
header's transaction slot pointed to by the top of the data
block. It then realises that this has been overwritten and
attempts to rollback the changes made to the rollback segment
header to get the original transaction slot entry.

       If it cannot rollback the rollback segment transaction
table sufficiently it will return ORA-1555 since Oracle can no
longer derive the required version of the data block.

 It is also possible to encounter a variant of the transaction
slot being overwritten when using block cleanout. This is
briefly described below :

 Session 1 starts a query at QENV 50. After this another process
updates the blocks that Session 1 will require. When Session 1
encounters these blocks it determines that the blocks have
changed and have not yet been cleaned out (via delayed block
cleanout). Session 1 must determine whether  the rows in the
block existed at QENV 50, were subsequently changed,

 In order to do this, Oracle must look at the relevant rollback
segment transaction table slot to determine the committed SCN.
If this SCN is after the QENV then Oracle must try to construct
an older version of the block and if it is before then the block
just needs clean out to be good enough for  the QENV.

 If the transaction slot has been overwritten and the
transaction table cannot be rolled back to a sufficiently old
enough version then Oracle cannot derive the block image and
will return ORA-1555.

 (Note: Normally Oracle can use an algorithm for determining a
block's SCN during block cleanout even when the rollback segment
slot has been overwritten. But in this case Oracle cannot
guarantee that the version of the block has not changed since
the start of the query).

Solutions
~~~~~~~~~

This section lists some of the solutions that can be used to
avoid the ORA-01555 problems discussed in this article. It
addresses the cases where rollback segment information is
overwritten by the same session and when the rollback segment
transaction table entry is overwritten.

It is worth highlighting that if a single session experiences
the ORA-01555 and it is not one of the special cases listed at
the end of this article, then the session must be using an
Oracle extension whereby fetches across commits are tolerated.
This does not follow the ANSI model and in the rare cases where
ORA-01555 is returned one of the solutions below must be used.

 CASE 1 - ROLLBACK OVERWRITTEN

  1.  Increase size of rollback segment which will reduce the
likelihood of overwriting rollback information that is needed.

  2.  Reduce the number of commits (same reason as 1).

  3.  Run the processing against a range of data rather than the
whole table. (Same reason as 1).

  4.  Add additional rollback segments. This will allow the
updates etc. to be spread across more rollback segments thereby
reducing the chances of overwriting required rollback
information.

  5.  If fetching across commits, the code can be changed so
that this is not done.

  6.  Ensure that the outer select does not revisit the same
block at different times during the processing. This can be
achieved by :

        - Using a full table scan rather than an index lookup
        - Introducing a dummy sort so that we retrieve all the
data, sort it and then sequentially visit these data blocks.

 CASE 2 - ROLLBACK TRANSACTION SLOT OVERWRITTEN

  1. Use any of the methods outlined above except for '6'. This
will allow transactions to spread their work across multiple
rollback segments therefore reducing the likelihood or rollback
segment transaction table slots being consumed.

  2. If it is suspected that the block cleanout variant is the
cause, then force block cleanout to occur prior to the
transaction that returns the ORA-1555. This can be achieved by
issuing the following in SQL*Plus, SQL*DBA or Server Manager :

      alter session set optimizer_goal = rule;
      select count(*) from table_name;

     If indexes are being accessed then the problem may be an
index block and clean out can be forced by ensuring that all the
index is traversed. Eg, if the index is on a numeric column with
a minimum value of 25 then the following query will force
cleanout of the index :

      select index_column from table_name where index_column >
24;

Examples
~~~~~~~~

Listed below are some PL/SQL examples that can be used to
illustrate the ORA-1555 cases given above. Before these PL/SQL
examples will return this error the database must be configured
as follows :

  o Use a small buffer cache (db_block_buffers).

    REASON: You do not want the session executing the script to
be able to find old versions of the block in the buffer cache
which can be used to satisfy a block visit without requiring the
rollback information.

  o Use one rollback segment other than SYSTEM.

    REASON: You need to ensure that the work being done is
generating rollback information that will overwrite the rollback
information required.

  o Ensure that the rollback segment is small.

    REASON: See the reason for using one rollback segment.

 ROLLBACK OVERWRITTEN

rem * 1555_a.sql -
rem * Example of getting ora-1555 "Snapshot too old" by
rem * session overwriting the rollback information required
rem * by the same session.

  drop table bigemp;
  create table bigemp (a number, b varchar2(30), done char(1));

  drop table dummy1;
  create table dummy1 (a varchar2(200));

  rem * Populate the example tables.
  begin
   for i in 1..4000 loop
     insert into bigemp values (mod(i,20), to_char(i), 'N');
     if mod(i,100) = 0 then
       insert into dummy1 values ('ssssssssssss');
       commit;
     end if;
   end loop;
   commit;
  end;
  /

  rem * Ensure that table is 'cleaned out'.
  select count(*) from bigemp;

  declare
   -- Must use a predicate so that we revisit a changed block at
a different
   -- time.

   -- If another tx is updating the table then we may not need
the predicate
   cursor c1 is select rowid, bigemp.* from bigemp where a < 20;

  begin
   for c1rec in c1 loop

     update dummy1 set a = 'aaaaaaaa';
     update dummy1 set a = 'bbbbbbbb';
     update dummy1 set a = 'cccccccc';
     update bigemp set done='Y' where c1rec.rowid = rowid;
     commit;
   end loop;
  end;
  /

 ROLLBACK TRANSACTION SLOT OVERWRITTEN

  rem * 1555_b.sql - Example of getting ora-1555 "Snapshot too
old" by
  rem *              overwriting the transaction slot in the
rollback
  rem *              segment header. This just uses one session.

  drop table bigemp;
  create table bigemp (a number, b varchar2(30), done char(1));

  rem * Populate demo table.
  begin
   for i in 1..200 loop
     insert into bigemp values (mod(i,20), to_char(i), 'N');
     if mod(i,100) = 0 then
       commit;
     end if;
   end loop;
   commit;
  end;
  /

  drop table mydual;
  create table mydual (a number);
  insert into mydual values (1);
  commit;

  rem * Cleanout demo table.
  select count(*) from bigemp;

  declare

   cursor c1 is select * from bigemp;

  begin

   -- The following update is required to illustrate the problem
if block
   -- cleanout has been done on 'bigemp'. If the cleanout
(above) is commented
   -- out then the update and commit statements can be commented
and the
   -- script will fail with ORA-1555 for the block cleanout
variant.
   update bigemp set b = 'aaaaa';
   commit;

   for c1rec in c1 loop
     for i in 1..20 loop
       update mydual set a=a;
       commit;
     end loop;
   end loop;
  end;
  /

Special Cases
~~~~~~~~~~~~~

There are other special cases that may result in an ORA-01555.
These are given below but are rare and so not discussed in this
article :

 o Trusted Oracle can return this if configured in OS MAC mode.
Decreasing LOG_CHECKPOINT_INTERVAL on the secondary database may
overcome the problem.

 o If a query visits a data block that has been changed by using
the Oracle discrete transaction facility then it will return
ORA-01555.

 o It is feasible that a rollback segment created with the
OPTIMAL clause maycause a query to return ORA-01555 if it has
shrunk during the life of the query causing rollback segment
information required to generate consistent read versions of
blocks to be lost.

Summary
~~~~~~~

This article has discussed the reasons behind the error
ORA-01555 "Snapshot too old", has provided a list of possible
methods to avoid the error when it is encountered, and has
provided simple PL/SQL scripts that illustrate the cases
discussed.
Review & Followup

Rating: 5
Snapshot too Old. March 20, 2001
Reviewer: Ganesh Raja from Chennai, Tamil Nadu India.
This Article Was real Good. Thanks Tom for Highlighting this.
Rating: 5
Snapshot too old error March 29, 2001
Reviewer: KP from India
yr reply is  ultimate.
Rating: 5
Simply Superb August 07, 2001
Reviewer: Nikhil S Bidwalkar from Singapore
Tom your reply was just terrific ... I need to know more about  how oracle makes
a read consistent views using rollback segment if more then 2 transactions are
reading same block which are updated by another transactions .
Rating: 4
Snapshot too old error October 31, 2001
Reviewer: AC from Deutschland
Superb! Excellent article and carefully explained.
Rating: 5
Snapshot too old November 09, 2001
Reviewer: Jo?o Paulo from Brazil
Finnaly I completely understand the rules for the snapshot too old.

Thanks Tom.
Rating: 4
"Order by" alternative January 14, 2002
Reviewer: walt from CT
I have found some situations where "Order By" is a reasonable way to eliminate
snapshot too old errors.  This can force all the I/O to occur before any rows
are returned. This seems particularly useful when cursor processing has time
consuming nested logic.
Rating: 5
Snapshot too old error February 07, 2002
Reviewer: Prasath Srinivasan from Chennai,India
Tom
I came across this site only on 07/02/2001
The informations you provide and the way you approach is really amazing and
extremely useful.
Thanks for ur work
Rating: 5
Snapshot too old error February 07, 2002
Reviewer: Prasath Srinivasan from Chennai,India
Tom
I came across this site only on 07/02/2001
The informations you provide and the way you approach is really amazing and
extremely useful.
Thanks for ur work
Rating: 4
facing same problem. March 31, 2002
Reviewer: Sudhanshu Jain from India, Delhi
Hi

I am facing the same problem in my application. My process involves fetch across
commit.

I will implement the suggestion as you mentioned.

Thanks.
Rating: 4
Great Job!!! August 01, 2002
Reviewer: Fiza from Canada
Thanks Tom your reply is more than useful...wonderful
Rating: 4
Snapshot too old January 09, 2003
Reviewer: K Mangesh from Pune, India
Thanks a lot Tom, This is really great.....
Rating: 5
Extremely Useful Information February 23, 2003
Reviewer: ik from BG
Tom,

Two Questions -

1) Further to the concept of delayed block cleanout - If assuming that
transactions happen on the database during the night to sync up with another
system, would it be advisable enough to do a SELECT /*+ FULL */ COUNT(*) FROM
updated_table after the nightly bulk inserts/updates?
This is to ensure that table is 'cleaned out'. Otherwise the next day regular
queries will find the updated blocks as uncommitted and would have to read the
rollback segments. Objective is to reduce work done by the regular queries on
the database.

2) Would the query lock the rollback segment for consistent reads?

Thanks as always.
Followup:
1) i would not, no.  The concept here is that the work is so neglible to the
guys the next day -- that it won't be noticed.

2) no
Rating: 5
Good article February 26, 2003
Reviewer: Victor Oosterbaan from Netherlands
Very good article, beginning to understand RBs now :P
Rating: 5
more help needed in understanding ora-01555 May 15, 2003
Reviewer: Jerry from USA
I read some of the postings on this website and your book. I still feel it
difficult to piece together the reason for ora-01555. Two questions:

1. How many slots the transaction table has for a rbs?
2. Could you help explain the second example in the article?

ROLLBACK TRANSACTION SLOT OVERWRITTEN

  rem * 1555_b.sql - Example of getting ora-1555 "Snapshot too old" by
  rem *              overwriting the transaction slot in the rollback
  rem *              segment header. This just uses one session.

  drop table bigemp;
  create table bigemp (a number, b varchar2(30), done char(1));

  rem * Populate demo table.
  begin
   for i in 1..200 loop
     insert into bigemp values (mod(i,20), to_char(i), 'N');
     if mod(i,100) = 0 then
       commit;  (Q: ==> why commit here?  I recall you mentioned in another
posting that it's better to bite the bullet and size the rbs properly.  Is it
for demo purpose to avoid the maxextents reached? Or is it suggested to do
commit every n rows?)
     end if;
   end loop;
   commit;
  end;
  /

  drop table mydual;
  create table mydual (a number);
  insert into mydual values (1);
  commit;

  rem * Cleanout demo table.
  select count(*) from bigemp;

  declare

   cursor c1 is select * from bigemp;

  begin

   -- The following update is required to illustrate the problem if block
   -- cleanout has been done on 'bigemp'. If the cleanout (above) is commented
   -- out then the update and commit statements can be commented and the
   -- script will fail with ORA-1555 for the block cleanout variant. (Q: ==>
could you please expand on this and explain why update is needed here?

   update bigemp set b = 'aaaaa';
   commit;

   for c1rec in c1 loop  (Q: ==> The cursor here already got the commited
changes from bigemp, right?)
     for i in 1..20 loop
       update mydual set a=a; ( Q: ==>Does fetch across cursor means that the
update/fetch is on the same table? Why ora-01555 occurs here?)
       commit;
     end loop;
   end loop;
  end;
  /
Followup:
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:895410916429
for my example of this.  I did not do the example above, yes, I would not have
filled the table in that fashion.  i would have used insert into select ....
from all_objects myself.

the number of transaction entries varies by blocksize.

If you have my book "Expert one on one Oracle" -- I spend lots of time on this
topic with lots of examples.  Basically, we get into a situation where we cannot
tell if the version of the block we have access to is "current enough" yet "not
too current" for our queries result set.  the information we needed to tell that
is wiped out of the rbs.
Rating: 5
nifty article May 30, 2003
Reviewer: Anirudh Sharma from New Delhi, India
Hi Tom,
 The article about snapshot too old error was very good but I have a small
doubt:
What if a procedure which is declared Autonomous transaction is called in a loop
and that procedure has commit in it. Can that be a cause for ORA 01555 as well.
Thanks
Followup:
the only CAUSE of a 1555 is improperly sized rollback segments.

committing in a cursor for loop is a way to experience the symptoms all by
yourself (without any outside help).
Rating: 4
June 09, 2003
Reviewer: A reader
Tom,
Here is the situation I run into.

I start with an empty database and insert millions of rows.

After the load is done and the updates are committed, I run a very long query
that will run for hours.

While the query is running other batch jobs are loading other tables. The table
that I am querying is not updated again.

After running for a long time my query fails with snapshot too old.

I assume this is due to the delayed block cleanout.

Do you agree?

If the data blocks were updated, committed and not cleaned out and the rollback
segments can be overwritten because it is committed how do the blocks ever get
cleaned out in this situation?
Followup:
yes.

your query is cleaning the blocks out itself.
Rating: 5
point 6 of note August 08, 2003
Reviewer: A reader
Hi

In the note you provided in point 6 of solutions of case 1 it states this:

6.  Ensure that the outer select does not revisit the same block at different
times during the processing. This can be achieved by :

- Using a full table scan rather than an index lookup
- Introducing a dummy sort so that we retrieve all the data, sort it and then
sequentially visit these data blocks.

How can a sort avoid this error?
Followup:
Umm, by RETRIEVING all of the data (before the first row is returned -- before a
commit happens)
Rating: 4
ORA-01555 during export August 25, 2003
Reviewer: Sunil Gururaj from Cyprus
Hi Tom,

I am performing an export of around 80 Million records on a table which is not
used by anybody else except the export itself.

I was successful in performing such an export many times in the past few days.

I am using the below command:

gzexp test1/test1 file = exp_RATED_EVENT_XXX.dmp log = exp_RATED_EVENT_XXX.log
tables = RATED_EVENT_XXX feedback = 1000000 buffer = 40960000 grants = n
constraints = n indexes = n compress = n direct = y

Note: gzexp is a piping script to overcome the 2GB limit.

However, today, after exporting about 70 million records, the export failed with
an ORA-01555 error.

The only operation that was performed on this table after the export began was
"alter table nologging"

My question is whether the "alter table nologging" is potential enough to cause
a "snapshot too old" error or am I missing something here.

Thanks,
Followup:
you don't even need that -- there can be NO activity on this table and you can
get an ora-1555.  it just means whilst you were querying there were lots of
other little transactions all committing and wiping out undo you needed to
ensure the consistent read.

it can happen on a READ ONLY tablespace even.
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:895410916429
for example demonstrates that.  it means your RBS is too small for what you do
on your system.

if you restart, it should be OK now since at least 7/8ths of the table has had
its blocks cleaned out.
Rating: 5
August 25, 2003
Reviewer: A reader
Rating: 5
what are systemTables ? what is the main usage ? September 09, 2003
Reviewer: raja from India
  hi, tom

     what are the systemtables and usages ?

     how to catch the for..loop Exception's ?

    i am writing one simple procedure , in that procedure executable block ,2 nd
statement raises an exception, it goes to  exceptions block  and after that i am
interested to execute again 3 rd statement's onwards...also  how to do this ?
Followup:
system tables are the data dictionary.

for ...
loop
    statement 1
    begin
       statement 2
    exception
       when named_exception then ....
    end
    statement 3
end loop

that'll execute statement 1, then statement 2. if statement 2 raises some
exception and you catch it, handle it -- then statement 3 will execute.
Rating: 4
OPTIMAL parameter for rollback segments September 11, 2003
Reviewer: Mohan from bangalore
Hi Tom,

I am confused about whether to specify the optimal parameter if storage cluase
of "create rollback segment". The article above says if a value is specified for
the OPTIMAL parameter then it can cause "snapshot too old error". When I don't
specify a value for the optimal parameter then the rollback segment ocuupies the
entire tablespace, never shrinks. How to overcome this.

Mohan
Followup:
let the rollback segment stay that size?!?

i mean, apparently, they need to be that big.  why keep shrinking them if they
really want to grow that large.  it is expensive to let them grow.  It is
expensive to let them shrink.  let them be the size they need to be for your
system.
Rating: 5
rebuild index and snapshot too old October 08, 2003
Reviewer: A reader
Hi

Recently we got snapshot too old errors when rebuild an index (not very big,
around 1GB). How so.... we rebuild index using conventional methods not using
ONLINE clause so no DML is allowed we dont understand why we can get ORA-01555.
Can it be delayed block clean out? If so how can we avoid that?
Followup:
it is the delayed cleanout -- yes.

you increase the size of your permanently allocated RBS to be large enough so as
to not wrap around during your longest running statement.
Rating: 4
Breaking my head. Please help me out ! October 10, 2003
Reviewer: Tony from India
I'm in the process of tuning pro*c programs. The existing code does the
following:

1. Opens a cursor for 10000 records.
2. Bulk fetch 100 records at time.
3. Process the data and update, insert...etc
4. Commit for every 500 records.
5. Colse the cursor and Open the cursor for another 10000 records.
6. step 1 to 5 again
........
.........

The above actions are repeated many times as the table has millions of records.
The application(month end process) is very slow but doesn't through any error.

Since cursor reopen for every 10000 records and frequent commit for every 500
records are identified as the main cause for the slow down of the application,
Its decided to 1. Remove cursor reopen,
2. Open the cursor only once with all records.
3. Bulk fetch 100 records at a time.
4. Process the data, update /insert ....
5. Create a large rollback segment.
6. Increase the commit interval (Commit for every 1 lack record (100000) instead
of every 500 record).

Now there is a significant improvement in the performance.
But ORA-01555 "Snapshot too old" error is encountered after commiting certain
number of records.

The tablespace for the large rollback segment size is above 3GB. And the large
rollback segment has the following values:
Initial extent= 50 MB,
min extent = 2,
next extents = 2 MB,
max extent = unlimited.

Now I encounter ORA-01555 "Snapshot too old" error, but rollback tablespace
still  has so much of free space and only 6 extents are allocated for the
rollback segment.

What could be the reason for the error. I'm breaking my head for the past 3
days. I've increased the initial extent to 512 and Still I face the problem.
Tom, Please help me out.

Thanks in advance.

Than
Followup:
the probability of a 1555 is directly related to the SIZE OF THE SMALLEST RBS.
Your query needs to READ from ALL rbs's -- your transaction might be writing to
one, but your query needs them all.  And they are wrapping around.

You want to make ALL of your RBS's big enough so they do not wrap around during
the course of this processing.

You want this to go really fast?  remove the procedural code as much as possible
(eg: single SQL insert/updates - NO QUERY) or even consider putting it into
plsql instead of bringing it all of the way OUT of the database just to put it
back INTO the database.
Rating: 5
Why query needs rollback segment? October 11, 2003
Reviewer: Tony from India
Tom, Thanks for your answer for my previous question on
ORA-01555. I have few more doubts.

1.As per I know, only insert, update and delete need rollback segs. Why the
query needs rollback segments?.
2.The pro*c programs are forced to use the BIG rollback segment and the error
ORA-01555 is raised for the BIG rollback segment and not for the samll ones.
then, why should I increase the size of small rollback segs?
Followup:
1) read:
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96524/c21cnsis.htm#17882
to learn all about the COOLEST feature in Oracle.  It explains why rollback is
NOT just for modifications.

This is something that EVERYONE needs to understand.  It is the fundemental
"thing" about Oracle.

2) if you have my book expert one on one Oracle -- I wrote on this extensively.

STOP committing until your TRANSACTION is complete.
Rating: 3
Question. November 10, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada.
Tom,

Can we say that we cannot get ORA-1555 more times then the sum of the WRAPS
column in v$rollstat for all rollback segments ? (given that we have not
droped/created/offlined any of them, and excluding the system rollback).

If not, can you explain why?

Thanks.
Followup:
no -- those wraps are different then "wrap arounds" -- they are wraps from one
extent to another.
Rating: 4
Ah ! November 10, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
Ah ... confusion again. I found a question about wraps, that explains it thanks.

Is there anyway to see wrap arrounds via a statistic ?

Is there are any usefull info we can get, via such a statistic?
Followup:
with undo segments -- it is easy.  with undo segments -- you don't need to worry
about such calculations.

with old fashioned ones, not so.  you could look at writes (bytes written) to
see how much activity it generated.  a statspack would give you this for a
discrete window of time.  You can see how fast you generate undo and the compute
the theoretical wrap arounds from there.
Rating: 5
If DML session starts first, is it possible to get ORA-01555? November 12, 2003
Reviewer: John from San Jose
Hi Tom,

Feel guilty everytime I post here - thinking you are being bombarded with
questions from all over the world. Such is the price of fame, yes? I typically
come here only when I have issues that I absolutely can not resolve myself. So
here you go...

If I have only 2 sessions running in a system, One running DML, one doing query.
If the DML session starts first, is it possible for the querying session to get
ORA-01555?

Here is my analysis:

1. Session 1 modifies 100 blocks in a table at time T1. 100 blocks copied to
rollback segment with SCN marked as, say, SCN1.
2. Session 2 comes in at time T2 and select one of the blocks, let's say block1.
Since the data is being modified by session 1, session 2 goes to the rollback
for the block. Let's say the current SCN is SCN2.
3. At time T3 session 1 commits. The rollback is now up for grab. Let's say it
is overwritten before session 2 finishes the long runnig query.
4. At time T4 session 2 ask for another data block, say block100.

Here is the critical point. What happens when session 2 ask for block100 at T4?
It looks up block100 and notice the data in the block has been committed and the
SCN is SCN1 which is older than SCN2, the starting SCN of the query. This is OK
for read consistency purpose. It then proceeded to read the data block from the
table. So, even though
the rollback data is now gone, it does not matter. Because session 1 does not
need it anymore.

What happens if session 2 at T4 asked for block1, the same block it visitied at
T2? Again it looks up the data block in the table, noticed the data has been
committed, SCN is older than its starting SCN and decided to read from it. So,
session 2 read block1 from rollback at T2 and from table itslef at T4. There is
no problem with this, it there?

Do you see any problems with my thinking? Am I correct in saying that the
querying session will not get ORA-01555, as long as it starts AFTER the DML
session? Thanks much!

John
Followup:
is this a SINGLE QUERY visiting blocks 1 and 100 or separate queryies?

if these are the result of a single query-- then the premise that the SCN1 is
old enough is wrong.

if they are individual queries, then no ora-1555 unless you were using
serializable which freezes your scn as of the time of the first statement you
execute in the transaction.
Rating: 5
great November 13, 2003
Reviewer: mengyp from China
ths Tom,
if in a long updating,most block is writlen back to
disk,then commit,and no transaction that visits any block affected by the update
to 'tidy up' the block.

1.days after,what happening to those blocks?(if select need to visit)
2.restart instance,what happening to those blocks?
are those blocks need to 'tidy up'(if select need to visit
those blocks)
Rating: 3
Question about delayed block cleanout November 13, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
When a given block is not cleaned out, and it's rollback transaction information
is lost due to high commit activity, how does oracle know what SCN to assign to
that block when cleaning it out?
Followup:
blocks don't have scn's really.  rows on the blocks do.  the scn is already on
the block (left behind from the transaction that modified it in the first place)
Rating: 2
ROW SCNs??? November 14, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
That's strange.

I thought that there's one SCN per block, am I wrong ?

Also, how is it possible for the SCN to be already on the modified block if it
is not cleaned out ? I also thought that all blocks modified by one transaction
should get the System SCN as of the time of the commit, which is unknown when
the blocks are been modofied. Is that wrong ?

Here's how I see the process of updating a row

1. Finds block P containing row N
2. Lock row N (if possible)
3. Make a copy of the block in the rollback segment
4. Updates header of block P with a pointer to a rollback segment transaction,
which poins to the previous made copy
5. Make the changes to the row and the block
6. Returns to user input

When the user commits, the following happens:

7. Gets the system current SCN
8. Marks the transaction as commited in the rollback segment header, writing the
SCN we got.
9. Cleans out some of the blocks (possibly not all)
10. Returns to user input

Is my understanding correct ? Can you correct any incorect steps ?
Followup:
sorry -- i knew after I posted this i mispoke -- was in a hurry last night.

But in any case, the transaction information on the block header is what we need
and that is all there -- it is just that the transaction information is "stale"
and needs to be refreshed.  The transaction header is whats vital here and it is
all there -- it is just that we have to peek to see if the transaction that
appears to have the rows locked is still active or not (it would appear in that
looking at the block that it is but peeking at the active transactions will tell
us it is not).

sorry about that.
Rating: 2
So where do we get the SCN? November 14, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
It's ok as long as we clear out the story.

So how does a block get the commit SCN when it is in the "delayed block cleanout
" state, if it's not revisited after the commit and it's transaction information
can be lost ?
Followup:
you do know "at least how old" it is.  it has the base scn on the block as of
the modification to the block..  this is one of the reasons this:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:895410916429
can happen
Rating: 3
more November 14, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
So when such a block (with lost transactional info) is been cleaned out, will it
keep that base SCN ?

Does that mean, that a block with a lost transaction information will be stamped
with a SCN earlier then the SCN of the commit? If so, does it mean that
different block modified by the same transaction can have different SCNs
depending on the cleanout?
Followup:
see, it is more complex then just a simple "scn on a block".  It really is "row
related", "transaction related" (trying to say "it is bigger then a bread box")

things really are at the row level (based on information in the transaction
header) at the lowest level.  there are base scn's, wrap scn's on the block,
there are commit times in the transaction headers -- the scn base is used in
that case.

conceptually, it is easy to think of "block has scn"
physically, it is not so cut and dry.

curious -- why do you ask?

you can see this is more or less at the "row level" via a simple simulation.
we'll create a table, two rows.  both rows -- same block.  open cursors on each
row (but don't fetch).  update one row 10,000 times and commit each.  Now, print
data.  Review the number of consistent gets for each cursor:

ops$tkyte@ORA920PC> create table t ( x int, data char(10) );

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert into t values ( 1, 'x' );

1 row created.

ops$tkyte@ORA920PC> insert into t values ( 2, 'x' );

1 row created.

ops$tkyte@ORA920PC> commit;

Commit complete.

ops$tkyte@ORA920PC> select x, dbms_rowid.rowid_block_number(rowid) from t;

         X DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)
---------- ------------------------------------
         1                                   53
         2                                   53

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> variable x refcursor
ops$tkyte@ORA920PC> variable y refcursor
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> @trace
ops$tkyte@ORA920PC> alter session set events '10046 trace name context forever,
level 12';

Session altered.

ops$tkyte@ORA920PC> begin
  2          open :x for select * from t where x = 1;
  3          open :y for select * from t where x = 2;
  4  end;
  5  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> begin
  2          for i in 1 .. 10000
  3          loop
  4                  update t set data = 'x' where x = 1;
  5                  commit;
  6          end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> print x

         X DATA
---------- ----------
         1 x

ops$tkyte@ORA920PC> print y

         X DATA
---------- ----------
         2 x

tkprof says:

SELECT * from t 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        1      0.05       0.27        113      10002          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.05       0.27        113      10002          0           1
********************************************************************************
SELECT * from t 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        1      0.00       0.00          0          7          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          7          0           1

So, only the query that hit the block that required a ROW changed after the
query began did the massive consistent reads needed to restore it.

It is more complex then simply "there is an scn on the block"
Rating: 5
Wow November 14, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
Wow, that was impressive.

I could've sweared that both queries would do 10 000 consistent reads.

Why I ask ? I've dedicated my self to solve every single "mystery" in Oracle
that I encouter. Just recently I was trying to explain to a friend of mine the
real reason why he was gething 1555, and that it's not just because he was doing
a commit accross fetch.

Anyways, I was thinking about the reasons, and that question came to my mind.
How does Oracle know the SCN of the block, when it's info is lost and a 1555 has
to be returned ...

Anyway , your example is most impressive, seriously why there's no need for a
consistent read on the second row ?

Just when I think I fully understand it, you show me I don't and there's a lot
of missing parts.

I am most interested in the aditional details on the "scn per block" concept.
Followup:
hmmm, not so impressive after all.  i should have been more thorough.  I found a
test that proved what I wanted to see :)

reverse the prints -- print y then x -- and you'll see what I mean.

In short, there is scn data on the block but each transaction entry when cleaned
out fills in information as well.  if not cleaned out, they use the scn base
which is "at least old enough".

as for:

"How does Oracle know the SCN of the block, when it's info is lost and a 1555
has
to be returned ..."

thats the issue, we don't KNOW at that point (search for

"alter tablespace" readonly 1555

on this site to read about a read only tablespace throwing a 1555 for this very
reason.

I shouldn't have rushed this answer -- so to answer:

"Anyway , your example is most impressive, seriously why there's no need for a
consistent read on the second row ?"

it is because it was in the buffer cache -- we didn't have to walk the undo
chain to reconstruct it, the first query did (we can see that by reversing the
print statements -- it works in reverse just as well)

time for you to start looking at block dumps maybe :)  -- i tend to totally
avoid them, get too bogged down in the weeds.
Rating: 3
Responibility for ORA-1555: developer or dba? December 03, 2003
Reviewer: Olga from Vienna
I have a transaction, where a 1555 occurs sometimes while I do a very big insert
into a temporary table in a pl/sql-Block like this:

begin
  insert into temp_parm_table ...;

  execute immediate( truncate temp_table1 );

  insert into temp_table1
  select ... from temp_parm_table, big_table where ...
  -<BANG ORA-1555 occurs!>

  commit;

end;

For the most entries in temp_parm_table the select runs a few seconds. But for
some entries it does take a lot of time. And then sometimes a 1555 occurs.

You said:

"the only CAUSE of a 1555 is improperly sized rollback segments."

I told it the DBA, but he said that my code is wrong (without seeing it) and
said also, that there would have been a lot of free space in the rollback
segment when my programm generated the ORA-01555. Could that be true?

I think, in his opinion the only CAUSE of a 1555 is the wrong coding of the
developers.
Followup:
he is wrong.

you have a query that is running for N minutes.

he has configured the system to hold (N-M) minutes of undo.

Your N minute long query will fail because they have not sized sufficient undo
space.
Rating: 5
Thanks for yorur Answer! December 03, 2003
Reviewer: Olga from Vienna
Many thanks for your answer, Tom.

I put it to my "private oracle rules":

An ORA-01555 will never be raised, when there is free space in the
rollback-segment.
Followup:
thats not true!

say you have 15 rollback segments.

you start a query.

while that is running, you have a big transaction that fills up rbs1 (almost).
it commits.

query still running.  rbs2..rbs15 haven't been touched.

Now you have 30 small transactions.  2 each to the 15 rbs's.  The first one in
rbs1 causes it to WRAP around and reuse some of the space of the big
transaction.  the second overwrites a tiny bit more.

unfortunately, your still running query needs the undo generated by big
transaction.  parts of big transaction are gonzo.

ora-1555, even though rbs2..rbs15 havent really been "touched" very much.
Rating: 4
Ok, I should say it clearer December 05, 2003
Reviewer: Olga from Vienna
Yes, that was my understanding. But if I have a very long running query and
nothing in a cursor for loop and a ORA-1555 occurs the only possiblity for me is
to make the rbs bigger. Ok? I understand the read consistency. But

Now I have an other ORA-1555 question:

I start a pl/sql-block:

BEGIN

update prg_log_table
set    program_state = 1, run_begin = sysdate
where  prg_id = 1
;

-- everybody should see that the prg is running
-- and working and nobody should start the same program
-- so I need a commit

commit;

Now some big inserts started and were running for a while.

-- At the end I must "give the program free":

update prg_log_table
set    program_state = 2, run_end = sysdate
where  prg_id = 1
;

And finally:

commit;

END;

At the last update I run into 1555. I could not understand it. Why does Oracle
need the rollback-information for this last update on my log-table?

Excuse me, ask you the same questions you answered hundred times before. But
when it does not seem logical for me in all details I would not understand it
(Perhaps it might make you happier when I tell you that I ordered your new book
some days ago ;-))

And what can I do? I need the first commit because I don't want that anbody else
runs the program while it is running. And with the commit
Followup:
use dbms_application_info to notify external sessions of what you are doing.

you can set 3 columns in v$session
you can set a row in v$session_longops

if you wanted to serialize this process, you would just use dbms_lock (actually
-- your UPDATE is quite enough to make sure that two processes do not run at the
same time!!)

I doubt the 1555 was coming from the update itself -- but it could.  updates
need read consistency to do their reading just like a select.

don't commit, you don't want to commit, use the other features
(dbms_application_info).  If you commit -- and fail in the middle -- then you
have a manual restart needed (you need to manually clean up the row).  Just use
dbms_application_info -- and no worries.
Rating: 5
What if no transaction revisits the block. December 15, 2003
Reviewer: Kamal
Tom,
.....
"When the user commits the transaction Oracle does
NOT go back and revisit these blocks to make the change permanent. It is left
for the next transaction that visits any block affected by the update to 'tidy
up' the block (hence the term 'delayed block cleanout').
 "
.....

What if  the block does not get revisted for some reason for a long time, Then
how will get cleaned out.
Followup:
if no one goes there, no need to clean it out then is there?

if it did not get cleaned out naturally (we clean most blocks out, only really
big transactions won't get them cleaned) it'll just stay that way until its
revisited -- 1 minute, 1 day, 1 week, 1 year, 1 decade in the future.
Rating: 4
Avoiding Block Cleanouts December 30, 2003
Reviewer: Vivek Sharma from Bombay, India
Dear Tom,

Thanks for your knowledge sharing. I would like to know how to minimize this
block cleanouts. Since the session doing the cleanout consumes resources how do
i minimize this. I have a database in which i see redos of around 24-25 mb
generated by those sessions which are only querying the database. I assume, due
to these the performance of the database is poor. What care should be taken
inorder to minimize this. Please clarify ?

At present, I have planned to increase the buffer cache size and will be
reboucing the database tomorrow morning. The database version is 8.1.7.4.

Another reason for increasing the buffer cache is to eliminate cache buffer
chain latch contention. But will this minimize the block cleanouts ?

Thanks and Regards
Vivek
Followup:
don't assume.

prove.

then come back and we can study it futher!

(to minimize block clean outs -- use BULK OPERATIONS on your loads (not slow by
slow -- opps meant row by row processing).  CREATE table as select -- no dirty
blocks.  insert /*+ append */  -- ditto.  direct path loads -- you got it, no
dirty blocks.  use a larger cache (the threshold for cleaning the blocks is 10%
of the buffer cache -- if you have large transactions that do more than that,
not all of the blocks get cleaned)

increasing the buffer cache won't do anything for cbc latching if you have a hot
block(s).
Rating: 4
Minimizing Block Cleanout December 30, 2003
Reviewer: Vivek Sharma from Bombay, India
Dear Tom,

Thanks for your prompt reply.

Many sessions doing block cleanouts and generating at an average 25 MB of redos.
You mean to say it will not degrade the performance of the database. My main
reason of Increasing the Buffer Cache is :

1. Minimize Block Cleanouts
2. Reduce Cache Buffer Chain Latch.

For point no.2, I have read from your site itself that more the number of block
buffers will eliminate the contention on Cache Buffer Chain.
The site address is :
http://asktom.oracle.com/pls/ask/f?p=4950:8:7944620723409784562::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1229436447262,
I know, i need to tune my queries in order to eliminate contention on hot
blocks. So If I increase the number of blocks will it have any impact on Cache
Buffer Chain ?

For delayed cleanouts, I cannot change the big procedures immediately since they
are developed by third party and will require major changes in the procedures. I
have initiated this process of changing the procedures one by one but as a
immediate workaround I have decided to increase the buffer cache.

Regards
Vivek
Followup:
I just made the counter point that IF the cbc latching is due to a hot block(s),
it will matter not if you have 100, 1000, or 1000000 blocks.  It *CAN* reduce
cbc latching IF the contention is for the lists of blocks, not specific blocks
(eg: increasing the number of lists of blocks will do nothing to reduce
contention for the list that contains the hot blocks).

That was what I said in the related note as well -- there is a big "if"
statement in that answer.

"Many sessions doing block cleanouts and generating at an average 25 MB of
redos.
You mean to say it will not degrade the performance of the database."

I mean to say that -- unless you show us that this is causing some performance
hit, there is nothing to look at.
Rating: 4
Unsure of approach to avoid this error for this scenario December 31, 2003
Reviewer: Mark from USA
BANNER
Oracle8i Enterprise Edition Release 8.1.7.3.0 - 64bit Production
PL/SQL Release 8.1.7.3.0 - Production
CORE    8.1.7.0.0    Production
TNS for HPUX: Version 8.1.7.3.0 - Production
NLSRTL Version 3.4.1.0.0 - Production

ACCOUNT REFRESH PROCEDURE
Oracle packaged procedure runs once a day, has a cursor that reads the account
table (1,000,000 rows) and returns accounts which need to be updated from the
mainframe. We expect this cursor to find 80,000 account refresh candidates.
There are 102 columns in the account table, 45 of which may be updated from the
mainframe.

Within the cursor loop we call an Oracle Java SP which communicates with the
mainframe to get updated account values.  We are tied to this method.

Updating the account table directly from Java was too slow (1.3 seconds per
account).  I know this is supposed to be much faster in Oracle9i, but we won't
be upgraded until late 2004 (and it doesn't solve the basic problem we are
having).  The Java program sends the update SQL statement back to the calling
procedure which then does EXECUTE IMMEDIATE to do the update (0.35 seconds per
account).

When this runs the users are also working these accounts via a GUI, and will be
reading and possibly updating the same accounts in the account table (different
columns).  Our SLA is 2 seconds response time at the GUI.  The refresh program
issues a COMMIT for each account, within the loop.

The obvious problem here is the "snapshot too old".  In addition, we'd like to
speed this up further. Do you have any ideas for a better approach?  We are
considering a temp table (or non-temp acting as a temp) to resolve the ORA-01555
but not sure if this would prevent it (I thought I understood this problem but
after reading your website found that it can happen even if the update is on a
different table from that in the cursor loop..?), seperating the "get the stuff
from the mainframe for all accounts" from the "update the account table" piece
instead of "get one, update one"...

Any ideas we can try, much appreciated.
Mark.
Followup:
well, updating the row in java isn't any "slower" per say in java then in plsql.
 it is just SQL after all?

to have a single row update take 0.35 seconds is way too long as well.

You have a lot of slow by slow (woops - meant row by row) processing that you
say "you are tied to".

think about 1,000,000 java sp calls.
and 1,000,000 mainframe calls

1,000,000 of anything takes a long long time.  the largest impact will be to
employ bulk processing where ever possible -- and perhaps parallelize the
process.

So, can you hit the mainframe with more than one session?
(you are using bind variables right????)
Rating: 3
More info... December 31, 2003
Reviewer: Mark from USA
Well it's only 80,000 accounts out of the 1,000,000 that this process will
update... but yes, it's row by row.
Bind vars - I need to check what the Java program is sending back. This isn't my
program, I'm trying to help out.
Stuff I should've mentioned:
We are already running 4 sessions to cut down the overall elapsed time.  The
0.35 - there is some additional processing of other tables within the loop when
we do the update. It's really not bad, but we'd like to do better.  I couldn't
come up with a way to 'bulk process' this...
Followup:
but -- you make 1,000,000 mainframe calls to find these 80k right?

the only way to speed this up will be to bulk it up.  array fetches, array
updates, not slow by slow.

80k is a huge number, 1,000,000 is larger -- doing anything that many times is
going to be slow.  that's what we have to cut down on.

have you tkprofed it
have you dbms_profiled it.
Rating: 2
bulking up December 31, 2003
Reviewer: Mark from USA
No, the cursor returns 80,000 rows from a 1,000,000 row table. For each of the
80,000 we call the Java SP to get 'fresh' data from the mainframe, then we
update the account table.
I have not TKPROF'ed it because I figured we need to rethink the approach in
order to avoid the snapshot too old problem. This is the main problem with this
program - while fixing it I want to see if we can make it go faster too. I
figured it's pointless tuning something that we may rewrite.
If we bulk up, we need to do it in a way that we don't lock any account for more
than a few seconds.
Followup:
if you make it go faster -- you'll help avoid the 1555 as well.

i always start with a good tkprof to see if there is any obvious low hanging
fruit.

but one way to avoid the 1555 would be to

insert into gtt select the 80k rows

and process them from there -- a global temporary table (gtt) in temp doesn't
need consistent reads -- it uses direct reads (since the data is known to be
consistent)
Rating: 5
That is exactly what I needed to know - thanks for the fast input December 31, 2003
Reviewer: Mark from USA
Rating: 4
About the off-line rollback segment January 08, 2004
Reviewer: Sanjaya Balasuriya from Colombo, Sri Lanka
Hi Tom,

I have created 180 rollback segments and I have set max_rollback_segments=180.
All of the rollback segments are public.

But whenever I restart the database only 125 rollback segments are on-line.
This is verified in v$resource_limit as well.

Why are the rest of the segments off-line ?

Thanks in advance.
Followup:

http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76956/rollbak.htm#498
...
Then, when an instance opens a database, it attempts to acquire at least
TRANSACTIONS/ TRANSACTIONS_PER_ROLLBACK_SEGMENT rollback segments to handle the
maximum amount of transactions.
.......

max is the max, the number of actually acquired is a function of your
transactions.
Rating: 5
Got the point January 10, 2004
Reviewer: Sanjaya Balasuriya from Colombo, Sri Lanka
Hi Tom,

Thanks a lot.

Got the point. I have really forgotten "TRANSACTIONS/
TRANSACTIONS_PER_ROLLBACK_SEGMENT".
Rating: 5
very good article February 10, 2004
Reviewer: Ravi Chander Kondoori from INDIA
Its really a good article with indepth explanation.
Rating: 3
unlimited maxextents for rbs February 20, 2004
Reviewer: ana from PA, USA
doc 50380.1 on metalink recommends not setting the MAXEXTENTS value to
UNLIMITED. Could you please explain in simple words why?

Thanks
Followup:
even in LMTS where the default (and in fact only value) is unlimited for
segments -- an exception of 32k is made for RBS's (they are not unlimited, they
are limited to 32k extents)

Has to do with the way these things are managed, big circular buffers.
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96524/b_deprec.htm#635261
"optimal" would have been killer on a DMT managed rbs with thousands of extents.
Rating: 4
Some more clarifications pls March 10, 2004
Reviewer: Zee from Singapore
Thanks for the indepth explanation of rollback segments. Can you give some
solution to the following issue?
We have two batch processes running in UAT and Production. They basically move
data from staging to main tables. On days when the volume of data is high (like
mondays) we get the "Snapshot too old" error only in UAT. I have checked the
rollback segments in both UAT n Production. The optimum size in UAT is 50 MB and
860 MB in Production. Can I safely assume that this is the reason why we get the
problem only in UAT? I know you dont like using optimal while defining rollback
segments, but I dont have much of an option here as I am only a developer and
the DBAs insist on using this feature.
Coming back to my problem, can you have a look at my procedure and suggest how I
can change the code to avoid this problem in UAT? Due to space issues, I am not
at a liberty to change the size of the Rollback Segments.

The procedure is as follows:
***start of code*****

DELETE TPH_FRM_TRD_DATA where trunc(STAMP_ADD_DZ)= trunc( Sysdate );
commit;

INSERT INTO TPH_FRM_TRD_DATA
  (
  tph_frm_trans_cd,
  tph_frm_rec_id_c,
  tph_frm_seq_nbr,
  tph_frm_acct_id_c,
  tph_frm_acct_typ,
  tph_frm_cusip_id_c,
  tph_frm_corr_q,
  tph_frm_rr_q,
  tph_frm_tot_pos,
  tph_frm_stk_splt,
  tph_frm_book_cost,
  tph_frm_div,
  tph_frm_avg_px,
  tph_frm_mkt_value,
  tph_frm_curr_px,
  tph_frm_user_px,
  tph_frm_ai_csh_div,
  tph_frm_dly_sls_crdt,
  tph_frm_prev_day_tot_tkt,
  tph_frm_cum_pnl_dly,
  tph_frm_td_cum_pnl_mtd,
  tph_frm_td_mtd_tot_tkt,
  tph_frm_td_trd_int,
  tph_frm_td_unrlz_pnl_mtd_chng,
  tph_frm_td_rlzd_pnl_mtd_chng,
  tph_frm_td_sls_crdt_mtd,
  tph_frm_persh_chrg_mtd,
  tph_frm_cum_pnl_mtd,
  tph_frm_mtd_tot_tkt,
  tph_frm_trd_int,
  tph_frm_unrlz_pnl_mtd_chg,
  tph_frm_rlzd_pnl_mtd_chg,
  tph_frm_sls_crdt_mtd,
  tph_frm_dte_of_data,
  user_id_add_c,
  stamp_add_dz,
  user_id_updt_c,
  stamp_updt_dz
  )
  SELECT
  TPH_FRM_TRANS_CD,
  TPH_FRM_REC_ID_C,
  TPH_FRM_SEQ_NBR,
  TPH_FRM_ACCT_ID_C,
  TPH_FRM_ACCT_TYP,
  TPH_FRM_CUSIP_ID_C,
  TPH_FRM_CORR_Q,
  TPH_FRM_RR_Q,
  TPH_UTIL_PKG.GET_SIGN((TPH_FRM_TOT_POS)||nvl(TPH_FRM_TOT_POS_SGN,' '))/100000
as TPH_FRM_TOT_POS,
  TPH_FRM_STK_SPLT,
  TPH_UTIL_PKG.GET_SIGN((TPH_FRM_BOOK_COST)||nvl(TPH_FRM_BOOK_COST_SGN,' '))/100
as TPH_FRM_BOOK_COST,
  TPH_FRM_DIV,
  TPH_UTIL_PKG.GET_SIGN((TPH_FRM_AVG_PX)||nvl(TPH_FRM_AVG_PX_SGN,'
'))/1000000000 as TPH_FRM_AVG_PX,
  TPH_UTIL_PKG.GET_SIGN((TPH_FRM_MKT_VALUE)||nvl(TPH_FRM_MKT_VALUE_SGN,' '))/100
as TPH_FRM_MKT_VALUE,
  TPH_UTIL_PKG.GET_SIGN((TPH_FRM_CURR_PX)||nvl(TPH_FRM_CURR_PX_SGN,'
'))/1000000000 as TPH_FRM_CURR_PX,
  TPH_FRM_USER_PX,
  TPH_UTIL_PKG.GET_SIGN((TPH_FRM_AI_CSH_DIV)||nvl(TPH_FRM_AI_CSH_DIV_SGN,'
'))/100 as TPH_FRM_AI_CSH_DIV,
  TPH_UTIL_PKG.GET_SIGN((TPH_FRM_DLY_SLS_CRDT)||nvl(TPH_FRM_DLY_SLS_CRDT_SGN,'
'))/100 as TPH_FRM_DLY_SLS_CRDT,

TPH_UTIL_PKG.GET_SIGN((TPH_FRM_PREV_DAY_TOT_TKT)||nvl(TPH_FRM_PREV_DAY_TOT_TKT_SG
N,' '))/100 as TPH_FRM_PREV_DAY_TOT_TKT,
  TPH_UTIL_PKG.GET_SIGN((TPH_FRM_CUM_PNL_DLY)||nvl(TPH_FRM_CUM_PNL_DLY_SGN,'
'))/100 as TPH_FRM_CUM_PNL_DLY,

TPH_UTIL_PKG.GET_SIGN((TPH_FRM_TD_CUM_PNL_MTD)||nvl(TPH_FRM_TD_CUM_PNL_MTD_SGN,'
'))/100 as TPH_FRM_TD_CUM_PNL_MTD,

TPH_UTIL_PKG.GET_SIGN((TPH_FRM_TD_MTD_TOT_TKT)||nvl(TPH_FRM_TD_MTD_TOT_TKT_SGN,'
'))/100 as TPH_FRM_TD_MTD_TOT_TKT,
  TPH_UTIL_PKG.GET_SIGN((TPH_FRM_TD_TRD_INT)||nvl(TPH_FRM_TD_TRD_INT_SGN,'
'))/100 as TPH_FRM_TD_TRD_INT,

TPH_UTIL_PKG.GET_SIGN((TPH_FRM_TD_UNR_PNL_MTD_CHG)||nvl(TPH_FRM_TD_UNR_PNL_MTD_CH
G_SGN,' '))/100 as TPH_FRM_TD_UNR_PNL_MTD_CHG,

TPH_UTIL_PKG.GET_SIGN((TPH_FRM_TD_RL_PNL_MTD_CHG)||nvl(TPH_FRM_TD_RL_PNL_MTD_CHG_
SGN,' '))/100 as TPH_FRM_TD_RL_PNL_MTD_CHG,

TPH_UTIL_PKG.GET_SIGN((TPH_FRM_TD_SLS_CRDT_MTD)||nvl(TPH_FRM_TD_SLS_CRDT_MTD_SGN,
' '))/100 as TPH_FRM_TD_SLS_CRDT_MTD,

TPH_UTIL_PKG.GET_SIGN((TPH_FRM_PERSH_CHRG_MTD)||nvl(TPH_FRM_PERSH_CHRG_MTD_SGN,'
'))/100 as TPH_FRM_PERSH_CHRG_MTD,
  TPH_UTIL_PKG.GET_SIGN((TPH_FRM_CUM_PNL_MTD)||nvl(TPH_FRM_CUM_PNL_MTD_SGN,'
'))/100 as TPH_FRM_CUM_PNL_MTD,
  TPH_UTIL_PKG.GET_SIGN((TPH_FRM_MTD_TOT_TKT)||nvl(TPH_FRM_MTD_TOT_TKT_SGN,'
'))/100 as TPH_FRM_MTD_TOT_TKT,
  TPH_UTIL_PKG.GET_SIGN((TPH_FRM_TRD_INT)||nvl(TPH_FRM_TRD_INT_SGN,' '))/100 as
TPH_FRM_TRD_INT,

TPH_UTIL_PKG.GET_SIGN((TPH_FRM_UNRLZ_PNL_MTD_CHG)||nvl(TPH_FRM_UNRLZ_PNL_MTD_CHG_
SGN,' '))/100 as TPH_FRM_UNRLZ_PNL_MTD_CHG,

TPH_UTIL_PKG.GET_SIGN((TPH_FRM_RLZD_PNL_MTD_CHG)||nvl(TPH_FRM_RLZD_PNL_MTD_CHG_SG
N,' '))/100 as TPH_FRM_RLZD_PNL_MTD_CHG,
  TPH_UTIL_PKG.GET_SIGN((TPH_FRM_SLS_CRDT_MTD)||nvl(TPH_FRM_SLS_CRDT_MTD_SGN,'
'))/100 as TPH_FRM_SLS_CRDT_MTD,
  TPH_FRM_DTE_OF_DATA,
  USER,
  SYSDATE,
  USER,
  SYSDATE
  FROM TPH_FRM_TRD_DATA_STG;

  COMMIT;
***end of code*****
Thanks in advance.

Z.
Followup:
yes, if uat has significantly smaller rbs's and is getting 1555 (which are the
result of rbs being too small), that would be the cause.

You are being prevented from doing your job.  $5 of disk space is causing your
company $$$$ in lost productivity.  Has anyone pointed that out to anyone?

You could try making the query portion (which is what is getting the 1555)
faster.  I see alot of plsql function calls in there.  Each one adds to the
runtime.

what is the code of "get_sign"
Rating: 4
Thanks for the response Tom! March 11, 2004
Reviewer: Zee from Singapore
For quantity fields, the last character of the input string indicates the sign.
(The data is coming from a third party feed provider.) The code of get_sign is
as follows:

**********start of code**********

v_temp_sign := SUBSTR(i_value,-1);
   v_temp_no   := TO_NUMBER(SUBSTR(i_value,1,length(i_value)-1));
   --
   IF v_temp_sign IN (' ','+') THEN
      RETURN (v_temp_no);
   ELSIF v_temp_sign = '-' THEN
      RETURN (v_temp_no*-1);
   ELSIF v_temp_sign = '{' THEN
      RETURN (v_temp_no*10);
   ELSIF v_temp_sign = '}' THEN
      RETURN (v_temp_no*-10);
   ELSIF v_temp_sign BETWEEN 'A' AND 'I' THEN
      RETURN ((v_temp_no*10)+(ASCII(v_temp_sign)-64));
   ELSIF v_temp_sign BETWEEN 'J' AND 'R' THEN
      RETURN ((v_temp_no*-10)-(ASCII(v_temp_sign)-73));
   END IF;

**********end of code****************

Can you suggest how to modify the previous SQL statement to prevent this error?

Some people have suggested to replace with the "insert into --- select ---"
chunk with a cursor which will fetch all the data from the staging table and
while inserting into the main table, do a commit for every 1000 odd rows. Is
this the right solution?

Please advise.
Followup:
use decode( SUBSTR(i_value,-1),
            ' ', TO_NUMBER(SUBSTR(i_value,1,length(i_value)-1)),
            '+', TO_NUMBER(SUBSTR(i_value,1,length(i_value)-1)),
            '-', -1*TO_NUMBER(SUBSTR(i_value,1,length(i_value)-1)),
            '{', 10*TO_NUMBER(SUBSTR(i_value,1,length(i_value)-1)),
            '}', -10*TO_NUMBER(SUBSTR(i_value,1,length(i_value)-1)),
            case when substr(i_value,-1) between 'A' and 'I'
                 then TO_NUMBER(SUBSTR(i_value,1,length(i_value)-1))*10+.... )
                 else ....
             end )

(or just case -- upto you) instead of plsql.

Only follow that last bit of advice (to incrementally commit) if you want to

a) have this run longer
b) use more resources
c) but most CERTAINLY assure that you will hit the ora-1555

that commit will pretty much guarantee your 1555 will happen, but after running
lots and lots of code, consuming much resources.

And then -- well, and then you are "hosed", cause you have to figure out "how
would I restart this"

no, that commmit every 1,000 is really bad advice.
Rating: 5
Great March 11, 2004
Reviewer: Zee from Singapore
Thanks for your advice on the commit per 1000 rows. I will modify the sql to
avoid the pl/sql calls and test it in UAT.
Thanks again for your help!!
Rating: 4
Some doubts .. March 17, 2004
Reviewer: Jagjeet Singh from INDIA
Hi,

         Using Redhat 8.0 + Oracle 9.2.0
         -------------------------------

Sir, You mentioned three reasons for "Snapshot too Old" in
     your book. I wast just trying to reproduce this error
     with this test.

$ sqlplus /nolog

SQL*Plus: Release 9.2.0.1.0 - Production on Tue Mar 16 11:17:00 2004

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

SQL> conn / as sysdba
Connected.
SQL>
SQL> Create Undo tablespace undo2 datafile '/tmp/undo2.dbf'
  2  size 2m reuse;

Tablespace created.

SQL> alter system set undo_tablespace=undo2;

System altered.

SQL>
SQL> Select file_name,user_blocks from dba_data_files
  2  where file_name = '/tmp/undo2.dbf' ;

FILE_NAME                      USER_BLOCKS
------------------------------ -----------
/tmp/undo2.dbf                         992

--#  We got 900+ blocks for storing undo image...

SQL>
SQL> conn rnd
Enter password:
Connected.
SQL> create table t (x    int,y  char(1800));

Table created.

SQL> insert into t select rownum r,'y' from dict where rownum
  2  < 1000 ;

999 rows created.

SQL> commit;

Commit complete.

SQL> select count(*),
  2  count(distinct(dbms_rowid.rowid_block_number(rowid)))
  3  from t;

  COUNT(*) COUNT(DISTINCT(DBMS_ROWID.ROWID_BLOCK_NUMBER
--------- -------------------------------------------------
999       999

--#  One row in One Block ...

SQL>
SQL> update t set y=y where rownum < 525;
update t set y=y where rownum < 525
*
ERROR at line 1:
ORA-30036: unable to extend segment by 512 in undo tablespace 'UNDO2'

--#  The First Question is ..
--#  previous image for 525 blks are not fitting in
     undo tablespace
--#  with 900+ available blocks .. ????????
--#  How much undo will be genrated for
--#  this statement, exactly 524 blocks or 525+ ...

--#  changing 525 to 480 blocks ...

SQL> c  /525/480
  1* update t set y=y where rownum < 480
SQL> /

479 rows updated.

SQL> Select Used_ublk from v$transaction;

 USED_UBLK
----------
       479   << ----- exactly 479 blocks ...

SQL> commit;

Commit complete.

--#   trying to genrate "Snapshot too old"

SQL>
SQL>
SQL> begin
  2     for i in ( select x,
  3     dbms_rowid.rowid_block_number(rowid) blk from t )
  4          loop
  5
  6        dbms_output.put_line(' -------  '||i.blk);
  7
  8            update t set y=y where rownum < 480 ;
  9                       commit;
 10          end loop;
 11  end;
 12  /
-------  165
-------  166
-------  167
-------  168
-------  169
-------  170
begin
*
ERROR at line 1:
ORA-01555: snapshot too old: rollback segment number 12 with name "_SYSSMU12$"
too small
ORA-06512: at line 2

--# Sir,If Previous image for 525 blocks are not fitting
    in undo tablespace then I think 480 undo blocks will
    consume the 90% of undo tablespace. Then this error
    should come after third record.

--#   Oracle fills undo segments in Round-Robin fashion
--#   My assumptions ..

o Undo Seg   |-s1-|-s2-|-s3-|-s4-|-s5-|-s6-|-s7-|-s8-|-s9-|
o Undo Tabsp |--------------------------------------------|
o First Upd. |11111111111111111111111111111111111---------|
  fill up 90%
o Sec. upd                                       222222222|
             |2222222222222222222222222                   |
o Third upd.                           3333333333333333333|
             | 333333333333333

--# Oracle will try  to read the blocks with "1" update
    previous image.. But third update will totaly overwrite
    the "1" image blocks then this error should come after
    third record ... ????????

--#   change 480 blocks to 100 blocks....
      Ora-1555 after 8 update ...

SQL> ed
Wrote file afiedt.buf

  1  begin
  2     for i in ( select x,
  3     dbms_rowid.rowid_block_number(rowid) blk from t )
  4          loop
  5           dbms_output.put_line(' -------  '||i.blk);
  6               update t set y=y where rownum < 100 ;
  7                       commit;
  8          end loop;
  9* end;
 10  /
-------  165
-------  166
-------  167
-------  168
-------  169
-------  170
-------  171
-------  172
begin
*
ERROR at line 1:
ORA-01555: snapshot too old: rollback segment number 11 with name "_SYSSMU11$"
too small
ORA-06512: at line 2

--##  change 100 to 50
      Ora-1555 after 9 update ...

SQL> ed
Wrote file afiedt.buf

  1  begin
  2          for i in ( select x,
  3      dbms_rowid.rowid_block_number(rowid) blk from t )
  4          loop
  5           dbms_output.put_line(' -------  '||i.blk);
  6            update t set y=y where rownum < 50 ;
  7                       commit;
  8          end loop;
  9* end;
SQL> /
-------  165
-------  166
-------  167
-------  168
-------  169
-------  170
-------  171
-------  172
-------  173
begin
*
ERROR at line 1:
ORA-01555: snapshot too old: rollback segment number 13 with name "_SYSSMU13$"
too small
ORA-06512: at line 2

Thanks,
Js
Followup:
you have an undo tablespace.

it is 900 some blocks "big"

IT has many rollback segments in it.

Each of them is smaller than 900 blocks (much much smaller as the entire 900
some odd blocks are allocated to the undo tablespace and you have N rbs's in
there -- like 2 at least I think and maybe more)

So, you do not have 900 some odd blocks available for a single transaction, but
rather for all concurrent transactions and the biggest a single transaction can
have cannot span the size of the rollback segments created therein.  so that
tells you why the update of all rows failed -- it used MORE undo than any single
rbs in your undo tablespace had space for.
Rating: 5
snapshot too old March 25, 2004
Reviewer: reader from Southborough, MA
The topic has been discussed extremly well.
March 17, 2004 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.
Rating: 5
mani March 17, 2004
Reviewer: Mani from USA
Hi Tom,
I read the post for duplicates but I see examples with
row-column combination duplicates.

In my case the, I need to identify duplicates which can belong to different
columns but same row.

I have a table T with columns C1, C2, C3 and C4.
T1:
row_id    C1    C2    C3    C4
  --      --    --    --    --
   1       1    50     121  789
   2       2    39     50   650
   3       1    21     39   50
   4       77   89     451  455
   5       87   89     898  999

I want to identify duplicate row values with >= 2 row data match.
e.g.
1) for row_id = 2
C2 = 39 and C3 = 50
and in row_id = 3
C3 = 39 and C4 = 50

2) for row_id = 1
C1 = 1 and C2 = 50
and in row_id = 3
C1 = 1 and C4 = 50

In this case 2 rows had 2 values duplicate but they belonged to different
columns.

What is the best way of writing the PL/SQL block or query to  do this?

I know this is very wierd requirement, Please help I have no other choice.

Thanks in advance.
Mani
Followup:
ugh, a cartesian join.  You do understand that every row must be compared to
every row.  If you have 10,000 rows that is 100,000,000 compares.

still want to do it? (we can)
Rating: 5
yes please, I am aware of cartesian March 17, 2004
Reviewer: Mani from USA
Hi Tom,
Infact my actual table has 6 cols, the other has 4.
If you can please show an example with 6 cols , that would be great!. Even if
the solution is not scalable to 4 cols thats ok. I can rewrite for 4 cols
understanding for 6 cols example.
I do not have huge data so cartesian will not affect me that bad, but still I
cannot do this manually , its very error prone and extremely time consuming.

Again I am sorry to ask solutions for wierd requirements like this.

Thanks always
Followup:
ops$tkyte@ORA9IR2> select t1.row_id, t2.row_id
  2    from t t1, t t2
  3 where case when t1.c1 in ( t2.c1, t2.c2, t2.c3, t2.c4 ) then 1 else 0 end +
  4       case when t1.c2 in ( t2.c1, t2.c2, t2.c3, t2.c4 ) then 1 else 0 end +
  5       case when t1.c3 in ( t2.c1, t2.c2, t2.c3, t2.c4 ) then 1 else 0 end +
  6   case when t1.c4 in ( t2.c1, t2.c2, t2.c3, t2.c4 ) then 1 else 0 end > = 2
  7     and t1.row_id < t2.row_id
  8  /

    ROW_ID     ROW_ID
---------- ----------
         1          3
         2          3

to make it for 6 should be easy... just add to the t2.c... list and add two more
"cases"

I tried this with your set of data, 1000, 2000 and 3000 rows and found

1000 = 0.87 seconds
2000 = 3.66 seconds
3000 = 8.42 seconds

as you can see - about 1 second per thousand rows :)

Not a total cartesian join with the "t1.row_id < t2.row_id" in there but...
March 17, 2004 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.
Rating: 5
timestamp question February 25, 2004
Reviewer: reader
Tom, why my dbtimezone and sessiontimezone are different? How do I make the
same? I am in CST. Thanks.

demo@PRACTICE> select dbtimezone from dual;

DBTIME
------
-07:00

demo@PRACTICE> select sessiontimezone from dual;

SESSIONTIMEZONE
-------------------------------------------
-06:00
Followup:
the dbtimezone is set for your server -- check the TZ environment variable if
you are on unix (or use the pretty gui to fix it on windows) to make sure it is
set right.  CST would be -6:00 so it looks like your pc is set right but your
server is not.
Rating: 5
Timestamp question cont.... February 25, 2004
Reviewer: reader
(1) On my windows 2000 laptop, I looked in RtClick on MyComputer --> Advanced
tab on System Properties ---> Environment Variables. I don't find any TZ
variable. How do I set on windows? Thanks.

(2) Also, please see below. What do I see in hour 20 as I don't see 20 in
current_timestamp?
Thanks.

demo@PRACTICE.US.ORACLE.COM> select extract( hour from current_timestamp ) from
dual;

EXTRACT(HOURFROMCURRENT_TIMESTAMP)
----------------------------------
                                20

demo@PRACTICE.US.ORACLE.COM> select current_timestamp from dual;

CURRENT_TIMESTAMP
------------------------------------------------------------25-FEB-04
02.11.21.779000 PM -06:00
Followup:
1) its in the clock utility thingy, usually on the lower right hand side of your
screen.

2) in gmt, it is 20.

2pm is 14, plus 6 is 20.

Purpose EXTRACT extracts and returns the value of a specified datetime field
from a datetime or interval value expression. When you extract a TIMEZONE_REGION
or TIMEZONE_ABBR (abbreviation), the value returned is a string containing the
appropriate time zone name or abbreviation. When you extract any of the other
values, the value returned is in the Gregorian calendar. When extracting from
a datetime with a time zone value, the value returned is in UTC. For a listing
of time zone names and their corresponding abbreviations, query the
V$TIMEZONE_NAMES dynamic performance view.
Rating: 4
timestamp cont February 25, 2004
Reviewer: reader
Tom, I checked the 'clock' thing on my laptop, the time zone is set properly
(GMT -06:00) Central Time. However,

demo@PRACTICE> select dbtimezone, sessiontimezone from dual;

DBTIME SESSIONTIMEZONE
------
---------------------------------------------------------------------------
-07:00 -06:00

Could you please help how I can change DBTIMEZONE to -06:00? Thanks.
Followup:
see the ALTER DATABASE command, someone must have set it at database creation
time.

read the ALTER DATABASE command -- it has important information -- eg: it does
not change existing data, it requires a restart to take effect.
Rating: 3
timestamp in email sent by UTL_SMTP is wrong March 17, 2004
Reviewer: A Reader from USA
Hi Tom,
The timestamp in email sent by UTL_SMTP is 5 hours early than the EST of USA. I
check the following,

SQL> SELECT SESSIONTIMEZONE,DBTIMEZONE FROM DUAL;
SESSIONTIMEZONE    DBTIME
-05:00             -04:00

SQL> select current_timestamp from dual;
CURRENT_TIMESTAMP
---------------------------------------------
17-MAR-04 12.44.13.390000 PM -05:00

The emails will have the right timestamp if I do some change according to a
input in the forum of meatlink.

ALTER SESSION SET TIME_ZONE='US/Eastern';

SQL> select current_timestamp from dual;
CURRENT_TIMESTAMP
------------------------------------------
17-MAR-04 12.40.45.934000 PM US/EASTERN

SQL> SELECT SESSIONTIMEZONE,DBTIMEZONE FROM DUAL;
SESSIONTIMEZONE    DBTIME
US/Eastern         -04:00

I don't know what's difference between US/Eastern and -05:00. I think it's just
different return format of the time zone. What's the actual number of the time
zone US/Eastern? Why does it solve the email problem?

According to Oracle Documenation about sessiontimezone, <Quote>
SESSIONTIMEZONE returns the value of the current session's time zone. The return
type is a time zone offset (a character type in the format '[+|]TZH:TZM') or a
time zone region name, depending on how the user specified the session time zone
value in the most recent ALTER SESSION statement.
<Quote>

Thanks, Tom
Followup:
how are you getting the "date" into the email -- are you putting it there
yourself.
Rating: 3
Timestamp is wrong March 17, 2004
Reviewer: A Reader from USA
Hi tom,
I put it like the following,

utl_smtp.data(SmtpConn, HtmlHead
||'Date: '|| to_char(current_timestamp, 'dd Mon yy hh24:mi:ss TZD')||crlf
|| 'From:' || inSender || crlf
|| 'Subject: ' || inSubject||crlf
|| 'To: ' || inRecip || crlf
|| 'Cc: ' || inCCRecip || crlf
|| 'Bcc: ' || inBCCRecip || crlf || crlf||
inMessage);

Thanks,
Followup:
until you give a TimeZone Designator (tzd) you don't "have one".

consider (forget utl_smtp for a moment...)

ops$tkyte@ORA9IR2> SELECT SESSIONTIMEZONE,DBTIMEZONE FROM DUAL;

SESSIONTIMEZONE
DBTIME
---------------------------------------------------------------------------
------
-05:00
-07:00

ops$tkyte@ORA9IR2> select to_char(current_timestamp, 'dd Mon yy hh24:mi:ss TZD')
from dual;

TO_CHAR(CURRENT_TIMESTAMP
-------------------------
17 Mar 04 18:25:49

ops$tkyte@ORA9IR2> ALTER SESSION SET TIME_ZONE='US/Eastern';

Session altered.

ops$tkyte@ORA9IR2> select to_char(current_timestamp, 'dd Mon yy hh24:mi:ss TZD')
from dual;

TO_CHAR(CURRENT_TIMESTAMP
-------------------------
17 Mar 04 18:26:03 EST

So, you see there is no TZD in the first one, hence -- smtp just gets the
date/time and assumes "GMT".  By giving a named timezone, you give smtp enough
data to "know what time it is"
Rating: 2
Timezone- eastern/us vs. -5:00 April 06, 2004
Reviewer: Patrick Dickey from Denver, CO USA
In response to the question about what the difference is in these two timezones:
utl_smtp does NOT handle -05:00 as a timezone- it'll strip it out. It must be
-0500 (no colon). One of my guys just noticed this in one of our notification
systems.
March 17, 2004 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.
Rating: 3
PROTOCOL=BEQ March 17, 2004
Reviewer: A reader from NJ, USA
Tom,

My understanding on PROTOCOL=BEQ is as follows :
It means it uses a bequeath protocol which will happen always when the client
process is running on the same machine as the database server.

1. In your example, however, the server process has been spawned by the listener
(LOCAL=NO) and still the PROTOCOL=BEQ. Pls clarify
2. Under what situation we will have LOCAL=YES and still have PROTOCOL not equal
to BEQ.
3.What is the meaning of the following :

a. The relation between PROTOCOL and LOCAL
b. Meaning of PROTOCOL=BEQ and Local=NO
c. Meaning of PROTOCOL=BEQ and LOCAL=YES
d. Meaning of PROTOCOL={TCP|xxx} and LOCAL={YES|NO}
e. Meaning of Network Latency and interprocess communication

Thanks much
Followup:
the local=no with beq is "a false positive", it doesn't really happen (there is
an open but very low priority issue with that - it has to do with dynamic
service registration and such).  so, ignore those -- they are not really remote
bequeaths.

e) google it, but network latency is a measure of round trip times (how fast can
you converse -- "ping").  IPC is interprocess communication -- two processes
"talking" to eachother.
Rating: 3
LOCAL=NO PROTOCOL=BEQ March 17, 2004
Reviewer: A reader from NJ, USA
Tom,

1. So in one of the example above that shows the LOCAL=YES vs LOCAL=NO, you have
LOCAL=NO and PROTOCOL=BEQ and I understand that from your previous reply it is
just an example that insists more on difference between LOCAL=NO vs LOCAL=YES
rather than the specifics of dynamic registration etc. Is that true?
2. So from the above reply, is it a good conclusion that LOCAL=NO,PROTOCOL=BEQ
will happen very very rarely (in scenarios such as dynamic registration as you
mentioned)
Followup:
1) yes - it is due to a pmon registering with the listener, blah blah blah -- i
didn't really "follow" the entire thread of the bug (there are a couple of
permutations that get it actually)

2) well dynamic registration is "the default" these days.... so....
March 17, 2004 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
Rating: 5
excellent... January 20, 2004
Reviewer: James Blanding from Kingston, RI USA
> define gname=idle

> at the top -- after the set termout off, before the query.

very cool.  this helps prevent confusion when sql*plus fails to connect to the
instance you specify.  my prompt variation looks like:

instance_name(host_name) >
or
NOT CONNECTED >

column sqlprompt_col new_value sqlprompt_value
set termout off
define sqlprompt_value='NOT CONNECTED'
select instance_name || '(' || host_name || ')'
  as sqlprompt_col
from v$instance;
set termout on
set sqlprompt '&sqlprompt_value > '
Rating: 4
Check out 10g! March 04, 2004
Reviewer: Mark J. Bobak from Ann Arbor, MI
oracle@dvlpq1 [/home/admin/oracle]. oraenv
ORACLE_SID = [ora10g] ?
oracle@dvlpq1 [/home/admin/oracle]sqlplus "/ as sysdba"

SQL*Plus: Release 10.1.0.2.0 - Production on Thu Mar 4 13:45:24 2004

Copyright (c) 1982, 2004, Oracle.  All rights reserved.

Connected to:
Oracle Database 10g Enterprise Edition Release 10.1.0.2.0 - 64bit Production
With the Partitioning, OLAP and Data Mining options

SYS@ora10g>connect adds@dvl1
Enter password:
Connected.
ADDS@dvl1>connect adds@pep1
Enter password:
Connected.
ADDS@pep1>connect system@prd1
Enter password:
Connected.
SYSTEM@prd1>show sqlprompt
sqlprompt "_user"@"_connect_identifier>"
SYSTEM@prd1>

Cool, eh?  Seems to me, this should have been a feature
of SQL*Plus years ago!
Followup:
you can even "spool filename append" in 10g   ;)
Rating: 5
Spool append? March 05, 2004
Reviewer: Mark J. Bobak from Belleville, MI
Excellent, I did not know that.  Now, the real bummer is
that we won't be on 10g for any substantial systems for
at least 2 years! ;-)
Rating: 3
Removing feedback info when connecting within SQL*Plus March 17, 2004
Reviewer: ht from California
Tom,
I'm using a shell script to connect to an instance and setting a shell env
variable.

I used to connect with "sqlplus -s user/password" but am now connecting within a
sql script to prevent command line password sniffing.

I can use awk to get rid of the "Connected." string but is there a SQL*Plus
setting that will allow me to not display the connection confirmation?

conn scott/tiger;
Connected.

TIA
Followup:
if you are using a script, set termout off will surpress the display.
Rating: 2
set termout off doesn't suppress the "Connected." display within a script. March 17, 2004
Reviewer: ht from California
Tom,
It doesn't seem that "set termout off" suppresses the display when connecting
within the shell script.  I'm using 2 connections (the first one uses
scott/tiger but the second one is the id/password that I don't want snooped on
the command line).

Besides using awk to remove "Connected.", is there another solution?

$ versiontest1.sh > versiontest1.log
$ cat versiontest1.sh
#!/bin/ksh

function check_db_version {
        DBVERSION=$(
                sqlplus -s scott/tiger <<- eof8
                set linesize 300;
                set echo on;
                set heading off;
                set feedback off;
                set verify off;
                set termout off;
                connect user2/pass2
                select substr(&_O_RELEASE,1,1) from dual;
                exit;
                eof1
                )
}
check_db_version
echo "xyz:$DBVERSION:123"
exit
        if [ $DBVERSION = "7" ];
        then
                echo v7 db
        else
                echo not a v7 db
        fi
# ---------------------------------------------------------------- #

$ cat versiontest1.log
xyz:Connected.

9:123
$
Followup:
it works if SQLPLUS is running a script.

so, either put the sets and all in a script like /tmp/$$.sql and run that or

select 'LOOK_FOR_ME' || substr( .... ) from dual;

and "grep" that in the sqlplus command...  I'd offer to script it -- but your
script doesn't see to run "as is" on linux?  (and I don't do ksh...)
March 17, 2004 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.
Rating: 5
February 27, 2004
Reviewer: A reader
I need all item_code, summary for specific month if it shipped, 0 if item wasn't
shipped, and month.
This select sta-t gives me items which were shipped.
select i.item_code,sum(a.itm_shipped) as shipped, to_char(b.ship_date,'mon/yy')
as monthly
from inventory i,order_items a, orders b
where a.order_id=b.order_id and
i.account_id=b.account_id   and
      i.inventory_id=a.inventory_id(+)
               and ship_date >= to_date('1/1/2004', 'mm/dd/yy')
               and ship_date <= to_date('3/1/2004', 'mm/dd/yy')
group by i.item_code,to_char(b.ship_date,'mon/yy');

item_code shipped   month
A-100        34    jan/04
A-101        23    jan/04
A-101        33    feb/04
A-234        66    feb/04
Items A-222, A-123 weren't shipped and they are not in this query.
I have to show:
item_code   jan/04  feb/04
A-100        34       0
A-101        23       33
A-123         0       0
A-222         0       0
A-234         0

thanks.
Followup:
sta-t?  did we lose some bytes there? anyway....

you have half way outer joins.  Anytime you see the construct:

where t1.c  = t2.c1(+)
  and t2.c2 = <anything, literal, another column>

you know you either

a) made a horrible mistake
b) are just making the optimizer not able to do its job.

You have:

where a.order_id=b.order_id
      i.inventory_id=a.inventory_id(+)

well, b.order_id could never ever be equal to a.order_id if we "made up a".

So, it seems you want to join orders to order_items, sum up the itm_shipped for
all inventory_ids in the period in question and then outer from inventory to
that result.

select i.item_code, sum(x.shipped), x.monthly
  from inventory i,
       (select a.inventory_id,
               a.itm_shipped shipped,
               trunc(b.ship_date,'mm') monthly
          from order_items a,
               orders b
         where a.order_id = b.order_id
           and b.ship_date between to_date('1/1/2004', 'mm/dd/yy')
                               and to_date('3/1/2004', 'mm/dd/yy')) X
 where i.inventory_id = x.inventory_id(+)
 group by i.item_code, x.monthly;

(not run, just coded off the cuff, please understand the concept -- join
order_items to orders to get inventory_id, shipped, and the month -- then
outerjoin that to inventory and aggregate as needed -- and verify we got it
right)
Rating: 5
February 27, 2004
Reviewer: A reader
Tom, thanks a lot. It works great.
But I need I suppose, pivot:
item_code   jan/04  feb/04
A-100        34       0
A-101        23       33
A-123         0       0
A-222         0       0
A-234         0       66
Followup:
ok, so pivot it then?

select i.item_code,
       sum(decode(x.monthly, jan ), x.shipped ),
       sum(decode(x.monthly, feb ), x.shipped ),
       sum(decode(x.monthly, mar ), x.shipped )
  from inventory i,
       (select a.inventory_id,
               a.itm_shipped shipped,
               trunc(b.ship_date,'mm') monthly
          from order_items a,
               orders b
         where a.order_id = b.order_id
           and b.ship_date between to_date('1/1/2004', 'mm/dd/yy')
                               and to_date('3/1/2004', 'mm/dd/yy')) X
 where i.inventory_id = x.inventory_id(+)
 group by i.item_code;
Rating: 5
February 27, 2004
Reviewer: A reader
I do not know dates, let's say ,
fromDate and ToDate.

Thanks.
Followup:
then you do not know the number of columns and pivoting will be impossible in
SQL, you'll have to do it in the client OR once you are given the to/from --
dynamically GENERATE the sql.

SQL wants to have "n" columns where N is a constant.  If you don't know the
to/from, you cannot pivot in this case (not until you do know anyway)
Rating: 5
Query or a Function? March 17, 2004
Reviewer: J from Vancouver
Hello Tom!

For your consideration (v9.2.0.4.0 - 64bit):

create table j_test1
  (col_a1 varchar2(5),
   col_a2 varchar2(5),
   col_a3 varchar2(5),
   gimme  varchar2(5));

insert into j_test1 values ('X',null,null,'Foo1');

insert into j_test1 values ('X','Y',null,'Foo2');

insert into j_test1 values ('X','Y','Z','Foo3');

insert into j_test1 values (null,'Y',null,'Foo4');

insert into j_test1 values (null,'Y','Z','Foo5');

insert into j_test1 values (null,null,'Z','Foo6');

insert into j_test1 values (null,null,null,'Foo7');

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

So my task is to create a function such that:

  if (parm1 = col_a1 and
      parm2 = col_a2 and
      parm3 = col_a3), return Foo3
  elsif
     (parm1 = col_a1 and
      parm2 = col_a2), return Foo2
  elsif
     (parm1 = col_a2 and -- notice the flip!
      parm2 = col_a1), return Foo2
  elsif
     (parm1 = col_a1), return Foo1
  elsif
     (parm2 = col_a2), return Foo4
  elsif
     (parm3 = col_a3), return Foo6
  else return Foo7
  end if;

Note a few gotcha's:
1) Since the users sometimes confuse col_a1 and col_a2,
   try finding a match via a parameter flip for the
   third matching criteria
2) According to theory (ha), Foo5 should never occur.

Q: Is this "analytic"ical (a single SQL query),
   or a "function" sort of thing (w/CASE)?

I've tried the former (love learning analytics),
bugged my teammates, but we can't figure out a
"single query" that would work.
Well, I'm stubborn, of course, and can't quit
on this until I've read your opinion/advice.

Many thanks!
- J
Followup:
It is a case statement

  case
       when (parm1 = col_a1 and ... ) then foo3
       when ...
       when ...
       else foo7
   end

case can be used in sql or plsql
March 17, 2004 Michael -- Thanks for the question regarding "GET and POST method using MOD PL/SQL", version 8.1.7
You Asked
Hi Tom

I came across documentation on MOD PL/SQL talking about various method of http
requests like GET and POST.

The format are:

GET:
http://host:port/pls/DAD/procedure?a=v&b=1
POST (all in one line):
http://host:port/pls/DAD/procedure,
POST data="a=v&b=1"
--the POST request end here--

Using htf.anchor, I have the GET request working. However, when I
tried the POST request I got a page not found problem.

For GET request, it works and the address bar shows:
http://myserver/pls/xxx/proc_test1?p_seed=1&p_validate=Y
The code is:

htf.anchor(curl => 'myacc.proc_test1?p_seed=1' ||'&'||'p_validate=Y',...

For POST request, it has error http 400 : bad request!
and the address bar shows:
http://myserver/pls/xxx/proc_test1,%20POST%20data=
The code is:

htf.anchor(curl => 'proc_test1, POST data="p_seed=1'
                    ||'&'||'p_validate=Y' || '"',...

Could you please point out where I might have gone wrong?

Thanks.
and we said...
anchors in HTML only support GET, period.

it is HTML, not mod_plsql, preventing you here.  anchors (links, urls) only
support GET.

FORMS in HTML can do POSTing.
Review & Followup

Rating: 4
Thanks March 18, 2004
Reviewer: Michael from Australia
Thanks for pointing that out.


Listing

Recent

Archives

Advanced Search

FAQ by Category

Most Popular

Hot Articles

Your Questions

[ Listing ]

RSS Feed

Subject

Article

Last Updated (East coast time)

Pipelined functions

article only

Mar 18, 2004 07:08 am

rank order

article only

Mar 18, 2004 07:17 am

native dynamic sql

article only

Mar 18, 2004 07:18 am

Rollback Segments - Unable to extend

article only

Mar 18, 2004 07:19 am

8i to 8.0.5 - Subqueries

article only

Mar 18, 2004 07:23 am

Nested Tables - accessing objects returned from a subquery

article only

Mar 18, 2004 07:28 am

efficient upsert

article only

Mar 18, 2004 08:19 am

All Parent - Child tables in the database

article only

Mar 18, 2004 08:25 am

Grants to Users

article only

Mar 18, 2004 09:40 am

rownum

article only

Mar 18, 2004 09:50 am

Case and Decode and others

article only

Mar 18, 2004 10:08 am

Question on Using Ref Cursor

article only

Mar 18, 2004 10:09 am

DBMS_PROFILER

article only

Mar 18, 2004 10:26 am

Update statement

article only

Mar 18, 2004 03:09 pm

Problem to perform TSPITR

article only

Mar 18, 2004 03:17 pm

sql to get data from flat file-- this is mind blowing

article only

Mar 18, 2004 03:18 pm

loading a EBCDIC file with no line termination

article only

Mar 18, 2004 03:22 pm

Indexing at very Large Table

article only

Mar 18, 2004 03:30 pm

Number Datatype

article only

Mar 18, 2004 05:02 pm

RMAN backup strategy!.

article only

Mar 18, 2004 05:03 pm

analyzing table

article only

Mar 18, 2004 05:07 pm

Refreshing Materialized Views

article only

Mar 18, 2004 06:12 pm

Inline Views Vs. Materialized Views

article only

Mar 18, 2004 06:16 pm

garbage cleanup, sql query

article only

Mar 18, 2004 07:33 pm

Instrumentation overhead

article only

Mar 19, 2004 08:10 am

getting my row from v$session

article only

Mar 19, 2004 08:59 am

Differrent Character set and NLS

article only

Mar 19, 2004 09:00 am

ROLLUP OR CUBE

article only

Mar 19, 2004 09:22 am

VARRAY

article only

Mar 19, 2004 09:24 am

difference between count(1) and count(*)

article only

Mar 19, 2004 09:28 am

date query

article only

Mar 19, 2004 09:54 am

Cluster on One Table

article only

Mar 19, 2004 01:55 pm

Use of Resumable Space Allocation

article only

Mar 19, 2004 02:04 pm

BLOBS etc

article only

Mar 19, 2004 02:18 pm

Date Validation with PL/SQL

article only

Mar 19, 2004 02:20 pm

Performance in Parallel query

article only

Mar 19, 2004 02:26 pm

How to improve this query

article only

Mar 19, 2004 03:07 pm

How to avoid the full table scan

article only

Mar 19, 2004 03:18 pm

Oracle 9i generates statistics automatically?

article only

Mar 20, 2004 09:45 am

Char Vs Varchar

article only

Mar 20, 2004 09:46 am

Analytics question

article only

Mar 20, 2004 09:47 am

Datatype coversion

article only

Mar 20, 2004 09:56 am

Running procedures dynamically.

article only

Mar 20, 2004 10:02 am

STRAGG

article only

Mar 20, 2004 10:04 am

Import/Export Views

article only

Mar 20, 2004 10:07 am

Outer Join

article only

Mar 21, 2004 09:39 am

ordering a connect by

article only

Mar 21, 2004 09:43 am

extract DDL

article only

Mar 21, 2004 09:46 am

Language Issue

article only

Mar 21, 2004 09:47 am

How Oracle Warehouse Builder compares to Informatica?

article only

Mar 21, 2004 09:52 am

Performance and Oracle Sequences

article only

Mar 21, 2004 09:59 am

Mass Delete

article only

Mar 21, 2004 10:03 am

IOT and more

article only

Mar 21, 2004 07:16 pm

Log off from mod pl/sql app

article only

Mar 22, 2004 06:58 am

Network Configuration Parameters

article only

Mar 22, 2004 07:10 am

tkprof output

article only

Mar 22, 2004 07:11 am

9i dynamic SGA, pre-allocated?

article only

Mar 22, 2004 07:12 am

Updating a table from another table

article only

Mar 22, 2004 07:16 am

moving tables across tablespaces

article only

Mar 22, 2004 07:17 am

Error using the example

article only

Mar 22, 2004 08:54 am

Problem using updateXML.

article only

Mar 22, 2004 09:19 am

utl_tcp usage

article only

Mar 22, 2004 11:50 am

Database Links and Test Databases

article only

Mar 22, 2004 12:35 pm

Oracle's Object Relational Model

article only

Mar 22, 2004 03:38 pm

export using QUERY

article only

Mar 22, 2004 04:46 pm

About Temporary tables

article only

Mar 22, 2004 08:48 pm

archivelog mode - some questions

article only

Mar 22, 2004 09:28 pm

Parse CPU to Parse Elapsed

article only

Mar 23, 2004 06:58 am

ARE INDEXES USEFUL FOR DELETES

article only

Mar 23, 2004 07:05 am

User Call

article only

Mar 23, 2004 07:12 am

how many days in a year

article only

Mar 23, 2004 07:15 am

Your favorite 10g features

article only

Mar 23, 2004 07:16 am

Calculating Size of SGA

article only

Mar 23, 2004 07:20 am

Using IN

article only

Mar 23, 2004 10:34 am

Login.sql and partitions nad & ,&&

article only

Mar 23, 2004 05:25 pm

Retrieve duplicate values of a field in a table......

article only

Mar 23, 2004 05:25 pm

Analytical Functions

article only

Mar 23, 2004 05:26 pm

Explain Plan

article only

Mar 23, 2004 05:28 pm

Mized size of packages causes inefficiency?

article only

Mar 23, 2004 05:31 pm

Performance comparison between SQL and PL/SQL stored procedures

article only

Mar 23, 2004 05:36 pm

correlated update

article only

Mar 24, 2004 07:59 am

SELECT FOR UPDATE cursors

article only

Mar 24, 2004 08:03 am

Sending e-mail! -- Oracle 8i specific response

article only

Mar 24, 2004 08:11 am

PL/SQL beginner book

article only

Mar 24, 2004 08:13 am

varying elements in IN list

article only

Mar 24, 2004 08:15 am

pivot a result set

article only

Mar 24, 2004 08:19 am

histogram

article only

Mar 24, 2004 08:27 am

Multiple DBWn

article only

Mar 24, 2004 08:30 am

miss of cache buffer chain

article only

Mar 24, 2004 08:33 am

Database Locks

article only

Mar 24, 2004 08:35 am

Cross Matrix

article only

Mar 24, 2004 08:36 am

High Availability questions

article only

Mar 24, 2004 08:53 am

Schema updates in 24/7 environment

article only

Mar 24, 2004 08:58 am

SQL plus session not getting updated on saving changes in editor.

article only

Mar 24, 2004 09:00 am

varrays, nested tables, and pl/sql records, and pl/sql tables, and ref cursor

article only

Mar 24, 2004 09:00 am

Calculating an exact percentile value in 8i

article only

Mar 24, 2004 09:02 am

DDL triggers in 8.1.7

article only

Mar 24, 2004 09:03 am

Constraints

article only

Mar 24, 2004 09:05 am

UNDO RETENTION

article only

Mar 24, 2004 09:07 am

Distributed Option in Oracle 7

article only

Mar 24, 2004 09:32 am

8i Automated Standby Database

article only

Mar 24, 2004 09:37 am

Hints When to use

article only

Mar 24, 2004 09:38 am

Connect sys/xxxxx does not work in 9i

article only

Mar 24, 2004 09:40 am

How can I get information of success or failure for individual record during bulk update and insert while using Pro*C

article only

Mar 24, 2004 09:42 am

Materialized View on Table Without Primary Key

article only

Mar 24, 2004 10:43 am

sql loader

article only

Mar 24, 2004 11:22 am

How can I do a variable "in list"

article only

Mar 24, 2004 01:17 pm

Greater / less than in Decode

article only

Mar 24, 2004 01:21 pm

Bind Variable values available to Optimizer in Oracle9i

article only

Mar 24, 2004 01:23 pm

db cloning -- what is it and why

article only

Mar 24, 2004 01:24 pm

creating text files from the clob column in table

article only

Mar 24, 2004 01:25 pm

Horizontal and Vertical subsets in replication

article only

Mar 24, 2004 01:26 pm

Forms on application server

article only

Mar 24, 2004 02:17 pm

Index Not used on a null column

article only

Mar 24, 2004 04:55 pm

SQL*Net data Security

article only

Mar 24, 2004 06:30 pm

Confusion in the interpretation of V$LIBRARYCACHE columns

article only

Mar 24, 2004 06:41 pm

Cascade Delete

article only

Mar 24, 2004 06:46 pm

row(s) 1 - 117 of 117

March 18, 2004 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!
Rating: 4
Ok February 24, 2004
Reviewer: Kate from Cork,Ireland
Dear Tom,
How we can use parallel_enable clause of pipelined functions
?Do you have any code for that?Please do reply.
Bye!
Followup:
it is just part of the "declare", see the syntax diagram in the SQL ref guide.
Rating: 5
March 03, 2004
Reviewer: A reader
The reason for this bug
select cast( column_value as varchar2(50)) from
table(daz.fut_string_to_table( 'a,b,c,d',',' ) );

was because some parameter was wrong , I update the old init.ora file this
database had, with this paramters and this works perfectly. NOT CAST NEEDED

DB_BLOCK_SIZE              = 8192
COMPATIBLE                 = 9.2.0.1.0
DB_NAME                    = XXX
INSTANCE_NAME              = XXX
REMOTE_LOGIN_PASSWORDFILE  = SHARED
CURSOR_SPACE_FOR_TIME      = FALSE # NO ELIMINA CURSORES EN SESION, No Habilitar
en Developer
#O7_DICTIONARY_ACCESSIBILITY=TRUE
###########################################
# CACHE AND I/O
###########################################
DB_CACHE_SIZE              =      60M
DB_KEEP_CACHE_SIZE         =      25M
DB_RECYCLE_CACHE_SIZE      =      10M
DB_FILE_MULTIBLOCK_READ_COUNT = 16
# DB_CACHE_ADVICE = ON/READY/OFF  # HABILITAR PARA OBTENER ESTADÍSTICAS DEL USO
DEL CACHE.
###########################################
# POOLS
###########################################
WORKAREA_SIZE_POLICY = AUTO
PGA_AGGREGATE_TARGET = 100M
#JAVA_POOL_SIZE
  #JAVA_POOL_SIZE    =  1000000 # SI NO UTILIZA JAVA
   JAVA_POOL_SIZE    = 33000000 # SI USA O SI SE ESTA INSTALANDO
LARGE_POOL_SIZE      =  8000000
SHARED_POOL_SIZE     = 50000000
# SHARED_POOL_RESERVED_SIZE  = 10000000
# PRE_PAGE_SGA = TRUE # OPTIMIZA SI HAY MEMORIA PERMITE ALCANZAR PUNTO OPTIMO
MAS RAPIDO AUNQUE DESACELERA INICIALMENTE
# LOCK_SGA = TRUE  # COLOCA TODO EL SGA EN MEMORIA, no funciona en NT
###########################################
# ARCHIVELOGFILES
###########################################
LOG_BUFFER              = 1000000 # CANTIDAD DE DATOS QUE PUEDEN GENERARSE
DURANTE 9 SEGUNDOS SIN COMMIT O SAVEPOINT
LOG_ARCHIVE_START       = TRUE
LOG_ARCHIVE_DEST        = D:\XXXARCHLOG
LOG_CHECKPOINT_INTERVAL = 0
FAST_START_MTTR_TARGET  = 300
###########################################
# CURSORS AND LIBRARY CACHE
###########################################
OPEN_CURSORS=800
SESSION_CACHED_CURSORS = 1000
###########################################
# UNDO MANAGEMENT
###########################################
UNDO_MANAGEMENT = AUTO
UNDO_TABLESPACE = TBL_UNDO
UNDO_RETENTION  = 56000
###########################################
# DIAGNOSTICS AND STATISTICS
###########################################
BACKGROUND_DUMP_DEST = E:\ORAXXX\TRACE\BACKGROUND
USER_DUMP_DEST       = E:\ORAXXX\TRACE\USER
CORE_DUMP_DEST       = E:\ORAXXX\TRACE\CDUMP
###########################################
# FILE CONFIGURATION
###########################################
CONTROL_FILES = ("E:\ORAXXX\CONTROLFILES\CTL_XXX01.CTL",
"E:\ORAXXX\CONTROLFILES\CTL_XXX02.CTL",
                 "D:\ORAXXX\CONTROLFILES\CTL_XXX01.CTL",
"D:\ORAXXX\CONTROLFILES\CTL_XXX02.CTL")
READ_ONLY_OPEN_DELAYED = FALSE   #TRUE, accede READONLY tablespaces solo al leer
datos ahi
DB_FILES=15
###########################################
# EXTERNAL FILES
###########################################
UTL_FILE_DIR           = *
SESSION_MAX_OPEN_FILES = 30
###########################################
# NÚMERO MÁXIMO DE ROLES PERMITIDOS PARA BD
###########################################
MAX_ENABLED_ROLES = 70
LICENSE_MAX_USERS = 70
PROCESSES         = 50             # 5 + (# USUARIOS CONCURRENTES*2
REPORT/DEVELOPER)
## SESSION        = DERIVED (1.1 * PROCESSES + 5)
###########################################
# JOBS
###########################################
JOB_QUEUE_PROCESSES = 1
###########################################
# DISTRIBUTED
###########################################
OPEN_LINKS = 4
DB_DOMAIN  = ""
REMOTE_DEPENDENCIES_MODE  = SIGNATURE
###########################################
# PARA FUNCTION BASED INDEXES
###########################################
QUERY_REWRITE_ENABLED    = TRUE
QUERY_REWRITE_INTEGRITY  = ENFORCED
###########################################
# CBO
###########################################
optimizer_max_permutations=2500
OPTIMIZER_INDEX_COST_ADJ  =  10 # Cambiar a 50 en datawarehousing
OPTIMIZER_INDEX_CACHING   =  90 # No cambiar, posibilidad de encontrar indice en
cache
CURSOR_SHARING             = EXACT # SIMILAR OR FORCE, quita performance, se
utilizan como auxiliar para mal binding
###########################################
# STATISTICS
###########################################
#TIMED_STATISTICS     = TRUE
#STATISTICS_LEVEL     = ALL
###########################################
# AUDIT
###########################################
AUDIT_SYS_OPERATIONS = TRUE
AUDIT_TRAIL = DB
Rating: 5
March 03, 2004
Reviewer: A reader
Any way there is a bug fixed apparentluy in 9.2.0.4
(Error): ORA-00600: c?digo de error interno, argumentos:
[kokeeafi1],
> > > [2], [2], [], [], [], [], []
> > >
> > > I get when i do a
> > > SELECT * FROM TABLE ( PCK_CCO.FUT_CCO_IMPUTACION( 'CCB',3000, NULL ) )
> > >
Rating: 5
Solved Function table without pipelined March 03, 2004
Reviewer: A reader
I solved the problem,I hope not to find a another bug.

CREATE OR REPLACE TYPE ctb.tyo_cco_imputacion AS OBJECT(
nTotal NUMBER,
cCCO1 VARCHAR2(9),
cCCO2 VARCHAR2(9),
cCCO3 VARCHAR2(9),
cCCO4 VARCHAR2(9),
cCCO5 VARCHAR2(9),
nMonto NUMBER(16,2))
/

CREATE OR REPLACE TYPE ctb.typ_cco_imputacion AS TABLE OF
CTB.TYO_CCO_IMPUTACION;
/

FUNCTION TEST
return TYP_CCO_IMPUTACION
as
l_data TYP_CCO_IMPUTACION := TYP_CCO_IMPUTACION();
begin
for i in 1..10
loop
l_data.extend;
l_data(l_data.count) :=
TYO_CCO_IMPUTACION( i,i,i,i,i,i,i );
end loop;
return l_data;
end;

select * from the ( select cast( test as TYP_CCO_IMPUTACION ) from dual )
Rating: 3
Help March 17, 2004
Reviewer: Ram from Bangalore,India
Hi Tom,
In the "create function " statement,I came across the clause "Return
datatype pipelined or Aggregate using "Implementation clause " "etc.
 What is the use of "aggregate" and Implementation Clause?Do you have a
simple example which can make it obvious?
Please do write a followup.
Bye!
Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2196162600402
it is for making user defined aggregates.
March 18, 2004 Larry -- Thanks for the question regarding "rank order", version 9.2.0
You Asked
Thanks for the great site Tom.  I have an easy one for you...
I have the following test data:

create table test_sort (t_nm varchar2(5), p_nm varchar2(5), id number);
insert into test_sort values('01t','02P',36);
insert into test_sort values('01t','01p',37);
insert into test_sort values('03t','02P',38);
insert into test_sort values('03t','01p',39);
insert into test_sort values('02T','02P',40);
insert into test_sort values('02T','01p',41);
commit;
select * from test_sort;

T_NM       P_NM            ID
---------- --------------- --
01t        02P             36
01t        01p             37
03t        02P             38
03t        01p             39
02T        02P             40
02T        01p             41

Would like to run a query that produces data in the following order:

T_NM       P_NM            ID
---------- --------------- --
01t        01p             37
01t        02P             36
03t        01p             39
03t        02P             38
02T        01p             41
02T        02P             40

Basically keeping the 1st column order and grouping and resorting the second
column.  Tried various subqueries and analytic funcs
(rank,dense_rank,row_number).  Seems so simple, but I can't seem to nail it.
Any ideas?

Thank you,
Larry
and we said...
that first "column" HAS NO ORDER.

If you think rows come out in any sort of "order of insertion" -- you have sort
of missed the point as far as RDBMS's go.

Sorry -- but there is nothing related to "order of insertion".

consider:

create table test_sort (t_nm varchar2(5), p_nm varchar2(5), id number, x
char(2000), y char(2000), z
char(2000) );
insert into test_sort values('01t','02P',36,null,null,null);
insert into test_sort values('01t','01p',37,'x','y','z');
insert into test_sort values('03t','02P',38,'x','y','z');
insert into test_sort values('03t','01p',39,'x','y','z');
insert into test_sort values('02T','02P',40,null,'y','z');
insert into test_sort values('02T','01p',41,null,'y','z');
commit;

ops$tkyte@ORA9IR2> select t_nm, p_nm, id from test_sort;

T_NM  P_NM          ID
----- ----- ----------
02T   01p           41
01t   02P           36
01t   01p           37
03t   02P           38
03t   01p           39
02T   02P           40

6 rows selected.

Hmm, now what??

(hint 9.2, using ASSM, 8k blocks........)

So, no, there is no way on earth to accomplish your goal since it seems
predicated on an act of "randomness"!!

You'd need to assign some column upon insert that says "order me like this
please"
Review & Followup

Rating: 3
March 17, 2004
Reviewer: Larry H from Emeryville, CA USA
not sure i follow.  there was no supposition of resident order in the table. so
i'm not sure *why* you demonstrated how straight fetches could be in a different
order than insertion. in my test, the ID field is sequentially assigned and can
therefor act as a pseudo timestamp to indicate that the T_NM='03t' grp was
inserted before the T_NM='02T' grp. I would like to qry the rows by maintaining
that "column" grp order as well as reorder the P_NM within each grp. If you mean
"randomness" in terms of how these values are named and when they are entered,
well that is true. And if you're saying I need to include more descriptive data
to order by then so be it.
Followup:
umm, well I saw the FIRST column reference:

"Basically keeping the 1st column order and grouping and resorting the second
column."

and (naturally?) assume you wanted the data ordered

01t -> 03t -> 02T (first column after all).  That is *why* I demonstrated that.
I did not see any references to ID in the problem statement, still don't see any
in the original problem statement (so I did not just read over it).

Soooo, now that you have explained it further, easy enough:

ops$tkyte@ORA9IR2> select *
  2    from (
  3  select test_sort.* , min(id) over (partition by t_nm) min_id
  4    from test_sort
  5         )
  6   order by min_id, p_nm
  7  /

T_NM  P_NM          ID     MIN_ID
----- ----- ---------- ----------
01t   01p           37         36
01t   02P           36         36
03t   01p           39         38
03t   02P           38         38
02T   01p           41         40
02T   02P           40         40

6 rows selected.

But bear in mind, that in order to get this -- you'll never be able to push a
predicate down into the innermost query (eg: you'll have to process the entire
set each time and then apply predicates).  Another approach could be:

ops$tkyte@ORA9IR2> select test_sort.*,
  2         (select min(id) from test_sort a where a.t_nm = test_sort.t_nm )
min_id
  3    from test_sort
  4   order by min_id, p_nm
  5  /

T_NM  P_NM          ID     MIN_ID
----- ----- ---------- ----------
01t   01p           37         36
01t   02P           36         36
03t   01p           39         38
03t   02P           38         38
02T   01p           41         40
02T   02P           40         40

6 rows selected.

Which would allow such a construct.  Which one works best is going to based on
the number of rows you actually retrieve.
Rating: 2
in question T is considered after t (in ASCII codes is not) March 17, 2004
Reviewer: Kamran from Pakistan
it does what you said, but it's a pathetic way.
select t_nm,p_nm,id from test_sort
order by substr(t_nm,3) desc, substr(t_nm,1,2),p_nm;
Rating: 5
Larry - is "id" equal to "the order of insertion" ? March 18, 2004
Reviewer: Kim Berg Hansen from Middelfart, Denmark
If the column id is populated as the order of insertion (sequence, perhaps?) you
could maybe do this :

SQL> select * from test_sort
  2  order by
  3  first_value(id) over (partition by t_nm order by id rows between unbounded
preceding and unbounded following), p_nm;

T_NM  P_NM          ID
----- ----- ----------
01t   01p           37
01t   02P           36
03t   01p           39
03t   02P           38
02T   01p           41
02T   02P           40

6 rows selected.

That will sort all '01t' as 36, '03t' as 38, '02T' as 40, etc.

Hope I understood your problem correctly :-)
Rating: 5
Addendum to my previous review March 18, 2004
Reviewer: Kim Berg Hansen from Middelfart, Denmark
Of course the solution I propose only works if the data arrives the way your
test data does.

If for example another '03t' value arrives with id 42, that will be sorted
together with the other '03t' values.

If that's not what you want, then you need a more complex query to get only
identical "t_nm" values next to each other when seen in "id" order. (Hope that
made sense :-)

If your data is simple, then my select should work OK.

Good luck.
Rating: 5
March 18, 2004
Reviewer: Larry H from Emeryville, CA USA
Sorry if I didn't state enough info in the original post.  Your examples should
give me a good approach to my real data.

Many thanks for your responses.  They're most helpful.
Rating: 5
March 23, 2004
Reviewer: A reader
March 18, 2004 antoine -- Thanks for the question regarding "native dynamic sql", version 8.1.7
You Asked
o/s sun solaris 5.8
oracle 8.1.7

Hi Tom,

Thank you for all your great help and suggestions.

I have a problem with this PL/SQL code. I created a native dynamic sql
statement, but when I tried to pass many arguments in a single string (str_BIND)
it didn't work. If you have some hints or solutions about this or about my code,
feel free to give me some advice.

This procedure is going to be called from java program and it is for a dynamic
search.

Yours sincerely,

Antoine

here's the code:

I look at your code from your book
expert one-on-one on page 711-712

create or replace package my_pkg as

type refcursor_Type is ref cursor;

PROCEDURE spGetPROPword(
v_HOTELPROPERTYCITY IN varchar2 default NULL,
v_HOTELPROPERTYADDRESS1 IN varchar2 default NULL,
v_HOTELPROPERTYADDRESS2 IN varchar2 default NULL,
v_HOTELPROPERTYADDRESS3 IN varchar2 default NULL,
v_HOTELPROPERTYADDRESS4 IN varchar2 default NULL,
v_HOTELPROPERTYSTATE IN varchar2 default NULL,
v_HOTELPROPERTYCOUNTRY IN varchar2 default NULL,
v_HOTELPROPERTYPOSTAL IN varchar2 default NULL,
v_HOTELPROPERTYPHONE1 IN varchar2 default NULL,
v_HOTELPROPERTYPHONE2 IN varchar2 default NULL,
v_HOTELPROPERTYNAME IN varchar2 default NULL,
p_cursor out refcursor_Type) ;

end;

CREATE OR REPLACE package body my_pkg as

procedure spGetPROPword
(
v_HOTELPROPERTYCITY IN varchar2 default NULL,
v_HOTELPROPERTYADDRESS1 IN varchar2 default NULL,
v_HOTELPROPERTYADDRESS2 IN varchar2 default NULL,
v_HOTELPROPERTYADDRESS3 IN varchar2 default NULL,
v_HOTELPROPERTYADDRESS4 IN varchar2 default NULL,
v_HOTELPROPERTYSTATE IN varchar2 default NULL,
v_HOTELPROPERTYCOUNTRY IN varchar2 default NULL,
v_HOTELPROPERTYPOSTAL IN varchar2 default NULL,
v_HOTELPROPERTYPHONE1 IN varchar2 default NULL,
v_HOTELPROPERTYPHONE2 IN varchar2 default NULL,
v_HOTELPROPERTYNAME IN varchar2 default NULL,
p_cursor out refcursor_Type ) IS

str_QUERY varchar2(500);
str_BIND varchar2(500);
tag varchar2(1);
begin

str_QUERY:= 'SELECT * from ODD_PROPERTY_MAINTENANCE';

if (V_HOTELPROPERTYCITY IS NOT NULL) OR (V_HOTELPROPERTYADDRESS1 IS NOT NULL) OR

(V_HOTELPROPERTYADDRESS2 IS NOT NULL)  OR (V_HOTELPROPERTYADDRESS3 IS NOT NULL)
OR

(V_HOTELPROPERTYADDRESS4 IS NOT NULL)  OR (V_HOTELPROPERTYSTATE IS NOT NULL)  OR

(V_HOTELPROPERTYCOUNTRY IS NOT NULL) OR (V_HOTELPROPERTYPOSTAL IS NOT NULL) OR

(V_HOTELPROPERTYPHONE1 IS NOT NULL) OR (V_HOTELPROPERTYPHONE2 IS NOT NULL) OR

(V_HOTELPROPERTYNAME IS NOT NULL) then

 str_QUERY:=str_QUERY || ' WHERE ';
end if;

if (V_HOTELPROPERTYCITY IS NOT NULL)
then
  str_QUERY := str_QUERY || 'HOTELPROPERTYCITY = :a ';
 tag:= 'Y';
 str_BIND := v_HOTELPROPERTYCITY;

end if;

if (V_HOTELPROPERTYADDRESS1 IS NOT NULL) and (tag ='Y')
then
 str_QUERY := str_QUERY || ' and HOTELPROPERTYADDRESS1 =:b ';
 str_BIND := str_BIND || ',' || V_HOTELPROPERTYADDRESS1;
elsif (V_HOTELPROPERTYADDRESS1 IS NOT NULL) and (tag <>'Y') then
 str_QUERY := str_QUERY || 'HOTELPROPERTYADDRESS1 =:b ';
 str_BIND := str_BIND || V_HOTELPROPERTYADDRESS1;
 tag:= 'Y';
end if;

if (V_HOTELPROPERTYADDRESS2 IS NOT NULL)  and (tag ='Y') then

 str_QUERY := str_QUERY || ' and HOTELPROPERTYADDRESS2 = :c ';
 str_BIND := str_BIND || ',' || v_HOTELPROPERTYADDRESS2;
elsif (V_HOTELPROPERTYADDRESS2 IS NOT NULL) and (tag <>'Y') then
 str_QUERY := str_QUERY || 'HOTELPROPERTYADDRESS2 = :c ';
 str_BIND := str_BIND || v_HOTELPROPERTYADDRESS2;
 tag:= 'Y';
end if;

if (V_HOTELPROPERTYADDRESS3 IS NOT NULL) and (tag ='Y') then

 str_QUERY := str_QUERY || ' and HOTELPROPERTYADDRESS3 =:d ';
 str_BIND := str_BIND || ',' || v_HOTELPROPERTYADDRESS3;
elsif (V_HOTELPROPERTYADDRESS3 IS NOT NULL) and (tag <>'Y') then
 str_QUERY := str_QUERY || 'HOTELPROPERTYADDRESS3 =:d ';
 str_BIND := str_BIND || v_HOTELPROPERTYADDRESS3;
 tag:= 'Y';
end if;

if (V_HOTELPROPERTYADDRESS4 IS NOT NULL) and (tag ='Y') then
 str_QUERY := str_QUERY || ' and HOTELPROPERTYADDRESS4 =:e ';
 str_BIND := str_BIND || ',' || v_HOTELPROPERTYADDRESS4;
elsif (V_HOTELPROPERTYADDRESS4 IS NOT NULL) and (tag <>'Y') then
 str_QUERY := str_QUERY || 'HOTELPROPERTYADDRESS4 =:e ';
 str_BIND := str_BIND || v_HOTELPROPERTYADDRESS4;
 tag:= 'Y';
end if;

if (V_HOTELPROPERTYSTATE IS NOT NULL)  and (tag ='Y') then
 str_QUERY := str_QUERY || ' and HOTELPROPERTYSTATE = :f ';
 str_BIND := str_BIND || ',' || V_HOTELPROPERTYSTATE;
elsif (V_HOTELPROPERTYSTATE IS NOT NULL) and (tag <>'Y') then
 str_QUERY := str_QUERY || 'HOTELPROPERTYSTATE = :f ';
 str_BIND := str_BIND || V_HOTELPROPERTYSTATE;
 tag:= 'Y';
end if;

if (V_HOTELPROPERTYCOUNTRY IS NOT NULL) and (tag ='Y') then
 str_QUERY := str_QUERY || ' and HOTELPROPERTYCOUNTRY = :g';
 str_BIND := str_BIND || ',' || V_HOTELPROPERTYCOUNTRY;

elsif (V_HOTELPROPERTYCOUNTRY IS NOT NULL) and (tag <>'Y') then
 str_QUERY := str_QUERY || 'HOTELPROPERTYCOUNTRY = :g ';
 str_BIND := str_BIND || V_HOTELPROPERTYCOUNTRY;
 tag:= 'Y';
end if;

if (V_HOTELPROPERTYPOSTAL IS NOT NULL)  and (tag ='Y') then
 str_QUERY := str_QUERY || ' and HOTELPROPERTYPOSTAL = :h ';
 str_BIND := str_BIND || ',' || V_HOTELPROPERTYPOSTAL;
elsif (V_HOTELPROPERTYPOSTAL IS NOT NULL) and (tag <>'Y') then
 str_QUERY := str_QUERY || 'HOTELPROPERTYPOSTAL = :h ';
 str_BIND := str_BIND || V_HOTELPROPERTYPOSTAL;
 tag:= 'Y';
end if;

if (V_HOTELPROPERTYPHONE1 IS NOT NULL)  and (tag ='Y') then
 str_QUERY := str_QUERY || ' and HOTELPROPERTYPHONE1 = :i ';
 str_BIND := str_BIND || ',' || V_HOTELPROPERTYPHONE1;
elsif (V_HOTELPROPERTYPHONE1 IS NOT NULL) and (tag <>'Y') then
 str_QUERY := str_QUERY || 'HOTELPROPERTYPHONE1 = :i ';
 str_BIND := str_BIND || V_HOTELPROPERTYPHONE1;
 tag:= 'Y';
end if;

if (V_HOTELPROPERTYPHONE2 IS NOT NULL) and (tag ='Y')  then
 str_QUERY := str_QUERY || ' and HOTELPROPERTYPHONE2 =:j ';
 str_BIND := str_BIND || ',' || V_HOTELPROPERTYPHONE2;
elsif (V_HOTELPROPERTYPHONE2 IS NOT NULL) and (tag <>'Y') then
 str_QUERY := str_QUERY || 'HOTELPROPERTYPHONE2 = :j ';
 str_BIND := str_BIND || V_HOTELPROPERTYPHONE2;
 tag:= 'Y';
end if;

if (V_HOTELPROPERTYNAME  IS NOT NULL)  and (tag ='Y') then
 str_QUERY := str_QUERY || ' and HOTELPROPERTYNAME = :k ';
 str_BIND := str_BIND || ',' || V_HOTELPROPERTYNAME;
elsif (V_HOTELPROPERTYNAME IS NOT NULL) and (tag <>'Y') then
 str_QUERY := str_QUERY || 'HOTELPROPERTYNAME = :k ';
 str_BIND := str_BIND || V_HOTELPROPERTYNAME;

end if;

open p_cursor for str_QUERY using str_BIND;

end;

end ;

Here the error I got:

SQL*Plus: Release 8.1.7.0.0 - Production on Fri Aug 30 14:24:14 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> variable t refcursor
SQL> DECLARE
  2    V_HOTELPROPERTYCITY VARCHAR2(200);
  3    V_HOTELPROPERTYADDRESS1 VARCHAR2(200);
  4    V_HOTELPROPERTYADDRESS2 VARCHAR2(200);
  5    V_HOTELPROPERTYADDRESS3 VARCHAR2(200);
  6    V_HOTELPROPERTYADDRESS4 VARCHAR2(200);
  7    V_HOTELPROPERTYSTATE VARCHAR2(200);
  8    V_HOTELPROPERTYCOUNTRY VARCHAR2(200);
  9    V_HOTELPROPERTYPOSTAL VARCHAR2(200);
 10    V_HOTELPROPERTYPHONE1 VARCHAR2(200);
 11    V_HOTELPROPERTYPHONE2 VARCHAR2(200);
 12    V_HOTELPROPERTYNAME VARCHAR2(200);
 13    VV_HOTELPROPERTYCITY VARCHAR2(200);
 14
 15  BEGIN
 16    V_HOTELPROPERTYCITY := 'Hong Kong';
 17    V_HOTELPROPERTYADDRESS1 := NULL;
 18    V_HOTELPROPERTYADDRESS2 := NULL;
 19    V_HOTELPROPERTYADDRESS3 := NULL;
 20    V_HOTELPROPERTYADDRESS4 := NULL;
 21    V_HOTELPROPERTYSTATE := NULL;
 22    V_HOTELPROPERTYCOUNTRY := 'HK';
 23    V_HOTELPROPERTYPOSTAL := NULL;
 24    V_HOTELPROPERTYPHONE1 := NULL;
 25    V_HOTELPROPERTYPHONE2 := NULL;
 26    V_HOTELPROPERTYNAME := NULL;
 27    --VV_HOTELPROPERTYCITY := NULL;
 28
 29   my_pkg.spGetPROPword ( V_HOTELPROPERTYCITY, V_HOTELPROPERTYADDRESS1,
V_HOTELPROPERTYADDRESS2,
V_HOTELPROPERTYADDRESS3, V_HOTELPROPERTYADDRESS4, V_HOTELPROPERTYSTATE,
V_HOTELPROPERTYCOUNTRY, V_HO
TELPROPERTYPOSTAL, V_HOTELPROPERTYPHONE1, V_HOTELPROPERTYPHONE2,
V_HOTELPROPERTYNAME, :t );
 30  END;
 31  /
DECLARE
*
ERROR at line 1:
ORA-01008: not all variables bound
ORA-06512: at "ABOCTI.MY_PKG", line 169
ORA-06512: at line 29

SQL>
and we said...
You only ever bind the first variable

open x for y using z;

will only bind a single variable with the value of Z.  If Z = 'a,b,c,d' that is
NOT the same as coding:

open x for y using a,b,c,d;

is it opening X using the string Y with the single value in Z which just happens
to be a comma delimited list.

Here is how to do this:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1288401763279
Review & Followup

Rating: 5
thank again Tom September 02, 2002
Reviewer: Antoine Bocti from Canada,Montreal
Interesting, I never knew about sys_context()!
Sorry for not searching properly in the archive.
Rating: 5
REF cursor out and intermedia September 03, 2002
Reviewer: antoine Bocti from Canada, Montreal
Hi Tom,

is it possible with your exemple that you gave me to declare a ref cursor out.

And in the applacation context to put a search with intermedia like for exemple:

if ( p_hotelpropertycity  is NOT NULL ) then
           dbms_session.set_context( 'MY_CTX', 'city',
                                     'contains
hotelpropertycity,'''p_hotelpropertycity%''' ');
           l_query := l_query ||
                ' and ename like
                  sys_context( ''MY_CTX'', ''city'' ) ';
       end if;

thanks.
Followup:
yes, you can use a ref cursor IN OUT parameter instead of a local variable.

that example you gave is not "intermedia"?  there is no contains....
Rating: 5
REF cursor out and intermedia September 03, 2002
Reviewer: antoine Bocti from Canada,Montreal
I put this code in expemple instead of your code.

This is the ink you gave me.

CURSOR
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1288401763279
My question was, is it possible in your exemple (that you gave me above
(CURSOR)) to put some intermedia operator like contains to build  a dynamic
search procedure?

If it's possible can you give me an exemple please.

Thank Tom.
Followup:
hmm, looks like contains doesn't get along with sys_context in 8i...

You'll have to use this technique instead:

      if ( p_ename is NOT NULL ) then
           l_query := l_query ||
                ' and contains( field, :p_ename ) > 0 '
       else
           l_query := l_query ||
                ' and 1 = DECODE(:p_ename,NULL,1,1)';
       end if;

and then when you open the query:

     open refcursor for l_query using p_ename;
Rating: 5
thanksTom! September 04, 2002
Reviewer: Antoine Bocti from Montreal,Canada
I just have a question for intermedia and the use of wildcard(%), what's your
opinion about it?
Followup:
wildcards slow everything down.  I only do them on structured data (where we
have a CHANCE at maintaining performance)
Rating: 5
thanksTom! September 04, 2002
Reviewer: Antoine Bocti from Montreal,Canada
Hi tom,

with intermedia can I search over multiple table with a single query.

If not, can you create a view or a materialize view to do the query?

What is your point of view?

thanks.
Followup:
Yes, in fact -- that is EXACTLY what I do here on asktom!

I indexed this procedure:

create or replace procedure index_ask_tom( p_id in rowid, p_lob IN OUT clob )
as
begin
    for x in ( select * from ask_tom.WWC_ASK_INDEXED_QUESTIONS$ where rowid =
p_id )
    loop
        dbms_lob.copy( p_lob, x.text, dbms_lob.getLength(x.text) );
        for y in ( select '<!--  ' || REVIEW_TITLE || ' ' || REVIEWER_NAME || '
' || REVIEWER_LOCATION || ' ' subj,
                          review_comments
                     from ask_tom.WWC_ASK_QUESTION_REVIEWS$ where displayid =
x.displayid )
        loop
            dbms_lob.writeAppend( p_lob, length(y.subj), y.subj );
            dbms_lob.append( p_lob, y.review_comments );
        end loop;
        for y in ( select followup_comments
                     from ask_tom.WWC_ASK_REVIEW_FOLLOWUPS$
                    where displayid = x.displayid )
        loop
            dbms_lob.writeAppend( p_lob, 1, ' ' );
            dbms_lob.append( p_lob, y.followup_comments );
        end loop;
        dbms_lob.writeAppend( p_lob, length(' -->'), ' -->' );
    end loop;
end;
/

that glues the questions with the answers to the comments and my followups (so
when you search, you are searching across all of them).

See:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:440419921146
for a full example.
Rating: 5
contains operator September 04, 2002
Reviewer: mo from Vienna, USA
Tom:

1.  What do you mean by "structured data". Is it char or varchar2 column types.

"wildcards slow everything down.  I only do them on structured data (where we
have a CHANCE at maintaining performance) "

2.  Does 8i support the contains operator.  How is that different than using the
like % .

Thank you,
Followup:
1) yes.

2) yes.

read the intermedia documentation that is freely available and you'll learn alot
about its capabilities:
http://download-west.oracle.com/docs/cd/A87860_01/doc/inter.817/a77063/toc.htm
Rating: 5
me again... September 04, 2002
Reviewer: Antoine Bocti from Montreal, Canada
Hi Tom,

So if I want to do a search on two table by exemple:

table brand
id_brand
name
code

table hotel
id_hotel
id_brand
name
number_room

Can I do a select and join brand and hotel and use one index for a field in
brand and and use one index for a field in hotel?

select * from brand A,hotel where contains(a.name,'Hilton') > 0 OR
                        contains(b.name,'hilton by the sea') > 0;

Is it better to create a PLSQL function that returns  the two fields glued
together?
Followup:
It would be more efficient if you indexed the function that glued the data
together if you frequently search on those two items as if they were a single
item.
Rating: 5
REF cursor out and intermedia September 05, 2002
Reviewer: Antoine Bocti from Montreal, Canada
Hi Tom,

Is it possible to add a wildcard add the end of  :p_hotelpropertycity in my
code?

I try these :
l_query := l_query ||   ' and contains( hotelpropertycity, :p_hotelpropertycity'
||' ||% ) > 0';

it compile but at the run time it failed!

Original code that work!

if ( p_hotelpropertycity is NOT NULL ) then
           l_query := l_query ||   ' and contains(hotelpropertycity,
:p_hotelpropertycity ) > 0 ';
else
           l_query := l_query || ' and 1 =
DECODE(:p_hotelpropertycity,NULL,1,1)';
end if;
Followup:
If that is not working just add the
' and contains( hotel.., :p_hotelpropertycity ) > 0'

or

' and 1 = decode( :p_hotel..., null, 1, 1 ) '

and when you open the query use

  USING p_hotelcityproperty || '%';
Rating: 5
Tom thank you so much again! September 06, 2002
Reviewer: A reader
Rating: 5
intermedia September 07, 2002
Reviewer: mo from vienna, va
Tom:

AM I Correct:

1. If you are doing queries on structured data you can get away with regular SQL
statements and using like % operator.
even using varchar2(2000).

2.  When you do text searches for unstructured data like blob, or long you need
intermedia and use contains operator?
Followup:
1) yes

2) "need" is too strong. WANT is the correct term.
Rating: 3
Help March 17, 2004
Reviewer: Srinivas from Hyderabad,AP,India
SQL> create table t(x number default 10);

Table created.

SQL> insert into t values(1);

1 row created.

SQL> insert into t values(2);

1 row created.

SQL> insert into t values(default);

1 row created.

SQL>  insert into t values(default);

1 row created.

SQL> commit;

Commit complete.

SQL> select * from t;

         X
----------
         1
         2
        10
        10

SQL> delete from t where x = default;
delete from t where x = default
                        *
ERROR at line 1:
ORA-00936: missing expression

SQL> delete from t where x is default;
delete from t where x is default
                         *
ERROR at line 1:
ORA-00908: missing NULL keyword

SQL> delete from t where x in(default);
delete from t where x in(default)
                         *
ERROR at line 1:
ORA-00936: missing expression

How to correct this?Will I have to specify as
sql>delete from t where x = 10;
Could you please help?
Bye!
Followup:
there is no way to "correct this" as default is allowed in the values clause of
an insert or the set clause of an update and that is "about it"
Rating: 5
this might help U March 18, 2004
Reviewer: A reader
Srinivas,

U really need this "feature" ?

the following might work in some cases

sqlplus> create or replace function data_default(table_name in varchar2,
column_name in varchar2) return varchar2
  2  is
  3  ret varchar2(32767);
  4  begin
  5  select data_default
  6  into ret
  7  from user_tab_columns
  8  where table_name=table_name
  9  and column_name=column_name;
 10  return ret;
 11  end;
 12  /

Funktion wurde erstellt.

sqlplus> create table t(x number default 10);

Tabelle wurde angelegt.

sqlplus> insert into t values(1);

1 Zeile wurde erstellt.

sqlplus> insert into t values(default);

1 Zeile wurde erstellt.

sqlplus> select * from t;

         X
----------
         1
        10

sqlplus> delete t where x in (select data_default('T','X') from dual);

1 Zeile wurde gel÷scht.

sqlplus> select * from t;

         X
----------
         1
March 18, 2004 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
Rating: 4
rollback segment March 12, 2004
Reviewer: Venkat from India
Dear Tom,

How to find the transaction size to determine the sizing of the rollback
segments. Kindly illustrate with an example.

Thanks,
Venkat
Followup:
the size of your transaction is directly related to the amount of undo you
generate

the amount of undo you generate will be stored in the rollback segments

therefore the rbs needs to be sized to hold the amount of undo you generate for
some period of time.
Rating: 4
undo size estimation March 15, 2004
Reviewer: K.Venkatasubramanian from India
Dear Tom,

Concept is understood. Like to know the way of finding undo generated during
batch process. Subsequently, the way to size the rbs.  Kindly explain the
computational method by a small example Alternatively, give us any script or
other source of information.

Thanks,
Venkat
Followup:
use automatic undo mgmt (AUM) and either OEM (enterprise manager) will give you
a pretty chart that shows you "if you want to set undo retention to 'x', you
will need 'y' bytes of undo space" or you can query the v$ tables directly to
ascertain this.
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96521/undo.htm#10085
Rating: 4
undo size estimation March 17, 2004
Reviewer: Venkat from India
Dear Tom,

Thank you very much for the inputs. This is on undo size estimation. I like to
make sure whether my understanding is right.

I ran one transaction.
I queried the v$undostat table.
The output of v$undostat for my transaction is given below:

BTIME              ETIME                 UNDOTSN   UNDOBLKS   TXNCOUNT
------------------ ------------------ ---------- ---------- ----------
17/mar/2004 12:20  17/mar/2004 12:30           1       1726     108674

undoblocks - 1726
block size - 8k (oracle data block size in this testing database)
Duration of this transaction is 10 seconds.
Hence total undo size for this transaction is - 13808K (1726x8)- around 14M.
When the extent size of rollback segment is 1M, can I say that  14 extents would
have got used by this transaction during the transaction period of 10 seconds?

Am I right in this? Pl. confirm.

Thanks
Venkat
Followup:
nope that is not quite right.
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96536/ch3235.htm#1128847
describes this view.

first, Etime-Btime is 10minutes there, not 10 seconds.

second v$undostat is "system wide", it is not for your transaction only. those
undoblocks are for everyone.  You can use this to "size" your undo tablespace.

take a glance at that link, it describes this view in more detail.
Rating: 5
undo size estimation March 17, 2004
Reviewer: Venkat from India
Tom,

Thank you so much...
Can we take that 14M ( 14 extents of 1M size in our database) as system wide
requirement during that 10 minutes?

Thanks,
Venkat
Followup:
yup.  thats what the documentation says..

UNDOBLKS

NUMBER

Represents the total number of undo blocks consumed. You can use this column to
obtain the consumption rate of undo blocks, and thereby estimate the size of the
undo tablespace needed to handle the workload on your system.
March 18, 2004 Anderson -- Thanks for the question regarding "8i to 8.0.5 - Subqueries", version 8.0.5
You Asked
Hi Tom,
I work with several queries like this :

select decode( d.id_dis_typ,
        'ISP', i.cd_pay_trm,
        'CMS', c.cd_pay_trm,
        'BSP', b.cd_pay_trm,
        'NSP', n.cd_pay_trm ) cd_pay_trm,
(     SELECT pt.nm_trm
    FROM PaymentTerm pt
    WHERE pt.cd_cpy = d.cd_cpy
    AND pt.cd_pay_trm = decode( d.id_dis_typ,
                    'ISP', i.cd_pay_trm,
                    'CMS', c.cd_pay_trm,
                    'BSP', b.cd_pay_trm,
                    'NSP', n.cd_pay_trm )
) nm_trm,
d.cd_cpy
from         Invoice i,
        Creditmemo c,
        Bill b,
        NotaFiscal n,
        Distribution d
where d.cd_cpy            = i.cd_cpy (+)
    and d.nu_seq_jnl_ent    = i.nu_seq_jnl_ent (+)
    and d.cd_cpy            = c.cd_cpy (+)
    and d.nu_seq_jnl_ent    = c.nu_seq_jnl_ent (+)
    and d.cd_cpy            = b.cd_cpy (+)
    and d.nu_seq_jnl_ent    = b.nu_seq_jnl_ent (+)
    and d.cd_cpy            = n.cd_cpy (+)
    and d.nu_seq_jnl_ent    = n.nu_seq_jnl_ent (+);

This query works fine in Oracle 8i, but not in 8.0.5:

(       SELECT pt.nm_trm
        *
ERROR at line 6:
ORA-00936: missing expression

What do you suggest me?
and we said...
The ability to "select a select" was added in Oracle8i.

quick fix:

create or replace function nm_trm_f( d_cd_cpy in varchar2,
                                     d_id_dis_typ in varchar2,
                                     i_id_dis_typ in varchar2,
                                     c_id_dis_typ in varchar2,
                                     b_id_dis_typ in varchar2,
                                     n_id_dis_typ in varchar2 )
return varchar2
as
  l_nm_trm varchar2(255);
begin
  SELECT pt.nm_trm into l_nm_trm
    FROM PaymentTerm pt
    WHERE pt.cd_cpy = d_cd_cpy
    AND pt.cd_pay_trm = decode( d_id_dis_typ,
                              'ISP', i_cd_pay_trm,
                              'CMS', c_cd_pay_trm,
                              'BSP', b_cd_pay_trm,
                               'NSP', n_cd_pay_trm );

   return l_nm_trm;
exception
   when no_data_found then return null;
end;
/

and select that funtion.  Now, if your query returns only a couple of rows (say
100 or so) - this will work fine.  for large result sets the overhead of calling
the plsql function might be too high -- in that case you should rewrite the
query as:

select pt.nm_trm, cd_pay_trm, cd_cpy
from (
select decode( d.id_dis_typ,
        'ISP', i.cd_pay_trm,
        'CMS', c.cd_pay_trm,
        'BSP', b.cd_pay_trm,
        'NSP', n.cd_pay_trm ) cd_pay_trm, d.cd_cpy
from    Invoice i,
        Creditmemo c,
        Bill b,
        NotaFiscal n,
        Distribution d
where d.cd_cpy            = i.cd_cpy (+)
    and d.nu_seq_jnl_ent    = i.nu_seq_jnl_ent (+)
    and d.cd_cpy            = c.cd_cpy (+)
    and d.nu_seq_jnl_ent    = c.nu_seq_jnl_ent (+)
    and d.cd_cpy            = b.cd_cpy (+)
    and d.nu_seq_jnl_ent    = b.nu_seq_jnl_ent (+)
    and d.cd_cpy            = n.cd_cpy (+)
    and d.nu_seq_jnl_ent    = n.nu_seq_jnl_ent (+)
) a, paymentTerm pt
where pt.cd_cpy(+) = a.cd_cpy
  and pt.cd_pay_trm(+) = cd_pay_trm;

it should return the same results.  the outer join to PT might not be necessary
if it always joins.
Review & Followup

Rating: 5
inline view and "select a select" July 07, 2002
Reviewer: Rajesh
Tom,
  What is the difference between an inline view and
"select a select" statements. Thanks.
Followup:
They are very similar.  The major difference is probably the same as the
difference between a "normal subquery" and a "correlated subquery".

An inline view is like a "normal subquery".
Selecting a select is more like a correlated subquery.

Consider:

select d.dname, nvl(e.cnt,0)
  from dept d, ( select deptno, count(*) cnt from emp group by deptno ) e
 where d.deptno = e.deptno(+);

that is an inline view -- it can "stand along", it is not correlated with the
outer query.  Now,

select dname, (select count(*) from emp where emp.deptno = dept.deptno ) cnt
  from dept;

that query returns the exact same results -- just using a correlated "select of
a select" -- it is very much like a correlated subquery.  In general, "selects
of a select" use a select statement that cannot "stand alone" -- it needs that
outer query to drive it.

But they are really quite similar (and equally handy)
Rating: 5
July 17, 2002
Reviewer: A reader
'
and select that funtion.  Now, if your query returns only a couple of rows (say
100 or so) - this will work fine.  for large result sets the overhead of calling

the plsql function might be too high -- in that case you should rewrite the
query as:
'

Tom, can you explain the different reasons why functions are so costly to use in
sql.
Followup:
they don't run as fast as when they are not there....

say you have a finely written PLSQL routine that runs in 1/100 of a second.  It
might also take 1/100 of a second to set up the "call environment, actually call
the procedure, and clean up the stack upon return -- including the time for the
Oracle context switch from the database kernel to the user code".  So, if you
call  it 100 times -- that query will take at least 2 seconds (just executing
the code).  Call it 1000 times and you are talking 20 seconds -- and so on.
Take a little number, mulitply by a bigger number and you get a sizable number
after a bit.  Each call is "fast" but 1000 of them -- start to feel slow.
Rating: 5
select a select December 30, 2002
Reviewer: Steve from NYC, USA
Hi tom,

For the select part of the following sql, I'd like to set
‘comp start date’ to add_months(b.open_date, 15) and  ‘comp end date’ to
nvl(b.close_date, sysdate). But I got Nothing
in COMP_START_DATE column. I don't know if it is possible using single SQL to do
it?

Thanks!

Steve

insert into srs_sales_comp_stores
(  STORE_NUM,  COMP_START_DATE, COMP_END_DATE, LY_COMP_START_DATE,
LY_COMP_END_DATE)
select b.store_num,  add_months(b.open_date, 15) comp_start_date,
nvl(b.close_date, sysdate) comp_end_date,
 ( select transact_date
   from   bn_period
   where  fiscal_yr||fiscal_wk||dow = (
                                      select fiscal_yr-1||fiscal_wk||6
                                      from bn_period
                                      where transact_date = ‘comp start date’
                                    ))  LY_COMP_START_DATE,
 ( select transact_date
   from   bn_period
   where  fiscal_yr||fiscal_wk||dow = (
                             select fiscal_yr-1||fiscal_wk||6
                            from bn_period
                            where transact_date = ‘comp end date’
                                       )) LY_COMP_END_DATE
from bn_store b
where b.status != 'Z'
Followup:
if you run the query by itself -- not as an insert-- do you see anything?

if not -- then there isn't any data being returned from the subquery.
Rating: 3
select a select December 30, 2002
Reviewer: Steve from NYC, USA
Hi tom,

The reason I am asking you is because
there isn't any data being returned from the subquery.
So I'd like to get an advice from you if there is a way to
get correlated column data back from a sigle query rather than adding a Updated
statement.

Thanks!

Steve
Followup:
not sure what you mean -- do you believe data *should be* returned and its not?

Cause if the select (select ).... returns nothing for the subquery the update
set x = (select ... ) would as well.
Rating: 5
December 30, 2002
Reviewer: Dave from Ohio
I notice that the query in the select-a-select doesn't get a mention in the
explain plan. Can we tell what the access path would be for the select-a-select
by isolating it and running an explain plan for just that query?
Followup:
Nope, it doesn't appear there and there is currently no way to see it.
Rating: 4
Oracle Developer March 14, 2004
Reviewer: Dave from California, USA
Tom,

I wrote SQL statement & function separtely. No I need to call this function in
select statement to get Account Num from other table.
Here is my select statemnt & function.

SELECT
DD_ID
||'~'||ACCOUNT_NUM
||'~'||TRADE_SEQ_ID
||'~'||DD_REASON_CD
||'~'||DD_REASON_DESC
||'~'||DD_REASON_TYPE_CD
||'~'||DD_REASON_TYPE_DESC
||'~'||DD_REASON_CAT_CD
||'~'||DD_REASON_CAT_DESC
||'~'||REQUIRED_COMMENTS_FLG
||'~'||DD_STATUS_CD
||'~'||DD_STATUS_DESC
||'~'||to_char(DD_OPEN_DATE,'DD-MON-YYYY')
||'~'||to_char(DD_CLOSE_DATE,'DD-MON-YYYY')
||'~'||translate(translate(OTHER,chr(10)||chr(13),' '),'~',' ')
||'~'||translate(translate(COMMENTS,chr(10)||chr(13),' '),'~',' ')
||'~'||DD_CREATE_USER_ID
||'~'||to_char(DD_CREATE_DATE,'DD-MON-YYYY')
||'~'||DD_UPDATE_USER_ID
||'~'||to_char(DD_UPDATE_DATE,'DD-MON-YYYY')
||'~'||CREATE_USER_ID
||'~'||to_char(CREATE_DATE,'DD-MON-YYYY')
||'~'||UPDATE_USER_ID
||'~'||to_char(UPDATE_DATE,'DD-MON-YYYY')||'~'
FROM fs_dd;

Here is my function.

FUNCTION GET_NFS_ACCT_NUM(p_merc_acct_num IN
NFS_ACCOUNT_XREF.mercury_account_num%TYPE)
  RETURN NFS_ACCOUNT_XREF.nfs_account_num%TYPE AS

    CURSOR get_nfs_cur IS
    SELECT NFS_ACCOUNT_NUM
      FROM NFS_ACCOUNT_XREF
     WHERE mercury_account_num = p_merc_acct_num;

    v_rc  NFS_ACCOUNT_XREF.nfs_account_num%TYPE := NULL;

  BEGIN

    OPEN get_nfs_cur;
    FETCH get_nfs_cur INTO v_rc;
    IF get_nfs_cur%NOTFOUND THEN

      v_rc := NULL;

    END IF;
    CLOSE get_nfs_cur;

    RETURN v_rc;

  EXCEPTION
    WHEN OTHERS THEN
      RETURN NULL;

  END GET_NFS_ACCT_NUM;

Any feed back will be appreciated. Thanks
Followup:
you should just be joining, or using a scalar subquery.  Never do in PLSQL what
can more easily (and faster to boot) be done in straight sql.

Also, your code would be "better" if simply coded as:

FUNCTION GET_NFS_ACCT_NUM(p_merc_acct_num IN
NFS_ACCOUNT_XREF.mercury_account_num%TYPE)
  RETURN NFS_ACCOUNT_XREF.nfs_account_num%TYPE AS
as
   v_rc NFS_ACCOUNT_XREF.nfs_account_num%TYPE;
begin
   select nfs_account_num into v_rc
     from nfs_account_xref
    where mercury_account_num = p_merc_acct_num;
   return v_rc;
exception
   when no_data_found then
        return null;
end;

but again, i would not even consider calling this function -- i would either

select ... ( select ... ) from ....

or outer join (if needed)
or just join (if appropriate)

Never do your own plsql based lookups like that, just use SQL.  faster, better,
cheaper.
Rating: 5
Oracle Developer March 15, 2004
Reviewer: Dave from California, USA
Tom,

Actually this is our common funtion. And we are using in other areas too.
The reason we like to use this function is that if we need to make any change in
our logic.
we can change only in the function. we do not need to makes changes in all
scripts.

Do you have any suggestion? Please let me know.

Regards,
Dave
Followup:
i do not code "do it yourself nested loops"

i use SQL.

"views" pop into mind.
Rating: 5
Oracle Programmer March 18, 2004
Reviewer: Dawar from California, USA
Dave,

 I think you may write script as below.

Tom,
Do you think is ok?

SELECT
DD_ID
||'~'||NVL(PKG_NFS_COMMON.GET_NFS_ACCT_NUM(ACCOUNT_NUM),ACCOUNT_NUM)
||'~'||TRADE_SEQ_ID
||'~'||DD_REASON_CD
||'~'||DD_REASON_DESC
||'~'||DD_REASON_TYPE_CD
||'~'||DD_REASON_TYPE_DESC
||'~'||DD_REASON_CAT_CD
||'~'||DD_REASON_CAT_DESC
||'~'||REQUIRED_COMMENTS_FLG
||'~'||DD_STATUS_CD
||'~'||DD_STATUS_DESC
||'~'||to_char(DD_OPEN_DATE,'DD-MON-YYYY')
||'~'||to_char(DD_CLOSE_DATE,'DD-MON-YYYY')
||'~'||translate(translate(OTHER,chr(10)||chr(13),' '),'~',' ')
||'~'||translate(translate(COMMENTS,chr(10)||chr(13),' '),'~',' ')
||'~'||DD_CREATE_USER_ID
||'~'||to_char(DD_CREATE_DATE,'DD-MON-YYYY')
||'~'||DD_UPDATE_USER_ID
||'~'||to_char(DD_UPDATE_DATE,'DD-MON-YYYY')
||'~'||CREATE_USER_ID
||'~'||to_char(CREATE_DATE,'DD-MON-YYYY')
||'~'||UPDATE_USER_ID
||'~'||to_char(UPDATE_DATE,'DD-MON-YYYY')||'~'
FROM fs_dd;

Dawar
Followup:
it should be a join.
March 18, 2004 Peter -- Thanks for the question regarding "Nested Tables - accessing objects returned from a subquery", version 8.1.7
You Asked
Question: Unable to reference nested-table objects returned from a subquery -
how to qualify them?

Basic Sample Record:
KEY_ID KEY_CONTEXT GROUPING     MD_NESTEDTABLE (NAME | VALUE PAIRINGS)
I123   PRI         Element Met  SEGMENT_UUID     I123A
                   adata        PRIMARY_PARENT   I100
                                PUB_CODE         FLLB
                                volume.break     1
                                IMMEDIATE_PARENT
                                ELEMENT_NAME     topic
                                abbv             UNIFORMCC

Query:
SELECT md_ntab
 FROM metadata_tab MT, TABLE(MT.md_ntab) MN
 WHERE MT.key_context = 'PRI' AND
       MT.grouping = 'Element Metadata' AND
       MN.name = 'PUB_CODE' AND
       MN.value = 'FLLB'

This gives me a collection of objects for FLLB, which I loop through and put
into a Java struct.  Each struct's attributes are then looped through and other
conditions are checked.  Eventually, I am just left with the values '1' (from
volume.break) and 'I123A' (from SEGMENT_UUID) (and 12 other records like it).
It works, but I know it's not efficient - need more of the work done within
Oracle.

Sample Record Returned (+ 137 other records...only 13 records match my real
criteria - which I figure out using Java/jdbc):
METADATA_NTABTYP(
   METADATA_OBJTYP('ELEMENT_NAME', 'topic'),
   METADATA_OBJTYP('IMMEDIATE_PARENT', ''),
   METADATA_OBJTYP('PRIMARY_PARENT', 'I100'),
   METADATA_OBJTYP('PUB_CODE', 'FLLB'),
   METADATA_OBJTYP('SEGMENT_UUID', 'I23A'),
   METADATA_OBJTYP('volume.break', '1'),
   METADATA_OBJTYP('abbv', 'UNIFORMCC'))

Trying do something like this...
  1  SELECT t FROM (SELECT md_ntab t
  2                   FROM metadata_tab m, TABLE(m.md_ntab) n
  3                   WHERE m.key_context = 'PRI'              AND
  4                         m.grouping    = 'Element Metadata' AND
  5                         n.name        = 'PUB_CODE'         AND
  6                         n.value       = 'FLLB')
  7*   WHERE t.metadata_objtyp.name = 'volume.break'
SQL> /
WHERE t.metadata_objtyp.name = 'volume.break'
                        *
ERROR at line 7:
ORA-00904: invalid column name

This would return me collection of objects (13) for FLLB with volume.break
information only - not all FLLB records...but I can't seem to access each
METADATA_NTABTYP and its attributes (METADATA_OBJTYP).

Ultimately, I only want the 'SEGMENT_UUID' value and the 'volume.break' value
for each record of 'PUB_CODE' FLLB

I am trying to do this all in SQL - seems as if I'll have to do it in PL/SQL
though...

I'm a bit of an Oracle newbie...I haven't worked much in PL/SQL or with OR -
just learning.

Thanks,
Pete
New York

Design:
CREATE OR REPLACE TYPE metadata_objtyp AS OBJECT
(
  name        VARCHAR2(20),
  value       VARCHAR2(500),
  version     NUMBER,
  status      VARCHAR2(1),
  create_date DATE
);
/

CREATE OR REPLACE TYPE metadata_ntabtyp AS TABLE OF metadata_objtyp;
/

CREATE TABLE metadata_tab (
  key_id         VARCHAR2(33),
  key_context    VARCHAR2(10),
  grouping       VARCHAR2(30),
  create_date    DATE,
  md_ntab        metadata_ntabtyp,
    PRIMARY KEY (key_id, key_context, grouping)
  )
  NESTED TABLE md_ntab STORE AS meta_ntab (
    (PRIMARY KEY (nested_table_id, name, value, version))
    ORGANIZATION INDEX COMPRESS
  )
  RETURN AS LOCATOR
/
and we said...
Do you mean like this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace type myScalarType as object
  2  ( nm varchar2(20),
  3    vl varchar2(20)
  4  )
  5  /

Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace type myTableType as table
of MyScalarType
  2  /

Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int, y myTableType )
nested table y store as y_tab
  2  /

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values
  2  ( 1, myTableType( myScalarType( 'volume.break', '1' ),
  3                    myScalarType( 'ELEMENT_NAME', 'topic'),
  4                    myScalarType( 'PUB_CODE', 'FLLB' )
  5                  ) );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values
  2  ( 2, myTableType( myScalarType( 'volume.break', '2' ),
  3                    myScalarType( 'ELEMENT_NAME', 'xtopic'),
  4                    myScalarType( 'PUB_CODE', 'BLLS' )
  5                  ) );

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 t.y
  2    from t, TABLE(t.y) t2
  3   where t.x = 1
  4     and t2.nm = 'PUB_CODE'
  5     and t2.vl = 'FLLB'
  6  /

Y(NM, VL)
---------------------------------------------------------------------------------
--------------------------------------------------
MYTABLETYPE(MYSCALARTYPE('volume.break', '1'), MYSCALARTYPE('ELEMENT_NAME',
'topic'), MYSCALARTYPE('PUB_CODE', 'FLLB'))

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
  2    from TABLE( select t.y
  3                  from t, TABLE(t.y) t2
  4                 where t.x = 1
  5                   and t2.nm = 'PUB_CODE'
  6                   and t2.vl = 'FLLB'
  7              )
  8  /

NM                   VL
-------------------- --------------------
volume.break         1
ELEMENT_NAME         topic
PUB_CODE             FLLB

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
  2    from TABLE( select t.y
  3                  from t, TABLE(t.y) t2
  4                 where t.x = 1
  5                   and t2.nm = 'PUB_CODE'
  6                   and t2.vl = 'FLLB'
  7              ) T3
  8    where T3.nm = 'volume.break'
  9  /

NM                   VL
-------------------- --------------------
volume.break         1

ops$tkyte@ORA817DEV.US.ORACLE.COM>
Review & Followup

Rating: 3
What if the subquery returns multiple records? August 16, 2002
Reviewer: Pete Krueger from Rochester, NY USA
SELECT *
  FROM TABLE(SELECT m.md_ntab
               FROM metadata_tab m, TABLE(m.md_ntab) n
               WHERE m.key_context = 'PRI' AND
                     m.grouping = 'Element Metadata' AND
                     n.name = 'PUB_CODE' AND
                     n.value = 'FLLB') t
  WHERE t.name = 'volume.break'
SQL> /
SELECT * FROM TABLE(SELECT m.md_ntab
                    *
ERROR at line 1:
ORA-01427: single-row subquery returns more than one row

I tried changing:
  WHERE t.name = 'volume.break'
TO
  WHERE 'volume.break' IN t.name

But I get the same error - any suggestions?

Thanks a ton!
-Pete
Followup:
You mean like this then:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int, y myTableType )
  2  nested table y store as y_tab
  3  /
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index y_idx on y_tab(nm,vl)
  2  /
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values
  2  ( 1, myTableType( myScalarType( 'volume.break', '1' ),
  3                    myScalarType( 'ELEMENT_NAME', 'topic'),
  4                    myScalarType( 'PUB_CODE', 'FLLB' )
  5                  ) );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values
  2  ( 2, myTableType( myScalarType( 'volume.break', '2' ),
  3                    myScalarType( 'ELEMENT_NAME', 'xtopic'),
  4                    myScalarType( 'PUB_CODE', 'FLLB' )
  5                  ) );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select t.x, t2.nm, t2.vl
  2    from T, TABLE(t.y) T2
  3   where t.x in ( select t.x
  4                    from t, TABLE(t.y) t2
  5                   where t.x in ( 1, 2 )
  6                     and t2.nm = 'PUB_CODE'
  7                     and t2.vl = 'FLLB' )
  8     and nm = 'volume.break'
  9  /

         X NM                   VL
---------- -------------------- --------------------
         1 volume.break         1
         2 volume.break         2
Rating: 3
I'm experimenting on it now but using your example... August 16, 2002
Reviewer: Pete Krueger from NY USA
Using your example, I would just add this (SEGMENT_UUID):

insert into t values
 ( 1, myTableType( myScalarType( 'volume.break', '1' ),
                   myScalarType( 'ELEMENT_NAME', 'topic'),
                   myScalarType( 'SEGMENT_UUID', 'I123'),
                   myScalarType( 'PUB_CODE', 'FLLB' )
                 ) );

1 row created.

insert into t values
 ( 2, myTableType( myScalarType( 'volume.break', '2' ),
                   myScalarType( 'ELEMENT_NAME', 'xtopic'),
                   myScalarType( 'SEGMENT_UUID', 'I124'),
                   myScalarType( 'PUB_CODE', 'FLLB' )
                 ) );

And the results I'm working for are:

VL                    VL
-------------------- --------------------
1                    I123
2                    I124

Alternatively, I could make do with:

NM                   VL
-------------------- --------------------
volume.break         1
SEGMENT_UUID         I123
volume.break         2
SEGMENT_UUID         I124

But maybe a join can be done somehow on the values.

I'll keep testing!  Thanks!

-Pete
Followup:
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values
  2  ( 1, myTableType( myScalarType( 'volume.break', '1' ),
  3                    myScalarType( 'SEGMENT_UUID', 'I123'),
  4                    myScalarType( 'ELEMENT_NAME', 'topic'),
  5                    myScalarType( 'PUB_CODE', 'FLLB' )
  6                  ) );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values
  2  ( 2, myTableType( myScalarType( 'volume.break', '2' ),
  3                    myScalarType( 'SEGMENT_UUID', 'I124'),
  4                    myScalarType( 'ELEMENT_NAME', 'xtopic'),
  5                    myScalarType( 'PUB_CODE', 'FLLB' )
  6                  ) );

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>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select t.x,
  2             max( decode( t2.nm, 'volume.break', t2.vl, null ))
"volumn.break",
  3             max( decode( t2.nm, 'SEGMENT_UUID', t2.vl, null )) SEGMENT_UUID
  4    from T, TABLE(t.y) T2
  5   where t.x in ( select t.x
  6                    from t, TABLE(t.y) t2
  7                   where t.x in ( 1, 2 )
  8                     and t2.nm = 'PUB_CODE'
  9                     and t2.vl = 'FLLB' )
 10     and nm in ('volume.break','SEGMENT_UUID')
 11   group by t.x
 12  /

         X volumn.break         SEGMENT_UUID
---------- -------------------- --------------------
         1 1                    I123
         2 2                    I124

ops$tkyte@ORA817DEV.US.ORACLE.COM>

QED
Rating: 4
It's close...probably good enough...but not perfect August 19, 2002
Reviewer: Pete Krueger from NY USA
It returns 138 rows instead of 14 that I'd like - the 124 unwanted records just
have an empty volume number (I could probably make due with this and in java
just say if the volume number is null or an empty string, go to the next
record).

It returns all FLLB records because all records will have a SEGMENT_UUID, but
only some will have volume.break.

Instead of:
        name IN ('volume.break', 'SEGMENT_UUID')

I need: 'If I have a volume.break, include the SEGMENT_UUID...else skip the
record'

At least I don't have to put the Oracle objects into Java structs and loop
through each objects' attributes - that helps a lot.

If you have any last advice/suggestions, that'd be great!

Thanks a lot Tom - it's been a good learning experience and I look forward to
following your future discussions.

Pete
Followup:
select * from
(
select t.x,
       max( decode( t2.nm, 'volume.break', t2.vl, null )) "volumn.break",
       max( decode( t2.nm, 'SEGMENT_UUID', t2.vl, null )) SEGMENT_UUID
  from T, TABLE(t.y) T2
 where t.x in ( select t.x
                  from t, TABLE(t.y) t2
                 where t.x in ( 1, 2 )
                   and t2.nm = 'PUB_CODE'
                   and t2.vl = 'FLLB' )
   and nm in ('volume.break','SEGMENT_UUID')
 group by t.x
)
where "volumne.break" IS NOT NULL
/

QED*2
Rating: 5
what if... February 17, 2004
Reviewer: dwl from uk
Tom

What about the following then:

drop table sbe1;
drop type myTableType;
drop type myScalarType;

create or replace type myScalarType as object
( org_id number(38),
reason_count number(38),
description     varchar2(26)
)
/

create or replace type myTableType as table
of MyScalarType
/

CREATE TABLE SBE1 (RETURN_DATE            DATE            NOT NULL,
        PAYMENT_ORG_ID                  NUMBER(38)          NOT NULL,
        st_count            NUMBER(38),
        sbe4            myTableType) nested table sbe4 store as n4;

insert into sbe1 values
(sysdate, 1, 2,    myTableType(     myScalarType( 1, 10, 'Under 16'),
                myScalarType( 1, 20, 'Student'),
                myScalarType( 1, 30, 'HC2')
));

insert into sbe1 values
(sysdate, 2, 4,    myTableType(     myScalarType( 2, 11, 'Under 16'),
                myScalarType( 2, 22, 'Student'),
                myScalarType( 2, 33, 'HC2')
));

insert into sbe1 values
(sysdate, 3, 8,    myTableType(     myScalarType( 3, 11, 'Under 16'),
                myScalarType( 3, 22, 'Student'),
                myScalarType( 3, 33, 'HC2')
));

insert into sbe1 values
(sysdate, 4, null,    myTableType(     myScalarType( 4, 11, 'Under 16'),
                    myScalarType( 4, 22, 'Student'),
                    myScalarType( 4, 33, 'HC2')
));

insert into sbe1 values
(sysdate, 5, 16,    myTableType());

select sbe1.payment_org_id, sbe1.st_count,t2.org_id, t2.reason_count,
t2.description
from sbe1, TABLE(sbe1.sbe4) t2
where sbe1.payment_org_id = t2.org_id (+);

This gives:

PAYMENT_ORG_ID   ST_COUNT     ORG_ID REASON_COUNT DESCRIPTION
-------------- ---------- ---------- ------------ --------------
             1          2          1           10 Under 16
             1          2          1           20 Student
             1          2          1           30 HC2
             2          4          2           11 Under 16
             2          4          2           22 Student
             2          4          2           33 HC2
             3          8          3           11 Under 16
             3          8          3           22 Student
             3          8          3           33 HC2
             4                     4           11 Under 16
             4                     4           22 Student
             4                     4           33 HC2

It appears to have ignored the outer join.
i wanted the outer join so that the nested table which is null, when outer
joined to the parent table, would still retrive the extra data from the null
row, ie i want to see something like this:

PAYMENT_ORG_ID   ST_COUNT     ORG_ID REASON_COUNT DESCRIPTION
-------------- ---------- ---------- ------------ --------------
             1          2          1           10 Under 16
             1          2          1           20 Student
             1          2          1           30 HC2
             2          4          2           11 Under 16
             2          4          2           22 Student
             2          4          2           33 HC2
             3          8          3           11 Under 16
             3          8          3           22 Student
             3          8          3           33 HC2
             4                     4           11 Under 16
             4                     4           22 Student
             4                     4           33 HC2
             5         16          5

How would you get this type of result?
Followup:
It is not that the outer join was ignored, it isn't part of the equation!

ops$tkyte@ORA920PC> select sbe1.payment_org_id, sbe1.st_count,t2.org_id,
t2.reason_count,
  2  t2.description
  3  from sbe1, TABLE(sbe1.sbe4) t2
  4  /

PAYMENT_ORG_ID   ST_COUNT     ORG_ID REASON_COUNT DESCRIPTION
-------------- ---------- ---------- ------------ --------------------------
             1          2          1           10 Under 16
             1          2          1           20 Student
             1          2          1           30 HC2
             2          4          2           11 Under 16
             2          4          2           22 Student
             2          4          2           33 HC2
             3          8          3           11 Under 16
             3          8          3           22 Student
             3          8          3           33 HC2
             4                     4           11 Under 16
             4                     4           22 Student
             4                     4           33 HC2

12 rows selected.

TABLE() doesn't "join" like you think. that was just a predicate performed
AFTER the fact...

but, back to the question -- how to do this?  well, check this out:

ops$tkyte@ORA920PC> select sbe1.payment_org_id, sbe1.st_count,t2.org_id,
t2.reason_count, t2.description
  2    from sbe1, TABLE(sbe1.sbe4)(+) t2
  3  /

PAYMENT_ORG_ID   ST_COUNT     ORG_ID REASON_COUNT DESCRIPTION
-------------- ---------- ---------- ------------ --------------------------
             1          2          1           10 Under 16
             1          2          1           20 Student
             1          2          1           30 HC2
             2          4          2           11 Under 16
             2          4          2           22 Student
             2          4          2           33 HC2
             3          8          3           11 Under 16
             3          8          3           22 Student
             3          8          3           33 HC2
             4                     4           11 Under 16
             4                     4           22 Student
             4                     4           33 HC2
             5         16

13 rows selected.

see:
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96594/adobjbas.htm#458840
and specifically:
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96594/adobjbas.htm#466890
Rating: 5
cool February 17, 2004
Reviewer: dwl from uk
That is awesome, thanks very much!!
Rating: 3
Nice March 18, 2004
Reviewer: Ram from Bangalore,India
Dear Sir,
Please have a look at the following objects and only one question

SQL> create type cust_ty as object(Bank_name varchar2(30),Bank_bal number(8,2))
  2  /

Type created.

SQL> create type cust_tab as table of cust_ty;
  2  /

Type created.

sql> create table customer(cust_name varchar2(30),cust_data cust_tab)
  2* nested table cust_data store as cust_nt_data
SQL> /

Table created.

SQL> insert into customer values('King',
  2                  cust_tab(
  3                     cust_ty('HSBC',10000.90),
  4                     cust_ty('Std_chartered',5000.50),
  5                     cust_ty('NationalBank',450.75)
  6                           ));

1 row created.

SQL> ed
Wrote file afiedt.buf

  1  insert into customer values('Jones',
  2                  cust_tab(
  3                     cust_ty('CommerzBank',1000.90),
  4                     cust_ty('Std_chartered',5000.50),
  5                     cust_ty('NationalBank',450.75),
  6                     cust_ty('ABN_AMRO',25000.55)
  7*                          ))
SQL> /

1 row created.

My question is
 1)Can the group functions be applied to nested table? For example
    How to get the sum(bank_bal),min(bank_bal),max(bank_bal) for each customer?
You may use the data objects provided if you like.
Please do reply.
Bye!
Followup:
since you can flatten the set like this:

ops$tkyte@ORA9IR2> select c.cust_name, x.bank_name, x.bank_bal
  2    from customer c, table( c.cust_data ) x;

CUST_NAME                      BANK_NAME                        BANK_BAL
------------------------------ ------------------------------ ----------
King                           HSBC                              10000.9
King                           Std_chartered                      5000.5
King                           NationalBank                       450.75
Jones                          CommerzBank                        1000.9
Jones                          Std_chartered                      5000.5
Jones                          NationalBank                       450.75
Jones                          ABN_AMRO                         25000.55

7 rows selected.

it becomes very natural to do this

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select c.cust_name, sum(x.bank_bal), min(x.bank_bal),
max(x.bank_bal)
  2    from customer c, table( c.cust_data ) x
  3   group by c.cust_name
  4  /

CUST_NAME                      SUM(X.BANK_BAL) MIN(X.BANK_BAL) MAX(X.BANK_BAL)
------------------------------ --------------- --------------- ---------------
Jones                                  31452.7          450.75        25000.55
King                                  15452.15          450.75         10000.9
March 18, 2004 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.
Rating: 5
merge with condition March 08, 2004
Reviewer: karma from MA USA
Using merge
 when matched then update
 when not matched then insert. In this case of insert can you only insert based
on some condition? like insert only if it meets certain criteria?
Followup:
no, you would have to filter out rows you did not want to insert in the USING
clause.
Rating: 3
OK March 18, 2004
Reviewer: NATHAN from Singapore
Dear Tom,
I would like to update the following table with a condition "Update
 first row by 1,second row by 2,third row by 3"

SQL> select * from tbl;

         I
----------
         1
         2
         3

SQL> desc tbl
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 I                                                  NUMBER

Updation results in
sql>select * from tbl;
       I
-----------
       2
       4
       6

How to do that?
Followup:
what is the "first row"... hmm, do you think things come out in the order they
went in?

set i = i+rownum;

but don't be surprised by the results.  there is no such thing as "the first row
in a table", the "nth row" or the "last row"
Rating: 5
upsert on master/detail? March 18, 2004
Reviewer: bob from PA
Hi Tom,

Thanks for this discussion above.  If the record from the master table you are
merging/upserting has detail records what do you do?

From a singe line of input from a flat file, I create a surrogate pk, some
attributes, and populate a detail table with somewhere between 1-8 rows of data
associated with the master record.  Some of these attributes define a unique
key, but potentially may change, so I went with the surrogate key.

each new line of the flat file requires an upsert (no problem there thanks to
the above discussions) but the detail records may be potentially different.

I decide if I should attempt to identify if all the associated detail records
are the same or not.  My initial thoughts say:

1. insert,
2. if unique constraints are violated in the master record, update it, and
delete associated detail records and reload them, even if they are potentially
the same.  This seems wasteful, but easier than trying to check each time
whether or not I have exactly the N number of records with that same FK and that
each of those records has the values that match my list of expected values.
(sounds like a simple question, but I can't convert it from a multi-step
procedure to simple sql)

your thoughts please?
Followup:
i don't understand why the fact that a table being merged into is a "master"
record with "details" has any bearing?

Your second paragraph describes a multi-table insert, not a merge?

so, it sounds like you are getting a single record with the master AND details,
it'll be near impossible to use a "MERGE" for that, given the "i create a
surrogate pk" statement.

don't see how you would process this efficiently

(better to update generally and upon hitting sql%rowcount=0, insert.   the
insert actually does all of the work of inserting and upon duplication runs
recursive sql to get constraint information and such.  can be very expensive)
Rating: 5
no merge possibilities. March 18, 2004
Reviewer: bob from PA
Tom,

Thank you for your timely response.
You described the situation correctly.  Multi-table insert, that might fail and
require an update when the master table pk is violated.

I have a >98% insert, <2% update situation, so the expense of the occasionaly
recursive sql on the failed update might be better than the 98% sql%rowcount=0.

Regardless, I can benchmark that to verify where the breakeven point is for my
data.  I can live without the efficiency of a merge, but where does that leave
me with regards to attempting to update multiple detail records from the new
line single line of input.

I see the only option for the detail table as delete everything and reinsert.

Since deletes may be required if too many values are currently in the detail
table, I might as well just delete everything and reinsert.

If I could devise a query that would tell me whether all detail values I have
are present with no extras, I could potentially avoid the delete/insert
altogether, but I haven't been able to do that.

It sounds simple though.  get the count of records matching the fk and the known
list of values, get the total count matching the fk, if they are not the same,
than delete and insert.
March 18, 2004 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"
Rating: 5
Let's say you DO have self-referential integrity January 22, 2004
Reviewer: Doug from CT, USA
Tom - if you DO have self - referenetial integrity, and say it's only a couple
of tables, how you can you cleanly remove those rows in temp_constraints so that
the output at least functions, although it is missing a description of the
self-referential stuff.
Followup:
go for it!  consider it a challenge!

(in 10g, i'd use "NOCYCLE" on the connect by :)

think about it -- a recusive relationship - when do you stop?

if you want to do this in 9i and before, you'll be writing procedural code to
process the data.
Rating: 5
"NOCYCLE" emulation in pre-10g March 17, 2004
Reviewer: Matthias Rogel from Kaiserslautern, Germany
also annoyed by ORA-01436 and not yet upgraded to 10g,
this one may be helpful.

input: any query resulting in a parent/child-relationship
output: all paths in the resulting graph, stopping at cycles
        (optionally no sub-path is delivered, "only the longest")

with it you can for example

query your Parent-Child-Table-Hierarchy

select * from table(hierarchical.allpaths(cursor(select a.table_name as parent,
b.table_name as child from user_constraints a, user_constraints b where
a.constraint_name = b.r_constraint_name and a.constraint_type = 'P' and
b.constraint_type = 'R')))

( try out
create table temp1 (t1 integer primary key, t3 integer);
create table temp2(t2 integer primary key, t1 references temp1(t1));
create table temp3(t3 integer primary key, t2 references temp2(t2));
alter table temp1 add constraint tc1 foreign key(t3) references temp3(t3);
and let the query run)

or query your object-dependeny-hierarchy
select * from table(hierarchical.allpaths(cursor(select referenced_name as
parent, name as child from user_dependencies where referenced_owner=user)));

note that 2 temp tables are needed.
note further that the in_list_number-function I learnt from
this site.

here we go

create global temporary table allpcr_h(
   entity varchar2(4000) unique,
   allpcr_h_id integer primary key)
on commit delete rows;

create sequence allpcr_h_seq;

CREATE TRIGGER allpcr_h_ID
BEFORE INSERT ON allpcr_h
FOR EACH ROW
begin
  select allpcr_h_seq.nextval
    into   :new.allpcr_h_ID
    from dual;
end;
/

create global temporary table allpcr(
   pid integer,
   cid integer,
   path varchar2(4000))
on commit delete rows;

create type numbertable is table of number;
/

create type longstringtable is table of varchar2(32767);
/

create package hierarchical is
     function in_list_number(vinstring in varchar2) return numbertable;

     type CPType is record(
        parent varchar2(512),
        child varchar2(512));

     type CPRType is REF Cursor return CPType;

     function AllPaths(vcp in CPRType,
                       vonlylongest in integer default 1, -- if set to 0, all
paths are returned
                       vseparator in varchar2 default ' -> ')
       return longstringtable pipelined;
end hierarchical;
/

create package body hierarchical is

function in_list_number(vinstring in varchar2) return numbertable is
l_string        long default vinstring || ',';
l_data          numbertable := numbertable();
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 in_list_number;

function AllPaths(vcp in CPRType,
                  vonlylongest in integer,
                  vseparator in varchar2) return longstringtable pipelined is
pragma autonomous_transaction;
cp CPType;
hnt numbertable;
i integer;
ret varchar2(32767);
begin
   loop
      fetch vcp into cp;
      exit when vcp%notfound;

      insert into allpcr_h(entity)
      (select cp.parent from dual
      union all select cp.child from dual)
      minus select entity from allpcr_h;

      insert into allpcr(pid, cid, path)
      select hp.allpcr_h_id, hc.allpcr_h_id, hp.allpcr_h_id || ',' ||
hc.allpcr_h_id
      from allpcr_h hp, allpcr_h hc
      where hp.entity = cp.parent
      and hc.entity = cp.child
      minus select pid, cid, path from allpcr;
   end loop;

   close vcp;

   loop
      insert into allpcr(pid, cid, path)
      select ap.pid, ac.cid, ap.pid || ',' || ac.path
      from allpcr ap, allpcr ac
      where ap.cid = ac.pid
      and (ap.pid, ac.cid) in
           (select ap.pid, ac.cid
            from allpcr ap, allpcr ac
            where ap.cid = ac.pid
            minus
            select pid, cid
            from allpcr);
      exit when sql%rowcount = 0;
   end loop;

   if vonlylongest != 0 then
      delete allpcr ad
      where exists
        (select 1
         from allpcr aa
         where aa.rowid != ad.rowid
         and (aa.path like '%,' || ad.path
           or aa.path like ad.path || ',%'));
   end if;

   for allpcr_rec in (select * from allpcr) loop
      hnt := in_list_number(allpcr_rec.path);
      ret := null;
      for i in 1 .. hnt.count loop
         if ret is null then
            select entity
            into ret
            from allpcr_h
            where allpcr_h_id = hnt(i);
         else
            select entity || vseparator || ret
            into ret
            from allpcr_h
            where allpcr_h_id = hnt(i);
         end if;
      end loop;
      pipe row(ret);
   end loop;

   rollback;

   return;
end AllPaths;

end hierarchical;
/
Rating: 3
Bug in HTML-DB ? March 18, 2004
Reviewer: Matthias Rogel from Kaiserslautern, Germany
funny, just played around with my test-account
on
http://htmldb.oracle.com/pls/otn/development_service_home
(

select * from v$version

SQL Query Results
Output to Excel
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
        1-5

Statement took 0.02 seconds.

when I try the statement I posted
select * from table(hierarchical.allpaths(cursor(select a.table_name as parent,

b.table_name as child from user_constraints a, user_constraints b where
a.constraint_name = b.r_constraint_name and a.constraint_type = 'P' and
b.constraint_type = 'R')))

I get a
Not Found
The requested URL /pls/otn/wwv_flow.accept was not found on this server.

Oracle HTTP Server Powered by Apache/1.3.22 Server at htmldb.oracle.com Port 80

I have no problems using sqlplus on my local 9-er dbs.
Followup:
Matthias,

Thank you for reporting this.

This is, in fact, a bug in the underlying database that is running HTML DB and
not a defect in HTML DB itself.

The bug, 3055525, is specific to a pipelined function returning an object type,
and this aborts the database session.  This problem is specific to Linux and
specific to 9.2.0.3 (which is what is currently running on htmldb.oracle.com).
This problem will be corrected on htmldb.oracle.com when the site is upgraded to
production 10g.

I reproduced this problem using SQL*Plus on this instance...thus removing HTML
DB from the picture.

Thanks again.

Joel
March 18, 2004 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
Rating: 5
Grant access from achema A to B against C March 18, 2004
Reviewer: A reader
Tom

We would like to use a trigger on a table that when populated creates a grant
for update access. Is this possible as someone here says a grant can only be
issued by the table owner?

So, table A has a column which contains a tablename. Trigger on update/insert
grants update to this schema/table to the account owning A. I was wondering if
the trigger could belong to a DBA acc and so be secure this way?

Ta in advance!
Dave
Followup:
depends on the release and I don't see any info here for that :(
March 18, 2004 GAURANG -- Thanks for the question regarding "rownum", version 8.1.5
You Asked
hi tom.

is rownum is function?

is rownum display physical location of row or
it display the order in which the row derived from database.

thanks
and we said...
Rownum is a "psuedo column", not a function.

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.
....

it has nothing to do with the physcal location of a row.

It is the order in which the row was selected by a given query plan (eg: given
the same query -- the rownums assigned to rows may very well be different using
the rule based optimizer then the cost based)
Review & Followup

Rating: 3
OK March 18, 2004
Reviewer: Richard from Minneapolis,USA
Hi Tom,
Where actually are the pseudocolumns stored?How does a table maintain a
connection with that?Any DD view exists over there which can show all the
pseudocolumns?
Please do reply.
Bye!
Followup:
psuedo columns by definition do not exist anywhere :) trick question.

rownum for example is assigned to rows in a result set as they are output.  it
doesn't "live" anywhere.

rowid is a function of where the row lives.  the rowid is assembled for a row
when it is retrieved based on the block we find it on.

and so on.....

there is no connection with them -- they are "dynamic".

they are documented in the sql reference.
March 18, 2004 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://download-east.oracle.com/docs/cd/A81042_01/DOC/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
Rating: 4
CASE or temp table? February 18, 2004
Reviewer: John from San Jose, CA
Hi Tom,

If I have a lot of values, say 50, to check in a CASE statement, would it be
beter to store the values in a table and join to it? Seems to me CASE would win
since it does not have to go to the disk to get data.

Also, if the same CASE construct appears twice in the same statement, would
ORALCE go through the same logic twice? From the autotrace I can not really
tell.

My thinking is that if you need to go through the same CASE logic several times
in the same query, then you mighe be better off using a table to store those
values. Since you will have to retrieve the data only once. Can yu comment on
this? Thanks!

John
Followup:
benchmark it for us :)
Rating: 3
case with exists February 19, 2004
Reviewer: wasey from US
I have three simple  tables
t1, t2, t3 and each have a date column dt1, dt2 and dt3 respectively.
I would like to do the following

 select * from t1
   where exists
  ( select case when to_number(to_char(sysdate, 'DD') = 19
    then
         select 'x' from t2
          where dt2 = dt1
        else
           select 'x' from t3
            where dt3 = dt1
           end)

Can you please let me know the best way to implement the above scenerio.

Thanks in advance
Wasey
Followup:
the best way would be to get a new schema, that isn't going to perform "very
well" (seriously)

ops$tkyte@ORA920PC> create table t1 ( x int );
Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert into t1 values ( 0 );
1 row created.

pretend all_users is your t2 and all_objects is your t3 and that it is the 19th.
 I have a user_id 0, but no object_id 0

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select *
  2    from t1
  3   where exists ( select null
  4                    from dual
  5                   where case
  6                         when to_char(sysdate,'dd')='19'
  7                         then (select 1 from all_users where user_id=t1.x and
rownum=1)
  8                         else (select 1 from all_objects where object_id=t1.x
and rownum=1)
  9                         end = 1 )
 10  /

         X
----------
         0

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select *
  2    from t1
  3   where exists ( select null
  4                    from dual
  5                   where case
  6                         when to_char(sysdate,'dd')='20'
  7                         then (select 1 from all_users where user_id=t1.x and
rownum=1)
  8                         else (select 1 from all_objects where object_id=t1.x
and rownum=1)
  9                         end = 1 )
 10  /

no rows selected
Rating: 4
case in where February 23, 2004
Reviewer: John from Canada
I would like to ask the question in similar to the earlier
 lets say t(id number(10), description varchar2(30)
   select *
  from t
   where (case when day =1
        then
            t.id = 1
        else
             t.id = 2
          end)
  How do I achieve this.
I would like to retrieve multiple rows depending upon there ocurrences

  Appreciate your response

Regards,
John
Followup:
where t.id = decode( day, 1, 1, 2 );
Rating: 3
case in where February 23, 2004
Reviewer: John from Canada
I would like to ask the question in similar to the earlier
 lets say t(id number(10), description varchar2(30)
   select *
  from t
   where (case when day =1
        then
            t.id = 1
        else
             t.id = 2 and t.description like 'a%'
          end)
  How do I achieve this.
I would like to retrieve multiple rows depending upon there ocurrences

  Appreciate your response

Regards,
John
Rating: 4
merge rows March 18, 2004
Reviewer: A reader
Hi

I have this following output after running a query against two table joins

select YY.BO_ID,
       YY.CM_ID,
       Y.ROLE_TYPE_ID,
       YY.CM_TYPE_ID
  from (select bo_id,
               cm_id,
               PROFILE_CM_SEQ,
               CM_TYPE_ID
         from sysadm.PS_BO_CM_PROFL_DTL
        where cm_type_id in (1, 2)
          and primary_ind = 'Y'
          and effdt < sysdate) YY,
       (select BO_ID,
               PROFILE_CM_SEQ,
               ROLE_TYPE_ID
          from sysadm.PS_BO_ROLE_CM
         where ROLE_TYPE_ID in (1, 9)) Y
 where YY.BO_ID = Y.BO_ID
   and YY.PROFILE_CM_SEQ = Y.PROFILE_CM_SEQ
   and ((cm_type_id = 1 and role_type_id = 9) OR
        (cm_type_id = 2 and role_type_id = 2))

BO_ID        CM_ID      ROLE_TYPE_ID
--------     -------    --------------
1002         1          2
1002         2          2

I need to merge them so they become

BO_ID        CM_ID1     CM_ID2    ROLE_TYPE_ID
--------     -------    --------  -------------
1002         1          2         2

CM_ID means contact method, basically I am getting a customer´s contact method,
either 1 or 2 (phone and address), if I dont merge this into one row I get
duplicate customer ids (BO_ID)

Maximum there will be two contact methods per customer so there is no need to
make number of columns dynamic...

Shall I do this with UNION?

Cheers
Followup:
if the query returns either 0, 1 or 2 rows (eg: you run this for a customer),
then

select bo_id, max(decode(r,1,cm_id)) cm_id1, max(decode(r,2,cm_id)) cm_id2,
ROLE_TYPE_ID
  from (
select YY.BO_ID,
       YY.CM_ID,
       Y.ROLE_TYPE_ID,
       YY.CM_TYPE_ID,
       rownum r
  from (select bo_id,
               cm_id,
               PROFILE_CM_SEQ,
               CM_TYPE_ID
         from sysadm.PS_BO_CM_PROFL_DTL
        where cm_type_id in (1, 2)
          and primary_ind = 'Y'
          and effdt < sysdate) YY,
       (select BO_ID,
               PROFILE_CM_SEQ,
               ROLE_TYPE_ID
          from sysadm.PS_BO_ROLE_CM
         where ROLE_TYPE_ID in (1, 9)) Y
 where YY.BO_ID = Y.BO_ID
   and YY.PROFILE_CM_SEQ = Y.PROFILE_CM_SEQ
   and ((cm_type_id = 1 and role_type_id = 9) OR
        (cm_type_id = 2 and role_type_id = 2))
)
group by bo_id, role_type_id
/

will do, else:

....
       YY.CM_TYPE_ID,
       row_number() over (partition by yy.bo_id order by yy.cm_id) r
  from (select bo_id,

.......

will do.
March 18, 2004 -- 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"
Rating: 3
ok March 18, 2004
Reviewer: Mini from Bangalore,India
Dear Sir,
I have a doubt on REF constraint and not on REF CURSOR.
Sorry for disturbing you.Is REF actually an integrity constraint?*Could you
please show a simple example how does
it act as a constraint?*
Followup:
do you mean referential integrity constraint, as in a foreign key perhaps.
March 18, 2004 Raj -- Thanks for the question regarding "DBMS_PROFILER", version 8.1.5.0.0
You Asked
Hi Tom,

I am trying to execute the following test procedure but continuously getting an
error below. I have a right execute privilege to public. What else could be the
reason ??

Many Thanks in advance.
-------------------------------------------------------------
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;
/
-------------------------------------------------------------
LINE/COL ERROR
-------- ----------------------------------------------------
5/2      PLS-00221: 'START_PROFILER' is not a procedure or is
                    undefined
5/2      PL/SQL: Statement ignored
12/2     PLS-00221: 'STOP_PROFILER' is not a procedure or is undefined
12/2     PL/SQL: Statement ignored

-------------------------------------------------------------
Following is the result which you have asked me to check:

SQL> set role none;
Role set.

SQL> desc dbms_profiler
FUNCTION FLUSH_DATA RETURNS BINARY_INTEGER
PROCEDURE GET_VERSION
 Argument Name                  Type                    In/Out Default?
------------------------------ ----------------------- ------ --------
 MAJOR                          BINARY_INTEGER          OUT
 MINOR                          BINARY_INTEGER          OUT
FUNCTION INTERNAL_VERSION_CHECK RETURNS BINARY_INTEGER
FUNCTION START_PROFILER RETURNS BINARY_INTEGER
 Argument Name                  Type                    In/Out Default?
------------------------------ ----------------------- ------ --------
 RUN_COMMENT                    VARCHAR2                IN     DEFAULT
FUNCTION STOP_PROFILER RETURNS BINARY_INTEGER
and we said...
Ok, thats easy now.

In 8.1.6 they added PROCEDURES start|stop_profiler.  In 8.1.5, they apparently
only had FUNCTIONS start|stop_profiler.  change the code to:

CREATE OR REPLACE PROCEDURE do_mod AS
 cnt      NUMBER := 0;
 n        number;
BEGIN
  n := 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;
  n := DBMS_PROFILER.STOP_PROFILER;
END;

it returns the runid (key into the profile tables) but you can discard that, not
really relevant to you.
Review & Followup

Rating: 5
May 27, 2003
Reviewer: A reader
Hi Tom,
 When I run report Some of the data is burr.

I am running your version of profsum.sql from your book..

Here is the output..

2 rows updated.

PL/SQL procedure successfully completed.

=
=
====================
Total time

GRAND_TOTAL
-----------
      17.76

=
=
====================
Total time spent on each run

RUNID RUN_COMMENT    SECS
----- ----------- -------
    3 mod            8.97
    4 mod            8.79

=
=
====================
Percentage of time in each module, for each run separately

RUNID RUN_COMMENT UNIT_OWNER  UNIT_NAME         SECS PERCEN
----- ----------- ----------- -------------- ------- ------
    3 mod         SBLINT      DO_MOD            8.97  100.0
    4 mod         SBLINT      DO_MOD            8.79  100.0

=
=
====================
Percentage of time in each module, summarized across runs

UNIT_OWNER  UNIT_NAME       SECS PERCENTAG
----------- -------------- ----- ---------
SBLINT      DO_MOD         #####    100.00  <=========

=
=
====================
Lines taking more than 1% of the total time, each run separate

RUNID   HSECS    PCT OWNER       UNIT_NAME       LINE# TEXT
----- ------- ------ ----------- -------------- ------ ---------------------
    3  636.11   35.8 SBLINT      DO_MOD              8 IF ( MOD(cnt,1000) =
                                                       0 ) THEN

    4  614.98   34.6 SBLINT      DO_MOD              8 IF ( MOD(cnt,1000) =
                                                       0 ) THEN

    4  143.38    8.1 SBLINT      DO_MOD              7 cnt := cnt + 1;
    3  141.19    8.0 SBLINT      DO_MOD              7 cnt := cnt + 1;
    4  118.61    6.7 SBLINT      DO_MOD              6 FOR I IN 1..500000
                                                       LOOP

RUNID   HSECS    PCT OWNER       UNIT_NAME       LINE# TEXT
----- ------- ------ ----------- -------------- ------ ---------------------
    3  117.00    6.6 SBLINT      DO_MOD              6 FOR I IN 1..500000
                                                       LOOP

6 rows selected.

=
=
====================
Most popular lines (more than 1%), summarize across all runs

  HSECS    PCT UNIT_OWNER  UNIT_NAME       LINE# TEXT
------- ------ ----------- -------------- ------ ---------------------
#######   70.5 SBLINT      DO_MOD              8 IF ( MOD  (cnt,1000) =
                                                 0 ) THEN
 ==============
================

 284.57   16.0 SBLINT      DO_MOD              7 cnt := cnt + 1;
 235.61   13.3 SBLINT      DO_MOD              6 FOR I IN 1..500000
                                                 LOOP

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
----------- -------------- -------------- ------------- ------
SBLINT      DO_MOD                      6             8   75.0

=
=
====================
Number of lines actually executed for all units

LINES_EXECUTED
--------------
             6

=
=
====================
Total number of lines in all units

LINES_PRESENT
-------------
            8

Could you explain me what do you mean by "##### " in report??
Thanks
Followup:
just means the number didn't fit in the format, adjust the format in the script
for hsecs there (use more 9's in the format)
Rating: 3
January 07, 2004
Reviewer: A reader
can one get your optimized version of profsum.sql?
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: 5
Very Useful January 08, 2004
Reviewer: Sami
Rating: 5
Isn't DBMS_PROFILER somewhat useless? March 18, 2004
Reviewer: Finn Ellebaek Nielsen (ChangeGroup ApS) from Denmark
Isn't DBMS_PROFILER somewhat useless? Seen that it:

1. Doesn't give elapsed time of procedure/function calls on the line on which
they are called (only the overhead of setting up call stack and the opposite
upon return). This makes it impossible to pinpoint expensive procedure/functions
that should be optimized. If you own the code for these procedures/functions you
may have a report of the lines for them that individually perform fine but as a
program unit has a bad performance.

2. Doesn't give elapsed time of code not owned by the current user. This makes
it impossible to pinpoint expensive Oracle system calls.

3. Counts executions of lines with procedure/function calls double.

Please comment.

Is it possible for a customer to hook into how DBMS_PROFILER or PL/SQL code
compiled with debug information works in order to remedy these "features" of
DBMS_PROFILER or build one's own?
Followup:
no, it is not "somewhat useless".  what you say really isn't true in my
experience.

1) don't understand your point there.  the reports I'm looking at show me a
breakout of the expensive functions (modules) right there at the top of the
stack.  The very first part of the report I see shows me stuff like:

Percentage of time in each module, for each run separately

RUNID RUN_COMMENT UNIT_OWNER  UNIT_NAME         SECS PERCEN
----- ----------- ----------- -------------- ------- ------
    1 factorial   OPS$TKYTE   FACT_RECURSIVE     .71   66.1
      recursive

    1 factorial   OPS$TKYTE   P1                 .36   33.8
      recursive

    2 factorial   OPS$TKYTE   FACT_ITERATIVE     .52   58.9
      iterative

    2 factorial   OPS$TKYTE   P2                 .36   41.0
      iterative

In my example, p1 did some work, called fact_recursive.  p2 did some work,
called fact_iterative.  It seems clear to me in each run which model is the
lower hanging fruit.

2) depends on your PRIVS and the setting of O7_DICTIONARY_ACCESSIBILITY.

with O7_DICTIONARY_ACCESSIBILITY set to false, you cannot see "SYS" owned things
in general.  when set to true (should be viable in a testing environment --
specially testing) and you have CREATE privs on the affected objects -- it all
shows up.  I re-run my above test and the report shows:

RUNID RUN_COMMENT UNIT_OWNER  UNIT_NAME         SECS PERCEN
----- ----------- ----------- -------------- ------- ------
    1 factorial   OPS$TKYTE   FACT_RECURSIVE     .66   59.7
      recursive

    1 factorial   OPS$TKYTE   P1                 .36   32.5
      recursive

    1 factorial   SYS         DBMS_OUTPUT        .07    6.7
      recursive

    2 factorial   OPS$TKYTE   FACT_ITERATIVE     .52   53.7
      iterative

    2 factorial   OPS$TKYTE   P2                 .37   38.0
      iterative

    2 factorial   SYS         DBMS_OUTPUT        .07    7.1
      iterative

    2 factorial   SYS         STANDARD           .01    1.1
      iterative

7 rows selected.

we now see what you say you cannot see.  documentation states you need CREATE
priv on the objects (meaning, the guy running the profiler would need CREATE ANY
PROCEDURE -- eg: DBA like privs) in order to have instrumented the invoked code
and probed it.

3) looks like an issue in the query (the script I use doesn't run this part,
never looked at it.  but it does seem rather trivial to adjust for no?).  In
this case -- you have all of the code for profsum, the tables being queried are
in fact fully documented -- you can do what i did which is to create your own
custom reports (profsum is just a "demo" really).  go for it.

So, the "features" in #1 -- don't get it, seems to show me that information.
#2, security related with documented settings to let you see what you want to
see.

#3 -- it is all sitting right there, query away.
Rating: 5
March 18, 2004
Reviewer: Finn Ellebaek Nielsen (ChangeGroup ApS) from Denmark
Thanks for the prompt reply. According to MetaLink Note:97270.1:

1. "The line of the procedure call ... is counted twice - the call and the
return from the procedure".

2. "The time of the procedure call is only the overhead of the call and return,
not the execution time of the entire procedure".

Comments:

1. is difficult to account for in the report (you would have to parse the source
code and understand that you're calling a procedure/function on a given line).
And it doesn't seem to be working that way always.

2. is illustrated by the following small test program:

create or replace procedure loop_test as
  procedure sleep(s in number) as

    a number;

  begin
    for i in 1 .. s * 1000000 loop
      a := a + 1;
    end loop;
  end;

begin
  dbms_profiler.start_profiler('loop_test ' ||
      to_char(sysdate, 'yyyy-mm-dd hh24:mi:ss'));
  sleep(1);
  null;
  sleep(1.5);
  null;
  sleep(3.3);
  null;
  dbms_profiler.stop_profiler();
end;
/

show errors

alter procedure loop_test compile debug;

delete
from plsql_profiler_data;

delete
from plsql_profiler_units;

delete
from plsql_profiler_runs;

set timing on

execute loop_test

Elapsed: 00:00:11.08

select p1.runid, p1.unit_number, p1.line#, p1.total_occur,
  p1.total_time / 1000000000 total_time,
  p1.min_time / 1000000000 min_time,
  p1.max_time / 1000000000 max_time,
          ( 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
where p1.runid = p2.runid and
  p2.unit_number = p1.unit_number
order by p1.unit_number, p1.line#;

RUNID UNIT_NUMBER  LINE# TOTAL_OCCUR TOTAL_TIME   MIN_TIME   MAX_TIME TEXT
----- ----------- ------ ----------- ---------- ---------- ----------
---------------------------------------------------------------------------------
-------------------
   16           1      7     5800003 5.00985816  .00000072  .04237248 for i in 1
.. s * 1000000 loop
   16           1      8     5800000 3.85492392  .00000056   .1229728 a := a +
1;
   16           1     10           3   .0000188  .00000608   .0000064 end;
   16           1     13           1  .00000112  .00000112  .00000112
dbms_profiler.start_profiler('loop_test ' ||
   16           1     15           1  .00000408  .00000408  .00000408 sleep(1);
   16           1     17           1  .00000384  .00000384  .00000384
sleep(1.5);
   16           1     19           1  .00000328  .00000328  .00000328
sleep(3.3);
   16           1     21           1  .00005016  .00005016  .00005016
dbms_profiler.stop_profiler();
   16           1     22           3  .00002136  .00000272  .00000992 end;

9 rows selected.

select grand_total / 1000000000 as grand_total
from plsql_profiler_grand_total;

=
=
====================
Total time

GRAND_TOTAL
-----------
       8.86

Problems:

1. Total execution time is 8.86, elapsed time was 11.08.

2. The execution time of line 7 and 8 (totalling about 8.86) is not reflected on
lines 15, 17, and 19 which are the calls to SLEEP. 15, 17, and 19 total to
0.00001000.

Could the problem be that granularity of the timing is a standalone program unit
(so I wouldn't see this problem if SLEEP was a standalone procedure instead of a
procedure in LOOP_TEST)?

Not currently able to test your suggested dictionary init.ora parameter but it
won't help if the aforementioned problems cannot be fixed.

We're running 9.2.0.2.0 on Solaris 8.

Best regards,

Finn Ellebaek
Followup:
#1 -- you have humans reading this right?

#2 -- if sleep were "top level", it would be reported.  as it is, it is just
code inside the larger procedure.

sorry if these two things make is useless to you - I'll just keep on using it
thanks.  the call count to me "isn't even on the radar screen" (eg: i'd never
even look at it.  Who cares if 100 calls took 5000 seconds or 200 calls took
5000 seconds.  I care that it took 5000 seconds and this identifies it for me)

For the second thing, use top level units.
Rating: 5
March 18, 2004
Reviewer: Finn Ellebaek Nielsen (ChangeGroup ApS)
Again, thanks for the prompt reply.

Conclusion: DBMS_PROFILER is completely useless with packaged code (I suspect
the same problem exists with object types), which is exactly the type of code
you would need to profile in the first place because you have thousands (perhaps
millions) of lines of PL/SQL code that need to be profiled. There are many
well-known good reasons to package related code in a PL/SQL package/object type.

I hope this will be changed by Oracle in future versions (sooner rather than
later :-)).

Best regards,

Finn Ellebaek
March 18, 2004 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!
Rating: 4
Update using dblinks for million records February 23, 2004
Reviewer: Karthik from Charlotte,NC
Tom
Thanks very much for the above techique. It is very nice and useful. Using your
techique, I am trying to do update using dblink.

My remote table emp_hist have about 6.5M records. Update is going to happen for
3M records.  I am using 8.1.7 in Unix.

Following is my sql, which is taking ages to finish job.
Could you please suggest me, how to tune this for better.

UPDATE    emp_hist@remotedb  t2
SET (yearly_percent,weekly_percent ) =
  (SELECT t1.yearly_percent,
          t1.weekly_percent
      FROM
         ( select  week_no,
                   fiscal_year,
                   weekly.emp_no empno,
                   DECODE(f_52weeks.ordered_dollars, 0,
0,ROUNDf_52weeks.shipped_dollars/f_52weeks.ordered_dollars,3))*100
yearly_percent,
                 DECODE(weekly.ordered_dollars, 0,
0,ROUND(weekly.shipped_dollars/weekly.ordered_dollars, 3))*100 weekly_percent
     from
             (  SELECT wdes.emp_no,
                    wdes.week_date,
                fnc.week_no   week_no,
                fnc.fiscal_year fiscal_year,
                  SUM(wdes.ordered_dollars) ordered_dollars,
                  SUM(wdes.shipped_dollars) shipped_dollars
             FROM weekly_dept_emp_sal wdes,
                              my_calendar@remotedb fnc
            WHERE   wdes.week_date = fnc.week_date
            AND fnc.fiscal_year IN (2003,2004)
            GROUP BY wdes.emp_no,
                 wdes.week_date,
                 fnc.week_no,
                 fnc.fiscal_year
            ) weekly,
         (   SELECT wdes.emp_no,
                wdes.week_date,
                    fnc.week_no  week_no,
                    fnc.fiscal_year  fiscal_year,
                SUM(wdes.ordered_dollars) ordered_dollars,
                SUM(wdes.shipped_dollars) shipped_dollars
            FROM weekly_dept_emp_sal wdes,
                 my_calendar@remotedb fnc
                WHERE wdes.week_date BETWEEN ( fnc.week_date - 363 ) AND
fnc.week_date
            AND     fnc.fiscal_year IN (2003,2004)
            AND     wdes.week_date = fnc.week_date
            GROUP BY wdes.emp_no,
                          wdes.week_date,
                     fnc.week_no,
                     fnc.fiscal_year
            ) f_52weeks
       WHERE weekly.week_date = f_52weeks.week_date
       AND    weekly.emp_no   =  f_52weeks.emp_no
       AND    fnc.fiscal_year IN (2003,2004)
      ) t1
  WHERE  t2.fiscal_year = t1.fiscal_year
  AND  t2.week_no = t1.week_no
  AND  t2.emp_no          = t1.emp_no
  AND t2.fiscal_year in (2003)
)

Thanks very much.
Followup:
that query doesn't fly

ops$tkyte@ORA9IR2> create table t1 ( a int, b int, c int );
Table created.

ops$tkyte@ORA9IR2> create table t2 ( a int, b int, c int );
Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> update t2
  2     set ( a,b ) = ( select a,b from t1 ) t1
  3   where t2.c = t1.c
  4  /
   set ( a,b ) = ( select a,b from t1 ) t1
                                        *
ERROR at line 2:
ORA-00933: SQL command not properly ended

you cannot join to a subquery like that.

But, assuming that

a) you want to update the remote table
b) row by row running a correlated subquery

You might consider:

a) creating a result set that has the primary key (fiscal_yes, week_no, emp_no)
b) put that into a real table (materialize that result set) on remote
c) on remote, update the join

rather then a correlated subquery 3million times over a dblink.
Rating: 5
can I do this in single SQL? February 26, 2004
Reviewer: A reader
Hi

I have this procedure

CURSOR C1 IS
     select  product, sum(qty) summy
     from proc_ff
     where flag_act is null
     group by product;
CURSOR C2 IS
       select sales, sumz
       from  proc_gg
       where  product   = v_product;

FOR i IN C1 LOOP
      v_product := c1.product;
      for   x in C2 loop
         UPDATE proc_gg
        set    percent = decode(C1.suma, 0, 0,
C2.sumz/C1.summy)
 where  product   = C1.product
        and    sales   = C2.sales;        commit;
      end  loop;
  END LOOP;

is it possible to do in single SQL?
Followup:
either of these:

merge into t2
using ( select t2.product, t2.sales,
               decode( sum(t1.qty), 0, 0, t2.sumz/sum(t1.qty)) pct
          from t1, t2
         where t1.product = t2.product
           and t1.flag_act is null
         group by t2.product, t2.sales, t2.sumz ) t
on (t.product = t2.product and t.sales = t2.sales )
when matched then update set percent = t.pct
when not matched then insert (product) values(null);

update t2
   set percent = ( select decode( sum(qty), 0, 0, t2.sumz/sum(qty) )
                     from t1
                    where t1.product = t2.product
                      and t1.flag_act is null )
 where product in ( select product from t1 )
/

(t1 = proc_ff, t2 = proc_gg)

Never never never commit in a for loop like that!! your transaction is what?
the successful update of proc_gg.  Not each row of proc_gg, the entire thing.

You commit when and only when YOU ARE DONE.
Rating: 5
Updating a table with a join to itself March 18, 2004
Reviewer: CJ from NY
I have a table with a a key that includes "real" keys plus a unique sequence
number. Records in this table may get adjustments, so that a new record appears
with the same real keys, but a new sequence number. If I wanted to update this
new record with some value from the original record, is there any easy way to do
it? I can determine the values with a select as follows (rec_type=3 means
adjustment):

select t1.key_1,t1.key_2,t1.key_3,t1.seqno,t2.upd_field
from t1, t1 t2
where t1.key_1=t2.key_1
  and t1.key_2=t2.key_2
  and t1.key_3=t2.key_3
  and t2.seqno < t1.seqno
  and t2.seqno = (select min(seqno) from t1 t3
                          where t2.key_1=t3.key_1
                            and t2.key_2=t3.key_2
                            and t2.key_3=t3.key_3)
  and t1.rec_type='3'
/
Thanks in advance.
Followup:
same "real keys" ;)  real keys would not be duplicated, but anyway - i
understand what you are saying...

ops$tkyte@ORA9IR2> select * from t;

        ID         K1         K2  UPD_FIELD   REC_TYPE UFLAG
---------- ---------- ---------- ---------- ---------- ----------
         1          1          1          0          0
         2          1          1         -1          3
         3          1          1          0          0
         4          1          1          0          0

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> merge into t t1
  2  using (select *
  3           from (select t.*,
  4                        row_number() over(partition by k1,k2 order by id) rn,
  5                        max(decode(rec_type,3,3)) over (partition by k1,k2)
max_rec_type
  6                   from t)
  7          where rn = 1
  8            and max_rec_type = 3) t2
  9     on (t1.k1 = t2.k1 and t1.k2 = t2.k2 and t1.rec_type = 3)
 10   when matched then update set t1.upd_field = t2.upd_field, t1.uflag =
'<<==='
 11   when not matched /* never happens */ then insert (id) values(null)
 12  /

1 row merged.

ops$tkyte@ORA9IR2> select * from t;

        ID         K1         K2  UPD_FIELD   REC_TYPE UFLAG
---------- ---------- ---------- ---------- ---------- ----------
         1          1          1          0          0
         2          1          1          0          3 <<===
         3          1          1          0          0
         4          1          1          0          0

ops$tkyte@ORA9IR2>
Rating: 5
Re: Updating a table with a join to itself March 18, 2004
Reviewer: CJ from NY
Yeah, funny about those "real" keys. :-)

Excellent answer, but I'm curious about:
max(decode(rec_type,3,3)) over (partition by k1,k2) max_rec_type

Why the decode?

Thanks
Followup:
I wanted to make sure there was a rec_type = 3 in the k1,k2 set of values.

the decode returns NULL for everything but rec_type 3

the max() over (partition by k1,k2) returns the max of the set of decodes for
that k1,k2 pair

that way:

  3           from (select t.*,
  4           row_number() over(partition by k1,k2 order by id) rn,
  5           max(decode(rec_type,3,3)) over (partition by k1,k2) max_rec_type
  6                   from t)
  7          where rn = 1
  8            and max_rec_type = 3) t2

only returns k1,k2 pairs when there is at least one rec_type = 3 so I could make
the following claim:

..
 10   when matched then update set t1.upd_field = t2.upd_field, t1.uflag =
'<<==='
 11   when not matched /* never happens */ then insert (id) values(null)
 12  /
March 18, 2004 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...
Rating: 5
Using TTS for TSPITR March 18, 2004
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom,
In 9i, before plugging in the tablespaces from auxiliary database into the
primary database, do I need to restrict access to primary database in any
manner? Only the tablespace that I am recovering will be offline, but users can
still use rest of the database. Is that right?
Thanks
Followup:
you did the recovery in the auxillary database already....  just plugging in
now.
March 18, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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))
Rating: 4
Can dynamic SQL be used to write merge statement February 10, 2004
Reviewer: Su from UK
Hi Tom,

 Will you please point to an example where merge statement is created
dynamically.
 Is it possible to use merge when external table and oracle tables dont have
same number of columns? In our system, we have same structure for External and
Oracle tables, but Oracle table has one more column - load_date which is sysdate
on which the row was inserted/updated in the table from file.

Thanks!!!

Cheers,
Su
Followup:
execute immediate l_stmt using bind_variable, bind_variable2,.....;

sure, it can be done dynamically -- as any SQL can be.

Yes, they do not have to have the same number -- you

merge into T1
using T2
on (join condition)
when matched then insert ( c1,c2,c3 ) values ( t2.c55, t2.c100, t2.c1000 )
when not matched then update set c1= t2.c22, c22 = t2.c12, c56 = t2.c100;

you can insert into T1 (any subset of columns from t1 ) values (any subset of
columns/constants from t2)

you can update (any subset of columns in t1) using (any subset of
columns/constants from t2)
Rating: 4
How above can be done using DBMS_SQL??? February 13, 2004
Reviewer: A reader
I have similar requirement where both the tables used in merge statement are
known at run-time. Will you please give some example illustrating this.

Thank you.
Followup:
dbms_sql.parse( l_cursor,
l_any_old_string_you_can_imagine_making_up_at_runtime);

merge is NOT any different than "insert", or "update", or "delete".

don't make more of this then you have to, it is the same as ANY other DML
statement.
Rating: 4
Urgent March 12, 2004
Reviewer: A reader
1.
My data is something like thsi

Name    vol

 a       32
 b       15
 b       15
 c       20

for those names which recur, I need to someup the vol and then merge it with
another table for
name A.

Is that possible using external tables => Merge

2.
Further, my csv file is on the nerwork drive, so from my database server can I
create a directory
using a folder on the network drive
Followup:
1) why external tables?  just merge:

merge into some_table
using ( select name, sum(vol) sum_vol from foo group by name ) X
on ( some_table.name = x.name )
when......

if that data is in fact in a file, sure, you can use an external table.

2) guessing by your terminology that you are using windows.  it is hard on
windows to do somethings -- this is one of them.

see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:241814624807
Rating: 5
Is NFS mount the solution for Unix platform? Thanks. March 13, 2004
Reviewer: reader
Can I place the flat file on the NFS mount for external tables to access the
file?
Followup:
absolutely
Rating: 2
NFS mount March 14, 2004
Reviewer: A reader
Tom

Can you tell something about 'NFS mount', or give us a link which explains the
basics about 'NFS mount '. Also please add your explantion..
Followup:
NFS = network file system.

It is the way to get a filesystem from "servera" mounted as if it were a
filesystem on "serverb"

Just makes a remote file system look "local"

sort of like a windows share, but lots easier.
Rating: 2
March 14, 2004
Reviewer: A reader
Tom

Out data in flat file is similar to
Name    vol

 a       32
 b       15
 b       15
 c       20
( it is not sorted though)

Yes, if the Name repeats then we have to take the summary of volume.

In my opinion, it is a must to have a staging table, where I will first dump my
data(using client_text_io(after configuring webutil)), then run  the following
statement

merge into some_table
using ( select name, sum(vol) sum_vol from STAGING_TABLE group by name ) X
on ( some_table.name = x.name )
when......

Our flat file will be available on the user desktop as such the use of utl_file
or external tables is ruled out.

2.
Another interesting question I have is about Sqlldr. Unlike utl_file, external
tables .. sqlloader is a client side utility. Is there any way I can write a
script having sqlloader statement which will comunicate with the user desktop
and dump the data into database table. BUT the catch is that this script access
should be through the application and not command line. We are using Forms 9i(
without webutil).

The challenge is that we access our application through the browser, Forms 9i
out of box cannot interact with the desktop. Now we have to provide this whole
data loading functionality through the application. How can it be done.
Followup:
1) you can query a flat file as if it were a table -- if the file is not
accessible on the server, then yes, you'll have to write code, maintain code.
Tis up to you at the end of the day.

2) sqlldr is a command line tool.  there is no "sqlldr api" to be called.

Sorry, I do not program forms.  My approach would involve pure HTML and file
upload into a LOB.
Rating: 1
March 14, 2004
Reviewer: A reader
"1) why external tables?  just merge:"

The data is in the flat file, I just cant use a straight forward merge.
Followup:
sure you can -- if it were on the server.  That is the problem here -- the data
isn't were it can be used by the database server.
Rating: 3
March 14, 2004
Reviewer: A reader
NFS = network file system.

Does NFS work if the database server is on UNIX, and the network servers are on
windows.
Followup:
It can, but windows is a little less open so you'd probably end up running Samba
on UNIX (very common) which allows Windows "shares" to be mounted on Unix
Rating: 2
March 14, 2004
Reviewer: A reader
sqlloader is a command line utility accpeted.

1.
But cant I do a HOST call and invoke this utility ?

But once again the issue here is even to do a host call in forms9i it will not
be possible out of box.

2."My approach would involve pure HTML and file
upload into a LOB. "

Please explain in detail about the above, how will you do it. How will you
communicate with the desktop using HTML and get the data from the flat file on
the user desktop?

Please share.
Followup:
1) and if the file were on the client, the host command would run on the middle
tier anywho.

I don't think you'll be using sqlldr.

2) just like you do here on asktom in general?  There is in html an "<input
type=file ...>" tag that lets you post a file to a webserver.
Rating: 3
March 14, 2004
Reviewer: A reader
"just like you do here on asktom in general?  There is in html an "<input
type=file ...>" tag that lets you post a file to a webserver. "

<input
type=file ...

The above will it look for the file on the desktop or on the middle tier, what
do you mean by "post a file to a webserver."

Sorry for the spate of clarifications requested.

2.

So utl_file is ruled out, external tables is ruled out, sqlldr is ruled out,
what is left is text_io( client_text_io) and your <input
type=file ...  , if it works in forms , I'm I right ?
Followup:
the above would look on the file system where the BROWSER lives, the only file
system it can see if the file system the browser sees.

<input type=file ...> is pure HTML, it is not forms at all.
Rating: 5
external tables March 18, 2004
Reviewer: Sukumar from CA USA
very good excellent discussion
Rating: 5
Having problem in loading data in a field which has line feed March 18, 2004
Reviewer: Sukumar from CA USA
Tom,

I read the full posting. It was very useful.
I followed these methods and created all the relevant scripts for my work.

One problem that I'm having is, if a field in the flat file (csv) is have line
feed characters then it is not getting loaded. I understand that the end of
record identifier is line feed character. Is there a way to get around this?

Please help.

Thanks, Sukumar
Followup:
if the records are terminated by something identifiable -- yes.

for example:

1,2,'Hello
world'!
4,2,'Good
bye'!
3,2'something else'!

here, each line ends with !\n -- we can use that in sqlldr to tell is the end of
line is really !\n (or !\r\n on windows) and load it up.

If not, -- no.
March 18, 2004 Vijay -- Thanks for the question regarding "loading a EBCDIC file with no line termination ", version 9.2.0.1.0
You Asked
I have a file in EBCDIC form from mainframe. The file doesn't have carraige
returns. The next row starts at the 133 position. I have opened this EBCDIC file
in textplorer and then coverted it into ascii which shows that there is not
carraige return and the next row starts at 133 position.
I was able to load the EBCDIC file into oracle through sqlloader.
Following are the content of my control file:

load data characterset WE8EBCDIC500
INFILE 'gaa5100_subdiv.txt' "FIX 133"
INSERT INTO TABLE ASSESSOR
(
sub_division_name position(37:63),
sub_divison_year position(66:67),
sub_recorders_book position(70:72),
sub_recorders_page position(76:78),
sub_block_from position(80:82),
sub_block_to position(84:86),
sub_lot_from position(89:93),
sub_lot_to position(95:99),
sub_parcel_book position(102:104),
sub_parcel_page position(106:108)
)

I am able  to create an external table using this parameter above.
1.create directory fromassessor as 'd:\oracle\FromAssessor';
GRANT READ ON DIRECTORY fromassessor to madprd;
GRANT write ON DIRECTORY fromassessor to madprd;

CREATE TABLE "MADPRD"."GAA5100_SUBDIV" ("SUB_DIVISION_NAME" VARCHAR2(27),
"SUB_DIVISON_YEAR" VARCHAR2(2), "SUB_RECORDERS_BOOK" VARCHAR2(3),
"SUB_RECORDERS_PAGE" VARCHAR2(3), "SUB_BLOCK_FROM" VARCHAR2(3),
"SUB_BLOCK_TO" VARCHAR2(3), "SUB_LOT_FROM" VARCHAR2(5), "SUB_LOT_TO"
VARCHAR2(5), "SUB_PARCEL_BOOK" VARCHAR2(3), "SUB_PARCEL_PAGE"
VARCHAR2(3))
organization external
(type oracle_loader
default directory fromassessor
access parameters (characterset WE8EBCDIC500
"FIX 133"
( sub_division_name position(37:63),
sub_divison_year position(66:67),
sub_recorders_book position(70:72),
sub_recorders_page position(76:78),
sub_block_from position(80:82),
sub_block_to position(84:86),
sub_lot_from position(89:93),
sub_lot_to position(95:99),
sub_parcel_book position(102:104),
sub_parcel_page position(106:108)
))
location ('gaa5100_subdiv.txt'))
Table Created.

But when I try to access this table I get the following error:
3. select * from gaa510_subdiv;
Gives error:

select * from madprd.gaa5100_subdiv
*
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: "exit, fields,
records"
KUP-01008: the bad identifier was: record
KUP-01007: at line 1 column 1
ORA-06512: at "SYS.ORACLE_LOADER", line 14
ORA-06512: at line 1

I am not sure what I am missing. I would appreciate your help on this. Thank you
very much.
and we said...
well, I took your ctl file and (after creating a dummy assessor table with all
varchar2(25) fields) ran:

$ sqlldrl / t.ctl EXTERNAL_TABLE=GENERATE_ONLY

the resulting t.log file contains the external table definition Oracle would
expect given that working control file ( this is a really cool way to convert
these things into external tables!)

In this case, we got:

CREATE TABLE "SYS_SQLLDR_X_EXT_ASSESSOR"
(
  SUB_DIVISION_NAME VARCHAR2(25),
  SUB_DIVISON_YEAR VARCHAR2(25),
  SUB_RECORDERS_BOOK VARCHAR2(25),
  SUB_RECORDERS_PAGE VARCHAR2(25),
  SUB_BLOCK_FROM VARCHAR2(25),
  SUB_BLOCK_TO VARCHAR2(25),
  SUB_LOT_FROM VARCHAR2(25),
  SUB_LOT_TO VARCHAR2(25),
  SUB_PARCEL_BOOK VARCHAR2(25),
  SUB_PARCEL_PAGE VARCHAR2(25)
)
ORGANIZATION external
(
  TYPE oracle_loader
  DEFAULT DIRECTORY SYS_SQLLDR_XT_TMPDIR_00000
  ACCESS PARAMETERS
  (
    RECORDS FIXED 133 CHARACTERSET WE8EBCDIC500
    BADFILE 'SYS_SQLLDR_XT_TMPDIR_00000':'gaa5100_subdiv.bad'
    LOGFILE 't.log_xt'
    READSIZE 1048576
    FIELDS LDRTRIM
    REJECT ROWS WITH ALL NULL FIELDS
    (
      SUB_DIVISION_NAME (37:63) CHAR(27),
      SUB_DIVISON_YEAR (66:67) CHAR(2),
      SUB_RECORDERS_BOOK (70:72) CHAR(3),
      SUB_RECORDERS_PAGE (76:78) CHAR(3),
      SUB_BLOCK_FROM (80:82) CHAR(3),
      SUB_BLOCK_TO (84:86) CHAR(3),
      SUB_LOT_FROM (89:93) CHAR(5),
      SUB_LOT_TO (95:99) CHAR(5),
      SUB_PARCEL_BOOK (102:104) CHAR(3),
      SUB_PARCEL_PAGE (106:108) CHAR(3)
    )
  )
  location
  (
    'gaa5100_subdiv.txt'
  )
)REJECT LIMIT UNLIMITED

Now, I don't have an ebcdic file to test with -- but, assuming the ctl file
worked, this should be OK
Review & Followup

Rating: 4
EXTERNAL TABLE using ebcdic file March 16, 2004
Reviewer: VJ from CA,USA
Thanks a lot.
Rating: 5
header in the file March 18, 2004
Reviewer: VJ from CA, USA
One more question! How do I skip the first record in the EBDCDIC file. I
discovered that the EBCDIC file has a header column name on top of (first
record) of each file. Is there any parameter like SKIP first while defining
sqlloader controlfile? Thanks in Advance. I always appreciate your help.
Followup:
you can use options=(....) in the control file to specify "command line" options
in the ctl file itself.
March 18, 2004 Shailandra -- Thanks for the question regarding "Indexing at very Large Table", version 9.0.1
You Asked
Hello Tom,

I am indexing a very large table with more than 500 million rows. This table is
partitioned. I needed to create the index on the table. I am creating the local
index with parallel and nologging option. Once I was doing it, it was running
for more than 24 hours before I had to abort the process due to client reasons.
But now I have to start the process again. What is your suggestion, to make
index creation faster on this table.

Thanks in Advance.
and we said...
Check out your sort area size, make sure it is "ok" (big enough)

Also, consider using the dbms_pclxutil package.  It provides intra-partition
parallelism for creating partition wise local indexes:
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89852/dbms_pcl.htm#998100
meaning you can parallelize the index builds against each PARTITION in addition
to ACROSS partitions.
Review & Followup

Rating: 4
Very useful May 30, 2002
Reviewer: Shailandra
My current sort_area_size was 64K. Now I will try building index using 2MB
sort_area_size.
Followup:
2mb -- make that much much larger please -- for an index build, 10 to 200mb
isn't unreasonable!
Rating: 4
How to estimate sort area size for index creation March 18, 2004
Reviewer: Dilip from PA, USA
Tom,

If we need to build an index on a very large table (index size is estimated to
be 65 GB...  Partitioned global index),
how do we estimate sort area size/temp space for index creation step.

Also, how do we estimate rollback requirement.  We are planning to build the
index in parallel with nologging.

Thanks.
Followup:
a 65gig single segment index, interesting.

well, the sort_area_size is something you "set", something you are willing to
give.  You choose.

You are going to need at least as much as 150% of the target index size in temp
probably as I'm going to guess most of it will have to sort to disk.

I'd really give "partitioning" a thought here

(rollback = just for data dictionary updates for space management/quotas/etc --
create index writes to temporary segments and when done converts the temporary
segments into permanent ones, bypassing the need to "rollback" as smon would
just clean up the temp extents in the real tablespace as part of its normal day
to day processing)
March 18, 2004 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://download-east.oracle.com/docs/cd/A81042_01/DOC/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)
Rating: 3
is there any way I can get the precision March 18, 2004
Reviewer: A reader
 i have table like

SQL> create table t ( id number(9));

Table created.

SQL> insert into t values(3.2);

1 row created.

SQL> select * from t;

        ID
----------
         3

SQL> column id format 999,999,999
SQL> l
  1* select * from t
SQL> /

          ID
------------
           3

is there any any any any way I can get 3.2 back
from t ?
Followup:
umm, you told us "number(9)"  that is (9,0).  it is an integer.

your very definition precludes the 0.2 from being saved.

No, that number was rounded to 3 upon insert as per your request.

ops$tkyte@ORA9IR2> create table t ( id number(9));

Table created.

ops$tkyte@ORA9IR2> insert into t values ( 3.4 );

1 row created.

ops$tkyte@ORA9IR2> insert into t values ( 3.5 );

1 row created.

ops$tkyte@ORA9IR2> select * from t;

        ID
----------
         3
         4

ops$tkyte@ORA9IR2>

You told us "integerize this please"
Rating: 3
Thanks !! we have to alter the column but table has 5M records !! March 18, 2004
Reviewer: A reader
Followup:
so?  it'll only take a couple of milli-seconds?

big_table@ORA9IR2> select count(*), count(some_number) from big_table;

  COUNT(*) COUNT(SOME_NUMBER)
---------- ------------------
   1000000            1000000

so, 1,000,000 non-null values (on a desktop pc to boot)....

big_table@ORA9IR2> set timing on
big_table@ORA9IR2> alter table big_table modify some_number number(11,2);

Table altered.

Elapsed: 00:00:00.01

it is a simple DDL change to give it 2 decimal places... no data was touched
in this operation

big_table@ORA9IR2> desc big_table;
 Name                                Null?    Type
 ----------------------------------- -------- ------------------------
 ID                                  NOT NULL NUMBER
 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)
 SOME_NUMBER                                  NUMBER(11,2)

And there you go, your 9 digit integer field is now an 11 digit field with 2
decimal places.
Rating: 3
yep March 18, 2004
Reviewer: A reader
but it has to

1.) get approved/change mgmt.
2.) put it in a script.
3.) test against all related apps.
4.) full qa cycle ***
5.) done

  it is not hard to code or run. but make sure everything works with that
change. Insted of that if I can query it in
different way for new apps. no change to the
**existing   **Working apps.

but, I know it is integer so it is integer. will schedule it.

Thanks for your answers..
Regards,
Followup:
but those steps are "regardless of number of records" -- they take the same
time.

Apps that are expecting a 9 digit integer could well break, yes.
March 18, 2004 -- 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://download-west.oracle.com/docs/cd/B10501_01/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 :)
Rating: 5
January 20, 2004
Reviewer: A reader
Rating: 5
March 18, 2004
Reviewer: A reader
Tom,

In RMAN what are the factors that need to be considered when allocating channels
for rman in 9i. I know we can set it to automatic and let oracle decide, but if
we want to do it manually do the number of cpus also need to be taken into
consideration.

Thanks.
Followup:
yes, each channel is a process (server session) on the target, it reads and
processes each block it reads.

it'll also be something that adds to the IO read load and the IO write load as
well.

those are the basic "factors", how many readers and writers do you want going at
the same time.
Rating: 5
March 18, 2004
Reviewer: A reader
Tom sorry I didn't get this one "how many readers and writers do you want going
at the same time."

Thanks.
Followup:
how many sessions do you want reading your disks at the same time (a channel is
a session, a session does reads).

same with writing to whatever you are backing up to.
Rating: 5
March 18, 2004
Reviewer: A reader
Lets say 5 each.
March 18, 2004 trinh -- Thanks for the question regarding "analyzing table", version 8.0.6
You Asked
Tom,
In your quetion answers, you prefer:

analyze table t compute statistics
   for table
   for all indexes
   for all indexed columns;

does "analyze table t compute statistics
   for table"
cover all the rest?

and
"analyze table t compute statistics
   for all indexes" covers "analyze table t compute statistics
   for all indexed columns"?
Thanks.
and we said...
They all do different things.

"for table" analyzes just the table.
"for all indexes" grabs the indexes for the table and analyzes all of them.
"for all indexed columns" computes histograms for any column that is indexed.

Here is an exmaple showing that each adds its own bit of data:

tkyte@TKYTE816> create table t ( x int primary key );
Table created.

tkyte@TKYTE816> insert into t values ( 1 );
1 row created.

tkyte@TKYTE816> select table_name, num_rows from user_tables;

TABLE_NAME   NUM_ROWS
---------- ----------
T

tkyte@TKYTE816> select index_name, num_rows from user_indexes;

INDEX_NAME                       NUM_ROWS
------------------------------ ----------
SYS_C005003

tkyte@TKYTE816> select * from user_tab_histograms;

no rows selected

So, when we start -- the optimizer knows NOTHING about the
table/index/columns...

tkyte@TKYTE816> analyze table t compute statistics for table;
Table analyzed.

tkyte@TKYTE816> select table_name, num_rows from user_tables;

TABLE_NAME   NUM_ROWS
---------- ----------
T                   1

tkyte@TKYTE816> select index_name, num_rows from user_indexes;

INDEX_NAME                       NUM_ROWS
------------------------------ ----------
SYS_C005003

tkyte@TKYTE816> select * from user_tab_histograms;

no rows selected

after analyzing the TABLE, the optimizer understands something about the table
itself but not the indexes and not the columns in the indexes

tkyte@TKYTE816> analyze table t compute statistics for all indexes;
Table analyzed.

tkyte@TKYTE816> select table_name, num_rows from user_tables;

TABLE_NAME   NUM_ROWS
---------- ----------
T                   1

tkyte@TKYTE816> select index_name, num_rows from user_indexes;

INDEX_NAME                       NUM_ROWS
------------------------------ ----------
SYS_C005003                             1

tkyte@TKYTE816> select * from user_tab_histograms;

no rows selected

After analyzing all of the indexes, the optimizer knows a little more...

tkyte@TKYTE816> analyze table t compute statistics for all indexed columns;
Table analyzed.

tkyte@TKYTE816> select table_name, num_rows from user_tables;

TABLE_NAME   NUM_ROWS
---------- ----------
T                   1

tkyte@TKYTE816> select index_name, num_rows from user_indexes;

INDEX_NAME                       NUM_ROWS
------------------------------ ----------
SYS_C005003                             1

tkyte@TKYTE816> select * from user_tab_histograms;

TABLE_NAME COLUMN_NAME ENDPOINT_NUMBER ENDPOINT_VALUE ENDPOINT_ACTUAL
---------- ----------- --------------- -------------- ---------------
T          X                         0              1
T          X                         1              1

tkyte@TKYTE816>

and finally, the histograms for the indexed columns are there.  What stats you
compute depend on your needs.  Histograms are generally not computed in an OLTP
system but would be in a data warehouse.  In many cases, just having the
statistics on the tables is sufficient for good performance.
Review & Followup

Rating: 5
Nicely explained May 30, 2001
Reviewer: Gururaj from GA USA
Thanks Tom, explanation is very good. Looks even better with examples
Rating: 1
Still Confused... November 16, 2001
Reviewer: Vineet from IN
Tom,

I am still confused. I read this documetn before asking you. My question is for
analyze table TABLE_NAME compute statistics command not for

analyze table t compute statistics
   for table
   for all indexes
   for all indexed columns;

I mean analyze table without any option like for table or for all indexes or for
all indexed columns...
Rating: 5
Don't mean to beat a dead horse... February 14, 2002
Reviewer: Steve McKee from Montreal, Quebec, Canada
Hi Tom,

I just want to make sure I understand the documentation. From the documentation
(8.1.7):

"Specify a table to be analyzed. When you collect statistics for a table, Oracle
also automatically collects the statistics for each of the table’s indexes and
domain
indexes, provided that no for_clauses are used."

Therefore, is "analyze table <table> compute statistics"

the same as:

analyze table t compute statistics
   for table
   for all indexes
   for all indexed columns;

One of our main designers and I too want to make sure.

I appreciate it Tom.

Steve
Followup:
No, it is not.  Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int primary key, y 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>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select 'TABLE', table_name, to_char(num_rows)
num_rows from user_tables
  2  union all
  3  select 'INDEX', index_name, to_char(num_rows) from user_indexes
  4  union all
  5  select 'HIST', table_name, column_name from user_tab_histograms;

'TABL TABLE_NAME                     NUM_ROWS
----- ------------------------------ --------------------
TABLE T
INDEX SYS_C005115

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', table_name, to_char(num_rows)
num_rows from user_tables
  2  union all
  3  select 'INDEX', index_name, to_char(num_rows) from user_indexes
  4  union all
  5  select 'HIST', table_name, column_name from user_tab_histograms;

'TABL TABLE_NAME                     NUM_ROWS
----- ------------------------------ --------------------
TABLE T                              1
INDEX SYS_C005115                    1
HIST  T                              X
HIST  T                              Y
HIST  T                              X
HIST  T                              Y

6 rows selected.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t delete statistics;

Table analyzed.

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> select 'TABLE', table_name, to_char(num_rows)
num_rows from user_tables
  2  union all
  3  select 'INDEX', index_name, to_char(num_rows) from user_indexes
  4  union all
  5  select 'HIST', table_name, column_name from user_tab_histograms;

'TABL TABLE_NAME                     NUM_ROWS
----- ------------------------------ --------------------
TABLE T                              1
INDEX SYS_C005115                    1
HIST  T                              X
HIST  T                              X

ops$tkyte@ORA817DEV.US.ORACLE.COM>

The analyze table t gets all of the columns, for all indexed columns does not.
Rating: 5
Subrat March 23, 2002
Reviewer: Subrat from NJ,USA
Hi tom,
I appreciate your explanations a lot..
Thats too good for gaining knowledge ...
Hats off to you .
I just want to know 'Will you advise to analyze the Primary Columns too ?'
As When you give 'Analyze table compute statistics for table for all indexes for
all indexed columns ' , you are also analyzing the primary key and Oracle
strongly says that only the skewed data should be analyzed .
And the other part is that when we Analyze the primary key columns we are
generating a lot of records for the primary key in the histograms ,as a result
during the parse phase the optimizer will call those records and will consume
the space from the DB Block Buffer and Dictionary Cache . Don't you think it
will adversely affect the performance in case of heavily used big size database
in a OLTP environment .
Above all it adds to the size of the sys tablespace .
Please rectify if I am wrong .
I have a doubt when the optimizer looks for the histograms does it brings the
full table of histograms to SGA or brings only the data required for the
selected tables during the Parse Process to db_block_buffer and dictionary cache
?
Followup:
I use for all indexed columns in my examples frequently -- its a short cut for
me.  It is easier then running N analyzes to get what I want for what is a very
short, quick demo.

I don't see it adversely affecting performance in most normal systems no.

the histograms are retrieved as needed, not all at once.
Rating: 5
Analyze Table March 24, 2002
Reviewer: R.V.Reddy from Bangalore, India
Hi tom

This is a beautiful explanation regarding analyze table. I have one doubt. so
for we developed some applications using around 200 tables. But I never ANALYZED
the tables. If I am not analyzing the tables is there any impact on the
performance or any other issues. If I do what I can achieve in my application.

Thanks

Reddy
Followup:
By not analyzing the tables you are missing out on many features such as:

desc indexes,
function based indexes,
bitmap indexes,
partitioning,
parallel query,
.......

and the list grows longer and longer each release.
Rating: 3
Analze tables compute statistics March 24, 2002
Reviewer: femi from UK
Tom,

Correct me if I am wrong, but when I issue the command "analyze table compute
statistics", It computes statistics for the indexes as well.
Followup:
read the answer, see the way to tell and try it!
(or read the comment a couple of lines up "Dont mean to beat a dead horse..."
Rating: 3
for all table March 24, 2002
Reviewer: Ak from India
Hi tom,
   1)Assume i have 500 tables in a schema,how do i analyze
all tables/indexes at a time,is there any way or i have to do one by one.

2)How frequently we should analyze the tables,assuming it is
oltp system.

3)Is there any performance problem ,when we analyze during
peek time,or it is advisable to do during non-peek hour.

Thanks,
 Ak
Followup:
well, parallel 500 would be a bit extreme, I haven't seen a machine that could
do 500 analyzes at the same time effciently yet.

1) use dbms_stats, use alter table monitoring, gather stats only on stale
tables, and if you have the resources -- do so on parallel.

2) use alter table monitoring, gather stats on stale tables when capacity is at
its maximum.  depends on the system.  some systems -- once a day, others once a
week, others yet -- once a month.

3) during peak, if you do the analyze, you'll be incurring massive IO overhead.
It is something you want to try to do during "off" times if at all possible.
Rating: 3
analyze March 24, 2002
Reviewer: Ak from India
Hi tom,
  Thanks for Your reply !!! Here according to your response
it look like i have to say
analyze table A compute statistics
analyze table B compute statistics
analyze table C compute statistics
analyze table D compute statistics

Like wise for all the 500 tables and then for indexes as well ?? Please correct
me if i am wrong.
Followup:
Read all about DBMS_STATS.  It can be done in as little as a single command.
http://download-west.oracle.com/docs/cd/A87860_01/doc/appdev.817/a76936/dbms_sta.htm#999107
....
Rating: 4
This is always something taht has bugged me March 25, 2002
Reviewer: Chris Slattery from Dublin, Ireland
I always thought a stats gather (default compute) did the whole lot. The manual
bears this out,,, And I quote from tahiti.oracle.com SQL 9.0 ref.

"TABLE table
Specify a table to be analyzed. When you collect statistics for a table, Oracle
also automatically collects the statistics for each of the table's indexes and
domain indexes, as long as

no for_clauses are used.

When you analyze a table, Oracle collects statistics about expressions occurring
in any function-based indexes as well. Therefore, be sure to create
function-based indexes on the table before analyzing the table"

and so on.

All I am looking for is a clear statement of the difference. There seems to be a
discrepancy in the manual, from what Mr. Kyte writes. (and I am quite happy to
follow his example).

as an interesting note I see from the same SQL reference that Oracle now wish
you to use DBMS_STATS rather than ANALYZE and in fact give an ominous warning
that DBMS_STATS will be the only method for generating stats that will actually
be used (by the CBO)  in some future release.
Any idea  as to what release this will actually be ?
Followup:
What discrepancy do you see?  I see NONE.

where is there a discrepancy??

I demonstrated (see comment Don't mean to beat a dead horse.) that analyze table
compute stats does more then

analyze table t compute statistics
   for table
   for all indexes
   for all indexed columns;

I also demonstrated that when you use for, you get only what you ask for.

the original QUESTION was:

In your quetion answers, you prefer:

analyze table t compute statistics
   for table
   for all indexes
   for all indexed columns;

does "analyze table t compute statistics
   for table"
cover all the rest?

and my answer was (and still is) no, no it does not.
Rating: 4
For AK from India March 25, 2002
Reviewer: Pawan from USA
AK,
  No you don't have to Analyze all the tables in the schema individually. u can
analyze the schema in one command :
This is what I use. I have the following script saved as analyze_schema.sql and
at the SQL prompt just run it and input the schema name when prompted.
------
accept user char format 'A40'-
prompt'Enter user name:'
begin
dbms_utility.analyze_schema(upper('&user'),'COMPUTE');
end;
/
-------
Tom,
   Correct me if I am wrong.
Rating: 5
Proper Analyze March 25, 2002
Reviewer: Randy Richardson from St. Louis, MO
Tom,  Thanks for sharing the knowledge.  I just bought your latest book.  Love
it!!.  Do you have one covering Oracle9i in the works?

As for the above question:  I too use

1.  dbms_utility.analyze_schema(USER_NAME),'COMPUTE');

Does this differ to
2.  analyze table t compute statistics
     for table
     for all indexes
     for all indexed columns;

and if it does why would you want to do it 1 rather than 2?
And if two is better why not change 1 to do what’s best.

Thanks again!!
Followup:
Here is the lowdown on analyzing objects.

in the real world, outside of the examples you see here, you will use
DBMS_STATS to compute statistics.  It is more flexible, has more features (eg: a
parallel analyze that analyze cannot do).  In examples here, I analyze only what
I need since I tend to CREATE something, use it, and drop it.  For me, analyzing
my entire schema would be a waste of time.

Never use dbms_utility, it is a legacy from 7.3.  Use dbms_stats instead.

(i'm in the process of outlining a performance book that would cover upto 9iR2)
Rating: 4
Post-laceration ! March 25, 2002
Reviewer: Chris Slattery from Dublin,Ireland
Yes Tom ...

You (as always ) are right. Stemmed from misreading the syntax diags. a long
time ago and being sucked into the  myth (that I need to debunk) that a
qualifier (such as for index or for table ) must be added for ANALYZE to work.
Rating: 4
Deleting Statistics June 06, 2003
Reviewer: Rob from pa
Is there anything to watch out for when
deleting stats in the fashion below ?

analyze table
ARCH_CANCEL_NOTICES
delete statistics ;

Thanks
Followup:
well, if you join or reference arch_cancel_notices with other analyzed tables,
we'll use default stats for arch_cancel_notices -- with disastrous results
(gartner says 99.99% probability ;)
Rating: 3
DBMS_UTILITY.ANALYZE_SCHEMA is not very smart July 31, 2003
Reviewer: A reader
Hi Tom,
the package DBMS_UTILITY.ANALYZE_SCHEMA is not very smart it picks up all tables
in a schema to analyse them and fails if the schema has a external table, i
think the package should omit external tables when it knows that analyze does
not work on external tables.
Followup:
well, you shouldn't be using dbms_utility -- dbms_stats is the only way to go.
and then, you CAN in fact analyze them:

ops$tkyte@ORA920LAP.LOCALHOST> 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@ORA920LAP.LOCALHOST> host flat scott/tiger emp > /tmp/emp.dat

ops$tkyte@ORA920LAP.LOCALHOST>
ops$tkyte@ORA920LAP.LOCALHOST> exec dbms_stats.gather_table_stats( user,
'EXTERNAL_TABLE' )

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP.LOCALHOST> select table_name, num_rows, last_analyzed from
  2  user_tables where table_name = 'EXTERNAL_TABLE';

TABLE_NAME                       NUM_ROWS LAST_ANAL
------------------------------ ---------- ---------
EXTERNAL_TABLE                         14 01-AUG-03

plus have a whole lot more control
Rating: 3
what the point of FOR ALL COLUMNS September 01, 2003
Reviewer: A reader
Hi

Why we dont use FOR ALL INDEXED COLUMNS instead of FOR ALL COLUMNS? I dont see
the point analyzing columns whcih are not indexed. Any suggestion?
Followup:
think of a join....

select *
 from t1, t2
where t1.indexed_column = t2.column
  and t2.unindexed_column = 'X';

tell me, should we nested loops join to t1 after full scanning t2 or...
should we hash join both

whatever you say is best, I'll put a counter example that shows the other was
really best.
Rating: 4
I dont see how statistics can help September 01, 2003
Reviewer: A reader
Hi

I dont see how statistics of a column help in a join, if let's say hash join is
best then shouldnt that decision based on number of rows in both tables...? If
nested loop is used shouldnt the statistics on the indexed column be enough?
Followup:
select *
 from t1, t2
where t1.indexed_column = t2.column
  and t2.unindexed_column = 'X';

say unindex_colulmn = 'X' is known (via histograms) to return 1 row and that
t1.indexed_column is know to be unique.

full scan t2, nested loops using index to t1

say  unindex_column = 'X' is known (via histograms) to return 500,000 rows.

full scan t2, filter, hash join to t1

the decision to hash join IS based to some degree on the number of rows in both
tables AFTER FILTERING.  After "unindex_column = 'X'" is evaluated.  If the
optimizer has no clue about the distribution of 'X' -- well, there you go, it
won't know -- it'll guess and could well be *wrong*

consider:

ops$tkyte@ORA920> create table t1
  2  as
  3  select *
  4    from all_objects;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table t1 add constraint t1_pk primary key(object_id);

Table altered.

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

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t2
  2  as
  3  select decode( mod(rownum,100), 0, to_char(rownum), 'x' ) unindex_column,
all_objects.*
  4    from all_objects;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table t2
  2  compute statistics
  3  for table;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select * from t1, t2 where t1.object_id = t2.object_id and
t2.unindex_column = '100';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=100 Card=317 Bytes=61498)
   1    0   HASH JOIN (Cost=100 Card=317 Bytes=61498)
   2    1     TABLE ACCESS (FULL) OF 'T2' (Cost=48 Card=317 Bytes=31066)
   3    1     TABLE ACCESS (FULL) OF 'T1' (Cost=50 Card=31708 Bytes=3043968)

ops$tkyte@ORA920> select * from t1, t2 where t1.object_id = t2.object_id and
t2.unindex_column = 'x';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=100 Card=317 Bytes=61498)
   1    0   HASH JOIN (Cost=100 Card=317 Bytes=61498)
   2    1     TABLE ACCESS (FULL) OF 'T2' (Cost=48 Card=317 Bytes=31066)
   3    1     TABLE ACCESS (FULL) OF 'T1' (Cost=50 Card=31708 Bytes=3043968)

ops$tkyte@ORA920> analyze table t2 compute statistics for columns unindex_column
size 100;

Table analyzed.

ops$tkyte@ORA920> Select * from t1, t2 where t1.object_id = t2.object_id and
t2.unindex_column = '100';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=49 Card=1 Bytes=194)
   1    0   NESTED LOOPS (Cost=49 Card=1 Bytes=194)
   2    1     TABLE ACCESS (FULL) OF 'T2' (Cost=48 Card=1 Bytes=98)
   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=2 Card=1 Bytes=96)
   4    3       INDEX (UNIQUE SCAN) OF 'T1_PK' (UNIQUE) (Cost=1 Card=1)

ops$tkyte@ORA920> Select * from t1, t2 where t1.object_id = t2.object_id and
t2.unindex_column = 'x';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=214 Card=31710 Bytes=6151740)
   1    0   HASH JOIN (Cost=214 Card=31710 Bytes=6151740)
   2    1     TABLE ACCESS (FULL) OF 'T1' (Cost=50 Card=31708 Bytes=3043968)
   3    1     TABLE ACCESS (FULL) OF 'T2' (Cost=52 Card=31710 Bytes=3107580)

ops$tkyte@ORA920> set autotrace off
Rating: 5
Too good explanation!!!! September 02, 2003
Reviewer: A reader
Rating: 5
dbms_stats October 10, 2003
Reviewer: Michael
Hi Tom,

is dbms_stats.gather_table_stats(ownname => 'OWNER',
                                 tabname => 'TABLE',
                                 cascade => 'TRUE');
the same as
analyze table t compute statistics
   for table
   for all indexes
   for all indexed columns;
If not, where are the differences and is there a way to let dbms_stats do the
same?
You're answer is as always appreciated.
Followup:
it is like

analyze table T
compute statistics
for table
for all columns size 1
for all indexes;

the method opt defaults to for all columns size 1
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96612/d_stats2.htm#1003993
you would use method_opt => 'for all indexed columns'
Rating: 3
Analyze January 11, 2004
Reviewer: Yogesh Hingmire from India
Hi Tom ,
i have read this in the Oracle documentation

"Specify a table to be analyzed. When you collect statistics for a table, Oracle
also automatically collects the statistics for each of the table's indexes and
domain indexes, as long as no for_clauses are used."

it is mentiones here
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96540/statements_46a.htm#SQLRF01105
is it wrong or what do they have to say ????
Followup:
it is correct --

analyze table t compute statistics;

does all three -- if you use NO for clauses.
Rating: 5
March 18, 2004
Reviewer: A reader
Tom,

Is there any other way to figure out if my tables my been analyzed other than
looking at *_tables.

Thanks.
Followup:
guess you could use dbms_stats and catch the exception, but sql seems easier

ops$tkyte@ORA9IR2> create table t as select * from all_objects;

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> variable numrows number
ops$tkyte@ORA9IR2> variable numblks number
ops$tkyte@ORA9IR2> variable avgrlen number
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> set autoprint on
ops$tkyte@ORA9IR2> exec dbms_stats.get_table_stats( user, 'T', null, null, null,
:numrows, :numblks, :avgrlen );
BEGIN dbms_stats.get_table_stats( user, 'T', null, null, null, :numrows,
:numblks, :avgrlen ); END;

*
ERROR at line 1:
ORA-20000: Unable to get values for table T
ORA-06512: at "SYS.DBMS_STATS", line 2582
ORA-06512: at line 1

   AVGRLEN
----------

   NUMBLKS
----------

   NUMROWS
----------

ops$tkyte@ORA9IR2> analyze table t compute statistics for table;

Table analyzed.

ops$tkyte@ORA9IR2> exec dbms_stats.get_table_stats( user, 'T', null, null, null,
:numrows, :numblks, :avgrlen );

PL/SQL procedure successfully completed.

   AVGRLEN
----------
        96

   NUMBLKS
----------
       435

   NUMROWS
----------
     30628

ops$tkyte@ORA9IR2>
Rating: 5
March 18, 2004
Reviewer: A reader
Duh! Totally forgot about that.

Thanks alot.
March 18, 2004 Andre -- Thanks for the question regarding "Refreshing Materialized Views", version 8.1.6
You Asked
Hi Tom, and Happy New Year !

I need to setup a policy to refresh a materialized view. Some
points were not clear to me.

Questions:

1) Since I plan to have summaries from tables which are loaded
at uncertain times -- and I need to have them updated. So I
cannot use START WITH. It seems to me there are 2 options:
a) ON DEMAND (default) by scheduling a manual refresh using
DBMS_MVIEW after the load.
b) ON COMMIT, since SQL*LOADER itself will commit the data.
Apparently, ON COMMIT is better because I don't have to schedule
a manual refersh, which involves more work. Am I correct, or are
there other implications ?

2) If I create a MV with a COMPLETE, FAST, FORCE or NEVER
method, will the manual refresh use the respective method to
refresh the MV ? Or will it, for example, always refresh the MV
entirely (something like the COMPLETE method) ?

3) I understand the COMPLETE and FAST methods. But -- according
to Oracle -- FORCE would "apply fast refresh if possible;
otherwise, apply complete refresh". What does he mean by "if
possible" ? Would you please comment on the FORCE method ?

4) The NEVER method is said to never refresh the MV in any way.
Does that also apply to the refresh procedures of DBMS_MVIEW ?

5) Is it mandatory that I create a materialized view log
whenever I use the fast refresh method ?

Thanks !
and we said...
1) you can use start with and next if you like to schedule a
refresh to occur on some scheduled time (eg: start right now and
refresh every night at midnight).  You can still do an on demand
based refresh as well -- by using the supplied refresh routines
in the plsql packages.

On commit will add a certain amount of overhead to the load
process so, if the load is "large", you might be best off doing
the load and then a complete refresh of the materialized views
(rather then applying all of the changes, it may be faster just
to rebuild the entire mv).

So, on commit might mean a little less administrative overhead
but a little more runtime processing horsepower.  If you are
loading large amounts of data, the overhead might be enough to
convince you to schedule a complete refresh "post" load.

2) the refresh will use by default whatever the MV was created
with (complete/fast).  you can force it to use one or the other
at runtime as well using the dbms_mview.refresh procedure with
method set to one of C, F, or ?

3) if we can do a fast (the snapshot log exists, it has not been
purged, the mv is a candidate for a fast refresh, and so on) we
do a fast.  If we cannot, we do a complete.  A force just
ensures that one way or the other, the MV is refreshed.

4) NEVER REFRESH prevents the materialized view from being
refreshed with any Oracle refresh mechanism or procedure. If you
issue a REFRESH statement on the materialized view, Oracle
returns an error.

5) For a fast refresh, yes we need a snapshot log as this is the
list of all changes to the base table(s) made since the last
fast refresh.
Review & Followup

Rating: 3
Force refresh :: if possible January 29, 2004
Reviewer: Brendan from Norway
In response to the answer to question 3 above ("if possible"). I understand that
the snapshot log needs to exist for the fast refresh to work.
My question is to do with the use of the word "purge".

When you say purged(an act of removing by cleansing), do you simply mean that
the log is empty?

eg.
When an inital refresh is performed on a simple MV, you would expect that a
force refresh would perform a complete refresh. The next time that a force
refresh is performed on this MV you would expect a fast refresh to occur.

Is this assumption correct?

A thing that I am struggling to find information on is the "if possible"
scenarios. I understand that complex queries, MVs with no log and MVs with
purged logs cannot use a fast refresh, but, is there a reference giving a list
of all other situations where a fast refresh will not be performed over a
complete when a force refresh is attempted?
Followup:
when I say purged, i mean purge as in the dbms_mview api calls -- to "cleanse"
the log, yes.

an initial refresh may or may not do a complete.  I would not expect it to, no.
I just created an mv with refresh fast.  I then refresh later -- i would expect
it to use the logs to fast refresh it (incrementally refresh it, i'm not fond of
the word "fast" there since it could be "really slow refresh" just as easily as
"fast")

If a mv is fast refreshable (we need not concern non-fast refreshable mv's at
all, they'll never be incrementally maintained), then it'll fast refresh as long
as the mv log table has the sufficient information in it.  that is, we didn't
drop and rebuild the log, we didn't do something to the parent table that didn't
maintain the log (admin bulky operations), we didn't purge the mv log.
Rating: 4
Large number of updates on MV source table March 18, 2004
Reviewer: Sami from NJ,USA
Dear Tom,

Snapshot TAB1_MV is created in DB1 based  on the table TAB1 located in remote db
DB2.

Refresh time      : 5 Min
Refresh Mode    : FAST
Updateable     : No
Segment Size    : 4000 MB
IM index rebuild is happening thru DBMS_JOB.

The table and snapshot have BLOB columns and intermedia index has been created
on BLOB column.
The table TAB1 at DB2 will get increased by 10% because of stress test by
running script SCRIPT1.sql.

1) We cannot run SCRIPT1.sql because TAB1_MV is Read-Only snapshot.

2) If we keep the replication on, it will break because of the large number of
updates in Remote site (DB2).

Based on the given scenario what is the best way to go?
Followup:
1) huh?  you run script1 in DB-2 against TAB1.  it'll change tab1. the changes
will propagate to DB-1, TAB1_MV

so, not sure why "you cannot run it"

2) why would it "break"?  I sure hope it doesn't "break".
March 18, 2004 -- 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.
Rating: 5
what is the benefit of MV March 18, 2004
Reviewer: A reader
Hi Tom

Can i infer from the discussion that the Materialized Views keep the results of
a query ready before we fire a select on the MV.
i.e say if i have a select with complex joins and if i create it as a normal
view and say that selects take around 2 minutes to return the results.
But now if i create the same normal view as a materialized view the query will
be fired in advance(sometimes earlier) . So when i query the MV , i only view
the results & the select actually does not fire on the base table when i query
on the MV. SO i will retrieve the results faster. Do let me know if my inference
is correct. If not do correct me.

Another point with MV is suppose if the query runs in the night and we view the
results only at the time when we fire the query on hte MV. It is quiet possible
i will be getting stale data. Say the changes made to the base table after the
Materialized View query fired will not be reflected when we have a select on the
MV

Bye
Followup:
correct.

think of MVs as the "indexes of your data warehouse".  Think of this MV:

select state, sum(sales), count(*), avg(sales), min(sales), max(sales)
  from one_billion_row_table
 group by state;

now, say your most popular queries ask for sum(sales) or count of sales for a
given state.

do you want to query one billion rows, sum them up (hundreds of times a day)
or..... just scan 50 or so records and get the answer straight up.

In a DW, the staleness is "not an issue" really.  DW's are "somewhat static" for
periods of time.

You can choose whether to accept stale data or not.

Check out:
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96520/toc.htm
chapter on mv's
March 18, 2004 -- 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.
Rating: 3
lastname, firstname != firstname, lastname --- is a problem March 18, 2004
Reviewer: Robert from CT
I need a sql or plsql solution to deal with a column
that contain people name but it may be
"last, first", "first, last"

I need to do comparative search on this column, so I thought
i should deal with this problem first

How would you deal with this ?

I tried to let Oracle do the sorting, so I tried nested-table code like below:
snip...
FOR i IN
(SELECT *
   FROM THE (
   SELECT CAST( common_util.str2vctbl( lower('mouse, mickey') ) AS vctabletype)
FROM dual ) ORDER BY 1 ASC)
loop
   l_one := i.??? --< What column name to use ?
  .......
end loop;

Thanks
Followup:
i.column_value
March 19, 2004 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 )
Rating: 4
Enabling the debug from another session. March 18, 2004
Reviewer: NOTNA from Phil.....
Hi Tom,

Just would like to ask if it's possible to enable my debugging my code from
another session (just like dbms_system.set_sql_trace_in_session), without
enabling the debug using triggers or table parameters.
Is it possible if set a context for that existing session? If not, what could be
the proper way to achieve this?

Regards,
NOTNA
Followup:
what kind of "debug"?
Rating: 4
like Debug.f March 18, 2004
Reviewer: NOTNA from Phil.....
I've searched your site regarding the above package and I think someone modified
and put's some additional features (such as the ability to on and off the
debugging, log thru url, etc....
http://asktom.oracle.com/pls/ask/f?p=4950:8:511477197117314807::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:7446151725537,
)
But still you have to turn it on from your current session (execute the
debug.enable). I want to modify this code so that I can enable the debugging of
my package/function/procedure or anything from a different session (just what
like dbms_system.set_sql_trace_in_session (...) is doing).
would that be possible?

Tnx and Rgrds....

NOTNA
Followup:
you have the source code -- anything is possible.

but actually, it already does what you want. (there is no "debug.enable"
routine)

You just invoke debugf.init from any session and all others will start debugging
if they are supposed to.
Rating: 5
to notna March 18, 2004
Reviewer: A reader
Hi Notna
I added the enable flag to modify the code so that there
is minimal overhead when the debugging is turned
off. I wanted to turn it on or off on a session
by session basis.

The url based rendering again assumes that you are
using a single connection while rendering the URL
(which is true in most cases I know of.) Again
there is a need for the session level flag..

hope this helps..
Menon:)
Rating: 4
don't debug all.. March 18, 2004
Reviewer: NOTNA from Phil.....
<QUOTE>
You just invoke debugf.init from any session and all others will start debugging

if they are supposed to.
</QUOTE>

This is what I do not want to happen. If I execute the debug.init, my code will
generate a debug message for all the sessions. I just want a particular session
to start debugging.

:) Cheers
NOTNA
Followup:
the way debug.init works is at that user/module (package/procedure/function
level)

your call to debug.init would affect only sessions with the same username.

99.99% of the time, that is sufficient.

However, if YOU want to do it session by session, you have total access ot the
source code!  Go for it!  it is not that huge of a change.
March 19, 2004 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.
Rating: 5
Just what I needed January 30, 2004
Reviewer: Evan from Atlanta, Ga
As always, you were right on the mark. The follow ups were helpful as well.
Rating: 4
V$MYSTAT March 19, 2004
Reviewer: Ramakrishna from India
Can we assume that V$MYSTAT will always have atleast one row? So that the query

"Select SID From V$MYSTAT where ROWNUM = 1" does not fail.
Followup:
yes (and the query would not "fail", it would return zero records which is not
"failure", just an empty set)

but not to worry -- you will always have a fixed number of stats in each release
and the number is much larger than 1.
March 19, 2004 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://download-east.oracle.com/docs/cd/A81042_01/DOC/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://download-east.oracle.com/docs/cd/A81042_01/DOC/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://download-west.oracle.com/docs/cd/B10501_01/server.920/a96536/ch3119.htm#1095949

http://download-west.oracle.com/docs/cd/B10501_01/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://download-west.oracle.com/docs/cd/B10501_01/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.
Rating: 3
Converting charactersets January 27, 2004
Reviewer: Benoit Hébert from Quebec
Tom,

In your initial response, you stated that it would be possible to convert a
database that was created as US7ASCII to the WE8ISO8859P1. When I created one of
my databases, I made the mistake of creating it as US7ASCII, instead of the
other one, so the database cannot store the French accented characters that I
need. I seem to remember from one of the Oracle University courses that I
attended that it isn't possible to change the database nls_characterset setting
once the database has been created, but your response seems to indicate
otherwise. Am I mistaken? Am I going to have to re-create the database?

Thanks!
Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96529/ch2.htm#101203
Rating: 2
Character Set Convert Issue February 25, 2004
Reviewer: Ivan from USA
Hi Tom,

I am having issues with accents when generating search names and I was hoping
from your assistance.

our database setting:       NLS_CHARACTERSET    WE8ISO8859P1

Passed in name:    TV Globo de São Paulo Ltda

Current results:   TV Globo de S?o Paulo Ltda

CORRECT results    TV Globo de Sao Paulo Ltda

Have tried the following:

select name, CONVERT(name,'US7ASCII', 'WE8ISO8859P1')
from companies
where company_id = 42430690

TV Globo de São Paulo Ltda    TV Globo de S?o Paulo Ltda

select name, CONVERT(name,'UTF8', 'WE8ISO8859P1')
from companies
where company_id = 42430690

TV Globo de São Paulo Ltda    TV Globo de SÃ£o Paulo Ltda

select name, CONVERT(name,'AL32UTF8', 'WE8ISO8859P1')
from companies
where company_id = 42430690

TV Globo de São Paulo Ltda    TV Globo de SÃ£o Paulo Ltda

Thanks,
Ivan
Followup:
why do you say "correct" results?  the conversions are mandated to us by
standards bodies, we follow those rules.

it is actually "correct", just not what you wanted.  You'll want to use
TRANSLATE or replace with a custom code table of your liking to achieve your
goal, your interpretation of how they should be mapped.
Rating: 5
export NLS_LANG=AMERICAN_AMERICA.UTF8 is not working for 'é' March 03, 2004
Reviewer: Sami from NJ,USA
Dear Tom,

We are dealing with so many different languages like Japanese,Korean,Simplified
Chinese,Traditional Chinese,spanish and French.

Everything works fine with UTF8 characterset and NLS_LANG=AMERICAN_AMERICA.UTF8
***EXCEPT*** 'é'

DB Version: Oracle 8.1.7.3
OS: Sun Solaris

Below example I tried on DB server itself.

SQL> drop table fr_char_test;

Table dropped.

SQL> create table fr_char_test(c1 varchar2(100));
insert into fr_char_test values ('é');
insert into fr_char_test values ('éé');
insert into fr_char_test values ('ééé');
Table created.

SQL>
1 row created.

SQL>
1 row created.

SQL>

1 row created.

SQL> commit;

Commit complete.

SQL> select dump(c1) from fr_char_test;

DUMP(C1)
--------------------------------------------------------------------------------
Typ=1 Len=1: 105
Typ=1 Len=2: 105,105
Typ=1 Len=3: 105,105,105

SQL> select * from fr_char_test;

C1
--------------------------------------------------------------------------------
i
ii
iii

SQL> !

njd05dsmdbc01 myuser mydb /opt/home/usssd429
$ echo $NLS_LANG

njd05dsmdbc01 myuser mydb /opt/home/usssd429
$ exit

SQL> select * from v$nls_parameters where PARAMETER like '%CHARACTERSET%';

PARAMETER
----------------------------------------------------------------
VALUE
----------------------------------------------------------------
NLS_CHARACTERSET
UTF8

NLS_NCHAR_CHARACTERSET
UTF8

SQL>
Followup:
looks like the nls_lang isn't even set?  it would default to us7ascii
Rating: 4
From SQL prompt, "!" or "host" command is not displaying UNIX ENV variable March 03, 2004
Reviewer: Sami
Dear Tom,

Looks like if I come out SQL prompt using "!" it is not displaying UNIX ENV
variable. But it is set in .profile.

Look at this

SQL> !

njd05dsmdbc01 myuser mydb /opt/home/myuser
$ echo $NLS_LANG

njd05dsmdbc01 myuser mydb /opt/home/myuser
$ exit

SQL> exit
Disconnected from 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

njd05dsmdbc01 myuser mydb /opt/home/myuser
$ echo $NLS_LANG
AMERICAN_AMERICA.UTF8
Followup:
not sure, I don't work with characters like that -- i'm not usre if the
behaviour is correct -- or not.  I'll have to suggest you contact support for
this particular issue (unless someone out there that works with this sort of
data knows...)
Rating: 4
March 16, 2004
Reviewer: A reader
Tom,

I got this from one of my friend, saying to change the character set of db, I
have seen some lengthy documents in metalink and even in your site.

But surprised to see this one. Please let me know if this is a valid method of
converting a db character set from default characterset to UTF8, Ver: 9.2.0

Run the following queries to set Character Set value to ‘UTF-8’

Update sys.props$ set value$='UTF8' where name= NLS_NCHAR_CHARACTERSET';
Update sys.props$ set value$='UTF8' where name='NLS_CHARACTERSET';
commit;

Thanks a lot
Followup:
bzzt -- your friend is no friend, not if they tell you to do this.

do NOT do this.

if support tells you to update a table owned by sys -- great, fine, go ahead.

If anyone else tells you to update a table owned by sys -- look at them funny
and walk away.
Rating: 3
How about rename database? March 16, 2004
Reviewer: A reader
I find that when I change the database name, I need to change one value of
sys.prop$, such that database link can work for this renamed database. Is that
OK?

David
Followup:
Unless support told you to do that -- you did absolutely the wrong thing.

you probably updated the global_db_name right?  well, the *right*, proper,
supported, supportable way to do that would be the alter datatabase command.
Rating: 5
Thanks Tom March 16, 2004
Reviewer: A reader
I was surprised and so was the question ... Thanks again
Rating: 3
alter database rename database... March 16, 2004
Reviewer: A reader
Tom,

   Yes, I update the GLOBAL_DB_NAME to change the database name for using the
database link. Before doing that, I first re-create the control file by using
"CREATE CONTROLFILE SET DATABASE ...", and it seems that the database is now
renamed without problem, except the database link cannot be used. After changing
the GLOBAL_DB_NAME, then everything seems OK.

David
Followup:
the command you were looking for was not UPDATE, but alter database again.  the
correct and proper way to rename a global name -- is to rename the global name!

ops$tkyte@ORA9IR2> alter database rename GLOBAL_NAME to
ora9ir2.us.oracle.com;
Database altered.

is that easy (and safe to boot!)
Rating: 4
Work in 8i? March 16, 2004
Reviewer: A reader
Tom,

   Is it work in 8i (by issue alter database rename instead of update
sys.props$)?

Thanks,
David
Followup:
Connected to:
Oracle8i Enterprise Edition Release 8.1.7.4.1 - Production
With the Partitioning option
JServer Release 8.1.7.4.1 - Production

tkyte@ORA8I> alter database rename global_name to foobar;

Database altered.

tkyte@ORA8I>

Yes.  In fact, here is the oldest release I have lying about:

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

tkyte@ORA716> alter database rename global_name to foobar;

Database altered.

it has never been the right way to update sys.props$ -- never
Rating: 4
10g and NLS March 19, 2004
Reviewer: Ajay from Geneva, Switzerland
Hi Tom,
The article is very useful, we have recently installed j2ee and web cache of 10
g and deployed a J2EE application on it, which will be inserting the french data
in the database Our db character set is WE8ISO8859P15.
My question is
1. Where should I set the NLS_LANG on application server ( .bashrc or some
configuration file)
2. Do I need to set the LANG variable at OS level?
Our operation system is Linux Suse.
Thanks in advance
Followup:
1) it is an environment variable
2) NLS_LANG, yes, else it'll default to us7ascii
March 19, 2004 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
Rating: 4
How to get a Sum of Sum March 10, 2004
Reviewer: Mir from Houston, TX
Tom,

I want to publish percentage of a given set of values

Example:

DEPT    SUM_EMP  ALL_SUM PERCENTAGE
------- -------- ------- -----------------
10      5        22      22.72% (5*100)/22
20      10       22      45.45% (10*100/22
30      7        22      31.81% (7*100)/22

How can I achieve the above result assuming I have the following data in a
table?

DEPT   SUM_EMP
------ -------
10     5
20     10
30     7

Thanks in advance for your valuable input
Mir
Followup:
scott@ORA9IR2> select deptno, sum_emp,
  2         round( 100*ratio_to_report( sum_emp ) over (),2) rtr
  3    from (
  4  select deptno, count(*) sum_emp
  5    from emp
  6   group by deptno
  7         )
  8  /

    DEPTNO    SUM_EMP        RTR
---------- ---------- ----------
        10          3      21.43
        20          5      35.71
        30          6      42.86

the inline view is "your table"....
Rating: 5
ratio_to_report? March 11, 2004
Reviewer: SD from UK
Could you please explain ratio_to_report ?
Followup:
it is an analytic function -- if you don't know what they are read either

a) my chapter on them in expert one on one Oracle
b) the chapter on them in the data warehousing guide (on otn.oracle.com)

they are extremely powerful, there are lots of them, they are the cooolest thing
to happen to sql since the keyword "select"

it computed the ratio of SUM_EMP with regards to the "report" value (the sum of
the values for the report).

it can be partitioned, windowed as well.
Rating: 3
Nice March 19, 2004
Reviewer: Maer from CT,USA
Dear Tom,
I'm little confused with grouping function.Please see the query below.

SQL> select grouping(deptno) from emp group by rollup(deptno);

GROUPING(DEPTNO)
----------------
               0
               0  <<-- Why is this 0,0,0,1 coming up

               0
               1

SQL> select grouping(deptno) from emp group by cube(deptno);

GROUPING(DEPTNO)
----------------
               1
               0 <<-- Why it comes as 1,0,0,0

               0
               0

Could you please explain?
Please do reply.
Bye!
Followup:
since it is rather uninteresting to look at a single column, we'll expand it.
but basically:
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96520/aggreg.htm#11441

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96520/aggreg.htm#25172
rollup is like a "report", it rolls up the data as it moves along.  you saw 0,
0, 0, 1 because you saw "detail, detail, detail, aggregation(detail)".  Cube on
the other hand, makes no such "flowing report" (it cannot, there is no logical
progression, it is creating every combination of the group by sets).  It
progresses value by value and returns data "by group by columns".  I'll start
with your simple example (but expanded row wise to make a point):

ops$tkyte@ORA9IR2> create table emp as select * from scott.emp;

Table created.

ops$tkyte@ORA9IR2> insert into emp (ename,sal,empno) values ( 'bob', 1000, 1234
);

1 row created.

ops$tkyte@ORA9IR2> insert into emp (ename,sal,empno) values ( 'mary', 2000, 1235
);

1 row created.

ops$tkyte@ORA9IR2> commit;

Commit complete.

Notice -- i stuffed some NULLS in there on you.... it'll materially change the
outcome:

ops$tkyte@ORA9IR2> select deptno, grouping(deptno), sum(sal)
  2    from emp
  3   group by rollup(deptno)
  4  /

    DEPTNO GROUPING(DEPTNO)   SUM(SAL)
---------- ---------------- ----------
        10                0       8750
        20                0      10875
        30                0       9400
                          0       3000
                          1      32025

Here it looks "flowing" if you will -- DEPTNO aggregates appear for 10, 20, 30
and then NULL (this shows why grouping(column) even exists -- so you can
differentiate the last row from the next to last row!).  After we give you the
aggregates by deptno, you get the aggregates OVER deptno (makes sense, we can
start feeding data to you before we get the final answer ourself -- the report
"builds" from top to bottom)

Now, cube:

ops$tkyte@ORA9IR2> select deptno, grouping(deptno), sum(sal)
  2    from emp
  3   group by cube(deptno)
  4  /

    DEPTNO GROUPING(DEPTNO)   SUM(SAL)
---------- ---------------- ----------
                          0       3000
                          1      32025
        10                0       8750
        20                0      10875
        30                0       9400

ops$tkyte@ORA9IR2>

See, it is not 1,0,0,0,0  but rather -- DEPTNO "null" is presented (and in
your example "null" was only the cubed result).  Here the first return is the
aggregate for deptno "null" and then the CUBED (summed over all deptnos) result
after squashing the deptno dimension out of existence.  Then we have the deptno
"10" results and so on.

To make it more interesting -- lets add another column:

ops$tkyte@ORA9IR2> select deptno, job, grouping(deptno), grouping(job),
sum(sal)
  2    from emp
  3   group by rollup(deptno,job)
  4  /

Here we'll report by DEPTNO, JOB:

    DEPTNO JOB       GROUPING(DEPTNO) GROUPING(JOB)   SUM(SAL)
---------- --------- ---------------- ------------- ----------
                                    0             0       3000
                                    0             1       3000

thats a little boring -- but only because there was one job "NULL" in that
DEPTNO "NULL", gets more interesting as we move along:

        10 CLERK                    0             0       1300
        10 MANAGER                  0             0       2450
        10 PRESIDENT                0             0       5000
        10                          0             1       8750

see, since job was second, we are basically getting data by deptno, job and
then a summary by deptno - for each deptno:

        20 CLERK                    0             0       1900
        20 ANALYST                  0             0       6000
        20 MANAGER                  0             0       2975
        20                          0             1      10875

another break... another subtotal...

        30 CLERK                    0             0        950
        30 MANAGER                  0             0       2850
        30 SALESMAN                 0             0       5600
        30                          0             1       9400

and the last subtotal by deptno, now for the grand total:

                                    1             1      32025

15 rows selected.

and we are done....  group by rollup is designed to do that for us.  Now onto
cube:

ops$tkyte@ORA9IR2> select deptno, job, grouping(deptno), grouping(job),
sum(sal)
  2    from emp
  3   group by cube(deptno,job)
  4  /

    DEPTNO JOB       GROUPING(DEPTNO) GROUPING(JOB)   SUM(SAL)
---------- --------- ---------------- ------------- ----------
                                    0             0       3000
                                    0             1       3000
                                    1             0       3000
                                    1             1      32025

there are all of the NULL deptno/jobs

           CLERK                    1             0       4150
           ANALYST                  1             0       6000
           MANAGER                  1             0       8275
           SALESMAN                 1             0       5600
           PRESIDENT                1             0       5000

all of the NULL deptno by job

        10                          0             1       8750
        10 CLERK                    0             0       1300
        10 MANAGER                  0             0       2450
        10 PRESIDENT                0             0       5000

all of the deptno=10 by job, and so on...

        20                          0             1      10875
        20 CLERK                    0             0       1900
        20 ANALYST                  0             0       6000
        20 MANAGER                  0             0       2975
        30                          0             1       9400
        30 CLERK                    0             0        950
        30 MANAGER                  0             0       2850
        30 SALESMAN                 0             0       5600

21 rows selected.

it is a "cube", it cannot be presented in some report like format (well, it
could but it would be hugely expensive to have report by report -- report the
details, report summed over dimension 1, report summed over dimension 2, report
summed over dimension 1 and 2 and so on....)  So they do it data value by data
value

Hope that helps.
Rating: 3
Nice March 22, 2004
Reviewer: Ram from Bangalore,India
Dear Sir,
Do you have any simple examples for using the functions
"Group_id" and "Grouping_id"?
Please do reply.
Bye!
March 19, 2004 Anitha -- Thanks for the question regarding "VARRAY", version 8.1.7
You Asked
HI Tom,
I wanted to use VARRAY in my tables . And when I query the table I want to make
look all the columns like a flat table.

for ex:I am creating a varray to store emails -----
create or replace type emails_va as varray(2) of varchar2(30);

and then use it in my emp table -----

create table EMP
(
  EMPID   NUMBER not null,
  EMPNAME VARCHAR2(20),
  EMAILS  EMAILS_VA
)

now I want to create a view on EMP which lists all the rows from EMP like a flat
table .
 may be some thing like
  create view EMP_V (EMPID,EMPNAME,EMAIL1,EMAIL2)
  as (SELECT EMPID,EMPNAME,?,? from EMP ).

I am stumbled at this point .
I appreciate any help on this or any other solution for this.

Thanks
and we said...
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into emp values       ( 1, 'tom',
emails_va( 'x', 'y' ) );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into emp values       ( 2, 'lori',
emails_va( 'a', 'b' ) );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select empid, empname, x.* from emp, table(
emp.emails ) x
  2  /

     EMPID EMPNAME              COLUMN_VALUE
---------- -------------------- ------------------------------
         1 tom                  x
         1 tom                  y
         2 lori                 a
         2 lori                 b
Review & Followup

Rating: 5
Excellent October 09, 2001
Reviewer: A reader
Rating: 5
VARRAY October 10, 2001
Reviewer: William from Ontario Canada
Exellent
Rating: 3
But waht EMAILS_VA data type mean October 10, 2001
Reviewer: Mohammed Esmael from Yemen
yes, useful,but could you please explain to me the benfit of using EMAILS_VA
data Type ,and what it is exactly.
Followup:
It an an array, a collection, that can be stored as a single column with the
table.  Typically it would have more then 2 elements -- you would use it when
you had say upto N values for something and did not want to have a
column/attribute.
Rating: 3
Thank You Tom October 10, 2001
Reviewer: Anitha from Ontario , Canada
Thanks for the reply ,Tom .
But I was looking, if we can display the information like :

 EMPID EMPNAME        email1  email2
    ---------- --------------------
    1 tom                  x    y
    2 lori                 a    b
Followup:
You can but it won't preserve the ORDER of the array -- your array must be
sortable:

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into emp values       ( 1, 'tom',
emails_va( 'x', 'y' ) );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into emp values       ( 2, 'lori',
emails_va( 'a', 'b' ) );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select empid, empname,
                            max( decode( rn, 1, column_value, null ) ) email1,
  2                         max( decode( rn, 2, column_value, null ) ) email2
  3    from ( select empid, empname, x.*,
  4                  row_number() over ( partition by empid order by
                                                             column_value ) rn
  5             from emp, table( emp.emails ) x
  6         )
  7  group by empid, empname
  8  /

     EMPID EMPNAME              EMAIL1                         EMAIL2
---------- -------------------- ------------------------------ ----------------
         1 tom                  x                              y
         2 lori                 a                              b
Rating: 5
thank you October 10, 2001
Reviewer: Anitha from Ontario , Canada
thank you , Tom
Rating: 5
Give the link for the documentation of this datatype-EMAILS_VA October 10, 2001
Reviewer: A reader
EMAILS_VA
Followup:
read the question.  Its a user defined type.  He made it up.  All of the
documentation on the entire planet for EMAILS_VA is on this page.
Rating: 3
What if we need to preserve order? November 21, 2002
Reviewer: Benoit Hebert from Quebec City, Canada
It was a very helpful beginning of an answer for our problem. But what if we
need to preserve the order of the varray, and the array itself is not sortable?
We store numeric values in the array, and the position in the array is just as
important as the data it contains. Is there a way of preserving the order in a
select query like this?
Followup:
You would need a VARRAY or NESTED TABLE type of an object type, eg:

create or replace type myScalarType as object
( seqno  number,
  email_id varchar2(255)
)
/
create or replace type myArrayType as table of myScalarType

so you could order by seqno within the table to get your "order" back.  Just
like you would with ANYTHING in SQL.  Unless you have something to order by --
don't expect anything to be ordered.

If you DON'T try to turn these into "columns" -- just leave them stuffed in the
array in the first place -- order will be maintained (for varrays, not nested
tables). simple select * from table would return the arrays in the order they
went in.
Rating: 3
OK January 07, 2004
Reviewer: Siva from Hyderabad,India
Dear sir,
well and wish the same from you.Could you please look into
the code and provide a solution as given below?

sql> set feedback off
sql> create type skills_va as varray(20) of varchar2(24);

sql> create table employee_skills(enm varchar2(30),skills skills_va);

sql> insert into employee_skills values('Jones',
                    skills_va('Java','C','C++','Oracle','Sybase','ASP','JSP'));

sql> insert into employee_skills values('James',
                    skills_va('Java','Oracle','Sybase','Proj_Mgmt'));

sql>insert into employee_skills values('Ward',
                    skills_va('Oracle','VB','VC++','COM','DCOM'));

sql>insert into employee_skills values('Turner',
                    skills_va('DW','PHP'));

My questions are
 1)Can we select only the skills_va column so that it appears
     in table format like
  enm     skills
  ---     ------
  Jones     Java
             C
            C++
            ..
 for all the rows of employee_skills table?
 2)I need a query which results in the output
            enm   count(skills)  skills
            ----  -------------  -----
            Jones    7           Java
                                  C
                                  c++
                                   ..
            James    4           Java
                                 Oracle
                                   ..
             Ward    5            ..

            Turner   2           ..
 3)I need a query that shows the ename
      i) with highest number of skills.
      ii)with least number of skills.
Could you please help?
Thanks in advance.
Bye!
Followup:
seems like you want to use normal relational tables to me!!!!!

really -- i've never used varray's or nested tables as a storage mechanism -- as
a programming device in plsql, constantly.  as a physical storage device --
nope.

ops$tkyte@ORA9IR2> select enm, column_value
  2  from employee_skills E, table(e.skills);

ENM                            COLUMN_VALUE
------------------------------ ------------------------
Jones                          Java
Jones                          C
Jones                          C++
Jones                          Oracle
Jones                          Sybase
Jones                          ASP
Jones                          JSP
James                          Java
James                          Oracle
James                          Sybase
James                          Proj_Mgmt
Ward                           Oracle
Ward                           VB
Ward                           VC++
Ward                           COM
Ward                           DCOM
Turner                         DW
Turner                         PHP

18 rows selected.

  1  select enm, column_value, count(*) over (partition by enm) cnt
  2* from employee_skills E, table(e.skills)
ops$tkyte@ORA9IR2> /

ENM                            COLUMN_VALUE                    CNT
------------------------------ ------------------------ ----------
James                          Java                              4
James                          Oracle                            4
James                          Sybase                            4
James                          Proj_Mgmt                         4
Jones                          Java                              7
Jones                          C                                 7
Jones                          C++                               7
Jones                          Oracle                            7
Jones                          Sybase                            7
Jones                          ASP                               7
Jones                          JSP                               7
Turner                         DW                                2
Turner                         PHP                               2
Ward                           Oracle                            5
Ward                           VB                                5
Ward                           VC++                              5
Ward                           COM                               5
Ward                           DCOM                              5

18 rows selected.

ops$tkyte@ORA9IR2> edit
Wrote file afiedt.buf

  1  select enm, cnt
  2  from (
  3  select enm, column_value, count(*) over (partition by enm) cnt
  4  from employee_skills E, table(e.skills)
  5  order by 3
  6  )
  7* where rownum = 1
ops$tkyte@ORA9IR2> /

ENM                                   CNT
------------------------------ ----------
Turner                                  2

ops$tkyte@ORA9IR2> edit
Wrote file afiedt.buf

  1  select enm, cnt
  2  from (
  3  select enm, column_value, count(*) over (partition by enm) cnt
  4  from employee_skills E, table(e.skills)
  5  order by 3 desc
  6  )
  7* where rownum = 1
ops$tkyte@ORA9IR2> /

ENM                                   CNT
------------------------------ ----------
Jones                                   7
Rating: 3
OK March 18, 2004
Reviewer: Naresh from Arlington,VA,USA
Dear Sir,
Regarding the above example,"How to select only the values
from Varray" like
 sql>select column_value from table(..)
I used this but throws errors as "Illegal Identified".
Please do reply.
Followup:
hows about your entire example -- no idea what you are trying to do really?
what goes in the .. there?
Rating: 3
OK March 19, 2004
Reviewer: Naresh from Singapore
Hi Tom,
 I meant " If a varray object type becomes part of the
relational table,How to select values only from the varray"
?To be more obvious
 sql>select column_value from table(relational table.varray);
Does this work?
Please do reply.
Bye!
Followup:
select x.*
  from relational_table r, TABLE(r.varray) X
/
March 19, 2004 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!
Rating: 4
Another way to look to count(*) issues... March 16, 2004
Reviewer: Sven Bleckwedel from Santos, SP - Brasil
Hi Tom,

Was in this site that I looked for some questions about count(*/1), and noted
the link:
http://www.oracledba.co.uk/tips/count_speed.htm
Looking around in oracledba.co.uk site, found another interesting link, in:
http://oracledba.co.uk/tips/sample_clause.htm
Made some tests with this function and found something very interesting, about
this specific issue:

SQL> select count(*) from sys.source$;

  COUNT(*)
----------
    109502

Elapsed: 00:00:02.08

SQL> set autotrace on
SQL> select count(*) from sys.source$;

  COUNT(*)
----------
    109502

Elapsed: 00:00:02.09

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'SOURCE$'

Statistics
----------------------------------------------------------
          0  recursive calls
         39  db block gets
       8996  consistent gets
       8996  physical reads
          0  redo size
        393  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 count(*) from sys.source$ sample(99.9999999);

  COUNT(*)
----------
    109502

Elapsed: 00:00:01.00

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (SAMPLE FAST FULL SCAN) OF 'I_SOURCE1' (UNIQUE) (Cost=3
Card=734794)

Statistics
----------------------------------------------------------
         14  recursive calls
          6  db block gets
        264  consistent gets
        262  physical reads
          0  redo size
        393  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

The only thing that anyone must be warned about this is to use this "tip"
carefully, knowing previously that EXIST at least an unique index for the target
table and using the sample clause with the parameter "99.9999999", to return the
right results, of course.   But opened a great possibility for reducing LIOs and
PIOs !  Using this "tip" with anothers published previously in this site, was
possible to count registers when joining with other tables, too...
http://asktom.oracle.com/pls/ask/f?p=4950:8:1931342621091931282::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:6075151195522,
Maybe this can help something...

Rgds,
Sven
Followup:
you don't need sample (which could return "the wrong answer"), just the use of
the CBO.

the use of the SAMPLE feature was not the cause of the plan change in as much as
the INVOCATION of the CBO in order to *use* the sample feature.  The sample
feature is "cbo only"

You can tell that in their example by the addition of the cost/card

Just add /*+ all_rows */ and you'd see the same (but always correct) answer

sys@ORA9IR2> select count(*) from source$;

  COUNT(*)
----------
    318845

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'SOURCE$'

sys@ORA9IR2> select /*+ all_rows */ count(*) from source$;

  COUNT(*)
----------
    318845

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: ALL_ROWS (Cost=4 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FAST FULL SCAN) OF 'I_SOURCE1' (UNIQUE) (Cost=4
Card=1352050)

(eg: don't use SAMPLE for this -- dangerous!)
Rating: 4
Another way to look to count(*) issues...is dangerous ? March 16, 2004
Reviewer: Sven Bleckwedel from Santos, SP - Brasil
Hi,

I can understand that I made some "overutilization" here, but I can´t believe
that is dangerous at all.  The main reason for using the value "99.9999999" in
sample clause parameter is the fact that this number is the nearest value to
100%, in this case.  And noted that this could return correct values (when exist
at least one UNIQUE index) and was possible to reduce LIOs and PIOs (autotrace
showed this), too...

Rgds,
Sven
Followup:
99.9999999 is not 100, the counts need not be the same!

the bottom line is:

a) sample was not the catalyst
b) the cbo was
c) there is a better, SAFER way to get the cbo involved without a 'trick'

use /*+ all_rows */ or /*+ first_rows */ and you'll get the same exact "effect"
without the worry and restrictions of SAMPLE.
Rating: 3
Ok March 17, 2004
Reviewer: Ben from Vancouver,Canada
Dear Tom,
My query finds the number of tables,views,procedures,func's
,packages etc which are owned by me.I used a query like
 sql>select object_name,count(*)
        from obj
      group by object_name
      order by object_name;
This works well.Is there any other ways that can be used and which may be
better?
Please do reply.
p.s) Is an inline view possible here like
  sql>select object_name,(select ..)cnt_tables,
      (select..)cnt_views,(select ..)cnt_procs
     from obj;
Is the above format possible?
Followup:
that finds the count of tables/views/procedures??

it seems to be getting object_names which won't tell you that which you say it
does.

I don't understand the usage of object_name here (use the USER_ views, like
USER_OBJECTS.  the data dictionary views you should use start with DBA_ ALL_ and
USER_ -- the ones like obj, tab -- they are for the "way back machine" --
backwards compatibility only)
Rating: 5
count question March 17, 2004
Reviewer: Tarun from India
Hi tom,
    Please verify these statements.
 SQL> select count(*) from emp;

  COUNT(*)
----------
        14

SQL> select count(1) from emp;

  COUNT(1)
----------
        14

SQL> select count(null) from emp;

COUNT(NULL)
-----------
          0

1. Why count is displaying same result, when we pass 1 or *?

2. When passed null why it is 0 rows?

Please explain?
Thanks you.
Followup:
1) because count(*) says count the records and count(<expression>) says count
the non-null occurrences of this expression (and of course 1 is "not null") so
count(*) and count(1) return the same answer (but count(*) shows you know how to
ask "how many records" properly)

2) because count(<expression>) counts NON NULL occurences.  try count(sal),
count(comm) and see what happens.
Rating: 3
OK March 17, 2004
Reviewer: Srinivas from Hyderbad,AP,India
Hello Sir,
What is the use of "sample clause " in a sql select statement?
Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96540/statements_103a.htm#2065954
Rating: 4
Pourpose of sample clause... March 17, 2004
Reviewer: Sven Bleckwedel from Santos, SP - Brasil
Srinivas,

Sample clause is used to return randomly selected data.

>sample(10) is 10%
>
>See the SQL Reference for information on SQL statements!
>
>
>
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a85397/state21b.htm#2065954
>
>sample(10) will return about 10% of the rows randomly.

Sven
Rating: 3
Count with * or 1 parameter, that´s the question... March 17, 2004
Reviewer: Sven Bleckwedel from Santos, SP - Brasil
Tarun,

q1) Doesn´t matter.  See:
http://oracledba.co.uk/tips/count_speed.htm
q2) Maybe is the same case as:

>select count(0) is just like
>
>select count(*)
>  from ( select 0 from t )
>/
>
>...oracle went to each block to find the rows to give >you a zero
http://asktom.oracle.com/pls/ask/f?p=4950:8:3678029641418332880::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:880343948514,
Sven
Rating: 5
count(column_name) March 18, 2004
Reviewer: A reader
Is count(column_name) also the same as count(*) or count(1)
Followup:
no, look up just a little:

2) because count(<expression>) counts NON NULL occurences.  try count(sal),
count(comm) and see what happens.

count(column_name) returns a count of NON-NULL occurences of column_name.
count(*) returns the count of records.  count(1) returns the count of records
where the constant 1 exists (eg: it is a bad way of saying count(*))
Rating: 4
Count rows March 19, 2004
Reviewer: Tarun Babu from Hyderabad, India
Hi Tom,
     I am trying to count the no of rows present in every table in my scott
schema. Though I was able to write a pl/sql block for the follwoing out
put..but, wondering how can we solve this with single query or subquery. Help
will be appreciated. Thank U very much.

TABLE_NAME                NO OF ROWS
----------                ----------
DEPT                               4
EMP                               14
SALGRADE                           6
........                          ..
........                          ..
Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:12120035671245

Rating: 3
Nice March 19, 2004
Reviewer: Ram from Bangalore,India
Dear Tom,
 I used the following queries which should return the same
count but it is not so.Why is this happening?
 SQL>  select deptno,count(*)
  2    from emp
  3   group by deptno;

    DEPTNO   COUNT(*)
---------- ----------
        10          3
        20          5
        30          6

SQL> select deptno,count(*) over(partition by deptno) from emp;

    DEPTNO COUNT(*)OVER(PARTITIONBYDEPTNO)
---------- -------------------------------
        10                               3
        10                               3
        10                               3
        20                               5
        20                               5
        20                               5
        20                               5
        20                               5
        30                               6
        30                               6
        30                               6

    DEPTNO COUNT(*)OVER(PARTITIONBYDEPTNO)
---------- -------------------------------
        30                               6
        30                               6
        30                               6

14 rows selected.

Please do reply.
Followup:
they are returning the same counts?

the first query says "aggregate the result set to return only distinct values of
the group by and count the records"

that is 10->3, 20->5, 30->6

The second query says "don't aggregate, but by deptno assign the count of
records to each record in the partition".  the same result is apparent to me --
the count of records in deptno 10 is 3, every deptno 10 record gets "3"

tell me, what were you expecting to see? (you are comparing APPLES to Toaster
Ovens however -- aggregates are not analytics, analytics are not aggregates)
Rating: 4
March 19, 2004
Reviewer: Tomas from Lithuania, Europe
Dear Ram,

in your second query you have used analytic function:
count(*) over(....)
so it returned count for each department (which is the same as in the query with
group by clause) but for each and every row in emp table.
March 19, 2004 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.
Rating: 5
comparing time... March 19, 2004
Reviewer: A reader
Hi TOM,

I have a confusion regarding cmparing of time.

Say we have a table sdheduler with columns

locations varchar(20)
,batchstarttime date
,batchnumber number

and we have data like
LOCATIONS BATCHSTARTTIME BATCHNUMBER
-------- -------------- ------------
DO     01-JAN-2000 23:15          1
DO     01-JAN-2000 09:15          2
DO     01-JAN-2000 09:12          3
DO     30-DEC-1999 10:00          4
CO     01-JAN-2000 08:15         85
CO     01-JAN-2000 13:15         89
FO     30-DEC-1999 11:00          6
GO     30-DEC-1999 05:00          7
GO     30-DEC-1999 10:10         77
HO     30-DEC-1999 04:00          8
HO     30-DEC-1999 07:00          9
HO     30-DEC-1899 09:00         10
HO     01-JAN-2000 10:30         78

now i want to find out the batchnumber which ran just before 11:00 Hrs at any
day for different locations

like from the above data i should get

DO     30-DEC-1999 10:00          4
CO     01-JAN-2000 08:15         85
GO     30-DEC-1999 10:10          7
HO     01-JAN-2000 10:30         78

How do i do this ?

i have been trying something like

SELECT a.locations, a.batchstarttime, a.batchnumber
  FROM scheduler a
 WHERE TO_CHAR (TO_DATE ('19-MAR-2004 11:00', 'DD-MON-YYYY hh24:mi'),
                'hh24:mi'
               ) >
           (SELECT MAX (TO_CHAR (b.batchstarttime, 'hh24:mi'))
              FROM scheduler b
             WHERE a.batchnumber = b.batchnumber AND a.LOCATION = b.locations)

how do i do this ? Please help.

Regards
Followup:
you can use this technique (object_id = batch_number, object_name = locations,
created = batchstartime

ops$tkyte@ORA9IR2> select object_id, object_name, to_char(created,'dd-mon-yyyy
hh24:mi') created
  2    from (
  3  select object_id, object_name, max(created)
              over (partition by trunc(created)) max_created, created
  4    from all_objects
  5   where to_char( created, 'hh24' ) < '11'
  6         )
  7   where created = max_created
  8   order by created
  9  /

 OBJECT_ID OBJECT_NAME                    CREATED
---------- ------------------------------ -----------------
     30772 ALL_PARAMETERS                 01-dec-2003 10:21
     31367 TMP                            02-jan-2004 09:31
     34892 MYCTX                          11-feb-2004 09:15
     33923 T                              15-jan-2004 09:05
     34117 LOAD_TEST_DATA                 16-jan-2004 06:42
     35557 OP                             17-mar-2004 08:28
     35783 FTP                            18-mar-2004 10:57
     35797 EMP                            19-mar-2004 09:14
     34288 BONUS                          20-jan-2004 06:31
     34286 EMP                            20-jan-2004 06:31
     34289 SALGRADE                       20-jan-2004 06:31
     34290 DUMMY                          20-jan-2004 06:31
     34287 DEPT                           20-jan-2004 06:31
     30492 UTL_RECOMP_INV_IDX1            25-nov-2003 09:04
     34562 MY_FILES                       28-jan-2004 07:40

15 rows selected.

Or:

ops$tkyte@ORA9IR2> select object_id, object_name, to_char(created,'dd-mon-yyyy
hh24:mi') created
  2    from all_objects x
  3   where to_char( created, 'hh24' ) < '11'
  4     and created = ( select max(created)
  5                       from all_objects
  6                                          where trunc(created) =
trunc(x.created)
  7                                            and to_char(created,'hh24') <
'11')
  8   order by created
  9  /

 OBJECT_ID OBJECT_NAME                    CREATED
---------- ------------------------------ -----------------
     30772 ALL_PARAMETERS                 01-dec-2003 10:21
     31367 TMP                            02-jan-2004 09:31
     34892 MYCTX                          11-feb-2004 09:15
     33923 T                              15-jan-2004 09:05
     34117 LOAD_TEST_DATA                 16-jan-2004 06:42
     35557 OP                             17-mar-2004 08:28
     35783 FTP                            18-mar-2004 10:57
     35797 EMP                            19-mar-2004 09:14
     34288 BONUS                          20-jan-2004 06:31
     34287 DEPT                           20-jan-2004 06:31
     34290 DUMMY                          20-jan-2004 06:31
     34286 EMP                            20-jan-2004 06:31
     34289 SALGRADE                       20-jan-2004 06:31
     30492 UTL_RECOMP_INV_IDX1            25-nov-2003 09:04
     34562 MY_FILES                       28-jan-2004 07:40

15 rows selected.

but you'll find the first one to be "better" i believe.
Rating: 1
March 19, 2004
Reviewer: A reader
sorry there was a typo in the above query....

SELECT a.locations, a.batchstarttime, a.batchnumber
  FROM scheduler a
 WHERE TO_CHAR (TO_DATE ('19-MAR-2004 11:00', 'DD-MON-YYYY hh24:mi'),
                'hh24:mi'
               ) >
          (SELECT MAX (TO_CHAR (b.batchstarttime, 'hh24:mi'))
             FROM scheduler b
            WHERE a.batchnumber = b.batchnumber AND a.locations = b.locations)
Rating: 3
installation March 19, 2004
Reviewer: haron from saudi arabia
Please help me how can i install oracle 8i and 8 and forms in windows xp
millions of thanks i need your responds thanks
Rating: 5
Thanks for the query March 20, 2004
Reviewer: A reader
Thanks a lot TOM....
March 19, 2004 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!)
Rating: 4
IOT / hash / heap January 22, 2004
Reviewer: Marcio from br
Just to clarifying:
Scenario:

DB A

t_source ( id_contract   varchar2(17), name_con varchar2(40), ... )

DB B
t_interface ( id_contract varchar2(17), dt_load date )

t_process (id_contract varchar2(17), dt_process date )

t_contract (id varchar2(17) pk, name ..., state check ('A','B','C','D','E'))

t_history (qty_rows_process number(7), qty_files ...)

There is no fk references between tables.

1) A job #1 wakes up --> collect all data from t_source between dt1 and dt2 and
insert into t_interface set null in dt_load. About 2,000,000 rows at time --
interval 1h (8am - 5pm) we talking about 20,000,000 rows/day.

2) Another job #2 wakes up --> insert into t_process from t_source where dt_load
is null, set dt_load.

3) Another yet job #3 wakes up --> select t_process, t_contract where
id_contract = id and state in ('A','B') -- export some files with information
based on state, insert into stats into t_history and set dt_process.

The tables t_contract and t_source I can't touch.
Wich would be create rest of them (t_interface, t_process and t_history)?
IOT/b*tree Hash/heap/Other? and please explain...
Followup:
those structures you mention

o IOT's
o b*tree cluster
o hash cluster

are all about ACCESS -- they are used to optimize your frequent retrievals from
the tables you load into.

Hence, a description of a load cannot tell us what sort of structures you should
use to load.  You have to look at

o how you use the data after loaded
o whether locale of data would be useful

if you want examples and discussions on this -- I go over it in both "Expert one
on one Oracle" and "Effective Oracle by Design" in some detail.
Rating: 4
IOT / hash / heap - Continued January 23, 2004
Reviewer: Marcio from br
The system above is all about. As is!
I've found full relevant information about iot, b*tree cluster and so on your
book, but I'd like know your opinion about what type of creation would be the
tables in the scenario above -- if you don't mind.

Btw - fyi: You have yet the warning on followup page about your trip. "Bear in
mind, I am out of the country from Jan 10th ..."
Followup:
you only load, you never query... hmmm

fastest way to do that is -- don't load.  you don't need it i guess.  if that is
"the system", just don't load -- you never query ;)

there you go.

(there MUST be more to the system, else this system does nothing.  If you want
to learn what structures would be best -- you need to understand how these
structures work

a) server concepts
b) my books

you can read those to gain understanding!)

the scenario above would call for simple "heap" tables.

the missing part is "what really happens to the data later" -- that is where
iot/hash clusters/b*tree clusters etc come into play
Rating: 5
Load performance... March 18, 2004
Reviewer: Scott Watson from Montreal, Canada
Tom,

One of the apps I support has a query like this.

select many columns from part_iot_table
where ctry_cd = 'XX' and id = 999;

On average there are 9 rows per id, ctry_cd will always be the same.

I decided to create the table as a partition IOT to colocate the data together
around the id column.  However, the team has decided that they also want to
query the table like so.

select many columns from part_iot_table
where id = 999;

Now my partition elimiation scheme is out the window.  I could create a global
secondary index on the subj_id, however, I was thinking that maybe a single hash
cluster on id might solve both problems at once.

My problem is that loading the single table hash cluster is so slow that this
option does not seem feasible.  Hopefully you can shed your experience on
increasing load performance for these types of objects.  So far it has been
running for about 8 hours and I have no way to estimate when it will complete.

Since I am loading from the IOT and the data is already sorted by id, I would
expect that most blocks would be cached after the first read and not accessed
again.

I tried to test the single hash cluster by loading a single partition of 30M
rows.

My cluster was created with the following syntax.

create cluster match_data_cluster
( subj_id number(13))
single table
hashkeys 30000000  -- should have used 30M/9 rows per key
size 94
tablespace SCOTT_DONT_USE
pctfree 0
parallel 4

Can you suggest anything I can do to increase the load performance.  I am not
running in Archive log mode on my test server.

My next option, if I cannot get the load to speed up, would be to try and create
a hash partitioned IOT on the id column and add a dummy column using rownum to
ensure that I have a primary key.  A head table was out of the question as we
sometimes need to lookup 5000 id at a time in the 300M table and there is too
much bouncing around.

I would appreciate your thoughts and comments.  I am running 9.2.0.3 on HP 11.

Thanks once again
Scott Watson
Followup:
I'm confused -- if they query:

select many columns from part_iot_table
where ctry_cd = 'XX' and id = 999;

select many columns from part_iot_table
where id = 999;

the primary key of the IOT for clustering should be:

(id,ctry_cd,.....anything else you need...)

and the data would be partitioned by ID ranges/hashes/whatever...

so, why doesn't the partitioned IOT work for you?
Rating: 5
More information March 19, 2004
Reviewer: Scott Watson from Montreal, Canada
That was my plan if the hash cluster didn't work.  However, I didn't want to
give up on the single table hash cluster just yet.  I was hoping you might know
why the load would be taking so long.
Followup:
funny -- thought the IOT was the plan but abandoned because you thought it would
not work, when it would have to be built with the key in that order in the first
place :)

you have size 94, is 94 bytes sufficient to hold 9 rows per hash key?
Rating: 5
More info cont.. March 19, 2004
Reviewer: Scott Watson from Montreal, Canada
The 94 bytes is the space used per row.  I guess I should be using 94*9 instead,
an oversite on my part.

The IOT is a great option and it is what I had been recommending.  The problem
was that they added another query that wasn't suited for how the table was
origninally built.

So given the new set of requirements, I thought I would build one IOT version
and one single table hash cluster   and compare the performance difference
between the two.  ( I would have never thought of using the cluster until I read
your new book)  Once I saw the terrible performance of the load into the cluster
I decided I did something wrong and sought your advice.  I did not mean for you
to think that I was not considering the IOT.

Thanks
Followup:
well, part of the horrid performance would be caused by the massive collisions
and chaining brought about by undersizing the cluster by an order of magnitude!

"size 1000"
March 19, 2004 Michelle -- Thanks for the question regarding "Use of Resumable Space Allocation", version 9i
You Asked
Does Resumable Space Allocation work with the Import Utility?
and we said...
yes.

$ imp help=y

Import: Release 9.0.1.0.0 - Production on Fri Mar 1 15:38:34 2002

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

You can let Import prompt you for parameters by entering the IMP
command followed by your username/password:

     Example: IMP SCOTT/TIGER

Or, you can control how Import runs by entering the IMP command followed
by various arguments. To specify parameters, you use keywords:

     Format:  IMP KEYWORD=value or KEYWORD=(value1,value2,...,valueN)
     Example: IMP SCOTT/TIGER IGNORE=Y TABLES=(EMP,DEPT) FULL=N
               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         import entire file (N)
BUFFER   size of data buffer         FROMUSER     list of owner usernames
FILE     input files (EXPDAT.DMP)    TOUSER       list of usernames
SHOW     just list file contents (N) TABLES       list of table names
IGNORE   ignore create errors (N)    RECORDLENGTH length of IO record
GRANTS   import grants (Y)           INCTYPE      incremental import type
INDEXES  import indexes (Y)          COMMIT       commit array insert (N)
ROWS     import data rows (Y)        PARFILE      parameter filename
LOG      log file of screen output   CONSTRAINTS  import constraints (Y)
DESTROY                overwrite tablespace data file (N)
INDEXFILE              write table/index info to specified file
SKIP_UNUSABLE_INDEXES  skip maintenance of unusable indexes (N)
FEEDBACK               display progress every x rows(0)
TOID_NOVALIDATE        skip validation of specified type ids
FILESIZE               maximum size of each dump file
STATISTICS             import precomputed statistics (always)
RESUMABLE              suspend when a space related error is encountered(N)
RESUMABLE_NAME         text string used to identify resumable statement
RESUMABLE_TIMEOUT      wait time for RESUMABLE
COMPILE                compile procedures, packages, and functions (Y)
VOLSIZE                number of bytes in file on each volume of a file on tape

The following keywords only apply to transportable tablespaces
TRANSPORT_TABLESPACE import transportable tablespace metadata (N)
TABLESPACES tablespaces to be transported into database
DATAFILES datafiles to be transported into database
TTS_OWNERS users that own data in the transportable tablespace set

Import terminated successfully without warnings.
Review & Followup

Rating: 5
Good to know. January 09, 2004
Reviewer: Robert from Memphis, USA.
Rating: 4
inctype March 19, 2004
Reviewer: jasdeep from patiala,india
hi tom

which are the types of inctype and to utilise it in export backup
Followup:
they are documented and not suggested.  if you want to read about them -- server
utilities guide documents it.

however, export is not a backup tool really.

do not use export as a backup tool.

backups are backups.
March 19, 2004 -- 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
Rating: 5
String search in BLOB March 19, 2004
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom,
A database design is using BLOBs for storing character strings. Is there a way
in SQL or PL/SQL to search for matching pattern (instr) in the BLOB column?
Thanks
Followup:
Oracle text will index that.

dbms_lob.instr( blob, utl_raw.cast_to_raw( 'your_string' ) )

will search for that text 'your_string' in the blob (slowly) as well.
March 19, 2004 -- 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;
Rating: 5
Date validation using sql March 19, 2004
Reviewer: A reader
date validation:
----------------

I have two dates given to my procedure p.

Now I should validate them and see that the two dates do no span over one month
i.e.

Dates like 01/01/2003 , 02/02/2003 are not valid. If the first date is
01/01/2003 then the second date
at the max can only be 01/31/2003 i.e. if should fall within the month of the
first date.

I'm pondering how to do this validation using sql, and avoid writing a procedure
to do the same.

can you help.
Followup:
"when abs( months_between(a,b) ) <= 1"

ops$tkyte@ORA9IR2> select abs(months_between('01-jan-2003','02-feb-2003')) from
dual
  2  /

ABS(MONTHS_BETWEEN('01-JAN-2003','02-FEB-2003'))
------------------------------------------------
                                      1.03225806

ops$tkyte@ORA9IR2> select abs(months_between('01-jan-2003','31-jan-2003')) from
dual
  2  /

ABS(MONTHS_BETWEEN('01-JAN-2003','31-JAN-2003'))
------------------------------------------------
                                      .967741935

ops$tkyte@ORA9IR2> select abs(months_between('15-jan-2003','15-feb-2003')) from
dual
  2  /

ABS(MONTHS_BETWEEN('15-JAN-2003','15-FEB-2003'))
------------------------------------------------
                                               1
Rating: 5
doesnt work for me, but good solution March 19, 2004
Reviewer: A reader
Tom

date validation:
----------------

I have two dates given to my procedure p.

Now I should validate them and see that the two dates do no span over one month
i.e.

Dates like 01/01/2003 , 02/02/2003 are not valid. If the first date is
01/01/2003 then the second date
at the max can only be 01/31/2003 i.e. if should fall within the month of the
first date.

I'm pondering how to do this validation using sql, and avoid writing a procedure

to do the same.

can you help.

Followup:
"when abs( months_between(a,b) ) <= 1"

ops$tkyte@ORA9IR2> select abs(months_between('01-jan-2003','02-feb-2003')) from
dual
  2  /

ABS(MONTHS_BETWEEN('01-JAN-2003','02-FEB-2003'))
------------------------------------------------
                                      1.03225806

ops$tkyte@ORA9IR2> select abs(months_between('01-jan-2003','31-jan-2003')) from
dual
  2  /

ABS(MONTHS_BETWEEN('01-JAN-2003','31-JAN-2003'))
------------------------------------------------
                                      .967741935

ops$tkyte@ORA9IR2> select abs(months_between('15-jan-2003','15-feb-2003')) from
dual
  2  /

ABS(MONTHS_BETWEEN('15-JAN-2003','15-FEB-2003'))
------------------------------------------------
                                               1

This will not work for me, I cant have the dates spanning across months, the
dates should be within one month.

The whole point behind this question was to force the users enter begin and end
dates  which fall within a month, any month but within that month. If they enter
01/01/2003 as the begin date , then they should enter a date which falls in jan
for the end date.

I should capture what they have entered and let them know if their dates span
accross more than 1 month i.e. jan in the give example.
Followup:
then it is even easier?

where trunc(d1,'mm') = trunc(d2,'mm')
March 19, 2004 Ravi -- Thanks for the question regarding "Performance in Parallel query", version 805
You Asked
Dear Tom,

I am testing our application for improving performance against
parallel query. One table have 8 million rows and thought
parallel query on full table scan of that table will improve
performance. On the contrary, I get less performance with
parallel query.

I altered that table for degree 4
No one is using that db except me
parallel_min_servers=4
parallel_max_servers=30
All the other parallel init.ora parameters are left to default

The query used in parallel query are

select /*+ PARALLEL (t_name, 4) */ count(*) from t_name;
select /*+ PARALLEL (t_name, 4) */ max(trx_amt) from t_name;

The query used in Non parallel  are

select /*+ NOPARALLEL (t_name) */ count(*) from t_name;
select /*+ NOPARALLEL (t_name) */ max(trx_amt) from t_name;

For the same query, I got the answer in non parallel mode in
59secs, where as the same is taking around 2min20sec in parallel
mode. I repeatedly got the answers with the same response time.

Will parallelism on full table scan  always not improve the
performance. Do I need to set any other parameters. Please give
your suggestion.

Regards
Ravi
and we said...
Well, lets see the plans.  I'll theorize here.

select /*+ PARALLEL (t_name, 4) */ count(*) from t_name;

will do a parallel 4 full tablespace on the table T_NAME.  If
T_name is "wide" this will be alot of data to scan. On the other
hand:

select /*+ NOPARALLEL (t_name) */ count(*) from t_name;

will do an INDEX FAST FULL SCAN on the primary key or any index
on a non-null column to count the rows.  As the index is much
smaller (perhaps much less then 1/4 of the width of the table)
this has infinitely less data to shift through and will run in a
very small fracation of the time -- even though its a single
thread (this single thread has less data to read then any of the
4 PQ slaves above given the index is "skinny" )

As for the MAX() -- I'll make the same guess.  TRX_AMT is
indexed and the parallel 4 goes for the full tablecan and
noparallel just uses an index range scan or index fast full
scan.

We ALWAYS need the plans to answer definitively -- otherwise we
are just guessing.

Also remember -- doing lots of things simultaneously is not
always going to be faster then doing one thing intelligently.
Putting 10 programmers to work on a subroutine might take longer
then letting 1 good programmer work on it.  Same with SQL -- PQO
is a tool, not a silver bullet.  It will be faster sometimes and
sometimes -- NOT.

So, if you can get the plans -- feel free to followup with a new
question (autotrace in sqlplus or
tkprof+sql_trace+timed_statistics will do that for you)
Review & Followup

Rating: 5
Parallel execution doesnt show in the explain plan- why October 11, 2001
Reviewer: A reader
SQL> select /*+ PARALLEL (T,4) */ count(*) from T;

  COUNT(*)
----------
   1003453

Elapsed: 00:01:67.12
SQL> set autotrace on
SQL> /

  COUNT(*)
----------
   1003453

Elapsed: 00:01:72.06

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6358 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'T(Co
          st=6358 Card=5399048)

Tom, can you conclusively show us a query using parallel query , and getting
benefitted from it.

That will help us tide over the starting problem.
Followup:
I don't think you have PQ setup.  It is most likely just doing a full scan.
Check your PARALLEL settings (show parameter parallel)

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2          l_n number;
  3          l_start number default dbms_utility.get_time;
  4  begin
  5          select count(*) into l_n from big;
  6          dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs'
);
  7  end;
  8  /
106 hsecs

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> /
99 hsecs

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2          l_n number;
  3          l_start number default dbms_utility.get_time;
  4  begin
  5
  6          select /*+ FULL(big) PARALLEL(big,4) */ count(*) into l_n
  7        from big;
  8          dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs'
);
  9  end;
 10  /
69 hsecs

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> /
66 hsecs

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace on
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from big
  2  /

  COUNT(*)
----------
    540064

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'BIG'

Statistics
----------------------------------------------------------
          0  recursive calls
         45  db block gets
       7382  consistent gets
       7367  physical reads
          0  redo size
        369  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

ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ FULL(big) PARALLEL(big,4) */
  2  count(*)
  3  from big
  4  /

  COUNT(*)
----------
    540064

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=7 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     SORT* (AGGREGATE)
                              :Q14000
   3    2       TABLE ACCESS* (FULL) OF 'BIG' (Cost=7 Card=18869)
                              :Q14000

   2 PARALLEL_TO_SERIAL            SELECT /*+ PIV_SSF */ SYS_OP_MSR(COUNT(*)) F
                                   ROM (SELECT /*+ NO_EXPAND ROWID(A2) */ 0 FRO
                                   M "BIG" PX_GRANULE(0, BLOCK_RANGE, DYNAMIC)
                                    A2) A1

   3 PARALLEL_COMBINED_WITH_PARENT

Statistics
----------------------------------------------------------
          0  recursive calls
        633  db block gets
       7578  consistent gets
       7367  physical reads
          0  redo size
        369  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
Rating: 4
Setup for parallel query October 13, 2001
Reviewer: mike from atlanta
Tom,
I have never got the parallel qyery work.
Please post your method of setup, if you do not mind.
Followup:
I just have one init.ora:

parallel_max_servers = 25

and the others are default:

ops$tkyte@ORA817DEV.US.ORACLE.COM> 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 25
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
Rating: 4
you have to run it to show the parallel query used October 13, 2001
Reviewer: mike from atlanta
Thanks tom.
I had to run the query to find out if the PQ used:

SQL> explain plan for select count(*) from t;

Explained.

SQL>  @plan

QUERY_PLAN
------------------------------------------------------------
  SORT AGGREGATE
    SORT AGGREGATE
      TABLE ACCESS FULL T

4 rows deleted.

$$$ it does not show the PQ used

SQL> set autotrace trace
SQL> select count(*) from t;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=104 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     SORT* (AGGREGATE)                                        :Q8000
   3    2       TABLE ACCESS* (FULL) OF 'T' (Cost=104 Card=13784)      :Q8000

   2 PARALLEL_TO_SERIAL            SELECT /*+ PIV_SSF */ SYS_OP_MSR(COUNT(*)) F
                                   ROM (SELECT /*+ NO_EXPAND ROWID(A2)

   3 PARALLEL_COMBINED_WITH_PARENT

$$ it is 8.1.5
Rating: 4
the BIG in your parallel query test October 15, 2001
Reviewer: mike from atlanta
Tom,
I have hardly  obtained better performance than non parallel. And I want to
duplicate your test. What is the definition of BIG and how to create it?
Followup:
My big was:

create table big as select * from all_objects;

insert /*+ append */ into big select * from big;
commit;

(repeat above two until there are lots of rows).

how many cpus do you have, how is your disk, what type of disk.  lots of things
affect this.
Rating: 4
Worked, but at the expense of resources (as expected) October 15, 2001
Reviewer: mike from atlanta
SQL> declare
  2   l_n number;
  3   l_start number default dbms_utility.get_time;
  4  begin
  5   select count(*) into l_n from big;
  6   dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs'
  7   );
  8  end;
  9  /
324 hsecs

PL/SQL procedure successfully completed.

SQL> declare
  2  l_n number;
  3  l_start number default dbms_utility.get_time;
  4  begin
  5  select /*+ FULL(big) PARALLEL(big,4) */ count(*) into l_n
  6  from big;
  7  dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs'
  8  );
  9  end;
 10  /
98 hsecs

PL/SQL procedure successfully completed.

SQL> select count(*) from big;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'BIG'

Statistics
----------------------------------------------------------
          0  recursive calls
         48  db block gets
       7842  consistent gets
       7773  physical reads
          0  redo size
        601  bytes sent via SQL*Net to client
        467  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 /*+ FULL(big) PARALLEL(big,4) */ count(*) from big;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     SORT* (AGGREGATE)                                        :Q18000
   3    2       TABLE ACCESS* (FULL) OF 'BIG' (Cost=5 Card=10047)      :Q18000

   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
       2205  db block gets
       8561  consistent gets
       7826  physical reads
          0  redo size
        601  bytes sent via SQL*Net to client
        500  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
          1  rows processed
Rating: 5
cbo October 17, 2001
Reviewer: A reader
In order to use parallel query , is it necessary to switch to cbo, or to
rephrase it, will using parallel switch the optimizer to cbo.
Followup:
parallel query implies CBO, you will be using CBO with parallel query.
Rating: 5
parallel query works only when I give table alias- why October 17, 2001
Reviewer: Nag
Tom

Look below , the parallel query works only when I give the table alias, why is
this happening.

SQL> select /*+ FULL(dev.t) PARALLEL(a,4) */
  2     count(*)  from  dev.t a ;

  COUNT(*)
----------
134234

Elapsed: 00:00:38.78

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6358 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     SORT* (AGGREGATE)                                        :Q239000
   3    2       TABLE ACCESS* (FULL) OF 'PORT_POSN_DEAL_DAILY_011010'  :Q239000
          (Cost=6358 Card=5399048)

   2 PARALLEL_TO_SERIAL            SELECT /*+ PIV_SSF */ SYS_OP_MSR(COUNT(*)) F
                                   ROM (SELECT /*+ NO_EXPAND ROWID(A2)

   3 PARALLEL_COMBINED_WITH_PARENT

Statistics
----------------------------------------------------------
         19  recursive calls
        323  db block gets
      66207  consistent gets
      66100  physical reads
        692  redo size
        209  bytes sent via SQL*Net to client
        253  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL> select /*+ FULL(dev.t) PARALLEL(dev.t,4)
  2  count(*)  from  dev.t ;
select /*+ FULL(dev.t) PARALLEL(dev.t,4)
       *
ERROR at line 1:
ORA-01742: comment not terminated properly

Elapsed: 00:00:02.97
SQL> select /*+ FULL(dev.t) PARALLEL(dev.t,4) co
unt(*)  from  dev.t ;
select /*+ FULL(dev.t) PARALLEL(dev.t,4) count(*
       *
ERROR at line 1:
ORA-01742: comment not terminated properly

SQL> select /*+ FULL(dev.t) PARALLEL(dev.t,4) */
 count(*)  from  dev.t ;

  COUNT(*)
----------
134234

Elapsed: 00:01:107.36

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6358 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'PORT_POSN_DEAL_DAILY_011010' (Co
          st=6358 Card=5399048)

Statistics
----------------------------------------------------------
          0  recursive calls
         21  db block gets
      66107  consistent gets
      66094  physical reads
          0  redo size
        210  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)
          1  rows processed

SQL>
Followup:
cause the documentation says that is the way it works:
http://download-east.oracle.com/docs/cd/A81042_01/DOC/server.816/a76992/hints.htm#4926
"The PARALLEL hint must use the table alias if an alias is specified in the
query."

(please try to clean up your examples -- the syntax errors just make it
confusing)
Rating: 5
October 18, 2001
Reviewer: Nag
"The PARALLEL hint must use the table alias if an alias is specified in the
query."

Tom , kindly observe theuse of the word 'if'  in the above sentence.

That means, in effect if no alias is using in the query , the query should still
use parallel query. That also means that alias is a not mandatory for parallel
query.

In the example I gave a above( sorry for posting it with cleaning it up), the
query is not doing parallel query if I dont use the table alias. That is my
concern.

It works when I use table alias, but it does not work when no table alias is
used in the query. This is telling me contrary to what I find in the
documentation -- i.e. use of a table alias is in fact mandatory to use parallel
query.

Kindly clarify.
Followup:
because you used the owner.tablename, not just the tablename as it said.
Consider:

ops$tkyte@ORA717DEV.US.ORACLE.COM> select /*+ full(big) parallel(big,2) */
count(*)
  2  from ops$tkyte.big
  3  /

  COUNT(*)
----------
         0

Elapsed: 00:00:00.72

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     SORT* (AGGREGATE)
                              :Q18000
   3    2       TABLE ACCESS* (FULL) OF 'BIG' (Cost=1 Card=82)
                              :Q18000

   2 PARALLEL_TO_SERIAL            SELECT /*+ PIV_SSF */ SYS_OP_MSR(COUNT(*)) F
                                   ROM (SELECT /*+ NO_EXPAND ROWID(A2) */ 0 FRO
                                   M "OPS$TKYTE"."BIG" PX_GRANULE(0, BLOCK_RANG
                                   E, DYNAMIC)  A2) A1

   3 PARALLEL_COMBINED_WITH_PARENT

ops$tkyte@ORA717DEV.US.ORACLE.COM> select /*+ full(big)
parallel(ops$tkyte.big,2) */ count(*)
  2  from ops$tkyte.big
  3  /

  COUNT(*)
----------
         0

Elapsed: 00:00:00.70

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'BIG' (Cost=1 Card=82)
Rating: 5
CTAS with no logging and parallel degree of 5 November 02, 2001
Reviewer: A reader
Tom

We have 5 tables and all have one to one relationship.
We are create table t with all the data from all the 5 tables.

The total number of rows are going to be 25 million.

We are trying to use no logging feature. But we want to know how to specify the
parallel degree along with this DDL.

Kindly guide us.
Followup:
see
http://download-east.oracle.com/docs/cd/A81042_01/DOC/server.816/a76965/c22paral.htm#5579
for full examples and documentation on how to do this.
Rating: 4
alter session enable parallel dml November 05, 2001
Reviewer: A reader
Tom, in book from J.Couchman I have read:"User must remember to issue the alter
session enable parallel DML" in order to use parallel DML, but I not saw you do
it. And I have ever thought, I should enable the use of parallel DML by create
table using parallel clause too.
Can you explain me what need I really set? For example, is my update statememt
will use parallel DML if I have not issued alter session enable parallel, but by
create table
parallel clausel was used? Or in my session parallel DML is enabled, but by
create table not?
How can I see if parallel DML is enabled in session. Show parameter not work
hier?
Followup:
you didn't see me do that for the simple reason that I did not do any parallel
DML (insert/update/delete) in any of these examples.

I did parallel queries -- selects -- none of which need parallel dml enables.

You do not need parallel DML enabled to create a table in parallel, see the link
in the above comment for how to achieve this.

If you want to do a parallel update, delete or insert, you will be enabling
parallel dml for your session. See
http://download-east.oracle.com/docs/cd/A81042_01/DOC/server.816/a76965/c22paral.htm#880
for details.

There is no way to see if PDML is enabled -- but it is perfectly safe to enable
it more then once so you can just enable it if you are not sure if it is enabled
or not.
Rating: 3
November 05, 2001
Reviewer: A reader
Tom, what I really mean in my question is parallel options
specified in the create table statment for later use of parallelism in select or
DML statement, not during the creation of table, not create as select.
create table test ... parallel(degree 4);
How does it works?
Works it as parallel hint? And what happens if table was created with parallel
clause and parallelism is disabled in session? Should I ever use parallel hint
to force oracle to use PDML?
Sorry for misunderstanding, english is not my native langvuage.
Followup:
If you:

create table tt ( owner, cnt ) parallel 5
as
select owner, count(*) from t group by owner;

It will create the table in paralle AND it will set the default degree of
parallelism for the table to 5.

It'll do both.  But this is NOT PDML, this is PDDL.

You really want to read the Oracle Server Concepts manual to get a better, full
understanding of this.  PDML, Parallel Query, PDDL -- they are all explained in
there with examples.

See
http://download-east.oracle.com/docs/cd/A81042_01/DOC/server.816/a76965/part_6.htm#435986
...
Rating: 4
The way to see if PDML is enabled November 05, 2001
Reviewer: Olaf from Germany
I can see this in PDML_ENABLED column in v$session.
Followup:
doh, you are right -- sorry about that
Rating: 4
Is multiple CPU required to enable parallel query? July 29, 2003
Reviewer: Tony from India
My server has only one CPU. Is it possible to do parallel query?
Followup:
can you run more then one program at a time on your computer?

if so, you can do parallel query.
Rating: 4
PQ not working October 20, 2003
Reviewer: A reader
Hi

I dont undestand why we cannot use PQ in our server

select name, value from v$parameter where name like '%parallel%';

NAME                                 VALUE
------------------------------------ ----------------------------------------
parallel_server                      FALSE
parallel_server_instances            1
recovery_parallelism                 0
fast_start_parallel_rollback         LOW
parallel_broadcast_enabled           FALSE
parallel_adaptive_multi_user         FALSE
parallel_threads_per_cpu             2
parallel_automatic_tuning            FALSE
optimizer_percent_parallel           0
parallel_min_percent                 0
parallel_min_servers                 0
parallel_max_servers                 12
parallel_instance_group
parallel_execution_message_size      2148

set autotrace trace exp

select /*+ parallel(a,4) */ * from GCIGT_INGRESOS_AF_TDE_RN a

Execution Plan
---------------------------------------------------------
  0    SELECT STATEMENT Optimizer=CHOOSE (Cost=54474 Card=15080420
Bytes=3800265840)
  1  0   PARTITION RANGE* (ALL) :Q84000
  2  1     TABLE ACCESS* (FULL) OF 'GCIGT_INGRESOS_AF_TDE_RN' (Cost=54474
Card=15080420 Bytes=3800265840) :Q84000
  1    PARALLEL_COMBINED_WITH_PARENT
  2    PARALLEL_TO_SERIAL
       SELECT /*+ Q84000 NO_EXPAND ROWID(A1) */
A1."IGINA_ANIO",A1."IGINA_COD_CLIENTE_S

set autotrace on
select /*+ parallel(a,4) */ * from GCIGT_INGRESOS_AF_TDE_RN a

select * from v$pq_sysstat;

STATISTIC                           VALUE
------------------------------ ----------
Servers Busy                            0
Servers Idle                            0
Servers Highwater                       0
Server Sessions                         0
Servers Started                         0
Servers Shutdown                        0
Servers Cleaned Up                      0
Queries Initiated                       0
DML Initiated                           0
DFO Trees                               0
Sessions Active                         0
Local Msgs Sent                         0
Distr Msgs Sent                         0
Local Msgs Recv'd                       0
Distr Msgs Recv'd                       0

Anything wrong with my setup?
Followup:
well, the query plan says "parallel"

did you actually "run" the query?  how about a cut and paste of:

set autotrace on
select /*+ parallel(a,4) */ COUNT(*) from GCIGT_INGRESOS_AF_TDE_RN a;
set autotrace off
select * from v$pq_sysstat;
Rating: 5
yes I did run October 20, 2003
Reviewer: A reader
Hi

I ran the query, the results of v$pq_sysstat I posted is after I ran the query!
Followup:
hows about that cut and paste for me.  cut and paste -- no editing!!!
Rating: 5
PDML October 20, 2003
Reviewer: shankar from Houston, TX
Tom, If I had to do PDML, does the table need to be a partitioned table? If I
have a table that is stiped across disks using RAID, why can't I do PDML on
this? Thanks.
Followup:
depends on the version.

9i does pdml without partitioning
8i did pdml with partitioning

RAID has nothing to do with it at our level.  We don't really know if you are on
raid or what.  it is not really relevant.
Rating: 5
performance issues with partitioning.... October 20, 2003
Reviewer: Sam Bernard from Salt Lake City, UT
Any known performance issues with partitioning of tables that one should know
before designing tables for partitioning? Is there any document or link that you
could guide me to learn about partitioning issues in relation to performance
would be greatly appreciated. Thanks.
Followup:
well, -- in both of my books I've written on this topic.  You need to UNDERSTAND
what is happening under the covers (the example I used in my last book was
someone who simply HASH partitioned an OLTP table and locally partitioned all
indexes.  the result -- the system did 8 TIMES the IO on this table.  They
didn't understand what they had just done, didn't design to use partitioning,
just turned it on and said "go fast".  that'll never fly)

You need to understand how the structures are implemented -- what you'll be
doing to the data.  Unless you do tons of full scans, partitioning probably
won't make you go "faster" -- but it can certainly make you go lots slower if
you don't design to use it.

You will want to read
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96520/toc.htm
just ctl-f for partition to see some of the relevant chapters.
Rating: 5
partition October 20, 2003
Reviewer: Reader
I was trying to create a paritioned table per your demo in your new book. I was
wondering why it is using the "regular" tablespace demo for temp segment instead
of the usual temp tablespace. Thanks.

SQL> create table b_hashed nologging
  2  partition by hash(object_id) partitions 10
  3  tablespace demo
  4  as
  5  select * from b;
select * from b
              *
ERROR at line 5:
ORA-01652: unable to extend temp segment by 640 in tablespace DEMO
Followup:
when you create a table oracle does this:

a) allocates an extent - this extent is marked as temporary
b) fills it
c) upon completion - it will turn the TEMPORARY extents into "real ones"

the reason it does this is that if the system fails for whatever reason during
a) or b) -- we don't need to do anything to recover.  upon system restart SMON
will simply clean up the temporary extents.

so, CREATE TABLE as select, CREATE INDEX and others will use temporary extents
while they are creating and then convert them over to real ones when done.

It is not "temp" like sort space "temp", it is temp as in "if we fail, get rid
of this junk, if we succeed, we'll just turn it into real stuff"
Rating: 5
pasting here October 20, 2003
Reviewer: A reader
Parameters are in previous post, cheers

sql>select table_name, num_rows, partitioned, degree from user_tables where
num_rows > 10000000;

TABLE_NAME                       NUM_ROWS PAR DEGREE
------------------------------ ---------- --- ----------
GCIGT_INGRESOS_AF_TDE_RN         15080420 YES          1
GCIGT_INGRESOS_TDE_RN            13471688 NO           1

sql>set autotrace trace exp
sql>col plus_plan_exp for a100
sql>set line 140
sql>select /*+ parallel(a,4) */ * from tgcigt10.GCIGT_INGRESOS_AF_TDE_RN a;

Execution Plan
----------------------------------------------------------
          0 SELECT STATEMENT Optimizer=CHOOSE (Cost=54474 Card=15080420
Bytes=3800265840)
          1 0  PARTITION RANGE* (ALL) :Q88000
          2 1  TABLE ACCESS* (FULL) OF 'GCIGT_INGRESOS_AF_TDE_RN' (Cost=54474
Card=15080420 Bytes=3800265840) :Q88000
          1    PARALLEL_COMBINED_WITH_PARENT
          2    PARALLEL_TO_SERIAL SELECT /*+ Q88000 NO_EXPAND ROWID(A1) */
               A1."IGINA_ANIO",A1."IGINA_COD_CLIENTE_S

sql>set autotrace trace exp stat
sql>select /*+ parallel(a,4) */ * from tgcigt10.GCIGT_INGRESOS_AF_TDE_RN a;

Execution Plan
----------------------------------------------------------
          0 SELECT STATEMENT Optimizer=CHOOSE (Cost=54474 Card=15080420
Bytes=3800265840)
          1 0  PARTITION RANGE* (ALL) :Q90000
          2 1  TABLE ACCESS* (FULL) OF 'GCIGT_INGRESOS_AF_TDE_RN' (Cost=54474
Card=15080420 Bytes=3800265840):Q90000
          1    PARALLEL_COMBINED_WITH_PARENT
          2    PARALLEL_TO_SERIAL SELECT /*+ Q90000 NO_EXPAND ROWID(A1) */
A1."IGINA_ANIO",A1."IGINA_COD_CLIENTE_S

Statistics
----------------------------------------------------------
         19  recursive calls
       1340  db block gets
    1154037  consistent gets
     566654  physical reads
        692  redo size
SP2-0642: Error interno SQL*Plus estado 1075, contexto 1:5:0
No es seguro continuar
   67390856  bytes received via SQL*Net from client
     607123  SQL*Net roundtrips to/from client
         26  sorts (memory)
          0  sorts (disk)
   15178027  rows processed

sql>select * from v$pq_sysstat;

STATISTIC                           VALUE
------------------------------ ----------
Servers Busy                            0
Servers Idle                            0
Servers Highwater                       0
Server Sessions                         0
Servers Started                         0
Servers Shutdown                        0
Servers Cleaned Up                      0
Queries Initiated                       0
DML Initiated                           0
DFO Trees                               0
Sessions Active                         0
Local Msgs Sent                         0
Distr Msgs Sent                         0
Local Msgs Recv'd                       0
Distr Msgs Recv'd                       0
Followup:
while this query is running -- query the various v$px views (eg: v$px_process)

if you don't see any, we'll continue here.

if you do, contact support and file a tar to see whats up with that v$ view not
getting filled in.
Rating: 5
there is no parallel process October 21, 2003
Reviewer: A reader
Hi

While I was running I checked at OS level as well and see if there are pxxx
processes.

ps -ef |grep gc00
oracle81  3730     1  0 23:15:20 ?        0:03 ora_smon_e81gc00
oracle81  3754     1  0 23:15:21 ?        0:00 ora_reco_e81gc00
oracle81 27569     1  0 09:19:43 ?        0:00 oraclee81gc00 (LOCAL=NO)
oracle81  3699     1  0 23:15:20 ?        0:00 ora_pmon_e81gc00
oracle81 23260     1  1 08:35:42 ?       36:54 oraclee81gc00 (LOCAL=NO)
oracle81  3704     1  0 23:15:20 ?        3:54 ora_lgwr_e81gc00
oracle81  4095  4093  0 23:18:26 ?        2:54 oraclee81gc00
(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))
oracle81 27571 27508  0 09:19:46 pts/11   0:00 grep gc00
oracle81  3701     1  0 23:15:20 ?        1:14 ora_dbw0_e81gc00
oracle81 27524     1  0 09:18:56 ?        0:00 oraclee81gc00 (LOCAL=NO)
oracle81 27472 27470  1 09:18:27 ?        0:12 oraclee81gc00
(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))
oracle81 27283     1  3 09:15:47 ?        1:31 oraclee81gc00 (LOCAL=NO)
oracle81 24583     1  0 08:57:37 ?        6:38 oraclee81gc00 (LOCAL=NO)
oracle81  3712     1  0 23:15:20 ?        0:26 ora_ckpt_e81gc00

None!

select * from v$px_process;

no rows selected

:-0
Followup:
should need to but can you alter that table "parallel" and try that.
Rating: 4
still no px process October 21, 2003
Reviewer: A reader
Hi, here is what I did

alter table tgcigt10.GCIGT_INGRESOS_AF_TDE_RN parallel 4;

select table_name, degree from dba_tables where degree > 1;

TABLE_NAME                     DEGREE
------------------------------ ----------
GCIGT_INGRESOS_AF_TDE_RN                4

set autotrace trace exp stat
select /*+ parallel(a,4) */ * from tgcigt10.GCIGT_INGRESOS_AF_TDE_RN a;

while running it in another session did this

select * from v$px_process;

no rows selected

ps -ef |grep gc00
oracle81  3730     1  0 23:15:20 ?        0:05 ora_smon_e81gc00
oracle81  3754     1  0 23:15:21 ?        0:00 ora_reco_e81gc00
oracle81 24074     1  0 13:29:09 ?        0:23 oraclee81gc00 (LOCAL=NO)
oracle81 23017     1  0 13:15:31 ?        2:48 oraclee81gc00 (LOCAL=NO)
oracle81  3699     1  0 23:15:20 ?        0:00 ora_pmon_e81gc00
oracle81  3704     1  0 23:15:20 ?        7:08 ora_lgwr_e81gc00
oracle81  4095  4093  0 23:18:26 ?        4:20 oraclee81gc00
(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))
oracle81 25253 25044  0 13:38:28 pts/2    0:00 grep gc00
oracle81 21238 21237  0 13:05:57 ?        0:00 oraclee81gc00
(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))
oracle81 15605     1  0 12:09:22 ?        0:23 oraclee81gc00 (LOCAL=NO)
oracle81  9230     1  5 10:52:08 ?       156:52 oraclee81gc00 (LOCAL=NO)
oracle81 25143 25142  1 13:36:20 ?        0:05 oraclee81gc00
(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))
oracle81  3158     1  0 10:00:05 ?        0:02 oraclee81gc00 (LOCAL=NO)
oracle81  3701     1  0 23:15:20 ?        2:33 ora_dbw0_e81gc00
oracle81 27524     1  0 09:18:56 ?        0:00 oraclee81gc00 (LOCAL=NO)
oracle81  3712     1  0 23:15:20 ?        0:48 ora_ckpt_e81gc00

!!!!! What´s is wrong?
Followup:
I'll have to ask you to contact support -- everything "looks ok", i see no
reason short of you queried v$parameter in the wrong database or something.

sorry -- don't see "whats wrong" from here.
Rating: 4
Dumb Question : what is hsecs October 21, 2003
Reviewer: A reader
Please forgive me for asking this dumb question.
What is hsecs in your example of timing
dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs'
);

Is it milli seconds.

Sometimes you divide it by hundred.
dbms_output.put_line( (dbms_utility.get_time-l_start)/100
);
What will that be . Just seconds ?
Followup:
hsecs = hundredths of seconds.

x/100 would be seconds.
Rating: 4
Table NOPARALLEL October 29, 2003
Reviewer: M S Reddy from USA
Tom,
  One quick question .Will the parallel hint work if the table is created as
NOPARALLEL.if yes then for what is CTAS noparallel useful.if we have the
parallel processing enabled on the server will a table be created automatically
as PARALLEL.

Thanks,
Reddy
Followup:
that just sets the default degree of parallelism, you can override that with
hints, yes.
Rating: 2
Parallel query server died unexpectedly. December 01, 2003
Reviewer: alex from uk
Tom

When I am running very large query for 3M records to be fetched having joins
with 4 tables those table are also very big 100m of data in each and are
partitoned but after some time I am getting following error message

Parallel query server died unexpectedly.

please tell me any soln.

thanks
Followup:
as you would with any internal error -- contact support and they'll help you
diagnose it.
Rating: 3
PDML December 30, 2003
Reviewer: A reader from NJ, USA
Tom,

Pls clarify the following :

1. Can I do a PDML on a table even its degree of parallelism at the table level
is 1 by using ALTER SESSION ENABLE PARALLEL DML
2. If I enable a PDML eg INSERT, do I need to specify the degree in both INSERT
and SELECT. If not, if I specify a degree of parallelism in SELECT alone say 4,
my understanding is that 8 PX slaves will be run ( 4 for SELECT and 4 for INSERT
- enabled automatically because PDML enabled). Is that correct?
3. Pls. clarify the following :
   a. CREATE TABLE AS SELECT...
   b. CREATE TABLE AS SELECT /*+PARALLEL(...)*/ ... Is this
      called PDDL?
   c. CREATE TABLE t DEGREE 4 AS SELECT ....
   d. CREATE TABLE t DEGREE 4 AS SELECT /*+ PARALLEL(...)*/
   e. INSERT INTO t SELECT ....
   f. INSERT INTO t SELECT /*+ PARALLEL(...)*/
   g. ALTER SESSION enable parallel DML
      INSERT INTO t SELECT /*+ PARALLEL(...) */

   In the above cases, which of the SELECTs/INSERTs puts data above the HWM and
the restrictions on them.

Kailash
Followup:
1) yes.
2)
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96520/tuningpe.htm#66511
and
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96520/tuningpe.htm#66531
the DOP would be 4.

3) suggest you read that entire chapter perhaps
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96520/tuningpe.htm#DWHSG021
it covers this all.
Rating: 3
PDDL??? December 30, 2003
Reviewer: A reader from NJ, USA
Tom,

I am getting confused with the terms. Pls clarify :

I have a DDL like the one below. You mention that it is called PDDL.

create table tt ( owner, cnt ) parallel 5
as
select owner, count(*) from t group by owner;

Can you tell me what this construct is called then :

create table tt ( owner, cnt )
as
select /*+PARALLEL(t,5) */ owner, count(*) from t group by owner;

Also pls clarify the following terms :

1. CTAS. Is it CREATE TABLE...AS SELECT ?
2. CTAS in parallel. Is it CREATE TABLE .. SELECT /*+PARALLEL(t,5) */...?
3. INSERT INTO .. SELECT
4. INSERT INTO .. SELECT in parallel
5. Direct load insert and Parallel Direct Load insert
6. pCTAS (Parallel CREATE TABLE AS)

Thanks
Followup:
things that start with CREATE are DDL.

That first one created a table serially, and that table has a default degree of
parallelism of 5.

the second could create the table in parallel and the table will have a default
degree of parallelism (no value set)

CTAS is create table as select

You need to read that chapter!  really, its all in there (as well as in the
concepts guide!)
Rating: 3
pCTAS December 30, 2003
Reviewer: A reader from NJ, USA
Tom,

Say you have a table T with DEGREE of parallelism set to 4. You enforce a
Parallel DML (e.g INSERT) in your session
ALTER SESSION ENABLE PARALLEL DML
INSERT INTO T SELECT ... FROM T1
1. In the above case
     a. Will only INSERT be done in parallel and SELECT in SERIAL since SELECT
does not have a degree of parallelism or
     b. Both INSERT and SELECT be done in PARALLEL using the degree of table T
or
     c. INSERT will be done in PARALLEL using degree of T and SELECT will also
be done in PARALLEL using degree of T1. Pls. clarify

2. When you say pCTAS do you mean
   CREATE TABLE a DEGREE 5
       AS SELECT ... FROM B
   or
   CREATE TABLE a
       AS SELECT /*+ PARALLEL(b,4) */ * FROM B ....
   My understanding is that in the first case the table A will be created with a
degree of 5, but the SELECT will be done in serial and in the second case A will
be created with a default degree of parallelism and the SELECT will be done in
parallel.

3. In Tim Gorman's book on Essential Oracle8i DW, he discusses the following :
   a. INSERT INTO .. SELECT
   b. INSERT INTO .. SELECT in parallel in Oracle7
   c. CREATE TABLE .. AS SELECT
   d. CREATE TABLE .. AS SELECT using parallel query
   e. Parallel CREATE TABLE .. AS SELECT
What is the difference between d and e

I read the documentation but I am confused. It would be really helpful if you
could clear my confusion
Followup:
from the link provided:
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96520/tuningpe.htm#66531
<quote>
Rules for Parallelizing INSERT ... SELECT

An INSERT ... SELECT statement parallelizes its INSERT and SELECT operations
independently, except for the DOP.

You can specify a parallel hint after the INSERT keyword in an INSERT ... SELECT
statement. Because the tables being queried are usually not the same as the
table being inserted into, the hint enables you to specify parallel directives
specifically for the insert operation.

You have the following ways to specify parallel directives for an INSERT ...
SELECT statement (assuming that PARALLEL DML mode is enabled):

    * SELECT parallel hints specified at the statement
    * Parallel clauses specified in the definition of tables being selected
    * INSERT parallel hint specified at the statement
    * Parallel clause specified in the definition of tables being inserted into

You can use the ALTER SESSION FORCE PARALLEL DML statement to override parallel
clauses for subsequent INSERT operations in a session. Parallel hints in insert
operations override the ALTER SESSION FORCE PARALLEL DML statement.

Decision to Parallelize

The following rule determines whether the INSERT operation should be
parallelized in an INSERT ... SELECT statement:

The INSERT operation will be parallelized if and only if at least one of the
following is true:

    * The PARALLEL hint is specified after the INSERT in the DML statement.
    * The table being inserted into (the reference object) has a PARALLEL
declaration specification.
    * An ALTER SESSION FORCE PARALLEL DML statement has been issued previously
during the session.

The decision to parallelize the INSERT operation is made independently of the
SELECT operation, and vice versa.

Degree of Parallelism

Once the decision to parallelize the SELECT or INSERT operation is made, one
parallel directive is picked for deciding the DOP of the whole statement, using
the following precedence rule Insert hint directive > Session> Parallel
declaration specification of the inserting table > Maximum query directive.
</quote>

the answer is (a).  there will be 4 slaves doing the insert as select -- each
fed from the same pq slave.  eg: if you:

ops$tkyte@ORA9IR2> alter table t parallel 4;

Table altered.

ops$tkyte@ORA9IR2> insert into t select * from big_table.big_table;

and looked at the running sql, you'd see 4 sessions running sql like this:

OPS$TKYTE(16,6611) ospid = 1020 command = 2 program =
oracle@tkyte-pc-isdn.us.oracle.com (P003) dedicated server=1020
Wednesday 09:07  Wednesday 09:07 last et = 5
INSERT /*+ APPEND */ INTO "OPS$TKYTE"."T"("ID","OWNER","OBJECT_N
AME","SUBOBJECT_NAME","OBJECT_ID","DATA_OBJECT_ID","OBJECT_TYPE"
,"CREATED","LAST_DDL_TIME","TIMESTAMP","STATUS","TEMPORARY","GEN
ERATED","SECONDARY") SELECT C0,C1,C2,C3,C4,C5,C6,C7,C8,C9,C10,C1
1,C12,C13 FROM :Q12000

If instead you did something like:

ops$tkyte@ORA9IR2> insert into t select /*+ parallel(t2 2) */ * from
big_table.big_table t2;

(note I have max pq servers at 5 in my setup....)  you would see sql like this:

OPS$TKYTE(10,12549) ospid = 1016 command = 2 program =
oracle@tkyte-pc-isdn.us.oracle.com (P001) dedicated server=1016
Wednesday 09:11  Wednesday 09:11 last et = 9
INSERT /*+ APPEND */ INTO "OPS$TKYTE"."T"("ID","OWNER","OBJECT_N
AME","SUBOBJECT_NAME","OBJECT_ID","DATA_OBJECT_ID","OBJECT_TYPE"
,"CREATED","LAST_DDL_TIME","TIMESTAMP","STATUS","TEMPORARY","GEN
ERATED","SECONDARY") SELECT C0,C1,C2,C3,C4,C5,C6,C7,C8,C9,C10,C1
1,C12,C13 FROM :Q17000
--------------------
OPS$TKYTE(15,47387) ospid = 1014 command = 2 program =
oracle@tkyte-pc-isdn.us.oracle.com (P000) dedicated server=1014
Wednesday 09:11  Wednesday 09:11 last et = 9
INSERT /*+ APPEND */ INTO "OPS$TKYTE"."T"("ID","OWNER","OBJECT_N
AME","SUBOBJECT_NAME","OBJECT_ID","DATA_OBJECT_ID","OBJECT_TYPE"
,"CREATED","LAST_DDL_TIME","TIMESTAMP","STATUS","TEMPORARY","GEN
ERATED","SECONDARY") SELECT C0,C1,C2,C3,C4,C5,C6,C7,C8,C9,C10,C1
1,C12,C13 FROM :Q17000
--------------------
OPS$TKYTE(16,6619) ospid = 1078 command = 3 program =
oracle@tkyte-pc-isdn.us.oracle.com (P004) dedicated server=1078
Wednesday 09:11  Wednesday 09:11 last et = 9
SELECT /*+ Q17000 NO_EXPAND ROWID(A1) */ A1."ID" C0,A1."OWNER" C
1,A1."OBJECT_NAME" C2,A1."SUBOBJECT_NAME" C3,A1."OBJECT_ID" C4,A
1."DATA_OBJECT_ID" C5,A1."OBJECT_TYPE" C6,A1."CREATED" C7,A1."LA
ST_DDL_TIME" C8,A1."TIMESTAMP" C9,A1."STATUS" C10,A1."TEMPORARY"
C11,A1."GENERATED" C12,A1."SECONDARY" C13 FROM "BIG_TABLE"."BIG
_TABLE" PX_GRANULE(0, BLOCK_RANGE, DYNAMIC)  A1
--------------------
OPS$TKYTE(19,8) ospid = 1018 command = 3 program =
oracle@tkyte-pc-isdn.us.oracle.com (P002) dedicated server=1018
Wednesday 09:11  Wednesday 09:11 last et = 9
SELECT /*+ Q17000 NO_EXPAND ROWID(A1) */ A1."ID" C0,A1."OWNER" C
1,A1."OBJECT_NAME" C2,A1."SUBOBJECT_NAME" C3,A1."OBJECT_ID" C4,A
1."DATA_OBJECT_ID" C5,A1."OBJECT_TYPE" C6,A1."CREATED" C7,A1."LA
ST_DDL_TIME" C8,A1."TIMESTAMP" C9,A1."STATUS" C10,A1."TEMPORARY"
C11,A1."GENERATED" C12,A1."SECONDARY" C13 FROM "BIG_TABLE"."BIG
_TABLE" PX_GRANULE(0, BLOCK_RANGE, DYNAMIC)  A1

It backed the DOP off to two for the insert, two for the select (since I can
only have 5) and did the query in parallel - each PQ slave feeding a different
insert process.

this sort of stuff is why I heavily promote that every single DBA/Developer on
the planet should have their own copy installed on their own machine.  In this
controlled environment -- it is very easy and fast to see what ACTUALLY takes
place!

2) you are correct on the first point.

Lets see on the second point:
ops$tkyte@ORA9IR2> create table a
  2  as
  3  select /*+ parallel(b,4) */ * from big_table.big_table b;

Table created.

ops$tkyte@ORA9IR2> select table_name, degree from user_tables where table_name =
'A';

TABLE_NAME                     DEGREE
------------------------------ ----------
A                                       1

A hint is not really part of the DDL, it will not affect the DOP setting for
the table itself, it applies to the query and the query alone

3) I've not read his book, i don't know what he might have been signifying there
-- it could have been your #2 example.

The first creates a "parallel" table as select (could be a parallel create table
as select), the second creates a table using parallel query.
Rating: 3
DOP December 31, 2003
Reviewer: A reader from NJ, USA
Tom,

Thanks much for your reply. I have the following questions :
1. If a degree of parallelism is not specified, does that mean it will use
degree of 1. If so, then what is meant by default degree of parallelism? How is
it determined?
2. In the Oracle link you had sent you about PX execution and in Tim Gorman's
book, he mentions that we can use the NONAPPEND insert in PARALLEL DML and here
is the quote from chapter 21 of the documentation :

<quote>
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.
</Quote>

I am not able to see any specific documentation on NOAPPEND.
My understanding so far has been that NOAPPEND insert performs a conventional
insert and a parallel DML is not applicable for conventional insert. Pls.
correct me if my understanding is wrong and clarify
  1. What NOAPPEND means and how it works in Parallel DML for both partitioned
and nonpartitioned tables and how HWM and table locks,constraints,triggers etc
are handled in that case
  2. Difference between NOAPPEND and APPEND hint in Parallel DML
  3. Under what situations would you use NOAPPEND hint

I would appreciate if you could throw some light on this
Followup:
1) not specified when?  where?  but anyway, if there is no DOP, then 1 is the
"dop" (eg: serial, single)

2) NOAPPEND is documented in the performance and tuning guide.  NOAPPEND uses
convention sql to insert, it'll use existing space on the freelists, it'll not
write directly to disk.  It'll be just like an insert in any application is
normally.  it cannot bypass undo and redo.

they tell you in that paragraph when you might use noappend.  "space
utilization"
Rating: 3
DOP December 31, 2003
Reviewer: A reader from NJ, USA
Tom,

Thanks much for your reply. I have the following questions :
1. If a degree of parallelism is not specified, does that mean it will use
degree of 1. If so, then what is meant by default degree of parallelism? How is
it determined?
2. In the Oracle link you had sent you about PX execution and in Tim Gorman's
book, he mentions that we can use the NONAPPEND insert in PARALLEL DML and here
is the quote from chapter 21 of the documentation :

<quote>
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.
</Quote>

I am not able to see any specific documentation on NOAPPEND.
My understanding so far has been that NOAPPEND insert performs a conventional
insert and a parallel DML is not applicable for conventional insert. Pls.
correct me if my understanding is wrong and clarify
  1. What NOAPPEND means and how it works in Parallel DML for both partitioned
and nonpartitioned tables and how HWM and table locks,constraints,triggers etc
are handled in that case
  2. Difference between NOAPPEND and APPEND hint in Parallel DML
  3. Under what situations would you use NOAPPEND hint

I would appreciate if you could throw some light on this
Rating: 5
Reader December 31, 2003
Reviewer: A reader
How large is your big_table.big_table
Followup:
didn't really matter here -- the DOP was set by me.

could be 1 row
could be 1,000,000 rows (it was)
could be 100,000,000,000 rows.....

I use this script (from my book Effective Oracle by Design) to set up this
"big_table" as big as I want it to be, you just pass it the number of rows you
want:

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;
Rating: 3
NOAPPEND December 31, 2003
Reviewer: A reader from NJ, USA
Tom,

You mentioned that NOAPPEND uses conventional SQL Insert. My understanding is
that conventional SQL does not support Parallel DML. If so how is Parallel DML
with NOAPPEND. Pls clarify

Thanks
Followup:
it isn't.  it can use parallel query to get the data, but the insert will be a
single insert path using conventional path.
Rating: 3
NOAPPEND December 31, 2003
Reviewer: A reader from NJ, USA
Tom,

Wishing you a wonderful and happy New Year 2004. You mentioned that NOAPPEND
will use a conventional path INSERT. What would happen if
1. Use ALTER SESSION ENABLE PARALLEL DML. Then use PARALLEL and NOAPPEND hints
in the query . Will this give an error or will it ignore the NOAPPEND error.
2. So it will be the same as a normal INSERT except that SELECT will be
performed in parallel. Pls confirm.

Thanks much
Followup:
it'll do noappend.  (you can try it!)

the query can be done in parallel.
the insert will not
Rating: 3
Contention January 04, 2004
Reviewer: A reader from NJ, USA
Tom,

1. If u specify ALTER SESSION PARALLEL DML and also use NOAPPEND hint, you
mention that it will use NOAPPEND hint and does not perform parallel DML and
only the SELECT will be performed in parallel if PARALLEL hint specified. Is my
understanding correct?
2. If a PARALLEL select is performed and the INSERT is done in serial, what
about FREELIST contention. Will FREELIST need to be increased to reduce
contention. Pls explain
Followup:
1) yes
2) you have only one inserting session.  no contention with yourself.
Rating: 5
DOP February 12, 2004
Reviewer: A reader from nyc
hi Tom,

when i use
ALTER SESSION FORCE PARALLEL DML parallel 12

select ...
from a,b,c   -- a,b,c are partiioned table
where ...

then 'alter session statment..' means the following (1) or (2)?  (when I open
top session tool from oracle, i ust see 12 sessions)

1)
select /*+ parallel(a,12) parallel(b,12) parallel(c,12) */
from a,b,c
from ...
where ...

2)
select /*+ parallel(a,i) parallel(b,j) parallel(c,k) */
from a,b,c
from ...
where ...

where i+j+k = 12

Thanks!
steve
Followup:
check out the link(s) above - we've documented how the DOP is computed.  In your
case, 12 is the number of parallel query processes you will try to use and
neither of 1) nor 2) would be true necessarily.
Rating: 4
parallel ddl March 18, 2004
Reviewer: sam from NJ USA
Tom,

I just want to be sure..

In the query below big_emp will be set with degree 4
& also create table in parallel ??

create table big_emp parallel (degree 4)
  as select * from emp;

What is a way to check this out ??
I am checking this by oracle process. Before & during execution. IS there any
alternate way of checking ?
Followup:
explain plan.... (if you don't have 9ir2, use your own query against the plan
table as dbms_xplan wont exist yet)

ops$tkyte@ORA10G> explain plan for create table big_emp parallel (degree 4)
  2  as
  3  select * from scott.emp;

Explained.

ops$tkyte@ORA10G> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
------------------------ --------------------------
Plan hash value: 2508419448

---------------------------------------------------------------------------------
------------------------ -------
| Id  | Operation              | Name     | Rows  | Bytes | Cost (%CPU)| Time
 |    TQ  |IN-OUT| PQ Di strib |
---------------------------------------------------------------------------------
------------------------ -------
|   0 | CREATE TABLE STATEMENT |          |    14 |   518 |     1   (0)|
00:00:01 |        |      |       |
|   1 |  PX COORDINATOR        |          |       |       |            |
 |        |      |       |
|   2 |   PX SEND QC (RANDOM)  | :TQ10000 |    14 |   518 |     1   (0)|
00:00:01 |  Q1,00 | P->S | QC (R AND)  |
|   3 |    LOAD AS SELECT      |          |       |       |            |
 |  Q1,00 | PCWP |       |
|   4 |     PX BLOCK ITERATOR  |          |    14 |   518 |     1   (0)|
00:00:01 |  Q1,00 | PCWC |       |
|   5 |      TABLE ACCESS FULL | EMP      |    14 |   518 |     1   (0)|
00:00:01 |  Q1,00 | PCWP |       |
---------------------------------------------------------------------------------
------------------------ -------

12 rows selected.

ops$tkyte@ORA10G>
Rating: 4
Why no PQ on FFS March 19, 2004
Reviewer: Rob from Pittsburgh, PA
Tom:

If I understand it correctly a Fast Full Scan (FFS) of an index reads all the
blocks of the index much like a FTS reads all of the blocks of a table. In your
first response to the original poster you imply that a FFS will not use Parallel
Query. Why is that? Also will a FTS of an IOT use PQ.

Thanks as always for your help.
Followup:
they asked using the way back machine -- version 8.0.5

IOT's do support PQ nowadays.

ops$tkyte@ORA9IR2> drop table t;

Table dropped.

ops$tkyte@ORA9IR2> create table t ( x int primary key, data char(80) )
organization index;

Table created.

ops$tkyte@ORA9IR2> alter table t parallel;

Table altered.

ops$tkyte@ORA9IR2> exec dbms_stats.set_table_stats( user, 'T', numrows =>
1000000, numblks => 100000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> set autotrace traceonly explain
ops$tkyte@ORA9IR2> select count(*) from t;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     SORT* (AGGREGATE)
                              :Q6000
   3    2       INDEX* (FAST FULL SCAN) OF 'SYS_IOT_TOP_35849' (UNIQUE) (Cost=2
Card=1000000)                  :Q6000

   2 PARALLEL_TO_SERIAL            SELECT /*+ PIV_SSF */ SYS_OP_MSR(COUNT(*)) F
                                   ROM (SELECT /*+ INDEX_RRS(A2 "SYS_IOT_TOP_35
                                   849") */ 0 FROM "T"  PX_GRANULE(0, BLOCK_RAN
                                   GE, DYNAMIC) A2) A1

   3 PARALLEL_COMBINED_WITH_PARENT

ops$tkyte@ORA9IR2>
March 19, 2004 Rakesh -- Thanks for the question regarding "How to improve this query", version 817
You Asked
Hi Tom

Recently i got a task of generating a very very complex query. My only aim
behind coding this query is to get the data appropriately.
I dont know anything about tuning queries & making them more effecient.

I have a table like this

desc work_table2
-----------------
 WORK_TABLE2_ID                            NOT NULL NUMBER
 APPLICATION_ID                                     NUMBER
 APPLICATION_VERSION                                NUMBER
 JOB_ID                                             NUMBER
 CUSTOMER_ID                                        NUMBER
 ATTRIBUTE_ID                                       NUMBER
 TEMPLATE_ID                                        NUMBER
 DISTRICT_ID                                        NUMBER
 SCHOOL_OID                                         VARCHAR2(32)
 PERSON_OID                                         VARCHAR2(32)
 CREATE_DATETIME                                    DATE
 SCHEMA_NAME                                        VARCHAR2(32)
 TABLE_NAME                                         VARCHAR2(32)
 COLUMN_NAME                                        VARCHAR2(32)
 ROW_ID                                             NUMBER
 VALUE                                              VARCHAR2(100)
 ACTION                                             VARCHAR2(15)
 CIA_VERSION                               NOT NULL NUMBER
 SUB_ROW_ID                                         NUMBER(9)

This Work_table2 is some sort of standby table where i get data to be inserted
into target tables.

Data in this table comes in column format which i
convert into a row. i.e if 1 row in the target table comprises of 10 columns
then i get 10 different rows for that in work_table2

 The important columns here are table_name, column_name, value which gives me
 the table in which i have to insert the values, the columns of the table & the
 requred values.

 I create views out of this table so i can directly insert into the target
tables from
 these views. The tables to insert into reside in another schema.

 Apart from these columns i also require the job_id, customer_id, action column
 which are part of the target tables i am  going to insert into but will not
come from the
 column_name column of the work_table2 but do come as separate columns in
work_table2

 For every row to be inserted into the table i take row_id as the separator and
 use it in my equi-join clause

 In some cases i get the same row_id for two different rows so i use sub_row_id
 to get individual rows.

 In some cases it is quite possible that i may not get data for all the columns
 of the target table. In that case i have to show null values for those columns.

 Here is the query i use to create the view. Actually i have created a view
generator
 PLSQL code which automatically creates views for all the tables.

Though this query gives me the desired output i dont know how appropriate it is.

CREATE OR REPLACE VIEW CDL_BELLPERIODMASTER
( OID, CUSTOMER_ID, ORGUNIT_OID, BELLPERIODSET_OID, PERIOD_NUMBER,
SHORT_LABEL, NAME, DESCRIPTION, BEGIN_TIME,
END_TIME, CREATEDBY_OID, CREATEDATE, UPDATEDATE,
ORIGINTYPECD_OID, OWNER_ORGUNIT_OID, CREATE_BY, UPDATE_BY,
UPDATE_DATE, YEAR_TAGGED, APPLICATION_VERSION, INACTIVESTATUS,
ISDELETED, JOB_ID, ACTION )
AS
SELECT OID.value,CUSTOMER_ID.value,ORGUNIT_OID.value,BELLPERIODSET_OID.value,
PERIOD_NUMBER.value,SHORT_LABEL.value,NAME.value,DESCRIPTION.value,
BEGIN_TIME.value,END_TIME.value,CREATEDBY_OID.value,CREATEDATE.value,
UPDATEDATE.value,ORIGINTYPECD_OID.value,OWNER_ORGUNIT_OID.value,
CREATE_BY.value,UPDATE_BY.value,UPDATE_DATE.value,
YEAR_TAGGED.value,APPLICATION_VERSION.value,INACTIVESTATUS.value,
ISDELETED.value,jobs.job_id, act.action
FROM
(SELECT value , row_id, job_id, sub_row_id FROM work_table2
WHERE table_name='WSF_BELLPERIODMASTER' and column_name ='OID')  OID,
(SELECT TO_NUMBER(VALUE) value  , row_id, job_id, sub_row_id FROM work_table2
WHERE table_name='WSF_BELLPERIODMASTER' and column_name ='CUSTOMER_ID')
CUSTOMER_ID,
(SELECT value , row_id, job_id, sub_row_id FROM work_table2
WHERE table_name='WSF_BELLPERIODMASTER' and column_name ='ORGUNIT_OID')
ORGUNIT_OID,
(SELECT value , row_id, job_id, sub_row_id FROM work_table2
WHERE table_name='WSF_BELLPERIODMASTER' and column_name ='BELLPERIODSET_OID')
BELLPERIODSET_OID,
(SELECT TO_NUMBER(VALUE) value  , row_id, job_id, sub_row_id FROM work_table2
WHERE table_name='WSF_BELLPERIODMASTER' and column_name ='PERIOD_NUMBER')
PERIOD_NUMBER,
(SELECT value , row_id, job_id, sub_row_id FROM work_table2 WHERE
table_name='WSF_BELLPERIODMASTER' and column_name ='SHORT_LABEL')  SHORT_LABEL,
(SELECT value , row_id, job_id, sub_row_id FROM work_table2 WHERE
table_name='WSF_BELLPERIODMASTER' and column_name ='NAME')  NAME,
(SELECT value , row_id, job_id, sub_row_id FROM work_table2
WHERE table_name='WSF_BELLPERIODMASTER' and column_name ='DESCRIPTION')
DESCRIPTION,
(SELECT value , row_id, job_id, sub_row_id FROM work_table2
WHERE table_name='WSF_BELLPERIODMASTER' and column_name ='BEGIN_TIME')
BEGIN_TIME,
(SELECT value , row_id, job_id, sub_row_id FROM work_table2
WHERE table_name='WSF_BELLPERIODMASTER' and column_name ='END_TIME')  END_TIME,
(SELECT value , row_id, job_id, sub_row_id FROM work_table2 WHERE
table_name='WSF_BELLPERIODMASTER' and column_name ='CREATEDBY_OID')
CREATEDBY_OID,
(SELECT TO_DATE(VALUE,'DD-MON-RRRR') value  , row_id, job_id, sub_row_id
FROM work_table2 WHERE table_name='WSF_BELLPERIODMASTER' and
column_name ='CREATEDATE')  CREATEDATE,
(SELECT TO_DATE(VALUE,'DD-MON-RRRR') value  , row_id, job_id, sub_row_id
FROM work_table2 WHERE table_name='WSF_BELLPERIODMASTER'
and column_name ='UPDATEDATE')  UPDATEDATE,
(SELECT value , row_id, job_id, sub_row_id FROM work_table2 WHERE
table_name='WSF_BELLPERIODMASTER' and column_name ='ORIGINTYPECD_OID')
ORIGINTYPECD_OID,
(SELECT value , row_id, job_id, sub_row_id FROM work_table2
WHERE table_name='WSF_BELLPERIODMASTER' and column_name ='OWNER_ORGUNIT_OID')
OWNER_ORGUNIT_OID,
(SELECT value , row_id, job_id, sub_row_id FROM work_table2 WHERE
table_name='WSF_BELLPERIODMASTER' and column_name ='CREATE_BY')  CREATE_BY,
(SELECT value , row_id, job_id, sub_row_id FROM work_table2
WHERE table_name='WSF_BELLPERIODMASTER' and column_name ='UPDATE_BY')
UPDATE_BY,
(SELECT TO_DATE(VALUE,'DD-MON-RRRR') value  , row_id, job_id, sub_row_id
FROM work_table2 WHERE table_name='WSF_BELLPERIODMASTER' and
column_name ='UPDATE_DATE')  UPDATE_DATE,
(SELECT value , row_id, job_id, sub_row_id FROM work_table2
WHERE table_name='WSF_BELLPERIODMASTER' and column_name ='YEAR_TAGGED')
YEAR_TAGGED,
(SELECT value , row_id, job_id, sub_row_id FROM work_table2
WHERE table_name='WSF_BELLPERIODMASTER' and column_name ='APPLICATION_VERSION')
APPLICATION_VERSION,
(SELECT value , row_id, job_id, sub_row_id FROM work_table2
WHERE table_name='WSF_BELLPERIODMASTER' and column_name ='INACTIVESTATUS')
INACTIVESTATUS,
(SELECT value , row_id, job_id, sub_row_id FROM work_table2
WHERE table_name='WSF_BELLPERIODMASTER' and column_name ='ISDELETED')
ISDELETED,
(SELECT decode(upper(ACTION),'W','U',ACTION) action ,row_id, job_id, sub_row_id
FROM work_table2 WHERE table_name='WSF_BELLPERIODMASTER' AND column_name='OID' )
ACT,
(SELECT row_id , job_id, sub_row_id  FROM work_table2
WHERE table_name='WSF_BELLPERIODMASTER' AND column_name='OID' ) JOBS
where
OID.job_id=CUSTOMER_ID.job_id(+)  and
OID.job_id=ORGUNIT_OID.job_id(+)  and
OID.job_id=BELLPERIODSET_OID.job_id(+)  and
OID.job_id=PERIOD_NUMBER.job_id(+)  and
OID.job_id=SHORT_LABEL.job_id(+)  and
OID.job_id=NAME.job_id(+)  and
OID.job_id=DESCRIPTION.job_id(+)  and
OID.job_id=BEGIN_TIME.job_id(+)  and
OID.job_id=END_TIME.job_id(+)  and
OID.job_id=CREATEDBY_OID.job_id(+)  and
OID.job_id=CREATEDATE.job_id(+)  and
OID.job_id=UPDATEDATE.job_id(+)  and
OID.job_id=ORIGINTYPECD_OID.job_id(+)  and
OID.job_id=OWNER_ORGUNIT_OID.job_id(+)  and
OID.job_id=CREATE_BY.job_id(+)  and
OID.job_id=UPDATE_BY.job_id(+)  and
OID.job_id=UPDATE_DATE.job_id(+)  and
OID.job_id=YEAR_TAGGED.job_id(+)  and
OID.job_id=APPLICATION_VERSION.job_id(+)  and
OID.job_id=INACTIVESTATUS.job_id(+)  and
OID.job_id=ISDELETED.job_id(+)  and
OID.job_id= ACT.job_id(+)  and
OID.job_id = JOBS.job_id(+)  and
OID.row_id=CUSTOMER_ID.row_id(+)   and
OID.row_id=ORGUNIT_OID.row_id(+)   and
OID.row_id=BELLPERIODSET_OID.row_id(+)   and
OID.row_id=PERIOD_NUMBER.row_id(+)   and
OID.row_id=SHORT_LABEL.row_id(+)   and
OID.row_id=NAME.row_id(+)   and
OID.row_id=DESCRIPTION.row_id(+)   and
OID.row_id=BEGIN_TIME.row_id(+)   and
OID.row_id=END_TIME.row_id(+)   and
OID.row_id=CREATEDBY_OID.row_id(+)   and
OID.row_id=CREATEDATE.row_id(+)   and
OID.row_id=UPDATEDATE.row_id(+)   and
OID.row_id=ORIGINTYPECD_OID.row_id(+)   and
OID.row_id=OWNER_ORGUNIT_OID.row_id(+)   and
OID.row_id=CREATE_BY.row_id(+)   and
OID.row_id=UPDATE_BY.row_id(+)   and
OID.row_id=UPDATE_DATE.row_id(+)   and
OID.row_id=YEAR_TAGGED.row_id(+)   and
OID.row_id=APPLICATION_VERSION.row_id(+)   and
OID.row_id=INACTIVESTATUS.row_id(+)   and
OID.row_id=ISDELETED.row_id(+)   and
OID.row_id= ACT.row_id(+)  and
OID.row_id = JOBS.row_id(+)  and
OID.sub_row_id=CUSTOMER_ID.sub_row_id(+)   and
OID.sub_row_id=ORGUNIT_OID.sub_row_id(+)   and
OID.sub_row_id=BELLPERIODSET_OID.sub_row_id(+)   and
OID.sub_row_id=PERIOD_NUMBER.sub_row_id(+)   and
OID.sub_row_id=SHORT_LABEL.sub_row_id(+)   and
OID.sub_row_id=NAME.sub_row_id(+)   and
OID.sub_row_id=DESCRIPTION.sub_row_id(+)   and
OID.sub_row_id=BEGIN_TIME.sub_row_id(+)   and
OID.sub_row_id=END_TIME.sub_row_id(+)   and
OID.sub_row_id=CREATEDBY_OID.sub_row_id(+)   and
OID.sub_row_id=CREATEDATE.sub_row_id(+)   and
OID.sub_row_id=UPDATEDATE.sub_row_id(+)   and
OID.sub_row_id=ORIGINTYPECD_OID.sub_row_id(+)   and
OID.sub_row_id=OWNER_ORGUNIT_OID.sub_row_id(+)   and
OID.sub_row_id=CREATE_BY.sub_row_id(+)   and
OID.sub_row_id=UPDATE_BY.sub_row_id(+)   and
OID.sub_row_id=UPDATE_DATE.sub_row_id(+)   and
OID.sub_row_id=YEAR_TAGGED.sub_row_id(+)   and
OID.sub_row_id=APPLICATION_VERSION.sub_row_id(+)   and
OID.sub_row_id=INACTIVESTATUS.sub_row_id(+)   and
OID.sub_row_id=ISDELETED.sub_row_id(+)   and
OID.sub_row_id= ACT.sub_row_id(+)  and
OID.sub_row_id = JOBS.sub_row_id(+)

I dont feel very comfortable with this huge join condition though it gives me
the desired output. I am scared what happens when the quantum of data increases.
Currently i am working with only limited data & it works.

My question how appropriate the above query is. Could you let me know how can i
improve on this.

Your suggestion will really help me a lot

Regards
Rakesh
and we said...
this *looks* like a simple pivot.  assuming job_id, row_id, sub_row_id are
really a primary key (not clear at all, really big stuff up there), I believe
this is equivalent -- we just need to pivot the ugly data into rows and columns:

select max( decode( column_name, 'OID', value ) ) oid,
       max( decode( column_name, 'CUSTOMER_ID', value ) ) CUSTOMER_ID,
       .....
       max( decode( column_name, 'ISDELETED', value ) ) isdeleted,
       max( decode( column_name, 'OID', decode(upper(ACTION),'W','U',ACTION) ) )
action,
       max( decode( column_name, 'OID', job_id ) ) job_id
  from work_table2
 where table_name = 'WSF_BELLPERIODMASTER'
 group by job_id, row_id, sub_row_id
/
March 19, 2004 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
Rating: 4
March 18, 2004
Reviewer: reader
HI TOM,

I am the beginner for sql turning.
I have a couple os of  questions.

1.QUESTION 1
---------------
Two tables

T1(K1,K2,F1,F2...)

T2(K1,K2,F1,F2...)

K1 AND K2 are the KEYS OF TABLE T1 and T2
I have a query like this

SELECT *
FROM
T1,
T2
WHERE
T1.K1=T2.K1(+)
AND
T1.K2=T2.K2(+)
AND
T2.F1 >4
;
The question is "Is that necessary to put index on T2.F1 to increase the
perfmance ?"

a table T3(F1,F2,F3...)

SELECT
SUBSTR(TO_CHAR(T3.F1,'000'),2,3),
COUNT(F2),
SUM(F3)
FROM T3
GROUP BY
SUBSTR(TO_CHAR(T3.F1,'000'),2,3);

QUESTION2
-----------------
Is that necessary like this in the group by
"
GROUP BY
SUBSTR(TO_CHAR(T3.F1,'000'),2,3)
"
instead of

"
GROUP BY
T3.F1
"
WHAT IS different btween them ?

QUESTION3 IS
----------------
What is the relationship btween 'index' and 'group by' ?

For the above case put the index on f1 can increase performance ? if so INDEX ON
FUNCTION SUBSTR(TO_CHAR(T3.F1,'000'),2,3) can be better?

Thank you very much in advance!
Followup:
1) the question should be "why did I outer join to T2 in the first place"

think about it.  why did you outer join to t2?  tell me if it makes sense.

2) you select to_char( .... ), that is what you must group by.  t3.f1 isn't what
you selected, it isn't what you can group by.

the difference is one query will actually execute - and the other will not
(cannot, doesn't make sense)

3) an index is a data structure.  group by is an operation.  there is not any
"relationship" between them whatsoever.

indexes can increase performance
indexes can (massively) decrease performance
indexes can have no impact whatsoever.

I can give examples of data whereby the answer to all of your index questions is
"yes", or "no" or "maybe".
Rating: 5
March 19, 2004
Reviewer: reader
Hi TOM,

Thank you for the perfect answers.

For the question 1

Is that you means the outjoin is useless

SELECT *
FROM
T1,
T2
WHERE
T1.K1=T2.K1(+)
AND
T1.K2=T2.K2(+)
AND
T2.F1 >4
;

my further question is  how the following can impact the performance  ?

SELECT *
FROM
T1,
T2
WHERE
T1.K1=T2.K1(+)
AND
T1.K2=T2.K2(+)
AND
T2.F1 >4
;

Is that outjoin on the k1 amd k2 will make the full scan for the t2 (the
parimary key will be useless here ?) ?

Thank you !
Followup:
it is not just that the primary key is or is not useless (indexes are not even
part of the equation here yet -- who knows if that should full scan or not.  its
all about the size and spread of the data), it is that who ever wrote the query
didn't truly understand what they were asking for if they used an outer join.

The outer join can preclude some access paths from consideration (the RBO was
massively impacted by this, the CBO much less so).
March 20, 2004 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://download-west.oracle.com/docs/cd/A91202_01/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)
Rating: 5
What about 'with' clause? February 17, 2004
Reviewer: Steve from Phoenix, AZ USA
Can dynamic sampling be applied to the temporary table that is created when you
use a query inside a 'with' clause?  The times that I've used a 'with' clause,
the explain has always shown that a temporary table is created prior to running
the rest of my query.  (9.2.0.4)
Followup:
the statistics in this case come from the UNDERLYING tables.

It is a chicken and egg problem -- the plan is fixed at parse time -- *before*
the "with" subquery would be materialized.  hence the dynamic sampling could
have no effect.

so, no dynamic sampling on the "materialized temp table" but dynamic sampling on
the objects that make up the temp table.
Rating: 5
Very impressive February 18, 2004
Reviewer: Nasir from Houston, Texas, USA
The term 'Tables' is so deep now. I am glad Oracle does not have term 'Chairs'
like 'Tables'
:)
Rating: 5
More about 'with' clause February 18, 2004
Reviewer: Steve from Phoenix, AZ USA
After seeing your response and looking at other related posts, I've come to the
conclusion that the way I'm using the with clause is a bad idea.  I'm using it
to retrieve a variable set of key values from a collection built by a packaged
function and using the results in several IN clauses in the main query.

Since the optimizer has no way of knowing how big that set of values is, it will
lean toward doing a hash join with full table scans (in my case, a bad choice
for an 80 million row table).  This issue has been discussed in a number of
posts on this site.

It seems that a much better choice for this kind of situation is to populate a
global temporary table and join to that table in my main query allowing the
DYNAMIC_SAMPLING hint to figure out how best to optimize it.

Am I on the right track here?

Another question:  if the GTT is used in a OLTP environment, how does the fact
that the GTT may contain rows for multiple users at any point in time affect the
statistics created by DYNAMIC_SAMPLING?  Does it somehow know to look at only
the portion of the GTT that belongs to the current user?

By the way, I would like to say that after referencing this site extensively in
the last few months (and posting for the first time yesterday), I have found it
to be an extremely valuable source of accurate information and extensive
pertinent examples.  I commend you for having the patience and willingness to
spend the time required to do it and Oracle for providing a venue for it.
Followup:
Actually..... this is an excellent opportunity for a discussion on the
CARDINALITY hint (not quite well documented).

It might just be best for you to say "about how many rows" will be coming out of
that plsql procedure -- you should have some sort of idea "about how many" will
be returned (does not have to be exact).

We'll take a look at how to do this, first a little setup:

ops$tkyte@ORA920PC> create or replace type str2tblType as table of varchar2(30);
  2  /

Type created.

ops$tkyte@ORA920PC> create or replace
  2  function str2tbl( p_str in varchar2, p_delim in varchar2 default ',' )
  3  return str2tblType
  4  as
  5      l_str      long default p_str || p_delim;
  6      l_n        number;
  7          l_data     str2tblType := str2tbltype();
  8  begin
  9          dbms_application_info.set_client_info( userenv('client_info')+1 );
 10      loop
 11          l_n := instr( l_str, p_delim );
 12          exit when (nvl(l_n,0) = 0);
 13                  l_data.extend;
 14                  l_data(l_data.count) := (
ltrim(rtrim(substr(l_str,1,l_n-1))) );
 15          l_str := substr( l_str, l_n+1 );
 16      end loop;
 17      return l_data;
 18  end;
 19  /

Function created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> drop table emp;

Table dropped.

ops$tkyte@ORA920PC> create table emp
  2  as
  3  select object_name ename, max(object_id) empno, max(object_type) ot,
max(created) created,
  4         rpad( '*', 80, '*') data
  5    from all_objects
  6   group by object_name;

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> alter table emp add constraint emp_pk primary key(empno)
  2  /

Table altered.

ops$tkyte@ORA920PC> create index ename_idx on emp(ename)
  2  /

Index created.

ops$tkyte@ORA920PC> analyze table emp compute statistics for table for all
indexes for all indexed columns
  2  /

Table analyzed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> variable in_list varchar2(255)
ops$tkyte@ORA920PC> exec :in_list := 'DBMS_PIPE,DBMS_OUTPUT,UTL_FILE';

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> set autotrace traceonly explain
ops$tkyte@ORA920PC> select *
  2    from TABLE(cast( str2tbl( :in_list ) as str2tblType) ) t
  3  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   COLLECTION ITERATOR (PICKLER FETCH) OF 'STR2TBL'

given that, we can see that we can "select" from a plsql function.  We'd be
interested in the cost/card information, so...

ops$tkyte@ORA920PC> select /*+ FIRST_ROWS */ *
  2    from TABLE(cast( str2tbl( :in_list ) as str2tblType) ) t
  3  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=11 Card=8168)
   1    0   COLLECTION ITERATOR (PICKLER FETCH) OF 'STR2TBL'

8,168 -- thats the magic number.  that's the magic number of rows the
optimizer uses against gtt's and this "trick".  That's a big number -- probably
many times larger than your reality.

but what if you knew that "in general" your routine returned "about 10-20" rows.
 Is there a way to tell the optimizer this information?  Yes..  Consider:

ops$tkyte@ORA920PC> select /*+ cardinality(t 10 ) */ *
  2    from TABLE(cast( str2tbl( :in_list ) as str2tblType) ) t
  3  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=11 Card=10)
   1    0   COLLECTION ITERATOR (PICKLER FETCH) OF 'STR2TBL'

Now, the optimizer thinks "10" not "8,168".  But...

ops$tkyte@ORA920PC> select *
  2    from emp
  3   where ename in ( select /*+ cardinality(t 10 ) */ *
  4                      from TABLE(cast( str2tbl( :in_list ) as str2tblType) )
t
  5                  )
  6  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=306920 Card=1 Bytes=88)
   1    0   NESTED LOOPS (SEMI) (Cost=306920 Card=1 Bytes=88)
   2    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=53 Card=18051 Bytes=1588488)
   3    1     COLLECTION ITERATOR (PICKLER FETCH) OF 'STR2TBL'

it didn't "work" in a more complex query  (we'd expect "index emp" if it did)
-- that is because the card was not attached to step 3 (as it was not attached
to step 1 above).  In fact, we'd find this plan to be "unappealing" for more
than just the bad card:

ops$tkyte@ORA920PC> set autotrace traceonly
ops$tkyte@ORA920PC> select *
  2    from emp
  3   where ename in ( select /*+ cardinality(t 10 ) */ *
  4                      from TABLE(cast( str2tbl( :in_list ) as str2tblType) )
t
  5                  )
  6  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=306920 Card=1 Bytes=88)
   1    0   NESTED LOOPS (SEMI) (Cost=306920 Card=1 Bytes=88)
   2    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=53 Card=18051 Bytes=1588488)
   3    1     COLLECTION ITERATOR (PICKLER FETCH) OF 'STR2TBL'

Statistics
----------------------------------------------------------
         23  recursive calls
          0  db block gets
        341  consistent gets
          0  physical reads
          0  redo size
        768  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          5  sorts (memory)
          0  sorts (disk)
          3  rows processed

ops$tkyte@ORA920PC> set autotrace off
ops$tkyte@ORA920PC> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
18051

hey -- our function (i counted the calls using dbms_application_info) was
called 18,050 times -- funny:

ops$tkyte@ORA920PC> select count(*) from emp;

  COUNT(*)
----------
     18051

thats the number of rows in the table -- the function was called over and
over.  The solution is to use rownum (order by, union, interect, group by, etc
will have the same effect) to have the subquery "materialized"

ops$tkyte@ORA920PC> set autotrace traceonly
ops$tkyte@ORA920PC> select *
  2    from emp
  3   where ename in ( select /*+ cardinality(t 10 ) */ *
  4                 from TABLE(cast( str2tbl( :in_list ) as str2tblType) ) t
  5                                     where rownum >= 0
  6                  )
  7  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=62 Card=10 Bytes=1050)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=2 Card=1 Bytes=88)
   2    1     NESTED LOOPS (Cost=62 Card=10 Bytes=1050)
   3    2       VIEW OF 'VW_NSO_1' (Cost=17 Card=10 Bytes=170)
   4    3         SORT (UNIQUE)
   5    4           COUNT
   6    5             FILTER
   7    6               COLLECTION ITERATOR (PICKLER FETCH) OF 'STR2TBL'
   8    2       INDEX (RANGE SCAN) OF 'ENAME_IDX' (NON-UNIQUE) (Cost=1 Card=1)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         15  consistent gets
          0  physical reads
          0  redo size
        768  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)
          3  rows processed

ops$tkyte@ORA920PC> set autotrace off
ops$tkyte@ORA920PC> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
1

Now we

a) have the card in the plan
b) use the index as expected
c) as a bonus, have our function called once per query -- not per row

sooo, long story short

a) yes, the insertion of the procedure into a gtt and then dynamic sampling on
the gtt will work

b) but, you might be able to avoid those steps with the cardinality hint (one of
the good hints -- gives the optimizer INFORMATION, does not tell it what to do)

Note that cardinality is 9ir1 and above and dynamic sampling is 9ir2 and above.
Rating: 5
Excellent response February 19, 2004
Reviewer: Steve from Phoenix, AZ USA
I appreciate your very detailed and helpful response.  I wasn't aware of the
cardinality hint, so that was a great help.  I'm not sure which of the two
options (gtt or subquery) will be better in this particular query, so I'll try
out both and see how they work.

My subquery will return one row in the majority of cases, but could return
hundreds of rows in other cases.  The gtt option would give the optimizer the
most accurate counts to work with, but it seems that the cardinality estimate
would be close enough to get it to make the right choice.

Thanks again for all the help.
Rating: 5
Even more about 'with' clause February 19, 2004
Reviewer: Steve from Phoenix, AZ USA
I tried using the cardinality hint that you suggested and
found that it worked great when I use my with clause query
only once in the main query.  However, when I use it twice
(as a subquery in two unioned selects), it no longer
recognizes the hint.

I think this is similar to the example you provided which
did not use the 'where rownum >= 0'.  The two queries were
merged together and the table alias t was lost, so the hint
was invalid and ignored.  With the rownum clause, the
queries were not merged and the alias stayed valid.

In my case, the subquery is being used twice in the main
query, so it is stripped out of the body of the query and
processed once at the beginning of the process to create a
materialized temp table which is then used in the body of
the query.  In the process of all that, the alias appears
to be getting lost once again and the hint becomes invalid.
I tried putting the hint in several places in the query
hoping to find a workaround, but it hasn't worked so far.

Since it's getting late, I'm hoping I can get away with
just including the two queries and their plans, but if
that's not enough, I can put together a better example
tomorrow.  Thanks in advance for your help.

Less complex query and plan:

WITH property_list_subquery AS
(SELECT column_value property_id FROM TABLE(CAST(
   ds_resstat_table_pkg.load_property_table('1', '98125', '~', 'S')
             AS ds_resstat_resort_table_type)) )
SELECT
     'A'  column1
    ,1    curr_ctr
     FROM pm_resort_s
    WHERE resort IN ( SELECT /*+ cardinality(t 5 ) */ * FROM
property_list_subquery  t where rownum >= 0 )

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=14 Card=1 Bytes=18)
   1    0   HASH JOIN (SEMI) (Cost=14 Card=1 Bytes=18)
   2    1     INDEX (FAST FULL SCAN) OF 'RESORT_PK' (UNIQUE) (Cost=2 Card=5022
Bytes=30132)
   3    1     VIEW OF 'VW_NSO_1' (Cost=11 Card=5 Bytes=60)
   4    3       COUNT
   5    4         FILTER
   6    5           COLLECTION ITERATOR (PICKLER FETCH) OF 'LOAD_PROPERTY_TABLE'

More complex query and plan:

WITH property_list_subquery AS
(SELECT column_value property_id FROM TABLE(CAST(
   ds_resstat_table_pkg.load_property_table('1', '98125', '~', 'S')
             AS ds_resstat_resort_table_type)) )
SELECT
     'A'  column1
    ,1    curr_ctr
     FROM pm_resort_s
    WHERE resort IN ( SELECT /*+ cardinality(t 5 ) */ * FROM
property_list_subquery  t where rownum >= 0 )
UNION
SELECT
     'B'  column1
    ,2    curr_ctr
     FROM pm_resort_s
    WHERE resort IN ( SELECT /*+ cardinality(t 5 ) */ * FROM
property_list_subquery  t where rownum >= 0 )

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=78 Card=31004 Bytes=558072)
   1    2     RECURSIVE EXECUTION OF 'SYS_LE_2_0'
   2    0   TEMP TABLE TRANSFORMATION
   3    2     SORT (UNIQUE) (Cost=78 Card=31004 Bytes=558072)
   4    3       UNION-ALL
   5    4         NESTED LOOPS (Cost=3 Card=15502 Bytes=279036)
   6    5           VIEW OF 'VW_NSO_2' (Cost=3 Card=16360 Bytes=196320)
   7    6             COUNT
   8    7               FILTER
   9    8                 VIEW (Cost=3 Card=16360 Bytes=196320)
  10    9                   TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D666D_D288A10B'
(Cost=3 Card=16360 Bytes=196320)
  11    5           INDEX (UNIQUE SCAN) OF 'RESORT_PK' (UNIQUE)
  12    4         NESTED LOOPS (Cost=3 Card=15502 Bytes=279036)
  13   12           VIEW OF 'VW_NSO_1' (Cost=3 Card=16360 Bytes=196320)
  14   13             COUNT
  15   14               FILTER
  16   15                 VIEW (Cost=3 Card=16360 Bytes=196320)
  17   16                   TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D666D_D288A10B'
(Cost=3 Card=16360 Bytes=196320)
  18   12           INDEX (UNIQUE SCAN) OF 'RESORT_PK' (UNIQUE)
Rating: 5
A link from "collection inlist" searches to here would be useful March 19, 2004
Reviewer: Bob Lyon from Houston
Tom,

FWIW, you are one impressive guru!

Showing how to "tune" collections as inlists was very useful, but I stumbled
onto this thread more or less by accident.  You might consider putting links to
this thread in the major threads found by searching for "collection inlist".

I've played with your example on several databases, and the "8168" default
cardinality appears to be based on the database block size.

If there is any Oracle documentation anywhere on the CARDINALITY hint, I
couldn't find it.

What about an example of doing this in 10g which is supposed to natively support
IN ( collection ). Sorry, I'm on windoze servers and 10g is not ready for us
yet, or I'd try it myself.  Will a CARDINALITY hint still be needed?

Whatever Larry pays you, its not enough...
Followup:
well, lets see -- it is not "in (collection) it is "member of collection" and
would look like this:

ops$tkyte@ORA10G> create table t
  2  as
  3  select * from all_objects
  4  /

Table created.

ops$tkyte@ORA10G> alter table t add constraint t_pk primary key(object_id);

Table altered.

ops$tkyte@ORA10G> analyze table t compute statistics for table for all indexes
for all indexed  2  columns;

Table analyzed.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> create or replace type ArrayType as table of Number
  2  /

Type created.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> @trace
ops$tkyte@ORA10G> alter session set events '10046 trace name context forever,
level 12';

Session altered.

ops$tkyte@ORA10G> variable x refcursor;
ops$tkyte@ORA10G> variable y refcursor;
ops$tkyte@ORA10G> declare
  2      l_data arrayType := arrayType(1,2,3,5,6);
  3  begin
  4      open :x for
  5      select object_name, owner
  6        from t
  7       where object_id member of l_data;
  8
  9          open :y for
 10          select object_name, owner
 11            from t
 12           where object_id in ( select /*+ cardinality( t 10 ) */ * from
TABLE(l_data) T ); 13  end;
 14  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> print x

OBJECT_NAME                    OWNER
------------------------------ ------------------------------
I_OBJ#                         SYS
C_TS#                          SYS
CLU$                           SYS
C_OBJ#                         SYS

ops$tkyte@ORA10G> print y

OBJECT_NAME                    OWNER
------------------------------ ------------------------------
C_OBJ#                         SYS
I_OBJ#                         SYS
CLU$                           SYS
C_TS#                          SYS

member of is not looking index friendly at this point -- even when hinted to use
the index, it fast full scans the index -- not range scan:

SELECT OBJECT_NAME, OWNER
FROM
 T WHERE OBJECT_ID MEMBER OF :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          6          0           0
Fetch        1      0.08       0.11          4        654          0           4
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.08       0.11          4        660          0           4

Rows     Row Source Operation
-------  ---------------------------------------------------
      4  TABLE ACCESS FULL T (cr=654 pr=4 pw=0 time=110229 us)
********************************************************************************

SELECT OBJECT_NAME, OWNER
FROM
 T WHERE OBJECT_ID IN ( SELECT /*+ cardinality( t 10 ) */ * FROM TABLE(:B1 )
  T )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.01       0.03          0          0          0           0
Fetch        1      0.00       0.00          0         11          0           4
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.01       0.03          0         11          0           4

Rows     Row Source Operation
-------  ---------------------------------------------------
      4  NESTED LOOPS  (cr=11 pr=0 pw=0 time=229 us)
      5   SORT UNIQUE (cr=0 pr=0 pw=0 time=101 us)
      5    COLLECTION ITERATOR PICKLER FETCH (cr=0 pr=0 pw=0 time=32 us)
      4   TABLE ACCESS BY INDEX ROWID OBJ#(49694) (cr=11 pr=0 pw=0 time=111 us)
      4    INDEX UNIQUE SCAN OBJ#(49695) (cr=7 pr=0 pw=0 time=70 us)(object id
49695)
March 20, 2004 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.
Rating: 5
Converting many CHAR columns to VARCHAR columns February 17, 2004
Reviewer: Sha from USA
Hi Tom,
I need to convert many char columns to varchar columns.

We have about 150 tables with appox 2 columns of type char and 500,000 rows
each. I need to convert all columns of datatype char to varchar2.

Please suggest the best way to do this.

Regards,
Sha
Followup:
you'll have to

a) alter the tables to change the types
b) update each of the tables setting column = rtrim(column) to remove the
trailing blanks.

or, rebuild them all.
Rating: 5
Table update slow February 18, 2004
Reviewer: Sha from USA
Hi Tom,
In our schema we have constraints,Indexes and Master Details relationships. I
need to update the CHAR columns to VARCHAR datatype and later RTrim the new
VARCHAR.

e.g
Lets say TMaster has 3 columns A, B and X, say X is of datatype CHAR(10) and
TMASTER has indexs multiple indexs on columns including index on X.

TMaster( A,B,X char(10) )

TDETAIL has 3 columns P,Q and Y, say Y is on datatype CHAR(10) and TDETAIL has
indexs multiple indexs on columns including index on Y.

X is primary key in TMASTER and Y is foreign key in TDETAIL.

TDETAIL (P,Q, Y char(10))

TMASTER has 50,000 records while TDETAIL has 1 Million records.

I did the follwing
1) Drop constraints on both tables
2) Removed Indexs on X and Y columns only all other Indexes for TMASTER and
TDETAIL are inplace.
3)Alter tables TMaster and than TDetail.
4) Update X = Rtrim(X) on TMASTER
5) Update Y = Rtrim(Y) on TDETAIL

To update column Y it took 8 hours which has 1 Millions rows.

1) Is this an efficient way to do this job because I have around 150 tables with
similar change.
2) How do I reduce the time to update the column atleast in couple of minutes
and not 8 hours.
3) I guess the oracle should be fast ( couple of minutes ) to update 1 million
columns with Rtrim, Is there something holding my table to get updated, how
should I figure that out.
4) Did I miss out anything or should I do things in a particular order, how do I
know what is making my table update slow.

I am new to oracle.

Thanks in advance,
Sha
Followup:
you did something wrong -- not sure what (i would guess you didn't really get
the indexes off).

to update 1,000,000 rows (unindexed) is a couple of minutes.

big_table@ORA920PC> create table t
  2  as
  3  select cast( object_name as char(30) ) x, big_table.*
  4  from big_table
  5  where rownum <= 1000000
  6  /

Table created.

big_table@ORA920PC> set timing on
big_table@ORA920PC> alter table t modify x varchar2(30);

Table altered.

Elapsed: 00:00:00.32
big_table@ORA920PC> select min(length(x)) from t;

MIN(LENGTH(X))
--------------
            30

Elapsed: 00:00:02.00
big_table@ORA920PC> update t set x = rtrim(x);

1000000 rows updated.

Elapsed: 00:01:26.51
big_table@ORA920PC> select min(length(x)) from t;

MIN(LENGTH(X))
--------------
             1

Elapsed: 00:00:03.64
big_table@ORA920PC>

So, don't know what you did wrong (logs big enough?  any checkpoint not complete
messages, are you sure the indexes were "gone").
Rating: 5
Why char as parameter not restricting? March 19, 2004
Reviewer: dharma from CA USA
I have been debugging some procedures from a Oracle 7.3 system which is in 9i
rel 2 now where I found one of your BIG NO NO. it had a "when others then null"
and the procedure has been there for 4 years.
What I found to be causing the error looks strange.

The defn of CHAR without any length is defaulted CHAR(1)

But If I have a parameter in the procedure as CHAR how is it able to get the
values larger than 1 byte?

DECLARE
  PROCEDURE p(p_a IN CHAR) IS
    b CHAR;
  BEGIN
    Dbms_output.put_line('Before assign'||p_a);
    b:=p_a; --raises error if assigned;
    Dbms_output.put_line('After assign'||b);
  END p;
BEGIN
  p('12');
END;

if the line b:=a is commented it works fine. should'nt it be throwing an error
for assigning parameter p_a a value of length greater than 1.
Am I missing something.

Thanks,
-dharma
Followup:
the parameters size is defined at runtime as specified by the INPUT parameter.

if you called

declare
   x char(10);
   y char(20);
begin
   p( x ) ;
   p( y ) ;
end;

the size of "p_a" would be 10 and then 20. sooo, look back in the code to the
invokers of this routine

(and stop using the dreaded CHAR type !!! hate it)
March 20, 2004 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
Rating: 4
still q's on analytics January 30, 2004
Reviewer: A reader from Madison, wi
Okay, so my web application logs "web transaction" statistics to a table.  This
actually amounts to 0 to many database tranactions... but anyway.. I need to
summarize (sum, min, max, count, average) each day's transaction times for each
class (name2) and action (name3) and ultimately "archive" this data to a hisory
table.  I am running 8.1.7 and pretty new to analytics.

My table looks like this:

SQL> desc tran_stats
 Name                    Null?    Type
 ----------------------- -------- ----------------
 ID                      NOT NULL NUMBER(9)
 NAME1                            VARCHAR2(100)
 NAME2                            VARCHAR2(100)
 NAME3                            VARCHAR2(100)
 NAME4                            VARCHAR2(100)
 SEC                     NOT NULL NUMBER(9,3)
 TS_CR                   NOT NULL DATE

        ID NAME1 NAME2                     NAME3         SEC NAME4 TS_CR
---------- ----- ------------------------- ---------- ------ ----- ---------
     35947       /CM01_PersonManagement    CREATE       .484       15-JAN-04
     35987       /CM01_PersonManagement    CREATE       .031       15-JAN-04
     36086       /CM01_PersonManagement    EDIT         .312       16-JAN-04
     36555       /CM01_PersonManagement    CREATE       .297       19-JAN-04
     36623       /CM01_PersonManagement    EDIT         .375       19-JAN-04
     36627       /CM01_PersonManagement    CREATE       .047       19-JAN-04
     36756       /CM01_AddressManagement   CREATE       .375       20-JAN-04
     36766       /CM01_AddressManagement   CREATE       .305       20-JAN-04
     36757       /CM01_AddressManagement   INSERT       .391       20-JAN-04
     37178       /CM01_PersonManagement    EDIT         .203       20-JAN-04

and I need output like this:

TS_CR     NAME2                     NAME3       M_SUM  M_MIN  M_MAX M_COUNT
M_AVG
--------- ------------------------- ---------- ------ ------ ------ -------
------
20-JAN-04 /CM01_AddressManagement   CREATE       .680   .305   .375       2
.340
20-JAN-04 /CM01_AddressManagement   INSERT       .391   .391   .391       1
.391
20-JAN-04 /CM01_PersonManagement    EDIT         .203   .203   .203       1
.203
19-JAN-04 /CM01_PersonManagement    CREATE       .344   .047   .297       2
.172
19-JAN-04 /CM01_PersonManagement    EDIT         .375   .375   .375       1
.375
16-JAN-04 /CM01_PersonManagement    EDIT         .312   .312   .312       1
.312
15-JAN-04 /CM01_PersonManagement    CREATE       .515   .031   .484       2
.258

This seems to work, but there has to be a better/cleaner/more efficient way to
do this:

select distinct ts_cr, name2, name3, m_sum, m_min,m_max,m_count,m_avg
from (
select  trunc(ts_cr) ts_cr,id, name2, name3, sum(sec) m_dummy
 , min(sum(sec)) over(partition by name2,name3,trunc(ts_cr)) as m_min
 , max(sum(sec)) over(partition by name2,name3,trunc(ts_cr)) as m_max
 , round(avg(sum(sec)) over(partition by name2,name3,trunc(ts_cr)),5) as m_avg
 , count(sum(sec)) over(partition by name2,name3,trunc(ts_cr)) as m_count
 , sum(sum(sec)) over(partition by name2,name3,trunc(ts_cr)) as m_sum
        from tran_stats group by name2, name3,trunc(ts_cr),id
)n order by 1 desc, 2, 3;

Any help or pointers would be appreciated.  Thanks in advance.
Followup:
why does there "have to be"?

what is "unclean" about this?  I could make it more verbose (and perhaps more
readable) but this does exactly what you ask for?

It seems pretty "good", very "clean" and probably the most efficient method to
get this result?
Rating: 2
Regarding the previous post ... January 30, 2004
Reviewer: A reader
Am I missing something or will the following do the same ..

select trunc(ts_cr) ts_cr, name2, name3,
       count(*) m_count, min(sec) m_min, max(sec) m_max,
       sum(sec) m_sum, avg(sec) m_avg
from tran_stats
group by trunc(ts_cr), name2, name3
order by 1 desc, 2, 3
Followup:
with the supplied data -- since "group by trunc(ts_cr), name2, name3" happened
to be unique

yes.

In general -- no.  consider:

ops$tkyte@ORA9IR2> select distinct ts_cr, name2, name3, m_sum,
m_min,m_max,m_count,m_avg
  2  from ( select trunc(ts_cr) ts_cr,
  3                id,
  4                            name2,
  5                            name3,
  6                            sum(sec) m_dummy ,
  7                            min(sum(sec)) over(partition by
name2,name3,trunc(ts_cr)) as m_min ,
  8                            max(sum(sec)) over(partition by
name2,name3,trunc(ts_cr)) as m_max ,
  9                            round(avg(sum(sec)) over(partition by
name2,name3,trunc(ts_cr)),5) as m_avg ,
 10                            count(sum(sec)) over(partition by
name2,name3,trunc(ts_cr)) as m_count ,
 11                            sum(sum(sec)) over(partition by
name2,name3,trunc(ts_cr)) as m_sum
 12               from tran_stats
 13                  group by name2, name3,trunc(ts_cr),id
 14        )n
 15  MINUS
 16  select ts_cr, name2, name3, m_sum, m_min,m_max,m_count,m_avg
 17  from (
 18  select trunc(ts_cr) ts_cr, name2, name3,
 19         count(*) m_count, min(sec) m_min, max(sec) m_max,
 20                    sum(sec) m_sum, avg(sec) m_avg
 21                            from tran_stats
 22                            group by trunc(ts_cr), name2, name3 )
 23  /

no rows selected

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into tran_stats
  2  select 35947,'/CM01_PersonManagement','CREATE', .484  ,'15-JAN-04'
  3   from all_users where rownum <= 5;

5 rows created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select distinct ts_cr, name2, name3, m_sum,
m_min,m_max,m_count,m_avg
  2  from ( select trunc(ts_cr) ts_cr,
  3                id,
  4                            name2,
  5                            name3,
  6                            sum(sec) m_dummy ,
  7                            min(sum(sec)) over(partition by
name2,name3,trunc(ts_cr)) as m_min ,
  8                            max(sum(sec)) over(partition by
name2,name3,trunc(ts_cr)) as m_max ,
  9                            round(avg(sum(sec)) over(partition by
name2,name3,trunc(ts_cr)),5) as m_avg ,
 10                            count(sum(sec)) over(partition by
name2,name3,trunc(ts_cr)) as m_count ,
 11                            sum(sum(sec)) over(partition by
name2,name3,trunc(ts_cr)) as m_sum
 12               from tran_stats
 13                  group by name2, name3,trunc(ts_cr),id
 14        )n
 15  MINUS
 16  select ts_cr, name2, name3, m_sum, m_min,m_max,m_count,m_avg
 17  from (
 18  select trunc(ts_cr) ts_cr, name2, name3,
 19         count(*) m_count, min(sec) m_min, max(sec) m_max,
 20                    sum(sec) m_sum, avg(sec) m_avg
 21                            from tran_stats
 22                            group by trunc(ts_cr), name2, name3 )
 23  /

TS_CR     NAME2                   NAME3         M_SUM      M_MIN      M_MAX
M_COUNT      M_AVG
--------- ----------------------- -------- ---------- ---------- ----------
---------- ----------
15-JAN-04 /CM01_PersonManagement  CREATE        2.935       .031      2.904
    2     1.4675

add more data and it won't be the same.
Rating: 3
OK January 31, 2004
Reviewer: Siva from Bangalore,India
Dear Tom,
Can analytics be used for the following formats of the same query
 sql>select ename,nvl(ename,'Name is null') from emp
 sql>select ename,decode(ename,null,'Name is null',ename)
 from emp
If you know other ways,Please let me know
Bye!
Followup:
umm, why ?
Rating: 5
with analytics February 18, 2004
Reviewer: A reader
with the following data

-- ------
1  val1_1
1  val1_2
1  val1_3
2  val1_1
2  val2_2

can i produce

-- ------  --------------------
1  val1_1  val1_1,val1_2,val1_3
1  val1_2  val1_1,val1_2,val1_3
1  val1_3  val1_1,val1_2,val1_3
2  val2_1  val2_1,val2_2
2  val2_2  val2_1,val2_2

with an  analytic that rocks
Followup:
if

select max(count(*)) from t group by id

has a reasonable maximum -- yes, but it would be a trick lag/lead thing.

I would probably join using stragg.  join the details to the aggregate using
inline views.
Rating: 3
OK March 01, 2004
Reviewer: Siddiq from UAE
Hi Tom,
What can be the business use cases of the analytic functions
1)cume_dist
2)percentile_dist
3)percentile_cont
Where can they be of immense use?
Bye!
Followup:
they are just statistical functions for analysis.

2 and 3 are really variations on eachother (disc=discrete, cont=continuous) and
would be used to compute pctcentiles (like you might see on an SAT test report
from back in high school).  percentile_* can be used to find a median for
example :)

cume_dist is a variation on that.  I'll cheat on an example, from the doc:

Analytic Example

The following example calculates the salary percentile for each employee in the
purchasing area. For example, 40% of clerks have salaries less than or equal to
Himuro.

SELECT job_id, last_name, salary, CUME_DIST() OVER (PARTITION BY job_id ORDER BY
salary) AS cume_dist FROM employees WHERE job_id LIKE  PU% ;

JOB_ID     LAST_NAME                 SALARY     CUME_DIST
---------- ------------------------- ---------- ----------
PU_CLERK   Colmenares                2500        .2
PU_CLERK   Himuro                    2600        .4
PU_CLERK   Tobias                    2800        .6
PU_CLERK   Baida                     2900        .8
PU_CLERK   Khoo                      3100         1
PU_MAN     Raphaely                  11000        1
Rating: 5
Stumped on Analytics March 04, 2004
Reviewer: Dave Thompson from West Yorkshire, England.
Hi Tom,

I have the following two tables:

CREATE TABLE PAY_M
(
  PAY_ID  NUMBER,
  PAYMENT  NUMBER
)

--
--

CREATE TABLE PREM
(
  PREM_ID       NUMBER,
  PREM_PAYMENT  NUMBER
)

With the following data:

INSERT INTO PREM ( PREM_ID, PREM_PAYMENT ) VALUES (
1, 100);
INSERT INTO PREM ( PREM_ID, PREM_PAYMENT ) VALUES (
2, 50);
INSERT INTO PREM ( PREM_ID, PREM_PAYMENT ) VALUES (
3, 50);
INSERT INTO PREM ( PREM_ID, PREM_PAYMENT ) VALUES (
4, 50);
COMMIT;
INSERT INTO PAY_M ( PAY_ID, PAYMENT ) VALUES (
1, 50);
INSERT INTO PAY_M ( PAY_ID, PAYMENT ) VALUES (
2, 25);
INSERT INTO PAY_M ( PAY_ID, PAYMENT ) VALUES (
3, 50);
INSERT INTO PAY_M ( PAY_ID, PAYMENT ) VALUES (
4, 50);
COMMIT;

PAY_M contains payments made against the premiums in the table prem.

Payments:

    PAY_ID    PAYMENT
---------- ----------
         1         50
         2         25
         3         50
         4         50

Prem:

   PREM_ID PREM_PAYMENT
---------- ------------
         1          100
         2           50
         3           50
         4           50

We are trying to find which payment Ids paid each premium payment in Prem.  The
payments are assigned sequentially to the premiums.

For example payments 1,2 & 3 pay off the £100 in premium 1 leaving £25.  Then
the remaining payment from payment 3 & payment 4 pay off premium 2 leaving a
balance of £25, and so on.

We are trying to create a query that will use the analytical functions to find
all the payment IDs that pay off the associated premium ids.  We want to keep
this SQL based as we need to Process about 30 million payments!

Thanks.

Great website, hope you enjoyed your recent visit to the UK.
Followup:
let me make sure I have this straight -- you want to

o sum up the first 3 records in payments
o discover they are 125 which exceeds 100
o output the fact that prem_id 1 is paid for by pay_id 1..3
o carry forward 25 from 3, discover that leftover 3+4 = 75 pays for prem_id 2
  with 25 extra

while I believe (not sure) that the 10g MODEL clause might be able to do this
(if you can do it in a spreadsheet, we can use the MODEL clause to do it).....

I'm pretty certain that analytics cannot -- we would need to recursively use lag
(eg: after finding that 1,2,3 pay off 1, we'd need to -- well, it's hard to
explain...)

I cannot see analytics doing this -- future rows depend on functions of the
analytics from past rows and that is just "not allowed".

I can see how to do this in a pipelined PLSQL function -- will that work for
you?
Rating: 3
Oops - Error in previous post March 04, 2004
Reviewer: Dave Thompson from West Yorkshire, England
Tom,

Sorry, ignore the above tables as they are missing the joining column:

CREATE TABLE PAY_M
(
  PREM_ID  NUMBER,
  PAY_ID   NUMBER,
  PAYMENT  NUMBER
)

INSERT INTO PAY_M ( PREM_ID, PAY_ID, PAYMENT ) VALUES (
1, 1, 50);
INSERT INTO PAY_M ( PREM_ID, PAY_ID, PAYMENT ) VALUES (
1, 2, 25);
INSERT INTO PAY_M ( PREM_ID, PAY_ID, PAYMENT ) VALUES (
1, 3, 50);
INSERT INTO PAY_M ( PREM_ID, PAY_ID, PAYMENT ) VALUES (
1, 4, 50);
COMMIT;

CREATE TABLE PREM
(
  PREM_ID       NUMBER,
  PAY_ID        NUMBER,
  PREM_PAYMENT  NUMBER
)

INSERT INTO PREM ( PREM_ID, PAY_ID, PREM_PAYMENT ) VALUES (
1, 1, 100);
INSERT INTO PREM ( PREM_ID, PAY_ID, PREM_PAYMENT ) VALUES (
1, 2, 50);
INSERT INTO PREM ( PREM_ID, PAY_ID, PREM_PAYMENT ) VALUES (
1, 3, 50);
INSERT INTO PREM ( PREM_ID, PAY_ID, PREM_PAYMENT ) VALUES (
1, 4, 50);
COMMIT;

SQL> l
  1  SELECT *
  2* FROM   PAY_M
SQL> /

   PREM_ID     PAY_ID    PAYMENT
---------- ---------- ----------
         1          1         50
         1          2         25
         1          3         50
         1          4         50

SQL> select *
  2  from prem;

   PREM_ID     PAY_ID PREM_PAYMENT
---------- ---------- ------------
         1          1          100
         1          2           50
         1          3           50
         1          4           50
Rating: 4
Thanks..... March 05, 2004
Reviewer: Dave Thompson from West Yorkshire, England.
Tom,

Thanks for your prompt response.

I am familiar with Pipeline functions.

I was however hoping we could do this as a set based opertion because of the
volume of data involved.

Thanks for your time.
Rating: 3
analytics book March 05, 2004
Reviewer: Ron Chennells from UK
Just another vote and pre order for the analytics book
Rating: 3
OK March 19, 2004
Reviewer: Gerhard from Dusseldorf,Germany
Dear Tom,
 I used the following query to find the difference of salaries between
employees.

SQL> select ename,sal,sal-lag(sal) over(order by sal) as diff_sal from emp;

ENAME             SAL   DIFF_SAL
---------- ---------- ----------
SMITH             800
JAMES             950        150
ADAMS            1100        150
WARD             1250        150
MARTIN           1250          0
MILLER           1300         50
TURNER           1500        200
ALLEN            1600        100
CLARK            2450        850
BLAKE            2850        400
JONES            2975        125

ENAME             SAL   DIFF_SAL
---------- ---------- ----------
SCOTT            3000         25
FORD             3000          0
KING             5000       2000

14 rows selected.

My Question is:
" What is the difference between King's sal with other
employees?".Could you please help with the query?
Bye!
Followup:
scott@ORA9IR2> select ename,sal,sal-lag(sal) over(order by sal) as diff_sal ,
  2         sal-king_sal king_sal_diff
  3    from (select sal king_sal from emp where ename = 'KING'),
  4         emp
  5  /

ENAME             SAL   DIFF_SAL KING_SAL_DIFF
---------- ---------- ---------- -------------
SMITH             800                    -4200
JAMES             950        150         -4050
ADAMS            1100        150         -3900
WARD             1250        150         -3750
MARTIN           1250          0         -3750
MILLER           1300         50         -3700
TURNER           1500        200         -3500
ALLEN            1600        100         -3400
CLARK            2450        850         -2550
BLAKE            2850        400         -2150
JONES            2975        125         -2025
SCOTT            3000         25         -2000
FORD             3000          0         -2000
KING             5000       2000             0

14 rows selected.
Rating: 3
Will this be faster? March 19, 2004
Reviewer: Venkat from Detroit
select ename, sal,
       sal-lag(sal) over(order by sal) as diff_sal,
       sal - max(case when ename='KING' then sal
                 else null end) over () king_sal_diff
from emp
Followup:
when you benchmarked it and tested it to scale, what did you see?  it would be
interesting no?
March 20, 2004 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
Rating: 4
Conversion? January 12, 2004
Reviewer: Vinnie from Orlando
Tom,

What if I have a attribute that is VARCHAR2 type that
stores both characters & numbers but wish to somehow test to see if what is
stored is a number?  Can this be done?
Followup:
there are many ways.  easiest

create or replace function isnum( pstr in varchar2 ) return number
is
   x number;
begin
   x:= pstr;
   return 1;
exception
   when others then return 0;
end;
/
Rating: 3
to_char(20) March 19, 2004
Reviewer: A reader
hi tom,

  if i do

 select * from t
where to_char(substr(code,2)) < to_char(20)

is it gurenteed that it will give me all the
substr(code,2) < number 20  ?

will it both get transfer in ascii and compare ?

will it produce right result ?
Followup:
ugh, no

there is no number 20 there, you have a string '20'

you will get the set of rows such that:

substr(code,2) < '20'

the to_char(substr( is redundant (substr returns a string already).

ugh, mess -- don't even know if code is a number or what.

If you want to compare to the "number" 20, there is but one thing to do.
compare a NUMBER to the number 20.
Rating: 3
to_char March 19, 2004
Reviewer: A reader
sorry tom.

 it is

select * from t
where substr(code,2) < to_char(20)

 i have codes

01-abc
02-bca
03-cdx
04-drf
12-dde
09-okd
aa-kdn  ***
10-bbc

 my client has this data. I have to query his data to develop his small
applicaiton. I need to find out all the codes where substr(code,2) < number 20

 what is the best way ?
Followup:
but you see, is "a" the string less than 20 the number.

it is not but is that "right"

none of those codes mean ANYTHING when compared to "20"

1-abc < 20???? what does that even mean?
March 20, 2004 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
Rating: 5
Simply AWESOME!!! March 12, 2004
Reviewer: Sam from Florida
I was struggling for couple of days to run a "procedure name" stored in database
from another procedure.
found this on google and got up and running in 10 mins.
YOU ARE SIMPLY AWESOME.
Rating: 4
How about for type Objects??? March 20, 2004
Reviewer: NOTNA from Phil...
Hi Tom,

I have a requirement wherein I need to create a procedure that you can pass any
object and will call another procedure to validate it's contents.

For instance :
create or replace type a_type (
c1 number, c2 varchar2(10), c3 date
)
/

create or replace type b_type (
b1 date,
b2 varchar2(10),
b3 number)
/

This package will handle all messages coming in from other channels........

create or replace package body public_api as

create or replace procedure process_a_type ( p_a_type a_type)
is

begin
    if validate_msg_cont ( p_a_type ) then
        ...
           process.
        .. .
    else
        raise invalid_message_sent.
    end if;
end;

create or replace procedure process_b_type ( p_b_type b_type)
is

begin
    if validate_msg_cont ( p_b_type ) then
        ...
           process.
        .. .
    else
        raise invalid_message_sent.
    end if;
end;

create or replace function validate_msg_cont(
p_actual_obj in "OBJECT???",
p_objType varchar2  "<- name of the type, a_type, b_type "
)
v_sql long;
v_good_record boolean;
is
begin
     v_good_record := false;
     v_sql := 'declare '||
               '  v_type '||p_objType ||';'||
               'begin '||
               ' :2 := check_'||p_objType ||'_contents(:1)' ||
               'end;'
           ;
      execute immediate v_sql using in p_actual_obj, out v_good_record;

end validate_msg_cont;
/

end public_api ;
/

This package will do the of the mandatory and process the bus rules.....

create or replace package bus_processing
as

create or replace procedure check_a_type (p_a in a_type) is
begin
    if pa.c2 is not null then
       if (pa.c3 is not null) and (pa.c3 > sysdate + 1)
           return true;
       else
          return false;
       end if;
    else
       return false;
    end if;

end;

create or replace procedure check_a_type (p_a in a_type) is
begin
    if pa.b2 is not null then
       if pa.b3 > sysdate + 1
           return true;
       else
          return false;
       end if;
    else
       return false;
    end if;

end;

end bus_processing ;

Flow :
  [ xml - Third Party Message]
       |
    [ transformation ]
       |
    [  public api   ]
            |
    [  Bus processing  ]

Regards,
NOTNA
Followup:
sorry, just a tad out of scope for this (and probably any other thread)
thread....

you are asking "design an application"

sounds like you want to keep stuff xml longer though (or earlier).  validating
xml against an xml schema/dtd/whatever is pretty straight forward and you as the
guy in the middle don't really care what is in the xml
March 20, 2004 Philip -- Thanks for the question regarding "STRAGG", version 9.0.2
You Asked
Hi Tom,

Need your help here.

Stage table:

Customer    Product    Order Date    Operator
--------        ------------------      ----------
C1        P1        D1        OP1
C1        P2        D2        OP2
C1        P3        D3        OP3
C1        P1        D4        OP3

C2        P2        D1        OP2
C2        P3        D2        OP4

C3        P1        D2        OP1
C3        P1        D3        OP1

I would like the results to look like the following (ordered by product name):

Output table:

Customer        Product_Str     Order_Date_Str  Operator_Str
--------        -----------     --------------  ------------
C1        P1,P1,P2,P3    D1,D4,D2,D3    OP1,OP3,OP2,OP4
C2        P2,P3        D1,D2        OP2,OP4
C3        P1,P1        D2,D3        OP1,OP1

There are about 14 millions customers and the number of orders per customer are
infinite.

I was able to use STRAGG function to build the string fields fast (less than an
hour).  But the product name is not in order and these three fileds are out of
sync. I know “Set” has no order concept.

Is there any other way to construct this?   I have tried to use a function but
it’s way too slow.  I have also tried to use analytic function row_number()
over… but since the order number is not known…

Create table stage
(customer varchar(2),
 product    varchar(2),
 order_date varhcar2(2),
 operator   varchar2(9));

insert into stage values (‘C1’,’P1’,’D1’,’OP1’);
insert into stage values (‘C1’,’P2’,’D2’,’OP2’);
insert into stage values (‘C1’,’P3’,’D3’,’OP3’);
insert into stage values (‘C1’,’P1’,’D4’,’OP3’);
insert into stage values (‘C2’,’P2’,’D1’,’OP2’);
insert into stage values (‘C2’,’P3’,’D2’,’OP4’);
insert into stage values (‘C3’,’P1’,’D2’,’OP1’);
insert into stage values (‘C3’,’P1’,’D3’,’OP1’);

commit;

create table output
(customer varchar2(2),
 product_str varchar2(100),
 order_date_str varchar2(100),
operator_str   varchar2(100));

SQL>Insert into output
Select customer, stragg(product) ,stragg(order_date), stragg(operator)
From stage
Group by customer;

Thank you!

=================

Tom,

On average the products ordered by a customer is 18. But it could be as high as
40 and as low as 1.

a) the three fields need to be correlated -- eg: sort order_date and operator
by product and string them in that order.

==> The three fields need to be correlated

b) are the three fields "unique" in the customer.  eg: will the same product
appear over and over

==> The product can be repeated over and over again. So can order date and
operator ID.

Thanks!
and we said...
I would use row_number() and pivot then.  It will be faster then modifying
stragg to procedurally "sort" the correlated data with duplicates.

ops$tkyte@ORA920PC> declare
  2          l_stmt long;
  3  begin
  4          l_stmt := 'insert into output select customer, rtrim(
max(decode(rn,1,product)) ';
  5      for i in 2 .. 40
  6          loop
  7                  l_stmt := l_stmt || ' || '','' || max(decode( rn, ' || i ||
', product ))';
  8          end loop;
  9          l_stmt := l_stmt || ', '','' ), rtrim( max(decode(rn,1,order_date))
';
 10      for i in 2 .. 40
 11          loop
 12                  l_stmt := l_stmt || ' || '','' || max( decode( rn, ' || i
|| ', order_date ))';
 13          end loop;
 14          l_stmt := l_stmt || ', '','' ), rtrim( max(decode(rn,1,operator))
';
 15      for i in 2 .. 40
 16          loop
 17                  l_stmt := l_stmt || ' || '','' || max( decode( rn, ' || i
|| ', operator ))';
 18          end loop;
 19          l_stmt := l_stmt || ', '','' ) from (select stage.*, row_number()
over (partition by customer order by rowid) rn from stage)';
 20          l_stmt := l_stmt || ' group by customer';
 21
 22          execute immediate l_stmt;
 23  end;
 24  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> select * from output;

CU PRODUCT_STR
--
---------------------------------------------------------------------------------
-------------------
ORDER_DATE_STR
---------------------------------------------------------------------------------
-------------------
OPERATOR_STR
---------------------------------------------------------------------------------
-------------------
C1 P1,P2,P3,P1
D1,D2,D3,D4
OP1,OP2,OP3,OP3

C2 P2,P3
D1,D2
OP2,OP4

C3 P1,P1
D2,D3
OP1,OP1

If you are worried about "having 41" someday, just add:

 l_stmt := l_stmt || ' where decode( rn, 41, 1/0 ) is null group by customer';

that way, the statement will FAIL if there are 41 -- letting you know -- so you
can increase the size of the "loop"
Review & Followup

Rating: 5
Product String field is not sorted February 16, 2004
Reviewer: Philip Kuan from Fairfax, VA USA
Tom,

Sorry if I did not make myself clear. The product_str field needs to be sorted
by product name.

So the output for C1 should be P1,P1,P2,P3. And sort order_date and operator by
product and string them in that order.

Thank you.
Followup:
sorry -- order by product_name, rowid

 (partition by customer order by PRODUCT_NAME, rowid)
Rating: 5
Excellent February 16, 2004
Reviewer: Philip Kuan from Fairfax, VA, USA
Tom,

The solution works really well.  Thank you so much!
Rating: 3
Good March 14, 2004
Reviewer: James from Oregon,USA
Dear Sir,
How to create our own operator using the command
 sql> create operator op1 ..
Do you have a demo for this?
Please do reply.
Followup:
No, i do not -- but the documentation does:
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96595/toc.htm
Rating: 4
user defined analytic function March 18, 2004
Reviewer: Nopparat V. from Thailand
It will be a good idea if we can create a user defined analytic function in the
same way we did with a user defined aggregate function. (Oracle should consider
its possibility)

I have modified your StrAgg function to use in case sorting is required.

create or replace type stringOrder as object (
TheString  varchar2(10) ,
TheOrder   number(2) ) ;
/
create or replace type StringAggType2 as object
(
   theString varchar2(4000),
   MaxOrder  number(2),
   static function
        ODCIAggregateInitialize(sctx IN OUT StringAggType2 )
        return number,
   member function
        ODCIAggregateIterate(self IN OUT StringAggType2 ,
                             value IN StringOrder )
        return number,
   member function
        ODCIAggregateTerminate(self IN StringAggType2,
                               returnValue OUT  varchar2,
                               flags IN number)
        return number,
   member function
        ODCIAggregateMerge(self IN OUT StringAggType2,
                           ctx2 IN StringAggType2)
        return number
);
/
create or replace type body StringAggType2
 is
 static function ODCIAggregateInitialize(
   sctx IN OUT StringAggType2)
 return number
 is
 begin
     sctx := StringAggType2( null,0 );
     return ODCIConst.Success;
 end;
 member function ODCIAggregateIterate(
   self IN OUT StringAggType2 ,
   value IN StringOrder )
 return number
 is
 begin
   if value.TheOrder > self.MaxOrder then
     self.theString := self.theString ||
                       lpad(',',value.TheOrder-self.MaxOrder,',') ||
                       value.TheString ;
     self.MaxOrder := value.TheOrder ;
   elsif value.TheOrder < self.MaxOrder then
     self.theString :=
substr(self.theString,1,instr(self.theString,',',1,value.TheOrder))||
                       value.TheString ||

substr(self.theString,instr(self.theString,',',1,value.TheOrder)+1) ;
   end if ;
   return ODCIConst.Success;
 end;
 member function ODCIAggregateTerminate(
   self IN StringAggType2,
   returnValue OUT  varchar2,
   flags IN number)
 return number
 is
 begin
     returnValue := rtrim( ltrim( self.theString, ',' ), ',' );
     return ODCIConst.Success;
 end;
 member function ODCIAggregateMerge(
   self IN OUT StringAggType2,
   ctx2 IN StringAggType2)
 return number
 is
 begin
     self.theString := self.theString || ',' || ctx2.theString;
     self.MaxOrder := self.MaxOrder + ctx2.MaxOrder ;
     return ODCIConst.Success;
 end;
end ;
/
CREATE OR REPLACE FUNCTION StrAggOrd(input StringOrder)
RETURN varchar2
PARALLEL_ENABLE AGGREGATE USING StringAggType2;
/

Then we can use it by

select deptno, StrAggOrd(StringOrder(ename,rn)) member
from
  (select deptno, ename,
   row_number () over (partition by deptno order by ename) rn
   from emp)
group by deptno ;

How about this solution ?
Followup:
what do you mean?  user defined aggregates are documented to be "analytic
friendly"

Just take my original stragg unchanged:

ops$tkyte@ORA9IR2> l
  1  select deptno, stragg(ename) over (partition by deptno order by ename)
ename
  2* from emp
ops$tkyte@ORA9IR2> /

    DEPTNO ENAME
---------- ----------------------------------------
        10 CLARK
        10 CLARK,KING
        10 CLARK,KING,MILLER
        20 ADAMS
        20 ADAMS,FORD
        20 ADAMS,FORD,JONES
        20 ADAMS,FORD,JONES,SCOTT
        20 ADAMS,FORD,JONES,SCOTT,SMITH
        30 ALLEN
        30 ALLEN,BLAKE
        30 ALLEN,BLAKE,JAMES
        30 ALLEN,BLAKE,JAMES,MARTIN
        30 ALLEN,BLAKE,JAMES,MARTIN,TURNER
        30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD

14 rows selected.
Rating: 5
exactly what I was looking for yesterday March 18, 2004
Reviewer: bob from PA
Your holding out on me Tom. :)

Yesterday when I asked:

"Using StringAgg, how can I order the concatenation so that it occurs
alphabetically? "

You gave me:

"You'd have to "save up" the inputs and then sort them and then concatenate
them.
 they come in random order to stragg... "

Today, you give Philip the key with the use of analytics.
I guess I just didn't know how to ask my question correctly, but thankfully
philp was trying to do the same exact thing I was.

This site is great!
Followup:
but analytics don't really do that -- for if you look at the results -- you
didn't get just an alphabetic listing of enames by deptno, you got a record per
employee!

So, in response to:

<quote>
It will be a good idea if we can create a user defined analytic function in the
same way we did with a user defined aggregate function. (Oracle should consider
its possibility)
</quote>

the answer is "well, we do that".  But if you want stragg to work as an
AGGREGATE with sorted data -- the answer is "aggregates don't do that"

Ok, here is one that does "sorted" aggregates:

ops$tkyte@ORA9IR2> create or replace type vcArray as table of varchar2(4000)
  2  /
Type created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace type string_agg_type as object
  2  (
  3     data  vcArray,
  4
  5     static function
  6          ODCIAggregateInitialize(sctx IN OUT string_agg_type )
  7          return number,
  8
  9     member function
 10          ODCIAggregateIterate(self IN OUT string_agg_type ,
 11                               value IN varchar2 )
 12          return number,
 13
 14     member function
 15          ODCIAggregateTerminate(self IN string_agg_type,
 16                                 returnValue OUT  varchar2,
 17                                 flags IN number)
 18          return number,
 19
 20     member function
 21          ODCIAggregateMerge(self IN OUT string_agg_type,
 22                             ctx2 IN string_agg_type)
 23          return number
 24  );
 25  /

Type created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace type body string_agg_type
  2  is
  3
  4  static function ODCIAggregateInitialize(sctx IN OUT string_agg_type)
  5  return number
  6  is
  7  begin
  8      sctx := string_agg_type( vcArray() );
  9      return ODCIConst.Success;
 10  end;
 11
 12  member function ODCIAggregateIterate(self IN OUT string_agg_type,
 13                                       value IN varchar2 )
 14  return number
 15  is
 16  begin
 17      data.extend;
 18      data(data.count) := value;
 19      return ODCIConst.Success;
 20  end;
 21
 22  member function ODCIAggregateTerminate(self IN string_agg_type,
 23                                         returnValue OUT varchar2,
 24                                         flags IN number)
 25  return number
 26  is
 27      l_data varchar2(4000);
 28  begin
 29      for x in ( select column_value from TABLE(data) order by 1 )
 30      loop
 31              l_data := l_data || ',' || x.column_value;
 32      end loop;
 33      returnValue := ltrim(l_data,',');
 34      return ODCIConst.Success;
 35  end;
 36
 37  member function ODCIAggregateMerge(self IN OUT string_agg_type,
 38                                     ctx2 IN string_agg_type)
 39  return number
 40  is
 41  begin -- not really tested ;)
 42      for i in 1 .. ctx2.data.count
 43      loop
 44              data.extend;
 45              data(data.count) := ctx2.data(i);
 46      end loop;
 47      return ODCIConst.Success;
 48  end;
 49
 50
 51  end;
 52  /

Type body created.

ops$tkyte@ORA9IR2> show err
No errors.
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> CREATE or replace
  2  FUNCTION stragg(input varchar2 )
  3  RETURN varchar2
  4  PARALLEL_ENABLE AGGREGATE USING string_agg_type;
  5  /

Function created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> column ename format a40
ops$tkyte@ORA9IR2> select deptno, stragg(ename) ename
  2    from emp
  3   group by deptno
  4  /

    DEPTNO ENAME
---------- ----------------------------------------
        10 CLARK,KING,MILLER
        20 ADAMS,FORD,JONES,SCOTT,SMITH
        30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD

You could take the above other example and apply the same technique (the
row_number() concept is intriguing as it allows you to specify some other sorted
order)
Rating: 5
same result March 18, 2004
Reviewer: bob from PA
Hi Tom,

I had just wrapped that earlier result (one row per employee) in another
analytic to grab the result from the last employee that contained the full list
for the dept, so ultimately, the initial use of analytics which returned a
result for each employee led me to use another one, to get what I was looking
for without rewriting the user defined aggregate like you did.

I am sure this is far from ideal, but it worked initially.
Rating: 5
Thank you March 18, 2004
Reviewer: Nopparat V. from Thailand
I'm sorry that I didn't finish the whole chapter of User-Defined Aggregate
Functions really. You're right. It all was said in the book.

Also I agree with Bob. If we can use original StringAgg with some analytic
options in order to complete our requirement, it should be the better choice. I
think the original one can be used in more general purpose than the other
modified ones.
Followup:
but analytics are fundementally *not* aggregates.

Think about how many times the analytic function is invoked vs how many times
the aggregate is.....
Rating: 5
More Questions March 19, 2004
Reviewer: Nopparat V. from Thailand
I have found from the book that there is another interface ODCIAggregateDelete
to use in case of analytic function. I, however, can't understand it well. Could
you explain me more how it work? or Just show me some example to implement.

Also please advise whether I should add this interface into the original
StrAggType. How it affect the performance when we use it in the simple aggregate
action?
Followup:
it is an optimization (and a good question -- at first I was going to leave this
as an exercise for the reader, but I got curious myself and proved that "every
day you can learn something new about Oracle", this was my new thing today :)
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96595/dci11agg.htm#1004794

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96595/dci19agg.htm#76648
It would be most useful with sliding windows, or non "top anchored" windows.

In the above example -- the stragg result was always "growing" or just "reset".
it would never delete.  If I had a SLIDING window however -- it would be
different.  Consider a sliding window of the "preceding, current and next" row.
stragg would be gluing three rows together and slide through the result set.
Well, there are two ways to do this -- with and without the delete.

In order to see the difference, we'll create a small package that is useful for
collecting your own custom statistics in your session:

ops$tkyte@ORA9IR2> create or replace context stats_ctx using stats_pkg
  2  /

Context created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace package stats_pkg
  2  as
  3      procedure reset;
  4      procedure inc( p_name in varchar2, p_amt in number default 1 );
  5      procedure show( p_reset in boolean default false );
  6  end;
  7  /

Package created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace package body stats_pkg
  2  as
  3  procedure reset
  4  is
  5  begin
  6      for x in ( select attribute
  7                   from session_context
  8                  where namespace = 'STATS_CTX' )
  9      loop
 10          dbms_session.set_context( 'stats_ctx', x.attribute, 0 );
 11      end loop;
 12  end;
 13
 14
 15  procedure inc( p_name in varchar2, p_amt in number default 1 )
 16  is
 17  begin
 18      dbms_session.set_context( 'stats_ctx', p_name,
              nvl(sys_context('stats_ctx',p_name),0)+p_amt );
 19  end;
 20
 21  procedure show( p_reset in boolean default false )
 22  is
 23  begin
 24      for x in ( select attribute, value
 25                   from session_context
 26                  where namespace = 'STATS_CTX'
 27                  order by attribute )
 28      loop
 29          dbms_output.put_line( rpad( x.attribute, 32 ) || x.value );
 30          if ( p_reset )
 31          then
 32              dbms_session.set_context( 'stats_ctx', x.attribute, 0 );
 33          end if;
 34      end loop;
 35  end;
 36
 37  end;
 38  /

Package body created.

Now, I instrumented the original stragg with:

...
  4  static function ODCIAggregateInitialize(sctx IN OUT string_agg_type)
  5  return number
  6  is
  7  begin
  8      sctx := string_agg_type( null );
  9      stats_pkg.inc( 'ODCIAggregateInitialize' );
 10      return ODCIConst.Success;
 11  end;
......

in each and every entry point.  Now, we run some queries:

ops$tkyte@ORA9IR2> column enames format a50
ops$tkyte@ORA9IR2> select deptno, stragg(ename) over (order by ename rows
between 1 preceding and 1 following ) enames
  2    from emp
  3  /

    DEPTNO ENAMES
---------- --------------------------------------------------
        20 ADAMS,ALLEN
        30 ADAMS,ALLEN,BLAKE
        30 ALLEN,BLAKE,CLARK
        10 BLAKE,CLARK,FORD
...
        30 SMITH,TURNER,WARD
        30 TURNER,WARD

14 rows selected.

ops$tkyte@ORA9IR2> exec stats_pkg.show( true );
ODCIAGGREGATEINITIALIZE         13
ODCIAGGREGATEITERATE            38
ODCIAGGREGATETERMINATE          15

PL/SQL procedure successfully completed.

as you can see -- that iterate routine was called LOTS as we slid through.
that is because each three row "window" was constructed one by one.  Now, look
at a "growing only" example:

ops$tkyte@ORA9IR2> select deptno, stragg(ename) over (partition by deptno order
by ename) enames
  2    from emp
  3  /

    DEPTNO ENAMES
---------- --------------------------------------------------
        10 CLARK
        10 CLARK,KING
        10 CLARK,KING,MILLER
        20 ADAMS
        20 ADAMS,FORD
...
        30 ALLEN,BLAKE,JAMES,MARTIN
        30 ALLEN,BLAKE,JAMES,MARTIN,TURNER
        30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD

14 rows selected.

ops$tkyte@ORA9IR2> exec stats_pkg.show( true );
ODCIAGGREGATEINITIALIZE         3
ODCIAGGREGATEITERATE            14
ODCIAGGREGATETERMINATE          15

PL/SQL procedure successfully completed.

iterate was called lots less (as was initialize -- once per partition)

Lets add this to the spec/body:

ops$tkyte@ORA9IR2> create or replace type string_agg_type as object
  2  (
....
 20     member function
 21          ODCIAggregateDelete(self IN OUT string_agg_type,
 22                                 value IN varchar2 )
 23          return number,
...

ops$tkyte@ORA9IR2> create or replace type body string_agg_type
  2  is
  3
...
 23  member function ODCIAggregateDelete(self IN OUT string_agg_type,
 24                                      value IN varchar2 )
 25  return number
 26  is
 27  begin
 28      self.total := replace( self.total, ',' || value, '' );
 29      stats_pkg.inc( 'ODCIAggregateDelete' );
 30      return ODCIConst.Success;
 31  end;
......

NOTE: warning Will Robinson -- danger danger....  I took a totally cheesy
cheat here.  A simple replace.  This only works ONLY works because ename is
UNIQUE (and contains no commas!).  Our code would have to obviously be more
sophisticated for this to really work in general!!!!!

Now lets rerun that query:

ops$tkyte@ORA9IR2> select deptno, stragg(ename) over (order by ename rows
between 1 preceding and 1 following ) enames
  2    from emp
  3  /

    DEPTNO ENAMES
---------- --------------------------------------------------
        20 ADAMS,ALLEN
        30 ADAMS,ALLEN,BLAKE
        30 ALLEN,BLAKE,CLARK
        10 BLAKE,CLARK,FORD
...
        20 SCOTT,SMITH,TURNER
        30 SMITH,TURNER,WARD
        30 TURNER,WARD

14 rows selected.

ops$tkyte@ORA9IR2> exec stats_pkg.show( true );
ODCIAGGREGATEDELETE             12
ODCIAGGREGATEINITIALIZE         1
ODCIAGGREGATEITERATE            14
ODCIAGGREGATETERMINATE          15

42 calls, vs 66 calls before -- and if the delete was "efficient", perhaps
significantly reduced resource usage.

Note however that a growing example:

ops$tkyte@ORA9IR2> select deptno, stragg(ename) over (partition by deptno order
by ename) enames
  2    from emp
  3  /

    DEPTNO ENAMES
---------- --------------------------------------------------
....
        30 ALLEN,BLAKE,JAMES
        30 ALLEN,BLAKE,JAMES,MARTIN
        30 ALLEN,BLAKE,JAMES,MARTIN,TURNER
        30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD

14 rows selected.

ops$tkyte@ORA9IR2> exec stats_pkg.show( true );
ODCIAGGREGATEDELETE             0
ODCIAGGREGATEINITIALIZE         3
ODCIAGGREGATEITERATE            14
ODCIAGGREGATETERMINATE          15

PL/SQL procedure successfully completed.

won't be affected...
Rating: 5
Thank you March 20, 2004
Reviewer: Nopparat V. from Thailand
Thank you very much. I'll try its effects on normal aggregrate by myself. But I
think it probably result in the same manner as the last one. Then it mean we can
add, and probably should add ,the delete function if we have any efficient one
with no negative effects.

I strongly agree with you that "everyday you can learn something new about
Oracle", especially from this website.
Rating: 5
Another Curiousity March 20, 2004
Reviewer: Nopparat V. from Thailand
I expect the usage count of OCIAggregateTerminate of all sample to be 14 (equal
to 14 rows of result). Why it become 15? When was it invoked for the additional
one?
Followup:
you fetch 15 times for 14 rows, guess they are calling it regardless.
March 20, 2004 Sameer -- Thanks for the question regarding "Import/Export Views", version 8.1.7
You Asked
Hi Tom,

On one of my instance i'm facing problem in export utility.
Well it exports most of the things that i want but exits before exporting stored
procedures. here is the log...

Export done in US7ASCII character set and WE8ISO8859P1 NCHAR character set
server uses WE8ISO8859P1 character set (possible charset conversion)
. exporting pre-schema procedural objects and actions
. exporting foreign function library names for user DEV
. exporting object type definitions for user DEV
About to export DEV's objects ...
. exporting database links
. exporting sequence numbers
. exporting cluster definitions
. about to export TRANSCPSDEV's tables via Conventional Path ...
. . exporting table            LEVEL_MASTER          3 rows exported
. . exporting table           FILE_DETAILS          2 rows exported. . exporting
table      MESSAGES_MASTER          5 rows exported
. exporting synonyms
EXP-00008: ORACLE error 904 encountered
ORA-00904: invalid column name
EXP-00000: Export terminated unsuccessfully

The database was upgraded from 8.1.6 to 8.1.7 and the problem could be there
itself(untidy upgradation). But how should i fix it?
I feel the views which are needed for exporting are not created properly. If you
could provide me with with the list of views and the scripts for creating it
then i think so it could serve my purpose.

Thanks and regards

Sameer Ternikar
and we said...
I think the DBA should read the migration guide -- I think all of the requisite
steps for PROPERLY upgrading a database are contained therein.  This is just the
TIP of the iceberg.  Sure, I can fix this individual problem, but you'll be back
tomorrow with something else (quick fix for this = catexp.sql, run it)

Get your DBA to do it *right*, or get a new DBA.

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

Most likely, this database was upgraded from a previous release and the DBA that
did that did not run the proper upgrade scripts.

Can you tell me -- is this an upgraded database and did the DBA follow the
procedures exactly as outlined in the migration guide?
Review & Followup

Rating: 5
Export March 20, 2004
Reviewer: enrique Ortiz from Spain
hello

Im having the same export problem and i think is i tried to export an 8.1.7.0
database with exp 8.1.7.4

database
Oracle8i Enterprise Edition Release 8.1.7.0.0 - 64bit Production
With the Partitioning option
JServer Release 8.1.7.0.0 - 64bit Production

export

Export: Release 8.1.7.4.0 - Production on Sat Mar 20 11:34:10 2004
Followup:
use the export tool that sits alongside the database.

make sure the database was upgraded in the proper fashion.
March 21, 2004 Sunder -- Thanks for the question regarding "Outer Join", version 8.1.6.0.0
You Asked
I'am working on enhancing the performance of some long running
queries in our application. The existing code uses the NOT
EXISTS
clause extensively and I wondered if it would be a good idea to
use OUTER JOINS instead, wherever possible. For example, all
else
being equal, which of the following would execute faster ?

Insert into Title(......)
select .....
from rollup R
where not exists ( select 'Found' from title T
                   where R.source_id = T.Title_ID);

Insert into Title(.......)
select ......
from title T, rollup R
where R.source_id = T.Title_id(+)
and T.Title_id is null;

Note that Rollup has about 5 million rows and Title about
1 million rows and both are indexed on the join fields.
In trials, I did notice that the latter was about 30% faster
but does that have a theoritical basis ?

Would appreciate an early reply.

Thanks,

Sunder
and we said...
There are at least 3 ways to do this above (minus might be a
4'th depending on your circumstances).

The way with NOT exists works well for small OUTER tables (or
outer queries that have other predicates the result in
"smallish" result sets before we have to do the correlated
subquery).  A NOT exists works something like this:

  for x in ( select * from rollup ) loop
     if ( not exists ( that query ) ) then
            OUTPUT
     end if;
  end

it in effect runs the subquery over and over and over....

The outer join, method number 2, allows us to do more large
scale, bulk type operations.  Anything we do in bulk, we can
probably do faster.  So, by not running that subquery over and
over, we gained some efficiency.  It'll depend on your indexes
and such as well how well this goes.

The bulk operations we do -- thats your "theroretical basis".

A 3'rd alternative is a NOT IN query.  As long as you understand
that NOT IN and NOT EXISTS are semantically different, this
might run even faster yet.  NOT IN will not work correctly if
the subquery returns a NULL (the result set will be empty if it
does).  So, we have to add a filter "where title_id is NOT NULL"
to the subquery to make this be the same.

Here is a very very small test showing the three methods.
Existence (or lack of) indexes may effect this test and so on.
You might consider the NOT IN with the HASH_AJ hint (although
using the CBO should have this happen automatically -- if the
NOT IN runs really really slow, its not using the HASH_AJ --
you'd have to hint it in that case).

this test is not a benchmark, it is just illustrative of the
three methods and contains some comments on their plans.

DOC>create table t as select * from all_objects;
DOC>create index t_idx on t(object_id);

DOC>create table t2 as select * from all_objects where
mod(object_id,50) = 0;
DOC>create index t2_idx on t2(object_id);

DOC>*/

ops$tkyte@DEV8I.WORLD> set autotrace on explain
ops$tkyte@DEV8I.WORLD> select count(object_name)
  2    from t
  3   where NOT EXISTS( select null
  4                       from t2
  5                      where t2.object_id = t.object_id )
  6  /

COUNT(OBJECT_NAME)
------------------
             22251

Elapsed: 00:00:00.85

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     FILTER
   3    2       TABLE ACCESS (FULL) OF 'T'
   4    2       INDEX (RANGE SCAN) OF 'T2_IDX' (NON-UNIQUE)

Here, it full scans T and does that subquery for each row --
thats the index probe we see in there, the subquery running

ops$tkyte@DEV8I.WORLD>
ops$tkyte@DEV8I.WORLD> select /*+ HASH_AJ */ count(object_name)
  2    from t
  3   where object_id NOT IN ( select object_id
  4                              from t2
  5                             where object_id IS NOT NULL )
  6  /

COUNT(OBJECT_NAME)
------------------
             22251

Elapsed: 00:00:00.67

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1
   1    0   SORT (AGGREGATE)
   2    1     FILTER
   3    2       INDEX (FAST FULL SCAN) OF 'T_IDX' (NON-UNIQUE)
   4    2       INDEX (RANGE SCAN) OF 'T2_IDX' (NON-UNIQUE)

There is the "anti join" method with may or may not run a
little better in your environment.  Its worth trying though

ops$tkyte@DEV8I.WORLD> select count(t.object_name)
  2    from t, t2
  3   where t.object_id = t2.object_id(+)
  4     and t2.object_id is NULL
  5  /

COUNT(T.OBJECT_NAME)
--------------------
               22251

Elapsed: 00:00:00.75

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     FILTER
   3    2       NESTED LOOPS (OUTER)
   4    3         TABLE ACCESS (FULL) OF 'T'
   5    3         INDEX (RANGE SCAN) OF 'T2_IDX' (NON-UNIQUE)

And then the outer join method -- in this PARTICULAR example
it fell between the not exists and the NOT IN.  Your results may
vary ;)

ops$tkyte@DEV8I.WORLD> set autotrace off
Review & Followup

Rating: 4
March 30, 2001
Reviewer: A reader
Rating: 5
Outer join in a corelated query - does not work November 16, 2001
Reviewer: Nag
SQL> ed
Wrote file afiedt.buf

  1  select a.ename, a.empno
  2  from emp a
  3  where exists(select null from  dept b
  4*               where a.deptno(+)=b.deptno)
SQL> /
              where a.deptno(+)=b.deptno)
                      *
ERROR at line 4:
ORA-01705: an outer join cannot be specified on a correlation column

Tom, in my output , I want to see deptno 40 though it doesnot have any
employees, but I ran into ora-1705, why is this so.

Further, can you give examples of correlation column's apart from the above.
Followup:
well, your query is funky to begin with.

If it worked, it would be asking:

"i want all rows in EMP a such there there exists a row in B"

You are trying to outer join from B to A in your example!

You simply want to query:

select a.empno, a.ename, b.deptno
from emp a, dept b
where b.deptno = a.deptno(+)
/
Rating: 3
HASH_AJ example is wrong May 08, 2002
Reviewer: MDz from Santa Clara, CA USA
The hash_aj-hint example above is not correct, and the explain plan shows it.
If the hash join worked, the plan would show something like this for the join
operation:

HASH JOIN                ANTI
  INDEX                  ...

and not FILTER as showed above.  To make the HASH ANTI-JOIN work, you need to
follow these guidelines:

1. Put the /*+ hash_aj */ hint in the subquery, not in the main query.  Oracle
"Tuning" documentation explains this.

2. Make sure that OBJECT_ID is not null, both in main query and in subquery.  If
there is no NOT NULL constraint in t and t2, then add "OBJECT_ID IS NOT NULL"
conditions.

Disclaimer: I am using Oracle 8.1.7
Rating: 3
A nudge in the right direction, please? July 22, 2003
Reviewer: Matt from Australia
I'm having similar difficulties tuning an outer join, do you have any
suggestions?

SQL> alter session set sort_area_size=180000000;

Session altered.

SQL> set autotrace traceonly explain statistics
SQL> set timing on
SQL> variable days number
SQL> exec :days := 15

PL/SQL procedure successfully completed.

SQL> alter session set optimizer_mode=choose;

Session altered.

Elapsed: 00:00:00.70
  1      SELECT ROWID,JOB_SID,FILE_STATUS,PARTICIPANT_SID,REPORT_TYPE,
  2
DESTINATION_ALIAS,REPORT_RUN_SID,OS_EXIT_CODE,EVENT_DESCRIPTION,FILE_NAME
  3      FROM
  4       TMP_REPORT_FILE_SET
  5      WHERE 1=1
  6      AND scheduled_start_datetime > sysdate - :days
  7*     order by REPORT_TYPE,JOB_SID
SQL> /

3584 rows selected.

Elapsed: 00:00:45.91

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=24607 Card=1179778
Bytes=156910474)
   1    0   SORT (ORDER BY) (Cost=24607 Card=1179778 Bytes=156910474)
   2    1     HASH JOIN (Cost=1216 Card=1179778 Bytes=156910474)
   3    2       HASH JOIN (OUTER) (Cost=533 Card=118714 Bytes=7597696)
   4    3         INDEX (FAST FULL SCAN) OF 'J_TMP2' (NON-UNIQUE) (Cost=507
Card=118714 Bytes=1899424)
   5    3         TABLE ACCESS (FULL) OF 'SYSTEM_EVENT' (Cost=1 Card=1162
Bytes=55776)
   6    2       TABLE ACCESS (FULL) OF 'REPORT_FILE' (Cost=562 Card=1178112
Bytes=81289728)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      17601  consistent gets
      17552  physical reads
          0  redo size
     391472  bytes sent via SQL*Net to client
      47065  bytes received via SQL*Net from client
        240  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
       3584  rows processed
@ dbutil_show_cbo_parameter.sql
HOST_NAME                      VERSION         INSTANCE   STARTUP_TIME
CURRENT_TIME
------------------------------ --------------- ---------- --------------------
--------------------
xxxxxx.xx.xxxxxx.xxx           9.0.1.4.0       XXXX      23/07/2003 05:03.44
23/07/2003 11:57.37

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
cursor_sharing                       string      EXACT
db_block_size                        integer     8192
db_file_multiblock_read_count        integer     32
hash_area_size                       integer     131072
hash_join_enabled                    boolean     TRUE
optimizer_features_enable            string      9.0.1
optimizer_index_caching              integer     0
optimizer_index_cost_adj             integer     100
optimizer_max_permutations           integer     2000
optimizer_mode                       string      RULE
parallel_broadcast_enabled           boolean     FALSE
partition_view_enabled               boolean     TRUE
query_rewrite_enabled                boolean     FALSE
sort_area_size                       integer     65536
star_transformation_enabled          string      FALSE

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
pga_aggregate_target                 big integer 0
workarea_size_policy                 string      MANUAL

The view is defined as:

CREATE OR REPLACE FORCE VIEW "TMP_REPORT_FILE_SET" ("REPORT_RUN_SID",
"JOB_SID", "REPORT_TYPE", "FILE_STATUS", "FILE_NAME", "DESTINATION_ALIAS",
"PARTICIPANT_SID", "OS_EXIT_CODE", "EVENT_DESCRIPTION",
"SCHEDULED_START_DATETIME") AS
SELECT RF.REPORT_RUN_SID, RF.JOB_SID,
RF.REPORT_TYPE, RF.FILE_STATUS, RF.FILE_NAME,
RF.DESTINATION_ALIAS, RF.PARTICIPANT_SID,
J.OS_EXIT_CODE,
SE.EVENT_DESCRIPTION,
J.SCHEDULED_START_DATETIME
FROM REPORT_FILE RF, SYSTEM_EVENT SE, JOB J
WHERE J.JOB_SID = RF.JOB_SID
   AND SE.EVENT_SID (+) = J.OS_EXIT_CODE;

The index being used is created as:

CREATE INDEX "J_TMP2" ON "JOB" ("OS_EXIT_CODE",
"SCHEDULED_START_DATETIME","JOB_SID")
COMPRESS 1
PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS  TABLESPACE "USERS" ;

The optimizer statistics are current.

The query is used by a client application, so should really be optimized for
FIRST_ROWS(10). However, the sort of the resultset negates any advantage of this
approach. Optimizing for throughput gives better overall times.

I am a little stuck - I started to look at parallelizing the FAST FULL SCAN
(J_TMP2 index) and FULL table scan (report_file) and got this query down to 27
seconds (the original query was taking nearly 400 seconds using RBO!). However,
some of the system these queries came from currently uses the RBO and I would
prefer not to go down this path and impact these parts of the system if
possible.

Given the information I have proivided, in particular reducing the impact of the
RBO part of the system, can you please suggest some alternatives to tuning the
statement above?

Thanks in advance,
Followup:
how does:

CREATE OR REPLACE FORCE VIEW "TMP_REPORT_FILE_SET"
("REPORT_RUN_SID","JOB_SID", "REPORT_TYPE", "FILE_STATUS", "FILE_NAME",
 "DESTINATION_ALIAS", "PARTICIPANT_SID", "OS_EXIT_CODE", "EVENT_DESCRIPTION",
 "SCHEDULED_START_DATETIME")
AS
SELECT RF.REPORT_RUN_SID,
       RF.JOB_SID,
       RF.REPORT_TYPE,
       RF.FILE_STATUS,
       RF.FILE_NAME,
       RF.DESTINATION_ALIAS,
       RF.PARTICIPANT_SID,
       J.OS_EXIT_CODE,
       (select event_description
          from system_event
         where event_sid = j.os_exit_code ) EVENT_DESCRIPTION,
       J.SCHEDULED_START_DATETIME
  FROM REPORT_FILE RF,
       JOB J
 WHERE J.JOB_SID = RF.JOB_SID
/

work?
Rating: 3
Followup on your request. July 23, 2003
Reviewer: Matt from Australia
With the outer join removed, it seems to be performing the same. See below for
the same test case, with the buffer cache warmed up. I will include a tkprof
report for the same test as well.

CREATE OR REPLACE FORCE VIEW "TK_REPORT_FILE_SET"
("REPORT_RUN_SID","JOB_SID", "REPORT_TYPE", "FILE_STATUS", "FILE_NAME",
 "DESTINATION_ALIAS", "PARTICIPANT_SID", "OS_EXIT_CODE", "EVENT_DESCRIPTION",
 "SCHEDULED_START_DATETIME")
AS
SELECT RF.REPORT_RUN_SID,
       RF.JOB_SID,
       RF.REPORT_TYPE,
       RF.FILE_STATUS,
       RF.FILE_NAME,
       RF.DESTINATION_ALIAS,
       RF.PARTICIPANT_SID,
       J.OS_EXIT_CODE,
       (select event_description
          from system_event
         where event_sid = j.os_exit_code ) EVENT_DESCRIPTION,
       J.SCHEDULED_START_DATETIME
  FROM REPORT_FILE RF,
       JOB J
 WHERE J.JOB_SID = RF.JOB_SID
/

SQL> l
  1  SELECT ROWID,JOB_SID,FILE_STATUS,PARTICIPANT_SID,REPORT_TYPE,
  2  DESTINATION_ALIAS,REPORT_RUN_SID,OS_EXIT_CODE,EVENT_DESCRIPTION,FILE_NAME
  3  FROM TK_REPORT_FILE_SET
  4  where 1=1
  5  AND scheduled_start_datetime > sysdate - :days
  6* order by REPORT_TYPE,JOB_SID
SQL> /

3584 rows selected.

Elapsed: 00:00:43.71

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=16598 Card=1178108
Bytes=100139180)
   1    0   SORT (ORDER BY) (Cost=16598 Card=1178108 Bytes=100139180)
   2    1     HASH JOIN (Cost=1114 Card=1178108 Bytes=100139180)
   3    2       INDEX (FAST FULL SCAN) OF 'J_TMP2' (NON-UNIQUE) (Cost=507
Card=118714 Bytes=1899424)
   4    2       TABLE ACCESS (FULL) OF 'REPORT_FILE' (Cost=562 Card=1178112
Bytes=81289728)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      17592  consistent gets
      17541  physical reads
          0  redo size
     391472  bytes sent via SQL*Net to client
      47065  bytes received via SQL*Net from client
        240  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
       3584  rows processed

I also tried removing the sort, but the results were similar.

Here is the tkprof:

********************************************************************************

 SELECT ROWID,JOB_SID,FILE_STATUS,PARTICIPANT_SID,REPORT_TYPE,
 DESTINATION_ALIAS,REPORT_RUN_SID,OS_EXIT_CODE,EVENT_DESCRIPTION,FILE_NAME
 FROM TK_REPORT_FILE_SET
 where 1=1
 AND scheduled_start_datetime > sysdate - :days
order by REPORT_TYPE,JOB_SID

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.04       2.41          0          0          0           0
Execute      1      0.00       0.02          0          0          0           0
Fetch       25     21.46      50.46      17541      17592          0        3584
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       27     21.51      52.90      17541      17592          0        3584

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 100

Rows     Row Source Operation
-------  ---------------------------------------------------
   3584  SORT ORDER BY
   3584   HASH JOIN
  18816    INDEX FAST FULL SCAN (object id 43141)
1178112    TABLE ACCESS FULL REPORT_FILE

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                      25        0.00          0.00
  db file scattered read                        558        0.09         29.95
  db file sequential read                         4        0.03          0.09
  latch free                                      1        0.02          0.02
  SQL*Net message from client                    25        1.07          1.64
  SQL*Net more data to client                   167        0.01          0.01
********************************************************************************

From the wait events I am concluding that the full scan of report_file is the
problem (surprisingly (to me) the INDEX FAST FULL SCAN appears to be relatively
inexpensive).

So, for now I think I am looking for a way to index report_file ( before I put
the outer join back in).

I just re-tested with the following index:

create index mgb_tmp on report_file(report_type, job_sid) compress 1;

I expected this to allow the optimizer to use the index for the join, before
going to the report file table for the rest of the column data. I also wondered
if it might effect the sort.

However, report_file has the following indexes which already include job_sid:

INDEXES FOR TABLE: REPORT_FILE
================================================

INDEX_NAME                     COLUMN_NAME                    COLUMN_POSITION
------------------------------ ------------------------------ ---------------
MGB_TMP                        REPORT_TYPE                                  1
                               JOB_SID                                      2
REPORT_FILE_INDX2              REPORT_RUN_SID                               1
                               JOB_SID                                      2
                               FILE_NAME                                    3
REPORT_FILE_INDX3              JOB_SID                                      1
                               FILE_NAME                                    2
REPORT_FILE_INDX4              FILE_STATUS                                  1
                               DESTINATION_ALIAS                            2
REPORT_FILE_INDX_PK            FILE_NAME                                    1
                               JOB_SID                                      2

In conclusion, I am still seeing the same behaviour as described above in the
autotrace and tkprof.

Do you agree that the FTS is the problem here?

Do you have any suggestions?
Followup:
don't have time to fully investigate right now -- try back next week (on
vacation - -just sort of "glancing" at things)
Rating: 3
Coolio. July 23, 2003
Reviewer: Matt from Australia
No worries Tom, I shall update this again next week.

Surely, a holiday means an absence of work? I think you need a real holiday!

M.
Rating: 3
Tuning query (reference previous few posts). July 28, 2003
Reviewer: Matt Butler from Australia
To summarise the above few posts.

I am trying to tune a query that uses an outer join. I have tried several
approaches and am unable to get the required query performance (original query
took 350+ seconds, currently the query takes 45 seconds). After your previous
suggestion to change the query I have concluded that the bottleneck is the FTS
access to the report_file table. I do not seem to be able to get the CBO to use
any index on the table.

I have managed to get the query to run in under 10 seconds by using parallel
query slaves. However, since the remainder of the system uses the RBO I do not
want to use this approach in production.

Can you please have a look at the previous posts and suggest an alternative
approach? I do not understand why the CBO refuses to use any of the indexes,
although I am sure that there are good reasons. Any ideas?

Thanks in advance.
Followup:
and exactly what index would you LIKE it to use?

Look at the query, it seems to be pulling all rows from each table and joining
-- why would an index help here (it would hurt)

If the "remainder" of the system is using RBO and that is a reason not to use PQ
-- why are you using the cbo here?  in for a penny in for a pound they say --
you are wet upto your knees already, whats another inch?
Rating: 3
Are you sure that there are no other options? July 28, 2003
Reviewer: Matt from Australia
>> and exactly what index would you LIKE it to use?

Well to be honest I was hoping to get the optimizer to pick one that contained
job_sid. I expected that the predicate on the job table (by
scheduled_start_datetime) would restrict the result from job significantly (to
be << 1%) and then the join to report_file by job_sid would use such an index
(since I expect there to be few rows that satisfy the join). I have tried a
single index on job_sid for this purpose....didn't work.

Having said all this, I now don't think that the CBO has any information about
how job and report_file are related - how could it?!? So, I think that all it
can do is full scan.

>> Look at the query,
>>  it seems to be pulling all rows from each table and >> joining
>> -- why would an index help here (it would hurt)

I understand what you are saying, but this is the query on the view that we are
talking about:

  1      SELECT ROWID,JOB_SID,FILE_STATUS,PARTICIPANT_SID,REPORT_TYPE,
  2
DESTINATION_ALIAS,REPORT_RUN_SID,OS_EXIT_CODE,EVENT_DESCRIPTION,FILE_NAME
  3      FROM
  4       TMP_REPORT_FILE_SET
  5      WHERE 1=1
  6      AND scheduled_start_datetime > sysdate - :days
  7*     order by REPORT_TYPE,JOB_SID
SQL> /

It pulls back 3584 rows - NOT all the data. Have a look at the tkprof, do you
still stand by your comments? Is there really no way to improve this query?

>>If the "remainder" of the system is using RBO and that is
>>a reason not to use PQ
>>-- why are you using the cbo here?  in for a penny in for
>>a pound they say --
>>you are wet upto your knees already, whats another inch?

We are still in the process of migrating from the RBO to the CBO after an
upgrade from 7.3 (we are moving a module at a time using an 'alter session'
approach) . Any tuning that I do, I try and move more of the system over to the
CBO. The reason I don't want to parallize the query is that I want to control
the scope of change and the required testing - I do not want parts of the system
to suddenly start altering their behaviour (queries that access tables with a
parallel setting use the CBO) - I have seen RBO queries that run well turn into
CBO queries that run not so well. This is not necessarily the fault of the CBO,
but relates to parts of the system joining to DD views (which can't have stats),
and finding the correct values for the optimizer_index_caching,
optimizer_index_cost_adj parameters.

This was the last execution plan I posted:

Rows     Row Source Operation
-------  ---------------------------------------------------
   3584  SORT ORDER BY
   3584   HASH JOIN
  18816    INDEX FAST FULL SCAN (object id 43141)
1178112    TABLE ACCESS FULL REPORT_FILE

I just read the Performance doco on hash joins and can see that there is nothing
that can be done to speed this join up, unless it is possible to restrict the
report_file data by adding something to the predicate.

Are you sure that there are no other options?

Thanks again,
Followup:
and if you use first_rows?
Rating: 4
Tuning July 29, 2003
Reviewer: Santhanam from India
Good example
Rating: 2
More Information and results... July 29, 2003
Reviewer: Matt from Australia
OK, from the top (search down for "Start of Test" to see the results)...

Datatypes for Table JOB

                               Data                 Data
Column Name                    Type                 Length      Nullable
------------------------------ -------------------- ----------- --------
JOB_SID                        NUMBER               (10,0)      not null
JOB_STATUS                     VARCHAR2             1           null
JOB_QUEUE_SID                  NUMBER               (10,0)      null
JOB_TYPE                       VARCHAR2             10          null
USER_SID                       NUMBER               (10,0)      null
PARENT_JOB_SID                 NUMBER               (10,0)      null
SCHEDULED_START_DATETIME       DATE                 7           not null
ACTUAL_START_DATETIME          DATE                 7           null
ACTUAL_COMPLETION_DATETIME     DATE                 7           null
MAX_RUN_TIME                   NUMBER               (6,0)       null
LAST_UPDATE_DATETIME           DATE                 7           null
OS_PID                         VARCHAR2             9           null
PARENT_OS_PID                  VARCHAR2             9           null
OS_PHYSICAL_IO                 NUMBER               (10,0)      null
OS_CPU                         NUMBER               (10,0)      null
OS_MEMORY                      NUMBER               (10,0)      null
OS_EXIT_CODE                   NUMBER               (5,0)       null
JOB_ABBREVIATED_PARAMS         VARCHAR2             2000        null

Indexes on JOB

Index                          Is
Name                           Unique COLUMNS
------------------------------ ------ --------------------------------
JOB_INDX2                      Yes    PARENT_JOB_SID, JOB_SID
JOB_INDX3                      No     JOB_TYPE
JOB_INDX4                      Yes    JOB_SID, JOB_STATUS
JOB_INDX5                      Yes    JOB_STATUS, JOB_SID
JOB_INDX6                      Yes    OS_PID, JOB_SID
JOB_INDX7                      No     JOB_QUEUE_SID, JOB_STATUS,
                                      SCHEDULED_START_DATETIME

JOB_INDX8                      No     OS_EXIT_CODE, JOB_SID
JOB_INDX9                      No     OS_EXIT_CODE,
                                      SCHEDULED_START_DATETIME,
                                      JOB_SID

JOB_INDX_PK                    Yes    JOB_SID

Datatypes for Table REPORT_FILE

                               Data                 Data
Column Name                    Type                 Length      Nullable
------------------------------ -------------------- ----------- --------
FILE_NAME                      VARCHAR2             255         not null
JOB_SID                        NUMBER               (10,0)      not null
REPORT_RUN_SID                 NUMBER               (10,0)      null
PARTICIPANT_SID                NUMBER               (10,0)      null
REPORT_TYPE                    VARCHAR2             10          null
DESTINATION_ALIAS              VARCHAR2             10          null
FILE_STATUS                    VARCHAR2             1           null

Indexes on REPORT_FILE

Index                          Is
Name                           Unique COLUMNS
------------------------------ ------ --------------------------------
REPORT_FILE_INDX2              No     REPORT_RUN_SID, JOB_SID,
                                      FILE_NAME

REPORT_FILE_INDX3              Yes    JOB_SID, FILE_NAME
REPORT_FILE_INDX4              No     FILE_STATUS, DESTINATION_ALIAS
REPORT_FILE_INDX5              No     REPORT_TYPE, JOB_SID
REPORT_FILE_INDX_PK            Yes    FILE_NAME, JOB_SID

Datatypes for Table SYSTEM_EVENT

                               Data                 Data
Column Name                    Type                 Length      Nullable
------------------------------ -------------------- ----------- --------
EVENT_SID                      NUMBER               (10,0)      not null
EVENT_SYMBOL                   VARCHAR2             20          not null
EVENT_DESCRIPTION              VARCHAR2             80          null
ARGUMENT1_DESCRIPTION          VARCHAR2             40          null
ARGUMENT2_DESCRIPTION          VARCHAR2             40          null
ARGUMENT3_DESCRIPTION          VARCHAR2             40          null
CONTEXT_DESCRIPTION            VARCHAR2             40          null
EXIT_STATUS_DESCRIPTION        VARCHAR2             80          null

Indexes on SYSTEM_EVENT

Index                          Is
Name                           Unique COLUMNS
------------------------------ ------ --------------------------------
SYSTEM_EVENT_INDX2             Yes    EVENT_SYMBOL
SYSTEM_EVENT_INDX3             Yes    EVENT_SYMBOL, EVENT_SID
SYSTEM_EVENT_INDX_PK           Yes    EVENT_SID

TABLE STATISTICS FOR : JOB
===================================================

  NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED
---------- ---------- ----------- -------------------
   2374272      58370         100 07/22/2003 16:23:44

COLUMN STATISTICS FOR : JOB
====================================================

COLUMN_NAME                    NUM_DISTINCT  NUM_NULLS NUM_BUCKETS
DENSITY LAST_ANALYZED
------------------------------ ------------ ---------- -----------
---------------- -------------------
JOB_QUEUE_SID                             5          0           4
.0000002106 07/21/2003 17:26:33
JOB_SID                             2374272          0          75
.0000004212 07/21/2003 17:26:33
JOB_STATUS                                3          0           2
.0000002106 07/21/2003 17:26:33
JOB_TYPE                                 61          0          28
.0104306613 07/21/2003 17:26:33
OS_EXIT_CODE                             24          0           1
.0416666667 07/22/2003 16:23:44
OS_PID                                16704      97792          75
.0000636187 07/21/2003 17:26:33
PARENT_JOB_SID                         3805    1332096          57
.0018233225 07/21/2003 17:26:33
SCHEDULED_START_DATETIME              12612          0          75
.0004318452 07/21/2003 17:26:33

8 rows selected.

INDEX STATISTICS FOR : JOB
===================================================

NAME                             NUM_ROWS   DISTINCT LEAF_BLOCKS         CF
LEVEL   ALFBPKEY LAST_ANALYZED
------------------------------ ---------- ---------- ----------- ----------
---------- ---------- -------------------
JOB_INDX2                         2374272    2374272        6370     551703
    2          1 07/21/2003 17:26:56
JOB_INDX3                         2374272         61        6784     331214
    2        111 07/21/2003 17:27:17
JOB_INDX4                         2374272    2374272        5966      58926
    2          1 07/21/2003 17:27:38
JOB_INDX5                         2374272    2374272        5966      76198
    2          1 07/21/2003 17:27:58
JOB_INDX6                         2374272    2374272        7299    2280191
    2          1 07/21/2003 17:28:17
JOB_INDX7                         2374272      13095        7968    1706109
    2          1 07/21/2003 17:28:41
JOB_INDX8                         2374272    2374272        6850     179906
    2          1 07/21/2003 17:29:07
JOB_INDX9                         2374272    2374272        8304    1668315
    2          1 07/22/2003 16:28:05
JOB_INDX_PK                       2374272    2374272        4763      58926
    2          1 07/21/2003 17:26:35

9 rows selected.

TABLE STATISTICS FOR : REPORT_FILE
===================================================

  NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED
---------- ---------- ----------- -------------------
   1178112       9207         100 07/21/2003 17:41:20

COLUMN STATISTICS FOR : REPORT_FILE
====================================================

COLUMN_NAME                    NUM_DISTINCT  NUM_NULLS NUM_BUCKETS
DENSITY LAST_ANALYZED
------------------------------ ------------ ---------- -----------
---------------- -------------------
DESTINATION_ALIAS                         4          0           3
.0000004244 07/21/2003 17:41:20
FILE_NAME                           1177487          0          75
.0000008611 07/21/2003 17:41:20
FILE_STATUS                               3    1156864           2
.0000235316 07/21/2003 17:41:20
JOB_SID                                4576          0          75
.0002221131 07/21/2003 17:41:20
REPORT_RUN_SID                         1152      28672          46
.0037663495 07/21/2003 17:41:20

INDEX STATISTICS FOR : REPORT_FILE
===================================================

NAME                             NUM_ROWS   DISTINCT LEAF_BLOCKS         CF
LEVEL   ALFBPKEY LAST_ANALYZED
------------------------------ ---------- ---------- ----------- ----------
---------- ---------- -------------------
REPORT_FILE_INDX2                 1178112    1178112        6928    1171692
    2          1 07/21/2003 17:42:34
REPORT_FILE_INDX3                 1178112    1178112        5950    1171669
    2          1 07/21/2003 17:43:02
REPORT_FILE_INDX4                 1178112          9        3282      20663
    2        364 07/21/2003 17:43:20
REPORT_FILE_INDX5                 1178112       4577        2804    1171782
    2          1 07/24/2003 09:51:06
REPORT_FILE_INDX_PK               1178112    1178112       10148     232016
    2          1 07/21/2003 17:41:20

TABLE STATISTICS FOR : SYSTEM_EVENT
===================================================

  NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED
---------- ---------- ----------- -------------------
      1162         15         100 07/21/2003 17:43:32

COLUMN STATISTICS FOR : SYSTEM_EVENT
====================================================

COLUMN_NAME                    NUM_DISTINCT  NUM_NULLS NUM_BUCKETS
DENSITY LAST_ANALYZED
------------------------------ ------------ ---------- -----------
---------------- -------------------
EVENT_SID                              1162          0          75
.0008605852 07/21/2003 17:43:32
EVENT_SYMBOL                           1162          0          75
.0008605852 07/21/2003 17:43:32

INDEX STATISTICS FOR : SYSTEM_EVENT
===================================================

NAME                             NUM_ROWS   DISTINCT LEAF_BLOCKS         CF
LEVEL   ALFBPKEY LAST_ANALYZED
------------------------------ ---------- ---------- ----------- ----------
---------- ---------- -------------------
SYSTEM_EVENT_INDX2                   1162       1162           4        627
    1          1 07/21/2003 17:43:32
SYSTEM_EVENT_INDX3                   1162       1162           5        627
    1          1 07/21/2003 17:43:32
SYSTEM_EVENT_INDX_PK                 1162       1162           5         49
    1          1 07/21/2003 17:43:32

CREATE OR REPLACE FORCE VIEW "TK_REPORT_FILE_SET"
("REPORT_RUN_SID","JOB_SID", "REPORT_TYPE", "FILE_STATUS", "FILE_NAME",
 "DESTINATION_ALIAS", "PARTICIPANT_SID", "OS_EXIT_CODE", "EVENT_DESCRIPTION",
 "SCHEDULED_START_DATETIME")
AS
SELECT RF.REPORT_RUN_SID,
       RF.JOB_SID,
       RF.REPORT_TYPE,
       RF.FILE_STATUS,
       RF.FILE_NAME,
       RF.DESTINATION_ALIAS,
       RF.PARTICIPANT_SID,
       J.OS_EXIT_CODE,
       (select event_description
          from system_event
         where event_sid (+) = j.os_exit_code ) EVENT_DESCRIPTION,
       J.SCHEDULED_START_DATETIME
  FROM REPORT_FILE RF,
       JOB J
 WHERE J.JOB_SID = RF.JOB_SID

*************
Start of Test
*************

alter session set sort_area_size = 180000000;

Session altered.

********************************************************************************

SELECT /*+ ALL_ROWS */ ROWID,JOB_SID,FILE_STATUS,PARTICIPANT_SID,REPORT_TYPE,
  DESTINATION_ALIAS,REPORT_RUN_SID,OS_EXIT_CODE,EVENT_DESCRIPTION,FILE_NAME
FROM
 TK_REPORT_FILE_SET
WHERE 1=1
 AND scheduled_start_datetime > sysdate - :days
 AND report_type = :report_type
order by REPORT_TYPE,JOB_SID

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       10     19.79      41.90       8334      21457          0        1280
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       12     19.79      41.90       8334      21457          0        1280

Misses in library cache during parse: 0
Optimizer goal: ALL_ROWS
Parsing user id: 100

Rows     Row Source Operation
-------  ---------------------------------------------------
   1280  SORT ORDER BY
   1280   HASH JOIN
  13056    TABLE ACCESS BY INDEX ROWID REPORT_FILE
  13056     INDEX RANGE SCAN (object id 43184)
 251648    INDEX FAST FULL SCAN (object id 43141)

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                      10        0.00          0.00
  db file sequential read                         4        0.00          0.00
  db file scattered read                        265        1.88         15.45
  latch free                                      1        0.01          0.01
  SQL*Net message from client                    10        4.36          5.88
  SQL*Net more data to client                    59        0.00          0.00
********************************************************************************
********************************************************************************

SELECT /*+ FIRST_ROWS */ ROWID,JOB_SID,FILE_STATUS,PARTICIPANT_SID,REPORT_TYPE,
  DESTINATION_ALIAS,REPORT_RUN_SID,OS_EXIT_CODE,EVENT_DESCRIPTION,FILE_NAME
FROM
 TK_REPORT_FILE_SET
WHERE 1=1
 AND scheduled_start_datetime > sysdate - :days
 AND report_type = :report_type
order by REPORT_TYPE,JOB_SID

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.03          0          0          0           0
Execute      1      0.00       0.14          0          0          0           0
Fetch       10      2.53       3.81         31      39263          0        1280
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       12      2.53       4.00         31      39263          0        1280

Misses in library cache during parse: 0
Optimizer goal: FIRST_ROWS
Parsing user id: 100

Rows     Row Source Operation
-------  ---------------------------------------------------
   1280  TABLE ACCESS BY INDEX ROWID JOB
  26113   NESTED LOOPS
  13056    TABLE ACCESS BY INDEX ROWID REPORT_FILE
  13056     INDEX RANGE SCAN (object id 43184)
  13056    INDEX RANGE SCAN (object id 43060)

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                      10        0.00          0.00
  db file sequential read                        31        0.07          0.19
  SQL*Net message from client                    10        0.09          0.16
  SQL*Net more data to client                    59        0.01          0.02
********************************************************************************
********************************************************************************

SELECT /*+ FIRST_ROWS(10) */
ROWID,JOB_SID,FILE_STATUS,PARTICIPANT_SID,REPORT_TYPE,
  DESTINATION_ALIAS,REPORT_RUN_SID,OS_EXIT_CODE,EVENT_DESCRIPTION,FILE_NAME
FROM
 TK_REPORT_FILE_SET
WHERE 1=1
 AND scheduled_start_datetime > sysdate - :days
 AND report_type = :report_type
order by REPORT_TYPE,JOB_SID

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.04       1.77          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch       10     23.43      68.58      16073      17618          0        1280
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       12     23.48      70.36      16073      17618          0        1280

Misses in library cache during parse: 1
Optimizer goal: FIRST_ROWS
Parsing user id: 100

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                      10        0.00          0.00
  db file sequential read                       130        0.07          0.97
  db file scattered read                       1362        0.12         29.67
  SQL*Net message from client                     9        0.02          0.11
  SQL*Net more data to client                    59        0.00          0.00
********************************************************************************

repdlvr@NDEVT.WORLD> l
  1  select object_id, object_name from user_objects
  2  where object_id in (43184,43141,
  3* 43184,43060)
repdlvr@NDEVT.WORLD> /

 OBJECT_ID OBJECT_NAME
---------- ------------------------------
     43060 JOB_INDX_PK
     43141 JOB_INDX9
     43184 REPORT_FILE_INDX5

*********************
Summary and Questions
*********************

FIRST_ROWS hinted query runs in 4 seconds.

Now I am simply confused. This now behaves nothing like I had expected.
Previously, (prior to the view re-write and new indexes) I had concluded that I
needed to optimize with ALL_ROWS, since get the rows to the screen I needed to
do the sort quickly and therefore optimize for throughput (ie: get all the
resultset for the sort asap). However, I think that the new indexes are now
being used - and so the sort is done by reading the indexes in order.

Questions:
==========

1) How does the outer join in the view work - you suggested placing this in the
select section rather than the join section in an earlier post.
2) Why don't I see an execution plan in the tkprof for the second FIRST_ROWS(10)
optimization?
3) Why does the FIRST_ROWS(10) optimization run significantly slower than
FIRST_ROWS?
4) I read somewhere (not sure where, but NOT in the Oracle Preformance doco)
that FIRST_ROWS uses heuristics and rules to generate the plan. The Oracle doco
suggests that this (FIRST_ROWS) is the same as FIRST_ROWS(1). So I
re-tested...Can you explain this (it suggests that FIRST_ROWS != FIRST_ROWS(1) )
:

********************************************************************************

SELECT /*+ FIRST_ROWS(1) */
ROWID,JOB_SID,FILE_STATUS,PARTICIPANT_SID,REPORT_TYPE,
  DESTINATION_ALIAS,REPORT_RUN_SID,OS_EXIT_CODE,EVENT_DESCRIPTION,FILE_NAME
FROM
 TK_REPORT_FILE_SET
WHERE 1=1
 AND scheduled_start_datetime > sysdate - :days
 AND report_type = :report_type
order by REPORT_TYPE,JOB_SID

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.52          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch       10     21.08      46.43      16072      17618          0        1280
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       12     21.10      46.96      16072      17618          0        1280

Misses in library cache during parse: 1
Optimizer goal: FIRST_ROWS
Parsing user id: 100

Rows     Row Source Operation
-------  ---------------------------------------------------
   1280  SORT ORDER BY
   1280   HASH JOIN
  13056    TABLE ACCESS FULL REPORT_FILE
 251648    INDEX FAST FULL SCAN (object id 43141)

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                      10        0.00          0.00
  db file scattered read                       1362        0.06         26.13
  db file sequential read                       129        0.02          0.70
  SQL*Net message from client                    10      104.12        104.20
  SQL*Net more data to client                    59        0.00          0.00
********************************************************************************

Thanks in advance
Followup:
1) i didn't suggest the outer join that way -- mine was:

       J.OS_EXIT_CODE,
       (select event_description
          from system_event
         where event_sid = j.os_exit_code ) EVENT_DESCRIPTION,
       J.SCHEDULED_START_DATETIME
  FROM REPORT_FILE RF,

it is just that instead of:

select emp.ename, dept.dname
  from emp, dept
 where emp.deptno = dept.deptno(+)

you can query:

select emp.ename, (select dname from dept where dept.deptno = emp.deptno) dname
  from emp
/

and that sometimes can open up query plan alterantives that were not possible
before -- it is a simple query rewrite that I try frequently.

2) you should, if you exited the sqlplus session BEFORE you ran tkprof.  we
write the STAT records out when the cursors are closed.

does it reproduce that you do not get a plan?

3) we'd have to see the plan

4) that does look funny - i would never expect to see a hash join in a
first_rows(1).  but based on the consistent gets and such -- it looks like
first_rows(1) and first_rows(10) are actually generating the same plan.

what you can do is search this site for 10053, and play with that to see if
anything pops out -- but, please don't post a trace file like that here (they
get largish)
Rating: 3
Thanks yet again... July 30, 2003
Reviewer: Matt from Australia
In response to your response to my question 1) :o)

I couldn't find any documentation on this ability to include a select in a the
select list. Can you reference any? I obviously have a bug in my query that I
must fix.

In response to your response to my question 2)

I always make sure that I exit the session for this very reason. I have
encountered this before during this tuning exercise, especially when consecutive
queries use the FIRST_ROWS and FIRST_ROWS(n) hints. I will have to verify
whether this is consistently reproducible. I assume that this MAY be a bug? If
so I will contact support witha test case.

In response to your response to my question 4)

I will investigate this a little further with the 10053 trace. I won't generate
any other long posts like this one - I just figured that the stats might be
relevant. I certainly wouldn't post a multi-MB file here.

In your experience should FIRST_ROWS and FIRST_ROWS(1) generate the same plan?

Thanks yet again,
Followup:
it is called a scalar subquery.

see
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96540/expressions.htm#1037302
It would be not unbelievable that first_rows and first_rows(1) generate
different plans -- they are generally "equivalent" but obviously different.

It is interesting to note that:

....<cut and paste source = 9ir2 perf guide>...

The optimizer ignores this hint in DELETE and UPDATE statement blocks and in
SELECT statement blocks that contain any of the following syntax:

Set operators (UNION, INTERSECT, MINUS, UNION ALL)
GROUP BY clause
FOR UPDATE clause
Aggregate functions
DISTINCT operator
ORDER BY clauses, when there is no index on the ordering columns
........

Now that last goody is a new one in 9ir2, not there before.  Given that the plan
for first_rows(1) and first_rows(10) are apparently the SAME and look to me to
NOT be first_rows type plans -- I'll BETCHA that is what it is here.

Hmm -- that would be interesting.  first_rows is "legacy", first_rows(n) is new
and obeys this new rule -- meaning the optimizer sees the order by and says "you
know what, I cannot get the first row fast here, hence we'll do ALL_ROWS instead
and sort the result"

and in fact, that seems to be true -- in the following, big_table is just a copy
of all_objects with a series of insert into big_table select * from big_table;
over and over until there were about 4,000,000 plus rows.  I created an index on
STATUS:

big_table@ORA920> select status, count(*) from big_table group by status order
by 2;

STATUS    COUNT(*)
------- ----------
INVALID      38512
VALID      3461488

2 rows selected.

big_table@ORA920> create index bt_status_idx on big_table(status) compute
statistics;

Index created.

Now look:

big_table@ORA920> set autotrace traceonly explain
big_table@ORA920>
big_table@ORA920> select /*+ FIRST_ROWS */ * from big_table where status =
'INVALID' order by created;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=58664 Card=1750000
Bytes=175000000)
   1    0   SORT (ORDER BY) (Cost=58664 Card=1750000 Bytes=175000000)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=31483
Card=1750000 Bytes=175000000)
   3    2       INDEX (RANGE SCAN) OF 'BT_STATUS_IDX' (NON-UNIQUE) (Cost=4155
Card=1750000)

big_table@ORA920> select /*+ FIRST_ROWS(1) */ * from big_table where status =
'INVALID' order by created;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=32042 Card=1750000
Bytes=175000000)
   1    0   SORT (ORDER BY) (Cost=32042 Card=1750000 Bytes=175000000)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=4861 Card=1750000
Bytes=175000000)

big_table@ORA920> select /*+ ALL_ROWS */ * from big_table where status =
'INVALID' order by created;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: ALL_ROWS (Cost=32042 Card=1750000
Bytes=175000000)
   1    0   SORT (ORDER BY) (Cost=32042 Card=1750000 Bytes=175000000)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=4861 Card=1750000
Bytes=175000000)

big_table@ORA920> select * from big_table where status =        'INVALID' order
by created;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=32042 Card=1750000
Bytes=175000000)
   1    0   SORT (ORDER BY) (Cost=32042 Card=1750000 Bytes=175000000)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=4861 Card=1750000
Bytes=175000000)

big_table@ORA920>
big_table@ORA920> set autotrace off

first_rows(1) and first_rows(10) are really ALL_ROWS due to the order by!

first_rows is first_rows still -- so they DO behave differently..
Rating: 4
But what about in 9.0.1? July 30, 2003
Reviewer: Matt from Australia
A couple of things:

Firstly I am using 9.0.1 (it was included in an earlier post on this thread, but
not emphasised in the last post)

For those that are interested, and consistencies sake -scalar subqueries were
new in 9i (apparently). This is the same reference for 9.0.1
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/server.901/a90125/expressions11.htm#1033552
This is the same snip from the 9.0.1 Perf doco (Optimizer Hints 5-7)

[SNIP...]
The optimizer ignores this hint in DELETE and UPDATE statement blocks and in
SELECT statement blocks that contain any of the following syntax:

o Set operators (UNION, INTERSECT, MINUS, UNION ALL)
o GROUP BY clause
o FOR UPDATE clause
o Aggregate functions

o DISTINCT operator
These statements cannot be optimized for best response time, because Oracle must
retrieve all rows accessed by the statement before returning the first row. If
you
specify this hint in any of these statements, then the optimizer uses the
cost-based
approach and optimizes for best throughput.
[...SNIP]

Which does not describe the same restriction. Also, the 9i R2 comment does not
hold in this case since I have an index on the columns in the order by:

[SNIP...]
Indexes on REPORT_FILE

Index                          Is
Name                           Unique COLUMNS
------------------------------ ------ --------------------------------
REPORT_FILE_INDX5              No     REPORT_TYPE, JOB_SID
[...SNIP]

I've re-run your test to see if the same behaviour holds in 9.0.1:

repdlvr@NDEVT.WORLD> l
  1  select status, count(*) from big_table group by status order
  2* by 2
repdlvr@NDEVT.WORLD> select status, count(*) from big_table group by status
order by 2 ;

STATUS    COUNT(*)
------- ----------
INVALID      11776
VALID     10014720

repdlvr@NDEVT.WORLD> create index bt_status_idx on big_table(status) compute
statistics ;

Index created.

repdlvr@NDEVT.WORLD> repdlvr@NDEVT.WORLD> select /*+ FIRST_ROWS */ * from
big_table where status = 'INVALID' order by created;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=161075 Card=5013248
Bytes=501324800)
   1    0   SORT (ORDER BY) (Cost=161075 Card=5013248 Bytes=501324800)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=85133
Card=5013248 Bytes=501324800)
   3    2       INDEX (RANGE SCAN) OF 'BT_STATUS_IDX' (NON-UNIQUE) (Cost=11912
Card=5013248)

select /*+ FIRST_ROWS(1) */ * from big_table where status =  'INVALID' order by
created;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=84388 Card=5013248
Bytes=501324800)
   1    0   SORT (ORDER BY) (Cost=84388 Card=5013248 Bytes=501324800)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=8446 Card=5013248
Bytes=501324800)

repdlvr@NDEVT.WORLD> select /*+ ALL_ROWS */ * from big_table where status =
'INVALID' order by created;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: ALL_ROWS (Cost=84388 Card=5013248
Bytes=501324800)
   1    0   SORT (ORDER BY) (Cost=84388 Card=5013248 Bytes=501324800)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=8446 Card=5013248
Bytes=501324800)

So, this all looks like the same behaviour you describe on 9.2 (although
undocumented for 9.0.1). I tried to reproduce the same situation as in my query,
where the index allows the sort to take place...

create index bt_status_created_idx on big_table(status,created) compute
statistics;

Index created.

repdlvr@NDEVT.WORLD> select /*+ FIRST_ROWS */ * from big_table where status =
'INVALID' order by created;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=161075 Card=5013248
Bytes=501324800)
   1    0   SORT (ORDER BY) (Cost=161075 Card=5013248 Bytes=501324800)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=85133
Card=5013248 Bytes=501324800)
   3    2       INDEX (RANGE SCAN) OF 'BT_STATUS_IDX' (NON-UNIQUE) (Cost=11912
Card=5013248)

repdlvr@NDEVT.WORLD> select /*+ ALL_ROWS */ * from big_table where status =
'INVALID' order by created;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: ALL_ROWS (Cost=84388 Card=5013248
Bytes=501324800)
   1    0   SORT (ORDER BY) (Cost=84388 Card=5013248 Bytes=501324800)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=8446 Card=5013248
Bytes=501324800)

repdlvr@NDEVT.WORLD> select /*+ FIRST_ROWS(1) */ * from big_table where status =
 'INVALID' order by created;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=84388 Card=5013248
Bytes=501324800)
   1    0   SORT (ORDER BY) (Cost=84388 Card=5013248 Bytes=501324800)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=8446 Card=5013248
Bytes=501324800)

However, I am seeing the same behaviour. Maybe this is because the select
requires all the rows in the table.

Any comments?

Thanks again.
Followup:
first_rows(n) and first_rows are different -- it seems you have what you need,
use "first_rows"?
Rating: 4
first_rows(n) August 06, 2003
Reviewer: John from Singapore
Tom,

is first_rows(n) hint simular to writing the query with rownum < n.

i.e. the below queries are same performance wise:

select * from emp where rownum < 100;

select /*+ first_rows(n) */ * from emp;
Followup:
No, where rownum < n is a predicate.  first_rows(n) specifies an optimization
technique.

for such trivial queries, yes, I would expect the same exact plans to be
generated.
Rating: 4
Documentation Bug? August 06, 2003
Reviewer: Steve from New York
Matt from Australia Et Al,
Scalar subqueries were added in 8i.

SELECT
    (SELECT version FROM v$instance) db_version,
    (SELECT COUNT(*) FROM emp)     emp_count
FROM
    dual
DB_VERSION        EMP_COUNT
----------------- ---------
8.1.7.4.0                14

This link
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96540/expressions11a.htm
Says that scalar subqueries are not valid expressions in the following places:
-In WHEN conditions of CASE expressions
-In GROUP BY and HAVING clauses

This is incorrect.

SELECT
    empno,
    deptno,
    COUNT(*) OVER(PARTITION BY deptno) emp_count,
    CASE
      WHEN (SELECT COUNT(*) FROM emp WHERE deptno = e.deptno) > 3
      THEN 'More Than 3 Emps In Deptno ' || e.deptno
      ELSE '3 or Fewer Emps In Deptno '  || e.deptno
    END    dept_status
FROM
    emp e
   EMPNO   DEPTNO EMP_COUNT DEPT_STATUS
-------- -------- --------- --------------------------------
    7782       10         3 3 or Fewer Emps In Deptno 10
    7839       10         3 3 or Fewer Emps In Deptno 10
    7934       10         3 3 or Fewer Emps In Deptno 10
    7369       20         5 More Than 3 Emps In Deptno 20
    7876       20         5 More Than 3 Emps In Deptno 20
    7902       20         5 More Than 3 Emps In Deptno 20
    7788       20         5 More Than 3 Emps In Deptno 20
    7566       20         5 More Than 3 Emps In Deptno 20
    7499       30         6 More Than 3 Emps In Deptno 30
    7698       30         6 More Than 3 Emps In Deptno 30
    7654       30         6 More Than 3 Emps In Deptno 30
    7900       30         6 More Than 3 Emps In Deptno 30
    7844       30         6 More Than 3 Emps In Deptno 30
    7521       30         6 More Than 3 Emps In Deptno 30

SELECT
    deptno,
    COUNT(*) emp_count
FROM
    emp e
GROUP BY
    deptno
HAVING
    (SELECT COUNT(*) FROM emp WHERE deptno = e.deptno) > 4

  DEPTNO EMP_COUNT
-------- ---------
      20         5
      30         6
Rating: 4
page m of n August 07, 2003
Reviewer: John
to display the records of a query pagewise,

we normally use this technique:

select * from
(
select a.empno,a.ename,rownum row_number from emp a
WHERE rownum <= i_page_number * i_rows_per_page
)
WHERE row_number > (i_page_number - 1) * i_rows_per_page;

in the above query, can we make use of first_rows(n) hint?

if yes, the value of n be a variable?
Followup:
you would have to use dynamic sql as the N cannot be bound (they would all have
the same plan).  So, you would have as many cursors in the pool as you had
distinct values for N
Rating: 5
Outer join only for certain values of join key ? August 15, 2003
Reviewer: Steve from CA
Hi,

I am wondering if it is possible to perform outer join only for certain values
of the join key. Let me explain.

If the DEPTNO column in the EMP table is SCOTT schema could have NULL values, I
would like a query to generate NULLs for the corresponding DEPT values. So, the
outer join should be in effect only when the EMP.DEPTNO value is NULL. If there
is mismatch in the join keys, it should not produce the a row with NULL DEPT
values.

The following query will generate NULL values for DEPT table
if EMP.DEPTNO is NULL or if there there no row in DEPT table that matched the
EMP.DEPTNO value.

  SELECT emp.*, dept.* FROM emp, dept
  WHERE emp.deptno = dept.deptno(+);

Although the following syntax is not allowed, this would have produced the
desired results.

SELECT emp.empno, dept.dname FROM emp, dept
WHERE
  (emp.deptno is null and emp.deptno = dept.deptno(+))          emp.deptno is
not null and emp.deptno = dept.deptno;

Since this is an over-simplification of my actual problem,
I cannot use UNION in my case. Are there any other alternatives ?

Thanks in advance,
-Steve
Followup:
confusion, you say:

So, the
outer join should be in effect only when the EMP.DEPTNO value is NULL. If there
is mismatch in the join keys, it should not produce the a row with NULL DEPT
values.

that is exactly what outer joins do.  If and only if the row is "mismatched"
will it be "created" by the outer join with null entries.

Please give me a concrete example with sample data -- eg: i have this, I want
that.
Rating: 5
Outer join only for certain values of join key ? August 15, 2003
Reviewer: Steve from CA
Here are the SQL commands using SCOTT schema

update emp set deptno = null where empno > 7850;
update dept set deptno = 22 where deptno = 10;

I would like to rewrite the following query such that the
row with empno = 7782 will not be part of the result set.
This is because there is a valid deptno for this row.
The rows with empno > 7840 are fine because their depno
is NULL.

SQL> SELECT emp.empno, dept.dname FROM emp, dept
  2  WHERE emp.deptno = dept.deptno(+);

     EMPNO DNAME
---------- --------------
      7782
      7369 RESEARCH
      7788 RESEARCH
      7566 RESEARCH
      7499 SALES
      7698 SALES
      7654 SALES
      7521 SALES
      7839
      7844
      7876

     EMPNO DNAME
---------- --------------
      7900
      7902
      7934

14 rows selected.

SQL> select deptno from emp where empno = 7782;

    DEPTNO
----------
        10

SQL> select deptno from emp where empno > 7850;

    DEPTNO
----------

SQL>

In other words, I want to generate dummy (NULL) values for DEPT table if and
only if the EMP.DEPNO is NULL. In all other cases, I would like to perform the
join as it is.

Thanks,
-Steve.
Followup:
ops$tkyte@ORA920> SELECT emp.empno, dept.dname, emp.deptno, dept.deptno
  2  FROM emp, dept
  3    WHERE emp.deptno = dept.deptno(+)
  4    and ((emp.deptno is not null and dept.deptno is not null) OR (emp.deptno
is null and dept.deptno is null));

     EMPNO DNAME              DEPTNO     DEPTNO
---------- -------------- ---------- ----------
      7369 RESEARCH               20         20
      7788 RESEARCH               20         20
      7566 RESEARCH               20         20
      7499 SALES                  30         30
      7698 SALES                  30         30
      7654 SALES                  30         30
      7844 SALES                  30         30
      7521 SALES                  30         30
      7876
      7900
      7902
      7934

12 rows selected.
Rating: 4
Blanket statement - outer joins to be avoided November 19, 2003
Reviewer: Doug from Redding, CT
Tom - what would you say to the perception that outer joins should be avoided
generally because of problems in optimizing them.  And, therefore design
decisions to use default values rather the null for "unknown" should be made? I
guess my own impression is that outer joins are not inherently evil, but I'm
wondering if you have a thought or two.
Followup:
i don't see how default values would avoid outer joins?

outer joins should be used in an optional 1:m relationship (have to be used).

outer joins should be used when they MUST BE.

outer joins should not be used "just in case they could be"
Rating: 5
question on outer join syntax January 08, 2004
Reviewer: Jerry from US
I saw people using outer joins like the one below:

SELECT e.ename FROM   emp e, dept d
WHERE  e.deptno (+) = d.deptno
AND    e.ename  (+) = 'abcd';

What does the query do?  Why outer join on the e.ename?  I can't find any
documentation about this usage. Thanks!
Followup:
no need to document something like that -- it is just a "natural side effect".

that would be like saying, you must document that you can:

  where deptno = 10;

(well, we do document that and we do document that ename(+) = EXPRESSION works
and 'abcd' is just an expression)

That is just like saying:

select e.ename
  from (select * from emp where ename = 'abcd') e, dept d
 where e.deptno(+) = d.deptno;

or, maybe this "makes more sense"

select e.ename
  from emp e, (select deptno, 'abcd' ename from dept ) d
 where e.deptno (+) = d.deptno
   and e.ename (+) = d.ename;

basically, it is saying:

output EVERY row in DEPT and if it has a match in EMP where ename = 'abcd', lets
see that data too.

if they queried:

select e.ename
  from emp e, (select deptno, 'abcd' ename from dept ) d
 where e.deptno (+) = d.deptno
   and e.ename = 'abcd';

that would be like saying:

take EVERY row in DEPT and if it has a match in DEPT, join it -- but output
EVERY row in dept regardless.

After you output every row in DEPT only really return the ones where ename =
'abcd'
Rating: 5
very helpful January 09, 2004
Reviewer: Jerry
Thanks for the quick and clear explanation This site is a great help!
Rating: 4
Internal Behaviour of Outer Joins March 20, 2004
Reviewer: Kapil from Singapore
Hi Tom,

The above was helpful indeed.

Would be greatful if you could help me understand the internal behaviour of
outer joins.

If we take the following example:

select tabA.colAn, tabB.colBn
from tabA, tabB
where tabB.colB1 = tabA.colA1(+)
and tabB.colB2 = tabA.colA2(+)
and tabB.colB3 = tabA.colA3(+)
and tabB.colB4 = 'ABC'

Here we have kept tabB as the driving table and we are searching for rows from
tabB in tabA.
There is also a composite index idxA1 on tabA(colA1, colA2, colA3)

Considering the above can I say that:
1.For every row in tabB satisfying the criteria [tabB.colB4 = 'ABC'] a search
would be made in idxA1 for a match.
2.If there is a match found then colAn would return the respective value else a
NULL would be returned,
3.The amount of computation involved in this, whille searching idxA1 or for that
matter in the execution of this whole query, is the same as compared to a
natural join.
4.Hence the amount of time taken to execute this query should be more or less
the same compared to a natural join in the same query.

The reason I request clarification is that in case my understanding is not right
and the outer join would take much more time than a natural join then I would
have to go for an alternate approach to achieve the desired results.

Kapil
Followup:
1, 2) no, yes, maybe.  conceptually yes.  the above query says:

return to me EVERY row in tabB such that colb4 = 'ABC'.  IF there exist any rows
in tabA that join to this, please return them as well.

3, 4) no, what if tabA had zero rows and tabB has 1,000,000,000 rows where colb4
= 'ABC'.  the outer join returns one BILLION rows -- the natural join zero.  big
big difference, you cannot even compare them.
Rating: 4
Internal Behaviour of Outer Joins March 20, 2004
Reviewer: Kapil from Singapore
1,2) But can you elaborate a little on why you say no and maybe for this. If an
index access is available with a high selectivity then wouldnt it be used?
3,4) Yes. I am in agreement that if the rows returned by a natural join are far
less than that returned by an outer join then there would be a big difference in
the time taken by the query due to increased fetch phase times. But can I say
that the time in the execution phase would be the same?

Also
5.If there exists a match 90% of the times then can I say that for those 90% of
the cases the total time taken by a natural join query would be the same as an
outer join query? The practical scenario is that I expect this query to return a
match most of the times.

Kapil
Followup:
1,2) because an index access MIGHT NOT be there, or not a good idea.  that is
why it is yes, no, maybe -- it depends (as you are pointing out!)

No, you cannot say the time would be anywhere near the same!   if the outer join
returns 1,000,000,000 records and the natural join returns 1 -- it is likely (in
my scenario above) that the natural join would execute intantaneously whereas
the outer join would take a while.

No, you cannot -- they are different queries 100%.  I do not see the relevance
of trying to even compare them for

a) it is mandated that you use an outer join when that returns the correct
result (as the natural join would not)

b) you would use the natural join in all other cases

so it is not like you have "a choice" except in B and that is not much of a
choice (not a hard decision to make -- you would use the outer join ONLY when a)
was true)
March 21, 2004 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!!
Rating: 3
Connect by ???? March 20, 2004
Reviewer: M.Srinivas from AP,India
Hi Tom,
 What does this "Connect by clause " do?Does it sort data
or compare data rowwise?I am not clear with this.Could you
Please explain ?
Please do write a followup.
Bye!
Followup:
better yet, i'll teach you to fish.

we document this stuff:
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96540/queries4a.htm#2053937
March 21, 2004 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 !!!
Rating: 4
New issue? February 05, 2004
Reviewer: Kimathi from Los Angeles, USA
Tom,
I am getting the following when I try to extract the comments from tables.
What could I be doing wrong?

  1  SELECT DBMS_METADATA.GET_DDL('COMMENT',comments) a1
  2  FROM ALL_TAB_COMMENTS
  3  WHERE OWNER ='MYTABLE'
  4* AND TABLE_NAME = 'SCOTT'
self@self> /
ERROR:
ORA-31600: invalid input value LONGNAME 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 571
ORA-06512: at "SYS.DBMS_METADATA", line 1221
ORA-06512: at line 1

no rows selected
Followup:
  1  select dbms_metadata.get_dependent_ddl( 'COMMENT', table_name, user )
  2*   from (select table_name from user_tab_comments where comments is not null
)
ops$tkyte@ORA9IR2> /

DBMS_METADATA.GET_DEPENDENT_DDL('COMMENT',TABLE_NAME,USER)
--------------------------------------------------------------------------------

   COMMENT ON TABLE "OPS$TKYTE"."FOO"  IS 'snapshot table for snapshot OPS$TKYTE
.FOO'

   COMMENT ON TABLE "OPS$TKYTE"."T"  IS 'hello world'

You are passing "comments" in your call as the "object name".

That and -- COMMENT is only allowed in the GET_DEPENDENT_DDL api as per the
table in the supplied packages guide.....
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96612/d_metad2.htm#1031509
See
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96612/d_metad2.htm#1028659
and give that section a read thru...
Rating: 5
excellent February 09, 2004
Reviewer: Kimathi Njeru from El Segundo, CA
Thanks Tom!!
Rating: 5
export/import inside a package March 17, 2004
Reviewer: snagaraj from pittsburgh,pa
Tom,

I have a situation that needs to drop and create tables dynamically.  Before I
drop I need to get the structure of the table and then recreate it.
Is there a way to export the structure of the table and import it within a
package?
In this thread I learnt the use of dbms_metadata package to extract the ddl.
Still I feel export and import may be a better deal and I want to know how can
that be incorporated within a stored package.

thanks
Followup:
what is wrong with TRUNCATE in this case?
Rating: 3
March 17, 2004
Reviewer: A reader
Well, it is not exactly drop and create, sometimes there is going to be new
columns or dropping unwanted columns.  Right now, the developer in his pl/sql
program do it dynamically and we are thinking of revoking the privilege of doing
DDL and instead call a procedure that does all kinds of DDL.

Thanks
Followup:
hows about

truncate (when you want to remove data)
alter table t add (when you want to add a column)
alter table t drop or set unused (when you want to remove a column)

be easier then parsing some ddl to remove columns/add columns and such.

(dbms_meta_data can extract the ddl, but this ddl would be *easier*)
Rating: 5
Is There a Way to Use DBMS_Metadata Remotely? March 18, 2004
Reviewer: Doug Wingate from New Orleans, LA USA
Is it somehow possible to use DBMS_Metadata.Get_DDL in an Oracle9i database to
query the data dictionary of a remote database that's at the Oracle8i level and
produce DDL for database objects in that Oracle8i database?  I anticipate that
your answer will be, "No," but of course if this is possible, it would be a
handy workaround in some shops--like mine--in which some of the databases are
still at the Oracle8i level and thus lack the DBMS_Metadata package.

Thanks.
Followup:
dbms_metadata doesn't exist in 8i, and the data dictionary format dbms_metadata
would be expecting in 9i differes radically from what was n 8i.

In 8i -- you would use

exp ...... rows=n
imp ...... indexfile=foo.sql
Rating: 3
wow lots of time coding ddl's and related problems March 19, 2004
Reviewer: rene dagenais from USA
by the way quest software as you know can do the job correctly under 1 minute
and generate any ddl on the fly.. so i kind of debating the notepad dba's that
have to much time on their hands and try to sql to death a function that can be
done in a more productive way. but this does not take away the knowlegde base
needed to complet dba task and understanding how oracle database work. and like
most of us we like a good chalenge anyway. Thanks for the info tom you are still
the best!
Rating: 5
extract FKs March 20, 2004
Reviewer: A reader
Hi

I am currently writing a PL/SQL package to delete several 100 million rows
tables. On of steps is disabling constraints. Since I am using
skip_unusable_indexes in my code I am forced to drop my parent primary keys in
order for my insert to work. When dropping PKs all FKs are dropped as well so I
am currently seeking a way to extract the affected FKs DDL. I am using 8.1.7.4
so DBMS_METADATA is out of question...

Is there any way using PL/SQL?

Just out of curiosity I was testing in 9i if I could extract FKs DDL using
dbms_metadata, I used get_dependant_ddl function but it does not work :-0

thx
Followup:
you could use deferrable primary key constraints, which would be implemented
with non-unique indexes, allowing you to disable them -- not drop.

this shows how to extract fkeys:

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')
/
Rating: 5
you dont need to drop the PKs March 21, 2004
Reviewer: A reader
To last reader

You can simply do this

ALTER TABLE A DISABLE NOVALIDATE CONSTRAINT A_PK CASCADE;

Then simply enable them, there is no need to extract DDL
March 21, 2004 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)
Rating: 2
Urdu Language Support March 20, 2004
Reviewer: Masroor from Karachi, Pakistan
Hi sir
I want to insert data in urdu
Please guide me on back end and front end level
how can i use UTF8 character set and how can i handle on front end. i use
database oracle 8i (8.1.7) and developer 6i

I shall be thankful to u

Yours obediently
Followup:
sorry, i've never worked with that.  try otn.oracle.com -> getting help ->
discussion forums.
March 21, 2004 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
Rating: 5
BI components? March 20, 2004
Reviewer: A reader
Tom,
When and in what order might one be using
Oracle Warehouse Builder, Oracle Discoverer, Oracle Data Mining, Oracle BI Beans
I have seen documentation which explains each of the above.  I would like to all
how all of the above would fit in or be used as an integrated tool.

Thank you
Followup:
OWB -> builds the warehouse that
   end users query with Disco
   power users use jdev with data mining/bi beans wizards to mine
   developers build olap apps against with jdev/bi beans
Rating: 3
How can i Install oracle in Windows XP March 20, 2004
Reviewer: Haron from saudi arabia
Hi tom,

   Tom,please help me how to install oracle 8i and 8 and forms   in windows XP
thanks
Followup:
please contact support for installation and configuration issues (but good luck
with really old software on really new os's -- if you want a bit of "old" you
have to go "all old" like windows NT as both 8i and 8 predate XP by many many
years)
Rating: 5
March 20, 2004
Reviewer: Dave from Colorado Springs
Haron, help yourself by reading the documentation
Rating: 4
How to install oracle 8i and 8 and forms in Windows XP March 21, 2004
Reviewer: Haron Palala from saudi arabia
hI TOM,

  Please help me how can i install ORACLE 8 AND 8I AND FORMS IN WINDOWS XP
THANKS A LOT
March 21, 2004 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;
Rating: 5
Sequence performance March 07, 2004
Reviewer: A reader
On the issue of sequences and performance, I have 2 questions.

1) How does the picture change (for the better or worse) if OPS/RAC is involved
for contention on sequences and cache values?

I'm sure it's been asked before, and I'm sure you have mentioned it somewhere,
but I can't find the answer to:

If we have a table that is just one col with one row and is used as a unique
number generator (just like a sequence). The process that needs "some unique
numbers" updates the value in the row by say 100 and commits. It then internally
issues the numbers in sequence until it exhausts them and then does it all over
again. Of course we have several processes doing the same thing to the same
table (all communicating to the same d/b from diff "client" servers) all caching
100 values and dishing them out as required.

Crazy as it may sound, I'm trying to convince the
developers to use sequences. I need ammunition...bench marks...test
cases...anything.

Thanks
Ma$e
Followup:
1) you want a generous cache, you never want to use "nocache" or "ordered"
options -- you just want to use the sequence for what it is -- a surrogate key
generator.

You want to drop your single table and update/commit and just use a sequence.

reason: it is easier.
Rating: 4
SQL/PLSQLplus March 21, 2004
Reviewer: Mohamed Al Maskari
Is there is a free Q&A for SQL/PLSQL+ ?
Followup:
right here?

but there are discussion forums on otn.oracle.com as well, yes.
March 21, 2004 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"
Rating: 5
mass delete March 21, 2004
Reviewer: A reader
Hi

We have a CRM which we need to purge historical data. The tables are not
partitioned because the CRM vendor does not support it.

I am trying to minimize the downtime because what I am going to do is create
copies of tables with historical data and delete them from original table. I
have been looking quite a few threads on your site but all talks about CTAS,
INSERT /*+ APPEND */ etc. Seems they are only the fast way to do this in Oracle
(always speaking about plain table since mine arent partitioned), obviously this
involves downtime, I have make an estimate and I need around 36 hours downtime
if I use the approach of CTAS/INSERT.

My question is, what does Oracle 8i, 9i and 10g offer about this kind of
organization? Can it be done online or at least minimize the downtime? I cannot
seem to find anything in the docs to do this sort of things online. I remember
in one shop, they had this delete process which took like 3 weeks to delete 50
million of rows of 500 million rows because they could not afford downtime (not
even 2 hours), you may say they should partition the tables but again this was a
third party tool which did not support this sort of fancy features.

Any comments?
Followup:
we do lots of stuff online, but it requires the use of the feature you have just
said isn't able to be used.

catch 22.  we do what you want.

partitioning is transparent to the application.  IF you are allowed to do what
you are describing shuffle their data all over), there should be nothing from
stopping you from diong it the other way either.  As soon as you touch their
tables with your own DML, you have become very intrusive.  In for a penny, in
for a pound as they say.

Just a couple of weeks ago, I decided to partition my logs here (were getting
really large).  So I did an online redef of my logs into a 12 way partitioned
table (by month).  Now I just "roll them".  I did this with 100% uptime -- you
never knew I was doing it (i did it between 9am and noon on a weekday, one of my
busiest times).
March 21, 2004 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
Rating: 5
IOT and "Most discriminating elements should be first" index myth March 21, 2004
Reviewer: Peter Tran from Houston, TX USA
Hi Tom,

Does the "Most discriminating elements should be first" index myth apply to a
partitioned-IOT table?  (Example to follow).

Original Table:

CREATE TABLE "ODFRC"
  ("ODIFID"             NUMBER(10,0) NOT NULL,
   "TRPORGN"            VARCHAR2(5) NOT NULL,
   "TRPDSTN"            VARCHAR2(5) NOT NULL,
   "POSCOUNTRYCODE"     VARCHAR2(3) NOT NULL,
   "PAXTYPE"            VARCHAR2(1) NOT NULL,
   "DCP"                NUMBER(2,0) NOT NULL,
   "ODIFDATE"           DATE NOT NULL,
   "FRCDATE"            DATE NOT NULL,
   "BKGMEAN"            NUMBER,
   "BKGMEANINFLUENCED"  NUMBER,
   "BKGVARIANCE"        NUMBER,
   "XXLMEAN"            NUMBER,
   "XXLMEANINFLUENCED"  NUMBER,
   "XXLVARIANCE"        NUMBER,
   CONSTRAINT "ODFRC_PK" PRIMARY KEY (
       "ODIFID", "ODIFDATE", "TRPORGN", "TRPDSTN",
       "POSCOUNTRYCODE", "PAXTYPE", "DCP", "FRCDATE") ENABLE
   ) ORGANIZATION INDEX NOCOMPRESS
  PCTTHRESHOLD 50
  PARTITION BY RANGE ("ODIFDATE")
 (
   PARTITION "ODFRC_20030826" VALUES LESS THAN (TO_DATE('20030827',
'YYYYMMDD')),
   PARTITION "ODFRC_20030827" VALUES LESS THAN (TO_DATE('20030828',
'YYYYMMDD')),
   PARTITION "ODFRC_20030828" VALUES LESS THAN (TO_DATE('20030829',
'YYYYMMDD')),
   PARTITION "ODFRC_20030829" VALUES LESS THAN (TO_DATE('20030830',
'YYYYMMDD')),
   PARTITION "ODFRC_20030830" VALUES LESS THAN (TO_DATE('20030831',
'YYYYMMDD')),
   PARTITION "ODFRC_20030831" VALUES LESS THAN (TO_DATE('20030901',
'YYYYMMDD')),
   PARTITION "ODFRC_20030901" VALUES LESS THAN (TO_DATE('20030902', 'YYYYMMDD'))
 )
/

NOTE, how the OdifId is the leading edge column in the index.

When used with the following SQL:

SELECT count(*)
FROM ODFRC frc
WHERE frc.OdifDate >= to_date('20030827', 'YYYYMMDD') AND
      frc.OdifDate <= to_date('20030901', 'YYYYMMDD') AND
      frc.OdifId IN (1012933,10237004,10336795,1042,1051286,106609)
/

I get the following performance characteristics:

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     1186      0.50       0.53          0       5801          0       17767
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1188      0.50       0.54          0       5801          0       17767

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 37

Rows     Row Source Operation
-------  ---------------------------------------------------
  17767  PARTITION RANGE ITERATOR PARTITION: 2 7
         (cr=5801 r=0 w=0 time=273976 us)
  17767   INLIST ITERATOR  (cr=5801 r=0 w=0 time=246074 us)
  17767    INDEX RANGE SCAN ODFRC_COMP2_PK PARTITION: 2 7
           (cr=5801 r=0 w=0 time=208403 us)(object id 439798)

Now, if I switch the OdifId and OdifDate columns.

CREATE TABLE "ODFRC"
  ("ODIFID"             NUMBER(10,0) NOT NULL,
   "TRPORGN"            VARCHAR2(5) NOT NULL,
   "TRPDSTN"            VARCHAR2(5) NOT NULL,
   "POSCOUNTRYCODE"     VARCHAR2(3) NOT NULL,
   "PAXTYPE"            VARCHAR2(1) NOT NULL,
   "DCP"                NUMBER(2,0) NOT NULL,
   "ODIFDATE"           DATE NOT NULL,
   "FRCDATE"            DATE NOT NULL,
   "BKGMEAN"            NUMBER,
   "BKGMEANINFLUENCED"  NUMBER,
   "BKGVARIANCE"        NUMBER,
   "XXLMEAN"            NUMBER,
   "XXLMEANINFLUENCED"  NUMBER,
   "XXLVARIANCE"        NUMBER,
   CONSTRAINT "ODFRC_PK" PRIMARY KEY (
       "ODIFDATE", "ODIFID", "TRPORGN", "TRPDSTN",
       "POSCOUNTRYCODE", "PAXTYPE", "DCP", "FRCDATE") ENABLE
   ) ORGANIZATION INDEX NOCOMPRESS
  PCTTHRESHOLD 50
  PARTITION BY RANGE ("ODIFDATE")
 (
   PARTITION "ODFRC_20030826" VALUES LESS THAN (TO_DATE('20030827',
'YYYYMMDD')),
   PARTITION "ODFRC_20030827" VALUES LESS THAN (TO_DATE('20030828',
'YYYYMMDD')),
   PARTITION "ODFRC_20030828" VALUES LESS THAN (TO_DATE('20030829',
'YYYYMMDD')),
   PARTITION "ODFRC_20030829" VALUES LESS THAN (TO_DATE('20030830',
'YYYYMMDD')),
   PARTITION "ODFRC_20030830" VALUES LESS THAN (TO_DATE('20030831',
'YYYYMMDD')),
   PARTITION "ODFRC_20030831" VALUES LESS THAN (TO_DATE('20030901',
'YYYYMMDD')),
   PARTITION "ODFRC_20030901" VALUES LESS THAN (TO_DATE('20030902', 'YYYYMMDD'))
 )
/

And run the same query which uses both OdifDate and OdifId in the predicate,
then my plan changes and performance is worse.

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     1186    119.35     116.62          0      36067          0       17767
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1188    119.35     116.62          0      36067          0       17767

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 37

Rows     Row Source Operation
-------  ---------------------------------------------------
  17767  PARTITION RANGE ITERATOR PARTITION: 2 7
         (cr=36067 r=0 w=0 time=116355985 us)
  17767   INDEX FAST FULL SCAN ODFRC_ODIFDATE_PK PARTITION: 2 7
          (cr=36067 r=0 w=0 time=116326105 us)(object id 439814)

(1) Why did Oracle change the execution plan?
(2) Why doesn't the "Most discriminating elements should be first" index myth
apply here?

I would like to make OdifDate the leading edge, because I can get better
compression.

Thanks,
-Peter
Followup:
the "myth" applies.

what dictates your order of primary key columns is how you use the data.
Nothing more, nothing less.  Nothing more, nothing less

Look at your example -- you are "equaling on ID", and "ranging on DATE"

that would imply to me you want ID,DATE

think about what must happen here! consider when id comes first and you say:

where id in ( finite small set ) and date between A and B

well, we can simply run a finite set of queries like:

  where id = X and date between A and B
UNION ALL
  where id = Y and date between A and B

and very very efficiently use the index (that is an inlist iterator)

Now, think about the index with DATE,ID and that same predicate.  You say

for all DATES between A and B (thats a BIG range to scan --partitions 2..7 in
your case!) if the id in (small finite set) then count it.

for all DATES between A and B... for all DATES between A and B.  Look for id in
(.....)

you cannot come up with the union all for that (there are a HUGE number of
values between A and B -- remember the date goes down to the SECOND there)

just try to picture what it must do -- think about the organization of the data
in the two indexes and ask your self "what work would I have to do in program to
answer this question myself"....
Rating: 5
Great! March 21, 2004
Reviewer: Peter Tran from Houston, TX USA
Hi Tom,

Thanks for the prompt response.  I knew I was missing something fundamental.

When I modified my query to:

SELECT count(*)
FROM ODFRC frc
WHERE (frc.OdifDate = to_date('20030827', 'YYYYMMDD') or
       frc.OdifDate = to_date('20030828', 'YYYYMMDD') or
       frc.OdifDate = to_date('20030829', 'YYYYMMDD') or
       frc.OdifDate = to_date('20030830', 'YYYYMMDD') or
       frc.OdifDate = to_date('20030831', 'YYYYMMDD') or
       frc.OdifDate = to_date('20030901', 'YYYYMMDD')) AND
       frc.OdifId IN (1012933,10237004,10336795,1042,1051286,106609)
/

Then I get the same execution plan as (odifid, odifdate, ...).

Elapsed: 00:00:02.32

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=3479 Bytes=208740)
   1    0   INLIST ITERATOR
   2    1     PARTITION RANGE (ITERATOR)
   3    2       INLIST ITERATOR
   4    3         INDEX (RANGE SCAN) OF 'ODFRC_ODIFDATE_PK' (UNIQUE) (Cost=5
Card=3479 Bytes=208740)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       5801  consistent gets
          0  physical reads
          0  redo size
     984494  bytes sent via SQL*Net to client
      14525  bytes received via SQL*Net from client
       1186  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      17767  rows processed

This is great, if I restructure the query I can use (OdifDate, OdifId, ...)
order in my PK and get better compression.

Thanks,
-Peter
March 22, 2004 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.
Rating: 3
logmeoff is not working as user press back button and get access to all web pages March 19, 2004
Reviewer: suresh
dear Sir

On using it on oracle8.1.7 when my users press signout they get a message that
they are deauthenticated but on just pressing BACK button on browser the get
accessa to all web pages . Funny and i had configured glowal owa which  asks for
usernaem and password when accessed web pages vfirst  time. Please provide me a
solution  keeping in view that i had oracle 8.1.7 and not oracle 9i. On asking
at  mealink they replyed with a link to oracle single sign on  but same is nota
vbailabe with 8.1.7

thanks in advance
Followup:
you cannot be using "logmeoff" then -- that would ask the user to enter their
password again (i wrote it that way) and the user would enter "bye" or "exit".

is that what you are doing -- if not, it is not "logmeoff"
Rating: 5
logmeoff March 19, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
You are seeing the pages in the local cache perhaps?  I can page backwards after
a "logmeoff", but if I try to refresh any of those pages, I get the logon
prompt...  Is that the behavior you are seeing?

- Mark
Rating: 3
logmeoff not working March 22, 2004
Reviewer: suresh from India
dear Sir,

I am useing fulldadpath/logmeoff.

And on all clients also users can press back and then see data.

suresh
Followup:
if you don't want them to go back, you have to use SSL so the pages are not
cached.

they are in fact logged off

the browser is just showing the cache, nothing we can do about that except say
"don't cache"
March 22, 2004 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?
Rating: 3
How to configure 2 servers for connect-time failover March 06, 2004
Reviewer: Hash from pakistan
Hi Tom,
I have read the documentation & tried to configure 2 servers for failover but i
couldn't do it.
i have 2 db's with same global names ie oracle.world on 2 different machines.
when i put connect time failover in tnsnames.ora of the client machine but it
didn't work
could you plz tell me where m i wrong
Followup:
you'll have to share your configuration with us and define "but it didn't work",
what were the symptons.
Rating: 3
It did'nt work March 20, 2004
Reviewer: Hash from Pakistan
Hello Tom,
thanks for your response,
I have 2 db servers i.e sales1-server and sales2-server.both have a service_name
oracle.world.a client e.g Client1 has the following tnsnames.ora entry.

sales=
 (description=
  (failover=on)
  (address=(protocol=tcp)(host=sales1-server)(port=1521))
  (address=(protocol=tcp)(host=sales2-server)(port=1521))
  (connect_data=
   (service_name=oracle.world)))

I think I m making a very foolish mistake but i don't know about it.if i remove
any of the 2 host entries in the above case, the client gets connected to the
respective host but both entries don't work.
I just want my client to failover to sales2-server if sales1-server is down and
vice versa.
could please please please explain
thanks
Followup:
"don't work"

hmmm, what does that mean exactly - i cannot see your screen from where I am
sitting so the error messages/symptons are not obvious to me :)
Rating: 3
Are you serious or joking? March 20, 2004
Reviewer: Hash from Pakistan
Does that mean there is nothing wrong with the above procedure?

could you please please give me a few steps to do this procedure in your words
so that i can try to reconfigure it
thanks for your response
Followup:
I'm not joking.

I want you to define to me "don't work"

what was the error
what was the sympton
what happens
"throw me a bone" here -- give me something to work with.

Else, this is like saying "my car won't start -- why not"
Rating: 3
Sorry for making you angry March 21, 2004
Reviewer: Hash from Pakistan
Ok Tom, here I go again. I have 2 db servers (both 9i R1) sales1-server and
sales2-server.On a client I have a tnsnames entries
sales1=
 (description=
  (address=(protocol=tcp)(host=sales1-server)(port=1521))
  (connect_data=
   (service_name=oracle.world)))

sales2=
 (description=
  (address=(protocol=tcp)(host=sales2-server)(port=1521))
  (connect_data=
   (service_name=oracle.world)))

With these entries I can connect to sales1_server and sales2_server

now i have another entry

sales=
 (description=
  (failover=on)
  (address=(protocol=tcp)(host=sales1-server)(port=1521))
  (address=(protocol=tcp)(host=sales2-server)(port=1521))
  (connect_data=
   (service_name=oracle.world)))

I shutdown sales1-server and attempted
connect scott/tiger@sales
I think i should be connected to sales2_server because sales1_server is down but
it says 'tnsnames not found'(sorry i dont remember the error message right now
its something like a message which you get when you attempt to connect to a db
which you dont have an entry in tnsnames

the problem is that i m not doin any other configuration. i want to ask IS THERE
ANYTHING ELSE I SHOULD DO ALONG WITH THIS TNS ENTRY is ther any other
configuration inviolve in failover

sorry to irritate you
thatks
Followup:
then the tnsnames.ora file is "incorrect".  you can test this via:

connect scott/tiger@(description=(failover=on)........)

(eg: don't use the tnames.ora file)

not seeing your tnsnames.ora file, I cannot really "comment" -- but if it says
"sorry, connect turn sales into something useful", you'll need to fix that --
there is something wrong in your configuration file, perhaps ABOVE the sales=
entry.
Rating: 3
Here I go again March 22, 2004
Reviewer: Hash from Pakistan
Ok Tom, this my tnsnames.ora
----------------------------

TEST1 =
  (DESCRIPTION =
         (ADDRESS = (PROTOCOL = TCP)(Host = UMAR)(Port = 1521))
    (CONNECT_DATA = (SERVICE_NAME = oracle.world)))

TEST2 =
  (DESCRIPTION =
         (ADDRESS = (PROTOCOL = TCP)(Host = HASH)(Port = 1521))
    (CONNECT_DATA = (SERVICE_NAME = oracle.world)))

TEST3 =
  (DESCRIPTION =
    (FAILOVER=ON)
        (ADDRESS = (PROTOCOL = TCP)(Host = UMAR)(Port = 1521))
        (ADDRESS = (PROTOCOL = TCP)(Host = HASH)(Port = 1521))
    (CONNECT_DATA = (SERVICE_NAME = oracle.world)))

now both the servers(umar & hash are running).I issued:
SQL> CONNECT SCOTT/TIGER@TEST1
Connected.        -- TO UMAR
SQL> CONNECT SCOTT/TIGER@TEST2
Connected.        -- TO HASH
SQL> CONNECT SCOTT/TIGER@TEST3
Connected.        -- TO HASH
NOW SERVER HASH IS DOWN AND...
SQL> CONNECT SCOTT/TIGER@TEST3
ERROR:
ORA-12203: TNS:unable to connect to destination

While I m thinking it should failover to 'umar' which is still running
Now could you tell me what m I doing wrong?
Thanks again
Followup:

http://download-west.oracle.com/docs/cd/B10501_01/network.920/a96581/tnsnames.htm#491460
You have no "address_list", you have an incorrectly specified tnsnames entry.
Suggest you might want to use the gui's to set this up the first time around --
but that link has concrete examples for you to study as well.

this is the detail one needs to see "whats wrong" -- it is a very different
error from what you described to me in the first place!
Rating: 5
IT WORKED March 24, 2004
Reviewer: Hash from Pakistan
Thank you Tom, The link you provided was ver usefull.<b> THIS THING WORKED <b>.
I m so grateful
March 22, 2004 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".
Rating: 5
cpu time and elapsed time January 26, 2004
Reviewer: A reader
Hi

I am wondering if elapsed time is the sum of CPU time plus wait time?

For example if I have this

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse     3507      1.18       4.55          2          4          0           0
Execute   4092     32.29     132.90      15774      26866      50269        1496
Fetch     3166      0.52       2.35        269       6332          0        6760
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    10765     33.99     139.80      16045      33202      50269        8256

Misses in library cache during parse: 36

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                    4708        0.00          0.04
  SQL*Net message from client                  4708        2.13         54.90
  db file sequential read                       656        0.09          0.27
  latch free                                     37        0.16          0.71
  resmgr:waiting in end wait                    112        0.10          0.83
  SQL*Net more data to client                     4        0.00          0.00
  log file sync                                 112        0.99          1.25
  db file scattered read                          2        0.00          0.00
  SQL*Net more data from client                  34        0.00          0.00
  SQL*Net message to dblink                     303        0.01          0.01
  SQL*Net message from dblink                   303        0.15          0.76
  resmgr:waiting in check                         1        0.00          0.00
  resmgr:waiting in check2                        1        0.01          0.01
  SQL*Net break/reset to client                   2        0.00          0.00

cpu time is 33.99 seconds
elapsed time is 139.80 seconds
wait time roughly 60 seconds

From this result elapsed time is not the sum of cpu time + wait time... As you
mentioned there can be errors because of small transactions "round ups". So I
guess that´s the reason the sum dont match. Or simply Elepased time != cpu +
wait?

I cannot find anywhere which explains this

Cheers
Followup:
you are missing the unreported wait time which is "waiting for CPU".

If you have a system with many users -- you are competing for that CPU.  CPU
"wait time" is not reported here.
Rating: 5
cpu time and elapsed time January 26, 2004
Reviewer: A reader
Hi

So I guess cpu time is part of elapsed time right? I have a collegue who thinks
total elapsed time is cpu time + elapsed time. I think he is incorrect but I
cannot find any documentation to show him
Followup:
elapsed time is elapsed time.

cpu time is cpu time.

cpu time is a subset of the time in elapsed.

if you have "5.0" in cpu time and "5.5" in elapsed -- your elapsed time (looking
at the clock on the wall) was 5.5 seconds -- 5 of which were spent on the CPU
and 0.5 of which were spent doing some waiting -- on disk, on cpu, on something.
Rating: 5
Query Time February 04, 2004
Reviewer: Vinnie from Orlando
Tom,

I have the following TKPROF output...

UPDATE test SET STATUS = :1
WHERE
 ID = :2 AND CLASS_NAME = :3 AND TYPE = :4 AND TIME = :5
  AND ELAPSED_TIME = :6

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute   6934    180.65     179.42        198    3253280      15359        6934
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     6935    180.65     179.42        198    3253280      15359        6934

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 36

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  UPDATE
   6934   TABLE ACCESS BY INDEX ROWID OBJ#(54288)
27941318    INDEX RANGE SCAN OBJ#(54296) (object id 54296)

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  db file sequential read                       198        0.13          0.25
  SQL*Net message to client                    6934        0.00          0.02
  SQL*Net message from client                  6934        0.01          4.42
  log file switch completion                      1        0.30          0.30
********************************************************************************

Why would the query count be so high?  The table is analyzed but not the index,
could this help?

Plus the waits above does not show the wait time for 'log file sync'
as shown from a snippet of a level 12 trace below.  Why not & what could be
causing this amount of wait besides basic disk configuration?

WAIT #14: nam='db file sequential read' ela= 72 p1=2 p2=5417 p3=1
EXEC #14:c=10000,e=4190,p=1,cr=4,cu=3,mis=0,r=1,dep=0,og=4,tim=3109543021141
WAIT #14: nam='SQL*Net message to client' ela= 2 p1=1413697536 p2=1 p3=0
WAIT #14: nam='SQL*Net message from client' ela= 3549 p1=1413697536 p2=1 p3=0
XCTEND rlbk=0, rd_only=0
WAIT #0: nam='log file sync' ela= 15175 p1=444 p2=0 p3=0
WAIT #0: nam='SQL*Net message to client' ela= 2 p1=1413697536 p2=1 p3=0
WAIT #0: nam='SQL*Net message from client' ela= 9534 p1=1413697536 p2=1 p3=0
...
...
WAIT #14: nam='db file sequential read' ela= 75 p1=2 p2=1827 p3=1
EXEC #14:c=0,e=1799,p=1,cr=4,cu=3,mis=0,r=1,dep=0,og=4,tim=3109543052078
WAIT #14: nam='SQL*Net message to client' ela= 2 p1=1413697536 p2=1 p3=0
WAIT #14: nam='SQL*Net message from client' ela= 2824 p1=1413697536 p2=1 p3=0
XCTEND rlbk=0, rd_only=0
WAIT #0: nam='log file sync' ela= 14320 p1=445 p2=0 p3=0
WAIT #0: nam='SQL*Net message to client' ela= 2 p1=1413697536 p2=1 p3=0
WAIT #0: nam='SQL*Net message from client' ela= 9170 p1=1413697536 p2=1 p3=0
...
...

THanks
Followup:
you have a very non-selective index on there:

   6934   TABLE ACCESS BY INDEX ROWID OBJ#(54288)
27941318    INDEX RANGE SCAN OBJ#(54296) (object id 54296)

you scanned 27,941,318 rows in the index that MIGHT be updated.  After doing
27,941,318 table access by index rowid - you found (by doing the rest of the
predicate) that only 6,934 of them were in fact to be updated.

so, find out what columns that index is one, ask "why do i have that index" and
if your goal is to make this update "as fast as possible", look no further than
the update predicate:

ID = :2 AND CLASS_NAME = :3 AND TYPE = :4 AND TIME = :5
  AND ELAPSED_TIME = :6

and consider creating an index on id,class_name,type,time,elapsed_time

OR as least some subset of those columns that select out the least number of
rows....

Log file sync is something you wait on during a COMMIT.  Never on an update.

the XCTEND (transaction END) records are right before the log file sync waits...
Rating: 4
New explain plan ??? February 10, 2004
Reviewer: BG
Hi Tom,

I got this expl plan.

Operation                                    Object Name    Rows    Bytes
Cost

SELECT STATEMENT Optimizer Mode=CHOOSE                    1                   41

  RECURSIVE EXECUTION                        SYS_LE_3_0

    RECURSIVE EXECUTION                        SYS_LE_3_1

  TEMP TABLE TRANSFORMATION

    SORT ORDER BY                                        1          195      32

      NESTED LOOPS                                        1          195      30

        NESTED LOOPS                                    1          180      28

          HASH JOIN                                        1          59      13

            HASH JOIN                                    35      1 K        10

              TABLE ACCESS FULL    tab_a                    35      735      7

              TABLE ACCESS FULL    tab_b                    93      1 K        2

            TABLE ACCESS FULL    tab_c                    238      4 K        2

          TABLE ACCESS BY INDEX ROWID    tab_d            9          1 K
15
            INDEX RANGE SCAN    tab_d_UK                9                   10

        TABLE ACCESS BY INDEX ROWID    tab_e                1          15      2

          INDEX UNIQUE SCAN    tab_e_PK                    3 K                 1

(Sorry, I have tried to format the layout).
Could you plase explain me what does mean:
"  RECURSIVE EXECUTION                        SYS_LE_3_0    RECURSIVE EXECUTION
                      SYS_LE_3_1        TEMP TABLE TRANSFORMATION"

Even if it shown that cost of a query is 41 take him a lot of time to finish.

Thanks!
Followup:
umm, new plan from what?

(looks like a star transformation to me)  see
http://dbazine.com/jlewis6.html

Rating: 5
February 10, 2004
Reviewer: Steve from NYC
Hi Tom,

I have the following trace data for the query I am going to tune.However, with
very few PIO/LIOs but huge elapsed time, I am thinking it is not the problem of
the query itself, What are possible reasons will cause this long response time?

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.27       0.30          0          0          0           0
Execute      1      0.30    4036.47          2          3        430      202431
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.57    4036.77          2          3        430      202431

Thanks!

Steve
Followup:
wait events.

use the 10046 trace event with level 12 to see binds and waits.  if you have 9i,
the waits will be reported in the tkprof itself.  if you have 8i and before, you
need to read the raw trace file to see what it was waiting on.

Since it has no fetch set, and all of the work was current mode gets really --
I'll say this was not a "query" but rather an insert/update/delete statement.
That is easy to get a wait like that -- and the wait would be for ENQUEUE
probably (eg: a lock).  if you have my expert one on one Oracle -- in chapter
10, I use that example exactly.  Easy to simulate just:

session1:   update emp set ename = ename;
session2:   set trace on, do same update, go to lunch
session1:   come back from lunch and type commit;
session2:   commit and exit

now run tkprof, you'll see something like this (in 9i)

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.30     104.42          0      18763      19155       18633
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.30     104.43          0      18763      19155       18633

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 101

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  UPDATE  (cr=18763 r=0 w=0 time=104429440 us)
  18633   TABLE ACCESS FULL OBJ#(34749) (cr=18762 r=0 w=0 time=58511 us)

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  enqueue                                        35        2.99        103.72
  log buffer space                                7        0.07          0.38
  SQL*Net message to client                       1        0.00          0.00
  SQL*Net message from client                     1        0.00          0.00
Rating: 5
more info for my question February 10, 2004
Reviewer: steve from nyc
hi tom,

my sql is not special.

insert ...
select ...
from a,b,c,d
where ...
 group by ...

and tkprof output:
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.28       0.30          0          0          0           0
Execute      1      0.64    3699.82          4       1226        688      202433
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.92    3700.12          4       1226        688      202433

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  file open                                       7        0.00          0.00
  db file sequential read                         4        0.02          0.05
  rdbms ipc reply                                14        0.06          0.56
  enqueue                                        11        0.35          0.46
  PX Deq: Join ACK                               24        0.00          0.00
  PX Deq: Execute Reply                        2311        2.06       3697.28
  PX Deq Credit: send blkd                       38        0.05          0.08
  PX Deq: Parse Reply                             4        0.01          0.01
  control file sequential read                   33        0.00          0.00
  local write wait                                9        0.06          0.33
  PX Deq: Signal ACK                              4        0.10          0.10
  log file sync                                   1        0.00          0.00
  SQL*Net message to client                       1        0.00          0.00
  SQL*Net message from client                     1        0.00          0.00
********************************************************************************

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  LOAD AS SELECT
      0   SORT GROUP BY
      0    SORT GROUP BY
      0     HASH JOIN
      0      HASH JOIN
      0       TABLE ACCESS FULL BN_STORE
      0       HASH JOIN
      0        TABLE ACCESS FULL IMM_LOOKUP_CODES
      0        NESTED LOOPS OUTER
      0         PARTITION RANGE ALL PARTITION: START=1 STOP=12
      0          TABLE ACCESS BY LOCAL INDEX ROWID IMM_STORE_INVENTORY PARTITION
: START=1 STOP=12
      0           INDEX RANGE SCAN PARTITION: START=1 STOP=12 (object id 5056581
)
      0         PARTITION HASH ITERATOR PARTITION: KEY KEY
      0          TABLE ACCESS BY LOCAL INDEX ROWID IMM_DIV_STRATEGY PARTITION: K
EY KEY
      0           INDEX UNIQUE SCAN PARTITION: KEY KEY (object id 3464554)
      0      PARTITION HASH ALL PARTITION: START=1 STOP=12
      0       TABLE ACCESS FULL BN_ITEM PARTITION: START=1 STOP=12

'PX Deq: Execute Reply' is an idle event. why idle events slow down the
performance? how to tune the sql based on tkprof output?

thanks!

Steve
Followup:
see, this is why you need to post *enough* information.  I guessed above.  My
guess was wrong.  wish I had this before I guessed.

ahh, the time was taken in the PQ slaves.  Your session was the coordinator.  It
was idle waiting for the PQ slaves to finish.  Very simple -- the time to
actually do the query was in the other processes -- not yours.  Your session had
a very simple job to do.  It was waiting for the PQ guys to finish their jobs.
Rating: 5
more info for my question February 11, 2004
Reviewer: Steve from nyc
Thank you, Tom!

How should I change the enviroment to speed up parallel slaves?

I noticed in top session that there are several SELECT sessions but just one
INSERT session, even if I added
'alter session enable parallel dml' and parallel hint to the insert into
statement.

Thanks!

Steve
Followup:
you'll want to read much of
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96520/toc.htm
and in particular to find out what your pq processes are doing (only way to
tune, to understand what it is you have to tune) see
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96520/tuningpe.htm#57125
Rating: 3
Help with tkprof output February 16, 2004
Reviewer: Denise from Virginia, USA
Hi Tom

We've been told by the software vendor that there is nothing
wrong with the efficiency of the module that produced the
following tkprof output based on an import of 10 records
with no idnumbers that took 10 minutes to process.

some background:
There is a module called Lockbox that allows us to import
records from varying sources into our system. This module
also performs duplicate matching/checking. We import files
that may or may not contain idnumbers and the volumne
can be anywhere from 10,000 to 15,000 records.

This is the newest version of their product that we
had installed on our testserver for testing purposes.

When we import files with idnumbers the system processes
them reasonably fast( a file with 325 records took less than 1 minute).

We did a test importing another file with no idnumbers
containing 10 records...it took 10 minutes to process..
hence we logged in a support call and ran a trace/tkprof.

I'll attach the tkprof output in the thread after this one..
as it is quite lengthy.

The old version works fine...therefore I am going to need to
do some analysis and investigating as to what changes have been made
to the new version that is causing performance to slowdown considerably
in the absence of idnumbers

Any insight/help you can provide is immensely appreciated
Rating: 3
here it is...thanks! February 16, 2004
Reviewer: Denise from Virginia,USA
TKPROF: Release 8.1.7.0.0 - Production on Thu Feb 12 09:08:57 2004

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

Trace file: ora00187.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  distinct(p.idnumber)  FROM phone ph, salutations s1, salutations s2,
additional_demographics ad, prospect p, address_view a  WHERE p.idnumber !=
:p_id    and p.salutcode = s1.salutcode(+) and p.csalutcode = s2.salutcode(+)
and p.idnumber = ad.idnumber(+)    and
            p.idnumber = a.idnumber and
            p.idnumber = ph.idnumber(+)  and  p.idnumber in (select idnumber
from prospect p1  where p1.last like SUBSTR(:p_last, 1, :p_num_last)||'%'  UNION
  select idnumber from prospect p2  where p2.clast like SUBSTR(:p_last, 1,
:p_num_last)||'%') AND  (UPPER(NVL(a.line1, :p_def)) like UPPER(SUBSTR(:p_line1,
1, :p_num_line1))||'%' or UPPER(NVL(a.line2, :p_def)) like
UPPER(SUBSTR(:p_line1, 1, :p_num_line1))||'%' or UPPER(NVL(a.line3, :p_def))
like UPPER(SUBSTR(:p_line1, 1, :p_num_line1))||'%' ) AND  NVL(a.zip, :p_def)
like SUBSTR(:p_zip, 1, :p_num_zip)||'%' and rownum < 1000

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute     12      0.00       0.00          0          0          6           0
Fetch      143      0.00       0.00      72802     804055        290         136
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      155      0.00       0.00      72802     804055        296         136

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: 12  (GUI)   (recursive depth: 1)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: RULE
      0   SORT (UNIQUE)
      0    COUNT (STOPKEY)
      0     NESTED LOOPS (OUTER)
      0      NESTED LOOPS (OUTER)
      0       NESTED LOOPS (OUTER)
      0        NESTED LOOPS (OUTER)
      0         NESTED LOOPS (OUTER)
      0          NESTED LOOPS
      0           NESTED LOOPS (OUTER)
      0            NESTED LOOPS (OUTER)
      0             NESTED LOOPS (OUTER)
      0              NESTED LOOPS
      0               VIEW OF 'VW_NSO_1'
      0                SORT (UNIQUE)
      0                 UNION-ALL
      0                  TABLE ACCESS   GOAL: ANALYZED (BY
                             INDEX ROWID) OF 'PROSPECT'
      0                   INDEX (RANGE SCAN) OF
                              'LNAME_INDEX' (NON-UNIQUE)
      0                  TABLE ACCESS   GOAL: ANALYZED (BY
                             INDEX ROWID) OF 'PROSPECT'
      0                   INDEX (RANGE SCAN) OF
                              'CNAME_INDEX' (NON-UNIQUE)
      0               TABLE ACCESS   GOAL: ANALYZED (BY INDEX
                          ROWID) OF 'PROSPECT'
      0                INDEX   GOAL: ANALYZED (UNIQUE SCAN)
                           OF 'PK_PROSPECT_IDNUMBER' (UNIQUE)
      0              INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF
                         'PK_AD_IDNUMBER' (UNIQUE)
      0             INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF
                        'PK_SALUTATIONS_SALUTC' (UNIQUE)
      0            INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF
                       'PK_SALUTATIONS_SALUTC' (UNIQUE)
      0           TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID)
                      OF 'ADDRESS'
      0            INDEX (RANGE SCAN) OF 'ADDRESS_IDNUMBER_INDEX'
                       (NON-UNIQUE)
      0          INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF
                     'PK_STATES_STATEC' (UNIQUE)
      0         INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF
                    'PK_ADDTAB1_CODE' (UNIQUE)
      0        INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF
                   'PK_ADDRS_CODE' (UNIQUE)
      0       INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF
                  'PK_COUNTRY_CODE' (UNIQUE)
      0      INDEX (RANGE SCAN) OF 'PHONE_IDNUMBER_INDEX' (NON-UNIQUE)

********************************************************************************

select length
from
 fet$ where file#=:1 and block#=:2 and ts#=:3

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       55      0.00       0.00          0          0          0           0
Execute     55      0.00       0.00          0          0          0           0
Fetch       55      0.00       0.00          0        110          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      165      0.00       0.00          0        110          0           0

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: SYS   (recursive depth: 2)
********************************************************************************

delete from seg$
where
 ts#=:1 and file#=:2 and block#=:3

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        7      0.00       0.00          0          0          0           0
Execute      7      0.00       0.00          1         35         21           7
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       14      0.00       0.00          1         35         21           7

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: SYS   (recursive depth: 2)
********************************************************************************

insert into fet$ (file#,block#,ts#,length)
values
 (:1,:2,:3,:4)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       32      0.00       0.00          0          0          0           0
Execute     32      0.00       0.00          0         32         42          32
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       64      0.00       0.00          0         32         42          32

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: SYS   (recursive depth: 2)
********************************************************************************

delete from uet$
where
 ts#=:1 and segfile#=:2 and segblock#=:3 and ext#=:4

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       30      0.00       0.00          0          0          0           0
Execute     30      0.00       0.00          0        150         30          30
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       60      0.00       0.00          0        150         30          30

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: SYS   (recursive depth: 2)
********************************************************************************

delete from fet$
where
 file#=:1 and block#=:2 and ts#=:3

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       32      0.00       0.00          0          0          0           0
Execute     32      0.00       0.00          0        144         32          32
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       64      0.00       0.00          0        144         32          32

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: SYS   (recursive depth: 2)
********************************************************************************

SELECT IDNUMBER.NEXTVAL nextseq
FROM
 dual WHERE ROWNUM = 1

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        7      0.00       0.00          3          7         35           7
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       21      0.00       0.00          3          7         35           7

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: 12  (GUI)   (recursive depth: 1)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: RULE
      0   SEQUENCE OF 'IDNUMBER'
      0    COUNT (STOPKEY)
      0     TABLE ACCESS (FULL) OF 'DUAL'

********************************************************************************

update seq$ set increment$=:2,minvalue=:3,maxvalue=:4,cycle#=:5,order$=:6,
  cache=:7,highwater=:8,audit$=:9
where
 obj#=:1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       30      0.00       0.00          0          0          0           0
Execute     30      0.00       0.00          4         30         90          30
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       60      0.00       0.00          4         30         90          30

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: SYS   (recursive depth: 2)
********************************************************************************

SELECT IDNUMBER
FROM
 PROSPECT  WHERE IDNUMBER = :b1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute      7      0.00       0.00          0          0          0           0
Fetch        7      0.00       0.00          0         21          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       14      0.00       0.00          0         21          0           0

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: 12  (GUI)   (recursive depth: 1)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: RULE
      0   INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF 'PK_PROSPECT_IDNUMBER'
              (UNIQUE)

********************************************************************************

SELECT SYSDATE
FROM
 SYS.DUAL

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Followup:
you seem to have done it without timed statistics enabled.

it is "not useful" in that form.

enable timed statistics and do it again and you'll be able to SEE where the
problem is (look at the total lines, look for a big total)
Rating: 3
sorry Tom...... February 16, 2004
Reviewer: Denise from Virginia,USA
the tkprof file is far to lengthy to post here, only
a portion of it got posted.

I'll figure it out eventually.

take care..
Rating: 4
thanks Tom!!! February 16, 2004
Reviewer: Denise from Virginia,USA
errr...one of their support technicians logged in and
performed the trace.

They forbid us to set the timed_statictics parameter to
TRUE in the init.ora file...so everytime we need to do
a trace the parameter is set to TRUE through SQL Plus
and apparently the tech forgot to do that.

I will do it myself this time and re-run the trace.

I'm surprised none of the developers took note of that!

as always Tom thanks a million!

:~)
Followup:
alter system set timed_statistics=true;

will turn it on....  your systems should be running with it on anyway.
impossible to tune or just even look at a system without it.

I refuse to run databases with it off ;)
Rating: 4
Tom... February 16, 2004
Reviewer: Denise from Virginia, USA
I set the timed_statistics=true;
then did:
execute dbms_system.set_sql_trace_in_session(sid,serial,TRUE)...then executed
the package through SQLPlus.

The latest trace/tkprof is about as lengthy as the first
one...you mention look for the big numbers???...do you
mean the summary numbers at the end of the report?

I see a lot of parsing from user SYS in the report???

I am not going to try and post the output here..it won't
fit.

I did see sections that related to the 10 records being
parsed,executed...etc, should I focus on those areas only
in trying to determine what is going on?

I'm still in the learning phase of trying to decipher
what the output represents.

thanks.
Followup:
i mean "look for low hanging fruit".  look at all of the totals (but not the
summaries).  Look for big totals.  zero in on those elements.
Rating: 4
Tom.... February 16, 2004
Reviewer: Denise from Virginia, USA
I went back and started looking at sections with big numbers and came across
this one that is doing an insert that appears to be taking alot of time.

INSERT INTO BATCHPAY ( ACKNOWLEDGED,ACKNOWLEDGED_DATE,ACKNOWLEDGEMENT,
  ANONYMOUS,APPEALCODE,BD_ID,BENEFIT,BILLING_PARTY,CERTIFICATE_NUM,CHECK_DATE,
  CHECK_NUMBER,CLASSCODE,COMPLIMENTARY,CRACCOUNTNO,DBACCOUNTNO,
  PREMIUM_DECLINED,T_AMOUNT,T_DATE,EVALUATION_DATE,EVALUATION_SOURCE,
  FULL_AMOUNT,FUNDCODE,GIFTGIVERID,GIFTTYPE,HIGH_VALUE,HMFLAG,HM_REMARK,HONOR,
  LOW_VALUE,MATCH,MEDIUM_VALUE,PAYAMOUNT,PAY_METHOD,PGTRANSNUM,PROPOSALNUMBER,
  QUANTITY,REASON,RECEIPT,RECEIPT_PARTY,RECEIVED,RECIPIENT,REMARK,
  SECURITY_NAME,SECURITY_TYPE,SEC_REMARK,SHARES,SOFT_CREDIT,SOLICITORCODE,
  UNITID,USERCODE1,USERCODE2,USERCODE3,USERCODE4,USERCODE5,USERCODE6,
  USERFLAG1,USERNUMBER1,USERTEXT1,VOLUNTEER_ID,YEAR,EST_VALUE,BATCHNO )
VALUES
 ( :b1,:b2,:b3,:b4,:b5,:b6,:b7,:b8,:b9,:b10,:b11,:b12,:b13,:b14,:b15,:b16,
  :b17,:b18,:b19,:b20,:b21,:b22,:b23,:b24,:b25,:b26,:b27,:b28,:b29,:b30,:b31,
  :b32,:b33,:b34,:b35,:b36,:b37,:b38,:b39,:b40,:b41,:b42,:b43,:b44,:b45,:b46,
  :b47,:b48,:b49,:b50,:b51,:b52,:b53,:b54,:b55,:b56,:b57,:b58,:b59,:b60,:b61,
  :b62  )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1     22.72      23.60          6          0          6           0
Execute     10      1.62       1.85         28          1        304          10
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       11     24.34      25.45         34          1        310          10
Followup:
hmm, a parse on an insert with values taking 23 cpu seconds.  that is rather
large.  what can you say about that batchpay table?  if you

exp userid=u/p tables=batchpay rows=n
imp userid=u/p full=y indexfile=batchpay.sql

what does that script look like -- lets see whats behind this table.
Rating: 4
Tom...last one... February 16, 2004
Reviewer: Denise from Virginia,USA
I think I'm beginning to understand this stuff.

here's another section:
It seems during inserts is where the performance is
getting bogged down..am I on the right track here?

promise not to bother anymore(at least for today  ;~))

INSERT INTO BATCHDETAIL ( TRANSNUM,BATCHNO,SPLIT_TRANSNUM,PARENT_ID,TRANTYPE,
  IDNUMBER,DOCUMENT_NUMBER,POST_STATUS,POST_RULE,POST_SEQUENCE,POST_DATE,
  ERRMSG,TIMESTAMP,USERID_ADD,UPDATE_DATE,USERID_CHANGE,NEW_TRANSNUM )
VALUES
 ( :b1,:b2,:b3,:b4,:b5,:b6,:b7,:b8,:b9,:b10,:b11,:b12,:b13,:b14,:b15,:b16,
  :b17  )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      4.26       4.54          2          0          2           0
Execute     10      2.85       2.95         10          1        150          10
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       11      7.11       7.49         12          1        152          10

Misses in library cache during parse: 1
Optimizer goal: RULE
Parsing user id: 12     (recursive depth: 1)
Followup:
your parse time is excessively high -- it should not be.  Not sure why you are
getting misses in the library cache if it already ran once before -- unless you
have massive bind variable issues that are squeezing it out.

A parse of an insert shouldn't be so big.
Rating: 5
less LIO but more elapsed time February 16, 2004
Reviewer: steve from NYC
Hi tom,

I am tuning a insert .. select /*+ index(t index_name) */ ...  its tkprof out
are:

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.03          0          0          1           0
Execute      1    495.20    1302.50    2882183    1285075         13          78
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2    495.21    1302.53    2882183    1285075         14          78

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 28

Rows     Row Source Operation
-------  ---------------------------------------------------
     79  SORT GROUP BY
18939046   HASH JOIN
5505677    TABLE ACCESS BY INDEX ROWID XX_ITEM
6228575     INDEX FULL SCAN (object id 3569676)
22491942    TABLE ACCESS FULL XX_INVENTORY PARTITION: START=12 STOP=12

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  file open                                       5        0.00          0.00
  db file sequential read                    140772        0.17          5.99
  db file scattered read                      19037        1.59        103.70
  db file parallel read                        1361        0.06          1.21
  direct path write                           90402        0.34         33.99
  latch free                                    157        0.02          1.60
  direct path read                           896434        2.95        720.95
  SQL*Net message to client                       1        0.00          0.00
  SQL*Net message from client                     1        0.01          0.01
********************************************************************************

and after I removed the index hint from the sql I got:

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1    417.52    1914.01    2171938    1071811         21          78
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2    417.53    1914.02    2171938    1071811         21          78

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 28

Rows     Row Source Operation
-------  ---------------------------------------------------
     79  SORT GROUP BY
18939618   HASH JOIN
5505696    TABLE ACCESS FULL XX_ITEM
22492514    TABLE ACCESS FULL XX_INVENTORY PARTITION: START=12 STOP=12

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  file open                                       4        0.05          0.05
  db file scattered read                      26087        0.81        143.09
  db file sequential read                       770        0.08          0.26
  direct path write                           82283        0.43         63.17
  latch free                                    144        0.02          1.46
  direct path read                           558661        1.05       1350.19
  SQL*Net message to client                       1        0.00          0.00
  SQL*Net message from client                     1        0.01          0.01
********************************************************************************

The sql with index hint run just 21 min,
the sql without index hint took 31 min.

You can see from the above output, the sql without hint has less CPU time, less
LIOs that is what you always suggest. But it has more response time that is not
the user wanted.
1) Does this means less LIOs are not always good?
2) elapsed = cpu + sum(total waited)?
    but in this example
    1914.01 < 417.52  + (1350.19 + 143.09 + 63.17+ ...)

Thanks!

Steve
Followup:
1) "in general, you tune to reduce lio"

for a big batch like this, you want to bump up your hash_area_size which looks
too small (for either query) given the number of direct path reads.

up the hash area size as big as you can and give both a whirl, you should see
large differences.

2) you miss "wait for cpu" - that is the time spent in runnable mode but no cpu
available to service you.  there is no wait event recorded for that.
Rating: 4
Here it is Tom February 16, 2004
Reviewer: Denise from Virginia,USA
REM  CREATE TABLE "GUI"."BATCHPAY" ("ACKNOWLEDGED" VARCHAR2(1) NOT NULL
REM  ENABLE, "ACKNOWLEDGED_DATE" DATE, "ACKNOWLEDGEMENT" VARCHAR2(4),
REM  "ANONYMOUS" VARCHAR2(1) NOT NULL ENABLE, "APPEALCODE" VARCHAR2(10),
REM  "BD_ID" NUMBER(8, 0) NOT NULL ENABLE, "BENEFIT" NUMBER(13, 2),
REM  "BILLING_PARTY" NUMBER(12, 0), "CERTIFICATE_NUM" VARCHAR2(40),
REM  "CHECK_DATE" DATE, "CHECK_NUMBER" VARCHAR2(16), "CLASSCODE"
REM  VARCHAR2(6), "COMPLIMENTARY" VARCHAR2(1), "CRACCOUNTNO"
REM  VARCHAR2(100), "DBACCOUNTNO" VARCHAR2(100), "PREMIUM_DECLINED"
REM  VARCHAR2(1) NOT NULL ENABLE, "T_AMOUNT" NUMBER(13, 2) NOT NULL
REM  ENABLE, "T_DATE" DATE NOT NULL ENABLE, "EVALUATION_DATE" DATE,
REM  "EVALUATION_SOURCE" VARCHAR2(6), "FULL_AMOUNT" NUMBER(13, 2),
REM  "FUNDCODE" VARCHAR2(10), "GIFTGIVERID" NUMBER(12, 0), "GIFTTYPE"
REM  VARCHAR2(4), "HIGH_VALUE" NUMBER(15, 4), "HMFLAG" VARCHAR2(1),
REM  "HM_REMARK" VARCHAR2(2000), "HONOR" NUMBER(12, 0), "LOW_VALUE"
REM  NUMBER(15, 4), "MATCH" VARCHAR2(1) NOT NULL ENABLE, "MEDIUM_VALUE"
REM  NUMBER(15, 4), "PAYAMOUNT" NUMBER(13, 2) NOT NULL ENABLE,
REM  "PAY_METHOD" VARCHAR2(4), "PGTRANSNUM" NUMBER(8, 0), "PROPOSALNUMBER"
REM  NUMBER(8, 0), "QUANTITY" NUMBER(8, 0), "REASON" VARCHAR2(4),
REM  "RECEIPT" VARCHAR2(1), "RECEIPT_PARTY" NUMBER(12, 0), "RECEIVED"
REM  VARCHAR2(40), "RECIPIENT" NUMBER(12, 0), "REMARK" VARCHAR2(2000),
REM  "SECURITY_NAME" VARCHAR2(60), "SECURITY_TYPE" VARCHAR2(6),
REM  "SEC_REMARK" VARCHAR2(2000), "SHARES" NUMBER(8, 0), "SOFT_CREDIT"
REM  VARCHAR2(1), "SOLICITORCODE" VARCHAR2(4), "UNITID" NUMBER(12, 0),
REM  "USERCODE1" VARCHAR2(4), "USERCODE2" VARCHAR2(4), "USERCODE3"
REM  VARCHAR2(4), "USERCODE4" VARCHAR2(4), "USERCODE5" VARCHAR2(4),
REM  "USERCODE6" VARCHAR2(4), "USERFLAG1" VARCHAR2(1), "USERNUMBER1"
REM  NUMBER(13, 2), "USERTEXT1" VARCHAR2(80), "VOLUNTEER_ID" NUMBER(12,
REM  0), "YEAR" NUMBER(4, 0), "EST_VALUE" NUMBER(11, 0), "BATCHNO"
REM  NUMBER(8, 0) NOT NULL ENABLE) PCTFREE 10 PCTUSED 40 INITRANS 1
REM  MAXTRANS 255 LOGGING STORAGE(INITIAL 440401920 NEXT 10485760
REM  MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS
REM  1 BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" ;
CONNECT GUI;
CREATE UNIQUE INDEX "GUI"."PK_BATCHPAY_BD_ID" ON "BATCHPAY" ("BD_ID" )
PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 89178112 NEXT 1048576
MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_ACKNOWLEDGEMENT_INDEX" ON "BATCHPAY"
("ACKNOWLEDGEMENT" ) PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL
60440576 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS
1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_APPEALCODE_INDEX" ON "BATCHPAY" ("APPEALCODE"
) PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 124174336 NEXT
1048576 MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1 FREELIST
GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_BILLING_PARTY_INDEX" ON "BATCHPAY"
("BILLING_PARTY" ) PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL
1048576 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1
FREELIST GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_CLASSCODE_INDEX" ON "BATCHPAY" ("CLASSCODE" )
PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 1048576 NEXT 1048576
MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_CRACCOUNTNO_INDEX" ON "BATCHPAY"
("CRACCOUNTNO" ) PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL
1048576 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1
FREELIST GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_DBACCOUNTNO_INDEX" ON "BATCHPAY"
("DBACCOUNTNO" ) PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL
1048576 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1
FREELIST GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_GIFTGIVERID_INDEX" ON "BATCHPAY"
("GIFTGIVERID" ) PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL
1048576 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1
FREELIST GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_EVAL_SOURCE_INDEX" ON "BATCHPAY"
("EVALUATION_SOURCE" ) PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL
1048576 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1
FREELIST GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_FUNDCODE_INDEX" ON "BATCHPAY" ("FUNDCODE" )
PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 94502912 NEXT 1048576
MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_GIFTTYPE_INDEX" ON "BATCHPAY" ("GIFTTYPE" )
PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 94511104 NEXT 1048576
MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_HONOR_INDEX" ON "BATCHPAY" ("HONOR" ) PCTFREE
10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 1048576 NEXT 1048576 MINEXTENTS
1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL
DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_PAY_METHOD_INDEX" ON "BATCHPAY" ("PAY_METHOD"
) PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 1048576 NEXT 1048576
MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_PGTRANSNUM_INDEX" ON "BATCHPAY" ("PGTRANSNUM"
) PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 1048576 NEXT 1048576
MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_PROPOSALNUMBER_INDEX" ON "BATCHPAY"
("PROPOSALNUMBER" ) PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL
1048576 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1
FREELIST GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_REASON_INDEX" ON "BATCHPAY" ("REASON" )
PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 1048576 NEXT 1048576
MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_RECEIPT_PARTY_INDEX" ON "BATCHPAY"
("RECEIPT_PARTY" ) PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL
1048576 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1
FREELIST GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_RECIPIENT_INDEX" ON "BATCHPAY" ("RECIPIENT" )
PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 1048576 NEXT 1048576
MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_SECURITY_TYPE_INDEX" ON "BATCHPAY"
("SECURITY_TYPE" ) PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL
1048576 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1
FREELIST GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_SOLICITORCODE_INDEX" ON "BATCHPAY"
("SOLICITORCODE" ) PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL
1048576 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1
FREELIST GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_UNITID_INDEX" ON "BATCHPAY" ("UNITID" )
PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 1048576 NEXT 1048576
MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_USERCODE1_INDEX" ON "BATCHPAY" ("USERCODE1" )
PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 1048576 NEXT 1048576
MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_USERCODE2_INDEX" ON "BATCHPAY" ("USERCODE2" )
PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 1048576 NEXT 1048576
MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_USERCODE3_INDEX" ON "BATCHPAY" ("USERCODE3" )
PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 1048576 NEXT 1048576
MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_USERCODE4_INDEX" ON "BATCHPAY" ("USERCODE4" )
PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 1048576 NEXT 1048576
MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_USERCODE5_INDEX" ON "BATCHPAY" ("USERCODE5" )
PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 1048576 NEXT 1048576
MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_USERCODE6_INDEX" ON "BATCHPAY" ("USERCODE6" )
PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 1048576 NEXT 1048576
MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_VOLUNTEER_ID_INDEX" ON "BATCHPAY"
("VOLUNTEER_ID" ) PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL
1048576 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1
FREELIST GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
CREATE INDEX "GUI"."BATCHPAY_BATCHNO_INDEX" ON "BATCHPAY" ("BATCHNO" )
PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 105127936 NEXT 1048576
MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT) TABLESPACE "PLEDGEMAKER" LOGGING ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "PK_BATCHPAY_BD_ID"
REM  PRIMARY KEY ("BD_ID") USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255
REM  STORAGE(INITIAL 89178112 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 505
REM  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
REM  TABLESPACE "PLEDGEMAKER" ENABLE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT
REM  "CHK_BATCHPAY_ACKNOWLEDGED" CHECK (acknowledged IN ('S','Y','N'))
REM  ENABLE NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "CHK_BATCHPAY_ANONYMOUS"
REM  CHECK (anonymous IN ('Y','N')) ENABLE NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT
REM  "CHK_BATCHPAY_COMPLIMENTARY" CHECK (complimentary IN ('Y','N'))
REM  ENABLE NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT
REM  "CHK_BATCHPAY_PREMIUM_DECLINED" CHECK (premium_declined IN ('Y','N'))
REM  ENABLE NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "CHK_BATCHPAY_HMFLAG"
REM  CHECK (hmflag IN ('H','M')) ENABLE NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "CHK_BATCHPAY_MATCH"
REM  CHECK (match IN ('Y','N')) ENABLE NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "CHK_BATCHPAY_RECEIPT"
REM  CHECK (receipt IN ('A','N','S','V','Y')) ENABLE NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT
REM  "CHK_BATCHPAY_SOFT_CREDIT" CHECK (soft_credit IN ('Y','N')) ENABLE
REM  NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "CHK_BATCHPAY_USERFLAG1"
REM  CHECK (userflag1 IN ('Y','N')) ENABLE NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT
REM  "FK_BATCHPAY_ACKNOWLEDGEMENT" FOREIGN KEY ("ACKNOWLEDGEMENT")
REM  REFERENCES "ACKNOWLEDGEMENTS" ("CODE") ENABLE NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "FK_BATCHPAY_APPEALCODE"
REM  FOREIGN KEY ("APPEALCODE") REFERENCES "APPEALS" ("APPEALCODE") ENABLE
REM  NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "FK_BATCHPAY_BD_ID"
REM  FOREIGN KEY ("BD_ID") REFERENCES "BATCHDETAIL" ("TRANSNUM") ON DELETE
REM  CASCADE ENABLE NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT
REM  "FK_BATCHPAY_BILLING_PARTY" FOREIGN KEY ("BILLING_PARTY") REFERENCES
REM  "PROSPECT" ("IDNUMBER") ENABLE NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "FK_BATCHPAY_CLASSCODE"
REM  FOREIGN KEY ("CLASSCODE") REFERENCES "CLASSIFICATIONS" ("CLASSCODE")
REM  ENABLE NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "FK_BATCHPAY_CRACCOUNTNO"
REM  FOREIGN KEY ("CRACCOUNTNO") REFERENCES "ACCOUNTS" ("ACCOUNTNO")
REM  ENABLE NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "FK_BATCHPAY_DBACCOUNTNO"
REM  FOREIGN KEY ("DBACCOUNTNO") REFERENCES "ACCOUNTS" ("ACCOUNTNO")
REM  ENABLE NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT
REM  "FK_BATCHPAY_EVALUATION_SOURCE" FOREIGN KEY ("EVALUATION_SOURCE")
REM  REFERENCES "EVAL_SOURCE" ("EVALUATION_SOURCE") ENABLE NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "FK_BATCHPAY_FUNDCODE"
REM  FOREIGN KEY ("FUNDCODE") REFERENCES "FUNDS" ("FUNDCODE") ENABLE
REM  NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "FK_BATCHPAY_GIFTGIVERID"
REM  FOREIGN KEY ("GIFTGIVERID") REFERENCES "PROSPECT" ("IDNUMBER") ENABLE
REM  NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "FK_BATCHPAY_GIFTTYPE"
REM  FOREIGN KEY ("GIFTTYPE") REFERENCES "GIFTTYPES" ("GIFTTYPE") ENABLE
REM  NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "FK_BATCHPAY_HONOR"
REM  FOREIGN KEY ("HONOR") REFERENCES "PROSPECT" ("IDNUMBER") ENABLE
REM  NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "FK_BATCHPAY_PAY_METHOD"
REM  FOREIGN KEY ("PAY_METHOD") REFERENCES "PAY_METHOD" ("PAYCODE") ENABLE
REM  NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "FK_BATCHPAY_PGTRANSNUM"
REM  FOREIGN KEY ("PGTRANSNUM") REFERENCES "PLANNEDGIFT" ("TRANSNUM")
REM  ENABLE NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT
REM  "FK_BATCHPAY_PROPOSALNUMBER" FOREIGN KEY ("PROPOSALNUMBER")
REM  REFERENCES "TRACKING" ("PROPOSALNUMBER") ENABLE NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "FK_BATCHPAY_REASON"
REM  FOREIGN KEY ("REASON") REFERENCES "GIFT_REASONS" ("CODE") ENABLE
REM  NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT
REM  "FK_BATCHPAY_RECEIPT_PARTY" FOREIGN KEY ("RECEIPT_PARTY") REFERENCES
REM  "PROSPECT" ("IDNUMBER") ENABLE NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "FK_BATCHPAY_RECIPIENT"
REM  FOREIGN KEY ("RECIPIENT") REFERENCES "PROSPECT" ("IDNUMBER") ENABLE
REM  NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT
REM  "FK_BATCHPAY_SECURITY_TYPE" FOREIGN KEY ("SECURITY_TYPE") REFERENCES
REM  "SECTYPES" ("SECURITY_TYPE") ENABLE NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT
REM  "FK_BATCHPAY_SOLICITORCODE" FOREIGN KEY ("SOLICITORCODE") REFERENCES
REM  "SOLICITORS" ("SOLICITORCODE") ENABLE NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "FK_BATCHPAY_UNITID"
REM  FOREIGN KEY ("UNITID") REFERENCES "PROSPECT" ("IDNUMBER") ENABLE
REM  NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "FK_BATCHPAY_USERCODE1"
REM  FOREIGN KEY ("USERCODE1") REFERENCES "BDTABLE1" ("CODE") ENABLE
REM  NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "FK_BATCHPAY_USERCODE2"
REM  FOREIGN KEY ("USERCODE2") REFERENCES "BDTABLE2" ("CODE") ENABLE
REM  NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "FK_BATCHPAY_USERCODE3"
REM  FOREIGN KEY ("USERCODE3") REFERENCES "BDTABLE3" ("CODE") ENABLE
REM  NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "FK_BATCHPAY_USERCODE4"
REM  FOREIGN KEY ("USERCODE4") REFERENCES "BDTABLE4" ("CODE") ENABLE
REM  NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "FK_BATCHPAY_USERCODE5"
REM  FOREIGN KEY ("USERCODE5") REFERENCES "BDTABLE5" ("CODE") ENABLE
REM  NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "FK_BATCHPAY_USERCODE6"
REM  FOREIGN KEY ("USERCODE6") REFERENCES "BDTABLE6" ("CODE") ENABLE
REM  NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT
REM  "FK_BATCHPAY_VOLUNTEER_ID" FOREIGN KEY ("VOLUNTEER_ID") REFERENCES
REM  "PROSPECT" ("IDNUMBER") ENABLE NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ADD CONSTRAINT "FK_BATCHPAY_BATCHNO"
REM  FOREIGN KEY ("BATCHNO") REFERENCES "BATCH" ("BATCHNO") ENABLE
REM  NOVALIDATE ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT "PK_BATCHPAY_BD_ID" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "CHK_BATCHPAY_ACKNOWLEDGED" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "CHK_BATCHPAY_ANONYMOUS" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "CHK_BATCHPAY_COMPLIMENTARY" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "CHK_BATCHPAY_PREMIUM_DECLINED" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT "CHK_BATCHPAY_HMFLAG" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT "CHK_BATCHPAY_MATCH" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT "CHK_BATCHPAY_RECEIPT" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "CHK_BATCHPAY_SOFT_CREDIT" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "CHK_BATCHPAY_USERFLAG1" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "FK_BATCHPAY_ACKNOWLEDGEMENT" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "FK_BATCHPAY_APPEALCODE" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT "FK_BATCHPAY_BD_ID" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "FK_BATCHPAY_BILLING_PARTY" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "FK_BATCHPAY_CLASSCODE" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "FK_BATCHPAY_CRACCOUNTNO" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "FK_BATCHPAY_DBACCOUNTNO" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "FK_BATCHPAY_EVALUATION_SOURCE" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT "FK_BATCHPAY_FUNDCODE" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "FK_BATCHPAY_GIFTGIVERID" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT "FK_BATCHPAY_GIFTTYPE" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT "FK_BATCHPAY_HONOR" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "FK_BATCHPAY_PAY_METHOD" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "FK_BATCHPAY_PGTRANSNUM" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "FK_BATCHPAY_PROPOSALNUMBER" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT "FK_BATCHPAY_REASON" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "FK_BATCHPAY_RECEIPT_PARTY" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "FK_BATCHPAY_RECIPIENT" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "FK_BATCHPAY_SECURITY_TYPE" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "FK_BATCHPAY_SOLICITORCODE" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT "FK_BATCHPAY_UNITID" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "FK_BATCHPAY_USERCODE1" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "FK_BATCHPAY_USERCODE2" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "FK_BATCHPAY_USERCODE3" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "FK_BATCHPAY_USERCODE4" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "FK_BATCHPAY_USERCODE5" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "FK_BATCHPAY_USERCODE6" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT
REM  "FK_BATCHPAY_VOLUNTEER_ID" ;
REM  ALTER TABLE "GUI"."BATCHPAY" ENABLE CONSTRAINT "FK_BATCHPAY_BATCHNO" ;
Followup:
Ok, that is why the hard parse the first time takes so darn long.  Look at the
number of indexes and constraints.

BUT -- that is a one time penalty.  It should only occur after a cold start of
the database -- ONCE.  After that -- soft parse city and the parsing should not
take so long.

So -- if you run this thing "more than once" and benchmark the second run (after
the shared pool has been warmed up) -- what do you see?
Rating: 4
Tom.... February 17, 2004
Reviewer: Denise from Virginia,USA
The last trace/tkprof file I created was taken from a
second run yesterday afternoon.

It is the same amount of time regardless if the test run
is 1st,2nd 3rd...etc....it takes 10 minutes to process
10 records..on any given run.

Given the situation it is the software vendor that needs
to look at the design of this module and figure out what
needs to be fixed.

Even if you were able to pinpoint what is causing the
slowdown there is nothing I can do to fix it...heaven forbid I should offer any
corrective measures based on any
recommendations provided by you. You know *what* would hit
the fan.

Thanks for helping at least I learned couple new things
especially using exp/imp to create a file that looks at
the index structure of a table...that's real neat.

have a good day!
Rating: 3
Tom...for my understanding.... February 17, 2004
Reviewer: Denise from Virginia,USA
In looking at the tkprof output from yesterday when I executed the package it
generated the following trace output.

I recall your mentioning of misses to library cache during
parse. I notice here it equals 1....in looking at other
sections it = 0.

could you explain what this represents...thanks.

begin lockbox_package.run_lockbox(file_name => 'testjan14.txt', file_path =>
  'G:\Pledge\PledgeMaker\bplog',def => 'GTELE_NOID'); end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.41       0.42          0          0          0           0
Execute      1    169.56     200.09          0          0          5           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2    169.97     200.51          0          0          5           1

Misses in library cache during parse: 1
Optimizer goal: RULE
Parsing user id: 12
Followup:
that means it was hard parsed -- probably due to the glaring lack of bind
variables.

if it were run again, it would not have a miss.
Rating: 4
thanks Tom..one last question... February 17, 2004
Reviewer: Denise from Virginia, USA
I'm re-reading chapter 10 of your book, specifically the
section on TKPROF interpretation.

While I look at the tkprof from yesterday I'm referring
to your book. Now I totally understand the Library Cache
misses relating to hard parse vs soft parse.

you state:
A large cpu or elapsed time number indicates queries
that represent the 'lowest hanging fruit'.

can you expand a little on what that means.

This seems to be where the problem lies with our issue.

thanks a zillion!!!
Followup:
It's like this.  this morning, I noticed one of my pages on my site was getting
slow (a page you guys don't see, one I use to see the "reviews").

So, i turned on tracing and ran the page.  Once I got the tkprof report, I
simply:

$ grep ^total tk.prf

total        1      0.00       0.00          0          0          0           0
total        2      0.00       0.00          0          0          0           0
...
total       28      2.01       2.09          3       5048          0          21
....
total        6      0.02       0.01          0          0          0           3
total        6      0.00       0.00          0          0          0           3
total        9      0.00       0.01          3         18          0          21
...
total      503      1.43       3.95        340       4955          0         501
...
total        4      0.00       0.00          0          3          0           1
total       46      0.02       0.01          0         60          0          15
....

Well, from there -- it is obvious what the "low hanging fruit" was.  Those two
statements consumed "the big time".

I fixed the first one by making a column NOT NULL (that should have been that
way all along) and indexing it (so I do an INDEX FAST FULL SCAN now for 70 LIO's
in 0.01 cpu seconds) and I fixed the 2cnd one by using the new HTMLDB pagination
that doesn't try to count all of the stupid records (just gives prev and next
buttons, like on my home page now).  Web page runs in 0.40 instead of 5 or 6
seconds (which is at least 10 to 20 times what any respectable webpage should
take)...

Those two statements were my "low hanging fruit -- fix these and problem goes
away"
Rating: 4
Oh!!..ok Tom February 17, 2004
Reviewer: Denise from Virginia, USA
like you said yesterday...look for the big numbers...
that's where the problem lies...and that is what is called
'low hanging fruit'..(did you come up with that term?).

on a more humorous note:
everytime I read that phrase "fruit-of-the-loom" underwear
pops in my head..ho-hum...sad commentary on the state of
my brain trying to figure out this stupid software.

BYE!!!
Rating: 3
I GOT IT!!! February 17, 2004
Reviewer: Denise from Virginia,USA
did some research on 'low hanging fruit'...
(in all my years of existence I never heard that phrase).

here are a couple meanings I found:
"The thing that gets you most value for least investment.
Why stretch to reach those apples way up there when there's
one right within arm's reach."

"Proper collection of 'Low Hanging Fruit' depends on proper
profiling and optimization. Identify your bottlenecks and
concentrate your efforts on them. That will give you the most value for the
least investment."

sooo...concentrate on the big numbers...that's telling you
where the problems are.

GOT IT!!
Rating: 5
wait for cpu February 17, 2004
Reviewer: steve from nyc
Hi tom,

 you said that if
 e != c + sum(wi), i =1,2,...n
 then miss 'wait for cpu'
 if this is true, then e > c + sum(Wi)
 in my case,  e < c + sum(Wi). so is it still 'miss cpu wait'?

Thanks!

Steve
Followup:
ahh, ok, i see now.

sure -- problem is the granularity of clocks and how timing is done.

Each "event" is timed.  Fast events might

a) contribute exactly what they took
b) contribute LESS than what they took
c) contribute MORE than what they took.

that is, if you do something like:

start_time = get_clock
for i in 1 .. 1000000
loop
   start_time_2 = get_clock;
   do_something_fast
   elapsed_loop = elapsed_loop + ( get_clock - start_time_2 )
end loop
elapsed_out_of_loop = get_clock - start_time

you would reasonably expect that the two elapsed times would be nominally
different (as yours are over the long long haul).

It is due to the different timing levels.  In my tiny example above, the timer
outside of the loop would be pretty much "dead on", but the one inside the loop
not so -- well, we time different things at different levels -- giving you a
very very good idea of the timing but sometimes these small differences add up
in long running/big operations.
Rating: 5
tkprof output February 25, 2004
Reviewer: A reader
Hi

I have following tkprof output, I am totally lost with this output,

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      1      0.00       0.00     367115   60624871   24776439           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        1      0.00       0.00     367115   60624871   24776439           1

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse      155      0.04       0.04          0          0          0           0
Execute  11003      7.06       9.54        383        508      86224        2851
Fetch    13656   1753.80    1765.61        425   13410381      10848       10932
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    24814   1760.90    1775.19        808   13410889      97072       13783

From Trace Analyzer

SUMMARY OF CPU TIME, ELAPSED TIME AND WAITS PER CURSOR

cursor user
non-idle          idle
id     id   command type                count       cpu top   elapsed top
waits top     waits top
------ ---- ----------------------- --------- --------- --- --------- ---
--------- --- --------- ---
1..... 36.. insert.................      2712      5.14 2        8.12 2
2.25 1        0.00
2..... 36.. select.................      8136      0.83 3        2.48 3
1.85 2        0.00 4
3..... 36.. select.................      2712      0.18          0.66 4
0.29 4        0.00 2
4..... 36.. select.................      5424      0.40 4        0.22
0.02          0.00 1
5..... 36.. select.................      5424   1754.30 1     1763.62 1
1.82 3        0.00
6..... 0... update.................       266      0.23 5        0.35 5
0.01          0.00
7..... 0... select.................        10      0.00          0.00
8..... 0... select.................        18      0.00          0.09
0.08 5        0.00 3
9..... 0... select.................        88      0.01          0.00
10.... 0... select.................         6      0.00          0.00
11.... 0... select.................         6      0.01          0.05
0.04          0.00 5
12.... 0... insert.................         4      0.00          0.00
13.... 0... delete.................         4      0.00          0.01
14.... 0... update.................         4      0.00          0.01
15.... 36.. pl/sql execute.........         1      0.00          0.00
0.00          0.00
------ ---- ----------------------- --------- --------- --- --------- ---
--------- --- --------- ---
total. .... .......................     24815   1761.10       1775.61
6.36          0.00

SUMMARY OF PHYSICAL READS, LOGICAL READS, ROWS AND MISSES PER CURSOR

cursor user
id     id   command type                    disk        query      current
  rows    misses
------ ---- ----------------------- ------------ ------------ ------------
------------ ---------
1..... 36.. insert.................          400          561        86221
  2712         0
2..... 36.. select.................          240        13560            0
  2712         0
3..... 36.. select.................          118         5424            0
  2712         0
4..... 36.. select.................           21         2712        10848
  2712         0
5..... 36.. select.................           12     13388489            0
  2712         0
6..... 0... update.................           20          255          420
   133         0
7..... 0... select.................            0           10            0
     4         0
8..... 0... select.................           18           46            0
     2         0
9..... 0... select.................            0          112            0
    76         0
10.... 0... select.................            0           10            0
     2         0
11.... 0... select.................           16           18            0
     0         0
12.... 0... insert.................            0            4            2
     2         0
13.... 0... delete.................            0           22            2
     2         0
14.... 0... update.................            0           22            2
     2         0
15.... 36.. pl/sql execute.........       367115     60624871     24776439
     1         1
------ ---- ----------------------- ------------ ------------ ------------
------------ ---------
total. .... .......................       367960     74036116     24873934
 13784         1

What is pl/sql execute? How can I do what queries used
60624871 - 13410889 consistent gets????

Dont know where to start!
Followup:
look at the details in the tkprof report for the "low hanging fruit"

don't look at the totals, look at the individual queries.
Rating: 3
What is this? February 27, 2004
Reviewer: Justin from PA
Tom,

I am benchmarking the execution a function in a package by executing:

set autotrace traceonly
alter system set sql_trace=true;

select pkg_name.func_name(parm) from dual;

alter system set sql_trace=false;

And in my tkprof output, I got this section, which was taking up a significant
amount of the total time for the query to execute.

SELECT PT.VALUE
FROM
 SYS.V_$SESSTAT PT WHERE PT.SID=:1 AND PT.STATISTIC# IN (7,40,41,42,115,236,
  237,238,242,243) ORDER BY PT.STATISTIC#

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.85       0.86          0          0          0          20
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        8      0.85       0.86          0          0          0          20

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 56

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                       4        0.00          0.00
  SQL*Net message from client                     4        0.00          0.00

When I executed the same process over again, I took out "set autotrace
traceonly", and the section was gone.
I wanted to ask the obvious, and that is, when benchmarking with tkprof, should
you not have autotrace running because it adds extra processing?

Thanks very much for your time.
Followup:
that is the stuff that reports back your statistics in the autotrace report.

You can use

set termout off
stuff to "benchmark"
set termout on

in your script instead (or just "ignore" the stuff that isn't yours -- both are
equally valid)
Rating: 5
Excellent review March 01, 2004
Reviewer: Nasir Sarwar
Thanks a zillion time for explaining in detail!
I am having hard time to understand how many time a query is executed by just
looking at the aoutput of tkprof.
Question#1) Would you please tell me how many time this query is executed
without going into detail of this tkprof output? Does it mean that the following
query is executed a total number of 174 times?

select a.attribute_id ,b.name ,b.editable ,b.type ,b.length ,b.uniqueness ,
  b.range_low ,b.range_high ,b.description ,b.num_columns ,b.num_rows ,
  b.create_date ,b.create_by_role ,b.iuser_id ,b.default_value ,
  b.perform_immediate ,b.perform_before ,b.perform_after ,b.perform_on_click
from
 attribute_map a ,attributes b ,class_attribute_sequence c where
  (((((a.attribute_id=b.attribute_id and a.attribute_id=c.attribute_id) and
  (a.class_id=:b1 or a.class_id='TT')) and a.attribute_id=c.attribute_id) and
  c.class_Id=:b1) and a.attribute_id not  in (select distinct attribute_id
  from nvisible_map where ((((class_id=:b1 or class_id='TT') and role_id<>:b4)
   and :b4<>(-1)) and attribute_id not  in (select distinct attribute_id
  from nvisible_map where (((class_id=:b1 or class_id='TT') and :b4<>(-1))
  and role_id=:b4))))) order by c.seq_num

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      166      0.12       0.13          0       1532          0         322
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      174      0.12       0.13          0       1532          0         322

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: 27  (CENTRA3)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: RULE
      0   SORT (ORDER BY)
      0    CONCATENATION
      0     FILTER
      0      NESTED LOOPS
      0       NESTED LOOPS
      0        TABLE ACCESS (BY INDEX ROWID) OF
                   'CLASS_ATTRIBUTE_SEQUENCE'
      0         INDEX (RANGE SCAN) OF
                    'CUK1_CLASS_ATTRIBUTE_SEQUENCE' (UNIQUE)
      0        INDEX (UNIQUE SCAN) OF 'CPK_ATTRIBUTE_MAP' (UNIQUE)
      0       TABLE ACCESS (BY INDEX ROWID) OF 'ATTRIBUTES'
      0        INDEX (UNIQUE SCAN) OF 'CPK_ATTRIBUTES' (UNIQUE)
      0      CONCATENATION
      0       FILTER
      0        FILTER
      0         INDEX (RANGE SCAN) OF 'CPK_NVISIBLE_MAP' (UNIQUE)
      0        CONCATENATION
      0         FILTER
      0          INDEX (RANGE SCAN) OF 'CPK_NVISIBLE_MAP' (UNIQUE)
      0         FILTER
      0          INDEX (RANGE SCAN) OF 'CPK_NVISIBLE_MAP' (UNIQUE)
      0       FILTER
      0        FILTER
      0         INDEX (RANGE SCAN) OF 'CPK_NVISIBLE_MAP' (UNIQUE)
      0     FILTER
      0      NESTED LOOPS
      0       NESTED LOOPS
      0        TABLE ACCESS (BY INDEX ROWID) OF
                   'CLASS_ATTRIBUTE_SEQUENCE'
      0         INDEX (RANGE SCAN) OF
                    'CUK1_CLASS_ATTRIBUTE_SEQUENCE' (UNIQUE)
      0        INDEX (UNIQUE SCAN) OF 'CPK_ATTRIBUTE_MAP' (UNIQUE)
      0       TABLE ACCESS (BY INDEX ROWID) OF 'ATTRIBUTES'
      0        INDEX (UNIQUE SCAN) OF 'CPK_ATTRIBUTES' (UNIQUE)
      0      CONCATENATION
      0       FILTER
      0        FILTER
      0         INDEX (RANGE SCAN) OF 'CPK_NVISIBLE_MAP' (UNIQUE)
      0        CONCATENATION
      0         FILTER
      0          INDEX (RANGE SCAN) OF 'CPK_NVISIBLE_MAP' (UNIQUE)
      0         FILTER
      0          INDEX (RANGE SCAN) OF 'CPK_NVISIBLE_MAP' (UNIQUE)
      0       FILTER
      0        FILTER
      0         INDEX (RANGE SCAN) OF 'CPK_NVISIBLE_MAP' (UNIQUE)

Question#2: How would I decrease the number of execute and fetch in the
following select max(...)..;

select max(object_id) into :b1
from
 objects where system_id=(select system_id  from objects where object_id=:b2)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        5      0.00       0.00          0          0          0           0
Execute  16278      4.60       4.46         82      48834          0           0
Fetch    16278    105.06     103.28          7    2856550          0       16278
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    32561    109.66     107.74         89    2905384          0       16278

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: 27  (CENTRA3)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: RULE
      0   SORT (AGGREGATE)
      0    TABLE ACCESS (BY INDEX ROWID) OF 'OBJECTS'
      0     INDEX (RANGE SCAN) OF 'OBJECTS_IDX3' (NON-UNIQUE)
      0      TABLE ACCESS (BY INDEX ROWID) OF 'OBJECTS'
      0       INDEX (UNIQUE SCAN) OF 'CPK_OBJECTS' (UNIQUE)
Followup:
q1) the query was executed 4 times. (execute = 4)

q2) the only way to do that would be to change your algorithm.  You'd need to
look at the surrounding code and ask yourself "why, why am I doing it this way".
 Perhaps you need to take a procedural approach and thing "in sets"
Rating: 5
Thanks! You are a "Savior" for me! March 01, 2004
Reviewer: Nasir Sarwar from USA Texas Houston
Otherwise I would be walking on the plank...
Rating: 5
Way to go Tom! March 01, 2004
Reviewer: David from USA
I have a followup question based upon your reply above "execute=4" means the
query was executed 4 times. Does it means that the following query was execute
'0' times? My understanding is that it probably fetching the data from shared
pool for the already executed query? Right? Would you please explain it in
detail if it is so that is execute '0' is this case?
SELECT object_id, object_name, title, system_id, system_version,
  system_revision, version, revision
FROM
 objects WHERE object_type IN ('D','C') AND ( class_id LIKE 'GGAGAA') AND
  EXISTS (select object_id from members WHERE objects.object_id =
  members.object_id AND node_id IN (SELECT link_node_id FROM node_map WHERE
  node_id LIKE 'ABAAAAAI' ) )

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     1593      4.14       4.57          0      74393          0       15923
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1593      4.14       4.57          0      74393          0       15923
Followup:
it means that tracing was turned on AFTER the query was parsed and after the
query was opened -- but before the query finished fetching data.  like this:

ops$tkyte@ORA9IR2> variable x refcursor
ops$tkyte@ORA9IR2> exec open :x for select * from dual;

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA9IR2> print x

D
-
X

ops$tkyte@ORA9IR2> exit

and tkprof would show:

SELECT *
from
 dual

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        1      0.00       0.00          0          3          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        1      0.00       0.00          0          3          0           1
Rating: 5
Excellent March 10, 2004
Reviewer: Dillip
tkprof indicates that it took 1.45 sec to execute the block, is it correct ? why
is it so expensive to execute a block ? What are the measures for tuning this,
for a larger bloock I have seen it takes 90 sec for 12500 executions.

declare
begin
insert into test1(id, value) values ('01',10);
insert into test2(id, value) values ('01',20);
update test1 set value = 100 where id = '01';
update test2 set value = 200 where id = '20';
end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse      505      0.24       0.32          2         14          0           0
Execute    505      1.09       1.13          0          0          0         505
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1010      1.33       1.45          2         14          0         505
Followup:
it is not expensive to execute a block.

time begin null; end;

you'll see.  You executed the block 505 times. (and parsed it 505 times -- thats
really bad, should be 1 parse, 505 executes in a well written application)

that is 0.17 elapsed seconds per execute (4 statements being executed).  Get rid
of the parse and that is 0.13 seconds per execute or about 0.03 seconds per
statement.
Rating: 5
Excellent March 11, 2004
Reviewer: Dillip
Tom, Thanks for the explanation, what I do not understand is I am excuting the
block (same block) for 505 times, so why it is parsing 505 times, and the time
for the inserts and updates are shown separately in the trace again (as
indivisual statement), this is the time only to execute the block (you can see
in the rows processed it is showing as 505 not 505*4). Can you pls explain this
?
Followup:
why is it parsing?  -- that is 100% under YOUR control.  if you are using
sqlplus, it is a rather "dumb" tool and parses each statement.  if you wrote a
program, it can be "smart" and parse once.  at the end -- it is up to you.

as for the timing, it depends on your release how much and whether the timings
of the statements in plsql sql (the recursive sql) is in your timnig or not.
some of it will be and in some releases -- it'll be 100%.

You can see this if you grep your trace file for the elapsed times on that
statement, for example I ran:

create table test1 (id varchar2(2), value int );
create table test2 (id varchar2(2), value int );

alter session set sql_trace=true;
begin
    for i in 1 ..&1
    loop
        execute immediate 'declare begin
          insert into test1(id, value) values (''01'',10);
          insert into test2(id, value) values (''01'',20);
          update test1 set value = 100 where id = ''01'';
          update test2 set value = 200 where id = ''20'';
          end;';
    end loop;
end;
/

Now, that parse/executed that block N times -- if you grep out the execute times
for that cursor (mine was always EXEC #8 if you log in clean and exit before
grepping) you'll see the times go up - as you update more and more and more
records each time.

I'd use the dbms_profiler tool to analyze plsql performance.
Rating: 5
tkprof not found March 22, 2004
Reviewer: A Reader from IND
hi,
I have installed oracle 9i on my win box.
I did not find tkprof on my pc.
Followup:
Oracle 9i release 1 with no patches did not have tkprof on windows.  there is a
patch on metalink you may retrieve to get it.

Oracle 9iR2 has it by default.
March 22, 2004 -- Thanks for the question regarding "9i dynamic SGA, pre-allocated?", version 9.2.0.1.0
You Asked
Hi Tom

I have been trying out Oracle 9i these couple of weeks, I notice that when I set
sga_max_size to a size then whenever I start the instance that amount of SGA is
assigned no matter how big are my data cache or shared pool. Are they
pre-allocated?

For example if I set sga_max_size 200M, db_cache 50M, shared pool 50M, java pool
10M, large pool 10M. When I start the instance and do show SGA it actually says
200M assigned instead of around 120M. At OS level I havent tested it still but
is this 200MB assigned to Oracle? Or Oracle memory consumption will only
increase only when we increase shared pool or db cache?
and we said...
It depends on the platform/operating system.

On Windows -- it has to pre-allocate it.  On Solaris, where we can dynamically
create and destroy shared memory segments -- we do it on the fly.

But -- it should be JUST FINE, since windows will not actually give us the
physical ram until we use the physical ram, it is just in your paging file.
Review & Followup

Rating: 3
Pre allocating February 14, 2003
Reviewer: Ramesh from Atlanta, GA
In sun Solaris 8 , with Oracle 9.2.0.2

I set like this in init.ora
sga_max_Size=104857600
db_cache_size = 25165824
shared_pool_size = 12000000
java_pool_size = 0
large_pool_size = 0
log_buffer = 163840
after starting the database
15:47:33 SQL> show sga
Total System Global Area 105907536 bytes
Fixed Size 730448 bytes
Variable Size 79691776 bytes
Database Buffers 25165824 bytes
Redo Buffers 319488 bytes
15:51:20 SQL>
15:51:37 SQL> select sum(bytes) from v$sgastat where pool like 'shared%';
SUM(BYTES)
----------
29360128
Elapsed: 00:00:00.01
15:53:37 SQL>
now sga is showing 100M where as my total sga is not ( after addition of
shared+log+buffer)
database buffers is showing as correct value but where as sum of bytes in
v$sgastat for shared pool is showing 28M It should be 12M why it is showing
as 28M why addition of 16M.. ( granule ?? if granule it should be of 4m)
but variable size showing 76M .. Then what is the meaning of this 76M?

I think it is preallocating

When I check with  ipcs -a | grep <MemID> it is showing
appp02:CRFO4DEM:/usr/home/oracle> ipcs -a | grep 216650
m 216650 0x733e394 --rw-r----- oracle dba oracle dba
7 113246208 25061 1784 10:06:17 10:06:17 16:17:46

in segsz column it is 113246208 -> 100+ M??

Please give your expert opinion
Followup:
well, things are allocated in granules AND the shared_pool_size init.ora
parameter is not the (never has been) the only factor in the variable size of
the SGA.  processes, max files, and many other init.ora parameters allocate
memory in that area as well.

see
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96524/c08memor.htm#19799
(actually, you might consider staring on page 1 of that document and reading end
to end).

Yet another reason to read the concepts guide with each and ever release -- the
concepts change over time and 9i radically changed many of the concepts behind
the SGA to make it "dynamic"
Rating: 5
Block Size validation May 12, 2003
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
While performing the following results I got some unexpected results. As it is
illegal to set as non-standard cache size = to standard block size. I have
db_block_size = 4K so I was expecting that I can't set a cache size of 4k but I
am able to do so and then I have created a ts as well. I have oracle 9i Rel 1.
After doing all this I can't shutdown the db or it give message that you can't
set cache of 4k which is right but it is not giving any message while I am
setting it.

SYS > show parameter block_size

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
db_block_size                        integer     4096
SYS > show parameter cache_size

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
db_16k_cache_size                    big integer 0
db_2k_cache_size                     big integer 0
db_32k_cache_size                    big integer 0
db_4k_cache_size                     big integer 0
db_8k_cache_size                     big integer 0
db_cache_size                        big integer 33554432
db_keep_cache_size                   big integer 0
db_recycle_cache_size                big integer 0
SYS > alter system set db_cache_size = 28M;

System altered.

SYS > alter system set db_4k_cache_size = 2M;

System altered.

SYS > show parameter cache_size

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
db_16k_cache_size                    big integer 0
db_2k_cache_size                     big integer 0
db_32k_cache_size                    big integer 0
db_4k_cache_size                     big integer 4194304
db_8k_cache_size                     big integer 0
db_cache_size                        big integer 29360128
db_keep_cache_size                   big integer 0
db_recycle_cache_size                big integer 0
SYS > create tablespace ts_4k datafile c:\temp\ts_4k01.dbf.dbf' size 2m
blocksize 8k;
create tablespace ts_4k datafile c:\temp\ts_4k01.dbf.dbf' size 2m blocksize 8k
                                 *
ERROR at line 1:
ORA-02180: invalid option for CREATE TABLESPACE

SYS > ed
Wrote file afiedt.buf

  1* create tablespace ts_4k datafile 'c:\temp\ts_4k01.dbf.dbf' size 2m
blocksize 8k
SYS > /
create tablespace ts_4k datafile 'c:\temp\ts_4k01.dbf.dbf' size 2m blocksize 8k
*
ERROR at line 1:
ORA-29339: tablespace block size 8192 does not match configured block sizes

SYS > ed
Wrote file afiedt.buf

  1* create tablespace ts_4k datafile 'c:\temp\ts_4k01.dbf.dbf' size 2m
blocksize 4k
SYS > /
create tablespace ts_4k datafile 'c:\temp\ts_4k01.dbf.dbf' size 2m blocksize 4k
*
ERROR at line 1:
ORA-01543: tablespace 'TS_4K' already exists

SYS > drop tablespace ts_4k;

Tablespace dropped.

SYS > create tablespace ts_4k datafile 'c:\temp\ts_4k01.dbf.dbf' size 2m
blocksize 4k

Tablespace created.

SYS > select * from v$tablespace;

         TS# NAME                           INC
------------ ------------------------------ ---
           2 CWMLITE                        YES
           3 DRSYS                          YES
           4 EXAMPLE                        YES
           5 INDX                           YES
           0 SYSTEM                         YES
           7 TOOLS                          YES
           1 UNDOTBS                        YES
           8 USERS                          YES
           6 TEMP                           YES
          12 TS_4K                          YES

10 rows selected.

SYS > select * from dba_tablespaces;

TABLESPACE_NAME                  BLOCK_SIZE INITIAL_EXTENT  NEXT_EXTENT
MIN_EXTENTS
------------------------------ ------------ -------------- ------------
------------
SYSTEM                                 4096          12288        12288
  1
UNDOTBS                                4096          65536
  1
CWMLITE                                4096          65536
  1
DRSYS                                  4096          65536
  1
EXAMPLE                                4096          65536
  1
INDX                                   4096          65536
  1
TEMP                                   4096        1048576      1048576
  1
TOOLS                                  4096          65536
  1
USERS                                  4096          65536
  1
TS_4K                                  4096          65536
  1

10 rows selected.

SYS > shtudown immediate
SQL Plus Hangs here................

Thanks for your great answers.
Followup:
I don't understand what problems you think you are facing with multi-block size
databases.

You cannot create an 8k tablespace since you have no 8k cache, configure an 8k
cache and you would be able to.

since your blocksize is 4k, of course you can create that.

the shutdown immediate has nothing to do with the tablespaces.  You have a
session that is rolling back or just preventing the shutdown immediate from
happening.  ctl-c it, reconnect as sysdba and shutdown abort if you need.
Rating: 5
How can I create cache of 4K May 12, 2003
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
I have 4K of DB and for this I have specified cache for this with db_cache_size.
Now I am defining non-standard block size and it is allowing me of 4K which
should not as 4K is the db block size. I can define 2,8,16,32.
Is it possible,

db_block_size=4k
db_cache_size=12M
db_4k_cache_size=2M
Followup:
it won't let me on my 9203 box

ops$tkyte@ORA920> alter system set db_8k_cache_size = 4m;
alter system set db_8k_cache_size = 4m
*
ERROR at line 1:
ORA-02097: parameter cannot be modified because specified value is invalid
ORA-00380: cannot specify db_8k_cache_size since 8K is the standard block size

In 9.0.1 it let me however.  and after that, connections failed with:

> plus

SQL*Plus: Release 9.0.1.0.0 - Production on Mon May 12 13:20:24 2003

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

ERROR:
ORA-00380: cannot specify db_8k_cache_size since 8K is the standard block size

so, what you need to do is

$ sqlplus "/ as sysdba"

SQL> create pfile from spfile;

edit the generated $ORACLE_HOME/dbs/init$ORACLE_SID.ora file and get rid of
the 4k cache setting you have and then

SQL> create spfile from pfile;
SQL> startup force

sorry about that, didn't try in 9iR1.
Rating: 5
Great May 12, 2003
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
Thanks TOM I think I should sorry as I am still using R1. So I may say a bug in
R1 which is removed in R2. I will try to get R2 soon. Thanks for your patience.
Rating: 4
Q on db_cache_size and tablespace January 04, 2004
Reviewer: Anil Pant from India
I've a peculiar problem. Im working Oracle 9 Rel2 on Windows. In init.ora i've
set db_block_size = 8192.
(1) alter system set db_4k_cache_size=2m

show parameter cache_size shows values
db_4k_cache_size=4194304
db_cache_size=20971520

All other nk_cache_size = 0 as i've not set.

Then I give Create Tablespace t1 datafile 'c:\db4k.dbf' size 2m blocksize 8k;
tablespace was created without any error.
As per ur response it shud not create. Then why on my db.
(2)Im also able to set db_4k_cache_size=2m . Oops it works !!!. Also I can set
db_16k_cache_size=2m. But as per ur previous response to one of the question
posted it shud not work. What's the reason ? Even for that matter
db_2k_cache_size=4m works. Even for other nk_cache_size is working without any
problem.
Am I doing something wrong ?
Followup:
it should create -- the DEFAULT block cache (8k as you said) comes from the
db_cache_size.

where did I say "it should not create"

2) why oops? why should it not work?  if you set the sga max size appropriately
or shrunk some other sga component after startup, you certainly should be able
to do this.

where did I say "it should not work"?
Rating: 3
Oracel 9i SGA's structure March 22, 2004
Reviewer: Nazmul Hasan from Bangalore, India
I'm confuse while reading the structuer of Oracle 9i's SGA from DB Concept guid.
The terms are:
 Buffer, Cache, Pool
 Free buffers, Pinned buffers, and Dirty buffers;
 KEEP buffer pool, RECYCLE buffer pool, DEFAULT buffer pool
What's the relation between them?
How they reside in the memory?
Followup:
that is big enough to be a chapter in a book, in fact it is ;)

it takes a couple of pages to go through -- if you have access to "Expert one on
one Oracle" -- i do it in there.

if you have a specific concrete question, I'd be glad to answer it.
March 22, 2004 Jingye -- Thanks for the question regarding "Updating a table from another table", version
You Asked
This may seem like a simple question: Update Column a1
in Table A with all data in Column b1 in Table B. But I am
trapped by the method that without using cursor to achieve it.
There are one column in each table, call id, to link them.
Using cursor is ok, but it brings with a un-toleratable speed
when operating on a large table. Even the idea create a
temporary
table holding only primary key and Column b, and then apply
cursor to it is slow.
I am think of the way without using cursor, script as below. But

the 2nd Where clause simply return the message of `more than one

row is return', since the id is unpredictable and this create a
`many to many' relationship in both tables.
For your kindly advice.
Many Thanks,

(script)
REM* the where-clause of the update cannot work

UPDATE table b SET
column_b1 = ( SELECT MAX(column_a1)
FROM table_a a, table_b b
WHERE a.id=b.id
GROUP BY a.id)
WHERE table_b.id IN (SELECT MIN(id)
FROM table_a
GROUP BY id);
and we said...
Your example is somewhat confusing -- you ask "update column a1
in table a where data in column b1 in table b" but your update
shows you updating column b1 in table B with some data from
table a.  Additionally -- given the way the where and set
clauses are CODED in the above -- it would succeed.  The query
would in fact execute without any error messages since the
correlated subquery in SET clause returns EXACTLY one row for
each row in B and the where clause executes without error.

Anyway.   Lets say you have a table A ( id int, a1 varchar2(25)
) and a table B ( id int PRIMARY KEY, b1 varchar2(25) ).  I put
a primary key on B(id) because if there is NO SUCH constraint --
the update is totally ambigous -- if there is more then 1 value
of B1 in B for a given ID -- we cannot tell what row in B should
be used to update the row in A.

Given that, we can:

tkyte@ORACLE> create table a ( id int, a1 varchar2(25) );
Table created.

tkyte@ORACLE> create table b ( id int primary key, b1
varchar2(25) );
Table created.

tkyte@ORACLE> insert into a values ( 1, 'Hello' );
tkyte@ORACLE> insert into a values ( 2, 'xxxxx' );
tkyte@ORACLE> insert into b values ( 2, 'World' );

tkyte@ORACLE> update ( select a1, b1 from a, b where a.id = b.id
)
  2     set a1 = b1
  3  /

1 row updated.

tkyte@ORACLE> select * from a
  2  /

        ID A1
---------- -------------------------
         1 Hello
         2 World

The following updates are equivalent as well - they do the same
thing differently.  It is best to use the above update of a JOIN
if possible (optimizer has best chance of getting a good plan).
It will need the primary key constraint on B though, if you do
not have one BUT b.id is unique, you can:

tkyte@ORACLE> update a
  2     set a1 = ( select b1 from b where b.id = a.id )
  3   where EXISTS ( select b1 from b where b.id = a.id )
  4  /

1 row updated.

tkyte@ORACLE> update a
  2     set a1 = ( select b1 from b where b.id = a.id )
  3   where a.id in ( select id from b )
  4  /

1 row updated.
Review & Followup

Rating: 5
October 23, 2001
Reviewer: A reader
Migration Workbench doc didn't have great examples of converting update joins
into Oracle syntax and this article had just that! thanks.
Rating: 5
Updating joined tables March 20, 2002
Reviewer: Robert Massey from Atlanta, GA USA
I can think of another instance (at least with v8.1.5) where the second example
(updating the table via a subquery) works better than the first example
(updating the joined tables).

If the user has update permission on table A, but only has select permission on
table B, they cannot update via the first example.  Oracle will return ORA-01031
(insufficient privileges).
Rating: 4
Updates are slow November 05, 2002
Reviewer: HB
Hi Tom,
        I have a related question for an UPDATE - that takes unexpected long
time.
I am using Table T1 to insert or update the data inthe Table T2 (which has a
unique key  column -c1+ c2) in oracle 8.1.7.
        The pl/sql block first tries to insert if exception occur because of
sqlcode = -1 then it knows it is for update. The performance degrade when T1 has
all rows for update.
I would appreciate your help
Thank

declare
   exec_code varchar2(200);
   exec_message varchar2(2000);
   cursor cur is select * from t1;

begin
for  x in cur loop
    declare
    begin
         Insert into T2(c1,c2,c3,c4,c5)
         values(x.c1, x.c2, x.c3 , x.c4, x.c5) ;

     exception when others then
        if sqlcode = -1 then
             declare
                begin
                    update t2 set
                    c3=x.c3,
                    c4=x.c4 ,
                    c5=x.c5  where c1=x.c1 and c2=x.c2;
                exception when others then
                    exec_code := sqlcode;
                    exec_message := sqlerrm;
                     insert into t3_exception(
                      error_code, error_message)
                      values (exec_code, exec_message);
                     commit;
              end;
        else
                    exec_code := sqlcode;
                    exec_message := sqlerrm;
                    insert into t3_exception(
                     error_code, error_message)
                     values (exec_code, exec_message);
                    commit;
       end if;
       end;
       end loop;
     --  rollback;
end;
Followup:
Danger Will Robinson! Danger!!   (anyone remember "lost in space?")

committing in for loop == terrible, horrible, really truly BAD idea.  Especially
since you only commit ON ERROR!!!!  Man, do you break transactional integrity or
what.

Either:

o move the error log into an autonomous transaction
o just LOSE the commit all together (without changing the outcome of your
program at all!!)

Consider what happens when.....

o you hit an error on the update
o you log it and commit
o you subsequently (later) hit an ora-1555 on the SELECT and bump out of the
loop

whoops -- you processed 1/2 of the table.

Anyway -- my suggestion -- lose the procedural code.  totally -- just update and
insert and if you have 9i, just MERGE.  make sure to use CBO or you'll have to
use HINTS as the RBO isn't smart enough to do the NOT IN really good.
Consider (i'll be updating most all of the rows, well, all of them actually):

ops$tkyte@ORA920.US.ORACLE.COM> create table t1 as select object_id, object_name
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> create table t2 as select * from t1;

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>
ops$tkyte@ORA920.US.ORACLE.COM> analyze table t1 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 t2 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> set timing on
ops$tkyte@ORA920.US.ORACLE.COM> declare
  2          l_icnt number := 0;
  3          l_ucnt number := 0;
  4  begin
  5      for x in ( select * from t1 )
  6      loop
  7          begin
  8              insert into t2 ( object_id, object_name ) values ( x.object_id,
x.object_name );
  9                          l_icnt := l_icnt+1;
 10          exception
 11              when dup_val_on_index then
 12                  update t2 set object_name = x.object_name where object_id =
x.object_id;
 13                                  l_ucnt := l_ucnt+1;
 14          end;
 15      end loop;
 16          dbms_output.put_line( 'Inserted ' || l_icnt );
 17          dbms_output.put_line( 'Updated  ' || l_ucnt );
 18  end;
 19  /
Inserted 0
Updated  29317

PL/SQL procedure successfully completed.

Elapsed: 00:01:04.07
ops$tkyte@ORA920.US.ORACLE.COM> rollback;

Rollback complete.

Elapsed: 00:00:00.86
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> begin
  2      update ( select t1.object_name t1_oname, t2.object_name t2_oname
  3                 from t1, t2
  4                where t1.object_id = t2.object_id )
  5         set t1_oname = t2_oname;
  6
  7          dbms_output.put_line( 'Updated  ' || sql%rowcount );
  8
  9         insert into t1
 10      select * from t2 where t2.object_id not in ( select object_id from t1
);
 11
 12          dbms_output.put_line( 'Inserted ' || sql%rowcount );
 13  end;
 14  /
Updated  29317
Inserted 0

PL/SQL procedure successfully completed.

Elapsed: 00:00:02.44
ops$tkyte@ORA920.US.ORACLE.COM> rollback;

Rollback complete.

Elapsed: 00:00:01.07
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> merge into t1
  2  using t2
  3  on ( t2.object_id = t1.object_id )
  4  when matched then
  5     update set t1.object_name = t2.object_name
  6  when not matched then
  7      insert (object_id, object_name) values( t2.object_id, t2.object_name);

29317 rows merged.

Elapsed: 00:00:02.87
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> rollback;

Rollback complete.

Elapsed: 00:00:01.08
Rating: 5
Oracle 9i Merge November 06, 2002
Reviewer: A reader
Hi Tom,
       Your help is always the best.
My last PL/SQL code was just a test as i was interested more in the performance
during update, so i missed the commit point.

I tried the MERGE command and it is 10 fold fast, however i have few questions.

You said to use Oracle9I Merge statement when inserting or updating the data
from a stagging table to history table.
In my case Table T1 (staging table with 300 columns) will have 200 thousand rows
every day for insert or update to the
history table  T2   ( 280 columns) that has 10 Million historical rows.
Since  I am inserting a massive amount of data, using Merge -
1.) How can I commit at every 1000 rows
2.) If the row to load fails during Insert or update, can i trap that rows and
put it into another table T3 .
3.) How can i find out how many rows are inserted verser update.
4.) Is Merge the best case in above scenario.

Thanks
Followup:
1) you don't want to -- incrementation commits -- baaaaaddddd terrible practice.
 size your system for your workload. (answer = you cannot, the statement is
transactional.  I could develop procedural workarounds but we'd be right back at
slow=true)

2) now you are back to procedural row by row processing.  suggestion: put the
constraints on the staging table -- SAME constraints, find them there, weed them
out there.

3) you don't -- you could do the two step (update existing, insert new)

4) IMO -- if you have requirements that permit it -- yes.
Rating: 5
Re: Updating a table from another table November 07, 2002
Reviewer: CJ from Albany, NY, USA
I'm sure this is a stupid question, but if we must process the table we are
updating/inserting from in timestamp order (i.e. table b has a timestamp
column), this must be done with procedural code?
Followup:
give me a concrete example -- having a super hard time trying to figure out why
the order of update matters to you at all.
Rating: 5
Re: Updating a table from another table November 07, 2002
Reviewer: CJ from Albany, NY, USA
Updates/inserts/deletes come from another system into a staging instance. The
staging instance tables looks exactly like the main tables other than a
timestamp and an update_type column are added. The records must be processed in
order so that for instance, if a record is updated, deleted, inserted, then
updated again (not likely, but it *could* happen) those operations should happen
in the correct order.

Note - I didn't design this system, but I have to work with it.
Followup:
Oh, well -- then you cannot do it in a single update anyway -- as the table
being updated would NOT be key preserved (and hence the result of the update
would be very ambigous).

If the table containing the changes can have MORE then one occurrence of the
"primary key" of the other table -- no chance for a single statement.  If you
tried:

table t1( x int primary key, y int );
table t2( x int, y int );

insert into t1 values ( 1, 0 );
insert into t2 values ( 1, 100 );
insert into t2 values ( 1, 200 );

then

update ( select t1.y t1_y, t2.y t2_y from t1, t2 where t1.x = t2.x )
  set t1_y = t2_y

would be "ambigous" -- no way we could know if y would end up with 100 or 200 --
hence we don't even permit it.

So, you are forced to do it procedurally.
Rating: 4
November 08, 2002
Reviewer: A reader
Hi Tom,
       As you said in my case scenario I cannot use UPSERT(MERGE) and i have to
write a pl/sql in case to achieve  -

1.) Insert /Update from temporary table to actual table.
(Nt- As i am loading data into temporaray table from a legacy system, i want
this load to be as fast as possible so i am not putting any constrain on temp
table and handling most of data error inside oracle. All fields in the temporary
tables are varchar2) The actual Temp as well as Target  table has 300 plus
columns.

2.) All rows failed during INSERT/uPdate log them to a Fault table
3.) Find out number of rows inserted verses update.

I am using the PL/SQL to handle this, the Inserts are fine but the Updates are
very slow. I thnk there is a better way to write this.The test PL/SQL is as
follows -

declare
    todate date;
    current_mode char(10) :='load';
    exec_code varchar2(200);
    exec_message varchar2(2000);
    this_total number :=0; this_succ number:=0;
    this_update number:=0;
    this_fail number:=0;
    this_insert number:=0;
    this_start char(10) ; this_end char(10);
    this_table_name varchar2(10) :='test';
    cursor c is select * from test_temp ;
    commit_point number :=0;
    test_case number := null;
    fault_id varchar2(30);
begin
select to_char(sysdate, 'hh24:mm:ss') into this_start from dual;
delete from  test_load_result where table_name =upper(trim(this_table_name));
commit;
for  xc1 in c loop
this_total:=this_total+1;
    commit_point:=commit_point+1;
    if commit_point > 10000 then
    commit;
    commit_point:=0;
    end if;
    declare
    begin
    test_case := xc1.test_jdate;
    insert /*+ append */ into test(  test_id, test_code ,test_case ,test_month
,test_jdate ,
                 sequence_no ,  check_status
                 )
    values (
          trim(xc1.test_id),
          trim(xc1.test_code),
         to_date( to_char(to_date('12-31-1900','mm-dd-yyyy') ,'j') +
abs(test_case),'j'), upper(to_char((to_date(
to_char(to_date('12-31-1900','mm-dd-yyyy') ,'j') + abs(test_case),'j')),'mon')),
         trim(xc1.test_jdate),
         trim(xc1.sequence_no),
         trim(xc1.check_status)
         );
    this_succ:=this_succ + 1;
    exception when others then
        if sqlcode = -1 then
             declare
                begin
                   update  test set
                    check_status = trim(xc1.check_status)
                where
                    test_id = trim(xc1.test_id) and
                    test_code=trim(xc1.test_code) and
                    test_jdate = trim(xc1.test_jdate) and
                    sequence_no =trim(xc1.sequence_no) ;

                    this_update := this_update + 1;
                exception when others then
                    exec_code := sqlcode;
                    exec_message := sqlerrm;
                    select sysdate into todate from dual;
                    if (this_update >0) then
                    this_update := this_update - 1;
                    end if;
                    this_fail:=this_fail + 1;
                    fault_id:= trim(xc1.rms_index);
                    insert into test_fault
                    select (select sysdate from dual), st.* from test_temp st
where
                    trim(rms_index)=trim(fault_id);
                    insert into test_exception(
                    exec_date, exec_mode, table_name,rms_index, exec_code,
                    exec_message) values

(todate,current_mode,this_table_name,fault_id,exec_code,exec_message);
              end;
        else
                exec_code := sqlcode;
                exec_message := sqlerrm;
                select sysdate into todate from dual;
                this_fail:=this_fail + 1;
                fault_id:= trim(xc1.rms_index);
                insert into test_fault
                select (select sysdate from dual), st.*  from test_temp st where
                trim(rms_index)=trim(fault_id);
                insert into test_exception(
                exec_date, exec_mode, table_name,rms_index, exec_code,
                exec_message) values

(todate,current_mode,this_table_name,fault_id,exec_code,exec_message);
       end if;
end;
end loop;
    this_succ := this_succ+this_update;
    select decode(sign(this_succ -(this_update+this_fail)),1,this_succ
-(this_update+this_fail),0) into this_insert from dual;
    select to_char(sysdate, 'hh24:mi:ss') into this_end from dual;
    test_results(todate,this_start, this_end, this_table_name, this_total,
this_succ,this_insert, this_update, this_fail);
commit;
end;
/
Followup:
you do realize that /*+ append */ hint is just a waste of keystrokes right?  It
isn't doing anything beyond confusing the reader of your code....

why do you believe the update is slow -- what led you to that particular
conclusion.
Rating: 4
November 09, 2002
Reviewer: A reader
Can we not use /*+append*/ inside a pl/sql block?
Followup:
sure you can.

You just cannot use it with VALUES.  It only works with INSERT as SELECT.
Rating: 4
Similar sub-queries in same parent query August 07, 2003
Reviewer: Praveen from Bangalore
Hi Tom,

I have a huge table similar to the following:

eno   ename   dno    sal    mgr
---------------------------------
101   A       1      100
102   B       1      200
103   C       1      300
104   D       2      100
105   E       2      200
---------------------------------

Here I want to update the 'mgr' column with 'eno' value having largest 'sal' for
each dno. Thus the result would be:

eno   ename   dno    sal       mgr
------------------------------------
101   A       1      1000.00    103
102   B       1      2000.00    103
103   C       1      3000.00
104   D       2      1000.00    105
105   E       2      2000.00
------------------------------------
I wrote a query like this:

UPDATE emp e1 SET mgr = (SELECT eno FROM emp e2
                         WHERE sal = (SELECT MAX(sal)
                                      FROM emp e3
                                      WHERE e2.dno=e3.dno)
                         AND e1.dno = e2.dno)
WHERE e1.sal < (SELECT MAX(sal) FROM emp e3
                WHERE e1.dno = e3.dno);

There are around 6 million data in the table (having about
350 fields). I feel that since I am using the same sub-quey at 2 places -(SELECT
MAX(sal) FROM emp e3
                WHERE e1.dno = e3.dno)- Oracle has to parse them 2 times and
then fetch which will reduce performance.
In what way can we let oracle know that there are two similar queries and
retrieving similar data from the table ?

Thanks and regards

Praveen KV
Followup:
ops$tkyte@ORA920.US.ORACLE.COM> create table emp as select * from scott.emp;

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> update emp set mgr = null;

14 rows updated.

ops$tkyte@ORA920.US.ORACLE.COM> commit;

Commit complete.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> update ( select sal, mgr,
  2                 (select max( to_char( e2.sal, 'fm000000000.00' ) || e2.empno
)
  3                          from emp e2
  4                         where e2.deptno = emp.deptno ) new_mgr
  5             from emp )
  6     set mgr = substr( new_mgr, 13 )
  7   where sal < substr( new_mgr, 1, 12 )
  8  /

10 rows updated.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> break on deptno skip 1
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select deptno, sal, empno, mgr
  2    from emp
  3   order by deptno, sal desc
  4  /

DEPTNO        SAL      EMPNO        MGR
------ ---------- ---------- ----------
    10       5000       7839
             2450       7782       7839
             1300       7934       7839

    20       3000       7788
             3000       7902
             2975       7566       7902
             1100       7876       7902
              800       7369       7902

    30       2850       7698
             1600       7499       7698
             1500       7844       7698
             1250       7521       7698
             1250       7654       7698
              950       7900       7698

14 rows selected.
Rating: 3
insert/update in faster way September 29, 2003
Reviewer: Shahadat from Bangladesh
I have a table named A containing say 100000 records.I have another table B
containg
 10000 records of incremented and edited records of A table.Both table has same
structure and 46 field.I am using the following codes to append data from B to
A.
--For incremental/New data-----
insert into A
select * from B where column_name NOT IN
(select column_name from B);
--For Edited Data-------
cursore C_AB
select * from A
minus
select * from A
For R in C_AB
loop
Update A set....where ...
end loop
End;

It's working but taking a huge time/sometimes hang the computer.
Would u please help me how can i faster my procedure.
Followup:
not a version to be seen....  hmmmm.

to update 10,000 rows in a 100,000 row table should take seconds (it'll be a
direct function of the number of indexes).  And it should take no procedural
code at all.

In 8i and before, it'll take 2 sql statements.
In 9i it'll take one sql statement.

consider:

ops$tkyte@ORA920> @big_table 100000
ops$tkyte@ORA920> create table big_table
  2  as
  3  select rownum id, a.*
  4    from all_objects a
  5   where 1=0
  6  /
Table created.

ops$tkyte@ORA920> declare
  2      l_cnt number;
  3      l_rows number := &1;
  4  begin
  5      insert /*+ append */
  6      into big_table
  7      select rownum, a.*
  8        from all_objects a;
  9
 10      l_cnt := sql%rowcount;
 11
 12      commit;
 13
 14      while (l_cnt < l_rows)
 15      loop
 16          insert /*+ APPEND */ into big_table
 17          select rownum+l_cnt,
 18                 OWNER, OBJECT_NAME, SUBOBJECT_NAME,
 19                 OBJECT_ID, DATA_OBJECT_ID,
 20                 OBJECT_TYPE, CREATED, LAST_DDL_TIME,
 21                 TIMESTAMP, STATUS, TEMPORARY,
 22                 GENERATED, SECONDARY
 23            from big_table
 24           where rownum <= l_rows-l_cnt;
 25          l_cnt := l_cnt + sql%rowcount;
 26          commit;
 27      end loop;
 28  end;
 29  /
old   3:     l_rows number := &1;
new   3:     l_rows number := 100000;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> alter table big_table add constraint
  2  big_table_pk primary key(id)
  3  /
Table altered.

ops$tkyte@ORA920> select count(*) from big_table;

  COUNT(*)
----------
    100000

ops$tkyte@ORA920> create table small_table
  2  as
  3  select * from big_table
  4  where 1=0;
Table created.

ops$tkyte@ORA920> insert into small_table
  2  select * from big_table
  3  where mod(id,20) = 0;
5000 rows created.

ops$tkyte@ORA920> insert into small_table
  2  select -rownum, a.*
  3  from all_objects a
  4  where rownum <= 5000;
5000 rows created.

ops$tkyte@ORA920> analyze table big_table compute statistics for table;
Table analyzed.

ops$tkyte@ORA920> analyze table small_table compute statistics for table;
Table analyzed.

ops$tkyte@ORA920> set timing on

here is the 9i way..

ops$tkyte@ORA920> merge into big_table B
  2  using small_table S
  3  on ( b.id = s.id )
  4  when matched then update set
  5   b.owner = s.owner, b.object_name = s.object_name,
  6   b.subobject_name = s.subobject_name, b.object_id = s.object_id,
  7   b.data_object_id = s.data_object_id, b.object_type = s.object_type,
  8   b.created = s.created, b.last_ddl_time = s.last_ddl_time,
  9   b.timestamp = s.timestamp, b.status = s.status, b.temporary = s.temporary,
 10   b.generated = s.generated, b.secondary = s.secondary
 11  when not matched then insert
 12   ( ID, OWNER, OBJECT_NAME, SUBOBJECT_NAME, OBJECT_ID, DATA_OBJECT_ID,
 13     OBJECT_TYPE, CREATED, LAST_DDL_TIME, TIMESTAMP, STATUS, TEMPORARY,
 14     GENERATED, SECONDARY )
 15  values
 16   ( s.ID, s.OWNER, s.OBJECT_NAME, s.SUBOBJECT_NAME, s.OBJECT_ID,
s.DATA_OBJECT_ID,
 17     s.OBJECT_TYPE, s.CREATED, s.LAST_DDL_TIME, s.TIMESTAMP, s.STATUS,
s.TEMPORARY,
 18     s.GENERATED, s.SECONDARY );

10000 rows merged.

Elapsed: 00:00:01.22

that won't "hang the computer" for very long..

ops$tkyte@ORA920> rollback;
Rollback complete.

Elapsed: 00:00:02.19

ops$tkyte@ORA920> alter table small_table add constraint small_table_pk primary
key(id)
  2  /
Table altered.

Elapsed: 00:00:00.34

this is needed to do this in 8i -- small table needs a primary key

ops$tkyte@ORA920> update ( select b.ID B_ID, b.OWNER B_OWNER, b.OBJECT_NAME
B_OBJECT_NAME,
  2  b.SUBOBJECT_NAME B_SUBOBJECT_NAME, b.OBJECT_ID B_OBJECT_ID,
  3  b.DATA_OBJECT_ID B_DATA_OBJECT_ID, b.OBJECT_TYPE B_OBJECT_TYPE,
  4  b.CREATED B_CREATED, b.LAST_DDL_TIME B_LAST_DDL_TIME,
  5  b.TIMESTAMP B_TIMESTAMP, b.STATUS B_STATUS,
  6  b.TEMPORARY B_TEMPORARY, b.GENERATED B_GENERATED,
  7  b.SECONDARY B_SECONDARY,
  8  s.ID S_ID, s.OWNER S_OWNER, s.OBJECT_NAME S_OBJECT_NAME,
  9  s.SUBOBJECT_NAME S_SUBOBJECT_NAME, s.OBJECT_ID S_OBJECT_ID,
 10  s.DATA_OBJECT_ID S_DATA_OBJECT_ID, s.OBJECT_TYPE S_OBJECT_TYPE,
 11  s.CREATED S_CREATED, s.LAST_DDL_TIME S_LAST_DDL_TIME,
 12  s.TIMESTAMP S_TIMESTAMP, s.STATUS S_STATUS,
 13  s.TEMPORARY S_TEMPORARY, s.GENERATED S_GENERATED,
 14  s.SECONDARY S_SECONDARY
 15  from big_table b, small_table s
 16  where b.id = s.id )
 17  set
 18  b_ID = s_ID,
 19  b_OWNER = s_OWNER,
 20  b_OBJECT_NAME = s_OBJECT_NAME,
 21  b_SUBOBJECT_NAME = s_SUBOBJECT_NAME,
 22  b_OBJECT_ID = s_OBJECT_ID,
 23  b_DATA_OBJECT_ID = s_DATA_OBJECT_ID,
 24  b_OBJECT_TYPE = s_OBJECT_TYPE,
 25  b_CREATED = s_CREATED,
 26  b_LAST_DDL_TIME = s_LAST_DDL_TIME,
 27  b_TIMESTAMP = s_TIMESTAMP,
 28  b_STATUS = s_STATUS,
 29  b_TEMPORARY = s_TEMPORARY,
 30  b_GENERATED = s_GENERATED,
 31  b_SECONDARY = s_SECONDARY
 32  /

5000 rows updated.

Elapsed: 00:00:02.82

ops$tkyte@ORA920> insert into big_table
  2  select * from small_table
  3  where id not in (select id from big_table)
  4  /

5000 rows created.

Elapsed: 00:00:00.25
ops$tkyte@ORA920>

so, a couple of seconds (add time for each index you have of course)

NO PROCEDURAL CODE!!!
Rating: 3
insert/update in a faster way October 01, 2003
Reviewer: Shahadat from Bangladesh
Thanks for kind reply.
Here i am using oracle8i(8.1.6.0.0) and developer6i and windows2000 server.
I have a table named A containing say 100000 records.I have another table B
containg
 10,000 records of incremented and edited records of A table.Both table has same

structure and 46 field.I am using the following codes to append data from B to
A.
--For incremental/New data-----
insert into A
select * from B where column_name NOT IN
(select column_name from B);
--For Edited Data-------
cursore C_AB
select * from B
minus
select * from A
For R in C_AB
loop
Update A set....where ...
end loop
End;

It's working but taking a huge time/sometimes hang the computer.
Would u please help me how can i faster my procedure.u have given information 9i
basis.will it same for oracle8i & dev6i.please help me
Followup:
hmmm, how to say this

"page up"

this was asked and answered already.
Rating: 4
Insert problem (taking time) October 02, 2003
Reviewer: Shahadat from Bangladesh
Thank u very much for ur kind reply.ur update information is really nice and
working very well.but insert----same prolem.I don't understand what's the
problem.i am going to give u full overview of my problem.Here's i have a
centralized place(Head office).The software is available in different
portion of the country for data entry and report generation etc.Every month
the client office is to give data(NEW & EDITED) "BY DATE RANGWISE"
to the headoffice in CD.The Headoffice is merge the data into their
system.In this way the whole system is going on.

For migration data first of all i create another
temporary user named VISTEMP then cotinuing this kinds of code

insert into
VISTEMP.REGISTRATION(BIN,NAME,NAME_ALIAS,COR_GROUP,AUTHOBY,AUTHODATE,CG_NAME,ADD1
,ADD2,ADD3,TEL1,FAX1,ADD5,ADD6,ADD7,TEL2,FAX2,ADD9,ADD10,ADD11,TEL3,FAX3,TP_TYPE,
TRD_LINC,TRD_FY,TRD_AUTH,IMP_REG,EXP_REG,REG_TYPE,TIN,STATUS,PRE_BIN,DATREG,STAT_
CHNG,ACT_CODE,
ACT_MULT,ITEM_TYPE,OLD_ACT,APP_CAT,LCODE,
ISSUE_DATE,VREG,M_POSI,MFUNC,SFUNC,LAST_USER,LAST_ACCS,TREG,PAY_FREQ,CREG,EREG,OR
EG,OP_BAL,OP_BAL_DT)
select BIN,NAME----
from VIS.REGISTRATION where BIN NOT IN
(select BIN from VIS.REGISTRATION);

RUN and Computer in BIG Busy mood(Hang).

When i am sending data i am using date rang?am in write way?Please help me.
Followup:
you are probably using the brain dead RBO

you are using NOT IN

it is slow -- deadly slow.

use the CBO and make sure BIN in VIS.REGISTRATION is "not null".  Read:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7565644027229
for the "why"
Rating: 5
insert in a faster way October 04, 2003
Reviewer: Shahadat from Bangladesh
Thank u very much for ur kind & very helpful reply.Its working very nicely.i
will greateful to u forever.i was suffering by this problem for a long time.now
i am using the following
/
insert into
VISTEMP.REGISTRATION(BIN,NAME,NAME_ALIAS,COR_GROUP,AUTHOBY,AUTHODATE,CG_NAME,ADD1
,ADD2,ADD3,TEL1,FAX1,ADD5,ADD6,ADD7,TEL2,FAX2,ADD9,ADD10,ADD11,TEL3,FAX3,TP_TYPE,
TRD_LINC,TRD_FY,TRD_AUTH,IMP_REG,EXP_REG,REG_TYPE,TIN,STATUS,PRE_BIN,DATREG,STAT_
CHNG,ACT_CODE,ACT_MULT,ITEM_TYPE,OLD_ACT,APP_CAT,LCODE,ISSUE_DATE,VREG,M_POSI,MFU
NC,SFUNC,LAST_USER,LAST_ACCS,TREG,PAY_FREQ,CREG,EREG,OREG,OP_BAL,OP_BAL_DT)select
 BIN,NAME,NAME_ALIAS,---- from VIS.REGISTRATION where not exists
 (select null from VIStemp.REGISTRATION where
vistemp.registration.bin=vis.registration.bin)
/am i right?
Again i have some query
a)cbo/rbo mean?
b)in privious question i told u that i am exporting incremental and edited data
by using system date.Whenever anyuser insert/update anything,system date is
inserted with them and i am exporting that data by using that system date."AM I
IN RIGHT WAY?"
c)if i have a composite key then
"---where a.key1=b.key1 and a.key2=b.key2 and----)
am i right for both insert/update by given ur advice?
d)I want to call windows dialogue box from forms.
Thanks..
Followup:
a) uh oh.  you don't know what CBO and RBO mean?  Please see:
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/optimops.htm#721
b) i don't know?  are you?  it is your system....  i could envision problems
with only a second of granularity on a clock -- you could miss a record or two
due to that.

c) er?

d) I don't program forms -- you can try otn.oracle.com -> discussion forums.
Rating: 5
Update problem October 06, 2003
Reviewer: Shahadat from Bangladesh
Thank u very much for ur nice reply.
In update portion i am facing another problem by ur recomended code.In my B(big
table record-79186) and in S(small table record-12871).

 UPDATE
 (SELECT B.BIN B_BIN,S.BIN S_BIN
 FROM VISTEMP.REGISTRATION B,VIS.REGISTRATION S
 WHERE B.BIN=S.BIN) SET
 B_BIN=S_BIN;

12871 rows updated.

Elapsed: 00:00:28.91

 UPDATE
 (SELECT B.BIN B_BIN,S.BIN S_BIN
 FROM VIS.TESTREG B,VIS.REGISTRATION S
 WHERE B.BIN=S.BIN) SET
 B_BIN=S_BIN;

2 rows updated.

Elapsed: 00:00:02.93

Have u got any discrimination?when i am using different user "IT'S UPDATING ALL
THE RECORDS & TAKING TOO MUCH TIME".
but when i using same user but different table its updating only the originally
edited record and taking few times.why this happening??
Followup:
obviously the two tables have DIFFERENT DATA????

VISTEMP.REGISTRATION apparently has 12,871 records that match the S.BIN value.
It is not updating EVERY record in VISTEMP.REGISTRATION, you said "that table
has 79,186 records" -- only 12,871 (which happens to match the number of records
in S) are updated.

It is apparent that when you join vis.registration to vistemp.registration --
12,871 rows are "matched".

when you join to testreg -- 2 rows are.

You are comparing apples to toaster ovens.

The differences in run time is easily explained -- it takes longer to update
6,000 times the data.

there is nothing wrong here -- the computer is just doing exactly what you told
it to do.
Rating: 5
again insert problem October 06, 2003
Reviewer: Shahadat from Bangladesh
Hi Tom
again the same problem.Taking 34 minutes for inesrt.whats happening what should
i do.i will lose my job

SQL>         INSERT INTO VISTEMP.HSCODELIST
  2          (BIN,ACT_SL,ACT_CODE,ACT_VAL,ENTRY_DATE)
  3          SELECT BIN,ACT_SL,ACT_CODE,ACT_VAL,ENTRY_DATE
  4          FROM VIS.HSCODELIST
  5          WHERE not exists
  6          (SELECT NULL FROM VIStemp.HSCODELIST where
vistemp.HSCODELIST.bin=vis.HSCODELIST.bin and
  7          vistemp.HSCODELIST.act_sl=vis.HSCODELIST.act_sl)
  8  /

12881 rows created.

Elapsed: 00:34:2098.07
Followup:
sorry about that?  not much i can do sitting here, this this amount of
information.

do you know how to use sql_trace?
tkprof?

read about them in the performance guide, you need them in order to see what
this is doing and then tune it.
Rating: 5
I have a related question October 06, 2003
Reviewer: brao from austin,tx
Hi,
i have a question , say for example
we have an update statement which fetches values from different tables for
update.
we have tables upd ( which has to be updated) upd(p,a,b,c)
Table a(p,a)
Table b(p,b)
Table c(p,c)

i have an update like this
update upd set a = (select (........) from a where upd.a=a.a),
b = (select (........) from b where upd.b=b.b),
c = (select (........) from c where upd.c=c.c)

but the table a,b,c has around 3000000 records
so the quesry takes loooong time to finish.
what could be the best approach to update this table.
the tables a,b,c are remote tables.

Hope i made my question clear.
Thanks
brao
Followup:
are there values of "p" in A that are not in B or C and vice versa -- eg -- if
you join a to b to c without using full outer joins -- would you lose any rows?

versions are always necessary.
Rating: 5
I have a related question October 06, 2003
Reviewer: Brao from austin,tx
Sorry about that .
I have oracle 9i and im using this update statement in a stored procedure.
Yes there are some rows in the upd table which are not in a ,b or c.
And moreover im using aggregate function like min(),max() group by p in each
subquery
like
update upd set a = (select min(a)  from a where upd.a=a.a group by a.p),
b = (select (....)  from b where upd.b=b.b),
c = (select (........) from c where upd.c=c.c)

Thanks for answering my question and we (our team) always admire your way of
solving problems.
Brao
Followup:
no, i mean

does A have a value of P that is not in B

that is, would we have to full outer join A to B to C in order to get all of the
rows.
Rating: 4
i have a question October 06, 2003
Reviewer: BRao from austin,Tx
yes there are some rows in a which are not in b and c
further elaborating the query

UPDATE upd au
   Set purchase_no     = (SELECT Count(DISTINCT WebOrderID)
                          FROM A ot
                         WHERE total > 0 and
                               (ot.ostatus <> -1 and ot.ostatus <> -1) and
                               ot.UREFITEM = au.UREFITEM),
       Amount   = (SELECT ISNULL(Sum(Total), 0)
                          FROM b ot
                         WHERE (ot.ostatus <> -1 and ot.ostatus <> -1) and
                               ot.UREFITEM = au.UREFITEM),
       date1 = (SELECT min(sodate)
                          FROM c so left join E ot on ot.sorefitem =
so.sorefitem
                         WHERE total > 0 and
                               (so.ostatus <> -1 and so.ostatus <> -1) and
                               so.UREFITEM = au.UREFITEM),
       date2  = (SELECT max(sodate)
                          FROM d so left join E ot on ot.sorefitem =
so.sorefitem
                         WHERE total > 0 and
                               (so.ostatus <> -1 and so.ostatus <> -1) and
                               so.UREFITEM = au.UREFITEM);
Followup:
what I suggest then is not to do it in a single sql statement -- just proving
that "there are exceptions to every rule".  Normally, I would try to use a
single sql statment -- here, due to the "data being spread all over the place",
and being distributed and all.

this shows how I would approach getting the first two columns -- just add the
other 2 and use merge to keep filling temp -- and then update the join:

ops$tkyte@ORA920> create global temporary table temp
  2  ( urefitem number(3) primary key,
  3    purchase_no int,
  4    amount  int
  5  )
  6  /

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create table upd
  2  ( urefitem number(3),
  3    purchase_no int,
  4    amount int
  5  )
  6  /

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into upd
  2  select rownum, null, null from all_objects where rownum < 1000;

999 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into temp( urefitem, purchase_no)
  2  select urefitem, count(distinct weborderid)
  3    from a@remote
  4   where total > 0
  5     and ostatus <> -1
  6   group by urefitem
  7  /

388 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> merge into temp
  2  using (select urefitem, sum(total) sum_total
  3           from b@remote
  4                  where ostatus <> -1
  5              group by urefitem ) b
  6  on (temp.urefitem = b.urefitem)
  7  when matched then update set amount = b.sum_total
  8  when not matched then insert (urefitem,amount) values ( b.urefitem,
b.sum_total)
  9  /

398 rows merged.

ops$tkyte@ORA920>
ops$tkyte@ORA920> update ( select upd.purchase_no u_pno, upd.amount u_a,
  2                  temp.purchase_no t_pno, temp.amount t_a
  3                     from upd, temp
  4            where upd.urefitem = temp.urefitem )
  5    set u_pno = t_pno, u_a = t_a
  6  /

635 rows updated.
Rating: 4
i have a quesion October 06, 2003
Reviewer: brao from tx
hi,
thanks for the idea , it works :)
but can i follow the approach of updating each column using a separate cursor
(to catch the error )when updating on bulk basis.
Thanks again
BRAO
Followup:
catch what errors?  looks pretty straight forward -- numbers.

using a cursor means you are back to "slow=very_true"

you already WERE updating on a bulk basis??? i didn't change that at all.
Rating: 4
i have a question October 06, 2003
Reviewer: brao
To catch dml errors.
but that is fine , i got your short cut working great.
and the query is working.
Thanks
Brao
Rating: 5
UPDATE PROBLEM October 08, 2003
Reviewer: Shahadat from Bangladesh
Hi Tom
SQL> DESC VIS.HSCODELIST;
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 BIN                                                VARCHAR2(10)
 ACT_SL                                             VARCHAR2(3)
 ACT_CODE                                           VARCHAR2(11)
 ACT_VAL                                            NUMBER(14,2)
 ENTRY_DATE                                         DATE

SQL>  DESC VIS.DBHSCODELIST;
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 BIN                                                VARCHAR2(10)
 ACT_SL                                             VARCHAR2(3)
 ACT_CODE                                           VARCHAR2(11)
 ACT_VAL                                            NUMBER(14,2)
 ENTRY_DATE                                         DATE

SQL>  UPDATE (SELECT DBHSCODELIST.BIN B_BIN,DBHSCODELIST.ENTRY_DATE
B_ENTRY_DATE,
  2  HSCODELIST.BIN S_BIN,HSCODELIST.ENTRY_DATE S_ENTRY_DATE
  3  FROM DBHSCODELIST,HSCODELIST
  4  where DBHSCODELIST.BIN=HSCODELIST.BIN)
  5  SET B_BIN=S_BIN,B_ENTRY_DATE =S_ENTRY_DATE;
SET B_BIN=S_BIN,B_ENTRY_DATE =S_ENTRY_DATE
    *
ERROR at line 5:
ORA-01779: cannot modify a column which maps to a non key-preserved table
what's wrong???
Followup:
hscodelist is perhaps missing the mandatory primary/unique constraint on BIN?

the database needs to know that each row in dbhscodelist will map to AT MOST one
row in hscodelist - this mandates a primary or unqiue key constraint on the join
columns

this is discussed in the original answer above.
Rating: 3
update too slow December 23, 2003
Reviewer: anandhi from PA, USA
We have a 2 CPU machine where at normal times, the topmost entry in top command
shows only .2 or .3 percentage of CPU use. But when I run the following query,
it takes up 50% of CPU. And takes around 8 to 12 minutes to run. The tables
concerned have around 12000 rows each. This is on a test database where nothing
else is going on concurrently.

tabA has these columns: id, cycle, pop
tabB has these columns: id, cycle, site_id,rel_cd,groupid

update tabA a
set pop=
(select count(*)
from tabB b
where b.id=a.id
and a.cycle >= b.cycle
and b.site_id=44
and b.rel_cd in('code1','code2','code3')
and b.groupid='123')
where pop is null and id in(select id from tabB);

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      2    496.35     499.54    7530955    9902630      76532       11444
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3    496.35     499.54    7530955    9902630      76532       11444

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 305

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  UPDATE tabA
  11445   MERGE JOIN
   5942    VIEW VW_NSO_1
   5942     SORT UNIQUE
  31227      TABLE ACCESS FULL tabB
  17385    SORT JOIN
  12601     TABLE ACCESS FULL tabA

Now my questions are:

1. Is this time, 10 minutes for 12000 rows tables an acceptable (expected) time?
2. Where do I start with tuning this query?

We have several such updates that creates the same problems on the server from
time to time and I would appreciate some guidance to resolve this.
Thanks
Anandhi
Followup:
well, you've really got to ask:

is this time, 10 minutes, acceptable for running this query:

select count(*)
from tabB b
where b.id=:x1
and :x2 >= b.cycle
and b.site_id=44
and b.rel_cd in('code1','code2','code3')
and b.groupid='123'

12,000 times?

That means -- just using math here -- that we have 600 seconds, 12,000 queries
to run, 12000/60 = 20, so we are doing 20 per second -- or each query is taking
0.05 cpu seconds to run.

0.05 cpu seconds is awesome for a query.

do anything 12,000 times and you might have a problem tho!

Ok, so what are some methods to correct this?  this might be one of the rare
times that a temp table can be useful.  What about:

create global temporary table gtt
( id int primary key,
  cnt int
)
on commit delete rows
/

you'll add that ONCE, it'll become part of your schema forever....

Now, you "two step" it:

insert into gtt
select b.id, count(*) cnt
  from tabb b, taba a
 where a.id = b.id
   and a.cycle >= b.cycle
   and b.site_id = 44
   and b.rel_cd in ( 'code1', 'code2', 'code3' )
   and b.groupid = '123'
   and a.pop is null
 group by b.id
/

that gets all of the id/cnts for only the rows of interest.  Now we can update
the join:

update ( select a.pop, b.cnt
           from taba a, gtt b
          where a.id = b.id )
   set pop = cnt
/

and thats it.  give that a whirl -- let us know how it does!
Rating: 5
performance issue December 31, 2003
Reviewer: A Reader
Hi Tom,
I’m  selecting approximately 1 million records from some tables and populating
another set of tables. Here the source tables have data with leading spaces and
the target data should be without spaces.
I plan to use RTRIM function in the WHERE clause to get specific set records and
then after fetching, I’ll insert with a RTRIM on each column.
 INSERT into abc values (RTRIM(col1), RTRIM(col2)……….)
There is another option where I can use SELECT RTRIM(col1), RTRIM(col2) from xxx
  where RTRIM(col1) = ‘yyy’ and then directly use insert into target table
without RTRIM.
 Can you please tell  me which option performs better if the number of records
are in millions.
Followup:
insert as select

will be better then

  for x in ( select )
      insert

in virtually all cases.
Rating: 5
Update January 02, 2004
Reviewer: ARC from Sing
Hi Tom,
I need your help in update.
I have a table t1 which is having all orders information.
Another table summary of orders tt1 which is having current year summation and
respective previous year summation columns.
SQL> desc tt1
Name                            Null?    Type
------------------------------- -------- ------------
ORDER_NUMBER                             NUMBER(10)
ORDER_DATE                               DATE
CY_ORD_AMT                               NUMBER
PY_ORD_AMT                               NUMBER

Order date is current year date. I am getting current year data first into above
table after I am trying to update previous year amount. I this updation I want
to find the equalent date/day in previous year corresponding to current year
date and update the amount. For this I am using another table to find previous
year dates for current year dates. But I am not able to identify the correct
record to update.
Please help.
Thanks
Followup:
and i, not knowing how your data all fits together, cannot say either.

one would need an example (complete, yet concise, with sample data and an
explanation of how the data all fits together)
Rating: 5
automatic update March 21, 2004
Reviewer: reader
hi tom ,

desc child_table
-------------------
child_id  number ;
child_birth_date date;
child_20_flag  number(1) ;

where the child_20_flag should indicate if the child reach 20 years old or not.
now , what is the best way to handle it automaticlly ?
Followup:
don't store it.  it is called "derived data"

create or replace view v
as
select child_id, child_birth_date,
       case when months_between( sysdate-child_birth_date ) >= 12*20 then 'Y'
            else 'N'
         end
from child_table;

that is the right way to do it.
Rating: 5
March 21, 2004
Reviewer: Reader.
dear sir ,
Acually my problem is huge that that simple , i just try to give u a sample of
my problem.
i just need to know the best way to update some column value that depends on
other columns value in same table (or /and ) in other tables .
specially when one of them is a date.
i do made a script that run every day at 5 am. but i need to know the best way
for that .
Followup:
they are derived columns and should not be stored -- especially something like
"20 years old flag" as that can change at any second.

that is, and will remain, my answer unless you give me a real world scenario
that would demand otherwise (the 20 year old flag should NOT be stored, period)
Rating: 5
delete child table March 22, 2004
Reviewer: A reader
Hi

I have three tables, A, B and C

A is parent of B and C, A has a composite PK deptno and dname

I would like to know if following delete SQLs are equivalent

1.------------------------------------------
delete b
where
(deptno, dept_name) in (select a.deptno, dname
                          from a, c
                         where a.deptno = c.deptno
                           and a.dname = c.dept_name);

2.-----------------------------------------
delete b
where
deptno = (select a.deptno
            from a, c
           where a.deptno = c.deptno
             and dname = dpt_name)
and
dept_name = (select a.dname
               from a, c
              where a.deptno = c.deptno
                and a.dname = c.dpt_name);

Are they :-?
Followup:
hows about this -- you write out in english what each one does -- write the
"specs" if you will that describe what each query does.

then, we'll all know.

(but #2 looks "wrong" if a's primary key is deptno,dname -- that means a given
deptno can return many dnames and a dname many deptnos and hence the "deptno = (
select .... )" would return more than one row in general leading to an
error)....
March 22, 2004 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.
Rating: 3
Gr8 January 19, 2004
Reviewer: Bipin Ganar from INIDA
Hi Tom,
I have two schmemas both are in same Test Database.
User1 and User2. I have taken Export from <User1> which is using tablespace
<tspace1> and <User2> with <tspace2>
When I import from <user1> to <User2>. This by default create all tables in
<tspace1> but I have to import in other one i.e. <tspace2>. How can i do this ?
Followup:
read the original answer.  it describes how to use exp/imp to move things across
tablespaces.  the SAME exact principle works for across tablespaces with two
accounts.  Follow the bulleted list above in the exp/imp section
Rating: 3
Nice March 22, 2004
Reviewer: Roselyn from PA,USA
Dear Tom,
 What else a tablespace can store other than datafiles?Are
they confined only to store datafiles?
Please do reply.
The command
 "sql>drop tablespace <name> including contents and datafiles cascade
constraints;"

made me to ask like that.
Followup:
tablespaces do not store datafiles.

tablespaces store segments.
they store segments in datafiles.

tablespaces are made up of one or more files.

tablespaces contain segments and store them in these files.

that command simply says "drop all segments in that tablespace, drop the
tablespace, and then erase the files"
March 22, 2004 D -- Thanks for the question regarding "Error using the example", version 9.2.0.3
You Asked

http://otn.oracle.com/tech/webservices/htdocs/samples/dbwebservice/DBWebServices_PLSQL.html
I installed the example as mentioned in the document. It compiles fine.  But
when executing it gives following error. I can't figure out what's wrong.

select * from book_quote
             *
ERROR at line 1:
ORA-31011: XML parsing failed
ORA-19202: Error occurred in XML processing
LPX-00210: expected '<' instead of 'f'
Error at line 1
ORA-06512: at "SYS.XMLTYPE", line 0
ORA-06512: at "AQU_NTRL.DEMO_SOAP", line 79
ORA-06512: at "AQU_NTRL.BOOK_SERVICE", line 13

select * from book_quote
             *
ERROR at line 1:
ORA-31011: XML parsing failed
ORA-19202: Error occurred in XML processing
LPX-00210: expected '<' instead of 'f'
Error at line 1
ORA-06512: at "SYS.XMLTYPE", line 0
ORA-06512: at "AQU_NTRL.DEMO_SOAP", line 79
ORA-06512: at "AQU_NTRL.BOOK_SERVICE", line 13

The version I am using is
SQL> 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 Solaris: Version 9.2.0.3.0 - Production
NLSRTL Version 9.2.0.3.0 - Production

Thanks for help.
Regards
D
and we said...
I asked Sean Dillon, our resident XML technologist, to take a look at this and
here's what he had to say:

From the error you posted, both of these failures are happening here:

ORA-06512: at "AQU_NTRL.DEMO_SOAP", line 79

If you look in the sample code, this line is:

   utl_http.end_response(http_resp);
   resp.doc := xmltype.createxml(env);
-->resp.doc := resp.doc.extract('/soap:Envelope/soap:Body/child::node()',
     'xmlns:soap="
http://schemas.xmlsoap.org/soap/envelope/
;<--
    -- show_envelope(resp.doc.getstringval());
    check_fault(resp);

On either line 78 or line 79, when DEMO_SOAP tries to create an XMLType
instance, the call is failing because either the value of "env" isn't a valid
XML document (line 78) or the envelope of the soap response isn't a valid XML
document (line 79).  What I recommend is you use a PL/SQL debugger to check the
value of ENV before line 78, the value of resp.doc before line 79.  This will
tell you if you have a valid XML document being returned from the Web Service.
If this is fine, debug the clob value of the xpath expression applied againt
resp.doc in line 79, kinda like this:

debug.f('Extracted value is %s',
  resp.doc.extract('/soap:Envelope/soap:Body/child::node()',
  'xmlns:soap="
http://schemas.xmlsoap.org/soap/envelope/
.getClobVal());

The operative part is extracting the XML data and getting the clob value using
the XMLType member function, "getclobval()".  This returns a clob with the
result of the extract call.  This will tell you if this, in fact, is a valid XML
document.

JDeveloper has a PL/SQL debugger.  I know there are also some tools that provide
PL/SQL debugging as well.  Personally, I use a PL/SQL debugging package written
by Chris Beck, "DEBUG".  Chris created a debug package that looked and acted
like a C debugger, and I find it incredibly handy when trying to resolve issues
just like this one.  DEBUG outputs to a file or to a web page, depending on
which approach you're more comfortable with.  You can get the source code along
with README here:
http://asktom.oracle.com/~sdillon/debug.tar
Good luck, I hope this helps!
March 22, 2004 -- Thanks for the question regarding "Problem using updateXML.", version 9.2.0.4.0
You Asked
Hi Tom,

I searched a lot for updatexml examples in your site and also in otn. But i did
not find any related to my problem.
i will be grateful if you can help me out or ask any of your xml experts.

I have registered an xml schema in the database and i created a table in which
one column is schema based storage.Then i inserted the xml data into the table.

When i query the column using the below query, i get such an output.

SELECT  EXTRACTVALUE(VALUE(x),
'/StepGeneralColumn[@DataType="KonfigFile"]/text()', 'xmlns="
http://www.w3.org/2001/XMLSchema/XMLAblDaten.xsd
  FROM TabL_FileContent
, TABLE(XMLSEQUENCE(EXTRACT(SequenceData, '/MEPSequenceData//StepGeneralColumn',

'xmlns="
http://www.w3.org/2001/XMLSchema/XMLAblDaten.xsd
)) x
WHERE FileID =  235633
/

c:\konfig1.cfg
c:\konfig2.cfg
c:\konfig3.cfg
c:\konfig4.cfg

Which means i get all the data. Now i want to update the same XPATH using
UpdateXML.
I want to remove the physical path from the filenames after it is stored in the
database. So it should become.

konfig1.cfg
konfig2.cfg
konfig3.cfg
konfig4.cfg

But when i use UpdateXML, it updates all the instances with the same value. So
all becomes konfig4.cfg. But i want to update for each instance.

some thing like

update table set column = konfig1.cfg where column = c:\konfig1.cfg.

Part 2.
-------
Another question is when i register a schema, the schema url is given using
http://
Can this be something like file:\\

Thanks.
and we said...
I asked Sean Dillon, our XML technologist, to take a look at this and here's
what he had to say:

UpdateXML can't be used to update the results of an xpath expression.  It should
probably be renamed to "ReplaceXML".  You use it to provide a new XML node or
document, and specify either the node or document to be replaced.

I'm not sure what your source document looks like, so I'll describe a couple of
approaches we can use to accomplish what you need to do, you can use the most
appropriate one depending on your needs.

If the values you need to update are all in a single XML node, and you could
reliably predict which node those values were in, you could extract JUST that
node, convert the extracted value to a clob, update the values in PL/SQL (while
maintaining the entire node), and THEN use UpdateXML to replace the original
node with the updated node.

ops$sdillon@GROOVYDB> create table xmldocs ( xml xmltype );
Table created.

ops$sdillon@GROOVYDB> insert into xmldocs
  2  values ( xmltype.createxml('<Files><File>c:\file1.txt</File>'||
  3                                    '<File>c:\file2.txt</File>'||
  4                                    '<File>c:\file3.txt</File>'||
  5                                    '<File>c:\file4.txt</File>'||
  6                             '</Files>' ) );
1 row created.

ops$sdillon@GROOVYDB> select * from xmldocs;
XML
-------------------------------------------------------------------------------<F
iles>
  <File>c:\file1.txt</File>
  <File>c:\file2.txt</File>
  <File>c:\file3.txt</File>
  <File>c:\file4.txt</File>
</Files>

ops$sdillon@GROOVYDB> declare
  2    l_idx     pls_integer := 0;
  3    l_xmlclob clob;
  4  begin
  5    select xmltype.extract(xml,'/Files').getclobval()
  6      into l_xmlclob
  7      from xmldocs
  8     where rownum = 1;
  9    --
 10    loop
 11      l_xmlclob := replace(l_xmlclob,'c:\','');
 12      l_idx := instr(l_xmlclob,'c:\');
 13      exit when l_idx = 0;
 14    end loop;
 15    update xmldocs
 16       set xml = updatexml(xml, '/Files', xmltype.createxml(l_xmlclob));
 17  end;
 18  /
PL/SQL procedure successfully completed.

ops$sdillon@GROOVYDB> select * from xmldocs;
XML
-------------------------------------------------------------------------------<F
iles>
  <File>file1.txt</File>
  <File>file2.txt</File>
  <File>file3.txt</File>
  <File>file4.txt</File>
</Files>

Another way to do this would be to count the number of values in the document
you need to update.  Once you knew how many times it occurred, you could loop
over the document that many times and submit a updatexml() call to replace the
value each time:

-- use the same create table & insert statement --
begin
  for i in 1 .. 4 loop
    dbms_output.put_line('updating node ['||i||']');
    update xmldocs
       set xml = updatexml(xml,
                           '/Files/File['||i||']/text()',
                           replace(
                             extractvalue(xml,'/Files/File['||i||']/text()'),
                             'c:\', ''));
  end loop;
end;
/
-- results in the same xml doc as the previous example, without the 'c:\'...

There are a couple of ways to do it.  I hope that helps!

Sean
Review & Followup

Rating: 4
March 22, 2004
Reviewer: A reader
Thanks a ton. you are genious. That's why i was desperate to get you gurus
online. Because your thinking always makes a difference. your test case exactly
had the solution i was looking for. But since it was bit late i tried the other
way. Can you please tell me if this is okay.

SQL> SELECT extractValue(value(x), '/StepGeneral.Column')
FROM TabL_FileContent
, TABLE(XMLSEQUENCE(EXTRACT(SequenceData,
'/MEPSequenceData//StepGeneral/StepGeneral.Row/StepGeneral.Column[@DataType="Konf
igFile"]'))) x
where fileid=236942
/

EXTRACTVALUE(VALUE(X),'/STEPGENERAL.COLUMN')
---------------------------------------------------------------------------------
-------------------
m:\data\konfig_1.xsd
m:\data\konfig_2.xsd
m:\data\konfig_3.xsd
m:\data\konfig_4.xsd

SQL> UPDATE TabL_FileCOntent
SET SequenceData = UpdateXML(SequenceData,
'/MEPSequenceData//StepGeneral/StepGeneral.Row/StepGeneral.Column[@DataType="Konf
igFile"]/text()', 'hello')
WHERE ExistsNode(SequenceData,
'/MEPSequenceData//StepGeneral/StepGeneral.Row/StepGeneral.Column[@DataType="Konf
igFile"]="m:\data\konfig_1.xsd"') = 1
AND FileID = 236942;

1 row updated.

SQL> SELECT extractValue(value(x), '/StepGeneral.Column')
FROM TabL_FileContent
, TABLE(XMLSEQUENCE(EXTRACT(SequenceData,
'/MEPSequenceData//StepGeneral/StepGeneral.Row/StepGeneral.Column[@DataType="Konf
igFile"]'))) x
where fileid=236942
/

EXTRACTVALUE(VALUE(X),'/STEPGENERAL.COLUMN')
---------------------------------------------------------------------------------
-------------------
hello
hello
hello
hello

SQL> rollback;

Rollback complete.

SQL> SELECT extractValue(value(x), '/StepGeneral.Column')
FROM TabL_FileContent
, TABLE(XMLSEQUENCE(EXTRACT(SequenceData,
'/MEPSequenceData//StepGeneral/StepGeneral.Row/StepGeneral.Column[@DataType="Konf
igFile"]'))) x
where fileid=236942
/

EXTRACTVALUE(VALUE(X),'/STEPGENERAL.COLUMN')
---------------------------------------------------------------------------------
-------------------
m:\data\konfig_1.xsd
m:\data\konfig_2.xsd
m:\data\konfig_3.xsd
m:\data\konfig_4.xsd

SQL> UPDATE TabL_FileCOntent
SET SequenceData = UpdateXML(SequenceData,
'/MEPSequenceData//StepGeneral/StepGeneral.Row/StepGeneral.Column[@DataType="Konf
igFile"][text()="m:\data\konfig_1.xsd"]text()', 'hello')
WHERE ExistsNode(SequenceData,
'/MEPSequenceData//StepGeneral/StepGeneral.Row/StepGeneral.Column[@DataType="Konf
igFile"]') = 1
AND FileID = 236942;

1 row updated.

SQL> SELECT extractValue(value(x), '/StepGeneral.Column')
FROM TabL_FileContent
, TABLE(XMLSEQUENCE(EXTRACT(SequenceData,
'/MEPSequenceData//StepGeneral/StepGeneral.Row/StepGeneral.Column[@DataType="Konf
igFile"]'))) x
where fileid=236942
/

EXTRACTVALUE(VALUE(X),'/STEPGENERAL.COLUMN')
---------------------------------------------------------------------------------
-------------------
hello
m:\data\konfig_2.xsd
m:\data\konfig_3.xsd
m:\data\konfig_4.xsd

Here i have updated only first record. In runtime it will be in a loop as
displayed by you in your test case.
Thanks once again.
March 22, 2004 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)
Rating: 5
ORA-29260: network error: TNS:lost contact March 17, 2004
Reviewer: Gabriel from Montreal, Canada
Hello Tom,

I am using an FTP package implemented by Tim Hall, probably you are fimiliar
with it if not:
http://www.oracle-base.com/Articles/9i/FTPFromPLSQL9i.php
I am getting the ORA-29260: network error: TNS:lost contact error only when
trying to connect to certain servers. I am using the following script to test
it:

declare
  l_conn  UTL_TCP.connection;

begin

l_conn :=
ftp.login('<server_name>','<server_port>','<user_name>','<user_password>');
     ftp.binary(p_conn => l_conn);
     ftp.put(p_conn      => l_conn,
             p_from_dir  => '<source_directory>',
             p_from_file => '<source_file_name>',
             p_to_file   => '<destination_>'||'test.txt');
     ftp.logout(l_conn);
     utl_tcp.close_all_connections;

end;

The manual ftp command works thou which is a bit puzzeling.
The sys admin is not very cooperative giving me the typical 'if the os command
is working than the error is on the oracle side; you fix it'. But the package
was working perfectly fine on other servers! Are you familiar with this package?
I are probably familiar with the utl_tcp package but I don't know exactly from
which of the two the error is comming from? Did you see this problem before?
I understand that you might not be able to test it and reproduce as it seems to
be dependent on the server. Can you at least tell me where to start looking, and
debugging?
Followup:
you'll have to debug it a bit and see if it always fails on the same line of
code (reproducible) or moves around

you have all of the source code -- you can instrument it a bit to see where and
why it is failing and if it reproduces, that'll be "a good thing"

database versions and all (and os's) would be "somewhat relevant" when asking as
well -- but put in some debug and figure out if it always fails at the same
place.
Rating: 4
Reproductible March 17, 2004
Reviewer: Gabriel from Montreal, Canada
Hello Tom,

It always fail as follows

ORA-29260: network error: TNS:lost contact
ORA-06512: at "SYS.UTL_TCP", line 410
ORA-06512: at "SYS.UTL_TCP", line 316
ORA-06512: at "SYS.UTL_TCP", line 380
ORA-06512: at "UTILITY.FTP", line 95
ORA-06512: at "UTILITY.FTP", line 81
ORA-06512: at "UTILITY.FTP", line 25
ORA-06512: at line 6

line 25:   send_command(l_conn, 'USER ' || p_user);
line 81:   get_reply(l_conn);
line 95:   g_reply(g_reply.last) := UTL_TCP.get_line(l_conn, TRUE);

I'm running on HP-UX 11.11 sending to windows nt and a friend of mine had the
same problem sending to a SUN machine. Again the proc works fine when sending to
another HP-UX 11.11 Database version on the hp_ux 9.2.0.4

Do you think we need the oracle sofware installed on the receiving machine? On
the machines that it was working we had that but I don't know about the machines
on which it was failing.
Followup:
no, no oracle software on the recieving machines, they are just ftp clients.

any firewalls here?
Rating: 5
We have a firewall March 18, 2004
Reviewer: Gabriel from Montreal, Canada
Hello Tom,

Yes, there is a firewall, but the sysadmin said it is open for the needed ports.
I tried an manual OS ftp and it worked.

I added:

EXCEPTION
WHEN UTL_TCP.END_OF_INPUT THEN
NULL;
WHEN others THEN
DBMS_OUTPUT.PUT_LINE(substr(SQLERRM, 1, 80));

and the procedures now ends with no error, sign that it is actually
UTL_TCP.END_OF_INPUT the error that I'm getting.
But what to do to fixit?

Thank you very much,
Followup:
i asked because I see an issue logged that is exactly the same -- a truss of the
process shows that in fact their is a sigpipe being sent (eg: the connection is
being messed about with) and the rather simple tcp layer you have in plsql is
reporting it as an error whereas ftp (a slight more sophisticated application)
must be retrying or handling it differently.

Basically the pipe is in fact being closed on the remote end -- this is
identical the issue I see logged (the truss verified the remote end in fact did
close the connection).  it only happened through the firewall.

the only thing I can suggest is to try more "sophisticated" ftp applications
(eg: load up a java class) or if repeated tries work - catch the exception and
retry.
Rating: 5
my bad March 18, 2004
Reviewer: gab
I think I made a mistake. I forgot to set server output on thats is why I didn't
get the error. With that one set I get
ORA-29260: network error: TNS:lost contact tried;
Does this make a difference?

Thank you,
Rating: 5
additional info March 18, 2004
Reviewer: gab
With the serverout set on I also see the foloowing:
220 toroondc898 Microsoft FTP Service (Version 5.0).
before
ORA-29260: network error: TNS:lost contact tried;
together they look like:

220 toroondc898 Microsoft FTP Service (Version 5.0).
ORA-29260: network error: TNS:lost contact tried; continuing2

can it be that utl_tcp can't handle this return message?
Thank you
Rating: 5
March 18, 2004
Reviewer: gab
Hello I'm getting the same error when trying to ftp to a sun server although it
works towards other sun servers. here is the error:

220 qe70pdb1 FTP server (SunOS 5.6) ready
ORA-29260: network error: TNS:lost contac

PL/SQL procedure successfully completed.

any ideas?
Rating: 5
utl_tcp port? March 22, 2004
Reviewer: gab from Montreal, Canada
From what I understand the utl_tcp doesn't use the os ftp to communicate. In
this case which port does the destination machine receives the communication on
and which port should be opened in a firewall if the communication goes through
a firewall?

Thank you very much,
Followup:
you'd have to read up on the ftp protocol itself (there is an RFC out there
somewhere)
March 22, 2004 Dennis -- Thanks for the question regarding "Database Links and Test Databases", version 7.3 & 8i
You Asked
Any advice on setting up test environments?  Any articles out
there I can look at?  We recently set up a more stable test
environment with one machine per Oracle version and I am looking
for advice.  Potential pitfalls?

For now, we simply maintain our test databases by appying cold
backups when necessary.  Can I change a SID's when recreating an
instance in the test environment. One problem I have is creating
database links between the test and production servers when
necessary.

Thanks.
and we said...
I am not aware of any white papers on this subject.

I don't see any pitfalls with 1 version / machine.  I run many
releases on the same machine, running 1 version / machine would
be ideal.

Applying the cold backup is a great way to copy an instance (if
you have the flexibility of doing that cold backup).  A hot
backup + restore would do it as well.

If you have problems creating links between test and production
-- its most likely due to GLOBAL NAMES.  You don't need to chang
the sid to fix this, just "alter database rename global_name to
new_name"

Here is how to change a database name and sid of an instance:

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.

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 if need be.

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
...

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 are prompted for an archived
redologfile, the database was not shutdown normally as specified
in step 4 and so, during recovery it is asking for the redo in
the current logfile. 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. Make sure the database is working.

14. Shutdown and backup the database.
Review & Followup

Rating: 3
find usage of db links March 22, 2004
Reviewer: big fan from nyc
tom,
How would you go about finding ALL usages of db links, anywhere, in any objects,
in a database ?

thanks
Followup:
DBA_DEPENDENCIES would show you static references from views, codes, whatever to
any object you want.

If you use lots of dynamic SQL -- there will be no hard link of course.
March 22, 2004 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:)
Rating: 5
Limitations of objects in Oracle March 20, 2004
Reviewer: A reader
Oracle objects have come a long way since their
introduction - the most important improvement perhaps
being that of type evolution.
However, it still has some major limitations.
For example, it seems that encapsulation of data
in object types is weak in Oracle since it does not support
private attributes. In Java if you have a "set" method
that sets an attribute(declared private),
you are guaranteed that the method is the *only* mechanism of changing the
attribute - this is a major feature of the object orientedness of Java (or for
that matter, any OO language.)

In Oracle objects (even in 10g), the attributes
are public. So you can change them directly
without using any "set" methods. Perhaps
a mechanism by which you can declare object attributes
within the body (instad of the specification) may
be useful (kind of like packages.) currently
this is not supported (even in 10g)

Following are the reasons I think Oracle does not
support this yet (assuming you are using objects in
tables instead of object views/as extensions to pl/sql.)

1. Object data is stored in tables in the end. If
   the only way to get at the data is through setter
   and getter then the performance of any dmls
   that retrieve/modify data would suffer due to the
   extra function calls required.
2. There may be ways of modifying the table data themselves
   thus overcoming the "private" data in objects.
   This could perhaps be handled by some privileges
   (e.g. give privilege to execute the object methods
   but dont give privilege to modify the underlying
   table directly?
Even though (as your book states), objects give us
a way of binding data to methods, they do not seem to
guarantee that the methods are the only way to change/see the data - which
defeats to a large purpose the encapsulation mechanism.

Your thoughts?
Thank you!
Menon:)
Followup:
my thoughts are that I use them as a useful programming technique (in plsql
code) but I've never used them to actually store data.

They are a tool.  I'm not even going to compare them to java or any other
language really.  they are what they are....
Rating: 5
even in extending pl/sql March 22, 2004
Reviewer: A reader
it may be useful only for small utilities perhaps. Any
serious OO programming is hampered by the lack
of true encapsulation mechanism. Of course on this
site and in your books, you have demonstrated lot of times how useful these
features are for utilities..
I do feel that if Oracle completes the OO ness
of its implementation, its range as a programming
language would increase tremendously...

Thanx!
Menon:)
Followup:
I guess that assumes that you have bought into "OO" is the "RW" (right way) -- i
myself find you can write very good code with little effort with in C, you can
write really bad code with lots of work in C.

You can write very good code with little effort in
Java|Smalltalk|Lisp|Eifel|C++.
You can write really bad code with lots of work in the same.

they are just programming languages.

Objects, Types, etc -- syntatic sugar, and sometimes sugar is bad for your
health.

OO is just one way.  It's funny how to me OO is just a codification of what we
used to call "modular programming".  Just as a rose smells just as sweet by any
other name -- a Subroutine is still a subroutine (even if you call it a method)
Rating: 5
well.. March 22, 2004
Reviewer: Menon
"I guess that assumes that you have bought into "OO" is the "RW" (right way)"

I never said that:)
All I am saying is that Oracle claims it has support
for Object Oriented programming but this claim is hampered
by the shortcomings (some of which I pointed above.)

OO in my humble opinion, is definitely not what I would
call the "right way" all the time. There is no right way for all situations.
There are times when OO is a more powerful programming paradigm that its
procedural counterparts - other times it only complicates
code and procedural language is better off.

Just because most J2EE developers abuse the database
developement by their lack of knowledge of db, does
not make Java or OO any less or more powerful paradigm than it is.
And in my opinion, OO is perhaps a bit more than mere
syntactic sugar...

Thanx!:)
Menon:)
Followup:
Object Relational is all we've claimed....

;)
March 22, 2004 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.
Rating: 5
Complex queries March 22, 2004
Reviewer: Anand from Austin, Texas
I have this query that I need to use to load data in to a remote database. I use
sql "COPY" command to accomplish this. Can this query be used inside an export
"query" claus ??

SELECT /*+ PARALLEL(tls,6) PARALLEL(TL,6)*/
tl.*  -
FROM test_limit_set tls, test_limit tl
WHERE tls.test_limit_set_sk = tl.test_limit_set_sk
AND mfg_area_name='TEST14'
AND NOT EXISTS
        (select * from test_limit@saph.jove a
    where tl.test_limit_sk = a.test_limit_sk)
AND NOT EXISTS
        (select * from test_limit@saph.jove a
    where tl.test_number = a.test_number
     and tl.test_limit_set_sk = a.test_limit_set_sk) ;

This is the my parfile :

userid=saphdba/******
full=n
file=exp_tl.dmp
log=exp_tl.log
buffer=8192000
tables=sapphire.test_limit, sapphire.test_limit_set
query=" WHERE tls.test_limit_set_sk in (select test_limit_set_sk from
test_limit_set where tls.mfg_area_name='TEST14')
        AND NOT EXISTS
         (select * from test_limit@saph.jove a
          where tl.test_limit_sk = a.test_limit_sk)
        AND NOT EXISTS
         (select * from test_limit@saph.jove a
          where tl.test_number = a.test_number
          and tl.test_limit_set_sk = a.test_limit_set_sk)"

It is erroring out with :

About to export specified tables via Conventional Path ...
Current user changed to SAPPHIRE
. . exporting table                     TEST_LIMIT
EXP-00056: ORACLE error 904 encountered
ORA-00904: invalid column name
. . exporting table                 TEST_LIMIT_SET
EXP-00056: ORACLE error 904 encountered
ORA-00904: invalid column name
Export terminated successfully with warnings.

Your help is appreciated...
Followup:
no, because the where clause is applied to a SINGLE table.

you would have to use two exports, each with a where clause that applied to a
single table (eg: instead of joining, you would use IN( subquery ))
Rating: 5
Thanks for the reply... March 22, 2004
Reviewer: Anand from Austin,TX
I agree. We are in RBO and using "IN" in RBO murders the performance!!
Followup:
CTAS and export the table.....
March 22, 2004 dw -- Thanks for the question regarding "About Temporary tables", version 8.1.5
You Asked
Hi tom...

Actually, ive never created any temporary table till now, i just like to ask if
when creating the table, on what tablespace will the temporary table be stored?
i need to consider the space to make sure that the creation is successful... and
by the way, can you site an example on creating a temporary table on the
duration of a session??

Thanks and God bless..
and we said...
search for

create global temporary table

to see examples.  I have many.

When you create a temporary table -- it will not use ANY tablespace.  It will
use the TEMPORARY tablespace of the current schema when accessed at runtime.
When you create a temp table -- no space allocated.  when you insert into temp
table, the space will be gotten from your temporary tablespace (or if the
temporary table is used in a stored procedure with definer rights - the
temporary tablespace of the owner of the table)
Review & Followup

Rating: 3
What about storage April 09, 2002
Reviewer: G Rama Subramanian from Bangalore, India
Hi Tom,

If the temporary table resides in the Temporary Tablespace during runtime, it
means that the Temporary tablespace should be large enough to accommodate all
the extents that might get created if the data stored at runtime in the
temporary table is going to be huge.  Does this create any sizing issue?  We
have a temporary tablespace, that does not take this aspect into account.  I now
think that I should add sufficient space by adding a datafile to the temporary
tablespace.  Am I right?
Followup:
Yes, TEMP needs to be sized appropriately to hold these things as well.
Rating: 4
What are all the drawbacks (performance), if they hold different datatype April 09, 2002
Reviewer: Prince
Tom,

Say I have the TEMPORARY table with one varchar2 column.

create global temporary table gtd_tab1 (col1 varchar2(30)) on commit delete
rows;

Is it better to use two different TEMP tables, if I want to keep NUMBER data
type in one transaction and VARCHAR2 data type in another transaction? or is it
OK, if I just share the same table?

Thanks,
Followup:
well, that table can only hold varchar2 data, it cannot hold a number type.

If you add a number column, it'll be just fine to use the single table --
holding numbers one time, varchar2's the next.
Rating: 5
ARCHIVE LOG April 10, 2002
Reviewer: ashraf
WHAT IS THE IMPACT TEMP. TABLE ON ARCHIVE LOG.

CAN YOU GIVE EXAMPLE
Followup:
WHY DO YOU SHOUT.

It will have no extraordinary impact on the logs.  If you have my book -- I
cover this in great detail.

If all you do is INSERT and SELECT from the temp tables -- very very little redo
is generated.  We do not generate any redo to REDO the insert, but we do
generate rollback (undo) information and this undo is protected by redo.

Hence, if you INSERT into a temp table, a very little amount of UNDO will be
generated (in the event you rollback, we need to undo the insert as well).  That
UNDO will be protected by REDO.  The INSERT itself will not be.

It is only when you start massively updating or deleting this temp object that
large amounts of redo would be generated.  For example, suppose you put
1,000,000 rows into the temp table.  That insert would generate little UNDO and
correspondingly little REDO.  Now, suppose you "delete from temp_table" -- that
will generate a massive amount of UNDO (in order to rollback the delete if you
need) and that UNDO will be protected by REDO.

Since temporary tables are used almost 100% for INSERTS and SELECTS, this is
just fine.  You should rely on a COMMIT, TRUNCATE or end of SESSION to "delete"
the data and that will have no effect on the logs since the data will just be
"wiped out" -- not logged.
Rating: 5
good one on the shout , I guess you are going to teach them many things i guess. April 10, 2002
Reviewer: Guest from India
Rating: 3
Redo and Rollback on Temporary Table June 11, 2003
Reviewer: kumar from India
Tom,

We have 9iR2 on NT. When I was doing some tests on global temporary tables, I
got the following results.
-------------------------------------------
TableType           Normal    Temporary
-------------------------------------------
Redo Generated      3035912   3396384
-------------------------------------------
Rollback (blks)     13        223
-------------------------------------------
The database in running in archivelog mode. I did a normal insert into table
select * from all_objects; As per my understanding the redo generated for a
temporary table should be much less than for a normal table..and also the
rollback generated was quite dramatic.. But I am not able to understand why I
got the above results.. Further, I did the same test with direct mode insert.
This time I got the results as below:
-------------------------------------------
TableType           Normal    Temporary
-------------------------------------------
Redo Generated      3247980   224
-------------------------------------------
Rollback (blks)     1         1
-------------------------------------------

Though the second set of results was understandable I could not understand the
first test.
Can you please explain why I am getting the above results ?
Rating: 5
Yeah, I can replicate this as well! August 28, 2003
Reviewer: Martin from UK
SQL> CREATE GLOBAL TEMPORARY TABLE gtt ( a VARCHAR2(10) ) ON COMMIT PRESERVE
ROWS;

Table created.

SQL> INSERT INTO gtt SELECT 'x' FROM all_objects;

30568 rows created.

Statistics
----------------------------------------------------------
         25  recursive calls
      31273  db block gets
     139489  consistent gets
          1  physical reads
    3806280  redo size
        880  bytes sent via SQL*Net to client
        663  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      30568  rows processed

SQL> CREATE TABLE t ( a VARCHAR2(10) );

Table created.

SQL> INSERT INTO t SELECT 'x' FROM all_objects;

30569 rows created.

Statistics
----------------------------------------------------------
        109  recursive calls
        444  db block gets
     139536  consistent gets
          0  physical reads
     389604  redo size
        880  bytes sent via SQL*Net to client
        661  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      30569  rows processed

Why is there a massive difference in the redo generated for these statements
(given what's been said above)?

Thanks in advance
Rating: 5
Sorry, forgot to mention versions.... August 28, 2003
Reviewer: Martin from UK
SQL> 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
Rating: 3
Followup February 24, 2004
Reviewer: Dušan from London, England
Tom,

Just as I thought you would shed some light on the questions posted above on
amount of Redo that the guys observed in their tests on 9.2, the thread came to
an end, without your further postings.

Could you please explain why we can see redo much bigger than we thought in
these test cases (and real apps too)?

Indeed, it seems that more redo gets generated with global temp tables.

Consider what I just saw on 9.2.0.3:

SQL> create table x1 as select * from all_objects where 0=1;

Table created.

SQL> create global temporary table x2 on commit delete rows as select * from
all_objects where 0=1;

Table created.

SQL> insert into x1 select * from all_objects;

26798 rows created.

Statistics
----------------------------------------------------------
        364  recursive calls
       2337  db block gets
     108386  consistent gets
          2  physical reads
    3045692  redo size
        378  bytes sent via SQL*Net to client
        330  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      26798  rows processed

SQL> insert into x2 select * from all_objects;

26798 rows created.

Statistics
----------------------------------------------------------
         18  recursive calls
      28631  db block gets
     107874  consistent gets
          0  physical reads
    3441496  redo size
        378  bytes sent via SQL*Net to client
        329  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      26798  rows processed

Redo is bigger with global temporary table on commit delete?

Many thanks, and all the best!
Rating: 4
Global Temporary Table February 25, 2004
Reviewer: Vamsi Dhar from India
I am using a global temporary table and trying to access the global temporary
table using synonym from a different schema. Its giving me an error saying
"Table does not exist"

Pls. show me an alternate way of accessing a global temporary table using a
differnt schema
Followup:
show me the example from start to finish where it does not work.  Perhaps you
are missing a grant.

Here is my working example:

ops$tkyte@ORA920PC> drop user a cascade;

User dropped.

ops$tkyte@ORA920PC> drop user b cascade;
drop user b cascade
          *
ERROR at line 1:
ORA-01918: user 'B' does not exist

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> grant create table, create session to a identified by a;

Grant succeeded.

ops$tkyte@ORA920PC> grant create synonym, create session to b identified by b;

Grant succeeded.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> @connect a/a

a@ORA920PC> create global temporary table gtt ( x int );

Table created.

a@ORA920PC> grant all on gtt to b;

Grant succeeded.

a@ORA920PC>
a@ORA920PC> @connect b/b

b@ORA920PC> create synonym gtt for a.gtt;

Synonym created.

b@ORA920PC>
b@ORA920PC> select * from gtt;

no rows selected

b@ORA920PC> insert into gtt values ( 1 );

1 row created.

b@ORA920PC> select * from gtt;

         X
----------
         1

b@ORA920PC>
Rating: 3
Follow up February 25, 2004
Reviewer: Dušan from London, England
I might actually be hitting the bug 2874489.
Rating: 2
Excessive redo on temporary tables March 22, 2004
Reviewer: gorr from Calgary, Alberta, Canada
Though I always appretiate Tom's useful responses, this seems to be a thread
that has been neglected.  I am having the same problem with temporary tables in
ver. 9.2.0.4 where there is a large volume of redo being generated for an insert
into a temporary table.  I have been watching this thread for some response but
there doesn't appear to be any.  I'm hoping for some follow up.  Even if there
is a patch related to this issue, that would be great as I can't seem to find
any on MetaLink related to it.

Thanks again Tom,
Love the books and the advice
Followup:
look up one review?
Rating: 5
Sorry, I must be blind March 23, 2004
Reviewer: feeling a little stupid... from Calgary, Alberta, Canada
Sorry about that.  I don't know why I didn't see that.
March 22, 2004 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"
Rating: 5
archiving files too fast March 22, 2004
Reviewer: jasdeep from patiala,india
hi tom

in our database sometimes archive files occurs very fast like 35 files in one
hour. at other times it 1 file in 4 hours as load of work is not in much
differnce. how is this occurring.
we have ibm - aix oracle 9.0.2 environment
Followup:
wellll, i would say "the workload is very much different"

it has to be, else you would not observe this.

you can use logminer to see "whats in the log".
March 23, 2004 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.
Rating: 2
Lots of PreparedStatement questions left unanswered March 15, 2004
Reviewer: Old, but young at SQL from Silicon Valley
Thanks for the education about bind variables and the chapter from your book
(and many related similar pages).  I'm getting 00604 and an 04031 errors, but I
don't know why.  It doesn't happen here, just in the field, with the exact same
environment, and near identical usage.

Perhaps it's the lack of PreparedStatement use in places, but I don't use
connections that long, yet the errors keep happening- it seems like closing
connections (not pooling, at least in our code...) isn't releasing the memory!
Q: Shouldn't all these resources be released when I close the connection?

I get the multi-vs-single user issue, which doesn't apply to this project, but
not the shared memory issue.
Q) If I do sequential inserts using Statement (not PreparedStatement), does it
keep around the memory and the latches after I use the statement object a second
time?
Q) How about if I close the statement and then create a new one?
The Java docs imply that re-use and closing both release associated Java and
database resources.

I also get the parse-time issue- that's big.  But I don't get why more latching
occurs with Statements.
Q) Does some latching occur during the parse?  Why?
I have only 3 processes using the database at a time, and only a few very
lightly used tables are possibly read and written at the same time.

Note: I prefer Statement over PreparedStatement because the latter are so hard
to debug.  If there's a bad value, it doesn't even say which field!  The errors
are obscure. I guess I should subclass it and all the setType methods and salt
away the values and reconstruct them for printing if there's an exception-
Q) Has anyone done this?

About 90% of the time PreparedStatement seems to work.  So I went back to
Statement for the rest.  Statement is so much easier to use/debug that it saves
a lot of development time, ie, money, so I use it for selects, which I do many
of, but each only once per hour (and never multi-user).

I understand the lower parsing time, but so many of my statements are
infrequent.

Q) Sometimes I have constants in my PreparedStatements.  Is this bad?  (I put
jobs in a table: time,0,0,0,0,0.  Later on when parts complete, they update the
column.)  What if I put the whole string literal in a PreparedStatement.
Q) Is a PreparedStatement containing no ? fields still more efficient than a
Statement?  Is it less efficient than putting some/all the fields in later with
ps.setInteger()?

I appreciate the help, and will try using PreparedStatements again where they
failed before.  I'd like some more help at times- I know you're busy.  In the 6
months I've been on this project this site has refused questions...
Q) Where can I go to buy some help, per question or per hour?

(other pet peeve:  you can't query a connection for the number of open
statements.  If this were possible, I could leave connections open with
confidence...  I guess I have to take the time to subclass everything and keep
track myself, sigh..., and trap exceptions in the class, and when one happens,
close the statement and then re-throw...)

Thanks,
obyas
Followup:
if connections are not being released, you are using a connection pool of some
sort.  but -- you would know it as you would be setting it up.

yes, if you log off, all resources associated with that connection are released.
 However, if you are seeing ora-4031's and you haven't been using prepared
statements every where and you have lots of activity, well, it is pretty
straightforward what is happening (you are doing it to yourself by using
statements).

Prepared statements are *not* just about single user vs multi-user.  They are
about "doing it right" vs "doing it wrong".  They kill performance in both
cases.  Not only that but the "common" solution is to make the shared pool
really large which just pushed the problem back a couple of feet and made it
BIGGER (the correct approach is to downsize the shared pool, make it smaller,
not bigger -- while you fix your bug)

If you are doing inserts with a statement -- there IS no second time.  If you
are doing inserts with a statement -- each insert is unique, never before seen.
It is quite simply the very *worst* thing on the planet you can do.  Nothing
worse really.

Latches are quick in/out type of locks.  latches are held very brief moments
while we access a shared data structure (like the shared pool).  You use them
very briefly.  You don't even need to finish the statement to give them up.

Using statements is bad bad bad.  cannot say it any other way.

Why is is hard to understand that statements result in higher  latching then
pstatements?  with statements every execute is a parse.  a parse takes many
latches.  with prepared statements, only a parse is a parse -- you can execute
over and over and over without parsing.

Tell me -- would you compile your "methods" (aka subroutines) before each
invocation and then throw out the code?  would you compile your subroutines
thousands of times in a single program execution?  No, you wouldn't.  so, why
are you doing just that??? that is what you are doing by using a statement.

Show me how a statement makes "debugging" easier -- do that for me ok?   So, you
get an ora-1401.  How the HECK does a statement help you out there?????  How how
how -- just give me a teeny tiny small example???  I don't get it.

It wastes time
It is harder to develop with
It costs $$$$$$$ to use statements.  Look at the money you are wasting right
here, right now.  This is cheaper?????????????????

(can you guess what MY pet peeve is by now?)

You do understand that statements introduce a HUGE security risk right?  (search
google for "sql injection")

You do understand that statements consume HUGE java resources right?  (betcha
just create String temporaries all over the place)

You do understand that statements literally can kill the database right? (you
are there, you should know that -- you are doing it and seeing the results)

A prepared statement with "?" does not make sense.  I cannot beleive you do the
same exact insert (what happened to the primary key fields eh?) over and over.
But yes, if in fact you have a statement with all literal values that you
execute over and over  -- a prepared statement is more efficient than a
statement -- but I cannot see the use case for such a beast.

In the 6 months you've been on this project this site has taken

ask_tom@ASKUS> select count(*) from WWC_ASK_SUBMITTED_QUESTIONS$
  2  where timestamp > add_months(sysdate,-6);

  COUNT(*)
----------
      1013

new questions and performed:

ask_tom@ASKUS> select count(*) from WWC_ASK_REVIEW_FOLLOWUPS$
  2  where created_on > add_months(sysdate,-6);

  COUNT(*)
----------
      5387

followups like this one.......

but what resources are out there?  how about

o groups.google.com -> comp.databases.oracle.*  The internet usenet newsgroups.
There you'll get lots of people willing to provide you advice.

o otn.oracle.com -> get support -> discussion forums.  Ditto

Sorry, we did not write the jdbc spec, we just implemented it.  However, it
would be somewhat trivial I believe to have a hash table based on you connection
and every time you parse, you add to the hash table, when you close, you remove.
 Not very difficult (sigh)

Tell me this -- when you used to process FILES in the olden days -- did you leak
file handles?  or did you close them when you were done, open them when you
needed them.  statements/prepared statements are no different than a file.
Rating: 2
Answers to your questions, and a few things are not yet clear March 16, 2004
Reviewer: Old, but young at SQL from Silicon Valley
It seems like from your answer, if I create a statement, use it and close it, it
keeps using more and more shared memory until I close the connection. I can
accept that.  (Note that it still doesn't make sense- why would Oracle keep it
around?  If people aren't getting it, perhaps you're not sharing a piece of the
reason.  I can take it on faith, but I can internalize it much more deeply if I
know why.)

Maybe the problem is different though- You say:
> statements/prepared statements are no different than a file.

So which has more memory in use at a single time:
   Statement foo = conn.createStatement();
   foo.execute("select foo from bar where xxx");
   foo.execute("select foo2 from bar where yyy");
   foo.close()
Or
   Statement foo;
   ResultSet rs;
   foo = conn.createStatement();
   rs = foo.execute("select foo from bar where xxx");
   foo.close()
   foo = conn.createStatement();
   rs = foo.execute("select foo2 from bar where yyy");
   foo.close()

I was assuming they were the same, based on the docs, that re-using foo closes
rs AND any resources associated with the first select. But my results would be
explained if it were not true, that Statement.execute() opens a file till the
close() (besides the ResultSet),  rather than a createStatement() opens a file.

> if connections are not being released, you are using a connection pool

Nope, no pooling:

  Open a connection,
     create statements A and B
        do 3-10 queries with statement A
           each with 1-20000 inserts with statement B
     close the statements
  close the connection

So I'll change the inserts to all use PreparedStatements.  If I can't get them
to work, I'll close/reopen the statement every 50 or so inserts.

You asked why PreparedStatement is hard to debug.

With a Statement, I can print out the String either before the exception
happens, or as part of the exception.  Then I can copy/paste it into SQLPlus and
get a real error message that points me to some exact place in the query.

With a PreparedStatement, I have to write my own code that assembles the string
and prints it out.  Sometimes I assembled the statement by hand and it worked
fine, but the PreparedStatement still complained about a bind variable (whoever
left out WHICH variable and its value out of the error msg should be shot.)  I
know it's not the way to run a project, but I don't have much time, and the
Statement worked, and there's no hint in the JDBC API docs that there was
anything wrong with Statement, except I figured it took a lot longer, but
performance was fine.  Until now.  Yes, I'll fix it.

Often in development I'd work and re-work queries.  Doing this in a string, the
Select query LOOKS like itself.  In my queries, which return lots of rows, the
"parameters" configure the query rather than pinpointing one answer, so seeing
them in the query helps a lot.  In some of them the database table was a
parameter.  So semantically, the "variables" are different from what Oracle
thinks of as the "bind variables."  Having it all laid out is clearer.

Putting in a ?, it's easier to set things in the wrong order.  Say I rework
three things in the the query and then go fix the order and ooops, I fixed the
last two stmt.setString() but not the first.  Since the data in my queries is
meaningful to the programmer, separating the data from the query makes it more
error prone.  Being called in to learn a new industry and their poorly designed
database and undocumented database/application/industry is bad enough, using
programming constructs that obscure clarity is a hindrance.  (In fact, were I to
do it over again, the only change I'd make is to PLAN to change to
PreparedStatements at the END.)

Does it really matter if the 1-10 Selects are Statements vs PreparedStatements?
Each happens once per connection so it won't save any time.  I can easily change
to closing each one after I use it.  I don't have time to change all the
Selects, and it has to ship, so too many changes is destabilizing.

Q) How do I get Oracle to tell me the amount of connections and statements that
are open and the amount of shared mem in use?

Again, many thanks for the help, and the pointers to others.

I have two days to fix a problem that takes 4 days to manifest...

Thanks,
obyas
Followup:
that is not what I said at all -- if you use a statement, close it, it is gone
from the client side.

But over in your shared pool you have a bunch of stuff that has to be managed,
but that no one is every going to use again.  A waste of energy, we compiled a
subroutine that'll never get used a again and now we have to manage it until we
figure that out and hopefully age it out of the shared pool subpool it is in
over time...

We keep it around in the hopes that the applications were designed to be
efficient ;)  sometimes we are wrong and the applications are not designed that
way.

I cannot compare those two snippets as the first piece of code is not
"sensible".  you don't just "execute" a SELECT.  You have to get and process a
result set (else -- why bother??)

If you executed that select AND you lost the "result set" (which is not the
statement but yet another object) that would be like opening a file and not
closeing it and letting the file handle go out of scope.  For all we know,
you'll come back in 5 minutes and want that result set -- we cannot close it,
only the well formed program can do that.

Maybe what you really want to do (if you want things fast, easy to code, easy to
debug, maintainable, BOUND) is to use PLSQL actually.  Java is a seriously hard
language to write code (especially database code) in by hand.  If you code your
data logic in PLSQL you'll be coding circles around the other developers.

a) you query the v$ tables -- a wealth of information exists therein.
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96536/ch3.htm#1109131
Rating: 3
The end is near, thanks! March 16, 2004
Reviewer: Old, but young at SQL from Silicon Valley
> But over in your shared ...  A waste of energy, we compiled a subroutine ..
and .. manage it until .. age it out of the shared pool subpool ...
> We keep it around in the hopes that the applications were designed to be

So if I am running the same query once every hour, that's fine to use as a
Statement, because the procedure et al will either have aged out or will be
re-used?

It just seems that when available memory in the pool gets low, Oracle should
jettison these cached procedures, instead of throwing an out-of-memory error...

But thanks, that explains most of it.  But it still leaves 2 questions:

1. If my PreparedStatement contains literals, like "SELECT foo FROM table WHERE
a=0 AND b=?", does the use of '0' cause the same sort of memory pool loss as a
Statement?  Is it better?  Perhaps when this is closed the procedure made for it
is completely freed?

2. How can I monitor the shared memory pools in SQLPlus?

Thanks much,
obyas

After this ships, I will register and follow that link and learn about Pl/Sql.
Followup:
if it is EXACTLY the same query -- yes.  a statement would be OK in that
particular case.

Oracle tries to jettison them but...

the shared pool is sub-divided into sub pools - especially as it gets larger and
larger (soon it takes longer to age out than to actually do the work you ask
us).  So, the larger you make it in a system that isn't binding -- the worse it
actually gets.

1) a=0 is GOOD when coupled with "and b=?".  the a=0 is invariant, the b=? is
variant.  you bind things that change (like you parameterize inputs to
subroutines)....

2) select * from v$sgastat

PLSQL rocks, you will like it.
Rating: 4
One last question (I promise) March 17, 2004
Reviewer: Old, but young at SQL from Silicon Valley
If I make a PreparedStatement with no bind variables, but still execute it as a
prepared statement, is this exactly the same as a Statement, or will the
PreparedStatement's procedures et al be nuked when the PreparedStatement is
closed (where the Statement's procedure et al is cached in the shared pool)?
Followup:
The prepared statement, when closed, will be just like a closed statement.
Rating: 5
Thanks March 17, 2004
Reviewer: Old, but young at SQL from Silicon Valley
Running out of memory due to inability to flush a cache seems like a serious
Oracle bug.  I'd submit it if I knew how.

Thanks much for the education.  It sounds like the way the customer used my
program in testing aggravated the problem, that the cache would have aged out
most queries in production.  With the fixes, using PreparedStatements for the
voluminous queries, there should be no problems.

Long term, I'll subclass PreparedStatement to handle errors better and
systematically and track the number of open statement, to support keeping
connections open.  Or go to Pl/Sql  (I'll surf this site for a book req.)

Thanks MUCH- your fast responses were a godsend on this one.
-obyas
Rating: 4
How long does it take for something to age out of the cache? March 21, 2004
Reviewer: Old, but young at SQL from Silicon Valley
How long does it take for a query to age out of the cache?

(I worked 70 hours last week, and thee's a ton of other things to do- I don't
want to take the extra time to change 20 complex queries into
PreparedStatements- each is used once per hour (most of the time would be in
testing.)  The changes I already made reduced the number of Statements by a
factor of 100 so far, but my boss is afraid there'll be pressure to change all
of them. )
Followup:
that is like asking how long does it take ice to melt or paint to dry.

it depends.

they may never age out.
they may age out really fast.
they may age out pretty soon.
they may age out after a long time.
Rating: 1
March 22, 2004
Reviewer: Old, but young at SQL from Silicon Valley
Can you give me any sense of average times,
or the algorithm used to determine leaving the queue,
or how to use the output of
   select * from v$sgastat
to determine this?  The customer wants a guarantee that it won't happen again
(but wants it shipped this week, with other tasks, as well.)

------
CREATE table tmp_DS AS
  (SELECT * FROM DS WHERE ts BETWEEN ? and ? )
 ORA-01027: bind variables not allowed for data definition operations

Interesting.  I suppose I could make a temporary table, but the values in there,
and use its values for this query...  I can see why you recommend PL/Sql
Followup:
there is no way to gauge this.

depends on the size of your shared pool
which subpool your statement hashes to
what other statements just happen to get assigned to the same place.
etc....

DDL btw is not in the shared pool, therefore, bind variable = not used for DDL
as it is not shareable.
Rating: 3
One more, sigh March 22, 2004
Reviewer: Old, but young at SQL from Silicon Valley
Okay, I wrote 3 subclassed which delegate to
  Connection, Statement and PrepStatement
For PreparedStatement I also make an array of the
parameters that come in so if an exception happens,
I can print out the query and paste it into SQLPlus.

Here's what my routine produces:

Error: prep.executeUpdate() with query
  INSERT INTO LastDataFeed
          (t1, s1, t1p, s1p, t2, s2, periodMin, curtime, num)
   values (?, ?,  ?, ?,  ?, ?,  ?, ?, ?)
  INSERT INTO LastDataFeed
          (t1, s1, t1p, s1p, t2, s2, periodMin, curtime, num)
   values (1069804800000, '2003-11-26 00:00:00.000',  1069805100000, '2003-11-26
00:05:00.000',  1070496000000, '00:00:00',  5, '23:28:44', 0);
java.sql.SQLException: ORA-01006: bind variable does not exist

And when I paste it into SQLPlus, it works!

Hmmm, if in my code I respond to the exception by calling
   prep.executeUpdate(failedQueryString);
I get the same error.  But if I make a Statement and call
   stmt.executeUpdate(failedQueryString);
IT WORKS!

For completeness, the table is:
 Name        Type
-------------------
 T1         NUMBER
 S1         VARCHAR2(25)
 T1P        NUMBER
 S1P        VARCHAR2(25)
 T2         NUMBER
 S2         VARCHAR2(25)
 PERIODMIN  NUMBER
 CURTIME    VARCHAR2(20)
 NUM        NUMBER(9)

(I did a search on ORA-01006, but the 3 results were just for Pl/Sql...
Maybe I should just hack- if the prepStatement doesn't work, maybe I should
submit the mfg'd query as a Statement, sigh...)
-obyas
Followup:
that means your subclass isn't doing the binding right?

java.sql.SQLException: ORA-01006: bind variable does not exist

you are "binding a variable that doesn't exist".. doesn't matter if you can
print a pretty string, you are not calling the bind routine with the proper
inputs.
Rating: 1
But what does that mean? March 22, 2004
Reviewer: Old, but young at SQL from Silicon Valley
Does this mean one of the field names is wrong?
If that were true the string produced out of the query+args would not produce
errors as well.

I think the problem was a combination of incomplete docs and presumtuousness on
my part.  I was also using the PreparedStatement as a Statement-- it inherits
Statement's
  executeUpdate(queryString)
routine, so I assumed it would execute a query (with no variables) without
disturbing the prepared-query.  I guess the queryString overwrote the earlier
one that had the '?'s, and it was complaining that it couldn't bind the vars I
gave it to the string with no '?'s...

'My bad.
Thanks,
-obyas
Followup:
No, it means you have a bug in the code you layered on top of the code.
Rating: 3
New, related problem March 30, 2004
Reviewer: Old, but young at SQL from Silicon Valley
Maybe I didn't explain the previous one well.  Executing a statement using a
PreparedStatement object was nuking the PreparedStatement.
   1 PreparedStatement p = con.prepareStatement("select x from foo where
bar=?");
   2 ResultSet rs = p.executeQuery("select count(*) from foo");
    ...
   3 ResultSet rs = p.executeQuery();
throws an error.
Making a separate Statement object for executing line 2 allows line 3 to
succeed.
------

The code also makes temporary views that focus on different aspects of an hour
of data, then queries use these views.  The view name changes, to avoid
multithreading problems.  My guess is that even preparedStatement queries won't
be reused from hour to hour because each references a different viewname.  True?

One solution would be to pool and reuse the view names.
Thanks,
Obyas
March 23, 2004 play -- Thanks for the question regarding "ARE INDEXES USEFUL FOR DELETES", version 8.1.7
You Asked
Tom

1.I have a delete statement which has a where clause , I will be deleing 30% of
the rows in the table using this where query. The columns in the where clause
have a composite index on them.

Now is it beneficial to have indexes to do selective deletes like the one
described above, where you have a where clause.

Because , it is commonly believed that indexes are not good for inserts and
deletes.

2.Further, what does the time format 00:00:00:00 mean.
example

00:01:128:34

3.when I see the raw trace file contents , I see a lot of cursors but I did not
issue so many sql statements, what do all these cursors refer to.

4.

This is the output of a delete statement, where in I forced the use of a local
non unique index using a hint.

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      2     34.98     114.33      25191      21135     214862      142117
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3     34.98     114.34      25191      21135     214862      142117

=====================
PARSING IN CURSOR #1 len=308 dep=0 uid=974 oct=7 lid=974 tim=901741764
hv=4159339775 ad='8d490908'
PARSE #1:c=0,e=5,p=4,cr=10,cu=0,mis=1,r=0,dep=0,og=4,tim=901741764
*** 2001-08-03 12:32:46.568
=====================

without using an index the output is

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
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2     52.54     126.18      38033      33109    1056178      142117

Do you think it is beneficial for me to force the use of the compostie index.

That you very much for you patience.
and we said...
1) if you are deleting about 30% of the rows -- I would say you have a very
NON-selective delete.  In this case, an index would probably slow you down.
Anytime you are accessing more then 10-20% of the data in the table -- an index
will be of questionable use (but it depends).

In this case, if there are stats on the table, the optimizer will probably just
full scan for the delete (rule based optimizer will always pick an index if it
can).

To answer whether indexes are useful for inserts and deletes -- the answer in
both cases is DEFINTELY YES...

for deletes it is straightforward -- indexes are as useful for deletes as they
are for selects -- think about it.  A delete is just a select that deletes the
rows as it finds it.  If a query takes 5 hours to run as a select -- it will
take longer then 5 hours as a delete.  If you add an index that makes that query
run in 1/1000 of a second, that delete will speed up as well. (eg: "delete from
one_million_row_table where x = 5"  where X is not indexed.  Then add "create
unique index x_idx on one_million_row_table(x)" and do the delete again -- it'll
be much faster)

for inserts -- maybe its not so straightforward.  Consider however these two
cases:

a) a table with a primary /unique constraint.  There are indexes on those
columns that are used to EFFICIENTLY and quickly enforce the constraint.
Without those indexes, each insert would have to full scan the entire table --
slow.....

b) an insert into "select * from ....." type of query.

The bottom line is -- indexes should be used where appropriate.  Over indexing =
bad.  Underindexing = bad.  You index when and where you must to improve
performance.

2) I've not seen 00:00:00:00, 00:00:00.00  yes....

it is hours:minutes:seconds.hundredths_of_secs

I've seen it go over 60 seconds like that on various platforms....

3) without having a single example, i'd have to guess that its recursive sql we
do on your behalf to parse queries, verify access to objects, allocate space,
etc...

Also, sqlplus does lots of its own stuff in the background as well.

4) In your case, with your specific delete, the hint helped (marginally).  If
you change the delete to go after different data -- or run it next month when
the data has changed -- you might not see the same effect.

The question to you is -- is the job the optimizer doing good enough or do you
need that extra 12 seconds.  Don't forget to DROP that local non-unique index
you are forcing the for the delete (if you created it for the delete) as you are
penalizing the other delete by forcing it to maintain it!
Review & Followup

Rating: 1
How to speed up a simple delete? March 23, 2004
Reviewer: Randy from Sunnyvale, CA
I have a simple table with 1.3 Million rows and a single index, TIME, a Unix
time (milliseconds since 1970).  Deleting the later half of it took about 30
minutes, (our disk is connected with USB2...)

Shouldn't it be faster?  Can't it delete half of it and just keep half the
index?

New data is constantly streaming in at later times, but the earlier hours never
change, except when I delete the oldest hours.

Is there some way I can speed this up?  Most of my queries are on hour
boundaries, but not all.  Can I partition the table by hour?  Can this be done
on a rule, eg Partition By TIME/3600000, or do I need to list each partition,
and keep adding new partitions each hour?

Is there a tutorial on table partitioning somewhere, or a FAQ, or a particular
book, or should I seek a DBA's help?
Thanks,
-r
Followup:
"half of the index" hmm.

an index is a complex data structure -- no such thing as "half" of an index.

if you have a database on USB2, that isn't going to be the "fastest" thing on
the planet.  especially if your logs et.al. are on it and you have a single
device.

You would want to partition by time to roll data in and out -- definitely.  you
would partition the table into 12 partitions, and either list or range partition
by hour (you might need to add an HOUR column to the table to facilitate this)

partitioning is fully documented, however yes -- you would want to involve your
DBA's.
March 23, 2004 marc -- Thanks for the question regarding "User Call", version ORA 817
You Asked
Dear Tom,

Could you tell me what is exactly a USER CALL (in v$sysstat)
Thanks for your help,

Marc
and we said...
The number of user calls to the Oracle kernel. The total number
of calls to the Kernel should be reduced if possible.  The
performance Tuning Guide indicates how to setup  array
processing to reduce the number of calls to Oracle.   Also,
reduce the number of times you parse statements.  Instead of
coding:

   for( i = 0; i < 10; i++ )
   {
      parse
      open
      bind
      execute
      close
   }

code

  parse
  open
  for ( i = 0; i < 10; i ++ )
  {
    bind
    execute
  }
  close

for example.
Review & Followup

Rating: 4
USER CALLS March 23, 2004
Reviewer: Anthony Reddy Thumma from KL, MALAYSIA
Hai Tom,

My previous instance run for 30 days, when i select
user calls from v$sysstat the count is 49,o91,622.

My current instance is running since 10 days, but when i
select the user calls from v$sysstat the count is 63,809,327.

  Lot of calls to oracle during this period. we deployed new application source
code 12 days ago. The application is a java program with SQL statements.

Can you explain, what could be the reason for lot of user calls to oracle these
days. The number of Transactions per day is almost the same.

thanks in advance.
Followup:
you are doing more calls to the database (sort of obvious).  different code,
different number of calls, not surprising.

there are an INFINITE number of reasons why the number of calls could have
increased.  new code = new execution profiles.
March 23, 2004 jimmy -- Thanks for the question regarding "how many days in a year", version
You Asked
How could i count how many days in a
year by SQL query.
ie. this year 2000 is a leap year
it has 366 days?
and we said...
truncate the date to the YEAR boundary, add 12 months and
subtract from that the date trunced to the year again:

ops$tkyte@8.0> select add_months(trunc(sysdate,'year'), 12) -
trunc(sysdate,'year') from dual
  2  /

ADD_MONTHS(TRUNC(SYSDATE,'YEAR'),12)-TRUNC(SYSDATE,'YEAR')
----------------------------------------------------------
                                                       366

ops$tkyte@8.0> select
add_months(trunc(to_date('01-jan-1999'),'year'), 12) -
trunc(to_date('01-jan-1999'),'year') from dual
  2  /

ADD_MONTHS(TRUNC(TO_DATE('01-JAN-1999'),'YEAR'),12)-TRUNC(TO_DATE
('01-JAN-1999'),'YEAR')
-----------------------------------------------------------------
-----------------------

                    365
Review & Followup

Rating: 5
Determination of Leap Year. October 02, 2002
Reviewer: Steve Booth from Waukesha, WI USA
I needed to determine whether a year was a leap year and was able to
successfully use his code snippet.
Followup:
even easier might be

to_char( last_day( to_date('02' || :year,'mmyyyy') ), 'dd' )

if that is 28, no leap year, 29 leap year.
Rating: 5
lesser code.. October 02, 2002
Reviewer: SB from CA
mod(:year, 4)
If the result is 0 then it's a leap year.
Rating: 3
Leap year is not so easy October 02, 2002
Reviewer: Rick from East Haven, CT
>
  mod(:year, 4)
  -- If the result is 0 then it's a leap year.
>
This is wrong!

Years divisible by 4 are leap years
   BUT
Years divisible by 100 are NOT leap years
   BUT
Years divisible by 400 ARE leap years

1900 was not a leap year, 2000 was a leap year
Followup:
Thanks, you saved me the effort of writing the query to show them wrong ;)
Rating: 5
But look out for the 'Gotcha!' October 02, 2002
Reviewer: Phil Singer from Brighton, MI USA
In terms of using Oracle functions to answer the question of
how many calendar days in a calendar year, this cannot be
questioned.  However, this brings back memories of my prior
life of doing financial calculations.  In the USA at least,
these questions are much more difficult.  For example,
depending on State law, and when the payments of a load are
made, a year can turn out to have anywhere from 358 to 367 days.  So, if the
question is not really about leap years,
but really about days to collect interest, I urge you to
contact your compliance officer.
Rating: 4
Year 2000 gives 366 days October 03, 2002
Reviewer: Dawar from CA , USA
Hello Tom,

It is interested when I used your defined query to the
year 2000. Output always is 366 days.
Rest of the years are fine. Even year 1900 gives 365 days.
why is that?
select add_months(trunc(to_date('01-jan-2000'),'year'), 12) -
trunc(to_date('01-jan-2000'),'year') from dual;

366
Followup:
  1* select last_day( to_date( '02-2000', 'mm-yyyy' )) from dual
scott@ORA920.LOCALHOST> /

LAST_DAY(
---------
29-FEB-00

2000 was a LEAP YEAR, 1900 was not, that is why.
Rating: 5
Sorry and thankx for the clarification !!! October 03, 2002
Reviewer: SB from CA
Rating: 5
RE: Year 2000 gives 366 days (dawar) October 03, 2002
Reviewer: Mark A. Williams from Indianapolis, IN USA
Dawar:

Because 2000 was a leap year, 366 is the correct NOD...

HTH,

Mark
Rating: 5
Leap Year Error. October 22, 2002
Reviewer: SSN. from Madurai, India.
Hi Tom,

last_day(to_date('01-FEB-1500','dd-mon-yyyy')) returns 29-FEB-1500. Is it
correct ? If not, then why it is like that?

I have tested it in 8.1.7 under HP-UX 11.0.

Thanks.
Followup:
Rating: 3
1500 was not a leap year October 22, 2002
Reviewer: A P Clarke from London, England
Tom

I was able to reproduce SBB's problem with 1500:

SQL> select last_day(to_date('01-FEB-1500','dd-mon-yyyy')) from dual;

LAST_DAY(
---------
29-FEB-00

This is wrong because 1500 was not a leap year - divisble by 100 but not 400.
This is also a problem with 1400, etc.  It isn't a problem with 1700, 1800 or
1900.  Nor does it apply to 2500, 2600 or 2900.

Yes, I have too much time on my hands - it's lunchtime and it's raining - but
this does imply that Oracle's date generation lacks a consistent algorithm.
Another good reason for hoping that a wormhole in the spacetime continuum
doesn't open up and send us back to the Middle Ages :P

Cheers, APC
Followup:
$ cal 02 1500
   February 1500
 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

and see below...
Rating: 3
Leap year October 22, 2002
Reviewer: Michel Cadot from France
Hi,

1200 is a leap year
1300 is a leap year
1400 is a leap year
1500 is a leap year
1600 is a leap year
1700 is not a leap year
1800 is not a leap year
1900 is not a leap year
2000 is a leap year
...

Why ?

Because in Gregorian calendar xx00 is a leap year if and only if xx is divisible
by 4.
But what about 1300, 1400 and 1500?
Gregorian calendar started during the night of 4 october 1582 to 15 october
1582. Before there was the Julian calendar in which all of the years divisible
by 4 are leaps.

select to_char(to_date('04/10/1582','DD/MM/YYYY')+1,'DD/MM/YYYY') from dual;
TO_CHAR(TO
----------
15/10/1582

Regards
Rating: 3
1500 October 22, 2002
Reviewer: Helen from UK
Up until 1582 any year divisibly by 4 qualified as a leap year.  After this Pope
Gregory XIII added that centennial years should also be divisible by 400 to
qualify.
Rating: 5
The metric system October 22, 2002
Reviewer: Connor McDonald from UK
I look forward to the day when we go metric with this...

100 mins in an hour
10 hours in a day
10 days in a month
10 months in a year

Simple!

:-)
Connor
Rating: 4
Leap Year March 22, 2004
Reviewer: A Reader from China
I understood that we can use Last_Day to find out leap year but my situation is
i will get from and to date as input parameters eg:

15/07/2003 from date
14/07/2004 to date

So the month feb falls in 2004 so the select should fetch Last_Day of feb-2004

another example

02/02/2004 from date
01/02/2005 to date

here i need to take the later one and find out whether it falls in leap year!

How can i do this?
Followup:
I don't understand what you need here?  "here i need to take the later one"
later one what?  find out if "what" falls in a leap year?

if you have a date and want to know "is it a leap year", then:

to_char( last_day( add_months( trunc(DATE,'y'), 1 ) ), 'dd' )

will do that -- it'll return 28 or 29
Rating: 5
Life, the Universe & Oracle March 22, 2004
Reviewer: DN from London, England
This site is fantastic for learning useful work-related stuff, but I never
thought the day would come when I'd be reading AskTom for info about Popes and
history and gregorians - and wormholes!!!  Bring on Who Wants To Be a
Millionaire!
Rating: 5
March 22, 2004
Reviewer: A reader
<Quote>

$ cal 02 1500

</Quote>

Can we know the script cal.sql you are running ??? Its Just excellent.
Followup:
it was the unix prompt, cal is a unix command.

but...
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:14741686777707#14796840665338
Rating: 5
An interesting observation about the Gregorian Calendar March 22, 2004
Reviewer: Robert Shepard from Denver, CO USA
I've been fascinated with the whole calendar reform issue ever since I was a
kid, so thought I'd share this little historical oddity with your readers.

select * from v$version;

Oracle8i Enterprise Edition Release 8.1.7.4.0 - 64bit 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

select to_date('10-4-1582','MM-DD-YYYY') from dual;

   10/4/1582

select to_date('10-5-1582','MM-DD-YYYY') from dual;

   10/15/1582

select to_date('10-10-1582','MM-DD-YYYY') from dual;

   10/15/1582

select to_date('10-15-1582','MM-DD-YYYY') from dual

   10/15/1582

select add_months(trunc(to_date('01-jan-1582'),'year'), 12) -
   trunc(to_date('01-jan-1582'),'year') from dual;

   355

None of the above should be surprising.  When Pope Gregory XIII adopted the
Gregorian Calendar in 1582, 10 days were dropped from October.  Thus, the day
after October 4 became October 15.  Oracle reflects this.

So far, so good.  But now, try this from a Solaris system prompt:

   csh> uname -a

      SunOS kaos 5.8 Generic_117000-01 sun4u sparc SUNW,Ultra-4

   csh> cal 10 1582

      October 1582
    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

Hmmmm.  This looks like a normal 31-day month to me.  What gives?

Well, my interest in calendars over the years prompted me to try this next:

   csh> cal 09 1752

      September 1752
    S  M Tu  W Th  F  S
          1  2 14 15 16
   17 18 19 20 21 22 23
   24 25 26 27 28 29 30

Aha!

Back in Oracle:

select add_months(trunc(to_date('01-jan-1752'),'year'), 12) -
   trunc(to_date('01-jan-1752'),'year') from dual

   366

So, to Oracle, 1752 is a perfectly ordinary (leap) year.

Why the difference?  Is Oracle "wrong"?  Or is the problem with Solaris?

Well, neither, as it turns out.  The issue is actually one of politics.  When
the Pope adopted the Gregorian calendar, Catholic countries tended to make the
switch pretty quickly.  Protestant countries, however, didn't like the Pope
telling them what to do, so they balked.

The United Kingdom (along with her American colonies), finally made the switch
in 1752.  Thus, Solaris reflects Anglo-American history, whereas Oracle goes
along with the original Gregorian calendar.

As a side note, other countries took even longer to make the switch, in
particular those with an Orthodox Christian background.  For instance, the
Russians didn't make the switch until around 1918, which is why the anniversary
of the "October Revolution" (on the old Julian calendar) currently falls in
November (on the Gregorian calendar).  Because the changeover was so recent,
dates in Russian history frequently are referred to as "Old Style" versus "New
Style".

At any rate, this disagreement as to when the Gregorian calendar actually began
could conceivably lead to some confusion for anyone using Oracle to store
historical dates, if they're performing date arithmetic on them.
Rating: 4
Just use Julian dates for a standard that works everywhere! Oh, wait.... March 22, 2004
Reviewer: Mark J. Bobak from Ann Arbor, MI
Lest one thinks that Julian dates are the way around these
date issues (surely, there's no variance in them, right?),
yes, there's a well-known, outstanding Julian date bug w/
Oracle. (Bug #106242)  See Steve Adams' write-up at:
http://www.ixora.com.au/notes/julian_zero.htm
for the full story! ;-)
Rating: 4
Thanks for the reply March 22, 2004
Reviewer: A Reader from China
Dear Tom,

   Lets assume start date is 15/07/2003 and the end is date 14/07/2004.Here
first thing we have to find whether feb falls inbetween this period if yes then
which year it belongs to. In this above example yes it is in 2004.So
we need to find 2004 is leap or not.
   The second example the starts is 02/02/2004 and the end date is
01/03/2005.Here the start falls in feb 2004 and end date also crosses feb 2005.
So we need to find both years 2004 and 2005 whether leap or not.

Thanks
Followup:
ops$tkyte@ORA9IR2> variable x varchar2(20);
ops$tkyte@ORA9IR2> variable y varchar2(20)
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec :x := '15/07/2003';

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec :y := '14/07/2004';

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select last_day( add_months( trunc(
to_date(:x,'dd/mm/yyyy'),'mm'), r ) )
  2    from
  3  (select rownum-1 r
  4     from all_objects
  5    where rownum <=
  6          ceil(months_between(last_day(to_date(:y,'dd/mm/yyyy')),
  7               trunc(to_date(:x,'dd/mm/yyyy'),'mm')))
  8  )
  9  where to_char( add_months( trunc( to_date(:x,'dd/mm/yyyy'),'mm'), r ), 'mm'
) = '02'
 10  /

LAST_DAY(
---------
29-FEB-04

ops$tkyte@ORA9IR2>
Rating: 5
Thanks a lot March 22, 2004
Reviewer: Muhammad Ibrahim from China
Dear Tom,

   You are THE MAN. Thanks a lot.

Regards,
Ibrahim.
Rating: 4
Function for DAYS360? March 23, 2004
Reviewer: Regee Chacko from Chennai, India
I was looking for the logic to create a function which will give me the result
of DAYS360  function in Microsoft Excel.
Can any of the Oracle Date functions be used for this purpose?
Followup:
you would sort of have to explain what days360 does/means.  i don't do
"spreadsheets"
March 23, 2004 -- Thanks for the question regarding "Your favorite 10g features", version 9.0.2.3
You Asked
I have surfed OTN for 10g information, but believe me it’s a lot of
documentation :)

If possible can you just name few important features that you loved most in
Oracle 10g (as a product designer or beta tester)

It will help me to read proper documentation.
and we said...
well, the docs for 10g are not there yet but......

a) ASM automatic storage management has caught my eye
b) ADDM -- the manageability features look awesome (automatic database
diagnostic monitor)
c) new sql features are always cool -- the model clause is really cool
Review & Followup

Rating: 5
When will the database be available for playing September 09, 2003
Reviewer: Tarry Singh from Netherlands
Any ideas when would the database be available for download and also the
documentation?
Followup:
sometime this calendar year, when it is done :)
Rating: 5
Sign up w/ OTN to be notified when 10g is available...... September 09, 2003
Reviewer: Mark J. Bobak from Ann Arbor, MI
Tarry,

If you go to
http://otn.oracle.com/
you can sign up to be
notified when 10g is available for download.  I don't have
the exact URL handy, but I know you can, cause I just did it
today.

-Mark
Rating: 4
We forgetting something...? September 09, 2003
Reviewer: Robert from CT
>> can you just name few important features that you loved >> most in Oracle 10g
(as a product designer or beta tester)

How'bout that old gal...Ms Marvel ?
Rating: 5
Thnaks September 09, 2003
Reviewer: A reader
Thanks for your answer.
Rating: 3
September 09, 2003
Reviewer: Pablo Rovedo from Argentina
Tom:

       Could you show us some example using the new sql model clause?. i'm
waiting for the official docs to read something about it. Have you already
upgraded your site to 10g?

Regards
Pablo Rovedo
Followup:
umm, no, i haven't upgraded to 10g.

you do not run production systems on beta software.  10g was just announced, it
is in beta.  I will not be using it in production until -- well -- until it is
in production.  And even then, i'll use 10g here when it is convienent or
necessary to upgrade.
Rating: 3
Your favourate 10g features - ASM September 10, 2003
Reviewer: A reader
With ASM,DBAs no longer need to manage files and drives individually. Instead,
disk groups can be created consistingof disks and their assigned files.
Essentialy, the combination of OMF and  ASM eliminates the need for a DBA to
specify the file name and location for the physical database files when creating
a new database, as well as in other database operations - you simply identify
the destination disk group, and oracle takes care of the rest.  ASM can also be
extended to support other administrative procedurees, including backup/recovery
and disk management.
Rating: 5
presentation September 11, 2003
Reviewer: Dave
just wondering how your sql model clause presentation was received on Monday and
how your book signing went as well
Followup:
i thought it went well, but it is hard to tell from my side :)
Rating: 5
re:presentation September 11, 2003
Reviewer: A reader
I thought the presentation was great - Tom's explanation
was very clear . Tom, I felt that you could have perhaps
given us some numbers as to compare different
equivalent queries (one with and one without the
model clause.) It would have perhaps made it a little
more clear as to the performance impact of this
clause.

I also got a chance to talk to Tom and get his
autograph on my copy of his latest book - it was a
privilege indeed. And I think Tom has done a wonderful
job in his latest book (from whatever I have read so far)

Menon:)

Menon:)
Rating: 2
I am jealous of you Mr. Menon!!! :) September 12, 2003
Reviewer: A reader
Rating: 3
September 12, 2003
Reviewer: Ramasamy from NC
I have surfed OTN for 10g information, but believe me it’s a lot of
documentation :)

I couldn't find any 10G documentation pdf in the otn website.It will be really
great if you could type the link.
Followup:
as stated:

well, the docs for 10g are not there yet but......

10g is announced, not shipping.
Rating: 5
September 12, 2003
Reviewer: Mirjana from Barcelona
Can you put a link to sql model clause documentation (or articles),please?
Followup:

http://www.oracle.com/oracleworld/sanfrancisco/conference/

Rating: 5
ADDM and SQL Tuning Advisor September 12, 2003
Reviewer: Mark from KC
Tom,  I just got your new book.  Very nice.

I was wondering if you could give a short overview of ADDM and SQL Tuning
Advisor in 10G.  I was looking at
http://technet.oracle.com/products/database/oracle10g/pdf/OW_General_OracleDatabase10G_Revolution_10R1_081903.pdf
How does this work?

Thanks
Followup:
think of it like stats pack PLUS the authors of stats pack inside the database.
they are codifying their knowledge and instead of just reporting back "whats
up", they are saying "whats up and here is what to do about it"
Rating: 5
Thanks a lot!!!!! September 12, 2003
Reviewer: Mirjana from Barcelona
Rating: 5
ow2003 whitepapers September 15, 2003
Reviewer: A reader

http://otn.oracle.com/ow2003/content.html

Followup:
thanks!!
Rating: 4
9i release 3 cancelled? September 15, 2003
Reviewer: juancarlosreyesp@yahoo.com
Hi Tom, now that oracle presented 10g, Oracle9iR3, is going ot be cancelled, I
remember it was going to have an c compilator included, etc.

Thanks :)
Followup:
9iR3 was never announced or talked about as far as I know.

the last time we had a c compiler was for the mainframe.  rather then port
Oracle to a "mainframe" language, we wrote a C compiler.  but that was years
ago....
Rating: 5
September 15, 2003
Reviewer: A reader
Rating: 4
about automatic SGA memory management September 15, 2003
Reviewer: zhou yuwen from CHINA
I have an oracle10g beta for Linux,but I can't find parameter SGA_TARGET,so I
can't use automatic SGA memory management feature. who can I enable this new
feature?

thanks Tom
Followup:
beta 1 didn't have it

beta 2 does

do you have beta2?
Rating: 4
about automatic SGA memory management September 15, 2003
Reviewer: zhou yuwen from CHINA
I have an oracle10g beta for Linux,but I can't find parameter SGA_TARGET,so I
can't use automatic SGA memory management feature. How can I enable this new
feature?

thanks Tom
Rating: 5
About automatic SGA memory management September 16, 2003
Reviewer: zhou yuwen from CHINA
thanks Tom.

I have only oracle10g beta1.
Rating: 5
:) Thanks September 16, 2003
Reviewer: A reader
I read this somewhere, but I don't remember where was that article.
Rating: 4
SQL Model Clause September 19, 2003
Reviewer: Suvarna Patki from Warrington, UK
Hi Tom,

 I was trying to access following link to read more on SQL Model clause. But it
leads to nowhere :(
http://otn.oracle.com/products/bi/pdf/10gr1_twp_bi_dw_sqlmodel.pdf
 Please help.
Followup:
i just clicked on it -- worked for me?

(not sure where you got the link from in the first place, that and I don't
manage all of OTN :)
Rating: 5
September 24, 2003
Reviewer: A reader from orlando,FL
Tom,
pls correct my understanding:
Oracle 10g grid computing deployment implies using RAC as its base component?
is'nt it?

Thanks as always..
Followup:
that is part of the picture, yes - an optional component.

there is no "base" component.  it is lots of pieces.
Rating: 4
10g beta September 25, 2003
Reviewer: Nathan from Berkshire , United Kingdom
Tom,

Why dont you provide your readers with a Beta copy :) .

Regards
Nathan
Rating: 5
September 26, 2003
Reviewer: A reader from orlando,FL
Tom,
a) Does sql model clause require 10g  OLAP option?
b) Is the OLAP option mdb a superset of the standard rdbms?

Thanks as always
Followup:
a) it won't be OLAP option no.  but the feature/option set is not yet set in
stone

b) don't know what you mean
Rating: 4
Server on one CD October 01, 2003
Reviewer: robert from CT
I just heard an interview with Andy Mendelsohn and he said
the 10g server software will be on ONE CD....wow ! now that's progress !
Rating: 4
cd or dvd? October 03, 2003
Reviewer: subbu from mn, usa
Followup:
cd
Rating: 5
10g New Features October 03, 2003
Reviewer: A reader
Guys, there is a sample chapter from the 10g New Features book -- with
commentary by J. Lewis ! This is an Oracle Press book.
http://otn.oracle.com/oramag/webcolumns/2003/techarticles/new_features_preview.pdf
Lewis' note is very funny -- what do you think, Tom ?

<q>
Flushing the Buffer Cache

Prior to Oracle Database 10 g, the only way to flush the database buffer cache
was to
shut down the database and restart it. This was perhaps not the most graceful
way of
performing this activity because it required shutting down applications and
disconnecting
users, creating all sorts of mayhem (not that flushing the buffer cache in and
of itself
can’t cause some short-term mayhem of its own!).
Oracle Database 10 gnow allows you to flush the database buffer cache with the
alter
system command using the flush buffer_cache parameter, as shown in this example:
Alter system flush buffer_cache;

NOTE

I am not sure why you would want to flush the buffer cache. If you
can think of a good reason why, let me know!
</q>

;)))))
Followup:
there are two times when it comes into play

a) you are using raw or unbuffered IO (forcedirectio on the mount option in
solaris for example) and you want to time your disk speed.  This is very rare.

b) you mistakenly believe that you need to benchmark with an empty buffer cache
to get "real world" numbers.  This "thought" occurs because your benchmark
contains a single query with a single set of inputs and you cannot be bothered
to simulate a real world workload.  Problem is -- in real world buffer cache is
neither 100% empty, not 100% full so problem is with benchmark, not with what is
cached or not.

Note: in 9i, you could "alter tablespace offline/online" to "flush" the buffer
cache of blocks relating to that tablespace.

This flushing command is mostly (IMO) to make people "feel better".

Unless someone can come up with a good reason for it....  besides "for
benchmarking" since benchmarking should simulate reality and reality never
starts with an empty buffer cache.
Rating: 5
Hey! It was possible at least in 9i without shutting down... October 04, 2003
Reviewer: Piotr Jarmuz from Poznan, Poland
Hello

<q>
Prior to Oracle Database 10 g, the only way to flush the database buffer cache
was to
shut down the database and restart it.
</q>

p_jarmuz@test> create table test tablespace example as select * from
all_objects;

p_jarmuz@test> create index test_idx on test(object_name);

p_jarmuz@test> set autotrace traceonly
p_jarmuz@test> set timing on

p_jarmuz@test> select * from test where object_name < 'a';

23838 rows selected.

Elapsed: 00:00:02.56

Execution Plan
----------------------------------------------------------
          0
SELECT STATEMENT Optimizer=CHOOSE

          1                  0
  TABLE ACCESS (BY INDEX ROWID) OF 'TEST'

          2                  1
    INDEX (RANGE SCAN) OF 'TEST_IDX' (NON-UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      18270  consistent gets
        327  physical reads
          0  redo size
    1575443  bytes sent via SQL*Net to client
      17978  bytes received via SQL*Net from client
       1591  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      23838  rows processed

So we see 327 physical reads.

One more time to "warm up" the buffer cache

p_jarmuz@test> select * from test where object_name < 'a';

23838 rows selected.

Elapsed: 00:00:02.48

Execution Plan
----------------------------------------------------------
          0
SELECT STATEMENT Optimizer=CHOOSE

          1                  0
  TABLE ACCESS (BY INDEX ROWID) OF 'TEST'

          2                  1
    INDEX (RANGE SCAN) OF 'TEST_IDX' (NON-UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      18270  consistent gets
          0  physical reads
          0  redo size
    1575443  bytes sent via SQL*Net to client
      17978  bytes received via SQL*Net from client
       1591  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      23838  rows processed

See this time ZERO physical reads all blocks were fetched from buffer cache...

Now let's set tablespace offline and back online again...

p_jarmuz@test> alter tablespace example offline;

Tablespace altered.

Elapsed: 00:00:00.13

p_jarmuz@test> alter tablespace example online;

Tablespace altered.

Elapsed: 00:00:00.06

And one more time our query:

p_jarmuz@DEVDB.ASCO.DE> select * from test where object_name < 'a';

23838 rows selected.

Elapsed: 00:00:02.60

Execution Plan
----------------------------------------------------------
          0
SELECT STATEMENT Optimizer=CHOOSE

          1                  0
  TABLE ACCESS (BY INDEX ROWID) OF 'TEST'

          2                  1
    INDEX (RANGE SCAN) OF 'TEST_IDX' (NON-UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      18270  consistent gets
        327  physical reads
          0  redo size
    1575443  bytes sent via SQL*Net to client
      17978  bytes received via SQL*Net from client
       1591  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      23838  rows processed

This time all blocks were read from disk again or really from OS buffer cache as
I did not flushed it and elapsed time also proves it
It is basically no different. I flushed buffer cache elapsed time after the
flush would be much, much longer

I learnt "this technique" from Oracle's Magazine and Tom's article.

Regards

Piotr
Rating: 5
Oops... Tom was faster as always... :-) October 04, 2003
Reviewer: Piotr Jarmuz from Poznan, Poland
Regards

Piotr
Rating: 4
10G features October 07, 2003
Reviewer: Mark from KC
Tom,

Can talk about Tablespace Groups and why you would want to use them verses a
single large temp tablespace?

Thanks
Followup:
I'll punt as I haven't tried them out myself yet.  this is from the docs so far:

....
Using a tablespace group, rather than a single temporary tablespace, can
alleviate problems caused where one tablespace is inadequate to hold the results
of a sort, particularly on a table that has many partitions. A tablespace group
enables parallel execution servers in a single parallel operation to use
multiple temporary tablespaces.
.........

I don't necessarily see the benefit of the first one so much as the second point
-- to reduce contention and spread the work load out (note: it would be true of
multiple sessions all sorting at the same time as well)
Rating: 3
10g features October 09, 2003
Reviewer: apl
i am am oracle8i certifeid professional. i am preparing for upgradation  exam in
oracle9i.
Yesterday i read about oracle 10g features. in 10g tuning,backup and recovery,
cloning,performance monitoring.. all can do by the database. means there is
little role for dba.
i heard that this can make organisation profitable and mke the reource utlise
for other jobs. So my question is what will be our role. only in the application
dba side.means database design and query writing.
now i am thinking seriously, is it necessary for an upgrade. since the role of
dba is coming down.
Followup:
Have you (as a DBA) been asked recently to

a) managed smaller databases (or do they seem to be getting larger)

b) databases with less users (or do they seem to be getting larger)

c) databases with more uptime requirements (or do they seem to be saying "it's
ok for the db to be down whenever, we don't care")

d) databases with more applications (or have they been getting rid of
applications, going back to simplier times)

What I mean is -- are you being asked to to MORE or LESS over time?  If being
asked to do more (that is what I'm supposing people will be saying, it is what I
hear) -- are they increasing your staff?  Or are you being asked to do more with
what you have?  Do you want some weekends off occasionally?

Here is my take on it.  Some (many) things are easier to manage.  Its a relief.
We don't have to worry about that "unfixable 3rd party query that cannot be
changed" in 10g because of a thing called a sql profile.  We don't have to worry
about rebalancing our IO when given a new mount point (taking files offline,
moving them, bringing them back on).  We can concentrate on everything else you
need to do -- secure it, manage it, keep it up, patch it, test it, configure it
and yes -- tune it.  It is more self managing -- it is not going to fire you.
Rating: 5
How to answer management November 14, 2003
Reviewer: A reader
The more management hears about 10g, the more I (DBA) am hearing "with 10g, we
don't need DBA's. The database takes care of itself". How can I answer that?
Followup:
are you being told any of the following:

a) we'd like you to manage smaller databases (and less of them)
b) we'd like you to manage less applications running on these databases
c) we are going to start lowering the number of users you need to support that
use
   these applications
d) it is OK to be down or suffer from poor performance whenever.  thats not a
   problem.

OR, are you hearing:

a) your databases are going to grow (and you have more of them)
b) you'll have more apps running on them
c) your user base is going to double
d) if you let these databases go down for a minute, or they start running slow
   I want you in my office to explain why
E) OH, YEAH, YOU CANNOT HIRE ANYMORE PEOPLE TO HELP

I hear the second set of requirements, not the first.  We are treading water as
it is - we need a little help here.  Do you really like moving files around to
tune IO?  finding the high load sql and figuring out what index the developers
forgot to tell you about?
Rating: 3
November 19, 2003
Reviewer: Antony
Has the number of bugs come down in 10g ?

Almost every new feature of oracle has quite a few bugs associated with that
whether it is partitioning or materialized view or cursor_sharing or automatic
segment management or parallel execution ....So one hesitates before
implementing a new feature, even though some of them are quite beneficial
Rating: 5
FLASHBACK November 26, 2003
Reviewer: A reader
Hi Tom, one question I read there will be a fulldatabase flashback,
the question is
can you go to a full database flashback permanently,
this is like restoring a backup (if you didn't do ddls)?

I think this will be nice, if you didn't execute any ddl command you will can to
do a restore backup with a simple command, very useful for our restore needs.
Followup:
that is exactly what it is, yes.
Rating: 5
Thanks November 26, 2003
Reviewer: A reader
Rating: 3
but still missing ... November 27, 2003
Reviewer: Aino from Netherlands
I am interested in the pl/sql features. Looking forward to regular expressions
and improved collection manipulations.
But my most wanted feature of the db, the boolean datatype for a column, is
still missing :-((
Rating: 5
MMON December 22, 2003
Reviewer: Reader
Tom, is MMON a manadatory background process in 10g? If it dies, will it crash
the instance or will it alert? Thanks.
Followup:
they are part of as far as I know -- and when I killed them, they got restarted
-- so, if they die, they are restarted for you.
Rating: 5
Re: How to answer management December 22, 2003
Reviewer: A reader
What I'm hearing is "10g automatically takes care of everything that you are
doing so we won't need a DBA if we upgrade to 10g. The database is self tuning
and self administering."
Followup:
umm, yeah, right.

look upwards to "How to answer management"
Rating: 5
10g Stole My Job December 22, 2003
Reviewer: Mark A. Williams from Indianapolis, IN USA
Many interesting and useful features - but I have not yet seen:

prescient_mode = TRUE

I mean what features are seriously going to eliminate the need for a DBA?  Sure,
some things will be easier to do and require less "busy work", but will that
actually eliminate the need for a DBA?  I don't mean to sound funny about it,
but no one has yet told me about a new feature that has me worried about job
security.

insert into bit_bucket values(.02);

- Mark
Rating: 5
Re: 10g Stole My Job December 23, 2003
Reviewer: A reader
Well, you know that, and I know that, unfortunately some management doesn't know
that. They'll eliminate their DBA's and only find out later that they've gotten
themselves into a huge mess. OK by me, because then they can hire me on to dig
them out at big $$$. :-)
Followup:
i cannot imagine anyone would make staffing decisions based on something they've
read in a magazine, not seen for real.
Rating: 5
Data pump December 23, 2003
Reviewer: Reader
Tom, in 10g is data pump a replacement for the ever popular exp/imp? How is
expdb and impdp utilities differ from exp/imp utility? Thanks.
Followup:
data pump is the "next generation" yes.  the venerable and ancient exp/imp still
exist.

data pump can do things like

o parallelize
o direct path imports
o server to server transfer (look ma, no disk)

for example.
Rating: 5
Imagine December 23, 2003
Reviewer: Jim from Oregon
Tom,
Dilbert is alive and well.  Sometimes I have gotten an eerie feeling that Scott
Adams must have been spying on companies I have worked for.  There definately
are management types that will reduce head count because:

1. People are pluggable.
2. Oracle says they have automated and made their database easier to administer.
3. They lack the wisdom and understanding of the purpose of DBAs.
(eg they think they rebuild tables and indexes to get them into one extent;
fortunately Oracle says we don't need to do this anymore so we can get rid of
the DBA.)

I thank my lucky stars that at least my immediate boss is more enlightened!

By the way does Oracle make a mauve database?  I hear mauve databases are the
best; they use the most RAM.

Happy holidays to you and everyone who peruses your site.
Followup:
i'm fond of the red myself.  it goes much faster then the other colors, just
like the red cars.
Rating: 5
FLASHBACK December 23, 2003
Reviewer: A reader
For that kind of FLASHBACK how much undo we need... 1TB? :-)
Rating: 5
December 24, 2003
Reviewer: A reader
I don't think 1 tera is too much, If think you don't have clear the way
flashback works.
here I got an excelente article ;)
http://www.geocities.com/juancarlosreyesp/OracleAskTom.pdf
It should work like now works, but with all tables at once, and it will read
only the info it need, so the speed should be very similar than the one you got
in 9i when you do a flashback, or better.

Merry Chrismas, and thanks to Tom
and to every one.
My presente

  --------
 /:)/:) / |
------- | |
|  |  | |/|
|-----|/|/
|  |  | /
-------

I don't have too much time to get a better one.
Rating: 4
January 08, 2004
Reviewer: A reader
hi tom,

 we are on 8.1.7 release right now.

it looks like (announced) that 10g(9.3 ?) is going to be released soon(6 months
from now).

  should we wait for 10g and migrate from 8i --> 10g or we wait for 10g to get
stable, normally we wait 1 year to get
the softwere stable (patch and all.)  My other concern is that 10g verion is
9.3.x(correct??), so is 10g full relase or an incremental to 9i.  Should I
worry?

  if 10g is going to be around soon. we don't really want to move to 9i if
possible !! because right now 8i is stable .

 please give your advise.
Followup:
10g is going to probably be 10.1.0.2 (that is what it is now).

9iR2 is very stable as well.

It is hard to tell you what to do.  Depends lots on needs and goals.
Rating: 4
January 09, 2004
Reviewer: A reader
I know 9ir2 is stable.  A large issue with me is that 9i relase schedule is alot
like mirosoft's release cycles(To Fast).  I would go to 9ir2 today, but I do not
want to upgrade to 10g in a year.  It is an expesive propersition in man hours
to fully test and implement.  My goal is to have a release that is supported by
Oracle, No major bugs.  There are features in 10g that would be on my wishlist,
but now entirely needed.  My business needs for oracle 9 or 10 is none.  The
businesses people do not care.  This is an entirely technolgy reason.
Followup:
guess it will come down to

a) do you want to upgrade in the next 4 months.  9i is your most likely
candidate

b) do you want to upgrade 6 or more months from now, 10g will be something to
seriously consider

c) in between 4/6 months -- hard to say.
Rating: 5
Upgrade - Oracle 9 or 10 ? January 09, 2004
Reviewer: Timothy from PA, USA
The previous poster mentioned wanting a stable release supported by Oracle.

Well - if so - that pretty much would answer the question - no?

Oracle has listed 8.1.7 as being desupported as of 12-31-03.

So, if you want a database fully supported by Oracle - and 8i is no longer
supported and 10 is not available yet - the answer would be to go with 9.
Rating: 5
Upgrade - Oracle 9 or 10 ? January 09, 2004
Reviewer: Timothy from PA, USA
My bad - I just re-read the Oracle desupport notices.  8.1.7 is being
desupported on 12-31-03 AND 12-31-04 --- depending on the platform it runs on.
So, depending on your platform, you may be fully supported on 8i by Oracle for
another year.

Metalink articles: 148054.1 & 250629.1
Rating: 5
Could you provide a link to 10g online document? Thanks. January 28, 2004
Reviewer: Reader
Followup:
not yet, as soon as it is released, it'll be right there on otn with the rest.
Rating: 5
10g Release Date January 30, 2004
Reviewer: 10g Man from Reston VA
Check otn.oracle.com in the next few days.  According to Chuck, 10g Linux was
due out today and Windows 30 days from today.  I would bet it will be online
next week ..
Rating: 4
Releases February 03, 2004
Reviewer: Sanjaya Balasuriya from Colombo, Sri Lanka
Tom,

I saw that Oracle 10g has been released today.
But it has been released only for HP-UX and Sparc.

Why it's not for other OSs ?
Followup:
they are coming day by day.
Rating: 4
Is 10g a replacement for 9i, or something completely new? February 03, 2004
Reviewer: Nasir Sarwar from Houston Texas USA
Followup:
10g is the next release of the database following in the footsteps of

7.0
7.1
7.2
7.3
8.0
8ir1
8ir2
8ir3
9ir1
9ir2
and now.... 10g release 1.
Rating: 5
What is the first release number of Oracle Database? February 04, 2004
Reviewer: Suresh Govindarajan from Chennai, India
Probably I think version 5 is the first release of oracle Database. There should
be some reason your list started with version 7.0. By the way I am a regular
reader of your site for the past 3 years and learned a lot. I really admire the
way you present the answers. Thanks.
Followup:
I had to start somewhere, i just did the last 11 years -- I'd have to go back to
version 2 in order to be "complete"

see
http://otn.oracle.com/oramag/oracle/01-nov/o61cover.html
for a 'history'
Rating: 5
Oracle 10g download February 04, 2004
Reviewer: Johan Snyman from JHB SA
I see Oracle 10g is now avaialble for download from OTN, but only for Solaris
(64bit) and HU-UX. Do you perhaps know when the release for Linux and Tru64 Unix
will be available for download - I would like to start testing as soon as
possible.
Followup:
soon.  just keep checking back. (see above -- this was just asked...)
Rating: 5
10g availability February 04, 2004
Reviewer: A reader
I was told by Oracle last week that Linux would be available this week and
windows 'would be some time later'
Rating: 5
9i and 10g on the same server February 04, 2004
Reviewer: Arun Gupta from Harrisbrug, PA USA
Tom,
We have development server (Solaris 64 bit) with 9.2.0.4 installed. Can we do a
test install of 10g in a different Oracle home on the same server without
impacting anything else?
Thanks
Followup:
I've got 7.x, 8.x, 9.x, 10.x all on the same server.  works dandy (as long as I
don't start too many of them at once :)
Rating: 5
what about 11x? February 04, 2004
Reviewer: D.B.Nemec from Austria
If you add a release year to the history of main versions of Oracle DB you
receive a nice sequence:

7  1992
8  1997 i.e. 5 years later
9  2001 i.e. 4 years later
10 2004 i.e. 3 years later

consequently the version 11x should by released in 2 years in 2006 (i.e.
announced in 2005).

J.Nemec
Followup:
;)
Rating: 5
Good obervation Nemec. February 05, 2004
Reviewer: A reader
Rating: 4
10g release February 05, 2004
Reviewer: Jorre from be
I have this in my crontab (every 5 minutes)
wget
http://otn.oracle.com/software/products/database/oracle10g/index.html
|grep "for Linux"
Rating: 5
Just downloaded Linux version February 08, 2004
Reviewer: A reader
The wait over...It's here.....
Rating: 1
Downloaded Oracle 10g February 09, 2004
Reviewer: praveen sehgal
Downloaded 10g but it won't install on redhat9 :(
Speaking of platform independency...will 10g ever install on Redhat9 ???
Followup:
No -- redhat 9, an end user desktop version of RedHat, a discontinued line
actually is neither of

RHAS2.1
RHAS3.0

the "server" platforms where people expect to be able to install, configure and
run database "server" software.

This would be like installing a server product on "Windows ME" or something.
Just not "appropriate"
Rating: 3
Redhat9 February 09, 2004
Reviewer: Praveen Sehgal from Virginia
Tom is it not possible and even smart to have OS dependency at Kernel level as
opposed to Distro level. I would love to create my own optimized distro of Linux
that runs Oracle and get support from Oracle since my distro is based on a
certain version of Linux Kernel.

Now Oracle always installed on Redhat9 or many other distros for that matter now
all of a sudden we remove that compatibility..it just throws us off.

I would really like to know your thoughts on why Oracle would not or can not be
distro independent.
Followup:
this is called "a database"

it is a bit more to the wire then say, oh, a word processor.

the thought here is "you want the data to be reliably stored and retrieved",
there is but one way to do that -- on a standard, expected, server platform.

This is for SERVERS.

This is not for 'end user desktop' platforms.  Never has been.

Open Source, Linux, et.al. does not mean the practices of the past are obsolete,
to be forgotten, etc.  Just as there was never Oracle for windows me, or the
other desktop only platforms -- there will not be for (a now obsolete, dead by
the way, why bother supporting) new desktop only platforms.  It makes no sense
to install a server on such a platform.

We support a finite set of platforms and distributions (guess that is what
"distro" is).  Just like Ximian for example -- the desktop guys -- they won't
intstall on AS.
Rating: 3
Linux February 09, 2004
Reviewer: Dave
it's perfectly possible to install 10g on Redhat 9  (or any version of Linux)

You just need to know what you are doing ...
Followup:
revise that to

"you just need to know how to hack the scripts, fake out the libraries, blah
blah blah"

it is not "intuitively obvious", I gave up on that path long long ago for the
cascadiing downstream "side effects" (eg: I could never get

SQL> shutdown immediate
SQL> startup <<<==== would always fail
SQL> startup <<<==== this second one would work

on rh8.0 with 9ir2.  You can imagine what a pain that would be during say, a
patch upgrade? or just normal scripts day to day.....

if you just want to play -- go ahead, give it a go, but don't expect "truly
amazing things".
Rating: 3
Redhat9 February 09, 2004
Reviewer: Praveen Sehgal from Virginia
Perhaps I missed something Dave, but in 10g first thing that the Installer does
is check the Linux Versions and it quits operation if you are not running Redhat
2.x 3.x or United linux. It does not proceed with installation on Redhat9. I've
tried this personally and checked the installer code. Unless there's a
workaround......

Maybe you can share how you made it work.
Followup:
it can be made to work -- but it is neither supported, nor recommended.  You'll
have cascading problems down the road....

I'm happily using RHAS3.0 on my laptop's and desktops (actually nicer then rh7
and rh8 were -- since rh9 was the end of the road -- decided to go with the
product line that actually has life in it)
Rating: 5
February 09, 2004
Reviewer: A reader
To be fair, its been pretty stable for a day :-).

Installed easier than 9i did on RedHat9 as well (and a lot quicker).  I only
want it for playing and basic testing, I wont run anything major on it.

I also dont have Redhat2 or 3 and dont want to run it on Windows - so I'm kinda
stuck.  Also dont have a spare Solaris server kicking about.

If it doesnt work - so be it, ill give up.  I didnt have to mess about with
libraries either (wouldnt of gone near it if i did).  I wont post how its done
because its not fair on others - but it works for me
Followup:
you can post -- i'm sure it'll be out there on the web any second now ;)  There
is  a site dedicated to doing this sort of stuff even (at least one)
Rating: 5
install 10g in redhat 9 or whatever February 09, 2004
Reviewer: A reader

http://www.dbasupport.com/forums/showthread.php?s=a4820e935cc186a90db922c5b116e0f5&threadid=41289

Rating: 5
February 09, 2004
Reviewer: A reader
Rating: 3
10 bug ? February 10, 2004
Reviewer: A reader
hi tom, I installed 30-day version of 10g for solaris 5.8
(added all required patches described in installation guide)
oracle version : 10.1.0.2.0

-------------------------------------------------------
my question :
 please look at the query listed below.
in
query#1 : I select all the columns and
          I get one row(s) back.
where as in
query#2 : I count the rows and it returns 0
         expacted answer is 1
Note : The From clause and the Where clause in the query is identical.

--- Please comment. it is me or 10g ?
--------------------------------------------------
bash-2.03$ sqlplus my10g/my10g

SQL*Plus: Release 10.1.0.2.0 - Production on Tue Feb 10 11:27:07 2004

Copyright (c) 1982, 2004, Oracle.  All rights reserved.

Connected to:
Oracle Database 10g Enterprise Edition Release 10.1.0.2.0 - 64bit Production
With the Partitioning, OLAP and Data Mining options

SQL> set lines 120
SQL> set pages 40
SQL> set feedback on
SQL> set echo on
SQL> set termout on
SQL>
SQL>
SQL>    SELECT  I.INST_cODE   ,I.INST_IS_LIVE  ,NVL(I.INST_LAST_UPDATED,SYSDATE)
,
  2                       I.INST_PARENT_CODE,I.INST_RGN_CODE,I.INST_RGN_NAME
  3      FROM institution_v40 i,institution_v40 P ,
  4              (SELECT *  FROM alias_v40 WHERE alias_aliastype_code = 'INAB')
a ,
  5                      (SELECT site_inst_code, site_code, site_name,
  6                                      GREATEST (site_last_updated,
sr.site2siter_last_updated) AS s_ts
  7               FROM site_v40,(SELECT * FROM site_to_site_role_v40
  8                                                      WHERE
site2siter_siter_code = 'PRMRY'
  9                                                      AND site2siter_is_live
= 'Y') sr
 10               WHERE site_code = sr.site2siter_site_code
 11                      ) s
 12     WHERE  1 = 1
 13     AND i.inst_parent_code = p.inst_code(+)
 14     AND UPPER (i.inst_name) LIKE UPPER ('%test%')
 15      AND i.inst_code NOT IN (SELECT i2ir.inst2instr_inst_code
 16                               FROM inst_to_institution_role_v40 i2ir
 17                              WHERE inst2instr_instr_code = 'CDSRE')
 18     AND i.inst_code IN (SELECT i2ir.inst2instr_inst_code
 19                           FROM inst_to_institution_role_v40 i2ir
 20                          WHERE (   inst2instr_instr_code = 'CUSTR'
 21                                 OR inst2instr_instr_code = 'BRKRG'))
 22       AND i.inst_code = a.alias_entity_code(+)
 23       AND UPPER (a.alias_name) LIKE UPPER ('%test%')
 24          AND i.inst_is_live = 'Y'
 25              and i.inst_code = s.site_inst_code(+);

INST_CODE   I NVL(I.INS INST_PARENT INST INST_RGN_NAME
----------- - --------- ----------- ----
--------------------------------------------------
I0000003915 Y 06-FEB-04             ASIA Asia

1 row selected.

SQL>
SQL>  SELECT  count(*)  --I.INST_cODE   ,I.INST_IS_LIVE
,NVL(I.INST_LAST_UPDATED,SYSDATE) ,
  2  --                   I.INST_PARENT_CODE,I.INST_RGN_CODE,I.INST_RGN_NAME
  3      FROM institution_v40 i,institution_v40 P ,
  4              (SELECT *  FROM alias_v40 WHERE alias_aliastype_code = 'INAB')
a ,
  5                      (SELECT site_inst_code, site_code, site_name,
  6                                      GREATEST (site_last_updated,
sr.site2siter_last_updated) AS s_ts
  7               FROM site_v40,(SELECT * FROM site_to_site_role_v40
  8                                                      WHERE
site2siter_siter_code = 'PRMRY'
  9                                                      AND site2siter_is_live
= 'Y') sr
 10               WHERE site_code = sr.site2siter_site_code
 11                      ) s
 12     WHERE  1 = 1
 13     AND i.inst_parent_code = p.inst_code(+)
 14     AND UPPER (i.inst_name) LIKE UPPER ('%test%')
 15      AND i.inst_code NOT IN (SELECT i2ir.inst2instr_inst_code
 16                               FROM inst_to_institution_role_v40 i2ir
 17                              WHERE inst2instr_instr_code = 'CDSRE')
 18     AND i.inst_code IN (SELECT i2ir.inst2instr_inst_code
 19                           FROM inst_to_institution_role_v40 i2ir
 20                          WHERE (   inst2instr_instr_code = 'CUSTR'
 21                                 OR inst2instr_instr_code = 'BRKRG'))
 22       AND i.inst_code = a.alias_entity_code(+)
 23       AND UPPER (a.alias_name) LIKE UPPER ('%test%')
 24          AND i.inst_is_live = 'Y'
 25              and i.inst_code = s.site_inst_code(+) ;

COUNT(*)--I.INST_CODE,I.INST_IS_LIVE,NVL(I.INST_LAST_UPDATED,SYSDATE),--I.INST_PA
RENT_CODE,I.INST_RGN_CODE,I.INST_RGN_NA
---------------------------------------------------------------------------------
---------------------------------------

                                      0

1 row selected.

SQL>
Followup:
if you give me

a) the smallest
b) yet 100% complete
c) reproducible (so I can run it myself)

testcase, I'd be glad to take a look and file a bug if needed.

a) is very very important -- cut it down, remove columns from table creates,
supply only that which is necessary to reproduce the issue.
Rating: 3
results with 9i February 10, 2004
Reviewer: A reader
another test i did is I ran this query with 9i R2
on solaris5.8 64 bit and it produces correct results.

 we are testing 9i and 10g as we are on 8.1.7 .
if 10g is stable we directly want to go to 10g and skip 9i.
possible ? your thoughts ?
Followup:
definitely possible.
Rating: 5
February 10, 2004
Reviewer: Dave
what is worthwhile though is learning all the new features on 9i as well to get
a full understanding  ...
Rating: 3
From 8.1.7 to where? March 01, 2004
Reviewer: Olga from Vienna
Hi Tom,

my client wants to migrate from 8.1.7 to 9ir2 in a few months. They think it
could be a risk to migrate to the newest 10g. It's not a a very big Database
(perhaps 10 GB) and they don't use a lot of "complicated" features. So why don't
try the newest release?

What's your opinion?

Bye,

Olga
Followup:
read up a couple, we talked about this briefly.
Rating: 5
asm March 07, 2004
Reviewer: reader
To use ASM in 10g, what should one do as a DBA? Using ASM a default feature in
10g? Has Oracle incorporated the software to manage ASM as part of database
software? Thanks.
Followup:
one would setup and configure disk groups.  using asm is an option -- the
default is still "plain old files".  yes, asm is part of the database feature
set itself.  Enterprise manager is your gui, sqlplus your command line to
manage.
Rating: 5
10g March 07, 2004
Reviewer: Dave
i am curious - now that you have upgraded to 10g here - what new features are
you using or did you just migrate the core db and stay as you are.
Followup:
we just migrated... (and picked up the plsql feature of being faster)

everything in steps.

we migrated so we can start using new features
Rating: 3
10G for Mandrake Linux March 07, 2004
Reviewer: A reader
Dear Tom,

Any idea when 10g will be available for Mandrake Linux(9.0)
Rating: 5
March 08, 2004
Reviewer: Dave
Only Linux supported is Redhat AS 2.1 and 3 and United Linux.

Anything else is unsupported
Rating: 5
10g management March 08, 2004
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom,
My understanding is that for a 10g database, there are two GUI management
methods, one using Database Control and other using the Grid Control. The
Database Control is like a self contained OEM with the repository residing
within the database to be managed (sysaux tablespace?) whereas the Grid Control
is the real Enterprise Manager. The Grid Control is to be installed separately
from the server software (as it is available as a separate 3 CD download from
OTN). The Database Control is installed by default with the server software and
any database can be configured to be managed by Database Control with minimum
configuration.

Is this understanding correct?
Thanks.
Followup:
correct - dead on.  dbconsole comes with the db, EM is a separate "thing"
Rating: 5
No more stats gathering - no, wait... March 09, 2004
Reviewer: Neil from UK
Tom,
I just noticed from the 10g performance tuning guide that stats can be
automatically gathered. Before I celebrated, further reading revealed that it
may not be the optimal way to gather stats if the tables are either volatile or
growing by more than 10% (due to a load or frenzied activity during the day). In
this case, one is advised to revert manual stats gathering. It struck me that
this could be a headache in a hybrid system: if automated stats gathering is
enabled, exceptions must be made for any table greatly affected by batch
processing (three batch runs per 24 hours following the sun), and still some
kind of script is needed to make sure those tables are properly analysed.
What would be the most pragmatic and common sense approach to stats gathering
for a system like this moving to 10g?
Followup:
why would this be a 'separate script', would not the obvious place be "the batch
process itself".  The batch (it would seem) would have sufficient information to
know that "hey, my last step might be a gather or even a SET (to avoid a
gather).
Rating: 5
Is 10g available only for 64 bit unix platform? Thanks. March 20, 2004
Reviewer: reader
Followup:
I'm running it on 32bit linux and it is there for 32bit windows as well.

it is not on 32bit solaris yet if that is what you mean specifically, hp and aix
only do 64bit now.
Rating: 5
Performance with 10g March 22, 2004
Reviewer: A reader
Ask Tom seems to be a bit slower now a days. Or is it because of my network. Can
you extract your famous performance log report and check :-)
Followup:
It used to generate pages in 1 unit of time with 9i.

It is now generating pages in 0.7 units of time with 10g. (eg: faster)

can you time a transfer of a presentation or something off of the site and see
what sort of Kbs you get? (see home page for link)
Rating: 5
Performance with 10g March 22, 2004
Reviewer: A reader
0.98 MB in 49 secs
Got around 30-35 KB/sec (kept changing). Anyway it might be my network :-)
(As my code is also in production, I have realized it is great to be prepared
for such queries - i.e by instrumenting the code and keeping the performance
history in a table)
Rating: 4
Oracle 10g and forms 6i March 23, 2004
Reviewer: Nikunj from India
Dear Tom,

I had installed oracle 10g ee on windows 2000, it works fine but when i try to
compile my exsting forms 6i form after connecting to 10g server on my some
pl/sql block i get below error message ora-00600 internal error code, arguments
[17069],[57985992],[],[],[],[],[],[].

Same form after get connect to oracle 9.2 ee it works fine.

Regards,
Followup:
please contact support.
March 23, 2004 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
Rating: 5
can we use db_object_size? January 28, 2004
Reviewer: A reader
Hi

I have some doubt about this view...
Can we use db_object_size to estimate shared pool needed for PL/SQL Packages? If
so I have some questions

1.- What is the difference between source_size and code_size?
2.- What is parsed_size and error_size?

In the manual it says about "Must be in memory during compilation" in the
definitions of these columns... What des it mean? I mean why we need to load the
package to memory to find out the source_size?

3.- Which size is used in shared pool? parsed_size or code_size?
4.- When I query these view for my packages I see values in parsed_size for
package but 0 for package body.... how so?

Finally I understand these values are not enough, I should add some overhead,
any rule for this overhead? 10%, 20%?

THANKS
Followup:
I know of no proven methods to do this, no..   there are too many variables.
Rating: 5
Thumb Rules !!! January 29, 2004
Reviewer: sankar from India
Dear Tom,

My experience shows that even OS and ERP Application Hardware sizing is very
much on the lower side. The technology partners does not give the Processor and
RAM estimates of ntier implementation clearly (specific to a particular
combination of ERP, RDBMS and OS versions taking the best practices into
account). Heavy paging / swapping, higher CPU utilisation and slow response
forces clients to upgrade after reaching full loads planned.
Rating: 5
Statement Conflicting March 23, 2004
Reviewer: Nishant Choudhary from India
Dear Sir,
after going through this doc ..... i am confused as why the conflict is there in
ur statement .

To retrive the sga size..
(for more details see the options below suggested by you )

show sga -- acceptable

the query -- why the variable size is divided by .9 to retrive the variable size
and this is really confusing or might be feeding mistake but i am really
confused  ................

plz suggest

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).
Followup:
what conflict?  not sure what question you are asking here?

if it is about divide by .9 == that just takes into account that the SGA is a
complex thing to compute size wise and is larger than the sum of those three
parameters.  .9 is a 'fudge factor' (and in 9i, it would be way off as the sga
is allocated in granules).
Rating: 4
FIXED SIZE OF SGA March 23, 2004
Reviewer: Nishant Choudhary from India - I proud
Sir,
plz let me know the contents of fixed size (SGA).what all thing makes up
(occupies) the fixed size and how does they affect .. their working ?

i have one more query ...
how do i get to know that i have beeen answered ... and where should i check ..
wil i be notified at my email address?? if not plz let me know once at my
personal address "nishant_ocp@yahoo.com"

thanx in adv
Followup:
it is internal stuff, pointers to inside the sga, it is compiled into the code.
we (you and me) have no need to know anything beyond the facts that

a) it is there
b) we cannot affect its size

You would come back here and look for the answer.  I cannot email everyone
personally.
March 23, 2004 -- Thanks for the question regarding "Using IN", version 8.1.6
You Asked
Hi,

I have a store procedure that takes a list of customer ID (number) separate by
coma (,) and update customer table.

Here is a modified version of my store procedure:

    PROCEDURE update_customer(id_list IN varchar2) IS
    BEGIN
        UPDATE customer
        SET modified_date = SYSDATE
        WHERE customer_id IN (id_list);
    END update_customer;

Where: id_list is a list of customer ID in format '1,2,3,4,5'
    and customer_id data type is NUMBER.

I got a "ORA-01722: invalid number" error when I execute the above store
procedure.  I believe it bombs b/c id_list is a varchar2.  Is there a better way
to do this?
and we said...
well, there are many ways to do this -- since this is an update, I might be
tempted to do this:

ops$tkyte@ORA920.US.ORACLE.COM> create table emp as select e.*, to_date(null)
modified_date from scott.emp e;

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace procedure update_customer(
p_id_list in varchar2 )
  2  is
  3      type list is table of number index by binary_integer;
  4      l_id_list long default p_id_list || ',';
  5      l_list    list;
  6  begin
  7      while( l_id_list is not NULL )
  8      loop
  9          l_list(l_list.count+1) := substr( l_id_list, 1,
instr(l_id_list,',')-1 );
 10          l_id_list := substr( l_id_list, instr(l_id_list,',')+1 );
 11      end loop;
 12
 13      forall i in 1 .. l_list.count
 14          update emp
 15             set modified_date = sysdate
 16           where empno = l_list(i);
 17  end;
 18  /

Procedure created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> exec update_customer( null );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> exec update_customer( '7369, 7499,7521  , 7566'
)

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select empno, modified_date from emp;

     EMPNO MODIFIED_
---------- ---------
      7369 14-SEP-02
      7499 14-SEP-02
      7521 14-SEP-02
      7566 14-SEP-02
      7654
      7698
      7782
      7788
      7839
      7844
      7876
      7900
      7902
      7934

14 rows selected.

As a very straightforward way.  Otherwise, I can use some of the techniques
listed on:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:110612348061
....
Review & Followup

Rating: 4
IN with list September 16, 2002
Reviewer: HP from Seattle, WA
Thanks Tom.  I currently calls Java Store Procedure to split/stringtokenize the
input list; however, I like your way better b/c it saves a call.

In your opinion, does the below query have a better performance than splitting
the list and loop through it? why?

UPDATE customer SET modified_date = SYSDATE
WHERE customer_id IN (&id_list);
Followup:
That would be using substitution variables meaning the query would be unique
each time meaning it would be hard parsed meaning I HATE IT as a solution.
Rating: 4
What about Designing September 17, 2002
Reviewer: Tejas from India
Dear Sir,

I always wonder that whether it is a good designing approach to save the comma
separated values (specially when the field is candidate for the foreign key) or
not ?

Please let us know us views.

Thanks
Followup:
I think it is a terrible idea in general to store a comma separate list of
values.  I would not store:

ID          VALUES
-------     --------------
1           1,2,3,4
2           3,4,5

I would store

ID          VALUES
-------     --------------
1           1
1           2
1           3
1           4
2           3
2           4
2           5

in general.
Rating: 4
Hi Tom March 23, 2004
Reviewer: Lou from USA
Please suggest me how can I deal with this query.
Here it is. The requirement is in the In clause
there about about 50 different values and I need
to find also the duplicates records. So I thought
I would find all the record first and then create
a table and then find the dups. Please tell me if
this approach is good.

SELECT PROGRAM_HEADER.SURQTR,
PROGRAM_HEADER.WGT,
PROGRAM_HEADER.MED,
PROGRAM_HEADER.SRC,
PROGRAM_HEADER.CAT,
PROGRAM_HEADER.AIRDATEFROM,
PROGRAM_HEADER.AIRDATETO
FROM DREUSER.PROGRAM_HEADER
WHERE (PROGRAM_HEADER.WGT In (.8510,.900,.9490,1.00000))
Followup:
well, if "duplicate" means "all columns"

select columns....
  from t
 where condition ....
 group by columns.....
HAVING COUNT(*) > 1;

if duplicate means SOME of the columns:

select *
  from (select columns....,
               count(*) over (partition by COLUMNS_THAT_ARE_KEY) cnt
          from t
         where condition..... )
 where cnt > 1;
Rating: 4
great March 23, 2004
Reviewer: A reader
But how about the different values in my In ( 50 different
values to search for)??

Thanks Tom
Followup:
just list them out?  not sure what you are asking....
Rating: 1
follow up March 23, 2004
Reviewer: A reader
In my where clause the list of values are
different and too many to type it. I think
there are about 50 different values.
Followup:
well, cut and paste them.  they must exist somewhere.

sorry, but we cannot just "think" them into the computer.

if they are in a table, use a subquery.

don't know what else I could possibly say.
Rating: 4
Come on, Tom March 23, 2004
Reviewer: LOL from USA
You mean you can't "think" things into your computer?

I find that hard to believe.
March 23, 2004 Sam -- Thanks for the question regarding "Login.sql and partitions nad & ,&&", version 8.1.7
You Asked
Tom

1.Can you give me the the link to your login.sql script. I want to display the
instance name and user name instead of sql>  in  my plus.
( the login.sql on your website prompts to enter a value when I login, that
doesnt work for me).
What is the difference between glogin and login.sql.

Can I change the color and font in sqlplusw?

2.A table t has partitions with partition bounds
values less than 16-nov-01  and
values less than max value

Now if I want to split max value and add partition 15-nov-01, Icannot , I need
to split partition with values less than 16-nov01.

Why is it so. What are the internal reasons for this kind of functinality.

Woudnt it be good, if I have the ability to split the max value partition and
add any date range I want, and oracle takes care of where it should put it.

3.Lastly tom, this is silly, but can you give a small example and explain the
difference between using & and && .

Thank you
and we said...
1) you apparently have my login.sql -- if it is prompting you, change it to:

define gname=NO_ACCESS_TO_GLOBAL_NAME
column global_name new_value gname
...

(add the bold text).  That will set the prompt to NO_...NAME  when you cannot
get to the global_name table for whatever reason.

see
http://download-west.oracle.com/docs/cd/A87860_01/doc/win.817/a82954/apa.htm#423093
2) Have you looked at your table and thought about this?

You have partition "a" with values LESS then 16-nov-2001.  That means data such
as:

01-nov-2001
02-nov-2001
03-nov-2001
...
15-nov-2001

are all in there (you might not have any data in there, but the fact remains --
that data CAN be in there, nothing preventing that data from being there).

Hence, if you want to add a partition that has the vales less than 15-nov-2001
-- there is exactly ONE partition that can be split to do that. You have to
split the partition "a" that might contain values that will be part of this new
partition.  It would make no sense whatsoever to split the maxvalue partition
here -- there may be data in partition "a" that would need to go into this newly
split one.

The "internal reason" is one of data consistency and integrity.  There may well
be data in partition "a" that needs to be in this newly split partition.  Hence
you must split that partition and rewrite the data it contains.

If you think about it -- this makes 100% perfect, logical sense and is in fact
the only way to do this.
3)

ops$tkyte@ORA9I.WORLD> set echo on
ops$tkyte@ORA9I.WORLD> @test
ops$tkyte@ORA9I.WORLD> prompt &x
Enter value for x: hello
hello
ops$tkyte@ORA9I.WORLD> prompt &x
Enter value for x: hello
hello
ops$tkyte@ORA9I.WORLD> prompt &&x
Enter value for x: hello
hello
ops$tkyte@ORA9I.WORLD> prompt &&x
hello
ops$tkyte@ORA9I.WORLD> prompt &x
hello

The && says -- just use this value for X without asking for it again.  Note how
the first three &x, &x, &&x all asked me to enter a value for X but after we hit
&&x -- it stopped doing that and just used whatever was there.
Review & Followup

Rating: 4
CONDITIONAL LOGIN.SQL March 11, 2002
Reviewer: Ravi Kumar from Chicago, IL
Hi Tom,

If I want the settings to be different for different servers I login, what
should I do.

For example, If I am connected to Dev and Test servers, I would like to set time
on and also serveroutput on. But if I am on Prod server I don't want these.

Thanks,
Ravi.
Followup:
create a script called DEV.sql, one called TEST.sql

then:

column foo new_value x
select global_name FOO from global_name;  -- run some query that returns TEST
                                          -- when in test and DEV when in dev

@&foo

that'll run either dev.sql or test.sql
Rating: 5
Thanks a lot!! March 12, 2002
Reviewer: A reader
Rating: 5
small modification ;) March 12, 2002
Reviewer: Ravi Kumar from Chicago, IL
instead of

@&foo

we need to write

@&x
Followup:
thats right -- thanks...  (should remember to run these things instead of just
typing sometimes ;)
Rating: 4
Glogin.sql March 13, 2002
Reviewer: Alexandre Matthes from Rio de Janeiro - Brasil
Hi tom,

I´ve added these commands bellow into glogin.sql file.

set termout off
define new_prompt='nolog'
column value new_value new_prompt
select username || '@' ||
substr(global_name,1,DECODE(instr(global_name,'.'),0,LENGTH(global_name),instr(gl
obal_name,'.')-1)) || ':' || host_name
value from  global_name, gv$instance,user_users;
set sqlprompt "&new_prompt> "
set termout on

Every time I log , sqlplus prompt shows me:

SYSTEM@DBTNET:DADOS1>

That´s Ok, but this only works for users who have access to those three views on
the select statement above, if I log in as a "merely mortal" user, the prompt
is:

nolog>

1) I´d like to know if there is a way to bypass this situation WITHOUT granting
select on those views to these simple users?

2) If I use the connect command on the same sqlplus screen to connect another
user into another database, the sqlplus prompt stays the same way... is there a
way to change the prompt "dinamically" when using this connect command on the
same sqlplus window?

Thanks in advance and congratulations for this outstanding site.
Followup:
create or replave view system_name
as
select username || '@' ||
substr(global_name,1,DECODE(instr(global_name,'.'),0,LENGTH(global_name),instr(gl

obal_name,'.')-1)) || ':' || host_name
value from  global_name, gv$instance,user_users;

run that as some user who has DIRECT select on global_name, gv$instance,
user_users.  grant select on that to public.

i have a script connect.sql that contains

login &1
@login

and have trained myself to use

SQL> @connect scott/tiger

instead of connect scott/tiger.
Rating: 5
why global_name , why not v$database or v$instance June 25, 2002
Reviewer: bole taro dolbaje
Tom

why should I select the instance name from global_name , why cant I select it
from v$database or v$instance.

Actually what is the purpose of global_name? is it there to just help us know
the instance we are connected to ...

Kindly explain
Followup:
go for it.  I myself like to use the global_database name cause

a) i use global_names = true, my databases are uniquely named by their global
name
b) i don't use the sid, i use the global name to id what database I'm in.

global_name is supposed to be a globally unique name for a database in a given
set of related but distributed databases.  They could ALL have the same sid (and
some of mine do!) but since they are on different machines and I've named them
differently (with alter database rename global_name ...) -- that makes it better
for me.
Rating: 5
Way to accept "hidden" password? June 26, 2002
Reviewer: Dennis from Missouri, USA
Tom,

This is good stuff.  I always had my glogin doing the time and elapsed time, and
feared putting the SID in there precisely for the connect to another instance
showing the same SID.  Now that you have a way to do it, I'm trying to duplicate
it.  If I could borrow your mind for a little while, then I'd be set :)

I tried the connect.sql with the login $1 and @login.sql, and it had errors.  It
seems like since I am using glogin that I would need to call glogin (in fact I
don't have a login.sql - do I need to make one?).  Now, I've changed connect.sql
to be:
connect &id/&pass@&sid
@D:\ora816\sqlplus\admin\glogin.sql

But when I run it, everything is of course visible:
Enter value for id: scott
Enter value for pass: tiger
Enter value for sid: hii2

Is there a way I can hide or "mask" the password so that it is either not
visible, or just a bunch of stars?  I tried turning termout off, but all that
did was hide the prompts, it was up to me to figure out that the blinking cursor
on the next line down wanted me to type something (ok so it didn't take a lot of
time for that), but what I typed was still visible.
07:03:47 SCOTT@HII2:sca02> @connect
scott
tiger
his2

Obviously the first approach is better as far as user interfaces go (but hey if
you know the program, you don't need bells and whistles!), but I would still
like to be able to hide the password.  I usually wind up with a developer in my
cube and me logging onto an instance with either my id or an id that they
shouldn't be using, and I think it may be disruptive if I keep yelling Fire or
something to make them look away so I could login and clear screen (ok, I would
add clear screen to the script to save time :) ).   Although I probably would
have more fun with that approach...

Thanks Tom, soon we'll all be Tom wannabes...wait a sec...I think we already
are!!!
Followup:
ops$tkyte@ORA817DEV.US.ORACLE.COM> accept pass prompt Password hide
Password
ops$tkyte@ORA817DEV.US.ORACLE.COM> select '&pass' from dual;
old   1: select '&pass' from dual
new   1: select 'abc' from dual

'AB
---
abc

Instead of letting us just ask you for &pass -- use an ACCEPT before the CONNECT
to prompt you for the password  and use the HIDE option of the accept command.
Rating: 5
The accept is killing me June 26, 2002
Reviewer: Dennis from Missouri, USA
I cannot make my accept work like yours:

07:56:12 SCOTT@HII2:sca02> accept pass prompt password hide pass
SP2-0003: Ill-formed ACCEPT command starting as pass

What am I doing wrong?

I am assuming I would have to get all three variables, and then use a connect
after gathering the details?
Rating: 5
gah June 26, 2002
Reviewer: Dennis from Missouri, USA
Ok, I figured out the accept thing (no pass after hide).

Should have spent more time on it...sorry.

I couldn't connect to the database at first.  I had changed my connect statement
to be id/pass@sid, so it wouldn't prompt (since I had the & there).  I should
have realized that this isn't AIX (I was thinking of $, but decided maybe name
would work).  Anyway, I had to use && to solve it.  It works great now.

accept id prompt userid:
accept pass prompt Password:   hide
accept sid prompt sid:
connect &&id/&&pass@&&sid
@D:\ora816\sqlplus\admin\glogin.sql

Thanks a lot for your help Tom.   Sorry a stupid syntax error is wasting bytes
on your server.

Dennis
Rating: 5
? June 26, 2002
Reviewer: bole taro dolbaje
"global_name is supposed to be a globally unique name for a database in a given
set of related but distributed databases.  They could ALL have the same sid (and

some of mine do!) but since they are on different machines and I've named them
differently (with alter database rename global_name ...) -- that makes it better

for me. "

1.Do you mean that global_name table will not get populated unless and untill
the global_names parameter is not set TO  yes in the init.ora.

2.Sid cannot be changed where as global_name can be changed using  'alter
database rename global_name' ?

3.Our dba says that we should not use global_name at all , but use only
V$database, and doesnt site any reasons. Is there any justification in this kind
of arguement.

4.'i don't use the sid, i use the global name to id what database I'm in.'

what difference does using sid over global_name or vice versa make..
Rating: 3
remember to change GLOBAL_NAME when cloning a database June 26, 2002
Reviewer: andrew from ca, usa
When you clone a database (by copying the datafiles) the GLOBAL_NAME retains the
name it had in the one it was cloned from. In my case, dynamically created shell
scripts use this value to specify where to connect to (sqlplus a/b@GLOBAL_NAME).
The production database almost got trashed by tests running on the cloned one.
Rating: 3
Surprising problem June 26, 2002
Reviewer: Vishal from NY USA
When I add these lines of code in my glogin.sql
set termout off
define new_prompt='nolog'
column value new_value new_prompt
select username || '@' ||
substr(global_name,1,DECODE(instr(global_name,'.'),0,LENGTH(global_name),instr(gl

obal_name,'.')-1)) || ':' || host_name
value from  global_name, gv$instance,user_users;
set sqlprompt "&new_prompt> "
set termout on

My prompt comes like
&new_prompt>

Although the query runs fine.
Followup:
use single quotes, not double.

sql sqlprompt '&new_prompt> '
Rating: 1
Double quotes is not a problem something else is June 27, 2002
Reviewer: VISHAL from NY
I am sorry for being less communicative on the first place. I had tried with
single quotes and even without quotes. It doesn't work. I tried on another
machine and "Double quotes" works fine. Let me ask this;; Can this be some
minor/major problem on SQLPLUS installation on that perticular machine where I
am not been able to set the sqlprompt with the help of a variable ????
Followup:
then someone has issued

set define off

or set define on <someother character>

issue

set define on

and it'll be

o enabled
o set to &
Rating: 5
Exactly That was the problem June 27, 2002
Reviewer: VISHAL from NY USA
It was the exact problem. Hats off to you.
Thanks
Rating: 5
@connect sys as sysdba November 15, 2002
Reviewer: SB from OH
Hi Tom,
I got used to using @connect for quite a while now. But, when I use @connect
sys@test as sysdba, comes out with ORA-28009 error. Any workaround for this?

Thankx.
Followup:
@connect "sys@test as sysdba"
Rating: 5
perfect November 18, 2002
Reviewer: SB from CA
Thankx,Tom
Rating: 5
something weird with nls_date in glogin? March 01, 2004
Reviewer: Dennis from Missouri, USA
I tried to do an alter session with the glogin file to set the nls_date_format
to "mm-dd-yyyy hh24:mi:ss", but it's not working correctly.   I tried it with
single quotes (') instead of double quotes, but that also didn't work correctly.
 Any tips here?

Glogin.sql snippet:
set sqlprompt "&new_prompt> "
set termout on
alter session set nls_date_format = 'mm-dd-yyyy hh24:mi:ss';

- end snippet -
logon to sqlplus...
14:36:43 winkid1@SDWD13:lpar106> @connect
userid:winkid1
password:********
sid:sdwd13
Connected.

Session altered.

Elapsed: 00:00:00.00
Elapsed: 00:00:00.00
14:45:11 winkid1@SDWD13:lpar106> select * from hrdaily.ddl_log;

USER_ID_CALLER                 OPERATION
------------------------------ ------------------------------
OBJECT_GROUP                                       DATE_CALLED
-------------------------------------------------- -----------------
HRCTLUTIL                      TRUNCATE
ODW704D1                                           01-MAR-2004

---
If I alter session at the prompt after logging in, then it works:
14:49:05 winkid1@SDWD13:lpar106> alter session set nls_date_format = 'mm-dd-yyyy
hh24:mi:ss';

Session altered.

Elapsed: 00:00:00.00
14:49:44 winkid1@SDWD13:lpar106> select * from hrdaily.ddl_log;

USER_ID_CALLER                 OPERATION
------------------------------ ------------------------------
OBJECT_GROUP                                       DATE_CALLED
-------------------------------------------------- -------------------
HRCTLUTIL                      TRUNCATE
ODW704D1                                           03-01-2004 13:21:57

Doing a show parameter nls after logging in and then after issuing the command
via sql plus reveals that the settings look the same.  nls_date_format is blank
if that matters.

Just trying to figure out why the glogin doesn't like it.

Thanks,
Dennis
Followup:
what version/platform -- I just tried it on 9204 with linux and it worked OK for
me

Maybe stick an alter session set sql_trace=true right before that, log in,
select sysdate from dual, log out and get the sql_trace out of there.

Then, edit the trace file and see if another alter session is coming along ?
Rating: 5
dfs March 01, 2004
Reviewer: Dennis from Missouri, USA
It's 9201 on Windows 2000.   Maybe it's the Windows :)

Thanks,
Dennis
Followup:
well, i did 9201 on windows XP pro with "local disk".  it *should* work, i've
never seen it not work.
Rating: 5
You were right the first time March 02, 2004
Reviewer: Dennis from Missouri, USA
I had a customer in my cube at the end of the day so I didn't try the trace
until now.   There is a second alter session that sets the format to
'dd-mon-yyyy'.   Do you have any ideas on where that would be coming from?  I
don't see it anywhere in my glogin file, and I don't have a login.sql.

Thanks,
Dennis
Followup:
verify you do not have a login.sql (you can do that by putting an empty
login.sql or a login.sql that has "prompt HEY" in your current working directory
before firing up sqlplus)...

if you have a sqlpath env variable set, it might be finding it elsewhere.
Rating: 5
Progress and Success March 02, 2004
Reviewer: Dennis from Missouri, USA
I thought you'd like to know that I had two login.sql files installed as part of
Quest under an "examples" and a "test" folder.  I removed those and the problem
persisted in a different fashion.  If I open a new sqlplus session, then it
keeps my date format.  If I issue an @connect then my date is the different
format.  That led me to my connect.sql file, which sure enough had an alter
session in it.  I guess that should have been the first place I looked, but
either I didn't realize that I saw two different formats, or it was pulling from
the other two login files.

Thanks for your time.  Sorry it wasn't more meaningful.
Dennis
Rating: 4
HTML MARKUP issue March 23, 2004
Reviewer: Anirudh Sharma from N.Delhi, India
Hi Tom,
I developed a report using SQLPLUS SET MARKUP HTML ON clause. The script the
output HTML report correctly in QA and DEV box....but when moved to production
the the SET MARKUP HTML ON was not working correctly in the sense that it was
producing only the ,html,head and body tags and not the table tags...so the
output was weird and unreadable.....
what could be the reason for this.
Code :
set feedback off
set termout off
column mc_type heading 'COPY TYPE'
column total heading ' Modified '
column count_dat heading ' Product_Document.dat '
column count_ci heading ' Copy Info '
column count_pdau heading ' Product Document AU '
column count_webp heading ' WEBP '
column isbn heading ' ISBN '
column sg_code heading ' SG-CODE '
set markup html on preformat off entmap off spool on
SPOOL /home/webpload/marketing_copy/report/aud_rep.htm
select '<center>'||TO_CHAR(SYSDATE,'dd-MON-YYYY')||'</center>' as "AUDIT REPORT"
from dual;
select mc_type,total,count_dat,count_ci,count_pdau,count_webp,isbn, sg_code
from audit_report_2
ORDER BY seq;
spool off
set markup html off entmap off
exit

In Production, The statement “set markup html on preformat off entmap off spool
on” is not generating the tags required for table generation in HTML for the two
SELECT statements after this i.e
select '<center>'||TO_CHAR(SYSDATE,'dd-MON-YYYY')||'</center>' as "AUDIT REPORT"
from dual;
AND
select mc_type,total,count_dat,count_ci,count_pdau,count_webp,isbn,
                           sg_code
from audit_report_2
ORDER BY seq;
----------------------------
Output
1. PRODUCTION
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> set feedback off
SQL> set termout off
column mc_type heading 'COPY TYPE'
SQL> SQL> column total heading ' Modified '
SQL> column count_dat heading ' Product_Document.dat '
SQL> column count_ci heading ' Copy Info '
SQL> column count_pdau heading ' Product Document AU '
SQL> column count_webp heading ' WEBP '
SQL> column isbn heading ' ISBN '
SQL> column sg_code heading ' SG-CODE '
SQL> set markup html on preformat off entmap off spool on
SQL> select '<center>'||TO_CHAR(SYSDATE,'dd-MON-YYYY')||'</center>' as "AUDIT
REPORT" from dual;

AUDIT REPORT
--------------------------------------------------------------------------------
<center>19-MAR-2004</center>
SQL>
SQL>”

--------------
QA

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> set feedback off
SQL> set termout off
SQL> column mc_type heading 'COPY TYPE'
SQL> column total heading ' Modified '
SQL> column count_dat heading ' Product_Document.dat '
SQL> column count_ci heading ' Copy Info '
SQL> column count_pdau heading ' Product Document AU '
SQL> column count_webp heading ' WEBP '
SQL> column isbn heading ' ISBN '
SQL> column sg_code heading ' SG-CODE '
SQL> set markup html on preformat off entmap off spool on
SQL> select '<center>'||TO_CHAR(SYSDATE,'dd-MON-YYYY')||'</center>' as "AUDIT
REPORT" from dual;
<br>
<p>
<table border="1" width="90%">
<tr>
<th>
AUDIT REPORT
</th>
</tr>
<tr>
<td>
<center>19-MAR-2004</center>
</td>
</tr>
</table>
<p>
SQL>
Followup:
do a "show all" on all machines and compare the sqlplus settings.
March 23, 2004 Vasan -- Thanks for the question regarding "Retrieve duplicate values of a field in a table......", version Version 8.0.0
You Asked
I would like to retrieve values from a field which have
duplicates within it in a table. ie. the occurence of a value in
the field must be more than once...

For eg:In table T1 with field F1 if i have the
following entries:
i.e in table T1 under field F1:

 1
 1
 1
 2
 3
 4
 4

I would like to retieve only those set of values which
has an entry more than one time in it.
i.e desired result should be:

 1
 1
 1
 4
 4

Thanks in advance

Vasan..
and we said...
Either of:

select *
  from t
 where f1 in ( select f1
                 from t
                group by f1
               having count(*) > 1 )
/

or

select *
  from t t1
 where exists ( select null
                  from t t2
                 where t2.f1 = t1.f1
                   and t2.rowid <> t1.rowid )
/

They will have totally different performance characterists. The
first will tend to get the last row of the result set the
fastest (but makes you wait longer for the first row).  The
second is the opposite -- it'll start getting rows back to you
faster but it'll take longer probably to get the last row.
Review & Followup

Rating: 4
great!!!!! March 23, 2004
Reviewer: Ragu from New York
Tom,

can I do this in a query?? I want to find the records where only product field
is different?

product house  person cat
1        1994    F     3
1        1994    F     3
1        1994    F     3
2        1994    F     3
Followup:
is different from what?
March 23, 2004 Saminathan -- Thanks for the question regarding "Analytical Functions", version 8.1.7
You Asked
Dear Tom,

Thanks for taking new questions. My question is related to Analytical Functions.

Table Data
==========
The table data(or inner select statement's output) is like below.

UserID    RefDate        GroupID    Flag    Activity    Count
-----    -------------    -----    ------    ------------    ----
U1    6/3/03 2:27         A         0          'D'        1
U1    6/9/03 7:47         B         0          'D'        1

U2    6/2/03 15:34         C         0          'D'        1
U2    6/2/03 15:37         D         0          'D'        1

U3    9/9/02 18:40         C         0          'D'        1
U3    9/9/02 18:45         E         0          'D'        1

U4    10/29/02 18:05         C         0          'R'        1
U4    10/29/02 19:59         E         0          'R'        1
U4    10/29/02 21:23         D         0          'R'        1

I have to group them by Activity and **FIRST** GroupID of the UserID.
U1's First GroupID is A (based on RefDate or sort by refDate).
So U1's both records count (here each records couunt is 1 -- last column) will
be reported under GROUPID "A"

GroupID    Flag    Activity    Count
-----    -----    ---------    -----
A        0            D        2

Expected output for the above table should be..

GroupID    Flag    Activity    Count
-----    -----    ---------    -----
A        0           D        2
C        0           D        4
C        0           R        3

Is there any Analytical Functions to make this job easier?
Kindly give me some hint on this.
and we said...
ops$tkyte@ORA9IR2> select first_value(groupid)
  2           over (partition by userid order by refdate)  groupid,
  3         flag,
  4         activity,
  5         cnt
  6    from t a
  7  /

G       FLAG A        CNT
- ---------- - ----------
A          0 D          1
A          0 D          1
C          0 D          1
C          0 D          1
C          0 D          1
C          0 D          1
C          0 R          1
C          0 R          1
C          0 R          1

9 rows selected.

ops$tkyte@ORA9IR2> select groupid, flag, activity, sum(cnt)
  2    from (
  3  select first_value(groupid)
  4           over (partition by userid order by refdate)  groupid,
  5         flag,
  6         activity,
  7         cnt
  8    from t a
  9         )
 10   group by groupid, flag, activity
 11  /

G       FLAG A   SUM(CNT)
- ---------- - ----------
A          0 D          2
C          0 D          4
C          0 R          3
Review & Followup

Rating: 3
OK March 14, 2004
Reviewer: Jacob from CT,USA
Dear Sir,
Could you please specify a simple example for "How lag or
lead function can be used to replace a * self join*?Please
do reply.
Bye!
Followup:
report requirement:

show employees sorted by hiredate and display the time between the current
employees hiredate and the prior/next hiredates by deptno (or think of an audit
trail for example -- show the period of times between accesses):

scott@ORA9IR2> select deptno,
  2         ename,
  3         hiredate,
  4             hiredate-lag(hiredate)
  5             over (partition by deptno order by hiredate) last_hire,
  6             lead(hiredate) over (partition by deptno
  7             order by hiredate) - hiredate next_hire
  8    from emp
  9   order by deptno, hiredate
 10  /

    DEPTNO ENAME      HIREDATE   LAST_HIRE  NEXT_HIRE
---------- ---------- --------- ---------- ----------
        10 CLARK      09-JUN-81                   161
        10 KING       17-NOV-81        161         67
        10 MILLER     23-JAN-82         67
        20 SMITH      17-DEC-80                   106
        20 JONES      02-APR-81        106        245
        20 FORD       03-DEC-81        245        371
        20 SCOTT      09-DEC-82        371         34
        20 ADAMS      12-JAN-83         34
        30 ALLEN      20-FEB-81                     2
        30 WARD       22-FEB-81          2         68
        30 BLAKE      01-MAY-81         68        130
        30 TURNER     08-SEP-81        130         20
        30 MARTIN     28-SEP-81         20         66
        30 JAMES      03-DEC-81         66

14 rows selected.

scott@ORA9IR2>
scott@ORA9IR2> select deptno,
  2         ename,
  3             hiredate,
  4             (select a.hiredate-max(b.hiredate)
  5                from emp b
  6                   where b.deptno = a.deptno
  7                     and b.hiredate <= a.hiredate
  8                     and b.empno <> a.empno ) last_hire,
  9             (select min(b.hiredate)-a.hiredate
 10                from emp b
 11                   where b.deptno = a.deptno
 12                     and b.hiredate >= a.hiredate
 13                     and b.empno <> a.empno ) next_hire
 14    from emp a
 15   order by deptno, hiredate
 16  /

    DEPTNO ENAME      HIREDATE   LAST_HIRE  NEXT_HIRE
---------- ---------- --------- ---------- ----------
        10 CLARK      09-JUN-81                   161
        10 KING       17-NOV-81        161         67
        10 MILLER     23-JAN-82         67
        20 SMITH      17-DEC-80                   106
        20 JONES      02-APR-81        106        245
        20 FORD       03-DEC-81        245        371
        20 SCOTT      09-DEC-82        371         34
        20 ADAMS      12-JAN-83         34
        30 ALLEN      20-FEB-81                     2
        30 WARD       22-FEB-81          2         68
        30 BLAKE      01-MAY-81         68        130
        30 TURNER     08-SEP-81        130         20
        30 MARTIN     28-SEP-81         20         66
        30 JAMES      03-DEC-81         66

14 rows selected.

scott@ORA9IR2>
Rating: 5
Excellent Tom!!! March 15, 2004
Reviewer: Sami
Rating: 5
Sequence Number reset - based on category March 23, 2004
Reviewer: Sami
Dear Tom,

Table Data is like below

UserID    RefDate
-----    -------------
U1        6/3/03 2:27
U1        8/9/03 7:47
U1        3/14/03 7:47
U1        6/17/03 7:47

U2        6/2/03 15:34
U2        9/2/03 15:37

U3        8/9/02 18:40
U3        9/9/02 18:45

U4        10/29/02 18:05
U4        10/29/02 19:59
U4        10/29/02 21:23

I need fetch the above data and insert into another temporary reporting table
like below.

Seq#    UserID    RefDate
----    -----    -------------
1    U1        6/3/03 2:27
2    U1        8/9/03 7:47
3    U1        3/14/03 7:47
4    U1        6/17/03 7:47

1    U2        6/2/03 15:34
2    U2        9/2/03 15:37

1    U3        8/9/02 18:40
2    U3        9/9/02 18:45

1    U4        10/29/02 18:05
2    U4        10/29/02 19:59
3    U4        10/29/02 21:23

Is there any Analytical Function can be used to achive this?
Followup:
row_number() over (partition by userid order by refdate)
March 23, 2004 Steve -- Thanks for the question regarding "Explain Plan", version 8.1.7
You Asked
Tom:
couple of questions:

Say i have a query. I do an explain plan and see the cost as 1278 ( an example).
I rewrite it  and lets say the cost becomes 200. Does it necessarily mean that
the second one will execute
faster than the first one??. I was reading one of your posts :
http://asktom.oracle.com/pls/ask/f?p=4950:8:7977184601576032730::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:9422487749968,
In the above query, you get to know the best one only after executing with
autotrace on. Is there anyway to know that before execution??

If cost is not the deciding factor , is there anyway to figure out which one
might have better performance  before executing the query??

**********************
I have two different tables and two different  files
Is it possible to have one control file to load these two files in to the two
tables..should i have to write seperate ones??i know loading one file to
multiple tables..but can this be done?
and we said...
No, it does not.

It "should", but it might not.

quiz -- which query runs faster:

big_table@ORA9IR2> set autotrace traceonly explain
big_table@ORA9IR2> select /*+ FIRST_ROWS */ * from big_table where id > 1000;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=16401
Card=998617 Bytes=99861700)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=16401 Card=998617
Bytes=99861700)
   2    1     INDEX (RANGE SCAN) OF 'BIG_TABLE_PK' (UNIQUE) (Cost=2088
Card=998617)

big_table@ORA9IR2> alter session set optimizer_index_cost_adj=10;

Session altered.

big_table@ORA9IR2> alter session set optimizer_index_caching=90;

Session altered.

big_table@ORA9IR2> select /*+ FIRST_ROWS */ * from big_table where id > 1000;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=1641 Card=998617
Bytes=99861700)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=1641 Card=998617
Bytes=99861700)
   2    1     INDEX (RANGE SCAN) OF 'BIG_TABLE_PK' (UNIQUE) (Cost=2088
Card=998617)

The cost is the result of an algorithm, the algorithm makes some assumptions,
uses available statistics and has many knobs (parameters) to adjust it.

you would have separate control files for your purpose.  the file name can be
supplied on the command line (data=....) but the tablename is sort of "in there"
Review & Followup

Rating: 5
March 23, 2004
Reviewer: A reader
so,you mean to say that you cannot rely on cost??
whats the use of cost if its misleading???
Followup:
cost is not misleading

cost is just a number, a number that is the result of an algorithm

an algorithm that takes many inputs, many parameters

an algorithm that given all of the inputs and parameters returns the query plan
with the "lowest cost" for that particular query

an algorithm that if the inputs are correct, the parameters good -- returns the
query plan that should run the fastest.

an algorithm implemented by mere humans, so........
Rating: 2
what about 10g? March 23, 2004
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
Tom,

I so that the question is related to 8i, but I remember reading somewhere that
in 10g, the cost is now based on a time model. i.e. higher cost = more time, not
on an "artifical number". Is that so ?

I have not yet had a chance to play arround extensivelly with this, but I did
notice that in 10g, when you use dbms_xpln you get a new "TIME" column. I have
not yet had a chance to see how accurate it is. What are your observations ?
Followup:
it is a mathematical model

it is an algorithm

it is trying to give a relative cost in time.

it is however not perfect (if you come up with a perfect one, let me know, we
have a job for you)
March 23, 2004 Owen -- Thanks for the question regarding "Mized size of packages causes inefficiency?", version 8.1.7
You Asked
Hi

Until recently I worked at a company where the policy was to have packages
matched for size, that is, as far as possible all packages were to be as close
as possible to the same, maximum size. This was alleged to improve performance
by reducing memory fragmentation. (Note that a consequence of this was that
routines were essentially grouped randomly throughout the set of packages, and
not by any idea of common function or such-like.)

I couldn't find any evidence to support this policy, nor was any ever provided,
but it was the policy nonetheless. Could you shed some light on it for me? Is it
a sensible but little-known technique, or just misguided?

Thanks in advance

Owen Phelps
and we said...
Sounds like a new myth to me.  I've never heard of such a thing.

Besides, a 1,000 line package might compile to be less p-code then a 100 line
package.

The fact is -- we page in code in 4k'ish chunks, regardless of size.  Once upon
a time, many many versions ago, we paged in entire packages/procedures -- for
many years now, that has not been the case.

So, it is a myth with a kernel of (old) truth behind it -- but an "unachievable"
myth.  Were the developers supposed to sit there and add/remove code until the
compiled code_size was "right on"???  Impossible, totally impossible.

Totally "non-sensible" and hopefully an almost unknown 'technique'

Reminds me of my Ada programming days.  Someone tried to make me ALPHABETIZE my
subroutines -- declare them in order alphabetically, define them in order --
alphabetically.  They didn't like to read code on "screen", they wanted to print
it out to read it and it would help them find stuff (since they couldn't
"search" the paper).  The end result - I wrote a script to rename all of my
routines to:

   aa_some_function;
   ab_another_function;
   ac_......

and so on (and used a big "pragma renames" at the bottom so I didn't have to
hack the rest of the code, sort of like a synonym for a procedure).  Met the
letter of the law, but didn't do anything for anyone.

Here is a query you can use to see that this is totally futile:

  1  select trunc(source_size/1024) src_size_k,
  2        min(code_size),
  3        max(code_size),
  4        max(code_size)-min(code_size) diff,
  5        count(code_size) cnt
  6    from dba_object_size
  7   where source_size > 0
  8     and type in ( 'PROCEDURE', 'FUNCTION', 'PACKAGE BODY' )
  9   group by trunc(source_size/1024)
 10  having max(code_size)-min(code_size) > 0
 11*  order by 4
ops$tkyte@ORA817DEV.US.ORACLE.COM> /

SRC_SIZE_K MIN(CODE_SIZE) MAX(CODE_SIZE)       DIFF        CNT
---------- -------------- -------------- ---------- ----------
        68          16006          16492        486          2
        21           6187           6722        535          3
        24           6120           6738        618          2
        12           1867           2627        760          3
        28           5684           6994       1310          2
        74          17689          19007       1318          3
        20           4954           6555       1601          2
        26           4135           6074       1939          3
        18           3170           5187       2017          3
       150          30077          32203       2126          2
         0            149           2383       2234         65
         1              0           2239       2239         19
        67          15850          18342       2492          2
         9           1450           4582       3132          5
        10           3191           6597       3406          3
        31           6554           9987       3433          3
        16           1469           5034       3565          7
        27           5006           8935       3929          2
         2            331           4375       4044          9
         4            268           4549       4281          4
         3           1502           5919       4417          2
       132          34193          38985       4792          2
         5            594           5520       4926          8
       122          25256          31011       5755          2
         6           1604           8053       6449          5
        47           6113          12649       6536          2
        11           2736           9588       6852          3
         7           1135           9943       8808          4
        15           4680          16379      11699          2
        13           3321          15039      11718          4
        22           3289          16350      13061          7
        32          21828          35886      14058          2
        54          15473          30590      15117          2
         8           2671          17999      15328          5
        25           4652          21423      16771          3
        23           5379          22723      17344          3
        38           8972          26923      17951          2
        34           6352          24784      18432          2
        17           3951          24080      20129          3
        45          10423          44512      34089          3
        71           8215          57543      49328          3

41 rows selected.

So, for example, there is some code out there that is 71k in size.  The smallest
results in about 8k of p-code, the largest in about 57k.  Hmmm, what would they
do then???
Review & Followup

Rating: 5
I love those demystifications !!! June 06, 2002
Reviewer: Andre from Brazil
I get "mystified" at how people think up such stuff. Have I told you about a
former (luckily) boss of mine, a "senior" DBA, who asked me to reduce the number
of free-lists because they were taking up too much space ? Yep, believe me, it's
the naked truth...
Followup:
Well, there is a nuggest of truth in there as well.

A session is associated with a freelist.  If you have 10 freelists and a single
session doing inserts -- it'll only use one of the freelists -- the others will
not be used.  We'll grab a new extent without considering the existing freespace
that is there.

So the freelists don't take up space really -- but they can lead to "less
efficient space utilization" if you have way more then you really need.  (stress
CAN -- eg might -- eg don't worry about it 99.999% of the time)
Rating: 5
Great, thanks. June 06, 2002
Reviewer: Owen from UK
That's much what I had expected, but it's great to have it confirmed. Thanks
very much!
Rating: 5
continued June 07, 2002
Reviewer: Andre from Brazil
Thanks for the feedback on the freelist stuff. In fact I had heard of it and it
makes sense, but surely it wasn't the case with my boss. As a matter of fact he
had no idea what a free list was all about...
Rating: 5
Package size March 23, 2004
Reviewer: mani from USA
Hi Tom,
We provide software services to our clients. One of the modules is reporting.
Currently we have created one package for each client.
Each package can have 15 to 20 huge procedures ( approx  150 lines each
procedure ). Each procedure relates to custom report.
1) A client user may just request one report, in this case since the report
procedure is in package, does the entire package get loaded in memory ( I know
4K chunks ).
That means extra 4K chunks are allocated , where in first place get could be
avioded.
2) I know you say its common sense to group procedures in packages, does this
mean only dependent procedures or procedures which belong to one transaction be
put in one package.
3) In my case the Client wise package is a business wise logical package , but
since client users may call reports independently the procedures doesn't seem to
be logically
bound in terms of database angle.
4) When a procedure ( in a package ) is called , does oracle load the entire
package in memory or just that procedure.

Please advice,
Mani
Followup:
loads by piece, but 150 lines is hardly "huge".

use the package to make your life "easier", do what makes most sense from a
programming point of view.
Rating: 4
Mixed Sizes of Packages causes inefficiency? April 06, 2004
Reviewer: Steve Booth from Pewaukee, WI USA
Hope you're having a good vacation. Thanks for the info regarding package sizes.
If you wouldn't mind, do you have any rule of thumb regarding overall sizes of
packages? What's too big? Does "Debug" make much of a difference? I used a
compile command without seeing any change so I probably screwed it up.

I had been looking for where I could find out package sizes. Thanks again!
March 23, 2004 -- Thanks for the question regarding "Performance comparison between SQL and PL/SQL stored procedures", version 8.1.6
You Asked
hi tom,

we are using jdbc to access the database via stored procedures. however, we
intend on developing our product across multiple DB platforms such as SQL Server
and DB2. This poses a problem of sorts for us. Since we are heavily dependent on
stored procedures, this will mean coding the same business logic in stored
procedures for SQL Server and DB2. This could mean a maintenance/logistical
nightmare for us.

Someone here has recommended converting all the stored procedures to standard
SQL so that we can have the portability we desire. However, another issue
confronts me and that is performance of SQL versus stored procedures. I need to
prove that PL/SQL stored procs are much faster than SQL and are preferrable for
enterprise scale applications.
Can you please shed some light on my dilemma and what would you do in my
situation. Please be mindful of the fact that we do get some really huge volumes
of data. Please give pros and cons of both i.e. SQL and stored procedures.

Many Thanks

Rajat
and we said...
Well, I've always found the porting issue between various databases to be
trivial when compared to the "logic" issues.  the same exact set of sql executed
in the same exact sequence on the same exact data will produce two entirely
different results when executed in different databases.

How will you code your application so that

- it is independent of the fact that Oracle gives you non-blocking queries,
whereas the others will not.  The application that works fine in Oracle may/may
not work find in the others.

- Oracle gives you consistent reads, others will not.  See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:431163879856
for a somewhat amusing story with regards to that.

Basically, you may well get different answers to the same queries run in the
SAME exact set of circumstances on different database engines.

- it is independent of the locking model employed by the database?  Concurrency
control is the major differentiator between databases.

And the list goes on.  Stored procedures will not only POSSIBLY provide a
performance boost (you can write bad code in any language), but since you will
need to implement them for each database -- they will give you the opportunity
to exploit the underlying database fully.  They will give you the opportunity to
solve some of the above issues (the logic you need will be different in
different databases for different things).

Unless you are building a READ ONLY application, your best bet would be stored
procedures for portability of the rest of the appication.  There will be
subtle differences, with different solutions, for each RDBMS you use.  You will
be able to exploit the underlying RDBMS to its fullest by using stored
procedures as there will be no hesitation to implement a function as it should
be given the target RDBMS.  Not as it has to be in order to be generic.

Just my 2cents worth.  I've been there, done that.  The right way to do things
varies by database. Sybase -- they need you to use temp tables, Oracle -- we
don't like you to (not necessary).  DB2 -- if you want a consistent result set,
you must use repeatable read isolationn, Oracle not so.   And so on.
Review & Followup

Rating: 5
April 03, 2001
Reviewer: Helena Markova from Bratislava, Slovakia
Rating: 3
OK March 23, 2004
Reviewer: Raju from Edinburgh,Scotland
Dear Sir,

  If I have a procedure with arguments like
   sql>create or replace procedure p(x in varchar2 default'Oracle',y
                                     out varchar2) as
       begin

       /* procedure body*/
       end;
       /
 Then how to make a call to the procedure?
  I tried options like
  sql>exec p(default,:a)
  sql>exec p(' ',:a)
  sql>exec p((),:a)
  sql>exec p(,:a)-- Does the work but throws errors.
How to get around with this?Could you please provide a solution?
Bye!
Followup:
exec p( y => :a );

use named notation if you want X to be defaulted.
Rating: 3
Thanks March 23, 2004
Reviewer: Raju from Edinburgh,Scotland
Dear Sir,
Thanks for your reply.But I tried the other option using
Call like

SQL> call rev( y => :a);
call rev( y => :a)
            *
ERROR at line 1:
ORA-00907: missing right parenthesis
I think it must be some sort of
 SQL> Call rev() into :a
But how we can pass the arguments in the Call method of
invoking a procedure?
Please do reply.
Bye!
Followup:
SQL> EXEC p(....

SQL> begin p( y=> :a); end;
  2> /

I never use call myself.  I've given two ways known to work....  Not sure if
call supports that or not.
March 24, 2004 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!!!!
Rating: 3
Nice January 25, 2004
Reviewer: Siva from Hyderabad,India
Dear sir,
Please look at the following query

 sql>select ename,sal,deptno from emp e where
sal > (select avg(sal) from emp where deptno = e.deptno
 group by deptno)
Are there any other ways to put this query?
Bye!
Followup:
sure

select *
  from (
select ename, sal, deptno, avg(sal) over (partition by deptno) avg_sal
  from emp
       )
 where sal > avg_sal;
Rating: 4
Nice January 26, 2004
Reviewer: Siva from Hyderabad,India
Dear sir,
Thanks for your response.Sorry for disturbing you again.
Any other ways to put the following query?
 1)select hiredate from emp where hiredate like '%DEC%';
 2)select hiredate from EMP where instr(hiredate,'DEC') >0;
 3)select hiredate from emp where substr(hiredate,4,3) = 'DEC';
I don't want to use to_char(hiredate,...)
Do you some other ways?
Please do reply.
Bye!
Followup:
compare DATES to DATES.

strings to strings

and numbers to numbers

never never compare a date to string (like you are).  dates are dates, dates are
NOT strings.  You want to use an explicit conversion in order to do what you are
trying to do.

YOU HAVE TO USE TO_CHAR if you want this to resemble anything near "correctly
done".

You are relying on default date formats and the english language being in place.
 Neither of which is assured.

the way to do this:

select * from emp where to_char(hiredate,'mm') = '12';

compare strings to strings -- never dates to strings.  You'll never be sorry.
Rating: 5
January 26, 2004
Reviewer: A reader
Tom,

I took this code from above and pasted in vi so that i can generate insert
statement.

03         0311111111111          10000
03         0311111111111          23000
03         0311111111111           5000
01         0101392301528         100000
05         0592999972455        5600000

Can you please show me the command in vi to put , (comma) after every column

Thanks.
Followup:
welcome to MY world.

to make that be an insert I would:

:1,$s/^/insert into t values (/
:1,$s/$/);/

and then just position the cursor on some whitespace in there and hit "r," to
turn a space into a comma and then "down arrow + period" to do it on the next
line and so on.
Rating: 5
January 26, 2004
Reviewer: A reader
Thanks ... I like your world. :-)
Rating: 5
January 26, 2004
Reviewer: A reader
Tom,

Instead of using replace and then , with down arrow & period for repitition is
there some command that will do that at once.

Thanks.
Followup:
:1,$s/.{5,5}/&,/

will add a comma in column 5 (using vim anyway -- might depend on your editor)
Rating: 4
I am trying to use this alternate update form March 19, 2004
Reviewer: Paul D. Phillips from Orlando USA
I am reading this with great interest, as I hate the need to go through a table
twice to do one update, once to insure that a update value is there for the
target row ( the one to be updated ) and then a second time to get the update
value to apply to the target.

I am attempting to use this form of inline view, I think that is what it is, but
am encountering an ORA error that I do not think I should be getting.  I'm
running 9.2.0.4.

The standard form of the query is:
UPDATE INSURED_LIVES i
SET i.Age =
( SELECT
m.Observed AGE_M
FROM
MEASURES m
WHERE
i.Policy_Holder_ID = m.Policy_Holder_ID
and i.Suffix_ID = m.Suffix_ID
and m.Measid = 'Age' )
WHERE EXISTS
( SELECT *
FROM
MEASURES m
WHERE
i.Policy_Holder_ID = m.Policy_Holder_ID
and i.Suffix_ID = m.Suffix_ID
and m.Measid = 'Age' );

The new form of the query, is ( I think? ):
UPDATE
( SELECT
i.Age Age_I,
m.Observed AGE_M
FROM
INSURED_LIVES i,
MEASURES m
WHERE
i.Policy_Holder_ID = m.Policy_Holder_ID
and i.Suffix_ID = m.Suffix_ID
and m.Measid = 'Age' )
SET Age_I = Age_M ;

But when I attempt to run this, I get:
ORA-01779 cannot modify a column which maps to a non key-preserved table

What is confusing me is both the tables involved are primary keyed. The
Insured_Lives table is PKed by the 2 I. columns and the Measures table is PKed
by the 3 M. columns refered to in the WHERE clause.

Any insights into why I'm encountering this will be VERY, very helpful. Doing
DSS work through multi million row tables, one less pass represents an enourmous
improvement.

And, by the way, your previous help with analytic operators has proven to be a
major productivity boost for me, also.
Followup:
well, i guess, that since
http://otn.oracle.com/products/oracle9i/
pdf/dss_enviroment_9i.pdf

seems to "officially" talk about it -- so can i.

UPDATE /*+ BYPASS_UJCV */
( SELECT i.Age Age_I, m.Observed AGE_M
    FROM INSURED_LIVES i, MEASURES m
   WHERE i.Policy_Holder_ID = m.Policy_Holder_ID
     and i.Suffix_ID = m.Suffix_ID
     and m.Measid = 'Age' )
   SET Age_I = Age_M ;

google bypass_ujvc or search on this site for a note or two.
Rating: 3
Please see this March 19, 2004
Reviewer: Catherine from OH,USA
Dear Sir,
 I tried the following queries.
SQL> SHO REL
     release 902000100
SQL> select ename,deptno,sal from emp e where sal >
  2* (select avg(sal) from emp where deptno = e.deptno group by deptno)
SQL> /

ENAME          DEPTNO        SAL
---------- ---------- ----------
ALLEN              30       1600
JONES              20       2975
BLAKE              30       2850
SCOTT              20       3000
KING               10       5000
FORD               20       3000

6 rows selected.

SQL> with a as (select avg(sal) as avg_sal from emp group by deptno),
  2       b as (select ename,deptno,sal from emp )
  3*     select ename,deptno,sal from b where b.sal > a.avg_sal
SQL> /
    select ename,deptno,sal from b where b.sal > a.avg_sal
                                                 *
ERROR at line 3:
ORA-00904: "A"."AVG_SAL": invalid identifier

Is not this a valid sql approach?What's wrong over there?
Please do reply.
Followup:
you did not query A in your second example. you only have from b

you'll need to JOIN to A by deptno.
Rating: 3
Still Not Working March 20, 2004
Reviewer: Catherine from OH,USA
Dear Tom,
 Still the query doesn't work.

SQL> with a as(select deptno,avg(sal) as avg_sal from emp group by deptno),
  2       b as(select deptno,ename,sal from emp)
  3   select deptno,ename,sal from b where deptno = a.deptno and sal >
a.avg_sal;
 select deptno,ename,sal from b where deptno = a.deptno and sal > a.avg_sal
                                                                  *
ERROR at line 3:
ORA-00904: "A"."AVG_SAL": invalid identifier
Rating: 4
Catherine Hope this helps March 20, 2004
Reviewer: A reader
SQL> with a as (select deptno ,avg(sal) as avg_sal from emp group by deptno),
  2           b as (select ename,deptno,sal from emp )
  3        select ename,a.deptno,sal from b ,a where b.sal >  a.avg_sal and
a.deptno = b.deptno ;

ENAME          DEPTNO        SAL
---------- ---------- ----------
KING               10       5000
CLARK              10       2450
FORD               20       3000
SCOTT              20       3000
JONES              20       2975
BLAKE              30       2850
ALLEN              30       1600

7 rows selected.

with the With clause you just pre defined the 2 queries.But you forget to join
them in the select statement.Thats what Tom the great meant.
Rating: 4
BYPASS_UJVC still does not work March 23, 2004
Reviewer: Paul from Orlando, USA
Tom, I hate to seem like I'm beating this to death, here, but I really want to
understand why this is not working.

First, the BYPASS_UJVC does not help, it gives the same ORA-01779 message.  I
copied the code right off the page and pasted it into Worksheet, and both tried
to run it and tried to Explain it and it shows the same thing both times.

Second, I still want to understand what KEY-PRESERVED means, that was part of my
(implied?) question.

If both tables have Primary Keys, one with 2 columns and one with 3 and the one
with the 3 has the 2 from the other table and a single value for the 3rd (
obviously ), how is this not key preserved?

Simply put:

from t1 a,
     t2 b
where a.c1 = b.c1
and   a.c2 = b.c2
and   b.c3 = 'SomeLiteral' ;

If a.c1, a.c2 is the PK of a and b.c1, b.c2, b.c3 is the PK of c, how is this
not be key preserved, please?
Rating: 4
It should have read: /*+ BYPASS_UJVC */ March 23, 2004
Reviewer: Ed Sherman from Orlando, FL
Paul,

The code doesn't work if you copy and paste it in because the "C" and the "V"
are transposed in  /*+ BYPASS_UJCV */

It should have read: /*+ BYPASS_UJVC */

It worked for me like that.

As for the key-preserved tables, I'm still working on understanding what this
means too.

I'm guessing that Oracle doesn't like the fact that you are joining on two
columns of a three column composite key. When I add a composite UNIQUE
constraint to the MEASURES table on the  POLICY_HOLDER_ID and SUFFIX_ID columns:

ALTER TABLE measures
ADD CONSTRAINT u_measures UNIQUE(policy_holder_id, suffix_id);

Then the UPDATE works without using the hint.

This is probably not a feasable workaround for you though.

I'm guessing that Oracle doesn't consider the join to be key-preserved since
joining on two columns of a three-column primary key won't necessarily produce a
unique result. You took care of this by writing "AND m.Measid = 'Age'; in your
query but Oracle doesn't seem to "get it". I don't know if this is a bug or if
it is intentional.

At any rate I wanted to let you know why the hint didn't work when you copied
and pasted from the Followup.

Good Luck!
Followup:
thanks -- hate it when i transpose :(

you need to join from T1 to T2 by the full key of T2 using columns from T1, eg:

update ( select t1_column, t2_column
            from t1, t2
           where t1.pk1 = t2.pk1
             and t2.pk2 = 'X' )
 set t1_column = t2_column

will fail, it wants to see

  where t1.pk1 = t2.pk1 AND t1.pk2 = t2.pk2
Rating: 5
Wow, what a difference! March 24, 2004
Reviewer: Paul from Orlando USA
Ed and Tom, when I fixed the hint, that did work, thank you both very much.

The shocking thing is how much of an improvement this makes in the update.  It
takes less than half the time and does only 1/4 of the IO and eliminates 7 disk
sorts ( tables are parallel and partitioned ), which is where I suspect the big
time savings comes in.  I'm going to use this in selected cases, I think.

I'm still debating whether to open a TAR, though.  I think from Tom's statement
this is the intended behaviour, though why a literal is not sufficient, but a
join is, is a bit puzzling.
Rating: 5
Same results on 10g March 26, 2004
Reviewer: Ed Sherman from Orlando, FL USA
Paul,

I get the same behaviour with Oracle Database 10g on Win2K.
I had to try... You never know.

Cheers!
March 24, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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.
Rating: 4
Ok, but why nowait requires column? February 06, 2004
Reviewer: ninoslav from croatia
Hi Tom, i still didn't understand why we have to put a column in case we wanna
use nowait:

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2  cursor c is
  3  select * from emp for update of sal nowait ; <==
Followup:
because, without it, it was not working...

in 9i, it works either way.  must have been an 8i issue that was resolved with
the common sql parser in 9i
Rating: 4
SELECT privilege without FOR UPDATE March 02, 2004
Reviewer: Sami from NJ,USA
Dear Tom,

We have given SELECT rights to many developers who are developing reports. Few
of them uses SELECT ..FOR UPDATE in there code which is holding the lock for
unknown reason.

Is there a way to give SELECT privilege with out FOR UPDATE capability?
Followup:
well, it is not "unknow" why it is holding the lock, they've ASKED for it.

They are not allowed to update, but that does not prevent them from explicitly
locking data upon request and they requested it.  In fact, they could even "lock
table" if they like.

they have a bug in their code, they need to correct such a bug and understand
when to actually use "for update".  sorry, select allows you to lock.
Rating: 5
Excellent! March 03, 2004
Reviewer: Reader from MN, USA
Great information. I always wondered when one should use the select for udate.
Rating: 4
How to unlock a table March 23, 2004
Reviewer: Anand from Austin
Tom,

I issue a command :

LOCK TABLE a IN ROW SHARE MODE NOWAIT;

How do I unlock the table ? What is the command for that ? I searched for UNLOCK
TABLE or RELEASE TABLE commands but to no avail.

Thanks for your help
Followup:
commit;

rollback;

thats the only way to release locks gained by SQL like that.  you "grow" locks
in your transaction and you give them all up when the transaction finishes.
March 24, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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://download-west.oracle.com/docs/cd/A87860_01/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.
Rating: 5
utl_smtp February 07, 2004
Reviewer: Ruben
Tom, How we can accomplish to send text to wireless
devices such as a mobile or pager?
It is possible via UTL_SMTP?

Thanks a lot for you reply.
Followup:
sure, if you can email your stuff right now (like phone_number@skytell.com
works) it is sort of obvious how to do it.

If you cannot, you might need something like 9iAS wireless that does SMS and
other protocols.
Rating: 5
Email Package February 13, 2004
Reviewer: Steven Foskett from Boston, MA, USA
Hi Tom:

This was extremly straight forward and to the point. I was able to use your
example and implement it very quickly.

Great Work
Rating: 5
February 28, 2004
Reviewer: A reader
Thanks a lot Tom....The examples were great
Rating: 5
not compiling in 8.1.7.4 but compiles in 9.2.0.4.0 March 11, 2004
Reviewer: seekanth gadhiraju from tampa, fl usa
Based on you Java expample i wrote the following.  But it compiles fine and also
works fine in 9.2.0.4.0 but when i try to compile in 8.1.7.4 it compile with
errors here are the details.

My Code
-------

SQL> select version from v$instance;

VERSION
-----------------
9.2.0.4.0

SQL> create or replace and compile
  2      java source named "gsijavamail"
  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.sql.*;
 12     public class gsijavamail
 13     {
 14   public static oracle.sql.NUMBER send(String host,
 15          String to,
 16          String from,
 17          String cc,
 18          String bcc,
 19          String RtnRcpt,
 20          String RtnPath,
 21          String ReplyTo,
 22          String priority,
 23          String Subject,
 24          String text,
 25          String filename)
 26   throws AddressException, SendFailedException, MessagingException{
 27   int rc = 0;
 28   try {
 29   Properties properties = System.getProperties();
 30   properties.put("mail.smtp.host",host);
 31   Session session = Session.getInstance(properties,null);
 32   MimeMessage message = new MimeMessage(session);
 33
 34   MimeBodyPart attachment = new MimeBodyPart();
 35   Multipart mp = new MimeMultipart();
 36   MimeBodyPart mtxt = new MimeBodyPart();
 37   mtxt.setContent(text,"text/html");
 38   mp.addBodyPart(mtxt);
 39
 40   message.setFrom(new InternetAddress(from));
 41   message.setRecipients(Message.RecipientType.TO,InternetAddress.parse(to));
 42   message.setSubject(Subject);
 43   message.setText(text);
 44   message.setSentDate(new Date());
 45
 46   if (priority != null)
 47   {
 48    message.addHeader("X-Priority", priority);
 49   }
 50   if (priority == "1")
 51   {
 52    message.addHeader("Priority", "Urgent");
 53    message.addHeader("Importance", "high");
 54   }
 55
 56   if (cc != null)
 57   {
 58
message.setRecipients(Message.RecipientType.CC,InternetAddress.parse(cc));
 59   }
 60
 61   if (bcc != null)
 62   {
 63
message.setRecipients(Message.RecipientType.BCC,InternetAddress.parse(bcc));
 64   }
 65
 66   if (RtnRcpt != null)
 67   {
 68    message.addHeader("Return-Receipt-To:",RtnRcpt);
 69   }
 70   if (RtnPath != null)
 71   {
 72    message.addHeader("Return-Path:", RtnPath);
 73   }
 74   if (ReplyTo != null)
 75   {
 76    message.addHeader("Reply-To:", ReplyTo);
 77   }
 78
 79   if (filename != null )
 80   {
 81    File f = new File(filename);
 82    FileDataSource fds = new FileDataSource(f);
 83    attachment.setDataHandler(new DataHandler(new FileDataSource(f)));
 84    attachment.setFileName(fds.getName());
 85    mp.addBodyPart(attachment);
 86   }
 87
 88   message.setContent(mp);
 89   Transport.send(message);
 90   rc = 1;
 91   } catch (Exception e)
 92    {rc = 0;
 93    }
 94    finally {
 95             return new oracle.sql.NUMBER(rc);
 96            }
 97
 98   }
 99  }
100
101  /

Java created.

in 8.1.7.4
--------
SQL> select version from v$instance;

VERSION
-----------------
8.1.7.4.1

SQL> create or replace and compile
  2      java source named "gsijavamail"
  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.sql.*;
 12     public class gsijavamail
 13     {
 14   public static oracle.sql.NUMBER send(String host,
 15          String to,
 16          String from,
 17          String cc,
 18          String bcc,
 19          String RtnRcpt,
 20          String RtnPath,
 21          String ReplyTo,
 22          String priority,
 23          String Subject,
 24          String text,
 25          String filename)
 26   throws AddressException, SendFailedException, MessagingException{
 27   int rc = 0;
 28   try {
 29   Properties properties = System.getProperties();
 30   properties.put("mail.smtp.host",host);
 31   Session session = Session.getInstance(properties,null);
 32   MimeMessage message = new MimeMessage(session);
 33
 34   MimeBodyPart attachment = new MimeBodyPart();
 35   Multipart mp = new MimeMultipart();
 36   MimeBodyPart mtxt = new MimeBodyPart();
 37   mtxt.setContent(text,"text/html");
 38   mp.addBodyPart(mtxt);
 39
 40   message.setFrom(new InternetAddress(from));
 41   message.setRecipients(Message.RecipientType.TO,InternetAddress.parse(to));
 42   message.setSubject(Subject);
 43   message.setText(text);
 44   message.setSentDate(new Date());
 45
 46   if (priority != null)
 47   {
 48    message.addHeader("X-Priority", priority);
 49   }
 50   if (priority == "1")
 51   {
 52    message.addHeader("Priority", "Urgent");
 53    message.addHeader("Importance", "high");
 54   }
 55
 56   if (cc != null)
 57   {
 58
message.setRecipients(Message.RecipientType.CC,InternetAddress.parse(cc));
 59   }
 60
 61   if (bcc != null)
 62   {
 63
message.setRecipients(Message.RecipientType.BCC,InternetAddress.parse(bcc));
 64   }
 65
 66   if (RtnRcpt != null)
 67   {
 68    message.addHeader("Return-Receipt-To:",RtnRcpt);
 69   }
 70   if (RtnPath != null)
 71   {
 72    message.addHeader("Return-Path:", RtnPath);
 73   }
 74   if (ReplyTo != null)
 75   {
 76    message.addHeader("Reply-To:", ReplyTo);
 77   }
 78
 79   if (filename != null )
 80   {
 81    File f = new File(filename);
 82    FileDataSource fds = new FileDataSource(f);
 83    attachment.setDataHandler(new DataHandler(new FileDataSource(f)));
 84    attachment.setFileName(fds.getName());
 85    mp.addBodyPart(attachment);
 86   }
 87
 88   message.setContent(mp);
 89   Transport.send(message);
 90   rc = 1;
 91   } catch (Exception e)
 92    {rc = 0;
 93    }
 94    finally {
 95             return new oracle.sql.NUMBER(rc);
 96            }
 97
 98   }
 99  }
100
101  /

Warning: Java created with compilation errors.

SQL> show err
Errors for JAVA SOURCE gsijavamail:

LINE/COL ERROR
-------- -----------------------------------------------------------------
0/0      gsijavamail:23: Class AddressException not found in throws.
0/0      gsijavamail:28: Class Session not found.
0/0      gsijavamail:28: Class Session not found.
0/0      gsijavamail:28: Undefined variable or class name: Session
0/0      gsijavamail:29: Class MimeMessage not found.
0/0      gsijavamail:29: Class MimeMessage not found.
0/0      gsijavamail:31: Class MimeBodyPart not found.
0/0      gsijavamail:31: Class MimeBodyPart not found.
0/0      gsijavamail:32: Class Multipart not found.
0/0      gsijavamail:32: Class MimeMultipart not found.
0/0      gsijavamail:33: Class MimeBodyPart not found.

LINE/COL ERROR
-------- -----------------------------------------------------------------
0/0      gsijavamail:33: Class MimeBodyPart not found.
0/0      gsijavamail:49: Variable message may not have been initialized.
0/0      gsijavamail:55: Variable message may not have been initialized.
0/0      gsijavamail:60: Variable message may not have been initialized.
0/0      gsijavamail:65: Variable message may not have been initialized.
0/0      gsijavamail:69: Variable message may not have been initialized.
0/0      gsijavamail:73: Variable message may not have been initialized.
0/0      gsijavamail:79: Class FileDataSource not found.
0/0      gsijavamail:79: Class FileDataSource not found.
0/0      gsijavamail:85: Variable message may not have been initialized.
0/0      gsijavamail:86: Undefined variable or class name: Transport

LINE/COL ERROR
-------- -----------------------------------------------------------------
0/0      Info: 22 errors
SQL> spool off;
Followup:
you didn't load the java packages into the db for 8i
Rating: 4
How to load March 11, 2004
Reviewer: sreekanth gadhirjau from tampa, fl usa
tom,

on  9.2.0.4.0  i did not load any classes explicitly. I just compiled.  here is
what happend.

we are running oracle 11i application on 8.1.7.4.  We are in the middle of
upgrading the database to 9i release 2.  So we cloned the test system from
production 11i system and then upgraded the database to 9i. now this one
compiles in 9i but not in 8i.

Do i need to download the javamail api from sun site to load these classes?
Please tell me how to load these classes.

thanks
sreekanth.g
Followup:
see above, the above example (step by steps) were developed for 8i.  the step by
step are there in the original answer.
Rating: 5
error while loading javaclass with loadjava March 12, 2004
Reviewer: sreekanth gadhiraju from tampa, fl usa
tom,

when try to load mailapi using the loadjava it is giving the following error

C:\mailapi\tmp>loadjava -u SYS/password0@NINE -o -r -v -f -noverify -synonym -g
public mail8i.zip >output.txt
arguments: '-u' 'SYS/password@NINE' '-o' '-r' '-v' '-f' '-noverify' '-synonym'
'-g' 'public' 'mail8i.zip'
creating : resource META-INF/javamail.charset.map
SQL Error while connecting with oci8 driver to NINE: ORA-01031: insufficient pri
vileges

exiting  : could not open connection

thanks
sreekanth.g
Followup:
ctl-f 1031 <enter>
Rating: 4
smtp error while sending an email March 12, 2004
Reviewer: Eugene from Seattle
Hi Tom,
Hope you can tell me why I may get this error?

CREATE OR REPLACE PROCEDURE Ep_Sendmail_1
                                                (p_sender IN VARCHAR2,
                                          p_recipient IN VARCHAR2,
                                          p_message   IN VARCHAR2)
AS
     l_mailhost VARCHAR2(255) := '192.168.1.6';
     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 Ep_Sendmail_1;
/

SQL> begin
  2  ep_sendmail_1('epipko@unionbay.com',
  3  'epipko@unionbay.com',
  4  'Hello there');
  5  end;
  6  /

PL/SQL procedure successfully completed.

Then our email program gives us following errors:

X-Mail-Format-Warning: Bad RFC2822 header formatting in MAIL
FROM:epipko@unionbay.com
X-Mail-Format-Warning: Bad RFC2822 header formatting in RCPT
TO:epipko@unionbay.com
X-GWIA: Thu, 11 Mar 2004 13:41:45 -0800; 192.168.1.6([192.6.1.80])
Received: from 192.168.1.6
 ([192.6.1.80])
 by unionbay.com; Thu, 11 Mar 2004 13:41:45 -0800
X-Mail-Format-Warning: Bad RFC2822 header formatting in Hello
Followup:
what "email" program??  I mean, how does an email program fit into this?
Rating: 4
what is ctl-f 1031 <enter> March 13, 2004
Reviewer: sreekanth gadhiraju from tampa, fl usa
tom,

can you please explain ctl-f 1031 <enter>  ?

thanks
sreekanth.g
Followup:
search for 1031 on this page -- its been asked and answered in the past.

ctl-f should pop up the little "search" dialog
1031 is the number you want to search for
<enter> will do it (the search)....
Rating: 3
e mail March 23, 2004
Reviewer: alex from uk
Tom

I am having trouble in tacking the undelivered mails using SMTP.
Can you please advise me who can we tracke these
Followup:
when I send an email using utl_smtp from asktom

and it is not deliverable

it comes back to me, in my inbox.  The error will goto whomever "sent" it (you
can use an errors-to: record as well, or something like that, you'd just google
the smtp protocol and see what options you have)

Soooo, whatever you are using in the from should be getting the "bounce".  I
sure do :)
March 24, 2004 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://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76965/toc.htm
is an absolute MUST READ

and
http://download-west.oracle.com/docs/cd/A87860_01/doc/appdev.817/a76939/toc.htm
is another you MUST READ

and you will find
http://download-west.oracle.com/docs/cd/A87860_01/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://download-west.oracle.com/docs/cd/B10501_01/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.
Rating: 2
Matrix Programming January 29, 2004
Reviewer: Denise from Virginia,USA
Hi Tom

Is PL/SQL designed and capable of producing data matrix
manipulation?

We are currently outsourcing specially designed Matrix
Step Reports(based on Largest Gift vs Last Gift Date)along
with numerous other matrix reports
that we are considering doing in-house to scale back
on costs.

I have Scott Urman's book Advanced PL/SQL Programming..
however I don't see any mention or reference to matrix
programming.

I've been looking into the GAUSS Programming Language..
(do you know anything about it?).

Could it be written in 'C'...my 'C' is very limited..
however I did write a tic-tac-toe game in 'C'(which is
kinda matrix in concept) for a school project some years
ago.

Anyhow..if you have any suggestions or recommendations
that would be great.

btw...I ordered the Beginning Oracle Programming book..
I like the style and hands-on exercises..even though
the early chapters are very elementary and rudimentary
some of the later chapters will definitely help me with
debugging PL/SQL, SQL Function Reference, Indexing...etc.

tata..and take care
Followup:
You'd have to describe what you mean by "matrix programming"

"largest gift" vs "last gift" sounds alot like "analytics"
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96520/analysis.htm#1020
Rating: 5
Thanks Tom!! January 29, 2004
Reviewer: Denise from Virginia,USA
I forgot about the enhanced SQL Analytic Functions.
The link you provided appears to be what will work in
designing data matrices.

Are these functions part of the OLAP Windows Functions?
I have 9i on my test server with OLAP=True.

I have a .pdf file showing the report design...I can't get it to copy and paste
here...it's one page.

With your permission I can send it to your Oracle E-Mail
address for you to assess whether the type of data we
are looking to analyze can be generated through SQL
Analytic Functions.

That would be fantastic!!
what else is there to say...you are simply the BEST!
Followup:
You need Enterprise Edition until 9iR2 when they became a feature of Standard
and above.

I'd rather not get an email -- I do not think I can tell just by looking at a
report if you can or cannot do it with analytics
Rating: 3
Thanks anyway.... January 29, 2004
Reviewer: Denise from Virginia, USA
I do have 9i EE on my testserver.
I'll do some research on using OLAP
and start playing around with the functions.

The link you sent had some good examples so that
should get me going.

I'll let you know when I figure out how to design
a data matrix report.

;~)
Rating: 3
What is matrix programming? January 29, 2004
Reviewer: Dan Kefford from New Haven, CT
Could the poster tell us what she meant by "matrix programming"?
Rating: 5
January 30, 2004
Reviewer: A reader
Dan Kefford have you watch the triology movie Matrix, if yes, then yeah!! Denise
is the one who programmed it :-)
Rating: 5
To the previous poster... January 30, 2004
Reviewer: Dan Kefford from New Haven, CT
There is no spoon.
Rating: 2
elusive Concepts Guide February 24, 2004
Reviewer: Lynn from NJ
I keep reading references to the Concepts Guide but I'm having a devil of a time
finding a pdf with the words "Concepts Guide".  Can someone point me in the
right direction?  I've gone to the OTN page and do not see anything with the
title "Concepts Guide".  Thanks.
Followup:

http://otn.oracle.com/pls/db92/db92.docindex?remark=homepage#index-CON
(it is called "concepts" in the list of books....)
Rating: 3
Cheap tech books February 24, 2004
Reviewer: Mark from USA
I PROMISE I don't work for these folks, but their books are a bargain, arrive
quickly, etc.
http://www.bookpool.com/
Examples-

Effective Oracle by Design Thomas Kyte
Oracle Press, Paperback, Published August 2003, 848 pages, ISBN 0072230657
List Price: $59.99
Our Price: $36.50
Amazon: $41.99

Expert One-on-One Oracle Thomas Kyte
Apress, Paperback, Published August 2003, 1297 pages, ISBN 1590592433
List Price: $59.99
Our Price: $38.50
Amazon: $41.99

Oracle PL/SQL Programming, 3rd Edition Steven Feuerstein, Bill Pribyl
O'Reilly & Associates, Paperback, 3rd edition, Published September 2002, 989
pages, ISBN 0596003811
List Price: $54.95
Our Price: $33.95
Amazon: $38.47

Orders of $40 or more ship free within the 50 US States

I REALLY don't work for them.
Rating: 5
Found it February 25, 2004
Reviewer: Lynn from NJ
Thanks so much.  I now have and am reading the Concepts manual.
Rating: 3
SQL error March 23, 2004
Reviewer: alex from uk
Tom

I have following statement in a.sql script file

spool   c:\a.txt

select empno || dname from emp where eno = 10;

spool off;

if the above select failes because of any reason can it be traced without using
pl/sql block, i mean only using SQL to extarct the data in some file. right now
it writes the error on file bt script say success.

any idea please.
thanks
Followup:
um, well, what would you like to have happen?

you can use the sqlplus "whenever" setting (see the sqlplus guide, all docs
freely available on otn) to have sqlplus exit upon error with a return code.

You could use plsql like this:

variable x refcursor

begin
   open :x for 'select empno || dname from emp where eno = 10';
exception when others.....
   blah blah blah
end;
/
print x

as well, it'll use sqlplus to format the results, plsql to give you control
March 24, 2004 zaheer -- Thanks for the question regarding "varying elements in IN list", version
You Asked
I am using Oracle Report Builder 3.0.5.8.0 . While
using bind refernce i have created a dynamic list of values to
give input for that particular bind reference , actually i want
multi selection from LOV for that bind refernce is it possible
and may i use IN Operator over bind reference variable.
and we said...
You can use a bind variable in an IN but I don't think it'll do
what you want.  I believe you want a string that has the value:

    1, 2, 3

for example (a single variable) to be used in a query like:

   select * from t where x in ( :that_variable );

and have all rows returned such that x is in the set of 1, 2, 3.
 What would happen though is that every row in T such that x =
"1, 2, 3" would be returned (if it worked the other way -- you
would never be able to IN on something with commas in it).

One 'trick' that works very well on Oracle8.0 and up is to use
an object type that is a nested table type and a simple parse
routine that returns this nested table type given a string
input.  What I mean is best explained via an example:

ops$tkyte@8i> create or replace type myTableType as table of
number;
  2  /
Type created.

ops$tkyte@8i> 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  /

Function created.

ops$tkyte@8i>
ops$tkyte@8i> select * from all_users
  2  where user_id in ( select *
  3    from THE ( select cast( str2tbl( '1, 3, 5, 7, 99' ) as
mytableType ) from dual ) )
  4  /

USERNAME                          USER_ID CREATED
------------------------------ ---------- ---------
SYSTEM                                  5 20-APR-99
Review & Followup

Rating: 5
cursors with in sql February 24, 2002
Reviewer: Munz from reston, va
TOm:

1.  just to clarify things up.  how do you implement this soultion in a stored
procedure that creates html

ket us say we have a web page that prompts user to enter up to three states and
then we want to pull all records from table that match these three states.

would you define cursor like

create or replace procedure test (p_state)
as
cursor find_records(p_state) is
  select * from table where state in (select ( from THE (select cast
(str2tbl(p_state) as mytabletype) from dual

you also create the str2table function separately.

am i correct.

what does cast do?

Thanks,
Followup:
1) is it no different then in a stored procedure that does NOT create HTML.

You will create the table type (myTableType).
You will create a function str2table.
You will create your procedure that generates HTML.

If p_state is a variable that has a comma delimited string like 'PA, VA, MD' --
then yes, what you have there is OK.

the cast just tells the SQL layer what type to expect back - mytabletype
Rating: 4
Lexical February 25, 2002
Reviewer: Steve from Blackburn
Couldn't you just use a lexical parameter within your report

select * from t where x in ( &that_variable );

e.g.

Declare you user parameter as normal give it a default value lets say ('x') just
so it will compile, and then in a After parameter form trigger , just assign the
dynamic value you talked about to the lexical parameter.  The only difference in
the SQL would be to use the &that_variable  instead of the :that_variable.
Followup:
does not use bind variables.  I despise code that does not use bind variables as
the is the best way to limit your scalability, flood your shared pool, kill your
repsonse time and fail.

Use bind variables!
Rating: 5
February 25, 2002
Reviewer: Steve from Blackburn
Is this because of soft and hard parsing ??? i.e. the bind vaiable approach
would be soft parsed because its sql as already been checked i.e. rights to
tables etc... but the lexical approach means the db can't gurantee that the
lexical bit won't referrence another table and thus as to hard parse.
Followup:
It is soft vs hard -- but not for the reason you specify.

If they code:

select * from t where x in ( &blah )

they will generate queries like:

select * from t where x in ( 1 );
select * from t where x in ( 1, 2 );
select * from t where x in ( 2, 1 );
select * from t where x in ( 1, 2, 3 );
....

virtually EVERY query that uses this approach will be hard parsed.  That will
KILL performance.  You want to avoid hard parses.
Rating: 5
Great February 25, 2002
Reviewer: Alla from Palo Alto, CA
Very useful info and examples.
Rating: 5
IN statement February 25, 2002
Reviewer: munz from Reston, VA
Tom:

as a followp to my example above, p_state is whatever the user selects which
means is either VA, or MD or PA without any commas.

Does this mean I have to make it comma delimited string by

FOR i in 1..p_state.count
v_state:=p_state(i)
If i > 1 then
v_state:='||v_state||,||p_state(i)||'
else
v_state:='||p_state(1)||'
end if;

BY THE WAY, would not this give the same result:

CURSOR find_state IS
  SELECT * from table where state=p_state1 or state=p_state1
  or state=p_state3;

If p_state2 or p_state3 are null then nothing will be found for those states
unless i have other records with a null state then it will not work.

Thanks,

Thanks,
Followup:
If you can have AT MOST three items, by all means code:

select * from t where state in ( p_state(1), p_state(2), p_state(3) );

If you know the (reasonable) upper bound on the number of elements -- go for it,
just code the explicit binds.

Else, use the str2table trick (in which case -- yes, you would have to "string
up" the elements of the array into a single string)
Rating: 5
bind variables February 26, 2002
Reviewer: Munz from Reston, USA
Tom:

If you do define the cursor

select * from t where state in (p_state(1),p_state(2),p_state(3))

is there a way to avoid records with null states in case user select 1 or 2
states only?

Thank you,
Followup:
NULLS will never come out from that.

scott@ORA817DEV.US.ORACLE.COM> select ename from emp where comm in ( 500, NULL,
NULL );

ENAME
----------
WARD

scott@ORA817DEV.US.ORACLE.COM> select count(*) from emp where comm is null;

  COUNT(*)
----------
        10

just set the last N array elements to NULL and they are effectively "ignored"
Rating: 1
Ref cursor and collections April 15, 2002
Reviewer: Ranjan from Connecticut, USA
I am getting the following error pls suggest.

  1  select * from dual
  2    where
  3  exists ( select *
  4    from THE ( select cast( danka_pkg.in_list( '1, 3, 5, 7, 99' ) as mytab )
from
  5* dual ) )
  6  /
select * from dual
*
ERROR at line 1:
ORA-03113: end-of-file on communication channel
Followup:
that error equals "call support".
Rating: 3
How about with dynamic SQL? April 16, 2002
Reviewer: James from New York
Could you suggest an approach to get a list of a varying number of elements to
behave as bind variables using NDS?  I'm applying the SYS_CONTEXT() technique
you described recently to an application that builds dynamic queries based on
user selected criteria (up to 45 or so of these).  Some of the criteria are
lists of things, US states for example.

One way, I suppose would be to use the technique you've described here and pass
the nested table type object to the NDS statement via 'OPEN CURSOR v_cursor
(v_stmt) USING v_bindvar'.  But, for the same reason that static queries would
get unwieldy when dealing with all the possible combinations of criteria,
figuring out which variables and in which order to list them in the USING clause
becomes unmanagable.

Suggestions?  (Thanks in advance)
Followup:
right above IS THE APPROACH!!!

str2tbl.
Rating: 5
Most helpful example May 02, 2002
Reviewer: Andreas Faafeng from Oslo, Norway
Once again, I'm impressed by your wonderfully simple and straight-forward
solution to what initially was an intricate question.

I'm working in a group of 4 people, of which 3 has bought your book.

Your mission to teach us all about latches, bind variables and parsing certainly
helps!

Best regards,
Andreas Faafeng
Rating: 4
Hmm. What about this? May 06, 2002
Reviewer: dre from Colorado
I'm not very clear about binding and nested object table types and latches but
does this fall into the 'despised' unbinded category or is it acceptable?

SELECT thisid
  FROM thistable
  WHERE INSTR(stringOnumbers,','|| thisid ||',') != 0;

tks.  DRE
Followup:
that looks just dandy -- I see no constants that would change from query
execution to query execution in there...

Won't use any indexes on thisid -- hope that is not a problem.
Rating: 4
Hmm. What about this? May 07, 2002
Reviewer: DRE
Hmm, my query plan says its using the index, not a full table scan. . . ?
Followup:
It is using a FAST FULL INDEX scan (using the table as an index).

So, it is full scanning a "skinny" version of your table.  It'll not be able to
use the index in a "conventional" sense (range scan for example).

Add another column:

SELECT thisid, some_other_column
  FROM thistable
  WHERE INSTR(stringOnumbers,','|| thisid ||',') != 0;

and see what happens.
Rating: 4
More hemming and hawing! May 07, 2002
Reviewer: DRE from colorado
Hi Tom, I didnt really know there was a difference but I'm still a bit not
jiving.  Heres the implemented query:

SELECT
        adl_dbt_id,
        adl_acc_id
      FROM
        adl,
        acc
      WHERE
        adl_acc_id = acc_id AND
        adl_dispute_credit = 0 AND
        INSTR(','|| '164,163,162,161' ||','  ,   ','|| acc_frw_id ||',') != 0
AND
        acc_caod_flag = 1 AND
        INSTR(','||'SK,G'||',' ,  ','|| acc_que_system_q ||',') != 0 AND
        acc_que_frr_id = 30

and heres what pops out of query plan:

SELECT STATEMENT CHOOSE 5

   NESTED LOOPS 5

      TABLE ACCESS (BY INDEX ROWID) of ACC_ACCOUNT ANALYZED 3
         INDEX (RANGE SCAN) of ACC_QUE_FRR_ID_IDX ANALYZED 1

      TABLE ACCESS (BY INDEX ROWID) of ADL_ACCDBT_LINK ANALYZED 2
         INDEX (RANGE SCAN) of ADL_ACC_ID_IDX ANALYZED 1

Now I'm pulling various fields out of the select and the plan still says range
scan and by index rowid which as I understand means its using the index
properly.  What am I missing? Err.  thats too broad.  Perhaps you can point me
somewhere where I can get a better understanding.  DRE
Followup:
I'll make the resonable assumption that

ACC_QUE_FRR_ID_IDX

is on the acc_que_frr_id column and that

ADL_ACC_ID_IDX

is on the adl_acc_id column.

Then, what is happening is:

the predicate "acc_que_frr_id = 30" is being used to find the rows in the ACC
table.  this uses the index ACC_QUE_FRR_ID_IDX.  Then, the TABLE ACCESS of
ACC happens and this predicate:

    INSTR(','|| '164,163,162,161' ||','  ,   ','|| acc_frw_id ||',') != 0
    AND acc_caod_flag = 1 AND
    INSTR(','||'SK,G'||',' ,  ','|| acc_que_system_q ||',') != 0

is evaluated.  this does not (and the instrs in fact CANNOT) use an index.
Then the index on ADL(ADL_ACC_ID) is used to find the joined to row in ADL.

So, any indexes on ACC_FRW_ID and ACC_QUE_SYSTEM_Q cannot be used by your
predicate.  In fact, the query you gave me:

SELECT thisid
  FROM thistable
  WHERE INSTR(stringOnumbers,','|| thisid ||',') != 0;

would not use a range scan (if you run it) it would FAST FULL SCAN.

So, I stand by my answer -- your example doesn't show an index on "thisid" being
used when the predicate is instr(stringOnumber,','||thisid||',') is used.
Rating: 2
missing something July 12, 2002
Reviewer: George from Chester Springs, PA USA
Tom,

It seems like your "trick" would accomplish what I need
to... but I can't seem to make it work (THE removed since
I'm working with 9i)

SQL> desc string_list_t
 string_list_t TABLE OF VARCHAR2(32000)

SQL> l
  1  declare
  2     v_value_list  string_list_t;
  3     v_rtn         number;
  4  begin
  5     spi_utils.add_string( v_value_list, 'X' );
  6     spi_utils.add_string( v_value_list, 'Y' );
  7     spi_utils.add_string( v_value_list, 'Z' );
  8     v_rtn := 0;
  9     select 1
 10     into   v_rtn
 11     from   dual
 12     where  dummy in ( select cast( v_value_list as string_list_t ) from dual
);
 13     dbms_output.put_line( 'expect success, returned ' || to_char( v_rtn ));
 14* end;
SQL> /
declare
*
ERROR at line 1:
ORA-00932: inconsistent datatypes
ORA-06512: at line 9

Honestly, I don't get why the CAST function is included
since v_value_list is already of type string_list_t, but
clearly I don't understand something that is necessary.
Can you help... please?
Followup:
ops$tkyte@ORA9I.WORLD> create or replace type string_list_t as TABLE OF
VARCHAR2(4000)
  2  /

Type created.

ops$tkyte@ORA9I.WORLD> declare
  2     v_value_list  string_list_t := string_list_t( 'X','Y','Z' );
  3     v_rtn         number;
  4  begin
  5     v_rtn := 0;
  6     select 1
  7     into   v_rtn
  8     from   dual
  9     where  dummy in (select * from TABLE ( cast( v_value_list  as
string_list_t ) ) );
 10     dbms_output.put_line( 'expect success, returned ' || to_char( v_rtn ));
 11  end;
 12  /
expect success, returned 1

PL/SQL procedure successfully completed
Rating: 5
in list July 13, 2002
Reviewer: Munz from Reston, USA
TOm:

AS a followup to your answer:
---------------------
If you can have AT MOST three items, by all means code:

select * from t where state in ( p_state(1), p_state(2), p_state(3) );

If you know the (reasonable) upper bound on the number of elements -- go for it,

just code the explicit binds.
----------

Do you mean defining p_state(1) as IN parameter into cursor and defining the
value in the PL/SQL program

create or replace proc1 as

 p_state  varchar2     types.array;

cursor find_state(p_state(1),p_state(2),p_state(3)) is
select * from table where state in (p_state(1),p_state(2),p_state(3));

Is this correct?

Thanks
Followup:
I would just code

for x in ( select * from t where state in ( p_state(1), p_state(2), p_state(3) )
)
loop
   ...

(hate explicit cursors)

Your syntax is wrong, the "concept" is ok but the syntax is wrong.

it would be:

cursor find_state(a in varchar2, b in varchar2, c in varchar2 ) is
select * from table where state in (a,b,c);
Rating: 4
performance issue? July 17, 2002
Reviewer: George from Chester Springs, PA USA
Tom,

Thanks for the help... solution worked exactly as purported-
but now we are seeing terrible performance.  The problem,
as best as I can tell based on the explain plan and a number
of timing tests, is that a hard-coded IN clause allows this select to happen
against the primary key index, but the TABLE(CAST()) is performing more like a
full scan.  Is there anything I can do?

Thanks in advance.

declare
   xyz xyz_t := xyz_t( '000A00A600000020','000A00A600000021','000A00A600000022',
       '000A00A600000023','000A00A600000024','000A00A600000025',
       '000A00A600000026','000A00A600000027','000A00A600000028',
       '000A00A600000029',
       '000A00A600000030','000A00A600000031','000A00A600000032',
       '000A00A600000033','000A00A600000034','000A00A600000035',
       '000A00A600000036','000A00A600000037','000A00A600000038',
       '000A00A600000039',
       '000A00A600000040','000A00A600000041','000A00A600000032',
       '000A00A600000043','000A00A600000044','000A00A600000045',
       '000A00A600000046','000A00A600000047','000A00A600000048',
       '000A00A600000049',
       '000A00A600000050','000A00A600000051','000A00A600000052',
       '000A00A600000053','000A00A600000054','000A00A600000055',
       '000A00A600000056','000A00A600000057','000A00A600000058',
       '000A00A600000059',
       '000A00A600000060','000A00A600000061','000A00A600000062',
       '000A00A600000063','000A00A600000064','000A00A600000065',
       '000A00A600000066','000A00A600000067','000A00A600000068',
       '000A00A600000069',
       '000A00A600000070','000A00A600000071','000A00A600000072',
       '000A00A600000073','000A00A600000074','000A00A600000075',
       '000A00A600000076','000A00A600000077','000A00A600000078',
       '000A00A600000079');
    p_outoids xyz_t := xyz_t();
    p_names xyz_t := xyz_t();
    x number;
    abc number;
    lmn number;
begin
   abc := dbms_utility.get_time;
         select obj_id
            ,obj_nm
        BULK COLLECT
        into p_outoids
            ,p_names
        from calsdba.OBJECT
       where obj_id IN (
'000A00A600000020','000A00A600000021','000A00A600000022',
       '000A00A600000023','000A00A600000024','000A00A600000025',
       '000A00A600000026','000A00A600000027','000A00A600000028',
       '000A00A600000029',
       '000A00A600000030','000A00A600000031','000A00A600000032',
       '000A00A600000033','000A00A600000034','000A00A600000035',
       '000A00A600000036','000A00A600000037','000A00A600000038',
       '000A00A600000039',
       '000A00A600000040','000A00A600000041','000A00A600000032',
       '000A00A600000043','000A00A600000044','000A00A600000045',
       '000A00A600000046','000A00A600000047','000A00A600000048',
       '000A00A600000049',
       '000A00A600000050','000A00A600000051','000A00A600000052',
       '000A00A600000053','000A00A600000054','000A00A600000055',
       '000A00A600000056','000A00A600000057','000A00A600000058',
       '000A00A600000059',
       '000A00A600000060','000A00A600000061','000A00A600000062',
       '000A00A600000063','000A00A600000064','000A00A600000065',
       '000A00A600000066','000A00A600000067','000A00A600000068',
       '000A00A600000069',
       '000A00A600000070','000A00A600000071','000A00A600000072',
       '000A00A600000073','000A00A600000074','000A00A600000075',
       '000A00A600000076','000A00A600000077','000A00A600000078',
       '000A00A600000079');
   lmn := dbms_utility.get_time;
   dbms_output.put_line( 'the hard coded in time was: ' || to_char( lmn - abc
));
   abc := dbms_utility.get_time;
         select obj_id
            ,obj_nm
        BULK COLLECT
        into p_outoids
            ,p_names
        from calsdba.OBJECT
       where obj_id IN (
             (SELECT * FROM TABLE(CAST(xyz as xyz_t))));
   lmn := dbms_utility.get_time;
   dbms_output.put_line( 'the where in time was: ' || to_char( lmn - abc ));
end;
/

SQL> /
the hard coded in time was: 2
the where in time was: 1596

PL/SQL procedure successfully completed.
Followup:
option 2:

build the string as ... where x in ( 1, 2, 3, 4, 5, 6, 7 ) ....

but enable cursor_sharing = force before you parse it , cursor_sharing = exact
after you parse it.  At least that way you will minimize the number of
statements in the shared pool to be minimized.
Rating: 4
global temp table? July 17, 2002
Reviewer: George from Chester Springs, PA USA
Tom,

Option 2 seems like a lot of work for not a lot of bang.
And, it limits the number of values in our list.

Instead, I tried defining a global temp table, inserting
my values into that, and selecting from the gtt in the
IN clause.  Its faster because it does a hash join now,
but it still doesn't use the primary key index on "OBJECT".
Is this a "feature", or is there something I can do to
take advantage of our index?

create global temporary table inlist( str_val varchar(20) );

xyz str_list_t :=
str_list_t(''000A00A600000076','000A00A600000077','000A00A600000078');

   insert into inlist( str_val )
             SELECT * FROM TABLE(CAST(xyz as str_list_t));

         select obj_id
            ,obj_nm
        BULK COLLECT
        into p_outoids
            ,p_names
        from calsdba.OBJECT
       where obj_id IN ( select str_val from inlist );

Thanks
Followup:
NOT A LOT OF BANG????

go ahead and use literals -- see what kind of "bang" you get from that (a bang
like "bang, you just shot yourself in the foot")

Tell me -- how hard is:

   alter session set cursor_sharing=force;
   parse
   alter session set cursor_sharing=exact;

??????
Rating: 4
Getting some error on compilation July 17, 2002
Reviewer: Yogesh from San Jose, CA
Hi Tom,

I tried this approach in my stored procedure to which I am passing a string. I
can compile the procedure stand-alone, but when in a package, it gives me an
error "PLS-00382: expression is of wrong type". Do u know what could be going
wrong.

Thank you,
Followup:
works for me.  provide an example (you know, like i always do)

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package demo_pkg
  2  as
  3          function str2tbl( p_str in varchar2 ) return myTableType;
  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  function str2tbl( p_str in varchar2 ) return myTableType
  5  is
  6      l_str   long default p_str || ',';
  7      l_n        number;
  8      l_data    myTableType := myTabletype();
  9  begin
 10      loop
 11          l_n := instr( l_str, ',' );
 12          exit when (nvl(l_n,0) = 0);
 13          l_data.extend;
 14          l_data( l_data.count ) := ltrim(rtrim(substr(l_str,1,l_n-1)));
 15          l_str := substr( l_str, l_n+1 );
 16      end loop;
 17      return l_data;
 18  end;
 19
 20  end;
 21  /

Package body created.
Rating: 5
Applying this solution to a cursor July 18, 2002
Reviewer: Mark Mclauchlan from London, UK
Tom,

I tried to implement this as a cursor. It works fine in normal SQL but when it's
inside a package it compiles with the following error on the casting of the
select "PLS-00513: PL/SQL function called from SQL must return value of legal
SQL type". I am running this on an 8i database

CREATE OR REPLACE PACKAGE panel_period_pkg AS

TYPE ppidTableType IS TABLE OF NUMBER;

FUNCTION alloc_to_dp(p_str IN VARCHAR2,  p_display_period_id IN NUMBER,
p_user_id IN VARCHAR2) RETURN VARCHAR2;

FUNCTION create_ppa_list_from_string(p_str IN VARCHAR2) RETURN ppidTableType;

END panel_period_pkg;
/

CREATE OR REPLACE PACKAGE BODY panel_period_pkg AS

       FUNCTION alloc_to_dp(p_str IN VARCHAR2,  p_display_period_id IN NUMBER,
p_user_id IN VARCHAR2) RETURN VARCHAR2 IS

       l_err_msg    varchar2(30);

       CURSOR c_available_pp (p_str IN VARCHAR2)
                 IS SELECT * FROM display_periods
                 WHERE display_period_id IN ( SELECT *
              FROM THE ( SELECT cast( create_ppa_list_from_string( p_str) AS
ppidTableType )
              FROM dual) ); --This line raises an error

       BEGIN

       RETURN l_err_msg;

       END alloc_to_dp;

       /* This function takes the panel period id's passed in as a string as
returns them */
       /* as a pl/sql tavle to be used in the main select of panel period
availabilty*/
       FUNCTION create_ppa_list_from_string( p_str in varchar2 ) RETURN
ppidTableType IS

       l_str    long default p_str || ',';
       l_n      number;
       l_data   ppidTableType := ppidTableType();

       BEGIN

               LOOP
                 l_n := instr( l_str, ',' );
                 EXIT  WHEN (nvl(l_n,0) = 0);
                    l_data.extend;
                    l_data( l_data.count ) :=
ltrim(rtrim(substr(l_str,1,l_n-1)));
                    l_str := substr( l_str, l_n+1 );
               END LOOP;
       RETURN l_data;

         END create_ppa_list_from_string;

END panel_period_pkg;
/

Any suggestions?
Followup:
The type must be a SQL type -- not a plsql type.

move the type outside of the package -- it is a rule, it cannot be any other
way.  The type must be known to SQL -- not just plsql.
Rating: 4
usefulness of cursor_sharing=force July 23, 2002
Reviewer: George from Chester Springs, PA USA
Tom,

I am sorry if I offended you by doubting your suggestion.  And after some review
of this feature, the work is much less than I assumed.. and I am seeing
performance in line
with my expectations (derived from running the statement
ad-hoc through SQLPlus.)

Still, I am quite convinced that I am benefitting from an
execution path that utilizes the the index where neither
the plsql table nor the temp table do.  Can you think of
any reason those solutions wouldn't use the index?

SQL> desc calsdba.object
 Name                           Null?    Type
 --------------------- -------- ---------------------
 OBJ_ID                NOT NULL VARCHAR2(16)
 OBJ_NM                         VARCHAR2(250)
....

  1  select index_name, column_name from user_ind_columns
  2* where index_name = 'OBJECT_PK'
SQL> /

INDEX_NAME                     COLUMN_NAME
------------------------------ --------------------
OBJECT_PK                      OBJ_ID

And I am still in a small bind, since a comma separated
element list is limited to 1000 elements.  And I haven't
seen how to set CURSOR_SHARING from within a procedure,
which is the only option I'd have (I tested from a sqlplus
prompt.)  Is this code what you'd have expected?

create or replace procedure do_select( where_clz in varchar2 )
as
   selstmt   varchar2(9999) :=
      'select obj_id, obj_nm from calsdba.OBJECT ' || where_clz;

    type rc is ref cursor;
    selrc rc;
    p_outoids string_list_t := string_list_t();
    p_names string_list_t := string_list_t();
    x number;
    abc number;
    lmn number;

begin

   abc := dbms_utility.get_time;

   open selrc for selstmt;

   fetch selrc BULK COLLECT into p_outoids, p_names;

   close selrc;

   lmn := dbms_utility.get_time;

   dbms_output.put_line( 'the fetch time was: ' || to_char( lmn - abc ));
end;
/

alter session set cursor_sharing=force

declare
   where_clz varchar2(20000) :=
      'where obj_id IN ( ' ||
      '''000A00A600000020'',''000A00A600000021'',''000A00A600000022'', ' ||
      '''000A00A600000023'',''000A00A600000024'',''000A00A600000025'', ' ||
      '''000A00A600000026'',''000A00A600000027'',''000A00A600000028'', ' ||
      '''000A00A600000029'' )';
begin
   do_select( where_clz );
end;
/

SQL> /
000A00A600000020
the fetch time was: 0

PL/SQL procedure successfully completed.

I wonder if you were suggesting something different because that sequence of
events ran just as quickly whether
I included the cursor_sharing statement or not...?

In your book you say: "A well-developed application would never need
[CURSOR_SHARING]"... if you still believe that
then you must believe my application is not well-developed
to suggest it.. but the app isn't developed yet!  Please
help me to develope it correctly.

Thanks, George
Followup:
calsdba, thats not cals like in computer aided logistics support system is it?

You didn't offend me -- I was just pointing out that if you avoid bind variables
-- you will hate yourself later.

in ( 1, 2, 3 )

is very different from

in ( select .... )

in the first case, the optimizer knows "hey, the set is 3 items -- 3 index
probes will do it -- i can put that in the plan and go".  In the second case the
optimizer says "hey, unknown number of items -- cannot just set 3 index probes
in the plan and go -- gotta do nested loops or something else"

Just different plans.

execute immediate 'alter session set cursor_sharing=force';

and if you have more then 1,000 elements -- you don't want to do 1,000 index
probes.  At some point between 0 and 1000 the other plan will be better for you.
Rating: 5
no more questions... at least on this subject :-) July 24, 2002
Reviewer: George from Chester Springs, PA USA
CALS is the project you suspect - I joined this team about 3 months ago to help
write some generic/dynamic procedures to facilitate replacing some custom code.
I have already pointed out that it will be very hard (perhaps impossible) to
tune these new functions properly since they're expected to perform in a numer
of to be determined situations (i.e. 1000 element selection lists.)  Still, the
codebase is large so we'll have to handle performance one (slow)function at a
time.

back to the question.. with your (thorough) explanation
in hand, I am ready to capitulate.  I did try to force using the index by
hinting the query with some success, but given our environment I tend to believe
the database will do a better job than (my current thoughts,) so I dropped that
idea.  Instead I'll just add this to the growing evidence that I can't fix
everything, and provide options instead of a firm reckommendation.

Thanks again, this discussion has been very helpful :-)
Followup:
Hey, I worked on the CALS project 1989-1993 ;)

When I was working on it -- it was DEC Ultix MLS+ (b1 multi-level secure OS) and
Trusted Oracle 7.0.9 with Xterms.  What a difference a decade makes....
Rating: 4
same problem as above October 10, 2002
Reviewer: A reader
Why is that while you use in list as  (select cast(...as)) etc  it is slower
than you directly hardcode the values in the in list?

Bind variables should make the execution faster isnt it?

I have a similar query as Geroge's,
Inside a package/procedure i have:
arr_list  tbl_list := tbl_list(obj_list('','',''));
/*obj_list is an object in the database, and tbl_list is table of obj_list. [
obj_list ->dep_no varcjar2(10), da varchar2(20)]
'arr_list'  populated with pairs of values(dep_no,da) by a loop .say a max
of...1 to 100 with values that are coming inside teh proc as parameters*/
/* then i have an out parameter as a refcursor:(method1)*/
open c_refcur for
  select ....
    where.....and
    where.....and
    where (something1,something2) in (select CC.dep_no, CC.da FROM TABLE(
CAST(arr_list AS tbl_list)) AS CC) ;

/*end of proc*/

If i pass say just 2 dep_nos and 2 das (i.e i loop twice) to populate the
arr_list) , teh proc takes about 8 seconds to execute.

Now if i change that refcursor and make it dynamic:(method2)
v_inlist := /* collect pairs of inputs from proc and generate string as '(
(depno1,da1),(dep_no2,da2),(dep_no3,da3) )'  etc/*
open c_refcur for
  'select ....'||
    'where.....and '||
    'where.....and '||
    'where (something1,something2) IN ''||
    v_inlist ;

This way it takes less than a second.

Now repeated execution of method1 will make use of bind variables (when passed
with diff parameters). STill the timetaken is 7 or 8 seconds everytime.

The method 2 while executed with diff values takes less than a second all teh
times...

Now i tried passing many diff values as parameters starting from 1 to 20
...works the same way.

So does this mean that one needs to weigh the options while using bind
variables? WHy is it slower .

Also, when we do 'select CC.dep_no, CC.da FROM TABLE( CAST(arr_list AS
tbl_list)) AS CC' , is  this  treated as a normal table inside the proc?
Meaning, if it does then can we make some dynamic indexes etc . will that ake it
faster?
Followup:
think about it:

where x in ( 1, 2, 3, 4, 5 )

where x in ( select z from a )

Just think about the differences between those two predicates.......  Nothing to
do with bind variables -- just think about the potential differences between
those two predicates (how many rows will come back from A -- 1? 10? 1000?
10000?)
Rating: 5
Casting in Dynamic SQL November 22, 2002
Reviewer: A reader from USA
Will this casting work with dynamic SQL?

When I tried it with static SQL, it works fine. With dynamic SQL, it gives a
strange error. See below.

SQL> DECLARE
  2     l_tbl x_table_type := x_table_type(1, 2, 3);
  3
  4  BEGIN
  5     FOR x IN (SELECT column_value FROM TABLE(CAST (l_tbl AS x_table_type)))
LOOP
  6        dbms_output.put_line(x.column_value);
  7     END LOOP;
  8  END;
  9  /
1
2
3

PL/SQL procedure successfully completed.

SQL> DECLARE
  2     type cur_type IS REF CURSOR;
  3     x cur_type;
  4     TYPE l_record_type IS RECORD (col1 NUMBER);
  5     l_record l_record_type;
  6
  7     l_tbl x_table_type := x_table_type(1, 2, 3);
  8
  9     l_sql VARCHAR2(4000) :=
 10        'SELECT column_value FROM TABLE(CAST (l_tbl AS x_table_type))';
 11
 12  BEGIN
 13     OPEN x FOR l_sql;
 14
 15     LOOP
 16     FETCH x into l_record;
 17     EXIT WHEN x%NOTFOUND;
 18
 19     dbms_output.put_line(l_record.col1);
 20     END LOOP;
 21
 22     CLOSE x;
 23  END;
 24  /
DECLARE
*
ERROR at line 1:
ORA-00904: invalid column name
ORA-06512: at line 13
Followup:
that is not strange -- type it in SQLPLUS and you'll get the same exact thing.

is l_tbl a column?  no- you aren't in Plsql anymore when you drop down to
dynamic sql - its all sql.  hence l_tbl just doesnt exists.

  9     l_sql VARCHAR2(4000) :=
 10        'SELECT column_value FROM TABLE(CAST (:l_tbl AS x_table_type))';
 11
 12  BEGIN
 13     OPEN x FOR l_sql using l_tbl;

see what that does for you.
Rating: 4
How parameters in IN list behave February 05, 2003
Reviewer: Dan from US
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
Rating: 4
Do you need Oracle Object installed? March 27, 2003
Reviewer: Anil from Paris, France
I tried getting the example to work by putting the type (table of number) in a
package and having the function str2tbl reference the packaged type but I'm
getting a type mismatch error when I attempt the cast.

Does the type need to be declared as in the example:
"create or replace type myTableType as table of number;"?

I tried that, but apparently we don't have the "Objects" functionality installed
in Oracle.

Please bare with me, I somewhat new to all of this!

Cheers.
Followup:
you must have a really old (like 8.0) version of Oracle.

Yes, you NEED the objects option (not an option in 8i) installed.
Rating: 5
Select statement May 05, 2003
Reviewer: lakshmi from Bangalore India
Excellent !!!!
Rating: 5
What if p_str of 4000 bytes is not enough? May 12, 2003
Reviewer: Alex Daher from Brazil
Tom,

Suppose the following type and function:

create or replace type myTableType as table of number;
/
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();
begin
    loop
        l_n := instr( l_str, ',' );
        exit when (nvl(l_n,0) = 0);
        l_data.extend;
        l_data( l_data.count ) := ltrim(rtrim(substr(l_str,1,l_n-1)));
        l_str := substr( l_str, l_n+1 );
    end loop;
    return l_data;
end;
/

So, we can query like:
cd_aldaher_621@ORACLE8> select * from Table(select cast( str2tbl( '947, 234' )
as mytableType ) from dual);

COLUMN_VALUE
------------
         947
         234

OK, but what if varchar2(4000) is not enough? If the number of elements of the
list is so high that the 'p_str' in 'str2tbl' can be veeeery big and 4000 bytes
is not big enough? What approach should I use?

Any idea?

Thank you for the great site!
Alex Daher
Followup:
populate a global temporary table via a bulk insert and use it.
Rating: 5
Awesome...... June 03, 2003
Reviewer: Bala Nemani from USA
Very...Very Helpful...
Rating: 4
Won't compile on str2tbl... August 21, 2003
Reviewer: A reader
Hi Tom,

I've used your example to create...

    open l_network_reqs_to_process for
    select nom.nom_nominal_id, nre.nre_nr_id, nre.nre_name
    from omx_mig_nominals nom, omx_mig_network_requirements nre
    where nom.nom_nominal_id  = nre.nre_nominal_id
    and   nom.nom_hier_status = omx_mig_logging_01.GC_OK_STATUS
    and   nom.nom_hier_mig_phase = omx_mig_logging_01.GC_CALC_WRK_PLANS
    and   nre.nre_nr_id in ( select *
                             from THE ( select cast( str2tbl(i_nr_list) as
omx_t_num_tab )
                                        from dual
                                      )
                           );

Where omx_t_num_tab is...

SQL> desc omx_t_num_tab
 omx_t_num_tab TABLE OF NUMBER

the str2tbl function is defined in the same package.

When I attempt to compile the package I get a "PLS-00231: function 'STR2TBL' may
not be used in SQL" on the "open for" statement.

I don't understand why I am not allowed to use it in my scenario, but you are
allowed to use it in yours?

Have I done something very stupid?

Thanks,

Mike.
Followup:
version?
Rating: 5
I was being stupid! August 21, 2003
Reviewer: Mike from England
Ignore the last "A Reader" I was indeed being stupid, and not reading your posts
properly, with the str2tbl function being a Stored function rather than being in
the package it works.  Which is obvious.
Rating: 3
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:
why do people put the same thing in many places???  squeaky wheels -- grease?

look where you asked this elsewhere for my followup.
Rating: 4
September 05, 2003
Reviewer: Vipin
Hi Tom,

Sorry for putting this in more than one place , but anyway thanks for the
followup.
Rating: 5
Working on the same trick September 11, 2003
Reviewer: Pramitayan chinya from Texas,USA
  Refurring to the review:
cursors with in sql  February 24, 2002
Reviewer:  Munz  from reston, va

I want to pass two lists of numbers to the procedure and get a result set out of
it.
The lists are dynamically varying in values as well as count.
The trick discussed here does not seem to work on a cursor declared in a stored
procedure. i get the error

  PL/SQL: SQL Statement ignored
  PLS-00320: the declaration of the type of this expression is
  incomplete or malformed

  PLS-00513: PL/SQL function called from SQL must return value of
  legal SQL type

  PL/SQL: SQL Statement ignored

My understanding is that this is because SQL not supporting the PL/SQL function.
what would be your recommendation on work around?Am i wrong anywhere?
This is the package..

CREATE OR REPLACE PACKAGE BODY WOS_PROC_PKG
 AS
FUNCTION STR2NUM( P_STR IN VARCHAR2 ) RETURN
VALUE
 AS
      L_STR   LONG default p_str || ',';
      l_n        number;
      l_data    VALUE := VALUE();
  begin
       loop
          l_n := instr( l_str, ',' );
          exit when (nvl(l_n,0) = 0);
          l_data.extend;
          l_data( l_data.count ) := ltrim(rtrim(substr(l_str,1,l_n-1)));
          l_str := substr( l_str, l_n+1 );
      end loop;
      return l_data;
  end;
 PROCEDURE WOS_PROC_GROUP(LOCATIONID_WHERE_CLAUSE IN VARCHAR2,
   MERCHANDISEID_WHERE_CLAUSE IN VARCHAR2,RESULT_SET OUT RESULT_SET_TYPE)
 AS
 CURSOR OH_VAR_CUR(DATE_VAR IN DATE)
  IS
   SELECT NVL(ON_HAND_QTY,0),MERCHANDISE_ID,LOCATION_ID    FROM
OAK.DAILY_INV_TBL
      WHERE TO_CHAR(LOCATION_ID) IN  ( SELECT * FROM THE(select CAST(str2NUM(
'LOCATIONID_WHERE_CLAUSE' ) as VALUE)  from dual))
         AND TO_CHAR(MERCHANDISE_ID) IN  (SELECT * FROM THE( select
CAST(str2NUM( 'MERCHANDISEID_WHERE_CLAUSE' ) as VALUE)  from dual))
    AND CALENDAR_DT > DATE_VAR
      ORDER BY MERCHANDISE_ID,LOCATION_ID;
CURSOR WOS_CUR(DATE_VAR IN DATE,LOCATIONID_VAR IN NUMBER,MERCHANDISEID_VAR IN
NUMBER)
                IS
                  SELECT WEEK_END_DT, NVL(ACTUAL_UNITS,0)+NVL(PLANNED_UNITS,0)
WEEKLY_UNITS,
   LOCATION_ID,MERCHANDISE_ID FROM
       FORECAST_MERCH_LOCATIONS_TBL
                  WHERE
        WEEK_END_DT > DATE_VAR
                  AND LOCATION_ID = LOCATIONID_VAR
                  AND MERCHANDISE_ID =MERCHANDISEID_VAR;
      OH_VAR     OAK.DAILY_INV_TBL.ON_HAND_QTY%TYPE;
      OH_NEW_VAR     OAK.DAILY_INV_TBL.ON_HAND_QTY%TYPE;
      COLOR_VAR        FORECAST_BOC_EOC_UNITS_TBL.COLOR_ID%TYPE;
      STORE_VAR       FORECAST_MERCH_LOCATIONS_TBL.LOCATION_ID%TYPE;
      WEEK_OF_SALES_VAR    FORECAST_MERCH_LOCATIONS_TBL.WEEK_END_DT%TYPE;
      SALES_VAR      NUMBER;
      WOS_SUM      NUMBER := 0;
      WEEK_COUNT   NUMBER;
      DECIMAL     NUMBER;
     MERCHANDISEID_VAR   NUMBER;
     LOCATIONID_VAR    NUMBER;
     OH_HIGH_FLAG VARCHAR2(10);
BEGIN
      WEEK_COUNT := 0;
    OPEN OH_VAR_CUR(SYSDATE);
 LOOP
       FETCH OH_VAR_CUR INTO OH_VAR,MERCHANDISEID_VAR,LOCATIONID_VAR;
  IF
   OH_VAR > 0
  THEN
       OPEN WOS_CUR(SYSDATE,LOCATIONID_VAR,MERCHANDISEID_VAR);
  LOOP
          FETCH WOS_CUR INTO WEEK_OF_SALES_VAR,SALES_VAR,STORE_VAR,COLOR_VAR;
   WOS_SUM:= WOS_SUM + SALES_VAR;
                 WEEK_COUNT:=WEEK_COUNT + 1;
   EXIT WHEN WOS_SUM > OH_VAR OR WOS_CUR%NOTFOUND;
         END LOOP;
                 WEEK_COUNT:= WEEK_COUNT-1;
                 WOS_SUM:=WOS_SUM-SALES_VAR;
        IF
            WEEK_COUNT > 52 OR WOS_CUR%NOTFOUND
           THEN
                 EXECUTE IMMEDIATE 'INSERT INTO WOS_PROC_TEMP
    VALUES(NULL,NULL,NULL,TRUE,0)';
   ELSIF
                   OH_VAR > WOS_SUM
             THEN
    BEGIN
                   OH_NEW_VAR:= OH_VAR-WOS_SUM;
                   DECIMAL := OH_NEW_VAR/SALES_VAR;
                   WEEK_COUNT := WEEK_COUNT + DECIMAL;
    END;
   END IF;
        EXECUTE IMMEDIATE 'INSERT INTO WOS_PROC_TEMP
                 VALUES(STORE_VAR,COLOR_VAR,WEEK_COUNT,OH_HIGH_FLAG,OH_VAR)';
  ELSE
    EXECUTE IMMEDIATE 'INSERT INTO WOS_PROC_TEMP
    VALUES(NULL,NULL,NULL,TRUE,0)';
     END IF;
   EXIT WHEN OH_VAR_CUR%NOTFOUND;
      END LOOP;
  OPEN RESULT_SET FOR SELECT LOCATION_ID,MERCHANDISE_ID,WEEK_COUNT,OH_HIGH_FLAG
FROM WOS_PROC_TEMP;
CLOSE WOS_CUR;
CLOSE OH_VAR_CUR;
END;
END;

Thanks a lot for your time,
Pramit
Followup:
well, i don't see where or how you defined "value"
Rating: 4
Well am i missing anything here??? September 11, 2003
Reviewer: A reader
 Hi,
  I did declare VALUE in the package SPEC..

CREATE OR REPLACE PACKAGE WOS_PROC_PKG
 AS
         TYPE  RESULT_SET_TYPE IS REF CURSOR;
    TYPE VALUE IS TABLE OF NUMBER;
FUNCTION STR2NUM( P_STR IN VARCHAR2 ) RETURN
VALUE;
         PROCEDURE
            WOS_PROC_GROUP(LOCATIONID_WHERE_CLAUSE IN VARCHAR2,
   MERCHANDISEID_WHERE_CLAUSE IN VARCHAR2,RESULT_SET OUT RESULT_SET_TYPE);
END;

It has compiled as expected...
Am i going wrong anywhere inside the package body?

Thanks for your time,
Pramit
Followup:
you cannot put it there if you want to use it in sql

create type value as table of number
/
Rating: 5
Another Solution, thoes this work? December 30, 2003
Reviewer: Ricardo Patrocínio from Porto, PORTUGAL
Hi Tom,

Before I found this tread I've solved this problem like this:

Declare

   TYPE refcursor IS REF CURSOR;

   my$condition varchar2(80);
   my$cursor refcursor;

Begin

   my$condition := '18404,18405,18406';

   OPEN my$cursor for
      Select
         1
      From
         my$table
      where
         my$condition like '%' || my$id || '%';

End;

And it worked just fine, but I'd like your opinion on this solution.

NOTE: the my$condition variable is my input parameter, i've just puted it here
for clarity purpose.

Thank you
R.P.
Followup:
no indexes, full scan.  if that is OK with you, it works just dandy.

if the goal is to have a varying in list that can use indexes - that approach is
no good.
Rating: 4
The size accepted by the LONG variable.. January 02, 2004
Reviewer: Naveen from India
Hi Tom,

Happy New Year.

Kindly help me with this problem. I was using your string tokenizer function to
seperate comma delimiters from the strings. Problem is that when i pass data of
more than 32,767 characters its throwing numeric or value error even though the
variable is of LONG datatype. Why is this happening and is there a way to
overcome this.

Thank you
Nav.
Followup:
32k is the biggest LONG plsql will ever deal with.

no ways around it.
Rating: 5
Thanks Tom..but January 02, 2004
Reviewer: Naveen from India
Hi Tom,

Is the restriction only for local variables or it applies to the input
parameters as well. I think i am asking a dumb question. What i wanted to try
is, before assiging the input parameter to a local variable i'll break the
string and assign. But this can happen only when the input parameter itself can
support more than 32k. I tried simulating it by assiging more than 32k. It
throws the error at the location where i am assiging the input parameter to a
local variable. So i am not able to make out whether the restriction applies to
the input parameter also. But logically, i feel  restriction applies to the
parameter also. Could you please clarify it.

Thank you.
Nav.
Followup:
32k is the MAX size of any plsql variable there.

the "input variable" is a plsql variable.

if you have 32k of stuff here, I personally think you need to rethink something.
 that is HUGE.  I'd recommend looking into global temporary tables -- stop
playing with a huge string and just stuff the rows into a gtt and use it
instead.
Rating: 5
The restriction.. January 02, 2004
Reviewer: Naveen from India
Hi Tom,

The Application server we are using does not support REF Cursors, Varrays etc.
If the user accessing the application deletes the records that are being
displayed,  i will get comma seperated ids of those records, i'll seperate
commas and delete the rows. What should i do in these cases. There is no doubt
that data keeps increasing and some day 32k limit would be reached. The reason
why i haven't used GTT in the first case is, there is restriction in the design
that all the DML has to be handeled in the Pl/Sql procedures. If i use GTT in
procedures i need to have the string. So based on your advice i'll try to
convince my superiors and change the approach.

Thank you
Nav.
Followup:
what "application server" are you using that doesn't support jdbc/odbc ???

parse it, insert it into a gtt (global temp table) and delete from t where id in
( select * from gtt)
Rating: 5
Thanks again.. January 04, 2004
Reviewer: Naveen from India
Hi Tom,

Thanks for the advice. And the application server we are using is JRun3.1. I
aware that JDBC/ODBC drivers support all those thing i mentioned earlier. But
JRun has its own restrictions(like, you can get values from Varray, but cannot
set values to it) even though it is using JDBC/ODBC drivers. Anyway, i am
following your approach(GTT) and it seems the most sensible approach.

Have a nice day.

Thank you
Nav.
Followup:
you can drop in better jdbc drivers -- that is what java is all about.  you can
fully utilize your database if you so desire.
Rating: 4
Error "invalid column name" with DBMS_SQL.parse February 04, 2004
Reviewer: robert from CT
Tom, pls help !..I kept hitting error ORA-00904 with the INSERT under
DBMS_SQL.PARSE
below:
INSERT INTO
tblctsearchresult (userseq, clientlistrowid, datestamp, userid)
SELECT :l_seq, id, sysdate, user
  FROM v_clientlist
 WHERE buscodeid IN
 ( SELECT * FROM THE ( SELECT CAST( common_util.str2numtbl  ( :p_cbus ) as
numbertabletype ) from dual ) )

ORA-00904: invalid column name

Same sql (binds removed) in sqlplus executes w/o problem:

INSERT INTO tblctsearchresult
(userseq, clientlistrowid, datestamp, userid)
SELECT 5, id, sysdate, user FROM v_clientlist
WHERE buscodeid IN ( SELECT * FROM THE ( SELECT CAST(
common_util.str2numtbl('20,30,29')
as numbertabletype ) from dual ) );

825 rows created.

Thanks
Followup:
I'll need some help reproducing....
ops$tkyte@ORA817DEV> drop table tblctsearchresult;

Table dropped.

ops$tkyte@ORA817DEV> create table tblctsearchresult ( userseq number,
clientlistrowid number, datestamp date, userid varchar2(30)
  2  );

Table created.

ops$tkyte@ORA817DEV> drop table v_clientlist;

Table dropped.

ops$tkyte@ORA817DEV> create table v_clientlist ( id number , buscodeid number );

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace type numberTableType as table of number
  2  /

Type created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace function str2numtbl( p_string in varchar2
) return numberTableType
  2  as
  3          l_data numberTableType := numberTableType( 1,2,3 );
  4  begin
  5          return l_data;
  6  end;
  7  /

Function created.

ops$tkyte@ORA817DEV> show errors
No errors.
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> declare
  2      l_theCursor     integer default dbms_sql.open_cursor;
  3  begin
  4      dbms_sql.parse(  l_theCursor, '
  5  INSERT INTO
  6  tblctsearchresult (userseq, clientlistrowid, datestamp, userid)
  7  SELECT :l_seq, id, sysdate, user
  8    FROM v_clientlist
  9   WHERE buscodeid IN
 10   ( SELECT * FROM THE ( SELECT CAST( str2numtbl     ( :p_cbus ) as
 11  numbertabletype ) from dual ) )
 12   ',
 13  dbms_sql.native );
 14
 15          dbms_sql.bind_variable( l_theCursor, 'l_seq', 100 );
 16          dbms_sql.bind_variable( l_theCursor, 'p_cbus', 'hello' );
 17
 18          dbms_output.put_line( dbms_sql.execute( l_theCursor ) );
 19
 20  end;
 21  /
0

PL/SQL procedure successfully completed.

Any reason you are not using execute immediate???
Rating: 4
re "invalid column error" February 04, 2004
Reviewer: robert from ct
This is 8.1.7
Followup:
see above, cannot reproduce.
Rating: 5
re Error "invalid column name" in PARSE February 05, 2004
Reviewer: Robert from CT
Out of the woods now...thanks Tom,
Parsing is done by another schema, I fully-qualified identifiers in my code,
granted EXECUTE on the Nested Table Type (didn't know that)...not sure which
action fixed it thou.

DBMS_SQL seems more appropriate than EXE. IMMED in my case here (build search
sql) because I get better control of binding (ala webdb.wwv_bind technique).

One "glitch" I have now is with SQL%ROWCOUNT after exe the INSERT...I cannot
seem to rely on it.
This is WEB app and I am storing rowid in the search result table for
pagination...so if first search inserted 300 then  2nd search inserted
nothing..the SQL%ROWCOUNT value 300 is still retained...is this a cache thing ?
isn't 2nd search a separate session from the first ? (8.1.7/OAS 4.0.8)

Thanks
Followup:
sys_context :)  I use to bind in native dynamic sql.

sql%rowcount won't work at all with dbms-sql, that'll only work with static sql.

dbms-sql returns the number of rows affected, dbms_sql.execute returns the rows
affected.
Rating: 4
Inconsistent datatypes February 05, 2004
Reviewer: Alan Wagner from Chicago, IL
Tom, I'm finding your information VERY useful, thanks for providing this forum
for us novices to utilize.

Here 's the code that is generating an error:
CREATE OR REPLACE TYPE SGYDBO.sgyTpcSel as table of number;

CREATE OR REPLACE FUNCTION SGYDBO.str2tbl( tpcs in varchar2 ) return sgyTpcSel
    as
        l_str    long default tpcs || ',';
        l_n        number;
        l_data    sgyTpcSel := sgyTpcSel();
    begin
        loop
            l_n := instr( l_str, ',' );
            exit when (nvl(l_n,0) = 0);
           l_data.extend;
           l_data( l_data.count ) := ltrim(rtrim(substr(l_str,1,l_n-1)));
           l_str := substr( l_str, l_n+1 );
       end loop;
       return l_data;
    end;

CREATE OR REPLACE PROCEDURE sgyproc.sel_tpc_for_entity
(
 tpcs in varchar2,
 entId in char/*,
 prfls_cursor in out sel_prfls_pkg.prfls*/
)
as

    cursor rule_val is
        select sgy_tran_dr_cr_mpng.ent_id, sgy_ent.ent_name,
sgy_tran_dr_cr_mpng.tran_dr_cr_mpng_id, sgy_tran_dr_cr_mpng.tran_rptg_cde,
                       sgy_tran_dr_cr_mpng.dr_cr_id,
sgy_tran_dr_cr_mpng.dr_cr_type,
                       sgy_tran_rptg_dr_cr.dr_cr_ord,sgy_dr_cr_cde.dr_cr_name,
                       sgy_tran_rptg_cde.tran_rptg_desc,
sgy_tran_subctg_cde.tran_subctg_name,
                       sgy_tran_supctg_cde.tran_supctg_name,
sgy_tran_dr_cr_mpng_prf.mpng_prf_id,  sgy_mpng_prf.mpng_prf_name,
sgy_mpng_prf.mpng_prf_desc,
                       sgy_gl_acc.gl_acc_id, sgy_gl_acc.gl_acc_num,
sgy_gl_acc.gl_acc_name, sgy_mpng_prf_hrchy.hrchy_num
                  from sgy_tran_dr_cr_mpng,
                       sgy_tran_rptg_dr_cr,
                       sgy_dr_cr_cde,
                       sgy_tran_rptg_cde,
                       sgy_tran_subctg_cde,
                       sgy_tran_supctg_cde,
                       sgy_gl_acc,
                       sgy_tran_dr_cr_mpng_prf,
                       sgy_mpng_prf,
                       sgy_mpng_prf_hrchy,
                       sgy_ent
                 where sgy_tran_dr_cr_mpng.ent_id = entId
                   and sgy_tran_rptg_cde.tran_rptg_cde in
                   (select * from THE (select cast(sgydbo.str2tbl(tpcs) as
number) from dual))
                   and sgy_tran_dr_cr_mpng.ent_id = sgy_ent.ent_id

error:  inconsistent datatypes

by the way sgydbo created str2tbl if I remove that from the call I receive
invalid column error.
Any help would be much appreciated.
Thanks
Followup:
can you get the query down to the smallest possible example and include the
entire test case (eg: a script anyone of us could run on our machine).  remove
tables -- remove as much as you can (perhaps then, it'll be obvious what the
issue is -- but maybe not)

Make it "really small"
Rating: 4
Re:Inconsistent datatypes February 06, 2004
Reviewer: Alan from Chicago
Here's the reader's digest version.

The input value will be 999200,2983.  The "cde" in the where clause is a
varchar2(6).

CREATE OR REPLACE TYPE sgyTpcSel as table of varchar2(255);
/

CREATE OR REPLACE FUNCTION str2tbl( tpcs in varchar2 ) return sgyTpcSel
    as
        l_str    varchar2(255)default tpcs || ',';
        l_n        number;
        l_data    sgyTpcSel := sgyTpcSel();
    begin
        loop
            l_n := instr( l_str, ',' );
            exit when (nvl(l_n,0) = 0);
           l_data.extend;
           l_data( l_data.count ) := ltrim(rtrim(substr(l_str,1,l_n-1)));
           l_str := substr( l_str, l_n+1 );
       end loop;
       return l_data;
    end;
/
_______________
CREATE OR REPLACE PROCEDURE sel_tpc_for_entity2
(
 tpcs in varchar2
)
as
    w_name varchar2(80);
   cursor rule_val is
        select desc
                  from yourTable
                 where cde in
                   (select * from (select cast(str2tbl(tpcs) as sgyTpcSel) from
dual));

begin

    Open rule_val;
      Loop
         Fetch rule_val
          into  w_name;
          exit when rule_val%NOTFOUND;
      end loop;
      close rule_val;
end
;
/

Everything compiles fine, the trouble is when I attempt to debug it, I receive
an inconsistent datatype in the stored proc at the select statement.  At one
time I had everything working to point of coming back from the function.
However then the l_data was null, in trying to fix that I've seemed to cause the
above.  So I basically have two issues.

Thanks
Followup:
use the syntax on line 11 -- yours above isn't like mine way above -- but that
was the "old deprecated 8.0" synatx anyway, this is much easier now in 8i and
up:

ops$tkyte@ORA10G> CREATE OR REPLACE PROCEDURE sel_tpc_for_entity2
  2  (
  3   tpcs in varchar2
  4  )
  5  as
  6      w_name varchar2(80);
  7     cursor rule_val is
  8          select descript
  9                    from yourTable
 10                   where cde in
 11                     (select * from TABLE( cast(str2tbl(tpcs) as sgyTpcSel))
);
 12  begin
 13
 14      Open rule_val;
 15        Loop
 16           Fetch rule_val
 17            into  w_name;
 18            exit when rule_val%NOTFOUND;
 19        end loop;
 20        close rule_val;
 21  end ;
 22  /

Procedure created.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> exec sel_tpc_for_entity2( '999200,2983' );

PL/SQL procedure successfully completed.
Rating: 4
Re:Inconsistent DataTypes February 09, 2004
Reviewer: Alan from Chicago
The change you suggested worked great!  Thanks.
I'm still having a problem with the function though. When debugging the field
l_data is unknown and does not get populated with the results.  Am I missing
something?

CREATE OR REPLACE FUNCTION SGYDBO.str2tbl( tpcs in varchar2 ) return sgyTpcSel
    as
        l_str    varchar2(255) default tpcs || ',';
        l_n        number;
        l_data    sgyTpcSel := sgydbo.sgyTpcSel();
    begin
        loop
            l_n := instr( l_str, ',' );
            exit when (nvl(l_n,0) = 0);
           l_data.extend;
           l_data( l_data.count ) := ltrim(rtrim(substr(l_str,1,l_n-1)));
           l_str := substr( l_str, l_n+1 );
       end loop;
       return l_data;
    end;
/

Thanks
Followup:
please define "l_data is unknown and does not get...".  that doesn't make sense
to me.

ops$tkyte@ORA920PC> create or replace type sgyTpcSel as table of number
  2  /

Type created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> CREATE OR REPLACE FUNCTION str2tbl( tpcs in varchar2 )
return sgyTpcSel
  2      as
  3          l_str    varchar2(255) default tpcs || ',';
  4          l_n        number;
  5          l_data    sgyTpcSel := sgyTpcSel();
  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@ORA920PC>
ops$tkyte@ORA920PC> select *
  2    from TABLE( cast( str2tbl( '1,2,3' ) as sgyTpcSel )  )
  3  /

COLUMN_VALUE
------------
           1
           2
           3

ops$tkyte@ORA920PC>
Rating: 5
how to use with bind variables? March 02, 2004
Reviewer: Ryan Gaffuri from Tysons Corner, VA
Anyway to set this up so we can reuse sql and not parse it every time
Followup:
bind the string '1,2,3' -- don't use a literal, bind a string with 1,2,3 in it.
Rating: 5
hard parsing and variable inlist March 08, 2004
Reviewer: Ryan from Tysons Corner, VA
I ran the code as you stated with a bind variable. I am testing for a hard
parse. I simply changed the data values and ran it twice. I used the following
script to check for a hard parse: ( i ran it before running the test and
immediately after)

select b.name,a.*
from v$sesstat a, v$statname b
where a.statistic# = b.statistic#
and a.sid = (select distinct sid from v$mystat)
and name like '%parse%'

It showed that hard parse incremented by one when I run the variable inlist and
change the value.

however, when I do a 10046 trace and check the 'parse' column of the statistics
table I see only .01 CPU usage and nothing else. That infers to me that a hard
parse did not occur?

Can you explain the discrepency?

CREATE OR REPLACE PACKAGE BODY GetInList AS
FUNCTION GetNumberList (pString IN VARCHAR2) RETURN NumberTableType
IS
--  l_string        long default pString || ',';
  l_string        VARCHAR2(2000);
  l_data          NumberTableType := NumberTableType();
  n               NUMBER;
BEGIN
--  DBMS_OUTPUT.PUT_LINE (pString);
  l_string := ltrim (rtrim (pString,''''),'''') || ',';
--  DBMS_OUTPUT.PUT_LINE (l_string);
  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;
END GetInList;
/

-- test script (we change the value of InTable(1) to test for a hard parse.
declare
  TYPE VarcharTable IS TABLE OF VARCHAR2(200) INDEX BY BINARY_INTEGER;
  InTable VarcharTable;
  InList VARCHAR2(200);
  CURSOR TestCur IS
    SELECT col
    FROM tab
    WHERE col in
      ( SELECT * FROM TABLE (CAST (GETINLIST.GetNumberList (InList) AS
NUMBERTABLETYPE)) );
BEGIN
  InTable(1) := '''10''';
  FOR ListId IN InTable.First..InTable.Last LOOP
    InList := InTable(ListId);
    DBMS_OUTPUT.PUT_LINE ('InList = ' || InList);
    FOR TestRec IN TestCur LOOP
      DBMS_OUTPUT.PUT_LINE (TestRec.col);
    END LOOP;
  END LOOP;
END;
Followup:
run it a couple of times -- you are counting all sql in your session.  run it
over and over and you should eventually see "0" hard parses.
Rating: 4
March 23, 2004
Reviewer: Rob Kato from Chicago, IL
I have a query similare to yours >
select * from all_users where user_id in ( select *
from THE ( select cast( str2tbl( '1, 3, 5, 7, 99' ) as mytableType ) from dual )
)

I put an index on what would be the user_id column but Oracle will not pick it
up at all.
Not even when I try to force it using a hint. Any suggestions?
Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3779680732446#15740265481549
but, if not using a hint -- that might mean that user_id is a number ans str2tbl
is a string and you are doing a conversion -- sooo, select
TO_NUMBER(column_value) from the( str2table(.....) )

to convert
March 24, 2004 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
Rating: 5
pivot March 23, 2004
Reviewer: A reader
SQL> CREATE TABLE T(key number, DAY DATE, VALUE NUMBER);

Table created.

SQL>
SQL> INSERT INTO T VALUES( 1, SYSDATE, 10);

1 row created.

SQL> INSERT INTO T VALUES( 2, SYSDATE -1 , 10);

1 row created.

SQL> COMMIT;

Commit complete.

SQL> SELECT * FROM T ;

key    DAY            VALUE
---- --------- ----------
1    23-MAR-04         10
2    22-MAR-04         10

My data looks like above. Some times for a given key I will have one day and
some times 31 days
or any number of days in a given month.

Now, the sql I want to write will be provided a given month and the key, and I
have to transpose
the data and show all the dates as columns  with their respective values. For a
given key I
need to have only one row with 30 or 31 date columns( depending on the month the
query is running for)
.

1.How should can I dynamically change the number of columns depending on the
month passed. March
will have 31 where as FEB has only 28.

2.Is there a way to transpose this query dynamically instead of writing 31
decode statements.

What is the best way of writing this query.

thanks
Followup:
1) you would have to use dynamic sql -- so I would suggest you do not use
dynamic sql and you just always return 31 days (31 columns) with nulls in the
columns that have no values.  You could also return a column that tells the
program which column is really the "last column"

select max(decode( to_char(day,'dd'),1,value)) d1,
       max(decode( to_char(day,'dd'),2,value)) d2,
       ...
       max(decode( to_char(day,'dd'),31,value)) d31,
       max( to_char( last_day(:x), 'dd' ) last_col
  from t
 where key = :a
   and day >= trunc(:a)
   and day < add_months(trunc(:a),1)
/

2) well, you could write code to dynmically do it in a loop of course, but 31
columns requires, well, 31 columns.....
March 24, 2004 -- 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.
Rating: 5
what is the best way to get skewness February 20, 2004
Reviewer: reader
Tom, in 9i is WIDTH_BUCKET function useful to generate info about skewed data?
If so, any example you have? Thanks.
Followup:
it is a tool you can use to measure "skewedness".   for example:

ops$tkyte@ORA920PC> select min(object_id), max(object_id), count(object_id), wb
  2    from (
  3  select object_id,
  4         width_bucket( object_id,
  5                       (select min(object_id)-1 from all_objects),
  6                       (select max(object_id)+1 from all_objects), 5 ) wb
  7    from all_objects
  8         )
  9   group by wb
 10  /

MIN(OBJECT_ID) MAX(OBJECT_ID) COUNT(OBJECT_ID)         WB
-------------- -------------- ---------------- ----------
             3           8639             7972          1
          8640          17277             8638          2
         17278          25915             8619          3
         25916          34528             4751          4
         35159          43191              238          5

shows that if I bucketize into 5 buckets, most of my data is "low" 1,2,3.
object_ids over 26k are "few and far between"

it is an analysis tool you can use to see what you have.
Rating: 4
Can you please explain above ,little detail for my tiny brain February 21, 2004
Reviewer: A reader
"
shows that if I bucketize into 5 buckets, most of my data is "low" 1,2,3.
object_ids over 26k are "few and far between"
"
I did not understand what is low 1,2,3. where how to see it is low.
Similarly where/how to see
object_ids over 26k are "few and far between". few must be the count *
Followup:
ops$tkyte@ORA920PC> select min(object_id), max(object_id), count(object_id), wb
  2    from (
  3  select object_id,
  4         width_bucket( object_id,
  5                       (select min(object_id)-1 from all_objects),
  6                       (select max(object_id)+1 from all_objects), 5 ) wb
  7    from all_objects
  8         )
  9   group by wb
 10  /

I took the range:

low object_id-1 .. hi object_id + 1

and broken it into 5 equi-sized buckets (well, I didn't, the database did).  In
this example, that was the range

2..43191.

Now those ranges are roughly the min and max object ids in the following
report:

MIN(OBJECT_ID) MAX(OBJECT_ID) COUNT(OBJECT_ID)         WB
-------------- -------------- ---------------- ----------
             3           8639             7972          1
          8640          17277             8638          2
         17278          25915             8619          3
         25916          34528             4751          4
         35159          43191              238          5

I say roughly cause I do not have a continous set here -- 34529 for example
"has gone missing" -- not in the set.

So, now that we have those five ranges, we assign a bucket to each row in the
result set based on which range they fall into.

As you can see, range 1, 2, 3 have about 8k members each.  Most of my data is
"low".  Most of my data has an object_id less than 26k.

When you look at buckets 4 and 5 here -- there is only 5k rows between them.

Hence, the data is skewed,  most of it resides in the first 3 buckets.

do with that information "what you will" (eg: if those facts are un-interesting
to you -- that is OK.  this function is useful to someone analyzing the data --
to them, this would be fascinating perhaps)
Rating: 4
Histogram February 21, 2004
Reviewer: Peter Rhoades from Berkeley California
Good example.  However, our DBA runs a script that creates Histograms on EVERY
column that's in EVERY index in EVERY table--whether it makes sense to have the
Histogram stats or not.  I think that's crazy but he says it "doesn't hurt."  If
this is not a new thread, what do you think?  Can too many, needless histograms
mess things up?
Followup:
I typically start with the equivalent of "table", "index" and "all indexed
columns" myself.  If you have the cycles to compute the histograms, they can be
helpful (with bind variable peeking and such).

Could you skip most/many of them? probably.
Would it change your systems?  stats would take a little less time/resources.

I'm not opposed to what they are doing, it does more work then you need in
theory.  they might consider "auto" or "skewonly" in 9i.
Rating: 5
what about bind variable peeking ? February 21, 2004
Reviewer: Alberto Dell'Era from Milan, Italy
Imagine that a column has skewed values.

As an extreme case, all values are unique except one, say 0, which occurs n
times, where n is big enough to make a fts cheaper than an index scan (we have
an index on this column).

What if the app is using bind variables (and so i assume it's an OLTP
application) and the app developers thought "well, we don't compute histograms,
so the plan will always be the same, and will be an index scan. That's ok, since
we bind 0 only in very very rare occasions, and we can afford to pay for a
(very) suboptimal index scan in this very very rare occasion."

Now, the aforementioned DBA builds histograms since "they don't hurt", and
immediately after 0 is bound ... voila', full table scans forever (ok, until the
death of the shared sql stmt in the shared pool).

Isn't this an example of "hurting histograms" ?
Wouldn't it be better to avoid collecting them, if the app designers didn't ask
for them ?

Alberto
Followup:
the aforementioned DBA would have been building histograms since the beginning
of time and you would have caught this in test/development since you always
test/develop on real live sized result sets (or you spend lots of money on
coffee and soda right before/during a rollout -- because you won't be getting
any sleep)

I'll have to assume that what is "standard operating procedure" in production is
also in test/dev -- else, well, you have bigger things to worry about than this.

My point is -- "if this is what they always do, from the get go, I don't see
anything inheritly evil in it"

Sure, anytime you walk into a production system and say "Hey, we are going to
make this change" and just do it -- you stand a risk of "something going wrong".

The app designers in my experience don't even know what a histogram is.  They
would not know to ask for that which they do not know about.
Rating: 5
understood February 21, 2004
Reviewer: Alberto Dell'Era from Milan, Italy
Thanks for your prompt answer. Well, I assumed the worst scenario since "it
doesn't hurt" is exactly the same phrase that a DBA said during a nightly
rollout of mine, before making a change to the prod db, and the Day After [pun
intended] we experienced an incredible loss of performance ...

Got a Pavlovian Reflex to that phrase.

thanks
Alberto

PS What about automatically removing trailing empty lines from reviews/questions
posts, like mine before ? I normally pay attention and get rid of them myself
but sometimes I overlook them ...
Rating: 3
why it's not good to generate histograms always February 21, 2004
Reviewer: A reader
Hi

Recently we generated histograms for our application and most queries was deadly
slow, when we got rid of histograms performance went back again except some
queries.

My question is why queries run slow with histograms? Arent we giving more
information to the optimizer in order to get a better plan? It seems the other
way round, the more information you give the worse CBO performs!
Followup:
it depends -- and it really depends on your system characteristics.

Many times people have OLTP systems that have "open ended queries" that they
fetch the first 10, 100 or so rows from and never get to the bottom.  These
(unless hinted as "first rows" or run on a system where thought was given to the
optimizer_index_* init.ora parameters) can perform poorly in BOTH cases
"apparently" (apparently perform poorly, but not really since the optimizer will
optimize for throughput in such a system -- not initial response time.  give it
more info and it'll come up with plans that better optimize throughput response
times at the expense of initial responses times.  tell it first rows and it'll
change that.)

But the bottom line here was -- you introduced "a rather large fundemental
change into the system" and would have to expect some ramifications.

Do this -- develop a system from day one with histograms generated.

Then, take them away

And then report back "what happened".  You will find pretty much any major
league "change in the way we do business" is going to have such an effect.  For
example -- in the above thread with regards to the DBA that always gets
table/index/column stats -- if you want to kill that system, stop gathering
histograms and see what happens.
Rating: 5
you said February 22, 2004
Reviewer: A reader
hi

You said that DBA should always generate histograms deelopment to catch bind
variable peeking effects.

I always thought we should not generate histograms always and identify those
columns with skewed data with analysist?!?!

As someone else already said, generating histograms always is deadly, I got
burnt as well
Followup:
no, no I did not.

where did I say "dba should always generate histograms"

generating histograms "as a new operating procedure after the code is in place"
is what is known as a "bad idea (tm)"

NOT generating histograms "as a new operating proceudre after the code is in
place" is what is knowns as a "bad idea(tm)"

what I said was "do what you do, but do what you do consistently.  don't use one
method in test/dev and another DIFFERENT method.   You most likely got burnt
either

o testing in a database with little or no data
o testing in a database with stats gathered one way and a different way in prod

else, you could not "get burned" as you would have discovered this stuff right
from the get go in test/dev.

What I propose is "consistency".
Rating: 5
histogram's effect on performance March 02, 2004
Reviewer: Ryan Gaffuri from Tysons Corner, VA
there is a debate on oracle-l about excessive use of histogram's negatively
effecting performance. Carrie Milsap posted the following:

"I don't have access to the data to back this up, but I have seen PARSE call
response time move from over a minute to less than a second by eliminating
histograms from the equation. I think when I last saw this, it was an Oracle8
system."

Do you have any comments on this? How often do you recommend gathering
performance? I have been using 'FOR ALL COLUMNS' by default recently with no
adverse affects on performance.
Followup:
and it could go the other way as well.  histograms are just *data*.  in the
above, i would have to guess that histograms caused the optimizer to consider
"more stuff", more join paths perhaps (but it is hard to say, it is really rare
to have parse times of a minute).

the big thing with histograms many times is the amount of resources they take to
compute in the first place.  I start with "none" or "for all indexed columns" --
the key thing is to be consistent (in dev, test, and prod).  In order to avoid
surprises.
Rating: 3
Why HISTOGRAM? March 23, 2004
Reviewer: Jayesh from India
Can you please explain why should we go for histogram? What is real purpose of
using histograms? What will be high and low value when using a varchar column?
Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/stats.htm#27027
March 24, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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)
Rating: 5
dbwr timeout January 25, 2004
Reviewer: Reader
Does DBWR checkpoint every three seconds because of the timeout? If so, is it a
full checkpoint or certain number of blocks that is determined by oracle itself?
Thanks.
Followup:
ckpt is what times out.  control files are updated.  lgwr times out.  redo log
buffers are flushed.

dbwr looks around as well - and does stuff when it is supposed to in accordance
with a request from lgwr (just switched logs), the fast start mttr target (hey,
flush blocks so recovery is fast enough in the event of failure) and so on.
Rating: 4
DBWR AND CPU March 20, 2004
Reviewer: Mahesh Kumar from INDIA
Hi Tom

  After reading all i just wanna know that why DBWn are dependent of No. of
CPUs.
Followup:
they are processes that would be working simultaneously.  the cpu is a shared
resource they all need.  hence the dependency.
Rating: 5
DBWR AND CPU March 22, 2004
Reviewer: Mahesh Kumar from India
Thanx Tom
Rating: 2
DBWR and Latches March 23, 2004
Reviewer: Aashish from India
Hi Tom

I reviewd all this page but still confusen in there regarding that latches are
dependent on no. of CPU or DBWR.
Can you please explain about the working of DBWR with Latches and how much
latches can be configure and which parameter need to change and what should be
the ratio between DBWR and latches or this depend on the CPU.

Thnx
Aashish Kapoor
Followup:
if you have a single cpu, you'll have a latch.
if you have more than one cpu, you'll have more than one latch.

it is dependent on the number of cpu's.  if you don't have more than one cpu
(only one thing at a time) we only need one latch to provide protection for this
stuff.

if you have multiple cpu's, more than one latch is useful -- in order to provide
concurrency.
March 24, 2004 wallacel -- Thanks for the question regarding "miss of cache buffer chain", version 8.1.6
You Asked
Tom:

when using oracle performance manager, i notice inside of
Top Latches misses chart, we have "cache buffers chains"
what's that?
if it is high , how to solve it?

Regards
and we said...
Blocks in the buffer cache are placed on linked lists
(cache buffer chains) which hang off a hash table.
The hash chain that a block is placed on is based on the DBA
and CLASS of the block. Each hash chain is protected by a
single child latch. Processes need to get the relevant latch
to allow them the scan a hash chain for a buffer so that the
linked list does not change underneath them.

Contention for these latches can be caused by:

- Very long buffer chains.
- very very heavy access to the same blocks.

When I see this, I try to see what SQL the waiters are trying to
execute.  Many times, what I find, is they are all running the
same query for the same data (hot blocks).  If you find such a
query -- typically it indicates a query that might need to be
tuned (to access less blocks hence avoiding the collisions).

If it is long buffer chains, you can use multiple buffer pools
to spread things out.  You can use DB_BLOCK_LRU_LATCHES to
increase the number of latches.  You can use both together.
Review & Followup

Rating: 4
Deeper explanation would be nice December 05, 2001
Reviewer: john from ukraine
Tom,

your explanation was very useful, thanks.
And I want to clear some things up:

If we query v$latch_children we will find
there exactly 1024 hash latches, no matter
what is the number of _db_block_hash_buckets.
So the more hash chains we have the more will
be the hash chain latch contention?

is it correct?
Followup:
what is the exact name you are querying on.
Rating: 4
December 06, 2001
Reviewer: john from ukraine
I'm using this query:

select count(*) from v$latch_children
    where name = 'cache buffers chains'
Followup:
there are N cache buffer chains latches, depending on the size of your buffer
cache.

ops$ora817@ORA817DEV.US.ORACLE.COM> show parameter db_block_buffers

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
db_block_buffers                     integer 11000

ops$ora817@ORA817DEV.US.ORACLE.COM> select count(*) from v$latch_children
  2      where name = 'cache buffers chains';

  COUNT(*)
----------
      1024

ops$ora817@ORA817DEV.US.ORACLE.COM> show parameter db_block_buffers

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
db_block_buffers                     integer 1000
ops$ora817@ORA817DEV.US.ORACLE.COM> l
  1  select count(*) from v$latch_children
  2*     where name = 'cache buffers chains'
ops$ora817@ORA817DEV.US.ORACLE.COM> /

  COUNT(*)
----------
       256

ops$ora817@ORA817DEV.US.ORACLE.COM>

So, tweak your block buffer cache and you'll get more/less cache buffers chains
latches.

The bigger the buffer cache, the more you get to reduce contention.
Rating: 5
December 06, 2001
Reviewer: john from ukraine
Thanks a lot!
Rating: 5
December 06, 2001
Reviewer: Tianhua Wu from Atlanta, GA
Hi Tom,
If I remember correctly,  the value of db_block_lru_tatches is defined between
0.5*cpu_count and 6*cpu_count.  From the previous exemple, this limitation is
not there any more.  Is that correct?  If you , do you have any new guide for
use this parameter?
Followup:
No, db_block_lru_latches and cache buffers chains are different beasts.
db_block_lru_latches is normally used in conjunction with multiple buffer pools
or multiple dbwr's.  cache buffers chains are latches to the individual, hashed
lists of buffered blocks.

they are separate.
Rating: 5
December 06, 2001
Reviewer: Tianhua Wu from Atlanta, GA
Hi Tom,
Ok, I confused lru chain with cache buffer chains. For my understanding, the
hash buckets by deault is a quarter of db buffers before Oracle 8i and two times
of buffers for 8i (of course there is a limitation).  Also the number of buckets
can be changed by _db_block_hash_buckets.  How can I increase the number if
cache buffer chain latches?
I just want to understand this better.
Thank you!

T. Wu
Followup:
Add more block buffers -- it is a function of the number of block buffers.
Rating: 5
DB_BLOCK_LRU_LATCHES and free buffer waits May 19, 2002
Reviewer: Nash
What co-relation does db_block_lru_latches have to 'free buffer waits'?

If we increase the db_block_lru_latches then will it possibly increase the dbwr
throughput and hence decrease the 'free buffer waits'...

Thanks
Followup:
you need to be using:

...
db_block_lru_latches is normally used in conjunction with multiple buffer pools
or multiple dbwr's.
.....

in conjunction with that parameter to make a difference.  You are basically
tuning dbwr with it.
Rating: 5
May 21, 2002
Reviewer: A reader
Hi tom,

 In your first paragraph you told
"Blocks in the buffer cache are placed on linked lists
(cache buffer chains) which hang off a hash table.
The hash chain that a block is placed on is based on the DBA
and CLASS of the block. Each hash chain is protected by a
single child latch. Processes need to get the relevant latch
to allow them the scan a hash chain for a buffer so that the
linked list does not change underneath them."

I understood "Blocks in the buffer cache are placed on linked lists
(cache buffer chains)"

Rest of the stuff go over my mind.

Can you explain "hash table","DBA" and other in simple terms?

Thanks.
Followup:
A hash table is a simple computer science data structure.  If you have my book
-- I go into details (with pictures and such) as to how it works.  If you don't
have my book -- most any book on data structures will cover what they are.  See
also
http://www.nist.gov/dads/HTML/hashtab.html
DBA = Data
      Block
      Address

it is the file/block#
Rating: 5
very big data buffer cause cache buffer chains latch contention August 07, 2002
Reviewer: A reader
hi tom

you said that very long buffer chain list could lead to latch contention, is
this the case of a very big data block buffer?
Followup:
if you mean "will a larger block size reduce this contention" -- the answer is
-- it depends.  If you have a hot block, you have a hot block and a bigger block
won't help that.
Rating: 4
I mean db cache August 08, 2002
Reviewer: A reader
Hi I mean a very large database cache (data block buffer) would cause latch
contention like a large shared pool causes latch contention
Followup:
the bigger the cache, the more hash buckets we create.

more blocks = more lists = more latches.

So no, it would not.
Rating: 4
then what exatly is August 08, 2002
Reviewer: A reader
hi

Then what do you mean by Very long buffer chains?

thank you
Followup:
lots of blocks all hashing to the same list (eg: having hundreds of consistent
read versions of the same block and having hundreds of blocks on a list can add
up)
Rating: 3
How to find the sql being waited on January 14, 2003
Reviewer: A Reader from UK
Hi Tom,

I have a really bad problem with cache buffers chains - you mention 'When I see
this, I try to see what SQL the waiters are trying to execute' - how do I find
exactly which SQL and which blocks this is a problem for ?

Regards,

Paul
Rating: 4
Simialr Issue February 28, 2003
Reviewer: Matt from Australia
I'm investigating a similar issue with cache buffer chains. (I also have some
library cache latch contention)

SQL> l
  1  select addr, gets, misses, sleeps from  v$latch_children  where name =
'cache buffers chains'
  2* and misses > 100
SQL> /

ADDR                   GETS     MISSES     SLEEPS
---------------- ---------- ---------- ----------
0000000406E4E7D8     382594        838        194
0000000406E35BD8      35723        110         85
0000000406E0DBD8     177819        334        149
0000000406DFE3D8     162678        223         69
0000000406DFB7D8     125035        117         54
0000000406DF93D8     198635        224         74
0000000406DF5BD8     144774        201         59
0000000406DBE7D8      93282        106         39
0000000406D973D8      68897        166         69
0000000406D907D8    2486324      18116       5372
0000000406D903D8    4970297      88713      51715
0000000406D74358     120722        102         44
0000000406D6A4E8     143278        117         73

I've highlighted that these are the two latches with contention.

0000000406D907D8    2486324      18116       5372
0000000406D903D8    4970297      88713      51715

So I look for the data blocks...

SQL> l
  1  SELECT file#, dbablk, class, state
  2  FROM X$BH
  3* WHERE HLADDR=address of latch;
SQL> 3  WHERE HLADDR='0000000406D907D8';
SQL> /

     FILE#     DBABLK      CLASS      STATE
---------- ---------- ---------- ----------
        69      61308          1          1
       108       2772          1          1
         2      14355         38          1
        89      28987          1          1
        28        346          1          1
        94      33852          1          1
        89      37179          1          1
        97       4110          1          1
        69     182506          1          1
        63        656          1          1
        63       2704          1          1
       105       2858          1          1
       124       1545          1          1
        69     137267          1          1
        37       1404          1          1
        94     150954          1          1
         1        267          1          1
        15      24365          1          1
       120       3086          1          1
        69     148531          1          1
        99       1200          1          1
        86        550          1          1
        69     102268          1          1
        21       6746          1          1

24 rows selected.

SQL> l
  1  SELECT file#, dbablk, class, state
  2  FROM X$BH
  3*  WHERE HLADDR='0000000406D907D8'
SQL> 3 WHERE HLADDR='0000000406D903D8'
SQL> /

     FILE#     DBABLK      CLASS      STATE
---------- ---------- ---------- ----------
        69      61307          1          1
       122        358          1          1
       103       2695          1          1
       108       2771          1          1
        89      28986          1          1
        68     180888          1          1
        69     221600          1          1
       118       2923          1          1
        91       1283          1          1
        69      28356          1          1
        16        381          1          1
        84       2435          1          1
        84       3459          1          1
        37       2427          1          1
        94     150953          1          1
         1        266          4          1
        15      24364          1          1
        69     145458          1          1
         9       1998          1          1
        69      96123          1          1
        69     148530          1          1
        69     102267          1          1
        69     153650          1          1
       129       1736          1          1
       126        682          1          1
         6      11606          1          1

26 rows selected.

I then use the following to identofy the objects

 SELECT owner, segment_name
  2  FROM DBA_EXTENTS
  3  WHERE file_id = &p1
  4* AND &p2 between block_id AND block_id + blocks - 1

Abobe I've noticed that the file ids 69, 84 and 63 appear more than once. Using
the above query I have identified three objects that seem to be the cause of
contention. These are ALL indexes.

THis is a snippet of teh statspack report:

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,027.46            617,719.38
              Logical reads:              3,624.48             13,014.84
              Block changes:                959.83              3,446.59
             Physical reads:                 21.83                 78.40
            Physical writes:                 10.07                 36.14
                 User calls:                872.40              3,132.62
                     Parses:                 10.52                 37.77
                Hard parses:                  0.03                  0.12
                      Sorts:                  6.62                 23.76
                     Logons:                  0.00                  0.01
                   Executes:              1,946.38              6,989.09
               Transactions:                  0.28

  % Blocks changed per Read:   26.48    Recursive Call %:    81.67
 Rollback per transaction %:    0.20       Rows per Sort:     5.33

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:   99.93       Redo NoWait %:  100.00
            Buffer  Hit   %:   99.41    In-memory Sort %:  100.00
            Library Hit   %:  100.00        Soft Parse %:   99.68
         Execute to Parse %:   99.46         Latch Hit %:   97.26
Parse CPU to Parse Elapsd %:   71.13     % Non-Parse CPU:   99.83

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   94.03   92.86
    % SQL with executions>1:   68.40   67.82
  % Memory for SQL w/exec>1:   19.74   26.75

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
          -------------------------------------------------------------

The SQL I am seeing appears to indicate that there is a data load occuring.

Is it updates to indexes that would be causing this problem?
How would I tune this in this case (inserting/updating the index).

I'm also working on the other waits listed above.
Rating: 3
cache buffer chains - 9.2.0.3 May 28, 2003
Reviewer: Baqir Hussain from Emeryville, CA USA
Tom,
What parameters need to be tuned to avoid cache buffer chain problems in
9.2.0.3?
Please advise.
Thanks
Followup:
it is less a problem of init.ora parameter and more of design.

avoid really really really hot blocks.
avoid 50 billion people full scanning the same table over and over and over.

those are the major causes of cbc latching issues.
Rating: 5
db buffer cache sizing January 01, 2004
Reviewer: mike
Tom, happy new year!!

i have 8174 db.
how can i know that i have the db buffer too small?
thanks
Followup:
trick question.

you have to first make sure your SQL is as good as it gets (reduce the number of
LIO's your sql does).

Then, you need to understand if a sql cache of any size can cache some
percentage of your data (maybe it can, maybe it can't)

Then you can look at ratio to see if you have a satisfactory hit.

But basically, you want to look at what you system is waiting for.  Not waiting
on physical IO?  then your cache is at least big enough.  Waiting on physical
IO?  DO NOT INCREASE YOUR BUFFER CACHE FIRST, rather look at your top SQL, if
you have a query that is executed lots and returns like 2 rows but does 100
LIO's to do it -- you need to look at that ( i would hope for single digit IO's
for a popular query that returns 2 rows).  do you have a popular query that just
does a ton of LIO's (regardless of the rows) -- why?  can you do anything about
it? if so -- fix it.

Then, you might find that your buffer cache is just fine after all (watch the
LIO's and the PIO's will take care of themselves).

Increasing the buffer cache to help a PIO problem is the last step -- you want
to exhaust other avenues first (more efficient sql, better algorithms, quit
running stuff you don't really need to run, that sort of stuff -- they'll give
you the big payoff.  increasing the buffer cache doesn't always have the same
potential as those approaches)
Rating: 2
db_block_hash_buckets March 23, 2004
Reviewer: Aashish from India
Hi Tom

Can you please explain about db_block_hash_buckets for what purpose it used how
do we find out the need to change this parameter, and what is the relation of
latches with this parameter.
Followup:
tis one of the magical _parameters.

no need to touch it without support saying 'please touch it'
Rating: 2
db_block_hash_buckets March 23, 2004
Reviewer: Aashish from India
Hi Tom

Can you please explain about db_block_hash_buckets for what purpose it used how
do we find out the need to change this parameter, and what is the relation of
latches with this parameter.

Thnx
March 24, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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://download-west.oracle.com/docs/cd/B10501_01/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.
Rating: 5
Is possible to create a view to know the rowid being modified February 02, 2004
Reviewer: A reader
Hi Tom, if possible could you show us how can we create a view to see which
table, rowids are locked and which users are locking them.
Thanks.
Followup:
we do not store a master list of row locks in memory.  that would be sqlserver,
db2 and others that have limits on the number of locks, consume memory for locks
and have to implement 'features' like lock escalation in order to work around
this issue.

So no, you cannot create such a view.
Rating: 5
Thanks a Lot February 03, 2004
Reviewer: A reader
Rating: 4
Externally locking a table?? March 10, 2004
Reviewer: Prasad from UK
Dear Tom,

I'll explan my scanario.
I've a third party C executable (which i cannot modify) which after running in
"select" mode inserts a table(CEU) with n number of rows..these are nothing but
the rowids in a MAIN table(MAIN_CEU).(It may take 10-15 mins for this to
complete). After this we run the same exe in "delete" mode so it picks up these
rowids from CEU table & deletes the MAIN table(MAIN_CEU) with these rowids.
Now the problem is that if some other user at the same time runs this process in
"select" mode the CEU table gets populated again but that user might not want to
delete these rows so he wont run the exe in "delete" mode. But as i'm running
the "delete" mode simultaneously, these rows will be getting deleted unwantedly.
So i want to lock this table as soon as i start my "select" mode & want to
unlock once i finish the "delete" mode so no other user can insert this table
during that time.

Please advise.
Followup:
can you use a trigger?

ops$tkyte@ORA9IR2> create table t as select object_id, object_name from
all_objects;

Table created.

ops$tkyte@ORA9IR2> alter table t add constraint t_pk primary key(object_id);

Table altered.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create trigger t_trigger before DELETE on t
  2  begin
  3          lock table t in exclusive mode;
  4  end;
  5  /

Trigger created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> delete from t where object_id = 55;

1 row deleted.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2     pragma autonomous_transaction;
  3  begin
  4          insert into t values ( -1, 'xxxx' );
  5          commit;
  6  end;
  7  /
declare
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
ORA-06512: at line 4
Rating: 5
Very TOM like March 10, 2004
Reviewer: Prasad from UK
Why didn't i think of that.
Thanx Tom.
Rating: 5
More on ORA-02049: timeout: distributed transaction waiting for lock March 23, 2004
Reviewer: Miroslav
Hello Tom,

regarding the ORA-02049: timeout: distributed transaction waiting for lock
error, how is it possible to get this error if a transaction is not locking an
object in a remote database?

Our application executes MERGE statement by doing a SELECT from a remote table
via DB link, and merging into the *local* table, and then it gets ORA-02049.  It
doesn't really modify or lock anything in the remote database (apart from
brakeable share locks for parsing), so, it seems to me that only the local locks
will be needed.  Does ORA-02049 mean that a lock in the remote database cannot
be obtained, and, if so, what lock would that be?
Followup:
An ORA 2049 is signalled if

o you are blocked
o you are in a distributed transaction (you are using a DB link, even just to
read)
o you wait for longer than distributed lock timeout

The use of a DB Link opens you up to distributed rules of operation even if you
only READ from it.

You can either increase the timeout OR handle the ORA 2049 as a 'try again'
exception that is not fatal.
Rating: 3
OK March 23, 2004
Reviewer: Jacob from Pune,India
Dear Tom,
Is it possible to have a "Exclusive Read Exclusive Write"
scenario in Oracle provided we have an Oracle environment
with users granted sufficient privileges to read and write
to data objects?
And also I am confused with "Shared Row Exclusive".What does this lock type do?
Followup:
what is "exclusive read exclusive write"
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96524/c21cnsis.htm#3112
for the definition of the shared row exclusive lock (and all others)
March 24, 2004 Mohammed -- Thanks for the question regarding "Cross Matrix", version 8.1.7
You Asked
Hi Tom,
       I was trying to do matrix report for these result of data
PHONE_NUMBER                     START_DAT END_DATE      SIM_ID
-------------------------------- --------- --------- ----------
71766147                         03-SEP-01 23-SEP-01 1001925400
71766147                         23-SEP-01 05-FEB-02 1000581032
71766147                         05-FEB-02 05-FEB-02 1004067313
71766147                         05-FEB-02           1004067123

these are the transaction of changing SIM_ID how can i do Cross matrix  like

PHONE_NUMBER       New IMSI      OLD_IMSI1  OLD_IMSI2  OLD_IMSI3 ....
-------------------------------- ---------- ---------- ----------
71766147         1004067123     1001925400  1000581032 1004067313 ...

these data from one table I was trying by doing this sql

select phone_number,
       max(decode(END_DATE,Null,Sim_id) ) Old_IMSI1,
       max(decode(END_DATE,Null,Sim_id) ) Old_IMSI2,
       max(decode(END_DATE, Null,Sim_id) ) Old_IMSI3
from (Select distinct
             phone_number,id,end_date,Sim_id
      from  subscriber_channels
      where phone_number ='71766147')
group by phone_number

but it does not realy work properly I was trying to Put Null NOT  insted of
NULL but it does not work .

Could you please help me to do this Report ,I am still  beginer in doing Matrix
Report in SQL LEVEL .

Thank you very much

Not : if END_DATE IS NULL then this is the NEW IMSI
################################################################
Good Morning :
I realy sorry if I sent you this updated agina because my computer was
shoutdowned when it was Updating . ;)

Well , Yes  there  is no  UPPER BOUND to the number of old "imsi's"
phone number can have at most N  old sim_id's)

As I explained  to that If i use This SQL :
SQL > select Phone_number,start_date,End_date from subscriber_channels
     where phone_number ='71766147 '
I will get this Informations;
PHONE_NUMBER                     START_DAT END_DATE      SIM_ID
-------------------------------- --------- --------- ----------
71766147                         03-SEP-01 23-SEP-01 1001925400
71766147                         23-SEP-01 05-FEB-02 1000581032
71766147                         05-FEB-02 05-FEB-02 1004067313
71766147                         05-FEB-02           1004067123

where some times phone_number can only have one Active SIM_ID
and may be many of old SIM_ID .
how can I get the Information as Cross Matrix  Report.
like :

PHONE_NUMBER       New IMSI      OLD_IMSI1  OLD_IMSI2  OLD_IMSI3 ....
-------------------------------- ---------- ---------- ----------
71766147         1004067123     1001925400  1000581032 1004067313 ...

With my best Regards .

####################################################################
and we said...
Either of:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select phone_number,
  2         max( decode( rn, 1, sim_id, null ) ) "New IMSI",
  3         max( decode( rn, 2, sim_id, null ) ) Old_imsi1,
  4         max( decode( rn, 3, sim_id, null ) ) Old_imsi2,
  5         max( decode( rn, 4, sim_id, null ) ) Old_imsi3
  6    from ( select phone_number,
  7                  sim_id,
  8                  row_number() over ( order by end_date NULLS FIRST ) rn
  9             from t
 10            where phone_number = 71766147
 11         )
 12   group by phone_number
 13  /

PHONE_NUMBER   New IMSI  OLD_IMSI1  OLD_IMSI2  OLD_IMSI3
------------ ---------- ---------- ---------- ----------
    71766147 1004067123 1001925400 1000581032 1004067313

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select phone_number,
  2         max( decode( rn, 1, sim_id, null ) ) "New IMSI",
  3         max( decode( rn, 2, sim_id, null ) ) Old_imsi1,
  4         max( decode( rn, 3, sim_id, null ) ) Old_imsi2,
  5         max( decode( rn, 4, sim_id, null ) ) Old_imsi3
  6    from ( select phone_number,
  7                  sim_id,
  8                  rownum rn
  9             from ( select phone_number, sim_id
 10                      from t
 11                     where phone_number = 71766147
 12                     order by end_date NULLS FIRST )
 13         )
 14   group by phone_number
 15  /

PHONE_NUMBER   New IMSI  OLD_IMSI1  OLD_IMSI2  OLD_IMSI3
------------ ---------- ---------- ---------- ----------
    71766147 1004067123 1001925400 1000581032 1004067313

should do it.  Here I assumed there was at most 4 sim_id's / phone.  If there
are more, add more decodes.
Review & Followup

Rating: 4
Cross Matrix May 24, 2002
Reviewer: Krishnan from India
This was very useful in getting the idea, though I was looking for a similar SQL
for Sybase.
Rating: 4
matrix report for these result using sql query March 23, 2004
Reviewer: niza from singapore
     I was trying to do matrix report for these result of data.
 Actuall i've stock table, when i
 select item_code,revison from stock..
the following result appear

item code    revision

1001           R1
1001           R2
1002           R1
1002           R2
1002           R3

i'm using oracle 8.16
currently my report the revisions show row wise, But i'm trying to the report
column  wise like that

1001       R1 R2
1002       R1 R2 R3

How do it from sql plus
Followup:
select item_code,
       max(decode( revision, 'R1', revision ) ),
       max(decode( revision, 'R2', revision ) ),
       .....
       max(decode( revision, 'RN', revision ) )
  from t
 group by item_code;
Rating: 2
matrix report for these result using sql query March 23, 2004
Reviewer: nisha from singapore
Hi tom , thanks for ur query.

 Actuall my requirments totally different. because some item code may be more
than 100 or 200 revision. My revison table more than 2000 id(revison). If n
number of revision how to do it. i need the result below like that, how to do it
from sql. i'm using the query for datawarhouse report.

for eg.

1001       R1 R2
1002       R1 R2 R3
1003       R1 R2 R3 R4 R5 R6 R7.....R100
Followup:
well, then you need to have the client pivot it OR run a query to figure out
"how many columns" and then dynamically build a query to query them up.

SQL sort of needs to have "n number of columns"
March 24, 2004 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://download-east.oracle.com/docs/cd/F49540_01/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://download-east.oracle.com/docs/cd/F49540_01/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.
Rating: 5
TAF January 26, 2004
Reviewer: Reader
Tom, is TAF a new feature in 9i for RAC? Thanks.
Followup:
no, it predates 9i
Rating: 5
TAF March 06, 2004
Reviewer: A reader
Tom,

In TAF (SELECT and SESSION), the aborting of instance on Node1, can failover
instance from Node1 to Node2 and keep the session alive. Where and how does
Oracle
store the session information during this failover. Instance has aborted and so
the
SELECCT information can not come from the Instance1. The RAC has switched over
from
Node1 to Node2, and hence the information can not come from PGA. How Oracle
stores
and retieves the result dataset of the session and continue to process the
SESSION
Followup:
the client does that bit of the magic.
Rating: 1
Asktom.oracle.com not 24x7 March 06, 2004
Reviewer: andrew from ca, usa
What ever happened to 24x7 at Oracle?

6 March 2004:
"Ask Tom and www.promedmail.org is currently being upgraded.

We will be back around midnight!"
Followup:
of course it is not 24x7x365.25 -- why would we invest in 24x7 for a casual use,
non-mission critical system?

People seem to think "24x7x365" is "free" or cheap.  It is not, it costs alot.
It is something you pay for on systems that need to be that way (and honestly,
there are few that truly need be 24x7x365).

Asktom uses scheduled downtimes to do things like, oh, OS patches (that was wed)
and DB upgrades (that was last night, we are on 10g now...)

Tell me -- why would we make something like this 24x7x365 at great expense?
(you would laugh if you knew how much this thing "costs" from a
hardware/maintenance perspective -- it is sort of a "out of my garage" like
situation really.
Rating: 3
Now that you are on 10G... March 06, 2004
Reviewer: Matt from Australia
...is there any chance that you can publish some of your before and after
migration page generation performance metrics for comparison? (I understand that
asktom is all PL/SQL and have read that this is "up to 30% faster" in 10G due to
various compiler enhancements).

What initiated the move to 10G?
Followup:
well, the programming language is PLSQL -- most of asktom is SQL.

The move was initiated by the fact we were on 9201 and were faced with either

a) patching upto 9204
b) going to 10g

in order to be on a supported release.  I opted for 10g (i regularly go with the
latest release)

We'll need a week of observations to make this more "scientific" however --
based on the the last twelve hours of asktom activity as compared to the same
period a week ago, I have observed:

ops$tkyte@ORA10G> select step_id,
  2         round(AVG_ELAP_RECENT,2) recent,
  3             round(AVG_ELAP_OLD,2) old,
  4             cnt_elap_recent recent_cnt,
  5             cnt_elap_old old_cnt,
  6             to_char( round( avg_elap_recent/avg_elap_old*100,2), '990.00')
pct,
  7             to_char(
round((avg_elap_recent/avg_elap_old*100),2)-100,'990.00') diff
  8    from tmp
  9  /

   STEP_ID     RECENT        OLD RECENT_CNT    OLD_CNT PCT     DIFF
---------- ---------- ---------- ---------- ---------- ------- -------
         1        .41        .58       1897       1734   70.20  -29.80
         6       1.15       1.51         84         20   76.58  -23.42
         8        .47        .54       1811       1839   87.47  -12.53
         9        .28        .33         38          7   83.33  -16.67
        12       1.87       7.34        181         35   25.43  -74.57
        17        .22        .27         72         71   82.75  -17.25
        18        .23        .27         30         21   84.75  -15.25
        22        .89       1.19          2          2   75.11  -24.89
        32         .6        .62         10          6   95.45   -4.55
        40        .19         .2         11         16   95.92   -4.08
        60        .17        .21         27         25   84.50  -15.50
        61        .28        .34          6          7   81.67  -18.33
        70       1.21       1.35         57         71   89.26  -10.74
       109        .14        .14         36          1   99.80   -0.20

14 rows selected.

(step ids are in the URL, they are the "page number" right after the 4950)

step 1 is the home page.
step 6 is the listing of archives available
step 8 is showing of the Q&A
step 9 is putting in a review

step 12 is interesting, that is a printout of the archives -- it is very plsql
intensive with lots and lots and lots of string operations (word wrapping).
It'll be interesting to see if that was an "exceptionally bad set of archives
chosen" last week or not (see if that increase holds up over the long haul).

step 17 faq list
step 70 is advanced search

the rest -- too few observations I think to say anything, but looking good so
far (only a db upgrade and OS patch, no hardware change as yet - thats coming
and yes, we'll be down for a couple of hours to switch from our now 7 year old
sparc/solaris to a new dell/redhat combo)

remind me in a week if you like and I'll do this report again for the week.
Rating: 3
Now that you are on 10G... March 06, 2004
Reviewer: Matt from Australia
...is there any chance that you can publish some of your before and after
migration page generation performance metrics for comparison? (I understand that
asktom is all PL/SQL and have read that this is "up to 30% faster" in 10G due to
various compiler enhancements).

What initiated the move to 10G?
Rating: 5
March 07, 2004
Reviewer: A reader
Excellent.
Rating: 5
Link to read more on TAF March 10, 2004
Reviewer: A reader

http://download-west.oracle.com/docs/cd/B10501_01/rac.920/a96597/pshavdtl.htm#19694
Oracle documentation states as one of the restriction for TAF is
“Continuing work on failed-over cursors can result in an error message”

Does this mean that if a cursor fetches a array_size number of rows in a SELECT
statement and send it to the client, after which, the instance fails, ORACLE
continue to
display the rows in the LIVE instance. When there is time to fetch the next set
of rows
oracle will result in error message

Can you suggest a link where I can read more in detail about the theory and
architecture
behind TAF which makes it possible
Followup:
actually, that link describes when it would fail:

...
Queries that cross the network after shutdown processing completes will
failover. However, Oracle returns an error for queries that are in progress
during shutdowns. Therefore, TAF only operates when the operating system returns
a network error and the instance is completely down.
........
http://otn.oracle.com/pls/db92/db92.drilldown?remark=&word=TAF&book=&preference=
would be a good place to start...
Rating: 3
A quick question on high availability and disaster recovery sites March 12, 2004
Reviewer: Matt from Australia
Is there a way of setting up a (geographically separated) disaster recovery site
so that NO data would be lost in the event of a failure?

I am looking for any approach that would make a transaction in the production
system and the replication to the remote DR site one atomic action.

All I can come up with involves DB link inserts initiated via table triggers. I
don't like this.

I believe there must be a better Oracle feature based solution - Yes, the
requirement is for a non-asynchrous transfer of data.

Can you please poke holes in the trigger approach and highlight a more reliable
solution?

Thanks.
Followup:
Yes, it is dataguard in maximum protection mode.

triggers would not be the way to go *at all*.
http://otn.oracle.com/pls/db92/db92.docindex?remark=homepage#index-DAT
Rating: 3
Apologies read the manual... March 12, 2004
Reviewer: Matt from Australia
It seems that data guard has come along in leaps and bounds and does what I
want:
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96653/concepts.htm#1027493
Maximum protection

This mode offers the highest level of data protection. Data is synchronously
transmitted to the standby database from the primary database, and transactions
are not committed on the primary database unless the redo data is available on
at least one standby database configured in this mode. If the last standby
database configured in this mode becomes unavailable, processing stops on the
primary database. This mode guarantees no data loss.

It sure beats the mickey-mouse approach I came up with.

Mat.
Rating: 3
A repeat request... March 24, 2004
Reviewer: Matt from Australia
...for a look at your before and after page generation performance metrics for
comparison? As above...

THanks.
Followup:
oh man, you missed it by one day -- we roll the log table with the timings every
week -- on wed.

So, my oldest observation is mar 10th, after the upgrade....

I bumped the more extensive 10g numbers against the intial ones and they all
hold except for page 12 (i sort of thought that would happen).  It is in the 6/7
second range now (average) which puts it back on par with the other stuff (must
have been small archives that were requested in the short window)
March 24, 2004 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")
Rating: 3
URL pointing wrongly March 24, 2004
Reviewer: Ashiq Shamsudeen A from Trichy, Tamil Nadu, India
Tom

  The URL which you given pointing wrongly.
Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96521/tables.htm#12458

March 24, 2004 amit -- Thanks for the question regarding "SQL plus session not getting updated on saving changes in editor.", version 3.2
You Asked
We have installed sql plus 3.2(client) in windows 2000 machine.Our server is
SCO-Unix. But notepad editor does not seems to be working in it.When we edit and
save it, sql buffer doesnot get updated instead it comes with old commands. This
seems to be problem in every windows 2000/XP machine.
I have checked that editor is defined as notepad.exe.There is one afiedt.buf in
C:\orant\bin path.

Same SQL Plus is working fine on Windows 98/ME machines.

Can you tell us what to do??Is there any patch for it??
and we said...
That is version 7.2 of the database client software.

It predates Windows 2000 by more then 5 years.

I'm surprised you got it to install but there is no chance of having any
reasonable expectations that software the predates the operating system by 5
years would work.

There is nothing I know of to do here other then consider using software
developed for the operation system you want to use.  Your choices are

o use the version of the database client software supported on your OS choice
o use the OS that goes with your software you wish to use -- eg, in this case
that would be windows 95 and windows NT 3.5x
Review & Followup

Rating: 5
September 28, 2002
Reviewer: amit from India
Thanks a lot. I get most suitable answer for my oracle related queries on this
site only. Keep it up.
Rating: 4
March 24, 2004
Reviewer: reader
sir ,
 i need to open the notepad at the web client and passing some data(some text )
to it
as done in SQL pluse when type ED
i know it may be not so related to oracle , but i hope u help me in that .
Followup:
well, it would totally and utterly depend on the technology you use  in the
middle.

In mod_plsql, I would use owa_util to set the mime header to
"application/notepad" and tell the users to configure their browser to have
notepad setup as the helper app for that mime type and then any page generated
with that mime type will goto notepad
March 24, 2004 play -- Thanks for the question regarding "varrays, nested tables, and pl/sql records, and pl/sql tables, and ref cursor", version 8.1.7
You Asked
Tom

what is the difference between a varray and a nested table, can you give me a
very very simple example , and explain the difference
when should I use  varrays and when should I use nested tables

the same with pl/sql record and pl/sql table
say I hvae a curser select ename,deptno from emp;
I can declare and use a pl/sql record or pl/sql table to hold these values, can
you tell me when should I choose to use pl/sql record and when should I use
pl/sql table.

lastly, can you give me a simple example for ref cursor

what is the difference between a regular explicit cursor and a ref cursor
and we said...
a varray is fixed in size (its N elements long and no longer)
a nested table is inifinite in size potentially

a varray stores data in a fixed order, element(1) of a varray is well known and
will be the same time after time
a nested table does not assure the order will be preserved, the value of
element(1) may change over time (all of the elements will be there, but in
different orders)

a varray is stored as a blob with the table
a nested table is stored in a separate physical database table as a parent child
table.

In PLSQL code (when I do not plan on creating a database column of that type) --
I use nested tables exclusively.  I cannot think of a good reason to constrain
my array size artificially when I don't have to.  In PLSQL code, there is very
little difference between a varray and nested table type (the fixed order issue
isn't an issue with PLSQL nested tables, they are just like varrays -- its only
when you SAVE the nested table to a database table that the order might be
different when you read it back in)

In SQL (using these in create tables) -- well, there I don't really have any
advice.  I've used a varray everynow and then but stick to relation constructs
pretty much.  If I've a need of a nested table construct, I set up a parent
child relationship.

I cannot compare a record to a table -- they are orthogonal to eachother.  You
can have a table of records...  A record is used to hold a row, you can use a
table of records to hold many rows at the same time.  They are somewhat apples
and oranges.

simple ref cursor, sure:

scott@ORA8I.WORLD> variable x refcursor
scott@ORA8I.WORLD> begin open :x for select * from dept; end;
  2  /

PL/SQL procedure successfully completed.

scott@ORA8I.WORLD> print x

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 ACCOUNTING     NEW YORK
        20 RESEARCH       DALLAS
        30 SALES          CHICAGO
        40 OPERATIONS     BOSTON

The 2 major differences between an static explicit cursor and a ref cursor are:

o the ref cursor is parsed each time it is opened in plsql.  The static cursor
will be parsed ONCE per session typically (unless it gets flushed out)

o the ref cursor can point to a different query time it is opened (hence the
need to parse each time, its not static).  the static query cannot.
Review & Followup

Rating: 2
How to print the ref cursor returned from a multi-row Function. August 06, 2001
Reviewer: Sandy from OH, USA
That was a good explanation. Tom if you could also demonstrate "How to print the
ref cursor returned from a multi-row Oracle Function", assuming the function
returns a ref-cursor type. I tried to print the output of "mce_output_qry_func"
as given below.

SQLPLUS> variable x refcursor
SQLPLUS> begin  x :=  mce_output_qry_func('CALENDAR',660,922,1); end;

It did'nt work. Would be of great help if you could get me that.
Rating: 4
good explanation ! August 09, 2001
Reviewer: A reader
Rating: 4
Number of limit on PL/SQL table September 23, 2002
Reviewer: A reader from usa
Hi, Tom,

We got a problem on number of limit on Pl/SQL table.

Suppose we use DBMS_SQL package dynamically bind variables, decribe variable and
get column values from any query. And further suppose we declare a pl/sql table
like:
type vc_arr is table of varchar2(2000) index by
                                       binary_integer;
g_cvalue  vc_arr;
use g_cvalue to hold values of some of the columns,
if the query returns more than millions of records, do we have a limit number of
records for g_cvalue variable to hold these many of records? If it does, what is
the work around?

Thanks
Followup:
You would NEVER want to fetch millions of rows.

You should fetch maybe 100 at a time, process them, fetch the next 100.

The APIs support this (array fetching of N records at a time), look at the
define_array procedure.
Rating: 4
varrays, nested tables, and pl/sql records, and pl/sql tables, and ref cursor March 24, 2004
Reviewer: Bal Krishan Mittal from New Delhi , India -110003
dear sir , the answer you gave were very usefull for me even if i did not raised
these questions but still while exploring your site the answers you gave were
very usefull and reader friendly.
Thanks.
Bal Krishan Mittal.
The Energy and Resources Institute (TERI),
New Delhi.
balkrishan_mittal@hotmail.com
Rating: 3
want to ask March 24, 2004
Reviewer: bal krishan mittal from new delhi, india -110003.
sir i want to ask that how can i put my questions to you.
Followup:
when taking new questions, there is a button/link on the home page.
March 24, 2004 Andrew -- Thanks for the question regarding "Calculating an exact percentile value in 8i", version 8.1.6
You Asked
Tom,

I need to calculate a 95th (or any) percentile from a list of values. I realise
there are NTILE and PERCENT_RANK functions in 8i but not the PERCENT_CONT
function that's in 9i.

Do you know of a way of using the above 8i functions or another easy way to
calculate a precise percentile value?

I realise this will involve interpolating the value. I'd like to use the
analytic functions as I believe that should reduce the amount of code required.

Thanks

Andrew
and we said...
You may well be able to get what you need with row_number() and count(*).  for
example:

ops$tkyte@ORA9IR2> select deptno,
  2         count(*),
  3         percentile_cont(0.5) within group (order by sal) med
  4    from emp
  5   group by deptno
  6  /

    DEPTNO   COUNT(*)        MED
---------- ---------- ----------
        10          3       2450
        20          5       2975
        30          6       1375

well, we want the average of the middle or middle two rows -- we can use this
to see what rows we are interested in:

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select deptno,
  2         sal,
  3         cnt,
  4         rn,
  5         case when
  6             ((mod(cnt,2) = 1 and rn = ceil(cnt/2) )
  7              or (mod(cnt,2) = 0 and rn in ( cnt/2, cnt/2+1 ) )) then
'<<===='
  8          end
  9    from (
 10  select deptno,
 11         sal,
 12         count(*) over (partition by deptno) cnt,
 13         row_number() over(partition by deptno order by sal) rn
 14    from emp
 15         )
 16  /

    DEPTNO        SAL        CNT         RN CASEWH
---------- ---------- ---------- ---------- ------
        10       1300          3          1
        10       2450          3          2 <<====
        10       5000          3          3
        20        800          5          1
        20       1100          5          2
        20       2975          5          3 <<====
        20       3000          5          4
        20       3000          5          5
        30        950          6          1
        30       1250          6          2
        30       1250          6          3 <<====
        30       1500          6          4 <<====
        30       1600          6          5
        30       2850          6          6

14 rows selected.

and then it becomes easy to get the 50th percenttile

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select deptno,
  2         avg(sal),
  3         cnt
  4    from (
  5  select deptno,
  6         sal,
  7         count(*) over (partition by deptno) cnt,
  8         row_number() over(partition by deptno order by sal) rn
  9    from emp
 10         )
 11   where (mod(cnt,2) = 1 and rn = ceil(cnt/2) )
 12      or (mod(cnt,2) = 0 and rn in ( cnt/2, cnt/2+1 ) )
 13   group by deptno, cnt
 14  /

    DEPTNO   AVG(SAL)        CNT
---------- ---------- ----------
        10       2450          3
        20       2975          5
        30       1375          6

using this technique, you should be able to find the row(s) such that 85% are
'before' it and 15% are 'after' it -- just by using different predicates on cnt
and rn.
Review & Followup

Rating: 5
Excellent March 23, 2004
Reviewer: Andrew Briggs from UK
Tom,

Thanks for that. I took some time to understand it though! I've tried it on my
sample data (too big to include) and I get a result that reconciles with my test
spreadsheet function.

Could you give me a quick example of how I would modify the cnt an rn predicates
to get a 95th percentile for example, just to be clear.

Thanks again

Andrew
Followup:
Ok, head hurts, been long day -- this is as far as I've gotten it, say you
wanted 95th percentile:

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> select distinct deptno,
  2             max( case when rn/cnt <= 0.95 then rn/cnt end ) over ( partition
by deptno ) min_rncnt,
  3             max( case when rn/cnt <= 0.95 then sal end ) over ( partition by
deptno ) min_sal,
  4             min( case when rn/cnt >= 0.95 then rn/cnt end ) over ( partition
by deptno ) max_rncnt,
  5             min( case when rn/cnt >= 0.95 then sal end ) over ( partition by
deptno ) max_sal
  6    from (
  7  select deptno,
  8         sal,
  9         count(*) over (partition by deptno) cnt,
 10         row_number() over(partition by deptno order by sal) rn
 11    from emp
 12         )
 13  /

    DEPTNO  MIN_RNCNT    MIN_SAL  MAX_RNCNT    MAX_SAL
---------- ---------- ---------- ---------- ----------
        10 .666666667       2450          1       5000
        20         .8       3000          1    7781.24
        30 .833333333       1600          1       2850

that gives you by each deptno, the row(s) that are closest to 0.95 -- one below
and one above (or just the one row if that applies).

Somewhere between 2450 and 5000 is your value for deptno 10 (and so on) :)

somewhere between....  I'll let someone who knows how to calculate a percentile
take it from here :)
Rating: 4
It's put me on the right track March 23, 2004
Reviewer: Andrew Briggs from UK
Tom,

Thaks for the help. The use of analytics has helped put me on the right track.

I have a mathematical formula for getting the exact percentile. I'll test with
the analytic functions and then post the answer.

Cheers

Andrew
Rating: 4
Formula for calculating the percentile March 24, 2004
Reviewer: Andrew Briggs from UK
Tom,

Hope your head is better this morning. This is the statement I eventually
produced. It only considers deptno = 10. I did this to simplify it for myself.

select      (max(sal)-min(sal)) *
            ((1+(0.95*(cnt-1))) - floor(1+(0.95*(cnt-1)))) +
            min(sal)
            from
    (
    select  sal,
            count(*) over () cnt,
            row_number() over(order by sal) rn
    from    scott.emp
    where   deptno = 10
    )
    where   rn = floor(1+(0.95*(cnt-1)))
       or   rn = ceil(1+(0.95*(cnt-1)))
group by    cnt

This example uses a formula that is consistent with the percentile function in
Excel, according to information I have found on the internet, although this
differs from other percentile calculation formulas.

The exact row number for a percentile is calculated using the formula below

m = 1 + p/100(n - 1)

If this gives an integer then the value at that row is the percentile.

If it gives a decimal then we have to interpolate between the value for the row
number immediately below the percentile (x) and the value at the row number
immediately above the percentile (y). The point between these values is
determined by the decimal fraction (df) for m, as defined above. e.g. if row
number is 2.9 then the decimal fraction is 0.9

These values are used in the formula below.

95th percentile = x + df(y - x)

Using the three values for deptno 10 in the emp table

1300
2450
5000

The exact row number = 1 + 0.95(3 - 1) = 2.9

Therefore the 95th percentile = 2450 + 0.9(5000 - 2450) = 4745

If anyone spots any errors in this or a better way of doing it please post an
answer

Thanks

Andrew
Followup:
that interpolation is the part i was missing, I could not get 4745
(percentile_cont() in 9i does...)
March 24, 2004 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.
Rating: 3
OK March 08, 2004
Reviewer: Jim from TX,USA
Dear Tom,
In the create trigger statement I found a clause like
 " referencing parent as parent".What does parent denote and
how to make use of it?
Thanks in advance.
Bye!
Followup:
<quote ref=sql reference>

The referencing_clause lets you specify correlation names. You can use
correlation names in the PL/SQL block and WHEN condition of a row trigger to
refer specifically to old and new values of the current row. The default
correlation names are OLD and NEW. If your row trigger is associated with a
table named OLD or NEW, use this clause to specify different correlation names
to avoid confusion between the table name and the correlation name.

o If the trigger is defined on a nested table, then OLD and NEW refer to the row
of the nested table, and PARENT refers to the current row of the parent
table.

</quote>
Rating: 3
OK March 24, 2004
Reviewer: Rajan from Chennai,TN,India
Dear Tom,
Do you have any simple examples for cascaded triggers?what
can be the maximum allowed levels for such triggers?Please
do reply.
Bye!
Followup:
no, no examples (i try to avoid triggers actually, only use them when absolutely
necessary)

there are no limits other than the number of open cursors you can have (you
control that).
March 24, 2004 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
Rating: 3
Nice March 04, 2004
Reviewer: Ram from Bangalore,India
Hi Tom,
Suppose if I have a table "Emp" with ename column,can it be
made to check that it is entered with only characters?
I mean
  ename varchar2(30) check (ename --only alphabets to take);
Do you have any programming trick to do this?
Please do reply.
Followup:
check ( replace(translate( upper(ename),'ABC....','A'),'A','') is null )
Rating: 3
Please see this March 24, 2004
Reviewer: Ram from Bangalore,India
Dear Tom,
Please see the following code.
SQL> create table t1(a number not null);

Table created.

SQL> drop table t1;

Table dropped.

SQL> create table t1(a number,constraint nn_a not null(a))
SQL> /
create table t1(a number,constraint nn_a not null(a))
                                         *
ERROR at line 1:
ORA-00904: : invalid identifier

My Question is "Can not 'not null' constraints be declared out of line"
Is this not possible?Please do reply.
Bye!
Followup:
ops$tkyte@ORA9IR2> create table t ( x int not null, y int, check (y is not null)
);

Table created.

ops$tkyte@ORA9IR2> select constraint_name, constraint_type, search_condition
from user_constraints where table_name = 'T';

CONSTRAINT_NAME                C SEARCH_CONDITION
------------------------------ -
--------------------------------------------------------------------------------
SYS_C005140                    C "X" IS NOT NULL
SYS_C005141                    C y is not null

a not null is just an implicit "check" constraint.
March 24, 2004 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.
Rating: 5
size of undo segment January 29, 2004
Reviewer: A reader from Boston, MA
Tom, great info. I read lot of articles on this site on undo management.

9.2.0.2 on Tru64.

I am getting ORA-1555 error on an undo tablespace 8GB in size. Without checking
if the undo tablespace really ran out of space, I increased it to 16GB. I got
the snapshot error again. I observed that it did not reach the space limit at
all. It made me wonder if it got to do anything with the size of the undo
segment. But, with Auto undo management, the segment size limit is determinded
by the  available space in the tablespace. right ?
Then I realised that our undo_retention was set to 0, a value recommended by an
oracle consultant, who thinks this should be set higher ONLY when using
"flashback queries".
At this point, I raised the undo_retention to 3 hours. the problem went away.
Did not get the 1555.
So, I want to make sure I understand this concept right :

1. the transaction did not run out of space either in tablespace or the undo
segment size
2. the transaction slot entry was overwritten. A  transaction made an entry in
the transaction slot and eventually was commited, and the next transaction "B"
saw that the transaction "A" has expired(since undo_retention is not set) and
tried to reuse the segment by overwriting the transaction header. A third query
"C" tried to get the values which transaction "A" was accessing before being
overwritten and got the "snashot" error.
Followup:
you did not set the undo retention period to a value that exceeds the length of
time your longest running query runs for.

1) correct

2) cannot say - but basically in very simple terms "since you requested undo to
be kept only for 0 seconds, we kept it for that long and felt free to overwrite
it as soon as we wanted to and we did"
Rating: 4
thank you January 29, 2004
Reviewer: A reader from boston, ma
so, is it possible in this case that even before processing all the rows(before
returning the first row), the transaction header was overwritten by another
transaction ?
Followup:
it is possible that the information needed by some query is overwritten in the
undo tablespace -- yes. that is what has happened in fact with the ora1555
Rating: 5
whats the right value January 29, 2004
Reviewer: A reader
Tom, please correct me if I am wrong in my understanding of undo_retention :
 I should be setting undo_retention to a value equal to the longest running
query. Let say, if a transaction runs for 6 hours, I should be setting the
undo_retention=6hours ?

Thanks as always for your godly advice.
Followup:
(query != transaction).

if you use serializable or read only isolation -- you set undo retention = a
little longer than your longest running transaction.

if you use only read committed -- you set undo retention = a little longer than
your longest running STATEMENT (insert/update/delete/merge or select)
Rating: 5
my bad January 30, 2004
Reviewer: A reader
Sorry about that Tom. Yes, Query != Transaction.
I meant transaction there, not query.

In serializable isolation, why would you set it LITTLE LONGER than the longest
transaction ? Can you share an example. Greatly appreciate it !!!
Followup:
in serializable -- you are read consistent with regards to the BEGINNING of the
transaction -- not the beginning of each statement -- so, if you started a
transaction at noon and ran 5000 queries in it and finished at 12:05 -- all
5,000 would be "as of noon"

in read committed, you would have 5,000 "as of" points instead of just one.
Rating: 5
nice January 30, 2004
Reviewer: A reader
Thank You !! Thank You !! Thank You !!
Rating: 4
Undo Behaviour March 15, 2004
Reviewer: Js from INDIA
Hi,

       Using RedHat 8.0 ORACLE 9.2.0
       ------------------------------

Sir,  In this test database undo_retention is already 0
and undo tablespace size = 1M and I am genrating 750 kb Redo
but Undo Tablespace is Still Increasing.

SQL> show parameter undo

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
undo_management                      string      AUTO
undo_retention                       integer     0      <<-----
undo_suppress_errors                 boolean     FALSE
undo_tablespace                      string      UNDO

SQL>
SQL> create undo tablespace undo2 datafile '/tmp/undo2.dbf' size 1M
  2  reuse;

Tablespace created.

SQL> alter system set undo_tablespace=undo2;

System altered.

SQL> conn rnd
Enter password:
Connected.
SQL>
QL> Create table a as select * from all_objects;

Table created.

SQL> update a set object_name=object_name;
update a set object_name=object_name
       *
ERROR at line 1:
ORA-30036: unable to extend segment by 32 in undo tablespace 'UNDO2'

SQL> ho ls -lh /tmp/undo2.dbf
-rw-r-----    1 oracle   oinstall     1.0M Mar 14 11:31 /tmp/undo2.dbf

     #### Size  of undo tablespace is already 1 MB ####

SQL> conn / as sysdba
Connected.
SQL> alter database datafile '/tmp/undo2.dbf' autoextend on ;

Database altered.

SQL> conn rnd
Enter password:
Connected.
SQL> set autot on stat
SQL> set autocommit on
SQL>
SQL> update a set object_name=object_name;

2803 rows updated.

Commit complete.

Statistics
----------------------------------------------------------
         33  recursive calls
       3159  db block gets
        164  consistent gets
         62  physical reads
     768860  redo size        <<----- 750 KB
     ........

SQL> ho ls -lh /tmp/undo.dbf
-rw-r-----    1 oracle   oinstall     1.3M Mar 14 11:33 /tmp/undo2.dbf

   #### Genrating 750 Kb Redo
   #### Undo Retention is 0
   #### Undo tablespace size 1m
   #### Undo Tablespace is still increasing

SQL> /

2803 rows updated.

Commit complete.

Statistics
----------------------------------------------------------
         25  recursive calls
       3147  db block gets
        161  consistent gets
          0  physical reads
     774420  redo size
        .........

SQL> ho ls -lh /tmp/undo2.dbf
-rw-r-----    1 oracle   oinstall     1.4M Mar 14 11:33 /tmp/undo2.dbf

             ### Size 1.4 mb ####

SQL> /

2803 rows updated.

Commit complete.

Statistics
----------------------------------------------------------
         25  recursive calls
       3149  db block gets
        161  consistent gets
          0  physical reads
     778036  redo size

SQL> ho ls -lh /tmp/undo2.dbf
-rw-r-----    1 oracle   oinstall     1.6M Mar 14 11:33 /tmp/undo2.dbf

           ####  Size 1.6 Mb ####

Could you pls. explain ..

Thanks
Js
Followup:
redo <> undo

undo is UNDO
redo is REDO

they are totally different.  redo is stored in the logs, undo is stored in the
undo tablespace.

you have many undo segments in that teeny teeny tiny datafile.  They are used in
a round robin fashion.  They start really really small.  As you cycle through
them, they are extending to hold your transactions data.  Keep cycling and
you'll see them hit a steady state size (assuming a single user system)

but -- i don't see the point as 1.x meg of undo tablespace isn't going to be
realistic for anything.
Rating: 5
Undo Retention Not working with me March 24, 2004
Reviewer: Vipin from IND
Hi Tom,

I am the only user connected in database.

9i on Sun os

** create table t as select rownum r from dict where rownum < 25;

Table created.

**
**
**
**
** Select dbms_flashback.get_system_change_number from dual;

GET_SYSTEM_CHANGE_NUMBER
------------------------
                  538552

** show parameter undo_Re

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
undo_retention                       integer 10800
** delete from t;

24 rows deleted.

** commit;

Commit complete.

** select  * from t;

no rows selected

** exec dbms_flashback.enable_at_system_change_number(538552);

PL/SQL procedure successfully completed.

** select * from t;
select * from t
              *
ERROR at line 1:
ORA-01466: unable to read data - table definition has changed

** show parameter undo

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
undo_management                      string  AUTO
undo_retention                       integer 10800
undo_suppress_errors                 boolean FALSE
undo_tablespace                      string  UNDOTBS1
** select name,bytes from v$datafile;

NAME                                BYTES
------------------------------ ----------
/data8/oracle/oradata/dvlp/sys  419430400
tem01.dbf

/data8/oracle/oradata/dvlp/und  251658240    <<-----
otbs01.dbf

/data8/oracle/oradata/dvlp/drs   20971520
ys01.dbf

/data8/oracle/oradata/dvlp/ind   26214400
x01.dbf

/data8/oracle/oradata/dvlp/too   96337920
ls01.dbf

/data8/oracle/oradata/dvlp/use   26214400
rs01.dbf

/data8/oracle/oradata/dvlp/xdb   39976960
01.dbf

7 rows selected.

Thanks
Vipin
Followup:
you have to wait a couple of minutes to flashback -- like about 5 minutes.
March 24, 2004 Anwar -- Thanks for the question regarding "Distributed Option in Oracle 7", version 7.3.2
You Asked
Hi Tom!

Is Oracle Distributed Option required for accessing remote
databases? Also, if there is some restriction on database
version i.e. the version shuld be same on all nodes?
and we said...
Distributed option was required to perform distributed
transactions in v7.x.  If you just query across a database link,
that was OK, if you update over a database link -- that was the
distributed option.

The versions may be hetergenous (eg: 7.3 to 7.1 works ok).
Review & Followup

Rating: 3
OK March 24, 2004
Reviewer: Srinivas from Vizag,AP,India
Hi Tom,
What is a distributed database?I think two terminologies
exist.
1)A database server houses multiple databases with in it and each database may
reside in a different physical location and we consider each database to be a
distributed one.We use a database link to refer each database.
 Is the above defintion correct?
2)Database servers may reside in different physical locations which have
databases with in them.we consider each server to be a distributed one.We use
server-to-server
RPCs and OCI calls to connect to the desired server.
Is this definition correct?
I don't know which one is correct?Do I have misconception of any type here?Could
you please clarify?
Bye!
Followup:
those are both the same.

doesn't matter if the database instances are on the same server or 5,000 miles
apart.  they are both "distributed" if you have more than one database...
March 24, 2004 Farah -- Thanks for the question regarding "8i Automated Standby Database", version 8i
You Asked
We are looking into creating a standby database for production,
and also building a read-only database for reporting.

What is "Oracle8i Automated Standby Database"?  How
stable/reliable is it... considering that this is a new feature?
 If anyone has implemented it, please list any issues or
problems encountered in configuring it, steps taken to switch
over to standby in case production database is down, are there
any performance issues or overhead on the production database.
How does it compare to the Parallel Server and Advanced
Replication Option?

Thanks,
Farah
and we said...
See
http://www.oracle.com/oramag/oracle/00-May/o30tom.html
for an article I did on standby databases, OPS,
Replication and other features.  It answers the bulk of your
questions.

As for "considering it is a new feature" -- the standby database
was introduced in Oracle7.3 -- some 4 years ago.  Its not really
too new of a feature.

See
http://download-east.oracle.com/docs/cd/A81042_01/DOC/server.816/a76995/toc.htm
for in depth documentation on this feature.
Review & Followup

Rating: 3
broken link March 24, 2004
Reviewer: Richard Jones from UK
Tom, as requested on the front page, the link:
http://www.oracle.com/oramag/oracle/00-May/o30tom.html
is broken - page not found error.

Ric.
Followup:

http://asktom.oracle.com/~tkyte/omag/00-May/o30tom.html
now
March 24, 2004 -- Thanks for the question regarding "Hints When to use", version 817
You Asked
Hello,
     Wish you a Merry christmas and Happy holidays.

 1)When tuning an sql statement in plsql when should Hints be applied ? How do
we know what Hint to apply and when see point 2 please .We use CBO.We are doing
fresh development.
 2) We have a Developer who by default starts with writing hints in a select
statement. Is this approach correct. I think it is not because we are still in
development and the database is empty only schema defined. so even if we analyze
the tables I dont think so it will be guranteed that Oracle will use that
particular hint.
Even if it does  in development what is the gurantee it will also use in
Production. I think this may be his publicity Stunt
3)Unless we use TKproff and explain plan  , how can we make a decision .Still is
it always necessary to spoon feed oracle with hints ? what must be the criteria
to specify Hints ?How do we know that Oracle did use our supplied Hints ?

Thanx
and we said...
1) Never.  They are the path of last resort.

In Oracle Applications development (11i apps - HR, CRM, etc) Hints are strictly
forbidden.  We find the underlying cause and fix it.

See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6601251003901
for some really important information that will make the use of hints obsolete
(as gartner would say "95% probability")

2) NO, not at all.

Tell me -- can this developer read a query plan and say "yup, that is the best
plan for all sizes of data" (hey, wait -- thats what the RBO did!!).  No, they
cannot.  See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6749454952894
This is "joe or josephine" developer -- thinks the CBO is broken because it full
scans a table.  Well, that example just speaks for itself.

If you are using the CBO, you MUST analyze your tables.

Also -- in development -- you MUST develop again real, representative data -- or
you will fail (your rollout will be very very painful -- 99.999% probability)

3) NO, NO, NO.  do not use hints.  That will be your last choice -- never the
first choice and never standard operating procedure.

The problem is -- we may well use your hints and your hints are just *wrong*,
slower, precluding us from taking a better path.

Hints -- only useful if you are in RBO and you want to make use of an access
path.

FIRST_ROWS is a hint I sometimes use in pagination queries (getting rows M thru
N) but that is about it.

Hints -- something I use to test a theory, but not in real code.
Review & Followup

Rating: 5
Now Its clear , I need no more Hints for when/ how to use Hints December 25, 2002
Reviewer: A reader
Thanx for your crystal clear and simple explanation.
U R Great.
Rating: 3
Tom, why not use views? December 25, 2002
Reviewer: Sam from Lexington, KY
View customized for the part of business logic
For example tables are:
ACCOUNTS
SALES
ITEMS

Views are:
Account_Sales
Account_Sale_Details

Now you can let developers use views and add hints based
on most benifitting business case.
If need more add more views.

So developers can work without interruption. DBAs are happy.
Code need not be recompiled either. Of course on the fly
performance change can be achieved.

what do you think?

thanks
Followup:
I think you should not be using hints....
Rating: 5
Hints used in some cases December 25, 2002
Reviewer: A reader from India
Hi Tom

We have to use the hints in these cases -

1. When joining a global temporary table with normal tables. Temporary tables
cannot be analyzed, so we sometimes need to use hints.

2. I need your advice on this -
We have a staging database where the data is inserted, processed, pushed to
production and then finally purged. This is an automatic activity as we keep
receiving files - some with less data some with large amount of data. We have no
clue to how much data we receive.

So in these cases we sometimes use hints as we do not analyze the tables. The
queries are also not very complex. Max number of tables in a join is 3.

Is it a good idea to switch on monitoring and gather statistics every time we
load a file?

Thanks
Followup:
1) dbms_stats.set_table_stats does that dandy....

2) analyze the tables then??

Otherwise you are telling me that the plans that work for 5 rows work great for
500,000 rows -- which is quite simply "not true"....

You don't even need monitoring in this case, just analyze as part of the load
process - you KNOW when the stats are invalid.
Rating: 5
Informative Explanation December 26, 2002
Reviewer: Yogeeraj from Mauritius
hello,

Maybe your next book for next year (after the performance tuning one) should be
on "Writing efficient SQL". Something specialized with lots of case studies and
guidelines.

This would definitely benefit the entire community here!

Happy New Year 2003

Best Regards
Yogeeraj
Rating: 5
How about data warehouse application? December 26, 2002
Reviewer: steve from NYC, USA
Hi Tom,

For data warehouse application, especially in creating
a report, it involved joins among five or more huge tables
(Huge table, I mean, contains ten to hundred million rows of records ), and it
take hours to run.
1) In this case, should we still depend on CBO? or we have to use the Hints?

2) We have some queries which they runs more than an hour without hint, and they
runs ten minutes with the hints. So each time, when I write a query if it runs
longer than we expected I was told by myself or my boss to try the hints. The
question is how can I tell there still is room for tuning based on tables' size,
rows return, LIO and PIO.

3)I'd like to konw what is your experience to judge a query is almost optimized
based on the data from TKPROF or autotrace. So I can reply to my boss, the query
is no room to tune

Thank you!
Followup:
1) you should ESPECIALLY rely on the CBO in that case.

2) you should try to investigate the underlying cause where -- bad stats?
missing stats?  need histograms?  optimizer_index_cost_adj set wrong?  what
caused the optimizer which works on mathematical principled to come to the wrong
conclusion?

3) when it meets the performance criteria laid out before the query itself was
designed.  otherwise you are suffering from CTD -- compulsive tuning disorder --
with no end in sight.
Rating: 5
Worthy of note... December 28, 2002
Reviewer: Connor from UK
Just to note for anyone reading that

"Hints -- only useful if you are in RBO and you want to make use of an access
path."

will immediately invoke the cost optimizer (in which case it may be useful to
have stats on your tables anyway)
Rating: 5
Woot! January 16, 2003
Reviewer: Chuck Jolley from Oklahoma City, OK USA
Well, I read this and started experimenting.
We now do:
dbms_utility.analyze_schema(ar_schema, 'COMPUTE', null, null, 'FOR TABLE FOR ALL
COLUMNS FOR ALL INDEXES');
for our two main active schemas every sunday morning while the office is closed
and only a few web serfers are hitting the database.
It takes about 3 hours to run.
What an improvement!
ALL the SQL that needed hints while we were RULE based, and ALL the SQL that
defaulted to bad hash joins when we went to CHOOSE, run correctly now without
hints.
Too Cool.

Thanks,
Chuck
Rating: 3
hints in pl/sql May 14, 2003
Reviewer: Scott from Perth, Western Australia
G'day Tom

Speaking of when to use hints and last resorts...
I have a distributed query over 2 tables / 2 databases and I used DRIVING_SITE -
one hint you recommend not to completely dismiss...
In SQL*Plus, this changed my 30 second query to 1 second.
But as soon as I put the query in my package, or test it in a declare/begin/end
anonymous block (using implicit and explicit cursor), the optimiser doesn't seem
to use the hint (takes 30 seconds).

Any reason why the hint would not be used in PL/SQL?

declare
v number;
begin
SELECT /*+DRIVING_SITE(s)*/
count(*)
into v
          FROM   ths_sales s --(remote table)
              ,trd_sales_rep_customers c
          WHERE  s.fin_yr        = 2003
          AND    s.fin_mth       = 1
          AND    s.state_code    = 'WA'
          AND    c.sales_rep_nbr = 3
          AND    s.acc_nbr       = c.acc_nbr;
dbms_output.put_line(v);
end;
Followup:
depending on the version, you may well need a space after the /*+

use tkprof and see if the hint is turned into

select /*+RIVING_SITE(s)*/

if so, you need a space.  tkprof -- it never lies.
Rating: 5
never mind May 14, 2003
Reviewer: Scott from Perth, WA.
G'day

Never mind. After a few hours investigating, I did a tkprof and remembered what
Penny Cookson taught me - In PL/SQL, leave a space between /*+ and the hint...

I just wanna kick myself.
Rating: 5
Should I use a hint here December 29, 2003
Reviewer: A reader
I have a table T with 1.2 Million rows. It has a column COMPACT_NAME with
366,560 distinct values. There is a nonuniqe index on this column.

In trying to tune the most I/O-intensive queries I find many of them are
accessing this table by this column with a like clause.

Example:
  Select last_name from t where compact_name like 'SLRGI%';

Gives the following Trace info

LAST_NAME
-----------------------------------
SLRG INC
S L R G INC

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1798 Card=6544 Bytes
          =189776)

   1    0   TABLE ACCESS (FULL) OF 'T_SOS_503' (Cost=1798 Card=6544 By
          tes=189776)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      18537  consistent gets
      18528  physical reads
          0  redo size
        346  bytes sent via SQL*Net to client
        495  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          2  rows processed

However
  Select last_name from t where compact_name like 'SLRGIN%';

Gives the following Trace info
S3_LAST_NAME
-----------------------------------
SLRG INC
S L R G INC

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1662 Card=2621 Bytes
          =76009)

   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T_SOS_503' (Cost=1662 Ca
          rd=2621 Bytes=76009)

   2    1     INDEX (RANGE SCAN) OF 'S3_COMPACT_NAME_IDX' (NON-UNIQUE)
           (Cost=12 Card=2621)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          6  consistent gets
          5  physical reads
          0  redo size
        346  bytes sent via SQL*Net to client
        495  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          2  rows processed

I have:

analyze table t
compute statistics for table for all indexes;

and

exec dbms_stats.gather_table_stats( user, 'T', cascade => TRUE);

I still get the same results on the queries.

Any suggestions?
Followup:
whoa there..  you are saying:

 Select last_name from t where compact_name like 'SLRGIN%';

Gives the following Trace info
S3_LAST_NAME
-----------------------------------
SLRG INC
S L R G INC

something smells fishy here.

that would be a BUG would it not -- if that second row came out?  are we seeing
"reality" here?
Rating: 5
More info December 29, 2003
Reviewer: A reader
Sorry different field in select, this might help, it shows same issue.

select last_name,compact_name
 from
 t
 where
 compact_name like 'SLRG%'
/

LAST_NAME                           COMPACT_NAME
----------------------------------- --------------------
SLRG INC                            SLRGINC
S L R G INC                         SLRGINC

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1798 Card=23564 Byte
          s=730484)

   1    0   TABLE ACCESS (FULL) OF 'T_SOS_503' (Cost=1798 Card=23564 B
          ytes=730484)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      18537  consistent gets
      18523  physical reads
          0  redo size
        413  bytes sent via SQL*Net to client
        495  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          2  rows processed
Followup:
try this:

analzye table t compute statistics for columns compact_name size 225;

and lets see what that does for us.  Consider (a 1,000,000 row table)

big_table@ORA9IR2> exec dbms_stats.gather_table_stats( user, 'BIG_TABLE',
cascade=>true );

PL/SQL procedure successfully completed.

big_table@ORA9IR2>
big_table@ORA9IR2> set autotrace traceonly explain
big_table@ORA9IR2> select count(*) from big_table where object_name like 'ABC%';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=24)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'BIG_TABLE_IDX' (NON-UNIQUE) (Cost=3 Card=57
Bytes=1368)

big_table@ORA9IR2> select count(*) from big_table where object_name like 'jav%';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=24)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'BIG_TABLE_IDX' (NON-UNIQUE) (Cost=3 Card=57
Bytes=1368)

big_table@ORA9IR2> analyze table big_table compute statistics for columns
object_name size 225;

Table analyzed.

big_table@ORA9IR2> select count(*) from big_table where object_name like 'ABC%';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=23)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'BIG_TABLE_IDX' (NON-UNIQUE) (Cost=3 Card=64
Bytes=1472)

big_table@ORA9IR2> select count(*) from big_table where object_name like 'jav%';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=413 Card=1 Bytes=23)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'BIG_TABLE_IDX' (NON-UNIQUE) (Cost=413
Card=84444 Bytes=1942212)

big_table@ORA9IR2> set autotrace off
big_table@ORA9IR2> select count(*) from big_table where object_name like 'ABC%';

  COUNT(*)
----------
         0

1 row selected.

big_table@ORA9IR2> select count(*) from big_table where object_name like 'jav%';

  COUNT(*)
----------
     89964

1 row selected.

The cardinalities are computed a little better with the histograms here (right
now you have size 1, insufficient).  It is "guessing" wrong.
Rating: 5
The hints December 30, 2003
Reviewer: Zoran Matic from Dublin, Ireland
Hi Tom,

I agree with you about hints.
Just to say a few more things:

1. Oracle did not get rid of hints becouse CBO is not perfect
2. Sometimes if you know that some critical SQL will be performed only in one
way and using huge tables (we have Terabyte OLTP tables), it is easier to fix it
with the hints (complete hints of course) then to rely on the procedure to
set/gather table statistics by DBA's or automatically. Huge tables are usually
partitioned.
Oracle CBO way in this cases is to invest in gathering proper statistics or
setting that will take a lot of time of some very good DBA/Developer. What you
should do?
3. Oracle recommended to not use hints in Oracle Apps 11i because much more
developers does not know to write hints then to write the complete hint, even
developers from Oracle. Also this is the generic application for 1000's of
companies and many SQL's need to execute differently dependant on the data skew
with different column histograms and sizes. In this case hints are bad.
May way of thinking, maybe wrong.
4. Hints are advanced thing that somebody should understand before try to use it
at all. As we said one time everything is good if you are using that in the
proper case.
5. You do not want to gather statistics for some huge tables because you do not
want to waste hardware resources.
Also estimating stats are not 100% secure for some 100% important SQL's. For
that you have set statistics option but in this case you are playing partly
blind game, relying on your knowledge of future data (not easy in some cases).
It looks easier to use hints in some particular cases.

At the end as you said in 99% SQL's you probably do not need hints, but Oracle
left it to cover some CBO imperfections, DBA laziness, developer doubts, ...
At the end hints are funny because you can understand which kind of crazy things
CBO is using.

Does Oracle 10g have hints? I am sure YES.
Why? Is it for that 1% (maybe less but never mind) cases you need it?

I am going back to play chess, I have got headache.

Regards,
Zoran
Followup:
2) that assumes you have 100% static sql - else you need to invest in the
gathering of proper stats for all of your ad-hoc.

3) i think developers can figure out how to write proper hints.  the problem
isn't that hints are hard to code (unfortunately, they are far far far too easy
to code).  It is that you are doing the optimizers job, you won't be able to
take advantage of new things in the future and you are hand tuning each and
every single sql statment in the system.  the more correct approach is to make
it so the optimizer has sufficient inputs to produce good outputs.  then you
don't have to hand tune each and every query.

4) they are not really that advanced at all.

5) disagree about 1,000% on that one.
Rating: 5
Hints December 30, 2003
Reviewer: Zoran Martic from Dublin, Ireland
I am just saying that hints are sometimes very useful.

3) I did not mention this for every SQL in your application. I said a few times
1% cases (maybe less). Important SQL's that should never execute slow if
possible.

What do you mean by new functionality in the future?

If I have some particular SQL that should run that way for many years which new
functionality I should use?
I am not that sure that it is a big deal to change a few critical SQL's from
time to time. We are doing code changes in any case from time to time. You
should just be aware of hints.

4) if you say that I agree, this was just my opinion

5) again in some cases like huge growing OLTP table partitioned per days or
hours.
Are you saying that gathering statistics here is not wasting resources, reducing
scalability?
If you do not need to do this why should your collecting statistics will be
better then to put a few hints into a few SQL's (not all).

Are you using hints at all?
Are you 100% for not using hints at all?
What about bind peeking imperfection?
Do we have hints in 10g? Why?
Why there is not dessuport for hints as Oracle was saying it will be the case in
9i?
It looks to me that there are more and more hints, to not say optimizer hidden
parameters.

Regards,
Zoran
Followup:
3) if i used a hint to tell the optmizer how to process a query in 8i -- and
along comes 9i with a new index skip scan access path -- I will not be able to
take advantage of it as the hint is saying "do it this way" -- removing other
possible access paths from consideration.

you lose the ability to take advantage of better plans over time.

5) I put hints into two catagories (documented this in my new book effective
Oracle by design)

good hints give the optimizer more information -- not directions on what to do.
good hints include

o first rows
o all rows
o driving site
o cardinality
o optimizer dynamic sampling

for example -- they give the optimizer a clue as to what to do.  hints that
specify access paths -- i use them to prove the optimizer got it wrong and then
try to figure out "why" it got it wrong.

I use hints -- the "good ones".  I am loathe to use the others.
Rating: 5
More info on question on when to use hints December 30, 2003
Reviewer: A reader
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 Linux: Version 9.2.0.4.0 - Production
NLSRTL Version 9.2.0.4.0 - Production

SQL> create table t1 as select * from t_sos_501;

Table created.
SQL> desc t1
 Name                                      Null?    Type
 ----------------------------------------- -------- -------------------------
 S1_OPERATION                                       VARCHAR2(1)
 S1_STATUS                                          VARCHAR2(1)
 S1_UCC1_NUMBER                                     VARCHAR2(9)
 S1_UCC2_NUMBER                                     VARCHAR2(9)
 S1_FILE_DATE                                       VARCHAR2(10)
 S1_FILE_TIME                                       VARCHAR2(6)
 S1_LAPSE_DATE                                      VARCHAR2(10)
 S1_ROLL                                            VARCHAR2(4)
 S1_FRAME                                           VARCHAR2(5)
 S1_NUMBER_OF_PAGES                                 VARCHAR2(5)
 S1_IMAGE_ID                                        VARCHAR2(15)
 S1_ELECTRONIC_FILING                               VARCHAR2(1)

SQL>
SQL> create table t3 as select * from t_sos_503;

Table created.

SQL> desc t3
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 S3_OPERATION                                       VARCHAR2(1)
 S3_STATUS                                          VARCHAR2(1)
 S3_UCC1_NUMBER                                     VARCHAR2(9)
 S3_UCC2_NUMBER                                     VARCHAR2(9)
 S3_SEQUENCE                                        VARCHAR2(3)
 S3_TYPE                                            VARCHAR2(1)
 S3_LAST_NAME                                       VARCHAR2(35)
 S3_NAME2                                           VARCHAR2(35)
 S3_NAME3                                           VARCHAR2(35)
 S3_NAME4                                           VARCHAR2(35)
 S3_NAME5                                           VARCHAR2(35)
 S3_FIRST_NAME                                      VARCHAR2(35)
 S3_MIDDLE_NAME                                     VARCHAR2(35)
 S3_ADDRESS1                                        VARCHAR2(35)
 S3_ADDRESS2                                        VARCHAR2(35)
 S3_ADDRESS3                                        VARCHAR2(35)
 S3_ADDRESS4                                        VARCHAR2(35)
 S3_CITY                                            VARCHAR2(30)
 S3_STATE                                           VARCHAR2(2)
 S3_ZIP                                             VARCHAR2(9)
 S3_SSN                                             VARCHAR2(9)
 S3_COMPACT_NAME                                    VARCHAR2(20)
 S3_PREFIX                                          VARCHAR2(15)
 S3_SUFFIX                                          VARCHAR2(15)

SQL>
SQL> create index t1_ucc1_number_idx on t1(s1_ucc1_number);

Index created.

SQL>
SQL> create index t3_ucc1_number_idx on t3(s3_ucc1_number);

Index created.

SQL>
SQL> create index t3_compact_name_idx on t3(s3_compact_name);

Index created.

SQL>
SQL> analyze table t1 compute statistics for table for all indexed columns;

Table analyzed.

SQL>
SQL> analyze table t3 compute statistics for table for all indexed columns;

Table analyzed.

HERE IS THE CARDINALITY INFO FOR T1 THAT I THINK YOU WOULD BE INTERESTED IN.

SQL> select count(*) from t1;

  COUNT(*)
----------
    979511
SQL> select count(*)
  2  from
  3  (select distinct s1_ucc1_number from t1);

  COUNT(*)
----------
    548614
  1  select max(cnt)
  2  from
  3  (
  4  select s1_ucc1_number,count(*) cnt
  5  from t1
  6  group by s1_ucc1_number
  7* )
SQL> /

  MAX(CNT)
----------
        82

HERE IS THE CARDINALITY INFO FOR T3 THAT I THINK YOU WOULD BE INTERESTED IN.

SQL> select count(*)
  2  from
  3  (select distinct s3_ucc1_number from t3);

  COUNT(*)
----------
    548625

  1  select count(*)
  2  from
  3* (select distinct s3_compact_name from t3)
SQL> /

  COUNT(*)
----------
    366560

  1  select max(cnt)
  2   from
  3   (
  4   select s3_ucc1_number,count(*) cnt
  5   from t3
  6    group by s3_ucc1_number
  7* )
SQL> /

  MAX(CNT)
----------
       298

 1  select count(*)
 2  from
 3  (
 4   select s3_compact_name,count(*) cnt
 5   from t3
 6    group by s3_compact_name
 7  having count(*)>500
 8* )
SQL> /

  COUNT(*)
----------
        15
  1  select count(*)
  2  from
  3  (
  4   select s3_compact_name,count(*) cnt
  5   from t3
  6    group by s3_compact_name
  7  having count(*)<10
  8* )
SQL> /

  COUNT(*)
----------
    345439

NOW WITHOUT USING HISTOGRAMS

SQL> set autotrace on
SQL> get q4
  1  SELECT /*+ RULE */
  2   S3_LAST_NAME,
  3   S3_COMPACT_NAME DE_COMPACT_NAME
  4  FROM KS.T3, KS.T1 t1
  5  WHERE
  6  s3_compact_name like :a||'%'
  7  AND S3_UCC1_NUMBER = t1.S1_UCC1_NUMBER
  8* AND t1.S1_STATUS IS NULL
SQL> /

S3_LAST_NAME                        DE_COMPACT_NAME
----------------------------------- --------------------
SLRG INC                            SLRGINC
S L R G INC                         SLRGINC

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: RULE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T1'
   2    1     NESTED LOOPS
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'T3'
   4    3         INDEX (RANGE SCAN) OF 'T3_COMPACT_NAME_IDX' (NON-UNI
          QUE)

   5    2       INDEX (RANGE SCAN) OF 'T1_UCC1_NUMBER_IDX' (NON-UNIQUE
          )

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         15  consistent gets
          0  physical reads
          0  redo size
        413  bytes sent via SQL*Net to client
        495  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          2  rows processed

SQL>
SQL> get q5
  1  SELECT
  2   S3_LAST_NAME,
  3   S3_COMPACT_NAME DE_COMPACT_NAME
  4  FROM KS.T3, KS.T1 t1
  5  WHERE
  6  s3_compact_name like :a||'%'
  7  AND S3_UCC1_NUMBER = t1.S1_UCC1_NUMBER
  8* AND t1.S1_STATUS IS NULL
SQL> /

S3_LAST_NAME                        DE_COMPACT_NAME
----------------------------------- --------------------
SLRG INC                            SLRGINC
S L R G INC                         SLRGINC

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1128 Card=50481 Byte
          s=2675493)

   1    0   HASH JOIN (Cost=1128 Card=50481 Bytes=2675493)
   2    1     TABLE ACCESS (FULL) OF 'T1' (Cost=1010 Card=48976 Bytes=
          538736)

   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'T3' (Cost=10 Card=6026
          9 Bytes=2531298)

   4    3       INDEX (RANGE SCAN) OF 'T3_COMPACT_NAME_IDX' (NON-UNIQU
          E) (Cost=2 Card=2)

Statistics
----------------------------------------------------------
         42  recursive calls
          0  db block gets
      10356  consistent gets
      10544  physical reads
          0  redo size
        413  bytes sent via SQL*Net to client
        495  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          2  rows processed

NOW WITH USING HISTOGRAMS

SQL> analyze table t3 compute statistics for table for columns s3_compact_name
size 225;

Table analyzed.

  1  SELECT
  2   S3_LAST_NAME,
  3   S3_COMPACT_NAME DE_COMPACT_NAME
  4  FROM KS.T3, KS.T1 t1
  5  WHERE
  6  s3_compact_name like :a||'%'
  7  AND S3_UCC1_NUMBER = t1.S1_UCC1_NUMBER
  8* AND t1.S1_STATUS IS NULL
SQL> /

S3_LAST_NAME                        DE_COMPACT_NAME
----------------------------------- --------------------
SLRG INC                            SLRGINC
S L R G INC                         SLRGINC

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1128 Card=50481 Byte
          s=2675493)

   1    0   HASH JOIN (Cost=1128 Card=50481 Bytes=2675493)
   2    1     TABLE ACCESS (FULL) OF 'T1' (Cost=1010 Card=48976 Bytes=
          538736)

   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'T3' (Cost=10 Card=6026
          9 Bytes=2531298)

   4    3       INDEX (RANGE SCAN) OF 'T3_COMPACT_NAME_IDX' (NON-UNIQU
          E) (Cost=2 Card=2)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      10352  consistent gets
      10446  physical reads
          0  redo size
        413  bytes sent via SQL*Net to client
        495  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          2  rows processed

SQL>analyze table t3 compute statistics for table for columns s3_compact_name
size 100;

SQL> get q5
  1  SELECT
  2   S3_LAST_NAME,
  3   S3_COMPACT_NAME DE_COMPACT_NAME
  4  FROM KS.T3, KS.T1 t1
  5  WHERE
  6  s3_compact_name like :a||'%'
  7  AND S3_UCC1_NUMBER = t1.S1_UCC1_NUMBER
  8* AND t1.S1_STATUS IS NULL
SQL> /

S3_LAST_NAME                        DE_COMPACT_NAME
----------------------------------- --------------------
SLRG INC                            SLRGINC
S L R G INC                         SLRGINC

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1128 Card=50481 Byte
          s=2675493)

   1    0   HASH JOIN (Cost=1128 Card=50481 Bytes=2675493)
   2    1     TABLE ACCESS (FULL) OF 'T1' (Cost=1010 Card=48976 Bytes=
          538736)

   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'T3' (Cost=10 Card=6026
          9 Bytes=2531298)

   4    3       INDEX (RANGE SCAN) OF 'T3_COMPACT_NAME_IDX' (NON-UNIQU
          E) (Cost=2 Card=2)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      10352  consistent gets
      10446  physical reads
          0  redo size
        413  bytes sent via SQL*Net to client
        495  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          2  rows processed

SQL> get q4
  1  SELECT /*+ RULE */
  2   S3_LAST_NAME,
  3   S3_COMPACT_NAME DE_COMPACT_NAME
  4  FROM KS.T3, KS.T1 t1
  5  WHERE
  6  s3_compact_name like :a||'%'
  7  AND S3_UCC1_NUMBER = t1.S1_UCC1_NUMBER
  8* AND t1.S1_STATUS IS NULL
SQL> /

S3_LAST_NAME                        DE_COMPACT_NAME
----------------------------------- --------------------
SLRG INC                            SLRGINC
S L R G INC                         SLRGINC

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: RULE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T1'
   2    1     NESTED LOOPS
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'T3'
   4    3         INDEX (RANGE SCAN) OF 'T3_COMPACT_NAME_IDX' (NON-UNI
          QUE)

   5    2       INDEX (RANGE SCAN) OF 'T1_UCC1_NUMBER_IDX' (NON-UNIQUE
          )

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         15  consistent gets
          0  physical reads
          0  redo size
        413  bytes sent via SQL*Net to client
        495  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          2  rows processed

SQL>

I can't seem to get anywhere close to the RULE hint using CBO. Do you have any
suggestions, or do I just have developers use hints for these queries?

Thanks Tom
Followup:
uh huh.  ok, so we've been talking and talking and talking here -- and this is
the first time you show us a bind variable.  okey dokey.  nothing like looking
at something that means nothing in the context of the real question....

try this "first_rows"
Rating: 5
more info December 30, 2003
Reviewer: A reader
I can demonstrate the same thing without the bind variable, just trying to
eliminate issues. The top I/O-intensive queries are showing me some with bind
variables and some without when accessing this T3 table using S3_COMPACT_NAME
and like;

  1  SELECT /*+ FIRST_ROWS */
  2   S3_LAST_NAME,
  3   S3_COMPACT_NAME DE_COMPACT_NAME
  4  FROM KS.T3, KS.T1 t1
  5  WHERE
  6  s3_compact_name like :a||'%'
  7  AND S3_UCC1_NUMBER = t1.S1_UCC1_NUMBER
  8* AND t1.S1_STATUS IS NULL
SQL> /

S3_LAST_NAME                        DE_COMPACT_NAME
----------------------------------- --------------------
SLRG INC                            SLRGINC
S L R G INC                         SLRGINC

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=98962 Card
          =50481 Bytes=2675493)

   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T3' (Cost=2 Card=1 Bytes
          =42)

   2    1     NESTED LOOPS (Cost=98962 Card=50481 Bytes=2675493)
   3    2       TABLE ACCESS (FULL) OF 'T1' (Cost=1010 Card=48976 Byte
          s=538736)

   4    2       INDEX (RANGE SCAN) OF 'T3_UCC1_NUMBER_IDX' (NON-UNIQUE
          ) (Cost=1 Card=2)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     837740  consistent gets
      54689  physical reads
          0  redo size
        413  bytes sent via SQL*Net to client
        495  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          2  rows processed

SQL>
Followup:
does the bind variable have a minimum length -- that is could :a be null, 1
character, 2 characters -- etc?

any inputs on that?
Rating: 5
More December 30, 2003
Reviewer: A reader
SQL> variable a varchar2(100)
SQL> begin :a := 'SLRG'; end;
  2  /

PL/SQL procedure successfully completed.

  1  SELECT
  2   S3_LAST_NAME,
  3   S3_COMPACT_NAME DE_COMPACT_NAME
  4  FROM KS.T3, KS.T1 t1
  5  WHERE
  6  s3_compact_name like :a||'%'
  7  AND S3_UCC1_NUMBER = t1.S1_UCC1_NUMBER
  8* AND t1.S1_STATUS IS NULL
SQL> /

S3_LAST_NAME                        DE_COMPACT_NAME
----------------------------------- --------------------
SLRG INC                            SLRGINC
S L R G INC                         SLRGINC

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1128 Card=50481 Byte
          s=2675493)

   1    0   HASH JOIN (Cost=1128 Card=50481 Bytes=2675493)
   2    1     TABLE ACCESS (FULL) OF 'T1' (Cost=1010 Card=48976 Bytes=
          538736)

   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'T3' (Cost=10 Card=6026
          9 Bytes=2531298)

   4    3       INDEX (RANGE SCAN) OF 'T3_COMPACT_NAME_IDX' (NON-UNIQU
          E) (Cost=2 Card=2)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      10352  consistent gets
      10551  physical reads
          0  redo size
        413  bytes sent via SQL*Net to client
        495  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          2  rows processed

  1  SELECT /*+ FIRST_ROWS */
  2   S3_LAST_NAME,
  3   S3_COMPACT_NAME DE_COMPACT_NAME
  4  FROM KS.T3, KS.T1 t1
  5  WHERE
  6  s3_compact_name like :a||'%'
  7  AND S3_UCC1_NUMBER = t1.S1_UCC1_NUMBER
  8* AND t1.S1_STATUS IS NULL
SQL> //

S3_LAST_NAME                        DE_COMPACT_NAME
----------------------------------- --------------------
SLRG INC                            SLRGINC
S L R G INC                         SLRGINC

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=98962 Card
          =50481 Bytes=2675493)

   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T3' (Cost=2 Card=1 Bytes
          =42)

   2    1     NESTED LOOPS (Cost=98962 Card=50481 Bytes=2675493)
   3    2       TABLE ACCESS (FULL) OF 'T1' (Cost=1010 Card=48976 Byte
          s=538736)

   4    2       INDEX (RANGE SCAN) OF 'T3_UCC1_NUMBER_IDX' (NON-UNIQUE
          ) (Cost=1 Card=2)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     837740  consistent gets
      54783  physical reads
          0  redo size
        413  bytes sent via SQL*Net to client
        495  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          2  rows processed

  1  SELECT /*+ RULE */
  2   S3_LAST_NAME,
  3   S3_COMPACT_NAME DE_COMPACT_NAME
  4  FROM KS.T3, KS.T1 t1
  5  WHERE
  6  s3_compact_name like :a||'%'
  7  AND S3_UCC1_NUMBER = t1.S1_UCC1_NUMBER
  8* AND t1.S1_STATUS IS NULL
SQL> /

S3_LAST_NAME                        DE_COMPACT_NAME
----------------------------------- --------------------
SLRG INC                            SLRGINC
S L R G INC                         SLRGINC

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: RULE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T1'
   2    1     NESTED LOOPS
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'T3'
   4    3         INDEX (RANGE SCAN) OF 'T3_COMPACT_NAME_IDX' (NON-UNI
          QUE)

   5    2       INDEX (RANGE SCAN) OF 'T1_UCC1_NUMBER_IDX' (NON-UNIQUE
          )

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         15  consistent gets
         12  physical reads
          0  redo size
        413  bytes sent via SQL*Net to client
        495  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          2  rows processed
Followup:
does the bind variable have a minimum length -- that is could :a be null, 1
character, 2 characters -- etc?

any inputs on that?
Rating: 5
More info December 30, 2003
Reviewer: A reader
does the bind variable have a minimum length -- that is could :a be null, 1
character, 2 characters -- etc?

any inputs on that?

The bind variable has a minimum length of 1, it could theoretically be 1
character or 2 characters, it is based on user input and after talking to the
developers it would almost always ( they felt 90% plus) of the time it would
have at least 4 characters. By having them put RULE hint on the querys the users
are much happier with the performance of the system. I realize that if they
enter 1 character the query probably will run slower with the RULE hint.

I would like very much to use the CBO but in the end user satisfaction with the
system is more important than my wants. I am also concerned about the potential
for RBO to not be available in future release (10g?).

If you have any suggestions on how I can get the same overall performance using
CBO, I would love to hear them.
Followup:
if you add to the predicate

and substr( s3_compact_name,1,1) = substr(:a,1,1)

what happens to the plan (using first_rows)
Rating: 5
You're the best December 31, 2003
Reviewer: A reader
SQL> variable a varchar2(10);
SQL> begin :a := 'SLRG'; end;
  2  /

PL/SQL procedure successfully completed.

SQL>  SELECT
  2     S3_LAST_NAME,
  3     S3_COMPACT_NAME DE_COMPACT_NAME
  4     FROM KS.T3, KS.T1 t1
  5    WHERE
  6    s3_compact_name like :a||'%'
  7    AND S3_UCC1_NUMBER = t1.S1_UCC1_NUMBER
  8    AND t1.S1_STATUS IS NULL
  9  and substr( s3_compact_name,1,1) = substr(:a,1,1);

S3_LAST_NAME                        DE_COMPACT_NAME
----------------------------------- --------------------
SLRG INC                            SLRGINC
S L R G INC                         SLRGINC

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=943 Card=615 Bytes=2
          9520)

   1    0   MERGE JOIN (Cost=943 Card=615 Bytes=29520)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=826 Card=489
          76 Bytes=538736)

   3    2       INDEX (FULL SCAN) OF 'T1_UCC1_NUMBER_IDX' (NON-UNIQUE)
           (Cost=26 Card=979511)

   4    1     SORT (JOIN) (Cost=117 Card=603 Bytes=22311)
   5    4       TABLE ACCESS (BY INDEX ROWID) OF 'T3' (Cost=112 Card=6
          03 Bytes=22311)

   6    5         INDEX (RANGE SCAN) OF 'T3_COMPACT_NAME_IDX' (NON-UNI
          QUE) (Cost=41 Card=25)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      99382  consistent gets
      12398  physical reads
          0  redo size
        413  bytes sent via SQL*Net to client
        495  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          2  rows processed

SQL> ed
Wrote file afiedt.buf

  1  SELECT /*+ FIRST_ROWS */
  2     S3_LAST_NAME,
  3     S3_COMPACT_NAME DE_COMPACT_NAME
  4     FROM KS.T3, KS.T1 t1
  5    WHERE
  6    s3_compact_name like :a||'%'
  7    AND S3_UCC1_NUMBER = t1.S1_UCC1_NUMBER
  8    AND t1.S1_STATUS IS NULL
  9* and substr( s3_compact_name,1,1) = substr(:a,1,1)
SQL> /

S3_LAST_NAME                        DE_COMPACT_NAME
----------------------------------- --------------------
SLRG INC                            SLRGINC
S L R G INC                         SLRGINC

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=1318 Card=
          615 Bytes=29520)

   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=2 Card=1 Bytes
          =11)

   2    1     NESTED LOOPS (Cost=1318 Card=615 Bytes=29520)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'T3' (Cost=112 Card=6
          03 Bytes=22311)

   4    3         INDEX (RANGE SCAN) OF 'T3_COMPACT_NAME_IDX' (NON-UNI
          QUE) (Cost=41 Card=108)

   5    2       INDEX (RANGE SCAN) OF 'T1_UCC1_NUMBER_IDX' (NON-UNIQUE
          ) (Cost=1 Card=20)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         15  consistent gets
          4  physical reads
          0  redo size
        413  bytes sent via SQL*Net to client
        495  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          2  rows processed

Thank you so much Tom.

Can you explain why the FIRST_ROWS hint changes the plan? And what guidance I
should give the developers for when to use FIRST_ROWS hint.

And again, you are the best resource for learning anything that I have ever
discovered.

Happy New Year
Followup:
well, first rows goes for a plan that tries to get the very very first row back
to the application ASAP.  by weighting the predicate heavier on s3_compact_name,
it made that index more appealing for getting the first rows first.

for most 'search' applications -- things that paginate on the web or something
-- first rows is very much appropriate.

Here -- the "like :a || '%'" didn't give the optimizer much to go on.  it looked
at that and said -- well, :a could be null, could be 50 characters -- lets just
guess (thats why the cardinality is so far off here, its thinking 615 rows, not
2)

first rows is one of the "good hints" in my book -- one of the "safe" hints.  I
like to use this substr technique with LIKE whenever possible since most of the
time the bind variable does have a minimum length (typically people must enter
at least A charcter, if not more).  It also prevents people from screwing your
search up by putting a '%' in the search field (eg: you don't want them
returning EVERY record but if they put % in it will -- but the substr trick
subverts that since that is equality and not "like" anymore -- been there, done
that -- rather then verify the inputs do not include % and _, we just use the
substr trick and say you must enter at least N characters.  Else, the system
comes to a screeching halt as the end user that enters %%% retrieves EVERY
record)
Rating: 5
Hints January 05, 2004
Reviewer: Zoran Martic from Dublin, Ireland
Tom,

Thanks for your response about hints.
I agree with you what you said.
Your idea is to not use hints or use less constraining hints as much as
possible.

I am not using hints in 99% cases but will use in <=1% the complete hints to be
sure that Oracle CBO will not "cheat" me. These SQL's are the most important in
many cases.
I just have a little bit different attitude when you have some critical SQL's
that span fast-growing OLTP tables (partitioned in most cases).
I am trying to optimize my time spent in optimizing some SQL.

In your book in chapter 6, on page 310, you said in the NOTE: do not worry if
you cannot replicate my findings exactly. A different setting for
WORKAREA_SIZE_POLICY, PGA_AGGREGATE_TARGET, SORT_AREA_SIZE, DB_CACHE_SIZE,
DB_FILE_MULTIBLOCK_READ_COUNT, different system loads, and so on may affect the
cost estimates of a query.

To not mention system statistics that are very relevant for CBO. Of course
Oracle is recommended collecting them and tweaking them during the different
workload.
With event 10053 you can see which parameters are in the game for CBO (even some
undocumented like _cpu_to_io = 0 that is telling CBO that CPU cost is trivial to
IO, or _optimizer_cost_model that is set to CPU in 10g (default IO) for Oracle
Apps 11i, to not mention other parameters).
You described in the book the most important of course.

What I am trying to tell is that CBO is not 100% reliable, or from Oracle
standpoint it is 100% reliable but you just need to setup your CBO environment
correctly (no mathematics here or formula). If there is not mathematics that
means CBO is not 100% reliable :)
Of course that CBO is becoming more and more reliable.

1) In some special cases when you know that your SQL on huge tables will execute
by some specific path as fast as you want: why putting the hint is wrong then
playing the interesting game with all these parameters/stats?

1a) These parameters will be changed from time to time and in this case will
maybe impose the CBO environment change that will lead some SQL's to the wrong
execution path.
That means sometimes we need to mask hints with using many alter session
commands.
Is this true?

2) Are Oracle outlines necessary at all (they are similar as hints to me just it
is smarter programatical way of using hints)?
Are you using stored outlines at all?
Do you think that Oracle outlines are good at all?

3) How are you collecting statistics for these huge fast-growing partitioned
OLTP tables (usually partitioned by date range)?

4) What is faster for you: put the exact complete hint to get satisfied
performances or to tweak parameters/collect statistics (for example some complex
SQL does not want to execute the way you want and you need to do something -
change parameters/stats or putting hints)?

5) You said in your previous response that you are using only "good hints". Does
this mean that even in 0.01% cases you are not using others?

I am using hints very rare, only when necessary or when I am going to spend more
time in doing this the different way by tweaking stats/parameters. Also System
stats can harm other SQL's that are working in the fastest way.

At the end as you said the great argument, you should beleive in Oracle CBO more
because it is smarter then you and will make your SQL's running the fastest way.
Also you should use what is provided to you much more and not invent water, but
sometimes you need to direct Oracle CBO like a child to listen what you have to
say.
In some small number of cases I will rely on old-fashion do what I am saying to
you hints.
Maybe in 10g this will change and I will forget hints forever. Oracle CBO is
becoming better and better, just hints are sometimes my work and
stats/parameters are customer DBA dependant task.

Your good response can change all my wrong thinking of course.

Thanks for any response in advance.

Regards,
Zoran
Followup:
1) i strongly discourage hints because they seem "addictive".  once someone uses
them for one thing, they seem to go off and use them everywhere.  I come down on
the side of "don't do that".

2) they can be a make or break difference in 9i and before to "fix" a real
"problem" query in a 3rd party app.

3) partition by partition -- not any different then a fast growing single
partitioned table really.

4) i collect the right statistics.

5) I cannot recollect any of my code in use that uses what I deem "bad hints".
Good hints, yes -- first_rows, use it lots.

<quote>
I am using hints very rare, only when necessary or when I am going to spend more

time in doing this the different way by tweaking stats/parameters.
</quote>

sounds prudent.

<quote>
 Also System
stats can harm other SQL's that are working in the fastest way.
</quote>

so can gathering statistics
adding a cpu
increasing ram
having less concurrent users
having more concurrent users

it can also be true that system stats can speed up considerably queries that
never performed well, it is something to think about.
Rating: 5
Hints January 06, 2004
Reviewer: Zoran Martic from Dublin, Ireland
Tom,

Thanks a lot for always good response.

>> 1) i strongly discourage hints because they seem "addictive".
There are too many ways to somebody to overuse some Oracle feature. You talked a
lot about overusing partitioning even when there is not any need for them.
I am fighting against partitioning used in some stupid ways for many years. The
same apply for OPS/RAC.
Idea is to use it when is good to use it.

2) agree fully. If there is not hints in the SQL you are free to tune that SQL
up to the limit with changing the environment for SQL

I can say that there are more things not to use "bad hints" as you numbered to
us.
Just to say that sometimes because of Oracle 8.1.6, 8.1.7, 9i R1, 9i R2, 10g you
need to play a lot with parameters/stats to achieve sometimes the same thing you
can easily with fixing the hints for some critical SQL (<1% of SQL's, for >99%
of SQL no need for hints).

Have you ever looked into setting the database parameters for Oracle
Applications 11i. And this is Oracle product where Oracle knows everything about
database internals. Imagine now some third party product, how easy is to setup
the proper environment for all SQL's. Oracle invented  first hints and later on
stored outlines to help with fixing execution plans for non-perfect CBO.
They are all specific to the database version (you have even different
parameters between 8.1.7.4 and other 8.1.7.x)
You know how differently you are collecting statistics between database
versions.
It is becoming better and better with new database versions.

Do we have "bad hints" in Oracle Apps 11i?

**********************************************************
But at the end I agree it is better not to use hints or to use just some "good
hints" (global hints) as you said.
**********************************************************

Thanks for your very good input.

Regards,
Zoran
Rating: 5
_optimizer_mode_force and sql inside pl/sql February 12, 2004
Reviewer: Kevin Meade from Connecticut USA
Tom, a good thread, but I got scared recently when I read the following that
"not to put words in the author's mouth; but" seems to suggest hints are almost
necessary when using sql inside pl/sql.  I've been doing oracle work for about
20 years so this blew me away because I didn't know about it.  The original
author is Garry Robinson, presented also is the original material published in
Quest Pipeline newsletter (hope I'm not breaking some copyright).  Material from
Quest is (like as from you) usually quite reliable so you can imagine my
concern.

--------
Compliments of Garry Robinson, (OracleAdvice.com.

Oracle's default behavior dictates that SQL statements embedded in a PL/SQL
block are always executed with the ALL_ROWS optimization method. For OLTP
applications that rely on packaged stored procedures, this can be bad news for
response times. OLTP queries need to return results back to the user as quickly
as possible which is why OLTP instances generally have parameter optimizer_mode
set to FIRST_ROWS.

There are two ways that you can override this default behavior and force the SQL
inside PL/SQL blocks to use the FIRST_ROWS optimization method. The first way is
to use a hint when declaring the cursor. For example

cursor C1 is
       select /*+ FIRST_ROWS */ empno, name
       from emp;

This is fine when you are still developing or testing an application but in most
cases it won't be easy or straightforward to change the code in a production
database; especially if the application has not been developed in-house. A
further disadvantage is that there is no instance parameter that can "turn off"
the hint if you decide to call the same procedure from a batch job.
For this reason, it is useful to know about the undocumented parameter
_optimizer_mode_force.
In Oracle 8i and 9i, the default setting for this parameter is TRUE, which
forces Oracle to ignore the optimizer_mode setting and behave in the following
way when executing user recursive SQL (SQL inside a PL/SQL block):

INIT.ORA OPTIMIZER_MODE        Mode used inside PLSQL block

RULE                           RULE
CHOOSE                         ALL_ROWS
ALL_ROWS                       ALL_ROWS
FIRST_ROWS                     ALL_ROWS

Setting _optimizer_mode_force to FALSE allows Oracle to behave in the way you
might expect when executing SQL in stored procedures.
--------
I have included above via cut/paste the original material on Quest so I can't
give you anything else.  My questions are: 1) is this true, 2) is it true for
just some forms of sql in pl/sql (i.e. just cursors maybe), 3) what do you say
about it?

thanks, Kevin Meade
Followup:
I've never used hints other then what I consider "good" hints in plsql.  good
hints give the optimizer more information (first_rows, all_rows, rewrite,
no_rewrite, append -- are good.  index, use_nl, hash, etc -- are bad hints IMO)

You would know pretty much IMMEDIATELY if a query needs first_rows (repsonse
time would sort of "not be good")

It is rare to even have to hint first/all rows -- especially in 2004 with many
apps being web based and not really "interactive" in the fashion of a
client/server application (and this caveat above with OLTP only applied to ref
cursors being returned as well really!)
Rating: 5
Wow, you deliver almost as fast as the chinese takeout around here February 12, 2004
Reviewer: Kevin Meade from Connecticut USA
But in your case I want quality not quantity.  Not that your response was bad,
but it leaves me wanting.

I am with you on "good vs. bad hints", and I am with you on "should be obviously
slow if first_rows is needed", but what concerns me is the fact that there is a
blatant difference in run environment behaviors between pl/sql and other sql
execution mechanisms that has never been made clear to me.  All along I was
assuming that pl/sql would do the same thing as sql*plus with regards to sql
execution (pass it to the database and let it do its thing), but now I don't
know.  This assumption made it easy to test execution plans.  Just use simple
"set autotrace on" or other "explain plan" stuff for quick and dirty results.
But neither gives plans for sql inside pl/sql.  Hence the need to run sql
standalone.  So in reality they are apples/oranges.

Reading between the lines of your initial response and adding some more reading
I have done since reading your initial response, I believe that:

1) Yes, pl/sql does not do the same optimizer environment mapping as sql*plus
and so, if I want to ensure an identical plan in pl/sql as I see in a sql*plus
test, I must either use first_rows hint, or set _optimizer_mode_force
appropriately (I assume =true?).

2) _optimizer_mode_force applies to recursive sql only, but all sql executed
from plsql is considered recursive sql and is thus subject to this phenomenon:
select into..., simple cursor, ref cursor, for loop cursor,
insert/update/delete.

Are these statements accurate? Shoud I set =true or =false?

I don't want to over react.  I only want to understand something I did not
realize was going on and correct my own testing and tuning methodologies that I
have used for many years but which now I may have to consider invalid.

Here is my dilemma: if a junior developer asks me, can you show me how to see
the plans for the sql in my package, what do I tell him?  Considered a senior
oracle guy wherever I go, I am expected to be able to handle this kind of stuff
and I don't feel confident anymore just showing him how to hack his code and do
autotrace on.

So,

Would you say the two statements above are accurate?  Is my only resort to use
tkprof?  Would you suggest I use the hidden parameter (as much as you and I both
don't like this)?  Do you have any other advice to add?

humbly yours, (you want eggroll with that?) Kevin Meade
Followup:
thing about it this way.

a plsql procedure returns when?  after all of its code has been executed.  with
the EXCEPTION of ref cursors -- it should be optimized for throughput (who cares
if the first row takes 5 seconds to get -- if I get the last row in 6 seconds
when the alternative is to get the first row in 1 second but the last row in 100
seconds.  the end user is waiting for the procedure to FINISH).  It makes sense
for plsql to default to all_rows type of processing.

I've always found explain plans to be "of great academic interest" but when the
rubber meets the road -- there is nothing but nothing that will supplant tkprof.
 explain plans -- cool, sort of neat, nice.  tkprof and reality -- the only
thing going.

1) not any more.  plsql picks up your optimization goal of rule choose first
rows all rows.  It used NOT TO, but does not.

if you use choose, choose is akin to "all rows".  If you run this script in 8.0
and before:

alter session set optimizer_mode=first_rows;

drop table t;
create table t ( x int );
analyze table t compute statistics;

@trace
select * from t sqlplus;

create or replace procedure p
as
begin
    for x in (select * from t plsql)
    loop
        null;
    end loop;
end;
/

exec p

you would find in the tkprof that the "from t sqlplus" was first_rows optimized
whereas the "from t plsql" was CHOOSE (in effect 'throughput').

In 8i and beyond, you would find it used first_rows in both.

2) you should leave _ parameters ALONE until and if support says "ok, go ahead"

In short -- PLSQL sql always behaved more or less like sqlplus sql since the
default optimizer goal of CHOOSE was used!  Only if you set first rows in your
session would you discover a difference and only in the older releases.

so, for now -- plsql and "sqlplus sql" are in effect one in the same for all
intents and purposes.

I don't really see even that much of a change from the 8.0 to 8.1 behavior in
general really -- it would come into play so so infrequently and now works the
way you appear to want it to work.
Rating: 4
Quick and Dirty... February 12, 2004
Reviewer: Brent Pathakis from Salt Lake City, UT
"Here is my dilemma: if a junior developer asks me, can you show me how to see
the plans for the sql in my package, what do I tell him?  Considered a senior
oracle guy wherever I go, I am expected to be able to handle this kind of stuff
and I don't feel confident anymore just showing him how to hack his code and do
autotrace on."

Quick and dirty are the key words here ... I would only use set autotrace on for
'quick and dirty' tests.  Things in sqlplus (like arraysize, and other's i'm
sure.) can affect the results / plans.

For more accurate testing I would have the developer - alter session set
sql_trace=true and use tkprof.
Followup:
I'd tell him "plans are of great academic interest, the proof is in run time
performance.  You nor I can look at a plan as say "tis the best plan ever" so --
use sql_trace and tkprof and see reality"

Honest, that is what I do say.

set autotrace is quick and dirty.

do you know that explain plan and hence autotrace "lie" frequently?  they
"guess", they do not have all of the needed information.

tkprof is the ONLY way to really find the REAL plan that would/will be used.

consider:

ops$tkyte@ORA9IR2> create table t ( x varchar2(25) primary key);

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> delete from plan_table;

0 rows deleted.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> variable x number
ops$tkyte@ORA9IR2> set autotrace on explain
ops$tkyte@ORA9IR2> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA9IR2> select * from t where x = :x;

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   INDEX (UNIQUE SCAN) OF 'SYS_C004718' (UNIQUE)

looks reasonable eh?  but wait, tkprof says.....

select *
from
 t where x = :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          3          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          3          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 101

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  TABLE ACCESS FULL T (cr=3 r=0 w=0 time=45 us)

so, which one is correct -- answer is tkprof is ALWAYS right, explain plan
might be right.

here what happened was the explain plan ASSUMED all binds are varchar's (always,
regardless of what you "variable" them as).  Well, char_column_that_is_indexed =
:bind_variable_of_char_type can use an index -- so that is the plan it showed.

However the reality is you have char_column = :number_bind and that is the same
as  to_number( char_column ) = :number_bind which of course obviates the index
access path.

quick and dirty is just that (add bind variable peeking to the mix and it gets
even more "gnarly")

If you want the honest to goodness plan, you would:

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2     l_my_input  number;
  3     cursor c is select * from t where x = l_my_input;
  4  begin
  5     open c;
  6     close c;
  7  end;
  8  /

PL/SQL procedure successfully completed.

and then use tkprof to see:

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: 101     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  TABLE ACCESS FULL T (cr=0 r=0 w=0 time=0 us)

what it is.
Rating: 5
Thanks Brent, point well taken February 12, 2004
Reviewer: Kevin Meade from Connecticut USA
Thanks Brent, point well taken.  I have been bitten by arraysize a couple of
times (was not aware of other factors but that don't mean there ain't any as you
point out).

So share a little with me.  Do you set _optimizer_mode_force in your systems?
If so, to what and why?  If not, why not?

I believe the affects of _optimizer_mode_force and the behavior of pl/sql in
this regard is not well known (maybe its just me), but I asked the other heavies
in my workplace and none of them had any idea about it or that pl/sql would vary
in this regard.  Indeed they all seemed a little agitated that the usual "quick
and dirty" may be a little too dirty than we all hade expected.  It is a
particular problem for us as we make heavy use of pl/sql in our environments.

As for tkprof, well, its great if you can get easy access to it.  I know what
some people will say (I have read the posts here about this issue and possible
ways to work tkprof, and I was hoping to avoid a discussion of this topic), but
the fact is not all oracle experiences are the same (a good thing really).
Though many oracle people have used tkprof consistently in their careers, many
others have worked for outfits that divide IM lines on a hardware basis.  In
practicality this means that you don't get access to the host systems and you
certainly can't run programs on them whenever you like.  I work in one such
environment.  Indeed, every company I have worked for has had similar dividing
lines and rule (most were/are bigger companies).  This makes it difficult at
best to run tkprof and get its results.

I am trying to gauge how important this issue is and if I need to now insist on
the ability to use tkprof on a regular basis for both myself and other
developers here.  We have several hundered oracle people of many levels of
experience.  Giving access to many host systems to this many people becomes a
protocol problem of sorts (gonna need a meeting of the architects, then the
support areas, then the app areas, then the infrastructure area, then the
security area (hmm.. maybe I should start with security first, they are most
likely so say no in this day and age), then ... this is gonna take a year to get
approved).

Ah but I don't want to complain.  Its good to see others offer help, makes me
feel more like a community member.  Thanks, Kevin Meade
Followup:
don't touch it, don't set it. leave it BE
Rating: 4
RE: Quick & Dirty February 12, 2004
Reviewer: Brent Pathakis from Salt Lake City, UT
Kevin,

  I dont use the _optimizer_mode ... settings in my system... I just leave it
set choose, analyze the table, and let it default to all_rows, unless / until I
need something different.

  You're right about tkprof... it can be tricky to get access to.  An
alternative I've seen to it is sql_analyze in OEM.  But ... I don't think I
trusted it's answers as much as tkprof.
Followup:
nothing but tkprof is good enough.  if your policies do not permit it on your
test/dev system - your policies in effect prevent you from doing your job.
Rating: 5
OK February 12, 2004
Reviewer: Kevin Meade from Connecticut USA
thanks, maybe my setups are not that far off from everyone elses in this regard
and I don't really need to worry.

Kevin
Rating: 5
cursor open/close with no fetch February 13, 2004
Reviewer: James Blanding from Kingston, RI USA
"If you want the honest to goodness plan, you would:

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2     l_my_input  number;
  3     cursor c is select * from t where x = l_my_input;
  4  begin
  5     open c;
  6     close c;
  7  end;
  8  /

PL/SQL procedure successfully completed.

and then use tkprof to see:
[...]"

I see that basically goes through the motions of submitting the SQL and having
it parsed, but never actually goes out to get any data.  So I guess the execute
step alone is pretty light-weight for plain old SELECT statement...  This will
help a lot for getting an accurate plan for a query that would normally take
tens of minutes to run.....

Very nice indeed.  Just throw in dbms_session.set_sql_trace (true); before the
open/close and the same for 'false' afterwards.  Wish I'd though of that a
couple months ago! :)
Followup:
oh, and to get bind variable peeking done right -- you would initialize
l_my_input as well :)
Rating: 3
odd March 04, 2004
Reviewer: Justin from PA
Tom,

I have a table t038_fi:

wic_owner8@DEV> select count(*) from t038_fi;

     COUNT(FI_NR)
=================
          5582265

I have a unique index on that column:
wic_owner8@DEV> select * from user_ind_columns where column_name ='FI_NR';

INDEX_NAME                     TABLE_NAME
============================== ==============================
COLUMN_NAME
==========================================================================
  COLUMN_POSITION     COLUMN_LENGTH       CHAR_LENGTH DESC
================= ================= ================= ====
X038IE01                       T038_FI
FI_NR
                1                10                10 ASC

If I issue the following select, it takes about 30 seconds to complete.
**********************************************************
wic_owner8@DEV> select t038_id from t038_fi where fi_nr = 7786789472;

          T038_ID
=================
         16103190

Elapsed: 00:00:31.06

Execution Plan
==========================================================
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=8591 Card=1 Bytes=15)
   1    0   TABLE ACCESS (FULL) OF 'T038_FI' (Cost=8591 Card=1 Bytes=15)

Statistics
==========================================================
          0  recursive calls
          0  db block gets
      89332  consistent gets
      52707  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
**********************************************************

If I issue the following select, it comes back in 2 seconds using the index. Why
isn't the CBO using the index if I try to return other columns?

wic_owner8@DEV> select fi_nr from t038_fi where fi_nr = 7786789472;

FI_NR
==========
7786789472

Elapsed: 00:00:02.04

Execution Plan
==========================================================
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1561 Card=1 Bytes=10)
   1    0   INDEX (FAST FULL SCAN) OF 'X038IE01' (UNIQUE) (Cost=1561 Card=1
Bytes=10)

Statistics
==========================================================
          0  recursive calls
          0  db block gets
      16270  consistent gets
          0  physical reads
          0  redo size
        384  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 have these parameters changed:
wic_owner8@DEV> show parameter index

NAME                                 TYPE        VALUE
==================================== =========== =====
optimizer_index_caching              integer     90
optimizer_index_cost_adj             integer     35

Switching them back and forth from the original values (0,100 respectively) do
not cause any differences for either query.

Can you help me understand why the CBO is working the way it is?

Thanks very much for your help.
Followup:
what does

select num_rows from user_tables where table_name = upper('t038_fi')

return? I'll guess "not very much".  It is saying "hey, table is just a block,
why bother with an index if I have to a) index range scan and b) table access"

that is also why it is index full scanning...

did someone analyze empty tables?
Rating: 2
Should have mentioned that March 05, 2004
Reviewer: Justin from PA
Yes. I have analyzed the table.

wic_owner8@DEV> select num_rows from user_tables where table_name =
upper('t038_fi')
  2  ;

         NUM_ROWS
=================
          5582265

Thanks very much for your help.
Followup:
oh, I see it now.

You are basically issueing:

select ... from t where apples = oranges;

You are comparing a string (fi_nr) to a number and that is the same as saying:

select ... from t where TO_CHAR(fi_nr) = 1234;

you are apply a function to fi_nr, making it impossible for us to use the index
properly.  consider:

ops$tkyte@ORA9IR2> create table t ( fi_nr       varchar2(10) NOT NULL , t038_id
number );

Table created.

ops$tkyte@ORA9IR2> create unique index x038ie01 on t(fi_nr);

Index created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec dbms_stats.set_table_stats( user, 'T', numrows =>
5000000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> set autotrace traceonly explain
ops$tkyte@ORA9IR2> select t038_id from t where fi_nr = 123456576;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=11 Card=50000 Bytes=1000000)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=11 Card=50000 Bytes=1000000)

ops$tkyte@ORA9IR2> select fi_nr from t where fi_nr = 123456576;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=50000 Bytes=350000)
   1    0   INDEX (FAST FULL SCAN) OF 'X038IE01' (UNIQUE) (Cost=4 Card=50000
Bytes=350000)

ops$tkyte@ORA9IR2> select t038_id from t where fi_nr = '123456576';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=20)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=1 Bytes=20)
   2    1     INDEX (UNIQUE SCAN) OF 'X038IE01' (UNIQUE) (Cost=1 Card=100)

ops$tkyte@ORA9IR2> select fi_nr from t where fi_nr = '123456576';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=7)
   1    0   INDEX (UNIQUE SCAN) OF 'X038IE01' (UNIQUE) (Cost=1 Card=1 Bytes=7)

ops$tkyte@ORA9IR2> set autotrace off

put your literal in quotes and see what you see (and ask yourself why a field
named "nr" for NumbeR is using a string datatype........)
Rating: 5
! :) March 05, 2004
Reviewer: Justin from PA
You rule.

Duh.. I totally missed that, and you're absolutely right.
I'll change it right away and look for other situations in the model where there
is similar misuse of datatypes.

Thanks a million Tom!
Rating: 4
use hints in deletes March 23, 2004
Reviewer: A reader
Hi

I have this delete statement

DELETE om_cmf_pkg
    WHERE package_instance_id in ( SELECT /*+ INDEX(A OM_ORDER_IDX01) USE_NL(A
C) USE_HASH(D) */
                                          c.package_instance_id
                                     FROM swb.om_order a,
                                          om_cmf_pkg c,
                                          om_cmf_pkg_components d
                                    WHERE a.order_id = c.order_id
                                      AND a.order_reference LIKE 'OEADSL%'
                                      AND d.action = 'E'
                                      AND c.package_instance_id =
d.package_instance_id )

Plan Table
--------------------------------------------------------------------------------
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| DELETE STATEMENT          |          |    67K|    2M| 127046 |       |       |
|  DELETE                   |OM_CMF_PK |       |      |        |       |       |
|   MERGE JOIN              |          |    67K|    2M| 127046 |       |       |
|    TABLE ACCESS BY INDEX R|OM_CMF_PK |    15M|  344M|  55996 |       |       |
|     INDEX FULL SCAN       |PK_OM_CMF |    15M|      |  14628 |       |       |
|    SORT JOIN              |          |    67K|  861K|    521 |       |       |
|     VIEW                  |VW_NSO_1  |    67K|  861K|  70529 |       |       |
|      SORT UNIQUE          |          |    67K|    3M|  70529 |       |       |
|       HASH JOIN           |          |    67K|    3M|  68895 |       |       |
|        TABLE ACCESS FULL  |OM_CMF_PK |     1M|    7M|  14078 |       |       |
|        NESTED LOOPS       |          |   740K|   28M|  53756 |       |       |
|         TABLE ACCESS BY IN|OM_ORDER  |    16K|  465K|   4418 |       |       |
|          INDEX RANGE SCAN |OM_ORDER_ |    16K|      |    128 |       |       |
|         TABLE ACCESS BY IN|OM_CMF_PK |    15M|  172M|      3 |       |       |
|          INDEX RANGE SCAN |RL_CMF_PK |    15M|      |      2 |       |       |
--------------------------------------------------------------------------------

How can i change the sort merge join to hash join? I tried to put hints in
DELETE /* ... */ but no effect, I used an alias for the delete table and put the
alias inside subquery hint but no efffect neither... Is there any way to do
this?
Followup:
i'd lose the HINTS all together.  see what that does (and one would need to
understand your pga settings -- sort area/hash area/pga aggregate target -- and
versions...)
Rating: 5
list of good and bad hints March 24, 2004
Reviewer: A reader
Hi Tom I'm trying to create a list of good hints (hints should be used when you
need them)
and bad hints (hints you shouldn't use, except in exceptional situations and
with good skill)

can you point in any of your book if you have a list like that.
.) thanks
Followup:
I've got a slide :)

this is my good list, if not on good list, it would be on the other kind of list

ALL_ROWS
FIRST_ROWS(n) or FIRST_ROWS
CHOOSE
(NO)REWRITE
DRIVING_SITE
(NO)PARALLEL
(NO)APPEND
CURSOR_SHARING_EXACT
DYNAMIC_SAMPLING
CARDINALITY
Rating: 5
om March 24, 2004
Reviewer: A reader
Thanks T
March 24, 2004 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
Rating: 5
O7_DICTIONARY_ACCESSIBILITY February 22, 2004
Reviewer: reader
As this parameter is much of a security issue, why does oracle have this
parameter in the first place and even made it as true as default in pre 9i? Any
history about this parameter that you could share? Why is it named with o7 for
example? Thanks.
Followup:
because the behaviour became "changeable" with 8.x (either do it the way O7 did
or do it the new way).  In 9i, the "new" way became "the default way"
Rating: 2
I can connect as sys March 24, 2004
Reviewer: Mylah from MA, USA
I did review this article and found it helpful however, I have
07_Dictionary_Accessiblity set to false and can still connect as sys.  From a
sqlplus prompt I simply do the following:

conn sys/password@sid AS SYSDBA

I connect fine.
Followup:
that is not as sys.

that is however AS SYSDBA which is a totally different beast.

connect sys/password

and let us know what "happens" then.
Rating: 5
March 24, 2004
Reviewer: A reader
March 24, 2004 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.
Rating: 5
Merge and update/insert count January 15, 2004
Reviewer: karma from MA USA
In using Merge statment how would i how many rows got updated and how many rows
got inserted?
Followup:
you cannot, they are "merged" -- period.

some are inserted, some are updated -- all are "merged"
Rating: 5
Which Column March 10, 2004
Reviewer: A reader
Hi Tom,

If we update two columns in a update statement

UPDATE <table_name> SET <column_1>=<value_1>,
                        <column_2>=<value_2>
;
And the following message is received

ORA-01722: invalid number

Is there a way to know if the invalid number was for <column_1> or <column_2>

Also, is there a way to let <column_1> gets updated if there is not a datatype
mismatch and let <column_2> fail if datatype for this is not correct? ie.
enabling partial updates?

I know this is very basic, but your comments as always are highly appreciated
and welcome.

Thanks
Followup:
No, there is not (for both questions)

statements are always atomic - they either happen 100% sucessfully, or they do
not happen at all.
Rating: 4
Failure for individual record during bulk update March 24, 2004
Reviewer: Shimon from Israel
Hi Tom!

I liked your answer to the question prsented in the title very much . The
problem is that using "...returning DEPTNO BULK COLLECT into ..." returns only
the successful rows, that were actually updated. I'd like to stop the whole
process even if only one fails because no matching record found, and print the
exact data that caused the failure   (i.e., print something like : " 'The
process failed due to NO_DATA_FOUND for Department number  '||l_deptno(i) ").
I hope it is possible....

Thanks in advance,
Shimon B.
Followup:
"no matching record found" is NOT AN ERROR in any way shape or form!

(i never understand this point of confusion really -- select * from t where 1=0
is not an error, update t set x = 55 where 1=0 is NOT and error and so on)

If you update by primary key -- and you bulk collect back the primary keys (and
you don't update the primary keys), you'll know what rows got updated and which
did not.
March 24, 2004 David -- Thanks for the question regarding "Materialized View on Table Without Primary Key", version 9.1.2
You Asked
Tom,

Is there any reason why it would be necessary for the master table of a
materialized view to have a primary key in order that fast refresh work?

For example...

SQL> CREATE TABLE FACT
2 (
3 store_key number(8),
4 prod_key number(6),
5 time_key number(8),
6 dollar_sales number(12)
7 );

Table created.

SQL> CREATE MATERIALIZED VIEW log on FACT
2 with rowid (store_key,time_key,dollar_sales)
3 including new values;

Materialized view log created.

SQL> CREATE MATERIALIZED VIEW FACT_STORE_TIME_MV
2 build immediate
3 refresh fast on demand
4 enable query rewrite
5 as
6 select
7 store_key,
8 time_key,
9 sum(dollar_sales) sum_dollar_sales,
10 count(dollar_sales) c_dollar_sales,
11 count(*) c_star
12 from
13 FACT
14 group by
15 store_key,
16 time_key;

Materialized view created.

SQL> set feedback off
SQL> insert into FACT values (1,1,1,1000);
SQL> insert into FACT values (1,1,2,1234);
SQL> insert into FACT values (1,1,3,2345);
SQL> insert into FACT values (1,2,1,1111);
SQL> insert into FACT values (1,2,2,2222);
SQL> insert into FACT values (1,2,3,3333);
SQL> insert into FACT values (2,1,1,2132);
SQL> insert into FACT values (2,1,2,3244);
SQL> insert into FACT values (2,1,3,2167);
SQL> insert into FACT values (2,2,1,8901);
SQL> insert into FACT values (2,2,2,1114);
SQL> insert into FACT values (2,2,3,8890);
SQL> commit;
SQL> set feedback on
SQL> select * from FACT_STORE_TIME_MV;

no rows selected

SQL> begin
2 DBMS_MVIEW.REFRESH
3 (
4 list => 'FACT_STORE_TIME_MV',
5 method => 'f',
6 push_deferred_rpc => false
7 );
8 end;
9 /

PL/SQL procedure successfully completed.

SQL> select * from FACT_STORE_TIME_MV;
STORE_KEY TIME_KEY SUM_DOLLAR_SALES C_DOLLAR_SALES C_STAR
---------- ---------- ---------------- -------------- ----------
1 3 5678 2 2
1 2 3456 2 2
2 1 11033 2 2
2 2 4358 2 2
1 1 2111 2 2
2 3 11057 2 2
6 rows selected.

This works fine, but I've been told that this usage is A Bad Thing because the
master table has to have a primary key.

Any thoughts on why this might be so?
and we said...
because more and more often rowids can change.

In 9i, urowids on IOTs change.  Partitioned tables with enable row movement
allow rowids to change.  ALTER TABLE t MOVE will change rowids.  In 10g more and
more things will change rowids.

So, a rowid is no longer this imutable key that is assigned when a row is
inserted and kept with the row forever.

That is why it is bad -- rowids can change and they will not be "captured"
during the refresh process.  You cannot rebuild, reorg or allow row movement on
tables where the rowid cannot change.  It is very inflexible and getting even
more so (lots of new features in 10g rely on row movement and rowids changing --
such as flashback table and online segment shrinking)
Review & Followup

Rating: 3
January 25, 2004
Reviewer: Dave from Colorado Springs
Can you give an example of how a change to a rowid in the master table could
"break" the fast refresh process? I'm stumped to think of a way this could
happen.  It seems to me that once a change has been logged in the MV log, row
movement in the base table would not be an issue -- fast refresh would not be
affected?
Followup:
alter table t move;

bamm. all rowids changed, nothing in the mv logs.

update a partition key, cause the row to move, bamm.  the rowid based changes
COUNT on the rowids being imutable (they were when snapshots were invented in
7.0, they stopped being so in 8i)

As every table should in fact have a primary key -- the use of rowids is frowned
upon for this reason -- and the fact that reorgs and many admin operations will
totally kill it.
Rating: 3
January 25, 2004
Reviewer: Dave from Colorado Springs
Well, that's an interesting "feature"! I commonly design data warehouse fact
tables to have no primary key, because the logical key tends to be so many
columns that it is unfeasible (say 12 columns out of a table of 16 columns
total), and a synthetic key has to be prefixed by the partition keys, so it's
still no lightweight single-column index.

If it weren't that Oracle automatically removes entries from the rowid-based log
("bamm", as you say) then the rowid MV log would hold all the information needed
for fast refresh of summary tables (due to the minimum conditions that the log
must meet in order for the MV to be created as fast refreshable) -- the fast
refresh process doesn't even touch the master table itself, and hence the
movement of the table is irrelevant to the refresh process. I'd agree that the
rowid change would not be irrelevant were I trying to create an updateable
materialized view througha rowid-based log, henve the change to PK-basedlogs,
but that's not even close to an option on a summary MV of the type I showed.

By the way, the Oracle Data Warehousing Guide is full of examples of non-PK'd
master tables with fast refresh through a rowid-based log. The "Sales History"
example schema is used throughout, and the sales fact table is not PK'ed.
Followup:
it is administrative and row moving thing that mess it up.  things that DO NOT,
CANNOT maintain the log.

we do not automatically remove things, it is that things are not put there - we
are assuming "rowid is imutable, never changing" -- however, rowid is changing
and many more things into the future will cause it to in fact change.

the fast refresh most certainly does touch the master table -- why do you
believe otherwise?

But -- it is because the ROWID CHANGES that we have a problem -- and there is no
such log record as "rowid changed, please update your rowid in your MV"

you asked me a question with "This works fine, but I've been told that this
usage is A Bad Thing because the
master table has to have a primary key."

I've tried to tell you why this falls into the category of "a bad idea".
Rating: 3
January 25, 2004
Reviewer: Dave from Colorado Springs
I understand that this is a Bad Thing in some circumstances, but there are very
clear circumstances in which it is not -- the key characteristic of the
circumstances in which it is Not a Bad Thing is where the materialized view does
not preserve individual records from the master table (because it aggregates
them), and where is does not have to access the master table to perform the
refresh (see below).

Unfortunately (for me, and surely for others) this is a circumstance which is
prevalent in data warehousing -- search Oracle Documentation for "Materialized
View" and a great many hits come from the Data Warehousing Guide, and as I
mentioned before this "Bad Thing" is a configuration that is widely advocated in
that document.

Anyhoo, "the fast refresh most certainly does touch the master table -- why do
you believe otherwise?".  Two reasons -- the documentation implies it, and in
the true spirit of scientific enquiry I have tested it. Here's the documentation
...

[QUOTE]
Restrictions on Fast Refresh on Materialized Views with Aggregates

Defining queries for materialized views with joins and aggregates have the
following restrictions on fast refresh:

    * All restrictions from "General Restrictions on Fast Refresh".

Fast refresh is supported for both ON COMMIT and ON DEMAND materialized views,
however the following restrictions apply:

    * All tables in the materialized view must have materialized view logs, and
the materialized view logs must:
          o Contain all columns from the table referenced in the materialized
view.
          o Specify with ROWID and INCLUDING NEW VALUES.
          o Specify the SEQUENCE clause if the table is expected to have a mix
of inserts/direct-loads, deletes, and updates.
[/QUOTE]

The inclusion of "Contain all columns from the table referenced in the
materialized view." implies that the fast refresh is performed without reference
to the master table -- i see no other reason for it's inclusion.

However, here is a script that i believe demonstrates it.

SQL> CREATE TABLESPACE "SPONGEBOB"
2 LOGGING
3 DATAFILE 'C:\ORACLE\ORADATA\LOCAL\SPONGEBOB1.ora' SIZE 1M
4 REUSE AUTOEXTEND
5 ON NEXT 500K MAXSIZE 5M EXTENT MANAGEMENT LOCAL
6 SEGMENT SPACE MANAGEMENT AUTO
7 /

Tablespace created.

SQL> CREATE TABLE FACT
2 (
3 store_key number(8),
4 prod_key number(6),
5 time_key number(8),
6 dollar_sales number(12)
7 )
8 tablespace spongebob;

Table created.

SQL> CREATE MATERIALIZED VIEW log on FACT
2 tablespace sandbox
3 with rowid (store_key,time_key,dollar_sales)
4 including new values;

Materialized view log created.

SQL> CREATE MATERIALIZED VIEW FACT_STORE_TIME_MV
2 tablespace sandbox
3 build immediate
4 refresh fast on demand
5 enable query rewrite
6 as
7 select
8 store_key,
9 time_key,
10 sum(dollar_sales) sum_dollar_sales,
11 count(dollar_sales) c_dollar_sales,
12 count(*) c_star
13 from
14 FACT
15 group by
16 store_key,
17 time_key;

Materialized view created.

SQL> set feedback off
SQL> insert into FACT values (1,1,1,1000);
SQL> insert into FACT values (1,1,2,1234);
SQL> insert into FACT values (1,1,3,2345);
SQL> insert into FACT values (1,2,1,1111);
SQL> insert into FACT values (1,2,2,2222);
SQL> insert into FACT values (1,2,3,3333);
SQL> insert into FACT values (2,1,1,2132);
SQL> insert into FACT values (2,1,2,3244);
SQL> insert into FACT values (2,1,3,2167);
SQL> insert into FACT values (2,2,1,8901);
SQL> insert into FACT values (2,2,2,1114);
SQL> insert into FACT values (2,2,3,8890);
SQL> commit;
SQL> set feedback on

SQL> select * from FACT_STORE_TIME_MV;

no rows selected

SQL> alter tablespace spongebob offline;

Tablespace altered.

SQL> select * from FACT;
select * from FACT
*
ERROR at line 1:
ORA-00376: file 23 cannot be read at this time
ORA-01110: data file 23: 'C:\ORACLE\ORADATA\LOCAL\SPONGEBOB1.ORA'

SQL> begin
2 DBMS_MVIEW.REFRESH
3 (
4 list => 'FACT_STORE_TIME_MV',
5 method => 'f',
6 push_deferred_rpc => false
7 );
8 end;
9 /

PL/SQL procedure successfully completed.

SQL> select * from FACT_STORE_TIME_MV;

STORE_KEY TIME_KEY SUM_DOLLAR_SALES C_DOLLAR_SALES C_STAR
---------- ---------- ---------------- -------------- ----------
1 3 5678 2 2
1 2 3456 2 2
2 1 11033 2 2
2 2 4358 2 2
1 1 2111 2 2
2 3 11057 2 2

6 rows selected.

You see from the above that the master table was not available at the time that
the refresh was requested -- hence i conclude that only the log was accessed.

Evidently there is an element within Oracle that thinks this scenario is
supported, because they are documenting it as such.
Followup:
In general, the defining tables are touched -- it is not the rule.  It was the
generalization:

"the fast
refresh process doesn't even touch the master table itself, and hence the
movement of the table is irrelevant to the refresh process."

i was have a problem with -- in general, that is not the case.

If ( you NEVER reorg the tables
     AND
     you do not enable row movement on these tables )
THEN
     using rowids is safe
ELSE
     using rowids is not safe.  anything that stored rowids that pointed
     to these objects is not safe.
end if

I never said it was supported.

You asked "i've been told this is not such a good idea, any ideas why"

I simply tried to respond with "this is why it can be a bad idea"

that's all.  do what you will -- it obviously is supported (hey, you are doing
it, the sql is right there -- it is documented)
Rating: 3
January 25, 2004
Reviewer: Dave from Colorado Springs
I suppose that the folks who wrote the documentation and coded the MV/MV log
restrictions that promote/allow this technique had the narrow set of
circumstances in mind in which this was a valid configuration, where changes to
rowid would not affect functionality. The emptying of the rowid-based MV log
when the master table is moved is unfortunately not documented as a limitation
on the validity of the methodology.

It's enough for me to know that it's supported -- I'd been told that it isn't.

The (thinly) humorous part of all this is that I don't even want to use
DML-based fast refresh -- what I'm doing is Partition Change Tracking (PCT) fast
refresh. One of the peculiarities of PCT fast refresh is that you must have an
MV log on the partitioned master table, even though you might never, ever use it
-- all modifications to the master table are through partition exchange and
other partition-based operations. It was during a request for assistance with a
PCT problem that I was told that the non-PK'ed master table was not supported,
and that I couldn't be helped further.

Is it possible that you can find out for me/us how Oracle tracks master table
partitions that have changes to push to PCT MV's? I'm guessing that there is a
comparison of SCN's for the last MV refresh and that of the partition changes,
but it appears to be undocumented.

Lastly, thanks muchly for the help on a Sunday. It's well above and beyond the
call of duty, and it's much appreciated.
Rating: 3
OK January 26, 2004
Reviewer: Stephen from Bangalore,India
Hello sir,
While creating materialized views What is the use of using
the clause 'enable query rewrite'?Could you please explain?
Thanks in advance.
Followup:
query rewrite allows Oracle to rewrite your query against some detail table to
be against the materialized view.  It lets Oracle rewrite the query.  consider:

big_table@ORA920PC> alter session set query_rewrite_enabled=true;
Session altered.

big_table@ORA920PC> alter session set query_rewrite_integrity=enforced;
Session altered.

big_table@ORA920PC> select status, count(*) from big_table group by status;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=8867 Card=2 Bytes=12)
   1    0   SORT (GROUP BY) (Cost=8867 Card=2 Bytes=12)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=4861 Card=3500000
Bytes=21000000)

Statistics
----------------------------------------------------------
       1477  recursive calls
          0  db block gets
      50822  consistent gets
      50543  physical reads
        480  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
         30  sorts (memory)
          0  sorts (disk)
          2  rows processed

big_table@ORA920PC> set autotrace off
big_table@ORA920PC> pause

big_table@ORA920PC>
big_table@ORA920PC> create materialized view big_table_mv
  2  build immediate
  3  refresh on commit
  4  enable query rewrite
  5  as
  6  select status, count(*)
  7    from big_table
  8   group by status
  9  /

Materialized view created.

big_table@ORA920PC> analyze table big_table_mv compute statistics;

Table analyzed.

big_table@ORA920PC> pause

big_table@ORA920PC>
big_table@ORA920PC> variable x varchar2(20);
big_table@ORA920PC> exec :x := 'VALID';

PL/SQL procedure successfully completed.

big_table@ORA920PC>
big_table@ORA920PC> set termout off
big_table@ORA920PC> set autotrace traceonly
big_table@ORA920PC> select status, count(*) from big_table group by status;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=2 Bytes=22)
   1    0   TABLE ACCESS (FULL) OF 'BIG_TABLE_MV' (Cost=2 Card=2 Bytes=22)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          4  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
          0  sorts (memory)
          0  sorts (disk)
          2  rows processed

see how the query was silently rewritten to use a totally different table than
you queried?  that is query rewrite.
Rating: 5
ROWID changes.. yes ... but arn't they safe in objects where they are used by defination January 27, 2004
Reviewer: Shouvik Basu from Kolkata, India
Thanks to Dave for posting the issue. I personally do not like the concept of a
Surrogate Key neither a PK of 8 columns. However now I am inclining to Surrogate
Keys (I am sure there will be a messup with rowid in database recovery
situations).

The concept of ROWID change has been aptly explained by Tom. IOT or Partitioned
tables with row movement or reorg. So "5 stars".

Just wanted to know
(1) ROWIDs are used in indexes, in MV logs, and probably in some other places.
How can they ever change in a scalable way ? Any reorg of the table which
impacts ROWIDs should implicitly mean a reorg of the indexes also. Or does
Oracle have a new internal ID (which it wont share with us ;) )?

(2) "Online segment shrinking" - what are they ? Based on point 1, are they
scalable ?

(3) Considering reorgs as DDLs and update of partition key and index key of IOT
as DMLs, if I make the general statement
"ROWID OF A ROW DOES NOT CHANGE IF THE TABLE IT IS IN IS NOT IMPACTED BY A DML
OR DDL".
Am I right ?

(4) Dave's example

"CREATE MATERIALIZED VIEW log on FACT
with rowid (store_key,time_key,dollar_sales)
including new values;"

has got "rowid" in the defination. A change in rowid of the base table should
rebuild the log. I still feel as long as we are not storing ROWID as values in
other columns it is safe.
Followup:
rowids won't get messed up in database recovery situations.  recovery involves
putting each bit back where it belongs.  it is a 100% physical thing.  It would
be reorgs and other things that change rowids that would mess it up.

1/2) alter table t move implies you will be doing a "index rebuild".  If you
move or reorg the table, you will be doing a rebuild of indexes.

If you update a partition key -- and it moves from partition 1 to partition 2,
it is processed much like a delete plus insert -- the index entry is maintained
for you.

In 10g, many other things will perform rowid changes (flashback table, online
segment shrink for example).  These will maintain indexes.

So, it depends on the operation - some will cause you to rebuild (alter table
move), others will maintain (shrink in 10g).

3) well, if a table is never affected by ddl or dml, table is empty.... but if
all you do is INSERT and DELETE data, and never use ddl on the table -- rowids
will be constant for a row as long as the row exists.

4) look at the log, it stores rowids in there.  you cannot really "rebuild" a
log.

It is "safe", it just prevents you from doing certain things or if you do them,
can cause full refreshes to resync everything.  If you use rowid as your primary
key (hey -- you just created a surrogate for 8 columns there), you have to live
with these facts.
Rating: 4
materialized view log March 24, 2004
Reviewer: sonali from waltham, ma
I have a MV and the materialized view log for fast refresh on one of them. I am
writing a installation script for customers. I do not want them to get error on

DROP MATERIALIZED VIEW LOG ON xyz;
ORA-12000: a materialized view log already exists on table

if it does not exists initially.. so I was thinking of adding a check to see if
it exists before doing the drop, but I cannot find which system table keeps the
refrence to it ?

DROP MATERIALIZED VIEW LOG ON xyz;
CREATE MATERIALIZED VIEW LOG ON xyz WITH ROWID, SEQUENCE
(a,b,c)
INCLUDING NEW VALUES;

I have wrote a small pl/sql block to do this for view itself
DROP MATERIALIZED VIEW mv_xyz;
CREATE MATERIALIZED VIEW mv_xyz
....

Declare n int;
begin
select COUNT(*) into n from all_objects where OBJECT_NAME=upper('xyz') AND
OBJECT_TYPE = 'MATERIALIZED VIEW' AND OWNER= UPPER('xxx');
If n=0 then
execute immediate 'DROP MATERIALIZED VIEW LOG ON xyz';
End if;
End;
/

How do I do something similar for mv logs ?

Thanks
Followup:
begin
   execute immediate 'drop materialized view log on xyz';
exception
   when others
       if ( sqlcode = 12000 ) then null;
       else raise;
       end if;
end;
/

just try to do it and ignore the errors you wish to ignore.
March 24, 2004 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.
Rating: 3
I have a simple (for you) doubt February 20, 2004
Reviewer: Raju from USA
Tom,
Thanks for the wonderful service. Second time poster but regular reader( my
homepage). I have a simple SQL*loader issue. I have a .dat file which looks like
----
Tom
C Horizons
9732994000
Sam
TXS
7322221345
Veronica
T Systems
123 456 7890
----
---
and so on
-------------------------
Basically the format is of the type name, company, phone. There are no "record"
delimiter. I want to load this into a table with three (3) fields name,
company,phone. Can we do it using SQL Loader - may be some workaround.
Thanks for all the help.
Followup:
I don't think so -- we have "concatenate 3", but that'll just create records
like:

TomC Horizons9732994000
SamTXS7322221345
VeronicaT Systems123 456 7890

no separators.  we'd have to have something at the end -- any chance you are on
unix?  be trivial to fix with pipes.
Rating: 3
Yes I am on Unix February 21, 2004
Reviewer: Raju from USA
Tom,
The datafile is on Unix. Can I just add a delimiter (| or comma) at the end of
each line and then use the CONCAT command. Can you please guide me.
Thanks
Followup:
$ mknod pipe.dat p

$ cat t.dat | sed 's/$/,/'                this'll add a comma for us
Tom,
C Horizons,
9732994000,
Sam,
TXS,
7322221345,
Veronica,
T Systems,
123 456 7890,

$ cat t.dat | sed 's/$/,/' > pipe.dat &     do this to start the load
[1] 4978

$ sqlldr / t data=pipe.dat

SQL*Loader: Release 9.2.0.4.0 - Production on Sat Feb 21 11:19:23 2004

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Commit point reached - logical record count 3
[1]+  Done                    cat t.dat | sed 's/$/,/' >pipe.dat

and t.ctl would be:
$ cat t.ctl
LOAD DATA
concatenate 3
INTO TABLE T
replace
FIELDS TERMINATED BY ','
(a,b,c)
Rating: 4
Thanks Tom February 21, 2004
Reviewer: Raju from USA
Thanks for the help.It worked like a charm
Rating: 3
Developer March 02, 2004
Reviewer: Dave Suzuki from California, Irvine
Hello Tom,

I am new to use SQL Loader.
We are using 8.1.7 version of DB.
Toad and SQL Plus is available for us.

we are loading data from our legacy system to Oracle DB.
I have created few temp tables for this purpose.

Now I need to create control file. OR  Am I missing some thing?

Do I need to create data file or it is a part of control file.
could you please give me some example?

Please note: I need to put this control file in UNIX server from windows.

Dave
Followup:
you create a control file (sort of like a "program") to tell sqlldr how to read
your input datafiles.

so yes, you need to create a control file for sqlldr.

you then need to provide some datafile to be loaded.  this is the file that your
sqlldr control file describes.

have you checked out the server utilities guide?  it describes the tools like
sqlldr.
Rating: 5
Developer March 02, 2004
Reviewer: Dave from California
have you checked out the server utilities guide?

No I do not have sever utilities guide.
So data file contains the data to be load. isn't?

Here is my script for the table.
Please give me some hint to create control file.

CREATE TABLE NFS_POSITION_TMP
(
  TRX_LOAD_ID                  NUMBER           NOT NULL,
  PRG_EXEC_ID                  NUMBER           NOT NULL,
  ORIGINAL_FILE_NAME           VARCHAR2(50),
  REC_CODE                     VARCHAR2(6),
  HDR_REC_CLIENT_ID            VARCHAR2(8),
  FILLER1_HDR                  VARCHAR2(22),
  FILE_ORIGIN                  VARCHAR2(21),
  FILLER2_HDR                  VARCHAR2(9),
  FILE_NAME                    VARCHAR2(21),
  FILLER3_HDR                  VARCHAR2(9),
  TRANS_CREATION_DATE          VARCHAR2(11),
  FILLER4_HDR                  VARCHAR2(38),
  FIRM                         VARCHAR2(9),
  BRANCH                       VARCHAR2(8),
  ACCOUNT_NO                   VARCHAR2(11),
  ACCOUNT_TYPE                 VARCHAR2(6),
  FILLER1_POS1                 VARCHAR2(7),
  CUSIP                        VARCHAR2(14),
  FILLER2_POS1                 VARCHAR2(6),
  SECURITY_TYPE                VARCHAR2(6),
  SECURITY_TYPE_MOD            VARCHAR2(6),
  MKT_CODE                     VARCHAR2(6),
  FILLER3_POS                  VARCHAR2(6),
  DTC_ELIG_CODE                VARCHAR2(6),
  MARGIN_IND                   VARCHAR2(6),
  REG_REP                      VARCHAR2(8),
  MARGIN_LST_ACT_DATE          VARCHAR2(11),
  STOCK_REC_LST_ACT_DATE       VARCHAR2(11),
  SYMBOL                       VARCHAR2(14),
  MKT_PRICE                    VARCHAR2(23),
  POS_FLD_SIGN1_POS1           VARCHAR2(6),
  TRADE_DATE_QTY               VARCHAR2(20),
  POS_FLD_SIGN2_POS1           VARCHAR2(6),
  FILLER3_POS1                 VARCHAR2(14),
  STLMNT_DATE_QTY              VARCHAR2(20),
  POS_FLD_SIGN1_POS2           VARCHAR2(6),
  SGRTD_QTY                    VARCHAR2(20),
  POS_FLD_SIGN2_POS2           VARCHAR2(6),
  TRANSIT_QTY                  VARCHAR2(20),
  POS_FLD_SIGN3_POS2           VARCHAR2(6),
  TRANSFER_QTY                 VARCHAR2(20),
  POS_FLD_SIGN4_POS2           VARCHAR2(6),
  LGL_TRANSFER_QTY             VARCHAR2(20),
  POS_FLD_SIGN5_POS2           VARCHAR2(6),
  NON_NEGBLE_QTY               VARCHAR2(20),
  POS_FLD_SIGN6_POS2           VARCHAR2(6),
  FILLER4_POS2                 VARCHAR2(9),
  TRADE_DATE_SHRT_SALE_QTY     VARCHAR2(20),
  POS_FLD_SIGN1_POS3           VARCHAR2(6),
  STLMNT_DATE_SHRT_SALE_QTY    VARCHAR2(20),
  POS_FLD_SIGN2_POS3           VARCHAR2(6),
  MTD_POS_TRADE_DATE_BAL       VARCHAR2(16),
  POS_FLD_SIGN3_POS3           VARCHAR2(6),
  MTD_POS_STLMNT_DATE_BAL      VARCHAR2(16),
  POS_FLD_SIGN4_POS3           VARCHAR2(6),
  MTD_POS_TRADE_DATE_POS_COMM  VARCHAR2(16),
  POS_FLD_SIGN5_POS3           VARCHAR2(6),
  MTD_SETTLE_DATE_POS_COMM     VARCHAR2(16),
  POS_FLD_SIGN6_POS3           VARCHAR2(6),
  FILLER1_POS3                 VARCHAR2(10),
  FILLER2_POS3                 VARCHAR2(6),
  FILLER3_POS3                 VARCHAR2(6),
  NO_OF_SEC_DESC_LINES         VARCHAR2(6),
  SHORT_NAME                   VARCHAR2(15),
  FILLER4_POS3                 VARCHAR2(7),
  SEC_DESC_LINE1               VARCHAR2(25),
  SEC_DESC_LINE2               VARCHAR2(25),
  SEC_DESC_LINE3               VARCHAR2(25),
  SEC_DESC_LINE4               VARCHAR2(25),
  SEC_DESC_LINE5               VARCHAR2(25),
  SEC_DESC_LINE6               VARCHAR2(25),
  FILLER1_POS5                 VARCHAR2(20),
  FILLER2_POS5                 VARCHAR2(6),
  FILLER3_POS5                 VARCHAR2(20),
  FILLER4_POS5                 VARCHAR2(6),
  FILLER5_POS5                 VARCHAR2(20),
  FILLER6_POS5                 VARCHAR2(6),
  FILLER7_POS5                 VARCHAR2(7),
  FILLER8_POS5                 VARCHAR2(6),
  FILLER9_POS5                 VARCHAR2(17),
  FILLER10_POS5                VARCHAR2(6),
  FILLER11_POS5                VARCHAR2(17),
  FILLER12_POS5                VARCHAR2(9),
  FILLER1_POS6                 VARCHAR2(6),
  FILLER2_POS6                 VARCHAR2(17),
  MTD_POS_INC_TRADE_DATE       VARCHAR2(16),
  POS_FLD_SIGN1_POS6           VARCHAR2(6),
  MTD_POS_INC_STLE_DATE        VARCHAR2(16),
  POS_FLD_SIGN2_POS6           VARCHAR2(6),
  REG_REP2                     VARCHAR2(8),
  AGENCY_CODE                  VARCHAR2(13),
  PROD_CODE                    VARCHAR2(17),
  MATURITY_DATE                VARCHAR2(13),
  CASH_COLLCTD_BAL             VARCHAR2(18),
  POS_FLD_SIGN3_POS6           VARCHAR2(6),
  FILLER3_POS6                 VARCHAR2(23),
  RECTTL_CODE_TLR             VARCHAR2(6),
  FILLER1_TLR                  VARCHAR2(25),
  TTL_RECS                     VARCHAR2(20),
  FILLER2_TLR                  VARCHAR2(10),
  TTL_LOGICAL_RECS             VARCHAR2(20),
  FILLER3_TLR                  VARCHAR2(51),
  LOADED_FLG                   VARCHAR2(1)      DEFAULT 'N'
NOT NULL,
  LOAD_DATE                    DATE             DEFAULT TRUNC(SYSDATE),
  REC_NUM                      NUMBER           NOT NULL,
  CONTENT                      VARCHAR2(450),
  CREATE_USER_ID               VARCHAR2(11)     NOT NULL,
  CREATE_DATE                  DATE             NOT NULL,
  UPDATE_USER_ID               VARCHAR2(11)     NOT NULL,
  UPDATE_DATE                  DATE             NOT NULL
)

Dave
California, Irvine
Followup:
yes, yes you DO so have it.  Everyone on the planet has it.

otn.oracle.com -> documentation

please check out the server utilities guide, lots of examples.

A create table cannot tell you how to create a control file to load a DATA FILE.
 the datafile is what drives the control file, not the table.  the control file
tells sqlldr how to read the datafile, your input file.
Rating: 3
To Dave Suzuki March 02, 2004
Reviewer: denni50 from NA
here is a sample of a control file I use. I replaced my
columns with yours...you can do some tweaking as needed.
This should get you going.

load data
infile "F:\DATA\loaddata.csv"
into table yourtable append
fields terminated by "," optionally enclosed by '"'
trailing nullcols
(
  TRX_LOAD_ID,
  ORIGINAL_FILE_NAME,
  REC_CODE,
  HDR_REC_CLIENT_ID,
  FILLER1_HDR,
  FILE_ORIGIN,
  FILLER2_HDR,
  FILE_NAME,
  FILLER3_HDR,
  TRANS_CREATION_DATE,
  FILLER4_HDR,
  FIRM,
  BRANCH,
  ACCOUNT_NO,
  ACCOUNT_TYPE,
  FILLER1_POS1,
  CUSIP,
  FILLER2_POS1,
  SECURITY_TYPE,
  SECURITY_TYPE_MOD,
  MKT_CODE,
  FILLER3_POS,
  DTC_ELIG_CODE,
  MARGIN_IND,
  REG_REP,
  MARGIN_LST_ACT_DATE,
  STOCK_REC_LST_ACT_DATE,
  SYMBOL,
  MKT_PRICE,
  POS_FLD_SIGN1_POS1,
  TRADE_DATE_QTY,
  POS_FLD_SIGN2_POS1,
  FILLER3_POS1,
  STLMNT_DATE_QTY,
  POS_FLD_SIGN1_POS2,
  SGRTD_QTY,
  POS_FLD_SIGN2_POS2,
  TRANSIT_QTY,
  POS_FLD_SIGN3_POS2,
  TRANSFER_QTY,
  POS_FLD_SIGN4_POS2,
  LGL_TRANSFER_QTY,
  POS_FLD_SIGN5_POS2,
  NON_NEGBLE_QTY,
  POS_FLD_SIGN6_POS2,
  FILLER4_POS2,
  TRADE_DATE_SHRT_SALE_QTY,
  POS_FLD_SIGN1_POS3,
  STLMNT_DATE_SHRT_SALE_QTY,
  POS_FLD_SIGN2_POS3,
  MTD_POS_TRADE_DATE_BAL,
  POS_FLD_SIGN3_POS3,
  MTD_POS_STLMNT_DATE_BAL,
  POS_FLD_SIGN4_POS3,
  MTD_POS_TRADE_DATE_POS_COMM,
  POS_FLD_SIGN5_POS3,
  MTD_SETTLE_DATE_POS_COMM,
  POS_FLD_SIGN6_POS3,
  FILLER1_POS3,
  FILLER2_POS3,
  FILLER3_POS3,
  NO_OF_SEC_DESC_LINES,
  SHORT_NAME,
  FILLER4_POS3,
  SEC_DESC_LINE1,
  SEC_DESC_LINE2,
  SEC_DESC_LINE3,
  SEC_DESC_LINE4,
  SEC_DESC_LINE5,
  SEC_DESC_LINE6,
  FILLER1_POS5,
  FILLER2_POS5,
  FILLER3_POS5,
  FILLER4_POS5,
  FILLER5_POS5,
  FILLER6_POS5,
  FILLER7_POS5,
  FILLER8_POS5,
  FILLER9_POS5,
  FILLER10_POS5,
  FILLER11_POS5,
  FILLER12_POS5,
  FILLER1_POS6,
  FILLER2_POS6,
  MTD_POS_INC_TRADE_DATE,
  POS_FLD_SIGN1_POS6,
  MTD_POS_INC_STLE_DATE,
  POS_FLD_SIGN2_POS6,
  AGENCY_CODE,
  PROD_CODE,
  MATURITY_DATE,
  CASH_COLLCTD_BAL,
  POS_FLD_SIGN3_POS6,
  FILLER3_POS6,
  RECTTL_CODE_TLR,
  FILLER1_TLR,
  TTL_RECS,
  FILLER2_TLR,
  TTL_LOGICAL_RECS,
  FILLER3_TLR,
  LOADED_FLG,
  LOAD_DATE,
  REC_NUM,
  CONTENT,
  CREATE_USER_ID,
  CREATE_DATE,
  UPDATE_USER_ID,
  UPDATE_DATE)

give your control file a name with .ctl then go to DOS:

c:> sqlldr.exe user@database control=f:\data\controlname.ctl
log=f:\data\controlname.log
(will be prompted for password).

and that's it!
Rating: 3
oops! March 02, 2004
Reviewer: denni50 from NA
make sure you change the DOS directory to f:
then enter sqlldr commands.

f:> sqlldr.exe...etc
Rating: 5
Sql Loader and Bind Variables March 04, 2004
Reviewer: Mir from Houston, TX
Hi Tom,

I have the following Sql Loader Control file. I am looking for a way to load
data from various directories into the staging table.

How can i pass values to siteid and sitedir columns dynamically.

for example:
when i am loading data from say c:\www01\1.log i want siteid to be "1" and
sitedir to be "www01"
and so on if c:\www02\1.log i want siteid to be "2" and sitedir to be "www02"

load data
into table stg_log append
fields terminated by ' ' optionally enclosed by '"' TRAILING NULLCOLS
(
   logid                          SEQUENCE(MAX, 1),
   c_date                         position(1:19) date 'RRRR-MM-DD HH24:MI:SS',
   c_ip                           char,
   cs_username                    char,
   s_ip                           char,
   s_port                         char,
   cs_method                      char,
   cs_uri_stem                    char(4000),
   cs_uri_query                   char(4000),
   sc_status                      char,
   c_user_agent                   char
   siteid                         number(*,0),
   sitedir                        varchar2(10))
Followup:
You need to dynamically create the control file one the fly then.  In unix, this
is really easy.  In windows (without a proper scripting language) it is rather
cumbersome.  But the script would be something akin to:

echo load data > t.ctl
echo into table stg_log append >> t.ctl
.....
echo siteid constant %1,  >> t.ctl
echo sitedir constant "%2" >> t.ctl
echo ) >> t.ctl

sqlldr u/p t.ctl data=c:\%2\1.log ....

and you would run the script as

script 1 www01

for example.
Rating: 5
Oracle Consultant March 06, 2004
Reviewer: Dawar from California, USA
Tom,

Is it possible to load multiple files through one control file?

Regards,
Dawar
Followup:
sqlldr will load a file at a time, but a single control file can be used to load
many files

sqlldr t.ctl data=file1.dat
sqlldr t.ctl data=file2.dat
Rating: 5
Oracle Consultnat March 07, 2004
Reviewer: Dawar from California, USA
Tom,

Thanks for your earlier feed back about multiple files in one control file.
So it is possible to use multiple 'infile' parameters as:

load data
infile 'c:\data1.txt'
infile 'c:\data2.txt'
into table MY_TABLE
terminated by ','
(x,
y
)

Dawar
Followup:
yes, that'll work as well (pretty easy to "test" no?)
Rating: 5
sqldlr objects March 22, 2004
Reviewer: Mariano from Córdoba, Argentina
Tom, hi.
I'm trying to load a flat file into the base with an object column (its
attribute is like hh24:mi:ss but it has some
inner aritmetical operations in order to achieve the results it was build for)
but i'm having diferent errors with it.
let's say:
*********************************************
create or replace type hour as object
(
    hhmmss varchar2(16)
);
*********************************************

i have this sample file (in this order: name, number, number, blank space, hour,
blank space, hour, date, date):
*********************************************
Guest 10;8042;30;;15:52;;15:57;21/03/2004;21/03/2004
Guest 10;8042;30;;15:58;;22:02;21/03/2004;21/03/2004
Guest 11;8043;8;;16:02;;16:08;21/03/2004;21/03/2004
Guest 11;8043;8;;16:11;;22:01;21/03/2004;21/03/2004
*********************************************

I build this control file:
********************************************
LOAD DATA
INFILE 'c:\lucent\data\loginlogoutdiario.txt'
BADFILE 'c:\lucent\data\loginlogoutdiario.bad'
DISCARDFILE 'c:\lucent\data\loginlogoutdiario.dsc'
APPEND
INTO TABLE clucent_login_logout
WHEN ((1) != 'Customer')
FIELDS TERMINATED BY ';'
TRAILING NULLCOLS
(LLL_ID SEQUENCE(MAX,1),
 LLL_NOMBRE,
 LLL_LOGINID,
 LLL_SKILL,
 filler_1 filler,
 LLL_SINCE EXPRESSION "hour(:lll_since)",
 filler_2 filler,
 LLL_TILL EXPRESSION "hour(:lll_till)",
 LLL_INITIALDATE date 'dd/mm/yyyy',
 LLL_FINALDATE date 'dd/mm/yyyy')
********************************************************
On running, i'm getting (e.g.):
Registro 2: Rejected - Error on table CLUCENT_LOGIN_LOGOUT.
ORA-01008: not all variables bound
********************************************************
I know it's just not a sqlldr problem but a wrong approach im using with
objects, however I have solutions in pl/sql but not here.
How can I load the object without problems with sqlldr?

Thanks.-
Followup:
TRAILING NULLCOLS
(LLL_ID SEQUENCE(MAX,1),
 LLL_NOMBRE,
 LLL_LOGINID,
 LLL_SKILL,
 filler_1 filler,
 LLL_SINCE column object ( hhmmss ),
 filler_2 filler,
 LLL_TILL column object ( hhmmss ),
 LLL_INITIALDATE date 'dd/mm/yyyy',
 LLL_FINALDATE date 'dd/mm/yyyy')
Rating: 5
Thanks but... March 23, 2004
Reviewer: Mariano from Córdoba, Argentina
That was just as cool as simple. However, if I want to verified the input string
before loading it (eg, let's say is lacking a digit) I can use my own construct
function. I was reading Database Utilities
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96652/ch07.htm#1656
and trying to apply the example in my case but it's raising
ORA-00904: invalid column name.

Here's my control file:
*********************************************************
LOAD DATA
INFILE 'c:\lucent\data\ctiauxdaily.txt'
BADFILE 'c:\lucent\data\ctiauxdaily.bad'
DISCARDFILE 'c:\lucent\data\ctiauxdaily.dsc'
APPEND
INTO TABLE clucent_aux_daily
WHEN (1)!= 'Totals'
FIELDS TERMINATED BY ';'
TRAILING NULLCOLS
(LAD_ID SEQUENCE(MAX,1),
 LAD_NAME,
 LAD_LOGID,
 LAD_STAFFED_TIME EXPRESSION "accumulate(:LAD_STAFFED_TIME)"
 LAD_DATE date 'dd/mm/yyyy')
**********************************************************
accumulate is a function that's part of my object hour.
Can you suggest a solution to this?
Thanks
Followup:
if the data is "bad", it will be automagically filtered to the bad file.  you
can post process the bad file.
Rating: 5
Post process March 23, 2004
Reviewer: Mariano from Córdoba, Argentina
It's a good suggestion and it's my plan B, however I have methods in my
constructor to handle the error, lets say i need an input like 1283:45:06
(hours:minutes:seconds) and the flat file has 1283:45:6 (lack of a digit: i
cannot control the system thats giving me this info)  but then in the special
constructor has routines that decide if it's 06 seconds or 60 seconds (then
transform it in 1 minute). Doing it with the bad file means i have to do 2
procceses when oracle documentation suggests i can do it in once (of course, i'm
horribly failing with it).
Plus, the attribute is used for different inputs masks (hh24:mi:ss as it
simplest mask but it can be about accumulative hours like the example i mention
above, too) so maybe the default constructor fails to realize there's a digit
lacking and can insert the value in the table.
Ok, I can control the table after the insert and repair the data corrupted but
it's 2 step process and I'd like to exhaust any chance to it in just one (it's a
standalone process running in background using dbms_pipe).

Any tip you can give me, will be greatly appreciate.
As always, thanks you a lot.
Followup:
then you don't need/want to call a function, just put the logic into your
constructor itself:

CREATE OR REPLACE TYPE hour
AS OBJECT
(
    hhmmss  varchar2(10),
    constructor FUNCTION hour(hhmmss in varchar2) RETURN self AS result
);
/

CREATE OR REPLACE TYPE body hour
AS

constructor FUNCTION hour(hhmmss in varchar2) RETURN self AS result
is
begin
    self.hhmmss := 'blah';
    return ;
end;

end;
/
Rating: 5
Constant Parameter March 24, 2004
Reviewer: Mariano from Córdoba, Argentina
Tom,

1) In general, can we assume that anything we suppose it can be done with a
particular feature that's not mention in the documentation, really does not
exist? E.g., I couldn't find an answer in the documentation to the example i'm
posting in my 2nd question in spite of several pieces of special sqlldr
functions or parameters seems that, put altogether, can work out my problem.

2)Let's say i've got a flat file like this:
*************************************************
21/03/2004
GUEST1;8000;9000;1000;1500
GUEST2;8000;9000;1000;1500
GUEST3;8000;9000;1000;1500
GUEST4;8000;9000;1000;1500
*************************************************
I want to load into a table like this:
create table download_tab (
cname varchar2(100),
cvalue1 number,
cvalue2 number,
cvalue3 number,
cvalue4 number,
cdate date);
My control file is...
******************************
LOAD DATA
INFILE 'c:\test.txt'
BADFILE 'c:\test.bad'
DISCARDFILE 'c:\test.dsc'
APPEND
INTO TABLE download_tab
-- this condition in order to not load the first line.-
WHEN (3:3) != '/'
FIELDS TERMINATED BY ';'
TRAILING NULLCOLS
(cname,
cvalue1,
cvalue2,
cvalue3,
cvalue4)
*****************************************

As you may see, i have a date column that's not in the flat file nor in the
control. How can I load in cdate column not a constant date or sysdate but the
value in the first row in my flat file example?
Using RECNUM with CONSTANT?

Thanks.-
Regards.-
Followup:
I'm not touching this one:

...can we assume that anything we suppose it can be done with a
particular feature that's not mention in the documentation...

don't know what you tried to say...

but, sqlldr is a very simple tool -- very simple.  You are not going to
"program" it to the extent you seem to want to.  No, I don't know of anyway
short of loading the first row into a table (using LOAD=1) and then running
sqlldr again with SKIP=1 to load the rest and let them reference this already
loaded value
Rating: 5
REF in sqlldr March 24, 2004
Reviewer: A reader
I am having dificulties inserting this object type:

Create or Replace Type Répertoire AS OBJECT (
ID NUMBER, -- PK
Propriétaire Ref Disque,
Nom Varchar2(255),
Pére Ref Répertoire,
Member ...)

Create or Replace Type Disque AS OBJECT (
Domain  Varchar2(15), -- NT Domain Name
Machine   Varchar2(15), -- NT Machine Name
Nom Character,  -- PK(Domain,Machine,Nom)
Member ...)

Create Table Disques of Disque(
CONSTRAINT PK_DISQUES primary key (Domain,Machine,Nom),
CONSTRAINT CKC_Nom_DISQUES CHECK (UPPER(Nom)>='A' and UPPER(Nom)
<='Z')
);

Prompt Table Répertoires
Create Table Répertoires of Répertoire(
Pére With ROWID SCOPE IS Répertoires,
CONSTRAINT PK_Répertoires primary key(ID)
);

Alter Table Répertoires
ADD CONSTRAINT FK_Répertoires_PERE FOREIGN KEY (Pére)
  REFERENCES Répertoires ON DELETE CASCADE;

Control file:

LOAD DATA
INFILE 'Repertoires.txt'
INTO TABLE Répertoires
FIELDS TERMINATED BY ';'
TRAILING NULLCOLS
( ID          "IDRépertoires.Nextval",
DDomain      FILLER    char(15),
DMachine      FILLER    char(15),
DNom         FILLER    Char,
Propriétaire    REF(Constant 'DISQUES',DDomain,DMachine,DNom),
Nom          Char(255)
)

WIth this one line there's no problem:

DOM_TEST;AMD2800;M;ROOT

But the log reports that "Error SQL*Loader-350: the directive REF for column
PROPRIETAIRE expects 2 arguments, but 4 have been found":

SQL*Loader-350: Erreur de syntaxe Ó la ligne 12.
La directive REF pour la colonne PROPRI+TAIRE attend 2 arguments ; 4
trouvÚs.
SQL*Loader-2026: Chargement arrÛtÚ : poursuite impossible.

I have barely found any referenece to REFs in sqlldr docs.

Can you help me ?
Followup:
sorry, but I'm not really sure what you are trying to accomplish there.
March 24, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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://download-west.oracle.com/docs/cd/A87860_01/doc/appdev.817/a76976/adobjvew.htm#434487

http://download-west.oracle.com/docs/cd/A87860_01/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
Rating: 4
Excellent, but..... February 09, 2004
Reviewer: Bruno Paquet from CANADA
Hi Mr Kyte,

I'm using this trick on a customer, on 8.1.7.0.0. and the table just get about 3
rows, but we modify the string very often.  We are experiencing some performance
problem, now.  When i'm using a tool to see what is happening, the SELECT
showing is allways the one on the view (because i created a view, like your
exemple).  Could you have an idea on what i going on : There are 4 active users
at a time, and we reference the view quite often.
Rating: 4
dummy rowid ? February 09, 2004
Reviewer: robert from ct
Tom,

SQL> desc tblctsearchresult
Name            Type           Nullable
--------------- -------------- --------
USERSEQ         NUMBER
CLIENTLISTROWID ROWID          Y
DATESTAMP       DATE
USERID          VARCHAR2(30)   Y
USERDATA1       VARCHAR2(2000) Y

This is used to store ROWID of web page search results...
I have to add column USERDATA1 and make CLIENTLISTROWID
NULLABLE because I need to store some extra info related to this search.

So is there a "valid" dummy ROWID I can use to avoid -ORA-01410 Invalid ROWID-
and put into this column for these extra info rows ?
Or if I leave the NULLS in that column, am I gonna have a problem when doing a
lookup on this column, like:

SELECT * FROM v_clientlist WHERE ROWID IN SELECT clientlistrowid  FROM
tblctsearchresult

'cause I read here about possible problem with either IN or NOT IN where there
are NULLS...

Thanks
Followup:
Just use NULL, it'll be OK.

it would be a NOT IN that would cause problems and then you just use

where not in ( select ... where something IS NOT NULL )

to fix that right up.
Rating: 5
varchar inlist February 27, 2004
Reviewer: Sha from USA
Hi Tom,
I read your solution for INlist and the example you gave

variable x varchar2(255)
define x="1,2,3,4,5"
exec :x := '&X'
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 )

This works great for Number List.

I need to implement for CHAR list e.g.
output should be
old   1: select * from t where object_id in ( &X )
new   1: select * from t where object_id in ( '1','2','3','4','5' )

Thanks in advance,
Regards,
Sha
Followup:
i would not suggest using literals.  use str2table as demonstrated (it is a
table of number or a table of varchar2's whatever you want it to be a table of)
Rating: 5
Indexes break February 27, 2004
Reviewer: Sha from USA
Hi Tom,
I was doing exactly that but my Indexes do not work if I use select cast..
I was surprise to see how indexes work in your example.
I converted all the dynamic IN queries to use select cast and now I realize that
in all SQL's our indexes are broken.
a) I tried where rownum > 0 it does not help
b) I tried /*+ first_rows */ hint , it does not work.

1) is the only option to use cursor sharing = force with the dynamic queries,
but again you say thats bad way of writing SQL.
2) Why does your SQL use index with select cast... In my case I may have more
that one select cast condition in where clause. Is it that by chance your SQl
uses indexes or I am doing something wrong.
3) Is the number of joins causing this.
Can you show an example of with 4 tables , multiple joins and use 2 select cast
in where clause. I want to see how your query will use Indexes.

I am new to oracle, I read your web-site alot. Thanks for helping me.

Thanks in advance
Sha
Followup:
the indexes are not "broken".  there are two things to look at
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3779680732446#15740265481549
and the use of a gtt to stash the values instead of a collection.
Rating: 4
moving sql into database March 05, 2004
Reviewer: dwl from uk
Tom

(Bear with me this does have relevance to refcursors!)

I am attempting to move the sql from the java pages into the database, as i
believe this is the best place for it.  A couple of questions i have though are

1)  For ad hoc sql i can create stored procedures using ref cursors and
sys_contexts in order to create dynamic WHERE clauses as determined by what the
client passes in, using your methods of a variable IN list etc. However do i
need to create a separate stored procedure for every simple sql statement that
selects from a different table?? Or can i also BIND the tablename at runtime and
still only hard parse once?

eg

PROCEDURE P1 (    P_1         IN    VARCHAR2 DEFAULT NULL,
        P_Cur         IN OUT     C_Return_Cursor    )

IS

l_query  varchar2(32000)
default 'SELECT    * from t1 where 1=1';

BEGIN

IF ( P_1 IS NOT NULL ) THEN

    DBMS_SESSION.SET_CONTEXT( 'MY_CTX', 'P1_STRING', P_1);
    l_query := l_query ||'     AND    c1 IN (    SELECT *
                FROM THE ( SELECT CAST( str2tbl( sys_context( ''MY_CTX'',
''P1_STRING'' ) ) as tbl_Numbers ) from dual ) )  ';

END IF;

open P_Cur for l_query;

END P1 ;

PROCEDURE P2 (    P_1     IN    VARCHAR2 DEFAULT NULL,
        P_Cur     IN OUT     C_Return_Cursor    )

IS

l_query  varchar2(32000)
default 'SELECT    * from t2 where 1=1';

BEGIN

IF ( P_1 IS NOT NULL ) THEN

    DBMS_SESSION.SET_CONTEXT( 'MY_CTX', 'P2_STRING', P_1);
    l_query := l_query ||'     AND    c1 IN (    SELECT *
                FROM THE ( SELECT CAST( str2tbl( sys_context( ''MY_CTX'',
''P2_STRING'' ) ) as tbl_Numbers ) from dual ) )  ';

END IF;

open P_Cur for l_query;

END P2 ;

Could i replace these 2 with :

PROCEDURE P3 (    P_1     IN    VARCHAR2 DEFAULT NULL,
        P_Table_name IN VARCHAR2,
        P_Cur     IN OUT     C_Return_Cursor    )

IS

l_query  varchar2(32000)
default 'SELECT    * from '||P_Table_name||' where 1=1';

BEGIN

IF ( P_1 IS NOT NULL ) THEN

    DBMS_SESSION.SET_CONTEXT( 'MY_CTX', 'P3_STRING', P_1);
    l_query := l_query ||'     AND    c1 IN (    SELECT *
                FROM THE ( SELECT CAST( str2tbl( sys_context( ''MY_CTX'',
''P3_STRING'' ) ) as tbl_Numbers ) from dual ) )  ';

END IF;

open P_Cur for l_query;

END P3 ;

So would P3 still use bind variable?? Is this the best method to use for a
generic simple procedure for sql from different tables?

2) In the above procedures is there any performance issue (or any other
issue)with using 'SELECT * FROM..' as opposed to selecting all field names from
the table?

3) How should i best deal with INSERTS and UPDATES, storing the sql in the
database?
Maybe try and create a generic procedure using eg varrays and objects to pass in
the parameters to insert or update?

Or just create individual procedures for each insert or update sql i require to
run that exists in the application??

Thanks for your help.
Followup:
1) you'll hard parse ONCE for each table you put in there (have to, they have
different plans!!)

You only need a single procedure.
You would concatenate the tablename in.
It'll hard parse as little as possible -- but at least once per tablename used!

2) yes, definitely.  first there is just the overhead of moving EVERY column
from server to client.  second there is the change in access plans that can and
will occurr.  consider:

big_table@ORA9IR2> select owner, object_type, object_id from big_table;

1000000 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=403 Card=999620
Bytes=19992400)
   1    0   INDEX (FAST FULL SCAN) OF 'BIG_TABLE_IDX1' (NON-UNIQUE) (Cost=403
Card=999620 Bytes=19992400)

big_table@ORA9IR2> select * from big_table;

1000000 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1379 Card=999620
Bytes=99962000)
   1    0   TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1379 Card=999620
Bytes=99962000)

You can skip hitting the table many times (just using indexes). but if you
select * -- tables will be in there.

(this is why you cannot just take a query "X" and

select count(*) from ( "X" )

to 'tune' it.  the optimizer sees "no columns needed, lets skip base table
accesses and such" )

3) don't think of it as inserts and updates.  think of it as transactions.

You would create a procedure to do "transaction x"

"transaction x" has a set of inputs.

You would use collections (maybe -- plsql table types are perhaps the easiest to
deal with) and scalars (for sure), but I would not use object types.  unless you
went hole hog with JPUB and let that generate the code.
Rating: 5
Eliminate dynamic sql for in list March 05, 2004
Reviewer: Jennifer Chen from Jennifer Chen, MD, USA
Hi Tom,

I have a stored procedure accepts a list of mpi_number and returns values to the
c++ code. The table contains 1776518 rows and took about 3 seconds to finish. Do
you have a better way to accomplish this? I tried your inlist function, but
seems take longer...

PROCEDURE get_cchperson_by_mpilist (
      p_mpi_list    IN       VARCHAR2,
      p_refcursor   OUT      sys_refcursor
   )
   AS
   BEGIN
      IF p_mpi_list IS NULL
      THEN
         raise_application_error (-20000, 'MPINumber List is Required');
      END IF;

      OPEN p_refcursor
       FOR    'SELECT pid_number,
                      mpi_number,
                      afis_county_code,
                      afis_flag,
                      afis_rfi,
                      consol_sid,
                      dna_flag,
                      domestic_assault_counter,
                      domestic_assault_flag,
                      domestic_violence_flag,
                      TO_CHAR(enter_date_time, ''MM/DD/YYYY HH24:MI''),

                      enter_user_id,
                      update_user_id
                 FROM alias.cch_person
                WHERE mpi_number in ('
           || p_mpi_list
           || ') ORDER BY mpi_number';
   END get_cchperson_by_mpilist;

   BEGIN GET_CCHPERSON_BY_MPILIST( '109080,329729,402969,
         465198,543705,559773,738160,758460,925904',:P_REFCURSOR ); END;

   call     count       cpu    elapsed       disk      query    current
rows
   ------- ------  -------- ---------- ---------- ---------- ----------
----------
   Parse        1      0.00       0.00          0          0          0
 0
   Execute      1      2.23       2.27          0          0          0
 1
   Fetch        0      0.00       0.00          0          0          0
 0
   ------- ------  -------- ---------- ---------- ---------- ----------
----------
   total        2      2.23       2.27          0          0          0
 1

   Misses in library cache during parse: 1
   Optimizer goal: CHOOSE
   Parsing user id: 66

select * from cch_person where pid_number in
  ( select *
    from TABLE ( select cast(
in_list('109080,329729,402969,465198,543705,559773,738160,758460,925904')
                      as a_mpi_number ) from dual ) );

9 rows selected.

Elapsed: 00:00:43.02

I don't have an index for your code. Oracle seems use index range scan on
mpi_number for my sp.

Thanks in advance for your time and help.
Followup:
read
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3779680732446#15740265481549
Rating: 5
Thank You March 05, 2004
Reviewer: A reader
Hi Tom,

Your solution has been proved faster. The note is incrediable helpful.

Thank you, thank you, thank you!!!

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: 61
********************************************************************************

select * from cch_person where pid_number in
  ( select /*+ cardinality(t 10 ) */ *
    from TABLE ( select cast(
in_list('109080,329729,402969,465198,543705,559773,738160,758460,925904')
                      as a_mpi_number ) from dual ) t )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          7          0           0
Execute      1      0.00       0.00          0          6          0           0
Fetch        2      0.00       0.00          0         29          0           9
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.01          0         42          0           9

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 61

Rows     Row Source Operation
-------  ---------------------------------------------------
      9  NESTED LOOPS  (cr=29 r=0 w=0 time=820 us)
      9   VIEW  (cr=0 r=0 w=0 time=224 us)
      9    SORT UNIQUE (cr=0 r=0 w=0 time=193 us)
      9     COLLECTION ITERATOR PICKLER FETCH (cr=0 r=0 w=0 time=25 us)
      9   TABLE ACCESS BY INDEX ROWID CCH_PERSON (cr=29 r=0 w=0 time=464 us)
      9    INDEX UNIQUE SCAN PK_CCH_PERSON (cr=20 r=0 w=0 time=242 us)(object id
34060)

********************************************************************************

BEGIN CRIMINAL_HISTORY_PKG.GET_CCHPERSON_BY_MPILIST( '109080,329729,402969,
  465198,543705,559773,738160,758460,925904',:c ); END;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          2           0
Execute      1      0.35       0.37          0          0          2           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.35       0.37          0          0          4           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 61
********************************************************************************

SELECT pid_number,
                      mpi_number,
                      afis_county_code,
                      afis_flag,
                      afis_rfi,
                      consol_sid,
                      dna_flag,
                      domestic_assault_counter,
                      domestic_assault_flag,
                      domestic_violence_flag,
                      TO_CHAR(enter_date_time, 'MM/DD/YYYY HH24:MI'),
                      enter_user_id,
                      TO_CHAR(expunge_date_time, 'MM/DD/YYYY HH24:MI'),
                      expunge_reason,
                      expunge_user_id,
                      fbi,
                      hyta_counter,
                      hyta_flag,
                      iffs,
                      iffs_disqual_counter,
                      iffs_poss_disqual_counter,
                      iii_status,
                      TO_CHAR(iii_update_date, 'MM/DD/YYYY'),
                      identifying_comments,
                      image_flag,
                      n7411_counter,
                      n7411_flag,
                      off_safety_flag,
                      pob,
                      prn,
                      palm_flag,
                      parent_kidnapping_counter,
                      parent_kidnapping_flag,
                      TO_CHAR(rights_restore_date, 'MM/DD/YYYY'),
                      rights_restore_flag,
                      rights_restore_ori,
                      sid,
                      suppressed_flag,
                      set_aside_flag,
                      status_code,
                      TO_CHAR(update_date_time, 'MM/DD/YYYY HH24:MI'),
                      update_user_id
                 FROM alias.cch_person
                WHERE mpi_number in
(109080,329729,402969,465198,543705,559773,738160,758460,925904) ORDER BY
mpi_number

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          2           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0         36          0           9
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0         36          2           9

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 61     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      9  INLIST ITERATOR  (cr=36 r=0 w=0 time=619 us)
      9   TABLE ACCESS BY INDEX ROWID OBJ#(33948) (cr=36 r=0 w=0 time=508 us)
      9    INDEX RANGE SCAN OBJ#(35197) (cr=27 r=0 w=0 time=325 us)(object id
35197)

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.01          0          7          2           0
Execute      3      0.35       0.37          0          6          2           1
Fetch        2      0.00       0.00          0         29          0           9
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        7      0.35       0.39          0         42          4          10

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        1      0.00       0.00          0          0          2           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0         36          0           9
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0         36          2           9

Misses in library cache during parse: 1

    4  user  SQL statements in session.
    0  internal SQL statements in session.
    4  SQL statements in session.
********************************************************************************
Trace file: aliasdev_ora_3044.trc
Trace file compatibility: 9.00.01
Sort options: default

       1  session in tracefile.
       4  user  SQL statements in trace file.
       0  internal SQL statements in trace file.
       4  SQL statements in trace file.
       4  unique SQL statements in trace file.
     105  lines in trace file.
Rating: 5
generic procedure March 08, 2004
Reviewer: dwl from uk
Tom

thanks for the reply.
Just going back to the generic procedure approach for adhoc sql.  Exactly how
generic would you make the procedure??

I mean do you recommend a generic one for just simple adhoc sql like

select .. .. .. from table1;

and then have individual procedures for anything more complicated?
What about another generic one for simple joins?

What about order by, maybe you could have an in ARRAY for
the order by elements?

What about DISTINCT, would you use an IN parameter concatenated in?

Do all these conditions require separate procedures or should you just build ONE
procedure for your application which can run all the sql you require, no matter
how complicated?? ie lots of IF statements?

I can think of several ways of doing all of this type of stuff but I am really
looking for your recommendation or best practice.

Many thanks
Followup:
hard to say in general, you'll have to use your design sense to figure out "one"
or "many"

array's can apply -- easy to pass a flexible number of arguments....

distinct would have to be concatenated in if optional, yes.

I find a procedure "per problem" is what I send up with -- you need to define
"problem" here :)
Rating: 5
Eliminate sort in this query March 08, 2004
Reviewer: Jennifer Chen from Jennifer Chen, MD, USA
Hi Tom,

Is there a way to eliminate the two sorts in this query. I tried to add hit /*+
INDEX_FFS (name_rac pk_name_rac) */, but the sorts are still there (mpi_number
is the PK of name_rac). Can you please help?

Thank you.

SQL> SELECT mpi_number, rac, primary_value_flag
  2    FROM alias.name_rac
  3   WHERE mpi_number IN (
  4         SELECT /*+ CARDINALITY(t 10 ) */
  5                *
  6           FROM TABLE
  7                (CAST
  8                    (MASTERINDEX_PKG.GET_MPILIST (:in_list) AS a_mpi_number
  9                    )
 10                ) t
 11                WHERE ROWNUM >= 0)
 12   ORDER BY mpi_number;

no rows selected

Elapsed: 00:00:00.03

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=34 Card=10 Bytes=220
          )

   1    0   SORT (ORDER BY) (Cost=34 Card=10 Bytes=220)
   2    1     NESTED LOOPS (Cost=33 Card=10 Bytes=220)
   3    2       VIEW OF 'VW_NSO_1' (Cost=11 Card=10 Bytes=130)
   4    3         SORT (UNIQUE)
   5    4           COUNT
   6    5             FILTER
   7    6               COLLECTION ITERATOR (PICKLER FETCH) OF 'GET_MP
          ILIST'

   8    2       TABLE ACCESS (BY INDEX ROWID) OF 'NAME_RAC' (Cost=2 Ca
          rd=1 Bytes=9)

   9    8         INDEX (UNIQUE SCAN) OF 'PK_NAME_RAC' (UNIQUE) (Cost=
          1 Card=1)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          6  consistent gets
          0  physical reads
          0  redo size
        343  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
Followup:
while you and i can see the secondary sort isn't technically necessary, the
optimizer is not seeing that and I could not make it see that.
Rating: 5
ORA-06502 error March 18, 2004
Reviewer: Arun Mathur from Marietta,GA
Tom,

I'm getting a

ORA-06502: PL/SQL: numeric or value error: character string buffer too small

error when the argument I pass into in_list exceeds a certain length.  Do you
have any thoughts?

Thanks again.
Arun
Followup:
thoughts -- you need to debug it a little to see which buffer it is having an
issue with?

the code is about as simple and small as you get -- please, take a minute to
take a look see.
Rating: 5
Thanks March 18, 2004
Reviewer: Arun Mathur from Marietta,GA
Once again, it would have helped if I actually took a second to read the error
message. My apologies, Tom. On a plus note, look forward to seeing you at the
Atlanta OUG next week.

Arun
Rating: 5
this approach in a view ? March 24, 2004
Reviewer: A reader from Europe
I'm having a problem with this approach in a view when I try to make the view
available to another schema:
Test scenario:

SQL> create or replace function fInlistIterator( p_string in varchar2 ) return
str_array
  2      as
  3          l_string        long default p_string || ',';
  4          l_data          str_array := str_array();
  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) :=
 12                   ltrim( rtrim( substr( l_string, 1, n-1 ) ) );
 13           l_string := substr( l_string, n+1 );
 14      end loop;
 15
 16      return l_data;
 17    end;
 18  /

Function created.

SQL>
SQL>
SQL> create or replace view EuropeanCountries as
  2  SELECT * FROM country
  3  WHERE countryid IN
  4  (SELECT * FROM
  5    THE(SELECT CAST(fInlistIterator(rulevalue) as str_array)
  6   FROM rule WHERE ruleid = 63 ));

View created.

SQL> create or replace public synonym EuropeanCountries for EuropeanCountries;

Synonym created.

SQL> create or replace public synonym fInlistIterator for fInlistIterator;

Synonym created.

SQL>
SQL> GRANT ALL ON fInlistIterator TO OTHER_USER;

Grant succeeded.

SQL> GRANT ALL ON EuropeanCountries TO OTHER_USER;

Grant succeeded.

SQL>
SQL> CONNECT OTHER_USER/...
Connected.
SQL>
SQL>
SQL> SELECT * from EuropeanCountries;
SELECT * from EuropeanCountries
              *
ERROR at line 1:
ORA-01031: insufficient privileges
Followup:
no version?

in 9ir2, all other user needs is "grant select on view"

in 8i, it appears they need execute on the TYPE str_array and the view.
Rating: 5
view issue March 24, 2004
Reviewer: A reader
Sorry, it's 9iR2 and still not working !

SQL> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.2.1 - Production
PL/SQL Release 9.2.0.2.1 - Production
CORE    9.2.0.2.0       Production
TNS for 32-bit Windows: Version 9.2.0.2.0 - Production
NLSRTL Version 9.2.0.2.0 - Production
Followup:
give me the entire example, like this, from start to finish that demonstrates
this issue:

ops$tkyte@ORA9IR2> create or replace type tabType as table of number
  2  /

Type created.

ops$tkyte@ORA9IR2> create or replace function f( p_string in varchar2 ) return
tabType
  2  as
  3          l_data tabType := tabType( 1,2,3 );
  4  begin
  5          return l_data;
  6  end;
  7  /

Function created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create table t ( x int );

Table created.

ops$tkyte@ORA9IR2> insert into t values ( 1 );

1 row created.

ops$tkyte@ORA9IR2> commit;

Commit complete.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace view V
  2  as
  3  select *
  4    from t
  5   where x in ( select * from TABLE( f('a,b,c') ) )
  6  /

View created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create user a identified by a;

User created.

ops$tkyte@ORA9IR2> grant create session to A;

Grant succeeded.

ops$tkyte@ORA9IR2> grant select on v to a;

Grant succeeded.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> @connect a/a
ops$tkyte@ORA9IR2> set termout off
a@ORA9IR2> set termout on
a@ORA9IR2> select * from ops$tkyte.v;

         X
----------
         1
March 24, 2004 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
Rating: 5
CASE in UPDATE clause of MERGE statement March 24, 2004
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom,
Just want to find out of it is possible to use CASE statement in the WHEN
MATCHED THEN UPDATE SET... clause of MERGE statement. Oracle 9ir2.
Thanks
Followup:
ops$tkyte@ORA9IR1> create table t ( x int, y int );

Table created.

ops$tkyte@ORA9IR1>
ops$tkyte@ORA9IR1> merge into t
  2  using (select 1 xxx, 2 yyy, 3 zzz from dual) d
  3  on ( t.x = d.xxx )
  4  when matched then update set y = case when xxx = 1 then yyy else zzz end
  5  when not matched then insert values ( xxx,zzz );

1 row merged.

ops$tkyte@ORA9IR1> select * from t;

         X          Y
---------- ----------
         1          3

ops$tkyte@ORA9IR1> merge into t
  2  using (select 1 xxx, 2 yyy, 3 zzz from dual) d
  3  on ( t.x = d.xxx )
  4  when matched then update set y = case when xxx = 1 then yyy else zzz end
  5  when not matched then insert values ( xxx,zzz );

1 row merged.

ops$tkyte@ORA9IR1> select * from t;

         X          Y
---------- ----------
         1          2

ops$tkyte@ORA9IR1>
March 24, 2004 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)
Rating: 3
Not missing the point: Grasping for some explanation of how bind peeking is useful March 19, 2004
Reviewer: Kerry from Austin, TX USA
I don't think Kamal's question can be easily dismissed. The hypothetical, more
dynamic peeking he is talking about is just an attempt to understand an
otherwise perplexing feature...

I think people's difficulty in understanding what bind peeking does is that they
are grasping for a set of behaviors that actually make it beneficial. Now that I
understand that the current implementation (in 9i), is simply not useful, it is
much easier to comprehend. With the plan cached and spanning sessions, it is not
a reasonable assumption that any "representative" bind values can be used at the
hard parse:

  (1) If data isn't skewed, then bind peeking isn't necessary - the overall
statistics for the tables and indexes, independent of bind values - should
suffice

  (2) If data is skewed and peeking makes a significant difference, then it will
produce a plan for the initial set of values different than what it would choose
on subsequent executions: likely a poor choice for any other set of bind values.

How could I possibly want this?

I do not want my application to know when the cache has flushed, so I cannot
supply "representative" values at the correct time. Furthermore, if there is
"representative" data, how skewed can the data actually be?

We have tables partitioned by clients, and bind peeking is nothing but trouble.
Coding all dynamic SQL in PL/SQL would be an absolute joke...
Followup:
1) correct
2) and the initial plan should be the right plan for that query at that place.
The concept is "i have an oltp app that picks "where x = :x".  the count(*) for
the x's i use is "small" reletive to the table.  I SHOULD use an index.
Therefore the first time we run, we see x = :x and :x = 5 and 5 should use an
index -- for that exact query (cause remember the plans are by EXACT queries)
the optimizer will choose an index.  that is the right plan for that
application.  no matter what happens -- that query should use and index and
every time I run that program that query at that place will supply bind inputs
that lean the optimizer towards the index.  I need to bind because there are
potentially hundreds of codes there -- but all would use the index.  The
value(s) that would not use the index would never be called there (remember, we
are "transactional")

sorry you don't see the use, I sure do and I am glad it is there -- else it
would lead to lots more hinting going on.
Rating: 4
Data in table may be skewed, but data seen by query must not be? March 24, 2004
Reviewer: Kerry from Austin, TX USA
To summarize: Bind peeking may be useful if the data in the table is skewed, but
the data that I query is of a consistent proportion relative to the size of the
table, for a given query.

Consider the skewness as falling into two equivalence classes: BIG fanout and
SMALL fanout.

Here is where I find this problematic: Unless I code my application to recognize
the equivalence classes and handle the BIG and SMALL cases with two different
queries then there will always be a chance that a BIG dataset will cause a plan
to cache that is not at all appropriate for the SMALL case, or vice versa. Since
I don't define the equivalence classes or provide the data, I can't control it.
(And this is a simple case: assuming just 2 equivalence classes)

It may be worth note that our database is hardly OLTP. It's an ASP-model
(website) with some OLTP aspects, some data mining, and some data warehousing. I
believe this is why bind peeking was particularly bad for us.
Followup:
the concept is -- in a given point in a program -- they will be "the same".

at point X in the program (say the user interactive part), always index, the
binds will drive you that way.

at point Y in the program (system), full scan, the binds will drive you that
way.

without bind peeking, you would always have that 50/50 chance of "not getting it
right".
March 24, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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.
Rating: 5
Clone a database March 24, 2004
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom,
Is it possible to clone a database A running in archivelog mode as another
database B which runs in noarchivelog mode? We want to clone production database
as pre-production test database on a weekly basis. This will give us the same
data volume as production for query tuning. The constraints are:
a) Production database cannot be shutdown.
b) The production database runs in archivelog mode whereas pre-production test
database runs in noarchivelog mode.
c) The cloning process has to be unattended, if possible.

We have Oracle 9ir2 and RMAN for backup.
Thanks.
Followup:
database b will initially be "archivelog" but then you'll shutdown -- change it
and bring it backup.

so, "yes" you can clone it cause you can very very easily change it.
March 24, 2004 Ashish -- Thanks for the question regarding "creating text files from the clob column in table", version 1.8.5
You Asked
HI TOM,

Thanks for the advice with the same. Yesterday I had told about
my requirement of creating text files from the data stored in a
clob column in one of the table. From the reference code in one
of the questions i tried the following procedure.

The table structure is like:
Table test_tab
( col_id    number(10) primary key,
  col_text  clob       not null)

The code fragment is:
create or replace procedure dump_table_to_file(p_dir in varchar2
                                              ,p_file_extn in
varchar2 default '.txt'
                                              ,p_col_id in
number default null)
is
    l_output utl_file.file_type;
    l_status integer;

    l1  number;
    l2  clob;

    txt varchar2(32767);
    amount binary_integer := 32767;
    position integer      := 1;
    offset integer        := 1;

    cursor c_1 is  select col_id
                         ,col_text
                     from test_tab
                    where col_id = nvl(p_col_id, col_id);
BEGIN
    open c_1;
    loop
        fetch c_1 into l1, l2;
        exit when c_1%notfound;
        loop
            l_output := utl_file.fopen(p_dir, l1||p_file_extn,
'w', 32767);
            loop
                dbms_lob.read(l2,amount,offset,txt);
                offset := offset + amount;
                utl_file.put(l_output, txt);
            end loop;
            utl_file.new_line(l_output);
            utl_file.fclose(l_output);
    end loop;
    end loop;
    close c_1;
EXCEPTION
     when others then
       if c_1%isopen
       then
           close c_1;
       end if;
       dbms_output.put_line(sqlerrm);
END;

The problem is it will create a file with the col_id name but
the file doesn't have any contents. Only the file is created. Is
anything going wrong. PLease advice.

Thanks a lot,

ashish
and we said...
The logic in your loop is wrong.  dbms_lob.read throws
NO_DATA_FOUND when you go beyond the end of the lob.  That takes
us out of the loop down to the exception handler after the very
first lob is processed.  It also skips right over the
utl_file.fclose -- hence the file buffers never get flushed.

Since writing the clob to a file is a good "generic" sort of
routine, I would write it as such -- no need to tie it to this
table and this table only.  Also, since I like cursor for loops
over explicit cursors in every single case -- I'll do it that
way as well (less code to write, easier to debug, faster as
well)

Here is how I might approach this:

ops$tkyte@DEV816> create or replace
  2  procedure clob_to_file( p_dir in varchar2,
  3                          p_file in varchar2,
  4                          p_clob in clob )
  5  as
  6      l_output utl_file.file_type;
  7      l_amt    number default 32000;
  8      l_offset number default 1;
  9      l_length number default
                       nvl(dbms_lob.getlength(p_clob),0);
 10  BEGIN
 11       l_output := utl_file.fopen(p_dir, p_file, 'w', 32760);
 12       while ( l_offset < l_length )
 13       loop
 14            utl_file.put(l_output,
 15                    dbms_lob.substr(p_clob,l_amt,l_offset) );
 16            utl_file.fflush(l_output);
 17            l_offset := l_offset + l_amt;
 18       end loop;
 19       utl_file.new_line(l_output);
 20       utl_file.fclose(l_output);
 21  end;
 22  /

Procedure created.

ops$tkyte@DEV816> create table test_tab ( col_id number,
col_text clob );

Table created.

ops$tkyte@DEV816> declare
  2       l_col_text clob;
  3  begin
  4       for i in 1..5 loop
  5            insert into test_tab values
  6            ( i, empty_clob() )
  7            returning col_text into l_col_text;
  8            for i in 1 .. 10 loop
  9                 dbms_lob.writeappend( l_col_text, 30001,
 10                        rpad('*',30000,'*') || chr(10) );
 11            end loop;
 12       end loop;
 13  end;
 14  /

PL/SQL procedure successfully completed.

ops$tkyte@DEV816> create or replace
  2  procedure dump_table_to_file
  3      (p_dir in varchar2,
  4       p_file_extn in varchar2 default '.txt',
  5       p_col_id in number default null)
  6  is
  7  BEGIN
  8       for x in ( select *
  9                    from test_tab
 10                   where col_id = nvl(p_col_id,col_id) )
 11       loop
 12            clob_to_file( p_dir,
 13                          x.col_id || p_file_extn,
 14                          x.col_text );
 15       end loop;
 16  END;
 17  /

Procedure created.

ops$tkyte@DEV816> exec dump_table_to_file( '/tmp' );

PL/SQL procedure successfully completed.

ops$tkyte@DEV816> host ls -l /tmp/?.txt
-rw-r--r--   1 ora816     300011 Sep 12 08:00 /tmp/1.txt
-rw-r--r--   1 ora816     300011 Sep 12 08:00 /tmp/2.txt
-rw-r--r--   1 ora816     300011 Sep 12 08:00 /tmp/3.txt
-rw-r--r--   1 ora816     300011 Sep 12 08:00 /tmp/4.txt
-rw-r--r--   1 ora816     300011 Sep 12 08:00 /tmp/5.txt

ops$tkyte@DEV816> select dbms_lob.getlength(col_text) from
test_tab;

DBMS_LOB.GETLENGTH(COL_TEXT)
----------------------------
                      300010
                      300010
                      300010
                      300010
                      300010

ops$tkyte@DEV816>

Which is exactly what we expected.  You'll get an exception on
the fclose if your text does not have a newline every 32k.
Review & Followup

Rating: 3
Creating Text File From Review July 02, 2002
Reviewer: Angelica Campbell from Washington, DC
The response was useful in the fact that it proved to me that a file could be
generated from a clob.  However I did not understand the last statement that you
made re: You'll get an exception on the fclose if your text does not have a
newline every 32k.

In my case I am combining several clobs of variable length which may or may not
be greater than 32k.  The final clob will be used to create a document which
will be used as a file attachment. Would I have to parse the new clob and add a
newline every 32k and if so, how?
Followup:
if the ASCII newline character (chr(10)) does not appear at least every 32k
bytes -- utl_file will fail.  That means if you try to write 32k+1 byte of text
that doesn't have a newline in it you'll get an exceptoin.  For example,
consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2          l_output                utl_file.file_type;
  3          l_data          varchar2(32000) default rpad( '*', 32000, '*' );
  4  begin
  5          l_output := utl_file.fopen( '/tmp', 'foo.txt', 'w', 32765 );
  6          utl_file.put( l_output, l_data );
  7          utl_file.fclose( l_output );
  8  end;
  9  /

PL/SQL procedure successfully completed.
that works, less then 32k

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2          l_output                utl_file.file_type;
  3          l_data          varchar2(32000) default rpad( '*', 32000, '*' );
  4  begin
  5          l_output := utl_file.fopen( '/tmp', 'foo.txt', 'w', 32765 );
  6          utl_file.put( l_output, l_data );
  7          utl_file.put( l_output, chr(10) );
  8          utl_file.put( l_output, l_data );
  9          utl_file.fclose( l_output );
 10  end;
 11  /

PL/SQL procedure successfully completed.
that works, 32,000 bytes -- newline -- 32,000 bytes

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2          l_output                utl_file.file_type;
  3          l_data          varchar2(32000) default rpad( '*', 32000, '*' );
  4  begin
  5          l_output := utl_file.fopen( '/tmp', 'foo.txt', 'w', 32765 );
  6          utl_file.put( l_output, l_data );
  7          utl_file.put( l_output, l_data );
  8          utl_file.fclose( l_output );
  9  end;
 10  /
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 8

that does NOT work 32,000 bytes -- NO newline -- 32,000 bytes

If your clob might contain a stream of characters over 32k in length that do not
contain a newline, you will get that error.  That is what I meant.

Yes, you would have to make sure a newline appears AT LEAST every 32k. How?
That is up to you (knowledge of the file you are producing, the data going into
it and what is allowable in the output file necessary in order to answer that
part of the question)
Rating: 4
Extracting line by line February 03, 2003
Reviewer: Abdel-Rahman Agha from Yemen
Hi Tom,
  I have a problem when I design a text item of type long based on a clob
column, that I need to extract the text filled in this item line by line. If the
line contains a linefeed or cariage return, no problem I could extract this line
before the linefeed supposed that the width not exceed specific number say 80.
But if the line wrap to the next one, I will not find any linefeed or cariage
return. How can I take this line just written in one line as it appears?
Followup:
?  don't understand.

this sounds like a "client" issue doesn't it?  I mean, the database just stores
whatever you put in there
Rating: 4
How about read file January 08, 2004
Reviewer: A reader
Hi, Tom,

Does this "32k in length that do not contain a newline, you will get that error"
suitable for reading a file?
Another words, what about the file need to be read contains a line of 32K length
without new line, is it a problem?

THanks
Followup:
right now, utl_file has a 32k linesize limit, yes.  reading and writing a "line"
of text >32k will not work.  You'll have to use a BFILE to read that file in
chunks instead.
Rating: 5
March 24, 2004
Reviewer: reader
dear sir ,
i want to create that clob to txt file at the web client machine
 not at client server machine.
how can i do that ?
Followup:
write a program that runs on the client, since only a program on the client can
write to the client machine.
Rating: 5
March 24, 2004
Reviewer: reader
Sir ,
is that mean no way to make the web client able to get that text file ?!
it's hard soluation to make a program and install it on each web client , iam
talking about 100s clients
Followup:
do you have "a browser"

that is client enough --
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:232814159006#6891533553691

March 24, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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.
Rating: 5
Oracle to SQL Server snapshots March 23, 2004
Reviewer: Abe from New York
Tom,
Your advice is always great.
I have used snapshots and replication in Oracle but I now have a new situation.
We have an Oracle 8.1.7 database which we must create snapshots for 8 tables on
a SQL Server 2000 database (not my decision).  I have no experience with MSSQL
and do not know where to start.
Followup:
are you saying you must push the changes to sqlserver?
Rating: 5
followup March 24, 2004
Reviewer: Abe from New York
Yes!
I must propagate any changes to the data made in our Oracle database to a SQL
Server database.
Followup:
well, it'll be really hard in 8i to trickle changes (9i streams -- easy).

In 8i, you could build a full refresh style MV, would that "suffice"
March 24, 2004 Omar -- Thanks for the question regarding "Forms on application server", version 4.0
You Asked
Hi Tom,

Thanks a lot for being my helper in Oracle. This Time I want
more than a Question , some of them I think will be a little bit
long, because I am trying to ask you a week a go and I could
not.

Q1. Here we are trying to develop a Library system on the web,
really I want you to tell me which best oracle products to use
and in what way.

Q2. What are you doing in this page is really greate , I want
you ,please , to provide me how and what are you doing here ,
with sample code of your search routines if possible, with the
structure of your database.

Q3.How can call a database procedure with html tags (I mean to
be displayed as html page using htp package) from a button in a
form developed on application server with forms server.

Q4.How to load a winword doc file in a lob column, and how to
search in it for a string and display the contents on a web
page.

thanks in advance
and we said...
q1 -- thats really hard to answer as I do not know exactly what
a "library system" means to you.  It could be as easy as using
Portal -- as that has content management functions, is
integrated with interMedia for searching, and so on.  If you
were building from scratch, some people would like to use Java
and servlets/jsps -- then JDeveloper might be the tool.  If this
is an intranet based application and demanded a very rich GUI,
forms developer server might be the right answer.  So, the
answer is -- I don't know.  You'll need to evaluate the tools --
the job you have to do -- and who you have to do it with (their
skill set).

q2 - my search routine is a simple query actually.  I use
interMedia to index all of my questions and answers.  When you
search, I am simply

"select * from T where contains( text, 'your search' ) > 0"

I use the interMedia "markup" routine to do the highlighting for
me.  I do virtually nothing in my search routine, the database
does it all.

See
http://download-east.oracle.com/docs/cd/A81042_01/DOC/inter.816/index.htm
for more info on intermedia.

q3 - The developer built-in procedure web.show_document will
launch a browser window directed to any URL which is specified
as a parameter.

q4 - see q2.  Intermedia is the way to do this.  It has filters
for many document types from pdf, to doc to ppt.
Review & Followup

Rating: 5
March 24, 2004
Reviewer: A reader
How can we call pakkage/procedure/function in HTML ???
Followup:
trick question.

HTML is a markup language, not a "programming" language.

but if you view "source" on this page, you'll see how I "call a procedure from
html"

<a href=.....>
Rating: 4
March 24, 2004
Reviewer: A reader
Sorry for diturbing you again But i am quite a beginner of Oracle w.r.t HTML :(

<A HREF=f?p=4950:1:6495359748603529131:RESET::1>

I think above will call procedure (of function ?) f with parameters

4950
1
6495359748603529131
RESET
1

(what a "::" means in this context then if all the remaining have a single : ?)

And again Sorry
Followup:
calls a function f

with one parameter P

whose value is 4950:1:6495359748603529131:RESET::1

Just one parameter...

it could have been:

<a href=f?p=Hello_World>

that string of numbers/colons means something to the function F, it isn't
"special" or anything.
March 24, 2004 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.
Rating: 5
Function on where clause column March 24, 2004
Reviewer: Sagar from Houston
Hi Tom,
  Following are explain plans for 2 queries; one using NVL function on columns
in where clause and other without. But BOTH the explain plans show index being
used. Can you advise?

Thanks,
Sagar

select
1 from ETL_FEED_EXCEPTIONS EFE
WHERE EXISTS
(
 SELECT 1 FROM
 (
  SELECT EX.FEED_INFO_ID,  EX.KEY1,EX.ETL_EXCEPTION_ID,
  EX.COLUMN_NM,EX.INVALID_COLUMN_VALUE
  FROM ETL_FEED_EXCEPTIONS EX, BO_AGING_REP AG
  WHERE EX.FEED_INFO_ID = AG.FEED_INFO_ID
  AND nvl(EX.KEY1,'zzzzzzzzzzz') = nvl(AG.KEY1,'zzzzzzzzzzz')
  AND nvl(EX.ETL_EXCEPTION_ID,99999999999) =
nvl(AG.ETL_EXCEPTION_ID,99999999999)
  AND nvl(EX.COLUMN_NM,'zzzzzzzzzzz') = nvl(AG.COLUMN_NM,'zzzzzzzzzzz')
  AND nvl(EX.INVALID_COLUMN_VALUE,'zzzzzzzzzzz') =
nvl(AG.INVALID_COLUMN_VALUE,'zzzzzzzzzzz')
  AND EX.AGING_FLG ='N' ) VIEW_A
WHERE EFE.FEED_INFO_ID = VIEW_A.FEED_INFO_ID
AND nvl(EFE.KEY1,'zzzzzzzzzzz') = nvl(VIEW_A.KEY1,'zzzzzzzzzzz')
AND nvl(EFE.ETL_EXCEPTION_ID,99999999999) =
nvl(VIEW_A.ETL_EXCEPTION_ID,99999999999)
AND nvl(EFE.COLUMN_NM,'zzzzzzzzzzz') = nvl(VIEW_A.COLUMN_NM,'zzzzzzzzzzz')
AND nvl(EFE.INVALID_COLUMN_VALUE,'zzzzzzzzzzz') =
nvl(VIEW_A.INVALID_COLUMN_VALUE,'zzzzzzzzzzz') )  ;

Explain plan:
With NVL -
SELECT STATEMENT Optimizer=CHOOSE (Cost=2766 Card=83645 Bytes=2760285)
  FILTER
    TABLE ACCESS (FULL) OF ETL_FEED_EXCEPTIONS (Cost=2766 Card=83645
Bytes=2760285)
    MERGE JOIN (CARTESIAN) (Cost=2353 Card=1 Bytes=70)
      INDEX (RANGE SCAN) OF FEED_EXCEPTION_ID_N5 (NON-UNIQUE) (Cost=2229 Card=1
Bytes=34)
      SORT (JOIN) (Cost=124 Card=1 Bytes=36)
        INDEX (RANGE SCAN) OF BO_AGING_REP_ID_N5 (NON-UNIQUE) (Cost=124 Card=1
Bytes=36)

            select 1 from ETL_FEED_EXCEPTIONS EFE
         WHERE EXISTS (
            SELECT 1 FROM (
                SELECT EX.FEED_INFO_ID,
EX.KEY1,EX.ETL_EXCEPTION_ID,EX.COLUMN_NM,EX.INVALID_COLUMN_VALUE
                  FROM ETL_FEED_EXCEPTIONS EX, BO_AGING_REP AG
                 WHERE EX.FEED_INFO_ID = AG.FEED_INFO_ID
                   AND EX.KEY1= AG.KEY1
                   AND EX.ETL_EXCEPTION_ID = AG.ETL_EXCEPTION_ID
                   AND EX.COLUMN_NM = AG.COLUMN_NM
                   AND EX.INVALID_COLUMN_VALUE = AG.INVALID_COLUMN_VALUE
                   AND EX.AGING_FLG ='N' ) VIEW_A
            WHERE EFE.FEED_INFO_ID = VIEW_A.FEED_INFO_ID
            AND EFE.KEY1 = VIEW_A.KEY1
            AND EFE.ETL_EXCEPTION_ID = VIEW_A.ETL_EXCEPTION_ID
            AND EFE.COLUMN_NM = VIEW_A.COLUMN_NM
            AND EFE.INVALID_COLUMN_VALUE= VIEW_A.INVALID_COLUMN_VALUE )  ;

Explain plan:
WITHOUT NVL

SELECT STATEMENT Optimizer=CHOOSE (Cost=2766 Card=83645 Bytes=2760285)
  FILTER
    TABLE ACCESS (FULL) OF ETL_FEED_EXCEPTIONS (Cost=2766 Card=83645
Bytes=2760285)
    MERGE JOIN (CARTESIAN) (Cost=5 Card=1 Bytes=70)
      INDEX (RANGE SCAN) OF FEED_EXCEPTION_ID_N5 (NON-UNIQUE) (Cost=3 Card=1
Bytes=34)
      SORT (JOIN) (Cost=2 Card=1 Bytes=36)
        INDEX (RANGE SCAN) OF BO_AGING_REP_ID_N5 (NON-UNIQUE) (Cost=2 Card=1
Bytes=36)

INDEXES:

CREATE INDEX FEED_EXCEPTION_ID_N5 ON
  ETL_FEED_EXCEPTIONS(FEED_INFO_ID, KEY1, ETL_EXCEPTION_ID, COLUMN_NM,
INVALID_COLUMN_VALUE, AGING_FLG)
  TABLESPACE DAC_DATA_IDX_LG PCTFREE 10
  STORAGE(INITIAL 2048K NEXT 2048K PCTINCREASE 0 ) ;

CREATE INDEX BO_AGING_REP_ID_N5 ON
  BO_AGING_REP(FEED_INFO_ID, KEY1, ETL_EXCEPTION_ID, COLUMN_NM,
INVALID_COLUMN_VALUE)
  TABLESPACE DAC_DATA_IDX_LG PCTFREE 10
  STORAGE(INITIAL 2048K NEXT 2048K PCTINCREASE 0 ) ;
Followup:
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7555175291763
cbo is very smart.
March 24, 2004 Kumar -- Thanks for the question regarding "SQL*Net data Security", version 8.1.5
You Asked
Hi Tom,

I want to know about SQL*Net data encryption. By default SQL*Net
use any encryption or not? If not then how to start encryption
for Oracle data transfer between Server and Client. Is there any
level or encryption use for SQL*Net data transfer? Also I want
to know, is there any tool/way to trap/decode the data that is
transferring over SQL*Net?

Thanks

Kumar
and we said...
See
http://www.oracle.com/ip/deploy/database/8i/index.html?advsec.html
by default only the PASSWORD used to connect is encrypted.  All
other data is sent in the clear.

To see what is sent, you can simply set:

trace_level_client=16
trace_directory_client = c:\temp
trace_file_client = sqlnet.trc

in the clients sqlnet.ora file and you'll get packet level dumps
in the sqlnet.trc file.
Review & Followup

Rating: 5
link March 24, 2004
Reviewer: Arun Mathur from Marietta, GA
Tom,

I'm sorry, but I got a Page Not Found when going to the site you recommended
below. Would you mind giving me the article's name so I can look it up on the
site?

Also, it was great meeting you at the Atlanta OUG.

Regards,
Arun
Followup:
search for

"advanced security"

on either of otn or www.oracle.com -- it was a pointer to where they kept the
ASO option information.
March 24, 2004 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.
Rating: 5
GETS, GETHIT, PINS, PINHIT March 02, 2004
Reviewer: Henry Yick from Hong Kong
Really useful for me.

So, could I say:
1. If I can share the SQL, the GETS should be comparatively slow.
2. For every object handles in a SQL request, GET is optional (if I get it
before), but PINS is unavoidable, and is normal.

Also, what is the meaning of a low GETHIT and a low PINHIT? Both of them
indicate that the sharepool size is too small? Any different between them?
Followup:
1) if you parse little, gets should be low.  not just share
2) true

could indicate, could -- not do.
Rating: 4
GETS or RELOADS March 08, 2004
Reviewer: Yves Debizet from Grenoble, France
Tom, you said:

"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."

In case an object has been flushed, how can Oracle make the difference between
"the object is no more in the cache: RELOAD will be issued" and "the object is
not in the cache: GET will be issued" ?

Where is the information which says: the object had been loaded but it has been
flushed and should be reloaded when needed ?

Thank you.
Followup:
a get happens with a reload.  a get is a get.

this is the view that provides the information about reloads.. if this view has
a slot (named slot) for an object that isn't in the cache -- it is a reload.
Rating: 4
This cleared so much March 23, 2004
Reviewer: hxl from MD USA
Tom,

Besides invalidation, what will be other cases when a sql stmt stored in the
library cache will be flushed? I am a little puzzled by the stats below which
shows I have free memory in shared pool, but I have lots of
reload/invalidations, what could that mean? Or is it because there is
pre-assigned portions in library cache for each category (sqlarea, data buffer,
etc) and each won't go over its quota?

-----------------------------------------------------------
NAME                          MBYTES MBYTES_TOT       PCT SHARED_POOL_SIZE
-------------------------- --------- ---------- --------- ----------------
free memory                    87.99        175        50              161
miscellaneous                   1.95        175         1              161
transactions                     .52        175         0              161
db_block_hash_buckets            .89        175         1              161
DML locks                        .28        175         0              161
fixed allocation callback          0        175         0              161
PL/SQL DIANA                    3.45        175         2              161
KGFF heap                        .04        175         0              161
trigger source                     0        175         0              161
table definiti                   .01        175         0              161
trigger inform                     0        175         0              161
sessions                        1.14        175         1              161
enqueue_resources                .24        175         0              161
State objects                    .62        175         0              161
joxs heap init                     0        175         0              161
pl/sql source                      0        175         0              161
PL/SQL SOURCE                      0        175         0              161
errors                             0        175         0              161
PLS non-lib hp                     0        175         0              161
ktlbk state objects              .25        175         0              161
KGK heap                         .02        175         0              161
trigger defini                     0        175         0              161
dictionary cache                2.13        175         1              161
db_files                         .35        175         0              161
table columns                    .03        175         0              161
KQLS heap                       1.44        175         1              161
temporary tabl                     0        175         0              161
library cache                  26.08        175        15              161
db_block_buffers                6.48        175         4              161
sql area                       35.25        175        20              161
joxlod: in phe                     0        175         0              161
processes                        .39        175         0              161
PL/SQL MPCODE                    3.3        175         2              161
joxlod: in ehe                   .06        175         0              161
event statistics per sess       1.82        175         1              161

Thanks as always.
Followup:
besides invalidation what causes invalidations...... (you say you have lots of
invalidations...)

sounds like you have invalidations -- why are you looking for "other things"?
Rating: 4
Just want to know all possible causes March 23, 2004
Reviewer: hxl from md usa
More importantly can you confirm on this: I have free memory in shared
pool(about 50%), but there are reloads/invalidations, is it because sql area
only got assigned a certain % of the shared pool so once its quota is reached,
some sql statements will be flushed?

Otherwise I have no idea why the old stmts have to be flushed out - not couting
being invalidated.

Thanks.
Followup:
how do you know they are getting flushed in the first place?

the sql area can get larger according to your info above.
Rating: 5
Cursor managment....... March 23, 2004
Reviewer: Mark J. Bobak from Ann Arbor, MI
This got me thinking.  I saw the Cursor Management talk at HotSos.  Afterwards,
I was chatting w/ Steve Adams, and he pointed out that when you set
session_cached_cursors, what you're really doing is caching the library cache
lock structure (which is what you create when you do a GET.)  He also explained
to me that when you set cursor_space_for_time=TRUE, you're additionally caching
the library cache pin structure, (which is what you create when  you do a PIN.)

So, I did some testing, 10.1.0.2, I was the only one on the instance.  First, I
did:
session_cached_cursors=0
cursor_space_for_time=FALSE
and I executed the following script:
select namespace, gets, gethits, pins, pinhits from v$librarycache where
namespace = 'SQL AREA';
For each execution, GETS and GETHITS increased by 1, while PINS and PINHITS
increased by 2.  Then I set session_cached_cursors=100  Now, GETS and GETHITS
stopped increasing, and PINS and PINHITS increased by 1 per execution.  So, I
set cursor_space_for_time=TRUE and session_cached_cursors=100.
I expected the PINS and PINGETS to go to zero/execution.  This was not the case,
I saw the same as I had before, with session_cached_cursors=100 and
cursor_space_for_time=false.

Any thoughts, Tom?
Followup:
what did you use to test with -- different environments will return radically
different results -- especially if you use PLSQL for this sort of stuff with its
magical cursor caching and such.

But, here was my example:

ops$tkyte@ORA10G> create or replace procedure p
  2  authid current_user
  3  as
  4      l_ns      varchar2(4000);
  5      l_gets    number;
  6      l_gethits number;
  7      l_pins    number;
  8      l_pinhits number;
  9      l_sgets    number;
 10      l_sgethits number;
 11      l_spins    number;
 12      l_spinhits number;
 13  begin
 14      for i in 1 .. 1000
 15      loop
 16          execute immediate
 17          'select namespace, gets, gethits, pins, pinhits
 18             from v$librarycache
 19            where namespace = ''SQL AREA'' '
 20          into l_ns, l_gets, l_gethits, l_pins, l_pinhits;
 21
 22          if ( i in (1,1000) )
 23          then
 24              if ( i = 1 )
 25              then
 26                  l_sgets := l_gets; l_sgethits := l_gethits;
 27                  l_spins := l_pins; l_spinhits := l_pinhits;
 28              end if;
 29              dbms_output.put_line
 30              ( l_ns || to_char(l_gets,'999,999') ||
 31                to_char(l_gethits,'999,999') ||
 32                to_char(l_pins,'999,999') ||
 33                to_char(l_pinhits,'999,999')  );
 34              if ( i = 1000 )
 35              then
 36                  dbms_output.put_line
 37                  ( l_ns || to_char(l_gets-l_sgets,'999,999') ||
 38                    to_char(l_gethits-l_sgethits,'999,999') ||
 39                    to_char(l_pins-l_spins,'999,999') ||
 40                    to_char(l_pinhits-l_spinhits,'999,999')  );
 41              end if;
 42          end if;
 43      end loop;
 44  end;
 45  /

Procedure created.

ops$tkyte@ORA10G> @connect "/ as sysdba"
ops$tkyte@ORA10G> set termout off
sys@ORA10G> @login
sys@ORA10G> set termout off
sys@ORA10G> REM GET afiedt.buf NOLIST
sys@ORA10G> set termout on
sys@ORA10G> alter system set session_cached_cursors=0 scope=spfile;

System altered.

sys@ORA10G> alter system set cursor_space_for_time=FALSE scope=spfile;

System altered.

sys@ORA10G> startup force
ORACLE instance started.

Total System Global Area  171966464 bytes
Fixed Size                   777956 bytes
Variable Size             145760540 bytes
Database Buffers           25165824 bytes
Redo Buffers                 262144 bytes
Database mounted.
Database opened.
sys@ORA10G> @connect /
sys@ORA10G> set termout off
ops$tkyte@ORA10G> @login
ops$tkyte@ORA10G> set termout off
ops$tkyte@ORA10G> REM GET afiedt.buf NOLIST
ops$tkyte@ORA10G> set termout on
ops$tkyte@ORA10G> exec p
SQL AREA   1,860   1,536   6,740   6,122
SQL AREA   1,864   1,540   7,751   7,133
SQL AREA       4       4   1,011   1,011

PL/SQL procedure successfully completed.

ops$tkyte@ORA10G> exec p
SQL AREA   1,866   1,542   7,756   7,138
SQL AREA   1,866   1,542   8,755   8,137
SQL AREA       0       0     999     999

PL/SQL procedure successfully completed.

ops$tkyte@ORA10G> exec p
SQL AREA   1,868   1,544   8,760   8,142
SQL AREA   1,868   1,544   9,759   9,141
SQL AREA       0       0     999     999

PL/SQL procedure successfully completed.

ops$tkyte@ORA10G> @connect "/ as sysdba"
ops$tkyte@ORA10G> set termout off
sys@ORA10G> @login
sys@ORA10G> set termout off
sys@ORA10G> REM GET afiedt.buf NOLIST
sys@ORA10G> set termout on
sys@ORA10G> alter system set session_cached_cursors=100 scope=spfile;

System altered.

sys@ORA10G> alter system set cursor_space_for_time=FALSE scope=spfile;

System altered.

sys@ORA10G> startup force
ORACLE instance started.

Total System Global Area  171966464 bytes
Fixed Size                   777956 bytes
Variable Size             145760540 bytes
Database Buffers           25165824 bytes
Redo Buffers                 262144 bytes
Database mounted.
Database opened.
sys@ORA10G> @connect /
sys@ORA10G> set termout off
ops$tkyte@ORA10G> @login
ops$tkyte@ORA10G> set termout off
ops$tkyte@ORA10G> REM GET afiedt.buf NOLIST
ops$tkyte@ORA10G> set termout on
ops$tkyte@ORA10G> exec p
SQL AREA     637     313   5,513   4,903
SQL AREA     637     313   6,520   5,910
SQL AREA       0       0   1,007   1,007

PL/SQL procedure successfully completed.

ops$tkyte@ORA10G> exec p
SQL AREA     638     314   6,525   5,915
SQL AREA     638     314   7,524   6,914
SQL AREA       0       0     999     999

PL/SQL procedure successfully completed.

ops$tkyte@ORA10G> exec p
SQL AREA     639     315   7,529   6,919
SQL AREA     639     315   8,528   7,918
SQL AREA       0       0     999     999

PL/SQL procedure successfully completed.

ops$tkyte@ORA10G> @connect "/ as sysdba"
ops$tkyte@ORA10G> set termout off
sys@ORA10G> @login
sys@ORA10G> set termout off
sys@ORA10G> REM GET afiedt.buf NOLIST
sys@ORA10G> set termout on
sys@ORA10G> alter system set session_cached_cursors=100 scope=spfile;

System altered.

sys@ORA10G> alter system set cursor_space_for_time=TRUE scope=spfile;

System altered.

sys@ORA10G> startup force
ORACLE instance started.

Total System Global Area  171966464 bytes
Fixed Size                   777956 bytes
Variable Size             145760540 bytes
Database Buffers           25165824 bytes
Redo Buffers                 262144 bytes
Database mounted.
Database opened.
sys@ORA10G> @connect /
sys@ORA10G> set termout off
ops$tkyte@ORA10G> @login
ops$tkyte@ORA10G> set termout off
ops$tkyte@ORA10G> REM GET afiedt.buf NOLIST
ops$tkyte@ORA10G> set termout on
ops$tkyte@ORA10G> exec p
SQL AREA   1,121     364   4,507   3,871
SQL AREA   1,180     372   4,824   4,167
SQL AREA      59       8     317     296

PL/SQL procedure successfully completed.

ops$tkyte@ORA10G> exec p
SQL AREA   1,181     373   4,828   4,171
SQL AREA   1,181     373   4,828   4,171
SQL AREA       0       0       0       0

PL/SQL procedure successfully completed.

ops$tkyte@ORA10G> exec p
SQL AREA   1,182     374   4,832   4,175
SQL AREA   1,182     374   4,832   4,175
SQL AREA       0       0       0       0

PL/SQL procedure successfully completed.

(which actually shows that 10g is doing a bit more than 9i was since...)

sys@ORA9IR2> alter system set session_cached_cursors=0 scope=spfile;

System altered.

sys@ORA9IR2> alter system set cursor_space_for_time=FALSE scope=spfile;

System altered.

sys@ORA9IR2> startup force
ORACLE instance started.

Total System Global Area  236000356 bytes
Fixed Size                   451684 bytes
Variable Size             167772160 bytes
Database Buffers           67108864 bytes
Redo Buffers                 667648 bytes
Database mounted.
Database opened.
sys@ORA9IR2> @connect /
sys@ORA9IR2> set termout off
ops$tkyte@ORA9IR2> REM GET afiedt.buf NOLIST
ops$tkyte@ORA9IR2> set termout on
ops$tkyte@ORA9IR2> exec p
SQL AREA   1,145     977   4,884   4,579
SQL AREA   2,172   1,983   7,908   7,603
SQL AREA   1,027   1,006   3,024   3,024

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec p
SQL AREA   2,175   1,985   7,918   7,611
SQL AREA   3,174   2,984  10,915  10,608
SQL AREA     999     999   2,997   2,997

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec p
SQL AREA   3,177   2,987  10,924  10,617
SQL AREA   4,176   3,986  13,921  13,614
SQL AREA     999     999   2,997   2,997

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> @connect "/ as sysdba"
ops$tkyte@ORA9IR2> set termout off
sys@ORA9IR2> REM GET afiedt.buf NOLIST
sys@ORA9IR2> set termout on
sys@ORA9IR2> alter system set session_cached_cursors=100 scope=spfile;

System altered.

sys@ORA9IR2> alter system set cursor_space_for_time=FALSE scope=spfile;

System altered.

sys@ORA9IR2> startup force
ORACLE instance started.

Total System Global Area  236000356 bytes
Fixed Size                   451684 bytes
Variable Size             167772160 bytes
Database Buffers           67108864 bytes
Redo Buffers                 667648 bytes
Database mounted.
Database opened.
sys@ORA9IR2> @connect /
sys@ORA9IR2> set termout off
ops$tkyte@ORA9IR2> REM GET afiedt.buf NOLIST
ops$tkyte@ORA9IR2> set termout on
ops$tkyte@ORA9IR2> exec p
SQL AREA     461     293   4,195   3,895
SQL AREA     485     296   6,216   5,916
SQL AREA      24       3   2,021   2,021

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec p
SQL AREA     487     297   6,225   5,923
SQL AREA     487     297   8,223   7,921
SQL AREA       0       0   1,998   1,998

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec p
SQL AREA     489     299   8,231   7,929
SQL AREA     489     299  10,229   9,927
SQL AREA       0       0   1,998   1,998

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> @connect "/ as sysdba"
ops$tkyte@ORA9IR2> set termout off
sys@ORA9IR2> REM GET afiedt.buf NOLIST
sys@ORA9IR2> set termout on
sys@ORA9IR2> alter system set session_cached_cursors=100 scope=spfile;

System altered.

sys@ORA9IR2> alter system set cursor_space_for_time=TRUE scope=spfile;

System altered.

sys@ORA9IR2> startup force
ORACLE instance started.

Total System Global Area  236000356 bytes
Fixed Size                   451684 bytes
Variable Size             167772160 bytes
Database Buffers           67108864 bytes
Redo Buffers                 667648 bytes
Database mounted.
Database opened.
sys@ORA9IR2> @connect /
sys@ORA9IR2> set termout off
ops$tkyte@ORA9IR2> REM GET afiedt.buf NOLIST
ops$tkyte@ORA9IR2> set termout on
ops$tkyte@ORA9IR2> exec p
SQL AREA     471     303   2,736   2,435
SQL AREA     495     306   3,751   3,450
SQL AREA      24       3   1,015   1,015

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec p
SQL AREA     497     307   3,757   3,454
SQL AREA     497     307   4,756   4,453
SQL AREA       0       0     999     999

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec p
SQL AREA     499     309   4,761   4,458
SQL AREA     499     309   5,760   5,457
SQL AREA       0       0     999     999

PL/SQL procedure successfully completed.
Rating: 5
Thanks for the followup, Tom.... March 24, 2004
Reviewer: Mark J. Bobak from Ann Arbor, MI
My test environment was just SQL*Plus.  I just ran that
query multiple times from the command line and observed the
results.

Obviously, my test was flawed.  I'm going to try my test
again, as well as your script.

Also, you mention that 10g was doing more than 9i, but,
with CURSOR_SPACE_FOR_TIME=TRUE in 10g, you no longer do
any pins.  With the same in 9i, you're still doing 1 pin/exec.

The analysis continues...

Thanks Tom!

-Mark
Rating: 3
If reload counts grow, can I be sure that at least some stored statements have been flushed? March 24, 2004
Reviewer: hxl from md usa
"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.
"

I have free space in shared pool, so shared pool too small is out.

Question 1: Say I have 500 reloads, 500 invalidations the 1st day, and 1500
reloads, 1000 invalidations the 2nd day, can I say that there are 1500 - 500 -
(1000 - 500) = 500 reloads?

Question 2: Can I say every invalidation increments counts for both reloads and
invalidations?

Thanks soooo much!
Followup:
you can say that, yes -- seems right.  reloads are "invalidated and reloaded" or
"just isn't here anymore and reloaded"

every invalidation may or may not reload (what if you don't use it again)
March 24, 2004 Vivek -- Thanks for the question regarding "Cascade Delete", version 8.0.5
You Asked
I want to write cascade delete trigger on a table. What I want is to SET NULL in
child records for every parent records I want to delete. I have written BEFORE
ROW LEVEL trigger to trap the parent key value and BEFORE STATEMENT LEVEL to
update the child record to null for that parent key. But as BEFORE STATEMENT
LEVEL trigger fires before ROW LEVEL, I am not getting the desired result. I can
not use OLD reference in STATEMENT LEVEL trigger. I am also using package to
store parent key value.

How can I set the child record to null for every parent record I want to delete?

Vivek
and we said...
In Oracle8i (release 8.1.5 and up) you'll code:

create table p ( x int primary key );
create table c ( x references p on delete set null );

In 8.0, this is quite simply "not doable" in a trigger unless you use deferrable
constraints and three triggers (a very convuluted approach).

I would suggest this calls for a stored proceudre.  You would code:

procedure delete_parent( p_id in number )
as
begin
   update child set fk_to_parent = null where fk_to_parent = p_id;
   delete from parent where id = p_id;
end;
/

and have the applications call that instead of issuing a delete straight out.
Review & Followup

Rating: 4
take care of this September 02, 2001
Reviewer: SHAKEEL AMJAD ALI from FAISLABAD,PAKISTAN
I am a student studying oracel 'database'from the college 'NICON'.
     I want to bring to your notice that one of ur testing centers in Pakistan
is giving out exam questions for as less 1000-Rs(rupees)I was reaily shocked to
learn this and wanted to do somthing so,I decided to tell u.I reaily wish u
could do somthing about the serious matter which is harmfull to both Oracle and
the students.
     I will be glad to tell u about the site no.= PKP,in Faisalabad,Pakistan.
Rating: 3
Cascade Delete September 03, 2001
Reviewer: Vivek Saxena from UK
What you said is stored procedure is only way to achieve the result, if I can't
change the constraint to deferrable constraint in 8.0.5 version.
I am bit confussed over the behaviour of trigers that i have written.Could you
please help me in that, what i have done is in BEFORE ROW LEVEL i got p_id to
delete and in BEFORE STATEMENT LEVEL i updated the child to null and AFTER
STATEMENT LEVEL i initialised the array. I have declared an package for array
values. Now for the first time when i delete p_id i get ora-02292 error but
second time it works perfectly, p_id is deleted. Why does it work like that? How
does BEFORE STATEMENT LEVEL trigger gets the value of p_id in second attempt.

Thanks.
Rating: 3
Okay. September 03, 2001
Reviewer: Pritish Kulkarni from Mumbai, India
Your response is okay and gives the solution.
But why can't you create a before delete row levele trigger on parent table in
which you can set the child to null
as
create table a( p_id    number primary key);

create table b( fk_id  number references a);

create or replace trigger bef_del_a
before delete on a
for each row
begin
  update b set fk_id = null where fk_id = :old.p_id;
end;
/

is there any problem with this approach?
Followup:
It won't work in 8.0 and before...

ops$tkyte@ORA806.WORLD> insert into a values ( 1 );
1 row created.

ops$tkyte@ORA806.WORLD> insert into b values ( 1 );
1 row created.

ops$tkyte@ORA806.WORLD> commit;
Commit complete.

ops$tkyte@ORA806.WORLD> delete from a;
delete from a
            *
ERROR at line 1:
ORA-04091: table OPS$TKYTE.A is mutating, trigger/function may not see it
ORA-06512: at "OPS$TKYTE.BEF_DEL_A", line 2
ORA-04088: error during execution of trigger 'OPS$TKYTE.BEF_DEL_A'
Rating: 5
U can do that at the creation of table itself September 04, 2001
Reviewer: Raj Natt from very near to ORACLE ...Fremont , CA USA
hi ! Vivek
  the solution to ur problem is :
  at the time of creating the table..enable the foreign key
  with set to null parameter. u will notice that hence forth
  whenever u delete a parent value..oracle will set the
  corresponding child values to Null. This is the best way
  to ensure the Global enforcement of the constraint.
Rating: 5
Response to statement level trigger on 8.0.5 September 06, 2001
Reviewer: Todd A. Cox from Birmingham USA
To respond to Pritish Kulkarni, I believe that the solution you have provided
(row level triggers) will cause "Table x is mutating" errors when used with
8.0.x, though I believe it would work with 8.1.x (but is not required, as Tom
points out).
Rating: 3
how to delete UNREF'ed parent records March 24, 2004
Reviewer: rob from nyc
Tom,
I am sure the answer has got to be somewhere in the AskTom treasure trove but I
have not been able to find...

So if given a range of PK values say 11 to 100
, of these 90 records I want to delete ones not
referenced by any FK....what's the right way to do this ?

Thanks
8.1.7
Followup:
there is no automagical way to do this (rather strange) thing.

it would be

delete from t
  where pk between 11 and 100
    and NOT EXISTS ( select null from child1 where fk = pk )
    and NOT EXISTS ( select null from child2 where fk = pk )
    ...
    and NOT EXISTS ( select null from childN where fk = pk );

for example.  but the not exists list is 100% dependent on the parent table --
each parent table will have different lists of course (of which you could
generate from user_constraints I suppose...)


Listing

Recent

Archives

Advanced Search

FAQ by Category

Most Popular

Hot Articles

Your Questions

[ Listing ]

RSS Feed

Subject

Article

Last Updated (East coast time)

Return values from a PL/SQL table into a ref cursor?

article only

Mar 25, 2004 08:51 am

session_cached_cursor values

article only

Mar 25, 2004 08:53 am

Date Calculation

article only

Mar 25, 2004 08:56 am

Backup and Recovery

article only

Mar 25, 2004 09:11 am

fragment issue

article only

Mar 25, 2004 09:12 am

Max string allowed in EXECUTE IMMEDIATE.

article only

Mar 25, 2004 09:12 am

cman.ora

article only

Mar 25, 2004 09:14 am

Question on indexing strategy

article only

Mar 25, 2004 09:16 am

DBMS_OBFUSCATION_TOOLKIT

article only

Mar 25, 2004 09:19 am

Backup and Recovery

article only

Mar 25, 2004 09:19 am

dbms_random.value in a subquery

article only

Mar 25, 2004 09:20 am

assige global temporary tables content to specific temporary tablespace

article only

Mar 25, 2004 09:24 am

(Single Trigger with IF condition) OR (Multiple Trigger)

article only

Mar 25, 2004 08:56 pm

Dynamic SQL to reset sequence value

article only

Mar 25, 2004 09:04 pm

How to use bind variables

article only

Mar 25, 2004 09:13 pm

Setting user information from jdbc client

article only

Mar 25, 2004 09:19 pm

Recovery Recovery

article only

Mar 25, 2004 09:23 pm

Compile procedure automatically -- how to avoid cascading invalidations

article only

Mar 25, 2004 09:23 pm

insert into Temporary tables

article only

Mar 25, 2004 09:26 pm

lob tablespace -- moving a lob

article only

Mar 25, 2004 09:28 pm

Restricting number of entries.

article only

Mar 25, 2004 09:28 pm

Questions About External Files

article only

Mar 25, 2004 09:31 pm

How to Update millions or records in a table

article only

Mar 25, 2004 09:35 pm

OEM of 10G

article only

Mar 25, 2004 09:38 pm

What is the reason that implicit cursors are faster than explicit cursors

article only

Mar 25, 2004 09:39 pm

Oracle Streams

article only

Mar 26, 2004 09:17 am

Foreign Keys

article only

Mar 26, 2004 09:20 am

locally management tablespaces

article only

Mar 26, 2004 09:23 am

exact length for ID field

article only

Mar 26, 2004 09:25 am

definition of user defined roles

article only

Mar 26, 2004 09:28 am

Why is cost based optimizer behaving poorly?

article only

Mar 26, 2004 09:31 am

Partitioning question

article only

Mar 26, 2004 09:35 am

statspack buffer gets aq$_queue_tables 50.3%Total

article only

Mar 26, 2004 09:53 am

May i uses Windows DLL in external procedure?

article only

Mar 26, 2004 09:56 am

How to predict the Explain Plan ?

article only

Mar 26, 2004 09:59 am

log file sync for cache storage

article only

Mar 26, 2004 10:14 am

PCTVERSION

article only

Mar 26, 2004 10:55 am

Difference between Transaction READ-ONLY and TRANSACTION ISOLATION LEVEL SERIALIZABLE

article only

Mar 26, 2004 10:55 am

All about the DUAL table

article only

Mar 26, 2004 11:28 am

Database not responding

article only

Mar 26, 2004 11:34 am

How To Count N Records & Stop

article only

Mar 26, 2004 06:58 pm

Binary Conversion to float

article only

Mar 26, 2004 07:10 pm

Views perform better?

article only

Mar 27, 2004 10:44 am

create a file for excel

article only

Mar 27, 2004 10:46 am

9i -- materialized views, analytics

article only

Mar 27, 2004 10:48 am

CALLING FUNCTIONS FROM VB

article only

Mar 27, 2004 10:50 am

cursor_sharing similar

article only

Mar 27, 2004 11:13 am

Primary key field datatype

article only

Mar 27, 2004 04:41 pm

How to auto increment a column - like a sqlserver Identity or Informix serial#

article only

Mar 27, 2004 04:43 pm

Reverse Key

article only

Mar 27, 2004 04:44 pm

shared_pool_size

article only

Mar 27, 2004 04:47 pm

Cross Matrix Report

article only

Mar 27, 2004 04:52 pm

Tablespace Type Change

article only

Mar 27, 2004 05:11 pm

Question Regarding System Privilege SYSDBA !

article only

Mar 27, 2004 05:14 pm

Deleting old records

article only

Mar 28, 2004 09:00 am

using & in sqlplus

article only

Mar 28, 2004 09:11 am

Pro*C

article only

Mar 28, 2004 09:14 am

Locks not getting released even after the session is killed

article only

Mar 28, 2004 05:02 pm

Using Statspack report

article only

Mar 28, 2004 05:07 pm

How can I track the execution of PL/SQL and SQL?

article only

Mar 28, 2004 05:10 pm

Misc. Questions

article only

Mar 28, 2004 05:10 pm

Constraint storage

article only

Mar 28, 2004 05:39 pm

CPU utilization

article only

Mar 28, 2004 07:24 pm

trapping transaction information

article only

Mar 29, 2004 07:13 am

Validation of a number within a VARCHAR2 field

article only

Mar 29, 2004 09:39 am

union question

article only

Mar 29, 2004 09:40 am

Question about hints

article only

Mar 29, 2004 10:02 am

Procedure vs Package

article only

Mar 29, 2004 10:03 am

Insufficient privileges

article only

Mar 29, 2004 10:11 am

Can Analytics function do this?

article only

Mar 29, 2004 10:26 am

FORALL issues...

article only

Mar 29, 2004 10:34 am

Date Field as Primary Key ...

article only

Mar 29, 2004 10:40 am

Logical Reads vs Physical Reads

article only

Mar 29, 2004 10:43 am

Assigning a DB profile to self.

article only

Mar 29, 2004 10:47 am

Fast refresh is taking longer than complete refresh of materialzed view

article only

Mar 29, 2004 11:45 am

dbms_job

article only

Mar 29, 2004 12:46 pm

Column order in Index

article only

Mar 29, 2004 12:48 pm

Displaying PDF files stored in the Database

article only

Mar 29, 2004 03:08 pm

Count(*)

article only

Mar 29, 2004 03:11 pm

Bitmap Indexes

article only

Mar 29, 2004 03:22 pm

compare range of dates with range of dates

article only

Mar 29, 2004 04:22 pm

CURSOR

article only

Mar 29, 2004 04:24 pm

sql for heirarchical relationship

article only

Mar 29, 2004 05:01 pm

Force use of index even when index value in where clause is modified by a function

article only

Mar 29, 2004 08:01 pm

CURSOR_SHARING=FORCE

article only

Mar 29, 2004 08:36 pm

Replacing procedural code with SQL

article only

Mar 30, 2004 07:49 am

Using SQL Loader

article only

Mar 30, 2004 07:57 am

ARRAY PROCESSING

article only

Mar 30, 2004 08:15 am

Global vs Sys_Context

article only

Mar 30, 2004 08:31 am

isnumeric function

article only

Mar 30, 2004 09:03 am

Import takes more time

article only

Mar 30, 2004 09:04 am

Index Organized Tables.

article only

Mar 30, 2004 10:31 am

database link

article only

Mar 30, 2004 10:55 am

Data Dict. vs Local tablespaces

article only

Mar 30, 2004 11:08 am

Database Developer Certification

article only

Mar 30, 2004 01:23 pm

Unable to change the mode of tablespace..

article only

Mar 30, 2004 02:03 pm

Unit Testing PL/SQL

article only

Mar 30, 2004 02:17 pm

Fined Grained Access Control

article only

Mar 30, 2004 06:29 pm

Update SQL Performance

article only

Mar 30, 2004 06:37 pm

Trigger v/s Procedure execution Speed (performance speed)

article only

Mar 30, 2004 06:39 pm

Concurrent upsert a problem?

article only

Mar 30, 2004 07:55 pm

how to print in unix monitor? -- extprocs, printf and fprintf

article only

Mar 31, 2004 08:22 am

Shared Memory Problem (unable to allocate shared memory ...)

article only

Mar 31, 2004 08:28 am

Set autotrace on

article only

Mar 31, 2004 08:30 am

ARC1: Beginning to archive log# 1 seq# 56947

article only

Mar 31, 2004 08:32 am

Global variables vs parameters

article only

Mar 31, 2004 08:42 am

Problem with BULK COLLECT with million rows

article only

Mar 31, 2004 08:45 am

sqlloader versus external table.

article only

Mar 31, 2004 08:46 am

using rownums & for clause in 9i

article only

Mar 31, 2004 08:48 am

Automate loading of data from Excel spreadsheets into database

article only

Mar 31, 2004 08:51 am

How to achieve this functionality?

article only

Mar 31, 2004 08:52 am

Import/export

article only

Mar 31, 2004 08:53 am

Hiding the password

article only

Mar 31, 2004 09:48 am

"minimize records_per_block" and compact bitmap indexes

article only

Mar 31, 2004 10:23 am

Retrieve Dates Between Two Dates

article only

Mar 31, 2004 10:38 am

Generate report for recreating Procedures

article only

Mar 31, 2004 10:39 am

shutdown immediate hung forever and ORA-03113

article only

Mar 31, 2004 10:40 am

Remote DB connection using "proxy"

article only

Mar 31, 2004 10:49 am

logging transactions

article only

Mar 31, 2004 10:54 am

Which Indexes are being used by our Application?

article only

Mar 31, 2004 11:26 am

Raise_application_error procedure in AFTER LOGON trigger

article only

Mar 31, 2004 11:27 am

using pl/sql to implement multiple option select

article only

Mar 31, 2004 02:59 pm

Missing query plan in trace file

article only

Mar 31, 2004 03:13 pm

Can session cached cursor improve the performance for queries with bind variable?

article only

Mar 31, 2004 03:17 pm

Difference between DB_NAME and Instance_Name

article only

Mar 31, 2004 03:21 pm

direct load not using temp tablespace

article only

Mar 31, 2004 04:01 pm

sql trace and tkprof

article only

Mar 31, 2004 04:29 pm

Returning values from Stored Procedures

article only

Mar 31, 2004 06:46 pm

SQL Query

article only

Mar 31, 2004 07:14 pm

row(s) 1 - 129 of 129

March 25, 2004 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
Rating: 4
Extending the table uses memory from which pool? February 12, 2004
Reviewer: Dale Ogilvie from New Zealand
In your example below you call l_data.extend and allocate a new object of
myScalarType. From what pool does Oracle grab memory for the new object, and
what limits might be reached in extending l_data? (Say for instance that the
loop went from 1..3000000)

  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;
Followup:
it is UGA memory which in

a) dedicated server is PGA (process) memory in the dedicated server memory space
b) shared server is SGA memory, optimally in the large pool but if no large pool
was configured will be in the shared pool

so, the limits are bounded by your connection type and then either based on the
max size a process can grow to or the size of your SGA components.
Rating: 5
Return sql table type into sql select February 17, 2004
Reviewer: Steve from Phoenix, AZ USA
I am currently using packaged functions to return collections of types which are
tables of record types.  For example:

CREATE OR REPLACE TYPE my_record_type IS OBJECT
(
 column1    VARCHAR2(200)
,column2    VARCHAR2(200)
,summary1   NUMBER
,summary2   NUMBER
,etc....      );

CREATE OR REPLACE TYPE my_table_type IS TABLE OF my_record_type;

I then retrieve my collection with a sql statement similar to the following:

SELECT * FROM TABLE( CAST(
  my_pkg.build_sql_table(:parm1, :parm2, etc.... )
AS my_table_type));

where the packaged function builds the collection using dynamic sql and bulk
collect.

This works very well as long as I stay within a single instance, but when I try
to pass the collection across a database link, it becomes much more difficult.
I define my types on both instances (say db1 and db2, where db1 is 9.0.1.4 and
db2 is 9.2.0.4), but I get a type mismatch error when I run the select.  It
seems that it wants me to use the SAME type in each instance, not just an
equivalent type.  If I try to use type@db1, I get an error saying that types
can't be used that way.

There is much, much more to this application than this, but it wouldn't be
practical to go into depth on all of the issues.  I've been working on the
application for several months during which I've read your books and this site
extensively and gotten many ideas and solutions to problems along the way, but I
haven't found any examples showing how to handle multiple instances.

I remember reading somewhere that collections can be passed across db links
whereas ref cursors cannot.  The method to do this is probably some simple
syntax trick, but I haven't been able to figure it out. Hopefully, you can shine
some light on this for me.  Thanks in advance.

Steve
Followup:
object types are not dblink friendly (like ref cursors).

you can pass PLSQL types back and forth easily however, you just create a
synonym for the remote package ( i find that easiest ) and then

is
   l_data remote_package.type_name;
   ....
Rating: 5
More on sql type.... February 17, 2004
Reviewer: Steve from Phoenix, AZ USA
Thanks much for the remarkably fast response.

When you say 'not dblink friendly', does that mean that either or both options
are not useable at all or that it's not a good idea to use them across a dblink?

Since I'm using the type in a sql statement, it's my understanding that I MUST
use a schema type.  Is that true?  Is it possible to convert my sql type
collection to a corresponding pl/sql type before passing it and convert it back
to a sql type before using it on the receiving end?
Followup:
if you want to readily pass collections back and forth across a dblink, you'll
be doing it procedurally with plsql types (eg: local procedure calls remote
procedure, gets data from remote procedure and packages it up locally)
Rating: 5
February 18, 2004
Reviewer: A reader
Rating: 3
How to bulk collect from refcursor to a table object February 18, 2004
Reviewer: Manoj from Denver,CO USA
The following compiles well,

But gives the error when you execute it
ERROR at line 1:
ORA-06504: PL/SQL: Return types of Result Set variables or query do not match
ORA-06512: at "MTEST.MKG_TEST1", line 13
ORA-06512: at line 1

create type mkgRecordType as object
    (institution_id                VARCHAR2(10) ,
    entity_id                      VARCHAR2(30) ,
    entity_acct_id                 NUMBER,
    USAGE                          VARCHAR2(30))
/

create or replace type mkgTableType
as table of mkgRecordType
/

create table mkg
    (institution_id                VARCHAR2(10) ,
    entity_id                      VARCHAR2(30) ,
    entity_acct_id                 NUMBER,
    USAGE                          VARCHAR2(30))
/

create or replace procedure mkg_test(mref OUT SYS_REFCURSOR)
as
begin
  open mref for
  select institution_id,
         entity_id,
         entity_acct_id,
         USAGE
  from mkg;
end;
/

PROCEDURE MKG_TEST1
as
 mref sys_refcursor;
l_data    mkgTableType   :=mkgTableType();
mrec mkg%rowtype;
begin

   mkg_test(mref);

   loop
     l_data.extend ;
        l_data(l_data.count) := mkgRecordType(null,null,null,null);
       fetch mref bulk collect into l_data;
       Exit when mref%NOTFOUND or mref%NOTFOUND Is  NULL ;
   end loop;
  close mref;

end;
Followup:
well, they do not match

the ref cursor selects 4 scalars.

the fetch tries to fetch (with a bulk collect no less) all 4 scalars into a
single object (not 4 scalars).

whats the goal here, what do you need to do?
Rating: 3
But can you bulk collect from an ARRRY into a table March 15, 2004
Reviewer: KDB from Colorado
I have a similar problem, howerver I need to buik insert into a table from an
array but get a similar error.

Example

RPT_PROP_MERCH_ARRAY ARRAY of ARRAY(2) of VARCHAR2(10)
   PROCEDURE prc_merch_qual(on_success        OUT NUMBER,
                            ovc_err_msg       OUT VARCHAR2,
                            id_start_date      IN DATE,
                            id_end_date        IN DATE,
                            iarr_prop_merch    IN RPT_PROP_MERCH_ARRAY)
   AS
      ld_processing_date date;
      ln_success number;
      ln_success2 number;
      lvc_test1 varchar2(10);
      lvc_test2 varchar2(10);
   BEGIN
   --- Insert Records from Array into Table for Better Performance on Queries

      for i IN iarr_prop_merch.first..iarr_prop_merch.last
      LOOP
             insert into mms_gtmp_prop_merch values
(iarr_prop_merch(i)(1),iarr_prop_merch(i)(2));
      END LOOP;

      forall i IN iarr_prop_merch.first..iarr_prop_merch.last
             insert into mms_gtmp_prop_merch values
(iarr_prop_merch(i)(1),iarr_prop_merch(i)(2));

END;

Thanks
Keith
Followup:
not sure what RPT_PROP_MERCH_ARRAY ARRAY of ARRAY(2) of VARCHAR2(10) represents
there.

But forall processing is going to want contigous stuff, assuming that is "arrays
of arrays", they won't be contigous segments of data.  You'll need two arrays
there.
Rating: 5
How to execute this code March 24, 2004
Reviewer: Jennifer Chen from Viena, VA USA
Hi Tom,

Your demo code is very helpful. I modified it slightly and am having trouble to
test it:

CREATE OR REPLACE TYPE t_rspweb AS OBJECT
(
 request_number        NUMBER(10),
 dob                    DATE,
 date_received          DATE,
 date_sent              DATE,
 nam                    VARCHAR2(30),
 prn                    VARCHAR2(7),
 response_data          CLOB,
 sid                    VARCHAR2(8),
 user_id                VARCHAR2(15),
 viewed_flag            CHAR(1),
 clob_length        NUMBER(10)
)
/

CREATE OR REPLACE TYPE a_rspweb AS TABLE OF t_rspweb
/
SHOW errors

CREATE OR REPLACE PACKAGE alias.qdmsg_pkg
......

CREATE OR REPLACE PACKAGE BODY alias.qdmsg_pkg
AS
   FUNCTION mod_rsp_web (p_userid IN alias.rsp_web.user_id%TYPE)
      RETURN a_rspweb
   AS
      v_data     a_rspweb := a_rspweb ();
      v_length   NUMBER;
   BEGIN
      FOR x IN (SELECT request_number, dob, date_received, date_sent, nam,
                       prn, response_data, SID, user_id, viewed_flag
                  FROM alias.rsp_web
                 WHERE user_id = p_userid)
      LOOP
         v_data.EXTEND;
         v_length := DBMS_LOB.getlength (x.response_data);
         v_data (v_data.COUNT) :=
            t_rspweb (x.request_number,
                      x.dob,
                      x.date_received,
                      x.date_sent,
                      x.nam,
                      x.prn,
                      x.response_data,
                      x.SID,
                      x.user_id,
                      x.viewed_flag,
                      v_length
                     );

         UPDATE alias.rsp_web
            SET viewed_flag = 'Y'
          WHERE request_number = x.request_number;
      END LOOP;

      RETURN v_data;
   END mod_rsp_web;

   PROCEDURE get_rsp_web_by_userid (
      p_userid      IN       alias.rsp_web.user_id%TYPE,
      p_refcursor   OUT      sys_refcursor
   )
   AS
   BEGIN
      OPEN p_refcursor
       FOR
          SELECT /*+ CARDINALITY(T 10 ) */
                 *
            FROM TABLE (CAST (mod_rsp_web (p_userid) AS a_rspweb)) t
           WHERE ROWNUM >= 0;
   END get_rsp_web_by_userid;
END qdmsg_pkg;
/
SHOW errors

SQL> variable c refcursor
SQL> set autoprint on
SQL> exec qdmsg_pkg.get_rsp_web_by_userid('CHEN', :c);
BEGIN qdmsg_pkg.get_rsp_web_by_userid('CHEN', :c); END;

*
ERROR at line 1:
ORA-14551: cannot perform a DML operation inside a query
ORA-06512: at "ALIAS.QDMSG_PKG", line 35
ORA-06512: at "ALIAS.QDMSG_PKG", line 77
ORA-06512: at line 1

ERROR:
ORA-24338: statement handle not executed

SP2-0625: Error printing variable "c"

Or if I do this:

SQL> DECLARE
  2    P_USERID VARCHAR2(200);
  3    P_QUERY_ALL NUMBER;
  4    P_REFCURSOR sys_refcursor;
  5
  6  BEGIN
  7    P_USERID := 'CHEN';
  8    P_REFCURSOR := C;
  9
 10    ALIAS.QDMSG_PKG.GET_RSP_WEB_BY_USERID ( P_USERID, P_QUERY_ALL,
P_REFCURSOR );
 11    COMMIT;
 13  END;
 14  /
  P_REFCURSOR := C;
                 *
ERROR at line 9:
ORA-06550: line 9, column 18:
PLS-00201: identifier 'C' must be declared
ORA-06550: line 9, column 3:
PL/SQL: Statement ignored

Would you please help?

Thank you for the demo code and this wonderful site.
Followup:
you cannot do modifications in a SELECT like that (unless you use an autonomous
transaction but that would probably be a "bug" on your part to do so)

Tell me, what is your "goal" here.
Rating: 5
Thank you March 24, 2004
Reviewer: Jennifer Chen from Viena, VA USA
Hi Tom,

Thank you for taking your time to look into this.

I would need to return the following data set and also modify each viewed_flag
in the data set:

SELECT request_number, dob, date_received, date_sent, nam,
       prn, response_data, SID, user_id, viewed_flag
  FROM alias.rsp_web
 WHERE user_id = p_userid

Thanks again for your time and help.
Followup:
You would have to use an autonomous transaction for that and it would be WHOLLY
unreliable.

with an array fetch (most all clients do that) the client might see the first
row (but have fetched 10 of them) and never see the next 9.

Or worse (and probably more common) the optimizer will use a plan that "sees"
lots more records then are returned -- hence leading to "false" viewed updates.

soooo -- whats the goal, what do you really need to do here?  Perhaps a result
set is NOT the right way and a stored procedure that feeds a single record using
OUT parameters is.
March 25, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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://download-west.oracle.com/docs/cd/B10501_01/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

Rating: 4
February 28, 2004
Reviewer: A Reader
Hi Tom,
I installed your runstats package successfully and tried your example above but
I don't get any output or errors after running runStats.stop.  Did I miss
something?
Thanks!

test> exec runStats_pkg.rs_start;
PL/SQL procedure successfully completed.

test> alter session set session_cached_cursors=0;
Session altered.

test> 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.

test> exec runStats_pkg.rs_middle;
PL/SQL procedure successfully completed.

test> alter session set session_cached_cursors=100;
Session altered.

test> 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.

test> exec runStats_pkg.rs_stop(100)
PL/SQL procedure successfully completed.
Followup:
test> SET SERVEROUTPUT ON SIZE 1000000
Rating: 5
March 24, 2004
Reviewer: A reader
Tom,

Environment is RAC 2 nodes 9.2.0.3
Middle Tier is weblogic with connection pooling.

I noticed value of soft parse is ~1.4 million and the database has been up for 4
days..

current session_cached_cursors value is set to 0. Will i benefit if i change my
session_cached_cursors to 100 or more. Memory is not a issue..

Thanks.
Followup:
probably -- but, only if latch contention on the library cache is

a) a problem
b) your problem
c) a material part of your problem (eg: accounts for some measurable time)

If not, you won't see a thing change really.

(in order to tune, you need to find your problems and these are always found by
tracing the problem/most important APPLICATIONS (not 'system'))
Rating: 5
March 25, 2004
Reviewer: A reader
Thanks.
March 25, 2004 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.
Rating: 4
Whats the difference between ROUND and TRUNC February 04, 2004
Reviewer: Sonali from waltham, ma
If I do

Select TRUNC(sysdate) from dual;

and
Select ROUND(Sysdate) from dual;

Whats the difference in these 2 ?

Thanks
Followup:
ops$tkyte@ORA10G> select sysdate, trunc(sysdate), round(sysdate) from dual;

SYSDATE              TRUNC(SYSDATE)       ROUND(SYSDATE)
-------------------- -------------------- --------------------
04-feb-2004 17:54:21 04-feb-2004 00:00:00 05-feb-2004 00:00:00

trunc -- well, it truncs....

round -- well, just like rounding a number, rounds up or down...
Rating: 5
Thanks, you are great February 05, 2004
Reviewer: sonali from waltham, ma
Rating: 5
Want to display 1 Year 6 Months <instead of 1.5 Years> March 24, 2004
Reviewer: Sami from NJ,USA
Dear Tom,
<From PreviousPost>
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
</From PreviousPost>

I want to display 1 year and N months and M Days instead of displaying 1.9
Years. Basically I want display all round numbers not decimals.

Is it possible to do it in single select?
Followup:
using the CASE statement, decode, to_chars, whatever , sure
http://otn.oracle.com/oramag/oracle/03-nov/o63asktom.html
shows a concept.  You just need to write the code....

Something like

select trunc( months_between(a,b)/12 ) || ' years and ' ||
       mod(months(between(a,b),12) || ' months....'

as well would work.
March 25, 2004 Mike -- Thanks for the question regarding "Backup and Recovery", version 8.1.7
You Asked
Tom,

In responding to a question you mentioned that when you take a cold backup in
NoArchiveLog Mode, it is a bad idea to include redo log files, in case you later
decided to put the database in archivelog mode.
- I understand that if we have to recover a database in NoArchiveLog mode we
will have to recover to our last full backup. How is this possible if we do not
have the redo log files included in our last full-backup.

When I run the following query:
SELECT group#, sequence#, members, archived, status
FROM v$log;
- My archived column is saying yes even though I am running a test database in
NoArchivelog mode, when I Alter System Switch Logfile the column for archived
changes to No?

When running the database in Archivelog Mode:
Why do we recover both datafiles and CONTROL FILES when using a binary backup of
the control file when restoring a tablespace?

Do hot backups by definition = Inconsistent backups?

When using RMAN, why are channels sometimes named and sometimes not named? What
is the significance of naming a channel?

Thank you,
Mike
and we said...
You will simply open the database with RESETLOGS.  We'll recreate them empty for
you.

Ignore all things relating to "archive log mode" features in noarchive log mode.

You recover the control files along with the datafiles in noarchivelog mode
since you are putting the database BACK to that point in time.  controlfiles
from today are not useful with a database from yesterday -- we need the old
controlfiles in that case.

hot backups are fuzzy or inconsistent and will need recovery since people are
constantly writing to the files whilst we are backing them up.

from the rman guide:

<quote>
name 'channel_name'

specifies the name of a sequential I/O device. If you do not specify a device
name, then the system uses any available device of the specified type. Do not
use this parameter in conjunction with the type parameter.

Currently, no platform supports the name parameter.
</quote>
Review & Followup

Rating: 4
follow up April 09, 2002
Reviewer: Ahmed from India
Yes, I know that u hate cold backup. But Can we run the Database in Archivelog
mode and instead of hot backup I take
Cold backup everyday. Incase of recovery can we restore last
cold backup and apply the archived redo log to restore the DB witout losing any
data.
Followup:
uh-oh.  another malfunctioning keyboard.  it is dropping the Y and the O
sometimes, not always.

I don't hate cold backups -- I just don't see the point in them if you are in
archive log mode.

You can take a cold backup whenever you want.  Here is what you will achieve by
doing so:

o you will cause downtime (the database will be shutdown)

o you will empty the buffer cache, causing us to refill it every day, incurring
more work every day.

o you will empty the shared pool, causing us to reparse the entire set of
queries every day, day in -- day out.

o you will most likely do an unattended shutdown and startup in order to do this
cold backup.  cold backup typically implies unattended shutdown/startup

you know how lightbulbs always seem to burn out when we turn them on?  Leave
them on and they are good to go?  Databases seem to be the same sometimes.

How many times have we seen unattended shutdowns take way too long to fit into
the backup window (or never complete at all for some reason or another).  Or the
database where the init.ora was changed, a bad value put in and hence doesn't
restart after the backup?

Just like I won't reboot my solaris box remotely (been burned by that before --
"fschk failed, please hit ctl-d to continue", problem is no one is there to hit
ctl-d, I don't like to shutdown/startup unattended.  People just get mad when
the database isn't up the next morning -- or hasn't yet shutdown.  the net
result -- looks like oracle downtime.

A hot backup removes that worry.  True, you could use a shutdown abort,
startup/shutdown cycle to remove many of the issues with unattended shutdowns
but not all (database won't restart without someone kicking something for
example)...

I think a hot backup, especially with rman (don't even have to put it into
backup mode) is easier then a cold for this reason.
On the positive side -- oh wait, there isn't one really.  I cannot think of any
upside to doing cold backups over hot.

So, the answer to your question is YES, you can do a cold backup and do point in
time recovery with it.

My question to you is WHY, it is not any easier to recover with, it is not any
harder to recover with.  It only adds problems and complexity to your system and
gives you nothing positive in return.  Why do you want to do something that will
cause you grief?  That database will get stuck in a shutdown some day -- I am
100% sure of that.  That will be the day you are late getting to work and there
are 100 really mad people waiting for you.
Rating: 4
Good answer... May 22, 2002
Reviewer: Randy from Austin, TX
If you took a previous cold backupset and tried to apply archived logs, wouldn't
you need to keep the current control file?  I thought the SCN in the control
would have to be the most recent to be able to apply logs.  Otherwise, wouldn't
it think it was consistent?
Followup:
....
You recover the control files along with the datafiles in noarchivelog mode
.......

I wasn't talking about archive log mode -- but yes, you are correct, in archive
log mode, you don't want to restore the control files.  In noarchive log mode,
when doing a restore, you do.
Rating: 3
We tried to do cold backup without redo logs, but ah... May 24, 2002
Reviewer: Nadya from Russia
We have done cold backup database,working in
NoArchiveLog Mode (after shutdown immediate) without redo log files and decided
to put the database in archivelog mode on test server.
The steps were:
Test server is fully identifical the production server.
Change init.ora (add parameters for archivelog mode)
startup mount
alter database archivelog
alter database open resetlogs
And received error:
ORA-01139 RESETLOGS option only valid after incomplete recovery.
 What is wrong?
Thanks, Nadya
Followup:
do a cancel based recovery and then open with resetlogs.

SVRMGR> shutdown
Database closed.
Database dismounted.
ORACLE instance shut down.
SVRMGR> startup mount
ORACLE instance started.
Total System Global Area                         72876192 bytes
Fixed Size                                          73888 bytes
Variable Size                                    64430080 bytes
Database Buffers                                  8192000 bytes
Redo Buffers                                       180224 bytes
Database mounted.
SVRMGR> alter database open resetlogs;
alter database open resetlogs
*
ORA-01139: RESETLOGS option only valid after an incomplete database recovery
SVRMGR> recover until cancel
Media recovery complete.
SVRMGR> alter database open resetlogs;
Statement processed.
Rating: 4
sorry for your time May 24, 2002
Reviewer: nadya from russia
Sorry,sorry,sorry...
We have missed statement
recover database until CANCEL
And now we have all O'KEY.
Rating: 4
Reader May 24, 2002
Reviewer: A reader
vvvvvvvvvvvvvvvvery nifty
Rating: 5
Backup and Recovery April 29, 2003
Reviewer: acpanugayan from Philippines
Do i need to take hot backups of the following tablespaces:

1.Rollback
2.Temp

I currently don't as advised by a more senior 'DBA' but another DBA told me that
i should backup the Rollback tbs because some instances requires linkage to the
RBS data.
Followup:
1) rollback -- yes, yes and yes -- definitely YES.

2) temp -- not if it is a CREATE TEMPORARY TABLESPACE TEMP tempfile.

that more "senior" DBA is either having a "senior moment" and forgetting the
most basic of basics -- or they aren't as senior as they purport.
Rating: 1
This was not the question I sent April 29, 2003
Reviewer: Michael McGinty from Charlotte, NC
Last night when I was in the process of posting my question. I clicked on the
Send button and was told that you were no longer receiving questions.
The questions I had meant to ask are as follows:

Hi Tom,

First, I am working with Oracle Flashback on a test database. I issued the
dbms_flashback.get_system_change_number to get the current SCN. I then performed
one update statement, waited a couple of minutes and executed
dbms_flashback.get_system_change_number again. The SCN increased by
approximately 200 integers. In previous questions you have stated that a SCN
increments when a transaction completes. Has something changed in 9i? What
specific processes could be causing the SCN to increment this way?

Second, I know that in a 9i database it is best to use automatic
undo_management. However, how does one move back to using manual RBS's. I tried
setting undo_management=manual and alter system set undo_tablespace=''; However,
when I try to create a rollback segment I am told that I cannot use the System
Tablespace to create a non-system segment?

Is there an easy way to get archive logs moved out of OEM?

Thank you in advance for your help.

Mike
Followup:
aq, smon, other users, lots of background stuff happing there - to limit it, set
aq_tm_processes = 0, job_queue_processes = 0.  that won't stop smon but it'll
limit it.

you need to create another non-system rbs in system first.  create a rbs in
SYSTEM, then create them, then drop that rbs.

archived logs are not "in" OEM?  i don't understand what it means for a log to
be "in" OEM.
Rating: 3
April 30, 2003
Reviewer: anita from India
Can u please tell about what is archieve log mode  and
nonarchieve log mode and please differentiate them.
Followup:
very simply:

archivelog mode is the mode whereby your data is recoverable.
noarchivelog mode is the mode whereby you will lose all of your data someday.

you decide which is acceptable to you.
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96524/c01_02intro.htm#50912
Rating: 4
recovery using new control file ... May 03, 2003
Reviewer: Abhi Malhotra from INDIA
hello sir,

    Normally you recommend that we should use current controle file, if we don't
have then we can use old controle file and if we have not even old controle file
then we have to make new controle file. But sir i think that in some scenario we
should create create new controle file, may be i am wrong.

1.
   Suppose i shut down my database normally and accidendly file is deleted and
the controle file which i having is 15 days old. Then if i want to recover my
database using this file then i have to apply 15 day's archive logs and have to
open database with "Resetlogs". and i create new controle file (Because my DB is
normally shutdown) then  i don't need to apply archive logs and i can open my
database without "Resetlogs"

Sir, even in this scenario , is it better to use old controle file, if yes then
why ?

Sir, actually i have no clearity about it so please tell me that is there only
drawback of using new controle file that if DBA creates a new controle file and
he missed any data file and opened the database with "Resetlogs" then data will
be lost.

2.
   In this Scenario, suppose i have only one tablespace SYSTEM no undo or no
temp ... tablespace

       T1     T2             T3           T4

       |      |              |            |
       |      |              |            |
       |      |              |            |
       |      |              |            |
       |      |              |            |
       |      |              |          |
-----------------------------------------------------
 Cold Bckp   bakp control   Hot backup    Lost System
             file binray    system        datafile and
             Control file   datafile      all controlfiles

T1 -->   Cold Bckp

T2 -->   backup control file binary

T3 -->   hot backup system datafile ..

T4 -->   Lost system datafile and all controlfiles

i have all archivelogs and redo logs till T4.

only have t2 controlfile ..

sir in this scnario how can i recover my database ..
 [should i use_of_new_controlfile= Applicable/Not Applicable ]

Regards ...
Followup:
read the scenarios -- they walk you step by step through the process.
http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96572/osrestore.htm#26899
Rating: 3
Re: May 08, 2003
Reviewer: abhi from INDIA
Thanx Sir for ur reply ...

 I have already gone through this link...
 but i think  second scenario [2]  is not there .. and
 Still i have some doubts..

sir .. suppose i have two redo group [ 1 mb each ] and at time "A" i had taken
hot backup  ... this time current sequence is 50 .. and scn 50000 . after that i
issued a transaction due to which oracle switch log file 10 time mean  current
sequence = 60 and curr scn = 60000 .. sir while doing incompelete recovery can i
issue
"Alter database until cance" and cancel recovery at any archive log  between  50
and 60 ..  log ...
(.. i tried this ) ..  i was unable to open my database..
error --> "need more recover " sir its mean i can't open database between scn
50000 and 60000 or log 50 to 60.
then sir what is use of "until cance " clause it might be possible database will
be consist at any log log switch...
and sir if i m right mean oracle can open database at any consist scn no... mean
after and before any transaction .. sir if this is true ... then how can open my
database if i don't know any consist scn .

2. and sir at time a hot backup was taken .. and time b controlfile was backedup
.. and at time c harddisk crashed and all controlfiles and datafiles were
damaged .
i have all archive log file just begore time C.

Sir can i do incompelete recover using b time controlfile
and a time datafiles ... .. mean consist time where database can open between
time "A" and "B" --> [incompelete Recovery ]

or can i do imcompelete recovery between time "B" and "C" [ just before C] ..
sir i tried this ..

i was unable to open database between "a" and "B" .. whenever i tried to open
database [ using "until cancel " ]  . .oracle says .. "File 1 need more recovery
"

and if i tried to go between time "B" and "C" oracle tried to apply log files ..
...

sir pls help me ..

Thax for ur patience ..
Followup:
that means you used datafiles that are NEWER then the point in time you tried to
cancel.

all of the datafiles restored to this system must PREDATE the point in time you
want to 'stop' recovering.
Rating: 4
read only datafiles June 05, 2003
Reviewer: Reader
In earlier versions, if I used backup controlfile to recover and if that backup
controlfile indicates the status of a data file is READ ONLY, i had to take the
file offline before recovery and PUT THE READONLY tablespace online after
startup. Otherwise, oracle would not let me proceed with the recovery without
taking the read only datafile offline. However, when I tried with version 9.2.0,
it is not case. It did let me perform the recovery. Is the behavior of oracle
changed with respect to read only files in 9.2.0. Please see below. Thanks for
your time.

SQL> create tablespace readonly
  2  datafile 'C:\oracle\oradata\practice\readonly01.dbf' size 2M
  3  uniform size 128k;

Tablespace created.

SQL> alter tablespace readonly read only;

Tablespace altered.

SQL> connect /as sysdba;
Connected.

SQL> archive log list
Database log mode              Archive Mode
Automatic archival             Enabled
Archive destination            C:\oracle\oradata\archivepractice\
Oldest online log sequence     0
Next log sequence to archive   1
Current log sequence           1
SQL> alter system switch logfile;

System altered.

SQL> /

System altered.

SQL> archive log list
Database log mode              Archive Mode
Automatic archival             Enabled
Archive destination            C:\oracle\oradata\archivepractice\
Oldest online log sequence     1
Next log sequence to archive   3
Current log sequence           3

SQL> col name for a55
SQL> select name,status,enabled from v$datafile;

NAME                                                    STATUS  ENABLED
------------------------------------------------------- ------- ----------
C:\ORACLE\ORADATA\PRACTICE\SYSTEM01.DBF                 SYSTEM  READ WRITE
C:\ORACLE\ORADATA\PRACTICE\UNDOTBS01.DBF                ONLINE  READ WRITE
C:\ORACLE\ORADATA\PRACTICE\CWMLITE01.DBF                ONLINE  READ WRITE
C:\ORACLE\ORADATA\PRACTICE\DRSYS01.DBF                  ONLINE  READ WRITE
C:\ORACLE\ORADATA\PRACTICE\EXAMPLE01.DBF                ONLINE  READ WRITE
C:\ORACLE\ORADATA\PRACTICE\INDX01.DBF                   ONLINE  READ WRITE
C:\ORACLE\ORADATA\PRACTICE\ODM01.DBF                    ONLINE  READ WRITE
C:\ORACLE\ORADATA\PRACTICE\TOOLS01.DBF                  ONLINE  READ WRITE
C:\ORACLE\ORADATA\PRACTICE\USERS01.DBF                  ONLINE  READ WRITE
C:\ORACLE\ORADATA\PRACTICE\XDB01.DBF                    ONLINE  READ WRITE
C:\ORACLE\ORADATA\PRACTICE\READONLY01.DBF               ONLINE  READ ONLY

11 rows selected.

SQL> create table t (x number)
  2  tablespace users;

Table created.

SQL> insert into t values(200);

1 row created.

SQL> commit;

Commit complete.

SQL> alter system switch logfile;

System altered.

SQL> /

System altered.

SQL> archive log list
Database log mode              Archive Mode
Automatic archival             Enabled
Archive destination            C:\oracle\oradata\archivepractice\
Oldest online log sequence     3
Next log sequence to archive   5
Current log sequence           5
SQL> alter system switch logfile;

System altered.

SQL> archive log list
Database log mode              Archive Mode
Automatic archival             Enabled
Archive destination            C:\oracle\oradata\archivepractice\
Oldest online log sequence     4
Next log sequence to archive   6
Current log sequence           6
SQL> shutdown abort
ORACLE instance shut down.

NOTE: I HAVE REPLACED THE CURRENT CONTROL FILES WITH BACKUP ONES.

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.
ORA-01122: database file 1 failed verification check
ORA-01110: data file 1: 'C:\ORACLE\ORADATA\PRACTICE\SYSTEM01.DBF'
ORA-01207: file is more recent than controlfile - old controlfile

SQL> select name,open_mode from v$database;

NAME                                                    OPEN_MODE
------------------------------------------------------- ----------
PRACTICE                                                MOUNTED

SQL> col error for a10
SQL> select * from v$recover_file;

     FILE# ONLINE  ONLINE_ ERROR         CHANGE# TIME
---------- ------- ------- ---------- ---------- ---------
         1 ONLINE  ONLINE  UNKNOWN        203235 05-JUN-03
                           ERROR

         2 ONLINE  ONLINE  UNKNOWN        203235 05-JUN-03
                           ERROR

         3 ONLINE  ONLINE  UNKNOWN        203235 05-JUN-03
                           ERROR

         4 ONLINE  ONLINE  UNKNOWN        203235 05-JUN-03
                           ERROR

     FILE# ONLINE  ONLINE_ ERROR         CHANGE# TIME
---------- ------- ------- ---------- ---------- ---------

         5 ONLINE  ONLINE  UNKNOWN        203235 05-JUN-03
                           ERROR

         6 ONLINE  ONLINE  UNKNOWN        203235 05-JUN-03
                           ERROR

         7 ONLINE  ONLINE  UNKNOWN        203235 05-JUN-03
                           ERROR

         8 ONLINE  ONLINE  UNKNOWN        203235 05-JUN-03

     FILE# ONLINE  ONLINE_ ERROR         CHANGE# TIME
---------- ------- ------- ---------- ---------- ---------
                           ERROR

         9 ONLINE  ONLINE  UNKNOWN        203235 05-JUN-03
                           ERROR

        10 ONLINE  ONLINE  UNKNOWN        203235 05-JUN-03
                           ERROR

SQL> recover database;
ORA-00283: recovery session canceled due to errors
ORA-01122: database file 1 failed verification check
ORA-01110: data file 1: 'C:\ORACLE\ORADATA\PRACTICE\SYSTEM01.DBF'
ORA-01207: file is more recent than controlfile - old controlfile

SQL> recover database using backup controlfile; <---- IT DID NOT SAY ora 01233
THE FILE IS READ ONLY .....
ORA-00279: change 202309 generated at 06/05/2003 15:11:47 needed for thread 1
ORA-00289: suggestion : C:\ORACLE\ORADATA\ARCHIVEPRACTICE\ARCH_3.ARC
ORA-00280: change 202309 for thread 1 is in sequence #3

Specify log: {<RET>=suggested | filename | AUTO | CANCEL}

ORA-00279: change 203233 generated at 06/05/2003 15:37:53 needed for thread 1
ORA-00289: suggestion : C:\ORACLE\ORADATA\ARCHIVEPRACTICE\ARCH_4.ARC
ORA-00280: change 203233 for thread 1 is in sequence #4
ORA-00278: log file 'C:\ORACLE\ORADATA\ARCHIVEPRACTICE\ARCH_3.ARC' no longer
needed for this recovery

Specify log: {<RET>=suggested | filename | AUTO | CANCEL}

ORA-00279: change 203235 generated at 06/05/2003 15:37:55 needed for thread 1
ORA-00289: suggestion : C:\ORACLE\ORADATA\ARCHIVEPRACTICE\ARCH_5.ARC
ORA-00280: change 203235 for thread 1 is in sequence #5
ORA-00278: log file 'C:\ORACLE\ORADATA\ARCHIVEPRACTICE\ARCH_4.ARC' no longer
needed for this recovery

Specify log: {<RET>=suggested | filename | AUTO | CANCEL}

ORA-00279: change 203323 generated at 06/05/2003 15:38:33 needed for thread 1
ORA-00289: suggestion : C:\ORACLE\ORADATA\ARCHIVEPRACTICE\ARCH_6.ARC
ORA-00280: change 203323 for thread 1 is in sequence #6
ORA-00278: log file 'C:\ORACLE\ORADATA\ARCHIVEPRACTICE\ARCH_5.ARC' no longer
needed for this recovery

Specify log: {<RET>=suggested | filename | AUTO | CANCEL}

ORA-00308: cannot open archived log
'C:\ORACLE\ORADATA\ARCHIVEPRACTICE\ARCH_6.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.

SQL> select * from v$log;

    GROUP#    THREAD#  SEQUENCE#      BYTES    MEMBERS ARC STATUS
---------- ---------- ---------- ---------- ---------- --- ----------------
FIRST_CHANGE# FIRST_TIM
------------- ---------
         1          1          2  104857600          1 YES INACTIVE
       202140 05-JUN-03

         2          1          3  104857600          1 NO  CURRENT
       202142 05-JUN-03

         3          1          1  104857600          1 YES INACTIVE
       190578 05-JUN-03

SQL> select * from v$logfile;

    GROUP# STATUS  TYPE
---------- ------- -------
MEMBER
--------------------------------------------------------------------------------
         3         ONLINE
C:\ORACLE\ORADATA\PRACTICE\REDO03.LOG

         2         ONLINE
C:\ORACLE\ORADATA\PRACTICE\REDO02.LOG

         1         ONLINE
C:\ORACLE\ORADATA\PRACTICE\REDO01.LOG

SQL> recover database using backup controlfile;
ORA-00279: change 203323 generated at 06/05/2003 15:38:33 needed for thread 1
ORA-00289: suggestion : C:\ORACLE\ORADATA\ARCHIVEPRACTICE\ARCH_6.ARC
ORA-00280: change 203323 for thread 1 is in sequence #6

Specify log: {<RET>=suggested | filename | AUTO | CANCEL}

C:\ORACLE\ORADATA\PRACTICE\REDO02.LOG <---------- CURRENT REDOLOG FILE PATH

Log applied.
Media recovery complete.
SQL> alter database open;
alter database open
*
ERROR at line 1:
ORA-01589: must use RESETLOGS or NORESETLOGS option for database open

SQL> alter database open resetlogs;

Database altered.

SQL> select * from t;

         X
----------
       200

SQL> col name for a55
SQL> select name,status,enabled from v$datafile;

NAME                                                    STATUS  ENABLED
------------------------------------------------------- ------- ----------
C:\ORACLE\ORADATA\PRACTICE\SYSTEM01.DBF                 SYSTEM  READ WRITE
C:\ORACLE\ORADATA\PRACTICE\UNDOTBS01.DBF                ONLINE  READ WRITE
C:\ORACLE\ORADATA\PRACTICE\CWMLITE01.DBF                ONLINE  READ WRITE
C:\ORACLE\ORADATA\PRACTICE\DRSYS01.DBF                  ONLINE  READ WRITE
C:\ORACLE\ORADATA\PRACTICE\EXAMPLE01.DBF                ONLINE  READ WRITE
C:\ORACLE\ORADATA\PRACTICE\INDX01.DBF                   ONLINE  READ WRITE
C:\ORACLE\ORADATA\PRACTICE\ODM01.DBF                    ONLINE  READ WRITE
C:\ORACLE\ORADATA\PRACTICE\TOOLS01.DBF                  ONLINE  READ WRITE
C:\ORACLE\ORADATA\PRACTICE\USERS01.DBF                  ONLINE  READ WRITE
C:\ORACLE\ORADATA\PRACTICE\XDB01.DBF                    ONLINE  READ WRITE
C:\ORACLE\ORADATA\PRACTICE\READONLY01.DBF               ONLINE  READ ONLY
Rating: 5
Coldbackup and Archivelogs June 27, 2003
Reviewer: Rajendra Majali from India
I have coldbackup and all archive log files after the coldbackup. My server is
crashed, all my data is lost. I want to do complete recovery using this set of
backup on other machine or atleast till last available archive log file.Is it
possible? Is any document available for this?
Followup:
Yes it is.

Yes there is.

I'll just have to "guess" on the version, say it is 8i:
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/index.htm
you are looking for the backup and recovery guide (similar docs are shipped with
every release)
Rating: 5
Recreate same db with hot backup. Arch logs not necessary November 21, 2003
Reviewer: Beth from Grand Blanc, MI
I have created a copy of a db from Prod onto a Dev system, by simply tarring it
up, transferring over the cold backup, untarring it, and starting up the db.

I need to do the same with a hot backup now.  The archives are not really
necessary....I just need a copy of the db, as of the last hot backup.

How would the steps differ?

Make hot backup (inc. datafiles, control files, logfiles).
Transfer all files to new system.
Restore hot backup. (Untar the files in the correct directory structure?)
Since archive logs are not needed there is no need to RECOVER UNTIL CANCEL or
similar.  Or just a RECOVER DATABASE command?
Would I need to open resetlogs?
And the Control file?
Followup:
you need

a) the hot backup of the datafiles
b) all of the logs generated during the backup

eg:

alter system switch logfile
make sure all are archived -- remember last archive (call it "X")
hot backup
backup controlfile
alter system switch logfile
make sure all are archived -- grab ALL archives AFTER X

that makes up your restore set.  (there are archives that are needed, the
database is CONSTANTLY doing stuff, even if you are not!!!).  do a cancel based
recovery and open resetlogs.
Rating: 5
old rman backups of any use? November 22, 2003
Reviewer: reader from pa
Are the RMAN backup sets that are no longer part of the catalog of any use?
Assuming the knowledge of these backups was no longer in the current control
file, and the catalog was not used or not available, is their any recoverability
from old rman backup sets?  I ask because I believe the answer is no, unless you
had the old control file that was aware of them or had autocontrolfile backup
turned on when those backup sets were taken.  There is no other way to make a
catalog or control file aware of the presence and contents of backup sets,
right?
Rating: 2
Am I right? December 12, 2003
Reviewer: Prince Faran from Pakistan
Respected Tom!

I got somewhat confused after reading this page.

I have a database which remains up for the whole day and I do it shutdown at 5'
o clock evening and start it again at 8' o clock morning. This Oracle 9.2.0.1.0
database is running in archivelog mode on a Windows 2000 Server. I want to move
this database from this machine say A to another machine say B with same
disk/drive configuration. Which of the options will be suitable for me...

************
* OPTION I *
************

1) shutdown normal
2) create a database using Database Configuration Assistant onto machine B with
the same database name on the same drive (name).
3) shutdown newly created database onto machine B
4) copy all control files from machine A to machine B.
5) copy all data files from machine A to machine B.
6) copy parameter file from machine A to machine B.
7) start the database on machine B.

*************
* OPTION II *
*************

1) shutdown normal
2) create a database using Database Configuration Assistant onto machine B with
the same database name on the same drive name.
3) shutdown newly created database onto machine B
4) copy all control files from machine A to machine B.
5) copy all data files from machine A to machine B.
6) copy all redolog files from machine A to machine B.
7) copy parameter file from machine A to machine B.
8) start the database on machine B.

Yes, step # 6 (of Option II) is confusing me either should I restore the online
redo logs from the old machine to the new one -or- not. Will there be need of
any command at the end like recover database -or- just startup will be enough on
machine B.

Thanks,
Prince
Followup:
why in the world would you shut it down like that! arg.

(don't forget your init.ora files!!!)

option 1 works (open resetlogs).

option 2 works (normal startup)

option 2 is "easiest" but there is an option 3

use oradim to register the necessary services.  restore your oracle files from
server a to server b (the $ORACLE_HOME/admin/ directory tree, your dbs directory
and your data/redo/control files).

(this question indicates you might not know how to restore from backups in the
event of a failure.  If you are the DBA, this is the one thing you are not
permitted to "not know how to do in your sleep". (hence you have some reading to
catch up on and now that you have two machines, some heavy duty practicing to
perform)  If you are not the DBA, they are the ones that should be doing this)
Rating: 5
Hot vs. Cold Backup before a database migration December 18, 2003
Reviewer: Gary from Indianapolis, IN USA
Thanks for your tireless efforts on this site, they are greatly appreciated.

As a close follow up to the subject of this thread, a question was posed to the
DBA Forum on Metalink (Note: 501376.995) as follows:

========================

Cold vs Hot backups when doing database upgrades

We are planning to upgrade one of our larger databases from Oracle 8.1.7 to
9.2.0. Over time the window available for cold backups before and after our
database upgrade initiatives has narrowed significantly and is causing real
scheduling problems. Running hot backups before and after an upgrade would
greatly reduce this outage and bring us back to an acceptable down time. What
considerations, if any, go along with taking this position? Is there a better
direction?

We are currently using RMAN to do hot backups daily on most databases and are
comfortable with the processes. Thanks in advance for any input.

=========================

The response was the company line of 'Development and the documentation say to
do a cold backup'

I wanted to get your feelings on the pros and cons of doing a hot backup (6
hours), a shutdown and copy archive files, and then the migration.

What pitfalls are we overlooking if any?

Thanks very much for your feedback.
Followup:
there would be none really.

At time t1, you do a hot backup.

At time t2, you shutdown (in preparation for the upgrade). if you switched off
all of your current redo logs and got them archived, you would have sitting in
your backup area 1) the hot backup 2) all of the archives needed to restore hot
backup to "right now"

At time t3, you upgrade

At time t4 (scenario 1), all is well -- upgrade succeeds.  carry on.

At time t4 (scenario 2), it went down the tubes.  backout upgrade.  put db back
to time t2.  Well, you could just restore the hot, PITR to t2 and open
resetlots.  done.  Everything is still "8i files" at that point right?  the
database won't even KNOW you did this 9i thing to it!

just make sure you have everything you need before t3 to do a full restore on a
new machine of your 8i database and recover it fully.  If you can do that -- you
can do the upgrade safely.  (hey, something to practice -- if you can do that,
restore the 8i database to another machine and do the full recover to the point
in time you shutdown -- you KNOW you have everything you need.  thats a good
idea.  Let your JR dba's practice this and see how to do a restore hands on for
themselves to work the kinks out!)

the goal of the cold backup is to make sure you can get back to the point in
time of "just before t3".  you can do it with hot as well as "cold"
Rating: 5
Reader January 02, 2004
Reviewer: A reader
Tom,

Is there a way to convert a binary control file
to text control file. I remember vaguely to have
read something like that in this site, regarding
a tricky recovery scenario.

Thanks
Followup:
alter database backup controlfile to trace;
Rating: 5
Reader January 02, 2004
Reviewer: A reader
When a database is in UNMOUNTED state
is there a way to create a text control file
from a earlier binary control file available
Followup:
you would take the binary backup
restore it
startup MOUNT <<<=== gotta be mounted
alter database backup controlfile to trace noresetlogs;

and then you can use that.  this is the documented procedure to recover after
the loss of all online, current controlfiles with just a binary backup (shows
why you don't really want binary backups, you want "to trace" backups)

short of typing it in by hand that is...
Rating: 4
Thanks January 02, 2004
Reviewer: A reader
This is the link I was referring to
http://asktom.oracle.com/pls/ask/f?p=4950:8:3514584107944377944::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:6601495804013,

Followup:
well, now I'm confused -- cause I don't know who you are or what this is in
regards to?
Rating: 5
Regular Reader of this Site January 02, 2004
Reviewer: A reader
I just want to set up backup and recovery screnarios
for production database and write the best practices
in backing up of databases

I remembered reading that backup of text copy of the
control file is a good practice and hence I asked the
question

Thanks very much for your time
Rating: 4
Problem with oracle Startup January 14, 2004
Reviewer: A reader
Dear Tom,

I have personal Oracle installed on my machine. While starting oracle it is
giving the following erro
ORA-00322 Log 1 of thread 1 is not the current copy
ORA-00312 online log 1  thread 1 : 'd:\orawin95\database\log2orcl.ora'

Can you please tell me the reason for this error and how to recover the
database.
Followup:
[tkyte@localhost tkyte]$ oerr ora 322
00322, 00000, "log %s of thread %s is not current copy"
// *Cause:  Check of log file header at database open found that an online
//          log appears to be an incorrectly restored backup.
// *Action: Restore correct file or reset logs.
Rating: 4
Recovery January 18, 2004
Reviewer: A Reader
Dear Tom,

I have personal Oracle installed on my machine. While starting oracle it is
giving the following erro
ORA-00322 Log 1 of thread 1 is not the current copy
ORA-00312 online log 1  thread 1 : 'd:\orawin95\database\log2orcl.ora'

Can you please tell me the reason for this error and how to recover the
database.

Followup:
[tkyte@localhost tkyte]$ oerr ora 322
00322, 00000, "log %s of thread %s is not current copy"
// *Cause:  Check of log file header at database open found that an online
//          log appears to be an incorrectly restored backup.
// *Action: Restore correct file or reset logs.

Please tell me the steps to be followed to reset the logs. I will be thankful to
you.
Followup:
well, not knowing how you "got here", I'll guess.

you did a restore

you restored log files (online log files)

that is deadly and wrong. you never never backup online redo logs.

but -- i'm guessing.  time to call support and work through this with them.
they'll ask you questions to figure out how you go where you are and offer you
your possible paths of "getting better" or at least "as good as you can get"
Rating: 3
What is backup controlfile January 21, 2004
Reviewer: noel seq from Mumbai, India.
To restore a databaes from a hotbackup, we usually restore the datafiles and
recreate the controfile from the (control file to) trace file.

When we recover the database and apply the archives, we need to issue the
command 'recover database using backup controlfile'

The controlfile is newly created here with the create controlfile command using
the headers of the datafiles. The SCN's in the controlfile should therefore be
the same as that in the datafile headers. What is this backup control file and
where does it come from ?

Please shed some light.

Noel.
Followup:
I don't "usually" do that.  I do that if and only if and only when forced to
because I've lost all copies of the current online control file.  You have to
open resetlogs if you don't have any current controlfiles (breaking the redo
chain).

USUALLY you restore "a file" from a hot backup in response to media failure.

it is only if you've lost the ENTIRE machine, all control files, all datafiles
everything that you would restore with a backup controlfile (many people have
never actually ever performed this operation in real life, it means you've have
a 100% catastrophic failure -- everything was wiped out and you've probably lost
your current online redo logs as well -- meaning YOU LOST DATA).

the new controlfile is totally out of sync with the database, all it knows now
are what files there are.  In order to make use of it you have to do a manual
recovery, stop at some point when you run out of redo and open the database
using resetlogs.

A binary backup control file is a "last ditch rescue" here -- it is something
that

a) you want to practice recovery with
b) pray you NEVER actually have to do it in real life cause something seriously
bad has happened (and you've most likely experienced data loss -- cause if you
lose all controlfiles -- are forced to go to backups -- you've most likely lost
the entire machine, current online redo logs included)
Rating: 3
backup controlfile January 22, 2004
Reviewer: noel seq from Mumbai, India.
While saying 'Usually restore the datafiles and recreate the control file' I
meant during restoration drills for checking validity of backups. To be in a
situation where my entire machine data is lost is not something I desire
especially if am not confident of my backups.

So, I understand that there is no 'Backup' controlfile other than the manually
backed up binary/text controlfile and the term is used to indicate that the
restored controlfile is out of sync with the database thats all. Ofcourse, it
needs a manual recovery to apply archives and a resetlogs (incase of incomplete
recovery).

Regards
noel.
Rating: 4
March 20, 2004
Reviewer: Kelly C.
I have been brought to site for assisting the db recovery.
We have 9.0.1 database in archive log mode.

We lost all the data files .
The backup took 3 weeks ago , and to two locations: locationA contains one
datafile SYSTEM01 and locationB has all the rest files. I looked the time stamps
of the files, and SYSTEM01 was a day newer than the rest of files. These
datafiles are only set of we have.
All the archived logs are intact since the backup on locationB before crash.

How/can we recovery the database?
Thanks for help.
Followup:
wow, why would system go to the left and everything else to the right.

I've no idea if you can recover or not, it doesn't sound like you were following
sound backup and recovery procedures (3 weeks!!  split to different places! at
different times!)

Rather than try to guide you- i'm going to ask you to contact support, they'll
collect the information and tell you what to do.

step 0 right now is MAKE SURE you can get back to the exact state you are
currently in (eg: backup these three systems now)
Rating: 5
backup strategy March 20, 2004
Reviewer: Shankar from Houston, TX
What would be the ideal backup strategy for multi-terabyte warehouse databases?
I was thinking backing up these databases using cp command or RMAN may not be
practical. I wanted to have your thoughts on this as you may have information
about real world datawarehouse database backup strategy. Thanks.
Followup:
too hard to answer in a small space.

depends on how you do things.  say you have lots of "rolling windows" -- adding
data at the end of a table and rolling off old data.

then, you can

a) load a new partition into its tablespace
b) alter the tablespace read only
c) backup that tablespace and never back it up agains

so you are only backing up that which you just added and only once.

there are many techniques for making this more manageable -- do you need to
backup indexes?  maybe, maybe not.  maybe some yes, others no.

you'll be investing in a nice piece of hardware, a tape librarian, so "cp" and
"tar" are robably "not happening" here -- bur RMAN would be.
Rating: 2
for TB+ Databases consider something like flashcopy March 22, 2004
Reviewer: idai from Germany
We have SAN Storage in the backend and luckily our SW supports flashcopy
and we are also able to use the results cloning test databases
Followup:
so, you still need to get that to tape or something else.  that doesn't really
make the backups any faster/easier really.  You would still look at the methods
above in order to minimize what you are backing up.  I have the same ability to
backup without bcv's/flashcopies/snapshots in a DW with hot backups.  and you
can easily clone a db this way as well (copy the files and restore them
elsewhere, just like you are right now)
Rating: 5
March 22, 2004
Reviewer: A reader
Tom,

Can you please explain me or provide some link on how to use RMAN in SAN
environment and how does RMAN fit in SAN architecture.

Thanks.
Followup:
no different then in direct attach or NAS.

it is just a database on disk.
Rating: 5
rman and san March 22, 2004
Reviewer: reader
<quote>it is just a database on disk<quote>

Could you please clarify on this? Thanks.
Followup:
in what sense?

to you and me (the oracle guys), that SAN is JBOD as far as we are concerned
from a use perspective.  we use it just like "disk"
Rating: 4
restore READONLY tablespace March 23, 2004
Reviewer: Jian from DC
Tom,
Quote from 8i Document
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76956/tspaces.htm#922
"
After making the tablespace read-only, it is advisable to back it up
immediately. As long as the tablespace remains read-only, no further backups of
the tablespace are necessary since no changes can be made to it.
"

In so called READONLY tablespace, dropping objects is still allowed. This may
have some problems during recovery. If I restore the readonly TBS which contains
dropped objects, ALL the archived logs since latest backup of the readonly
tablespace should be kept in order to replay these DROPs.

Imagine I backuped a READONLY TBS 1 yr ago, and backuped other READWRITE TBSs a
week ago, and there were some DROPs happening between 1 year ago and 1 week ago.
I have to keep ALL archived logs for 1 yr instead of 1 week, this sounds so
badly. The benefit of backing up READONLY TBS once costs keeping much MORE
archived logs. If every time some objects are dropped, a new backup of the
READONLY tablespace should be taken, then it's not so practical for DBAs since
we have no control whether end users will drop objects in the READONLY
tablespace or not, nor do DBAs know when users do it in order to take a backup.
Besides, backing up READONLY TBS once isnt true anymore.

Could you explain whether I am wrong or not? Why doesnt ORACLE make it a TRUE
READ ONLY tablespace?
Followup:
no you don't, did you try it to see what would happen?

The data dictionary tells us how to read the readonly tablespace.

If the readonly tablespace is full of dropped objects -- thats OK -- the data is
"just there" but we "don't know about it"

think about it.............

the "so called" readonly tablespaces ARE read only (they could be on cdrom), you
can still 'drop' objects from there (we don't write to the tablespace) since the
drop affects only the data dictionary!

it is a "true" read only tablespace.  we read it, we don't write it and the data
dictionary tells us how to read it.
Rating: 3
RMAN Catalog Backup Strategies March 23, 2004
Reviewer: Hien from South Australia
Hi Tom,

So much talk about different backup methods and strategies and that is all good.

However, I can't find anything about backup strategies for the RMAN catalog.

What are your recommendations for backing up the RMAN catalog, either it is
residing on the same machine as the production database or is residing on a box
on its own. Would you do the same level of backup for you production database eg
Level 0, 1, 2 etc.

Thanks.

Regards
Hien
Followup:
you use conventional (eg: old fashioned) backups of that.

cold, hot whatever.  probably easiest for such a tiny database just to shutdown,
tar it all up and start backup.  It is a "special" case.
Rating: 4
Is DROP operation is different than normal tablespace for READONLY TBS March 24, 2004
Reviewer: Jian from DC
<<
the "so called" readonly tablespaces ARE read only (they could be on cdrom), you

can still 'drop' objects from there (we don't write to the tablespace) since the

drop affects only the data dictionary!
>>

so DROP operation is different than normal tablespace for the READONLY
tablespace, for the normal tablespace, at least I can see the segments are free
after dropping them and if they are locally managed, there got to be something
written to the tablespace instead of dictionary to mark these space are free to
use. RIGHT?
Followup:
ops$tkyte@ORA9IR2> create tablespace foo datafile size 1m;
Tablespace created.

ops$tkyte@ORA9IR2> create table xxx ( x int ) tablespace foo;
Table created.

ops$tkyte@ORA9IR2> alter tablespace foo offline;
Tablespace altered.

ops$tkyte@ORA9IR2> drop table xxx;
Table dropped.

No, it is not any different at all.  It just doesn't update the freespace until
it can and with readonly, it cannot.
Rating: 4
Can the Performance be affected while backing up the control file March 24, 2004
Reviewer: Subodh Deshpande from India
Hi Tom,

Is this true that if we carry out a backup of control file at peak hour or peak
load on database, the performance is affected, if it is so please explain , if
not still then please -:)

yes, tom things are improving at my work place, now we also are allowed to
access internet for browsing oracle sites..
Followup:
you'd need to get the control file enqueue (basically get exclusive access to
it) but backing it up should be "pretty darn fast" (it isn't that large after
all) and you only get it once and give it right up so..... not sure it would
really be an "issue"
March 25, 2004 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.
Rating: 5
Bubble Frag March 25, 2004
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
I have used the following script to check any fragmentation,

SELECT   dfsc.tablespace_name tablespace_name,
         DECODE (
            dfsc.percent_extents_coalesced,
            100,
            (DECODE (
                GREATEST ((SELECT COUNT (1)
                             FROM dba_free_space dfs
                            WHERE dfs.tablespace_name = dfsc.tablespace_name),
1),
                1,
                'No Frag',
                'Bubble Frag'
             )
            ),
            'Possible Honey Comb Frag'
         )
               fragmentation_status
    FROM dba_free_space_coalesced dfsc
ORDER BY dfsc.tablespace_name
/

the query is showing many tablespaces with Bubble Frag while I have locallay
managed ts. This is the case on an 8i db while I am surprised to see that on my
latest installation of 9i the system and UNDOTBS1 ts's are also showing bubble
frag.

CWMLITE  No Frag
DRSYS  No Frag
ESD  Bubble Frag
EXAMPLE  No Frag
INDX  No Frag
ODM  No Frag
SYSTEM  Bubble Frag
TOOLS  No Frag
UNDOTBS1  Bubble Frag
USERS  No Frag
XDB  No Frag

Please let me know,
1- The reason of these fragmentation even if we are using locally managed ts?
2- How much these types of frag effects the performance?
3- What is the easiest way to get rid of these frag other than moving data to
another tbs and droping the old one?
4- How to avoid such frag in future?
Followup:
so, tell me, is "bubble" a "problem" (no, it is not, if all of the bubbles are
the same size or nice multiples)

You do not have fragmentation as I define it.

It does not affect your performance.
You do not need (or want) to get rid of it.
You do not need to avoid it.
March 25, 2004 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.
Rating: 5
How to return/refer values in/from block of statements? March 25, 2004
Reviewer: Tony from India
I use EXECUTE IMMEDIATE to execute block of statments
( DECLARE ... BEGIN ..... END). At the end of execution, I wanna refer/return
values of some of the local variables in the block executed. Could you please
let me know how to achieve this?
Followup:
use bind variables -- fully documented in the PLSQL guide, you can bind IN, IN
OUT, or OUT parameters to dynamic blocks.
March 25, 2004 jasdeep -- Thanks for the question regarding "cman.ora", version 9.0.2
You Asked
please tell how to configure cman.ora to block particular tcp/ip address from
using sqlplus.

thanks
and we said...
you cannot.

there is no way to know that someone is using sqlplus, or toad, or odbc from a
vb app or whatever.

they all look the same at the other end of the wire.

And if you try to block it be program name -- I would just copy sqlplus.exe to
"something_you_are_not_looking_for.exe"
Review & Followup

Rating: 4
cman.ora January 25, 2004
Reviewer: jasdeep from patiala,india
but cman.ora can be used to prevent TCP/IP connections from specific servers
that is what i have studied from metallink.oracle.com
Followup:
that is totally different than blocking sqlplus.

you cannot block sqlplus.

you can block IP's and that is done in either the protocol.ora or sqlnet.ora
depending on release.

for 9i, it is in the sqlnet.ora.
http://download-west.oracle.com/docs/cd/B10501_01/network.920/a96581/sqlnet.htm#496697
describes the settings.
Rating: 5
And for a method in 8i January 26, 2004
Reviewer: Riaz Shahid from PRAL, Lahore, Pakistan
From Documentation:

Protocol-Specific Parameters (protocol.ora)

Some protocols have platform-specific parameters. These are stored in the
protocol.ora file. The protocol.ora file is located in
$ORACLE_HOME/network/admin on UNIX and ORACLE_HOME\network\admin on Windows
platforms, or in the directory specified by the TNS_ADMIN environment variable
or registry value.
Listed next are some of the common parameters. For a complete listing of
parameters for your operating system and platform, see your
operating-system-specific documentation.
protocol.EXCLUDED_NODES
Purpose:     Specifies which clients are restricted access to the database.
Syntax:     protocol.excluded_nodes= (hostname | ip_address, hostname |
ip_address,...)
Example:     tcp.excluded_nodes= (hr.com, 144.25.5.25)

protocol.INVITED_NODES
Purpose:     Specifies which clients are allowed access to the database. This
list takes precedence over the protocol.EXCLUDED_NODES parameter if both lists
are present.
Syntax:     protocol.invited_nodes= (hostname | ip_address, hostname |
ip_address,...)
Example:     tcp.invited_nodes= (sales.com, 144.185.5.73)

protocol.VALIDNODE_CHECKING
Purpose:     Checks for the protocol.INVITED_NODES and protocol.EXCLUDED_NODES
to determine which clients to allow or deny access.
Default:      NO
Values:      YES | NO
Example:     tcp.validnode_checking=yes

Hope this helps
Rating: 4
cman.ora January 26, 2004
Reviewer: rabeila from karachi
Rating: 4
Restricting access January 26, 2004
Reviewer: Adam Musch from Omaha, NE
What you could do (and what I had to do once) would be to create an allow_login
table that will allow a client to login to the database based on one of the
following being true:

host name of connection
os userid
application used

In a login trigger, you can interrogate those values (from either v$session or
application context, I don't recall which) and then check if any of the "valid
criteria" are met -- if so, login proceeds, else, raise an application error
stating that login is not permitted for User X running Application Y from Host
Z, and log it to the audit trail.

The challenge with using sqlnet.ora and the like is it restricts solely on the
basis of IP, and that can be a real challenge in a DHCP world.  This workaround
allowed the DBA staff to connect to the database from anywhere using any tool,
any app running "approved" servers to connect, and other "approved" apps running
from anywhere to connect.

This solution is by no means perfect, and the most probable failure point is the
application name, and probably should be used as the validation of last resort,
as someone with bad intentions could rename the application to an "approved"
name -- provided they knew what it was.

However, hostnames and usernames are significantly harder to fudge in a secured
environment.

And, while it goes without saying, if you have such a requirement, you should be
auditing successful and failed login events to determine if there appear to be
(or have been) unauthorized access.
Rating: 5
thanks January 26, 2004
Reviewer: jasdeep from patiala,india
thanks for your responses
Mr :  Riaz Shahid,Adam Musch
Rating: 3
configuration about MTS and Dedciated server March 25, 2004
Reviewer: r kulkarni from INDIA BANGLORE
hi

first time i am Seting the Mts and Dedciated server.

could u plz tell me, how to do set up.
Followup:
read the docs -- there is a net admin guide and it has a chapter/section on
"configuring shared server"
March 25, 2004 Tom -- Thanks for the question regarding "Question on indexing strategy", version 8.1.7.,2
You Asked
Tom,

Can you please explain whether or not it matters to use the most selective
column first in the order of column that consists the index ?.

I had always assumed that the most selective column should be the first column
of an index. But if this rule of thumb is used, we cannot take advantage of
"compress" indexes (this is my understanding of how compress works).

Can you please comment on what the best practise is ?.

Thanks & Regards
and we said...
Well, I actually go into this in some depth in my book.

It has not been true since at least version 6 that having the most selective
first is better.

The PRIMARY rule that drive the order of columns in a concatenated index should
be the types of predicates you use.  If you query:

select * from t where a = :a and b = :b and c = :c;
select * from t where b = :b and c = :c;
select * from t where c = :c;

The most logical index might be on (c,b,a) as it can EASILY be used for all
three.  Note that I don't really care about the selectivity of anything here --
the optimizer will take care of that for me (if C is not selective enough by
itself, it'll not use the index, if it is -- it will).

In 8i, as you pointed out -- there is a compelling reason to put the LEAST
discriminating columns first -- compression.

In 9i, there is even a MORE compelling reason.  Index skip scans -- see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1156161921465
here, having the least discriminating entry first can allow us to use an index
even when the columns in the predicate are NOT on the leading edge of the index.
 For example given a table t with an index t_idx(a,b), if A is not selective, we
might be able to use a skip scan on a query like:

   select * from t where b = 5;
Review & Followup

Rating: 4
Tom, could you please elaborate on "compress index" feature May 09, 2002
Reviewer: Andrew from Calgary Canada
I always find something new in your answers. This time "skip index" (explained
very well in your previous link) and "compressed index". Could you please
elaborate on this compressed index feature, how it works and how it is related
to the "most selective" column. I probably shouldn't ask this, because it is
probably in the docs, but, Tom, you have such a talent to make it clear....
Followup:
Better yet -- I've explained it in great deatil in my book in the chapter on
indexes!  I explained tons of stuff in there.
Rating: 3
column order - oracle docs May 22, 2002
Reviewer: kapil
Hi Tom,
You have said that since version 6 it is not better to have the most selective
column in the leading position
But in the oracle documentation - Oracle8i Designing and Tuning for Performance
- Data Access Methods
the following statement is mentioned
"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"
Could you throw some light on that statement?

Kapil
Followup:
it is in my experience -- a mistatement on their part.  I would say it is
inaccurate.

In my book -- I actually benchmark it:

tkyte@TKYTE816> create table t
  2  nologging
  3  as
  4  select * from all_objects;
Table created.

tkyte@TKYTE816> create index t_idx_1 on t(owner,object_type,object_name)
  2  nologging pctfree 0;
Index created.

tkyte@TKYTE816> create index t_idx_2 on t(object_name,object_type,owner)
  2  nologging pctfree 0;
Index created.

tkyte@TKYTE816> select count(distinct owner), count(distinct object_type),
  2   count(distinct object_name ), count(*)
  3  from t;

(DISTINCTOWNER) (DISTINCTOBJECT_TYPE) (DISTINCTOBJECT_NAME)   COUNT(*)
--------------- --------------------- --------------------- ----------
             24                    23                 12265      21975

Now, to show that neither is more efficient space wise, we?ll measure their
space utilization:

tkyte@TKYTE816> exec show_space( 'T_IDX_1', user, 'INDEX' );
Free Blocks.............................0
Total Blocks............................192
Total Bytes.............................1572864
Unused Blocks...........................51
Unused Bytes............................417792
Last Used Ext FileId....................6
Last Used Ext BlockId...................4745
Last Used Block.........................13

PL/SQL procedure successfully completed.

tkyte@TKYTE816> exec show_space( 'T_IDX_2', user, 'INDEX' );
Free Blocks.............................0
Total Blocks............................192
Total Bytes.............................1572864
Unused Blocks...........................51
Unused Bytes............................417792
Last Used Ext FileId....................6
Last Used Ext BlockId...................4937
Last Used Block.........................13

PL/SQL procedure successfully completed.

They use exactly the same amount of space ? there are no differences there
(however, the first index is a lot more COMPRESSABLE if we used index key
compression!  There is an argument for going from least discriminating to most
discriminating).  Now, lets see how they perform ? if either index is generally
more efficient than the other.  In order to test that, I used a PLSQL block with
hinted queries (so as to use one index or the other) like this:

tkyte@TKYTE816> alter session set sql_trace=true;
Session altered.

tkyte@TKYTE816> declare
  2          cnt int;
  3  begin
  4          for x in ( select owner, object_type, object_name from t )
  5          loop
  6                  select /*+ INDEX( t t_idx_1 ) */ count(*) into cnt
  7                    from t
  8                   where object_name = x.object_name
  9                     and object_type = x.object_type
 10                     and owner = x.owner;
 11
 12                  select /*+ INDEX( t t_idx_2 ) */ count(*) into cnt
 13                    from t
 14                   where object_name = x.object_name
 15                     and object_type = x.object_type
 16                     and owner = x.owner;
 17          end loop;
 18  end;
 19  /

PL/SQL procedure successfully completed.

That read ever single row in the index one at a time.  The TKPROF report shows
us:

SELECT /*+ INDEX( t t_idx_1 )  */COUNT(*)
FROM
 T  WHERE OBJECT_NAME = :b1  AND OBJECT_TYPE = :b2  AND OWNER = :b3

call     count       cpu    elapsed  disk      query    current        rows
------- ------  -------- ---------- ----- ---------- ----------  ----------
Parse        1      0.00       0.00     0          0          0           0
Execute  21975      2.35       2.55     0          0          0           0
Fetch    21975      1.40       1.57     0      44088          0       21975
------- ------  -------- ---------- ----- ---------- ----------  ----------
total    43951      3.75       4.12     0      44088          0       21975

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
  21975   SORT (AGGREGATE)
  21975    INDEX (RANGE SCAN) OF 'T_IDX_1' (NON-UNIQUE)

********************************************************************************

SELECT /*+ INDEX( t t_idx_2 )  */COUNT(*)
FROM
 T  WHERE OBJECT_NAME = :b1  AND OBJECT_TYPE = :b2  AND OWNER = :b3

call     count       cpu    elapsed  disk      query    current        rows
------- ------  -------- ---------- ----- ---------- ----------  ----------
Parse        1      0.00       0.00     0          0          0           0
Execute  21975      2.10       2.44     0          0          0           0
Fetch    21975      1.65       1.60     0      44088          0       21975
------- ------  -------- ---------- ----- ---------- ----------  ----------
total    43951      3.75       4.04     0      44088          0       21975

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
  21975   SORT (AGGREGATE)
  21975    INDEX (RANGE SCAN) OF 'T_IDX_2' (NON-UNIQUE)

They processed the same exact number of rows, blocks, used equivalent amounts of
CPU time and ran in about the same elapsed time (run this same test again and
the CPU/ELAPSED numbers will be a little different but on average ? they will be
the same).  There are no inherit efficiencies to be had ordering the columns in
order how discriminating they are.
Rating: 4
comparison May 24, 2002
Reviewer: kapil from india
Hi Tom
That was helpful indeed.
Well the only thing which could be added to the above analysis is comparing the
space and performance with the same index in the compressed form.
Would it be possible for you to draw that comparison?
Also, could you share the code of the PL/SQL block "show_space"?
Followup:
simple search for show_space on this site will find the code (its in the book as
well)

Sort of thought with the above -- anyone would be able to do the compress
example if interested....  go for it.
Rating: 4
thanks May 27, 2002
Reviewer: Kapil from india
Hi Tom,
No problem
Thanks for all your help
Rating: 4
March 23, 2004
Reviewer: Sushanta from INDIA
Hi Tom,
  I have a problem in the Index Picking.

#----------------------------I have a table
SQL> desc check_008
 Name                                      Null?    Type
 ----------------------------------------- -------- ---------------
 A                                                  NUMBER
 B                                                  NUMBER
 C                                                  VARCHAR2(2000)

#----------------------------Number of Records present in the table
SQL> SELECT COUNT(*)
  2    FROM check_008
  3  /

  COUNT(*)
----------
     99001

#----------------------------I have a index on the table on column (A,B)
SQL> l
  1  SELECT index_name,SUBSTR(column_name,1,20),column_position
  2    FROM all_ind_columns
  3*  WHERE table_name='CHECK_008'
SQL> /

INDEX_NAME                     SUBSTR(COLUMN_NAME,1 COLUMN_POSITION
------------------------------ -------------------- ---------------
DEL_001                        B                                  2
DEL_001                        A                                  1

SQL> set autotrace traceonly exp
SQL> DELETE FROM check_008
  2   WHERE (a=10 OR 10 IS NULL)
  3     AND (b=2  OR 2 IS NULL)
  4     AND (c='23-MAR-049218' OR '23-MAR-049218' IS NULL)
  5  /

1 row deleted.

Execution Plan
----------------------------------------------------------
   0      DELETE STATEMENT Optimizer=CHOOSE (Cost=14 Card=1 Bytes=19)
   1    0   DELETE OF 'CHECK_008'
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'CHECK_008' (Cost=14 Card=1
Bytes=19)
   3    2       INDEX (RANGE SCAN) OF 'DEL_001' (NON-UNIQUE) (Cost=7 Card=1)

Then i create a procedure with the same Delete statement like this
SQL>CREATE OR REPLACE PROCEDURE pr_without_nvl(p_a IN NUMBER,p_b IN NUMBER,p_c
IN VARCHAR2,p_rows_affected OUT NUMBER)
   IS
BEGIN
 DELETE FROM check_008
  WHERE (a = p_a OR p_a IS NULL)
    AND (b = p_b OR p_b IS NULL)
    AND (c = p_c OR p_c IS NULL);
 p_rows_affected := SQL%ROWCOUNT;
END;
/
Procedure created.

SQL> alter session set sql_trace=true;

Session altered.

SQL> alter session set timed_statistics=true;

Session altered.

SQL> variable delete_count NUMBER;
SQL> exec pr_without_nvl(10,2,'23-MAR-049218',:delete_count);

PL/SQL procedure successfully completed.

SQL> print :delete_count

DELETE_COUNT
------------
           1

By seeing the Tkprof File it looks like

DELETE FROM CHECK_008
WHERE
 (A = :b1  OR :b1 IS NULL ) AND (B = :b3  OR :b3 IS NULL ) AND (C = :b5  OR
  :b5 IS NULL )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.00          0         70          0           0
Execute      1      0.11       0.16        206        831         24           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.13       0.16        206        901         24           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 46  (ATLAS)   (recursive depth: 1)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  DELETE STATEMENT   GOAL: CHOOSE
      0   DELETE OF 'CHECK_008'
      0    TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'CHECK_008'

Where the Plan is shows as FULL Table Scan ........................????

However from this it is picking Index.

SQL> DELETE FROM check_008
  2   WHERE (a=10 OR 10 IS NULL)
  3     AND (b=2  OR 2 IS NULL)
  4     AND (c='23-MAR-049218' OR '23-MAR-049218' IS NULL)
  5  /

1 row deleted.

Execution Plan
----------------------------------------------------------
   0      DELETE STATEMENT Optimizer=CHOOSE (Cost=14 Card=1 Bytes=19)
   1    0   DELETE OF 'CHECK_008'
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'CHECK_008' (Cost=14 Card=1
Bytes=19)
   3    2       INDEX (RANGE SCAN) OF 'DEL_001' (NON-UNIQUE) (Cost=7 Card=1)

My Point is is it better to change the Query from
DELETE check_008
 WHERE a = NVL(p_a,a)
   AND b = NVL(p_b,b)
   AND c = NVL(p_c,c);

to

DELETE check_008
 WHERE (a = p_a OR p_a IS NULL)
   AND (b = p_b OR p_b IS NULL)
   AND (c = p_c OR p_c IS NULL)

will increase performance when there is a composite index on a and b.
Also why there is a deviation in the Plan, from the SQL*PLUS to Procedure .

Thanks And Best Regards
Sushanta
Followup:
when you use bind variables -- think about it...

sometimes A is provided.  sometimes not.
sometimes B is provided.  sometimes not.

when neither of A or B is provided -- full scan
when A is not provided -- full scan (or index skip scan but probable full scan)
when A is provided -- possible to use index

but -- no one knows IF or WHEN A would be provided.....

in the example with constants, it is well known that A is provided (try your
sqlplus example with binds and you'll get the same result)

there is a deviation in the plan because you are comparing APPLES to Toaster
ovens and there is no possible comparision there....

Now, 100,000 rows is pretty tiny, so if this delete is not executed very often,
what you have is probably fine.  Else you can

  l_string := 'delete from t ';
  if ( p_a is not null )
  then
     l_string := l_string || ' where a = :a ';
  else
     l_string := l_string || ' where :a is null ';
  end if;
  if ( p_b is not null )
  then
     l_string := l_string || ' and b = :b ';
  else
     l_string := l_string || ' and :b is null ';
  end if;
  if ( p_c is not null )
  then
     l_string := l_string || ' and c = :c ';
  else
     l_string := l_string || ' and :c is null ';
  end if;
  execute immediate l_string using p_a, p_b, p_c;

if A is provided, index.
if A is not provided, probable full scan
Rating: 4
March 25, 2004
Reviewer: A reader from INDIA
Hi Tom,
  Many thanks for ur input. One thing to clear and it will overall clear on this
concept
  When i try for this one in SQL*PLUS

SQL> variable b1 NUMBER
SQL> variable b2 NUMBER
SQL> variable b3 VARCHAR2
STAGE - I
=========
SQL> DELETE FROM CHECK_008
WHERE
 (A = :b1 ) AND (B = :b2  OR :b2 IS NULL ) AND (C = :b3  OR :b3 IS NULL )  2
3
  4  /

0 rows deleted.

Execution Plan
----------------------------------------------------------
   0      DELETE STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=2028)
   1    0   DELETE OF 'CHECK_008'
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'CHECK_008' (Cost=1 Car
          d=1 Bytes=2028)

   3    2       INDEX (RANGE SCAN) OF 'DEL_008' (NON-UNIQUE)

STAGE-II
========
SQL> DELETE FROM CHECK_008
WHERE
 (A = :b1 OR :b1 IS NULL) AND (B = :b2  OR :b2 IS NULL ) AND (C = :b3  OR :b3 IS
NULL )  2    3
  4  /

0 rows deleted.

Execution Plan
----------------------------------------------------------
   0      DELETE STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=2028)
   1    0   DELETE OF 'CHECK_008'
   2    1     TABLE ACCESS (FULL) OF 'CHECK_008' (Cost=1 Card=1 Bytes=
          2028)

Colud you please clear on this that
In case of STAGE-I, ORACLE-OPTIMIZER knows that the value of the bind
variable(:b1) must come,whereas in case
of STAGE-II, ORACLE-OPTIMIZER is in "Doubt!!" , whether the value of the bind
variable (:b1) will come or not,
for that reason it will go for "FULL TABLE SCAN", without taking any RISK.

Is it so ??

Also how ORACLE generates PLAN in case of "OR" Operator ?

Many Thanks ...

Sushanta
Followup:
the plan is set up ONCE and must be capable of doing ANY set of inputs, not just
yours.

Hence, sometimes b1 could be null and sometimes not.  therefore -- well, it has
to do what it has to do (full scan)

first query -- you can always use an index.
second query -- you can use one when b1 is not null, you cannot when b1 is null.
Rating: 5
March 26, 2004
Reviewer: sushanta from INDIA
Tom,
   Thanks a Lot Tom for ur valuable Time.

Regards
Sushanta
March 25, 2004 LOUIS -- Thanks for the question regarding "DBMS_OBFUSCATION_TOOLKIT", version 8.1.7
You Asked
Hi tom,

I am in the process of testing a web application. However
I am trying to use your example about DBMS_OBFUSCATION_TOOLKIT but
you used bind variables. So, I am running into a problem. I need to incrypt and
most important descrypt  the credit card field. So, I guess I need to use the
DBMS_OBFUSCATION_TOOLKIT in a procedure or function. Can you help me please or
tell me the best way to do this?? I will appreciate it so much, without a doubt
I will take to you lunch when you are in new york....

Louis Lugo
DBA
and we said...
Here is the same example from
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:791026226790
written as procedures.  Bind variables are simply replaced with formal
parameters (thats the only change I've made)...

Here it is:

ops$tkyte@DEV816> variable x varchar2(25)

ops$tkyte@DEV816> exec :x := 'How Now Brown Cow';
PL/SQL procedure successfully completed.

X
--------------------------------
How Now Brown Cow

ops$tkyte@DEV816> create or replace procedure crypt( p_str in out 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
  7      dbms_obfuscation_toolkit.DESEncrypt
  8          ( input_string => l_data,
  9        key_string   => 'MagicKey',
 10        encrypted_string=> p_str );
 11  end;
 12  /
Procedure created.

ops$tkyte@DEV816> create or replace procedure decrypt( p_str in out varchar2 )
  2  as
  3      l_data  varchar2(255);
  4  begin
  5      dbms_obfuscation_toolkit.DESDecrypt
  6          ( input_string => p_str,
  7        key_string   => 'MagicKey',
  8        decrypted_string=> l_data );
  9
 10      p_str := rtrim( l_data, chr(0) );
 11  end;
 12  /
Procedure created.

ops$tkyte@DEV816> set autoprint on
ops$tkyte@DEV816> exec crypt( :x )

PL/SQL procedure successfully completed.

X
--------------------------------
Rr=p]w]pSWq:]PtJ?T,

ops$tkyte@DEV816> exec decrypt( :x )

PL/SQL procedure successfully completed.

X
--------------------------------
How Now Brown Cow

follow up to comment one.

Well, I guess I would write a FUNCTION then.  Something like this:

  2  function crypt( p_str in varchar2 ) return varchar2
  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
  8      dbms_obfuscation_toolkit.DESEncrypt
  9          ( input_string => l_data,
 10            key_string   => 'MagicKey',
 11            encrypted_string=> l_data );
 12
 13      return l_data;
 14  end;
 15  /

Function created.

scott@DEV816>
scott@DEV816> create or replace
  2  function decrypt( p_str in varchar2 ) return varchar2
  3  as
  4      l_data  varchar2(255);
  5  begin
  6      dbms_obfuscation_toolkit.DESDecrypt
  7          ( input_string => p_str,
  8            key_string   => 'MagicKey',
  9            decrypted_string=> l_data );
 10
 11      return rtrim( l_data, chr(0) );
 12  end;
 13  /
Function created.

scott@DEV816> column decrypt format a30
scott@DEV816> drop table t;

Table dropped.

scott@DEV816> create table t ( data varchar2(32) );
Table created.

scott@DEV816> insert into t values ( crypt( 'Hello World' ) );
1 row created.

scott@DEV816> select data, decrypt(data) decrypt from t;

DATA                             DECRYPT
-------------------------------- ------------------------------
7??-,?P I{CK??!                  Hello World

scott@DEV816>
Review & Followup

Rating: 3
"DBMS_OBFUSCATION_TOOLKIT", version 8.1.7 May 25, 2001
Reviewer: Louis Lugo from new york
Thanks tom for your prompt response. However, how am I going  to use this
example with let's say encrypting
a table field?
Rating: 5
Excellent - how to hide the key though? November 16, 2001
Reviewer: Doug Cowles from CT, USA
Very useful.  How do we hide the key though.   A bind variable will help with
tkprof, but it will have to be defined in the code somewhere which will allow it
to be seen in all_source.  Other ideas?
Rating: 4
Little modification required.... March 25, 2004
Reviewer: Kiran Shah from India
Dear Tom,
The procedures for encrypting/decrypting are very helpful.
But as per my understanding it requires one little change.

The procedure for encrypting string contains following statement...

l_data := rpad( p_str, (trunc(length(p_str)/8)+1)*8, chr(0) );

The purpose of this statement is to make the length of input string to be in
multiples of eight bytes.

BUT this will also add another eight chr(0) characters  when the input string
size is exactly eight or in multiplies of eight bytes...(And when the input size
is eight or in multiples of eight bytes, we do not need to append any more
characters...)

So following if condition is needed to make it perfect...

if mod(length(p_str),8) <> 0 then
l_data := rpad( p_str, (trunc(length(p_str)/8)+1)*8, chr(0) );
else
l_data := p_str;
end if;

Pl. tell me if I am wrong...

Thanks,
Kiran.
Followup:
(i didn't really care about 8bytes honestly, was going for shortest, easiest
code)
March 25, 2004 Sandy -- Thanks for the question regarding "Backup and Recovery", version 8.1.5
You Asked
Hi Tom,

I am mid-level DBA and trying to more information about recovery. About recovery
I have some questions here:

1. Some time ago there was some problem in our database and we were not able to
open the database and after that one of our Sr. Oracle DBA
apply all the archve logs file and database is just running fine.
My question is
.how to find, when to apply archive log files,
.how to find out what all archive logs we need to apply and
.how to apply arrchive logs.

2. Is there any document (Except Oracle documentation) available where I can get
so many recovery scenarios and their solution. I have gone through with Oracle
documentation and still looking for so many
recovery scenarios.

3. I was looking a document and find some thing says, database runninng in to
recovery mode. What do you mean by "recovery mode".

Thanks for your help.

Sandy
and we said...
1) the database would tell you when you issue the startup command.  It would say
"file x needs recovery"

The database will tell you what archives it needs when you tell the database to
recover that file.

The database will apply the redo logs itself.  You only need to make them
available.

2) well, I've found the backup and recovery guide to have hit on most of them
myself.
http://download-east.oracle.com/docs/cd/A81042_01/DOC/server.816/a76993/toc.htm
But, yes, there is a backup and recovery guide published by Oracle Press.
http://www.amazon.com/exec/obidos/ASIN/0072127171/
3) you probably mean a standby database.  See
http://download-east.oracle.com/docs/cd/A81042_01/DOC/server.816/a76995/toc.htm
Review & Followup

Rating: 2
July 15, 2001
Reviewer: A reader
Rating: 3
Recovery March 25, 2004
Reviewer: A reader
Tom I have a 9.0.2 database running on WinNT
an experimental area running Archive Log mode.

The HD has crashed but I think I can get all the Oracle
files off.

Do you know if I can copy them to another WinNT and "recreate" the DB there?
Followup:
yes, you can.  it would be just like a backup and recovery -- it's documented.
March 25, 2004 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.
Rating: 3
Nice Topic March 25, 2004
Reviewer: Srinivas from India
Thanks Tom for your Efforts

Is there any other way to Generate Random Numbers between a set of numbers.
Followup:
dbms_random.value( a,b ) does that.
March 25, 2004 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
Rating: 4
temp tables March 25, 2004
Reviewer: reader from Russia
"..they are just like regular tables in that regard."
Tom, if after insert in global temporary table we begin select from it, for
example, FULL SCAN, then which of two parameters would be use:
- db_file_multiblock_count - because it is FULL SCAN
or
- sort_multiblock_read_count - because it is the work with
temporary tablespace
Thanks a lot.
Followup:
it is a table that happens to reside in temp.

dbfmbrc is what is used (yes, i turned on 10046 trace and tested it just to
verify , it read upto 16 blocks at time even though the underscore sortmbrc was
set to 2)
March 25, 2004 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.
Rating: 4
trigger March 15, 2004
Reviewer: mo
Tom:

I have created an "before update trigger" on table "books" that sends an email
of all books that get updated when their status changes from "W" to "S".

However, the email is getting sent for each book.  I am trying to change that to
a statement level trigger but then I seem to lost the access to "WHEN" clause
and old and new variables.

Which one should I do that allows me to send 1 email if I update 10 books from a
table with 100 books.

Thanks,
Followup:
well, how can you have access to the :old and :new without a row by row
trigger??

You can use the method outlined:
http://asktom.oracle.com/~tkyte/Mutate/index.html
in order to capture the information you want in the row triggers and then send
the email from the AFTER trigger.  Please use dbms_job to send email!! don't
send email in "real time" -- not only is it slow -- but it is not transactional.
 if the insert/update rolls back -- the email will be sent if you do it
directly, if you use dbms_job to send the email, the queue to the job queue will
be rolled back as well (eg: only send mail when the update succeeded)
Rating: 5
trigger March 24, 2004
Reviewer: mo
Tom:

Would there be a problem of doing this?

a. Add a column (job_id) to table A.
b. Update A.job_id (with l_job) in the original update statment that updates the
table instead of doing it using a trigger. I can get job id by:

SELECT sys.jobseq.nextval into l_job from dual;

c. Create a statement level after-update trigger on Table A that would schedule
the job.

1.  However How can I get the job id here since I can't select the table.record
I have the trigger defined on.

2.  WHen I ran the above SELECT statment in a trigger I kept getting an error
that "invalid column name".
Followup:
you BETTER NOT get it with that.  You better not touch anything owned by sys.

I'm not clear on what you are doing here, no.
Rating: 4
trigger March 24, 2004
Reviewer: mo
Tom:

You refered me to a link before but I do not think it applies here:

Here is the problem:

I have table Books(book_no,process_date,status,job_id);

When user select several books for processing they update the book_date(sysdate)
and status.  They do that 20 books at a time (using powerbuilder client/oracle
database).

What I need to do is schedule an reminder email seven days later that will get
sent to shipping if processed books do not get shipped after 7 days.

TO get process_date (sysdate usuallly) I have to use row level trigger. Problem
here I need to schedule one email job and not 20 jobs that will be sent if
status did not change. I am trying to update job_id for each record with the
same job_id and then schedule one email job for that job id. When shipping
department creates a shipment for these books I will remove the job and no email
will be sent.

I appreciate your help.
Followup:
if they do 20 in a single statement, the link (on mutating tables) most
certainly applies.

if they do 20 in 20 separate statements -- well, then you need to modify your
code to "save them up" -- you have an application issue, nothing automagical we
can do in the database there.
Rating: 3
trigger March 25, 2004
Reviewer: mo
Tom:

It is 20 different update statements.  The  problem is that I need to update 20
records with job id and send the email once for the 20 books.

I was thinking of doing it this way:

Create a job that rescedules itself  every 24 hours.  The job will run a
procedure that checks table BOOKS for anything flagged to be processed.  If any
records exist, send email else rescedule job 24 hours later. I need to include
in the procedure code to check if that status of those 20 books changed, so I
can delete the job in case it exists since books were shipped before 7 days.

How would you do it? What do you mean by "I havr to save them up". Do you mean
insert those 20 in a spearate table?
Followup:
what you just suggested sounds reasonable.
March 25, 2004 Marcelo -- Thanks for the question regarding "Dynamic SQL to reset sequence value", version 8.1.6 / 8.1.7
You Asked
Hi Tom,
I'm trying to create a dynamic code to reset a sequence number, like that...

declare
  v_initial number := 1;
  temp      number := 0;
  cmd       varchar2(100);

begin
  cmd := 'alter sequence SEQ increment by -1';
  execute immediate cmd;

  while true loop
    cmd := 'select SEQ.nextval from dual';
    execute immediate cmd;

    select last_number
      into temp
      from user_sequences
     where sequence_name = 'SEQ';

    if temp = v_initial then
       exit;
    end if;
  end loop;
end;

This is not working out. The first command is ok (the increment by is modified),
but the second command doesn't alter (decrease) the sequence value. What's wrong
?

Thanks for your attention,
Marcelo
and we said...
Sequences are CACHED, the data dictionary won't reflect a nextval/nextval
decrement.

An easier way to do this would be:

tkyte@TKYTE901.US.ORACLE.COM> create or replace
  2  procedure reset_seq( p_seq_name in varchar2 )
  3  is
  4      l_val number;
  5  begin
  6      execute immediate
  7      'select ' || p_seq_name || '.nextval from dual' INTO l_val;
  8
  9      execute immediate
 10      'alter sequence ' || p_seq_name || ' increment by -' || l_val ||
                                                               ' minvalue 0';
 11
 12      execute immediate
 13      'select ' || p_seq_name || '.nextval from dual' INTO l_val;
 14
 15      execute immediate
 16      'alter sequence ' || p_seq_name || ' increment by 1 minvalue 0';
 17  end;
 18  /

Procedure created.

tkyte@TKYTE901.US.ORACLE.COM>
tkyte@TKYTE901.US.ORACLE.COM> drop sequence my_seq;

Sequence dropped.

tkyte@TKYTE901.US.ORACLE.COM> create sequence my_seq increment by 1000;

Sequence created.

tkyte@TKYTE901.US.ORACLE.COM>
tkyte@TKYTE901.US.ORACLE.COM> select my_seq.nextval from dual;

   NEXTVAL
----------
         1

tkyte@TKYTE901.US.ORACLE.COM> select my_seq.nextval from dual;

   NEXTVAL
----------
      1001

tkyte@TKYTE901.US.ORACLE.COM> alter sequence my_seq increment by 1;

Sequence altered.

tkyte@TKYTE901.US.ORACLE.COM>
tkyte@TKYTE901.US.ORACLE.COM> select my_seq.nextval from dual;

   NEXTVAL
----------
      1002

tkyte@TKYTE901.US.ORACLE.COM> select my_seq.nextval from dual;

   NEXTVAL
----------
      1003

tkyte@TKYTE901.US.ORACLE.COM>
tkyte@TKYTE901.US.ORACLE.COM> exec reset_seq( 'my_seq' );

PL/SQL procedure successfully completed.

tkyte@TKYTE901.US.ORACLE.COM> select my_seq.nextval from dual;

   NEXTVAL
----------
         1
Review & Followup

Rating: 5
IT Consultant August 14, 2001
Reviewer: A reader from Australia
Very useful Tom. I like the way you are thinking and solving problems.
Rating: 3
Innovative work September 03, 2001
Reviewer: Amod from Bangalore, India
Hi,

 A really nifty and innovative work to reset sequences. But then wouldn't it be
more efficient to drop and recreate the sequence?

Just wondering...
Followup:
It is better to reset then drop/create.

The major #1 reason is that dependent objects will NOT go invalid during a
reset, they will during a drop/create cycle.  If you have stored
procedures/triggers dependent on this sequence -- dropping would be bad.
Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure p
  2  as
  3          l_number number;
  4  begin
  5          select my_seq.nextval into l_number from dual;
  6  end;
  7  /
Procedure created.

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

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop sequence my_seq;
Sequence dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create sequence my_seq;
Sequence created.

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
------------------ ------------------------------ ----------
PROCEDURE          P                              INVALID

the very next time the procedure is run, it'll be implicitly compiled.  This
adds overhead -- especially if you have LOTS of dependent objects

ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2          for i in 1 .. 100
  3          loop
  4                  p;
  5          end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

so the procedure is valid again -- now, we'll "reset it"

ops$tkyte@ORA817DEV.US.ORACLE.COM> select my_seq.nextval from dual;

   NEXTVAL
----------
       101

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec reset_seq( 'my_seq' )
PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select my_seq.nextval from dual;

   NEXTVAL
----------
         1

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

and this time the procedure is NOT invalid -- thats better.
Rating: 4
Very detailed September 07, 2001
Reviewer: CL from DE USA
This is cool and informative.
Rating: 4
More Sequences January 28, 2004
Reviewer: Vinnie from Orlando
We are using ORACLE Sequences for our PK. They are generated by a JAVA app call
"select hs_seq.nextval from dual"
The problem is, we call this many, many times & notice we are taking a hit
waiting for SQL*NET messaging.
We would like to have the app call a function/proc to get the next seq number &
increment it by 5000 & let the app handle the PK's. My question is, what would
the best way to accomplish this, keeping in mind, there may be more than one
session calling this?
Followup:
hmm, or, you could -- just perhaps -- put the seq.nextval into the SQL insert
itself and avoid the call alltogether.

   begin
      insert into t (a,b,c,d) values ( seq.nextval, ?, ?, ? )
      returning a into ?;
   end;

if you need to get the value back.  much better than get sequence, send sequence
back.
Rating: 4
sequence January 28, 2004
Reviewer: Vinnie from Orlando
I only wish it was that easy!

The application has a convaluted way of using these unique keys generated by a
sequence.  They build up all the inserts required & send them all together in 1
big batch.  They require the sequence numbers when they are building the
statements.
Don't ask why!  This is why I would like to pull in perhaps 5k at a time.
Perhaps using:
Getting the nextval
then incrementing the SEQ by 5000.
What do you think?
Followup:
you can

create sequence S increment by 5000;
Rating: 4
Using "returning into ?" clause with JDBC March 25, 2004
Reviewer: Amol from Cincinnati, OH USA
Hi,

firstly thanks for such an informative discussion!

I have a question about the suggestion to use "..returning into ?" clause to
retrieve the sequence value just used in an insert statement. I know that you
can set values in a java PreparedStatement by using setInt(..) etc. But how do
we retrieve an int value from a PreparedStatement execution? (the returned value
being the parameter to the "..returning into" clause).

Thanks and Regards,
~ amol
Followup:
Callable statement:

"begin insert into t values ( x.nextval ) returning id into ?; end;"

bind the OUT ? parameter...
March 25, 2004 Sonali -- Thanks for the question regarding "How to use bind variables", version 8.1.7/ 8.0.6
You Asked
I am trying to use bind variables for the 1st time.

I have a part of the code here from a test stored procedure-

Declare
x number;
y number;
z number;
a date;
b date;
c number;
d number;
e number;

execute immediate 'insert into Auth (AUTH_ID, Auth_Res_ID, Auth_Work_ID,
Auth_Role_ID, Auth_Status,
Auth_Start_Date, Auth_Fin_Date, Auth_Restricted, Auth_Timecard,
Auth_Secondary_Status, Auth_Amount, Auth_Rem_Amount, Auth_Schedule)
Values (Auth_SEQ.NextVal, :x, :y, :z, 70, :a, :b , :c, :d, 40, :e, 0, 10)'
Using inResID, inWorkID, inRoleID, dtStartDate, dtEndDate , inRestricted,
inTimecard, inAmount;

I keep getting error -

MGR-00072: Warning: PROCEDURE PR_SONALI created with compilation errors.
SQLWKS>
SQLWKS> show errors
Errors for PROCEDURE PR_SONALI:
LINE/COL ERROR
--------------------------------------------------------------------------------

112/22 PLS-00103: Encountered the symbol "insert into Auth (AUTH_ID, Auth_

What is wrong with my code. I am reading Experts one on one by Tom Kyte and was
trying this.

Also, if my stored procedure is already compiled then how does a bind variable
used inside procedure going to make a difference as far as parcing goes?

I tried this on Oracle8.1.7.

Also if I have to do this on Oracle 8.0.6, I will need to use
DBMS_SQL package right ?
How do I write this code to use DBMS_SQL package ?

Thanks
Sonali
and we said...
Well, in order to use bind variables -- just use PLSQL variables!!! PLSQL is
awesome for this reason, every reference to a PLSQL variable is in fact a BIND
VARIABLE.

Just

  insert into auth ( ... ) values ( auth_seq.netval, inResId, inworkId, .... );

inResid IS a bind variable.
inWorkId IS a bind variable.
Review & Followup

Rating: 4
I did not know PL/SQL variable is a BIND variable !! December 27, 2001
Reviewer: Sonali from Newton, MA
Great thanks !!!
So one should use bind variables when you pass in SQL statements directly to the
server , right ?
Unfortunately our architecture do not support oracle bind variables, because
they support both SQL server and Oracle.
Followup:
guess what -- SQL SERVER does bind varibles too.  You can use it (and SHOULD USE
IT) in both environment.

Bind variables are not an Oracle'ism.
Rating: 2
Is the following approach right December 27, 2001
Reviewer: Kulguru
Tom

I observed that my predecessor developers,have coded a lot of stuff lik e

create or replace procedure ....

begin

....
....
select ..... from ..... where
....
...
col3='hard coded variable'

...
...
group by
order by

....other code
end;

What I had done simply is to declare a variable for the hardcoded valued.

like

lv_var  varchar2:='hardcoded value'

and then substitued this variable for the hardcoded value in the queries as
shown above.

I have done this for all such occurences of hardcoded values in sql's.

Do you think this is the right way of removing hard coded variables with bind
variables.

I have done this in test instance, after taking your opinion I will move it into
prod.

Further, can you give the link to the thread where you have discussed the
adverse impact of using hardcoded variables on the library cahce memory.
Followup:
In this case -- the bind variable is not necessary (believe it or not).

With this query -- the value that is hard coded is also IMMUTABLE, it'll never
change.  This query is shareable -- everyone that runs the procedure, runs the
same exact sql.  Its perfect.  No need to bind in this particular case.

It is when you are dynamically building a query -- putting hard coded literals
in there -- that you MUST bind.

The link is my book actually.  I go over it time and time and time again.  There
are many things to consider - increased parse times due to hard parses instead
of soft, long lines to get into the library cache due to excessive latching
caused by hard parses, excessive CPU used to optimize queries, trashing of the
shared pool since it fills up with unique statements that will never be
reused.....
Rating: 4
Related to bind variable December 27, 2001
Reviewer: A reader from GA,USA
Case1:
  Proceddure xxyy(prama1) is
  begin
    oper ref_cur_ a for 'select * from tab1 where col=:y' using :prama1
   .............

Case2:
  Proceddure xxyy(prama1) is
  begin
    oper ref_cur_ a for select * from tab1 where col=prama1
...

Will bind variable will be used in case2 for col
Followup:
sure will -- you can see this via TKPROF and SQL_TRACE:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure p( param in
varchar2 )
  2  as
  3          type rc is ref cursor;
  4          l_cursor rc;
  5  begin
  6          open l_cursor for select * from dual THIS_IS_OUR_QUERY where dummy
= param;
  7  end;
  8  /

Procedure 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> exec p( 'xxx' );

PL/SQL procedure successfully completed.

tkprof shows us:

SELECT *
FROM
 DUAL THIS_IS_OUR_QUERY  WHERE DUMMY = :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

it was rewritten as a bind.  ALL plsql variable references will be "bound"
Rating: 2
Well, I feel as if Iam running around and around the same bush December 27, 2001
Reviewer: Kulguru
****************************

Can you go into the specifics or inother words can you dig further into your
statement

"With this query -- the value that is hard coded is also IMMUTABLE, it'll never
change.  This query is shareable -- everyone that runs the procedure, runs the
same exact sql.  Its perfect.  No need to bind in this particular case.
"

Every time  the sql given in the procedure executes doesnt it have to be parsed,
as a hard coded variable been used in the where clause ... HOW IS THE QUERY
SHAREABLE TO EERY ONE THAT RUNS THE PROCEDURE.. HOW ?

AND

Out of all the consequences of not using bind variables, how will the below 2
happen , please explain

1.long lines to get into the library cache due to excessive latching caused by
hard parses,
2.excessive CPU used to optimize queries( why in this world do we have to use
excessive CPU to optimize queries).

Lastly..

give us the link for an example for the statement

"It is when you are dynamically building a query -- putting hard coded literals
in there -- that you MUST bind."

Sorry Tom, I might be sounding a bit frustrated but believe me Iam. I want to
get this into my head. Please help.
Followup:
A query like this:

 select * from emp where empno = 5;

If executed over and over and over -- is just as shareable as:

 select * from emp where empno = :x;

You need not bind that particular one  -- if you execute it OVER and OVER and
OVER.  It is when people build queries on the fly (using EXECUTE IMMEDIATE or
DBMS_SQL in plsql) that you want to take care to BIND values.

Anytime you have STATIC sql in PLSQL -- your job is done.  All plsql variable
references will be bound and any literals in there are OK cause the query will
be reused over and over again.

The query is shareable.

As for #2, excessive CPU.

Ok, instead of hard parsing "select * from emp where ename = :x" once and then
soft parsing it 1,000 times (thus SKIPPING the optimization step 1,000 times)
you instead submit 1001 queries like:

select * from emp where ename = 'A';
select * from emp where ename = 'B';
...
select * from emp where ename = 'KING';
...
select * from emp where ename = 'AFDAFDFSDAS';

You have now run through query optimization (a cpu intensive task) 1,000 more
times -- using excessive CPU (1000 times MORE cpu then you needed to use).
Rating: 3
How should I know which sql's are are not using bind variables December 27, 2001
Reviewer: Kulguru
Tom

Can you give me the sql which will list out the sql's where bind variables are
not being used ,and are being parsed again  and again like your example

select * from emp where ename = 'A';
select * from emp where ename = 'B';
...
select * from emp where ename = 'KING';
...
select * from emp where ename = 'AFDAFDFSDAS';

Which v$ views do I need to query( V$sql, V$sqlarea, or v$sqltext, by the way
what is the difference b/w the 3), or v$library_cache.

Thank you
Followup:
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1163635055580
...
Rating: 5
Why does this happen, is there a limit on number of sql's which can get ... December 27, 2001
Reviewer: Kulguru
hard parsed at a given time, and ( doesnt this happen when too many queries are
issued at a given time, even though they carry bind variables)

How does using of bind variables avoid the following..

"long lines to get into the library cache due to excessive latching caused by
hard parses"
Followup:
A hard parse happens when the SQL you submitted isn't found in the shared pool
and we have to do ALL of the steps necessary to process it -- including
optimization.

A soft parse happens when the SQL you submitted IS found in the shared pool and
we can skip some steps in the processing of it.

It does not have anything to do with how many queries are submitted
simultaneously.

Using bind variables avoids those long lines by the fact that is promotes the
sharing of SQL (the reuse of plans) and allows you to SKIP steps that would
otherwise cause you to get a latch (lock) on the library cache.  You skip
processing -- you avoid waiting.
Rating: 5
Hard Parses eat up CPU December 28, 2001
Reviewer: Jim Kennedy from Beaverton, OR USA
Hard parses are very CPU intensive.  I worked on a system where an import
process benchmarked too 26 minutes and pegged the server's CPU due to not using
bind variables.

Got the developer to switch to using bind variables and the same benchmark took
under 6 minutes and had 35% CPU peaks with an average CPU usage of 5%.

Quite a difference.

With repeated DML if you can avoid hard parses and soft parses as much as
possible you will be amazed at how much faster your DML will be (after the 1st
hard parse) and how much more scalable (more users on the same box) your
application will be.
Rating: 5
Reader December 28, 2001
Reviewer: Reader from USA
Tom,

In your script to find literal SQLs in the Library Cache,

URL
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1163635055580
why do you assign "'" to "'#"

in the script
<excerpt>
elsif ( l_char = '''' and NOT l_in_quotes )
        then
            l_in_quotes := TRUE;
            l_query := l_query || '''#';
        end if;
<excerpt>
Thanks
Followup:
I am turning a string like:

 select * from emp where ename = 'KING'

into

 select * from emp where ename = '#'

As I'm going through the string, byte by byte -- I look for an opening quote and
when I find it -- I turn it into '#, then i "eat" the rest of the bytes until i
find the close quote.  Then we stick that on.  that turns 'KING' into '#'  so
that all character string literals look exactly the same.
Rating: 5
How to use bind variables January 30, 2002
Reviewer: Mark from Phoenix, AZ
Most useful for fixing legacy code.
Rating: 4
performance gain look up in Trace file TKPROF results When using Bind Variables September 02, 2002
Reviewer: Vikas Sharma from Delhi India
Hi Tom,
Thanks for my last answer.
I have a PL/SQL block like :
declare
  v varchar2(30);
begin
   select dname into v from dept where  DEPTNO = :x;
end;
/
I am using bind variable in this. So as per my understanding, the SELECT stmt in
this block should get parse only once and should be sharable by another users.

Now at sql*Plus i create a new session and executes this block For the first
time. and then second time and the result of my trace by tkprof i dont see much
differance. Following are the results:
When i run first time:
********************************************************************************

 declare
 v varchar2(30);
 begin
   select dname into v from dept where  DEPTNO = :x;
 end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.01       0.01          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: 41
********************************************************************************

SELECT DNAME
FROM
 DEPT  WHERE DEPTNO = :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        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: 1
Optimizer goal: CHOOSE
Parsing user id: 41     (recursive depth: 1)
******************************************************************

When Second time:
********************************************************************************

 declare
 v varchar2(30);
 begin
   select dname into v from dept where  DEPTNO = :x;
 end;

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           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.02       0.02          0          0          0           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 41
********************************************************************************

SELECT DNAME
FROM
 DEPT  WHERE DEPTNO = :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        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: 41     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS FULL DEPT

********************************************************************************

The cpu useages was more when i executed it second time. But i can see one more
thing extra that is
Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS FULL DEPT

Would you please explain me this behaviour.

Regards,
Vikas
Followup:
When dealing with timings -- you have to take them with a grain of salt.  there
is no difference between 0.00 and 0.02 here.  The timings are gathered with
1/100 of a second (1/1000's in 9i) precision.  Things that take less then 1/100
of a second can result in 0 being contributed -- or 0.01 being contributed.
Depends on WHEN the event being measured took place.

Hence, these two run times are 100% the same in all respects.

We can see the shared sql kicking in with:

Misses in library cache during parse: 0

in the second example -- that indicates a library cache hit.  Now as to why the
one has the plan and the other doesn't -- the plans are kicked out when the
cursor is totally closed which for the plsql stuff probably won't happen UNTIL
YOU LOG OUT.

So, I'll guess that you run tkprof on the trace file the first time BEFORE
exiting sqlplus (so the trace was incomplete).  The second time, you exited
sqlplus and then ran it (and the trace file was complete)
Rating: 4
Thanks September 03, 2002
Reviewer: Vikas Sharma from Delhi India
Thanks a lot.

Regards,
Vikas
Rating: 4
Shareable queries... October 29, 2002
Reviewer: Kashif from Houston, TX
Hi Tom,

I'm using EXECUTE IMMEDIATE inside one of our procedures to generate the next
value of a sequence as follows:

--
FUNCTION GetNextSeqVal (
vFldName IN VARCHAR2,
vDbid IN VARCHAR2)
RETURN NUMBER
IS
vSeq NUMBER;
vString VARCHAR2 (256);
BEGIN
vString := 'select '||vFldName||'_'||vDbid||'_'||'SEQ.nextval From Dual';
   EXECUTE IMMEDIATE vString
   INTO vSeq;
   RETURN (vSeq);
END;
--

An example of the generated sql:

select vartxt_main1_seq.nextval from dual;

My question:

Will the generated sql be shareable/reuseable inside the shared pool, when a
subsequent user calls this function with the same arguments? I'm thinking yes,
but I just need you to re-confirm.

Thanks.

Kashif
Followup:
That is a terrible approach if you want my opinion ( and you must, cause you put
something here ;)

never never never do dynamically what you can do statically in plsql!!!  since
the caller obviously has knowledge of the sequence name -- they should just do
the select from dual.  Watch this:

ops$tkyte@ORA920.LOCALHOST> drop sequence seq;

Sequence dropped.

ops$tkyte@ORA920.LOCALHOST> create sequence seq;

Sequence created.

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> drop table run_stats;

Table dropped.

ops$tkyte@ORA920.LOCALHOST> create table run_stats ( runid varchar2(15), name
varchar2(80), value int );

Table created.

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> grant select any table to ops$tkyte;

Grant succeeded.

ops$tkyte@ORA920.LOCALHOST> 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.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> alter system flush shared_pool;

System altered.

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> declare
  2       l_start number;
  3       l_run1  number;
  4       l_run2  number;
  5           l_num   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 .. 1000
 11       loop
 12               select seq.nextval into l_num from dual;
 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 .. 1000
 20       loop
 21               execute immediate 'select seq.nextval from dual' into l_num;
 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  /
13 hsecs
21 hsecs
run 1 ran in 61.9% of the time

PL/SQL procedure successfully completed.

so obviously static sql is faster -- but, more importantly:

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
------------------------------ ---------- ---------- ----------
...
STAT...recursive calls               3164       2240       -924
STAT...parse count (total)            127       1072        945
STAT...opened cursors cumulati        108       1072        964
ve

STAT...redo size                    63932      67056       3124
LATCH.shared pool                    3160       6659       3499
LATCH.library cache pin alloca        566       4376       3810
tion

LATCH.library cache pin              5089      10682       5593
LATCH.library cache                  6744      16104       9360

68 rows selected.

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> spool off

static sql is more scalable -- remember that latches are a serialization
device, serialization devices are also known as LOCKS, locks inhibit scalability
and performance and .......

Your dynamic sql is shareable.  it is not as good as it should be tho!
Rating: 4
Function called from numerous places... October 29, 2002
Reviewer: Kashif from Houston, TX
Thanks for the response Tom. The deal is that this function is called from 70+
different database packages, AND from various screens/application level code.
Originally this function generated 'sequence' numbers by incrementing the value
of a record in a table by 1, updating the table, and then returning the new
incremented value. You guessed it, third party app, one code fits all databases
philosophy. We had performance issues, obviously, and decided that it would be
more efficient to use sequences. However, there are potentially 70+ different
sequences, and I thought I would simply use dynamic sql to construct the name of
the sequence, and then call that sequence. Your results however humble me. I
think I'll code the if-then-else statement, for each potential set of arguments
passed in, e.g. something like:

--
if vFldname = 'VARTXT' and vDbid = 'MAIN1' then
   select vartxt_main1_seq.nextval
     into vseq
     from dual;
...
end if;
return (vseq);
--

Any other ideas you might have of getting around this would be helpful.

Also, the reason I decided not to do the 'select seq.nextval from dual' in the
individual packages was to avoid having to maintain the packages ourselves
(those packages are also owned by the third party). The same reason applied for
the forms.

Kashif
Followup:
don't use 70+ different sequences then -- they just need random numbers right,
for surrogate keys.  You can get away with a single sequence, no problem.

Just change the function to:

  select myseq.nextval into l_returnval from dual;
  return l_returnval;
Rating: 4
NDS and BV October 30, 2002
Reviewer: Robert C from PA
Tom, i want to confirm with you on this, altho I
read your chapter on NDS like 5 times....

NDS uses BV in a "positional" fashion correct ?
meaning :

sql_stmt := ’INSERT INTO dept VALUES (:1, :2, :3)’;
EXECUTE IMMEDIATE sql_stmt USING id, dept, location;

the BVs : id, dept, location - are mapped to - :1, :2, :3
- by position, right ?

so if i am to build an DYNAMIC SQL at run-time like:

'insert into GTT
select * from emp_a where hiredate between to_date(:s) and to_date(:e)
UNION ALL
select * from emp_b where hiredate between to_date(:s) and to_date(:e)
UNION ALL
.....'

and I DON'T know how many emp_? tables to UNION at compile,
I can NOT make use of NDS with the USING clause, correct ?

The DBAs - infected with the FUD disorder - is sitting on my request for app
context so I have to make do w/o it for now...
Thanks
Followup:
yes, positional.

you can definitely use DBMS_SQL for that without an application context.
if you can pack S and E into a varchar2(64) -- you can use
dbms_application_info.

The following shows the issue and how you can use dbms_application_info in your
case (without waiting for the DBA)

ops$tkyte@ORA920.US.ORACLE.COM> create table t ( x date, y date );

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> declare
  2      l_x date := sysdate;
  3      l_y date := sysdate;
  4  begin
  5      execute immediate
  6      'insert into t ( x, y )
  7       select :x, :y from dual' using l_x, l_y;
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> declare
  2      l_x date := sysdate;
  3      l_y date := sysdate;
  4  begin
  5      execute immediate
  6      'insert into t ( x, y )
  7       select :x, :y from dual
  8       union all
  9       select :x, :y from dual' using l_x, l_y;
 10  end;
 11  /
declare
*
ERROR at line 1:
ORA-01008: not all variables bound
ORA-06512: at line 5

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> declare
  2      l_x date := sysdate;
  3      l_y date := sysdate;
  4  begin
  5      dbms_application_info.set_client_info(
to_char(l_x,'yyyymmddhh24miss')||to_char(l_y,'yyyymmddhh24miss') );
  6
  7      execute immediate
  8      'insert into t ( x, y )
  9       select
to_date(substr(userenv(''client_info''),1,14),''yyyymmddhh24miss''),
 10
to_date(substr(userenv(''client_info''),15),''yyyymmddhh24miss'')
 11         from dual
 12       union all
 13       select
to_date(substr(userenv(''client_info''),1,14),''yyyymmddhh24miss''),
 14
to_date(substr(userenv(''client_info''),15),''yyyymmddhh24miss'')
 15         from dual';
 16  end;
 17  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> select * from t;

X         Y
--------- ---------
31-OCT-02 31-OCT-02
31-OCT-02 31-OCT-02
31-OCT-02 31-OCT-02

ops$tkyte@ORA920.US.ORACLE.COM>
Rating: 5
SUBSTR on sys_context('userenv','client_info') - still a BV ? November 01, 2002
Reviewer: Robert C from PA
>>if you can pack S and E into a varchar2(64) -- you can >>use
dbms_application_info.
thank you, Tom,
Yes, that's exactly what i am resorting to doing now...
and i'd pack segments of data into that 64 byte space
like....
l_Param := pdate_start || pdate_end || pClientID || psState;
DBMS_APPLICATION_INFO.SET_CLIENT_INFO(l_Param);

and then I'd have to SUBSTR on the sys_context
like...hiredate BETWEEN
To_Date(substr(SYS_CONTEXT ('userenv','client_info'),1,11))

Now is Oracle still treating this as BV ?
Followup:
yes -- it will (treat like bind variable)
Rating: 5
How to pass NULL to a bind variable? March 31, 2003
Reviewer: A Reader from USA
We have a select statement like the following:

select * from t where some_value=:judge_value;

The judge_value could be NULL.  Since NULL cannot use "=", how should we pass
the NULL to use the bind varible?
Thank you for your help.
Followup:
well, you'll have to tell me what you want to happen when you bind null?

many times, people want "all rows"
some times, people want "null rows"

which do you want?
Rating: 5
How to pass NULL to a bind variable? March 31, 2003
Reviewer: A Reader from USA
Since you mentioned it... *BOTH* ... ;)
Followup:
Ok, this would be it then:

scott@ORA817DEV> create or replace package demo_pkg
  2  as
  3      type rc is ref cursor;
  4
  5      procedure get_data( p_cursor in out rc,
  6                          p_filter in varchar2 default NULL,
  7                          p_all    in boolean default FALSE );
  8  end;
  9  /

Package created.

scott@ORA817DEV>
scott@ORA817DEV> create or replace package body demo_pkg
  2  as
  3      procedure get_data( p_cursor in out rc,
  4                          p_filter in varchar2 default NULL,
  5                          p_all    in boolean default FALSE )
  6      is
  7      begin
  8          if ( NOT p_all )
  9          then
 10              if ( p_filter is not null )
 11              then
 12                  open p_cursor for select empno, ename from emp where job
like p_filter;
 13              else
 14                  open p_cursor for select empno, ename from emp where job is
null;
 15              end if;
 16          else
 17              open p_cursor for select empno, ename from emp;
 18          end if;
 19      end;
 20  end;
 21  /

Package body created.

scott@ORA817DEV>
scott@ORA817DEV> variable x refcursor
scott@ORA817DEV> set autoprint on
scott@ORA817DEV> exec demo_pkg.get_data( :x, '%A%' );

PL/SQL procedure successfully completed.

     EMPNO ENAME
---------- ----------
      7499 ALLEN
      7521 WARD
      7566 JONES
      7654 MARTIN
      7698 BLAKE
      7782 CLARK
      7788 SCOTT
      7844 TURNER
      7902 FORD

9 rows selected.

scott@ORA817DEV> exec demo_pkg.get_data( :x );

PL/SQL procedure successfully completed.

no rows selected

scott@ORA817DEV> exec demo_pkg.get_data( :x, p_all => TRUE );

PL/SQL procedure successfully completed.

     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.
Rating: 3
Procedure params as Bind Vars. August 18, 2003
Reviewer: Clark from Leeds, UK
Hi Tom,

I understand BV's - I think! - but...

My front end app calls a stored proc with an acctno as the parameter.  I can
make that acctno a BV upon calling Oracle.

However, as far as I can tell, there is no way to SET that BV without at some
point registering a different piece of SQL in the cache. Is that correct?

Example.

I can exec a stored proc thus from SQL*Plus:
    BEGIN ACCT_PROC(:ACCBV); END;

But to set :ACCBV from SQL*Plus before executing ACCT_PROC I have to
    BEGIN :ACCBV := 123; END;

So although I've made the first 'query' shareable, I've had to introduce a
different hardcoded, non-reusable 'query' into the pool in order to set the BV.

Is there a way of setting the :ACCBV bind var without at some point hardcoding
the value?  I feel sure I must be missing something, but I've run out of
ideas...

Thanks.
Followup:
only in sqlplus and since sqlplus is NOT A DEVELOPMENT TOOL really - just a
casual interface to the database -- you would not be doing this.

you would be using a real programming language to build a real program.
Rating: 2
Development tools/ bind vars August 19, 2003
Reviewer: Clark from UK
Tom,

I know sqlplus isn't a dev tool, but nevertheless the front-end tool, Delphi,
Developer, whatever, must at some point issue a command to Oracle to set a
bindvar.  If it's not to 'clog up' the sql cache, like the sqlplus example
would, how does it do it?

Fancy GUI says
"Please enter the account number: _______"

GUI posts a command to Oracle along the lines of
    BEGIN ACCT_PROC(123); END;
but presumably using bindvars
    BEGIN ACCT_PROC(:ACCBV); END;

So what command does the GUI send to Oracle to set that bindvar, :ACCBV, to 123?
 There must always be an interface (yes?) between the client and the server:
    Client -> Issue Oracle command
    -> Oracle/server processes command
    -> Returns result to client
irrespective of whether the client is sqlplus or the best dev tool in the world.

How/when did the client GUI set any bindvars - does Oracle bind to variables in
memory in the client?

I'm just trying to understand properly how these things work so I can use Oracle
exactly how it is meant to be used.

Cheers.
Followup:
no, the front end tools use API's to bind values to statements programatically.

think about it.  you execute:

begin :x := 55; end;

there is a bind variable there already!!!  ;x!!!  sqlplus programatically bound
:x on input (as null) -- just like delphi would.

binding doesn't require executing SQL

executing sql can require binding.

consider if you execute:

begin :y := 55; end;

begin :x := :y; end;

that second statement -- uses binds totally right?  no unique sql must be parsed
for that.

don't get lost in the weeds.
Rating: 4
bind variable September 16, 2003
Reviewer: reader
declare cursor c1
....

begin

for rec in c1 loop

select col1, col2
from ...
where col3 = rec.col2;

end;

In the above case, rec.col2 is a bind variable or not?
Followup:
in plsql with static sql is is IMPOSSIBLE to not bind correctly.

yes, rec.col2 is a bind variable -- 100%

you want to scale, use static sql in plsql, all binds -- all the time.
Rating: 5
Refcursor and execute immediate .... September 16, 2003
Reviewer: reader
In general, If I use refcursor or dynamic sql using 'execute immediate' in
pl/sql procedures, there is no way I can use bind variable. Is this correct?
Thanks.
Followup:
false, totally 110% false.

execute immediate p_string using l_input1, l_input2, l_input3;

open p_cursor for l_string using l_input1, l_input2;
Rating: 4
Why refcursor cannot be cached? September 16, 2003
Reviewer: reader
my colleague says, If I use refcursor in the pl/sql procedure, it cannot be
cached. Does it mean that Orcale has to do hard parse every time refcursor is
used even though I open the refcursor for the same statement?
Followup:
not a HARD parse, just a parse.  we don't know if it will be a

a) hard parse
b) soft parse
c) softer soft parse via "session_cached_cursors"

the reason it cannot be cached (i go into more detail in my new book "Effective
Oracle by Design") in a nutshell?  consider:

is
   open l_cursor for p_string;
   ....
end;

or

is
   if ( x = 5 )
   then
       open l_cursor for select * from emp;
   else
       open l_cursor for select * from dept;
   end if;
   ...
end;

so there, the query changes from call to call, not really possible to "cache",
the query associated with the cursor is not static -- but dynamic (confusing,
the second example is STATIC sql, but the sql associated with the ref cursor is
not "static" itself

or, say you have a procedure P that opens a ref cursor and returns it.  you
code:

variable x refcursor
variable y refcursor

exec p( :x );
exec p( :y );

the cursor is now opened TWICE at the same time -- if it were cached, they would
both point to the same result set, set cursor -- but they cannot.

so, the flexibility of the ref cursor comes at a cost -- that of incurring a
PARSE (be it hard, soft or softer soft) each time.
Rating: 4
how to use binding on a Function. September 26, 2003
Reviewer: jane from boston, usa
Hi Tom,

I would like know how to use binding on a fuction like this that is throughtout
all my code.

FUNCTION GET_JOB_STUDIES (P_JOB IN VARCHAR2)
RETURN CURSORTYPE_COMPONENT
IS
V_LOG CURSORTYPE_COMPONENT;
STR   VARCHAR2(3000);
BEGIN
STR:='SELECT SAMPLE_pkg.get_study_number(samples_fk_id) STUDY_NUMBER FROM
      JOB_SAMPLES WHERE jobs_fk_id='''||P_JOB||'''
and record_status = ''CURRENT''
AND SAMPLE_TRACK_JAVA_PKG.get_study_number(samples_fk_id) NOT IN (
SELECT
NVL(SUBSTR(COMPONENT_FK_ID,1,INSTR(COMPONENT_FK_ID,''_'',1,2)-1),COMPONENT_FK_ID
)
FROM TP_COMPONENTS
WHERE TEMPLATE_PREP_FK_ID LIKE ''%'||P_JOB||'%''
AND RECORD_STATUS=''CURRENT'')';

 OPEN V_LOG FOR STR;
 RETURN V_LOG;
 CLOSE V_LOG;
END GET_JOB_STUDIES;
Followup:
i am so afraid of all of the java/plsql functions I see in your code -- this
"java_pkg" stuff keeps popping up.  not a good thing.  used judiciously, when
you have NO OTHER CHOICE, ok.  used all over the place, not good.

tell me -- why is this a dynamic query at all?  looks like a static sql query to
me.

but it would be:

STR:='SELECT SAMPLE_pkg.get_study_number(samples_fk_id) STUDY_NUMBER
        FROM JOB_SAMPLES
       WHERE jobs_fk_id= :x
         and record_status = ''CURRENT''
         AND SAMPLE_TRACK_JAVA_PKG.get_study_number(samples_fk_id)
             NOT IN (SELECT
NVL(SUBSTR(COMPONENT_FK_ID,1,INSTR(COMPONENT_FK_ID,''_'',1,2)-1),COMPONENT_FK_ID

)
FROM TP_COMPONENTS
WHERE TEMPLATE_PREP_FK_ID LIKE :y
AND RECORD_STATUS=''CURRENT'')';

open v_log for str using p_job, '%'||p_job||'%';

 OPEN V_LOG FOR STR;
Rating: 5
How to unbind? December 19, 2003
Reviewer: A reader
Hi tom, I have queries, used in forms, functions, in loops, etc.
like this

FOR A IN (SELECT CODE FROM TABLE ) LOOP
 SELECT SOMETHING FROM TABLE2 WHERE CODE=A.CODE;
END LOOP;

 SELECT SOMETHING FROM TABLE2 WHERE CODE=A.CODE;
is binded
 SELECT SOMETHING FROM TABLE2 WHERE CODE=:1

What can I do to get the parse not bind the value in the code field.
this is
 SELECT SOMETHING FROM TABLE2 WHERE CODE='valu1'
 SELECT SOMETHING FROM TABLE2 WHERE CODE='value2'
 SELECT SOMETHING FROM TABLE2 WHERE CODE='value3'

Something like

 SELECT /*+ not bind column CODE, because I want to use an
histogram, create a distinct execution plan for every different value I use */
 SOMETHING FROM TABLE2 WHERE CODE=:1

Thanks Tom.
Followup:
that is horrible.

it should just be:

for a in ( select table.code, table2.something from table, table2 where
table.code = table2.code )

never never never do what you are doing.  LET THE DATABASE DO THAT WHICH IT WAS
CODED TO DO.  (eg: databases were BORN to join)

but yes, "where code = a.code" is using binds and running at a fraction of the
speed, using orders of magnitude more resources then it should.  but, at least
it uses binds.

do it in a join and don't sweat it.  if you do a big bulky query (bulky meaning
"let sql do ITS JOB", not "bulky awkward") -- you won't even have to concern
yourself with the stuff you are trying to now.

You'd have to dynamically build, prepare, execute, fetch and close a query to
"avoid binds"
Rating: 5
December 22, 2003
Reviewer: A reader
Thanks Tom.
Rating: 3
ref cursor all very well but .... January 08, 2004
Reviewer: Simon from Lyme Regis, UK
Hey Tom!
Thanks for all over the years man.

I want to create  an API with a ref cursor returning function a bit like a view,
advantage is that I can keep the schema private. But I can not seem to get it to
work like a view in terms or returning the data into another table...

SQL> insert into tasksx select  test_pkg.fn_gettasks(123) from dual;
insert into tasksx select  test_pkg.fn_gettasks(123) from dual
            *
ERROR at line 1:
ORA-00947: not enough values

-- This is weird as the package just opens a cursor on the tasks table, the
tasksx table was created as "select * from tasks where 1=2" so they should be
the same.
Followup:
a ref cursor is a query, a cursor.

specifically -- it is not a TABLE (and a view is just a special kind of table).

seems to me you really DO want a view here.  You have the same "security and
encapsulation" niceness with a view.
Rating: 4
view vs ref cursor January 08, 2004
Reviewer: Simon
I don't think a view shields a client from the underlying schema? Surely they
can look at the code it is based on?
The ref cursor must be usable from the package as an output parameter, I just
can't seem to find an example. Typically, would be great to see how to put the
data into a table if it is possible - hey maybe it isn't?
Followup:
you are saying "i want to insert into"

they are unshielded right there.  which way do you want it?  shielded (means
they call a procedure that does the insert into) or not (means they need a
database TABLE to use)

A cursor is procedurally processed.  You would have to actually write code,
fetch rows, insert them.
Rating: 2
from view / ref cursor to client app/database January 09, 2004
Reviewer: Simon
Hi Tom
The situation is that we have an off the shelf app and schema. We wish to expose
some data but using a view allows the client to easily see where it comes from.
Using a package with a function that returns a ref cursor seems great
particularly as we can parameterise the function. But, the issue I have is how a
user would use the function, ie what code would be used to call the function and
loop through the results or insert them into the customer's own database. I am
sure it is possible but cant find any code examples.
Thanks for addressing this.
Followup:
a view does not allow that?

not anymore than your code will.

If you want them to be able to use SQL, they will NEED a view or table.  Period.

If you want them to procedurally process this data, you will need to read the
PL/SQL docs (it is literally FULL of cursor loops that fetch from an open cursor
and do stuff with the data -- very very basic stuff)
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96624/06_ora.htm#1510

Rating: 3
March 22, 2004
Reviewer: Krishna from Bangalore, India
>if you can pack S and E into a varchar2(64) -- you can use
>dbms_application_info.

What if it is not possible to pack the vars into a varchar2(64)? What are the
other options I have of easily binding the vars?
Followup:
well, bind variables to start (you can almost always use them)

CREATE context MY_CTX using MY_PROC;

is the other way -- there you can set as many variables as you want in your own
context and access them via:

  sys_context( 'my_ctx', 'variable_name' );

that is the "other option".  You can write a plsql function as well -- that
returns the "parameter" and then

   select * from t where x = f();

or -- to make it get called once per query (to help ensure it gets called once
per query):

   select * from t where x = ( select f() from dual where rownum > 0 );

but the application context would be the right way.
Rating: 4
Another method besides using an "if" statement to check for a null parameter? March 24, 2004
Reviewer: ht from california
Tom,
One of the threads above discusses using "if" statements to handle joins that
include nulls:
if in_x is null then
 update y set a=1 where x is null;
else
 update y set a=1 where x=in_x;
end if;
Is this the only way to handle parameters that may pass a null?  If I have an
update statement with 10 columns that join on 10 parameters that are passed, I
would need 10 queries?

TIA

ht
Followup:
update t
   set a = 1
 where x = in_x or (x is null and in_x is null);

However, remember entirely NULL keys = not indexed.  Soooo, that update will
always full scan, soooo, if you want it using an index -- you will avoid NULLs
in the first place.

(thats not really "a join", it is a "predicate" technically...)
Rating: 5
How does one avoid nulls? March 25, 2004
Reviewer: ht from california
Tom,
Thanks for the solution and it's performance impact.  I'm wondering if I should
be using a combination of SQL*Loader / pl/sql or another method to implement
this:

1.  pass all dba_users recs into x_dba_users to a stored proc.
2.  the stored proc updates a date column if no recs have changed or inserts if
the rec has changed
3.  query x_dba_users to monitor changes in users

Since multiple-versioned instances will be inserting into this table so I can
alert on password changes, new users created, account status', etc., nulls need
to be handled in the predicate since v7, v8-8.06, and 8.06+ instances have
varying dba_users columns (not to mention that a v9 instance may have a null
dba_users.account_status that needs to be handled).

Since performance is an issue when the stored proc checks if account_status (or
another column) has changed since the last time the stored proc ran, can you
suggest a better methodology?

Thanks again,
ht
Followup:
sorry -- not sure what you are doing here entirely.

don't know why you would be updating by anything other than 'username' and that
would sort of be "constant"

To find changes (null or otherwise) would be a simple "MINUS"

select * from dba_users
MINUS
select * from your_table;

would give everything in dba_users not in your_table and the reverse would give
the reverse...
March 25, 2004 Paul -- Thanks for the question regarding "Setting user information from jdbc client", version 8.1.5
You Asked
Hi Tom,

if you run
  select username,server,osuser,machine,terminal from v$session.
it displays:
username    server    osuser       machine        terminal
USER1        NONE    jdbcuser      jdbcclient     unknown

My question is: Is there any way for a jdbc client application to set the values
for server, machine and terminal?

Thank you!

Paul Huang
and we said...
Not really.

Point 1 -- they most likely don't have a terminal (middle tier I suspect or
running on a platform like windoze that does not have the concept of a
terminal).

Point 2 -- if you use the Oracle jdbc drivers, they do set the machine name
properly, i've never seen them NOT set it.

Point 3 -- the OSUSER is the osuser running the dedicated or shared server, not
the remote OS user.

Using DBMS_APPLICATION_INFO, you can set this information manually into
v$session -- the client_info, action and module columns.

Followup to comment

I just tried it out:

$ cat Emp.java
import java.sql.*;

class Emp
{
    public static void main(String[] args) throws SQLException
    {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try
        {
            // Load Oracle driver
            DriverManager.registerDriver
            (new oracle.jdbc.driver.OracleDriver());
            String connect_string =
                "jdbc:oracle:oci8:@ora8i.us.oracle.com";

            // Connect to the local database
            conn = DriverManager.getConnection
            (connect_string,"scott", "tiger");

            // Query the employee names by job
            ps = conn.prepareStatement
                 ( "select username || ',' || server ||','||
osuser||','||machine||','||terminal " +
                    " from v$session " +
                   "where sid = ( select sid from v$mystat where rownum = 1 )"
);

            rs = ps.executeQuery();

            while (rs.next())
                System.out.println( rs.getString(1) );

            rs.close();
            ps.close();
            conn.close();
        }
        catch (Exception e)
        {
            System.out.println(e + "\n");
        }
        finally
        {
            if ( rs != null ) rs.close();
            if ( ps != null ) ps.close();
            if ( conn != null ) conn.close();
        }
    }
}

$ javac Emp.java

$ java Emp
SCOTT,DEDICATED,tkyte,aria,pts/5

followup to comment

did you use the THICK or THIN drivers.  I used OCI8.
Review & Followup

Rating: 3
DBArtisan does it July 08, 2001
Reviewer: Paul Huang from NY, USA
I did use Oracle jdbc driver when I made the select.
If you use DBArtisan, you can see the columns for OSUSER, MACHINE, TERMINAL,
PROGRAM and COMMAND are set when you bring up the monitor screen.
Rating: 3
Does it run anywhere? July 09, 2001
Reviewer: Paul Huang from NY, USA
I ran the program on NT. Here is part of the output:

MIS,DEDICATED,jdbcuser,jdbcclient,unknown
MIS,DEDICATED,jdbcuser,jdbcclient,unknown
MIS,DEDICATED,jdbcuser,jdbcclient,unknown
MIS,DEDICATED,U028071,FINANCIAL_MIS\WK,WK1CMP22_ZHIMIH
MIS,DEDICATED,U028071,FINANCIAL_MIS\WK,WK1CMP22_ZHIMIH

The 2 lines with U028071 in ODUSER column are connections started by DBArtisan.
The rest are jdbc client connections. Are there any configuration or platform
requrements needed to get what you got?

Paul,
Rating: 3
Version 8.1.6 and above only? July 09, 2001
Reviewer: Paul Huang from NY, USA
If I connect to version 8.1.6, I see the info. What about version 8.1.5?

Paul,
Rating: 3
Question on your 'Point 2' April 03, 2002
Reviewer: A reader from NJ, USA
Tom, regarding your following comment...
###
Point 2 -- if you use the Oracle jdbc drivers, they do set the machine name
properly, i've never seen them NOT set it.
###

Do oracle thin driver also always set the machine name? I have come across some
pesky 'jdbc client' type of connections (with oracle thin driver), where machine
name is not filled in. What would it take for us to get the machine name with
thin driver? Or would we have to use OCI8 instead? We use weblogic 5.1 and
oracle 8.1.6 or 9i. What would be the advantages or disadvantages of switching
from thin to OCI8.
Thanks.
Followup:
thin probably does not do it (easy to find out, take the application i have
above and try it)

there are many advantages to OCI8, features not available with thin and speed
being the two that pop into my head.

On an app server (you have 10's of these), I would definitely be using THICK oci
drivers -- on a client workstation (you have 100's or 1000's of these) I would
be using THIN.
Rating: 4
Can JDBC thin client use share server on 9.0.1? March 25, 2004
Reviewer: John from Canada
Hi Tom,

As I understand, client must use Oracle net to use share server. That means JDBC
thin client cannot use share server, only dedicate? I checked SERVER column on
the v$session table, and got NONE value. Can you please tell me what NONE means
since I didn't find it in the Oracle document. Are there only two types
conection(dedicated or shared) in the Oracle 9.0.1?

Thanks.
Followup:
jdbc thin uses "net"  (think about it -- thin clients are usually NOT on the
server, they must be using net...)

if you got NONE, that is an idle shared server connection!

see also:
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96536/ch3171.htm#1122127

March 25, 2004 Gururaj -- Thanks for the question regarding "Recovery Recovery", version 7.x Or 8.x
You Asked
Hi Tom,

1)How can I use Cold backup files to recover a dropped table, in
other words How can I restore a dropped table using cold back up
files ?

2) Is 'Hot standby database ' fool proof one ?

   I have copied the following lines from a Oreilly's book.

"With the hot stand by database you actually use one database at a time The
second database is constantly in recover mode. AS an
archived redo log file is generated, it is copied to the remote site
and applied to the database.In the event of a failure of the primary
database, you complete the recovery process and bring up the stand by
database.

With this facility you will have to maintain redundant computer and
storage systems and in the case of recovery, you will lost any
committed transactions that have been moved to an archived log file."

 My questions
1) what recovery process is he talking about ? How do I do it ?
2) Could you brief me about 2nd paragraph ?

Thanks
Gururaj
and we said...
1) you would do a point in time recovery of the tablespace.  we must do an
ENTIRE tablespace, not just a table

If you are using RMAN, it automates this for you.  See the rman guides:
http://download-east.oracle.com/docs/cd/A81042_01/DOC/server.816/a76990/rmanconc.htm#439215
If not, you would

o restore system, rollback, and the tablespace constaining the table elsewhere
o you would point in time recover this database to before the drop table
o you would export (or transport using export in 8i) the table (or tablespace)
out of this database and imp to the other database

you MUST be running in archivelog mode or else you will have to revert to the
copy of the table that was in the last cold backup, losing all of the
intervening changes.

2) The recovery process is the standard recovery process Oracle uses when in
archivelog mode.

See
http://download-east.oracle.com/docs/cd/A81042_01/DOC/server.816/a76993/toc.htm
for details on HOW backup and recovery work

and for details on standby to answer the 2cnd paragraph see
http://download-east.oracle.com/docs/cd/A81042_01/DOC/server.816/a76995/toc.htm
...
Review & Followup

Rating: 4
Pretty clear.. also true in 9i? December 11, 2001
Reviewer: Doug from CT, USA
I recognize the problem of recovering "a" table to a different SCN number than
others, but can RMAN do just a table in 9i? (by checking constraints or
something?)
Followup:
Just a tablespace -- it'll be a TABLESPACE.  all of the objects in a tablespace
must be consistent with respect to eachother.  The tablespace is a data
structure, it must be consistent as well.
Rating: 4
Never mind about the 9i thing December 11, 2001
Reviewer: Doug from CT, USA
K jumped the gun...
http://downloadeast.oracle.com/otndoc/oracle9i/901_doc/server.901/a90135/rmancona.htm#459447

Rating: 4
Please provide the above links for 9i R1 Thanks March 25, 2004
Reviewer: A reader
Followup:
all docs are on OTN, really easy to list them, search them.
March 25, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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
Rating: 4
Package state January 25, 2004
Reviewer: Dale Ogilvie from New Zealand
What events cause package state (variables) to be wiped out?

I know that if I make a change to a package then the resulting recompile kills
my package state, but it would seem that a simple compile without changing the
package does not. We are seeing invalidation of package state on production and
I'd like to know what might be causing it. We flush the shared pool each night,
but my testing seems to show that this does not cause invalidation of package
state.

Also, how come this line in my package body causes my package to have state, I
would have expected a constant to not be "state". If I change the function
getdefs below, I get the dreaded invalid state error *only* if I have the
constant in the body.

evt_xyz CONSTANT NUMBER := 84;

The full body def:

PACKAGE BODY PKG_X as

evt_start_moving   CONSTANT NUMBER := 95;

  procedure getdefs(defs out sys_refcursor)
  is
  begin

OPEN defs FOR
  select
    'dale'
  from
    dual;

  end;

end;

thanks

Dale
Followup:
only a call to dbms_session.reset_package or a recompilation would -- and the
recompilation would return "sorry, package state invalidated"

The constant causes that cause the constant could well have become "not so
constant" due to a recompilation.  One time it could have been 95, the next 55
-- we have no way of knowing (don't track things at that level) hence -- you
have a state and you've potentially destroyed it.
Rating: 4
But recompiles alone don't kill state January 26, 2004
Reviewer: Dale Ogilvie from New Zealand
Thanks, just to confirm though. If I do a recompile (without  actually changing
the package body) my state survives.

nccdb@P1ZUSD> alter package pkg_x compile body;

Package body altered.

nccdb@P1ZUSD> exec pkg_x.getdefs( :ignitions );

PL/SQL procedure successfully completed.

'DAL
----
dale
Rating: 5
making procedures invalid voluntarily March 09, 2004
Reviewer: Pushparaj Arulappan from NJ USA
Tom,

Is there a way to make all the dependent stored
programs of a table invalid without dropping or
renaming the table.

Thanks
Pushparaj Arulappan
Followup:
whats the goal?  (eg: why?)

in short, no, but what are you trying to do?
Rating: 3
March 25, 2004
Reviewer: A reader
I have a package that defines all the constants, and all other packages
reference the constants defined in this constant_pkg.

If I add a new constant to constant_pkg, and then recompile all packages and
verify that all packages are "VALID", execute another package that references
this constant_pkg, I will get

ORA-04061: existing state of  has been invalidated
ORA-04061: existing state of package "HCSA1.CONSTANT_PKG" has been invalidated
ORA-04065: not executed, altered or dropped package "HCSA1.CONSTANT_PKG"
ORA-06508: PL/SQL: could not find program unit being called
ORA-06512: at "HCSA1.HOUSEHOLD_PKG", line 320
ORA-06512: at "HCSA1.HOUSEHOLD_PKG", line 1354

What can I do to avoid this problem?
Followup:
schedule system upgrades during periods of little to no activity.

ask yourself "if I had to patch a DLL on windows, a .so file on Unix, a binary
on any OS -- what would I need to do"  that in effect is what you are doing
here....
March 25, 2004 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.
Rating: 5
Insert performance of gtt vs normal heap table March 24, 2004
Reviewer: Peter Tran from Houston, TX USA
Hi Tom,

A coworker recently showed this to me.  Do you do have any light you can shed on
this?

Thanks for your help.

-Peter

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

This is interesting...  It appears that CREATE-AS-SELECT produces equivalent
timing, but if the tables are created first and then populated, the timing
diverges significantly.  And if a PK is added it takes even more time to
populate the temp table.  These timings are consistently repeatable.

This testing was done on cmsr2@soldb2.

SQL> CREATE TABLE objects
  2  AS
  3  SELECT object_id, object_name, object_type, owner
  4    FROM dba_objects
  5   WHERE object_id IS NOT NULL
  6     AND ROWNUM < 100000;

Table created.

Elapsed: 00:00:01.04

SQL> CREATE global temporary TABLE temp_objects
  2  ON COMMIT DELETE ROWS
  3  AS
  4  SELECT object_id, object_name, object_type, owner
  5    FROM dba_objects
  6   WHERE object_id IS NOT NULL
  7     AND ROWNUM < 100000;

Table created.

Elapsed: 00:00:01.04

SQL> rollback;

Rollback complete.

SQL> drop table objects;

Table dropped.

SQL> drop table temp_objects;

Table dropped.

SQL> CREATE TABLE objects (
  2   OBJECT_ID     NUMBER,
  3   OBJECT_NAME   VARCHAR2(128),
  4   OBJECT_TYPE   VARCHAR2(18),
  5   OWNER         VARCHAR2(30)
  6  );

Table created.

SQL> create global temporary table temp_objects
  2  on commit delete rows
  3  as
  4  select * from objects where 1=0;

Table created.

SQL> INSERT INTO objects (object_id, object_name, object_type, owner)
  2  SELECT object_id, object_name, object_type, owner
  3    FROM dba_objects
  4   WHERE object_id IS NOT NULL
  5     AND ROWNUM < 100000;

38468 rows created.

Elapsed: 00:00:01.06

SQL> INSERT INTO temp_objects (object_id, object_name, object_type, owner)
  2  SELECT object_id, object_name, object_type, owner
  3    FROM dba_objects
  4   WHERE object_id IS NOT NULL
  5     AND ROWNUM < 100000;

38468 rows created.

Elapsed: 00:00:04.05

SQL> rollback;

Rollback complete.

Elapsed: 00:00:02.03

SQL> drop table objects;

Table dropped.

SQL> drop table temp_objects;

Table dropped.

SQL> CREATE TABLE objects (
  2   OBJECT_ID     NUMBER,
  3   OBJECT_NAME   VARCHAR2(128),
  4   OBJECT_TYPE   VARCHAR2(18),
  5   OWNER         VARCHAR2(30)
  6  );

Table created.

SQL> ALTER TABLE objects
  2    ADD  ( CONSTRAINT objects_PK
  3           PRIMARY KEY (OBJECT_ID)
  4  );

Table altered.

SQL> create global temporary table temp_objects
  2  on commit delete rows
  3  as
  4  select * from objects where 1=0;

Table created.

SQL> ALTER TABLE temp_objects
  2    ADD  ( CONSTRAINT temp_objects_PK
  3           PRIMARY KEY (OBJECT_ID)
  4  );

Table altered.

SQL> INSERT INTO objects (object_id, object_name, object_type, owner)
  2  SELECT object_id, object_name, object_type, owner
  3    FROM dba_objects
  4   WHERE object_id IS NOT NULL
  5     AND ROWNUM < 100000;

38470 rows created.

Elapsed: 00:00:02.02

SQL> INSERT INTO temp_objects (object_id, object_name, object_type, owner)
  2  SELECT object_id, object_name, object_type, owner
  3    FROM dba_objects
  4   WHERE object_id IS NOT NULL
  5     AND ROWNUM < 100000;

38470 rows created.

Elapsed: 00:00:07.09
Followup:
if you are in noarchivelog mode, it looks like this:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3329273788164#15940387620208
(the CTAS = no redo, insert = redo)
Rating: 4
9.2.0.5 fixes the bug March 24, 2004
Reviewer: Peter Tran from Houston, TX USA
Hi Tom,

The database I'm testing against was 9.2.0.4 in noarchivedlog mode.

This bug is fix in 9.2.0.5 and 10g.  I just reinstalled the 9.2.0.5 patch, and
reran my test.  The numbers are much better.

Thanks,
-Peter

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

sqlplus> CREATE TABLE objects
  2  AS
  3  SELECT object_id, object_name, object_type, owner
  4    FROM dba_objects
  5   WHERE object_id IS NOT NULL
  6     AND ROWNUM < 100000;

Table created.

Elapsed: 00:00:01.09

sqlplus> CREATE global temporary TABLE temp_objects
  2  ON COMMIT DELETE ROWS
  3  AS
  4  SELECT object_id, object_name, object_type, owner
  5    FROM dba_objects
  6   WHERE object_id IS NOT NULL
  7     AND ROWNUM < 100000;

Table created.

Elapsed: 00:00:01.07

sqlplus> drop table objects;

Table dropped.

Elapsed: 00:00:00.01

sqlplus> drop table temp_objects;

Table dropped.

Elapsed: 00:00:00.00

sqlplus> CREATE TABLE objects (
  2   OBJECT_ID     NUMBER,
  3   OBJECT_NAME   VARCHAR2(128),
  4   OBJECT_TYPE   VARCHAR2(18),
  5   OWNER         VARCHAR2(30)
  6  );

Table created.

Elapsed: 00:00:01.00

sqlplus> create global temporary table temp_objects
  2  on commit delete rows
  3  as
  4  select * from objects where 1=0;

Table created.

Elapsed: 00:00:01.00

sqlplus> INSERT INTO objects (object_id, object_name, object_type, owner)
  2  SELECT object_id, object_name, object_type, owner
  3    FROM dba_objects
  4   WHERE object_id IS NOT NULL
  5     AND ROWNUM < 100000;

39682 rows created.

Elapsed: 00:00:03.01

Statistics
----------------------------------------------------------
        891  recursive calls
       3028  db block gets
      33818  consistent gets
          0  physical reads
    2247796  redo size
        625  bytes sent via SQL*Net to client
        946  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      39682  rows processed

sqlplus> INSERT INTO temp_objects (object_id, object_name, object_type, owner)
  2  SELECT object_id, object_name, object_type, owner
  3    FROM dba_objects
  4   WHERE object_id IS NOT NULL
  5     AND ROWNUM < 100000;

39682 rows created.

Elapsed: 00:00:03.01

Statistics
----------------------------------------------------------
          9  recursive calls
       1464  db block gets
      33315  consistent gets
          0  physical reads
     141152  redo size
        628  bytes sent via SQL*Net to client
        951  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      39682  rows processed

sqlplus> CREATE TABLE objects (
  2   OBJECT_ID     NUMBER,
  3   OBJECT_NAME   VARCHAR2(128),
  4   OBJECT_TYPE   VARCHAR2(18),
  5   OWNER         VARCHAR2(30)
  6  );

Table created.

Elapsed: 00:00:00.00

sqlplus> ALTER TABLE objects
  2    ADD  ( CONSTRAINT objects_PK
  3           PRIMARY KEY (OBJECT_ID)
  4  );

Table altered.

Elapsed: 00:00:00.02

sqlplus> create global temporary table temp_objects
  2  on commit delete rows
  3  as
  4  select * from objects where 1=0;

Table created.

Elapsed: 00:00:00.00

sqlplus> ALTER TABLE temp_objects
  2    ADD  ( CONSTRAINT temp_objects_PK
  3           PRIMARY KEY (OBJECT_ID)
  4  );

Table altered.

Elapsed: 00:00:00.00
sqlplus> INSERT INTO objects (object_id, object_name, object_type, owner)
  2  SELECT object_id, object_name, object_type, owner
  3    FROM dba_objects
  4   WHERE object_id IS NOT NULL
  5     AND ROWNUM < 100000;

39684 rows created.

Elapsed: 00:00:04.06

Statistics
----------------------------------------------------------
       1361  recursive calls
      36830  db block gets
      34505  consistent gets
          2  physical reads
    8143996  redo size
        629  bytes sent via SQL*Net to client
        946  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          4  sorts (memory)
          0  sorts (disk)
      39684  rows processed

sqlplus> INSERT INTO temp_objects (object_id, object_name, object_type, owner)
  2  SELECT object_id, object_name, object_type, owner
  3    FROM dba_objects
  4   WHERE object_id IS NOT NULL
  5     AND ROWNUM < 100000;

39684 rows created.

Elapsed: 00:00:04.04

Statistics
----------------------------------------------------------
        209  recursive calls
      34676  db block gets
      34069  consistent gets
          0  physical reads
    3496400  redo size
        630  bytes sent via SQL*Net to client
        951  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          8  sorts (memory)
          0  sorts (disk)
      39684  rows processed
Rating: 4
Is this possible in ORACLE March 25, 2004
Reviewer: Rick from Oregon
I will put the pseudo code here

procedure1 ( xxxxx, variable_A)

and then in another procedure

procedure_2(xxxx,xxxx)
variable_A = "happy"

in order words I want to be able to reference
a variable that has been defined in
procedure2 in procedure1 IN THE
INPUT AREA. I Hope this make sense.
Followup:
nope, no sense....  not sure what you are asking.

what is an "input area"?

but basically variable_a's scope is procedure1 -- if you code:

procedure p1( x, y )
as
  ...
  procedure p2( ... )
  as
  begin
      y := .....;
  end;
begin
   ......

that'll work, but otherwise, Y (variable_a) is private to procedure1.
March 25, 2004 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
Rating: 5
how do i find lob's tablespace February 24, 2004
Reviewer: Umesh Kasturi from Bangalore , India
Tom
Without using the export import( show=y) Is there any query to find out in which
Tablespace the LOB column is stored
Thanks in advance
Followup:
select * from user_segments

you can join user_segments to user_lobs if you like as well.

user_segments will give you tablespace info.
user_lobs will give you the lob segment name.
Rating: 4
Blob, Import March 02, 2004
Reviewer: Ignatius from India
Hi Tom,
I have a dump file containing blob datatypes, when i import the dump file in a
schema it gives an error stating that the tablespace for Blob datatype does not
exists. My question is how do i import the dump file in the default tablespace
of the importing user.
Followup:
You'll have to precreate the table.

do this:

imp userid=u/p tables=that_table indexfile=that_table.sql

edit that_table.sql, fix up the tablespace references to be whatever you want to
be, run that sql.

then imp with ignore=y

for any MULTI-SEGMENT object (iot's with overflows, tables with lobs,
partitioned tables, for example), you have to do this -- imp will not rewrite
ALL of the tablespaces in that multi-tablespace create -- hence you either need
to have the same tablespaces in place or precreate the object with the proper
tablespaces.

Only for single tablespace segments will imp rewrite the create to go into the
default if the requested tablespace does not exist.
Rating: 3
Trying to move lob, getting error message March 03, 2004
Reviewer: Mike from Portland, OR
As part of moving lobs in an anonymous block (not sure if that's relevant), we
are running into the following error:

...
cLine := 'ALTER TABLE DOCS move lob(DOCTEXT) store AS (TABLESPACE doclobs)';
dbms_output.put_line(cLine);
execute immediate cLine;
...

ALTER TABLE DOCS move lob(DOCTEXT) store AS (TABLESPACE Doclobs)
DECLARE
*
ERROR at line 1:
ORA-01562: failed to extend rollback segment number 1
ORA-01650: unable to extend rollback segment ROLLB1 by 256 in tablespace
ROLLBACKTBS
ORA-06512: at line 14

I know, the solution is to increase the rollback space.  But how can I determine
how much rollback space this command will take?
Followup:
well, the undo is going to be all about the LOBINDEX, not the lob segment
itself, so I would look at the size of the lobindex segment (dba_segments) in
order to see how big it is an consider 3-4 times that as a start
Rating: 4
Export, Blob Datatype March 05, 2004
Reviewer: Ignatius from India
Thank you very much Tom, Thank you for your help and support.
So you mean to say that to import tables with Blob datatype (in different
tablespace - non existent in the importing Database) i have to pre-create the
tablespace name containing in the dump file or edit the dump file and remove the
tablespace pointer. Is there no way to import the dump file in the default
tablespace of the importing user without editing the dump file or precreating
the tablespace in new Database.
Thank you very much
Followup:
you have to precreate the EMPTY table.

never never never edit a dmp file -- they are binary.

You do not have to precreate the tablespace -- rather, you would create the
empty table to import into (try the steps I outlined above -- you'll see that I
extracted the create table statement from the dmp file, then said "edit that
create table" to fix up the tablespace references to be whatever you want,
create the table and import as normal)
Rating: 4
missing left parens March 10, 2004
Reviewer: Jim from Phila,PA
I am having a problem using your example.  I enter:
alter table t modify lob(y) store as ( tablespace lrg_tbl01)

and sql returns:

missing left parenthesis and points to the beginning of the word store.
this is not a partitioned table.
any suggestions?  I am trying to rebuild the tablespace and this is the last
item in it.
Thanks for your help.
Followup:
hmm, interesting, looks like the above syntax was "invalid" but accidently
accepted in older releases (it should never have worked)

ops$tkyte@ORA9IR2> alter table t move lob(y) store as ( tablespace users  )
  2  /

Table altered.

works BUT it has the side effect that the table is also moved.  There does not
seem to be a way to move the lob but not the table as well.

one that won't move the table could be:

ops$tkyte@ORA9IR2> create table t ( x int, y clob )
  2  tablespace t2_demo
  3  lob(y) store as (tablespace t1_demo)
  4  /

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2          l_data long := rpad('*',30000,'*');
  3  begin
  4          for i in 1 .. 100
  5          loop
  6                  insert into t values ( i, l_data );
  7          end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> commit;

Commit complete.

ops$tkyte@ORA9IR2> select tablespace_name, segment_name, segment_type, blocks
from user_segments;

TABLESPA SEGMENT_NAME                   SEGMENT_TYPE           BLOCKS
-------- ------------------------------ ------------------ ----------
T2_DEMO  T                              TABLE                       8
T1_DEMO  SYS_IL0000035562C00002$$       LOBINDEX                    8
T1_DEMO  SYS_LOB0000035562C00002$$      LOBSEGMENT                512

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> alter table t add z clob lob(z) store as (tablespace
t3_demo);

Table altered.

ops$tkyte@ORA9IR2> lock table t in exclusive mode;

Table(s) Locked.

ops$tkyte@ORA9IR2> update t set z = y;

100 rows updated.

ops$tkyte@ORA9IR2> alter table t set unused column y;

Table altered.

ops$tkyte@ORA9IR2> alter table t rename column z to y;

Table altered.

ops$tkyte@ORA9IR2> select tablespace_name, segment_name, segment_type, blocks
from user_segments;

TABLESPA SEGMENT_NAME                   SEGMENT_TYPE           BLOCKS
-------- ------------------------------ ------------------ ----------
T2_DEMO  T                              TABLE                       8
T1_DEMO  SYS_IL0000035562C00002$$       LOBINDEX                    8
T3_DEMO  SYS_IL0000035562C00003$$       LOBINDEX                    8
T1_DEMO  SYS_LOB0000035562C00002$$      LOBSEGMENT                512
T3_DEMO  SYS_LOB0000035562C00003$$      LOBSEGMENT                512

ops$tkyte@ORA9IR2> alter table t drop unused columns;

Table altered.

ops$tkyte@ORA9IR2> select tablespace_name, segment_name, segment_type, blocks
from user_segments;

TABLESPA SEGMENT_NAME                   SEGMENT_TYPE           BLOCKS
-------- ------------------------------ ------------------ ----------
T2_DEMO  T                              TABLE                       8
T3_DEMO  SYS_IL0000035562C00002$$       LOBINDEX                    8
T3_DEMO  SYS_LOB0000035562C00002$$      LOBSEGMENT                512
Rating: 2
Moving a LOB works fine March 20, 2004
Reviewer: Jon Waldron from Sydney
Unless I'm missing something here, I think you may have made a mistake:

column table_name format a20
column column_name format a20
column lob_tablespace format a20
column table_tablespace format a20
drop table jw
Table dropped

create table jw (x int, y clob) tablespace users
Table created

insert into jw values(1, rpad('*',30000,'*'))
1 row inserted

insert into jw values(2, rpad('*',30000,'*'))
1 row inserted

insert into jw values(3, rpad('*',30000,'*'))
1 row inserted

commit
Commit complete

select l.table_name, l.column_name, s.tablespace_name lob_tablespace, t.tablespa
from user_lobs l, user_segments s, user_segments t
where l.table_name = 'JW'
and l.segment_name = s.segment_name
and t.segment_name = 'JW'
and t.segment_type = 'TABLE'

TABLE_NAME           COLUMN_NAME          LOB_TABLESPACE       TABLE_TABLESPACE
-------------------- -------------------- -------------------- -----------------
JW                   Y                    USERS                USERS
1 row selected

alter table jw move lob(y) store as (tablespace data_lob)
Table altered

select l.table_name, l.column_name, s.tablespace_name lob_tablespace, t.tablespa
from user_lobs l, user_segments s, user_segments t
where l.table_name = 'JW'
and l.segment_name = s.segment_name
and t.segment_name = 'JW'
and t.segment_type = 'TABLE'

TABLE_NAME           COLUMN_NAME          LOB_TABLESPACE       TABLE_TABLESPACE
-------------------- -------------------- -------------------- -----------------
JW                   Y                    DATA_LOB             USERS
1 row selected

Moving a LOB, without moving the table works just fine.
Followup:
post your version, it USED to work (I demonstrated that) but they fixed the
"bug" that allowed it to work "by accident"

as I said:
...
hmm, interesting, looks like the above syntax was "invalid" but accidently
accepted in older releases (it should never have worked)
.....
Rating: 2
Yea of little faith... March 21, 2004
Reviewer: Jon from Sydney
column table_name format a20
column column_name format a20
column lob_tablespace format a20
column table_tablespace format a20
set linesize 100
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

drop table jw
Table dropped

create table jw (x int, y clob) tablespace users
Table created

insert into jw values(1, rpad('*',30000,'*'))
1 row inserted

insert into jw values(2, rpad('*',30000,'*'))
1 row inserted

insert into jw values(3, rpad('*',30000,'*'))
1 row inserted

commit
Commit complete

select l.table_name, l.column_name, s.tablespace_name lob_tablespace,
    t.tablespace_name table_tablespace
from user_lobs l, user_segments s, user_segments t
where l.table_name = 'JW'
and l.segment_name = s.segment_name
and t.segment_name = 'JW'
and t.segment_type = 'TABLE'
TABLE_NAME           COLUMN_NAME          LOB_TABLESPACE     TABLE_TABLESPACE

-------------------- -------------------- ------------------ -------------------
JW                   Y                    USERS              USERS

1 row selected

alter table jw move lob(y) store as (tablespace csee_lob)
Table altered

select l.table_name, l.column_name, s.tablespace_name lob_tablespace,
    t.tablespace_name table_tablespace
from user_lobs l, user_segments s, user_segments t
where l.table_name = 'JW'
and l.segment_name = s.segment_name
and t.segment_name = 'JW'
and t.segment_type = 'TABLE'
TABLE_NAME           COLUMN_NAME          LOB_TABLESPACE     TABLE_TABLESPACE

-------------------- -------------------- ------------------ -------------------
JW                   Y                    CSEE_LOB           USERS

1 row selected
Rating: 2
Hello...you still out there? March 24, 2004
Reviewer: Jon from Sydney
Followup:
ahh, I see -- yes, that one with MOVE works (i said as much above)

...<quote>
hmm, interesting, looks like the above syntax was "invalid" but accidently
accepted in older releases (it should never have worked)

ops$tkyte@ORA9IR2> alter table t move lob(y) store as ( tablespace users  )
  2  /

Table altered.

works BUT it has the side effect that the table is also moved.  There does not
seem to be a way to move the lob but not the table as well.
</quote>

It is:
alter table t modify lob(y) store as ( tablespace lrg_tbl01)
              ^^^^^^

that does NOT
Rating: 2
Yes, but... March 24, 2004
Reviewer: Jon from Sydney
the point being that MOVE does not move the table along with the lob.  You can
in fact move lob and table segments independently of each other.  Your quote was
saying that this is not possible.
Followup:
yes it most certainly does!

ops$tkyte@ORA817DEV> create table t ( x int , y clob );

Table created.

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

1 row created.

ops$tkyte@ORA817DEV> select rowid from t;

ROWID
------------------
AAAOOPAAHAAAJdKAAA

ops$tkyte@ORA817DEV> alter table t move lob(y) store as ( tablespace users  )
  2  /

Table altered.

ops$tkyte@ORA817DEV> select rowid from t;

ROWID
------------------
AAAOOSAAHAAAJkKAAA

see how the rowid changed, the table MOVED (in the same tablespace)

You can just:

alter table t move;

and it just moves the table.  alter table t move <lob junk> moves the table AND
the lob -- your test didn't show that the table doesn't move, it just shows that
move will leave the table in the same tablespace -- but it certainly "moved"
it
Rating: 3
Updating LOBS March 25, 2004
Reviewer: Vikas Bagga from Alpharetta(Atlanta), GA
Great opportunity to hear and meet you at the GOUSER.
I have a hash partitioned table, with LOBS.The way the application works is they
should have the ability to update LOBS with a new LOB image as and when
required. The issue I am having is that the updates take place fine, but the
storage does not get released. This will make the tablespace to grow
indefinitely!!
Second question is every month the application is supposed to delete data
greater than a certain holding period. With a hash partition what special
maintenance issues will I need to perform any performance issues I need to be
aware of.
Thanks
Vikas
Followup:
give me a test case to work with here -- that is not my experience.

your purge won't affect the partitioning here since you'll just be "deleting".
You could have used partitioning to allow you to make for an easier purge, but
hash won't do that.

so, hash partitioning and your purge are really "unrelated".  no different than
if you had a single table...
Rating: 5
hash March 25, 2004
Reviewer: reader
<quote>You could have used partitioning to allow you to make for an easier
purge, but hash won't do that.<quote>

Does it mean one cannot drop a hash partition? Thanks.
Followup:
you can, but the data in there is "uncontrollable"

to do a rolling window of data for a purge, you need RANGE or LIST partitioning
-- where you control the data going into each partition.  hash means "whatever
oracle feels like stuffing in there..."
Rating: 5
Alter table <x> move <lob> March 27, 2004
Reviewer: Jon from Sydney
Too Sharp... 5 stars for that one! I think I'll call it the phantom move :-)
March 25, 2004 -- 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.
Rating: 4
hi March 25, 2004
Reviewer: A reader
I have a before insert/update/delete trigger that inserts :old or :new values
into an audit table. I have defined pragma autonomous_transaction so that I can
commit inside my trigger.

When a user inserts a record into the regular table, an equivilent record is
inserted into the audit table and commits.  Later if the user feels to rollback
the record from the regular table, I am not sure how to get rid off the new
record in the audit table.
Is there a way to work around on this kind of situation.

Thanks
Followup:
delete the line that reads "pragma autonomous_transaction" and it'll "just
happen"
March 25, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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
Rating: 4
Quick question about utl_file.fgetattr February 05, 2004
Reviewer: Jimmy
Hey Tom!

Just a quick question. When loading data into my database via External Tables
there is sometimes a External_Table.bad file that is created in the DATA_DIR.

This is fine but is there any way of using utl_file.fgetattr to scan if the file
exists? Normally I would do something like:

l_filename := 'EXTERNAL_TABLE.bad';
        utl_file.fgetattr('DATA_DIR',l_filename,l_exists,l_size,l_block_size);

But the problem i am having is that the data loading from the external table is
done monthly and the latter part of the .bad file changes depending on when the
procedure is run. Is there any way of using a
LIKE(EXTERNAL_TABLE_%.bad) to pick up the .bad file from the DATA_DIR?

searching for any .bad file will not do because i have multiple external tables
each potentailly creating a .bad file.

Any help would be most appreciated!

Thanks
Followup:
what is the format you used in the create table statement to name the bad file?
did you use a format?
Rating: 4
February 05, 2004
Reviewer: Jimmy
Not at first and this was the underlying reason why i needed to search the
filename of the bad file within the DATA_DIR.

I think that i am gonna have to define a fixed format for each bad file within
my create external tables scripts thus avoiding the problem.

Thanks for your help though

Regards
Rating: 5
Accessing a flat file stored in XDB February 05, 2004
Reviewer: Dave from Colorado Springs
I have recently been exploring the use of XDB, specifically using XDB as an ftp
server.

So people/systems can ftp files to Oracle, through the listener, and they are
stored directly in the database. Now my question,and it may be a rather broad
one I'm afraid, is this:- since this file is now within the database, is there a
way for me to ...

i) find it
ii) read the data in it

... through SQL?

I suspect that there's some XML-based way of doing this, but I've been a
relational database guy for a long time and the XML stuff is an intimidating
learning curve. Obviously I'd like to be able to accept files through ftp and
access them directly without writing them out to the o/s and reading them in
again.

Does this seem achievable?
Followup:
I asked Sean Dillon, our XML technologist, to take a look at this and here's
what he had to say:
-----

Hi Dave,

Yes, absolutely.  There are a couple of ways to do what you want to do, and the
good news is they've all been documented/sampled/explained time and again.  I'll
explain some of these things and then point you to some resources on OTN that
you should read that will tell you what you want to know.

First... how do you FIND your XML files in the XML DB repository?  Well, that's
pretty simple.  With XML DB comes a couple of views you'll want to familiarize
yourself with.  These are RESOURCE_VIEW and PATH_VIEW.  These views provide you
a mechanism to access data stored in the XML Repository from SQL.

RESOURCE_VIEW:
Column   Datatype Description
------   -------- -------------------------------------------------------
RES      XMLTYPE  A resource in Oracle XML repository
ANY_PATH VARCHAR2 A path used to access the resource in the repository

PATH_VIEW:
Column Datatype Description
------ -------- -----------------------------
PATH   VARCHAR2 Path name of a resource
RES    XMLTYPE  The resource referred by PATH
LINK   XMLTYPE  Link property

If you look in the XML DB Developer's Guide, chapter 18 is entitled SQL Access
Using RESOURCE_VIEW and PATH_VIEW.  You can find the documentation on OTN here:
http://otn.oracle.com/pls/db92/db92.docindex?remark=homepage
Go to the bottom of that page, get the XML DB Developer's Guide.

For accessing the data, there are a number of ways to do this.  If you load in
arbitrary XML (i.e., XML that does NOT use a database-registered XML Schema, the
only option you have is through XMLType access methods (because essentially,
that's what you've loaded into the repository).

There have been quite a few articles on this subject in Oracle Magazine, I'd
suggest reading those and doing the examples to get a better understanding of
how you can access the XML data stored in the repository.  If after that you
still have questions, come back to AskTom and fire away ;-)  (i.e., no need to
rewrite the article's contents here ;-)).  Check out these articles:
http://otn.oracle.com/pls/db92/db92.docindex?remark=homepage

http://otn.oracle.com/oramag/oracle/03-jul/o43xml.html
Hope that helps!

_smd_
Rating: 4
External tables or ETL tool February 07, 2004
Reviewer: A reader
Hi Tom,
There is a requirement to create logical record from ASCII input files (total
10). The input files size vary from 1GB to 5GB. The relationships between
various input ASCII files are known. The final o/p is required in ASCII file. We
also have third party ETL tool. Initially, I was thinking of using ETL tool, but
on reading your new book (page 411), I am thinking of using external tables (we
have Oracle9i - 9.2). Could you please give your advice with the approach below:
1.    Create external tables with for each input file, and then create tables
(as per example on page 411) in parallel.
2.      Create required indices
3.    Join various tables created in step 1, and then create ASCII file through
sqlplus.
The advantage of ETL tools is I need not to create tables ie., I can join ASCII
files, and finally create ASCII file. But I am not sure about performance. With
external tables approach, even creating tables, and then creating indices , and
finally spool the file, will also have performance impact. Further, I noticed
before (in older versions of oracle) that there was some limitation in o/p file
size produced through sqlplus. I am expecting o/p file size in 10 GB (records
are formed from various fields of input files). Is there maximum file size limit
for the file to be produced through sqlplus in 9.2 (64bit)?
Thanks
Followup:
well, there is 0% chance I would even remotely consider using sqlplus for a
10gig output file.

I might use my array_flat program and just run a query that queries the external
tables directly to produce the resulting output file.  Just configure sufficient
TEMP space, pga_aggregate_target (or set big sort_area/hash_area sizes) and let
it rip.  It could be as easy as 5 create tables (with dbms_stats.set_table_stats
to give us a tiny bit of info) and a query.  (don't even bother indexing, just
do a big bulk query and let it rip).
http://asktom.oracle.com/~tkyte/flat/index.html
look for the pro*c program.
Rating: 5
February 08, 2004
Reviewer: Dave from Colorado Springs
Thanks Sean (and Tom), those are great references to start with.

As an architectural followup, is the inclusion of web-server capabilities in the
database product a sign that Oracle is aiming to move away from Apache for some
products? How "well-featured" do you regard Oracle as an HTTP server currently?

Thanks again.
Followup:
Not a chance -- apache is part and parcel of the solution -- still there in 10g.

HTTP is a very very simple protocol when used simply.  apache offers lots more
(the whole module concept and all of the existing modules).  Setting up http
services is pretty easy -- doesn't require apache in all cases.  apache is a web
server -- we are not trying to take over that entire realm there.
Rating: 3
Another question on external files. February 10, 2004
Reviewer: Matt from Australia
I am working on Windows and have to load UNIX base files.

I have been using the following access parameters:

   records delimited by newline

Obviously on windows this is "\r\n". The UNIX files are "\n". I am currently
getting the message:

*
ERROR at line 1:
ORA-29913: error in executing ODCIEXTTABLEFETCH callout
ORA-29400: data cartridge error
KUP-04020: found record longer than buffer size supported, 524288, in D:\GRMS
Migration Letterbox\REMCODEV\inbox\regdata.csv
ORA-06512: at "SYS.ORACLE_LOADER", line 14
ORA-06512: at line 1

Looking at the utilities doco I understand that I can use hex values. I have
tried the following:

   records delimited by "OA"

   records delimited by "OD"

and I get the error;

*
ERROR at line 1:
ORA-29913: error in executing ODCIEXTTABLEFETCH callout
ORA-29400: data cartridge error
KUP-04020: found record longer than buffer size supported, 524288, in D:\GRMS
Migration Letterbox\REMCODEV\inbox\regdata.csv
ORA-06512: at "SYS.ORACLE_LOADER", line 14
ORA-06512: at line 1

What am I doing wrong?

Best Regards,
Followup:
can you ftp the files to your machine to get the newlines correct?  ftp from
unix to windows will accomodate for the fact that windows wants 2 things to end
a line.

Short of that -- not having a windows machine to try anything with and not
seeing the entire example, I cannot really comment further right now.
Rating: 3
An example February 16, 2004
Reviewer: Matt from Australia
Tom,

According to
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96652/ch12.htm#1007431
If I am reading the doco correctly, I should be able to use 'delimited by "OA"'
to enable me to access a unix file on a windows platform.

Can you please provide a quick and dirty example of this syntax working - it
doesn't for me. I have a call open with Oracle support and am not getting a very
good response.

If you can please just show me a unix file on a unix box (if that is all you
have) being accessed as an external table using the "delimited by "<hex value
here>"" syntax it would really be appreciated.

Thanks.
Followup:
Ok, so I install 9ir2 on my windoze VM (vmware -- very cool, lets me runs
windows ironically enough in a window.  perfection).....

created a flat file in unix, binary ftp it.  then:

CREATE TABLE ext_table
(
  EMPNO NUMBER(4),
  ENAME VARCHAR2(10),
  JOB VARCHAR2(9),
  MGR NUMBER(4),
  HIREDATE DATE,
  SAL NUMBER(7,2),
  COMM NUMBER(7,2),
  DEPTNO varchar2(4000)
)
ORGANIZATION external
(
  TYPE oracle_loader
  DEFAULT DIRECTORY MYFILES
  ACCESS PARAMETERS
  (
    RECORDS DELIMITED BY 0x'0a' CHARACTERSET WE8ISO8859P1
    FIELDS TERMINATED BY "|" LDRTRIM
    REJECT ROWS WITH ALL NULL FIELDS
    (
      EMPNO CHAR(255)
        TERMINATED BY "|",
      ENAME CHAR(255)
        TERMINATED BY "|",
      JOB CHAR(255)
        TERMINATED BY "|",
      MGR CHAR(255)
        TERMINATED BY "|",
      HIREDATE CHAR(255)
        TERMINATED BY "|",
      SAL CHAR(255)
        TERMINATED BY "|",
      COMM CHAR(255)
        TERMINATED BY "|",
      DEPTNO CHAR(255)
        TERMINATED BY "|"
    )
  )
  location
  (
    'emp.dat'
  )
)REJECT LIMIT UNLIMITED
/

loads it - the trick is 0x'0a'
                        ^^
Rating: 4
Excellent - thanks! February 16, 2004
Reviewer: Matt from Australia
I think that the doco that I have read is a little thin on the ground on this
part of the syntax - maybe it has improved in 10G?

Thanks for the quick turnaround - questions like these seem to take an age for
Oracle Support to respond to accurately.

How do you find the VMWare - does it work well? I plan to use it to get a 10G
install on Linux working on my Win2K machine.

Thanks again.
Followup:
you got that backwards.  You should use it to get 10g under windows running on
your linux machine!  windows in a window is such a nice thing.

I find vmware to be pretty stable -- use it to access AOL and other "we only do
windoze" software when needed.  The database is fully supported under vmware as
well.
Rating: 5
March 04, 2004
Reviewer: A reader
Tom,

Lets say if I have text file which is delimited by '|' and ','

Can you please provide me an example by which i can take care of both of these
delimiters by creating one external table.

Thanks.
Followup:
it doesn't do delimited by this that or the other thing.  only "delimited by
this"
Rating: 5
March 05, 2004
Reviewer: A reader
Can I do this in sqlldr then?
Followup:
sqlldr and external tables are virtually synonymous.

fields are delimited by "a thing"

we'd have to parse a line ourselves (see
http://asktom.oracle.com/~tkyte/SkipCols/index.html
for an example)
Rating: 5
Thought about preprocessing? March 05, 2004
Reviewer: Tanja Wilson from Indianapolis, IN, USA
Depending upon file size, it might be faster to preprocess the file and change
the "|" to a "," or vice versa, so you have a file delimited by only one thing.

We use sed (a basic Unix command) to do simple things like this to "clean up"
files before passing them to sqlldr / external tables.
Rating: 5
location of files March 07, 2004
Reviewer: reader
Should the flat files locate on the serever where the databse is if I use
external tables? Thanks.
Followup:
yes, they must be accessible to that server.
Rating: 5
March 08, 2004
Reviewer: A reader
Thanks Tom and Tanja..
Rating: 4
Any limitations on the number of external tables you can create March 25, 2004
Reviewer: Grant in Calgary from Calgary, Alberta, Canada
Hi Tom,

I have recently built procedure that uses dynamic sql to generate an external
table with a unique_name based on the business file type and loads the data into
the system.  This is an operation that occurs frequently every day and includes
files that have sizes exceeding 100mb.  The end users that use the apps that
call this procedure also have the ability to "correct" the data after it has
been loaded into the system and occasionally make the wrong correction.  Because
of this, our old system use to involve tracking down the load file and finding
out what the original values looked like to find the errors.
(Sorry for being so long winded!)

With the new system, I would like to maintain the external tables for 6 months
to a year, in order to build queries that compare the data in the file to what
exists in the system.  The only thing that I can think of with this approach is
that it may mean there could be thousands or 10's of thousand of these external
tables.

I wanted to know if there is a downside to maintaining all of these external
tables or any limitations in oracle as to how many external table definitions
the database can maintain at one time.

If there is, I can build into the procedure, the functionality necessary to
create and drop the external table whenever I access it.  Is this necessary?

Thanks Again Tom in advance
Followup:
you would just be having a really large data dictionary, on the size of an
Oracle Apps/peoplesoft/SAP one :)

Queries that hit the data dictionary (monitoring tools, guis mostly) might be
affected, but should be a serious issue in most cases -- it wouldn't even affect
it as much as a "real" table since it won't be filling up the extents views like
real ones would..
March 25, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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)
Rating: 5
Online updation of very big table March 06, 2004
Reviewer: Praveen from Delhi
Hi Tom
Iwant to update a table with one go on online system.A table has 20 lakh records
and has 110 columns. when i give the update command it takes ~ one hrs.I don't
know why it is taking so much time even index is created on that particular
field.

select count(*),app_flg from test;
app_flag has only two values Approved or unapproved.By defualt is unapproved.
approved =170000
unapproved=30000
update test set app_flg='APPROVED' where app_flg='UNAPPROVED'
it took 1hrs to update the records and other application online users processing
got slow down and locks started to occure on the table.

Suggest the best solution
pkr
Followup:
It would be the INDEX that slowed it down.

You want to FULL SCAN this table.
You want there to be NO index on the column you are updating.
Rating: 4
it gives error March 07, 2004
Reviewer: praveen from india
on dropping the index it gives error ora-00054

thanks
Followup:
that'll happen whilst there are outstanding transactions, yes.  You'll want to
keep trying until you get a chance to drop it.
Rating: 5
thanks March 07, 2004
Reviewer: jasdeep,praveen from patiala india
i have solved that problem as an user has locked rows on the but at present not
logged on i killed that session and index was dropped immediately.
and updates were as fast as u can think.

thanks
Rating: 4
Update taking hrs of time March 09, 2004
Reviewer: Sachin from India
Hi Tom,
I have a query as:
I have two tables Table1(with around 10000 records max) and table2(actually the
GL code combination master table with around 600,000 records). I need to update
three fields in table1 (which is a temp processing table) with a unique value
from table2. T1 has ccid fields which need to be updated, and s1-s4 fields
corresponding to segment1-segment4 fields of table2
Following is the query like:

UPDATE  table1 t1
SET     t1.ccid1 = (SELECT t2.code_combination_id
                    FROM   gl_code_combinations t2,
                           gl_sets_of_books gb
                    WHERE  t2.segment5 = 'yyyyy'
                    and    t2.segment4 = t1.s4
                    and    t2.segment3 = 'XXXXX'
                    and    t2.segment2 = t1.s2
                    and    t2.segment1 = t1.s1
                 and    t2.coa_id = gb.chart_of_accounts_id
                and    gb.set_of_books_id = 1),
        t1.ccid2 = (SELECT t2.code_combination_id
                        FROM   gl_code_combinations t2,
                               gl_sets_of_books gb
                    WHERE  t2.segment5 = 'yyyyy'
                    and    t2.segment4 = t1.s4
                    and    t2.segment3 = 'zzzzzz'
                    and    t2.segment2 = t1.s2
                and    t2.segment1 = t1.s1
                and    t2.coa_id = gb.coa_id
                and    gb.set_of_books_id = 1),
        t1.ccid3 = (SELECT t2.ccid
                        FROM   gl_code_combinations t2,
                               gl_sets_of_books gb
                    WHERE  t2.segment5 = 'yyyyy'
                    and    t2.segment4 = t1.s4
                    and    t2.segment3 = 'wwwww'
                    and    t2.segment2 = t1.s2
                and    t2.segment1 = t1.s1
                and    t2.coa_id = gb.coa_id
                and    gb.set_of_books_id = 1

But this is taking more than 6 hrs to complete. When I check the table locks the
table remains locked Row Xclusively. I am committing immediately after the
update statement in the procedure. Cud u pls tell me why is this happening?
Followup:
not that i don't believe you but -- about the locking issue -- I'm not sure i
believe you.

when you commit -- locks are released.  You'd have to show me the info from
v$lock showing that t1 is still "locked" and locked by your session.

you could

set (c1,c2,c3) = ( select max(decode(segment3,'xxxxx',ccid)),
                             max(decode(segment3,'zzzzz',ccid)),
                             max(decode(segment3,'wwwww',ccid))
      FROM   gl_code_combinations t2,
                               gl_sets_of_books gb
                    WHERE  t2.segment5 = 'yyyyy'
                    and    t2.segment4 = t1.s4
                    and    t2.segment3 in ('xxxxx','zzzzzz', 'wwwww')
                    and    t2.segment2 = t1.s2
                and    t2.segment1 = t1.s1
                and    t2.coa_id = gb.coa_id
                and    gb.set_of_books_id = 1 )

instead of running a query per column.

if you run the query

select max(decode(segment3,'xxxxx',ccid)),
                             max(decode(segment3,'zzzzz',ccid)),
                             max(decode(segment3,'wwwww',ccid))
      FROM   gl_code_combinations t2,
                               gl_sets_of_books gb
                    WHERE  t2.segment5 = 'yyyyy'
                    and    t2.segment4 = some_value_value
                    and    t2.segment3 in ('xxxxx','zzzzzz', 'wwwww')
                    and    t2.segment2 =  some_value_value
                and    t2.segment1 =  some_value_value
                and    t2.coa_id = gb.coa_id
                and    gb.set_of_books_id = 1

how long does that take?
Rating: 3
Updating millions of rows March 15, 2004
Reviewer: A reader from NJ, USA
Tom,

In this discussion you mention that you would not do an UPDATE/DELETE of
millions of rows, but rather would create a new table (in parallel/nologging)
with the rows that needs to be retained and then create indexes in parallel,
drop the old table and rename the new table to old table. If so, my
understanding is :

1. Delete/UPDATE of tables with more than 1 million row would also have to
maintain the index structure and also will generate considerable rollback and
undo.
2. Other consideration with respect to update/delete is that the blocks will be
moved in and out of the freelists. Please confirm if this is true and if so,
please explain what happens during update/delete of large number of rows with
regard to freelists?
3. Other consideration is that it will cause lot of table/segment fragmentation
and lot of empty blocks below the HWM and all full table scans on the table will
read lot of empty blocks.

Is my understanding correct and if I have missed any other things as to why
DELETE/UPDATE of large tables is not recommended, please let me know.

4. If my table that needs to be updated/deleted is the primary/parent table with
dependent child tables/constraints and triggers, is this approach correct
   Disable all the triggers/constraints
   Drop the table
   Rename the new table to the old table
   Reenable all constraints and triggers
   What to do in such cases?
Followup:
I "might", "probably" if it was most of the records...

1) correct
2) correct, deletes would put them on, updates might put them on, might take
them off.

3) it could, yes.

the delete/update can take lots longer than CTAS + reindex if you are doing MOST
all of the records in a LARGE table with lots of indexes.

4) triggers are not relevant here....

but you would have to drop the fkeys
do the CTAS+drop+rename
add the constraints back with enable/rely (don't need validation)
Rating: 3
Deletes/Updates March 15, 2004
Reviewer: A reader from NJ, USA
Tom,

In the above discussion you mention :
  1. The deletes will put them on because the used space in the block will drop
below PCTUSED. Is it correct?
  2. The update will put them on if the updated column makes the used space in
the block fall below PCTUSED or the updated column makes the free space
available in the block less than PCTFREE. Is it correct.

If both of the above understanding is not correct, please explain. Also, please
let me know how we could track the movement of the blocks on and off the
freelist. Any SQL scripts/tools available that I could use?

Thanks
Followup:
1) the deletes could, yes.

2) yes.

to track individual blocks -- not without dumping blocks (and I don't go there).
 dbms_space and other tools (search this site for show_space for the script I
use) can show you how MANY things are on the freelist.
Rating: 4
A small correction March 15, 2004
Reviewer: A reader from NJ, USA
Tom,

In the above scenario for question 2, there is a small correction marked in
CAPITAL letters :
"The update will put them on if the updated column makes the used space in the
block fall below PCTUSED AND WILL TAKE OFF of the freelist if the updated column
makes the free space available in the block less than PCTFREE. "
Is it correct?

Thanks
Followup:
I'll state it simply, forget pctfree/pctused/etc -- in english:

if the update reduces the row size, it can put it back onto the freelist.

if the update increases the row size, it can take it off the freelist.
Rating: 5
Parallel DML March 16, 2004
Reviewer: A reader
1)On page 630 of your Oracle Expert One on One, you mention that parallel DML
can be used only upon partitions -- in 8i. I'm using 9.2. Has this behavior
changed in the later versions ?

2)I need to do a mass delete on a 12 GB table and it's taking to much time (far
too much, really). How could I speed it up ?

Thanks
Followup:
1) yes, that is a 9ir2 new feature.
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96531/ch2_9ir2.htm#74432
2) did you read the original answer above?
Rating: 5
enable PK with parallel clause March 20, 2004
Reviewer: A reader
Hi

I am working in 8.1.7.4.

I want to delete 30 million rows of 60 million so I am doing this

1. create a copy of original table with good data
2. disable original tables´s constraints, primary keys (and child FKs) and
foreign keys
3. truncate the original table
4. make all original tables indexes unusable
5. insert append back from copy to original, I have to do this way because I am
not allowed to modify constraint names
6. drop the copy
7. enable constraints. here when I enable PK I cannot provide paralle clause
right? I searched the doco but it seems that I can only specify using index
tablespace clause or I am missing something?
8. rebuild all unusable indexes
Followup:
you can create an index in parallel (unique) and then enable/rely/novalidate the
pk.
Rating: 4
How to Update millions or records in a table March 25, 2004
Reviewer: A reader
Hi Tom,
I read your response to Murali's question above and believe, there will be a
downtime window for the application.  This is because if I want to keep the
index names same as before, I will have to create the new table, drop the old
table, rename the new table to old table name and then create the required
indexes on the same.

Wondering, how if we can create the indexes by some other name on the new table
and rename the indexes after dropping the old table.

As always your valuable advice helps a lot!
Followup:
you can rename the indexes.

alter index supports this.
Rating: 5
how to make this restartble? March 25, 2004
Reviewer: A reader
Hi

I want to delete 80% of rows from a couple of 50 million rows table. The process
is divided in 10 steps

1. Create TEMP table, CTAS from original table
2. Disable constraints
3. Truncate original table
4. Set indexes to UNUSABLE
5. Insert into original table from TEMP table
6. Drop TEMP table
7. Create PK and UK
8. Enable PK and UK
9. Enable FKs
10. Rebuild indexes

I want to make this process restartble, i.e if it fails in step 3 if I later
rerun the procedure it will start from step 3 again. How can we achiveve this? I
have been thinking using a table which stores the two table name and process
status something like following table then update the status as the process is
going

TABLE: PROCESS_STATUS
TABLE_NAME       STATUS
-----------      ---------------
EMP              2
DEPT             0

Process_status is the step, 0 means that I need to start from beginning so in
each process I would query this table and see which step should be run. For
example if the process failed at step 3 then STATUS would be 2

select status into l_proc_status from PROCESS_STATUS
where table_name = 'EMP'
if l_proc_status = 0
then
   run process 1
end if;

select status into l_proc_status from PROCESS_STATUS
where table_name = 'EMP'
if l_proc_status = 1
then
   run process 2
end if;

select status into l_proc_status from PROCESS_STATUS
where table_name = 'EMP'
if l_proc_status = 2
then
   run process 3
end if;

select status into l_proc_status from PROCESS_STATUS
where table_name = 'EMP'
if l_proc_status = 2
then
   run process 3
end if;

and so on.

Any suggestions :-? What I see is that I will hav quite a few redundant codes...
Followup:
you'd have to keep a state table and have you code query it up, much like you
are suggesting.   That would do it yes.

another "inserting" idea might be to

a) insert the steps to process
b) delete them as you complete them (and commit)
c) to restart, just pick up at the step you wanted

you could insert the procedures to be called and just:

  for x in ( select name from procedures order by seq )
  loop
     execute immediate 'begin ' || x.name || '; end;';
     delete from procedures....
     commit;
  end loop

(just a thought, not fully baked)
March 25, 2004 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.
Review & Followup

Rating: 5
Still on the Topic of Performance-Analytic Capabilities in 10g... January 12, 2004
Reviewer: Doug Wingate from New Orleans, LA USA
I'm very curious to know about the further development of the X$TRACE table in
Oracle Database 10g.  In Oracle 9i, of course, it's undocumented and
unsupported.  Is it expected to be documented and supported in 10g?  What will
it do for session tracing?

I'd also like a link to an advance copy of the Oracle Database 10g Concepts
manual, although for that I suppose I'll have to wait along with everyone else.
Still, someone who actually wants to READ the Concepts manual deserves special
consideration for that reason alone--right?  :-)
Followup:
the docs will be out soon -- real soon -- less then a month i would say.
Rating: 4
9.0.4 = 10G? January 13, 2004
Reviewer: A reader
Hi Tom,

   Is 9.0.4 = 10G? I found that I can download AS9.0.4 (10G) and DS 9.0.4 (10G)
from the website, but why Oracle call 9.0.4 = 10G? Why just 10.0.0? Is this
version (9.0.4) final version of 10G?

Thanks,
David
Followup:
Oracle AS 10g is 9.0.4 yes.

10g is "the name"
9.0.4 is "the version"
Rating: 4
10G Database January 14, 2004
Reviewer: S.Darshan from Singapore
Dear Mr.Tom,

  Is 10G database available for download?
Followup:
not yet
Rating: 5
A Release Date of February 15? January 28, 2004
Reviewer: Doug Wingate from New Orleans
One of my coworkers said today that February 15 is the new expected release date
of Oracle Database 10g; however, I haven't seen a published report saying that.
Can you comment?

Thanks
Followup:
I'll just say "q1 calendar year 2004", real soon.
Rating: 4
1Q 2004 January 29, 2004
Reviewer: Doug Wingate from New Orleans
Okay, thanks.
Rating: 5
Thanks February 23, 2004
Reviewer: A reader
10g available now for 4 OS versions, but it is not available for Windows.

Do you know when Oracle is going to relage 10g on Windows?
Followup:
soon, windows is so different from every other OS out there.
Rating: 3
OEM February 24, 2004
Reviewer: Nikunj from India
Dear Tom,

Oracle Enterprise Manager 10g bydefault comes with download of Oracle EE 10g or
we have to download it Separately.
Followup:
the database console, used to manage a single instance, comes with and will be
installed with the 10g server.

EM -- enterprise manager, for managing multiple instances, app servers, etc --
that is separate.
Rating: 5
OEM for 10g February 25, 2004
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom,
In the installation guide, there are two options for OEM, enterprise wide or for
a single database. If I choose to install OEM for a single database, will it
install the web server also? The client feels uncomfortable running web server
on database server.
Thanks
Followup:
the database install is going to plop dbconsole (the standalone thingy) there.
you either

a) start it
b) or not

If they are truly nervous -- you can do as I sometimes do, install with a
hostname of localhost -- that way only 127.0.0.1 can contact the "webserver" --
so you have to be on the server in order to manage it.

otherwise it can be done via an emagent remotely as well.  I find the dbconsole
so nice to have tho, localhost might be something to consider.
Rating: 5
EM in 10g March 25, 2004
Reviewer: David
<quote>EM -- enterprise manager, for managing multiple instances, app servers,
etc -- that is separate<quote>

Is this extra cost?

Also, If I used EM Grid control to manage all of the databases, waht do I do
with the repository in the individual databases that was used for EM database
control? Is it still needed? Thanks.
Followup:
I believe it is, yes (but that is a question for your sales rep...)

dbconsole will still have a repository in each database - they are each "self
managing", that'll still be there.
March 25, 2004 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?"
Rating: 5
10g implicit cursors February 06, 2004
Reviewer: Zoran Martic from Dublin, Ireland
Hi Tom,

Now I found why is good sometimes folowing your path.

You said using constructs like this is the best and very easy to use:

for i in (select * from table_x) loop

In 10g Oracle finally optimizes this thing to use some sort of prefetching and
it went 10x faster in my simple test then in 9i or 8i.
I was thinking why and how, but basically statistics showed me that on 8i and
9i, both explicit and implicit cursors are doing 1 db block read per one fetch
(disaster that Oracle is doing this when you have in OCI prefetching from XX
release of Oracle). In 10g they are finally optimizing fetches but only for
implicit cursors :)

That means who ever coded for loops with Select inside will benefit immediately
with upgrade to 10g.

In 9i you need to opt to BULK COLLECT to be fast as 10g normal for i in (select
....

Again, explicit cursors are working bad as in 8i, 9i story.
That is a shame for Oracle.
No reason that new smart optimizer cannot find out that you are going to loop in
both cases: implicit or explicit.

Did not put tests and results because they are too obvious (10 lines of code).
Just loop through some big table and check that you are not hitting funny disks
to make your results pointless.

In any case I wrote a lot of code your way with putting SQL inside the for loop
and I am happy now, because upgrade to 10g will automatically benefit me :)

It is very strange that I realize now, after many years that this thing (looping
through cursors) in PL/SQL was that bad. Without prefetching (I used from Oracle
7.3) in PL/SQL in 8i, 9i !!!!!

Regards,
Zoran
Followup:
oh, that is cool -- I did not even realize that was happening :)

It is trivial to show, just:

begin
   for x in ( select * from all_objects where rownum < 10000)
   loop
    null;
   end loop;
end;

and in 9i, you'll see:

select *
from
 all_objects where rownum < 10000

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.03       0.27          0          0          0           0
Execute      1      0.01       0.00          0          0          0           0
Fetch    10000      3.40       3.78         11      81153          0        9999
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    10002      3.44       4.05         11      81153          0        9999

and in 10g, you'll see:

SELECT *
FROM
 ALL_OBJECTS WHERE ROWNUM < 10000

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.10       0.24          0        121          0           0
Execute      1      0.01       0.00          0          0          0           0
Fetch      100      1.13       1.18          0      15756          0        9999
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      102      1.24       1.42          0      15877          0        9999

they are using a 100 (my favorite) row arraysize....

:)
Rating: 5
Cursors February 09, 2004
Reviewer: Zoran Martic from Dublin, Ireland
Tom,

Yes, that is great.
100 is also my favourite or to say starting prefetching count, but of course
depends mostly on the size and number of columns you are retreiving.
But it is very easy to find out for any SQL what is the best prefetching count
with simple tests.

Maybe in Oracle 11 will be implemented some adaptive prefetching :)

This thing is looking as much bigger improvement in PL/SQL then some others
mentioned in the docs :)

Thanks,
Zoran
Rating: 4
this a bug or what ? March 24, 2004
Reviewer: Robert from CT
Tom, code below works on 8.1.7 but busts on 9iR1
(I think i does not happen on 9iR2)

DECLARE
  CURSOR cur_selectlist
   IS
   SELECT BANNER || '?' x FROM v$version
   UNION ALL
   SELECT 'Hello' x FROM dual;

BEGIN
  FOR i IN cur_selectlist
  LOOP
    dbms_output.put_line('this works on ' || i.x || '--Yes');
    EXIT;
  END LOOP;
EXCEPTION
  WHEN OTHERS THEN
     dbms_output.put_line(SQLERRM);
     FOR i IN (SELECT banner FROM v$version)
     LOOP
        dbms_output.put_line(i.banner);
     END LOOP;
END;

-- 8.1.7 OUTPUT ----------------
this works on Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production?---Yes

-- 9i OUTPUT ----------------
ORA-06502: PL/SQL: numeric or value error
Personal Oracle9i Release 9.2.0.1.0 - Production
PL/SQL Release 9.2.0.1.0 - Production
CORE    9.2.0.1.0    Production
Followup:
seems to be the union all with the concat.... repros in 9ir1, but not the latest
9ir2 (but apparently did in the first 9ir2's which you have above...)

ops$tkyte@ORA9IR1> DECLARE
  2    CURSOR cur_selectlist
  3     IS
  4     SELECT 'x'||'x' x
  5       FROM dual
  6      UNION ALL
  7     SELECT 'x' x
  8       FROM dual;
  9
 10  BEGIN
 11    FOR i IN cur_selectlist
 12    LOOP
 13       null;
 14    END LOOP;
 15  END;
 16  /
DECLARE
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at line 11

ops$tkyte@ORA9IR2> DECLARE
  2    CURSOR cur_selectlist
  3     IS
  4     SELECT 'x'||'x' x
  5       FROM dual
  6      UNION ALL
  7     SELECT 'x' x
  8       FROM dual;
  9
 10  BEGIN
 11    FOR i IN cur_selectlist
 12    LOOP
 13       null;
 14    END LOOP;
 15  END;
 16  /

PL/SQL procedure successfully completed.

Yes, it would be a "product issue"

found this:

BugTag:             Support notes on <Bug:1918751>  - DDR info <BugDesc:1918751>
Affects:            RDBMS (90-A0)
NB:                    FIXED
Abstract:           ORA-6502 possible from UNION and CONCAT (||) with constant
strings
Fixed-Releases:     9014 9202 A000

Details:
  This problem is introduced in 9.0.1.0.
  "ORA-6502 Value or Numeric error" can occur when using a
  PLSQL block  that has UNION and CONCAT clauses involving
  constant strings.
  eg: for I in
        (SELECT 'TEXT01 ' ||'TEXT02 ' stmt FROM  sys.dual
          UNION
         SELECT 'TEXT11 ' stmt FROM  sys.dual) loop ...
Rating: 4
explicit March 24, 2004
Reviewer: Muhammad Ibrahim from China
Dear Tom,
   In some situation we may need to use Ref cursor so again
open
fetch
close
   According to my understanding the ref cursor looks like explicit cursor. How
about the performance then? Or is it possible to use Ref cursor as a implicit
cursor i mean

  For i in ( lRef_Cursor ) Loop
  End Loop;
   I am not that much familiar with Ref cursor! Please clarify. I have seen many
of ref cursor you have defined in this site with open,fetch,close!

Regards,
Ibrahim.
Followup:
You cannot use a ref cursor in a loop like that.  it would only be

  open
  loop
     fetch into

     exit when...
  end loop;
  close;

that is the only way to process a ref cursor.
Rating: 4
Ref cursor March 25, 2004
Reviewer: Muhammad Ibrahim from China
Dear Tom,

   Then if we use ref cursor it may not be a good idea. Is that right?

Regards,
Ibrahim.
Followup:
the rule is:

use STATIC sql in plsql until you get into a situation where you cannot use
static SQL.  ref cursors are a tool, they are neither 100% evil nor 100% good --
like EVERYTHING in the database.
March 26, 2004 Hirisave -- Thanks for the question regarding "Oracle Streams", version 9.2.0
You Asked
I am looking for specific example of setting up streams for bi-directional
schema level replication. What are your thoughts on using Oracle Streams to
implement active-active configuration of databases for high availability?

Thanks,

Pratap
and we said...
replication is for replication.

replication is definitely nothing I would consider for HA.

For HA there is:

o RAC -- active active servers in a room.
o Data Guard -- active/warm for failover in the even the room disappears.

Replication is a study in complexity.  Update anywhere will make your
application

o infinitely hard to design
o fairly impossible to test
o more fragile (more moving pieces, more things that can go wrong. which
  conflicts with your stated goal of HA)

I would not consider replication for HA in any circumstance.  Data Guard is the
feature you are looking for.
Review & Followup

Rating: 3
replication as ha option March 08, 2004
Reviewer: ana from PA, USA
Tom, Correct me if I am wrong. My understanding is that for a Dataguard
failover, manual intervention of the DBA is required. But if I have a replicated
database (2 masters - synchronous from primary to replicated database and
asynchronous the other way around - and only the primary being updated in normal
circumstances), the failover would be automatic and does not require the DBA to
be on site immediately.

Thanks
Anandhi
Followup:
the problem is you have to design your entire system from day 1 to be replicated
since when you "failover" (lose the ability to connect to db1) there will be
QUEUED transactions that have not yet taken place on db2 (eg: your users will
say "hey, I know i did that already and do it all over again")  when db1
"recovers" it'll push its transactions and db2 will push its transactions.  bamm
-- update conflicts.

So, replication is a tool developers can use to build a replicated database.

dataguard is a tool DBA's can use to set up a highly available environment.

they are not to be confused - you cannot replicate 3rd party applications like
Oracle Apps, people soft, SAP, etc.  You cannot replication most custom
developed applications without major design/coding efforts.

you can data guard ANYTHING.

and yes, when failover is to take place, you want a human deciding that.
failover is something that happens in minutes, it is in response to a disaster
(hence the name DR).  It is a fire, an unrecoverable situation.  You do not want
to failover because a system blue screened (wait for it to reboot).  You do not
want to failover some people but not others (as would happen with db1, db2 and
siteA, siteB if siteA cannot route to db1 but can route to db2 but siteB can
still route to db1 - bummer, now you have transactions taking place on BOTH and
unless you designed the systems to be "replicatable" you are in a hole world of
hurt)

DR is something you want a human to be involved in.  They need to pull the
trigger.
Rating: 5
March 08, 2004
Reviewer: A reader
Rating: 5
difference between streams and change data capture March 25, 2004
Reviewer: Nemec from Austria
Hi Tom,

can you please provide a classification of streams and change data capture.
I guess the main difference is that streams covers event capture, transport
(transformation) and consumption. CDC only the capture.
But if you consider only event capture, are there technical differences between
streams and change data capture? What was the main reason to made CDC as a
separate product?

thx

Jaromir
http://www.db-nemec.com/
Followup:
think of streams like a brick.

think of CDC like a building made of brick.

streams can be used to build CDC.  CDC is built on top of streams (async CDC is
anyway, sync CDC is trigger based).

they are complimentary, not really competing.
March 26, 2004 Michael -- Thanks for the question regarding "Foreign Keys", version Oracle8i 8.1.6.0.0
You Asked
Hi Tom,
      this question was put to me by a developer/designer and as
I couldn't a) Find the Answer or b) make up a convincing answer I
thought I would ask you.

Basically it is ....

HOW does Oracle make the changes to a child table when you update a Parent table

i.e They have a table (parent) with code numbers which is the foreign key on a
large number of other tables.  Some of the child tables are very big (+4 Million
rows) so they are wondering if they make a change to a code number does Oracle
perform full table scans to make whatever action has been specified for the
restraint i.e if the Action specified on the constraint is Delete
Set Null does that mean that Oracle will read the whole child table to set all
the relevant fields to null or does it have some sort of internal index it uses?

I hope you can help but as usual thanks in advance!  and as its that time of the
year  I would like to take this opportunity
to wish you A Merry Christmas and A Happy New Year
and thanks very much for all the help you have already given to me this year

Regards,
        Michael Horrocks
and we said...
I believe:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:292016138754
will fully answer this for you.  Basically -- if you are using an ON DELETE
SET NULL | CASCADE, you want to make sure you index the foreign key.  Or if you
delete from the parent table -- or if you ever update the parents primary key
(you don't want to do that anyway)....

I have scripts to detect unindexed foreign keys for you there.  In addition to
slowing on delete operations, deletions from the parent tables -- unindexed
foreign keys can also cause full table locks of the child tables as well.
Review & Followup

Rating: 5
Funny behavior of Foreign Key July 19, 2002
Reviewer: Ravi from Big Apple
Tom,
I have a situation where foreign key is not being enforced as described below.
MS SQL has the same behavior which leads me to believe this must be a SQL
standard - is that right? (though MS Access correctly gave me a Foreign Key
violation .. so I am not sure).

Also, is there a way to ensure the constraint is enforced (other than a trigger)
?

Thanks

SQL> create table A (col1 integer not null, col2 integer, constraint xx
unique(col1, col2));

Table created.

SQL> create table B (col1 integer not null, col2 integer);

Table created.

SQL> alter table B add constraint yy foreign key (col1, col2) references A(col1,
col2);

Table altered.

------ THIS STATEMENT SUCCEEDS ! ------
SQL> insert into B values (1, NULL);

1 row created.

SQL> select * from a;

no rows selected

SQL> select * from b;

      COL1       COL2
---------- ----------
         1
Followup:
It is the expected (ANSI) behavior (what we do)

goto
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76965/c24integ.htm#2161
and look (ctl-f) for "Nulls and Foreign Keys"
Rating: 5
RE: Partially NULL foreign keys July 20, 2002
Reviewer: Brad Peek from Irving, TX
Tom - I wasn't aware of the behavior that Ravi's example demonstrated.  I read
the Oracle documentation at the link you provided and here is an excerpt:

<Ora Doc>
A composite foreign key can be all null, all non-null, or partially null. The
following terms define three alternative matching rules for composite foreign
keys:

match full:
 Partially null foreign keys are not permitted. Either all components of the
foreign key must be null, or the combination of values contained in the foreign
key must appear as the primary or unique key value of a single row of the
referenced table.

match partial:
 Partially null composite foreign keys are permitted. Either all components of
the foreign key must be null, or the combination of non-null values contained in
the foreign key must appear in the corresponding portion of the primary or
unique key value of a single row in the referenced table.

match none:
 Partially null composite foreign keys are permitted. If any column of a
composite foreign key is null, then the non-null portions of the key do not have
to match any corresponding portion of a parent key.

</Ora Doc>

It seems like the documentation is suggesting that the user/developer can
influence or choose between the "match partial" and "match none" behaviors when
a partially null foreign key is present.  However, I didn't see any further
comment on it and am assuming that this is not the case.

Can you elaborate on when (if ever) Oracle would use "match partial" instead of
"match none" when a partially null FK is present?

Thanks for shining a light into the Oracle cave...

P.S.  I am assuming that Ravi's example of a multi-column foreign key with the
second column being null is considered a partially null FK and further that this
applies to the descriptions given for both "match partial" and "match none".
Followup:
The docs are listing out all of the possible cases (just like in the concurrency
control section they discuss general isolation levels that we don't do).

We do the match none and match full.

We cannot directly do a match partial (we would need multiple levels of tables
to do that -- one table each to represent each combination of columns -- so if
you have C1,C2 in a fkey, you need a table with  unique(c1), another with
unique(c2) and yet a third with unique(c1,c2))
Rating: 4
Enqueue Locks May 06, 2003
Reviewer: Vivek Sharma from Bombay, India
Dear Tom,

I have a query. I have gone thru your book "Expert one on one" which indicates
well that a foreign key index needs to be created for performance reason and
resolving locking issues as well. But I have a query, How do I know that my
database has Locking problem due to unindexed Foreign Keys. I can see many
sessions waiting for TM Locks with lock type as 6 or 3 and only 1 or 2 sessions
with lock type 4. I think lock type 4 corresponds to Unindexed Foreign Key.
Correct me if I am wrong. I have a session
select * from v$lock where sid=69;

       SID TY        ID1        ID2      LMODE    REQUEST      BLOCK      CTIME
---------- -- ---------- ---------- ---------- ---------- ---------- ----------
        69 TM      51131          0          4          0          0      11282
        69 TM      50884          0          3          0          0      11282
        69 TM      51181          0          4          0          0      11282

v$session_event does not shows any enqueue wait for this session. I indexed the
foreign keys of 51131 and 51181 objects which were referencing to the primary
key of 50884. Even after creating the indexes this session is showing in v$lock.

I went thru the session_event and found the majority of the sessions waiting for
log file sync. Is this the reason many of the sessions waiting for TM Locks.
Running UTLLOCKT.sql also does not shows anything, it means there are no
deadlocks. But why are so many sessions waiting. The output of the query
select event, count(*) from v$session_event group by event
order by 2 desc shows
EVENT                                                              COUNT(*)
---------------------------------------------------------------- ----------
file open                                                                79
db file sequential read                                                  73
SQL*Net message from client                                              72
SQL*Net message to client                                                72
log file sync                                                            60
db file scattered read                                                   51
latch free                                                               24
SQL*Net more data to client                                              23
SQL*Net more data from client                                            19
rdbms ipc message                                                         8
buffer busy waits                                                         7
SQL*Net break/reset to client                                             6
control file sequential read                                              4
file identify                                                             4
db file parallel write                                                    4
control file parallel write                                               3
direct path read                                                          3
enqueue                                                                   3
direct path write                                                         3
log file sequential read                                                  2
log file switch completion                                                2
LGWR wait for redo copy                                                   1
log file single write                                                     1
pmon timer                                                                1
smon timer                                                                1
rdbms ipc reply                                                           1
log file parallel write                                                   1
db file single write                                                      1

Can u please help me out in identifying the problem. Also please let me know how
to identify locking problem due to unindexed foreign keys and from where to find
these objects.
Thanks and Regards
Vivek
Followup:
double check your work -- make sure to have run the query from page 110

this shows that with a INDEX, the table lock on the child is not taken (in 9i,
this script isn't meaningful as the locking methods changed entirely with
regards to fkeys)

ops$tkyte@ORA817DEV> drop table c;
Table dropped.

ops$tkyte@ORA817DEV> drop table p;
Table dropped.

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> commit;
Commit complete.

ops$tkyte@ORA817DEV> update p set x = 2;
1 row updated.

ops$tkyte@ORA817DEV> select v$lock.sid, id1,
  2         (select object_name from user_objects where object_id = v$lock.id1)
oname,
  3             id2, lmode, request, block, v$lock.type
  4    from v$lock
  5    where v$lock.sid = (select sid from v$mystat where rownum=1)
  6  /

       SID        ID1 ONAME        ID2      LMODE    REQUEST      BLOCK TY
---------- ---------- ----- ---------- ---------- ---------- ---------- --
         8      56386 P              0          3          0          0 TM
         8      56388 C              0          4          0          0 TM
         8     196691             5332          6          0          0 TX

ops$tkyte@ORA817DEV> create index c_idx on c(x);
Index created.

ops$tkyte@ORA817DEV> update p set x = 2;
1 row updated.

ops$tkyte@ORA817DEV> select v$lock.sid, id1,
  2         (select object_name from user_objects where object_id = v$lock.id1)
oname,
  3             id2, lmode, request, block, v$lock.type
  4    from v$lock
  5    where v$lock.sid = (select sid from v$mystat where rownum=1)
  6  /

       SID        ID1 ONAME        ID2      LMODE    REQUEST      BLOCK TY
---------- ---------- ----- ---------- ---------- ---------- ---------- --
         8      56386 P              0          3          0          0 TM
         8     327746             5642          6          0          0 TX

s$tkyte@ORA817DEV> delete from p;

1 row deleted.

ops$tkyte@ORA817DEV> select v$lock.sid, id1,
  2         (select object_name from user_objects where object_id = v$lock.id1)
oname,
  3             id2, lmode, request, block, v$lock.type
  4    from v$lock
  5    where v$lock.sid = (select sid from v$mystat where rownum=1)
  6  /

       SID        ID1 ONAME        ID2      LMODE    REQUEST      BLOCK TY
---------- ---------- ----- ---------- ---------- ---------- ---------- --
         8      56390 P              0          3          0          0 TM
         8     327765             5634          6          0          0 TX
Rating: 5
But is the DB having Locking Due to Unindexed FK May 07, 2003
Reviewer: Vivek Sharma from Bombay, India
Dear Tom,

Thanks for ur reply. But seeing from the wait events does the database have a
problem due to unindexed FKeys or the problem with log file sync wait event.
When I created an index, why the session was still waiting with type 4.

How do I identify that the database is having locks due to unindexed foreign
keys (seeing which wait events).

Please explain.
Vivek Sharma
Followup:
i didn't see anyone "waiting" -- where do you?

look at it this way -- find the unindexed fkeys then ask yourself

a) do we delete from parent
b) do we update parent primary key

if the answer is YES to either -- index them immediately.  They are a problem.

if the answer is NO to both -- ignore them, they are not a problem.
Rating: 5
foreign keys and performance June 09, 2003
Reviewer: A reader from ca
hi tom
Thanx for sharing your knowledge with the community!

I have a situation where a table is part of a framework.
(let us call it table t whose primary key column is called
t_pk).

t_pk column should become a foreign key in most of
the other tables that use this framework. The
developing team of this table maintains that they
discourage the users of the framework from creating
foreign key to their table. Since their table
undergoes many inserts, deletes and updates, they
say that the users should register callbacks that
get executed from their framework if they want to
enforce things like cascade delete.
So, if i have a table my_table which has a column
t_pk, I should not create a foreign key to the table
t. This makes is more complicated to ensure that
the rows in my table get deleted when an entry is
deleted in table t - ( I have to register a callback
to delete my row)

I argued that since the call to the "delete" callback
would be synchronous, the performance would be the
same (since in both cases, the deletion happens
in one case automatically, and in another through callback). But I am not sure
if in case of inserts and updates this could be an issue.

My  question is:
1. Have you encountered this situation before and what
   is your opinion about prefering callbacks over
   foreign key for implementing features like
   cascade delete?
2. How would you verify or dispute this claim.

Thanx!
Followup:
(gotta be java programers, "framework", "callbacks")

ask them to prove that they can enforce integrity 100% -- efficiently,
effectively and most importantly *CORRECTLY*.

Q) How can they "lock those rows in the child table that do not exist"????
A) they cannot (but oracle can)

what I mean is -- someone decides to delete from t where t_pk = 5;  Ok, now
what?  they must query each table in turn (oh, that'll be super efficient won't
it - doing it themselves).  Now, the problem is *they cannot see rows that are
inserted in the child tables yet not committed*.  Now what?

the database provides tools.  you use them, period.  foreign keys are not the
stuff of science fiction -- pretty much every database of worth out there
supports them.

This is, simply put, the height of absurdity.  I went out of my way to spend a
couple of pages on silliness like this in the book I'm working on.  My example
comes from here:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6091237036109
advice -- if you can get off of that project, you would be better off.  These
guys are not serious about data, data integrity.  it is going to be a computer
science project -- lots of fun to code, lots of pizza late a night, plenty of
soda -- but it won't be something you want to actually maintain.  That is my
guess.
Rating: 5
June 09, 2003
Reviewer: A reader from ca
Please bear with me as I try to understand the scenario:

so we have a table t with a primary key t_pk
I have another table t1 with a foreign key reference
 to t_pk (t1's column is also called t_pk)

 I also have an index on t1.t_pk column. (if i have
 a foreign key, I have to index it - as we very well know)

When you delete (or insert or update) an entry from t,
all the indexes on  the TABLE T have to updated.
The index (or the foreign key) on  t1.t_pk has nothing
to do with it (at least so far.) (i.e. it has no impact
on the performance of the delete, insert or update on T -
it is immaterial. Correct?

Now let us say I add a "cascade delete" constraint on
 t1.t_pk column .

 Now, ONLY in case of deletes from t_pk, you will have to delete all entries
from t1. This has to be done either
 by Oracle or by the delete callback (assuming it is even
feasible to be done by a callback  - a wrong assumption)
 that has been registered - either way, I dont
 see why a callback would be faster (since the delete callback action is
synchronous.)

Also, for inserts and updates the fk index on t1 has no impact on the
insert/updates of the table t, right?
(we are assuming that primary keys are not updated)

 The last point is that I could have an index on t1.t_pk
 even if I dont have a  foreign key because I just want to index it due to
performance reasons. The requierment
imposed by the SDK does not make sense and in fact
hurts my performance.

One point that if you could explain more clearly,
it would be great!
You mentioned that

"
*they cannot see rows that are inserted in the child
tables yet not committed*. "

so i guess what you are saying is

I am inserting a value in t1 with a pk value of 1 but
haven't yet committed.
The table t's row with value pk=1 gets deleted in another session triggering all
"callbacks".

Then my session commits having a row which is orphan.
Is this what you meant?

Thanx!:)
PS: I was hoping to just forward your answer's link -
but thought better - given the bashing:)

And "get out of project"? Easier said than done
in today's economy!:) besides I just got in:)

Thanx a lot!:)
Followup:
yes, you will have orphans.

ask them "why do you want to write a ton of code to maintain, at extra cost to
the company -- especially in this economy -- when it is already 100% done for
us?"
Rating: 5
What was it Kevin Loney quoted? June 09, 2003
Reviewer: Trevor Welch
Kevin Loney author of several Oracle books
had a quote in the front of his books
something like

"Controlling developers is like herding cats"
Rating: 5
well, are there any other reasons you can think of June 25, 2003
Reviewer: A reader
"yes, you will have orphans."

We have a similar argument - except that it
turns out that the scenario (which creates child orphans)
is not possible in our case - because of the timings
of deletes.

Now the argument is that in case of delete cascades
the user would see a performance hit - since the
parent table could potentially have child
tables (with a 2 level heirarchy) with potentially hundreds of rows. The
argument is that in case of purge delete
callbacks - the user would just experience the delay
associated with deleting parent entry (in the UI) - the remaining entries would
get deleted later on - so at least
the delete would "look" fast.

So , the question is what impact DOES the cascade
delete have (assuming indexes on foreign keys are in place)?

I am thinking of benchmarking but thought would ping
the guru first!

To setup the benchmarking, I thought of following steps.

1. Time the case of a delete that has a parent key
and a child table of two levels with may be 400 rows or
so. 2. Time the above delete without any foreign keys and
cascade deletes.

Find out difference in 1 and 2. Should I be thinking of anything else in terms
of resources (latches etc)?

That would be the difference I guess (assuming that
the "background" purge callbacks would not be noticiable.)

Of course, we all are aware of overhead of maintaining
additional lines of code - I am trying to prove the case
without that logic...This looks like
Oracle foreign keys one handed versus two handed purge callback solution. We are
on 9i,btw.

Thank you!
Followup:
Your case is "today"
Tomorrow will be different.

Avoid using database RI at your own risk.

IMHO -- there is no "performance impact" as you must do it, it is a requirement,
it is not overhead since overhead is something that can be avoided (and that is
not true here).

Of course just deleting the parent and orphaning the childen is faster.
Conversely, Of course just deleting the parent and orphaning the child is
wrong, we call this a bug

If you are worried about performance from the end users perspective, you might
go with a "flag delete" on the parent (end date it) and use views that only show
you the "non-deleted" rows in that table.  You can schedule a job to delete them
in the background if you like.
Rating: 5
thanx! June 26, 2003
Reviewer: A reader
That is what i was thinking - doing it in background -
but you clarified the actual approach a little more.
I do agree with you that it is a requirement not
an overhead!
Rating: 5
foreign keys June 26, 2003
Reviewer: A reader
"If you are worried about performance from the end users perspective, you might
go with a "flag delete" on the parent (end date it) and use views that only show

you the "non-deleted" rows in that table.  You can schedule a job to delete them

in the background if you like. "

How about
1. marking the parent row to be deleted in one transaction
  that returns immediately (With views that logically
  delete for the entire application)
2. doing the actual delete in the background job (with
   foreign key constraints)

Any thoughts (ASSUMING performance is a real issue -
which probably is not the case)?
Followup:
thats what I meant (1) and (2)
Rating: 5
thanx ! June 26, 2003
Reviewer: A reader
nice to get validation from you!
Rating: 5
ok - i ran some benchmarks July 04, 2003
Reviewer: A reader
Hi Tom
As usual, I appreciate the incredible amout of service
you are providing to Oracle community! Thank you
so much!

I ran some benchmarks to find out the overhead of
foreign key delete cascade constraints versus the
callback mechanism. I assumed that the callback mechanism
would have to internally select and delete the child
records. (please see discussions above on the issue:
claims that delete cascade constraints would result
in performance issue with the solution being callbacks
instead.) Although I agree with the FK solution for the
reasons said in the above threads (less coding, no "correctness" issues,
database gives it to you
free etc), I wanted to get an idea of what kind of
performance impacts are we talking about here - hence
the benchmarks. I would appreciate your comments on
the validity of the benchmark results and my conclusions.

Following are the two cases I tested (using your runstats
utility to compare the two cases)

Case 1:
  Parent table has one record, child has 400 records.
  child has a  FK constraint with cascade delete on
  parent key column.

Case 2:
  Parent table has one record, child has 400 records.
  child has no FK constraints.

In both cases, parent has a primary key constraint on the
parent key column.

Following are my results:

<b>Run1 - with foreign key constraints</b>
<b>Run2 - without foreign key constraints</b>

-----results ---
Run1  ran in 7 hsecs
Run2 ran in 8 hsecs
run 1 ran in 87.5% of the time

Name                                Run1      Run2      Diff
STAT...no buffer to keep pinne         0       349       349
STAT...consistent gets - exami         9       360       351
STAT...session logical reads       1,780     2,172       392
STAT...consistent gets                25       425       400
STAT...table scan rows gotten          1       401       400
LATCH.cache buffers chains         8,186     8,625       439

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
20,130    20,494       364  98.22%
----------------

The results seem to indicate that FK constraints
definitely do not incur any overhead compared
to a callback implemented solution - in fact in the
above case, it seems to be marginally better.

Following is my run - showing the code that I ran
(the benchmark is run twice though in both runs the
results seemed to be about the same)

--- my code (with results)

test_user@apps> set echo on
test_user@apps> @fkb
test_user@apps>
test_user@apps> set serveroutput on
test_user@apps> --create a parent table
test_user@apps>
test_user@apps> drop table parent_table_with_fkrefs cascade constraints;

Table dropped.

test_user@apps>
test_user@apps> create table parent_table_with_fkrefs
  2  as select sys_guid() as parent_key , owner, object_type, object_name
  3  from all_objects
  4  where rownum = 1
  5    and object_type = 'FUNCTION';

Table created.

test_user@apps>
test_user@apps> alter table parent_table_with_fkrefs add constraint
parent_table_pk1 primary key ( parent_key);

Table altered.

test_user@apps>
test_user@apps> drop table parent_table_without_fkrefs cascade constraints;

Table dropped.

test_user@apps>
test_user@apps> create table parent_table_without_fkrefs
  2  as select sys_guid() as parent_key , owner, object_type, object_name
  3  from all_objects
  4  where rownum = 1
  5    and object_type = 'PROCEDURE';

Table created.

test_user@apps>
test_user@apps> alter table parent_table_without_fkrefs add constraint
parent_table_pk2 primary key ( parent_key);

Table altered.

test_user@apps>
test_user@apps> drop table child_table_with_fk;

Table dropped.

test_user@apps> create table child_table_with_fk
  2  (
  3    parent_key raw(16) not null,
  4    child_value varchar2(400) not null,
  5    constraint child_pk primary key(parent_key, child_value),
  6    constraint child_fk foreign key(parent_key)
  7         references parent_table_with_fkrefs(parent_key)
  8         on delete cascade
  9  );

Table created.

test_user@apps>
test_user@apps> drop table child_table_without_fk;

Table dropped.

test_user@apps> create table child_table_without_fk
  2  (
  3    parent_key raw(16) not null,
  4    child_value varchar2(400) not null,
  5    constraint child_pk1 primary key(parent_key, child_value)
  6  );

Table created.

test_user@apps>
test_user@apps> declare
  2    cursor l_fk_cursor is select * from parent_table_with_fkrefs;
  3    l_fk_rec l_fk_cursor%rowtype;
  4    cursor l_no_fk_cursor is select * from parent_table_without_fkrefs;
  5    l_no_fk_rec l_no_fk_cursor%rowtype;
  6    l_num_of_recs integer := 400;
  7  begin
  8
  9    for l_fk_rec in l_fk_cursor loop
 10       dbms_output.put_line( 'obj type ' || l_fk_rec.object_type );
 11       for i in 1 .. l_num_of_recs loop
 12         insert into child_table_with_fk values ( l_fk_rec.parent_key,
 13            i || l_fk_rec.object_type);
 14       end loop;
 15    end loop;
 16
 17    for l_no_fk_rec in l_no_fk_cursor loop
 18       dbms_output.put_line( 'obj type ' || l_no_fk_rec.object_type );
 19       for i in 1 .. l_num_of_recs loop
 20         insert into child_table_without_fk values ( l_no_fk_rec.parent_key,
i ||
 21           l_no_fk_rec.object_type);
 22       end loop;
 23    end loop;
 24  end;
 25  /
obj type FUNCTION
obj type PROCEDURE

PL/SQL procedure successfully completed.

test_user@apps>
test_user@apps> commit;

Commit complete.

test_user@apps> @run_bm
test_user@apps> create or replace package run_benchmark_pkg as
  2    procedure delete_with_fk;
  3    procedure delete_without_fk;
  4    procedure benchmark;
  5  end;
  6  /

Package created.

test_user@apps> create or replace package body run_benchmark_pkg as
  2    procedure delete_with_fk
  3    is
  4    begin
  5       delete parent_table_with_fkrefs;
  6    end;
  7
  8    procedure delete_without_fk
  9    is
 10    begin
 11       delete child_table_without_fk where parent_key in
 12         (select parent_key from parent_table_without_fkrefs);
 13
 14       delete parent_table_without_fkrefs;
 15    end;
 16
 17    procedure benchmark
 18    is
 19    begin
 20       runstats_pkg.rs_start;
 21       run_benchmark_pkg.delete_with_fk;
 22       runstats_pkg.rs_middle;
 23       run_benchmark_pkg.delete_without_fk;
 24       runstats_pkg.rs_stop(200);
 25    end;
 26
 27  end;
 28  /

Package body created.

test_user@apps>
test_user@apps> begin
  2    --run_benchmark_pkg.benchmark;
  3       runstats_pkg.rs_start;
  4       run_benchmark_pkg.delete_with_fk;
  5       runstats_pkg.rs_middle;
  6       run_benchmark_pkg.delete_without_fk;
  7       runstats_pkg.rs_stop(200);
  8  end;
  9  /
Run1 ran in 7 hsecs
Run2 ran in 8 hsecs
run 1 ran in 87.5% of the time

Name                                Run1      Run2      Diff
STAT...no buffer to keep pinne         0       349       349
STAT...consistent gets - exami         9       360       351
STAT...session logical reads       1,780     2,172       392
STAT...consistent gets                25       425       400
STAT...table scan rows gotten          1       401       400
LATCH.cache buffers chains         8,186     8,625       439

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
20,130    20,494       364  98.22%

PL/SQL procedure successfully completed.

test_user@apps> @fkb
test_user@apps>
test_user@apps> set serveroutput on
test_user@apps> --create a parent table
test_user@apps>
test_user@apps> drop table parent_table_with_fkrefs cascade constraints;

Table dropped.

test_user@apps>
test_user@apps> create table parent_table_with_fkrefs
  2  as select sys_guid() as parent_key , owner, object_type, object_name
  3  from all_objects
  4  where rownum = 1
  5    and object_type = 'FUNCTION';

Table created.

test_user@apps>
test_user@apps> alter table parent_table_with_fkrefs add constraint
parent_table_pk1 primary key ( parent_key);

Table altered.

test_user@apps>
test_user@apps> drop table parent_table_without_fkrefs cascade constraints;

Table dropped.

test_user@apps>
test_user@apps> create table parent_table_without_fkrefs
  2  as select sys_guid() as parent_key , owner, object_type, object_name
  3  from all_objects
  4  where rownum = 1
  5    and object_type = 'PROCEDURE';

Table created.

test_user@apps>
test_user@apps> alter table parent_table_without_fkrefs add constraint
parent_table_pk2 primary key ( parent_key);

Table altered.

test_user@apps>
test_user@apps> drop table child_table_with_fk;

Table dropped.

test_user@apps> create table child_table_with_fk
  2  (
  3    parent_key raw(16) not null,
  4    child_value varchar2(400) not null,
  5    constraint child_pk primary key(parent_key, child_value),
  6    constraint child_fk foreign key(parent_key)
  7         references parent_table_with_fkrefs(parent_key)
  8         on delete cascade
  9  );

Table created.

test_user@apps>
test_user@apps> drop table child_table_without_fk;

Table dropped.

test_user@apps> create table child_table_without_fk
  2  (
  3    parent_key raw(16) not null,
  4    child_value varchar2(400) not null,
  5    constraint child_pk1 primary key(parent_key, child_value)
  6  );

Table created.

test_user@apps>
test_user@apps> declare
  2    cursor l_fk_cursor is select * from parent_table_with_fkrefs;
  3    l_fk_rec l_fk_cursor%rowtype;
  4    cursor l_no_fk_cursor is select * from parent_table_without_fkrefs;
  5    l_no_fk_rec l_no_fk_cursor%rowtype;
  6    l_num_of_recs integer := 400;
  7  begin
  8
  9    for l_fk_rec in l_fk_cursor loop
 10       dbms_output.put_line( 'obj type ' || l_fk_rec.object_type );
 11       for i in 1 .. l_num_of_recs loop
 12         insert into child_table_with_fk values ( l_fk_rec.parent_key,
 13            i || l_fk_rec.object_type);
 14       end loop;
 15    end loop;
 16
 17    for l_no_fk_rec in l_no_fk_cursor loop
 18       dbms_output.put_line( 'obj type ' || l_no_fk_rec.object_type );
 19       for i in 1 .. l_num_of_recs loop
 20         insert into child_table_without_fk values ( l_no_fk_rec.parent_key,
i ||
 21           l_no_fk_rec.object_type);
 22       end loop;
 23    end loop;
 24  end;
 25  /
obj type FUNCTION
obj type PROCEDURE

PL/SQL procedure successfully completed.

test_user@apps>
test_user@apps> commit;

Commit complete.

test_user@apps> @run_bm
test_user@apps> create or replace package run_benchmark_pkg as
  2    procedure delete_with_fk;
  3    procedure delete_without_fk;
  4    procedure benchmark;
  5  end;
  6  /

Package created.

test_user@apps> create or replace package body run_benchmark_pkg as
  2    procedure delete_with_fk
  3    is
  4    begin
  5       delete parent_table_with_fkrefs;
  6    end;
  7
  8    procedure delete_without_fk
  9    is
 10    begin
 11       delete child_table_without_fk where parent_key in
 12         (select parent_key from parent_table_without_fkrefs);
 13
 14       delete parent_table_without_fkrefs;
 15    end;
 16
 17    procedure benchmark
 18    is
 19    begin
 20       runstats_pkg.rs_start;
 21       run_benchmark_pkg.delete_with_fk;
 22       runstats_pkg.rs_middle;
 23       run_benchmark_pkg.delete_without_fk;
 24       runstats_pkg.rs_stop(200);
 25    end;
 26
 27  end;
 28  /

Package body created.

test_user@apps>
test_user@apps> begin
  2    --run_benchmark_pkg.benchmark;
  3       runstats_pkg.rs_start;
  4       run_benchmark_pkg.delete_with_fk;
  5       runstats_pkg.rs_middle;
  6       run_benchmark_pkg.delete_without_fk;
  7       runstats_pkg.rs_stop(200);
  8  end;
  9  /
Run1 ran in 8 hsecs
Run2 ran in 9 hsecs
run 1 ran in 88.89% of the time

Name                                Run1      Run2      Diff
STAT...consistent gets - exami        10       358       348
STAT...no buffer to keep pinne         0       349       349
STAT...session logical reads       1,755     2,136       381
STAT...consistent gets                20       417       397
STAT...table scan rows gotten          1       401       400
LATCH.cache buffers chains         8,150     8,552       402

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
20,028    20,310       282  98.61%

PL/SQL procedure successfully completed.

test_user@apps> spool off
Followup:
I used this:

ops$tkyte@ORA920> /*
DOC>
DOC>drop table c1;
DOC>drop table c2;
DOC>drop table p1;
DOC>drop table p2;
DOC>set echo on
DOC>
DOC>
DOC>create table p1 ( x int primary key, data char(80) );
DOC>create table p2 ( x int primary key, data char(80) );
DOC>
DOC>insert into p1 select object_id, 'x' from all_objects;
DOC>insert into p2 select * from p1;
DOC>
DOC>create table c1 ( x references p1 on delete cascade, data char(30) );
DOC>create table c2 ( x int, data char(30) );
DOC>create index c1_idx on c1(x);
DOC>create index c2_idx on c2(x);
DOC>
DOC>insert into c1 select x, 'x' from p1, (select rownum from all_objects where
rownum<=5);
DOC>insert into c2 select * from c1;
DOC>
DOC>analyze table p1 compute statistics for table for all indexes for all
indexed columns;
DOC>analyze table p2 compute statistics for table for all indexes for all
indexed columns;
DOC>analyze table c1 compute statistics for table for all indexes for all
indexed columns;
DOC>analyze table c2 compute statistics for table for all indexes for all
indexed columns;
DOC>*/
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec runStats_pkg.rs_start;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> begin
  2          for x in ( select * from p1 )
  3          loop
  4                  delete from p1 where x = x.x;
  5                  commit;
  6          end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runStats_pkg.rs_middle;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> begin
  2          for x in ( select * from p2 )
  3          loop
  4                  delete from c2 where x = x.x;
  5                  delete from p2 where x = x.x;
  6                  commit;
  7          end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runStats_pkg.rs_stop;
Run1 ran in 7343 hsecs
Run2 ran in 8385 hsecs
run 1 ran in 87.57% of the time

as it better simulates "real world" -- and does it often enough in a loop to
give measurable timings (the difference between 8 and 9 hsecs is statistically
insignificant to the point where the 9hsecs could represent the same or less
time then the 8hsecs really).  But we see the on delete cascade is in fact
faster -- and if we actually did this OUTSIDE THE DATABASE using a java client,
you would find it faster even more (we removed the network and the extra java
code).

Name                                  Run1        Run2        Diff
STAT...CPU used by this sessio       5,981       6,350         369
STAT...redo size                99,192,988 105,686,168   6,493,180

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct

4,969,270   5,020,724      51,454     98.98%

PL/SQL procedure successfully completed.

generated lots less redo and used less CPU -- those are always good things as
well.
Rating: 5
additional comments on benchmark July 04, 2003
Reviewer: A reader
well,
Looks like the FK solution also does very well
in terms of consistent gets aswell (as expected).
btw, what does "STAT..consistent gets - exami" stand for?

Thanx!

Name                                Run1      Run2      Diff
STAT...consistent gets - exami        10       358       348
STAT...no buffer to keep pinne         0       349       349
STAT...session logical reads       1,755     2,136       381
STAT...consistent gets                20       417       397
STAT...table scan rows gotten          1       401       400
LATCH.cache buffers chains         8,150     8,552       402
Rating: 5
thanx Tom for the previous followup July 07, 2003
Reviewer: A reader
on Foreign Keys benchmarks. I was writing the case that
you gave to me already (though my code was more complicated - I was using
dynamic sql to generate the
schema - as I wanted to run it for 10 parent tables each
refering to 10 child tables each of which has around 400 records.) One problem
was to write non-dynamic code for
the benchmarks (as the no. of tables is huge - you will have to write lots of
repetitive static code for a real world scenario.)

An argument against FKs brought up by a colleague of mine
is that if there are 20 child tables and you delete
one record from parent table - Oracle will have to go through all 20 child
tables whereas the callback mechanism
doesnot (as - based on the type of info, it knows which
child tables that can contain the record) This is why I am
trying to create a benchmark where I can simulate this scenario. (The above
benchmark by me does not address this
issue though - I am still working on the new benchmark)

Overall, I am definitley convinced that FKs is a better
sollution. The aforementioned case would be microoptimization at best of a
relatively rare scenario
and is not worth all the extra code.
Followup:
Oracle will have to go through as many child tables as your callback mechanism
would.

How could it be otherwise?

if a child was not registered for a callback, you would not have any fkeys on
it.

Otherwise, you would be doing totally differently implementations.

Tell your guy to try doing the callback thing, then posting the code here.  I'll
tell you why it won't work.  It is very very hard to lock data that doesn't
exist see - so basically, your callback implementation would have to basically
prevent all inserts and updates against the child tables -- ALL inserts and ALL
updates.

Oracle doesn't (assuming the fkeys are indexed -- if the fkeys are not indexed,
we too must resort to full table locks on child tables)

Ask this guy "so, what do you think the performance impact of FULL TABLE locks
which is the only correct way for you to implement this wacky logic will be?"
Rating: 5
wow that was fast!!! July 07, 2003
Reviewer: A reader from ca
"Oracle will have to go through as many child tables as your callback mechanism
would."

Following is the argument:

In the case of callbacks, we "know" from the data
as to which child tables have the data for a parent
record with a value of this "type" - say.

For example, our code (pseudo code) would be something like:

if parent_type_col = 'A' then
  delete all children rows from table t1;
  delete all children rows from table t2;
else if parent_type_col = 'B' then
  delete all children rows from table t3;
...
end if;

Oracle implementation can not do the above as it probably
is going through all the child tables - it does not know
that a parent record with value of 'A' in the column
parent_type_col can not exist in t3 for example.

Regarding the table level locking, that is an excellent point. However, when I
am deleting child records, there can not be any inserts against the parent for
which the record is deleted. Well, in our case, it so happens that the parent
record will be marked as "deleted" first. All subsequent inserts and updates
will not be able
to see the parent record to initiate an insert in
child record.  In short, deletion of a parent record is only allowed when the
record is a of a certain status at which time you wont be doing inserts in child
tables (or so is the claim) Since when deleting the parent record
we are sure that there cant be any inserts into child
record, we dont need to lock the child table (sounds ugly
but I think that would be the response I would get)

Thanx a billion for all the help again!!
Hope you had a great 4th of July!
Followup:
so -- ask them why they want to write code, are they paid by the line?

I don't get it, I will never get it, it will never make sense to me, it will
never be the right approach.

how will they prevent future inserts/updates from seeing their parent?  oh, more
code.  And they do realize in order for THAT code to work -- they must first
LOCK some stuff and yadda yadda yadda.

bottom lines here:

a) which will be the fastest to implement (code wise)
b) which requires the least maintainence
c) have they benchmarked their approach (which will definitely be slower then
using builtin stuff)
d) have they justified to anyway why they need to write tons of code that you
have to maintain forever that does exactly what they database they already paid
for does.

last I'll say on the topic.
Rating: 5
thanx Tom! July 07, 2003
Reviewer: A reader from ca
I can see your frustration and understand it - thanx
for all the help so far! I completely agree with your
arguments - I am actually writing up an article
that I would be sending to this team with benchmarks
that would hopefully convince them.
Rating: 5
your benchmark runs slightly slower in my test July 07, 2003
Reviewer: A reader
Hi Tom
Surprised that your benchmarks ran a little slower
on my database (9i).

Thanx and Regards.

SQL> @toms_followup
SQL> drop table c1;

Table dropped.

SQL> drop table c2;

Table dropped.

SQL> drop table p1;

Table dropped.

SQL> drop table p2;

Table dropped.

SQL> set echo on
SQL>
SQL> create table p1 ( x int primary key, data char(80) );

Table created.

SQL> create table p2 ( x int primary key, data char(80) );

Table created.

SQL>
SQL> create table c1 ( x references p1 on delete cascade, data char(30) );

Table created.

SQL> create table c2 ( x int, data char(30) );

Table created.

SQL> create index c1_idx on c1(x);

Index created.

SQL> create index c2_idx on c2(x);

Index created.

SQL>
SQL> analyze table p1 compute statistics for table for all indexes for all
  2  indexed columns;

Table analyzed.

SQL> analyze table p2 compute statistics for table for all indexes for all
  2  indexed columns;

Table analyzed.

SQL> analyze table c1 compute statistics for table for all indexes for all
  2  indexed columns;

Table analyzed.

SQL> analyze table c2 compute statistics for table for all indexes for all
  2  indexed columns;

Table analyzed.

SQL>
SQL> insert into p1 select object_id, 'x' from all_objects;

33317 rows created.

SQL> insert into p2 select * from p1;

33317 rows created.

SQL> insert into c1 select x, 'x' from p1, (select rownum from all_objects where
  2  rownum<=5);

166585 rows created.

SQL> insert into c2 select * from c1;

166585 rows created.

SQL> exec runStats_pkg.rs_start;

PL/SQL procedure successfully completed.

SQL>
SQL> begin
  2    for x in ( select * from p1 )
  3    loop
  4       delete from p1 where x = x.x;
  5       commit;
  6       end loop;
  7    end;
  8  /

PL/SQL procedure successfully completed.

SQL>
SQL> exec runStats_pkg.rs_middle;

PL/SQL procedure successfully completed.

SQL>
SQL> begin
  2    for x in ( select * from p2 )
  3    loop
  4       delete from c2 where x = x.x;
  5       delete from p2 where x = x.x;
  6       commit;
  7    end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

SQL>
SQL> exec runStats_pkg.rs_stop;
Run1 ran in 21184 hsecs
Run2 ran in 18593 hsecs
run 1 ran in 113.94% of the time

Name                                Run1      Run2      Diff
LATCH.ncodef allocation latch                  4                 3
-1
LATCH.session switching                        4                 3
-1
LATCH.transaction branch alloc                 4                 3
-1
STAT...calls to kcmgcs                         3                 4
1
STAT...parse time cpu                          3                 4
1
STAT...transaction tables cons                 0                 1
1
STAT...cluster key scans                       1                 2
1
STAT...cluster key scan block                  1                 2
1
STAT...CR blocks created                     419               420
1
LATCH.channel handle pool latc                 0                 2
2
STAT...sorts (rows)                        2,873             2,875
2
STAT...redo synch time                        26                24
-2
STAT...commit cleanout failure                 3                 5
2
LATCH.latch wait list                          2                 0
-2
LATCH.user lock                               44                42
-2
LATCH.library cache load lock                  2                 4
2
LATCH.process group creation                   0                 2
2
LATCH.FOB s.o list latch                       4                 7
3
LATCH.job_queue_processes para                39                36
-3
STAT...switch current to new b            33,230            33,227
-3
STAT...sorts (memory)                          9                14
5
STAT...prefetched blocks                     370               376
6
LATCH.channel operations paren                69                62
-7
STAT...consistent changes                    881               889
8
STAT...transaction tables cons                 0                 8
8
LATCH.session timer                           71                62
-9
LATCH.hash table column usage                  0                10
10
STAT...cursor authentications                 15                 4
-11
LATCH.post/wait queue latch                   61                49
-12
LATCH.sort extent pool                        20                 4
-16
STAT...parse time elapsed                     24                 5
-19
STAT...bytes received via SQL*               968               997
29
LATCH.session idle bit                    33,514            33,556
42
STAT...redo buffer allocation                574               532
-42
STAT...table fetch by rowid                    8                50
42
STAT...consistent gets                   170,839           170,884
45
STAT...opened cursors cumulati                21                68
47
LATCH.ktm global data                         49                 1
-48
STAT...redo log space wait tim                40                89
49
STAT...shared hash latch upgra            33,422            33,474
52
STAT...consistent gets - exami           102,243           102,190
-53
STAT...parse count (total)                    27                80
53
STAT...execute count                      99,978           100,033
55
LATCH.transaction allocation             100,519           100,580
61
STAT...commit cleanouts succes           266,624           266,549
-75
STAT...messages sent                         399               310
-89
LATCH.shared pool                          2,861             2,951
90
LATCH.global ctx hash table la               110               204
94
STAT...calls to get snapshot s           133,300           133,400
100
STAT...buffer is not pinned co            33,355            33,482
127
STAT...enqueue releases                  533,566           533,697
131
STAT...enqueue requests                  533,561           533,697
136
LATCH.list of block allocation            66,903            67,068
165
STAT...commit cleanout failure               168                 3
-165
LATCH.active checkpoint queue                350               184
-166
LATCH.dml lock allocation                133,708           133,881
173
LATCH.redo writing                        36,411            36,228
-183
STAT...write clones created in               385               199
-186
STAT...calls to kcmgas                    67,352            67,165
-187
STAT...commit cleanouts                  266,795           266,557
-238
STAT...CPU used by this sessio             7,222             6,937
-285
STAT...CPU used when call star             7,222             6,937
-285
STAT...no work - consistent re            34,737            34,432
-305
STAT...deferred (CURRENT) bloc           100,513           100,207
-306
STAT...db block changes                  944,406           944,741
335
STAT...free buffer requested              42,272            42,629
357
STAT...recursive cpu usage                 6,966             6,578
-388
LATCH.resmgr group change latc           433,891           434,325
434
LATCH.resmgr:schema config               433,891           434,325
434
STAT...recursive calls                   166,782           167,237
455
STAT...redo entries                      504,940           505,397
457
STAT...physical reads                      1,391             1,863
472
STAT...immediate (CURRENT) blo            66,531            66,036
-495
LATCH.messages                             5,221             4,722
-499
STAT...hot buffers moved to he               823               297
-526
LATCH.undo global data                   112,162           111,541
-621
LATCH.enqueue hash chains              1,069,654         1,070,329
675
LATCH.redo allocation                    508,035           508,736
701
STAT...change write time                   9,310             8,496
-814
STAT...cleanouts only - consis               105               944
839
STAT...immediate (CR) block cl               105               944
839
LATCH.enqueues                         1,971,834         1,972,839
1,005
LATCH.row cache objects                    1,965             3,151
1,186
STAT...dirty buffers inspected             2,038               564
-1,474
LATCH.session allocation                  68,658            70,238
1,580
LATCH.multiblock read objects                176             2,374
2,198
STAT...redo size                     119,764,720       119,768,928
4,208
LATCH.cache buffers lru chain             72,384            76,939
4,555
STAT...free buffer inspected               7,071               564
-6,507
LATCH.library cache                      252,281           259,907
7,626
LATCH.checkpoint queue latch              80,163            71,806
-8,357
STAT...session logical reads             925,023           856,846
-68,177
STAT...db block gets                     754,184           685,962
-68,222
LATCH.cache buffers chains             3,954,381         3,767,795
-186,586

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
9,339,536         9,164,060          -175,476 101.91%

PL/SQL procedure successfully completed.

SQL> spool off
Followup:
given that all machines are not created equal, I'm not.  I can find slower
machines to run on as well :)
Rating: 5
I meant something else July 07, 2003
Reviewer: A reader
I meant that run1 (foreign keys cascade delete case) ran slower than run2
(callback approach). Sorry for the
confusion!

Regards
Followup:
you had:

STAT...CPU used by this sessio             7,222             6,937

which is only about 4% different (elapsed time can be very much dependent on
whats happening)

but, I believe it has lots to do with you analyzing AND THEN loading rather then
LOADING and then analyzing.
Rating: 5
thanx tom ! July 08, 2003
Reviewer: A reader
but even after analyzing after loading data the run1 (with FKs) runs slower than
run2 (using direct deletes without
FK cascade deletes.) I would have expected that this should
never happen...sorry about the hassel - feel free to ignore
this as the difference is not too much.

--
SQL> set echo on
SQL> set serveroutput on size 1000000
SQL> @toms_followup
SQL> set echo on;
SQL> set serveroutput on size 1000000
SQL> drop table c1;

Table dropped.

SQL> drop table c2;

Table dropped.

SQL> drop table p1;

Table dropped.

SQL> drop table p2;

Table dropped.

SQL>
SQL> create table p1 ( x int primary key, data char(80) );

Table created.

SQL> create table p2 ( x int primary key, data char(80) );

Table created.

SQL>
SQL> create table c1 ( x references p1 on delete cascade, data char(30) );

Table created.

SQL> create table c2 ( x int, data char(30) );

Table created.

SQL> create index c1_idx on c1(x);

Index created.

SQL> create index c2_idx on c2(x);

Index created.

SQL>
SQL> insert into p1 select object_id, 'x' from all_objects;

33318 rows created.

SQL> insert into p2 select * from p1;

33318 rows created.

SQL> insert into c1 select x, 'x' from p1, (select rownum from all_objects where
  2  rownum<=5);

166590 rows created.

SQL> insert into c2 select * from c1;

166590 rows created.

SQL>
SQL> analyze table p1 compute statistics for table for all indexes for all
  2  indexed columns;

Table analyzed.

SQL> analyze table p2 compute statistics for table for all indexes for all
  2  indexed columns;

Table analyzed.

SQL> analyze table c1 compute statistics for table for all indexes for all
  2  indexed columns;

Table analyzed.

SQL> analyze table c2 compute statistics for table for all indexes for all
  2  indexed columns;

Table analyzed.

SQL>
SQL> exec runStats_pkg.rs_start;

PL/SQL procedure successfully completed.

SQL>
SQL> begin
  2    for x in ( select * from p1 )
  3    loop
  4       delete from p1 where x = x.x;
  5       commit;
  6       end loop;
  7    end;
  8  /

PL/SQL procedure successfully completed.

SQL>
SQL> exec runStats_pkg.rs_middle;

PL/SQL procedure successfully completed.

SQL>
SQL> begin
  2    for x in ( select * from p2 )
  3    loop
  4       delete from c2 where x = x.x;
  5       delete from p2 where x = x.x;
  6       commit;
  7    end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

SQL>
SQL> exec runStats_pkg.rs_stop;
Run1 ran in 19833 hsecs
Run2 ran in 19026 hsecs
run 1 ran in 104.24% of the time

Name                                Run1      Run2      Diff
LATCH.job_queue_processes para                32                31
-1
LATCH.user lock                               42                43
1
STAT...cluster key scan block                  1                 2
1
STAT...parse time elapsed                      4                 5
1
STAT...redo log space requests                 1                 2
1
STAT...parse time cpu                          5                 4
-1
STAT...cluster key scans                       1                 2
1
LATCH.latch wait list                          2                 0
-2
LATCH.library cache load lock                  2                 4
2
LATCH.loader state object free                 2                 4
2
LATCH.session timer                           66                64
-2
STAT...sorts (rows)                        2,873             2,875
2
LATCH.session idle bit                    33,553            33,551
-2
LATCH.channel operations paren                64                61
-3
LATCH.FIB s.o chain latch                      4                 8
4
STAT...calls to kcmgcs                         3                 7
4
LATCH.post/wait queue latch                   54                50
-4
LATCH.FOB s.o list latch                       4                 8
4
STAT...commit cleanout failure                 6                 1
-5
STAT...table fetch by rowid                   56                51
-5
STAT...deferred (CURRENT) bloc            98,935            98,940
5
STAT...sorts (memory)                          9                14
5
LATCH.hash table column usage                  0                 6
6
LATCH.sort extent pool                        10                 4
-6
STAT...switch current to new b            33,240            33,234
-6
STAT...parse count (total)                    76                82
6
STAT...consistent changes                    881               887
6
STAT...calls to get snapshot s           133,401           133,407
6
STAT...enqueue requests                  533,723           533,716
-7
STAT...consistent gets - exami           100,391           100,399
8
STAT...enqueue releases                  533,724           533,716
-8
STAT...execute count                     100,030           100,038
8
STAT...commit cleanouts                  266,557           266,566
9
STAT...cursor authentications                  7                17
10
STAT...redo entries                      504,478           504,467
-11
STAT...buffer is not pinned co            33,500            33,486
-14
STAT...commit cleanouts succes           266,547           266,561
14
STAT...prefetched blocks                     359               376
17
STAT...calls to kcmgas                    67,090            67,109
19
LATCH.ktm global data                         21                 1
-20
STAT...redo buffer allocation                493               517
24
STAT...redo synch time                        30                54
24
STAT...write clones created in               111               136
25
STAT...bytes received via SQL*               968               997
29
STAT...shared hash latch upgra            33,383            33,426
43
STAT...redo log space wait tim                42                87
45
STAT...hot buffers moved to he                50               108
58
STAT...recursive calls                   167,193           167,272
79
STAT...consistent gets                   168,965           168,878
-87
LATCH.global ctx hash table la               207               108
-99
STAT...no work - consistent re            34,768            34,667
-101
LATCH.list of block allocation            67,058            66,956
-102
STAT...messages sent                         503               387
-116
LATCH.active checkpoint queue                399               265
-134
LATCH.dml lock allocation                133,857           133,722
-135
STAT...db block changes                  944,003           943,834
-169
LATCH.transaction allocation             100,615           100,418
-197
STAT...physical reads                      1,634             1,856
222
STAT...CPU used by this sessio             7,183             6,877
-306
STAT...CPU used when call star             7,183             6,877
-306
LATCH.redo writing                        36,599            36,263
-336
LATCH.shared pool                          3,030             2,657
-373
STAT...dirty buffers inspected             1,567             1,191
-376
STAT...free buffer inspected               1,569             1,191
-378
STAT...recursive cpu usage                 6,884             6,504
-380
STAT...free buffer requested              42,158            42,582
424
LATCH.resmgr group change latc           434,345           433,894
-451
LATCH.resmgr:schema config               434,345           433,894
-451
STAT...change write time                   6,506             7,223
717
LATCH.messages                             5,926             4,993
-933
LATCH.redo allocation                    508,289           507,355
-934
LATCH.multiblock read objects              1,132               160
-972
LATCH.undo global data                   110,573           109,420
-1,153
LATCH.session allocation                  70,292            68,749
-1,543
LATCH.enqueue hash chains              1,070,958         1,069,238
-1,720
LATCH.row cache objects                    3,834             1,379
-2,455
LATCH.enqueues                         1,974,058         1,970,959
-3,099
LATCH.checkpoint queue latch              79,841            72,710
-7,131
LATCH.cache buffers lru chain             76,212            68,531
-7,681
LATCH.library cache                      261,633           252,993
-8,640
STAT...redo size                     119,676,360       119,661,884
-14,476
STAT...db block gets                     754,486           685,937
-68,549
STAT...session logical reads             923,451           854,815
-68,636
LATCH.cache buffers chains             4,014,000         3,729,485
-284,515

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
9,421,151         9,098,076          -323,075 103.55%

PL/SQL procedure successfully completed.

SQL>
SQL> spool off
Rating: 5
fk cascade delete July 14, 2003
Reviewer: A reader
hi tom
I am back (the reader above with cascade delete problem)
Now that it is shown that cascade delete outperforms the callback delete, the
argument from the team is that the main reason we use callbacks is because we
can control the number of deletes. The argument is that we have around 200 child
tables each containing say around 20000 rows. If we use cascade delete we run
out of rollback space. Using callbacks we can control the rows that we delete
(and commit) at a time.

I know your views about committing
frequently and the fact that we should size the rollback
segment properly. My question is
1. do indexes come into play in this? (I think they do) So is there a way that I
could "disable indexes" on parent and child tables - just during the cascade
delete and enable them afterwards?
Does that mitigate the RBS concerns?
As I write I think this may not work especially if the deletes are frequent but
your views may not hurt anyways.

2. Also, what are the hardware costs that we are talking about? I am not a DBA,
would like to get a sense of how
much $$ we spend on production disks - if I want to say
configure a rollback segment of 100G say (arbitrary number -may not represent
reality but want to get a cost
sense)

Overall, you dont need to convince me about disadvantages of committing
frequently.

Thanx!
Followup:
I sort of give up, they are determined to write tons of code, maintain tons of
code.

disk is pretty cheap -- I'll bet you could buy more disk then you need for
rollback for less then what we got paid during the time you've had the
discussions with the people that want to write code.
Rating: 5
thanx tom! July 15, 2003
Reviewer: A reader
Well, I can understand why you would give up.. Anyways,
I am now trying to benchmark the case where I do commits
in a loop using dynamic sql for deleting child and parent
records versus a simple cascade delete. I was expecting
to do worse in the case of commit in loop but surprisingly
my results *seem* to show otherwise. I have a feeling I may have made a mistake
though I cant seem to put my finger on it.

I am creating two tables p1 and p2. Both have 200 child
tables (created dynamically.) Each child table has
around 20000 records.
In one case (p1), I just do a simple delete and commit to
delete the parent record and all child records. In the
second case (p2), I go in a loop deleting and committing 500 records at a time
for each child table till all records are deleted in all 200 child tables. Then
I delete the parent table record.

Here is the code - would appreciate your input in the
correctness of this benchmark. It seems to show that committing in a loop is
twice as faster and takes
half as much resources! Question is where am I making
a mistake!

Thank you Tom, I (and other readers) owe you a big time for all the help and
knowledge and insight you always give
to us!!

---- the benchmark follows

test@apps> @schema
test@apps>
test@apps> set doc off
test@apps> set serveroutput on size 1000000
test@apps>
test@apps> -- package to create schema
test@apps> create or replace package commit_freq_schema_pkg
  2  as
  3    g_fk_table_name_prefix varchar2(10) := 'C_FK_';
  4    g_no_fk_table_name_prefix varchar2(10) := 'C_NO_FK';
  5    g_num_of_child_tables integer := 200;
  6    g_recs_per_child_table integer := 20000;
  7
  8    procedure create_schema;
  9  end;
 10  /

Package created.

test@apps> create or replace package body commit_freq_schema_pkg
  2  as
  3    procedure create_table ( p_table_name in varchar2,
  4       p_create_table_sql in varchar2 )
  5    is
  6       l_dummy varchar2(30);
  7    begin
  8       begin
  9         select table_name into l_dummy
 10         from  all_tables
 11         where table_name = upper( p_table_name );
 12
 13         -- drop table if above select finds something;
 14         execute immediate 'drop table '|| p_table_name ||
 15             ' cascade constraints ';
 16       exception
 17         when no_data_found then
 18           null;
 19       end;
 20
 21       dbms_output.put_line( 'creating table ' ||p_table_name );
 22       execute immediate p_create_table_sql;
 23    end;
 24
 25    procedure create_schema
 26    is
 27       l_table_name varchar2(30);
 28       l_create_table_sql varchar2(4000);
 29    begin
 30       -- drop two parent tables
 31       begin
 32         execute immediate 'drop table p1 cascade constraints';
 33         execute immediate 'drop table p2 cascade constraints';
 34         execute immediate ' create table p1 '||
 35         '( '||
 36         '  parent_key raw(16) default sys_guid() primary key, data char(20)
'||
 37         ') ';
 38
 39         execute immediate ' create table p2 '||
 40         '( '||
 41         '  parent_key raw(16) default sys_guid() primary key, data char(20)
'||
 42         ')';
 43
 44         -- create child tables
 45         for i in 1 .. g_num_of_child_tables loop
 46           l_table_name := g_fk_table_name_prefix||i;
 47           l_create_table_sql := 'create table '|| l_table_name ||
 48           ' ( parent_key references p1 on delete cascade, data char(20) )';
 49
 50           create_table ( l_table_name, l_create_table_sql );
 51
 52           execute immediate 'create index '|| l_table_name || '_idx on ' ||
 53          l_table_name || '( parent_key ) ';
 54
 55
 56           l_table_name := g_no_fk_table_name_prefix||i;
 57           l_create_table_sql := 'create table '|| l_table_name ||
 58           ' ( parent_key raw(16) references p2, data char(20) )';
 59
 60           create_table ( l_table_name, l_create_table_sql );
 61         end loop;
 62       end;
 63    end;
 64
 65  end;
 66  /

Package body created.

test@apps>
test@apps> show errors;
No errors.
test@apps> --create benchmark schema
test@apps> begin
  2    commit_freq_schema_pkg.create_schema;
  3    null;
  4  end;
  5  /

PL/SQL procedure successfully completed.

test@apps> @data
test@apps> set echo on
test@apps> set serveroutput on size 1000000
test@apps>
test@apps> create or replace package commit_freq_data_pkg
  2  as
  3
  4    g_recs_per_child_table integer := 20000;
  5    procedure populate_data ;
  6  end;
  7  /

Package created.

test@apps> create or replace package body commit_freq_data_pkg
  2  as
  3    procedure populate_data
  4    is
  5       l_table_name varchar2(30);
  6    begin
  7       -- parent data
  8       insert into p1 values( sys_guid(), 'P1 data');
  9       insert into p2 values( sys_guid(), 'P2 data');
 10
 11       -- child data
 12       for i in 1 .. commit_freq_schema_pkg.g_num_of_child_tables loop
 13         l_table_name := commit_freq_schema_pkg.g_fk_table_name_prefix||i;
 14         execute immediate 'insert into ' || l_table_name ||
 15         ' select p1.parent_key , object_type as data'||
 16         ' from all_objects, p1 '||
 17         ' where rownum < :x '
 18         using g_recs_per_child_table ;
 19
 20         execute immediate 'analyze table ' || l_table_name ||
 21           ' compute statistics for table for all indexes for all indexed
columns ';
 22
 23         l_table_name := commit_freq_schema_pkg.g_no_fk_table_name_prefix||i;
 24         execute immediate 'insert into ' || l_table_name ||
 25         ' select p2.parent_key , object_type as data'||
 26         ' from all_objects, p2 '||
 27         ' where rownum < :x '
 28         using g_recs_per_child_table ;
 29
 30         execute immediate 'analyze table ' || l_table_name
 31           || ' compute statistics for table for all indexes for all indexed
columns ';
 32       end loop;
 33    end;
 34
 35  end;
 36  /

Package body created.

test@apps>
test@apps> show errors;
No errors.
test@apps> --create benchmark data
test@apps> begin
  2    commit_freq_data_pkg.populate_data;
  3    null;
  4  end;
  5  /

PL/SQL procedure successfully completed.

test@apps> @test.sql
test@apps> select count(*) from c_fk_1;

     19999

test@apps> select count(*) from c_no_fk1;

     19999

test@apps> select count(*) from c_fk_200;

     19999

test@apps> select count(*) from c_no_fk200;

     19999

test@apps> !echo 

test@apps> @bm1
test@apps> set serveroutput on size 100000
test@apps> create or replace package run_benchmark_pkg as
  2    g_max_commit integer := 500;
  3
  4    procedure delete_with_fk;
  5    procedure delete_with_no_fk;
  6  end;
  7  /

Package created.

test@apps> create or replace package body run_benchmark_pkg as
  2    procedure delete_with_fk
  3    is
  4    begin
  5       delete p1;
  6       commit;
  7       dbms_output.put_line( 'FK case done' );
  8       null;
  9    end;
 10
 11    procedure delete_with_no_fk
 12    is
 13         l_delete_stmt varchar2(4000);
 14         l_counter number;
 15    begin
 16       for crec in (select a.table_name
 17                from user_tables a
 18                where a.table_name like(
 19               commit_freq_schema_pkg.g_no_fk_table_name_prefix
 20               ||'%') )
 21       loop
 22         dbms_output.put_line( 'NO FK: table ' || crec.table_name );
 23         l_delete_stmt := 'delete from ' || crec.table_name ||
 24            ' where rownum <= ' || g_max_commit;
 25         loop
 26           execute immediate l_delete_stmt;
 27           l_counter := SQL%ROWCOUNT;
 28           commit;
 29           if l_counter < g_max_commit then
 30             exit;
 31           end if;
 32         end loop;
 33       end loop;
 34
 35       commit;
 36       delete p2;
 37       commit;
 38    end;
 39
 40  end;
 41  /

Package body created.

test@apps>
test@apps> begin
  2    runstats_pkg.rs_start;
  3    run_benchmark_pkg.delete_with_fk;
  4    runstats_pkg.rs_middle;
  5    run_benchmark_pkg.delete_with_no_fk;
  6    runstats_pkg.rs_stop(200);
  7  null;
  8  end;
  9  /

Run1 ran in 349039 hsecs
Run2 ran in 188440 hsecs
run 1 ran in 185.23% of the time

Name                                Run1      Run2      Diff
STAT...cursor authentications                202               403
201
STAT...opened cursors current                203                -2
-205
STAT...redo ordering marks                   231                 0
-231
LATCH.hash table column usage                323                89
-234
LATCH.job_queue_processes para               604               369
-235
LATCH.file number translation                244                 2
-242
LATCH.user lock                              785               423
-362
LATCH.resmgr:actses active lis               806               433
-373
STAT...table fetch by rowid                  172               573
401
LATCH.channel operations paren             1,184               617
-567
LATCH.session timer                        1,270               684
-586
LATCH.post/wait queue latch                1,282               662
-620
STAT...cluster key scans                       0               800
800
STAT...consistent gets - exami               203             1,013
810
STAT...hot buffers moved to he             2,807             1,953
-854
STAT...deferred (CURRENT) bloc            23,086            24,004
918
STAT...redo log space wait tim             1,606               633
-973
LATCH.cache buffer handles                     0             1,194
1,194
LATCH.global ctx hash table la             2,627             1,410
-1,217
STAT...immediate (CURRENT) blo             1,567                 0
-1,567
LATCH.child cursor hash table              2,248             3,816
1,568
STAT...cluster key scan block                  0             1,600
1,600
LATCH.active checkpoint queue              2,817               932
-1,885
STAT...messages sent                       2,762               835
-1,927
LATCH.session idle bit                     5,686            10,079
4,393
STAT...redo buffer allocation             10,854             6,231
-4,623
STAT...pinned buffers inspecte            22,465            15,998
-6,467
STAT...switch current to new b            24,740            31,803
7,063
STAT...user commits                            1             8,001
8,000
STAT...table scans (long table                 0             8,200
8,200
STAT...opened cursors cumulati               375             8,579
8,204
STAT...parse count (total)                   375             8,579
8,204
STAT...physical reads                     75,392            67,180
-8,212
LATCH.session allocation                  51,330            42,707
-8,623
LATCH.shared pool                         56,268            46,887
-9,381
LATCH.multiblock read objects             12,692            22,258
9,566
LATCH.row cache objects                   38,513            28,042
-10,471
LATCH.redo writing                        44,064            32,525
-11,539
LATCH.dml lock allocation                  9,669            21,630
11,961
LATCH.list of block allocation             5,006            18,576
13,570
STAT...session uga memory                143,776           129,124
-14,652
STAT...calls to kcmgas                    25,023            39,879
14,856
STAT...execute count                         375            16,578
16,203
LATCH.undo global data                   138,061           118,376
-19,685
LATCH.transaction allocation               9,843            29,529
19,686
STAT...calls to get snapshot s               375            24,593
24,218
STAT...recursive calls                     2,183            31,405
29,222
LATCH.messages                            67,016            37,462
-29,554
STAT...commit cleanouts                      168            31,806
31,638
STAT...commit cleanouts succes                 0            31,802
31,802
STAT...recursive cpu usage                80,811            45,786
-35,025
STAT...dirty buffers inspected            99,446            55,438
-44,008
LATCH.library cache                      397,104           347,630
-49,474
STAT...free buffer requested             227,185           176,249
-50,936
STAT...free buffer inspected             126,715            71,437
-55,278
STAT...prefetched blocks                       1            55,945
55,944
STAT...change write time                 221,046           126,811
-94,235
LATCH.cache buffers lru chain            437,177           318,085
-119,092
LATCH.checkpoint queue latch             687,195           388,708
-298,487
STAT...no work - consistent re            24,888           536,952
512,064
STAT...consistent gets                    25,464           538,340
512,876
STAT...table scan blocks gotte                 5           533,605
533,600
STAT...buffer is not pinned co               521           537,718
537,197
STAT...enqueue requests                8,000,807         4,025,225
-3,975,582
STAT...enqueue releases                8,000,808         4,025,225
-3,975,583
LATCH.resmgr group change latc         8,009,876         4,014,672
-3,995,204
LATCH.resmgr:schema config             8,009,876         4,014,672
-3,995,204
STAT...table scan rows gotten                  1         3,999,801
3,999,800
STAT...redo entries                    8,098,114         4,080,247
-4,017,867
LATCH.redo allocation                  8,146,262         4,109,230
-4,037,032
STAT...session logical reads          12,434,833         4,866,493
-7,568,340
LATCH.enqueue hash chains             16,045,376         8,078,815
-7,966,561
STAT...db block changes               16,231,706         8,171,668
-8,060,038
STAT...db block gets                  12,409,369         4,328,153
-8,081,216
LATCH.enqueues                        32,070,806        16,122,620
-15,948,186
LATCH.cache buffers chains            58,341,726        26,768,151
-31,573,575

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
132,598,313        64,581,794       -68,016,519 205.32%

PL/SQL procedure successfully completed.

test@apps> spool off
Followup:
why indexes on one set of child tables but not the other.

looks like the overhead of index maintanence here.

since you only have one parent record, the index is "not a good idea",

eg: your test is not real world and the schemas are not equivalent.  the child
tables have but one value in them.  you made the RI version do many more times
the work.
Rating: 5
thanx tom! July 15, 2003
Reviewer: A reader
"why indexes on one set of child tables but not the other."

The reason is that I wanted to index the foriegn keys in the case of delete
cascade for obvious reasons. However
you may have a point here - I should test the case
with index on the child tables for the second case
also! Thanx for pointing that out!!!

"since you only have one parent record, the index is "not a good idea",

eg: your test is not real world and the schemas are not equivalent.  the child
tables have but one value in them.  you made the RI version do many more times
the work.
"
Actually the child tables have 20K records not
one record.

Well, our scenario is that we would be committing
after every record in the parent is deleted - you
can assume that the parent record will be deleted
in one transaction. Since number of child
records is a lot - I wanted to just test a case
of deleting one parent record that has 200 child
tables that itself has as many as 20K records.
It reflects the scenario that I am trying to
portray. a parent with lots of children each with lots
of records - and we delete one entry at a time in
one transaction.

Thank you!
Followup:
the PARENT record has one value

you have 20,000 child records with a constant value
hence index = bad idea for your test csae.
Rating: 5
thanks tom July 15, 2003
Reviewer: A reader
but how would you test this scenario then? At a time
I would be deleting only one parent record and
committing (in my actual program)
which in the child record would by necessity be
the same (because of RI.) I can populate the parent
with multiple records but run the test for deleting only
one record of parent - would that help?

Also, if I create an index in both cases, the
field should be level, right?
Followup:
yes, the fields should be level by creating an index on both
Rating: 5
here are test results! July 15, 2003
Reviewer: A reader
I would have expected a little but more disparity.
Run1 - FK cascade delete
Run2 - piecemeal delete with intermittent commits.

This test proves that they are "comparable" (if you
just compare performance in terms of elapsed time
and latches consumed). Of course, if you bring in other
factors such as code complexity, increased redo log
contention, overall slower system - then the FK cascade
delete solution is the superior solution any day!!

thank you for everything!

test@apps> set echo on
test@apps> set serveroutput on size 100000
test@apps> @schema
test@apps>
test@apps> set doc off
test@apps> set serveroutput on size 1000000
test@apps>
test@apps> -- package to create schema
test@apps> create or replace package commit_freq_schema_pkg
  2  as
  3    g_fk_table_name_prefix varchar2(10) := 'C_FK_';
  4    g_no_fk_table_name_prefix varchar2(10) := 'C_NO_FK';
  5    g_num_of_child_tables integer := 200;
  6    g_recs_per_child_table integer := 20000;
  7
  8    procedure create_schema;
  9  end;
 10  /

Package created.

test@apps> create or replace package body commit_freq_schema_pkg
  2  as
  3    procedure create_table ( p_table_name in varchar2,
  4       p_create_table_sql in varchar2 )
  5    is
  6       l_dummy varchar2(30);
  7    begin
  8       begin
  9         select table_name into l_dummy
 10         from  all_tables
 11         where table_name = upper( p_table_name );
 12
 13         -- drop table if above select finds something;
 14         execute immediate 'drop table '|| p_table_name ||
 15             ' cascade constraints ';
 16       exception
 17         when no_data_found then
 18           null;
 19       end;
 20
 21       dbms_output.put_line( 'creating table ' ||p_table_name );
 22       execute immediate p_create_table_sql;
 23    end;
 24
 25    procedure create_schema
 26    is
 27       l_table_name varchar2(30);
 28       l_create_table_sql varchar2(4000);
 29    begin
 30       -- drop two parent tables
 31       begin
 32         execute immediate 'drop table p1 cascade constraints';
 33         execute immediate 'drop table p2 cascade constraints';
 34         execute immediate ' create table p1 '||
 35         '( '||
 36         '  parent_key raw(16) default sys_guid() primary key, data char(20)
'||
 37         ') ';
 38
 39         execute immediate ' create table p2 '||
 40         '( '||
 41         '  parent_key raw(16) default sys_guid() primary key, data char(20)
'||
 42         ')';
 43
 44         -- create child tables
 45         for i in 1 .. g_num_of_child_tables loop
 46           l_table_name := g_fk_table_name_prefix||i;
 47           l_create_table_sql := 'create table '|| l_table_name ||
 48           ' ( parent_key references p1 on delete cascade, data char(20) )';
 49
 50           create_table ( l_table_name, l_create_table_sql );
 51
 52           execute immediate 'create index '|| l_table_name || '_idx on ' ||
 53          l_table_name || '( parent_key ) ';
 54
 55
 56           l_table_name := g_no_fk_table_name_prefix||i;
 57           l_create_table_sql := 'create table '|| l_table_name ||
 58           ' ( parent_key raw(16) references p2, data char(20) )';
 59
 60           create_table ( l_table_name, l_create_table_sql );
 61           execute immediate 'create index '|| l_table_name || '_idx on ' ||
 62          l_table_name || '( parent_key ) ';
 63         end loop;
 64       end;
 65    end;
 66
 67  end;
 68  /

Package body created.

test@apps>
test@apps> show errors;
No errors.
test@apps> --create benchmark schema
test@apps> begin
  2    commit_freq_schema_pkg.create_schema;
  3    null;
  4  end;
  5  /

PL/SQL procedure successfully completed.

test@apps> @data
test@apps> set echo on
test@apps> set serveroutput on size 1000000
test@apps>
test@apps> create or replace package commit_freq_data_pkg
  2  as
  3
  4    g_recs_per_child_table integer := 20000;
  5    procedure populate_data ;
  6  end;
  7  /

Package created.

test@apps> create or replace package body commit_freq_data_pkg
  2  as
  3    procedure populate_data
  4    is
  5       l_table_name varchar2(30);
  6    begin
  7       -- parent data
  8       insert into p1 values( sys_guid(), 'P1 data');
  9       insert into p2 values( sys_guid(), 'P2 data');
 10
 11       -- child data
 12       for i in 1 .. commit_freq_schema_pkg.g_num_of_child_tables loop
 13         l_table_name := commit_freq_schema_pkg.g_fk_table_name_prefix||i;
 14         execute immediate 'insert into ' || l_table_name ||
 15         ' select p1.parent_key , object_type as data'||
 16         ' from all_objects, p1 '||
 17         ' where rownum < :x '
 18         using g_recs_per_child_table ;
 19
 20         execute immediate 'analyze table ' || l_table_name ||
 21           ' compute statistics for table for all indexes for all indexed
columns ';
 22
 23         l_table_name := commit_freq_schema_pkg.g_no_fk_table_name_prefix||i;
 24         execute immediate 'insert into ' || l_table_name ||
 25         ' select p2.parent_key , object_type as data'||
 26         ' from all_objects, p2 '||
 27         ' where rownum < :x '
 28         using g_recs_per_child_table ;
 29
 30         execute immediate 'analyze table ' || l_table_name
 31           || ' compute statistics for table for all indexes for all indexed
columns ';
 32       end loop;
 33    end;
 34
 35  end;
 36  /

Package body created.

test@apps>
test@apps> show errors;
No errors.
test@apps> --create benchmark data
test@apps> begin
  2    commit_freq_data_pkg.populate_data;
  3    null;
  4  end;
  5  /

PL/SQL procedure successfully completed.

test@apps> @test.sql
test@apps> select count(*) from c_fk_1;

     19999

test@apps> select count(*) from c_no_fk1;

     19999

test@apps> select count(*) from c_fk_200;

     19999

test@apps> select count(*) from c_no_fk200;

     19999

test@apps> @bm1
test@apps> set serveroutput on size 100000
test@apps> create or replace package run_benchmark_pkg as
  2    g_max_commit integer := 500;
  3
  4    procedure delete_with_fk;
  5    procedure delete_with_no_fk;
  6  end;
  7  /

Package created.

test@apps> create or replace package body run_benchmark_pkg as
  2    procedure delete_with_fk
  3    is
  4    begin
  5       delete p1;
  6       commit;
  7       dbms_output.put_line( 'FK case done' );
  8       null;
  9    end;
 10
 11    procedure delete_with_no_fk
 12    is
 13         l_delete_stmt varchar2(4000);
 14         l_counter number;
 15    begin
 16       for crec in (select a.table_name
 17                from user_tables a
 18                where a.table_name like(
 19               commit_freq_schema_pkg.g_no_fk_table_name_prefix
 20               ||'%') )
 21       loop
 22         dbms_output.put_line( 'NO FK: table ' || crec.table_name );
 23         l_delete_stmt := 'delete from ' || crec.table_name ||
 24            ' where rownum <= ' || g_max_commit;
 25         loop
 26           execute immediate l_delete_stmt;
 27           l_counter := SQL%ROWCOUNT;
 28           commit;
 29           if l_counter < g_max_commit then
 30             exit;
 31           end if;
 32         end loop;
 33       end loop;
 34
 35       commit;
 36       delete p2;
 37       commit;
 38    end;
 39
 40  end;
 41  /

Package body created.

test@apps>
test@apps> begin
  2    runstats_pkg.rs_start;
  3    run_benchmark_pkg.delete_with_fk;
  4    runstats_pkg.rs_middle;
  5    run_benchmark_pkg.delete_with_no_fk;
  6    runstats_pkg.rs_stop(200);
  7  null;
  8  end;
  9  /

Run1 ran in 362632 hsecs
Run2 ran in 345783 hsecs
run 1 ran in 104.87% of the time

Name                                Run1      Run2      Diff
STAT...redo log space wait tim             1,694             1,433
-261
STAT...no buffer to keep pinne                 0               311
311
STAT...shared hash latch upgra               560               216
-344
LATCH.ktm global data                         31               403
372
STAT...buffer is pinned count                  0               373
373
STAT...table fetch by rowid                  168               803
635
STAT...redo ordering marks                   655                 0
-655
LATCH.child cursor hash table              2,292             1,624
-668
STAT...cluster key scans                       0               800
800
LATCH.cache buffer handles                     0               884
884
STAT...consistent gets - exami               201             1,325
1,124
STAT...cluster key scan block                  0             1,600
1,600
STAT...dirty buffers inspected           104,894           106,991
2,097
LATCH.messages                            65,519            67,736
2,217
STAT...recursive cpu usage                76,049            78,267
2,218
STAT...hot buffers moved to he             1,977             4,614
2,637
LATCH.row cache objects                   39,501            42,300
2,799
LATCH.shared pool                         58,608            62,305
3,697
LATCH.resmgr group change latc         8,011,684         8,018,380
6,696
LATCH.resmgr:schema config             8,011,683         8,018,380
6,697
LATCH.multiblock read objects             11,538            19,192
7,654
STAT...physical reads                     74,278            82,040
7,762
STAT...opened cursors cumulati               371             8,209
7,838
STAT...parse count (total)                   371             8,209
7,838
LATCH.session idle bit                     3,410            11,291
7,881
STAT...switch current to new b            23,814            31,807
7,993
STAT...table scans (long table                 0             8,000
8,000
STAT...user commits                            1             8,001
8,000
LATCH.redo writing                        43,513            51,664
8,151
LATCH.session allocation                  52,818            63,168
10,350
STAT...free buffer requested             225,161           237,765
12,604
LATCH.dml lock allocation                  9,966            24,850
14,884
STAT...calls to kcmgas                    24,522            39,924
15,402
LATCH.list of block allocation             5,154            20,600
15,446
STAT...execute count                         371            16,208
15,837
STAT...pinned buffers inspecte            23,295             1,641
-21,654
STAT...change write time                 208,045           186,300
-21,745
STAT...enqueue releases                8,000,803         8,024,482
23,679
STAT...enqueue requests                8,000,803         8,024,482
23,679
STAT...recursive calls                     2,151            25,837
23,686
STAT...calls to get snapshot s               372            24,217
23,845
LATCH.transaction allocation              10,178            34,167
23,989
STAT...prefetched blocks                       0            26,003
26,003
STAT...free buffer inspected             138,607           108,683
-29,924
STAT...immediate (CURRENT) blo             2,964            34,904
31,940
STAT...redo entries                    8,098,570         8,138,730
40,160
LATCH.redo allocation                  8,148,350         8,188,747
40,397
STAT...db block changes               16,232,119        16,280,040
47,921
LATCH.enqueue hash chains             16,049,718        16,098,137
48,419
LATCH.undo global data                   138,106           188,149
50,043
LATCH.cache buffers lru chain            436,763           491,219
54,456
STAT...commit cleanouts                      168            63,691
63,523
STAT...commit cleanouts succes                 0            63,680
63,680
LATCH.checkpoint queue latch             660,007           724,996
64,989
LATCH.enqueues                        32,079,437        32,154,357
74,920
LATCH.library cache                      410,336           501,279
90,943
STAT...db block gets                  12,408,821        12,540,683
131,862
STAT...session uga memory max            160,040            12,744
-147,296
STAT...session uga memory                171,808             8,492
-163,316
STAT...no work - consistent re            23,954           501,325
477,371
STAT...consistent gets                    24,524           502,859
478,335
STAT...table scan blocks gotte                 5           498,005
498,000
STAT...buffer is not pinned co               509           502,125
501,616
STAT...session logical reads          12,433,345        13,043,542
610,197
LATCH.cache buffers chains            58,190,408        59,889,518
1,699,110
STAT...table scan rows gotten                  1         3,999,801
3,999,800

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
132,450,811       134,684,654         2,233,843  98.34%

test@apps> spool off
Rating: 3
DB Schema July 26, 2003
Reviewer: Rajeev Soni from Hyderabad, India
Hi Tom,
I just wanted to know is there any in-built package/procedure in Oracle (i am
using Oracle 9iR2) which will give me the complete DB schema info of the current
user, such as All Tables, All Columns, Constraints... Etc.

I have browsed the complete Oracle DB to get the Meta data info regarding the DB
Schema... this views will give me all the info required USER_TAB_PRIVS_RECD,
All_CONSTRAINTS, ALL_CONS_COLUMNS, ALL_TABLES, ALL_VIEWS, ALL_TAB_COMMENTS,
ALL_TAB_COLUMNS, ALL_COL_COMMENTS.

But i have two issues w.r.t. to this views:
1. For NOT NULL column for table.. Check constraint is added to All_CONSTRAINTS,
which we can get from ALL_TAB_COLUMNS.
2. And is there any way i can process Check constraints like (<colname> check
(Length(username) > 10).

Actually i am require to build a Test Data Generator Tool, for inserting test
data for the select Data Source.

I seen SQL Server 2000 also for the tool, there i have package which gives me
complete DB Schema. So just wanted to know if there is any package or procedure
before writing my own code to get DB schema.

And if you can give me any tips w.r.t. to reading DB schema, it will be very
helpful for me.

Thanks and Regards
Rajeev Soni
Followup:
check out dbms_metadata

scott@ORA920LAP> 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)
   ) 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 "USERS"

You can setup filters to restrict the verbosity and so on.  search this site for
dbms_metadata for examples and see:
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96612/d_metada.htm#1656
but i don't understand issue #1 in your list -- that seems to be "a statement",
not an issue....

for #2, you can use dynamic sql

for te docs.
Rating: 5
Primary Key Caching July 26, 2003
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
I was trying new features of 9i and found a new feature "Primary Key Caching"
but unable to get any detailed info about this feature. I hope you will give
some tips of this feature or any link of docs where I can get info about this
feature. As I am unable to find any thing.
Followup:
tell me where you saw this term in context.  I myself am not familar with it.
Rating: 5
primary key caching July 26, 2003
Reviewer: A reader
From a google search, it seems Robert Freeman's book
on "Oracle 9i New features" mentions this. However, I could
only get the "contents" :)

It seems it has something to do with FK creation - not
sure if it refers to the new feature where Oracle
does not lock the entire table even if you dont create
the FK indexes but I am not sure about this...

Menon:)

Please see
http://www.osborne.com/products/0072223855/0072223855_toc.pdf
"Chapter 6 Oracle9i SQL, PL/SQL New ....
....

Constraint Enhancements . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . 166
Locking and Nonindexed Foreign Keys . . . . . . . . . . . . . . . . . . . . 166
Primary Key Caching During FK Creation . . . . . . . . . . . . . . . . . . 1"
Rating: 5
I don't have this book July 27, 2003
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
Yes I was reading RG. Freeman New Features book. I need detail on this as I
don't have the book mentioned in the link.

Thanks
Followup:
you were reading a book you don't have?

You'll have to ask RG Freeman what he meant, I don't have the referenced book
and it is not a term I am familar with.
Rating: 3
RE: DB Schema July 28, 2003
Reviewer: Rajeev Soni from Hyderabad, India
Hi Tom,
Well i didnot put my query in proper way. Actually my requirement is to create a
automated tool "Test Data Generator", which will dynamically read the DB schema
and shows users the list of all tables and after that for the selected table(s)
application will generated no. rows specified by the user and insert them in DB.

So here while inserting data i should
1> Show all the user tables and other tables which he has been given
permission....
2> Take care of all the constraints defined on the table/column scuh as Primary
key, ref key... etc..., so w.r.t. this requirement i just got stucked at CHECK
constraint.... for processing that, i suppose i need to implement some sought of
SQL-Engine...?

I did identify all the views from where i can get the desired details. I wanted
to know weather Oracle provideds any package that will give me all informationl
such as list of tables with privileges, all columns of tables with constraints
etc., so that i dont re-write the code for queries which are already there.

Thanks and Regards
Rajeev Soni
Followup:
you just query the data dictionary -- it is all there.
Rating: 5
re: primary key caching August 18, 2003
Reviewer: A reader
From the Freeman book, it seems it is the caching of
primary key (don't know where - SGA perhaps) for
inserting of a row in child table. Since each child table
insert results in a lookup in the parent table Oracle
tries to cache the primary key value to save on time...
Rating: 4
Column Width Mismatch in Primary key and Foreign Key January 10, 2004
Reviewer: Vivek Sharma from Bombay, India
Dear Tom,

I have certain tables which has a primary key and other tables having a foreign
key referencing to these primary key.  I would like to know whether can a Data
Width Mismatch cause a Performance Problem.

Suppose, I have a primary key

t1.x varchar2(3) primary key which is referenced by t2.x of type varchar2(9)

Similaryly

t3.a number(9,2) primary key referenced by t4.a of type number(13,4).

Will this mismatch have performance impact due to data conversion. The Data
Types are same only the Width differs.

I have several tables with such mismatches.

Please Suggest.

Regards
Vivek Sharma
Followup:
read
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7640334135875
shows

a) how to discover these and other interesting facts for yourself

b) that no, the "conversion" isn't really a "conversion" at all.  a number(x,y)
is an edit to be applied at data creation/assignment time.  It affects not at
all the "compare" time since at compare time they are just binary strings in
effect that are compared for equality -- they are not compared "as numbers" but
as strings of bytes.
Rating: 4
Is is Applicable for any Datatype ? January 10, 2004
Reviewer: Vivek Sharma from Bombay, India
Dear Tom,
Thanks a ton for your response. The link you provided helped me understand that
there should be no performance Impact if the width differs. But Sir, is it
applicable for all datatypes.

i.e. varchar2(10) primary key v/s varchar2(30) foreign key and

char(10) primary key to char(30) foreign key.

Thanks and Regards
Vivek Sharma
Followup:
char(10) to char(30) won't work, but a varchar2(n) to varchar2(M) can work

ops$tkyte@ORA9IR2> create table p( x char(10) primary key );

Table created.

ops$tkyte@ORA9IR2> create table c( y char(30) references p );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into p values ( 'hello' );

1 row created.

ops$tkyte@ORA9IR2> insert into c values ( 'hello' );
insert into c values ( 'hello' )
*
ERROR at line 1:
ORA-02291: integrity constraint (OPS$TKYTE.SYS_C004049) violated - parent key
not found

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> drop table c;

Table dropped.

ops$tkyte@ORA9IR2> drop table p;

Table dropped.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create table p( x varchar2(10) primary key );

Table created.

ops$tkyte@ORA9IR2> create table c( y varchar2(30) references p );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into p values ( 'hello' );

1 row created.

ops$tkyte@ORA9IR2> insert into c values ( 'hello' );

1 row created.

but in general, one might consider it "sloppy", something to ask yourself --
why, why did I do that in the first place

Neat trick -- watch this:
ops$tkyte@ORA9IR2> create table p( x varchar2(10) primary key );

Table created.

ops$tkyte@ORA9IR2> create table c( y references p );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> desc c
 Name                    Null?    Type
 ----------------------- -------- ----------------
 Y                                VARCHAR2(10)

hmm, leave off the datatype and let it pick it up from the parent directly.
Rating: 5
User defined name for FK January 14, 2004
Reviewer: Sami from NJ,USA
<AskTom>
Neat trick -- watch this:
ops$tkyte@ORA9IR2> create table p( x varchar2(10) primary key );

Table created.

ops$tkyte@ORA9IR2> create table c( y references p );

Table created.
</AskTom>

Dear Tom,
I like this trick. Is there a way to specify the NAME for foreign key constraint
using this trick (instead of system generated name)?
Followup:
ops$tkyte@ORA9IR2> create table p ( x int primary key );

Table created.

ops$tkyte@ORA9IR2> create table c ( x constraint c_fk_to_p references p );

Table created.
Rating: 3
Number of Foreign Keys February 02, 2004
Reviewer: chet from Gainesville, FL USA
Tom,
I have been tasked with creating a small application in our database (9.2.0.4).
In my CREATE TABLE script, I have created a foreign key that references the
primary key in another schema.  This parent table is the center piece of our
entire system.

Also, there is a procedure that checks to see if the sample number exists in the
parent table, if it doesn't, then the new sample number is inserted.  So there
would never be a case where the sample number is not in the table.

My boss asked if it was necessary to have a foreign key.  I didn't really know
how to reply.

Would there be an issue of having probably hundreds of tables referencing the
parent table?
Followup:
suppose someone comes along and delete from parent table. (or updates it,
whatever)

If you have no foreign key, you now have an orphan.

If you have a foreign key, you have data integrity.

Of course you need fkeys.  If you do not, you just have bits and bytes on disk,
garbage.

Data integrity is 'sort of important'.  Enforcing data integrity is "pretty darn
hard" to do yourself.  And you'll make a mistake at some point leading to
garbage in your database.
Rating: 5
Perfect February 02, 2004
Reviewer: chet from Gainesville, FL USA
Thanks Tom.
Rating: 5
Questions about foreign keys February 02, 2004
Reviewer: A reader
Hi Tom, I have two questions about foreign keys.

1). Say table Child references table Parent and the foreign key column P_Id is
not null. Then the following two queries should return exactly same results:

a: select c.name from child c, parent p where c.p_id = p.p_id;

b: select c.name from child c;

However the first one will be slow than the second one because of the join. Why
Oracle doesn't optimize the first query to run as the second one?

2). Say if Parent table references a lookup table and I denormalize this column
into the Child table as well, using triggers. Do you recommend to create foreign
constraints on this column in the Child table.

Thanks.
Followup:
1) i suppose technically it could, but it just "doesn't".

One might ask "why did you ask for data from two tables when you only wanted
one"

2) yes, if you want correct data, yes. but - i would not denormalize unless
there was some truly -- really -- utterly compelling reason to do so.  I've been
known to say "normalization is overrated" -- but it is not "very overrated".  it
isn't something to do without a serious reason.

Joins are pretty darn efficient in rdbms's -- it is what they were born to do.
Rating: 5
re: Questions about foreign keys February 02, 2004
Reviewer: A reader
Thanks for the answer.

We are building a complex reporting system, in which for example one report can
have different combinations of parameters and it can end up with thousands of
different queries. Sometime the developer may join a lookup table without using
any columns from it (like I illustriated in example a.).

I just want to know if there is a way to "tell" the optimizer bypass the join of
the lookup table is it's not necessary for the result.

Thanks any way.
Rating: 4
Output of the script February 03, 2004
Reviewer: A Reader
The script you have written to find out the unindexed foriegn keys. It shows
four columns

1)status
2)table_name
3)columns
4)columns

What last two headings represent. Please Explain.

Regards
Followup:
columns needing to be indexed and the columns that are in fact in the index

ops$tkyte@ORA10G> create table p ( id number primary key );

Table created.

ops$tkyte@ORA10G> create table c ( id references p NOT NULL, name varchar2(20)
);

Table created.

ops$tkyte@ORA10G> create index c_idx on c(id,name);

Index created.

ops$tkyte@ORA10G> set echo off

STAT TABLE_NAME                     COLUMNS              COLUMNS
---- ------------------------------ -------------------- --------------------
ok   C                              ID                   ID, NAME

that shows the columns = ID need to be indexed and in fact is indexed by an
index with columns = ID,NAME in it.
Rating: 4
Indexed Foreign Keys February 03, 2004
Reviewer: A Reader from India
Dear Tom,

I have executed the script for checking indexed foreign keys. So I got the
following results.

As you can see for only one table status is ok. for other its not. But as you
can see the table creation script. Both are similar. What can be the reason for
this?

Thanks

STAT TABLE_NAME       COLUMNS              COLUMNS
---- ------------ -------------------- --------------------

ok   F_EQUIP_DTL  JOB_CODE, PO_SL_NO,  JOB_CODE, PO_SL_NO,
                  RFQ_NO               RFQ_NO, ITEM_SL_NO

**** GEN_ITEM_DTL JOB_CODE, PO_SL_NO,
                  RFQ_NO

CREATE TABLE GEN_ITEM_DTL
(
  JOB_CODE      VARCHAR2(4)                     NOT NULL,
  PO_SL_NO      NUMBER(4)                       NOT NULL,
  PO_REV        NUMBER(2)                       NOT NULL,
  ITEM_SL_NO    VARCHAR2(10)                    NOT NULL,
  ITEM_EDD      DATE,
  ITEM_COMP_DT  DATE,
  QTY_COMP      NUMBER(15,3),
  RAS_DATE      DATE,
  DESP_DETAIL   VARCHAR2(1000),
  RFQ_NO        NUMBER(4)                       NOT NULL,
  PROGRESS      NUMBER(3),
  ITEM_CDD      DATE,
  EFF_PO_REV    NUMBER(2),
  EFF_PR_REV    NUMBER(2),
  PR_REV        NUMBER(2),
  S_ITEM_AVAIL  VARCHAR2(3),
  S_ITEM_INSP   VARCHAR2(3)
);

ALTER TABLE GEN_ITEM_DTL ADD (
  CONSTRAINT PK_GEN_ITEM PRIMARY KEY (JOB_CODE, PO_SL_NO, ITEM_SL_NO, RFQ_NO));

ALTER TABLE GEN_ITEM_DTL ADD (
  CONSTRAINT FK_GEN_PO FOREIGN KEY (JOB_CODE, PO_SL_NO, RFQ_NO)
    REFERENCES PO_SUMMARY (JOB_CODE,PO_SL_NO,RFQ_NO)
    ON DELETE CASCADE);

CREATE TABLE F_EQUIP_DTL
(
  JOB_CODE      VARCHAR2(4)                     NOT NULL,
  PO_SL_NO      NUMBER(4)                       NOT NULL,
  PO_REV        NUMBER(2)                       NOT NULL,
  ITEM_SL_NO    VARCHAR2(10)                    NOT NULL,
  ITEM_EDD      DATE,
  ITEM_COMP_DT  DATE,
  MATERIAL      VARCHAR2(30),
  OD            NUMBER(10,2),
  THICKNESS     NUMBER(10,2),
  WEIGHT        NUMBER(11,3),
  LENGTH        NUMBER(10,2),
  QTY_COMP      NUMBER(15,3),
  RAS_DATE      DATE,
  DESP_DETAIL   VARCHAR2(1000),
  RFQ_NO        NUMBER(4)                       NOT NULL,
  PROGRESS      NUMBER(3),
  ITEM_CDD      DATE,
  EFF_PO_REV    NUMBER(2),
  EFF_PR_REV    NUMBER(2),
  PR_REV        NUMBER(2),
  S_ITEM_AVAIL  VARCHAR2(3),
  S_ITEM_INSP   VARCHAR2(3)
);

ALTER TABLE F_EQUIP_DTL ADD (
  CONSTRAINT PK_FED_ITEM PRIMARY KEY (JOB_CODE, PO_SL_NO, RFQ_NO, ITEM_SL_NO));

ALTER TABLE F_EQUIP_DTL ADD (
  CONSTRAINT FK_FED_PO FOREIGN KEY (JOB_CODE, PO_SL_NO, RFQ_NO)
    REFERENCES PO_SUMMARY (JOB_CODE,PO_SL_NO,RFQ_NO)
    ON DELETE CASCADE);
Followup:
ALTER TABLE GEN_ITEM_DTL ADD (
  CONSTRAINT PK_GEN_ITEM PRIMARY KEY (JOB_CODE, PO_SL_NO, ITEM_SL_NO, RFQ_NO));

that'll add an index on job_code, po_sl_no, item_sl_no, rfq_no in that order

ALTER TABLE GEN_ITEM_DTL ADD (
  CONSTRAINT FK_GEN_PO FOREIGN KEY (JOB_CODE, PO_SL_NO, RFQ_NO)
    REFERENCES PO_SUMMARY (JOB_CODE,PO_SL_NO,RFQ_NO)
    ON DELETE CASCADE);

and since the fkey is on job_code, po_sl_no, rfq_no -- and item_sl_no is in
the "middle" of the index -- that index is not good enough

the fkey columns must be on the LEADING EDGE of an index if you want to use it
for the fkey.
Rating: 4
can wee ad a FK initially disabled? March 17, 2004
Reviewer: A reader
Hi is it possible add foreign keys but leave it disabled because the data is not
loaded in the parent table yet?
Followup:
yup.

ops$tkyte@ORA9IR2> create table p ( x int primary key );

Table created.

ops$tkyte@ORA9IR2> create table c ( x references p disable );

Table created.

ops$tkyte@ORA9IR2> insert into c values ( 100 );

1 row created.

ops$tkyte@ORA9IR2> commit;

Commit complete.
Rating: 3
Nice March 17, 2004
Reviewer: Siva from Bangalore,India
Dear Tom,
Do you have a sample table which enforces "self referential
integrity"?Please do reply.
Bye!
Followup:
emp

alter table emp add constraint emp_pk primary key(empno);
alter table emp add constraint mgr_fk forieng key(mgr) references emp(empno);
Rating: 4
Foreign Key Using DB Link March 22, 2004
Reviewer: A reader from India
Dear Tom,

We have two Oracle Servers. In one server S1, we have a Employee Master Table.
In second server S2, we have Employee releated other informations. We want to
create Foreign Key to Employee Table in Server S1 from Server S2. Is it
possible, If yes then Please tell how.

Thanks
Followup:
it will fortunately NEVER happen.

what you have just described means you really want one single database.  to have
a fkey from one to the other would only mean that one of the servers avialablity
will be severely limited and performance would be slow.

think about what would happen if S1 suffered a media failure and needed a bit of
point in time recovery.  Or what happens to S1 when S2 is down? (S1 is
effectively "down" as well).  Ditto for S2.  the failure of either machine means
both machines are dramatically impacted.

You want a *single* server.
Rating: 3
OK March 23, 2004
Reviewer: Kumar from Bangalore,KA,India
Dear Tom,
Can Referential Integrity be specified between tables of
different schemas?
 From SYS schema WHEN I issue :

 Create table emp(deptno references scott.dept)
Is this a valid one?
Bye!
Followup:
do NOT even in a billion years use SYS for anything like this.

yes, you can use declaritive RI across schemas.

ops$tkyte@ORA9IR2> grant create session, create table to a identified by a;

Grant succeeded.

ops$tkyte@ORA9IR2> grant create session, create table to b identified by b;

Grant succeeded.

ops$tkyte@ORA9IR2> alter user a default tablespace users quota unlimited on
users;

User altered.

ops$tkyte@ORA9IR2> alter user b default tablespace users quota unlimited on
users;

User altered.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> @connect a/a
ops$tkyte@ORA9IR2> set termout off
a@ORA9IR2> REM GET afiedt.buf NOLIST
a@ORA9IR2> set termout on
a@ORA9IR2> create table t ( x int primary key );

Table created.

a@ORA9IR2> grant references on t to b;

Grant succeeded.

a@ORA9IR2>
a@ORA9IR2> @connect b/b
a@ORA9IR2> set termout off
b@ORA9IR2> REM GET afiedt.buf NOLIST
b@ORA9IR2> set termout on
b@ORA9IR2> create table t ( x references a.t );

Table created.
Rating: 3
Please see this March 26, 2004
Reviewer: Jane from NorthCarolina,USA
ear Tom,
Oracle Documentation says "A single column can be the part of more than
one foreign key".I tried this and it gets created but when we insert rows
it is recognised by the optimizer.Could you please clarify this?
I have showed here the Table creation and some sample insert statements.
Please look into that.
Bye!
SQL> create table a(x int primary key);

Table created.

SQL> create table c(x int primary key);

Table created.

SQL> create table b(x int,constraint fk_x foreign key(x) references a,constraint
fk2_x foreign
  2  key(x) references c);

Table created.

SQL> insert into a values(1);

1 row created.

SQL> insert into a values(2);

1 row created.

SQL> insert into c values(3);

1 row created.

SQL> insert into b values(0);
insert into b values(0)
*
ERROR at line 1:
ORA-02291: integrity constraint (SCOTT.FK2_X) violated - parent key not found

SQL> insert into b values(1);
insert into b values(1)  /* scott.fk_x not considered
*
ERROR at line 1:
ORA-02291: integrity constraint (SCOTT.FK2_X) violated - parent key not found

SQL>
Followup:
what did you expect?

you've basically said "b(x) must be in A and C"

the value 1 is in A
the value 1 is not in C

hence, it fails....
March 26, 2004 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)
Rating: 5
extend size in temporary tablespace (tempfile) March 17, 2004
Reviewer: reader from Russia
"you should set the extent size on your temporary tablespaces to be in line
with your SORT_AREA_SIZE setting"
Tom, if sort_area_size = 1048576 (1M)
then the uniform extent size should be 1024K (1M)
or 1024K+db_block_size(8K)=1032K ?
This formula is on Metalink:
temp extent size =(n*sort_area_size)+db_block_size
Thanks.
Followup:
just use 1m with pga_aggregate_target

if you are using sort_area_size, follow what metalink tells you.
Rating: 5
Thanks! March 26, 2004
Reviewer: Florin from The Netherlands
Hi Tom,

I had a snapshot created in dictionary managed tablespace with 100 million rows.
The performance was good.
Last week I rebuilt this snapshot in a locally managed tablespace , uniform size
and even I reduced the records number (from 100 million to 30 million). I was
sure that after these 2 major changes (LMT + reducing 2/3 of the table) I will
gain performance but I have to admit that the performance is now even worst!
Could be a problem related to LMT and snapshots?

Many thanks in advance!
Followup:
why did you assume that?
what did you make your assumptions based on?
did you identify what your performance problems WERE in the first place?

hey -- everyone knows that if you get the dings out of your car, you can
increase the efficiency and speed right (wind tunnels prove that).  so, do you
get the dings out just to decrease wind drag?  and if you do, do you go faster?
what if the problem was TRAFFIC, now what is the answer?

you probably lost and index (guess) in your "reorg".  I see as many reorgs 'fix'
things as 'don't fix a thing' as 'kill the system totally'.  That is,

o maybe you get lucky and it makes something better purely by accident
o maybe you get lucky and it affects NOTHING whatsoever
o maybe you don't get so lucky and you break stuff big time.

You must identify why your performance is not what you want it to be

And then (and only then) correct it.
March 26, 2004 -- 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.
Rating: 3
OK March 26, 2004
Reviewer: Nancy from Oklahoma,USA
Hi Tom,
Is there any way to find the length of the sql statement in
kb.Can the following one do?
 sql>select round(length(select ...from emp where ..)/1024)
      as length_sql_in_kb from dual;
Is this a correct and valid approach?
Could you please check this?
Thanks in advance.
Followup:
sure, you could just

exec dbms_output.put_line( length('...')/1024 )

as well.  (seems like a funny metric to have to collect though, curious as to
"why" it matters...)
March 26, 2004 mike -- Thanks for the question regarding "definition of user defined roles", version 8.0
You Asked
Tom,
How can we find out the definitions of the user defined roles?
ROLE_SYS_PRIVS and ROLE_TAB_PRIVS are only for Oracle defined roles, such as
DBA...

Thanks
and we said...
that's not true:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create role xxx;

Role created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> grant select on t to xxx;

Grant succeeded.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from role_tab_privs where role =
'XXX';

ROLE                           OWNER                          TABLE_NAME
            COLUMN_NAME
------------------------------ ------------------------------
------------------------------ ------------------------------
PRIVILEGE                                GRA
---------------------------------------- ---
XXX                            OPS$TKYTE                      T
SELECT                                   NO

It only provides information to ROLES which you have ACCESS to -- maybe that is
your issue?

If so, DBA_TAB_PRIVS and DBA_SYS_PRIVS can show you info about any role:

  1* select * from dba_tab_privs where grantee = 'XXX'
sys@ORA817DEV.US.ORACLE.COM> /

GRANTEE                        OWNER                          TABLE_NAME
            GRANTOR
------------------------------ ------------------------------
------------------------------ ------------------------------
PRIVILEGE                                GRA
---------------------------------------- ---
XXX                            OPS$TKYTE                      T
            OPS$TKYTE
SELECT                                   NO

Just treat the GRANTEE as a role -- which it can be....
Review & Followup

Rating: 3
Confusion September 30, 2001
Reviewer: A reader
Iam connected as scott

SQL> select * from session_privs
  2  /

PRIVILEGE
----------------------------------------
CREATE SESSION
ALTER SESSION
UNLIMITED TABLESPACE
CREATE TABLE
CREATE CLUSTER
CREATE SYNONYM
CREATE VIEW
CREATE SEQUENCE
CREATE DATABASE LINK
CREATE PROCEDURE
CREATE TRIGGER

PRIVILEGE
----------------------------------------
CREATE TYPE
CREATE OPERATOR
CREATE INDEXTYPE

14 rows selected.

SQL> select * from user_sys_privs
  2  /

USERNAME                       PRIVILEGE                                ADM
------------------------------ ---------------------------------------- ---
SCOTT                          UNLIMITED TABLESPACE                     NO

I expect to see the same results even in the second query, but I could not , can
you explain why

Actually, what I want is to check
1.what are the roles assigned to my schema,
2.and then what are the privileges assigned to my schema, 3.and then what are
the privileges assigned to  my schema which are not assigned to any role.

Further Tom

session_privs gives session privileges
v$mystat gives session statistics

what are some of the other kind of views which give specific information on the
schema and user.

Lastly,

is session_privs a table or a view, if it is a view how much it is not prefixed
with a v$, how many tables are there like session_privs which are not suffixed
with v$,and give useful information on the schema , the session and other useful
information.
Followup:
Easy -- session_privs show the privs in place in your session.  It looks like
SCOTT has the resource role which gives him the create table, create type, etc
priv.  SCOTT also has the UNLIMITED tablespace priv granted directly to him --
thats what you see in USER_SYS_PRIVS.

Consider:

scott@ORA817DEV.US.ORACLE.COM> select * from user_sys_privs;

USERNAME                       PRIVILEGE                                ADM
------------------------------ ---------------------------------------- ---
SCOTT                          ALTER SESSION                            NO
SCOTT                          ALTER TABLESPACE                         NO
SCOTT                          CREATE SNAPSHOT                          NO
SCOTT                          UNLIMITED TABLESPACE                     NO

scott@ORA817DEV.US.ORACLE.COM> @connect /
ops$tkyte@ORA817DEV.US.ORACLE.COM> grant create table to scott;

Grant succeeded.

ops$tkyte@ORA817DEV.US.ORACLE.COM> @connect scott/tiger
scott@ORA817DEV.US.ORACLE.COM> select * from user_sys_privs;

USERNAME                       PRIVILEGE                                ADM
------------------------------ ---------------------------------------- ---
SCOTT                          ALTER SESSION                            NO
SCOTT                          ALTER TABLESPACE                         NO
SCOTT                          CREATE SNAPSHOT                          NO
SCOTT                          CREATE TABLE                             NO
SCOTT                          UNLIMITED TABLESPACE                     NO

user_sys_privs shows those things granted directly to me, session_privs includes
privs from roles.

Ok, to see YOUR roles:

scott@ORA817DEV.US.ORACLE.COM> select granted_role from user_role_privs;

GRANTED_ROLE
------------------------------
PLUSTRACE
CONNECT
RESOURCE

To see YOUR privs --- user_sys_privs and user_tab_privs.

And then to see the privs assigned to your schema which are not assigned to a
role -- thats more complex.  The problem is a ROLE can have a ROLE can have a
ROLE can have a ROLE.  They are hierarchical.  If someone had the connect role
and someone granted "NEW_ROLE" to connect -- you would have to add up the privs
granted to connect and new_role.  Your best bet there might be a stored
procedure (although, if you have the priv granted directly to you --
user_sys|tab_privs -- whether you have that in a role as well or not isn't
really relevant, maybe you really don't need this bit of information anyway)

As for the last two things -- see the Oracle Reference Manual.  There are dozens
of USER_* views and V$ views, far far too many to enumerate here.  The are all
views.
Rating: 3
does not work for my case September 30, 2001
Reviewer: MIKE from Atlanta
The site DBA grant the role, RO_DBA, which is a 'read only' DBA.

SQL> select * from dba_role_privs where grantee='TOOLER';

GRANTEE                        GRANTED_ROLE                   ADM DEF
------------------------------ ------------------------------ --- ---
TOOLMAN                        RO_DBA                         NO  YES

SQL> select * from role_sys_privs where role='RO_DBA';

no rows selected

SQL> select * from role_tab_privs where role='RO_DBA';

no rows selected

SQL>

I do have the privs to read DBA views and the most of schemas' objects. But I
want to find out exactly what is RO_DBA.
Followup:
select * from dba_tab_privs where grantee = 'TOOLMAN';
select * from dba_sys_privs where grantee = 'TOOLMAN';

will tell you that.  Alternatively, log in as toolman and use the query from the
view I reference below...
Rating: 5
Why did you tell us September 30, 2001
Reviewer: TJ
Tom

Curiously, I have observed that previously you have advetised a couple of
queries to get the privileges granted to all the roles for a particular schema.

When we have something like session_privs, why do we need those queries.

If I want a list of my procedures which are running with definer rights on
underlying objects, and a list of procedures running with invoker rights on
underlying objects,  which views should I query, and how should I get them.
Followup:
I think you mean my user_role_hierarchy view:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:853427230099
That answers the question for any given user -- what are all of the possible
privs they might have.  Session privs only shows the privs in place at that
point in time (eg: in a procedure, you have a different set of rows in session
privs then when NOT in a procedure -- session privs changes as the schemaid
under which the procedure is running changes).  user_role_hierarchy shows all of
the privs for the currently logged in user regardless of the current schemaid.

Currently, this information does not seem to be exposed in the standard data
dictionary.  A view such as:

create or replace view dba_procedures
as
select owner,
       object_name name,
       object_id,
       object_type,
       created,
       last_ddl_time,
       status,
       (select decode( bitand(options,16), 16, 'INVOKER', 'DEFINER' )
          from procedure$
         where obj# = dba_objects.object_id ) auth_id
  from dba_objects
 where object_type in ( 'PROCEDURE', 'FUNCTION', 'PACKAGE', 'PACKAGE BODY' )
/

create public synonym dba_procedures for sys.dba_procedures
/

can be used in order to retrieve that however...
Rating: 3
still no work September 30, 2001
Reviewer: MIKE from atlanta
Sorry for a missleading:
The 'TOOLER' should be TOOLMAN, I changed it for did want to release the real
User schema. It is OK , i think now.

SQL> select user from dual;

USER
------------------------------
TOOLMAN

SQL> select * from dba_role_privs where grantee='TOOLMAN';

GRANTEE                        GRANTED_ROLE                   ADM DEF
------------------------------ ------------------------------ --- ---
TOOLMAN                        CONNECT                        NO  YES
TOOLMAN                        RESOURCE                       NO  YES
TOOLMAN                        RO_DBA                         NO  YES

SQL> select * from dba_tab_privs where grantee = 'TOOLMAN';

GRANTEE                        OWNER
------------------------------ ------------------------------
TABLE_NAME                     GRANTOR
------------------------------ ------------------------------
PRIVILEGE                                GRA
---------------------------------------- ---
TOOLMAN                        SYS
V_$RESOURCE_LIMIT              SYS
SELECT                                   NO

SQL> select * from dba_sys_privs where grantee = 'TOOLMAN';

GRANTEE                        PRIVILEGE                                ADM
------------------------------ ---------------------------------------- ---
TOOLMAN                        ANALYZE ANY                              NO
TOOLMAN                        RESTRICTED SESSION                       NO

SQL>

It is 8.0.6.
Just can not figure out of what the RO_DBA role is.
Followup:
select * from dba_tab_privs where grantee = 'RO_DBA';
select * from dba_sys_privs where grantee = 'RO_DBA';
Rating: 5
September 30, 2001
Reviewer: mike from Atlanta
These worked.
Do not know the reason for no found in
ROLE_SYS/TAB_PRIVS.
Rating: 5
Grants March 26, 2004
Reviewer: B from India
Hi Tom,
In our application there are two schemas: APPL and APPL_VIEW. All the custom
objects are owned by APPL. And synonyms have been created on the
APPL.objects(tables/views) for APPL_VIEW, so that users logging to APPL_VIEW can
select data from the tables/views.

Now there is a requirement where access to certain tables/views will have to be
removed from  APPL_VIEW. To achieve this the respective synonyms were dropped.
But even after dropping the synonym we are able to access the table/view in
APPL_VIEW by qualifying the owner (user)name APPL to the objects in question.

APPL_VIEW has the following privileges: CREATE SESSION, ALTER SESSION, CREATE
TABLE, SELECT ANY TABLE and EXECUTE ANY PROCEDURE.

No direct grants have been given on these objects by APPL to APPL_VIEW. But
APPL_VIEW has been granted a role, appl_role and this role has has been given
select privilege on the objects by APPL. Tried revoking the privilege from the
role but still we are able to access the table/view by qualifying APPL from
APPL_VIEW.

Request your guidance on why this happening and what needs to be done to set
this right.

Thanks,
B
Followup:
select any table, execute any procedure = "i have no security whatsoever on my
system, I can do anything at all I want to any object pretty much"

amazing....

that user needs no direct grants, they are almost a DBA!  they can see all, they
can run all.

select ANY table <<<=== think about what 'any' might mean in that context!
Rating: 5
RE: Select ANY Table March 26, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
select ANY table might be bad, but "EXECUTE ANY PROCEDURE" hands the keys to the
kingdom right over...

- Mark
Rating: 5
Select Any Table March 29, 2004
Reviewer: B from India
Hi Tom,
Thanks for your clarification.

Regards,
B
March 26, 2004 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.
Rating: 5
March 24, 2004
Reviewer: A reader
Hi Tom I was reading articles like
http://www.dbazine.com/jlewis18.shtml
As I understand the best you can get to set optimizer_index_cost_adj and caching
values
are ratios to estimate them.
Could you please giveme the ones for 9i,
and any other advice to set that parameters,please

Or the only thing you have are starting ratios and testing around them.

Thanks :)
Followup:
I think those two have two, maybe three, settings.

way to the right (defaults)
way to the left
right in the middle

you can play with ratios but I think you'll find

0,100 (default)
90,10 (way to the left)
50,50 (maybe, hypothesis)

are what you might be looking for.  something as fine grained as "82,17"... I
don't think I'd spend the time.
Rating: 5
March 24, 2004
Reviewer: A reader
Thanks Tom,
the point is the following
in your new book (effective oracle by design ) I found using system statistics,
but what I want is to get an average, my system is a small oltp and dds, so I
can take statistics from one process only.
I supposed ratios could giveme an average value to set both parameter.
I had been reading several articles and there is not a perfect value, but I was
wondering if there could be a way to find the average values for all the process
in the day.
Or a way to gather system statics, several hours

Thanks Tom again
Followup:
statspack gives you averages over all processes.

but I'll just say "to the left for oltp perhaps" and "to the right for dss
perhaps" and you can use alter sessions to let each have their own way (eg:
write a "startup" package that you call and have people call startup.oltp or
startup.dss after connecting for example..)
Rating: 5
How to set values using statpacks? March 25, 2004
Reviewer: A reader
Thanks Toms, can you please help me with this, once I got statpacks reports,
based on which parameter and using which formula I give a value to both
parameters
OPTIMIZER_INDEX_COST_ADJ  =
OPTIMIZER_INDEX_CACHING   =

:) Thanks Tom.

After reading a lot about this, this is my conclusion, do you agree?

the conclusion seems to be gather statics from every distinct process or group
of process, and set manually this values in every one of them.
If this process is by time, for example night and day, you can use alter
system parameter,
if this is only for ap articular process you will have to set alter session.

And set the value.
This seems to be a hard work.
Followup:
sorry -- as i said -- i see two, maybe three settings regardless.

way to the left
way to the right

based on the type of application (which you already know)...  the end.
Rating: 5
March 25, 2004
Reviewer: A reader
Tom something other people had been saying me is
do not set both of them parameters.
What do you think about this
For example

----- Original Message -----
From: "Cary Millsap" <cary.millsap@hotsos.com>
To: "'Juan Cachito Reyes Pacheco'" <jreyes@dazasoftware.com>
Cc: "'Jared Still'" <jkstill@cybcon.com>
Sent: Thursday, March 25, 2004 11:41 AM
Subject: RE: how to set optimizer_index_cost_adj and cache parameters?

> I agree with what I believe Jared has said. The correct procedure is not
> to mess with the parameters. Adjusting the parameters is a futile
> attempt to respond to the symptoms. If you have query optimization
> problems, address them in the more productive way, by fixing your SQL,
> ensuring that you have representative statistics, etc.
>
>
> Cary Millsap
> Hotsos Enterprises, Ltd.
>
http://www.hotsos.com/
> * Nullius in verba *
Followup:
I have not seen the entire thread so cannot comment out of context (it might be
considered "rude" to post private emails, don't you think?)
Rating: 5
March 26, 2004
Reviewer: A reader
Hi Tom, I agree with you, to copy private emails are "rude", but they are not
private emails, and all the informatoin posted here is the same you can find in
oracle-l list if you subscribe there.

You can search here:
http://www.freelists.org/archives/oracle-l/03-2004/index.html
I started two thread trying to find a definitive answer and as I couldn't get
it, I decided to ask to you.

What they say briefly is that "don't touch them", and they seems to be right
based in the fact that several dbas had and have correct execution plans without
changing that values, but any way you can to see the thread you can search here.

Please Tom, I had been investigating long time to find a definitive ansewr about
the use of both parameters,
I understand your are not god neither know all the answers but could you please
give your position about the use of both parameters.

There are two opinions
"Don't use this parameters, unless you see you need"
"Set them accordingly to the kind of processing you do"
Followup:
haven't I already done that?

way to the left
way to the right

done in TEST and DEV (not just thrown out on prod).
Rating: 5
March 26, 2004
Reviewer: A reader
Thanks
March 26, 2004 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://download-west.oracle.com/docs/cd/B10501_01/server.920/a96524/c12parti.htm#461507
goes into the "max value" partition

list:
http://download-west.oracle.com/docs/cd/B10501_01/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://download-west.oracle.com/docs/cd/B10501_01/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
Rating: 5
range partitioning indexes January 12, 2004
Reviewer: A reader
Hi

We want to partition some of our tables and indexes. We are going to hash
partition our tables since the data distribution is not very uniform, i.e no
logical range.

However we want to GLOBAL partition our indexes because this is a OLTP database.
Many of indexes have no logical range neither. My question if I want to use
logical ranges I will end up having partitions of different size, for example

account_no between 1 and 100000 I will have 400000 rows
account_no between 100001 and 200000 I will have 100000 rows

and so on

Does this affect performance?

I have another way to distribute the data uniformly using range partition (using
the NTILE function) however my range will be very "DODGY"

something like this

--------------------------------------------------------------------------------
        NT    MIN(ID)    MAX(ID)   COUNT(*) MAX(ID)-MIN(ID)
---------- ---------- ---------- ---------- ---------------
         1          1     175108     984025          175107
         2     175108     310148     984025          135040
         3     310148     445173     984025          135025
         4     445173     586530     984025          141357
         5     586530     773611     984025          187081
         6     773611    1004361     984025          230750
         7    1004362    1220448     984025          216086
         8    1220449    1533474     984025          313025
         9    1533474    1814891     984025          281417
        10    1814891    2081213     984025          266322
        11    2081214    2317213     984025          235999
        12    2317213    2549731     984025          232518
        13    2549731    2771108     984025          221377
        14    2771108    2973580     984024          202472
        15    2973580    3259862     984024          286282
--------------------------------------------------------------------------------

To distribute the number of rows uniformly across partitions I have to use
min(id) and max(id) as ranges... As you can see number of rows is uniform
however range not...

Which way is better? Or none of them is feasible?
Followup:
they do not have to be "even".  it'll not really affect performance.

what is your goal -- why are you partitioning the index.  is it "huge".

if this is an increasing number (like a sequence) -- you'll be constantly
splitting and adding partitions at the end.
Rating: 5
we have heavy contention problems January 12, 2004
Reviewer: A reader
Hi

Our goal is to aliviate contention problems. Our database resources are heavily
contended. Very high number of buffer busy waits and cache buffer chain latches
(these two accounts 80% of total waits, wait time is as high as CPU time). We
have rebuilt the tables and indexes and increased freelists and initrans with no
improvement.

The indexes are roughly 100mb ~ 400mb in size. The root blocks are heavily
contended that's why we wish to partition them.

I was reading the administrator guide and it says to achieve best performance
make sure the data is distributed in range partitions, that's why I thought we
need to have even partitions.
Followup:
buffer busy/cache buffer chains waits can happen for lots of reasons - it could
be all about reads.

Have you ascertained that it is INSERT contention here -- and not READ
contention?
Rating: 5
partitioning problem February 18, 2004
Reviewer: SR
Tom,

We have a table T(SEQ NUMBER, C1 VARCHAR2(1), C2 VARCHAR2(1), V NUMBER). Its not
partitioned now. However we have huge buffer busy wait problem (p3=130). It seem
to arise from the fact multiple daemons are trying to read blocks of T, using
T_SI index (C1,C2). However T_PK is SEQ. It seems like range scan on T_SI is
working correctly, however rows r1 belongs to Daemon1, r2 belongs to Daemon2 and
they are on the same block. Another problem is after they are done with the
processing, C1 and C2 become null. Thus partitioning on C1,C2 won't work either.
Only thing I can think of hash partition on SEQ and hope the rows will get
dispersed (I think bbw may still occur but much lesser?).

Do you have any suggestion? T_PK is primary for insert, its not used in lookups
as such.

Thanks in advance
Followup:
how do you divvy up the rows?  little more detail please.
Rating: 3
9i Table Partitioning February 18, 2004
Reviewer: Denise from Virignia,USA
Tom

I'm in the the process of transporting some of our
8.1.7 tables to the new db I created on a testserver
that runs Oracle 9i.

One of the tables(prospect)has approx 3mil records.
Those records are basically split into two separate
clients(ACLJ,ACGA)which is stored in the usercode1 column.

I'm planning to create a hash partitioned table
on the 9i database and I have a threefold question:

1) can I import a nonpartitioned table from 8.1.7
   into a hash partitioned table on 9i.
   Or would I have to do an insert statement?

2) can the 2 partitions be stored within the same tablespace?
   some of the examples indicate separate tablespaces for each partition.

3) do I have to hash other tables that are referenced
   from this hash partitioned table..such as the GIFT
   and ADDRESS tables?

thanks
Followup:
1) yes, just create the new table and use ignore=yes on the import

2) sure, they can by -- but it limits the availability features of partitioning.

3) no.
Rating: 2
partitioning issue February 19, 2004
Reviewer: SR
Hi Tom,
insert is done by various clients. There is one-to-one relation between a row
and a daemons (they don't stomp on each other and they don't enqueue either)
Select is done using C1=:1 and C2=:2. Once done with the work they are set to
null. While reading your 1-on-1 book, you mention enabling row movement, we can
use partitioning approach. I am kind of interested in that. only thing is almost
each and every row will get twice inserted and once deleted. potentially deleted
as it gets purged. I am not sure I understood your question on divvying rows.

Thanks again,
Followup:
are you sure your bbw's are on the read -- how did you determine that -- how
many concurrent "inserts" do you have and are you using ASSM or manual segment
space mgt.  If doing it manually -- what do you have set for freelists and
freelist groups for this set of segments (table, indexes).

why do you believe partitioning will help?

just looking for information.

the divvy up is -- how do you assign rows to the consumers of this data, the
guys "selecting" data out.
Rating: 5
bitmap index February 19, 2004
Reviewer: Shankar from Houston, TX
Tom, in 9i R2 can we create bitmap Global index? Thanks.
Followup:
no
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96524/c12parti.htm#459872
Rating: 3
local index partition. March 24, 2004
Reviewer: A reader
Hi tom,

 Can you Please tell me from which data-dictionary view
I can find out that the index partition is local or global ?
I tried user_ind_partitions but i can't figure out this partition is local or
global ? (if it is not global why there is that key word LOCAL ?)

CREATE INDEX IDX_005 ON t
(ENTITY_ID,CURRENCY)
  TABLESPACE FDATA01
  INITRANS   2
  MAXTRANS   255
  STORAGE    (
              INITIAL          320K
              NEXT             320K
              MINEXTENTS       1
              MAXEXTENTS       1024
              PCTINCREASE      0
             )
NOLOGGING
LOCAL (
  PARTITION Y2000
    NOLOGGING
    TABLESPACE FDATA01
    PCTFREE    0
    INITRANS   2
    MAXTRANS   255
    STORAGE    (
                INITIAL          20M
                NEXT             15M
                MINEXTENTS       1
                MAXEXTENTS       1024
                PCTINCREASE      0
                FREELISTS        1
                FREELIST GROUPS  1
                BUFFER_POOL      DEFAULT
               ),
  PARTITION Y2001Q1
    NOLOGGING
    TABLESPACE FDATA01
    PCTFREE    10
    INITRANS   2
    MAXTRANS   255
    STORAGE    (
                INITIAL          1280K
                NEXT             1280K
                MINEXTENTS       1
                MAXEXTENTS       1024
                PCTINCREASE      0
                FREELISTS        1
                FREELIST GROUPS  1
                BUFFER_POOL      DEFAULT
               )
)
PARALLEL ( DEGREE DEFAULT INSTANCES DEFAULT );
Followup:
select index_name, locality from user_part_indexes
Rating: 3
Thanks !! March 24, 2004
Reviewer: A reader
Rating: 3
how about function based index ? March 24, 2004
Reviewer: A reader
if I query the user_ind_coumns for function based index
it gives me alpha numeric #
how can I get the actual column name and property like
asc or desc. (what is the table name where I can find this.)

CREATE INDEX T_IDX_008 ON T
(ID1, "ENTRY_DATE" DESC, ID3)

and like

CREATE INDEX T_IDX_0030 ON T
("FADD"("ID","ID2"))

Thanks,
Followup:
ops$tkyte@ORA10G> desc user_ind_expressions
 Name                                 Null?    Type
 ------------------------------------ -------- -------------------------
 INDEX_NAME                                    VARCHAR2(30)
 TABLE_NAME                                    VARCHAR2(30)
 COLUMN_EXPRESSION                             LONG
 COLUMN_POSITION                               NUMBER
Rating: 3
thanks tom. March 25, 2004
Reviewer: A reader
Rating: 3
how do I convert ? March 25, 2004
Reviewer: A reader
how can I convert long to varchar or varchar to long ?

select decode(column_expression,null,column_name,column_expression)
from user_ind_expressions uie,user_ind_columns uic
where uic.index_name = uie.index_name (+)
and index_name = 'T_IDX_0030'

select decode(column_expression,null,column_name,column_expression)
              *
ERROR at line 1:
ORA-00932: inconsistent datatypes
Followup:
You cannot, they are "not the same" and "not convertable"

see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:55212348054
Rating: 3
thanks, March 26, 2004
Reviewer: A reader
hi tom.

  so Oracle developer should not use LONG then why does oracle use it ? and why
does oracle put the value in that column ?. From the above question the need of
conversion from long to varchar2 is fair I think ? how does oracle displays the
"column name" of the function based index ? writing a whole PL/SQL routine ?
shouldn't it be done in single query ?(i mean it is just a simle outer join) I
tried case stmt too it a same error....
Followup:
because if we go back and change fundemental things like that, in views that
have been around longer than I've been typing code into a terminal, "bad things"
could happen.

you can use dbms_metadata to see what you need as well.
March 26, 2004 -- Thanks for the question regarding "statspack buffer gets aq$_queue_tables 50.3%Total", version 9.2.0.1.0
You Asked
why i have so high waiting for aq$_queue_table, aq$_queue?

following is statspack report:
----------------------------------------------------------
DB Name         DB Id    Instance     Inst Num Release     Cluster Host
------------ ----------- ------------ -------- ----------- ------- ------------
ORCL          1047768090 orcl                1 9.2.0.1.0   NO      PRIMARY

            Snap Id     Snap Time      Sessions Curs/Sess Comment
            ------- ------------------ -------- --------- -------------------
Begin Snap:       4 22-03 -2004 13:00:05      70       2.5

  End Snap:       5 22-03 -2004 14:00:04      75       2.9

   Elapsed:                               59.98 (mins)
----------------------------------------------------------
SQL ordered by Gets for DB: ORCL  Instance: orcl  Snaps: 4 -5
-> 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
--------------- ------------ -------------- ------ -------- --------- ----------
         47,108          113          416.9   50.3     0.23      1.45  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

         17,463        2,486            7.0   18.6     0.06      0.42 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

         11,276            1       11,276.0   12.0     0.00      2.10 2301846564
DECLARE job BINARY_INTEGER := :job; next_date DATE := :mydate;
broken BOOLEAN := FALSE; BEGIN statspack.snap; :mydate := next_d
ate; IF broken THEN :b := 1; ELSE :b := 0; END IF; END;

         10,633            1       10,633.0   11.3     0.11      0.11 4059808258
Module: SQL*Plus
INSERT into stats$sqltext                  ( hash_value
         , text_subset                  , piece
 , sql_text                  , address                  , comman
d_type                  , last_snap_id                  )
      select st1.hash_value                  , ss.text_subset

          1,868           22           84.9    2.0     0.39      0.38 3935265464
SELECT DISTINCT marc_rec_no FROM  marc_idx WHERE (marc_idx_grp =
:p1 AND  marc_idx_cont Like :p2)

          1,792          597            3.0    1.9     0.20      0.14 3416568850
SELECT DISTINCT ASORD_MARC_REC_NO,ASORD_MARC01,ASORD_MARC02,ASOR
D_MARC03
    FROM ASORD_MARC_DATA ASORD
    WHERE ASORD.ASORD_MA
RC_REC_NO = :"SYS_B_0"
    ORDER BY ASORD_MARC_REC_NO

          1,791          597            3.0    1.9     0.09      0.10 3400090404
 SELECT MARC_TYPE
    FROM ASORD_MARC
    WHERE ASORD_MARC_REC_N
O= :"SYS_B_0"
    AND  ROWNUM<:"SYS_B_1"

          1,638          123           13.3    1.7     0.03      0.02 3861970592
INSERT INTO lend_lst (cert_id,   prop_no,   lend_date,   norm_re
t_date,   rule_no_f,   resp_wkr,   location_f,   attachment)   V
ALUES (:b8,   :b7,   :b6,   :b5,   :b4,   :b3,   :b2,   :b1)

          1,406          702            2.0    1.5     0.00      0.09 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
and we said...
I don't see any waits in there.  you've chopped out that part.

tell me -- are you experiencing a performance issue?
have you tied it back to this?

Look at the report -- 1hour long.

Now, 1.25 SECONDS spent executing your longest query..... hmmm.  I think I see a
totally idle system that isn't doing any work here.

In order to tune you need

a) a system doing something
b) an identified problem application (or two, or three or whatever)
c) a root cause identified

then and only then can you find a solution.  I see nothing wrong here. (well,
other than you are using cursor_sharing which implies you have a bug in the
developed code -- it is not using bind variables -- but thats another story :)

If you are not using AQ, you can "turn it off" pretty easily.  Won't do very
much for your performance, but it'll remove that query from the report.
March 26, 2004 Kevin -- Thanks for the question regarding "May i uses Windows DLL in external procedure?", version 9i
You Asked
I am work in windows platform.I want to use WinSocket in my Oeacle Procedure to
communicate to a Socket Server When some Data is inserted to my database,my
question is whether I Can use a windows dll in 'C' function as external
procedure in oracle.
and we said...
yes, that is what an external procedure is all about.  If you have my book
"Expert one on one Oracle" -- i spend about 70 pages on extprocs, if not, it is
fully documented (and available on otn.oracle.com)
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96595/dci04lng.htm#1004625
You might well find you can do all you need with UTL_TCP and avoid writing lots
of low level code.
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96612/u_tcp.htm#998101
in that fashion, you would retain your portability as well, instead of being
tied to the windows platform exclusively.
March 26, 2004 -- Thanks for the question regarding "How to predict the Explain Plan ?", version 9.2.0.4.0
You Asked
Hi Tom,

We are in development phase for a J2EE based application with Oracle as the
database, that is going to be live soon (this will be the first deployment).

We are currently looking into tuning of the sqls that are being used by the
application.

My problem is that how do i know or make sure that the explain plan that the
sqls are showing right now will be same when the application goes live.

What i actually wanted to ask was that we are having different explain plans for
sqls running in different development environments.

For example say a sql shows that it costs more if it uses index and i make it do
a full table scan. Now when I run the same on another environment have the same
data it costs more if it runs a full table scan. Of course that is not the case
for all sqls but still such things raise doubts.

We are little confused here as we are not sure how much this tuning process will
help if things behave differently on production. What if we tune our SQLs based
on plan shown on development databases and the same eplan is totally different
for production environment.

We have tried to simulate the production as much as possible by loading 50-60%
of the expected data (meaningful and not junk) in production.

How do we predict what the explain plan will be and how to approach in such a
scenario. AS we know that the hardware configuration will be more powerful and
much different in prod. Also the physical database layout will be different.

I hope I'm able to convey my concerns appropriately

Thanks.
and we said...
Actually you don't and they might not be -- and if there were, that could be
really bad.

What you need to do is test your application to scale, with the requisite number
of simulated users against the requisite size and scale of data.

Else, go live day will be your most exciting day ever.

If you can look at a query plan and tell me 100% of the time "that is the best
plan" or "this would be a better plan" -- we have a job for you!

Explain plans are not excessively useful as a predicator of performance, they
are useful as a diagnostic tool to help you figure out what is going on -- but
not as a predictor of future performance.
Review & Followup

Rating: 5
Oracle job March 26, 2004
Reviewer: Marcio from Brazil
<quote>
If you can look at a query plan and tell me 100% of the time "that is the best
plan" or "this would be a better plan" -- we have a job for you!
</quote>

:) Hey! I can :) You said "if you can". I can, surely it won't be right, but I
CAN TELL YOU, anyone can :)
I want my job. Well, you could offer me any job you want, even office boy ;)

Sorry, just kidding
Rating: 5
Doubts Cleared March 29, 2004
Reviewer: A reader
Thanks Tom...
That gives some clear view on how to approach further.

Regards
March 26, 2004 arturo -- Thanks for the question regarding "log file sync for cache storage", version 8.1.7
You Asked
Could  IO wait event log file sync reduce response time for OLTP databases?

I'm tunning a Oracle DB 8.1.7 . I'm checking the statspack report and I find the
follow Top 5 Events:

                        Waits    Wait Time cs    %Total Wt Time
db file sequential read    6,390,209    1,595,144    36.44
log file sync              440,103    1,580,288    36.1
db file scattered read      875,488    370,944            8.47
latch free              805,760    332,173            7.59
log file parallel write      272,341    207,004            4.73

The IO are distribuited in the follow schema:

- Mounted on HPUX - VxFS - XP-1024 Raid 1
- The online redo logs are complete independent: diferent disksgroups, diferent
ASP, etc
- sar -d shows low %busy, 0.5 avgwait, 2 avgque, 2 svct -> No problem

These are my questions:

- Could  IO wait event log file sync reduce response time for OLTP databases
according to top 5 wit events?
- Is good idea to mount online redo logs on raw devices with this type of
storage?

I will apreciate any help.
and we said...
"Could  IO wait event log file sync reduce response time for OLTP databases?"

Absolutely -- that is the wait event generated when you say "commit".  you are
not committed until the log files are synced and the client waits patiently for
that response.

You've neglected to mention how LONG the stats pack was for so those numbers
might as well be "55" or "44" or "521523523" - they are all equally meaningful
in that context!

You need to step back from the statspack and ask yourself some questions:

a) are my applications running at or above the speed they need to

b) if not, have I prioritized the order in which I need to fix them (forget
tuning a "system", not going to happen unless you are really lucky)

c) once I have, have I traced them to see what their low hanging fruit is (what
if you discover your OLTP apps are not waiting significantly on log file syncs,
but rather SOMETHING else -- something that is not showing up on the 'system's'
radar screen?)

It is a good idea to identify that you have a problem, where the problem is,
what the problem is and then figure out how to fix it.  There are no "silver
type bullets".

Yes, putting logs onto dedicated devices, using directio mount options or raw,
can decrease the log file syncs (as the redo writes will be faster) but unless
it is "a problem", it is "not worth your energy"
March 26, 2004 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.
Rating: 3
Different CHUNK size March 14, 2004
Reviewer: A reader from NJ, USA
Tom,

In our application we have some tables that utilize BLOB (LOB) columns to store
data. I would like to know the following :
1. What is CHUNK size in LOB declaration
2. Our database has a CHUNK size set to 32K. Our DBAs mentioned that the table
was growing very rapidly because of the 32K chunk size BLOB column in that and
they reorganized the table's BLOB column to 8k CHUNK size and they also stored
the BLOB in a separate tablespace of it's own. They also mentioned that the
CHUNK size is also a reason why the table could grow exponentially large. My
understanding however is that when a BLOB column is stored, only a BLOB locator
(which is a pointer to the BLOB) will be stored in the table. In that case how
will the chunk size make any difference? Please clarify
3. You mention that PCTVERSION is the percentage of space used for LOB
read-consistency.  Does that mean LOBs does not use RBS for read-consistency.
Also, in one of your discussions you locked the table explicitly when a lob
column was updated. Any reason why the table was locked explicitly?
Followup:
have you read:
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96591/toc.htm
1)
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96591/adl07mds.htm#117572
chunk is how the blob is stored as well -- if you use a 32k chunk -- the blob
will allocate 32k of database blocks at a time.  So, if you store 10k, you'll
have 32k.  if you store 15k, you'll have 32k.  if you store 33k, you'll have 64k
and so on.

2) the blob is stored (by default) INLINE upto about 4000 bytes.  Then the lob
locator would be used to point to space OUTSIDE of the table (so no, a lob wont
cause a table to have "exponential" growth, whatever that is)...

3) correct, that is what it means.

do you have a pointer to that Q&A, if you do, I can tell you why I did it in
that context.  I searched for

"lock table" blob

"lock table" clob

and could not find it and it is not ringing any bells for me...
Rating: 3
Please clarify my understanding March 14, 2004
Reviewer: A reader from NJ, USA
Tom,

In response to the question 1 above, you mention that CHUNK size specifies
the way space is allocated for a LOB.
Please confirm my understanding :
1. If my database block size is 8k and my chunk size is 32K
   it will allocate 4 database blocks each time. Is that
   correct? It is the same case as a table storage
   parameter declarations where a table will allocate
   INITIAL_EXTENT bytes even if the data stored in the
   table is less than INITIAL_EXTENT.
2. If my understanding to the above question is correct,
   why do books insist about carefully declaring the CHUNK
   size to avoid wastage of space and how different is it
   from the INITIAL_EXTENT,NEXT_EXTENT table storage
   parameters (because space will be wasted anyway when
   the data in the table occupies less than
   INITIAL_EXTENT,NEXT_EXTENT and Oracle will allocate
   that much number of bytes anyway)
3. In the above discussion you mention
   "the blob is stored (by default) INLINE upto about 4000 bytes.  Then the lob
   locator would be used to point to space OUTSIDE of the table (so no, a lob
wont
   cause a table to have "exponential" growth, whatever that is)..."
   Does that mean that declaring a CHUNK size of 8k or 32K
   will not cause the table to grow exponentially because only a LOB
   locator will be stored in the table segment and the actual LOB
   segment will be stored outside of the table. Is my understanding
   correct?
4. Please refer to the discussion about
http://asktom.oracle.com/pls/ask/f?p=4950:8:14788130159327906654::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:378418239571,
   for the discussion titled
   "lob tablespace -- moving a lob"

Thanks much
Followup:
1) yes.

2) the chunk belongs to A LOB instance.  not all lobs, a single lob.  a 32k
chunk means that the lob in a single row will take 32k all to itself, regardless
of how big it is.  chunks belong to a single lob

3) by default, the blob will be stored IN the table (enable storage in row) when
the blob is less than 4000 bytes.  When the blob exceeds 4000 bytes, it'll be
moved out of line (disable storage in row) and pointed to.

Chunk size has nothing to do with this behaviour.  It just means that for lobs
<= 4000 bytes, they are IN the table, for lobs > 4000 bytes they are pointed to
and stored outside of the table.  In neither case would they cause the table to
"explode"

4) uh huh.  I was not updating a lob, we were MOVING EVERY SINGLE ONE of them.
In order to ensure "fastest success", so as to not have to wait for individual
row locks in a multi-user environment, I locked the table in anticipation of
"having to lock every row in the table".  I wanted to prevent INSERTS, DELETES
and the like which would get in the way.  This was a one time and one time only
move.  It made sense to lock the table.
Rating: 4
LOB chunk size March 26, 2004
Reviewer: Vikas Bagga from Atlanta,GA
I have a table with LOBS. The DBMS_LOB.GETLENGTH tells me my LOBS are about
1175111 bytes in size. My CHUNK size is set to 8192.
What should I be setting my Chunk size. I will be having 3 millions plus records
with 4 LOBS in the table.
Also what queries/views will be helpful to track my LOBS.

Thanks
Followup:
have you read through the application developers guide for large objects?

the chunk size is the smallest unit of allocation.  If you use an Nk chunk, each
INSTANCE (row/column) of a lob will take at least Nk (when not null) and will
take M*Nk always (multiples of Nk)....

probably with averages is, it is hard to say.  If 50% of your LOBS are really
small and 50% are really big -- it could be that a large chunk size wastes tons
of space 50% of the time.

for 3million records with 4 lobs and 1m per lob, that would be about 11terabytes
of LOB data (3,000,000*4*1m = 12,000,000m = 11,718gig = 11.44 terabytes)

that'll be "large", you'll need to include partitioning for manageability
reasons in this design from the get go I would think.
March 26, 2004 Logo -- Thanks for the question regarding " Difference between Transaction READ-ONLY and TRANSACTION ISOLATION LEVEL SERIALIZABLE", version 8,1.6
You Asked
Tom,

What is the difference between SET TRANSACTION READ ONLY and SET TRANSACTION
ISOLATION LEVEL SERIALIZABLE? As for as my understanding goes, both of them
don't see any committed changes from other sessions during the duration of the
transaction.

Thanks,
Logo Palanisamy
and we said...
one can update.

the other cannot.

set transaction read only predates serializable by a couple of years -- if they
had come out at the same time, we probably would not have read only as
serializable is a superset of read only.
Review & Followup

Rating: 5
Transaction Read only and isolation level serializable June 01, 2001
Reviewer: Logo Palanisamy from Santa Clara, CA
Well said.
Rating: 4
set transaction read write March 26, 2004
Reviewer: michael from USA
Tom,

I understand the need for set transaction read only; however, I do not
understand the need for set transaction read write.

If the transaction is not committed due to error, will the entire transaction
ROLLBACK?

Can you please give me an example of a situation where SET TRANSACTION READ
WRITE is necessary and not necessary?

Thank you.
Followup:
it is our way of "begin transaction" if you well.  It is part of the standard
SQL.

you don't ever really need to use it.  I've never used it.
March 26, 2004 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!!
Rating: 5
Excellent Answers January 15, 2004
Reviewer: Jean from Caracas, Venezuela
This kind of topics helps us to know a lot of the secrets behind Oracle. I've
never imagine that DUAL, beeing such a little thing, can make so many people to
talk about it.
Rating: 5
Are autotrace stats on SYS.DUAL for real? February 09, 2004
Reviewer: Doug from Cincinnati, OH USA
By default, BEA's WebLogic application server (6.1 sp1) uses the query

select count(*) from dual

to test a JDBC connection before making it available to users of its connection
pool.  Unfortunately, it does this A LOT... like before and after every
statement!  I'll leave the "wisdom" of this strategy as a discussion for another
day.  In our OLTP application, the above query has accounted for at least 74
million buffer gets (33% of the total!) since the last instance restart a couple
of weeks ago.

Execs       Buffer gets  % of total  Disk reads  Gets/exe  Disk/exe
74,792,695  373,813,512  33.35       21,757      5.00      0.00
select count(*) from dual

By making an index organized table as suggested in this thread like so:

SQL> create table dual_iot(dummy varchar2(1) primary key) organization index;
SQL> insert into dual_iot values('X');
SQL> analyze table dual_iot compute statistics;

I can bring the number of buffer gets per execution down from 3 to 1:

SQL> set autot on stat
SQL> select count(*) from dual;

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          3  consistent gets
          0  physical reads
          0  redo size
        491  bytes sent via SQL*Net to client
        651  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 count(*) from dual_iot;

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          1  consistent gets
          0  physical reads
          0  redo size
        491  bytes sent via SQL*Net to client
        651  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 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 IBM/AIX RISC System/6000: Version 9.2.0.3.0 - Production
NLSRTL Version 9.2.0.3.0 - Production

My question... is the savings of two consistent gets here a "real" savings?  In
other words, is there something magic going on with SYS.DUAL that makes the
autotrace stats misleading?  Or did I just become a hero by saving the database
a whole lot of unnecessary buffer gets (and latches, and cpu, and ...)?
Followup:
I can get it to zero LIO's (10g does this sort of "magic" out of the box btw)

But yes, this can reduce your LIO's and more:

ops$tkyte@ORA920PC> /*
DOC>
DOC>create table dual_iot ( dummy primary key ) organization index
DOC>as
DOC>select * from dual;
DOC>analyze table dual_iot compute statistics
DOC>/
DOC>
DOC>@connect "/ as sysdba"
DOC>
DOC>create or replace view dual_view
DOC>as
DOC>select dummy from x$dual;
DOC>
DOC>grant select on dual_view to public
DOC>/
DOC>
DOC>create public synonym dual_view for dual_view
DOC>/
DOC>
DOC>@connect /
DOC>*/
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> exec runStats_pkg.rs_start

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> declare
  2     l_cnt number;
  3  begin
  4     for i in 1 .. 50000
  5     loop
  6          select count(*) into l_cnt from dual;
  7     end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> exec runStats_pkg.rs_middle

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> declare
  2     l_cnt number;
  3  begin
  4     for i in 1 .. 50000
  5     loop
  6          select count(*) into l_cnt from dual_iot;
  7     end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> exec runStats_pkg.rs_stop(10000)
Run1 ran in 966 hsecs
Run2 ran in 721 hsecs
run 1 ran in 133.98% of the time

faster by the wallclock and more importantly:

Name                                  Run1        Run2        Diff
STAT...buffer is not pinned co      50,000           0     -50,000
STAT...index scans kdiixs1               0      50,000      50,000
STAT...no work - consistent re      50,000           0     -50,000
STAT...table scan blocks gotte      50,000           0     -50,000
STAT...table scans (short tabl      50,000           0     -50,000
STAT...table scan rows gotten       50,000           0     -50,000
STAT...shared hash latch upgra           0      50,000      50,000
STAT...session uga memory           65,464           0     -65,464
STAT...session uga memory max       65,464           0     -65,464
STAT...session pga memory           74,048           0     -74,048
STAT...consistent gets             150,002      50,002    -100,000
STAT...calls to get snapshot s     150,002      50,001    -100,001
STAT...session logical reads       150,540      50,536    -100,004
LATCH.cache buffers chains         302,633     102,637    -199,996

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
553,916     353,767    -200,149    156.58%

PL/SQL procedure successfully completed.

latches are way down, now compare IOT to "view"

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> exec runStats_pkg.rs_start

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> declare
  2     l_cnt number;
  3  begin
  4     for i in 1 .. 50000
  5     loop
  6          select count(*) into l_cnt from dual_iot;
  7     end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> exec runStats_pkg.rs_middle

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> declare
  2     l_cnt number;
  3  begin
  4     for i in 1 .. 50000
  5     loop
  6          select count(*) into l_cnt from dual_view;
  7     end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> exec runStats_pkg.rs_stop(10000)
Run1 ran in 788 hsecs
Run2 ran in 493 hsecs
run 1 ran in 159.84% of the time

wall clock faster still and...

Name                                  Run1        Run2        Diff
STAT...session logical reads        50,523         531     -49,992
STAT...consistent gets              50,003           5     -49,998
STAT...index scans kdiixs1          50,000           0     -50,000
STAT...shared hash latch upgra      50,000           0     -50,000
LATCH.cache buffers chains         102,609       2,592    -100,017

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
353,890     253,740    -100,150    139.47%

PL/SQL procedure successfully completed.

and even less latches...

BUT -- changing DUAL like that globally = "potentially dangerous" and
"definitely not something support would like to hear"

Best if you can set up a private synonym "dual" in the schema(s) you want to
effect -- as few as possible.

If you use the IOT -- all queries involving DUAL will go CBO (if they are not
already)

If you use the x$dual -- well, it's an X$ table and might have other
unanticipate side effects I haven't thought about yet.

If you do it - make sure you can back it out.
Rating: 3
Nice March 03, 2004
Reviewer: James Stephen from CT,USA
Hi Tom,
I have a table as
 sql>select * from t;
  name
------
 Joe
 Jack
Can this table be joined with the DUAL table to produce
four rows?
Followup:
sort of, but we have to make dual return 2 rows first which we can do...

but -- what do you really need to do here?  if you need two rows for each one,
you must join to a table with 2 rows in it (cartesian product)
Rating: 3
Unsatisfied March 04, 2004
Reviewer: James Stephen from CT,USA
Dear TOM,
Mr.Chuck Weiss,Creator of DUAL table said the following info.in one of the
Oracle Mag.Issues"For every row in your
table ,DUAL Table is capable of producing a pair of rows from it using
join".That's why I asked this question.Can it be done?
Followup:
He is wrong or you are mis-quoting him.

Dual is nothing special really, just a one row, one column table.

It cannot be done with a join -- maybe a UNION ALL, but not a join. (group by
cube can be used as well but that still isn't a join).

well, hold on :)

in 9i I can do it as a join -- anyone want to guess HOW (clue: it is really a
UNION ALL in disguise but it is a join, clue2: in "9i")

There you go -- make a game of it.
Rating: 3
A join March 04, 2004
Reviewer: vc
You can

select * from dual a full join dual b on a.dummy='a'

-- result:

DUMMY    DUMMY_1
X
    X

VC
Followup:
:)
Rating: 5
here March 04, 2004
Reviewer: Mac
SQL> select t.*
  2  from t,(select*from dual union all select* from dual);

NAME
----------
Joe
Joe
Jack
Jack

4 rows selected.
Followup:
that uses a union -- not just a join.  the full outer join is the right answer
above...
Rating: 5
Exellent March 14, 2004
Reviewer: Shahinur Rahman from Bangladesh
Really very helpfull as usual.
Rating: 4
Weekly challenge March 26, 2004
Reviewer: Dave Thompson from West Yorkshire, England
Hi Tom,

Whatever happened to the idea of a weekly challenge?

Setting everyone a tricky little SQL problem based on the new stuff like
analytics would be a good way for us all to learn.

Regards,

Dave.
Followup:
too little time, too much to do :)
Rating: 3
Fair Enough! March 26, 2004
Reviewer: A reader
No problem, I often wonder how you fit all this stuff into 24 hours!

Cheers.

Dave.
Rating: 2
The name ! March 26, 2004
Reviewer: DaPi from Geneva - CH
I can't resist:   WHY was it called DUAL when the number of rows and columns are
SINGULAR?
Followup:
predates me by many many years.  no idea.

dual does have many definitions in math and science as well, might be geek humor
for all i know.
Rating: 5
RE: The Name? March 26, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
Well, there are two of them... maybe that had an influence?  Maybe Bruce Scott
would know?

- Mark
Rating: 4
The name March 26, 2004
Reviewer: A reader
It seems chuck weiss is right :
http://otn.oracle.com/oramag/oracle/02-jan/o12sendmail.html
He says that he created a two rows table and called it dual because he wanted to
duplicate easily one table.
Since this table has lost one row but kept its name.
Rating: 5
Dual Table March 26, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
Well, there you go... Bob's your uncle and all that...

- Mark
March 26, 2004 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 .
Rating: 5
found dead multi-threaded server March 26, 2004
Reviewer: Shailesh from USA
Hi Tom,
We randomly received error "ORA-03113: end-of-file on communication channel" in
the client application.
We checked the log file on Database server and found the following error "found
dead multi-threaded server 'S000', pid = (8, 141)" at the same time when we
received the error on client application.
Please help,
Shailesh
Followup:
this would be a support issue.  normally, you would find a message in the alert
log and a trace file (that support can use) would be generated...
March 26, 2004 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...
Rating: 5
Is there a really improvement between this two counts March 26, 2004
Reviewer: A reader
Hi Tom recently I got one idea to use rownum in the count, using it I got two
distinct
execution plans.

My question is if one of this querys is better than the other.

SQL> select count(*) from (select /*+ first_rows(1)*/ 'a' from iva where rownum
=1);

 COUNT(*)
---------
        1

 real: 110

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=396 Card=1
          )

   1    0   SORT (AGGREGATE)
   2    1     VIEW (Cost=396 Card=1)
   3    2       COUNT (STOPKEY)
   4    3         VIEW OF 'IVA' (Cost=396 Card=1723479)
   5    4           UNION-ALL
   6    5             INDEX (FAST FULL SCAN) OF 'IDX_IVA_CODCLI_CTACOR
          R_RW' (NON-UNIQUE) (Cost=150 Card=763231)

   7    5             INDEX (FAST FULL SCAN) OF 'IDX_IVA_FECHA' (NON-U
          NIQUE) (Cost=246 Card=960248)

Statistics
----------------------------------------------------------
         12  recursive calls
          0  db block gets
         30  consistent gets
          0  physical reads
          0  redo size
        246  bytes sent via SQL*Net to client
        417  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 rownum from iva where rownum=1;

   ROWNUM
---------
        1

 real: 110

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=396 Card=1)
   1    0   COUNT (STOPKEY)
   2    1     VIEW OF 'IVA' (Cost=396 Card=1723479)
   3    2       UNION-ALL
   4    3         INDEX (FAST FULL SCAN) OF 'IDX_IVA_CODCLI_CTACORR_RW
          ' (NON-UNIQUE) (Cost=150 Card=763231)

   5    3         INDEX (FAST FULL SCAN) OF 'IDX_IVA_FECHA' (NON-UNIQU
          E) (Cost=246 Card=960248)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         26  consistent gets
          0  physical reads
          0  redo size
        244  bytes sent via SQL*Net to client
        417  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed
Followup:
they are not really that significantly different -- where do you see the major
differences?

I see one did a probable hard parse (the recursive sql) which could account for
the extra LIO's but beyond that they are basically the same

(and well, i sort of would expect different queries could result in different
plans in general... they are in fact very very different queries after all)
Rating: 5
March 27, 2004
Reviewer: A reader
Thanks
March 26, 2004 Kjell -- Thanks for the question regarding "Binary Conversion to float ", version 8.1.6
You Asked
I’m passing binary data in to a PL/SQL procedure (PL/SQL long raw datatype).
The procedure clip out 4 or 8 bytes with UTL_RAW.SUBSTRING.

The binary representation is accordance with IEEE 754 float.

I want to convert 4 bytes to float and 8 bytes to double float.
The float shall be returned in Oracle number datatype.

Can this be done in PL/SQL?
If I want to use Java stored procedures to solve this,
how do I write the Java procedure?

Mor details:
The machine the server is running on use IEEE 754 float.

The spec for the storage of the IEE 754 float is found here:
http://research.microsoft.com/~hollasch/cgindex/coding/ieeefloat.html
As you see I’m not familiar with Java or C programming.

I want to pass the binary representation of the float which in pl/sql is stored
in raw(4)or raw(8).

Which data types shall be used when passing and returning data?

Here is sketch of what I try to do with Java stored procedures:

Create or replace JAVA SOURCE NAMED ‘’BinToFloat’’ AS
Public class BinToFLOAT{
Public static ???? BinToFloat( ){
?
?
?
Return ????;

}
};
/

--wrapper function

create or replace function BinToFloat(?? in ??) Return NUMBER
as LANGUAGE JAVA NAME ‘bintofloat’()return number’;
/

Thanks for any help
and we said...
(Thanks to Mark Piermarini for the Java bit here....)

Ok, here we go.  first I generated a test set of data in C:

#include "stdio.h"

void main()
{
float x = 1.123;
double y = 12121;
FILE * output;

    output = fopen( "test.dat", "wb" );
    fwrite( &x, 1, sizeof(x), output );
    fwrite( &y, 1, sizeof(y), output );
    fclose(output );
}

then, i loaded that up in the database:

create or replace directory my_files as '/export/home/tkyte'
/

drop table t;

create table t ( x blob );

declare
    l_bfile bfile;
    l_blob blob;
begin
    l_bfile := bfilename( 'MY_FILES', 'test.dat' );
    dbms_lob.fileopen( l_bfile );

    insert into t values ( empty_blob() ) returning x into l_blob;

    dbms_lob.loadfromfile( l_blob, l_bfile, dbms_lob.getlength(l_bfile) );
    dbms_lob.fileclose( l_bfile );
end;
/

and then:

ops$tkyte@DEV816> create or replace and compile java source
  2  named "RawIEEE" as
  3  import oracle.sql.*;
  4
  5  public class RawIEEE {
  6    static public NUMBER rawFloatToNUMBER(RAW r) throws Exception {
  7      return new NUMBER(
  8                    Float.intBitsToFloat(
  9                          Integer.parseInt(r.stringValue(), 16)
 10                                        )
 11                       );
 12    }
 13
 14    static public NUMBER rawDoubleToNUMBER(RAW r) throws Exception {
 15      return new NUMBER(
 16                    Double.longBitsToDouble(
 17                        Long.parseLong(r.stringValue(), 16)
 18                                           )
 19                       );
 20    }
 21  }
 22  /

Java created.

ops$tkyte@DEV816> show errors
No errors.
ops$tkyte@DEV816>
ops$tkyte@DEV816> create or replace function
  2  RAW_FLOAT_TO_NUMBER(p_raw in RAW) return NUMBER
  3  as language java
  4  name 'RawIEEE.rawFloatToNUMBER(oracle.sql.RAW) return
  5        oracle.sql.NUMBER';
  6  /

Function created.

ops$tkyte@DEV816>
ops$tkyte@DEV816> create or replace function
  2  RAW_DOUBLE_TO_NUMBER(p_raw in RAW) return NUMBER
  3  as language java
  4  name 'RawIEEE.rawDoubleToNUMBER(oracle.sql.RAW) return
  5        oracle.sql.NUMBER';
  6  /

Function created.

And now I can simply:

ops$tkyte@DEV816> column rfloat format a8
ops$tkyte@DEV816> column rdouble format a16
ops$tkyte@DEV816>
ops$tkyte@DEV816> select dbms_lob.substr( x, 4, 1 ) rfloat,
  2         RAW_FLOAT_TO_NUMBER(dbms_lob.substr( x, 4 , 1)) xfloat,
  3         dbms_lob.substr( x, 8, 5 ) rdouble,
  4         RAW_DOUBLE_TO_NUMBER(dbms_lob.substr( x, 8, 5)) xdouble
  5    from t
  6  /

RFLOAT       XFLOAT RDOUBLE             XDOUBLE
-------- ---------- ---------------- ----------
3F8FBE77      1.123 40C7AC8000000000      12121
Review & Followup

Rating: 5
RAW_FLOAT_TO_NUMBER June 01, 2001
Reviewer: Kjell Lie from Norway -Karm?y
This is exactly what I was looking for.
The Java part is implemented and tested with success.

(The Raw input to Oracle is coming from Programmable Logical Controller’s via a
OPC client.)

Thanks very much
 Kjell Lie
Rating: 3
Binary Conversion to float January 28, 2004
Reviewer: Evgeniy from Russia
Excuse me for mine bad English.
What if negative?  < 0 ?

raw(4)='C014DD4C'

  i=-1072374452, f=-2.326007
Followup:
you'd have to figure out the java to accomidate that -- you have the start, now
you just need to find a java programmer :)
Rating: 5
1 March 26, 2004
Reviewer: astrid from hungary
1
Followup:
I think it is 55 actually
March 27, 2004 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://download-east.oracle.com/docs/cd/A81042_01/DOC/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.
Rating: 5
Pros & Cons March 27, 2004
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
I would like to know the Pros and Cons of using views and the situations where
we must use views.

Like what is the advantage of view like,

create view myview as select * from emp;
I mean what are the reasons that we are not using select * from emp and creating
view on it?
Followup:
if you wanted to be able to "rename the columns" without renaming the columns...

if you wanted to "reorder the columns" without physically reordering the
columns...

if you wanted to split emp into two physical tables in the future (but not tell
anyone)....

basically views are a way to protect applications from physical schema changes
-- consider it a "translation layer"

I use views like subroutines.  When a query gets too large/complicated to be in
the code -- I would consider "viewing it"
March 27, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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.
Rating: 5
Can I load the CSV data output in my db without using sqlldr March 26, 2004
Reviewer: Subodh Deshpande from India
Hi Tom,

1) How to load this data generated in CSV file to the Oracle Database, I have
heard about External Tables in 9i (and if i mistake not it also uses sqlldr..)
how do it in 8.1.7,

2)I want to develop an inerface like form etc..
I want to provide two menu items one to generate and one to load the data which
is CSV format.

thanx in advance...subodh
Followup:
1) sqlldr using a control file like this (change | to , or whatever)

LOAD DATA
INFILE *
INTO TABLE emp
REPLACE
FIELDS TERMINATED BY '|'
(
empno
,ename
,job
,mgr
,hiredate
,sal
,comm
,deptno
)
BEGINDATA
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

2) ok, go for it?
Rating: 4
Can I load the CSV data output in my db without using sqlldr March 27, 2004
Reviewer: Subodh Deshpande from India
Hi Tom,

Sorry to bother you.. probably this may be one of the leangiest question you
have ever seen, and I think I couldn't place my doubt properly, as I am confused
in usuing the proper tool of oracle in my following case..

1) we are constrainted to use replication so at different locations our
databases are different without any logical structural change, and so we are
forced to use of csv files whenever we are required to send the data urgently to
any of the locations..

2) In Client Server Environment using 8.1.7 as db, A db Client needs to be
installed if we want to load the data using sqlldr from client end, what we
should do in web based architecture ?

3)Whether its Client Server or Web based, if am supposed to use sqlldr, it means

3.1) the user will directly interact with database, defeating the security of
database as end users have tendency to inform the password to others, when some
on is going on leave..etc..

3.2) a IT/system person will have to do the this job, i will have to dedicate a
person for this, from my team, as every dept will need to send urgent data to
other loaction.

3.3) what we are thinking is when our end user wishes to send certain
transaction among of his/her( say out of 50
custmer orders booked, he want to sent selected 5 of which say despatches are
held), an interface will generate the csvs for those, the user will mail from
his desktop at the desired location, at the desired location, another end user
will save his csv at his/her end, and will use recevied cvs file as input in
interface provided, the interface will load the data in db of that locations

4) so am looking for a suitable solution which will avoid use of sqlldr and
sending and receiving of data to certain location will be available to the end
users just as one of the attribute of that transaction, and so i require your
gudence,on whether can it be a workable, feasable solution or how oracle
technology can help me in such complex matters

5) please point out my each and every mistake freely and sorry for such a long
question..

have a nice weekend and excellent start of new week -:)

thanx in advance...Subodh
Followup:
why would not an administrator on the DB server itself be responsible for
loading up this important stuff?

3.1 is sort of laughable -- why does the "web" remove the need for passwords?

You can load data using ANY 3gl you want in your middle tier.  C can do it,
PLSQL in the server can do it, VB can even do it, java can do it.  You can write
code to do virtually *anything* you want.
Rating: 4
Can I load the CSV data output in my db without using sqlldr March 28, 2004
Reviewer: Subodh Deshpande from India
Hi Tom,

We want to this just to reduce the dependability on systems dept, yes what you
are saying in 3.1 ,i got it, web is not going to remove the need of passwords.

thanx a lot -:)..subodh
March 27, 2004 Surya -- Thanks for the question regarding "9i -- materialized views, analytics", version 9.2
You Asked
Tom,

thanks for this wonderful site!!!

i have few questions

if I create a materialized view on a query.

1. can I directly select from that materialized view as i select from a table

2.what are the new enhancements in materialized views in 9i?-- would be of great
help(if an example is given)

3.what are the new enhancements in analytics in 9i -- would be of great help(if
an example is given)

4. what are new OLAP functions in 9i?
and we said...
1) yes.

2) documented

3) see above

4) see above

we have a new features guide:
http://otn.oracle.com/pls/db92/db92.docindex?remark=homepage#index-NEW
as well, the data warehousing guide as a "whats new in DW"
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96520/whatsnew.htm#967650
Review & Followup

Rating: 2
How to Auto-refresh a Materialized View March 26, 2004
Reviewer: Ram
I have written a materialized view which actually contains of 23 tables. I have
built a 2 materialized view with around 10 tables each and on top of it will be
the master materialized view which we will be using in our code. It is taking
around 30 seconds to refresh manually. I am searching for autorefresh,  . can
you give some idea about this.
Because within this 30 seconds if anyone updates the table and want to see the
result immediately it may take sometime for him and within these 30 seconds if
another person updates some other table, how can we do that these two changes
can be seen immedialtely after refresh. Is there any possibility for this
Followup:
do you mean "refresh on commit"?

doubt you want to go there (it will slow down the commits)... but - what design
decision forced you down the path of having so many MV's -- perhaps you are
trying to fix a problem with string, gum and band-aids when the real solution
might lie elsewhere?
Rating: 1
It is not what you expected March 26, 2004
Reviewer: Ram
Becuase all these tables do not have primary key index. They  were designed long
back before 5- 10 years.
SO ordinary view cannot do anything, It takes more time.
Of course if it refreshes it commits. I know that. But how to make the
autorefresh easy.
Followup:
what is easier than "dbms_mview.refresh()"?
Rating: 1
I know that but it is taking 30 seconds to refresh using that script March 26, 2004
Reviewer: Ram
Is there any way to improve the performance that it refreshes faster than 30
seconds. Because within these 30 seconds if any request comes from another user,
he has to wait until this gets refreshed.
Followup:
well, 23 mv's, with mv's of mv's and lots of tables -- well, it is going to take
"time" to refresh.

any refresh -- not matter how you invoke it -- will take the same path there.

You are trying to fix a problem with string and gum here.  You have a bad model,
that you cannot query and you are trying to replicate it into a schema that you
can (query).

Other than normal "tuning" (eg: enable sql trace and refresh and see if there is
anything "obvious"), it is going to take a while.
Rating: 3
OK March 26, 2004
Reviewer: Ben from Vancouver,Canada
Hi Tom,
Please see the following queries.
SQL> select mgr,(select ename from emp where empno = mgr) as mgr_ename,count(*)
from emp
  2  group by mgr;

       MGR MGR_ENAME    COUNT(*)
---------- ---------- ----------
      7566                     2
      7698                     5
      7782                     1
      7788                     1
      7839                     3
      7902                     1
                               1

7 rows selected.
I tried to put this query using Analytics.It comes as:

  1* select distinct mgr,ename,count(*) over(partition by mgr) as cnt_subs from
emp
SQL> /

       MGR ENAME        CNT_SUBS
---------- ---------- ----------
      7566 FORD                2
      7566 SCOTT               2
      7698 ALLEN               5
      7698 JAMES               5
      7698 MARTIN              5
      7698 TURNER              5
      7698 WARD                5
      7782 MILLER              1
      7788 ADAMS               1
      7839 BLAKE               3
      7839 CLARK               3

       MGR ENAME        CNT_SUBS
---------- ---------- ----------
      7839 JONES               3
      7902 SMITH               1
           KING                1

14 rows selected.
Where do I err?Could you please correct this?
Bye!
Followup:
no errors.

you are just comparing apples to toaster ovens.

you are comparing aggregates (apples)
to
analytics (toaster ovens)

they do TWO totally different things!
Rating: 3
OK March 26, 2004
Reviewer: Ben from Vancouver,Canada
Sorry for previous post since it contains errors.
This is the first one and second query is used to implement
this answerset.
SQL> select mgr,(select ename from emp e1 where e1.empno = e2.mgr) as
mgr_ename,count(*) from
  2  emp e2 group by mgr;

       MGR MGR_ENAME    COUNT(*)
---------- ---------- ----------
      7566 JONES               2
      7698 BLAKE               5
      7782 CLARK               1
      7788 SCOTT               1
      7839 KING                3
      7902 FORD                1
                               1

7 rows selected.
Followup:
they are totally different queries.

tell me -- what was the "question", then I can tell you which was appropriate.

If the question was "show me the count of employees managed by each person and
the name of the manager" -- that is query one.

If the question is "show me each employee, the name of their manager, and the
number of people managed by that person" -- that is query two.

apples and toaster ovens.
Rating: 1
Not 23 mv's only 2 mv's March 26, 2004
Reviewer: Ram
Not 23 mview's . We have 23 tables and two MV's are built on these tables. So
tell me the solution if you have . It is harder than gum, it is like gel with
which gum is prepared. I know in our company even Great DBA's of 20 yrs exp also
could not do anything with this.
SO if you can give appropriate solution it is ok.
Followup:
an mv of 23 tables isn't going to "be fast".

no ideas for you other then -- well, you already know.  Fix the problem, not the
symptons.
Rating: 3
OK March 27, 2004
Reviewer: Ben from Vancouver,Canada
Dear Sir,
Any other ways exists to put this query?

SQL> select mgr,(select ename from emp e1 where e1.empno = e2.mgr) as
mgr_ename,count(*) from
  2  emp e2 group by mgr;
Please specify any alternatives if you have any?
Bye!
Followup:
there are an infinite number of ways to rewrite that query.

It is as good as any -- go for it.
March 27, 2004 DERMOT -- Thanks for the question regarding "CALLING FUNCTIONS FROM VB", version 8.1.5
You Asked
Tom, not sure if this the right area to ask you this!
Trying to call a function from VB (6.0 SP4) to return values using ado.

Any ideas or sample code would be helpful

Thanks.
and we said...
Instead of returning a value as an OUT or an IN/OUT parameter, this sample uses
a Stored Function to return a numeric value. The key is the use of the
adParamReturnValue parameter. (Last test Aug 2000 using VB 6 SP3 and ODBC
version 8.1.6.1.0)

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

--****************************Start PL/SQL****************************
CREATE OR REPLACE PACKAGE Calc AS
   Function Add5 (num in number) RETURN NUMBER;
END Calc;
/

CREATE OR REPLACE PACKAGE BODY Calc AS
   Function Add5 (num in number) RETURN NUMBER IS
      Begin
      Return (num + 5);
   End Add5;
END Calc;
/
--****************************End PL/SQL****************************

'****************************Start VB Code****************************
   Dim con As New ADODB.Connection
   Dim cmd As New ADODB.Command
   Dim InputParam As New ADODB.Parameter
   Dim ReturnParam As New ADODB.Parameter

   con.ConnectionString = "DSN=ODBC8; UID=scott; PWD=tiger;"
   con.Open

   cmd.ActiveConnection = con
   cmd.CommandText = "Calc.Add5"
   cmd.CommandType = adCmdStoredProc
   Set InputParam = cmd.CreateParameter("Prm1", adSmallInt, adParamInput, , 30)
   'Using adParamOutPut instead of adParamReturnValue will result in the
   'following error:
   'ORA-24334 - no descriptor for this position
   'Set Prm2 = cmd.CreateParameter("Prm2", adSmallInt, adParamOutput)
   Set ReturnParam = cmd.CreateParameter("Prm2", adSmallInt, adParamReturnValue)

   'You will also get the ORA-24334 error if you don't Append the parameters
   'in the correct order. Make sure to bind the Returning parameter first.
   cmd.Parameters.Append ReturnParam
   cmd.Parameters.Append InputParam

   cmd.Execute
   MsgBox "Input Value = " & cmd.Parameters(1)
   MsgBox "Return Value = " & cmd.Parameters(0)
'****************************End VB Code****************************
Review & Followup

Rating: 2
Error even when no parameter is passed October 04, 2001
Reviewer: Pranav from CT, USA
I have a function that goes like:
create or replace function TestRecordset
return types.cursorType
AS
  l_cursor  types.cursorType;
begin
  open l_cursor for select * from table1 where rownum <= 5;
  return l_cursor;
end;

The type is created earlier in this manner:
create or replace package types
as
    type cursorType is ref cursor;
end;

I am trying to use ADO To return the recordset but I get the

[Oracle][ODBC][Ora]ORA-24334: no descriptor for this position

error.

the way the code is executed on the asp side is like
Get the connection(objConn) and now set the Command.

Set cmdStoredProc = Server.CreateObject ("ADODB.Command")
Set cmdStoredProc.ActiveConnection = objConn
cmdStoredProc.CommandText = "TESTRECORDSET"
cmdStoredProc.CommandType = adCmdStoredProc

Set rs1 = cmdStoredProc.Execute

I get the error on cmdStoredProc.Execute
Can you explain why??
Rating: 5
ADODB and Ref Cursor parameters: the last piece in the puzzle June 30, 2003
Reviewer: Nigel Heffernan from London, England
I've been trying, for far too long, to get ref cursors back from Oracle and into
a VBA client. You can do it using the Oracle InProc server, but that does't
support asynchronous fetching. You can try doing it using ADO, but I suspect
that most developers give up. Here's why:

The ADODB .CreateParameter method only allows a very limited set of return
types, making it almost impossible to get a cursor back into the client
application.

.CreateParameter("myCursor", adVarChar, adParamOutput)

Try it - even if the parameter creation method succeeds, you'll get an 'invalid
SQL' error when the ADO Fetch is completed.

The last piece in the puzzle was the 'adParamReturnValue' constant in this
example.

.CreateParameter("myCursor", adNumeric, adParamReturnValue)

Every other code sample I've seen uses adParamOutput and I have yet to see a
working example. You'll still have to play with the type - adVarChar doesn't
work everywhere - but at least it compiles, runs, and returns data on an 8.06
client.

Here's a sample (not the full listing, which is full of statistical stuff) which
gives a simple example:

/*PL SQL FRAGMENT **************/

CREATE OR REPLACE PACKAGE HISTVOLSTATS AS

TYPE Recordset IS REF CURSOR;

PROCEDURE GetRecordset
(
    StartDate    IN NUMBER,
    EndDate      IN NUMBER,
    lngStockID   IN NUMBER,
    myRecordset IN OUT Recordset
);

END HistVolStats;

CREATE OR REPLACE PACKAGE BODY HISTVOLSTATS AS

PROCEDURE GetRecordset
(
    StartDate   IN NUMBER,
    EndDate     IN NUMBER,
    lngStockID  IN NUMBER,
    myRecordset IN OUT Recordset
)
IS
   BEGIN

   OPEN myRecordset FOR
        SELECT
            tblPrice.*
        FROM
            tblPrice
        WHERE
            tblPrice.TradingDay  > StartDate
        AND tblPrice.TradingDay <= EndDate
        AND tblPrice.StockID     = lngStockID

        ORDER BY
            tblPrice.TradingDay DESC;

    END;

END HistVolStats;

'VBA Fragment *******************

lngStart = CLng(StartDate)
lngEnd = CLng(EndDate)
lngStockID = LookupStockID(Ticker)

Set rst = New ADODB.Recordset
Set cmd = New ADODB.Command

With cmd

    .ActiveConnection = connLocal

    .CommandText = "HistVolStats.GetRecordset"
    .Prepared = False

    .CommandType = adCmdStoredProc

    .Parameters.Append .CreateParameter( _
                            "StartDate", _
                             adInteger, _
                             adParamInput _
                             , , lngStart)

    .Parameters.Append .CreateParameter( _
                            "EndDate", _
                             adInteger, _
                             adParamInput _
                             , , lngEnd)

    .Parameters.Append .CreateParameter( _
                            "lngStockID", _
                             adInteger, _
                             adParamInput _
                             , , lngStockID)

    .Parameters.Append .CreateParameter( _
                            "myRecordset", _
                             adNumeric, _
                             adParamReturnValue)

    Set rst = .Execute(, , adAsyncFetchNonBlocking)

End With 'cmd

Set cmd = Nothing
Rating: 2
a little bit March 27, 2004
Reviewer: aditya from India
ya its helped me a lil bit ..my problem is a similar one..but here in my proc
which is using a ref cursor..is working finely in database and when calling from
vb its giving error in special way(typical microsoft way vb6.exe
the instruction at "0x286a87cf" referenced memory at "0x00000047" the meory
could not be read..the same vb code is working for original database and its
giviong errors for replica databse only that  too only when calling procs with
ref cursors...can u help me
Followup:
nope -- i fortunately have never actually seen such an error message.  Linux is
like that.

Contact support for assistance on this (if you are using the Oracle ODBC
drivers, if not, contact the vendor that supports your ODBC drivers and make
sure to have a teeny tiny, concise yet 100% complete reproducible test case for
them to work with!)
March 27, 2004 -- 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.
Rating: 5
similar is not peeking! February 22, 2004
Reviewer: reader
I have created employees table with two columns (ename and empid) with 100020
rows of which 20 rows will contain the value "ORACLE" for employee name. I have
created histogram on employee column and set the session cursor_sharing =
similar. However, when I used the bind variable the optimizer does not see it
and does the FTS. However, without bind variable if I specified where ename =
'ORACLE', it is doing the right ting in using the index. I thought similar
setting would peek at the bind variable value and use the appropriate execution
plan. Could you clarify this please? Thanks.

SQL> analyze table employees compute statistics;

Table analyzed.

SQL> analyze table employees compute statistics for columns ename size 10;

Table analyzed.

SQL> variable b varchar2(20);

SQL> show parameter cursor_sharing

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
cursor_sharing                       string      EXACT

SQL> alter session set cursor_sharing=similar
  2  ;

Session altered.

SQL> begin
  2  :b := 'ORACLE';
  3  end;
  4  /

PL/SQL procedure successfully completed.

SQL> print b

B
--------------------------------
ORACLE

SQL> set autotrace traceonly explain
SQL> select * from employees where ename = :b; <.............. not peeking so
does the FTS

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=39 Card=50010 Bytes=
          500100)

   1    0   TABLE ACCESS (FULL) OF 'EMPLOYEES' (Cost=39 Card=50010 Byt
          es=500100)

SQL> select * from employees where ename = 'ORACLE'; <----------- uses the
histogram info and does the right thing

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=20 Bytes=200)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMPLOYEES' (Cost=2 Card=
          20 Bytes=200)

   2    1     INDEX (RANGE SCAN) OF 'EMPLOYEES_IDX' (NON-UNIQUE) (Cost
          =1 Card=20)
Followup:
autotrace lies, autotrace does not have bind information available to it --
heck, autotrace doesn't even know if :b is a date, varchar2, number, whatever -
it just assumes "varchar"

You have to use tkprof for this stuff.  autotrace, just uses explain plan, and
explain plan is "blind" when it comes to binds.
Rating: 5
similar ... February 22, 2004
Reviewer: reader
Thanks. I used tkprof and the output is as follows. Execution plan says FTS but
row source operation says index scan. Which one optimizer used?

select *
from
 employees where ename = :b

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.00       0.00          0          8          0          20
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      0.00       0.00          0          8          0          20

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 5  (SYSTEM)

Rows     Row Source Operation
-------  ---------------------------------------------------
     20  TABLE ACCESS BY INDEX ROWID EMPLOYEES
     20   INDEX RANGE SCAN EMPLOYEES_IDX (object id 31029)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
     20   TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'EMPLOYEES'
Followup:
don't use explain=u/p -- it is really slow and "not accurate".  you don't need
it.

the first plan is the real plan.  It is output by the server itself as it used
it.
Rating: 5
SIMILAR not peeking cont..... February 22, 2004
Reviewer: reader
Tom, please see below.

To get 100000 (see (1) below) rows or 20 (see (2) below) rows it used the index
eventhough I have created histogram on the ename column. CURSOR_SHARING for the
Session was set to use SIMILAR option in both cases. My conclusion is that
histogram information is not used by optimizer if bind variable is used. If I
hard coded the value in where clause like where ename = 'ORACLE', ename =
'SMITH' it uses different execution plan as you can see from the copy and paste
above in this thread as it used the index to get 20 rows containing 'ORACLE' and
FTS for 100000 rows containing 'SMITH'.

Is it right to assume that histogram is needed only if I hard code the value in
the where clause? Thanks for your time.

(1) output from trace file

..............

alter session set events '10046 trace name context forever, level 12'
END OF STMT

..............

select * from employees where ename = :b
END OF STMT
PARSE #1:c=0,e=108,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=12626921145
BINDS #1:
 bind 0: dty=1 mxl=32(20) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=32
offset=0
   bfp=0745c638 bln=32 avl=05 flg=05
   value="SMITH"

.......< I did not paste all of the info from raw trace file here>

......

STAT #1 id=1 cnt=100000 pid=0 pos=1 obj=31027 op='TABLE ACCESS BY INDEX ROWID
EMPLOYEES '
STAT #1 id=2 cnt=100000 pid=1 pos=1 obj=31029 op='INDEX RANGE SCAN EMPLOYEES_IDX
'

alter session set events '10046 trace name context off'
END OF STMT

(2) output from trace file

alter session set events '10046 trace name context forever, level 12'
END OF STMT

..............

select * from employees where ename = :b
END OF STMT
PARSE #1:c=0,e=111,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=12163982758
BINDS #1:
 bind 0: dty=1 mxl=32(20) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=32
offset=0
   bfp=0728c638 bln=32 avl=10 flg=05
   value="ORACLE"

.......

STAT #1 id=1 cnt=20 pid=0 pos=1 obj=31027 op='TABLE ACCESS BY INDEX ROWID
EMPLOYEES '
STAT #1 id=2 cnt=20 pid=1 pos=1 obj=31029 op='INDEX RANGE SCAN EMPLOYEES_IDX '

alter session set events '10046 trace name context off'
END OF STMT
Followup:
the histogram was used the first time the query was hard parsed with the bind.
from then on it, it would be "fixed".  search for peeking on this site and
you'll see many examples of that.
Rating: 5
THANKS. Very helpful February 22, 2004
Reviewer: Reader
Finally I have understood about peeking. Thanks much.
Rating: 3
does cursor_sharing similar works? March 27, 2004
Reviewer: GB
Hi Tom,

Regarding cursor_sharing similar, according to my understanding of your
explanations found here on the site, option "similar" should work like this
(your words):

"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."

So, I tried to play with this and check what are the results.

create table t_all_obj as select owner, object_name, object_id from all_objects;

update t_all_obj set owner = 'VLADE DIVAC';

update t_all_obj set owner = 'DIVAC' where rownum = 100;

create index t_all_obj_i on t_all_obj(owner);

analyze table T_ALL_OBJ compute statistics
    for table
    for all indexes
    for all indexed columns

alter session set cursor_sharing = exact;

select * from t_all_obj where owner = 'VLADE DIVAC';

-- as a exec. plan I got a full table scan

select * from t_all_obj where owner = 'DIVAC';

-- as a exec. plan I got index scan

-- and in shared pool I have to statements:
select sql_text from v$sql
where sql_text like '%t_all_obj%';
--------------------------------------
select * from t_all_obj where owner = 'VLADE DIVAC'
select * from t_all_obj where owner = 'DIVAC'

-- THIS IS OK!!!

---------------------------------------
alter system flush shared_pool;
alter session set cursor_sharing = force;
---------------------------------------
-- then I have executed:
select * from t_all_obj where owner = 'VLADE DIVAC';
select * from t_all_obj where owner = 'DIVAC';

--------------------
select sql_text from v$sql
where sql_text like '%t_all_obj%';
--------------------------------------
select * from t_all_obj where owner =: "SYS_B_0"

-- THIS IS OK !!!

---------------------------------------
alter system flush shared_pool;
alter session set cursor_sharing = similar;

-- then I have executed:
select * from t_all_obj where owner = 'VLADE DIVAC';
select * from t_all_obj where owner = 'DIVAC';

--------------------
select sql_text from v$sql
where sql_text like '%t_all_obj%';
--------------------------------------
select * from t_all_obj where owner =: "SYS_B_0"
-- with exec. plan of full table scan (from the trace file)

-- THIS IS WRONG (IF I UNDERSTAND CURSOR SHARING SIMILAR CORRECT)
-- According to what you have said, optimizer should recognize that value for
OWNER = 'DIVAC' will radicaly change a exec. plan and should generate a new one,
with index use.

Am I doing something wrong?

Thanks!
Followup:
ops$tkyte@ORA9IR2> create table t_all_obj as select owner, object_name,
object_id from
  2  all_objects;

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> update t_all_obj set owner = 'VLADE DIVAC';

30634 rows updated.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> update t_all_obj set owner = 'DIVAC' where rownum = 100;

0 rows updated.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create index t_all_obj_i on t_all_obj(owner);

Index created.

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

Table analyzed.

In your example, you seem to be missing a ";" there -- are you sure the CBO
was in fact being used???

ops$tkyte@ORA9IR2> alter session set cursor_sharing = exact;

Session altered.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> set linesize 121
ops$tkyte@ORA9IR2> set autotrace traceonly explain
ops$tkyte@ORA9IR2> select * from t_all_obj where owner = 'VLADE DIVAC';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=26 Card=30634 Bytes=1255994)
   1    0   TABLE ACCESS (FULL) OF 'T_ALL_OBJ' (Cost=26 Card=30634
Bytes=1255994)

ops$tkyte@ORA9IR2> select * from t_all_obj where owner = 'DIVAC';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=204 Bytes=8364)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T_ALL_OBJ' (Cost=3 Card=204
Bytes=8364)
   2    1     INDEX (RANGE SCAN) OF 'T_ALL_OBJ_I' (NON-UNIQUE) (Cost=1 Card=204)

ops$tkyte@ORA9IR2> set autotrace off
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select sql_text from v$sql
  2  where sql_text like 'select%t_all_obj%';

SQL_TEXT
---------------------------------------------------------------------------------
----------------------------------------
select * from t_all_obj where owner = 'VLADE DIVAC'
select * from t_all_obj where owner = 'DIVAC'
select sql_text from v$sql where sql_text like 'select%t_all_obj%'

Ok so far...  - full scan and index range..

ops$tkyte@ORA9IR2> alter system flush shared_pool;

System altered.

ops$tkyte@ORA9IR2> alter session set cursor_sharing = force;

Session altered.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> set autotrace traceonly explain
ops$tkyte@ORA9IR2> select * from t_all_obj where owner = 'VLADE DIVAC';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=26 Card=30634 Bytes=1255994)
   1    0   TABLE ACCESS (FULL) OF 'T_ALL_OBJ' (Cost=26 Card=30634
Bytes=1255994)

ops$tkyte@ORA9IR2> select * from t_all_obj where owner = 'DIVAC';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=204 Bytes=8364)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T_ALL_OBJ' (Cost=3 Card=204
Bytes=8364)
   2    1     INDEX (RANGE SCAN) OF 'T_ALL_OBJ_I' (NON-UNIQUE) (Cost=1 Card=204)

ops$tkyte@ORA9IR2> set autotrace off
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select sql_text from v$sql
  2  where sql_text like 'select%t_all_obj%';

SQL_TEXT
---------------------------------------------------------------------------------
----------------------------------------
select * from t_all_obj where owner = :"SYS_B_0"

ops$tkyte@ORA9IR2>

there is only one query in the shared pool -- shows that autotrace "lies"
really sometimes.  there is only one plan out there -- the first one

ops$tkyte@ORA9IR2> alter system flush shared_pool;

System altered.

ops$tkyte@ORA9IR2> alter session set cursor_sharing = similar;

Session altered.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> set autotrace traceonly explain
ops$tkyte@ORA9IR2> select * from t_all_obj where owner = 'VLADE DIVAC';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=26 Card=30634 Bytes=1255994)
   1    0   TABLE ACCESS (FULL) OF 'T_ALL_OBJ' (Cost=26 Card=30634
Bytes=1255994)

ops$tkyte@ORA9IR2> select * from t_all_obj where owner = 'DIVAC';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=204 Bytes=8364)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T_ALL_OBJ' (Cost=3 Card=204
Bytes=8364)
   2    1     INDEX (RANGE SCAN) OF 'T_ALL_OBJ_I' (NON-UNIQUE) (Cost=1 Card=204)

ops$tkyte@ORA9IR2> set autotrace off
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select sql_text from v$sql
  2  where sql_text like 'select%t_all_obj%';

SQL_TEXT
---------------------------------------------------------------------------------
----------------------------------------
select * from t_all_obj where owner = :"SYS_B_0"
select * from t_all_obj where owner = :"SYS_B_0"

ops$tkyte@ORA9IR2>

but here I get two -- i have a feeling you might have missed the ";" on the
analyze and it never ran actually (but hard to say since you don't straight cut
and paste like I do...
March 27, 2004 -- Thanks for the question regarding "Primary key field datatype", version 9i
You Asked
For all tables, I would use a single field for primary key.  Now, I'm wondering
whether the key field should be defined as NUMERIC(9,0) or VARCHAR2(9) in order
to have a better performance in searching ?
and we said...
If the field is a NUMBER, by all means USE A NUMBER.

If the field is alpha-numeric, can contain non-numbers, then by all means use
VARCHAR.

Pick your datatype based on the values contained therein, not on any other
factor.

In your case, since you appear to have a "choice", the NUMBER datatype would be
the only sensible datatype.
Review & Followup

Rating: 5
Use CODE or TEXT as primary key December 19, 2002
Reviewer: Jeff in Miami from Miami, Florida
Tom,

I have lots of dimension tables holding let's say Cities.

Now, we have a varchar(3) city code as primary key, such as 'MIA', with a city
Name varchar(30) say, 'Miami Beach'.

My question is why create a CODE, why would we not want to use the Name
VARCHAR(30) column as the primary key itself?

Is there any performance tradeoff, to using a VARCHAR(30) column as the primary
key rather than a VARCHAR(3) column?

Thanks,
Jeff
Followup:
varchar2(30) is bigger -- index is larger.

varchar2(30) is bigger -- takes longer to compare string A to string B.

varchar2(30) would have to be in the fact table -- it is bigger, it adds tons of
mega/giga bytes to the table size....
Rating: 4
Use Numeric Surrogate Primary Keys December 19, 2002
Reviewer: Sundeep from Peoria,IL
To the previous reader:

If you have dimensions, you should use signle surrogate numeric PKs. Not only
for all the benefits Tom mentioned but also for preventing cascading updates to
primary keys. As historical data what appears to be an unchanging Primary key in
the short run (and to OLTP folks) invariable changes over a period of time.
Even city names for example are not immune from changing. A surrogate key will
prevent fact tables useing the city name from needing updates. Even Feds have
changed its coding schemes from medial procedures to diagnosis codes to Ferc
codes etc many times causing lot of heart burn for the unsuspecting.
Rating: 5
date as the PK March 27, 2004
Reviewer: A reader from Houston
Tom,
I have an option of using a surrogate key as the pk or a column of datatype
number and a column of datatype date.

1) Is there a performace hit having a column of date datatype as the primary
key?
2) Is there a performace hit having a column of date datatype and a column of
datatype number as the primary key?
3) I did read in one of your threads about the size of the index when using
varchar2 as the PK.  How does the size of the index play when having date as the
pk or having date as a part of the composite key.

Thank you
Followup:
1) nope, just bits in the bucket to us (7 bytes for dates, 7-11 bytes for
timestamps, just bits to be compared to other bits at the end of the day...)

2) nope, just more "bits" to compare perhaps

3) you add up the sizes (0..22 bytes for the NUMBER, 0 or 7 or 11 bytes for the
date, that is the size of the key...)

IF you have a natural key
AND it is a reasonable number of columns (2 is more than reasonable)
AND you are pretty sure you will never update them

use em.
Rating: 5
Primary key datatype March 27, 2004
Reviewer: A reader from Houston
Tom,
Can I assume that for a PK, a number datatype would give better performance
compared to varchar2(30) based on your followup
<quote>
Title of Review: Use CODE or TEXT as primary key  December 19, 2002

Followup:
varchar2(30) is bigger -- index is larger.

varchar2(30) is bigger -- takes longer to compare string A to string B.

varchar2(30) would have to be in the fact table -- it is bigger, it adds tons of

mega/giga bytes to the table size....
</quote>

Thank you
Followup:
in that one we were looking at "should I use varchar2(3) or a varchar2(30)"

given the choice, I would use the smaller of the two.

If I had a table that had a natural key that was already there and was a
varchar2(30) -- I would use it.

We were looking at a FACT table with a DIMENSION.  It is different than picking
a primary key for a table in general.
March 27, 2004 Chun -- Thanks for the question regarding "How to auto increment a column - like a sqlserver Identity or Informix serial#", version 8.0.5
You Asked
Hi Tom,

How can I automatic insert a serial number in the column when
inserting a row into a table?

Thanks,

Chun
and we said...
The answer in Oracle is to use a sequence + trigger.  It might
look like this:

ops$tkyte@8i> create table t ( x int primary key, y int );
Table created.

ops$tkyte@8i> create sequence myseq;
Sequence created.

ops$tkyte@8i> create trigger t_trigger
  2  before insert on t for each row
  3  begin
  4          if ( :new.x is null ) then
  5                  select myseq.nextval into :new.x from dual;
  6          end if;
  7  end;
  8  /
Trigger created.

I used "if ( :new.x is null ) then..." in order to allow the
application to supply a value for the primary key if it desires.
 You may leave that off -- it would then assign a unique value
for EVERY row

ops$tkyte@8i> insert into t (x,y) values (-1,2);
1 row created.

ops$tkyte@8i> insert into t (y)   values (3);
1 row created.

ops$tkyte@8i> select * from t;

         X          Y
---------- ----------
        -1          2
         1          3

ops$tkyte@8i>
Review & Followup

Rating: 5
Most Helpful. February 12, 2002
Reviewer: Christian Maslen from Sydney, Australia.
Excellent and to the point.

The Oracle docs said you couldn't assign the nexval in a trigger so I didn't
think to use a select from dual into the transition variable.
Rating: 3
Auto number trigger May 16, 2002
Reviewer: Calvin Chu from USA
I got an error message like this: "Warning: Trigger created with compilation
errors." when compiling the trigger to do auto numbering.

After inserting a value into the table, I got "ORA-04098: trigger XXX is invalid
and failed re-validation".

Please help!!
Followup:
show errors trigger TRIGGER_NAME

and fix your coding error.
Rating: 5
Why Oracle doesn't provide something like identity May 17, 2002
Reviewer: Moorthy Rekapalli from Atlanta GA USA
Tom,

First of all, let me thank you for your excellent responses.  I learned a lot
from your site.  In my view, you are doing a great service to the Oracle
community.

Generating a sequence number for primary key is very common in lot of
applications.  It would be great if Oracle provided the capability like identity
in SQL Server.  This eliminates the need for everybody on the planet writing
before insert trigger to generate a primary key.

I hope that Oracle development team is reading this post and add this feature in
a future release.
Rating: 5
I hope that Oracle development team is not!!! May 17, 2002
Reviewer: J from ma
create or replace function emp_ident return number is
l_return number;
begin
 select id.nextval
   into l_return
   FROM dual;
 if mainoff then l_return = l_return +1000000; end if;
 if othoff then then l_return = l_return +2000000;end if;
 --if what>> then then l_return = l_return +3000000;
 return l_return ;
end;
/
insert into emp(prim_key, first) values (emp_ident,'Joe');
/
-- disclaimer - not tested half ranting
write your own!!!
Rating: 5
April 12, 2003
Reviewer: A reader
thanks Tom.  very useful.
Rating: 5
10G? March 26, 2004
Reviewer: Kashif from Reston, VA
Hi Tom,

Is the auto-increment functionality implemented in a different way in 10G, e.g.
through an IDENTIFIER type (as is used in other databases) or something similar?
Or is using a before insert trigger to set the value of an ID column still the
only way to do it in Oracle? I hadn't heard anything different in 9i either.
Thanks in advance.

Kashif
Followup:
nope, it is still as functional and flexible as it always has been.
Rating: 4
Is this safer/better March 27, 2004
Reviewer: A reader
Tom,

Assume you have just created the table t and sequence myseq starts with 1.

insert into t(y) values(2)
insert into t(x,y) values(2,2)
insert into t(y) values(3)

The third insert PK unique contraint will fail.

Is it better to use
 create or replace trigger t_trigger
    before insert on t for each row
    begin

                   select nvl(:new.x, myseq.nextval) into :new.x from dual;

    end;
   /.
Followup:
what is you point here?  You made a programming choice to do what you did and in
your case it would be "the wrong thing to do"
Rating: 5
What if? March 27, 2004
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
Although this is a good way to generate PK but I would like to know if someone
is using web based application and the db trigger is generating the PK the how
the user will be notified about his/her transaction#.
Secondly what will be the performance impact in multiusers environment of this
technique?
Rating: 2
pl explain March 27, 2004
Reviewer: A reader
Can you please explain why
"in
your case it would be "the wrong thing to do" "

for the nvl in trigger ?.
We generate seq for every insert this way whether we use it as PK or not use it
( when user supplies one)
Followup:
because the third insert would fail and you seemed to want to do that third
insert?

so -- in that case -- it would be the "wrong approach" as you would have failed
inserts lots.

The only time it might make total sense would be:

"we are going to load data with existing primary keys.  these keys range between
1 and 1 billion.  We will therefore start a sequence for new records at one
billion -- if the inserted record did not supply a primary key, we'll generate
one (starting at 1,000,000,000,001) else we'll use the supplied one"
March 27, 2004 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.
Rating: 4
Reverse Key Index and RBO March 27, 2004
Reviewer: Vivek Sharma from Bombay, India
Dear Tom,

Does RBO make use of Reverse Key Index ? I have a table in which the primary key
is populated by sequence and I see lots of Buffer Busy waits on the primary key
Index and hence I suggested to drop and recreate it as a revarse key index. Also
before suggesting, I had made sure that the queries on this table and on this
column are used with equality and not range scan. But immediately after
recreating this index as a Reverse Key, the queries started doing Full Table
Scan. The optimizer_mode is choose but the schemas are not analyzed hence the
optimizer is RBO.

When the Index was dropped and recreated as NORMAL, the Index was used.

Hence my question are

1. Does RBO does not recognize Reverse Key Index ?
and 2.Mine is Oracle 8174 database with Single Instance. So this type of Index
is only useful in OPS ?

Regards
Vivek
Followup:
RBO cannot see a reverse key index, no.

This index can be useful in other cases (to spread the insert activity out, to
prevent "sweeping indexes" where you delete much of but not all of the left hand
side (so you do not need to rebuild)...

but you have to use the CBO
March 27, 2004 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.
Rating: 4
Shared Pool too large? March 27, 2004
Reviewer: Fran from Madrid, SPAIN
Tom,

I'm reading your expert one-on-one Oracle book and found an interesting remark
on page 81. You talk about a system with a 1.1G SGA and a 1G Shared Pool, the
DBAs had to shut down that instance every day because otherwise performance
would drop due to the overhead of aging structures out of the pool.

Can you elaborate a little more on how objects are aged out of the shared pool?
How would it help to have a smaller Shared Pool? Wouldn't objects be aged out
the same way?

Many times i have to set up initial values for an SGA at the initial stages of a
project, and up to now I believe i've been oversizing it.

My point is that i don't quite understand how an oversized Shared pool can hurt
performance a lot.

Thanx
Followup:
it would have dramatically helped to have a very small shared pool.

The knee jerk reaction to "we don't use binds, we have shared pool issues" is to
"make it bigger (the shared pool)".  That is "the wrong reaction".  The bigger
they are -- the harder they fall, and the same is true of the shared pool.  All
you've done by making it really big is push the problem back a while -- but the
problem will be even bigger.

The oversized shared pool is a problem when the developers coded bugs into the
code on purpose -- bugs in the form of "not using bind variables".  Imagine the
overhead of having to move tons of stuff in and out of 500meg of ram -- as
opposed to moving things in and out in a 50meg shared pool.  One takes lots of
work (to manage and age lots of junk that'll never be reused) wereas the other
is a much smaller, more manageable problem.
March 27, 2004 Amin -- Thanks for the question regarding "Cross Matrix Report", version Reports 2.5
You Asked
Here is a real eg. a portion of SQL from report designer
***********************************
select
distinct
m.segment1||'.'||m.segment2||'.'||m.segment3||'.'||m.segment4,
m.description,
'00 - 07',
count(s.oe_trx_schedule_id) c
from can_cust_oe_trx_schedules s,
mtl_system_items m
where s.inventory_item_id = m.inventory_item_id
and m.description like  '%TACOMA%'
and m.segment4 = '0'
and shipping_date is not null
and s.created_by <> -1
and oe_tracking_date between :p_creation_date_low and
:p_creation_date_high
and trunc(shipping_date) - trunc(oe_tracking_date) <= 7
group by
m.segment1||'.'||m.segment2||'.'||m.segment3||'.'||m.segment4,m.d
escription

union all

select distinct
m.segment1||'.'||m.segment2||'.'||m.segment3||'.'||m.segment4,
m.description,
'08 - 14',
count(s.oe_trx_schedule_id) c
from can_cust_oe_trx_schedules s,
mtl_system_items m
where s.inventory_item_id = m.inventory_item_id
and m.description like  '%TACOMA%'
and m.segment4 = '0'
and shipping_date is not null
and s.created_by <> -1
and oe_tracking_date between :p_creation_date_low and
:p_creation_date_high
and (trunc(shipping_date) - trunc(oe_tracking_date) > 7 and
trunc(shipping_date) - trunc(oe_tracking_date) <= 14)
group by
m.segment1||'.'||m.segment2||'.'||m.segment3||'.'||m.segment4,m.d
escriptio
***************

The what i have done is made three groups
Group 1 have two fields. In default layout repeat is down
1. Segement1....
2. Description

Group 2 has my bucket field, Across in default layout

Group 3 has count c field, Down in default layout

Group 1 and 2 are cross product and group 3 is child of cross
product.

Resultant of this sql and cross matrix format is

                          00-07          08 - 14

Segment 1 & Description       99            55
Segment 2 & Description       44            22
and so on

so if have 7 buckets i write the same query 7 times with union
all, only differnce is third field which is my bucket.

*******
I wanted to create a Age Trial balance report. I used Cross
Matrix type report and was able to do it, but its very slow.
What i have done is for each bucket i used union all clause
for e.g.
Select
xxxx,
yyyy,
'First Bucket'
from
xxx
where
xxxxx

union all Select
xxxx,
yyyy,
'Second Bucket'
from
xxx
where
xxxxx

and so on. Is there a better approach using Cross Matrix method.
and we said...
Decode can let us do this transposition with 1 pass on the data.
 You basically have 3 columns
"segment,description,column-to-be-transposed".  I set up a small
example as thus:

tkyte@TKYTE816> create table t
  2  ( segment        varchar2(10),
  3    description       varchar2(10),
  4    schedule_id       int,
  5    shipping_date  date
  6  )
  7  /

Table created.

tkyte@TKYTE816>
tkyte@TKYTE816> declare
  2     r1 number;
  3     r2 number;
  4  begin
  5     for i in 1 .. 5
  6     loop
  7        for j in 1 .. 3
  8        loop
  9           for k in 1 .. random.rand_max(20)+1
 10           loop
 11              r1 := random.rand_max( 45 ) + 1;
 12              r2 := random.rand_max( 20 ) + 1;
 13
 14              insert into t values
 15              ( 'seg ' || i, 'desc ' || j, r2, trunc
                                               (sysdate+r1) );
 16           end loop;
 17        end loop;
 18     end loop;
 19  end;
 20  /

PL/SQL procedure successfully completed.

random.rand_max is something i wrote, see
http://osi.oracle.com/~tkyte/Misc/Random.html
for it if you would like.  That just generates some "dummy" data
for me.

Now, I'll dump out some of it for segment = 'seg 1'.  I'll pull
the days difference from "oe_tracking_date" (i'm using TODAY as
my oe_tracking_date -- where you have trunc(oe_tracking_date) i
have to_date( '19-JAN-2001')).  I'll also pull out the "week"
offset from that as well -- the ceil( difference/7 ).  That'll
return a number 1 if we are within 1..7 days, 2 if 8..14 and so
on.

tkyte@TKYTE816> select description,
  2         shipping_date-to_date('19-JAN-2001') days_diff,
  3     ceil( (shipping_date-to_date('19-JAN-2001'))/7 ) week_no
  4    from t
  5   where segment = 'seg 1'
  6   order by 3
  7  /

DESCRIPTIO  DAYS_DIFF    WEEK_NO
---------- ---------- ----------
desc 2              5          1
desc 2              6          1    <<== expect 2 for desc 2 in
desc 3              3          1         the 0-7 column
desc 3              2          1    <<== expect 2 for desc 3 in
desc 1             10          2         the 0-7 column
desc 2             11          2
desc 2             13          2
desc 3              8          2    <<== expect 3 for desc 3 in

desc 3              8          2         the 8-14 column
desc 3             14          2
desc 2             21          3
desc 2             21          3    and so on...
desc 3             16          3
desc 2             17          3
desc 2             22          4
desc 2             22          4
desc 2             23          4
desc 2             33          5
desc 2             31          5
desc 2             30          5
desc 3             32          5
desc 2             38          6
desc 2             42          6
desc 3             39          6
desc 3             37          6
desc 1             43          7

26 rows selected.

tkyte@TKYTE816> select segment, description,
  2         count( decode(
               ceil( (shipping_date-to_date('19-JAN-2001'))/7 ),
  3                       1, schedule_id, null ) ) "0-7",
  4         count( decode(
               ceil( (shipping_date-to_date('19-JAN-2001'))/7 ),
  5                       2, schedule_id, null ) ) "8-14",
  6         count( decode(
               ceil( (shipping_date-to_date('19-JAN-2001'))/7 ),
  7                       3, schedule_id, null ) ) "15-21",
  8         count( decode(
               ceil( (shipping_date-to_date('19-JAN-2001'))/7 ),
  9                       4, schedule_id, null ) ) "22-28"
 10    from t
 11   group by segment, description
 12  /

SEGMENT    DESCRIPTIO      0-7       8-14      15-21      22-28
---------- ---------- -------- ---------- ---------- ----------
seg 1      desc 1            0          1          0          0
seg 1      desc 2            2          2          3          3
seg 1      desc 3            2          3          1          0
seg 2      desc 1            3          2          3          2
seg 2      desc 2            2          0          1          2
seg 2      desc 3            1          1          1          4
seg 3      desc 1            3          2          0          1
seg 3      desc 2            0          4          3          8
seg 3      desc 3            1          4          1          3
seg 4      desc 1            1          1          5          1
seg 4      desc 2            3          1          4          3
seg 4      desc 3            1          2          2          1
seg 5      desc 1            3          1          1          3
seg 5      desc 2            2          0          0          1
seg 5      desc 3            0          0          2          1

Looking at this a little more closely:

tkyte@TKYTE816> select segment, description,
  2         count( decode(
               ceil( (shipping_date-to_date('19-JAN-2001'))/7 ),
  3                       1, schedule_id, null ) ) "0-7",

We are counting the DECODE of the "week number".  That "week
number" is a function that turns the difference in days between
our two dates into a number like 1, 2, 3, ... and so on.  If the
days are 1..7 days apart, we get 1.  8-14, we get two and so on.

We want COLUMN "0-7" to be a count of the schedule_id when the
difference between shipping_date and oe_tracking_date is less
then or equal to 7 -- hence when the decode of our function is
1, we return schedule_id else we return NULL.

We do the same thing for the column 8-14 only we look for the
ceil function to return 2.  And so on.

A simple group by and we get our counts.  In one pass we can not
only collect all of the data we need but we can transpose it as
well (reports won't need to do this for us)....
Review & Followup

Rating: 4
Is very useful May 10, 2001
Reviewer: Juan Carlos from Colombia
This query solve a lot of my problems
Rating: 3
Creating a View for a Matrix Report Template March 26, 2004
Reviewer: denni50 from NA
Hi Tom!
hope all is well with you....
I've created a Matrix Report Template that I use to produce
results from varying Views depending on select criteria:
here's an example of one:

SELECT LPAD(DECODE(CEIL(months_between(trunc(sysdate),am.maxdate)/3)*3
 , 3, '0-3'
 , 6, '3-6'
 , 9, '6-9'
 , 12, '9-12'
 , 15, '12-18'
 , 18, '12-18'
 , 21, '18-24'
 , 24, '18-24'
 , 27, '24-36'
 , 30, '24-36'
 , 33, '24-36'
 , 36, '24-36'
 , '36-999'),6) months,
 sum(case when am.maxgift between .01 and 9.99 then 1 else 0 end) ".01-9.99",
 sum(case when am.maxgift between 10 and 24.99 then 1 else 0 end) "10-24.99",
 sum(case when am.maxgift between 25 and 49.99 then 1 else 0 end) "25-49.99",
 sum(case when am.maxgift between 50 and 99.99 then 1 else 0 end) "50-99.99",
 sum(case when am.maxgift between 100 and 249.99 then 1 else 0 end)
"100-249.99",
 sum(case when am.maxgift >=250 then 1 else 0 end) "250+"
 from deni.acga_maxgift_maxdate_nondon am
 group by LPAD(DECODE(CEIL(months_between(trunc(sysdate),am.maxdate)/3)*3
 , 3, '0-3'
 , 6, '3-6'
 , 9, '6-9'
 , 12, '9-12'
 , 15, '12-18'
 , 18, '12-18'
 , 21, '18-24'
 , 24, '18-24'
 , 27, '24-36'
 , 30, '24-36'
 , 33, '24-36'
 , 36, '24-36'
 , '36-999'),6)

MONTHS   .01-9.99   10-24.99   25-49.99   50-99.99 100-249.99       250+
------ ---------- ---------- ---------- ---------- ---------- ----------
   0-3          2         48         53          4          6          0
   3-6        790       2975       1815        726        479         61
   6-9       6768      38041      24180       8952       5449        560
  9-12       8546      42093      25371       9119       5155        540
 12-18      16097      86481      56678      19294      11750       1233
 18-24      19747      96514      58724      20149      11956       1022
 24-36      38536     182304     118633      38921      22678       2266
36-999     134785     569609     388885     137957      80815      12274

I'm creating another view that selects Single Donors
for purposes of running it through the Matrix Template.
I am having the darndest time trying to get it to select those donors having
only 1 record...I know they exist!
just can't seem to get the code right...I need a second
pair of eyes from an expert :~)....thanks!

SQL> desc acga_single;
 Name                                                  Null?    Type
 ----------------------------------------------------- -------- --------------
 IDNUMBER                                              NOT NULL NUMBER(12)
 GIFTAMOUNT                                            NOT NULL NUMBER(13,2)
 GIFTDATE                                              NOT NULL DATE

SQL> create or replace view ACGA_Single
  2  as
  3   select idnumber,giftamount,giftdate
  4   from gift where idnumber in(select count(g.idnumber) from gift g
  5   where g.usercode1='ACGA'
  6   and g.giftamount>0
  7   group by idnumber,giftamount,giftdate
  8   having count(g.idnumber)=1);

View created.

SQL> select * from acga_single;

no rows selected
Followup:
(select count(g.idnumber) from gift g
  5   where g.usercode1='ACGA'
  6   and g.giftamount>0
  7   group by idnumber,giftamount,giftdate
  8   having count(g.idnumber)=1)

if that query returned anything other than "1" -- there would be a problem, no?

assuming idnumber is "the key" to getting the donor identity -- then:

select *
  from ( select gift.*, count(*) over (partition by idnumber) cnt
           from gift
          where usercode1 = '...'
            and giftamount > 0 )
  where cnt = 1

will give you all of the records from that table such that there is only one
record for that usercode/giftamount for that idnumber...
Rating: 3
a little confused? March 26, 2004
Reviewer: denni50 from NA
are you saying the 'having count(g.idnumber)=1' is equating the 1 to a literal
"1" and not a count number of 1.

I believe I have used similar code in the past where
I was selecting a count(something) = 1 and it worked...
that's the whole purpose of count??

I'll re-work the code logic...thanks
Followup:
(select count(g.idnumber) from gift g

that is the key part -- you are selecting the COUNT(g.idnumber) and doing this:

 idnumber in(select count(g.idnumber) from gift g
 ^^^^^^^^           ^^^^^^^^^^^^^^^^^

the only idnumber that could ever be returned by that predicate would be "1"
Rating: 5
OH!!! March 26, 2004
Reviewer: denni50 from NA
I see what you mean....got it!!

thanks and have yourself a great w/e!
Rating: 4
View solution.... March 27, 2004
Reviewer: denni50 from NA
hey Tom....
it's turning out to be a long weekend...
thanks for making me realize that I was trying to evaluate
the donor idnumber with the value of COUNT(which = 1 based on the HAVING
clause)...gotta remember when writing this stuff I need to think like the
computer instead of vice versa(maybe one day computers of the future will KNOW
exactly what we want)...which brings me back to this View dilemma.

I have tried every creative nuance I could think of trying to create one view
that would display the columns I want from the base table(gift) while counting
all the records and selecting those donors with only 1 record (multi donors can
have 20,30,40 records spanning 10+ years using obviously same idnumber with
different giftdates and giftamounts). I used your code in many variations and
nothing worked. This is being done on 8.1.7(no AF).

thinking like the computer I thought....I can't select
idnumber,giftdate,giftamount then group by those columns(the only column I need
to group by is idnumber).

so my solution was to create 2 views, the first view finds all the single
idnumbers the second view selects the columns I
want(idnumber,giftamount,giftdate) and then
matches on idnumber from the view that has the single donor id's.   (see results
below)

If there is a better way to do this...I'm all eyes and ears...but this was my
solution.

question:
when I run this again next month will both views refresh..as the single vs multi
is dynamic and constantly changing.

*********************************************************************************
***
create or replace view ACGA_SingleID
as
 select idnumber,count(giftamount) as GftCnt
 from gift
 where usercode1='ACGA'
 and giftamount>0
 group by idnumber
 having count(giftamount)=1

  IDNUMBER     GFTCNT
---------- ----------
    667915          1
    667922          1
    667928          1
    667933          1
    667945          1
    667954          1
    667959          1
    667964          1
    667968          1
    667983          1
    667987          1

SQL> select idnumber,giftamount,giftdate from gift where idnumber=667945;

  IDNUMBER GIFTAMOUNT GIFTDATE
---------- ---------- ---------
    667945         31 31-DEC-88  (imagine that!...this donor hasn't given since
'88)

SQL> ed
Wrote file afiedt.buf

  1  create or replace view ACGA_Single
  2  as
  3   select idnumber,giftdate,giftamount
  4*  from gift where idnumber IN(select idnumber from ACGA_SINGLEID)
SQL> /

View created.

SQL> select * from acga_single where idnumber=667945;

  IDNUMBER GIFTDATE  GIFTAMOUNT
---------- --------- ----------
    667945 31-DEC-88         31

now I can run my Matrix Report using the acga_single view:

SQL> SELECT LPAD(DECODE(CEIL(months_between(trunc(sysdate),a.giftdate)/3)*3
  2   , 3, '0-3'
  3   , 6, '3-6'
  4   , 9, '6-9'
  5   , 12, '9-12'
  6   , 15, '12-18'
  7   , 18, '12-18'
  8   , 21, '18-24'
  9   , 24, '18-24'
 10   , 27, '24-36'
 11   , 30, '24-36'
 12   , 33, '24-36'
 13   , 36, '24-36'
 14   , '36-999'),6) months,
 15   sum(case when a.giftamount = 0 then 1 else 0 end) "NonDon",
 16   sum(case when a.giftamount between .01 and 9.99 then 1 else 0 end)
".01-9.99",
 17   sum(case when a.giftamount between 10 and 24.99 then 1 else 0 end)
"10-24.99",
 18   sum(case when a.giftamount between 25 and 49.99 then 1 else 0 end)
"25-49.99",
 19   sum(case when a.giftamount between 50 and 99.99 then 1 else 0 end)
"50-99.99",
 20   sum(case when a.giftamount between 100 and 249.99 then 1 else 0 end)
"100-249.99",
 21   sum(case when a.giftamount >=250 then 1 else 0 end) "250+"
 22   from acga_single a
 23   group by LPAD(DECODE(CEIL(months_between(trunc(sysdate),a.giftdate)/3)*3
 24   , 3, '0-3'
 25   , 6, '3-6'
 26   , 9, '6-9'
 27   , 12, '9-12'
 28   , 15, '12-18'
 29   , 18, '12-18'
 30   , 21, '18-24'
 31   , 24, '18-24'
 32   , 27, '24-36'
 33   , 30, '24-36'
 34   , 33, '24-36'
 35   , 36, '24-36'
 36   , '36-999'),6)
 37  /

MONTHS     NonDon   .01-9.99   10-24.99   25-49.99   50-99.99 100-249.99
250+
------ ---------- ---------- ---------- ---------- ---------- ----------
----------
   0-3            0       713        5782       5923       1561       1494
  47
   3-6            0     1603       14804      15691      4092       3569
52
   6-9            0       432        3522       4005       1109       1101
  23
  9-12           0       765        2723       2799        676          692
   10
 12-18          0       1062       7041       7008       1828       1776
50
 18-24          0       1267       8106       7659       2037       1924
46
 24-36          0       4194      20674      20021       5319       3654
106
36-999         0     19826      68734      50498      12137       8539
589

8 rows selected.

SQL>

well the alignment is a bit out of whack...when I run this through my
Reports software using headers..etc it'll look real dandy!
Followup:
816 has analytics, 817 has them too.

without using analytics, your approach is "sound".  since you are interested in
the "one" record -- not the n-record count, you have another alternative:

select idnumber,
       max(giftdate) giftdate,
       max(giftamount) giftamount
  from gift
 where usercode1='ACGA'
   and giftamount>0
 group by idnumber
having count(*)=1

it would most likely be more efficient than your current approach as it answers
the question in one pass.  It works since if count(*) by idnumber = 1, then the
max() of those two columns must be for that singleton record...
Rating: 5
great Tom.... March 27, 2004
Reviewer: denni50 from NA
that is definitely better as I was trying to do this
all with one view...and with 1 single record the giftdate
and giftamount would also be the MAX value for both columns
thus avoiding having to include them in the grouping clause.

we have 8.1.7 Standard Edition(aaarrrrghhh!!)...very, very
frustrating waiting for our upgrade to 9i.

back to the drawing board....
thx,  :~)
Rating: 5
View Solution.....fyi March 29, 2004
Reviewer: denni50 from NA
hey Tom...

your view solution worked perfectly...for some reason I was hung up on the
notion that using the COUNT function with the HAVING clause required it to be in
the SELECT statement.

in looking at your solution substituting COUNT with MAX was perfect since I
needed an aggregate function to use GROUP BY...and needed to GROUP BY idnumber
only and not the other 2 columns which also worked out great since having to
search for donors with 1 record....both column values were also MAX values.

when I find some time need to go back and read my SQLPlus text book
from school....

thx..have a great day!
March 27, 2004 -- 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.
Rating: 3
Segment Space Management March 27, 2004
Reviewer: Jayesh Antony Jose from Chennai
Hello Tom,
  Can I convert Segment Space management auto to manual and manual to auto?
Followup:
alter table t move tablespace manual;

alter table t move tablespace auto;

you have to MOVE the objects from a manual to auto and auto to manual.  ASSM is
a physical storage placement thing -- every block would need be rewritten.

So no, you cannot "convert" a tablespace, you can however MOVE objects into the
tablespace of your choosing.
Rating: 4
How to know whether a LMT is original LMT or Converted from DMT March 27, 2004
Reviewer: Vivek Sharma from Bombay, India
Dear Tom,

I have a database version 8174. All the Tablespaces in this database are LMT.
The Size of the Database is around 400Gb with the maximum size of a tablespace
being 90GB. I have a doubt that the tablespaces are converted from DMT to LMT
and hence want to create new LMT and move the objects to these new LMT's (to get
the benefit of true LMT). How do I check whether the Tablespaces are true LMT
and not converted from a DMT and vice versa ?

Is there any way ?

Thanks and Regards
Vivek
Followup:
SQL> create tablespace lmt_uniform
  2  datafile 'c:\x.dbf'
  3  size 512k
  4  extent management local uniform size 64k;

Tablespace created.

SQL> create tablespace lmt_auto
  2  datafile 'c:\y.dbf'
  3  size 512k
  4  extent management local;

Tablespace created.

SQL> exec sys.dbms_space_admin.tablespace_migrate_to_local('USERS');

PL/SQL procedure successfully completed.

SQL> select tablespace_name, extent_management, allocation_type
  2  from dba_tablespaces;

TABLESPACE_NAME                EXTENT_MAN ALLOCATIO
------------------------------ ---------- ---------
SYSTEM                         DICTIONARY USER
RBS                            DICTIONARY USER
USERS                          LOCAL      USER     <<<== migrated, user
TEMP                           DICTIONARY USER
TOOLS                          DICTIONARY USER
INDX                           DICTIONARY USER
DRSYS                          DICTIONARY USER
LMT_UNIFORM                    LOCAL      UNIFORM  <<<=== for real
LMT_AUTO                       LOCAL      SYSTEM   <<<=== for real

9 rows selected.
Rating: 4
Sorry TOM March 27, 2004
Reviewer: Vivek Sharma from Bombay, India
Dear Tom,

I am sorry for asking such a silly question. I had read this before but could
not recollect.But thanks for your patience and quick response.

Regards
Vivek
March 27, 2004 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.
Rating: 4
Question Regarding System Privilege SYSDBA March 27, 2004
Reviewer: Olga Cornejo from Guayaquil, Ecuador
Hi Tom:

When you said:

"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."

You said that I can enter a WNT user in this NODE?

I have the same problem with the authentication error when I try to do a backup
of data, and is very important to do, because I need to recreate a new database
(in production) with this developer data.

Thanks for your help,

Olga
Followup:
not really sure what "wnt" might be meaning there.  probably windows nt - if so,
sorry, but I don't really have access to that OS in an admin role (no command
lines, too hard to work with).  Sooo -- I'll have to refer you to support on
that one.  most of this was a cut/paste of a metalink support note (especially
the windows stuff)
March 28, 2004 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.
Rating: 4
Purging/Archiving (Referential Integrity) March 27, 2004
Reviewer: CA from Vancouver, BC
Tom, we reached a point that we have to start to archive the old data (around
80% first time and then on nightly basis) from some tables in our production
system. The problem is referential integrity.
For example almost 20 tables are related to our orders table, directly or
indirectly. Looks like

Order_header
  Order_Details
    Order_Item
      Order_item_locations
  Customer_order
    Customer_order_Details
      Customer_Order_Delivery_Schedule
        Customer_Order_Delivery_Details
.....

Orders table should be driving the entire process but I don't like the idea of
going 20 children table check the data, if data exists then move the data into
archive table, then delete the records. I am stuck. Company is looking forward
to buy something but looks like it's gonna take 1~2 years, longer than I can
survive.
Any suggestions or links ?

Thanks,
CA
Followup:
insufficient data to really say anything.  Not sure what is driving the
archival, what the keys look like (eg: is the order_id the primary key and is
that carried all of the way down)...

If, in order to identify the order_item_locations rows to archive) you must join
to order_item to order_details to order_header to get the order_date (because
the fkeys only go one level), that'll pretty much be "what you have to do"

guess you could:

a) archive order_header to order_header_archive adding a constant archive_id to
it.
b) archive order_details to order_details_archive adding that same constant
using "where fkey in ( select pkey from order_header_archive where archive_id =
CONSTANT )"
c) archive order_item to order_item_archive using "where fkey in ( seelct pkey
from order_details_archive where archive_id = CONSTANT )"
d) repeat over and over....
e) go back and delete from the bottom up, using the rows you just moved into
archive to id the rows to delete...
March 28, 2004 Casey -- Thanks for the question regarding "using & in sqlplus", version
You Asked
Is there an escape character or something I can use so
I can insert an '&' (ampersand) into a field without getting
prompted for a value?
and we said...
I assume you are using SQLPlus.  By default, the macro
definition character in sqlplus is an &.  We can either

- change this character
- turn off macro preprocessing.

To change this character you would:

ops$tkyte@8i> set define ^
ops$tkyte@8i> select '&1' from dual;

'&
--
&1

ops$tkyte@8i> select '^1' from dual;
Enter value for 1: Hello
old   1: select '^1' from dual
new   1: select 'Hello' from dual

'HELL
-----
Hello

That made the define character a ^.  To turn off this feature,
you could either issue:

SQL> set define off
SQL> set scan off

for example:

ops$tkyte@8i> set scan off
ops$tkyte@8i> select '^1' from dual;

'^
--
^1
Review & Followup

Rating: 5
succinct soln to freq occuring problem December 06, 2001
Reviewer: Paul from London
straight to the point
Rating: 5
using & in PL/SQL March 28, 2004
Reviewer: Wael from KSA
I noticed that even when using '&' (ampersand) in PL/SQL block comment (e.g.
--following stmt does this & this & this ..), you will be prompted to enter the
value of "this".

Any comments.

Thanks
Followup:
SQL> set define off

in sqlplus will disable that.
Rating: 5
inserting '&' into a table with out getting prompt March 28, 2004
Reviewer: srinivasa from MA, USA
There is another way to do this.

select '&'||'1' from dual;

example:

SQL*Plus: Release 9.2.0.1.0 - Production on Sun Mar 28 15:29:00 2004

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

wrok@acharya> set define on
wrok@acharya> set scan on
wrok@acharya> select '&1' from dual;
Enter value for 1: hello
old   1: select '&1' from dual
new   1: select 'hello' from dual

'HELL
-----
hello

wrok@acharya> select '&'||'1' from dual;

'&
--
&1

wrok@acharya> select '^1' from dual;

'^
--
^1

wrok@acharya>

wrok@acharya>   select '&' from dual;

'
-
&

wrok@acharya>
March 28, 2004 Pawan -- Thanks for the question regarding "Pro*C", version 8.1.7
You Asked
What is the best way for someone to learn using Pro*c if one does not know C
very well. Why would one use Pro*c and not PL/SQL?
Thanks
and we said...
You *need* to know C well before using Pro*C.

Pro*c simply lets you embedded SQL in C code. If you do not know how to code in
C, you will not get very far at all with Pro*C.

I use pro*c only when I cannot accomplish the task efficiently in PLSQL or SQL.
In 9i, with external tables, merge, pipelined functions -- I'm very very hard
pressed to find a reason to use C.

I've use pro*c to dump data to flat files and sometimes to load it.
Review & Followup

Rating: 3
sample program September 28, 2002
Reviewer: Khalid from Yemen
Hi Tom
1.Could you show us a very simple program in Pro C.
2.What is the requirements to run it complier...etc.
Followup:
see
http://asktom.oracle.com/~tkyte/proc_makefile/
for a simple makefile for Unixland that works with pretty much every release of
Oracle since v7.

You need

a) proc
b) the C compiler supported on your operating system

An example could be:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5408706816128
near the bottom - i just did a little proc to benchmark something recently.
Rating: 3
Pro-c February 13, 2003
Reviewer: Rose from Brazil
Hi,Tom

I have doubts about ProC and I need your help!

We have 2 Server machines and 2 Client machines:
a)Server A with Oracle8i and Server B with Oracle9i
b)Machine C with Client Oracle8i and machine D with Client Oracle9i

I create an application using C and ProC.
I am confused. My application must access  Database 8i and 9i too. My doubts:
1)What version of ProC I must use: 8i or 9i?
It depends what Database my application access or in what client it executes?

2)If I create 2 aplications: App8i and App9i ,using ProC 8i and Proc 9i
respectively , I can execute both without problem in any client machine?

Your response is very important! Thanks!
Followup:
1) depends on the client software installed where the pro*c is to run.

8i pro*c can access 8i and 9i
9i pro*c can access 8i and 9i

2) yes, as the clients can support more then one client installation of the
oracle software.  You would just make sure the oracle_home was set properly for
each.
Rating: 4
Pro*C February 14, 2003
Reviewer: Rose from Br
My application app8i.exe (C and ProC 8i) access DB Oracle9i.
I executed this apllication in Client 9i, but it was unsuccessful. The
application  required Oracle8i .dll files (ex: oraclient8.dll, oranls8.dll,...).

To execute app8i.exe in Client 9i, I must have Oracle8i dll files?

Thanks!!
Followup:
No -- this statement:

To execute app8i.exe in Client 9i, I must have Oracle8i dll files?

is false (actually, it is a trick question).  YOU CANNOT execute an 8i client
application using 9i client software.

In order to execute app8i.exe, you NEED to run with the 8i client -- the 9i
client may well be installed on that same machine but that is not relevant.  You
need the 8i client for -- well -- 8i clients.
Rating: 3
Pro*C to write C Program February 14, 2003
Reviewer: robert from PA
Can you use Pro*C to write just any program you can write in C ?
Followup:
Pro*c is "greater than" C as pro*c is in fact C with embedded SQL in it.

pro*c is a precompiler that takes c that looks like:

{
 ...
  exec sql open c;
  exec sql fetch c into :n;
 ....
  printf( "n = %d\n", n );
}

and turn it into 100% C
Rating: 5
Connecting from C program to Oracle9i DB Server February 17, 2003
Reviewer: Moiz from OH USA
I have a C program and through this program I have to insert some (heavy)
mathematically processed data (say, variable x in column x of table xx) into the
Oracle 9i database. I do not know Oracle (unfortunately) and I have to submit my
program for testing to my Oracle DBA very soon.

Please tell me what connection string or driver I have to use to connect to the
server and issue the insert SQL command.

Thanks
Followup:
You need to learn just a little proc.  This will be enough pro*c actually:

main( argc, argv )
int argc;
char *  argv[];
{
EXEC SQL BEGIN DECLARE SECTION;
varchar   oracleid[31];
int       x;
EXEC SQL END DECLARE SECTION;

    strcpy( oracleid.arr, "scott/tiger@foo" );
    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);

    x = really_complex_calculation();

    exec sql insert into xx ( x ) values ( :x );

    /* Disconnect from ORACLE. */
    /* EXEC SQL COMMIT WORK RELEASE; */
    exit(0);
}
Rating: 4
But... April 29, 2003
Reviewer: A reader
Hi Tom,

Once you've written the above code, how do you call it? As far as I understand,
it needs to be pre-compiled using the pre-compiler provided by Oracle, and then
compiled again using a supported C compiler. But how do you call this executable
now? Specifically, can it be called from inside pl/sql or sql code (external
proc perhaps)? Or is the idea of this pro*c code to be called
stand-alone/independently from the OS command line or from within an
application? I guess I'm not clear on what purpose pro*c serves. Thanks in
advance.

Kashif
Followup:
this makes a command line program suitable to be used to unload large amounts of
data fairly quickly from the command line.

It is not to be called from plsql (although there is no reason why you could not
make it into an external procedure and call it.  if you have my book "Expert one
on one Oracle" -- i cover that in some detail)
Rating: 4
Can you make code more secure April 29, 2003
Reviewer: David Gibbs from Burnaby, BC Canada
Tom

You gave a Pro*C code snippet that is very similar to the way we run our C app
on an 8.1.7 database.  However I don't like the way the connection string is
hard coded and echoed out.  I want to have our developers change to running the
app with an OS authentication instead.  The developers here don't really want to
spend the time researching how to do this so I will ask you straight out.  How
can this be acheived?
Followup:
    strcpy( oracleid.arr, "/" );

that is it...
Rating: 5
linker error June 22, 2003
Reviewer: abdul wahab from Jeddah,K.S.A
Dear TOM,
   I am trying to learn using C language with Oracle.I wrote one simple program
which is as follows:
  #include <stdio.h>
#include <ctype.h>
EXEC SQL BEGIN DECLARE SECTION;
VARCHAR uid[20];
VARCHAR pwd[20];
EXEC SQL END DECLARE SECTION;
EXEC SQL INCLUDE SQLCA;
main()
{
printf("ENTER USER-NAME:");
scanf("%s",uid.arr);
printf("ENTER PASSWORD:");
scanf("%s",pwd.arr);
uid.len=strlen(uid.arr);
pwd.len=strlen(pwd.arr);
EXEC SQL WHENEVER SQLERROR GOTO errproc;
EXEC SQL CONNECT :uid IDENTIFIED BY :pwd;
printf("CONNECTED TO ORACLE.\n");
exit(0);
errproc:
printf("erroro occured.\n");
exit(1);
}

then i compiled this with pro C.
when i loaded this compiled file(corc1) in C compiler and compiled it..it went
fine with out warnings.
But when i try to run this i get this disgusting error:
linking C:\turboC2\CORC1.exe
linker error: undefined symbol '_sqlcxt' in module corc1.c
Can you please help me resolve this.
Thanks
Followup:
"disgusting", that is an interesting adjective.

link in the libraries needed for pro*c.  check out the sample makefiles.  Last I
used windoze was in the 816 timeframe -- the library back then was

$ORACLE_HOME\precomp\lib\msvc\orasql8.lib

the 8 is probably different, poke around.
Rating: 5
Pro*c debuging July 02, 2003
Reviewer: atul from India
Hi Tom,
       In pro*c program, i have a insert statement.At runtime,values coming as
parameters are invalid. For ex, a
char value comes as parameter for a column of type number.Is there any way to
find out the exact position in the insert statement which has the wrong value or
where the error starts in the insert statement.
Followup:
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7143933880166
for an idea
Rating: 5
July 02, 2003
Reviewer: A reader
Tom,

I am totally new to pro*c..I copied your sample source code
as saved it as source1.pc, now do i have to relink first by giving make command
or i have to give proc comamnd

thanks..
Followup:
see the first follow above.
Rating: 4
c or c++ July 04, 2003
Reviewer: umesh from bangalore india
Tom
I have started learning c++ for my application and for pro*c++ i  have the
prerequisite of learning c++ . My question is is it necessaary to go purely for
C to do Pro*c or everything in C is doable with C++ The relevance of this
question is related only from Pro*c/c++ point of view
Thanks
Followup:
Most things are doable in C++, there are some pro*c restrictions, such as the
setting of PARSE= on the command line which mandate the use of certain
constructs (like you need the exec sql begin/end declare section tags and such)
Rating: 4
Bulk Insert, Bulck Collect/Fetch , Bulk Update/Delete in Pro*c July 18, 2003
Reviewer: Tony from Chennai, India
1. Is Bulk Insert, Bulck Collect/Fetch and Bulk Update/Delete possible in Pro*C?
If yes, could you give an example please?

2. Will there be any performance gain if I put my business logic in stored
procedure and call it in my Pro*c  instead of doing it in Pro*C.
Followup:
1) proc .... prefetch=100

is one way,
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a97269/pc_07pls.htm#1365
another

2) yes, no, maybe, it depends.  (mostly yes in my experience but common sense
must reign supreme here.  if your business logic includes computing a eigen
vector for a set of values -- C might be faster.  If you are just making sure
that A > B+5, plsql would be)

If you can use a stored procedure to avoid pulling the data out of the database
-- over the network -- just to send it back to the database over the network to
insert again -- plsql would be good.
Rating: 4
Example for bulk update July 18, 2003
Reviewer: Tony from India
Can you give an example for bulk update in Pro*C?
Followup:
look in $ORACLE_HOME/precomp/demo/proc, there are examples.

it is fully - fully - documented as well.
Rating: 5
Try Perl July 18, 2003
Reviewer: Vin from NY
If you don't know C/Pro*C and don't want to work directly in PL/SQL, then take a
look at Perl.
Rating: 4
a question about sys_refcursor with PRO*C November 24, 2003
Reviewer: Fernando from Madrid, Spain
Inside a PRO*C program I need to call a PL/SQL stored procedure that has an
output parameter of the type sys_refcursor. How can I do that?

In case it wasn't posible, what could I do?

(The reason why the procedure uses that parameter is because it is used to keep
several rows of a query).
Followup:
create or replace procedure proc( p_cursor in out sys_refcursor )
as
begin
open p_cursor for select ename, empno from emp;
end;
/

static void process()
{
EXEC SQL BEGIN DECLARE SECTION;
    char ename_data[31] ;
    int empno_data ;
    SQL_CURSOR  dyn_cursor;
EXEC SQL END DECLARE SECTION;

    EXEC SQL ALLOCATE :dyn_cursor;

    EXEC SQL EXECUTE
    BEGIN
        proc( p_cursor=>:dyn_cursor );
    END;
    END-EXEC;

    EXEC SQL WHENEVER NOT FOUND DO BREAK ;

    while (SQLCODE == 0)
    {
        EXEC SQL FETCH :dyn_cursor INTO :ename_data, :empno_data;
        printf("Ename = %s Empno = %d\n", ename_data, empno_data) ;
    }
    EXEC SQL WHENEVER NOT FOUND CONTINUE;
    EXEC SQL CLOSE :dyn_cursor;
}
Rating: 3
A reader January 02, 2004
Reviewer: alex from uk
Tom

I have a query having joins with four tables each having around 100Millions of
data and result of this query is around 600,000 records. But when I am opening a
cursor using Pro C then I get following error message

ORA-12805: parallel query server died unexpectedly

I am using the following statment to open and fetch the records...

  EXEC SQL CONTEXT USE DEFAULT;
  EXEC SQL WHENEVER SQLERROR DO err_report(sqlca);
  EXEC SQL OPEN OpnCursor USING :x:indx;
              Error is coming from the above statement
  EXEC SQL WHENEVER NOT FOUND DO break;
  for (;;) {
    memset(rec, '\0', sizeof(rec));
    EXEC SQL FETCH OpnCursor INTO :rec INDICATOR :indRec;
    .
    .

Can you please advise me why this error is coming and how it can be sorted out.

Thanks
Followup:
contact support for things like this
Rating: 4
pcscfg.cfg file January 08, 2004
Reviewer: Fernando Sánchez from Madrid, Spain
I've recently installed Oracle 8.1.7 on Mandrake 9.1. I want to
precompile my first Pro*C program (just doing "proc my_program.pc",
not using any makefile).

I've written this pcscfg.cfg file (it was empty after installation):

sys_include=($ORACLE_HOME/precomp/public,/usr/lib/
gcc-lib/i586-mandrake-linux-gnu/3.2.2/include,/usr/include)
include=($ORACLE_HOME/precomp/public)
include=($ORACLE_HOME/rdbms/demo)

When trying to precompile, I get the following:

.................

Syntax error at line 43, column 9, file /usr/lib/g
cc-lib/i586-mandrake-linux-gnu/3.2.2/include/stdarg.h:
Error at line 43, column 9 in file /usr/lib/gcc-li
b/i586-mandrake-linux-gnu/3.2.2/include/stdarg.h
typedef __builtin_va_list __gnuc_va_list;
........1
PCC-S-02201, Encountered the symbol "__builtin_va_list" when
expecting one of the following:

auto, char, const, double, enum, 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,
union, unsigned, utext, uvarchar, varchar, void, volatile,
a typedef name,
The symbol "enum," was substituted for "__builtin_va_list" to
continue.

.......................

and a lot of more errors of the same kind.

Any idea? Thanks in advance.
Followup:
we don't really do mandrake as a supported linux distro.

redhat and united  (Conectiva Linux Enterprise Edition     SuSE Linux Enterprise
Server 8 (SLES 8)     Turbolinux Enterprise Server 8 Advanced and Basic)
Rating: 5
January 08, 2004
Reviewer: A reader
Do you use windows??
Followup:
not a chance.

well, I look out them alot (have a bird feeder outside my home office, lots of
nice birds) but as software on a computer, no.
Rating: 4
questions about pro*C performance February 15, 2004
Reviewer: Fernando Sánchez from Madrid, Spain
I have a pair of doubts:

1. Is embedding PL/SQL code, instead of SQL, bad for the performance of a pro*C
program? (not being expert at C, I find easier to write PL/SQL code)

2. Is there any important difference in terms of performance between embedding
PL/SQL anonymous blocks and embedding stored subprograms (without nested calls)?

Thanks in advance.
Followup:
1) i'd rather embedd a call to a PLSQL stored procedure :)

    exec sql execute
    begin
       foo( :x, :y );
    end;

but you can embedd anonymous blocks easily.  They'll be sent to the server,
execute entirely on the server and the results will be sent back.  In many
cases, you'll find this to be much faster than sending row after row after row
back to the C program for processing just to send the rows back to the server
again.

But -- in the end, the answer is "it depends".  If what you have to do is
computationally expensive, C might win in the speed race.  Otherwise, PLSQL will
probably be "more than fast enough"

I would skip the pro*c alltogether here if my skills lie in PLSQL.  Why
complicate it at all?

2) yes, the first hard parse must compile the entire block -- where as stored
procedures would already be compiled.  It is safer with stored procedures as
well as the dependency mechanism is in place (you can answer the question "so,
who is using this stuff" easily with stored procedures -- no so with anonymous
blocks).

I would encourage the liberal use of stored procedures over embeding monsterous
blocks of anonymous plsql in an application.
Rating: 5
basic question about pro*C when records are blocked March 28, 2004
Reviewer: Fernando Sanchez from Madrid, Spain
Hello Tom.

What can happen when a pro*C function tries to update or delete a record that is
blocked by another transaction. What kinds of sqlcode can I get? Or can the
function wait until the end of the other transaction?

Thanks in advance.
Followup:
it'll wait.

and you will of course have suffered a lost update, classic RDBMS problem
introduced by code....

what I mean is -- if your pro*c code is trying to delete a record

that someone else is simultaneously updating....

why are they even bothering to do the update, since you are going to erase it
without even looking at it.....

(eg: you might have some transactional integrity issues to work out in your
application if this is a common occurence)
March 28, 2004 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.
Rating: 5
locks not getting released March 28, 2004
Reviewer: jasdeep from patiala,india
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.

sometimes it takes 4 hours and get killed whilw othrs are killed fast. Does it
show pmon is not releasing resources properly.

thanks in advance
Followup:
pmon does not release the resources.

the SESSION does.

if it took a long time to get to the point where you killed it, it'll take a
long time to get back to the point where it began (eg: killing an update that
has been running for 30 seconds will take slightly less time to release all
resources than will killing an update that has been running for 6 hours.
"rollback" takes "time")
Rating: 5
thanks but March 28, 2004
Reviewer: jasdeep from patiala,india
hi tom,
when we go through the application server which shows that user has logged out.
and still locks are there by that username and even when user has disconnected
the session and is not on duty.
in this case can pmon will not release the rsources occupied the user.
Followup:
that is a bug in your application, nothing to do with Oracle at all.

You are using a connection pool.
You are not committing your transactions.
That is the only thing that could be happening here.
You have a bug in your code.
March 28, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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.
Rating: 5
Statspack Report for Datawarehouse March 11, 2004
Reviewer: Warren McCall from Victoria, BC.
Tom,

Excellent as always!  Below is a statspack report for a about 10 snaps taken
over about a 2 hour period.

Can you comment on the wait times, memory usage and parsing?

I see an issue with the redo/transaction being high here.  Comments?

Thanks!

STATSPACK report for

DB Name         DB Id    Instance     Inst Num Release     Cluster Host
------------ ----------- ------------ -------- ----------- ------- ------------
IDWPROD1      2334540272 idwprod1            1 9.2.0.4.0   NO      slkux1

            Snap Id     Snap Time      Sessions Curs/Sess Comment
            ------- ------------------ -------- --------- -------------------
Begin Snap:       3 11-Mar-04 13:30:33      399      64.8
  End Snap:      21 11-Mar-04 14:20:03      404      64.7
   Elapsed:               49.50 (mins)

Cache Sizes (end)
~~~~~~~~~~~~~~~~~
               Buffer Cache:     1,024M      Std Block Size:        16K
           Shared Pool Size:       128M          Log Buffer:       768K

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:             10,108.84             24,251.42
              Logical reads:             10,701.67             25,673.65
              Block changes:                 74.08                177.73
             Physical reads:                210.80                505.71
            Physical writes:                 25.14                 60.30
                 User calls:                100.74                241.68
                     Parses:                 43.72                104.89
                Hard parses:                  1.83                  4.40
                      Sorts:                116.08                278.48
                     Logons:                  0.10                  0.25
                   Executes:                153.23                367.60
               Transactions:                  0.42

  % Blocks changed per Read:    0.69    Recursive Call %:    86.73
 Rollback per transaction %:    9.69       Rows per Sort:   116.19

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:   99.99
            Buffer  Hit   %:   98.69    In-memory Sort %:   99.99
            Library Hit   %:   97.25        Soft Parse %:   95.81
         Execute to Parse %:   71.47         Latch Hit %:   99.98
Parse CPU to Parse Elapsd %:   45.61     % Non-Parse CPU:   92.83

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   73.01   84.05
    % SQL with executions>1:   84.91   78.35
  % Memory for SQL w/exec>1:   80.99   77.01

Top 5 Timed Events
~~~~~~~~~~~~~~~~~~                                                     % Total
Event                                               Waits    Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
db file sequential read                           231,339       2,023    50.23
CPU time                                                        1,383    34.34
db file scattered read                             45,995         224     5.56
direct path write                                  11,329         104     2.58
direct path read                                   70,161         102     2.54
          -------------------------------------------------------------
 Wait Events for DB: IDWPROD1  Instance: idwprod1  Snaps: 3 -21
-> 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           231,339          0      2,023      9    186.9
db file scattered read             45,995          0        224      5     37.2
direct path write                  11,329          0        104      9      9.2
direct path read                   70,161          0        102      1     56.7
library cache pin                      20          9         30   1507      0.0
SQL*Net more data to client     1,079,452          0         27      0    871.9
SQL*Net message from dblink        26,136          0         22      1     21.1
log file sync                       1,627          7         20     12      1.3
log file parallel write             3,837      3,774         19      5      3.1
library cache load lock                24          5         19    778      0.0
db file parallel write                265          0         12     47      0.2
control file parallel write         1,539          0         12      8      1.2
async disk IO                         251          0          7     27      0.2
process startup                        42          0          3     64      0.0
latch free                            589         79          2      4      0.5
db file single write                  613          0          2      2      0.5
control file sequential read        6,406          0          1      0      5.2
direct path read (lob)                122          0          1      7      0.1
log file sequential read               46          0          1     15      0.0
buffer busy waits                      51          0          1     11      0.0
db file parallel read                   2          0          0    168      0.0
enqueue                                13          0          0     25      0.0
log file switch completion              6          0          0     50      0.0
log buffer space                        3          0          0     98      0.0
SQL*Net message to dblink          26,136          0          0      0     21.1
SQL*Net break/reset to clien          454          0          0      0      0.4
row cache lock                         21          0          0      3      0.0
log file single write                   4          0          0      4      0.0
SQL*Net more data from dblin          350          0          0      0      0.3
LGWR wait for redo copy                46          0          0      0      0.0
SQL*Net message from client       227,022          0    215,823    951    183.4
virtual circuit status                 99         99      2,874  29029      0.1
wakeup time manager                    94         94      2,807  29859      0.1
jobq slave wait                       900        870      2,674   2971      0.7
SQL*Net more data from clien        2,492          0          1      1      2.0
SQL*Net message to client         227,027          0          0      0    183.4
          -------------------------------------------------------------
 Background Wait Events for DB: IDWPROD1  Instance: idwprod1  Snaps: 3 -21
-> 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             3,837      3,774         19      5      3.1
db file parallel write                265          0         12     47      0.2
control file parallel write         1,067          0         10     10      0.9
db file sequential read               282          0          3     12      0.2
direct path read                    1,584          0          2      2      1.3
db file scattered read                 78          0          1     19      0.1
direct path write                     442          0          1      2      0.4
log file sequential read               46          0          1     15      0.0
async disk IO                          66          0          0      6      0.1
enqueue                                 2          0          0    158      0.0
control file sequential read          945          0          0      0      0.8
log file single write                   4          0          0      4      0.0
LGWR wait for redo copy                46          0          0      0      0.0
rdbms ipc reply                        30          0          0      0      0.0
log file sync                           1          0          0      1      0.0
latch free                              2          1          0      0      0.0
rdbms ipc message                  13,562      6,727     17,454   1287     11.0
smon timer                             10          9      2,947 ######      0.0
pmon timer                            993        993      2,898   2918      0.8
Followup:
24k of redo per transaction does not sound obnoxiously high but only you can say
-- only you know what you are doing.

remember the thing about averages -- is that 1 really big transaction plus 1000
tiny ones giving an average of 24k or is that 1000 same sized giving 24k or
......

49minutes is too long for a statspack, the shorter the better -- 15.

You really want to start with the applications themselves.

(your soft parse is too low unless you are a data warehouse/mostly reporting
system)
Rating: 5
soft parse should be 100%? March 28, 2004
Reviewer: David from Houston, TX
<quote>your soft parse is too low unless you are a data warehouse/mostly
reporting system <quote>

Why do you say soft parse is too low? I see  from the above report, Soft Parse
%:   95.81. Soft parse ratio of 96% is not good for OLTP? Should it be close to
100%?

Also, Execute to Parse %:   71.47 . Should it also be close to 100%?

What is ROT for these stats related to parsing and executions? Thanks.
Followup:
it should be at or near 100% after the system is running for a while.  The set
of SQL is fixed in OLTP.

In client server, execute to parse should be very high.
In web based, 40% or more is to be expected (sessions can be very very "short"
in web based apps -- hence the ability to parse once and use 500 times is very
small)
March 28, 2004 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.
Rating: 4
How to track down possible causes? March 27, 2004
Reviewer: j
hi tom,

we are facing performance problems with some package procedures/functions which
used to run for years without those issues. now statspack summary reports
elapsed times per execution that in turn can not be reproduced on purpose by
just calling that specific pieces of code.

so in order to identify whether it is a matter of the time those code pieces get
executed (work load) and/or of the parameters that are used we think of just
tracing every execution of that certain package procedures/functions -
regardless of who is calling them.

is there a better way to solve the problem? can such a tracing only be achieved
by touching the code to be traced: turning trace on - executing the call -
turning trace off?
Followup:
trace them -- turn it on, run it, turn it off, analyze output.

use the 10046 level 12 and you'll get wait events and bind variables.  You'll
see if it is workload related

(low cpu, high elapsed, no wait events would lead me to think "starved for CPU")
(low cpu, high elapsed, lots o'waits -> contention for that resource)
(high cpu -> function does lots of work, tune algorith)

and so on.
Rating: 4
March 28, 2004
Reviewer: j.
... and in order to focus on certain calls the best/only way is to modify that
code to turn tracing on and off, right?

please let me ask an additional more specific question:

our statspack reports show a very low parse cpu to parse elapsed percentage (20
to 30) and an event "cpu time" on top of the top 5 timed events (up to 85% of
elapsed time are spent for it). furthermore shared pool memory usage is very
high (up to 95%). almost up to 3 hard parses per second might indicate some
"aging out" issues ...

is it the right conclusion that we should try to:
1: reduce hard parses by increasing shared pool size
2: identify possible causes of "cpu time" wait events and
3: figure out why there is so much time wasted while parsing?
Followup:
3 hard parses/second with a shared pool that is near full with lots of parsing
happening makes me think "no binds" first and foremost.

I'd look there BEFORE even considering upping the shared pool.
Rating: 4
March 28, 2004
Reviewer: j.
yes, we could double check this. but as mentioned before: the application used
to run for years without any "bind issues". most of the processing is done with
pl/sql and neither server nor client code has been changed.

as opposed to that several major changes in hard-/software architecture took
place in the last few months: 1st - rdbms release (8i -> 9i), 2nd - server
hardware and operating system (32 bit -> 64 bit) and last but not least network
configuration.

that 's why we just thought of configuration problems first.

but lets assume "bind problems": we 've investigated the shared pool content
(using the script provided in your book) and have found out some statements
"different" only due to constants instead of bind variables:

10x: SELECT value from sys.v_$mystat where statistic# = ?
 7x: SELECT RECORD_SIZE FROM V$CONTROLFILE_RECORD_SECTION WHERE TYPE = ?
 5x: select decode(value,'TRUE',1,0) into :b1 from v$option where parameter=?
 4x: SELECT "A1"."VALUE" FROM "STATS$SYSSTAT" "A1" WHERE "A1"."NAME"=? AND ...

plus  2 statements in 3 "different" versions
plus 11 statements in 2 "different" versions

our application doesn't issue those statements. so it doesn't look too bad from
our point of view.

what would you suggest to further attack our issues?
what 's the most efficient way to identify the root cause(s)?
Followup:
how do you know the application ran for years without any bind issues.
something changed here (had to).  Perhaps what changed is "we are supporting
more users" and the bind thing (which kills scalability)......

You see, I do not have as much information as you.  All I knew was -- app ran
for a while.  app appears to be slower now.  (something changed, I don't know
what)...

and now you dump a ton of *new* info here -- a ton.....

I would trace the application and see what the application is waiting on --
(that was mentioned before :)

avoid statspack for now, go after the application itself.
March 28, 2004 Riaz -- Thanks for the question regarding "Misc. Questions", version 8.1.7.2.1
You Asked
Hello Tom !

Thanks GOD I can ask question here...i was waiting for this since a long time. I
have some silly questions for you:

(1) I have a table like:

reg_no     varchar2(13)
ver_date   date

And data is Like:

12            08-10-2002 18:20:31
11            08-10-2002 18:20:34
11            08-10-2002 18:20:36
11            08-10-2002 18:20:39
11            08-10-2002 18:20:41
11            08-10-2002 18:20:44
03            08-10-2002 18:20:50
11            08-10-2002 18:20:52
11            08-10-2002 18:20:55
11            08-10-2002 18:20:57
11            08-10-2002 18:21:00
11            08-10-2002 18:21:07
12            08-10-2002 18:21:13
11            08-10-2002 18:21:16
11            08-10-2002 18:21:18
11            08-10-2002 18:21:21
11            08-10-2002 18:21:23
12            08-10-2002 18:21:26

I want to have a query that can tell me on specific time(s), whether the record
was inserted or not (just yes/no) for each day. The output should look like:

Date:  08-10-2002

               (reg_no)
       11       12      03

Mor.   Y         N       Y
AN     N         Y       Y
Eve.   Y(or N)   N       N

Date:  09-10-2002
............................
.............................
and so on

Where

Mor(Morning) is time between 1000 hrs and 1030 hrs
AN (After Noon) is time between 1400 hrs and 1430 hrs
Eve (Evening) is time between 1700 hrs and 1730 hrs

(2) How we can put triggers, packages etc in Large Pool
(3) Can i have a query which, on giving the specific year, prints the calender
of that year ? The Calender should look like the calenders we daily see.
(4) What is the diff. between Oracle Instance & Oracle Database ?

Regards
and we said...
1)
ops$tkyte@ORA920PC> select ver_date,
  2         when,
  3         nvl( max( decode( reg_no, 11, 'Y' ) ), 'N' ) "11",
  4         nvl( max( decode( reg_no, 12, 'Y' ) ), 'N' ) "12",
  5         nvl( max( decode( reg_no, 03, 'Y' ) ), 'N' ) "03"
  6    from (
  7  select distinct
  8         reg_no, trunc(ver_date) ver_date,
  9         case when to_char(ver_date,'hh24') between 0 and 11 then 'Morn'
 10                  when to_char(ver_date,'hh24') between 12 and 17 then
'After'
 11                          else 'Evening'
 12              end when
 13    from t
 14         )
 15   group by ver_date, when
 16  /

VER_DATE            WHEN    11 12 03
------------------- ------- -- -- --
08-10-2002 00:00:00 Morn    N  Y  N
08-10-2002 00:00:00 After   Y  N  N
08-10-2002 00:00:00 Evening Y  Y  Y

ops$tkyte@ORA920PC> select * from t;

    REG_NO VER_DATE
---------- -------------------
        12 08-10-2002 18:20:31
        11 08-10-2002 13:20:34
        11 08-10-2002 18:20:36
        11 08-10-2002 18:20:39
        11 08-10-2002 18:20:41
        11 08-10-2002 18:20:44
         3 08-10-2002 18:20:50
        11 08-10-2002 18:20:52
        11 08-10-2002 18:20:55
        11 08-10-2002 18:20:57
        11 08-10-2002 18:21:00
        11 08-10-2002 18:21:07
        12 08-10-2002 18:21:13
        11 08-10-2002 18:21:16
        11 08-10-2002 18:21:18
        11 08-10-2002 18:21:21
        11 08-10-2002 18:21:23
        12 08-10-2002 11:21:26

18 rows selected.

2) doesn't even make SENSE.

we invented the large pool specifically to KEEP triggers/packages/etc away from
the data we put in the large pool!!!!

The shared pool (where these things go) is managed in an LRU fashion.  It makes
sense for them there.

The large pool (where UGA, rman and PQ message buffers go) is managed in a heap
using "allocate" and "free".  Memory is not "aged" out -- it is released

3) don't know what calendar you see daily, but give

break on month skip 1
set linesize 20
column month 20
select lpad( Month, 20-(20-length(month))/2 ) month,
       "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"
  from (
select to_char(dt,'fmMonthfm YYYY') month,
       to_char(dt+1,'iw') week,
       max(decode(to_char(dt,'d'),'1',lpad(to_char(dt,'fmdd'),2))) "Su",
       max(decode(to_char(dt,'d'),'2',lpad(to_char(dt,'fmdd'),2))) "Mo",
       max(decode(to_char(dt,'d'),'3',lpad(to_char(dt,'fmdd'),2))) "Tu",
       max(decode(to_char(dt,'d'),'4',lpad(to_char(dt,'fmdd'),2))) "We",
       max(decode(to_char(dt,'d'),'5',lpad(to_char(dt,'fmdd'),2))) "Th",
       max(decode(to_char(dt,'d'),'6',lpad(to_char(dt,'fmdd'),2))) "Fr",
       max(decode(to_char(dt,'d'),'7',lpad(to_char(dt,'fmdd'),2))) "Sa"
  from ( select trunc(sysdate,'y')-1+rownum dt
           from all_objects
          where rownum <= add_months(trunc(sysdate,'y'),12) - trunc(sysdate,'y')
)
 group by to_char(dt,'fmMonthfm YYYY'), to_char( dt+1, 'iw' )
       )
 order by to_date( month, 'Month YYYY' ), to_number(week)
/

a look see

4)
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:533422350794

Review & Followup

Rating: 5
January 08, 2004
Reviewer: A reader
Rating: 5
January 08, 2004
Reviewer: A reader
Tom,

What tool/utility do you use to load table data examples in your db??

Thanks.
Followup:
unfortunately, it is cut and paste and vi.

I edit what they provide here.

I LOVE it when people give me inserts, table creates and so on!!!
Rating: 4
tiny fix January 08, 2004
Reviewer: Marcio from br
on calendar script

column month format a20
Followup:
thanks :)
Rating: 3
Hmmm... January 08, 2004
Reviewer: A reader
I find it interesting that Riaz had been waiting for a long time to ask Tom a
question, and yet when he finally got the chance all he could come up with was
silly questions! Just pulling your leg Riaz...
Rating: 5
January 08, 2004
Reviewer: A reader
!!!Note to Everybody !!!

"Please provide inserts, table creates and so on "

Thanks ...
Rating: 4
Ok January 09, 2004
Reviewer: Ram from OH,USA
Dear sir,
 why don't you use
  $exec owa_util.calendarprint ?
Bye!
Followup:
cause that would be HTML?
Rating: 5
Regarding Question. 3 January 09, 2004
Reviewer: Riaz Shahid from PRAL, Lahore, Pakistan
Tom ! Actually what i needed in Question 3 was a output like:

Month: Jan 2004

S  M  T  W  T  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 30

Month: Feb 2004

S  M  T  W  T  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

And So on....
Followup:
uh huh.

did you run the script supplied?????????

ops$tkyte@ORA920PC> break on month skip 1
ops$tkyte@ORA920PC> set linesize 20
ops$tkyte@ORA920PC> column month format a20
ops$tkyte@ORA920PC> select lpad( Month, 20-(20-length(month))/2 ) month,
  2         "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"
  3    from (
  4  select to_char(dt,'fmMonthfm YYYY') month,
  5         to_char(dt+1,'iw') week,
  6         max(decode(to_char(dt,'d'),'1',lpad(to_char(dt,'fmdd'),2))) "Su",
  7         max(decode(to_char(dt,'d'),'2',lpad(to_char(dt,'fmdd'),2))) "Mo",
  8         max(decode(to_char(dt,'d'),'3',lpad(to_char(dt,'fmdd'),2))) "Tu",
  9         max(decode(to_char(dt,'d'),'4',lpad(to_char(dt,'fmdd'),2))) "We",
 10         max(decode(to_char(dt,'d'),'5',lpad(to_char(dt,'fmdd'),2))) "Th",
 11         max(decode(to_char(dt,'d'),'6',lpad(to_char(dt,'fmdd'),2))) "Fr",
 12         max(decode(to_char(dt,'d'),'7',lpad(to_char(dt,'fmdd'),2))) "Sa"
 13    from ( select trunc(sysdate,'y')-1+rownum dt
 14             from all_objects
 15            where rownum <= add_months(trunc(sysdate,'y'),12) -
trunc(sysdate,'y')
 16  )
 17   group by to_char(dt,'fmMonthfm YYYY'), to_char( dt+1, 'iw' )
 18         )
 19   order by to_date( month, 'Month YYYY' ), to_number(week)
 20  /

MONTH
--------------------
Su Mo Tu We Th Fr Sa
-- -- -- -- -- -- --
    January 2004
             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

   February 2004
 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
......

and if the extra lines bother you -- just put it into a plsql "for loop" and use
dbms_output to format it however pleases you most.
Rating: 5
Why I am not getting output like You showed ? January 09, 2004
Reviewer: Riaz Shahid from PRAL, Lahore, Pakistan
Hello Tom !

Sorry for bothering but i am not getting the output as yours one.

Instead i get like this:

SQL> break on month skip 1
SQL> set linesize 20
SQL> column month format a20
SQL> select lpad( Month, 20-(20-length(month))/2 ) month,
  2             "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"
  3        from (
  4      select to_char(dt,'fmMonthfm YYYY') month,
  5             to_char(dt+1,'iw') week,
  6             max(decode(to_char(dt,'d'),'1',lpad(to_char(dt,'fmdd'),2)))
"Su",
  7             max(decode(to_char(dt,'d'),'2',lpad(to_char(dt,'fmdd'),2)))
"Mo",
  8             max(decode(to_char(dt,'d'),'3',lpad(to_char(dt,'fmdd'),2)))
"Tu",
  9             max(decode(to_char(dt,'d'),'4',lpad(to_char(dt,'fmdd'),2)))
"We",
 10            max(decode(to_char(dt,'d'),'5',lpad(to_char(dt,'fmdd'),2))) "Th",
 11            max(decode(to_char(dt,'d'),'6',lpad(to_char(dt,'fmdd'),2))) "Fr",
 12            max(decode(to_char(dt,'d'),'7',lpad(to_char(dt,'fmdd'),2))) "Sa"
 13       from ( select trunc(sysdate,'y')-1+rownum dt
 14                from all_objects
 15               where rownum <= add_months(trunc(sysdate,'y'),12) -
trunc(sysdate,'y')
 16     )
 17      group by to_char(dt,'fmMonthfm YYYY'), to_char( dt+1, 'iw' )
 18            )
 19      order by to_date( month, 'Month YYYY' ), to_number(week)
 20  /

MONTH
====================
Su
====================
Mo
====================
Tu
====================
We
====================
Th
====================
Fr
====================
Sa
====================
    January 2004

 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

   February 2004
 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

MONTH
====================
Su
====================
Mo
====================
Tu
====================
We
====================
Th
====================
Fr
====================
Sa
====================
29

     March 2004

 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

     April 2004

 1
 2
 3

 4
 5
 6
 7
 8
 9
10

11
12
13
14
15
16
17

MONTH
====================
Su
====================
Mo
====================
Tu
====================
We
====================
Th
====================
Fr
====================
Sa
====================
18

(and so on....)

20 rows selected.

Elapsed: 00:00:09.38

SQL> spo off
Followup:
run with a clean login.sql file -- you must have other (unknown to me) settings.

but as I said, you can use use dbms_output with "set server serveroutput on
format wrapped" to get what EVER you want.

add a

set pagesize 9999

in there as well.
Rating: 5
January 09, 2004
Reviewer: A reader
Rating: 4
For output extra lines calendar. January 09, 2004
Reviewer: Marcio from br
from your calendar script I left my linesize with 131 and got this:
ops$marcio@MARCI9I1> @cal

MONTH                Su Mo Tu We Th Fr Sa
-------------------- -- -- -- -- -- -- --
    January 2004                  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

   February 2004      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

     March 2004          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
....
Followup:
yup, i was trying to emulate the unix 'cal' command
Rating: 5
9i? January 12, 2004
Reviewer: George from Chicago
I ran in 9i and got similar results to Riaz's:

MONTH
--------------------
Su
---------------------------------------------------------------------------------
--------------------------------------------------
Mo
---------------------------------------------------------------------------------
--------------------------------------------------
Tu
---------------------------------------------------------------------------------
--------------------------------------------------
We
---------------------------------------------------------------------------------
--------------------------------------------------
Th
---------------------------------------------------------------------------------
--------------------------------------------------
Fr
---------------------------------------------------------------------------------
--------------------------------------------------
Sa
---------------------------------------------------------------------------------
--------------------------------------------------
    January 2004

 1
 2
 3

 4
 5
 6
 7
 8
 9
10

11
12
13
14
15
16
17

18
19

Works like a charm in 8i
Followup:
you have cursor_sharing=force set on so all of the constants "disappear"
Rating: 4
Help with query ... January 12, 2004
Reviewer: Anand from Austin, Texas
I have a table COUNTRY :

SQL> select * from country;

       CID COUNTRY_NAME
---------- --------------------
         1 USA
         2 CANADA

that contains country names.

and another table STATE :

SQL> select * from state;

       SID        CID STATE_NAME
---------- ---------- --------------------
         1          1 TEXAS
         2          1 VIRGINIA
         3          1 CALIFORNIA
         1          2 ONTARIO

that contains names of states under a country in the COUNTRY table.

I am looking for a query that would display the country name and all the states
under that particular coutry in a single row like :

USA Virginia Texas Alabama California
Canada Ontario Quebec

How can I achieve this?

Thanks
Rating: 5
!!! January 12, 2004
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
>>>> you have cursor_sharing=force set on so all of the constants "disappear"

Now I give 5 stars for figuring this out, with close to no information. I am
most impressed with your telepathic skills.
Followup:
(having seen this about 1,000,000 times by now helps too :)
Rating: 5
Excellent January 13, 2004
Reviewer: Riaz Shahid from PRAl, Lahore, Pakistan
Now I've found why the same query wasn't providing same results as that of
yours. Actually i had cursor_sharing set to force.when i set it back to exact,
It worked and provided me the same results.

Thanks for your great help...
Rating: 5
RE: Help with query ... (Anand) January 13, 2004
Reviewer: Marcio from br
Anand you can find it out on this site, just search
to "stragg".

To do this in 9i
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2196162600402
To do this in 8i
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:229614022562
Like this in 9i:
ops$t_mp00@MRP9I1> create or replace type string_agg_type as object
  2  (
  3     total varchar2(4000),
  4
  5     static function
  6          ODCIAggregateInitialize(sctx IN OUT string_agg_type )
  7          return number,
  8
  9     member function
 10          ODCIAggregateIterate(self IN OUT string_agg_type ,
 11                               value IN varchar2 )
 12          return number,
 13
 14     member function
 15          ODCIAggregateTerminate(self IN string_agg_type,
 16                                 returnValue OUT  varchar2,
 17                                 flags IN number)
 18          return number,
 19
 20     member function
 21          ODCIAggregateMerge(self IN OUT string_agg_type,
 22                             ctx2 IN string_agg_type)
 23          return number
 24  )
 25  /

Type created.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> create or replace type body string_agg_type
  2  is
  3
  4  static function ODCIAggregateInitialize(sctx IN OUT string_agg_type)
  5  return number
  6  is
  7  begin
  8      sctx := string_agg_type( null );
  9      return ODCIConst.Success;
 10  end;
 11
 12  member function ODCIAggregateIterate(self IN OUT string_agg_type,
 13                                       value IN varchar2 )
 14  return number
 15  is
 16  begin
 17      self.total := self.total || ',' || value;
 18      return ODCIConst.Success;
 19  end;
 20
 21  member function ODCIAggregateTerminate(self IN string_agg_type,
 22                                         returnValue OUT varchar2,
 23                                         flags IN number)
 24  return number
 25  is
 26  begin
 27      returnValue := ltrim(self.total,',');
 28      return ODCIConst.Success;
 29  end;
 30
 31  member function ODCIAggregateMerge(self IN OUT string_agg_type,
 32                                     ctx2 IN string_agg_type)
 33  return number
 34  is
 35  begin
 36      self.total := self.total || ctx2.total;
 37      return ODCIConst.Success;
 38  end;
 39
 40
 41  end;
 42  /

Type body created.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> CREATE or replace
  2  FUNCTION stragg(input varchar2 )
  3  RETURN varchar2
  4  PARALLEL_ENABLE AGGREGATE USING string_agg_type;
  5  /

Function created.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> select country_name, state_name
  2    from country natural join state
  3  /

COUNTRY_NA STATE_NAME
---------- ------------------------------
USA        TEXAS
USA        VIRGINIA
USA        CALIFORNIA
CANADA     ONTARIO

4 rows selected.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> column state_name format a30 word_wrap
ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> select country_name, stragg(state_name) state_name
  2    from country natural join state
  3   group by country_name
  4  /

COUNTRY_NA STATE_NAME
---------- ------------------------------
CANADA     ONTARIO
USA        TEXAS,VIRGINIA,CALIFORNIA

2 rows selected.
Rating: 4
Please Clarify January 13, 2004
Reviewer: Reader from Columbus, OH USA
Tom
  Please explain
  ' you have cursor_sharing=force set on so all of the constants "disappear"
'
Regards
Followup:
search for

cursor_sharing force

on this site.... to see what it means/does....

ops$tkyte@ORA9IR2> alter session set cursor_sharing=force;

Session altered.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select 'hello', 'world', 55 from dual;

'HELLO'                          'WORLD'                                  55
-------------------------------- -------------------------------- ----------
hello                            world                                    55

ops$tkyte@ORA9IR2> select sql_text from v$sql where sql_text
  2  like 'select %, %, % from dual';

SQL_TEXT
---------------------------------------------------------------------------------
--------------------------------------------------
select :"SYS_B_0", :"SYS_B_1", :"SYS_B_2" from dual

cursor sharing is an "autobinder", a crutch for bad programmers....
Rating: 3
Answer to Reader from Columbus, OH USA January 13, 2004
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
To: Reader  from Columbus, OH USA

Let me try to explain, Tom can correct me if I am wrong.

Because Oracle no longer nows that "Su" is can only be at max 2 caraters long.

Consider this:
max(decode(to_char(dt,'d'),'1',lpad(to_char(dt,'fmdd'),2))) "Su"

Becomes:
max(decode(to_char(dt,:"SYS_B_06"),:"SYS_B_07",lpad(to_char(dt,:"SYS_B_08"),:"SYS
_B_09"))) "Su"

See how the second parameter of lpad, '2' is replaced with :"SYS_B_09", so the
width of the column becomes unknown, thus defaults to max possible. sqlplus uses
the with to format the query, thus he formats them with a possibility of 4000
caraters.

Consider this example:

SQL> create table XX (a varchar2(5));

Table created.

SQL> insert into XX values ('abc');

1 row created.

SQL> select * from XX;

A
-----
abc

SQL> alter table XX modify a varchar2(50);

Table altered.

SQL> select * from XX;

A
--------------------------------------------------
abc

See how in the first there were 5 '-' caraters, in the second there are '50'.
Well, think about 4000.

I hope this helps.
Followup:
yup....
Rating: 5
Cursor Sharing and the column width January 13, 2004
Reviewer: Henry Yick from Hong Kong
I'm impressed with the finding you've made on this. In my 9i environment,
nomatter I set the cursor_sharing to EXACT or FORCE, the column width of the day
is 4 all the time. Like that:

MONTH
--------------------
Su   Mo   Tu   We
---- ---- ---- ----
Th   Fr   Sa
---- ---- ----
    January 2004

 1    2    3

 4    5    6    7
 8    9   10

11   12   13   14
15   16   17
....

Although a few column formating can solve the problem. But what is the clue of
this behaviour?

Regards
Followup:
perhaps you have a multi-byte character set.

 select * from nls_database_parameters where parameter = 'NLS_CHARACTERSET'

what is the output of that.
Rating: 4
Cursor Sharing and column width January 14, 2004
Reviewer: Henry Yick from Hong Kong
Exactly! You are right. Mine is a multi-byte system and that's why. Thanks so
much.

For the second question, I have tried the query in EXACT mode and retry the same
query by changing the cursor_sharing to FORCE mode on session level. I belive
the system will not perform autobinding and just use the cached statement if
there's an exact match(with constant). That's why the result is the same even in
FORCE mode.

Correct me if I'm wrong.
Rating: 3
I had to change January 14, 2004
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
I had to change the query (adding a dummy space somewhere) to force oracel to
hard-parse it again, otherwise the force/exact change has no effect.
Rating: 5
Thank you, Marcio January 16, 2004
Reviewer: Anand from Austin, Texas
Thanks , Marcio. It works like a charm.
Rating: 5
February 13, 2004
Reviewer: A reader
Rating: 4
Logic and Strategies March 22, 2004
Reviewer: Hien from South Australia
Tom

I am refering to the cal emulation script. Brilliant!

Could you please share with us what is the strategy to come up such a script.
The script somewhat looks complex but it produces such elegant output.

Perhaps you can explain step-by-step how you came up with the script.

Thank heaps.

Hien
Followup:
I'll have to write "zen and the art of sql" someday -- hard to explain -- but if
you start with the innermost inline view, I started there and built out
iteratively.

select lpad( Month, 20-(20-length(month))/2 ) month,
       "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"
  from (
select to_char(dt,'fmMonthfm YYYY') month,
       to_char(dt+1,'iw') week,
       max(decode(to_char(dt,'d'),'1',lpad(to_char(dt,'fmdd'),2))) "Su",
       max(decode(to_char(dt,'d'),'2',lpad(to_char(dt,'fmdd'),2))) "Mo",
       max(decode(to_char(dt,'d'),'3',lpad(to_char(dt,'fmdd'),2))) "Tu",
       max(decode(to_char(dt,'d'),'4',lpad(to_char(dt,'fmdd'),2))) "We",
       max(decode(to_char(dt,'d'),'5',lpad(to_char(dt,'fmdd'),2))) "Th",
       max(decode(to_char(dt,'d'),'6',lpad(to_char(dt,'fmdd'),2))) "Fr",
       max(decode(to_char(dt,'d'),'7',lpad(to_char(dt,'fmdd'),2))) "Sa"
  from ( select trunc(sysdate,'y')-1+rownum dt
           from all_objects
          where rownum <= add_months(trunc(sysdate,'y'),12) - trunc(sysdate,'y')

)
 group by to_char(dt,'fmMonthfm YYYY'), to_char( dt+1, 'iw' )
       )
 order by to_date( month, 'Month YYYY' ), to_number(week)
/

I started with the set of dates for the year:

select trunc(sysdate,'y')-1+rownum dt
           from all_objects
          where rownum <= add_months(trunc(sysdate,'y'),12) - trunc(sysdate,'y')

then I "pivoted" using a max/decode routine I use lots...

then I used sqlplus to format it a tiny bit...
Rating: 3
Another Misc.Question March 23, 2004
Reviewer: Ram from Bangalore,KA,India
Dear Sir,
 What may be wrong with the following query?
SQL> select deptno,max_sal,(select ename from emp where sal = max_sal and
  2  rownum = 1) max_ename,min_sal,(select ename from emp where sal =
  3  min_sal and rownum = 1) min_ename from(
  4    select deptno,max(sal) as max_sal,min(sal) as min_sal from emp
  5      group by deptno)
  6  /
  select deptno,max(sal) as max_sal,min(sal) as min_sal from emp
                    *
ERROR at line 4:
ORA-00979: not a GROUP BY expression

Please do reply.
Followup:
  1  select deptno,max_sal,(select ename from emp where sal = max_sal and
  2      rownum = 1) max_ename,min_sal,(select ename from emp where sal =
  3      min_sal and rownum = 1) min_ename from(
  4        select deptno,max(sal) as max_sal,min(sal) as min_sal from emp
  5*         group by deptno)
scott@ORA9IR2> /

    DEPTNO    MAX_SAL MAX_ENAME     MIN_SAL MIN_ENAME
---------- ---------- ---------- ---------- ----------
        10       5000 KING             1300 MILLER
        20       3000 SCOTT             800 SMITH
        30       2850 BLAKE             950 JAMES

runs "fine" for me in 8.0.6 and up.....
Rating: 3
Nice March 27, 2004
Reviewer: Ram from Bangalore,KA,India
Hi Tom,
What do the group functions Variance and Stddev provide?
SQL> select variance(sal) from emp;

VARIANCE(SAL)
-------------
   1398313.87

SQL> select stddev(sal) from emp;

STDDEV(SAL)
-----------
 1182.50322
I have used them but don'n know what they represent?
Could you please explain?
Bye!
Followup:
google em.  they are standard statistical functions.  they come from "math"

(if you don't know what they are, you probably don't need them)
Rating: 5
Statistical measures March 27, 2004
Reviewer: Jim from wilsonville, OR
standard deviation is the square root of the varience.  They are statistical
measures of how tightly or loosly a series of measurements "group" around a
mean. (average)  If you have ever heard the expression in school "Graded on a
curve" it refers to adjusting grades to follow a statistical bell curve (usually
what is refered to as a normal distribution).  (there are a ton of other
distributions used for a variety of things)

Tom is correct, if you don't know what they are then you probably don't need
them.  However, they are important.
Rating: 3
OK March 28, 2004
Reviewer: Kumar from Pune,India
Hi Tom,
What is a multicolumn subquery?Where they can be useful?
Bye!
Followup:
select * from emp
where (deptno,job) in ( select deptno, job from some_other_table );

try getting the right answer without using a multi-column subquery....  That is
where they are useful.
March 28, 2004 Scott -- Thanks for the question regarding "Constraint storage", version 8.1.7.4
You Asked
Hi Tom,

Consider this:

I create a table, T1 with columns COL1, COL2, COL3 (let's say)...

create table T1 ...
...
tablespace T_A_01;

Then I index COL1 (it's a number type)...
create index I1 ...
...
tablespace I_A_01:

Next I say "alter table T1 add constraint PK_BLAH ... primary key" for COL1, but
with "using index tablepace I_A_01"

When I use Enterprise Manager (DBA Studio, or whatever) it reports that the
storage clause for the PK constraint is "<Default>", which our DBA tells us
(perhaps correctly, I don't know) means the default for the table (i.e. T_A_01),
and not for the index (i.e. I_A_01).

He also suggests that Oracle will not use the index I created to enforce the
constraint, but will, instead create another, "implicit" index to enforce the
constraint.

If I say... "alter table T1 disable constraint cascade" Oracle will drop index
I1.  If I then "alter table T1 add constraint ... primary key .. using index
tablespace I_A_01), Oracle will create an index of its own accord.

This time DBA studio reports the storage for the constraint to be in I_A_01.

Question:  Why doesn't Oracle use the index I created?

Going back to the first scenario where the intended-to-be primary key column was
already indexed.  EM reports that the storage of the constraint will be
"Default".  If I rename the index I1 to PK_BLAH, then EM will tell me that it is
in tablespace I_A_01.

Question: Why?

What are the mechanics of what Oracle is doing here?

Another question:  What data dictionary view can tell me what EM is showing me
here?

(ps:  I'm not a DBA, but when I asked our DBA that question, his answer was
"Err, I don't know")

Many thanks.
and we said...
question: why didn't you verify that it didn't use the index first?

(cause it did use the index).  tis easy to see that.  I cannot answer exactly
what query EM might be running -- it is a GUI, I don't really use them.  I can
however show you exactly how to see whats what here.

tkyte@ORA8I> create table t1 ( c1 number, c2 number, c3 number );
Table created.

tkyte@ORA8I> create index i1 on t1(c1);
Index created.

tkyte@ORA8I> alter table t1 add constraint t1_pk
  2  primary key(c1);
Table altered.

tkyte@ORA8I> drop index i1;
drop index i1
           *
ERROR at line 1:
ORA-02429: cannot drop index used for enforcement of unique/primary key

apparently, it did kidnap my index!

tkyte@ORA8I> select index_name from user_indexes
  2  where table_name = 'T1';

INDEX_NAME
------------------------------
I1

tkyte@ORA8I>

the very fact another isn't created confirms this further...
March 28, 2004 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.
Rating: 5
Method to know the current load on Oracle server. January 29, 2004
Reviewer: Pat from US
Hi Tom,
In my reporting application users will create alot of SQL's on real time basis,
which I store in Database table ( these sqls can return huge data of 100,000 + )
.Now based on the Oracle Server Load ( cpu utilization ) I would like to execute
these SQL's. I do not want to execute these SQL's when already the Oracle server
is under heavy load due to some other operations. e.g How do I get a callback
mentioning that the server load ( cpu utilization ) is less that say less than
50% giving me a green signal to automatically execute some SQL's and stop when
the load increases.
Followup:
rather then you write tons of code to implement a resource manager, why don't
you just use the builtin one?
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96521/part5.htm#435958
Rating: 2
CPU costing February 23, 2004
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
Since we are talking about CPU usage I have a small question.

Since 9i there's the concept of CPU costing. Could one expect to see somehow
lower CPU usage due to more efficient use of CPU, or more CPU usage (and less IO
for example) due to the system been aware of the spare CPU time available.

Also, what kind of queries would change plans if we had CPU costing enabled ?
Followup:
One could expect to see "both" depending on the circumstances

All kinds (every in short) of queries could change.  You are changing the
mathematical model that spits out the cost of a given plan.  In effect every
query plan is affected.
Rating: 3
Follow up February 23, 2004
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
I was hoping that you would provide 1 or 2 examples of a query plan that flips
due to enabling CPU costing.

Or if not examples, theritcally specific cases in which it will flip.
Rating: 5
What is a blade server? March 28, 2004
Reviewer: reader
I have been hearing about blade server but I don't know what it is and how is it
different frpm a SMP? Thanks.
Followup:
google "blade server"
http://search390.techtarget.com/sDefinition/0,,sid10_gci770169,00.html
the difference is - a blade typically means SMP.  SMP does not mean "blade".
blades are interchangeable "pizza boxes" that fit into a rack.  cheap enough to
throw away when then break (like razor blades).  easy to expand, easy to
reconfigure, easy to manage -- all in a big cabinet.
March 29, 2004 Sudhir -- Thanks for the question regarding "trapping transaction information", version 8.1.7.2
You Asked
Hello Tom! Hope your vacation was great. Also, thanks for your response on my
prior question.

This is my new question: In our application a user may update/insert/delete
row(s) from many tables. If we want to replay the user action we need to trap
the user transactions. One other problem is all the users have their own id but
backend server which does work for them always logs into db with the same
userid. Thus using LogMiner etc very difficult. Also I dont have any say in
asking for code change to reflect such information. Mainly if this can be done
on database non-intrusively then it is great.

I was thining along following lines
1) create a sequence to be used as transaction number (unless there is such
thing as a transaction number accessible in trigger)
2) use a global temporary table.
3) Put triggers on each table such that each checks if a row exists in global
temp. table. If one exists then you know some other trigger fired before you and
you are either in the middle of transaction or at the end of one. If no row
exists then insert sequence number in it and use it in "4" below.
4) Of course after every trigger fire, we will save all table row information
and transaction number and sysdate in txn_info table.
5) at the end of transaction the row in global temp will be gone unless it has
some use

So I should have a trail table like below

TXN#   TABLE_NAME    ACTION    TXN_DATE            OLD_VAL  NEW_VAL
   1   CUST          Delete    09/08/2001 12:12:30 some     some
   2   CUST_ADDR     Delete    09/08/2001 12:12:35 some     some
   1   CUST_ADDR     Update    09/08/2001 12:12:36 some     some
   2   ACCT_INFO     Add       09/08/2001 12:12:36 some     some
...

etc

So now if I say
select TXN#, TABLE_NAME, ACTION from tran_info
order by txn#, txn_date

i should get all the transactions.

Do you think this will work or is there any better way? Mind you I have really
not developed any complex triggers or such application.

thanks for sea of information you provide.

Sudhir
and we said...
Well, you could use our transaction id instead (less work on your part, less
triggers and you could tie this into logminer after the fact).  A simple select
would get it for you.  You might consider having your DBA create a view that
replaces the query below and granting you select on that view (so you don't need
select on sys.v_$transaction, v_$session and v_$mystat, just the view).

ops$tkyte@ORA8I.WORLD> create table t ( x int );
Table created.

ops$tkyte@ORA8I.WORLD> create or replace trigger t_trigger
  2  after insert or update or delete on t
  3  for each row
  4  declare
  5      l_transaction_id varchar2(75);
  6  begin
  7      select xidusn || '.' || xidslot || '.' || xidsqn
  8        into l_transaction_id
  9        from v$transaction
 10       where addr = ( select taddr
 11                        from v$session
 12                       where sid = ( select sid
 13                                       from v$mystat
 14                                      where rownum = 1 )
 15                    );
 16
 17      dbms_output.put_line( l_transaction_id );
 18  end;
 19  /
Trigger created.

ops$tkyte@ORA8I.WORLD> insert into t values ( 1 );
28.48.74514

1 row created.

ops$tkyte@ORA8I.WORLD> insert into t values ( 2 );
28.48.74514

1 row created.

ops$tkyte@ORA8I.WORLD> commit;
Commit complete.

ops$tkyte@ORA8I.WORLD> insert into t values ( 3 );
29.64.74394

1 row created.
Review & Followup

Rating: 5
very good information September 08, 2001
Reviewer: A reader
I thank you for the information. I will play with it.
Tom you are the best and the most humble person I have ever met.

thanks again!

Sudhir
Rating: 2
See Tom's more recent transaction_id recommendation March 28, 2004
Reviewer: Duke Ganote from in Warsaw Indiana USA (Orthopaedic Capitol of the World)
I found the query doesn't always seem to reliably provide a transaction_id (at
least in my hands).  However, Tom's response to a later question "Locking and
Cocurrency in 9i.", found at
http://asktom.oracle.com/pls/ask/f?p=4950:8:4472160545442891124::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:8913646338893,
recommends using the DBMS_TRANSACTION package,
which even in my hands appeared reliable.

Interestingly, combining a transaction_id with the 9i flashback's get SCN
function or 8i's COMMITSCN feature (see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2591957673673#2592288690458
for both) and it appears one could
construct a Do-It-Yourself (DIY) replication.
Followup:
how could this query not "reliably" provide a transaction_id?

You have a session (so the v$session query works)

and if the trigger fires you have a v$transaction entry for sure.

so, i'd be at a loss to understand the circumstances whereby this won't work.
do you have some sort of details for this?

You'd need to use the commitscn (that is what our replication uses) in order to
get the proper ORDERING of transactions.  (but if you DIY replicate, you've
totally missed the point of purchased software.......)
Rating: 4
DIY Fat-fingering March 29, 2004
Reviewer: Duke Ganote from Warsaw, Indiana USA (Orthopaedic Capitol of the World)
Tom-- I'll have to embarass myself by admitting I'm unable to reproduce the
results after a full night's sleep...perhaps I butter-fingered the sys view I
created for the query.  I'll attribute the erratic results to being too fatigued
that night!  Apologies.  Certainly, frustration led me to scour your site
further and find your DBMS_TRANSACTION.LOCAL_TRANSACTION_ID recommendation,
which even I couldn't foul up despite fatigue.

As for "why" I was experimenting with DIY replication: it primarily
self-educational while reading your "Expert 1-on-1 Oracle" appendix on log
miner, but partly (1) because our corporate standard for data transport is a
non-Oracle product, (2) the goal is "low latency" or "real time" replication,
and (3) we're on 8.1.7.4 for a while yet.  Thus, I was exploring means to
pushing granular changes to a central data store.  I'm a big fan of not
re-inventing the wheel; I assume 9i's Change Data Capture (CDC), apparently
based on log miner, will provide this functionality, which I was reading about
in Oracle magazine's Nov/Dec 2003 issue.

Thank you!
March 29, 2004 Alan -- Thanks for the question regarding "Validation of a number within a VARCHAR2 field", version 8.1.7
You Asked
Hi Tom,
Could you tell me how to quickly determine whether values in a VARCHAR2 field
are actually numbers? I would like to do this using a single SQL statement,
rather than a explicit cursor in a block.
The problem is I'm working on a system that's already been designed and would
like to speed up the validation of the data within a particular table, which
holds both numbers and characters in a certain column.
For example, a much simplified look at my table is:

TMP
TMPID  NUMBER,
TMPVALUE  VARCHAR2(30) (Contains either text or number data)
TMPTYPE   VARCHAR2(1) (N = number, T = text)

It is possible for the TMPVALUE field to hold text even when the TMPTYPE field
is 'N' (due to lack of access to the data-entry part of the system, I can't
change this area!).

So, I need to find out which records contain text where TMPTYPE = 'N'. There can
be hundreds of thousands of entries, and I'd like to avoid using an index-based
function. I tried putting a function into the where clause, but this obviously
slows the query down big time.

Thanks for the help (I've got a feeling I'm being really stupid about this one!)
and we said...
Ok, we can start by knowing that any string that has something other then
0123456789. cannot be a number and things with those MIGHT be a number (eg:
8.1.7 is not a number, its a version).

We can use a predicate like this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( tmpvalue varchar2(10),
tmptype varchar2(1) );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( '123.54', 'N' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( '123.54a', 'N' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( '123.54.5', 'N' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace function my_to_number(
p_num in varchar2 ) return number
  2  as
  3          x number;
  4  begin
  5          x := to_number( p_num );
  6          return 1;
  7  exception
  8          when others then return 0;
  9  end;
 10  /

Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
  2    from t
  3    where tmptype = 'N'
  4      and decode(
replace(translate(tmpvalue,'1234567890.','00000000000'),'0',''),
  5                                          NULL, my_to_number(tmpvalue),
  6                                              0 ) = 0;

TMPVALUE   T
---------- -
123.54a    N
123.54.5   N

it'll only call the PLSQL function, my_to_number, when we have something that is
all digits and dots.  The replace/translate will more efficiently find the text
strings.  If you are really daring -- you can assume that anything with digits
and dots is a number and skip the my_to_number test...
Review & Followup

Rating: 4
another solution? January 14, 2002
Reviewer: Barbara Boehmer from Norwalk, CA USA
What about negative numbers?  And, how about this?:

create or replace function numbers_only
  (p_num in varchar2)
  return number deterministic
as
  x number;
begin
  x := to_number (p_num);
  return x;
exception
  when others then return null;
end numbers_only;
/
select *
from   t
where  tmptype = 'N'
and    numbers_only (tmpvalue) is null
/
Followup:
sure, add a dash into the replace function and turn the dash into a zero.  would
hope that the example would lead to a real implementation...

Your approach would work but would

a) call the plsql function for every row -- I wanted to avoid that when
possible, hence the decode trick (its faster to not call the plsql when
possible)

b) record tmpvalue values that are NULL as not being a number -- but they are a
number.  Adding the is not null as you do below corrects that.
Rating: 4
additional January 14, 2002
Reviewer: Barbara Boehmer from Norwalk, CA USA
It just dawned on me that there might be null values in the tmpvalue column, so
if you are looking for text strings, not just non-numbers, revise that to:

select count(*)
from   t
where  tmptype = 'N'
and    numbers_only (tmpvalue) is null
and    tmpvalue is not null
/
Rating: 4
issues January 14, 2002
Reviewer: Barbara Boehmer from Norwalk, CA USA
I see two issues:

1.  Unless we use a function to evaluate every value, are we really sure whether
it is or is not a number?  In addition to negative numbers, what about things
like 1+2.  Is that legitimate?  Should we count that as 3?  Maybe it depends on
the data?

2.  Which is faster, one deterministic function, or decode and replace and
translate and sometimes a function, and how do we tell?  Could that, once again,
depend on the data?
Followup:
1) the decode trick shorts circuts us from having to call PLSQL when the string
is DEFINITELY not a number -- we call the function for everything that MIGHT be
a number.  We would not count 1+2 as a number (we could using

  execute immediate 'select ' || p_str || ' from dual' into l_number

instead of just assigning the string to a number but that would be even
slower...

2) deterministic doesn't even come into play here, it is not relevant.  it
affects nothing.

The decode( replace(translate())....., my_to_number() )  will be faster if it
can short circut having to call my_to_number() a couple of time (decode,
replace, translate are pretty fast).  It will depend on the data, but since we
know some of the data is bad-- we'll short circut a couple of times so it
probably is worth the effort.
Rating: 4
deterministic January 14, 2002
Reviewer: Barbara Boehmer from Norwalk, CA USA
Tom,

You say, "deterministic doesn't even come into play here, it is not relevant.
it affects nothing."?

My Oracle Advanced PL/SQL Student Guide page 2-28 (kit produced 2/23/01 BOM
Updated 7/20/00) says, "The hint DETERMINISTIC helps the optimizer avoid
redundant function calls."  Further up on the page, it says, "Means the SQL may
choose to use a cached copy of the return result rather than actually invoking
the function."

So,if there are many of the same value, wouldn't the function potentially only
be invoked once per value, using cached results for repeat values, thereby
running faster?

I jumped in on this one because several of us have been bouncing around various
ideas about validation of numbers, dates, and so forth, with and without
functions, with and without deterministic, on the OTN forums, the PL/SQL
pipelines, and Orafans.  I am hoping to get detailed clarification, so that, in
the future, I can just provide a link to this as a final reference.  I think
most of us, at least on the OTN forums, regard you as the ultimate authority.

Also, I know you said that the combination of decode and replace and translate
with an occasional function call is faster than multiple function calls, but
will you please provide some more detail as to why or better yet some proof?  I
tried SET AUTOTRACE ON and running your function and mine and saw no difference,
but it was on a very small sample of data.  I didn't try TKPROF.

Thanks,
Barbara
Followup:
There is NO way the optimizer will be able to avoid redundant calls here as
there are no redudant calls to be removed.

It would not reuse the values for called plsql within the same result set.

So, here is the complete example:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( tmpvalue varchar2(10),
tmptype varchar2(1) );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select 1234567890, 'N' from
all_objects;

17130 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select 'abcdefghij', 'N' from
all_objects;

17130 rows created.

so, I've made half the rows numbers, half the rows not numbers...

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package my_state
  2  as
  3     cnt number;
  4  end;
  5  /

Package created.

that'll be so we can count how many times our function was invoked...

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace function non_deterministic(
p_num in varchar2 ) return number
  2  as
  3     x number;
  4  begin
  5      my_state.cnt := my_state.cnt+1;
  6     x := to_number( p_num );
  7     return 1;
  8  exception
  9     when others then return 0;
 10  end;
 11  /
Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace function is_deterministic(
p_num in varchar2 ) return number
  2  DETERMINISTIC
  3  as
  4     x number;
  5  begin
  6      my_state.cnt := my_state.cnt+1;
  7     x := to_number( p_num );
  8     return 1;
  9  exception
 10     when others then return 0;
 11  end;
 12  /
Function created.

and those are the two functions.... Now, we'll test the decode vs no decode
and deterministic vs non deterministic

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics;
Table analyzed.

using CBO -- just in case this deterministic option would be a function of
it

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set sql_trace=true;
Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec my_state.cnt := 0
PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*)
  2    from t
  3   where tmptype = 'N'
  4     and decode(
replace(translate(tmpvalue,'+-1234567890.','0000000000000'),'0',''),
  5                 NULL, non_deterministic(tmpvalue), 0 ) = 0
  6  /

  COUNT(*)
----------
     17130

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( my_state.cnt );
17130

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec my_state.cnt := 0
PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*)
  2    from t
  3   where tmptype = 'N'
  4     and decode(
replace(translate(tmpvalue,'+-1234567890.','0000000000000'),'0',''),
  5                 NULL, is_deterministic(tmpvalue), 0 ) = 0
  6  /

  COUNT(*)
----------
     17130

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( my_state.cnt );
17130

PL/SQL procedure successfully completed.

so, that shows that both types of functions were called 17,130 times -- there
is no short circut there.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec my_state.cnt := 0
PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*)
  2    from t
  3   where tmptype = 'N'
  4     and non_deterministic(tmpvalue) = 0
  5  /

  COUNT(*)
----------
     17130

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( my_state.cnt );
34260

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec my_state.cnt := 0

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*)
  2    from t
  3   where tmptype = 'N'
  4     and is_deterministic(tmpvalue) = 0
  5  /

  COUNT(*)
----------
     17130

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( my_state.cnt );
34260

PL/SQL procedure successfully completed.

and again -- both were called the same number of times...  Now for the
tkprof

********************************************************************************

select count(*)
  from t
 where tmptype = 'N'
   and decode(
replace(translate(tmpvalue,'+-1234567890.','0000000000000'),'0',''),
               NULL, non_deterministic(tmpvalue), 0 ) = 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      1.26       1.26          0         89          5           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      1.26       1.26          0         89          5           1

********************************************************************************

select count(*)
  from t
 where tmptype = 'N'
   and decode(
replace(translate(tmpvalue,'+-1234567890.','0000000000000'),'0',''),
               NULL, is_deterministic(tmpvalue), 0 ) = 0

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      1.29       1.29          0         89          5           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      1.30       1.29          0         89          5           1

********************************************************************************

select count(*)
  from t
 where tmptype = 'N'
   and non_deterministic(tmpvalue) = 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        2      5.16       5.16          0         89          5           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      5.16       5.17          0         89          5           1

********************************************************************************

select count(*)
  from t
 where tmptype = 'N'
   and is_deterministic(tmpvalue) = 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      5.13       5.13          0         89          5           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      5.13       5.13          0         89          5           1

here, bypassing calling PLSQL when possible made a huge difference, huge
difference.  Now, the cool thing is -- looking forward to 9i, the numbers on the
same exact box were:

********************************************************************************

select count(*)
  from t
 where tmptype = 'N'
   and decode(
replace(translate(tmpvalue,'+-1234567890.','0000000000000'),'0',''),
               NULL, non_deterministic(tmpvalue), 0 ) = 0

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.08          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.71       0.72          0         96          3           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.71       0.80          0         96          3           1

********************************************************************************

select count(*)
  from t
 where tmptype = 'N'
   and decode(
replace(translate(tmpvalue,'+-1234567890.','0000000000000'),'0',''),
               NULL, is_deterministic(tmpvalue), 0 ) = 0

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.73       0.73          0         96          3           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.74       0.73          0         96          3           1

********************************************************************************

select count(*)
  from t
 where tmptype = 'N'
   and non_deterministic(tmpvalue) = 0

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.04          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      1.03       1.04          0         96          3           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      1.05       1.08          0         96          3           1

********************************************************************************

select count(*)
  from t
 where tmptype = 'N'
   and is_deterministic(tmpvalue) = 0

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      1.01       1.00          0         96          3           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      1.02       1.01          0         96          3           1

so, there is still a marked improvement by short circuting the call to PLSQL
but it is not nearly as much AND in fact, the query that doesn't short circut in
9i actually runs faster then the 8i query showing the speed up in 9i related to
calling PLSQL from SQL (it is much reduced in 9i)

Deterministic is mostly useful with function based indexes....
Rating: 4
additional validation scenario January 14, 2002
Reviewer: Barbara Boehmer from Norwalk, CA USA
Tom,

It occurred to me that which method is preferred might depend on the scenario
and how you want to handle it.  In the original post above, Alan apparently
wants to identify the invalid data, the character strings in the supposed
numeric field.  I don't know if he then has plans for some data correction or
what.

I have a slightly different approach when transferring data from old systems
where the data wasn't validated to new ones where it is validated.  If the data
in the old table is invalid, I typically just want a null value inserted into
the corresponding column in the new table and don't plan to try to clean up the
old data.  For example, if C is found in an age column that should be numeric, I
don't plan to try to figure out what they meant to input when they typed C.  So,
I typically use something like (simplified for demonstration purposes):

insert into new_table (number_column)
select numbers_only (varchar2_column)
from   old_table;

Would this be the best approach under these circumstances, or would you
recommend something else?:

Thanks again,
Barbara
Followup:
sure -- that works and works cleanly.  You can use the decode/replace/translate
trick to filter even faster as shown above.
Rating: 5
Nice trick January 15, 2002
Reviewer: Alan from Frankfurt, Germany
An additional problem I have is that the numbers aren't necessarily using the
same decimal format. For example some are entered using british nls settings and
others german. This results in a mixture of number types such as 1,2399.34,
12399.34, or 12399,34. So I think I'll definitately need a user function in
there somewhere (or do you think extra replace would do the trick).
My only goal is to identify which rows do not contain valid numbers, so I can
flag them.
Followup:
you would add +- and , to the list in the replace (add three 0's as well) and
then code my_to_number to try various formats when converting the number....
Rating: 5
Excellent! January 15, 2002
Reviewer: Barbara Boehmer from Norwalk, CA USA
Tom,

Thank you very much for providing the explanation and proof in the form of a
detailed example including TKPROF.  It was exactly what I was looking for.
Anytime this subject comes up, I can now just post a response using your method
with decode, replace, translate, (including + and - just in case) and your
function (without deterministic) and provide a link to this for the details.

Thanks,
Barbara
Rating: 5
Great January 16, 2002
Reviewer: Alan from Frankfurt, Germany
Thanks for the help Tom. Fantastic as always.
Rating: 5
Optimizer And Deterministic Function January 18, 2002
Reviewer: Mike from VA
Tom,

In your example, the function is referenced only once for each row, it makes
sense the optimizer doesn't do anything to it, just like you said, "there are no
redudant calls to be removed". Also the function modifies the value of cnt in
my_state package, it probably should be called every time it's referenced.
However, in the following example, I thought the optimizer should be able to
remove some redudant calls, but it didn't.

SQL> create table t as select 1 n from all_objects;
Table created.

SQL> create or replace function f1 return number
  2  as
  3     i number;
  4  begin
  5     for i in 1..100 loop
  6        null;
  7     end loop;
  8     return 1;
  9* end;
SQL> /
Function created.

SQL> l
  1  create or replace function f2 return number deterministic
  2  as
  3     i number;
  4  begin
  5     for i in 1..100 loop
  6        null;
  7     end loop;
  8     return 1;
  9* end;
SQL> /
Function created.

SQL> analyze table t compute statistics for table for all columns;
Table analyzed.

SQL> alter session set timed_statistics = true;
Session altered.

SQL> alter session set sql_trace = true;
Session altered.

SQL> select max(f1), count(f1) from t where f1 = 1;

   MAX(F1)  COUNT(F1)
---------- ----------
         1      25043

SQL> select max(f1), count(1) from t where f1 = 1;

   MAX(F1)   COUNT(1)
---------- ----------
         1      25043

SQL> select max(f2), count(f2) from t where f2 = 1;

   MAX(F2)  COUNT(F2)
---------- ----------
         1      25043

SQL> select max(f2), count(1) from t where f2 = 1;

   MAX(F2)   COUNT(1)
---------- ----------
         1      25043

SQL> select count(*) from t where f1 = 1;

  COUNT(*)
----------
     25043

SQL> select count(*) from t where f2 = 1;

  COUNT(*)
----------
     25043

Now look at the trace file:

***************************************
select max(f1), count(f1)
from
 t where f1 = 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.08       0.08          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      6.45       6.48          0         42         12           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      6.53       6.56          0         42         12           1

********************************************************************************

select max(f1), count(1)
from
 t where f1 = 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.07       0.07          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      3.29       3.31          0         42         12           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      3.36       3.38          0         42         12           1

********************************************************************************

select max(f2), count(f2)
from
 t where f2 = 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.07       0.07          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      6.52       6.55          0         42         12           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      6.59       6.62          0         42         12           1

********************************************************************************

select max(f2), count(1)
from
 t where f2 = 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.07       0.07          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      3.48       3.50          0         42         12           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      3.55       3.57          0         42         12           1

********************************************************************************

select count(*)
from
 t where f1 = 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.07       0.07          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.02       0.02          0         42         12           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.09       0.09          0         42         12           1

********************************************************************************

select count(*)
from
 t where f2 = 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.07       0.07          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.02       0.02          0         42         12           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.09       0.09          0         42         12           1

It looks like the deterministic key word doesn't make any difference. If I have
count(f1) and max(f1) in the select statement, it's going to call function
twice. It's also interesting that the optimizer only evalute f1 = 1 once for the
whole table instead of once for each row even when f1 is not a deterministic
function. When I run the following query twice, it gives different results.

SQL> l
  1  select * from
  2   ( select dbms_random.random r from t )
  3  where r > 0
  4* and rownum < 10
SQL> /

no rows selected

SQL> /

         R
----------
-911661048
-664769205
 150161117
-397720797
-864258392
-1.555E+09
-845515666
-1.723E+09
 765505919

9 rows selected.

Thanks,
Mike
Followup:
That is sort of what I said -- it doesn't factor it out in these cases, you can
HELP it to do that.

DETERMINISTIC, so far, it mostly useful with function based indexes (where it is
MANDATORY).

There are cases where it will factor it out on its own in the where clause,
consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package state_pkg
  2  as
  3          cnt number;
  4  end;
  5  /
Package created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace function f return number
  2  as
  3  begin
  4          state_pkg.cnt := state_pkg.cnt+1;
  5          return 1;
  6  end;
  7  /
Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop table t;
Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t as select rownum r from
all_objects;
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec state_pkg.cnt := 0;
PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where r = f();

         R
----------
         1

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( state_pkg.cnt );
17137

PL/SQL procedure successfully completed.

so, there is called it once per row apparently, sort of as we expected by
now...

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec state_pkg.cnt := 0;
PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where r = (select f() from
dual);

         R
----------
         1

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( state_pkg.cnt );
1

PL/SQL procedure successfully completed.

Ahh, interesting, by hiding it in a subquery, it did it once per QUERY.  But,
you'll discover this doesn't always work as we'll see in a moment...

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec state_pkg.cnt := 0;
PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t, (select f() x from dual)
where r = x;

         R          X
---------- ----------
         1          1

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( state_pkg.cnt );
17138

PL/SQL procedure successfully completed.

You see, if we turned the "where r = (subquery)" into a join (can happen) we
are back to once/row.  What we have to do then is involve rownum or some other
thing (group by, order by) in the subquery to make us materialize the subquery
and use that instead, so:

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec state_pkg.cnt := 0;
PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t, (select f() x from dual
where rownum=1) where r = x;

         R          X
---------- ----------
         1          1

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( state_pkg.cnt );
1

PL/SQL procedure successfully completed.

Now, we are back to one.  This all changes however, if we index R:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx on t(r);
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec state_pkg.cnt := 0;
PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where r = f();

         R
----------
         1

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( state_pkg.cnt );
2

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>

 now the optimizer evaluated F and used that value to do an INDEX RANGE
scan...  Less rows to work with.  We can see it does this only one time (it
calls the function 2 times for some reason but only does that once) by updating
some rows:

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec state_pkg.cnt := 0;

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where r = f();

         R
----------
         1
         1
         1
         1
         1
         1
         1
         1
         1
         1
         1
         1
         1
         1
         1
         1
         1
         1
         1

19 rows selected.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( state_pkg.cnt );
2

PL/SQL procedure successfully completed.

and seeing that the function was still only called 2 times...

What I like to do is:

A) if the function is in fact returning a constant like yours -- USE A BIND
VARIABLE or if you cannot bind -- use an application context and SYS_CONTEXT.
They are appropriate for constants.  Bind variables = BEST, sys_context = 2cnd
best, plsql function = way way back in 3rd.

B) use the (subquery) with rownum trick whenever possible

C) postpone calling functions in the select list until the very last possible
moment to ensure they are called as infrequently as possible, eg: instead of
coding:

select f(x), .... from .....

I would:

select f(x), ....
  from ( select ROWNUM, ..... from .... )

where the inner query is simply the original query with the functions removed
and rownum added (so no view merging takes place)
Rating: 5
What if without REPLACE February 04, 2002
Reviewer: Irina
translate(l_str,'x0123456789','x')           ;
translate(l_str,chr(0)||'0123456789',chr(0)) - delete all digits, replace is not
needed
Rating: 5
How is this possible June 19, 2002
Reviewer: Kumar from USA
select
    ((to_number(b.ARRANGE_FEE_TXT))),b.tr_id
from
       tr_deal_source b,
       tr_fac_currency d ,
       deal_source c
where c.deal_id = b.deal_id
and   b.tr_id = d.tr_id
and   c.data_source_id = b.data_source_id
and   c.data_source_id = 13
and   c.deal_currency_id = d.currency_id
and   b.ARRANGE_FEE_TXT not in ('N/A','TBD','NA')
and   b.ARRANGE_FEE_TXT is not null

Why i am getting Invalid Number Error even filtering
Varchar value from the table for that column?

Only possble varchar value in that column is N/A,TBD,NA
(Note:I checked Data in the Table)
Followup:
well, without the table defs themselves -- it is not possible to answer.

You assume it is ont he to_number(arrange_fee_text), but it might not be.
Rating: 5
Re-How is this possible June 20, 2002
Reviewer: Kumar from USA
I am sure the error i am getting bcos of to_number function in the select.If i
remove the to_number function from the select then it retrieves without any
error.

workaround :
Using Decode,replace & Translate in to_number function solves this.But my
question is even after filtering the varchar value in where clause why to_number
function is not working?

Tabel def:

TR_DEAL_SOURCE:
deal_id              Varchar2(10)
tr_id                Number(10,0)
arrange_fee_tx       Varchar2(25)

DEAL_SOURCE
deal_id              Varchar2(10)
data_source_id       Number(2,0)
deal_currency_id     Varchar2(4)
Followup:
you are assuming some "order of operation" -- an order of operation which is --
in sql -- explicitly not defined.  Can you supply a couple (2 or 3) rows of data
with which to reproduce this issue?
Rating: 5
Response-How this is possible June 20, 2002
Reviewer: Kumar from USA
Tom-
Thanks for your quick response.
I have gone thru this link->
http://asktom.oracle.com/pls/ask/f?p=4950:8:951523::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1299201885045,%7Bto_number%7D
It seems like somebody already reported this has bug and got back as not a BUG
but to use Decode in the to_nunber function.
Followup:
it isn't a bug in the link you referred me to.  They were 100% dependent on the
order of operation in a sql predicate and there IS NO DEFINED ORDER OF OPERATION
in a sql predicate.

The only bug in that link is in the query posed by the orginal poster.

If you give me some rows, I'll explain to you what's happening.  This is the old
"if it is a number, store it in a number column, if it is a date, put it in a
date column" problem.  Give me a small test case and I'll be glad to tell you
whats going on.
Rating: 5
Now i know how is this happening June 21, 2002
Reviewer: Kumar from USA
Sorry tom.
The problem is with the data.one of the value is (1,2345).

Thanks for your suggestion!
Rating: 5
Now i know how is this happening June 21, 2002
Reviewer: Kumar from USA
Sorry tom.
The problem is with the data.one of the value is (1,2345).

Thanks for your suggestion!
Rating: 5
Add another Where July 19, 2002
Reviewer: A reader
Tom
I agree with your solution totally.
But the following query just retrieves all the values. What if we need to query
a specific tmpvalue such as
Where tmpvalue=123.4. If I plug this where clause in the following query it
again gives me a number invalid error?

select * from t where tmptype = 'N'
and decode( replace(translate(tmpvalue,'1234567890.','00000000000'),'0',''),
  NULL, my_to_number(tmpvalue),  0 ) = 0;

I appreciate all the help
Thanks
Followup:
add where tmpvalue = '123.4' then.  compare a string to a string.
Rating: 5
order by number in varchar2 columns November 02, 2003
Reviewer: Sean from NJ, USA
create table t(c varchar2(20));

insert into t
values('101St');

insert into t
values('11CT');

insert into t
values('10 Street');

I need the result ordered by the number and ignore the char or space after the
number as shown bellow:

11CT
12 Street
101St

Thanks so much for your help.

Sean
Followup:
ops$tkyte@ORA920> select c, to_number(substr( c, 1,
length(c)-length(ltrim(translate(c,'01234567890','0000000000'),'0')))) cn
  2   from t
  3  order by to_number(substr( c, 1,
length(c)-length(ltrim(translate(c,'01234567890','0000000000'),'0'))))
  4  /

C                            CN
-------------------- ----------
10 Street                    10
11CT                         11
101St                       101
hello world

ops$tkyte@ORA920>
Rating: 4
Split a VARCHAR2 November 25, 2003
Reviewer: M S Reddy from USA
Tom,
 I have a small issue.consider this example.

TAB1
               c1
-------------------------------
123 345 567 789 980 234 543 567
1234567 456789

i want to write a query and select something like this

             c1
-------------------------------
123
345
567
789
980
234
543
567
1234567
456789

make one record into many records by spilting using space.
can this be done in one single SQL without looping?
my requirement is to spilt and then join that with someother table.

Thanks,
M S Reddy
Followup:
it could, if there were some reasonable upper limit on the number of tokens, but
it would be really obscure.

a pipeline function is what you want to investigate here.

ops$tkyte@ORA920PC> create or replace type myTable as table of varchar2(25)
  2  /

Type created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create or replace function str2tbl( p_str in varchar2 )
return myTable
  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 := ltrim( substr( l_str, l_n+1 ) );
 12      end loop;
 13      return;
 14  end;
 15  /

Function created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create table t ( id int, str varchar2(2000) );

Table created.

ops$tkyte@ORA920PC> insert into t values ( 1, '123 345 567 789 980 234 543 567
1234567 456789');

1 row created.

ops$tkyte@ORA920PC> insert into t values ( 2, '32123 3455 5567' );

1 row created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select * from t;

        ID STR
---------- ------------------------------
         1 123 345 567 789 980 234 543 56
           7 1234567 456789

         2 32123 3455 5567

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select t.id,  t2.column_value
  2    from t, TABLE(str2tbl(t.str)) t2
  3  /

        ID COLUMN_VALUE
---------- -------------------------
         1 123
         1 345
         1 567
         1 789
         1 980
         1 234
         1 543
         1 567
         1 1234567
         1 456789
         2 32123
         2 3455
         2 5567

13 rows selected.
Rating: 5
Thanks!! November 26, 2003
Reviewer: M S Reddy from USA
Tom,
   thanks a lot .that helped me a lot.
M S Reddy
Rating: 5
for ver 8.1.7 January 08, 2004
Reviewer: Pal from USA
Hi Tom,
My problem is similar to above but I have Ora 8.1.7
and it seems the pipeline works only for ver 9
Also I have max 5 delimited data per row as shown

TABLE1
               c1
-------------------------------
123,345,567,789,980
111,222,333

i want to write a query and select something like this

             c1
-------------------------------
123
345
567
789
980
111
222
333

Thanks alot in advance,
Pal
Followup:
either of the following, probably #2 is best but as always -- test test test

ops$tkyte@ORA817DEV> create table t ( a int, x varchar2(40) );

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert into t values ( 1, '123,345,567,789,980' );

1 row created.

ops$tkyte@ORA817DEV> insert into t values ( 2, '111,222,333' );

1 row created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select a, substr(x, instr(x,',',1,rr.r)+1,
instr(x,',',1,rr.r+1)-instr(x,',',1,rr.r)-1 ) x
  2    from
  3  (select a, ','||x||',' x, length(x)-length(replace(x,',',''))+1 cnt
  4     from t ) t,
  5  (select rownum r
  6     from all_objects
  7    where rownum <= 50) rr
  8   where rr.r <= t.cnt
  9   order by 1, 2
 10  /

         A X
---------- ------------------------------------------
         1 123
         1 345
         1 567
         1 789
         1 980
         2 111
         2 222
         2 333

8 rows selected.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace type myTable as table of varchar2(25)
  2  /

Type created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace function str2tbl( p_str in varchar2 )
return myTable
  2  as
  3      l_str   long default p_str || ',';
  4      l_n     number;
  5      l_data  myTable := myTable();
  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 := ltrim( substr( l_str, l_n+1 ) );
 13      end loop;
 14      return l_data;
 15  end;
 16  /

Function created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select a, column_value
  2    from t, TABLE( cast(str2tbl(x) as myTable) ) x
  3  /

         A COLUMN_VALUE
---------- -------------------------
         1 123
         1 345
         1 567
         1 789
         1 980
         2 111
         2 222
         2 333

8 rows selected.
Rating: 3
German Decimal notation January 12, 2004
Reviewer: Yogesh from Pune India
In German, decimal notation is contrary to the British one. They use decimal
points as separators for thousands and decimal comma as separator for the
non-integer part of the number.

If we define a datatype as number and we need to handle above stuff, what
approach should be used ?
Followup:
no problems, database sort of "does that"

decimals and commas are just formats... they are not really "stored" with the
number..

consider:

ops$tkyte@ORA9IR2> alter session set NLS_NUMERIC_CHARACTERS='.,';

Session altered.
thats american (and others...)

ops$tkyte@ORA9IR2> select 1,2 , '1,2',
  2         to_char( x, '999g999d999' ), to_char(x, '999,999.999' )
  3    from t;

         1          2 '1, TO_CHAR(X,'9 TO_CHAR(X,'9
---------- ---------- --- ------------ ------------
         1          2 1,2  123,456.789  123,456.789

if you use G and D, you get the group separator and decimal point -- here they
are the "same"

ops$tkyte@ORA9IR2> select 1,2 , to_number( '1,2' ),
  2         to_char( x, '999g999d999' ), to_char(x, '999,999.999' )
  3    from t;
select 1,2 , to_number( '1,2' ),
                        *
ERROR at line 1:
ORA-01722: invalid number

and 1,2 is not a number in this case...

ops$tkyte@ORA9IR2> insert into t values ( 1.2 );

1 row created.

ops$tkyte@ORA9IR2> insert into t values ( 1,2 );
insert into t values ( 1,2 )
            *
ERROR at line 1:
ORA-00913: too many values

this is to demonstrate a certain ambiguity that'll crop up

ops$tkyte@ORA9IR2> insert into t values ( '1,2' );
insert into t values ( '1,2' )
                       *
ERROR at line 1:
ORA-01722: invalid number

ops$tkyte@ORA9IR2> rollback;

Rollback complete.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> alter session set NLS_NUMERIC_CHARACTERS=',.';

Session altered.

this is your format..

ops$tkyte@ORA9IR2> select 1,2 , '1,2',
  2         to_char( x, '999g999d999' ), to_char(x, '999,999.999' )
  3    from t;

         1          2 '1, TO_CHAR(X,'9 TO_CHAR(X,'9
---------- ---------- --- ------------ ------------
         1          2 1,2  123.456,789  123,456.789

see the difference -- ., insetead of ,.

ops$tkyte@ORA9IR2> select 1,2 , to_number( '1,2' ),
  2         to_char( x, '999g999d999' ), to_char(x, '999,999.999' )
  3    from t;

         1          2 TO_NUMBER('1,2') TO_CHAR(X,'9 TO_CHAR(X,'9
---------- ---------- ---------------- ------------ ------------
         1          2              1,2  123.456,789  123,456.789

but see how 1,2 are two COLUMNS, not 1,2 the number -- you need to "string"
it... that is the ambiguity there

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t values ( 1.2 );

1 row created.

(I'm sitting next to a guy that wrote the NLS course) according to him, the
parser has . and comma hard coded - so that is really "1,2" but...

ops$tkyte@ORA9IR2> insert into t values ( 1,2 );
insert into t values ( 1,2 )
            *
ERROR at line 1:
ORA-00913: too many values

1,2 is two columns again so ...

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

1 row created.

that goes there...

It is all about the formats (and the sql parser which must tokenize things --
hence the hard coded "." and "," meaning on parse)
Rating: 4
Great Explaination January 21, 2004
Reviewer: Yogesh from Pune, India
Thanks for the explaination tom, I can even set NLS_NUMERIC_CHARACTERS at
instance level, right ? But is there any way by which I can refer 1,2 as 1.2
without using to_char ?
Followup:
you can set things at the instance level HOWEVER if the client sets any NLS_*
parameter, they will all default for that client -- so, in practicality, no, you
cannot really set things at the instance level (and expect them to be in place
by default).

a login trigger can help you.

consider this:

insert into t values ( 1,2 )
select 1,2 from dual;

is that inserting one or two columns into T?  See the ambiguity -- you have to
use "to_number" on a string if you want to use literals with ","'s in it like
that.
Rating: 5
Number-in-a-VARCHAR2: Any Holes in This Approach? January 21, 2004
Reviewer: Mac from Bloomington, IL
This function seems to work. Anybody see any potential problem?

CREATE OR REPLACE FUNCTION is_numeric
  (p_string IN VARCHAR2)
RETURN VARCHAR2
IS
  l_nv_err EXCEPTION;
  l_number NUMBER;
  l_result VARCHAR2(1) := 'N';
  PRAGMA EXCEPTION_INIT(l_nv_err, -6502);
BEGIN
  BEGIN
    l_number := TO_NUMBER(p_string);
    l_result := 'Y';
  EXCEPTION
    WHEN invalid_number THEN
      l_result := 'N';
    WHEN l_nv_err THEN
      l_result := 'N';
    WHEN OTHERS THEN
      RAISE_APPLICATION_ERROR(-20001, 'Error in is_numeric: '||SQLERRM);
  END;
  --
  RETURN(l_result);
END;
/

SQL> select*from dual where is_numeric('12345')='Y';

D
-
X

SQL> select*from dual where is_numeric(12345)='Y';

D
-
X

SQL> select*from dual where is_numeric('1234A')='Y';

no rows selected
Followup:
it works, much in the same fashion as above...

I just assumed ANY error converting the string to a number meant 'not a number'
Rating: 3
String To Number Conversion February 09, 2004
Reviewer: Denise from Virginia,USA
Hi Tom

We have an Address Table with the Zip Column as a Varchar2(10).

We also have a package that does matching records with the
first 5 digits of the zip code(zip column).

We noticed that when the first digit of the zip code was
zero(0)...it was being trimmed off (see below):
SQL> select zip from address where idnumber=1234;

ZIP
----------
06826-4204

SQL> declare
  2  zipc number(5);
  3  begin
  4  select substr(zip,1,5) into zipc
  5  from address
  6  where idnumber=1234;
  7
  8  dbms_output.put_line(zipc);
  9  end;
 10  /
6826
************************************************************
I tried several scenarios trying to convert the zip column
to a number then assigning the value to a number variable
(zipc)...keep getting the ora-01722 error.

SQL> declare
  2  zipc varchar2(5);
  3  begin
  4  select to_number(substr(zip,1,5)) into zipc
  5  from address
  6  where idnumber=1234;
  7  dbms_output.put_line(zipc);
  8  end;
  9  /
6826

PL/SQL procedure successfully completed.

SQL>
  1  declare
  2  zipc number(5);
  3  begin
  4  select to_number(substr(zip,1,5)) into zipc
  5  from address
  6  where idnumber=1234;
  7  dbms_output.put_line(zipc);
  8* end;
SQL> /
declare
*
ERROR at line 1:
ORA-01722: invalid number
ORA-06512: at line 4

SQL> declare
  2  zipc number(5);
  3  begin
  4  select to_number(zip,'99999') into zipc
  5  from address
  6  where idnumber=1234;
  7  dbms_output.put_line(zipc);
  8  end;
  9  /
declare
*
ERROR at line 1:
ORA-01722: invalid number
ORA-06512: at line 4
************************************************************
perhaps this is the wrong approach to solving the issue
of why leading zero(s) are being trimmed off.

why isn't the to_number function working here?
you mention replace and deterministic..are those functions
that can be applied here?

thanks!
Followup:
it is not getting "stripped off"

NUMBERS (in any sense of the word) do not have leading zeroes.  When you get a
check does it read

$000,000,000,000,000,123.21

or does it read

$123.21

leading zeroes on number types "don't make sense".

Because you convert this into a number, the zeroes (which make sense in a
character string) "go away"

So, my question to you -- why are you trying to put this string data (it must be
string data since you want the string unmodified here) into a number in the
first place????
Rating: 3
point well taken.... February 09, 2004
Reviewer: Denise from Virginia, USA
the problem we are encountering is that records that are
being imported(and run through this package) are not being
matched correctly because of the leading zero issue.
When we run the unmatched reports we are seeing:

John Doe   123 Main Street   06842-XXXX (in the system)
John Doe   123 Main Street   6842-XXXX(from the import file)

We are matching on last name, line1 and zip(first 5 characters) and John Doe
does not get included in the matching because the leading zero is not there(for
want of a better description). We have verfied this through the
reports.

My logic in trying to convert the string to a number was
based on a zero being numerically valid unless you
explicity insert code to remove leading zero's.

This is not a package I wrote it's part of the Software
Application.
Followup:
lpad it

lpad( zip, '0', 10 )
Rating: 5
thanks Tom!...works perfectly!!!! February 09, 2004
Reviewer: Denise from Virginia,USA
SQL> declare
  2  zipc varchar2(5);
  3  begin
  4  select lpad(zip,5,'0') into zipc
  5  from address
  6  where idnumber=1234;
  7  dbms_output.put_line(zipc);
  8  end;
  9  /
06826

PL/SQL procedure successfully completed.
Rating: 3
Nice February 25, 2004
Reviewer: Steve from Dominican Republic
Hi Tom,
Is it possible in Oracle to get the maximum positive number
and maximum negative number?Are there any constants like
POSITIVE_INFINITY and NEGATIVE_INFINITY in Oracle?I wish to know this.Please do
reply.
Thanks in advance.
Followup:
other than the documented limits, no, there is no "constant".  you could
certainly create a package with constants in it for your own use.
Rating: 3
OK March 26, 2004
Reviewer: Siva from Bangalore,KA,India
Hi Tom,
Oracle documentation says "Positive numbers can take a max.
value of 9.999...*10 raised to 125 and for negative numbers
as 1.0*10 raised to -130".Is there any way in Oracle to
retrieve such values?I think Mr.Steve was referring like this.*I mean,Any
programmatic way to retrieve such values Please do reply.
Bye!
Followup:
sure, just select them?

ops$tkyte@ORA9IR1> select 9.999 * power(10,125) from dual;

9.999*POWER(10,125)
-------------------
         9.999E+125

ops$tkyte@ORA9IR1> select power(10,-130) from dual;

POWER(10,-130)
--------------
    1.000E-130

but they are NOT +- infinity, they are "really numbers"
Rating: 3
Looking for an approach to reduce the overhead of function call March 28, 2004
Reviewer: Matt from Australia
I have some data with a field varchar2(10). I need to query this data and
generate a computed value based on this field. There may be many of these fields
in the result set with the same value. I am looking for ways of reducing the
overhead of the PL/SQL calls.

My options appear to be:

i) Remove the function and do it all in SQL
ii) Reduce the number of function calls
iii) Cache values calculated by the function to reduce the computation required
for duplicate field values.
iv) Something I haven't thought of.

So,

i) I implemented the algorithm in SQL and it ran significantly slower.
ii) I can't remove any PL/SQL calls since each row needs to have the computed
value calculated (a function call)
iii) I am considering generating a package to store those values already
calculated. The function would need to carry out a cache lookup prior to doing
any of the computation. This should reduce some of the required work.
iv) I am looking for alternative approaches, hence this post.

Can you please comment on the above? In particular I am looking for alternative
approaches that I have not considered.

THanks,
Followup:
i) that i don't believe -- show me the before and after please!  somethings up
there.
Rating: 3
Here arethe two implementations... March 28, 2004
Reviewer: Matt from Australia
--
drop table test_data
/
create table test_data (id varchar2(10), id_check number(1))
/
insert into test_data values ('5600012357',9)
/
insert into test_data values ('5500000278',4)
/
insert into test_data values ('5500003074',5)
/
insert into test_data values ('5500008129',2)
/
insert into test_data values ('5500012357',8)
/
insert into test_data values ('5500023478',0)
/
insert into test_data values ('5500047359',4)
/
insert into test_data values ('5500067253',5)
/
insert into test_data values ('5500079467',6)
/
insert into test_data values ('5500089000',8)
/
insert into test_data values ('5500099352',6)
/
insert into test_data values ('5500102781',5)
/
insert into test_data values ('5500139654',8)
/
insert into test_data values ('5500200000',4)
/
insert into test_data values ('5500289367',3)
/
insert into test_data values ('5500346583',7)
/
insert into test_data values ('5600000278',2)
/
insert into test_data values ('5600003074',3)
/
insert into test_data values ('5600008129',0)
/
insert into test_data values ('5600012357',9)
/
insert into test_data values ('5600023478',8)
/
insert into test_data values ('5600047359',2)
/
insert into test_data values ('5600067253',3)
/
insert into test_data values ('5600079467',4)
/
insert into test_data values ('5600089000',6)
/
insert into test_data values ('5600099352',4)
/
insert into test_data values ('5600102781',3)
/
insert into test_data values ('5600139654',6)
/
insert into test_data values ('5600200000',2)
/
insert into test_data values ('5600289367',1)
/
insert into test_data values ('5600346583',5)
/
commit
/

create or replace function gen_check(p_id in varchar2) return pls_integer
deterministic is
--  l_result pls_integer;
  i   pls_integer :=0;
  d   pls_integer :=0;
  l_buffer varchar2 (50) := '';
  l_val pls_integer :=0;
  l_multiply boolean := true;
  l_check pls_integer := 0;
begin
  if p_id is not null then
    l_buffer := UPPER(p_id);
    i := length(p_id);

    l_multiply := TRUE;
    WHILE i > 0 LOOP

      d := ascii(substr(l_buffer,i,1));

      IF (l_multiply = TRUE) THEN
         d := d*2;
      END IF;
      l_multiply := NOT l_multiply;

      WHILE d > 0 LOOP
        --dbms_output.PUT_LINE('d='||d);
        --dbms_output.PUT_LINE('mod(d,10)='||MOD(d,10));
        l_val := l_val + MOD(d,10);
        --dbms_output.PUT_LINE('val='||val);
        d := floor(d / 10);
      END LOOP;
      i := i - 1;

    END loop;
    --dbms_output.PUT_LINE('val='||val);
    l_check := MOD((10 - MOD(l_val,10)),10);

/*
    select mod((10 - mod(sum(padded_val_ch),10)),10) into l_check
    from ( select sum(substr(padded_val,length(padded_val)- row_num + 1,1)) as
padded_val_ch
--                 ,pos
--                 ,doubled_val
             from ( select pos
--                          ,chr
--                          ,ascii_val
                          ,doubled_val
                          ,lpad(doubled_val,5,'0') as padded_val
                          ,row_number() over (partition by pos,doubled_val order
by doubled_val ) row_num
                      from ( select rownum as pos
--                                   ,substr(p_id,length(p_id) - rownum + 1,1)
as chr
--                                   ,ascii(substr(p_id,length(p_id) - rownum +
1,1)) as ascii_val
--                                   ,case when mod(rownum,2) = 1 then 'Y'
--                                         else 'N' end to_double
                                   ,case when mod(rownum,2) = 1 then
2*ascii(substr(p_id,length(p_id)- rownum + 1,1))
                                         else ascii(substr(p_id,length(p_id)-
rownum + 1,1)) end as doubled_val
                               from table(vtable(length(p_id))) )
                                   ,table(vtable(5)) )
           group by pos, doubled_val );
*/
  else
    null;
  end if;
  return l_check;
--  return l_result;
end gen_check;
/

create type array
as table of number
/

create or replace function vtable (n in number default null)
return array
pipelined
as
begin
  for i in 1.. nvl(n,999999999)
  loop
    pipe row(i);
  end loop;
  return;
end;
/

create or replace function gen_check_sql(p_id in varchar2) return pls_integer
deterministic is
  l_check pls_integer := 0;
begin
  if p_id is not null then
    select mod((10 - mod(sum(padded_val_ch),10)),10) into l_check
    from ( select sum(substr(padded_val,length(padded_val)- row_num + 1,1)) as
padded_val_ch
--                 ,pos
--                 ,doubled_val
             from ( select pos
--                          ,chr
--                          ,ascii_val
                          ,doubled_val
                          ,lpad(doubled_val,5,'0') as padded_val
                          ,row_number() over (partition by pos,doubled_val order
by doubled_val ) row_num
                      from ( select rownum as pos
--                                   ,substr(p_id,length(p_id) - rownum + 1,1)
as chr
--                                   ,ascii(substr(p_id,length(p_id) - rownum +
1,1)) as ascii_val
--                                   ,case when mod(rownum,2) = 1 then 'Y'
--                                         else 'N' end to_double
                                   ,case when mod(rownum,2) = 1 then
2*ascii(substr(p_id,length(p_id)- rownum + 1,1))
                                         else ascii(substr(p_id,length(p_id)-
rownum + 1,1)) end as doubled_val
                               from table(vtable(length(p_id))) )
                                   ,table(vtable(5)) )
           group by pos, doubled_val );
  else
    null;
  end if;
  return l_check;
--  return l_result;
end gen_check_sql;
/

select t.id
      ,t.id_check tbl_val
      ,gen_check(t.id) calc_val
      ,case when t.id_check = gen_check(t.id) then 'SUCCESS'
            else 'FAILURE'
       end
  from test_data t
/

select t.id
      ,t.id_check tbl_val
      ,gen_check(t.id) calc_val
      ,case when t.id_check = gen_check_sql(t.id) then 'SUCCESS'
            else 'FAILURE'
       end
  from test_data t
/

Over the small data set provided the two implementations are comparable. When
run against 500,000 rows the SQL implementation was significantly slower - I
don't have that data to hand.

My conclusion was that the SQL implementation was more CPU expensive since it
has to loop more times for each "add up all the digits" computation (as SQL does
not know when it has processed all the digits that it loops through)

At the point where I realised the PL/SQL routine was faster I stopped looking at
incorporating the SQL in the larger query. As I expected that this result would
carry through to an 'entirely in SQL approach'.

I now expect you will tell me that the all in SQL will be the fastest
implementation.
Followup:
Now I understand -- you never ever did a pure SQL implementation.  You just have
two plsql functions :)

Ok, assuming we can assume a max length on the input (i chose 10 from your
example), we can take a function like this:

ops$tkyte@ORA9IR2> @mystat "CPU used by this session"
ops$tkyte@ORA9IR2> column value new_val V
ops$tkyte@ORA9IR2> define S="&1"
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> set autotrace off
ops$tkyte@ORA9IR2> 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('&S')||'%'
  5  /
old   4: and lower(a.name) like '%' || lower('&S')||'%'
new   4: and lower(a.name) like '%' || lower('CPU used by this session')||'%'

NAME                                VALUE
------------------------------ ----------
CPU used by this session             3764

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> set autotrace traceonly
ops$tkyte@ORA9IR2> select id, id_check, gen_check(id) plsql
  2    from test_data
  3  /

10031 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (FULL) OF 'TEST_DATA'

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        695  consistent gets
          0  physical reads
          0  redo size
     262962  bytes sent via SQL*Net to client
       7847  bytes received via SQL*Net from client
        670  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      10031  rows processed

ops$tkyte@ORA9IR2> set autotrace off
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> set linesize 121
ops$tkyte@ORA9IR2> @mystat2
ops$tkyte@ORA9IR2> select a.name, b.value V, b.value-&V diff
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&S')||'%'
  5  /
old   1: select a.name, b.value V, b.value-&V diff
new   1: select a.name, b.value V, b.value-      3764 diff
old   4: and lower(a.name) like '%' || lower('&S')||'%'
new   4: and lower(a.name) like '%' || lower('CPU used by this session')||'%'

NAME                                    V       DIFF
------------------------------ ---------- ----------
CPU used by this session             3846         82

running in about 0.82 cpu seconds for 10,000 records and use SQL like this

ops$tkyte@ORA9IR2> select id, id_check, mod((10-mod(x,10)),10) sql
  2    from (
  3  select substr(c1,1,1)+substr(c1,2,1)+substr(c1,3,1)+
  4         substr(c2,1,1)+substr(c2,2,1)+substr(c2,3,1)+
  5         substr(c3,1,1)+substr(c3,2,1)+substr(c3,3,1)+
  6         substr(c4,1,1)+substr(c4,2,1)+substr(c4,3,1)+
  7         substr(c5,1,1)+substr(c5,2,1)+substr(c5,3,1)+
  8         substr(c6,1,1)+substr(c6,2,1)+substr(c6,3,1)+
  9         substr(c7,1,1)+substr(c7,2,1)+substr(c7,3,1)+
 10         substr(c8,1,1)+substr(c8,2,1)+substr(c8,3,1)+
 11         substr(c9,1,1)+substr(c9,2,1)+substr(c9,3,1)+
 12         substr(c10,1,1)+substr(c10,2,1)+substr(c10,3,1) x,
 13         id, id_check
 14    from (
 15  select decode( mult, 2,   c1, 2*c1 ) c1,
 16         decode( mult, 2, 2*c2,   c2 ) c2,
 17         decode( mult, 2,   c3, 2*c3 ) c3,
 18         decode( mult, 2, 2*c4,   c4 ) c4,
 19         decode( mult, 2,   c5, 2*c5 ) c5,
 20         decode( mult, 2, 2*c6,   c6 ) c6,
 21         decode( mult, 2,   c7, 2*c7 ) c7,
 22         decode( mult, 2, 2*c8,   c8 ) c8,
 23         decode( mult, 2,   c8, 2*c8 ) c9,
 24         decode( mult, 2,2*c10,  c10 ) c10,
 25         id, id_check
 26    from (
 27  select ascii(substr(x,1,1)) c1,
 28         ascii(substr(x,2,1)) c2,
 29         ascii(substr(x,3,1)) c3,
 30         ascii(substr(x,4,1)) c4,
 31         ascii(substr(x,5,1)) c5,
 32         ascii(substr(x,6,1)) c6,
 33         ascii(substr(x,7,1)) c7,
 34         ascii(substr(x,8,1)) c8,
 35         ascii(substr(x,9,1)) c9,
 36         ascii(substr(x,10,1)) c10,
 37         decode( mod(length(x),2), 0, 2, 1 ) mult,
 38         id, id_check
 39    from ( select id, id_check, upper(id) x from test_data )
 40         )
 41         )
 42         )
 43  /

10031 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (FULL) OF 'TEST_DATA'

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        695  consistent gets
          0  physical reads
          0  redo size
     268444  bytes sent via SQL*Net to client
       7847  bytes received via SQL*Net from client
        670  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      10031  rows processed

ops$tkyte@ORA9IR2> set autotrace off
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> @mystat2
ops$tkyte@ORA9IR2> select a.name, b.value V, b.value-&V diff
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&S')||'%'
  5  /
old   1: select a.name, b.value V, b.value-&V diff
new   1: select a.name, b.value V, b.value-      3846 diff
old   4: and lower(a.name) like '%' || lower('&S')||'%'
new   4: and lower(a.name) like '%' || lower('CPU used by this session')||'%'

NAME                                    V       DIFF
------------------------------ ---------- ----------
CPU used by this session             3856         10

running consistently 8 times faster....

If you need more than 10, the "algorithm" should be pretty visible there.  I
just diagnosed what your code did and "flatlined" it.
Rating: 4
Great! March 29, 2004
Reviewer: Matt from Australia
Thanks Tom.

I think that if I had continued on and generated a full SQL version I wouldn't
have "flatlined" the SQL quite like you have (and may therefore have ended up
with a less efficient implementation).

I think it is a rather neat solution really, almost like a procedural SQL
implementation.

Thanks again.
Rating: 3
Just for completeness.. March 29, 2004
Reviewer: Matt from Australia
The final query.

Fixed:

- a minor typo in the "mult" part
- addition of nvl (as substr appears to return null when looking past the end of
the string).

Thanks again.

select id, id_check, mod((10-mod(x,10)),10) sql
    from (
  select nvl(substr(c1,1,1),0)+nvl(substr(c1,2,1),0)+nvl(substr(c1,3,1),0)+
         nvl(substr(c2,1,1),0)+nvl(substr(c2,2,1),0)+nvl(substr(c2,3,1),0)+
         nvl(substr(c3,1,1),0)+nvl(substr(c3,2,1),0)+nvl(substr(c3,3,1),0)+
         nvl(substr(c4,1,1),0)+nvl(substr(c4,2,1),0)+nvl(substr(c4,3,1),0)+
         nvl(substr(c5,1,1),0)+nvl(substr(c5,2,1),0)+nvl(substr(c5,3,1),0)+
         nvl(substr(c6,1,1),0)+nvl(substr(c6,2,1),0)+nvl(substr(c6,3,1),0)+
         nvl(substr(c7,1,1),0)+nvl(substr(c7,2,1),0)+nvl(substr(c7,3,1),0)+
         nvl(substr(c8,1,1),0)+nvl(substr(c8,2,1),0)+nvl(substr(c8,3,1),0)+
         nvl(substr(c9,1,1),0)+nvl(substr(c9,2,1),0)+nvl(substr(c9,3,1),0)+
         nvl(substr(c10,1,1),0)+nvl(substr(c10,2,1),0)+nvl(substr(c10,3,1),0) x,
         id, id_check
    from (
  select decode( mult, 2,   c1, 2*c1 ) c1,
         decode( mult, 2, 2*c2,   c2 ) c2,
         decode( mult, 2,   c3, 2*c3 ) c3,
         decode( mult, 2, 2*c4,   c4 ) c4,
         decode( mult, 2,   c5, 2*c5 ) c5,
         decode( mult, 2, 2*c6,   c6 ) c6,
         decode( mult, 2,   c7, 2*c7 ) c7,
         decode( mult, 2, 2*c8,   c8 ) c8,
         decode( mult, 2,   c9, 2*c8 ) c9,
         decode( mult, 2,2*c10,  c10 ) c10,
         id, id_check
    from (
  select ascii(substr(x,1,1)) c1,
         ascii(substr(x,2,1)) c2,
         ascii(substr(x,3,1)) c3,
         ascii(substr(x,4,1)) c4,
         ascii(substr(x,5,1)) c5,
         ascii(substr(x,6,1)) c6,
         ascii(substr(x,7,1)) c7,
         ascii(substr(x,8,1)) c8,
         ascii(substr(x,9,1)) c9,
         ascii(substr(x,10,1)) c10,
         decode( mod(length(x),2), 0, 2, 1 ) mult,
         id, id_check
    from ( select id, id_check, upper(id) x from test_data )
         )
         )
         )
March 29, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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
Rating: 4
Back to SQL 101 February 17, 2004
Reviewer: robert from ct
Tom,
pls see :
http://32.97.40.160/graphic/sql_result.gif
forgot how to do this...
how to "collapse" the result of this query so it becomes 3 rows:

SELECT one, two, three FROM
(SELECT statusdescription one, NULL two, NULL three FROM tlkpclientstatus t
WHERE upper(statusdescription)  LIKE 'ACT%'
UNION
SELECT NULL one, statusdescription two, NULL three FROM tlkpclientstatus t WHERE
upper(statusdescription)  LIKE 'INACT%'
UNION
SELECT NULL one, NULL two, statusdescription three FROM tlkpclientstatus t WHERE
upper(statusdescription)  LIKE 'CAN%')

Thanks
Followup:
first - neat idea with the link, thanks so much for not email me a bitmap
screenshot (you would be surprised how many of those megabyte emails I do
recieve!)

well, in general -- it won't happen given your result set.

Each of those queries return 3 rows.  I would assume that each of them could
actually return N rows where N1 <> N2 <> N3 (eg: each could be different in
size)

Now, we'd be talking "join" and in fact FULL OUTER join if they have different
cardinalities...

select one, two, three
   from
(SELECT statusdescription one, rownum r
   FROM tlkpclientstatus t
  WHERE upper(statusdescription)  LIKE 'ACT%' ) A,
(SELECT statusdescription two, rownum r
   FROM tlkpclientstatus t
  WHERE upper(statusdescription)  LIKE 'INACT%') B,
(SELECT statusdescription three, rownum r
   FROM tlkpclientstatus t
  WHERE upper(statusdescription)  LIKE 'CAN%')
  where a.r = b.r
    and b.r = c.r
/

solves your threesome problem.

we'd have to full outer join those guys in the event the cards are different.
Rating: 5
cool February 17, 2004
Reviewer: robert from CT
Tom, thanks..works like a charm.
The syntex you gave is 9i FULL OUTER JOIN ? (no "(+)")
I'm on 8.1.7 this app...
I recall FOJ is not available in 8.1.7 , is it ?
cause i got this error:

......snip.....
  where a.r (+) = b.r (+)
    and b.r (+) = c.r (+)

ORA-01468: a predicate may reference only one outer-joined table

But i'm happy with this result:
(anything not ACTIVE/INACTIVE go to column #3)
SQL> select one, two, three
  2     from
  3  (SELECT statusdescription one, rownum r
  4     FROM tlkpclientstatus t
  5    WHERE upper(statusdescription)  LIKE 'ACT%' ) A,
  6  (SELECT statusdescription two, rownum r
  7     FROM tlkpclientstatus t
  8    WHERE upper(statusdescription)  LIKE 'INACT%') B,
  9  (SELECT statusdescription three, rownum r
 10     FROM tlkpclientstatus t
 11    WHERE upper(statusdescription)  LIKE 'CAN%'
 12       OR statuscodeid > 9) c
 13    where a.r (+) = b.r
 14      and b.r (+) = c.r
 15  /

ONE              TWO              THREE
---------------- ---------------- ----------------
Active Client    Inactive Client  Cancelled Client
Active Comp      Inactive Comp    Cancelled Trial
Active Trial     Inactive Trial   Cancelled Comp
                                  Pure Prospect
                                  Defunct Company
Followup:
correct, no full outer join in 8i, 9i and above.

you can simulate it in 8i, but it gets "nasty" (a union all of a join and an
"anti-join")
Rating: 3
OK March 28, 2004
Reviewer: Ram from Bangalore,India
Hi Tom,
Can the UNION ALL approach eliminate the GROUPING SETS
Feature?Please do reply.
Bye!
Followup:
you got it backwards.

grouping sets is there to elminate the heinous overhead of having to use UNION
ALL.
March 29, 2004 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)
Rating: 3
Please explain March 29, 2004
Reviewer: Bruce from British Columbia
Hi Tom,
What may the following hints do?
1)HWM_BROKERED
2)BYPASS_UJVC
3)CIV_GB and CUBE_GB
4)PIV_GB and PIV_SSF
 Do you have any doc.links?
Please do reply.
Bye!
Followup:
search for bypass_ujvc on this site.

for the others -- nothing you can use, not meaningful to us (self included --
really).  they are internal PQ (parallel query) hints.
March 29, 2004 -- Thanks for the question regarding "Procedure vs Package", version 9.2
You Asked
Which is the best to user for code and when is it best to use a package rather
than a procedure
and we said...
Always use a package.

Never use a standalone procedure except for demos, tests and standalone
utilities (that call nothing and are called by nothing)
Review & Followup

Rating: 5
Procedure vs Package January 22, 2003
Reviewer: Craig Richards from England
Thanks very much it answered my questions OK
Rating: 1
Why? January 22, 2003
Reviewer: reader
Can you please justify your statement.
Followup:
because packages

o break the dependency chain (no cascading invalidations when you install a new
package body -- if you have procedures that call procedures -- compiling one
will invalidate your database)

o support encapsulation -- I will be allowed to write MODULAR, easy to
understand code -- rather then MONOLITHIC, non-understandable procedures

o increase my namespace measurably.  package names have to be unique in a
schema, but I can have many procedures across packages with the same name
without colliding

o support overloading

o support session variables when you need them

o promote overall good coding techniques, stuff that lets you write code that is
modular, understandable, logically grouped together....

If you are a programmer - you would see the benefits of packages over a
proliferation of standalone procedures in a heartbeat.
Rating: 5
Follwup Why? January 22, 2003
Reviewer: Jim Shang
Followup to A Reader
Why?

Use Tom's search facility to look for articles
which have "standalone procedure" or "dependency chain"

heres just one of the previous discussion
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:286816015990
Rating: 4
How to see the code of package body February 05, 2003
Reviewer: Ajeet from cincinnati
Tom - If a user A is the owner of a package then what privilegs a user B need in
order to view the code of that package body.Assuming that Package has a public
synonym.
Also user B is a normal user having looker and updater roles on the objects
owned by user A and execute on the proecdures,functions and packages.
The reason I asked this question is -- user B can see the code of procedure and
functions owned by A (as B has execute privilleges on them) , but in case of
packages it can only see package specification not the body.
can you please let me know how user B can see the package body as well.
Followup:
from the definition of ALL_OBJECTS:

  and
  (
    o.owner# in (userenv('SCHEMAID'), 1 /* PUBLIC */)
    or
    (.....
    or
    (
       o.type# = 11 /* pkg body */
       and
       exists (select null from v$enabledprivs
           where priv_number =   -141 /* CREATE ANY PROCEDURE */
              )
    )

so, either you OWN it or you have create any procedure, then you can see the
body.

alternatively, DBA_OBJECTS shows you all code regardless of whether you can
execute it or not.

B cannot see the package body -- although A can show it to them....  Consider:

ops$tkyte@ORA817DEV> drop user a cascade;
User dropped.

ops$tkyte@ORA817DEV> drop user b cascade;
User dropped.

ops$tkyte@ORA817DEV> grant connect, resource to a identified by a;
Grant succeeded.

ops$tkyte@ORA817DEV> grant connect to b identified by b;
Grant succeeded.

ops$tkyte@ORA817DEV> @su a

a@ORA817DEV> create or replace package demo_pkg
  2  as
  3          type rc is ref cursor;
  4
  5          procedure show_me_the_code( p_pkg_name in varchar2, p_code in out
rc );
  6  end;
  7  /
Package created.

a@ORA817DEV> create or replace package body demo_pkg
  2  as
  3
  4  procedure show_me_the_code( p_pkg_name in varchar2, p_code in out rc )
  5  is
  6  begin
  7          open p_code
  8          for
  9          select text
 10            from user_source
 11           where name = upper(p_pkg_name)
 12           order by type, line;
 13  end;
 14
 15  end;
 16  /
Package body created.

a@ORA817DEV> grant execute on demo_pkg to b;
Grant succeeded.

a@ORA817DEV> @su b

b@ORA817DEV> select text from all_source where name = 'DEMO_PKG' order by type,
line;

TEXT
----------------------------------------------------------------------
package demo_pkg
as
        type rc is ref cursor;

        procedure show_me_the_code( p_pkg_name in varchar2, p_code in out rc
);

end;

6 rows selected.

Now you can't see it ....

b@ORA817DEV> variable x refcursor
b@ORA817DEV> exec a.demo_pkg.show_me_the_code( 'demo_pkg', :x )

PL/SQL procedure successfully completed.

b@ORA817DEV> print x

TEXT
----------------------------------------------------------------------
package demo_pkg
as
        type rc is ref cursor;

        procedure show_me_the_code( p_pkg_name in varchar2, p_code in out rc
);

end;
package body demo_pkg
as

procedure show_me_the_code( p_pkg_name in varchar2, p_code in out rc )

is
begin
        open p_code
        for
        select text
          from user_source
         where name = upper(p_pkg_name)
         order by type, line;
end;

end;

21 rows selected.

and now you can...
Rating: 5
why execute works for procedures and function February 06, 2003
Reviewer: Ajeet from oh,usa
Tom -- Thanks so much for your answer.
I am not able to understand the logic behind the fact that
if we grant B execute on the procedures and function created by A -- it let them
see the code,but why it is not applicable for packages.
Appreciate your all help.
Followup:
that is one of the advantages of packages actually.

it is called "encapsulation"

you see the spec, the interface, the inputs and outputs.

The body -- meaningless.  It is just the implementation.  You only need be aware
of the interface.
Rating: 5
About package specification April 30, 2003
Reviewer: Tanmoy Datta Choudhury from India
Tom can you tell me the reason behind the package specification..why we do that
..we can do everything in the package body itself ..then why specification ..
why oracle think that a specification is necessary ????
Followup:
it is all about encapsulation, modular coding (yes, we can still call it that),
and scoping.

I have a global variable.  If I put it in a spec, EVERYONE can read and write it
directly.  That is in general "not a good thing".  Hence, we put it in a package
body.  now, only my package body can read and write it.  I can "protect" it.
The only way to modify that variable is via my code which knows what to do with
it and does it properly.

I have 10 procedures.  4 of which are meant to be called by others.  6 of which
are internal routines no one but me has any business knowing about -- let alone
calling.  The spec exposes the 4, but not the other 6.  You can only call what I
let you call.

The spec is what OTHER pieces of code are dependent on (read the link above
about breaking the dependency chain)
Rating: 5
why in out ref cursor April 30, 2003
Reviewer: John
Tom,

Thanks for the answers. everything was to the  point.

I have question on followup to the Ajeet's first question,

for the demo_pkg, you have in out ref cursor..
In our applications we allways use "out" ref cursor..

is there any benefit that we can leverage with "in out" ref cursor.

thanks
Followup:
it is just what I always type -- a holdover from the days when IN OUT was the
mode ref cursors were documented to be passed as.  today OUT suffices.
Rating: 4
Grants? August 01, 2003
Reviewer: Kashif from Reston, VA
Hi Tom,

In The Application Developer's Guide, one of the benefits of using packages
mentioned is:

"Let you grant privileges more efficiently."

I'm not clear on what they mean here, and I thought you might know. How can
packages be used to grant privileges, short of using execute immediate 'grant
select, insert...' type statements? Thanks.

Kashif
Followup:
they probably mean exactly this:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:11472567434530
Rating: 5
Makes sense... August 01, 2003
Reviewer: Kashif from Reston, VA
Thanks Tom, I guess I misread it totally. Though it's not really a benefit
associated with packages only, as they claim, but all stored
procedures/functions/packages.

Kashif
Followup:
well, they are just alluding to the fact that all good, real, production code
would be in a package anyway.  we all know you wouldn't use standalong
procedures/functions in a "real" system for real code -- just for small
utilities.
Rating: 3
Developing Packages August 01, 2003
Reviewer: Brian from Phoenix
You say to use a package over a procedure.  But if you are experimenting and
only have one to two procedures, does it make sense to dump them into a package
to start with or does it make more sense to develop procedures and then dump
those that are similiar in scope into a package once you have further tested
them?

I ask because I approach the writing of procedures and SQL to answer questions
that I am asked.  Thus, after I receive a lot of similiar questions, I would
then be able to wrap up those procedures created into a package.

The problem that I am faced with is that I am asked questions that need
immediate responses (within 15-30 minutes).  I do not have the luxury of being
able to have those asking the questions to define a project and prepare
questions to develop from.

What are your thoughts?
Followup:
remember what I said:

all good, real, roduction code
would be in a package anyway.  we all know you wouldn't use standalong
procedures/functions in a "real" system for real code -- just for small
utilities.

testing
experimenting
demoing

use procedures (i do, all over the place)

going to production, using real code, in the real world -- it is all about
packages and nothing but.
Rating: 5
August 02, 2003
Reviewer: A reader
"I have 10 procedures.  4 of which are meant to be called by others.  6 of which

are internal routines no one but me has any business knowing about -- let alone
calling.  The spec exposes the 4, but not the other 6.  You can only call what I

let you call.
"

Well when I describe a package I can see all the 10 procedure. How can you hide
the 6. I'm missing something here. Please illustrate your claim.
Followup:
well, then you must have put all 10 in the specification!

ps$tkyte@ORA920> create or replace package demo_pkg
  2  as
  3          procedure p1;
  4          procedure p2;
  5          procedure p3;
  6          procedure p4;
  7  end;
  8  /

Package created.

ops$tkyte@ORA920> create or replace package body demo_pkg
  2  as
  3          procedure p10 as begin null; end;
  4          procedure p9 as begin null; end;
  5          procedure p8 as begin null; end;
  6          procedure p7 as begin null; end;
  7          procedure p6 as begin null; end;
  8          procedure p5 as begin null; end;
  9          procedure p4 as begin null; end;
 10          procedure p3 as begin null; end;
 11          procedure p2 as begin null; end;
 12          procedure p1 as begin null; end;
 13  end;
 14  /

Package body created.

ops$tkyte@ORA920> desc demo_pkg
PROCEDURE P1
PROCEDURE P2
PROCEDURE P3
PROCEDURE P4

ops$tkyte@ORA920>
Rating: 4
Another view on packages September 23, 2003
Reviewer: Ninoslav Cerkez from Croatia
Hi Tom,
thanx for Your efforts.
When i read this message, i have recalled one view on packeges i was thinking
about for a while. Well, these years, object oriented approach is in. and i was
thinking about developing packages for all relevant tables in scheme.
Every package might be used for getting information from table and also for
inserting in it. That would be object approach, because we dont deal with data
directly.
That's the approach just for the packages for getting/setting data in tables.
Of, course, i would have many packages for business logic-processes purposes.
Generating TAPI from designer is one way of object approach i was thinking
about, but, i think not the general solution.
I would appreciate Your opinion
nino
Followup:
depends -- if you need bulk access to this table (forall processing, bulk
collect), this is quite confining.

I think the sql should be done in PLSQL -- and the clients just call optimized,
well written, efficient stored procedures.

rather then the client calling "my_pkg.do_insert( ..... )"  they call
"my_pkg.hire_employee( ... )"
Rating: 3
Another view on packages2 September 23, 2003
Reviewer: Ninoslav from Croatia
Hi Tom,
forgive me for my lack of understanding of Your example.
You said:
"....
rather then the client calling "my_pkg.do_insert( ..... )"  they call
"my_pkg.hire_employee( ... )"
..."
Well, i didn't catch the difference,except in names :-).
or I missed the point.
We could agree that in every relevant system there are at least dozen of repeted
lines of code " insert into table". Some tables are more frequently in use, some
not. Don't we get modular system when in every part of system developer just
call procedure for inserting data in these 'important' tables?
Isn't it faster and reliable than seperate calling of insert into? Isn't that
the same that You said in the second
part "I think the sql should be done in PLSQL -- and the clients just call
optimized,
well written, efficient stored procedures." ?
To be honest, i have no experiance with bulk processing, and can't say anything
than: agree :-).
the best regards
nino
Followup:
to hire someone, say it takes 3 inserts and an update.

client would have to have the logic to do this row by row.
    my_table1.do_insert( ... );
    my_table2.do_insert( ... );
    my_table3.do_insert( ... );
    my_table4.do_update( .... );

I suggest, the way to do it is like this:

   my_pkg.hire_emp( .... );

period.  put the logic into the server, do not put the logic into the client.

it is less work for the server (it would have to response 4 times to the client
instead of just once), it is more resuable (everyone can "hire" someone now), it
is more "safe" (i can audit, security check, whatever), it is more of everything
-- less of nothing.  put your transactions in the database.
Rating: 4
Another view on packages September 23, 2003
Reviewer: Ninoslav from Croatia
Oh, yes..of, course the package should go  in the database. I had no doubt about
that. I was just curious about (db) package-ing insert statements, and functions
for each table.
Sth very similar TAPI.
Table emp --> I have pck_emp and inside procedure for insert and various
functions for data i need from table temp.

Process hire_emp could go in that package or in just separate one (for some
preocesses in application), and it would call pck_tem.insert_emp for insert.

Just object approach, that was my question.
Followup:
i would probably just do the inserts, avoid the overhead of yet another
procedure call, unless there was a compelling reason to do so (eg: some logic
that when beyond a simple "insert into")
Rating: 5
packages November 04, 2003
Reviewer: mo
Tom:

1.  In a web app where you do not really have procedure dependency and each page
is considered separate other than it passed data to the other would you still
use a packages.

2. let us say you have 100 standalone procedures for the real production app.
Would you try to create like 10 packages and stick the procedures in there and
then delete the procedures and keep the package.

3.  Is not easier to edit a standalone procedure rather than a package because
when you open the package you will have 1000 lines of code rather than 100 for a
procedure.

4. When you design an app, do you create one package per link on the main menu
(3 or 4 pages) or you do one package per subject area like shipping where you
have several links or you do one package for the whole app (100 - 150
procedures).

Thanks,
Followup:
1) yes, I in fact do.  Absolutely. 100%.  It is a good modular coding practice.
asktom is basically a package (the api is anyhow)

ask_tom@ASKUS> desc wwc_ask_api
FUNCTION ACCEPTING_NEW_QUESTIONS RETURNS BOOLEAN
PROCEDURE ADD_BAD_EMAIL
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 P_EMAIL                        VARCHAR2                IN
PROCEDURE ADMIN_UPDATE_QUESTION
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 P_ID                           VARCHAR2                IN
 P_SUBJECT                      VARCHAR2                IN
 P_ANSWER                       VARCHAR2                IN
 P_STATUS                       VARCHAR2                IN
PROCEDURE ASSIGN_TO_FAQ
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 P_DISPLAYID                    VARCHAR2                IN
 P_FAQ_LIST                     VARCHAR2                IN
FUNCTION FETCH_ACTIVITY_COOKIE_VAL RETURNS VARCHAR2
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 P_VAL_NAME                     VARCHAR2                IN
.........

2) why 10?  it might be 1, it might be 100 packages.  It will probably be some
number in between.  You group things based on "am i related to the others".
Asktom -- a "real" app -- has basically that single package:

ask_tom@ASKUS> select distinct object_name from user_arguments where
package_name = 'WWC_ASK_API';

OBJECT_NAME
------------------------------
ACCEPTING_NEW_QUESTIONS
ADD_BAD_EMAIL
ADMIN_UPDATE_QUESTION
ASSIGN_TO_FAQ
FETCH_ACTIVITY_COOKIE_VAL
FETCH_ALL_VALUES_CHAR
FETCH_ASKCOOKIE_VAL
FETCH_QUESTION_ROW
FETCH_VALUE_CHAR
FETCH_VALUE_DATE
FETCH_VALUE_NUMBER
GET_PREFERENCE
IS_ADMINISTRATOR
NOTIFY
NOTIFY_OF_FOLLOWUP
PARSE_SEARCH_STRING
QUESTION_COUNT
READ_ACTIVITY_COOKIE
READ_ASKCOOKIE
REFRESH_TOP_QUESTIONS
REMOVE_BAD_EMAIL
SAVE_QUESTION
SAVE_REVIEW
SAVE_REVIEW_FOLLOWUP
SET_PREFERENCE
UPDATE_ADMINISTRATORS
UPDATE_APPLICATIONS
UPDATE_CATEGORIES
UPDATE_FAQ_CATEGORIES
UPDATE_QUESTION
URLENCODE
WORD_WRAP
WRITE_ACCESS_LOG
WRITE_ACTIVITY_COOKIE
WRITE_ACTIVITY_LOG
WRITE_ASKCOOKIE
WRITE_SEARCH_LOG

37 rows selected.

3) i don't see it that way.  I see a package as a collection of 60/80 line
subroutines.

Look at it this way -- how long would you make a procedure if:

a) each procedure was a separate object
b) each procedure was just a little snippet in a larger package.

I find people that write 100 standalone procedures have what would be to me MANY
more smaller, more modular, easier to code/debug procedures in a package.

the 37 procedures you see above?  that is just the TIP of the iceberg  there are
other functions and procedures in there you cannot even see -- they are not
relevant outside of the package and in a standalone procedure world -- they
would not even be functions or procedures - they would be inlined code.

My subroutines fit on a screen (without paging up and down), how about yours?

4) again, they are grouped by "what the heck do they do", not by "how does a not
very relevant user interface that will change over time appear to organize them"
Rating: 4
Lines doesn't make sense November 05, 2003
Reviewer: Ashiq Shamsudeen A from Trichy, Tamil Nadu, India
Hi Tom,

 From mo question " 3.Is not easier to edit a standalone procedure rather than a
package because when you open the package you will have 1000 lines of code
rather than 100 for a procedure"

  I sincerely believe number of line doesn't make sense going for procedure
instead of package. Say even if we've 100 procedure's in a package, its not
going tough in debugging or to modify a procedure in it ,because every
procedure(or function) will be a separate subroutine in the package.

   Maintainability will be difficult if we've hundred's standalone procedure
instead of packages.

 what do you say ,Tom?
Followup:
i agree with you.
Rating: 5
packages November 25, 2003
Reviewer: mo
Tom:

Is this true about using packages:

1.  You can increase security if you make the procedure private thus it can't be
called from the URL.  On the other hand all procedures can be called from URL.

2.  If we declare a variable in the package body declaration section, then the
variable is global to all procedures in the package.  If we call proc A which
creates Web Page 1 and it set x := 2, then as we go to proc B which creates Web
Page B, X is still read by proc B as being 2.  This sounds to me that the
application is becoming stateful since you can maintain values for variables in
one package but not across packages? Am I correct.

3.  If you have an app grouped by function like shipping, receiving, reports
etc. and each group has about 10 links relation to the group. would you be doing
one package per group (about 30 procedures/package) or one package per link (4
procedures/package)?

4.  With packages you can have the same procedure name used as many times as
long as each belongs to a package. With standalone procedures you can not?

5.  Packages will speed up the application because if you call the package all
procedures in the package get cached?  However since there are dynamic pages we
should never run them from cache and always go to database?
Followup:
1) only procedures that have been GRANTED on can be called from a URL -- grants
can do this as well -- but yes, procedure hiding in package bodies is one of the
really nice features (don't really think of it as a security thing tho
personally)

2) the session state should not be persisted across web pages -- so this is "not
true".  x will not be 2 in proc B.

3) none of the above.  the laws of modular coding kick in and my FUNCTIONALITY
will tell me how many packages I'll have.  I might have UI packages, application
logic packages, and common utilities .... (for example)

4) you have overloading with packages, yes.  not with standalone procedures.

5) there are others who would say the converse it true.  then you have me that
says "don't care really, it is noise and the advantages of packages far outstrip
any percieved difference")

don't understand your last point -- we are talking about cached CODE, not data
in the database at this point.
Rating: 5
How to list package bodies having internal procedure routines January 05, 2004
Reviewer: Dilip from Maryland, USA
Tom,
   Thank You for your excellent site. I was trying to find the list of package
bodies that uses internal procedure routines , i.e. procedure not declared in
package specification. Is there any Dictionary view that will help me write SQL
to list all those package bodies?

Thanks
Followup:
trick question.

the ONLY package that can use an internal routine is the one that defined it.

the list is only 1 item long and it is exactly the name of the package you are
currently looking at.  no need for the DD here.
Rating: 3
Ok February 18, 2004
Reviewer: Ram from Bangalore,India
Dear Tom,
 Does Oracle allow us to create an alias for a package atleast for dbms defined
packages?I mean similar to the
import in java and #include in c or c++.Like
  with a for dbms_output
  with b for dbms_shared_pool
 so that
  I can use in the procedure body like
   a.put_line(..)
   b.keep(...)
Calls can be many in the body of the procedure or function
rather than each time using FQON.
Does Oracle have any facility like that?
Thanks in advance.
Please do reply.
Followup:
create synonym a for dbms_output;
Rating: 3
Continuation February 19, 2004
Reviewer: Natasha from UAE
Tom,

I have a big package and recently I have modified in that. When I compile the
package and execute it...I'll get the accurate result. But When I try to execute
it 2nd time soon after the first execution, it gives me wrong values.

Please explain me why it behaves like that....I have lots of Pl/Sql tables and
global variables in that.

Pls. explain why it gives wrong results when it's executed for the second time
soon after the compilation. For the first attempt after compilation...it gives
accurate results.

Pls. advice ASAP

Natasha
Followup:
I would guess you have programmed a "bug" into your code.  PLSQL general only
does that which we tell it to.  Perhaps if you have a small, concise example --
we can take a look at it.

Probably related to your use of global variables -- perhaps you need to "reset
them" or just stop using them all together (many program bugs are due to
"globals" and improper use thereof)
Rating: 4
In Continum February 20, 2004
Reviewer: Natasha from UAE
Tom,

Thxs for ur reply...

Currently I am in a state of confusion.

When i executed the same package from "SQL PROMPT", it gives me perfect results.
But when executed from "PL/SQL DEVELOPER - Test Window", It gives me wrong
values. Even though I have checked the default Date Format of PL/SQL DEVELOPER.
Still it gives me wrong output.

But I wonder how come the same procedure executed with other tools like
"SQL-NAVIGATOR", "TOAD", "EMBAR'CO-RAPID SQL"....it give me accurate output.

I have noticed couple of bugs in PL/SQL DEVELOPER. What do u suggest TOM? Was
this a genuine reason...

Pls. advice

Thxs
Natasha
Followup:
this falls into the category of "my car won't start -- why?"

all any of these tools do is submit the code to the database, the database runs
the code.

I'd have to see a small, concise, yet 100% complete example -- else I cannot
comment.
Rating: 4
privileges on packages March 29, 2004
Reviewer: A reader
I have a user that I only want execute one of the functions in a package. The
only way I can see is to have a wrapper aound the package function.

can I .. grant execute on p_account.insert to user1

thanks
Followup:
no, you can only grant on top level objects (packages, procedures, functions,
.....)
March 29, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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
Rating: 3
Please see this March 29, 2004
Reviewer: Lenin from CT,USA
Dear Sir,
Recently I have installed Oracle9i EE in my PC.The accounts
SYS AND SYSTEM are no longer accessible.The only account I can use is
scott/tiger.Though this session has "Alter Session" privilege,I am not able to
even alter the session.

SQL>  alter session set current_schema = 'SYS';
 alter session set current_schema = 'SYS'
                                    *
ERROR at line 1:
ORA-02421: missing or invalid schema authorization identifier

SQL> alter session set current_schema = 'SYSTEM';
alter session set current_schema = 'SYSTEM'
                                   *
ERROR at line 1:
ORA-02421: missing or invalid schema authorization identifier

Any help you can provide?Actually I am now working in Sybase and we plan to
migrate our data to Oracle in about
three or four months time.
Please do write a followup.
Bye!
Followup:
ops$tkyte@ORA9IR2> alter session set current_schema=sys;

Session altered.

no quotes.
March 29, 2004 -- Thanks for the question regarding "Can Analytics function do this?", version 9.2.0
You Asked
Hi Tom,

While reading Joe Celko's article Puzzle Time (
http://www.intelligententerprise.com/000410/celko.jhtml?_requestid=13748
), I think the answer for the puzzle 2, a change-making machine, is not
that straightforward. I was wondering if the problem can be solved by analytic
function.

Thanks!

Steve
and we said...
well, since he cheated (try adding a 500$ bill to his example, he relies on
"knowing" there are N rows in money), I'll do the same.

Technically -- I don't need analytics (order by and rownum would do it for me)
but I'll use them just to pivot.

My answer might be this.  It works for upto 6 denominations (as does his)...

It could be lots shorter but the inline views made it so much easier to program.

select :cash,
       decode( c1, 0, null, c1 || ' ' || txtc1 ) || ' ' ||
       decode( c2, 0, null, c2 || ' ' || txtc2 ) || ' ' ||
       decode( c3, 0, null, c3 || ' ' || txtc3 ) || ' ' ||
       decode( c4, 0, null, c4 || ' ' || txtc4 ) || ' ' ||
       decode( c5, 0, null, c5 || ' ' || txtc5 ) || ' ' ||
       decode( trunc(rcash/c6), 0, null, trunc(rcash/c6) || ' ' || txtc6 )
  from (
select :cash, c1, c2, c3, c4, trunc(rcash/c5) c5, rcash-(trunc(rcash/c5)*c5)
rcash, c6, txtc1, txtc2, txtc3, txtc4,
txtc5, txtc6
  from (
select :cash, c1, c2, c3, trunc(rcash/c4) c4, rcash-(trunc(rcash/c4)*c4) rcash,
c5, c6, txtc1, txtc2, txtc3, txtc4,
txtc5, txtc6
  from (
select :cash, c1, c2, trunc(rcash/c3) c3, rcash-(trunc(rcash/c3)*c3) rcash, c4,
c5, c6, txtc1, txtc2, txtc3, txtc4,
txtc5, txtc6
  from (
select :cash, c1, trunc(rcash/c2) c2, rcash-(trunc(rcash/c2)*c2) rcash, c3, c4,
c5, c6, txtc1, txtc2, txtc3, txtc4,
txtc5, txtc6
  from (
select :cash, trunc(:cash/c1) c1, :cash - (trunc(:cash/c1)*c1) rcash, c2, c3,
c4, c5, c6, txtc1, txtc2, txtc3, txtc4,
txtc5, txtc6
  from (
select max(decode(rn,1,amt)) c1,
       max(decode(rn,2,amt)) c2,
       max(decode(rn,3,amt)) c3,
       max(decode(rn,4,amt)) c4,
       max(decode(rn,5,amt)) c5,
       max(decode(rn,6,amt)) c6,
       max(decode(rn,1,denomination)) txtc1,
       max(decode(rn,2,denomination)) txtc2,
       max(decode(rn,3,denomination)) txtc3,
       max(decode(rn,4,denomination)) txtc4,
       max(decode(rn,5,denomination)) txtc5,
       max(decode(rn,6,denomination)) txtc6
  from (
select amt, denomination, row_number() over (order by amt desc) rn
  from money
       )
       )
       )
       )
       )
       )
       )
/
Review & Followup

Rating: 5
any better ? March 29, 2004
Reviewer: Sajid Anwar from London
Hi Tom,
Is there any performance gain by using mod(:cash,c1) rather than
:cash-(trunc(:cash/c1)*c1)?

select :cash,
       decode( c1, 0, null, c1 || ' ' || txtc1 ) || ' ' ||
       decode( c2, 0, null, c2 || ' ' || txtc2 ) || ' ' ||
       decode( c3, 0, null, c3 || ' ' || txtc3 ) || ' ' ||
       decode( c4, 0, null, c4 || ' ' || txtc4 ) || ' ' ||
       decode( c5, 0, null, c5 || ' ' || txtc5 ) || ' ' ||
       decode( trunc(rcash/c6), 0, null, trunc(rcash/c6) || ' ' || txtc6 )
  from (
select :cash, c1, c2, c3, c4, trunc(rcash/c5) c5, mod(rcash,c5)
rcash, c6, txtc1, txtc2, txtc3, txtc4,
txtc5, txtc6
  from (
select :cash, c1, c2, c3, trunc(rcash/c4) c4, mod(rcash,c4) rcash,
c5, c6, txtc1, txtc2, txtc3, txtc4,
txtc5, txtc6
  from (
select :cash, c1, c2, trunc(rcash/c3) c3, mod(rcash,c3) rcash, c4,
c5, c6, txtc1, txtc2, txtc3, txtc4,
txtc5, txtc6
  from (
select :cash, c1, trunc(rcash/c2) c2, mod(rcash,c2) rcash, c3, c4,
c5, c6, txtc1, txtc2, txtc3, txtc4,
txtc5, txtc6
  from (
select :cash, trunc(:cash/c1) c1, mod(:cash,c1) rcash, c2, c3,
c4, c5, c6, txtc1, txtc2, txtc3, txtc4,
txtc5, txtc6
  from (
select max(decode(rn,1,amt)) c1,
       max(decode(rn,2,amt)) c2,
       max(decode(rn,3,amt)) c3,
       max(decode(rn,4,amt)) c4,
       max(decode(rn,5,amt)) c5,
       max(decode(rn,6,amt)) c6,
       max(decode(rn,1,denomination)) txtc1,
       max(decode(rn,2,denomination)) txtc2,
       max(decode(rn,3,denomination)) txtc3,
       max(decode(rn,4,denomination)) txtc4,
       max(decode(rn,5,denomination)) txtc5,
       max(decode(rn,6,denomination)) txtc6
  from (
select amt, denomination, row_number() over (order by amt desc) rn
  from money
       )
       )
       )
       )
       )
       )
       )

Thanks in advance.
Regards,
Sajid Anwar
London.
Followup:
could be -- didn't benchmark it.
March 29, 2004 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.
Rating: 3
Appears to be a bug on partition table with forall save exceptions. January 15, 2004
Reviewer: ramasamy from Charlotte, NC USA
platform :
Linux fdlxdv13 2.4.9-e.25enterprise #1 SMP Fri Jun 6 17:55:13 EDT 2003 i686
unknown

oracle ver#  :
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

Problem Description:

Save exception clause loosing the errors , error counts
when we are inserting into a partitioned destination table with bulk insert. If
you drop down the partion number to 1 in is woking good and listing all errors.
But the higher the number of partitions the more error counts are lost.

my test case.

1.Data Setup.

DROP TABLE dest CASCADE CONSTRAINTS ;

CREATE TABLE dest (
  order_no       NUMBER (9)    NOT NULL,
  RECTYPE        VARCHAR2 (10),
  CONSTRAINT PK_dest PRIMARY KEY(order_no)
 )
PARTITION BY HASH (RECTYPE)
   PARTITIONS 12;

DROP TABLE source CASCADE CONSTRAINTS ;

CREATE TABLE source (
  order_no       NUMBER (9)    NOT NULL,
  RECTYPE        VARCHAR2 (10),
  CONSTRAINT PK_source PRIMARY KEY(order_no)
 );

insert into source values (1,'HDR');
insert into source values (2,'TAX');
insert into source values (3,'SKU');
insert into source values (4,'TND');
insert into source values (5,'TND');
insert into source values (6,'HDR');
insert into source values (7,'TAX');
insert into source values (8,'SKU');
insert into source values (9,'TND');
insert into source values (10,'TND');
insert into source values (11,'TND');
insert into source values (12,'TND');
insert into source values (13,'TND');
insert into source values (14,'TND');

commit;

exit;

2.) Bulk insert code. (simple_bulk.sql)

set serveroutput on
DECLARE
        cursor c is select *
from source;

        type array is table of dest%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 SAVE EXCEPTIONS
insert into dest values data(i);
           EXCEPTION
               WHEN dml_errors THEN
                    errors := SQL%BULK_EXCEPTIONS.COUNT;
                    l_cnt := l_cnt + errors;
                    FOR i IN 1..errors LOOP
                        dbms_output.put_line
                        ('Error occurred during iteration ' ||
                          SQL%BULK_EXCEPTIONS(i).ERROR_INDEX ||
                        ' Oracle error is ' ||
                          SQL%BULK_EXCEPTIONS(i).ERROR_CODE );
                    end loop;
           end;
           exit when c%notfound;
        END LOOP;
        close c;
       dbms_output.put_line( l_cnt || ' total errors' );
end;
/

3.) run it two times.

SQL> select count(*) from dest;

  COUNT(*)
----------
         0

SQL> select count(*) from source;

  COUNT(*)
----------
        14

SQL> @simple_bulk.sql
0 total errors

PL/SQL procedure successfully completed.

SQL> select count(*) from dest;

  COUNT(*)
----------
        14
SQL> @simple_bulk.sql
Error occurred during iteration 1 Oracle error is 1
Error occurred during iteration 3 Oracle error is 1
Error occurred during iteration 5 Oracle error is 1
Error occurred during iteration 7 Oracle error is 1
Error occurred during iteration 9 Oracle error is 1
Error occurred during iteration 10 Oracle error is 1
Error occurred during iteration 11 Oracle error is 1
Error occurred during iteration 12 Oracle error is 1
Error occurred during iteration 13 Oracle error is 1
Error occurred during iteration 14 Oracle error is 1
10 total errors

PL/SQL procedure successfully completed.

missing the errors 2,4,6,8 and so on.

Hope i explained the problem clearly.
Your assistance to confirm this is as a bug is greatly
appreciated.

Thanks
Ramasamy T
Followup:
i just filed this as bu 3377931, please open a tar and reference that.
Rating: 5
bulk collect January 29, 2004
Reviewer: sanjay from Ind
hi TOM

pls help me to understand where i am doing wrong .

DECLARE
TYPE LOAD_REC IS RECORD (
HS_SEQ_CODE            EMP.EMPNO%TYPE,
HS_SAL                 EMP.SAL%TYPE);
TYPE LOAD_TAB IS TABLE OF LOAD_REC
INDEX BY BINARY_INTEGER;
LOAD_DATA_TAB LOAD_TAB ;
BULK_ERRORS EXCEPTION;
PRAGMA EXCEPTION_INIT(BULK_ERRORS,-24381);
BEGIN
SELECT EMPNO,SAL
BULK COLLECT INTO LOAD_DATA_TAB
FROM EMP;

FORALL I IN LOAD_DATA_TAB.FIRST..LOAD_DATA_TAB.LAST  --SAVE EXCEPTIONS

   INSERT INTO TRY(empno,sal) VALUES LOAD_DATA_TAB(I);

EXCEPTION
       WHEN OTHERS THEN
           DBMS_OUTPUT.PUT_LINE(SQLERRM(SQLCODE));
END;
/

it show error
ORA-06550: line 16, column 31:
PL/SQL: ORA-00947: not enough values
ORA-06550: line 16, column 4:
PL/SQL: SQL Statement ignored

BUT IF IN THE LINE 16 i change
   "INSERT INTO TRY VALUES LOAD_DATA_TAB(I);"
it works fine

can i pick a the reqried col from the record.

regards

sanjay
Followup:
 14
 15  FORall I IN LOAD_DATA_TAB.FIRST..LOAD_DATA_TAB.LAST  --SAVE EXCEPTIONS
 16     INSERT INTO TRY VALUES LOAD_DATA_TAB(I);
 17

you don't use column names with records in the values clause.
Rating: 5
January 30, 2004
Reviewer: sanjya from ind
thanks tom for the prompt reply .

can u pls explain why i cannot use column names with records in the values
clause .

regards.
Followup:
because it doesn't work that way?

see
http://otn.oracle.com/oramag/oracle/03-jul/o43asktom.html
and look for "Insert and Update from a Record"
Rating: 3
How to correct this March 17, 2004
Reviewer: Ram from Bangalore,India
Dear Tom,
How to correct the following code?
 SQL> desc t
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 X                                                  NUMBER

SQL> begin
  2   forall i in 1..10
  3    insert into t values(i);
  4  end;
  5  /
  insert into t values(i);
                       *
ERROR at line 3:
ORA-06550: line 3, column 24:
PLS-00430: FORALL iteration variable I is not allowed in this context
ORA-06550: line 3, column 3:
PLS-00435: DML statement without BULK In-BIND cannot be used inside FORALL
Please do reply.
Followup:
use an array, like the examples in the docs?

declare
   type array is table of number index by binary_integer;
   l_data array;
begin
   for i in 1 .. 10
   loop
       l_data(i) := i;
   end loop;

   forall i in 1 .. l_data.count insert into t values ( l_data(i) );
end;
/
Rating: 3
Please see this March 22, 2004
Reviewer: Richard from Minneapolis,USA
Dear Sir,
Please see the code below
SQL> 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

SQL> create table t(x number check(x <= 10));

Table created.

SQL> commit;

Commit complete.

SQL>declare
  2   bulk_errors exception;
  3   pragma exception_init(bulk_errors,-20012);
  4   type t is table of number
  5    index by binary_integer;
  6   data t;
  7  cursor c is select rownum from emp;
  8  begin
  9   open c;
 10   loop
 11  fetch c bulk collect into data;
 12   begin
 13    forall j in 1..data.count save exceptions
 14     insert into t values(data(j));
 15   exception
 16   when bulk_errors then
 17    for i in 1..sql%bulk_exceptions.count loop
 18     dbms_output.put_line(sql%bulk_exceptions(i).error_index ||chr(9)||
 19                          sqlerrm(-sql%bulk_exceptions(i).error_code));
 20    end loop;
 21   end;
 22   exit when c%notfound;
 23  end loop;
 24  close c;
 25* end;
SQL> /
ERROR:
ORA-24381: error(s) in array DML
ORA-06512: at line 13

Warning: PL/SQL compilation errors.

My effort was to simulate "Getting the error_code and error_index of
sql%bulk_exceptions".
How to correct this?But it has inserted values into Table "t" upto 10.
SQL> select * from t;

         X
----------
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
Followup:
you used the wrong error code in your exception mapping.

Look at the orig example,

  PRAGMA exception_init(dml_errors, -24381)
Rating: 3
ok March 22, 2004
Reviewer: Richard from Minneapolis,USA
Dear Tom,
 *Cannot* other error numbers be used?Why is it so specific
about "-24381"?Could you please clarify?
Thanks for your followup.
Bye!
Followup:
well, the error being thrown is 24381 -- that is the one you want to catch.

you would not want this to be fired for 1401 for example!

[tkyte@localhost tkyte]$ oerr ora 24381
24381, 00000, "error(s) in array DML"
// *Cause:  One or more rows failed in the DML.
// *Action: Refer to the error stack in the error handle.

you have to catch the error that is fired here...
Rating: 3
OK March 29, 2004
Reviewer: Stephenson from Dominican Republic
Hi Tom,
Any simple example for the cursor attribute
"SQL%BULK_ROWCOUNT"?How to use this attribute?Please do reply.
Bye!
Followup:
when you put SQL%BULK_ROWCOUNT into the search field and hit enter.... what
happened?
March 29, 2004 Pramod -- Thanks for the question regarding "Date Field as Primary Key ...", version 8.1.5
You Asked
Sometimes a column with "date" Datatype needs to be part of
Primary Key or Unique Key. While retrieveing result sets in a
SQL Query, we have to use TRUNC(date column) for date
comparision or ( Selecting rows greater or less than of database
field with TRUNC(date column)). I assume if I use TRUNC
function, optimizer may not be able to use Indexes for
retrieving result sets.

How do we handle this type of situation ? (Creating Function
Based Indexes ??? or Set NLS_DATE_FORMAT ???)
and we said...
where date_field between to_date( '01-jan-2000' ) and
                         to_date( '01-jan-2000' ) + 1-1/24/60/60

is the same as:

where trunc(date_field) = to_date( '01-jan-2000' )

That would be my preferred way to get all records for a given
DAY.
Review & Followup

Rating: 4
Useful Info....More on this January 27, 2002
Reviewer: Kishan from Singapore.
Hi Tom:

Is there any disadvantages using DATE datatype as part of Primary Key. I got
this information from one of my colleagues, taken from the net, source of which
I don't know:

<quote>
Are there any problems if I select a date column as a primary key for an Oracle
8i database?

There are many problems when having a date field as a primary key:
a) Exact fetch is practically impossible to do.
b) Referential integrity is very, very hard to accomplish.
c) In most cases you will have to do a convert function on the column and that
will eliminate the use of an index.

What I suggest is to:

First, if you can, do not choose a date; choose number(8) or number(12) and when
you update the field,
use to_number(sysdate,'yyyymmdd')

Second, to enforce a foreign key, when you update the father in a transaction,
save the date/time entered in a temporary variable, and when updating the child,
use the same variable.

If you must carry with you the info about date and time, use another column
number(4) with the hh and mm value.

Hope that's a help.
</quote>

How far is this reasoning valid? Or is this one of the Great Myths of Database
Design? If indeed its so, any suggestions as to how we can best design the
database?

Thanks for your time.

Take care,
Kishan.
Followup:
Well, my followup to their comments are:

a) exact fetch is practically impossible to do.... How so, how is the number:

20020128075256

any harder to use then a number generated from a sequence?  I mean, a date is
just a value, a number is just a value, a value is a value -- it is not ANY
harder using a date then a number

b) referential integrity is practically impossible to do... How so, how is it
harder then using a sequence?  Its just a VALUE -- its not magic, it doesn't
morph into some other value, its not liquid -- it is a stable, constant, never
changing value -- just like "sequence.nextval"

c) In most cases you will have to do a convert function ... How so?  Only if
you write really really bad code.  I cannot imagine this being an issue at all

If the date is to be part of the primary key -- so be it, it is not any
different from any other datatype.  It is not hard to use, it is just like using
a number.  I view it as NO different whatsoever.

Now -- back to the original question "if i select a date column as a primary
key".... I do have issues with a DATE being the primary key.  Dates only go down
to the second in Oracle8i and before.  What happens when two people insert at
about the same time?  You'll get the same date.  It is my opinion that a primary
key consisting of a single DATE column is an incorrect design.  You should use a
sequence instead.
Rating: 5
Thank You Very much.... January 29, 2002
Reviewer: Kishan from Singapore
Thank you very much. I reckoned that if a date column is not supposed to be used
as PK, then Oracle would certainly not allow it. But a word from an authority,
it surely helps.. :-) Thanks..
Rating: 5
why between ? September 30, 2002
Reviewer: A reader
Hi tom,

  why would you use between operator over
 date_field >= to_date( '01-jan-2000' )
and date_field <= to_date( '01-jan-2000' ) + 1-1/24/60/60

Thanks,
Followup:
less keystrokes?

reads better?

means more semantically (it is one standalone predicate compared to your two
disjoint ones)
Rating: 5
will the performance be same ? October 01, 2002
Reviewer: A reader
Hi tom,

  thanks, for answring the question.
will the performance be same using between and
two disjoint stmts ?
 also that is primary key attributes
and about 1 mil records ( will it use index ?)

Thanks,
Followup:
they will be about the same.  they do the same thing.
Rating: 5
March 29, 2004
Reviewer: A reader
Hi Tom,

I think the point being made by Kishan's comment above is that if you needed to
use just the date (and NOT the time component of the date) as part of a primary
key, then you're hosed since every insert with a 1-second interval will make it
into the table. To demonstrate what I mean:

create table t2 (x varchar2 (1), y int, z date, constraint
pk_t2 primary key (y, z) )
/

insert into t2 values ('a', 111, sysdate )
/
insert into t2 values ('a', 111, sysdate )
/
insert into t2 values ('a', 111, sysdate )
/
insert into t2 values ('a', 111, sysdate )
/

All of the above inserts, assuming a one-second gap, will make it into the
table, whereas the requirement might have been to fail all but the first insert
in my example above. I've run into this situation a few times, and some database
designers and modelers have suggested excluding dates from the primary key for
such a requirement. And then the only avenue is to use a formatted string that
includes just the date component, such as "to_char (sysdate, 'MMDDYYYY')", as
part of the primary key, instead of the date. I hope I've been able to explain
myself... I think this is what was being attempted to be explained in the
comments above. Any thoughts? Thanks.
Followup:
but that has nothing to do with the expressed concerns above.

IF you are using SYSDATE as a primary key value
AND you are inserting more than one record per second
BY DEFINITION you cannot use SYSDATE as the primary key value

Is there anything wrong with using a column of type DATE as a primary key?  NO.

Can there be a logical problem with it in your application? YES.  but the same
is true of numbers and strings.  not really datatype based -- but rather data
VALUE based.
March 29, 2004 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
Rating: 4
LIO always larger than PIO ? January 27, 2004
Reviewer: FZ from CA USA
Tom,
  My understanding is LIO should always be larger than PIO for any SQL.  But by
querying statspack data, it has been seen that PIO > LIO for some SQLs.  Can you
shed light on this?
  Thank you.
Followup:
IO to temp is not counted as LIO.

So, it is very easy to see PIO's far exceed LIO if you do lots of hash/sorts.
Rating: 5
clarification March 29, 2004
Reviewer: amit poddar from CT, USA
quoting you from above
"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. "

Does that mean at any time there are two blocks in the PGA for a index access ?

1. Index block
2. Table block

and Oracle keeps getting different data blocks as guided by the entries in the
index blocks. and when it has completed reading the whold index block it gets
the next leaf block and the process continues in similar fashion ??
Followup:
yes, no maybe.

depends on the array size, the query plan, whats going on.
Rating: 5
So much useful info, so well explained! March 29, 2004
Reviewer: hxlvt from md usa
Thanks so much.
March 29, 2004 Mandar -- Thanks for the question regarding "Assigning a DB profile to self.", version 7.3
You Asked
I have come across a form (Forms 4.5) where a supervisor( SUPERVISOR role with
all DBA kind of privs) can  create a visitor user with read only permissions and
assigns it a temporary profile. This temp profile forces visitor to change the
password when he logs in first time through forms. And this temp profile gives
new user 5 days time. As soon as visitor changes its password the following
kstatement is fired  to change its own password.

ALTER USER <username> IDENTIFIED BY <password> PROFILE PERM_PROFILE;

but if visitor does not have ALTER USER system privilege then it fails on the
above command.

Which is the DB privilege, we can give to VISITOR on the PERM_PROFILE, that can
resolve this problem. Obviously I don't want to give visitor 'ALTER USER'
privilege.

Is there any other way to handle this situation?
and we said...
Create a stored procedure that is owned by some "admin" account.

This procedure will use dbms_sql to dynamically execute

   'alter user ' || user || ' identified by ' || password ||
     ' profile perm_profile';

The OWNER of that procedure will need the "ALTER USER" priv (granted directly,
not VIA a role).  The owner of that procedure can grant execute on this to
"public". As it can only change the profile of the currently logged in user to
something you want, it is "safe" in that regards.

Execute that procedure instead of attempting to issue the command directly.  You
are wise to not want all users to have the extremely powerful alter user command
(with which they can take over your entire database -- all i need is create
session and alter user and I can do anything I want)
Review & Followup

Rating: 5
Assigning DB Profile to self. November 20, 2001
Reviewer: Mandar Gokhale from Sydney, Australia
USEFUL????
The most useful, I would say.
This resolved my entire security issue.
Rating: 5
How to force users to change passwords the first time they logon? March 29, 2004
Reviewer: Ryan Gaffuri from Tysons Corner, VA
I want to do the following:

1. Create several users with a generic password
2. require the users to change their password the first time they login and have
VERIFY_FUNCTION validate it.

I noticed that I can have a policy with PASSWORD_LIFE_TIME and set it to 0. This
would force an immediate password change.

I don't see any triggers for 'alter any user' so that I can then switch the user
to a different profile with a longer PASSWORD_LIFE_TIME

or is there another way to do this?
Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96521/secure.htm#7375
March 29, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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
Rating: 4
How do you predict relative refresh times? March 29, 2004
Reviewer: Peter from UK
Tom, you comment that 'Fast' is not the best description for the non-complete
refresh.

The business model in our DWH requires that we report 'AS IS' on data (that is
we always join the fact to the current values of the reference data) The
downside of this that when items change parentage (You'd think that <they> would
know apples are members of fruit!) There is a need to refresh the pre-built
aggregations
Fast MV refresh (on demand) is, in general, OK for our needs, but sometimes (to
often) the business throws a major change that is really faster to process as a
complete refresh - can you think of a way we can "guess" which approach will be
quicker for a given data change and then execute the most appropriate refresh?.
A straight count of changed 'child' records will not help as the number parents
affected could vary greatly - we have seen a change of 5% of the data affecting
25% of a parent summary and the same amount of change affecting less than 3% the
same summary
Followup:
well, there are no "non-complete" -- they are "incremental"

but, the timing is going to be based on not only the percentage of change -- but
the complexity involved and the spread of the data.

update 1 row 1,000,000 times -- not much resulting work.

update 1,000,000 rows 1 time each -- tons of resulting work.

an mv that takes 1,000,000 detail rows down to 1,000 aggregate rows would
probably refresh much faster than one that goes down to 100,000 aggregate rows.

The only think I can think would be to capture over time the refresh times
you've experienced...
Rating: 2
Thanks March 29, 2004
Reviewer: Peter from UK
OK... I was trying not to use the F (fast) word ;-} but chose the wrong
alternative!

I fully understand your points (and they match our experience) But for a
production system it would be great to have qualitative indicator to say "fast
is best" or not.

Could we use the view logs to help on this?
Followup:
you'd have to customize the refresh -- you would want to create a procedure that

snooped the mv log to get a count (perhaps by dml_type and maybe get counts of
distinct primary keys)

got the timestamp
called dbms_mview to refresh
get the timestamp again

and log that information

schedule THAT job instead of using the MV refreshing by itself -- to get the
history.
March 29, 2004 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.
Rating: 4
Any issue with remote procedures? February 18, 2004
Reviewer: David from USA
Hi, Tom.

We have a package with several procedures that are run at various times.

Ideally, we'd like to send out an email when a procedure finishes.

The package is on a (production) 8.0.6 box.  We have a 9i box, so I've set up a
send_mail job on it, and I remotely call it in the 'end_proc' procedure that's
part of the package.  (end_proc logs the end time and calculates the elapsed
time, logs it, and then calls a LOCAL STANDALONE procedure, which in turn calls
the REMOTE STANDALONE send_mail procedure.)

This works like a charm when the packaged procedures are called from SQL*Plus.

But when we use dbms_job and SUBMIT procedures in this package, the email never
gets sent.  All the steps in end_proc prior to the call to send_mail (the local
one) get executed.

End_proc has an exception block with log_error in it, but nothing gets logged.

Is there anything special about the way a job run by dbms_jobs would resolve a
database link?  Here's end_proc:

procedure end_proc (p_procedure_name in varchar2) as

l_procedure   varchar2(64);
l_finished    date;
l_elapsed     number(5,2);
l_run_for     date;
l_osuser      varchar2(20);
l_message     varchar2(2000);
l_cr_lf       varchar2(2) := chr(13) || chr(10);

begin

l_procedure
    := p_procedure_name;

-- set the end time and calculate the elapsed time

update cap_proc_log set end_time = sysdate
 where ....;

update cap_proc_log set elapsed_time = (end_time - start_time) * 24 * 60;

/* the proc log is small so we just update all the rows rather than searching
for the right row */

commit;

-- capture end time for email

 select end_time, elapsed_time, run_for_date
 into l_finished, l_elapsed, l_run_for
 from .....

 select osuser
 into     l_osuser
 from  v$Session
 where  audsid = (select userenv ('sessionid')
                          from dual);

l_message  := 'User - '   || l_osuser || l_cr_lf ||......

-- this doesn't happen when submitted through dbms_jobs
send(l_message);

commit;

exception

when others then
  log_error;

end end_proc;

Thanks as always.
Followup:
how is the dblink created?
Rating: 4
Nevermind February 18, 2004
Reviewer: David from USA
Figured it out.
Rating: 4
But,.... February 18, 2004
Reviewer: David from USA
If you know how I could include the OS user....

;)
Followup:
the os user is sort of "constant", it would be ORACLE (it is the job that is
running)

if you want the osuser of the person submitting the job, you'd have to capture
that with the job submission.
Rating: 3
Actually... February 19, 2004
Reviewer: David from USA
It was this:

select osuser
into   l_osuser
from   v$Session
where  audsid = (select userenv ('sessionid')
                          from dual);

This was apparently returning more than one row.  I wasn't catching it because
of the 'bug' :  when others then - without a raise.

There are cases elsewhere, in other procedures, where too_many_rows is an
expected condition, so I was ignoring them in the error_table, when in fact they
were thrown by end_proc and should have been raised.

Now, how would I capture the osuser when the job is submitted?
Followup:
select * from v$session where sid = ( select sid from v$mystat where rownum=1 )

that is what I like to use -- it is "safe"

you would run that before submitting the job and pass it to the job (I like to
use a parameter table for jobs!!!)

create table job_parameters (job_id number primary key, whatever inputs it
needs);

and then:

begin
   dbms_job.submit( l_job, 'P(job);', ..... );
   insert into job_parameters( l_job, ..... );
   commit;
end;

that way the job can start with:

procedure P( p_job in number )
is
   l_rec job_parameters%rowtype;
begin
   select * into l_rec from job_parameters where job_id = p_job;
   .....

   delete from job_parameters where job_id = p_job;
   commit;
end;

(eg: very very bind variable friendly!!!)
Rating: 4
dbms_job March 25, 2004
Reviewer: mo
Tom:

If  I have this statement running in instance A but I want the job to be
scheduled in instance B how do you do that:

dbms_job.submit(l_job,'SEND_EMAIL(JOB);',sysdate+7);

Thanks
Followup:
read about submit, it takes an "instance" parameter you can use to do just that.
Rating: 5
dbms_submit March 26, 2004
Reviewer: mo
Tom:

Are you sure? I am looking at 8i book and the syntax is:

PROCEDURE SUBMIT(job OUT  BINARY_INTGER,
what IN VARCHAR2,
next_date  IN DATE  DEFAULT SYSDATE,
interval  IN VARCHAR2  DEFAULT  NULL,
no_parse  IN  BOOLEAN  DEFAULT  FALSE);

Nothing on instance?
I tried to call a procedure from another instance and that does not work either.
Followup:
Yes, i am sure.  Here is "our" 8i book:
http://download-west.oracle.com/docs/cd/A87860_01/doc/index.htm

Rating: 5
re: dbms_job and instance March 26, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
Another method if, for some reason, you can't find it in the Oracle docs is just
this:

SQL> desc dbms_job

[snip]

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

[snip]

Hey!  Look at that!  There is an 'INSTANCE' parameter...

- Mark
Followup:
and

select text from all_source where name = 'DBMS_JOB' and type = 'PACKAGE' order
by line;

will give you "auto-doc"
Rating: 5
dbms_job March 26, 2004
Reviewer: mo
Tom:

1.  The instance parameter is "integer".  How can you get instance number when
every instance I log into has a number of "1"?

2.
instance=  When a job is submitted, specifies which instance can run the job.

The description for this parameter implies that you an specify which instance
can run the job.  This means that job scedhled is still in "Instance A" but
Instance B is allowed to run it.

What I am trying to do is that I have one package in instance A with 12
procedures.  One procedure for another app is in instance B that schedules the
email and running the email procedure in instance B.  I am trying to have the
email procedure part of the package in instance A.  All I will be doing is
submitting job in instance B.
Followup:
this is a derived parameter.

show us the output of:

select value from gv$parameter where name = 'instance_number';
Rating: 5
dbms_job March 29, 2004
Reviewer: mo
Tom:

I logged in to several instances and the result to:

select value from gv$parameter where name = 'instance_number';

VALUE
-----------------------
0

Correct me if I am wrong:

The instance parameter allows another instance (B) to call the job I scheduled
(instance A). This is not what I really want.  What I want is to do osomething
like this:

I want to run this in instance A (call a procedure in instance B):

dbms_job.submit(l_job,'packageB.SEND_EMAIL(JOB)@b_link;',sysdate+8);

Is it possible? I am getting an error when I try it.
Followup:
it is zero when you are not using RAC.

so -- are you using RAC.
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96536/ch179.htm#1017835
if not, you only have a single instance with your database.

so, you really don't want to specify an INSTANCE, you want to specify a
DATABASE.

"i am getting an error" is as clear as a muddy lake.  care to share? (the syntax
would really be  packageb.send_email@remote( job ).... but I do see another
issue you would be passing the JOB id to the remote site -- what will it do with
that?  the data is on the local site)

suggest you do this

a) create a local procedure send_email_at_remote
b) have it call the remote procedure
c) use a synonym: create synonym remote_package for package@remote

schedule the LOCAL job that does the REMOTE call after retrieving the inputs
from the LOCAL table and pass them as formal named parameters to the remote job.
March 29, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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?
Rating: 5
why this does not use index skip scan? February 24, 2004
Reviewer: pinguman
I have question about this feature, using 9.2.0.4 on HP-UX

Basically I have query which fits perfectly in this feature but Oracle is not
using it, when I force skip scan hint it does index full scan instead of
range... The SETID columns have only a single value.

When I add setid condition uery runs fast and use proper plan.

Can you throw some lights?

SELECT

a.RBTACCTNAME,c.inst_prod_id,c.product_id,d.descr,e.PA_EXTERNAL_ID,e.PA_COMP_INST
_ID,e.PA_FAC_STATUS_VAL,
           f.attribute_id,f.attribute_value,c.ORDER_DATE,c.INSTALLED_DATE
FROM sysadm.ps_rbt_account a,sysadm.ps_rftinst_prod b,sysadm.ps_rf_inst_prod
c,sysadm.PS_PROD_ITEM d,
         sysadm.ps_pa_arbor_elemen e,sysadm.ps_rf_attr_insp f
WHERE a.RBTACCTID = b.RBTACCTID
          AND f.INST_PROD_ID (+)= c.INST_PROD_ID
          AND e.INST_PROD_ID = c.INST_PROD_ID
          AND c.inst_prod_id = b.inst_prod_id
          AND d.product_id =c.product_id
          AND a.RBTACCTNAME='N28281879K0002'
ORDER BY a.RBTACCTNAME

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.16       0.19          2          3          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2     23.70     106.40      97324      97462          0           7
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4     23.86     106.60      97326      97465          0           7

Rows     Row Source Operation
-------  ---------------------------------------------------
      7  HASH JOIN OUTER (cr=97462 r=97324 w=0 time=106407272 us)
      6   NESTED LOOPS  (cr=61414 r=61293 w=0 time=94604699 us)
      6    NESTED LOOPS  (cr=46995 r=46910 w=0 time=63306469 us)
      6     NESTED LOOPS  (cr=46981 r=46910 w=0 time=63305379 us)
      6      NESTED LOOPS  (cr=7 r=0 w=0 time=294 us)
      1       INDEX RANGE SCAN OBJ#(46628) (cr=3 r=0 w=0 time=82 us)(object id
46628)
      6       INDEX RANGE SCAN OBJ#(55878) (cr=4 r=0 w=0 time=174 us)(object id
55878)
      6      PARTITION HASH ITERATOR PARTITION: KEY KEY (cr=46974 r=46910 w=0
time=63304776 us)
      6       TABLE ACCESS FULL OBJ#(47711) PARTITION: KEY KEY (cr=46974 r=46910
w=0 time=63304587 us)
      6     TABLE ACCESS BY INDEX ROWID OBJ#(46023) (cr=14 r=0 w=0 time=809 us)
      6      INDEX RANGE SCAN OBJ#(46170) (cr=8 r=0 w=0 time=529 us)(object id
46170)
      6    PARTITION HASH ITERATOR PARTITION: KEY KEY (cr=14419 r=14383 w=0
time=31297927 us)
      6     TABLE ACCESS FULL OBJ#(48103) PARTITION: KEY KEY (cr=14419 r=14383
w=0 time=31297773 us)
2703952   TABLE ACCESS FULL OBJ#(33064) (cr=36048 r=36031 w=0 time=5457645 us)

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=18548 Card=15 Bytes=3015)
   1    0   HASH JOIN (OUTER) (Cost=18548 Card=15 Bytes=3015)
   2    1     NESTED LOOPS (Cost=14779 Card=15 Bytes=2250)
   3    2       NESTED LOOPS (Cost=11320 Card=15 Bytes=1890)
   4    3         NESTED LOOPS (Cost=11290 Card=15 Bytes=1200)
   5    4           NESTED LOOPS (Cost=5 Card=15 Bytes=630)
   6    5             INDEX (RANGE SCAN) OF 'PS5RBT_ACCOUNT' (NON-UNIQUE)
(Cost=3 Card=1 Bytes=23)
   7    5             INDEX (RANGE SCAN) OF 'PS0RFTINST_PROD' (NON-UNIQUE)
(Cost=3 Card=15 Bytes=285)
   8    4           PARTITION HASH (ITERATOR)
   9    8             TABLE ACCESS (FULL) OF 'PS_RF_INST_PROD' (Cost=753 Card=1
Bytes=38)
  10    3         TABLE ACCESS (BY INDEX ROWID) OF 'PS_PROD_ITEM' (Cost=2 Card=1
Bytes=46)
  11   10           INDEX (RANGE SCAN) OF 'PSBPROD_ITEM' (NON-UNIQUE) (Cost=1
Card=1)
  12    2       PARTITION HASH (ITERATOR)
  13   12         TABLE ACCESS (FULL) OF 'PS_PA_ARBOR_ELEMEN' (Cost=231 Card=1
Bytes=24)
  14    1     TABLE ACCESS (FULL) OF 'PS_RF_ATTR_INSP' (Cost=3467 Card=2845310
Bytes=145110810)

============================================================================

SELECT /*+ index_ss(c PS_RF_INST_PROD) */

a.RBTACCTNAME,c.inst_prod_id,c.product_id,d.descr,e.PA_EXTERNAL_ID,e.PA_COMP_INST
_ID,e.PA_FAC_STATUS_VAL,
           f.attribute_id,f.attribute_value,c.ORDER_DATE,c.INSTALLED_DATE
FROM sysadm.ps_rbt_account a,sysadm.ps_rftinst_prod b,sysadm.ps_rf_inst_prod
c,sysadm.PS_PROD_ITEM d,
         sysadm.ps_pa_arbor_elemen e,sysadm.ps_rf_attr_insp f
WHERE a.RBTACCTID = b.RBTACCTID
          AND f.INST_PROD_ID (+)= c.INST_PROD_ID
          AND e.INST_PROD_ID = c.INST_PROD_ID
          AND c.inst_prod_id = b.inst_prod_id
          AND d.product_id =c.product_id
          AND a.RBTACCTNAME='N28281879K0002'
ORDER BY a.RBTACCTNAME

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.15       0.14          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2    188.57     347.84     524458     524598          0           7
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4    188.72     347.98     524458     524598          0           7

Rows     Row Source Operation
-------  ---------------------------------------------------
      7  HASH JOIN OUTER (cr=524598 r=524458 w=0 time=347839584 us)
      6   NESTED LOOPS  (cr=488550 r=488424 w=0 time=315275165 us)
      6    NESTED LOOPS  (cr=474131 r=474042 w=0 time=293947883 us)
      6     NESTED LOOPS  (cr=474117 r=474042 w=0 time=293945089 us)
      6      NESTED LOOPS  (cr=7 r=0 w=0 time=307 us)
      1       INDEX RANGE SCAN OBJ#(46628) (cr=3 r=0 w=0 time=83 us)(object id
46628)
      6       INDEX RANGE SCAN OBJ#(55878) (cr=4 r=0 w=0 time=173 us)(object id
55878)
      6      TABLE ACCESS BY GLOBAL INDEX ROWID OBJ#(47711) PARTITION: ROW
LOCATION ROW LOCATION (cr=474110 r=474042 w=0 time=293944
414 us)
      6       INDEX FULL SCAN OBJ#(55877) (cr=474104 r=474042 w=0 time=293943840
us)(object id 55877)
      6     TABLE ACCESS BY INDEX ROWID OBJ#(46023) (cr=14 r=0 w=0 time=2476 us)
      6      INDEX RANGE SCAN OBJ#(46170) (cr=8 r=0 w=0 time=2247 us)(object id
46170)
      6    PARTITION HASH ITERATOR PARTITION: KEY KEY (cr=14419 r=14382 w=0
time=21326288 us)
      6     TABLE ACCESS FULL OBJ#(48103) PARTITION: KEY KEY (cr=14419 r=14382
w=0 time=21326143 us)
2703952   TABLE ACCESS FULL OBJ#(33064) (cr=36048 r=36034 w=0 time=24444187 us)

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1238733 Card=15 Bytes=3015)
   1    0   HASH JOIN (OUTER) (Cost=1238733 Card=15 Bytes=3015)
   2    1     NESTED LOOPS (Cost=1234964 Card=15 Bytes=2250)
   3    2       NESTED LOOPS (Cost=1231505 Card=15 Bytes=1890)
   4    3         NESTED LOOPS (Cost=1231475 Card=15 Bytes=1200)
   5    4           NESTED LOOPS (Cost=5 Card=15 Bytes=630)
   6    5             INDEX (RANGE SCAN) OF 'PS5RBT_ACCOUNT' (NON-UNIQUE)
(Cost=3 Card=1 Bytes=23)
   7    5             INDEX (RANGE SCAN) OF 'PS0RFTINST_PROD' (NON-UNIQUE)
(Cost=3 Card=15 Bytes=285)
   8    4           TABLE ACCESS (BY GLOBAL INDEX ROWID) OF 'PS_RF_INST_PROD'
(Cost=82098 Card=1 Bytes=38)
   9    8             INDEX (FULL SCAN) OF 'PS_RF_INST_PROD' (UNIQUE)
(Cost=82096 Card=1)
  10    3         TABLE ACCESS (BY INDEX ROWID) OF 'PS_PROD_ITEM' (Cost=2 Card=1
Bytes=46)
  11   10           INDEX (RANGE SCAN) OF 'PSBPROD_ITEM' (NON-UNIQUE) (Cost=1
Card=1)
  12    2       PARTITION HASH (ITERATOR)
  13   12         TABLE ACCESS (FULL) OF 'PS_PA_ARBOR_ELEMEN' (Cost=231 Card=1
Bytes=24)
  14    1     TABLE ACCESS (FULL) OF 'PS_RF_ATTR_INSP' (Cost=3467 Card=2845310
Bytes=145110810)

============================================================================

SELECT

a.RBTACCTNAME,c.inst_prod_id,c.product_id,d.descr,e.PA_EXTERNAL_ID,e.PA_COMP_INST
_ID,e.PA_FAC_STATUS_VAL,
           f.attribute_id,f.attribute_value,c.ORDER_DATE,c.INSTALLED_DATE
FROM sysadm.ps_rbt_account a,sysadm.ps_rftinst_prod b,sysadm.ps_rf_inst_prod
c,sysadm.PS_PROD_ITEM d,
         sysadm.ps_pa_arbor_elemen e,sysadm.ps_rf_attr_insp f
WHERE a.RBTACCTID = b.RBTACCTID
          AND f.INST_PROD_ID (+)= c.INST_PROD_ID
          AND e.INST_PROD_ID = c.INST_PROD_ID
          AND c.inst_prod_id = b.inst_prod_id
          AND d.product_id =c.product_id
          AND a.RBTACCTNAME='N28281879K0002'
          and c.setid = 'SHARE'
          and e.setid = 'SHARE'
ORDER BY a.RBTACCTNAME

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.15       0.15          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2     12.29      12.20      36032      36109          0           7
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4     12.44      12.35      36032      36109          0           7

Rows     Row Source Operation
-------  ---------------------------------------------------
      7  HASH JOIN OUTER (cr=36109 r=36032 w=0 time=12201778 us)
      6   NESTED LOOPS  (cr=61 r=0 w=0 time=1312 us)
      6    NESTED LOOPS  (cr=41 r=0 w=0 time=968 us)
      6     NESTED LOOPS  (cr=27 r=0 w=0 time=650 us)
      6      NESTED LOOPS  (cr=7 r=0 w=0 time=214 us)
      1       INDEX RANGE SCAN OBJ#(46628) (cr=3 r=0 w=0 time=90 us)(object id
46628)
      6       INDEX RANGE SCAN OBJ#(55878) (cr=4 r=0 w=0 time=101 us)(object id
55878)
      6      TABLE ACCESS BY GLOBAL INDEX ROWID OBJ#(47711) PARTITION: ROW
LOCATION ROW LOCATION (cr=20 r=0 w=0 time=367 us)
      6       INDEX UNIQUE SCAN OBJ#(55877) (cr=14 r=0 w=0 time=222 us)(object
id 55877)
      6     TABLE ACCESS BY INDEX ROWID OBJ#(46023) (cr=14 r=0 w=0 time=259 us)
      6      INDEX RANGE SCAN OBJ#(46170) (cr=8 r=0 w=0 time=155 us)(object id
46170)
      6    TABLE ACCESS BY GLOBAL INDEX ROWID OBJ#(48103) PARTITION: ROW
LOCATION ROW LOCATION (cr=20 r=0 w=0 time=294 us)
      6     INDEX UNIQUE SCAN OBJ#(55791) (cr=14 r=0 w=0 time=200 us)(object id
55791)
2703952   TABLE ACCESS FULL OBJ#(33064) (cr=36048 r=36032 w=0 time=5854299 us)

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3864 Card=15 Bytes=3195)
   1    0   HASH JOIN (OUTER) (Cost=3864 Card=15 Bytes=3195)
   2    1     NESTED LOOPS (Cost=95 Card=15 Bytes=2430)
   3    2       NESTED LOOPS (Cost=65 Card=15 Bytes=1980)
   4    3         NESTED LOOPS (Cost=35 Card=15 Bytes=1290)
   5    4           NESTED LOOPS (Cost=5 Card=15 Bytes=630)
   6    5             INDEX (RANGE SCAN) OF 'PS5RBT_ACCOUNT' (NON-UNIQUE)
(Cost=3 Card=1 Bytes=23)
   7    5             INDEX (RANGE SCAN) OF 'PS0RFTINST_PROD' (NON-UNIQUE)
(Cost=3 Card=15 Bytes=285)
   8    4           TABLE ACCESS (BY GLOBAL INDEX ROWID) OF 'PS_RF_INST_PROD'
(Cost=2 Card=1 Bytes=44)
   9    8             INDEX (UNIQUE SCAN) OF 'PS_RF_INST_PROD' (UNIQUE) (Cost=1
Card=1)
  10    3         TABLE ACCESS (BY INDEX ROWID) OF 'PS_PROD_ITEM' (Cost=2 Card=1
Bytes=46)
  11   10           INDEX (RANGE SCAN) OF 'PSBPROD_ITEM' (NON-UNIQUE) (Cost=1
Card=1)
  12    2       TABLE ACCESS (BY GLOBAL INDEX ROWID) OF 'PS_PA_ARBOR_ELEMEN'
(Cost=2 Card=1 Bytes=30)
  13   12         INDEX (UNIQUE SCAN) OF 'PS_PA_ARBOR_ELEMEN' (UNIQUE) (Cost=1
Card=1)
  14    1     TABLE ACCESS (FULL) OF 'PS_RF_ATTR_INSP' (Cost=3467 Card=2845310
Bytes=145110810)
Followup:
hows about telling us what columns constitute the index.

Lots of data but none of it helps one out this time.

Need to know

a) what the columns in order are.
b) what the distinct cardinality of the FIRST column in the index is.
Rating: 5
more info February 24, 2004
Reviewer: pinguman
Hi

a) what the columns in order are.

'PS_RF_INST_PROD' is made up of SETID and INST_PROD_ID
'PS_PA_ARBOR_ELEMEN' is made up of SETID and INST_PROD_ID

b) what the distinct cardinality of the FIRST column in the index is.

SETID is the first/leading column and has value 'SHARE'
Followup:
tell you what - if you export the schema definition for those tables:

exp userid=u/p 'tables=(list of tables in the query)' rows=n

and export the stats using dbms_stats into another dmp file

tar and compress them

email them to me.... I'll take a peek.  need to understand the partitioning
scheme.
Rating: 4
Index Skip Scan in 8174 Database March 29, 2004
Reviewer: Vivek Sharma from Bombay, India
Dear Tom,

I am facing a Unique Problem in my 8174 Version of Database. I have not seen
this behaviour before and hence I feel this as a Unique Behaviour.

I have a table of 65 Lakhs record. Table Name is PDC_BANKING_TBL. I have created
an Index PDC_BANKING_TBL_VS on columns(PBN_CHQ_NO, PBN_OUTBATCH_NO, PBN_CUST_CD,
PBN_AGREEMENT_NO). The Ordering are same as mentioned. BUt my below mentioned
query is doing a Index Full Scan on this Index and the queries takes lots of
time.

select distinct(pbn_cust_cd) from pdchome.pdc_banking_tbl
where  pbn_agreement_no = 'LBCHE000564718' ;

Query Plan
-----------------------------------------------------------
SELECT STATEMENT   [CHOOSE] Cost = 9
  SORT UNIQUE
    INDEX FAST FULL SCAN PDC_BANKING_TBL_VS

Whereas when I drop this Index, it uses a Range Scan on another Index with
column(PBN_AGREEMENT_NO, PBN_XYZ_COL). i.e. this Index has only one column in
the query. When Using this second Index, the query runs in seconds.

The Schemas are analyzed and the statistics are latest. Then why the optimizer
is using this Index Skip Scan feature? As per my knowledge, the index should
only be used when the leading columns are used in the query.

Please help me out as this is the query which is executed most of the time in
our database and takes heavy resources.

Regards
Vivek
Followup:
that is not a skip scan, its a full scan, very different.

show use the tkprofs with and without the indexes and use AUTOTRACE TRACEONLY
explain as well please.
Rating: 4
Difference between Full Scan and Index Skip Scan March 29, 2004
Reviewer: Vivek Sharma from Bombay, India
Dear Tom,

Thanks the update. But I am not satisfied the answer given by you. I basically
know the difference between the Full Scan and Index Skip Scan and hence had
asked this question that why the query is using an Index with the trailing
columns. Basically it is the concept of Index Skip Scan introduced in 9i. But
mine is an Oracle8i 8174 Database and my query is doing a Index Full Scan on
Index having a trailing columns. Why ?

I cannot drop and recreate the Index as it is my production database and
creating an Index takes about an hour but have tried this once. I created the
Index, noted this behaviour and the response time was poor. I dropped the Index
and then checked the plan, it used an index with the column involved in the
where clause as a leading column. The response time was very good (in seconds).
In the evening again I created this index again the query started using the new
Index and the respone time poor.

Why is this behaviour ? I know this is not Index Skip Scan but the behavious is
almost same. My question is not the concept. My question is why the query is
using an Index with a trailing column ?

Regards
Vivek
Followup:
satisfied or not, it is NOT skip scanning.  If it were -- it would say "index
skip scan".

It says clearly INDEX FAST FULL SCAN PDC_BANKING_TBL_VS

It is using the index as a SKINNY version of the table.  It is full scanning the
index using multi-block IO.  It is not even a little related to skip scans.

The optimizer has decided that this index is small enough that the advantage of
multi-block IO to read a little more outweighs the single block IO used in the
range scan.

when you created this new index, did it have any statistics?
what is the count returned by said query
Rating: 4
Thanks Tom March 29, 2004
Reviewer: Vivek Sharma from Bombay, India
Thanks Tom for your patience and quick response. The output of the query is only
one row. Based your answer, I can come to this conclusion that even in oracle
versions prior to 9i, Optimizer can make use of Index with a trailing columns.
Then How do I avoid using this Index Full Scan. I cannot  change the code and
provide HINT as it is a third party application and I cannot drop the Index as
it has improved the performance of other query.

Please provide me some solution.

Regards
Vivek
Followup:
i know it is only ONE row -- my question was "how many records did we have to
scan" -- what was the OUTPUT?

oracle has for a very very long time been able to use the index in this fashion,
yes.

would you like to provide me with what I asked for?

did you compute stats on the index itself?
what was the output of the query?
Rating: 4
Thanks a lot Tom March 30, 2004
Reviewer: Vivek Sharma from Bombay, India
Dear Tom,

Thanks a lot for your valuable suggestions. After Analyzing the Index, it got
resolved. I appreciate your patience and know that when I come to this site with
some queries it will be resolved definetely. This is why I feel that I have
learnt a lot from this site of yours.

Thanks again.

Regards
Vivek
March 29, 2004 sanjay -- Thanks for the question regarding "Displaying PDF files stored in the Database", version Intermedia
You Asked
Tom,
How can we display PDF files stored in the database,
using htp.p (OAS PL/SQL cartridge )calls and dbms_lob ?
We are developing a document archival and display app.
Using ctxload, I can upload the files. Now, I would like
to display the file in its native format (PDF) on the
web.
thanks
sanjay
and we said...
Sure, if you have the images in a BLOB, its quite easy.  Here is
an example that loads a GIF into the datbase and displays it.
You would of course change the mime type and the name of the
procedure from .gif to .pdf.  Then you will be able to use HTML
like:

<a href=/dcd/owa/image.pdf?p_id=55>Click Me</a>

to retrieve "document 55" for example.

The following code just created a demo table and loads an
image into it -- you already have the pdfs loaded so its not
100% relevant to your problem

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;
/

Now here is the package that can retrieve the pdf (or anything
for that matter.  Just keep adding procedures that are named
after the file type like .doc, .pdf, .xls and so on.  Some
browsers really want the extension in the URL to be "correct")

create or replace package image_get
as
    procedure gif( p_id in demo.id%type );
end;
/

create or replace package body image_get
as

procedure gif( p_id in demo.id%type )
is
    l_lob    blob;
    l_amt    number default 30;
    l_off   number default 1;
    l_raw   raw(4096);
begin
    select theBlob into l_lob
        from demo
         where id = p_id;

        -- make sure to change this for your type!
    owa_util.mime_header( 'image/gif' );

        begin
           loop
              dbms_lob.read( l_lob, l_amt, l_off, l_raw );

              -- it is vital to use htp.PRN to avoid
              -- spurious line feeds getting added to your
              -- document
              htp.prn( utl_raw.cast_to_varchar2( l_raw ) );
              l_off := l_off+l_amt;
              l_amt := 4096;
           end loop;
        exception
           when no_data_found then
              NULL;
        end;
end;

end;
/

UTL_RAW might not be installed on your database.  It is part
of replication.  If you do not have it installed, simply:

o cd $ORACLE_HOME/rdbms/admin
o find the two files with "raw" in their name (eg: ls *raw*)
o using svrmgrl connect as INTERNAL OR SYS -- only these users,
no one else can successfully install UTL_RAW
o run the .sql and then the .plb file
Review & Followup

Rating: 4
April 04, 2001
Reviewer: A reader from UK
Rating: 4
May 24, 2001
Reviewer: PK from Raleigh, NC
Excellent subject knowledge.
Rating: 4
May 24, 2001
Reviewer: PK from Raleigh, NC
Excellent subject knowledge.
Rating: 4
Excellent in providing the guideline July 04, 2001
Reviewer: PraveenKumar Talla from India
There are no words to give any review on the data you are providing to the
users. Thanks and very much for the beneficial work that Oracle Corporation is
giving to the people who loves the Oracle.
Rating: 5
Just in time February 26, 2002
Reviewer: Dave from Florida
It works!
Rating: 4
March 14, 2002
Reviewer: daxu from usa
Tom,
I tried to use the codes you presented here to display the pdf file. It works
partly. The pdf file downloaded from database just show bookmark contents on the
right side, the main contents of pdf is blank, do you have any idea about this?
Thanks,
Followup:
instead of letting the browser display the pdf, right click the link, save it to
disk and make sure its EXACTLY the same as the source document.  If its
bigger/smaller - then check to make sure the NLS_LANG of the "app server" is the
same as the database character set (if not, character set translation could be
kicking in and changing the data)
Rating: 3
How to install htp March 15, 2002
Reviewer: kiro
Hi how to install package htp? Is it come with OAS?
Thanks
Followup:
it comes with OAS/iAS and in 816 and up -- its in the database upon install.
Before that, installing OAS/iAS will get it.
Rating: 5
March 15, 2002
Reviewer: daxu
Tom,
Thanks a lot. After we change the NLS on both db server and web app server, the
PDF file works perfectly. Thanks again.
Rating: 3
How to insert PDF file into database using BLOB without BFILE March 16, 2002
Reviewer: PRS from USA
Hi Tom,
     I have a table defined in oracle as follows.
     Create table pdf_test(pdf_id number(10),pdf_file blob);
     I am trying to insert from the powerbuilder
     application after scanning couple of pages and storing
     that pages as PDF files on to my local c:\ drive. I
     cannot use BFILE or create directory stuff as my
     oracle database resides on UNIX and my PDF file
     is on local c:\ drive.

     Powerbuilder has a datatype BLOB. I am storing that
     PDF files into BLOB using fileread(filepointer,blob)
     after opening the file in stream mode. BLOB Variable
     pdf_content gets populated properly on powerbuilder
     side. Then I issue following insert statement.

     insert into pdf_test values(:pdf_id,:pdf_content);

     But this gives me an error of sqlca.code = -1.
     So the execution is not successful. What am i doing
     wrong here? I think i cannot insert straight BLOB
     variable from my powerbuilder application.

     I would appreciate any insight on this.

Thanks
Followup:
Sorry -- never programmed "powerbuilder" in my life.  If their documentation
doesn't tell you how -- don't know what to tell you .  I don't know what they
mean by "blob".

If some PB programmer out there knows, feel free to followup.  Otherwise this is
a question for Sybase to answer.  Sorry.
Rating: 5
April 02, 2002
Reviewer: Ramachandran S from New Delhi, India
Dear Tom,

    Using the the code which u have provided i am in a position to insert the
gif image on to a table.  But my problem remains how to retrieve the image on a
Form 6i (Patch 2)  using PL/SQL and display it while running the form.  If you
can guide me I will be thankful.

Thanking you
Followup:
with forms, you should use read_image_file() to load it into the database.  This
will convert the image into a format that forms can use.
Rating: 5
The Incredible Tom Strikes Again... June 17, 2002
Reviewer: Benoit Hebert from Quebec City, Canada
I've tried adapting the procedure to display jpg images stored in the lobs, but
the result is a broken image link. I know the image makes it into the database
just fine, since a Perl script can download it again, and the image is the same
as the original (confirmed through visual inspection and checksum). I tried
right-clicking the broken link and saving the image, in case it's just a display
problem, but the image, while being the same size as the original, is corrupted.

Here's the package body for the procedure. Any idea what the problem might be?
Our initial impression was that it might be a mime problem.

Thank you!

CREATE OR REPLACE package body insitu.image_get
AS

    procedure jpg (p_id in images.no_image%type)
    AS
        s_image blob;
        l_amt number default 30;
        l_off number default 1;
        l_raw raw(4096);

    BEGIN

       SELECT fichier_image INTO s_image FROM images WHERE no_image = p_id;
       owa_util.mime_header( 'image/jpeg' );

       begin
       LOOP
           dbms_lob.read(s_image, l_amt, l_off, l_raw);
        htp.prn( utl_raw.cast_to_varchar2( l_raw ));
        l_off := l_off + l_amt;
        l_amt := 4096;
       END LOOP;

       EXCEPTION
        when no_data_found then
           NULL;

       end;
    end;
end;
/
show errors;
Followup:
Is the character set of the client (the webserver) exactly the same as the
database -- if not, since owa does varchar2 -- the character set conversion will
corrupt the image.
Rating: 5
Why default of 30 for l_amt? August 20, 2002
Reviewer: Mark A. Williams from Indianapolis, IN USA
Tom:

Why does l_amt get assigned a default of 30 in the procedure?  Is there any
significance to 30?  After the 1st iteration through the loop l_amt is assigned
4096... why not start at 4096 instead of 30?

Sorry if I'm being daft (not been the best of days...)

Thanks,

Mark
Followup:
Sorry -- should have put it at 4096.

Why did I do 30?  Just to make sure in my test that I iterated at least once, to
test the looping.
Rating: 5
ORA-22288: file or LOB operation FILEOPEN failed September 05, 2002
Reviewer: A reader
Tom,
I was changing the create directory statement to
create or replace directory my_files as 'c:/test';
and then running
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', 'oracle_image_map.gif' );
    dbms_lob.fileopen( l_bfile );

    dbms_lob.loadfromfile( l_blob, l_bfile,
                               dbms_lob.getlength( l_bfile ) );

    dbms_lob.fileclose( l_bfile );
end;

got an error:
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 line 9
Followup:
Hmm, would appear that the file oracle_image_map.gif does not exist on the
SERVER in the c:/test directory (why do people insist on using Unix / on
windoze?)

does the file actually exist on the SERVER in that directory.
Rating: 5
yes it does exist on the server September 06, 2002
Reviewer: A reader
I have tried to put the file in one of the local drives of the server...and also
networking to a drive on the server..and putting a file there...
still getting the same error

8.1.7.3.0 (Enterprise Edition) on Windows 2000 advanced server

Thanks..
Followup:
sorry -- but I don't actually believe you.

Log onto the server.
in a DOS like window (just text).
do a "dir" for us, show us the file exists.
using sqlplus.exe (character mode), log into sqlplus.
do the create directory.
run the code.
cut and paste the results.

This has always been a case of "file actually truly did not exist", every time I
see this -- every time (and I've seen it lots), and plesae use the right slash
for this backwards OS -- / is wrong on NT, \ is "right".
Rating: 4
Excellent Material Tom !!! September 06, 2002
Reviewer: C Singh from Netherlands
Have a question related with it .. am working on a document imaging system where
i need to open more than one file ( in this case .tif) at once in the same
window. for example
have a table with two columns as below

select a, b from wblob ;

   A    B
------  ---
(BLOB)  6
(BLOB)  5
(BLOB)  5

And have changed the code a little bit as below (added another loop)

create or replace package body wtest_get as
procedure tif -- ( p_id in number )
is
       l_lob    blob;
       l_amt    number default 30;
       l_off   number default 1;
       l_raw   raw(4096);
cursor c1 is select a
             from wblob
             where b = 5 ; -- p_id;

begin
  for v1 in c1
   loop
     owa_util.mime_header( 'image/tiff' );
     begin
       loop
         dbms_lob.read( v1.a , l_amt, l_off, l_raw );

         htp.print( utl_raw.cast_to_varchar2( l_raw ) );
         l_off := l_off+l_amt;
         l_amt := 4096;
       end loop;
       exception
         when no_data_found then
             NULL;
     end;
  end loop ;
end;

BUT the problem is (as from the table it can be seen my cursor will return 2
rows for b = 5 ) it still opens only one file and not 2 as i would like. what am
i doin wrong OR how can we open more than one file at the same time.
Followup:
You would have to look up on the internet what the exact format of a multi-part
document is and write out that specific formatted document using its protocol.
It'll look like a multi-part email if you've ever seen one of those.

Proabably not a good thing in PLSQL -- you really don't have the degree of
control you really need over the document format to do that.

Not the answer you wanted to hear -- but I don't think there is a really "good"
one here.
Rating: 5
"sorry -- but I don't actually believe you." September 06, 2002
Reviewer: A reader
You were right...I was day dreaming I guess...I was putting file on another
server...
Here are some issues:

1.We are about to put lot of images into the database...because there is one
particular software..that can not pull the images from CD's...
Anyway...since they have to be on the server for us to move them into the
database....I was trying to map a network drive to the server....and then run
your procedure to move them into the database....it worked O.K. on the local
drives but the network drive gave an error:
ORA-22288: file or LOB operation FILEOPEN failed
The data is invalid.
ORA-06512: at "SYS.DBMS_LOB", line 475
ORA-06512: at line 9

2. Is there a way to avoid this whole situation of putting the images into the
database....
I mean the database is like 3 Gigs only...and these images are going to beef it
up so much...
So, can we move the images into TEMP tables through PL/SQL
and return them as a record set to the front end...
or is this a "Dream on Son..." situation...

Thanks...
Followup:
we can use SQLLDR to load them over the network.

see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:624423639385
all you'll need is a file that is the list of file names to load and a control
file....
Rating: 4
Is it posible ?? October 10, 2002
Reviewer: C Singh from Netherlands
Tom,
is it posible to store/insert more than one file in a blob column (same row) ???
Followup:
It seems it would be more sensible to have a blob column per file but you could
if you wanted to just keep appending to a single blob.

It would be upto you to remember where one file starts and the other ends -- it
would just be a blob to us
Rating: 4
SQLLDR October 10, 2002
Reviewer: ashraf from kuwait
i have pdf files and that files are in some directories
you link is tell that i can load gif or pdf file using sqlldr but how to mention
the derictory in sqlldr
Followup:
Just like you might think:

BEGINDATA
7782,CLARK,MANAGER,7839,2572.50,,10,/home/tkyte/test/allbytes.dat

(from that same example, but using a directory with the filename)
Rating: 3
Is it posible ?? October 10, 2002
Reviewer: C Singh from Netherlands
Thankyou Tom for the quick reply.

but
1) how can i insert more than one file in a blob column
2) sorry, but what do you mean by "It would be upto you to remember where one
file starts and the other ends -- it
would just be a blob to us "

3) the question actually is related to the one i asked before (in this thread) .
what if can store more than one file in a BLOB column and then display that
using the package image_get given above ???
Followup:
1) you would
  a) open file, read file, use dbms_lob.writeAppend to add file to blob
  b) close that file
  c) goto A and do it with another file

2) sorry, but I don't know what I could possibly say further.  If you loaded 3
files into a blob -- WE (oracle) would have no way to tell you "file 1 starts at
byte 1 and goes to byte N, file 2 starts at byte N+1 and goes to M and so on".
YOU would have to "remember" that.

3) that doesn't compute. the browser expects a file back from a link, not a set
of files back.  The browser wouldn't be capable of dealing with it nicely.  You
could use the MIME multi-part document encoding scheme to stream back more then
one file but it probably won't do what you want anyway.
Rating: 3
October 10, 2002
Reviewer: Sagi from India
Hi Tom!

My DAD name is sagi.

How do i execute it now with passing the value 1 as input parameter. I have not
used modplsql much.

If I execute it as below:
http://localhost/pls/sagi/image_get.gif
it gives me the below ERROR:

image_get.gif: SIGNATURE (parameter names) MISMATCH
VARIABLES IN FORM NOT IN PROCEDURE:
NON-DEFAULT VARIABLES IN PROCEDURE NOT IN FORM: P_ID

  DAD name: rk_dad
  PROCEDURE  : image_get.gif
  URL        :
http://localhost/pls/RK_DAD/image_get.gif
  PARAMETERS :
  ============

Regards,
Sagi
Followup:

http://localhost/pls/sagi/image_get.gif?p_id=1
(you know, sort of like my example

<a href=/dcd/owa/image.pdf?p_id=55>Click Me</a>

in the answer)
Rating: 3
October 10, 2002
Reviewer: A reader
Sorry Tom,

I mistakely gave the dad name as sagi. It was RK_DAD....

Regards,
Sagi
Rating: 4
Reading Blob details December 04, 2002
Reviewer: Daya from India
Hi Tom!
 IT is very good article regarding Blob.
  I have a problem in reading the Blob data from the database.
After storing the data to database, i have a procedure to read
the data from database as given below:
  We are using Database version 8.1.7.

create or replace procedure read_lob( p_id in demo.id%type )
is
    l_lob    blob;
    l_amt    number default 30;
    l_off   number default 1;
    l_raw   raw(32767);
begin
    select theBlob into l_lob
        from demo
         where id = p_id for update;
        begin
           loop
              dbms_lob.read( l_lob, l_amt, l_off, l_raw );
              dbms_output.put_line( utl_raw.cast_to_varchar2( l_raw ) );
              l_off := l_off+l_amt;
              l_amt := 4096;
           end loop;
        exception
           when no_data_found then
             dbms_output.put_line(sqlerrm);
        end;
end;

When i try to execute this procedure, i get a error like:

SQL> exec read_lob(2);

PL/SQL procedure successfully completed.

SQL> exec read_lob(2);
spool e:\woc\woctabledel.lst
BEGIN read_lob(2); END;

*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "SYS.DBMS_OUTPUT", line 57
ORA-06512: at "WOC.READ_LOB", line 14
ORA-06512: at line 1

  What could be the problem? For another file which has got no
space or null values, the same procedure works fine.
Thanks
Daya
Followup:
I do not understand what I'm looking at:

SQL> exec read_lob(2);

PL/SQL procedure successfully completed.        <<<==== it ran OK

SQL> exec read_lob(2);
spool e:\woc\woctabledel.lst                    <<<==== what is this, it cannot
                                                        be there, not SQLPlus
                                                        syntax
BEGIN read_lob(2); END;

*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error       <<<=== this is probably a result
                                                       of way overflowing the
                                                       dbms_output buffer.
ORA-06512: at "SYS.DBMS_OUTPUT", line 57
ORA-06512: at "WOC.READ_LOB", line 14
ORA-06512: at line 1

dbms_output.put_line has a limit of 255 characters/line.  the second time
through the loop, you use 4k, not 30bytes -- hence you are overflowing it.

ops$tkyte@ORA920.US.ORACLE.COM> exec dbms_output.put_line( rpad('*',4000,'*') );
BEGIN dbms_output.put_line( rpad('*',4000,'*') ); END;

*
ERROR at line 1:
ORA-20000: ORU-10028: line length overflow, limit of 255 chars per line
ORA-06512: at "SYS.DBMS_OUTPUT", line 35
ORA-06512: at "SYS.DBMS_OUTPUT", line 133
ORA-06512: at line 1

use 255 max.
Rating: 4
Problems with bigger (> 3 MB) PDF-Files December 11, 2002
Reviewer: Michael from Germany
Hello Tom!

I tried your procedures to display PDF-files.
They work fine, but when i try to display PDF-files which are bigger than 3 MB
(or so) then i get an error message.

The details:

create table mypdfs (
id number(38) primary key,
theblob blob not null);

create or replace package getpdfs
as
  procedure pdf(p_id in mypdfs.id%type);
end;
/

create or replace package body getpdfs
as
  procedure pdf(p_id in mypdfs.id%type)
  is
    l_lob blob;
    l_amt number := 32767;
    l_off number := 1;
    l_raw raw(32767);
  begin
    select theblob into l_lob
    from mypdfs where id = p_id;
    owa_util.mime_header('application/pdf');
    begin
      loop
        dbms_lob.read(l_lob, l_amt, l_off, l_raw);
        htp.prn(utl_raw.cast_to_varchar2(l_raw));
        l_off := l_off + l_amt;
      end loop;
    exception
      when no_data_found then
        null;
    end;
  end;
end;
/

Then i loaded with a little java some PDF-files into this table. The result:

select id, dbms_lob.getlength(theblob) from mypdfs;

        ID DBMS_LOB.GETLENGTH(THEBLOB)
---------- ---------------------------
         1                       47985
         2                     5069381
         3                      483618
         4                     1192106
         5                     2584836
         6                     3974764

Then i tried in my browser the url:
http://mike:mike@localhost/pls/ora92dbdad/mike.getpdfs.pdf?p_id=X
With X = 1, 3, 4 and 5 it works perfect. But with X= 2 and 6, i get the error
(in MS IE and Netscape 4.7x):

Not Found
The requested URL /pls/ora92dbdad/mike.getpdfs.pdf was not found on this server.

(Even wget can download 1, 3, 4, and 5, but not 2, 6 - it too gives a "not
found"-error message).

Do you have any idea what i am doing wrong?
Followup:
when a procedure "fails" with an unhandled execption, by default with mod_plsql,
it returns a 404-not found instead of "error".

Check your web server logs and mod_plsql logs in particular.

Odds are -- since the document is in fact "cached" in ram here -- you are
running out of process memory -- you would be looking for an ora-4030
Rating: 5
You are right! December 11, 2002
Reviewer: Michael from Germany
Thanks Tom, you are right ... and fast ;-)
In the logs from Apache i found the error about a too small large pool (only 4
MB).
After restart with 8 MB large pool everything work perfect!
Thanx!!!
Followup:
ok, that means you are using MTS (shared server).

that means the UGA for all sessions will be in the SGA.

8mb sounds small unless you have a tiny number of connections -- so, be aware of
that.  This is where the session states will be stored.

In my experience, using MTS with a connection pool (ala mod_plsql) is like
buffering a buffer -- eg: not something i would do.
Rating: 5
You are right again December 11, 2002
Reviewer: Michael from Germany
Yes, the DB is in MTS and mod_plsql connects over an MTS-connection - and so i
had a problem with files which are bigger than my large pool.
(Even after i doubled the large pool from 4 to 8 MB, i had a problem with files
> 10 MB).

So i set up a new DAD which connects explicitly to dedicated server and it seems
to work now with all (small and big) files.
Rating: 5
Another (standard) method December 13, 2002
Reviewer: Michael from Germany
Hello all!

I found the following solution - in the oracle documentation! - to download a
blob to the browser:

(My blobs are in a table called "files" in the column "content" and the mime
type of the content is store in another table called "mimetypes" column "name".
Master-Detail-Relationship.)

create or replace procedure download(p_file_id files.id%type)
as
  l_lob files.content%type; l_mime mimetypes.name%type;
begin
  select f.content, m.name
  into l_lob, l_mime
  from files f, mimetypes m
  where f.id = p_file_id
  and f.mimetype_id = m.id;
  owa_util.mime_header(l_mime, false);
  owa_util.http_header_close;
  wpg_docload.download_file(l_lob);
end;

This worked on my 9i Rel. 2 DB.
I think this is the best way to do this - and it's already there in the DB!
So, an hour invested in reading the docus is a well invested hour.
Rating: 2
Why use LOB's in the first place ??! January 02, 2003
Reviewer: Santhosh from On top of the world !
Hello Tom,

I have gone thru the concept of using LOB's in Oracle. But, I'm still unclear as
to WHAT IS THE NEED TO USE LOB's in the first place ?

LOB's store picture / gif images in the database. But, to ask u, can't I simply
refer the "physical" file from the front-end instead of storing it in the
database, & then retrieving it from the db ?

Why use LOB's, when I can do the same operation of storing files / pictures in
the OS directory itself ??

Pls clarify.

Warm Regards,

Santhosh
Followup:
are the images important to you?

might you want to back them up?
recover them?
even to a specific point in time, you know -- like your relational data.

Do you want to export your servers file system?
or maybe it is easier to put everything you own into a DATABASE and serve it
from there?

what happens when someone says "hey, what are all of these silly files doing
here, let me erase them"

what happens when you try to put 10,000 images into a single directory (try it
sometime).  So now you need to come up with some algorithm to spread files out
over many directories, many file systems, etc.

what happens when you try to put 1,000,000 images into a single table?  same
thing that happens with 1, or 10,000,000.
Rating: 2
But... January 03, 2003
Reviewer: Santhosh from Again on top of the world :-)
Hi Tom,

You said right - to handle around 10,000 images in a folder is cumbersome &
quite difficult, BUT if the user has limited images to display, will the OS file
system be better ?
Also, we can give perfect rights to the folder / files that no one can meddle
with it....

Also, to use so many LOB functions need much more technical know-how, and
require more efforts from the developers... right ?

Santhosh
Followup:
I don't think so -- but you seem to want to use it so go for it.

If your developers cannot figure out the LOB stuff -- well, keep them away from
the database then please!  It is not rocket science.

Go for giving "perfect rights".  Manage access control here, there, everywhere.
Me, I like backing up my database and knowing "i got it all".  I might PUBLISH
my images from the database to the file system (eg; the images you see on this
page) however, the IMAGES exist primarily in the database.  If some knucklehead
wipes them out on the file system or the file system gets out of sync --
republish them.  PRoblem solved.  I use the database to -- well you know --
manage data.
Rating: 5
Thank you Tom! February 20, 2003
Reviewer: Joyce Fowler from Belleville, IL USA (across the river from St. Louis, MO)
My boss (for some unknown reason) decided to go with just Apache as a web server
and not the full blown iAS, therefore, so sad...no dad.  I am about to reinstall
iAS and then, I expect, I will be able to call through HTML a PL/SQL procedure
to display an image stored as a BLOB in our database (we're actually using XML
but I figured if I could get it to display in HTML..).  Also, your answer to
Daya when he said he got the error 'PL/SQL: numeric or value error' when
executing a PL/SQL procedure to read a BLOB was also helpful since I have
several PL/SQL procedures to do this and got the same error - didn't realize I
was overflowing the buffer.  You certainly seem very knowledgeable!!!!!!!!!!!!!
Also enjoy your writing style in the newsletter.  If you're ever in the St.
Louis area see me - I owe you a dinner.  :-)
Followup:
St Louis -- I was fogged out of and then stuck in St Louis just last friday --
didn't think I was going to make it back east before the snow starting
falling... Finally made it out of there though late friday.

(you will find the mod_plqsl is just an apache module -- doesn't need "full
blown" ias.  you have it in your install already I bet.  Look in
$ORACLE_HOME/Apache.
Rating: 4
can u give a procedure to store pdf file in database February 20, 2003
Reviewer: Nikunj from India
hi tom,

can u give me procedure to store pdf file in database from local machine ?

Thanks,
Followup:
Nope.

think about it.  what is your local machine, how does it communicate with the
server, what tools do you use, are you client server, 3 tier, are you looking to
occasionally upload a document or to do a bulk load, etc etc etc etc

search for

load lobs

on this site for many different alternatives -- there is less said in this
request then is actually "said".
Rating: 4
Very useful!!. Can I show the image with scaled down/up size on the web? February 25, 2003
Reviewer: Prince from CA, USA
Tom,

When displaying the image on the web browser, how do I reduce the dimension of
the image retrieved from the database?

I am using java to retrieve the image from the DB. But when displaying, I would
like to scale down/up the image size. Is there a way to do this in java/html
without storing the file in the web server?

Your help is much appreciated.
Followup:
<img src=foobar height=xx width=nn>

in the html.  pretty standard method.
Rating: 3
I guess, I wasn't clear when explaining February 26, 2003
Reviewer: Prince
I want to retrieve the blob image from the DB and show them on the web page,
without storing in any temporary file, before displaying.

This is the pseudocode.

    PreparedStatement pstmt = conn.prepareStatement(sql);

    //get the result from database
    ResultSet rs =  pstmt.executeQuery();

    if (rs!=null && rs.next())
    {
            Blob l_Blob = rs.getBlob("B_COL");
            response.setContentType("image/jpeg");
            byte[] ba = l_Blob.getBytes(1, (int)l_Blob.length());
            response.getOutputStream().write(ba);
            response.getOutputStream().flush();    }
            // close your result set,
Followup:
ummm -- guess I'm totally missing your point.

where does the temporary file come into play?????

you have to create a HTML file that'll have an <img src = > tag in it that will
tell the browser "hey, grab this url to get a image from the server" which in
turn runs your code.
Rating: 5
still having problems February 26, 2003
Reviewer: Gabriel Ilarda from Aus
Hi Tom,

I've used the code you provided in your above answer about 3 months ago taken
from your expert one-on-one book. It worked perfectly fine and I even used the
code to view excel files. I revisted my code just recently and it no longer
works. When it tries to open the excel file it says that the file is in an
unrecognizable format, and if I open it, it displays the raw data as jumbled
text. The only thing i know that has changed in the last 2-3 months is that our
iAS was upgraded from  9iAS V1.0.2.2 to 9iAS V1.0.2.2.2. I checked with my DBA
if the DB character set and web server character set were changed, and they say,
nope they are still both the same. I have even done the 'save as' test and the
bytes loaded and received are exactly the same. I've pretty much hit a dead end
now and not sure what to do. I've written a TAR to metalink and i won't make a
comment of how long it is taking them just to figure out my problem before they
have even started coming up with a solution. if you could suggest anything that
might help, please let me know. actually another thing i found odd that i've
only had to include now not 3 months ago, is in my loading procedure, i had to
add a commit statement. if the commit statement isn't there and i load the file
and try to view it through the browser, my select statement fails as it can not
find the row. once i do a commit, it finds the row and trys to open the file but
obviously i can't read it because of the unrecognizable format error. i never
had the commit statement in my loading procedure previously when this was all
working 3 months ago.

Thanks
Followup:
download the file, do a binary compare with original source

if they are the same size but the bytes differ a bit -- then the NLS_LANGS are
in fact different.

search for wpg_docload on this site for an alternative way to download (interal
to ias) as well
Rating: 4
Insert PDF file into database using BLOB - IN Power Builder February 27, 2003
Reviewer: Riyaz from Chennai, India
Hats off to Tom for excellent & fantabulous guidance to oracle users across the
world.
I just came across this query, only couple of days back.

Suggestion for doing the storage and retrieval of BLOB (oracle database) from
Power Builder.

Try out the following script in update and retrieve buttons (user defined) in
one window with 2 ole controls.

declaration
-----------

lb_object = ole_1.objectdata

for update
----------

updateblob demo set theblob = :lb_object
where id=1;
//messagebox('',sqlca.sqlcode)
commit using sqlca;

for Retrieve
------------
selectblob theblob  into :ole_blob from demo
where id= 2 ;
ole_2.objectdata = ole_blob

Hope it will be useful.
Rating: 5
wpg_docload.download_file works!!! March 03, 2003
Reviewer: Gabriel Ilarda from Australia
Tom,

I did the binary compare you asked me too and the bytes compared exactly the
same so I don't think it is an NLS_LANG problem, but I did try replacing the
whole loop algorithm in your procedure, which uses DBMS.READ to retrieve the lob
in chunks, with just the wpg_docload.download_file procedure, as suggested by
you and also some other guy on this page, and amazingly it all worked fine. the
lob is retrieved and the browser reads it as an excel file without saying its in
an unrecognizable format. i could ask why it worked this way and not the other
way but i'm quite happy just knowing thats its working fine again. thanks for
your help.
Rating: 3
How to display the gife files? April 14, 2003
Reviewer: junior from VA USA
Hi, Tom:

I followed your example to create procedure and I am able to store the gif files
in Database now. Only different is I set MYFILES directory as 'C:\TEMP';

Question is I do not understand how to display the gif file. I installed 9iAS
already.
You told us create a html and have a link like follow:
http://localhost/pls/sagi/image_get.gif?p_id=1
What is the acture path for the link (can you give me an example) and should I
put my gif files or my html file to the directory? Thanks.
Followup:
read this page from top to bottom.  The answer is there already.

You need to setup and configure mod_plsql.
sagi in this exmaple is the DAD
image_get.gif is the package.
p_id=1 is the primary key value for this example.
Rating: 4
Using dbms_lob under Oracle Forms July 02, 2003
Reviewer: MMatsuya from Brasil
Hi Tom,

I would like to use this Oracle package (dbms_lob) to display .rtf files under
Oracle forms. I'm using Oracle8i under Win200 Server.
Rating: 5
Use BLOB August 13, 2003
Reviewer: Hector Ulloa Ligarius from Santiago of Chile
TOM , YOU ARE THE BEST !!!!!

THANK YOU
Rating: 5
Upload from Database (blob object) to disk August 29, 2003
Reviewer: Hector tito1973 Ulloa from Santiago of Chile
Hi Tom :

  I am using the WebUtil beta (great!!) , and i store  in the dataBase a blob
object , but i like upload to disk ..

 How?

Thank you very much..

PD: You are the best ....y sabes muchísimo de Oracle
PPD : Sorry my english
Followup:
no idea what "webutil" is. sorry.
Rating: 5
performance?? August 29, 2003
Reviewer: Prasad from San Francisco,CA
Tom,

Could you please tell me which  will be  better .

1. store the pdf file in O/S and write some java beans for displaying and
retrieving the pdf
or
2. store as BLOB in database itsel .

Thanks
Prasad
Followup:
depends if you want

o data integrity
o security
o backup
o RECOVERY
o ease of use and access

if you don't want those, use the file system.
Rating: 5
Why Java, when it's so much easier with PL/SQL?! August 30, 2003
Reviewer: Jens from Deutschland, Niedersachsen
> Could you please tell me which  will be  better .
> 1. store the pdf file in O/S and write some java beans
> for displaying and retrieving the pdf
> or
> 2. store as BLOB in database itsel .

Do you really think, that PL/SQL-Oracle-Guru Tom would prefer the OS/Java way?

Look at the review from January 02, 2003. There you could find the answer.

Bye,

Jens
Followup:
;)
Rating: 5
Store File in the BBDD September 04, 2003
Reviewer: Hector Gabriel Ulloa Ligarius from Santiago of Chile
>Hi Tom :
>
>  I am using the WebUtil beta (great!!) , and i store  in >the dataBase a blob
>object , but i like upload to disk ..
>
> How?

>Thank you very much..

>PD: You are the best ....y sabes muchísimo de Oracle
>PPD : Sorry my english

>Followup:
>no idea what "webutil" is. sorry.

Hi Tom..

Webutil is a tools created by Duncan Mills and Frank Nimphius and it serves for
Forms9i , it work into the client..
http://otn.oracle.com/products/forms/htdocs/webutil/webutil.htm
Example :

TEXT_IO , is a command in the server

But in the Web (Forms9i) , TEXT_IO, not used because write the files in the
server , it should write in the client

Oracle Team Developers create the Webutil package for architecture web.

 I used the WebUtil Beta..and store the data in the BBDD from to disk .

but i like upload to disk ..

 How?

Thank Tom

PD : Sorry my english
Followup:
I guess you would have to ask Duncan or Frank this question?  I don't know a
thing about this.
Rating: 5
Store File in the BBDD September 08, 2003
Reviewer: Hector Gabriel Ulloa Ligarius from Santiago of Chile
But you know PL/SQL, i like know how upload files from database to disk , only
this...

You help me ???
Followup:
you can use utl_file to write files to disk from plsql after they are uploaded
into the database (where they RIGHTLY belong, in the database)

i don't know what else to tell you as I do not use forms at all myself and this
is a forms utility here.  utl_file is fully documented in the supplied packages
guide.

a java stored procedure can do it was well
Rating: 1
Store Files in the DDBB September 08, 2003
Reviewer: Hector
Well..

  You don't understand me

Ok...

Thanks
Rating: 5
file permissions for my_files September 09, 2003
Reviewer: Sandeep from German
Oracle OS: HP Unix
Oracle Version: 8.1.7.4
Application Server (IIS) OS: Windows 2000 Server

Scenerio: We have to initially upload some 1500 Excel (average size 4k) file
into the database..and then user will upload file using a web page (So Web
server will copy the file to one of it's directory...and then Oracle will have
to upload from there)

Questions:
1. What is the best way to handle the initial batch?
2. using your demo package above....how do we set the permission on oracle
server (my_files directory) to see this directory on the web server or any
directory may be on my local computer...
is it possible to do it this way?

Thanks...a bunch
Followup:
1) sqlldr is probably what i would use

2) i don't know what you mean.
Rating: 5
Size on Blobs September 09, 2003
Reviewer: Sandeep from Germany
Tom,
I created the table demo...in a tablespace as follows...
Questions:

1. why sizes are shown 38 and 86?
2. Do we need to give a size to blob column...like if the average file inserted
will be 4k...

Oracle version 8.1.7.4 (HP Unix)
SQL Worksheet (Oracle Enterprise Manager Ver 1.6.0)

CREATE TABLESPACE TS_PMP_BLOB
    DATAFILE '/u03/oradata/dev/ts_pmp_blob.dbf' SIZE 10m
    AUTOEXTEND ON NEXT 1m MAXSIZE 20m
    EXTENT MANAGEMENT LOCAL UNIFORM SIZE 128K
    LOGGING
    ONLINE
/

create table demo
( id        int primary key,
  theBlob    blob
)
tablespace TS_PMP_BLOB
lob (theBlob) store as the_blob
(disable storage in row
chunk 4000
pctversion 20
nocache logging)
/

desc demo

Column Name                    Null?    Type
------------------------------ -------- ----
ID                             NOT NULL NUMBER(38)
THEBLOB                                 BLOB(86)
Followup:
1) because an int is just a number and a number with 38 digits of precision.

the 86 is interesting, i've never actually seen that do that.  a lob locator is
about 80 some odd bytes so it must be that length.

oh i see - you are using sql worksheet, not sqlplus -- that is why i've never
seen it, i use plus.  it is the lob locator length.
Rating: 5
file permissions for my_files follow up September 09, 2003
Reviewer: sandeep from germany
"Followup:
2) i don't know what you mean. "

Let me try again...
Different users will upload files into the database from a web page.
So, Oracle server has to read the file that it needs to insert into the blob
from a
directory on the application server....so it will need permissions to read that
directory...or to see it...
In your example you created a directory using create directory..and used it to
read the file path and
then inserted the file into the table:

"
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' );

.........

......."

So, in this case my_files path existed on the server..and server had permissions
on that path...so it could read..
But in my case files will be on Application server (Windows 2000) and database
server is HP Unix box..
So, how would Oracle see the directory...what do we need to do to grant
permissions..

Thanks again for your patience...
Followup:
you will need to talk to your system admins and see about getting the drive
mounted on the unix box -- totally outside the realm of "Oracle" here....

if you used iAS and mod_plsql, we would upload right into the database,
bypassing this extra work.
Rating: 5
file permissions for my_files.... September 09, 2003
Reviewer: Sandeep from Germany
Oracle OS: HP Unix
Oracle Version: 8.1.7.4
Application Server (IIS) OS: Windows 2000 Server

May be a different approach...
Active Server Pages (ASP) has this binary data type called
LongVarBinary for binary data....can there be a procedure that expects BLOB
parameter and ASP will send this binary data (the whole file) to this procedure
and it will then insert into the blob column....

Couple of problems:
Will this work?
What is the code to do this?

Thanks a bunch
Rating: 5
file permissions for my_files.... September 10, 2003
Reviewer: Sandeep from Germany
Any thing about this:

May be a different approach...
Active Server Pages (ASP) has this binary data type called
LongVarBinary for binary data....can there be a procedure that expects BLOB
parameter and ASP will send this binary data (the whole file) to this procedure
and it will then insert into the blob column....

Couple of problems:
Will this work?
What is the code to do this?

Thanks a bunch
Followup:
the procedure will NOT expect a blob

the procedure WILL expect a long raw
the procedure can take upto 32k of it at a time.

i've never programmed (nor will I ever program) using ASP or VB.  that stuff
doesn't run on any computer I own.  a tad locked in to a single OS it is.
sorry.
Rating: 5
Inserting Blob..question September 10, 2003
Reviewer: Sandeep from Germany
file permissions for my_files....continues
Oracle OS: HP Unix
Oracle Version: 8.1.7.4
Application Server (IIS) OS: Windows 2000 Server

So, If I get an ASP parameter (Binary type) passed into a procedure that accepts
blob...here is the procedure I found in oracle docs:

CREATE or REPLACE PROCEDURE INSERT_TEMP_CTC_XLS_SHEET (pXLSSHEET
CTC_XLS_SHEET.XLSSHEET%TYPE) AS
   vXLSSHEET TEMP_CTC_XLS_SHEET.XLSSHEET%TYPE;
   Buffer          VARCHAR2(32767);
   Amount          BINARY_INTEGER := 32767;
   Position        INTEGER;
   i               INTEGER;
   Chunk_size      INTEGER;
BEGIN

INSERT INTO TEMP_CTC_XLS_SHEET (CTCID, XLSSHEET, MODIFIEDBY)
VALUES (1, EMPTY_BLOB(),'C0013')
RETURNING XLSSHEET INTO vXLSSHEET;
     /* Opening the LOB is optional: */
    DBMS_LOB.OPEN (vXLSSHEET, DBMS_LOB.LOB_READWRITE);
    Chunk_size := DBMS_LOB.GETCHUNKSIZE(vXLSSHEET);

    /* Fill the buffer with 'Chunk_size' worth of data to write to
       the LOB. Use the chunk size (or a multiple of chunk size) when writing
       data to the LOB.  Make sure that you write within a chunk boundary and
       don't overlap different chunks within a single call to DBMS_LOB.WRITE. */

    Amount := Chunk_size;

    /* Write data starting at the beginning of the second chunk: */
    Position := Chunk_size + 1;
    FOR i IN 1..3 LOOP
        DBMS_LOB.WRITE (vXLSSHEET, Amount, Position, Buffer);
        /* Fill the buffer with more data (of size Chunk_size) to  write to
           the LOB: */
        Position := Position + Amount;
    END LOOP;
    /* Closing the LOB is mandatory if you have opened it: */
    DBMS_LOB.CLOSE (vXLSSHEET);

END INSERT_TEMP_CTC_XLS_SHEET;

I have modified it a little bit...so that instead of selecting a blom from the
table...I am using the parameter...

Question:
1.The parameters coming from ASP/VB will be average 150K..
(Excel files in binary form)
Will this still work...parameter limits etc.
If not how can we make it work...

2. "FOR i IN 1..3 LOOP"
Why does it always go 3 times

Thanks.
Rating: 5
ASP VB and BLOBS September 11, 2003
Reviewer: Sandeep from Germany
Tom,
Figured it out...it turned out to be really simple:
    INSERT INTO BLOB_TBL (ID, THEBLOB)
    VALUES (1, pTHEBLOB);
---WHERE pTHEBLOB IS THE BLOB VARIABLE PASSED TO THE PROCEDURE

AND THEN
Here is the code for ASP/VB people:
http://www.asp.net/Forums/ShowPost.aspx?tabindex=1&PostID=238489
only extra line in asp code was:
declare xx blob; begin dbms_lob.createtemporary(xx, false, 0); :tempblob := xx;
end;

and that did it....

We inserted a 5 meg file and it took like 2  seconds...

Thanks...
Rating: 4
Accessing PDF files September 19, 2003
Reviewer: Senthil.S from KL
Dear Tom,

How to access the  PDF file from PL/SQL. The requirement is like
this:

There is one report prints one page. There are some pages in PDF file. We
have to select only a few pages corressponds to the first page printed
(Oracle) and print it. For example:

Say page 1 will be printed using oracle report writer . Pages from 2 - 5
will have to be printed from PDF file. There is a key field (matches the key
field in Oracle) in PDF file . We have to select the pages based on this
key. ie. select those pages from PDF files where ever this key is appearing
and print those pages..just print without formatting. Hope I made you
understand the requirement.

Can you pls. show some lights on it..

Thanks,
Senthil.
Followup:
not going to be doing that in plsql.  PDF is something the acrobat reader can
read, but plsql cannot.
Rating: 5
Reply Immd Pls September 22, 2003
Reviewer: suresh from India
  hi tom ,

     How to Change the fonts & Sizes  In AcrobatReader  document?

     pls reply immd ....

  ok,Thanking
Followup:
dude, this is "asktom.oracle.com" :)

ask me about Oracle...

(and reply immd is sort of pushy isn't it?)
Rating: 3
November 11, 2003
Reviewer: JP
I know this is a silly question..
Does the directory in <a href=/dcd/owa/image.pdf?p_id=55>Click Me</a>

should be the same in
create or replace directory my_files as '/export/home/tkyte/public_html'; ??

Thanks
Followup:
nope, you are not reading a file from the OS at all -- you are running a
procedure.  you need to configure a "dad" in iAS for mod_plsql
Rating: 3
Memory usage? CLOB? November 20, 2003
Reviewer: msc
Hello Tom,

I am concerned of htp.prn() memory usage.

My app would generate quite large XMLType which I can send out with loop and
htp.prn():
  ...
  l_lob := l_xml.getCLOBval();
  loop
    htp.prn( dbms_log.substr( l_lob, l_amt, l_loff) );
    ...
  end loop

But how does htp.prn() use session memory?
1. is all response content held in session memory or is it streamed in chunks to
mod_pls?
2. is there size limit to for CLOB / BLOB size using this technique?

Is there any alternate method to stream CLOB or XLMType out?
Followup:
1) all in session memory.
2) 4gig in 9i, terabytes in 10g

3) haven't tried it, but you might look at wpg_docload -- it works with BLOBS,
not CLOBS but might be useful (it streams)
Rating: 4
how about resizing the image November 23, 2003
Reviewer: Frank from NL
Hi,

I try to build some sort of photogallery using mod_plsql.
I can insert images into db. I can also display images in a page.
What I want to do is to make a downsized example of the image (i.e. a thumbnail)
to show in an overview page.
I tried this using java, but I got stuck because of the GUI not supported error
of the JVM.
(Preferrably I want to create the thumbnails 'on the fly', not store them in the
db)

I tried this code, which works outside of the db (using files instead of BLOBs):

create or replace and compile java source named "Thumbnail" as
package nl.tripode.thumbs;

import java.awt.Image;
import java.awt.Toolkit;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.OutputStream;
import oracle.sql.BLOB;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;

class Thumbnail {

  public static void createThumbnail(BLOB orig, BLOB thumb, int maxDim) throws
Exception {

    int length = (int) (orig.length());
    byte[] imageBytes = orig.getBytes(1, length);
    Image inImage = Toolkit.getDefaultToolkit().createImage(imageBytes);

    // Determine the scale.

    double scale = (double)maxDim / (double)inImage.getHeight(null);
    if (inImage.getWidth(null) > inImage.getHeight(null)) {
        scale = (double)maxDim/(double)inImage.getWidth(null);
    }

    // Determine size of new image.
    int scaledW = (int)(scale * inImage.getWidth(null));
    int scaledH = (int)(scale * inImage.getHeight(null));

    // Create an image buffer in
    //which to paint on.
    BufferedImage outImage = new BufferedImage( scaledW
                                              , scaledH
                                              , BufferedImage.TYPE_INT_RGB);

    // Set the scale.
    AffineTransform tx =
      new AffineTransform();

    // If the image is smaller than
    //the desired image size,
    // don't bother scaling.
    if (scale < 1.0d) {
        tx.scale(scale, scale);
    }

    // Paint image.
    Graphics2D g2d = outImage.createGraphics();
    g2d.drawImage(inImage, tx, null);
    g2d.dispose();

    OutputStream os = thumb.getBinaryOutputStream();
    JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(os);
    encoder.encode((BufferedImage)outImage);
    os.close();

  }
}
/

Wrapper:
create or replace procedure make_thumb
( p_orig    blob
, p_thumb   blob
, p_maxdim  number
)
as language java
name 'nl.tripode.thumbs.Thumbnail.createThumbnail(oracle.sql.BLOB,
oracle.sql.BLOB, int)'
;

and funtion:

create or replace function make_thumbfnc
( p_image in out blob
, maxdim  in     int
)
return blob
as
  l_thumb blob;
begin
  if p_image is null
  then
    return null;
  end if;
  dbms_lob.createtemporary( lob_loc => l_thumb
                          , cache   => true
                          );
  make_thumb(p_image, l_thumb, maxdim);
  return l_thumb;
end make_thumbfnc;

Any ideas/hints ?
Followup:
no need for java. interMedia does this
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a88786/toc.htm
Rating: 4
December 08, 2003
Reviewer: Huss from Kuwait
Dear tom ,
the case i have is
i want to store all forms and report *.rdf and *.fmb
in Blobs, to be stored in the database.
so ,
1 - no problem in upload files , but what about retrive from db to client machin
(i mean by clients developers machins)
 i.e developer can retrive the report or form file and modify it and upload
again to db
2 - i need a hint about how to encrypt the Blobs ?!

thnakx for ur help
Followup:
if you want to store the rdf/fmb files in blobs, you should be looking at
collaboration suite (a filesystem in the database)

if you just put them into blocks, you are responsible for getting them out -- If
you figured out how to "upload them", downloading is done using the same
technique (which you did not describe, so I cannot be less vague.  did you write
a program, use mod_plsql what)

read about dbms_obfuscation_toolkit and if you have my book expert one on one
Oracle -- i demonstrated how to encrypt blobs 32k at a time using that api
Rating: 4
PLease explain December 09, 2003
Reviewer: umesh s kasturi from bangalore India
Tom
In your answer at the top refer to :
<a href=/dcd/owa/image.pdf?p_id=55>Click Me</a>

Do I need to have this
I have utl_file_dir = c:\usk_utl_file_dir
and I have a file called "rarrow.gif","oracle.gif" in that directory

  1  declare
  2      l_blob    blob;
  3      l_bfile    bfile;
  4  begin
  5      insert into demo values ( 2, empty_blob() )
  6      returning theBlob into l_blob;
  7      l_bfile := bfilename( 'MY_FILES', 'oracle.gif' );
  8      dbms_lob.fileopen( l_bfile );
  9      dbms_lob.loadfromfile( l_blob, l_bfile,
 10                                 dbms_lob.getlength( l_bfile ) );
 11      dbms_lob.fileclose( l_bfile );
 12* end;
SQL> /

PL/SQL procedure successfully completed.

Do I use
<a href=c:\usk_utl_file_dir\Oracle.gif?p_id=1>Click Me</a>
to display the "oracle.gif" image in it

I have not understood the point of displaying on html page from the DB Please
help
Followup:
no, we are not using a file system, we are using blobs.
Rating: 4
my above question is incorrect December 09, 2003
Reviewer: umesh kasturi from Bangalore India
Tom
In your answer at the top refer to :
<a href=/dcd/owa/image.pdf?p_id=55>Click Me</a>

Do I need to have this
I have utl_file_dir = c:\usk_utl_file_dir
and I have a file called "rarrow.gif","oracle.gif" in that directory

SQL> show parameter utl

NAME                                 TYPE    VALUE
------------------------------- ------- ---------------utl_file_dir
       string  c:\usk_utl_file_dir

SQL> create or replace directory my_files as 'c:\usk_utl_file_dir';

Directory created.

  1  declare
  2      l_blob    blob;
  3      l_bfile    bfile;
  4  begin
  5      insert into demo values ( 2, empty_blob() )
  6      returning theBlob into l_blob;
  7      l_bfile := bfilename( 'MY_FILES', 'oracle.gif' );
  8      dbms_lob.fileopen( l_bfile );
  9      dbms_lob.loadfromfile( l_blob, l_bfile,
 10                                 dbms_lob.getlength( l_bfile ) );
 11      dbms_lob.fileclose( l_bfile );
 12* end;
SQL> /

PL/SQL procedure successfully completed.

Do I use
<a href=c:\usk_utl_file_dir\oracle.gif?p_id=2>Click Me</a>
to display the "oracle.gif" image in it

I have not understood the point of displaying on html page from the DB Please
help
Rating: 5
Retaining a filename in blob December 12, 2003
Reviewer: Tony Reed from Oslo, Norway
Hi Tom,

Is there any way of retaining the original filename of an image stored as a
blob, (actually an ordsys.ordimage)?

When users save an image from the web they recieve the procedure name + image id
which served the image.
I've tried various approaches including using the mime-header (Content-Type:
image/jpeg; name="filename") attribute to no avail.
I know this probably is out of your scope, but I thought that you might have
come across the problem, and any hints would be much appriciated as always

Tony Reed

ps. On a personal note. Looking forward to seeing you in Denmark in January!
Followup:
This should do it:

dbms_lob.open(i.blob_content, dbms_lob.lob_readonly);
owa_util.mime_header(i.mime_type, false);
htp.p('Content-Length: ' || i.doc_size);
htp.p('Content-Disposition: attachment; filename=' || l_filename);
htp.p('Last-Modified: ' || to_char(i.last_updated,'Dy, DD Mon YYYY  HH:MI:SS')
|| ' GMT');
owa_util.http_header_close;
wpg_docload.download_file(i.blob_content);
Rating: 5
Displaying PDF files stored in the Database December 15, 2003
Reviewer: Leonard Anukam from Finland
This was very useful, as I learned another way of loading imgae into the DB. But
still could not get thr PDF side of it running successfully. I will like to know
how to get it done.
It says Acrobat can not open the file image_get.my_pdf. Either it is not a
support file type or the file is corrupt. I will like my info on the best way to
do it

Thanks
Followup:
well, you didn't tell me which approach you used.

if you use the htp approach -- you need to make 100% sure the app/web servers
NLS_LANG is exactly the same as the databases character set -- else characterset
conversion takes place.

if your app server and db are on say windows and unix respectively -- this is
most likely not the case by default -- you'll have to make sure it is

(or use the   wpg_docload.download_file(l_lob); approach which was added after
the question was originally asked and is a superior method)
Rating: 5
December 17, 2003
Reviewer: A reader
Rating: 5
Displaying PDF files December 18, 2003
Reviewer: Leonard Anukam from Finland
Tom can you be a bit specific on where to add the following codes you gave
dbms_lob.open(i.blob_content, dbms_lob.lob_readonly);
owa_util.mime_header(i.mime_type, false);
htp.p('Content-Length: ' || i.doc_size);
htp.p('Content-Disposition: attachment; filename=' || l_filename);
htp.p('Last-Modified: ' || to_char(i.last_updated,'Dy, DD Mon YYYY  HH:MI:SS')
|| ' GMT');
owa_util.http_header_close;
wpg_docload.download_file(i.blob_content);

At what point in the Package body can we add it. Or do we totally replace the
following section with it.

begin
    select theblob into l_lob
    from mypdfs where id = p_id;
    owa_util.mime_header('TEST/pdf');--- I tried to put the code here but it
failed.
    begin
      loop
        dbms_lob.read(l_lob, l_amt, l_off, l_raw);
        htp.prn(utl_raw.cast_to_varchar2(l_raw));
        l_off := l_off + l_amt;
      end loop;

if you can please rewrite the original code by using the this

dbms_lob.open(i.blob_content, dbms_lob.lob_readonly);
owa_util.mime_header(i.mime_type, false);
htp.p('Content-Length: ' || i.doc_size);
htp.p('Content-Disposition: attachment; filename=' || l_filename);
htp.p('Last-Modified: ' || to_char(i.last_updated,'Dy, DD Mon YYYY  HH:MI:SS')
|| ' GMT');
owa_util.http_header_close;
wpg_docload.download_file(i.blob_content);

Thanks.
Followup:
see the followup in "Another (standard) method "
Rating: 5
Oracle Reports reading a BLOB December 22, 2003
Reviewer: MT from USA
We have a requirement to display the content of BLOB stored in Oracle database
using Oracle reports. The BLOB can have various document files (pdf, Word files,
Excel files....).

Is this possible, to display in an Oracle Report output. I am new to Oracle
Reports, so, any input would be valuable. Really appreciate the help. Thanks.
Followup:
sorry, i don't use or do reports.

try otn.oracle.com -> discussion forums.

there you will find people that use it everyday.
Rating: 5
creating pdf December 29, 2003
Reviewer: A reader
Dear Tom,
How best would you suggest to create pdf files from database.
The requirement is to create one pdf file corresponding to a database record.
Oracle reports could have been used if the generated file was required to be
one, instead of many?
Please provide any reference or third party tool that you think might do the
job.
Best regards
Followup:
i'll let others comment, not aware of anything specific.

(although reports can do this, you would just pass in the primary key to the
report and run reports for each key value)
Rating: 5
Create Pdf file in a database December 29, 2003
Reviewer: Lev from Toronto
Hi,

To create PDF file I loaded java library from
http://www.lowagie.com/iText/
in database.

I just repeated example from "Expert One on One Oracle".
It works fine but I have not tested it in a real environment.
By default this library creates PDF file, but it is not difficult to
create it BLOB instead.

Hope this helps.

Lev
Rating: 5
December 30, 2003
Reviewer: A reader
Hi Tom,
I loaded iText.jar file into database from
http://www.lowagie.com/iText/
site using loadjava.. and I am
able to create pdf file from database...This is fine for static text BUT my
datasource is SQL query like SELECT * FROM EMP
WHERE EMPNO = 100 and I want output written in pdf file in Tablar format.

How can I do that??

Thanks

Public class  pdftest  {
    public static void pdfdemo(String args) {

       System.out.println("Chapter 1 example 3: PageSize");

        // step 1: creation of a document-object
        Document document = new Document(PageSize.A4.rotate());

        try {

            // step 2:
            // we create a writer that listens to the document
            // and directs a PDF-stream to a file

            PdfWriter.getInstance(document, new
FileOutputStream("c:\\Chap0101.pdf"));

            // step 3: we open the document
            document.open();

            // step 4: we add some phrases to the document
            for (int i = 0; i < 10000; i++) {
                document.add(new Phrase("Jay Swaminarayan; "));
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<---- HERE I WANT SQL QUERY
            }

        }
        catch(DocumentException de) {
            System.err.println(de.getMessage());
        }
        catch(IOException ioe) {
            System.err.println(ioe.getMessage());
        }

        // step 5: we close the document
        document.close();
Followup:
you'd have to find a java programmer who codes jdbc -- it is very
straightforward to do that....
Rating: 5
PFF is NOT working but MS-Word and Text is working fine. December 30, 2003
Reviewer: Sami from NJ,USA
Browser: IE 6.0   DB:Oracle 9.2.0.4 EE OS: Windows NT 4.0

Dear Tom,

I went thru this page from top to bottom.

But for me...

1)
http://rutq/pls/sami/getpdfs.txt
==> is working fine

2)
http://rutq/pls/sami/getpdfs.doc
==> is working fine IF i specify,
    l_amt number := 32767;
    l_raw raw(32767);

But IF l_amt number and l_raw  := 4096; is opening ms-word applications but not
opening the file.

3)
http://rutq/pls/sami/getpdfs.pdf
==> is NOT working.

There is no error message on the screen nor on apache logfile. As soon as I hit
the above URL, status bar says "start downloading from site
http://rutq/pls/sami/getpdfs.pdf
and keep hanging, after while status became "Done".

Could you please throw some light on this?

--spec is not included
CREATE OR REPLACE package body getpdfs
as
  PROCEDURE pdf
  is
    l_lob blob;
--  l_amt number := 32767;
    l_amt number := 4096;
    l_off number := 1;
    l_raw raw(4096);
  begin
    select theblob into l_lob
    from mypdfs where id = 1; -- pdf fileid
    owa_util.mime_header('application/pdf');
    begin
      loop
        dbms_lob.read(l_lob, l_amt, l_off, l_raw);
        htp.prn(utl_raw.cast_to_varchar2(l_raw));
        l_off := l_off + l_amt;
      end loop;
    exception
      when Others then
      htp.prn('PDF Error'||SQLCODE||' : '||SQLERRM(SQLCODE)||' '||l_off);
    end;
  exception
      when Others then
      htp.prn('PDF Error'||SQLCODE||' : '||SQLERRM(SQLCODE)||' '||l_off);
  end;

  PROCEDURE doc
  is
    l_lob blob;
    l_amt number := 32767;
    l_off number := 1;
    l_raw raw(32767);
  begin
    select theblob into l_lob
    from mypdfs where id = 4;-- ms-word doc fileid

    owa_util.mime_header('application/msword');
    begin
      loop
        dbms_lob.read(l_lob, l_amt, l_off, l_raw);
        htp.prn(utl_raw.cast_to_varchar2(l_raw));
        l_off := l_off + l_amt;
      end loop;
    exception
      when no_data_found then
      htp.prn('Doc Error'||SQLCODE||' : '||SQLERRM(SQLCODE));
    end;
  end;

  PROCEDURE txt
  is
    l_lob blob;
    l_amt number := 32767;
    l_off number := 1;
    l_raw raw(32767);
  begin
    select theblob into l_lob
    from mypdfs where id = 3; --Text fileid
    owa_util.mime_header('text/plain');
    begin
      loop
        dbms_lob.read(l_lob, l_amt, l_off, l_raw);
        htp.prn(utl_raw.cast_to_varchar2(l_raw));
        l_off := l_off + l_amt;
      end loop;
    exception
      when no_data_found then
      htp.prn(' Text Error'||SQLCODE||' : '||SQLERRM(SQLCODE));
    end;
  end;

  end;

end;
/
Followup:
use

wpg_docload.download_file(l_lob);

tis easier, faster and takes less memory.
Rating: 4
DB PU usage went to 100% December 30, 2003
Reviewer: Sami
Dear Tom,

1)Replaced a single line(wpg_docload.download_file(l_lob); ) instead of
dbms_lob.read as you suggested.

2)did hit
http://rutq/
<== works fine

3)did
http://rutq/pls/hsbc/getpdfs.txt
 <== cpu went to 100%
(oracle.exe process cpu usage went to 100% & stays there until I issue shutdown
abort.)

4)replaced wpg_docload.download_file with dbms_lob.read.

5)did
http://rutq/pls/hsbc/getpdfs.txt
 <== WORKS FINE.

I tried even restarting Apache but no luck.

      procedure txt
  is
    l_lob blob;
    l_amt number := 32767;
    l_off number := 1;
    l_raw raw(32767);
  begin
    select theblob into l_lob
    from mypdfs where id = 3;
    owa_util.mime_header('text/plain');
    begin
      loop
      wpg_docload.download_file(l_lob); -- <==newly added line
        --dbms_lob.read(l_lob, l_amt, l_off, l_raw);
        htp.prn(utl_raw.cast_to_varchar2(l_raw));
        l_off := l_off + l_amt;
      end loop;
    exception
      when no_data_found then
      htp.prn('\n Text Error'||SQLCODE||' : '||SQLERRM(SQLCODE));
    end;
  end;
Followup:
the docload approach should look like this:

create or replace procedure download(p_file_id files.id%type)
as
  l_lob files.content%type; l_mime mimetypes.name%type;
begin
  select f.content, m.name
  into l_lob, l_mime
  from files f, mimetypes m
  where f.id = p_file_id
  and f.mimetype_id = m.id;
  owa_util.mime_header(l_mime, false);
  owa_util.http_header_close;
  wpg_docload.download_file(l_lob);
end;

don't call in a loop, no htp.prn, just that.
Rating: 5
Dear Tom, YOU ARE THE MAN! December 30, 2003
Reviewer: Sami
Rating: 4
Image Processing January 08, 2004
Reviewer: Rajashekhar GANGA from Mumbai, INDIA
Dear Sir,

is it possible to compare two image file.
for exmple.
we have a table t as .
pid NUMBER
pname VARCHAR2(200)
p_pic blob

i want to store the photograph (From digital Camera) search in database. if new
patient insert the row,
if old patient fetch old consulting details.

i have an alternative solution like asking for last visit prescription and
getting details from it. But if it happens like earlier it will very much new
experience for me.

Thank for reading the request.

Raj.
Followup:
well, you wouldn't be comparing images i hope here (i mean, today i look
different than yesterday).

You would be looking for the patient record before even wasting the batteries on
the camera no?
Rating: 4
OK January 09, 2004
Reviewer: Siva from Hyderabad,India
Dear Sir,
 Is "wpg_docload" a new package?I have not seen this before.
what does "wpg" denote?Is there any docs for it with you?Please do reply.
Bye!
Followup:
it is a couple of years old.
http://download-west.oracle.com/docs/cd/A97329_03/web.902/a90855/toc.htm
see section 2.6
Rating: 5
Displaying PDF files stored in the Database January 28, 2004
Reviewer: Cosmin Sideras from Medias, Romania
You're the man TOM
Rating: 5
Displaying PDF files (stored as BLOBs in Database) using Forms 6i February 10, 2004
Reviewer: umasankar from California, USA
Hi Tom,

We have PDF and Word documents stored in Database. We are using Oracle Forms 6i
as User Interface. Through Forms we could view word documents which are stored
in database, using OLE container.

But we could not view PDF files.

It would be more helpful and valuable if I get ideas/suggestions on this.

Thanks in advance,
Umasankar
Followup:
sorry -- I haven't touched forms since about 1995 and have no computers capable
of doing "ole" -- you can try otn.oracle.com -> discussion forums.
Rating: 5
Different sizes in uploading and downloading February 25, 2004
Reviewer: A reader
Hi Tom, Sorry, I tried several times, and checked, to do it work, but I can't
I upload one form and get other in other size, I copy the examples from here,
but I can't see what is wrong.
If you can please giveme a hand.

create table demo
( id        int primary key,
  theBlob    blob
)
/

drop directory my_files ;
create or replace directory my_files as 'c:\';

--UPLOADING

declare
    l_blob    blob;
    l_bfile    bfile;
begin
DELETE FROM ADM.DEMO;
    insert into demo values ( 1, empty_blob() )
    returning theBlob into l_blob;

    l_bfile := bfilename( 'MY_FILES', 'f.fmb' );
    dbms_lob.fileopen( l_bfile );

    dbms_output.put_line(dbms_lob.getlength( l_bfile ));
    --file length is 2699264
    dbms_lob.loadfromfile( l_blob, l_bfile,
                               dbms_lob.getlength( l_bfile ) );

    dbms_lob.fileclose( l_bfile );
    COMMIT;
end;
/

-- DOWNLOADING
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 'MY_FILES';
    p_file varchar2(30) default 'g.fmb';
begin

-- get the blob locator
    l_output := utl_file.fopen(p_dir, p_file, 'w', 32760);
    for l_cur in (SELECT theblob mylob FROM demo)
    loop
        len :=  DBMS_LOB.GETLENGTH(l_cur.mylob);
        vblob := l_cur.mylob ;
        dbms_output.put_line('Length of the Column :  ' || to_char(len));
--****Length of the Column :  2699264 correct

        vstart := 1;
        while (vstart < len) loop   -- loop till entire data is fetched
            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);
        dbms_output.put_line('vstart : ' || to_char(vstart));
    end loop;
exception when others then
        utl_file.fclose(l_output);
        dbms_output.put_line(sqlerrm);
end ;

Verifying generated file
declare
    l_blob    blob;
    l_bfile    bfile;
begin

    l_bfile := bfilename( 'MY_FILES', 'g.fmb' );
    dbms_lob.fileopen( l_bfile );

dbms_output.put_line(dbms_lob.getlength( l_bfile ));
-- has an incorrect lenght = 2716188
    dbms_lob.fileclose( l_bfile );
    COMMIT;
end;
/

Tom what is wrong in this code to recreate file I copied from here, I checked
several times and I don't see nothing wrong, please.
Followup:
utl_file is not "binary friendly" -- is this file "binary" (fmb = forms binary,
perhaps you want to use an FMT = forms text instead)....
Rating: 5
February 25, 2004
Reviewer: A reader
Thank I compared hexadecimals
and the problem is the same
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6379798216275
has

it replaces every 0a for 0a0d

Thanks
Rating: 5
Ugly question February 26, 2004
Reviewer: A reader
Tom I have an ugly question to you.
What could happen specifically if I do an extract ddl from utl_file package in
9024 and try to put it in my 902,
In my opinion the worst will be utl_fil will not work, i execute the previous
utl_file ddl and all is OK.
or not?
for example alert log uses utl_file package?
Thank you.
Followup:
you cannot take internal code like utl_file and others from version X and plop
it into version y.  You should only use the code supplied in the
$ORACLE_HOME/rdbms/admin codetree with the install.

no, the alert log writing does not use utl_file.  other utilities like logminer
might but not writing the alert log.
Rating: 5
Thanks Tom February 26, 2004
Reviewer: A reader
Rating: 5
What about a pdf search March 05, 2004
Reviewer: Mike Cheatham from Kwajalein, Marshall Islands
This was very helpful information.  I would like to take it a step future.

How would I search a blob containing the pdf?  I have been able to get my search
to work with other file types (doc,xls,txt), but not pdf.

What do I need to change in my code so that I can search the blob containing the
pdf data?

PROCEDURE search (in_search VARCHAR2) IS
lob_doc        BLOB;
Pattern        VARCHAR2(30);
l_doc_title    varchar2(255);
Position       INTEGER := 0;
Offset         INTEGER := 1;
Occurrence     INTEGER := 1;
BEGIN
  Pattern    := utl_raw.cast_to_raw(in_search);
  for c in (select * from my_docs) loop
    lob_doc     := c.doc_blob;
    l_doc_title := c.doc_title;
  DBMS_LOB.OPEN (lob_doc, DBMS_LOB.LOB_READONLY);
  Position := DBMS_LOB.INSTR(lob_doc, Pattern, Offset, Occurrence);
  IF Position = 0 THEN
    DBMS_OUTPUT.PUT_LINE('Pattern not found');
  ELSE
    DBMS_OUTPUT.PUT_LINE('The pattern '''|| in_search||''' can be found in
'||l_doc_title);
  END IF;
  DBMS_LOB.CLOSE (lob_doc);
  end loop;
END search;
Followup:
you use Oracle Text to index it.  pdf is generally not "clear text", it is lzw
compressed text and needs be filtered.

Even doc, xls will need this (both will contain text that is not YOUR text)

See
http://otn.oracle.com/pls/db92/db92.docindex?remark=homepage#index-TEX

Rating: 3
some additional notes March 16, 2004
Reviewer: Zsolt Müller from Hungary
Hi!

I've read through the answers/followups and would like to add some notes.

1.) Concerning the "wpg_docload vs. htp.prn" question

The wpg_docload.download_file() method for outputting BLOBs to the browser is
definitly better than the htp.prn() method.

Reasons:
a.)
- the latter reads the BLOB from the database in small pieces and puts them to
the htp.htbuf array by calling htp.prn ... mod_plsql reads this buffer at
successful completion of the procedure and submits the data to the browser
- the wpg_docload method reads the BLOB directly from the database ... no
intermediate buffer is used (that's the reason for the smaller memory usage :) )

b.)
- the htp.prn way includes an utl_raw.cast_to_varchar2() call ... that means the
data is converted from RAW to VARCHAR2 ... not a big overhead, but still ;)
- the wpg_docload method does not incorporate such a datatype conversion ...
since it's absolutely unnecessary :)

2.)
You can put the file_download code (owa_util.mime_header(),  ...,
wpg_docload.download_file()) anywhere in your procedure(s) if you put a
"htp.init" before it and a "return" after it. :)

Like this:

  procedure gif(p_id in demo.id%type)
  is
    l_lob blob;
  begin
    select theBlob into l_lob
      from demo
         where id = p_id;

    -- ... any code ... (might even contain htp.p() calls :)

    htp.init;
    owa_util.mime_header('image/gif');
    wpg_docload.download_file(theBlob);
    return;

    -- ... any code ...
  end gif;

htp.init: clears the htp.htbuf buffer and resets the htp package state
variables.
(Note: many people think owa_util.showpage is meant for reseting the htp output
... they are wrong. It's htp.init.)
Followup:
1) 100% concurr -- absolutely.  Unfortunately, it post-dates the original
question by a long time.

1b) that is virtually no overhead, there is no conversion -- it is a datatype
field change only -- couple of bits are modified, nothing else.
Rating: 3
and one more thing :) March 16, 2004
Reviewer: Zsolt Müller from Hungary
I almost forgot one more thing ... ;)

Nobody should use Internet Explorer for debugging such things like mod_plsql
output, webserver <-> client connections, etc. IE's error pages are far from
helpful and most times misleading.

I always use tools like this:
- wget (a command line download utility ... can provide very in-depth view of
the webserver-client connection :) )
- curl (another command line tool)
- tcpdump (Only in worst case ... when stuff above doesn't help. Might be
replaced by some personal firewall for Win* with capability to save raw TCP
packets.)

Both wget and curl are available for Windows and both are open-source and
freeware programs. :)
(wget is protected by GPL. curl is not under GPL, but it's free to use.)
Followup:
I use sqlplus myself :)  the ultimate in command line tools...

but actually, now I use htmldb and find i don't really need to resort to plus or
wget or anything like that -- it is rather nice.
Rating: 4
March 29, 2004
Reviewer: reader
dear sir ,
 i use DB_To_Client in the webutil
to Downloads a file from a BLOB column in the database
to the web client machine.
but my problem is that i have a CLOB column now
and i want to make web client user downlad it !
can u help me in this
Followup:
sounds like forms -- i haven't touched forms since about March of 1995....

I'll have to refer you to otn.oracle.com -> discussion forums.
March 29, 2004 Priya -- Thanks for the question regarding "Count(*) ", version 8.1.6
You Asked
Hi Tom ,
Please suggest the best way to check if any rows exist for a given condition as
compared to getting the same using the count function .

Sql > select count(*) from emp where sal=5000 ;

Lets say there are 10 rows satisfying the above condition ,so the query does a
FTS and gets the count .

But my purpose is just to check and see if there is even a single row thats
satisfies the condition then return the count and exit .
Would Using rownum limit a FTS ?
eg
Sql > select count(*) from emp where sal=5000 and rownum < 2 ;

Please suggest .
Thanx as always .
Priya
and we said...
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.
Review & Followup

Rating: 4
Count(*) October 02, 2001
Reviewer: Parminder Garcha from London, England
The use of 'exists' was certainly useful and was something I had forgotten
about, however after generating two SQL statements one using the 'Exists' and
the other doing a straight forward count as in the original question I found :-

The explain plan output showed more 'consistent gets' with the use of 'Exists'
meaning Oracle used more data blocks to process the query.  So if the Query is
used very frequently in whatever scenario does the 'Exists' mean less effiency ?
Why else would it retrieve more 'consistent gets' ?
Thanks.
Followup:
does not seem probable to me.  You would need to supply a test case such as I am
following.

Here I create a table based on all objects.  I then find the name of the "last
object" and the name of the "first object" as they exist in this newly created
table (a row from the first block that is scanned and one from the last block).

I then run both styles of queries.  It shows that the "where exists" will stop
scanning as soon as it finds a row -- so when we search for the first row -- it
does SIGNIFICANTLY less work.  When we look for the last row -- it does about
the same amount of work (tiny bit more, it has the "overhead" of scanning DUAL
as well).

On average -- the where exists will significantly outperform the count(*).  You
would need to provide a counter case (with explain plans and all) for us to look
at.

Note that in a heavily modified database -- consistent gets will go up and down
with respect to the amount of modifications due to consistent reads to the
rollback segment.  Make sure that is not affecting your tests!

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t as select * from all_objects;
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> column first_oname new_val f
ops$tkyte@ORA817DEV.US.ORACLE.COM> column last_oname new_val l

ops$tkyte@ORA817DEV.US.ORACLE.COM> select object_name first_oname from t where
rownum = 1;

FIRST_ONAME
------------------------------
/1005bd30_LnkdConstant

ops$tkyte@ORA817DEV.US.ORACLE.COM> select object_name last_oname
  2   from ( select object_name
  3            from ( select object_name, rownum r from t )
  4           order by r DESC
  5            )
  6   where rownum = 1
  7  /

LAST_ONAME
------------------------------
B

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly statistics
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*)
  2    from dual
  3   where exists ( select NULL
  4                                    from t
  5                                   where object_name = '&f' )
  6  /
old   5:                                 where object_name = '&f' )
new   5:                                 where object_name =
'/1005bd30_LnkdConstant' )

Statistics
----------------------------------------------------------
          0  recursive calls
         16  db block gets
          6  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
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*)
  2    from t
  3   where object_name = '&f'
  4  /
old   3:  where object_name = '&f'
new   3:  where object_name = '/1005bd30_LnkdConstant'

Statistics
----------------------------------------------------------
          0  recursive calls
         15  db block gets
        236  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
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*)
  2    from dual
  3   where exists ( select NULL
  4                                    from t
  5                                   where object_name = '&l' )
  6  /
old   5:                                 where object_name = '&l' )
new   5:                                 where object_name = 'B' )

Statistics
----------------------------------------------------------
          0  recursive calls
         19  db block gets
        237  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
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*)
  2    from t
  3   where object_name = '&l'
  4  /
old   3:  where object_name = '&l'
new   3:  where object_name = 'B'

Statistics
----------------------------------------------------------
          0  recursive calls
         15  db block gets
        236  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
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA817DEV.US.ORACLE.COM>
Rating: 4
Count(*) October 02, 2001
Reviewer: Parminder Garcha from London,England
Yes I agree, apologies for my earlier inaccuracies, putting in another test
similar to your example my results replicate yours.........conclusion 'where
exists' is the speed king....well over count(*) anyway.
Rating: 4
Count(*) October 02, 2001
Reviewer: Parminder Garcha from London,England
Yes I agree, apologies for my earlier inaccuracies, putting in another test
similar to your example my results replicate yours.........conclusion 'where
exists' is the speed king....well over count(*) anyway.
Rating: 5
Sorry to tread old ground but.... September 24, 2002
Reviewer: A reader from England
Tom, on the current thread, but i'm trying to work out given that I agree
wholeheartedly that a) the count(*) from dual... is better than a straight
check, why the following code is returning exactly the same times in execution?

SQL> desc big_table
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 A                                                  VARCHAR2(10)

SQL> select count(*) from big_table;

  COUNT(*)
----------
    100000

no indexes etc, and table not analyzed...

SQL> set timing on
SQL> ed
Wrote file afiedt.buf

  1  declare
  2    cursor c1 is select NULL from big_table where a = 'B';
  3    l1  VARCHAR2(1);
  4  begin
  5    for i in 1..1000 loop
  6      open c1;
  7      fetch c1 into l1;
  8      IF c1%FOUND THEN
  9        null;
 10      END IF;
 11      close c1;
 12    end loop;
 13* end;
SQL> /

PL/SQL procedure successfully completed.

Elapsed: 00:00:33.04

SQL> declare
  2    cursor c1 is select count(*)
  3                 from dual
  4                 WHERE EXISTS (SELECT NULL
  5                               FROM big_table
  6                               where a = 'B'
  7                               );
  8    l1  NUMBER;
  9  begin
 10    for i in 1..1000 loop
 11      open c1;
 12      fetch c1 into l1;
 13      IF c1%FOUND THEN
 14        null;
 15      END IF;
 16      close c1;
 17    end loop;
 18  end;
 19  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:33.05

It's probably something very straightforward, maybe not enough data / iterations
to show any significant increase, but I expected the second code to be quicker
than the first using you're previous comments.

Thanks (as always) for your response
Followup:
why?  they do the SAME exact thing.

where exists -- stops after the first hit.

open
fetch
close  -- stops after the first hit

what you want to time:

SQL> declare
  2    cursor c1 is select count(*)
  3                 from dual
  4                 WHERE EXISTS (SELECT NULL
  5                               FROM big_table
  6                               where a = 'B'
  7                               );
  8    l1  NUMBER;
  9  begin
 10    for i in 1..1000 loop
 11      open c1;
 12      fetch c1 into l1;
 13      IF c1%FOUND THEN
 14        null;
 15      END IF;
 16      close c1;
 17    end loop;
 18  end;
 19  /

vs

SQL> declare

  8    l1  NUMBER;
  9  begin
 10    for i in 1..1000 loop
         select count(*) into l1
  3                 from dual
  4                 WHERE EXISTS (SELECT NULL
  5                               FROM big_table
  6                               where a = 'B'
  7                               );
 17    end loop;
 18  end;
 19  /
Rating: 4
Great info on consistent gets vs. rollback segments! September 24, 2002
Reviewer: Robert from Memphis, USA
Tom,

Great info!!

"...consistent gets [may] go up and down with respect to the amount of
modifications due to consistent reads to the rollback segment."

This is an important fact to remember and valuable information for analyzing our
query statistics.

Thanks,

Robert.
Rating: 4
select count(*) September 24, 2002
Reviewer: john feng from DCX.COM, Michigan, USA
I appreciate this tip to detect if there is any record regardless the record
count.
Rating: 4
Tell me at least something.... December 19, 2002
Reviewer: Robert from PA
Tom,
Using EMP as example with one extra column - COMPANY - that has the SAME value
"MSOFT"

EMPNO    NUMBER(4)
ENAME    VARCHAR2(10) Y
JOB      VARCHAR2(9)  Y
MGR      NUMBER(4)    Y
HIREDATE DATE         Y
SAL      NUMBER(7,2)  Y
COMM     NUMBER(7,2)  Y
DEPTNO   NUMBER(2)    Y
COMPANY  VARCHAR2(10) Y

How can I return this:
COMPANY      COUNT(*)
---------- ----------
MSOFT              0

with a query like :
SQL> select max(company), count(*) from emp where job = 'ACTOR' ;

...when there is NO such job as "ACTOR" ?

Thanks
Followup:
It will not.

scott@ORA920> select max(ename), count(*) from emp where 1=0;

MAX(ENAME)   COUNT(*)
---------- ----------
                    0

scott@ORA920> select max(ename), count(*) from emp where job = 'NOT_A_JOB';

MAX(ENAME)   COUNT(*)
---------- ----------
                    0

scott@ORA920>

You would need to post a full example if you think otherwise...
Rating: 5
February 12, 2003
Reviewer: Helena Markova from Bratislava, Slovakia
Rating: 5
rownum is better December 11, 2003
Reviewer: A reader
create table x as select * from dba_objects order by object_id;

select count(*) from x where object_id = 20000;
  COUNT(*)
----------
         1
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'X'
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        380  consistent gets
        240  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
select count(*) from x where object_id = 20000 and rownum = 1;
  COUNT(*)
----------
         1
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     COUNT (STOPKEY)
   3    2       TABLE ACCESS (FULL) OF 'X'
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        267  consistent gets
        156  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

select count(*) from dual where
exists (select null from x where object_id = 20000)
  COUNT(*)
----------
         1
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     FILTER
   3    2       TABLE ACCESS (FULL) OF 'DUAL'
   4    2       TABLE ACCESS (FULL) OF 'X'
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        270  consistent gets
        170  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

rownum = 1 is better than exists
Rating: 3
OK February 09, 2004
Reviewer: Catherine from OH,USA
Hi Tom,
Nice to meet you.
Regarding the Exists operator,Can it *refer* the same table
in the outer and inner query?This is the thing I want to
know and could you please provide a simple example?
Please do reply.
Followup:
sure, it matters not what 'tables' are referenced -- SQL doesn't care if you
reference the same table or different tables -- they are all "different" tables
to it.

here we get a list of all the objects owned by people that have at least one
materialized view:

ops$tkyte@ORA920PC> select owner, object_name, object_type
  2   from all_objects
  3  where exists ( select null from all_objects a2
  4                 where a2.owner = all_objects.owner
  5                   and a2.object_type = 'MATERIALIZED VIEW' )
  6  /

OWNER                          OBJECT_NAME                    OBJECT_TYPE
------------------------------ ------------------------------ ------------------
SH                             CAL_MONTH_SALES_MV             TABLE
SH                             CAL_MONTH_SALES_MV             MATERIALIZED VIEW
SH                             CHANNELS                       TABLE
SH                             CHAN_PK                        INDEX
SH                             COSTS                          TABLE PARTITION
SH                             COSTS                          TABLE PARTITION
SH                             COSTS                          TABLE PARTITION
Rating: 4
Nice February 11, 2004
Reviewer: Catherine from OH,USA
Dear Tom,
Thanks for your reply.Could you please show some examples
involving the traditional EMP and DEPT Tables?I would be
very happy to see that.Please do reply.
Thanks.
Followup:
programming assignment for you:

show me the list of emp who work in a department that has at least one sales
person.

result set will be:

    DEPTNO ENAME      JOB       DECODE(
---------- ---------- --------- -------
        30 ALLEN      SALESMAN  <<<====
        30 WARD       SALESMAN  <<<====
        30 MARTIN     SALESMAN  <<<====
        30 BLAKE      MANAGER
        30 TURNER     SALESMAN  <<<====
        30 JAMES      CLERK

6 rows selected.

as this is a variation on the

<quote>
here we get a list of all the objects owned by people that have at least one
materialized view:
</quote>

from above but PEOPLE will be deptno's and MATERIALIZED VIEW will be SALESMAN
Rating: 4
Count(*) response time February 11, 2004
Reviewer: Karthik from NC,USA
Tom

Thanks for your nice response. Appreciate your time on this forum.

Is there any best way to get good response time for
COUNT(*) against table, which has 50Million recs.

Using COUNT(primarykey_column)will give good response time?

Please suggest me any techique to get good response time for Select count(*)
from table. I am using Oracle8.1.7

Thanks very much.
Followup:
analyze the table and the count(*) will fast full scan any index on a NOT
NULLable column -that'll be as fast as it can be, reads least  amount of data.
Rating: 4
About your programming assingnment February 12, 2004
Reviewer: MV From India
Hi Tom
   I've tested u'r programming assignment with "Where Exists" as well as with
"Where deptno in". but the second one is running faster than first one. Why?

(Testing purpose, i've inserted some records in emp table, since those records
are deleted from the emp table by other users.)

SQL> select deptno, ename, job, decode(job, 'SALESMAN','<<<<====')
  2  from emp a
  3  where exists (select null from emp b where b.deptno = a.deptno
  4*                  and b.job = 'SALESMAN')
SQL> /

    DEPTNO ENAME      JOB       DECODE(J
---------- ---------- --------- --------
        30 fff        SALESMAN  <<<<====
        30 hhh

Elapsed: 00:00:04.10

SQL> select deptno, ename, job, decode(job, 'SALESMAN','<<<<====')
  2  from emp a
  3  where deptno in (select deptno from emp where job = 'SALESMAN');

    DEPTNO ENAME      JOB       DECODE(J
---------- ---------- --------- --------
        30 fff        SALESMAN  <<<<====
        30 hhh

Elapsed: 00:00:01.60

Thanks in advance

MV from India
Followup:
you are probably using the brain dead RBO is why - -but it is hard to say since
you don't share a tkprof with us and using timing in sqlplus isn't very
reliable.  tkprof is what you want (need) to use.
Rating: 3
OK February 15, 2004
Reviewer: Ben from Vancouver,Canada
Dear sir,
I'm well and wish the same from you.I would like to transform the following
query
 sql>select deptno,ename,sal from emp where
      sal = any(select max(sal) from emp group by deptno);
  using *Exists * operator to get the same output?Could you
please help with the query?
Awaiting your reply
Bye!
Followup:
homework?
Rating: 1
Sad February 16, 2004
Reviewer: Ben from Vancouver,Canada
Dear Sir,
Is that difficult to arrive at?I felt to be stupid at this
moment.Can you give a try?
Bye!
Followup:
it is easy, it is basic SQL.  Give it a try -- show us what you've tried.  Gotta
master the fundementals and this is one of the more basic ones here.
Rating: 2
Can this do? February 17, 2004
Reviewer: Ben from Vancouver,Canada
Dear Sir,
Thanks for your advice.Can the following query do?
 sql> select deptno,ename,sal from emp e where
     exists(select 'x' from emp where deptno = e.deptno
     and /* How to coordinate the max(sal) with (outer) sal*/
 I am confused with this and FYI My system crashed two
days back and I am in no position to test with statements.
Hope you understand my problem.
Bye!
Followup:
read about the HAVING clause

look at your first query:

select deptno,ename,sal from emp where
      sal = any(select max(sal) from emp group by deptno);

the deptno isn't something you want to correlate on.  you want to generate the
set of max(sals) having the max(sal) the same as the current rows sal.....

there where deptno = e.deptno isn't part of your solution.
Rating: 3
OK February 18, 2004
Reviewer: Ben from Vancouver,Canada
Dear sir,
Can this work?
  sql>select deptno,ename,sal from emp e where
      exists(select 'x' from emp group by deptno
              having max(sal) = e.sal);
I think this can work and Is there any refinement for
 the query?
Bye!
Followup:
there you go, you got it.

scott@ORA10G> select deptno,ename,sal from emp where
      sal = any(select max(sal) from emp group by deptno);  2

    DEPTNO ENAME             SAL
---------- ---------- ----------
        10 KING             5000
        20 SCOTT         7781.24
        30 BLAKE            2850

scott@ORA10G> select deptno,ename,sal from emp e where
      exists(select 'x' from emp group by deptno
              having max(sal) = e.sal);  2    3

    DEPTNO ENAME             SAL
---------- ---------- ----------
        30 BLAKE            2850
        10 KING             5000
        20 SCOTT         7781.24

the refinement is to go back to the = any or "in"
Rating: 4
I am very confused about this March 29, 2004
Reviewer: Nick
Hi Tom,

Can you clear this perky confusion.

when I run this query
select count(*),wrawardcat
from member group by wrawardcat

176    C
929    G
63    H
208    J
91    L
144    R
471418         <----total

However if I run this
when I run this query
select count(wrawardcat),wrawardcat
from member group by wrawardcat

176    C
929    G
63    H
208    J
91    L
144    R
0         <----without total
Followup:
thats not a total.

You have nulls.

scott@ORA9IR2> select comm, count(*), count(comm), decode(comm,null, 'IS NULL',
'IS NOT NULL' )
  2  from emp
  3  group by comm;

      COMM   COUNT(*) COUNT(COMM) DECODE(COMM
---------- ---------- ----------- -----------
         0          1           1 IS NOT NULL
       300          1           1 IS NOT NULL
       500          1           1 IS NOT NULL
      1400          1           1 IS NOT NULL
                   10           0 IS NULL

you have 471,418 null wrawardcat's in there.  count(*) counts NULLs,
count(expression) counts NON-NULL occurences of expression....
Rating: 5
Thanks!!! March 29, 2004
Reviewer: A reader
March 29, 2004 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://download-east.oracle.com/docs/cd/A81042_01/DOC/server.816/a76965/toc.htm
Virtually all of your questions are addressed in this manual,
specifically on the section dedicated to bitmapped indexes:
http://download-east.oracle.com/docs/cd/A81042_01/DOC/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
Rating: 5
Bitmap with single row inserts March 09, 2004
Reviewer: A reader
I have a fact table (star schema),that due to requirements is loaded
transactionally. As transactional data reaches a certain status, records get
loaded into the fact table.

Should I not be using bitmaps for my indexes?

Should I rebuild them periodicallly?
Any other guidance?

Thanks Tom
Followup:
see the jonathan lewis series of articles on bitmaps on dbazine.com...

bitmaps will deteriorate over time (rapidly) if you do lots of row by row
modifications (not in bulk)
Rating: 4
Lock at Bitmap level March 23, 2004
Reviewer: A Reader from IND
Hi,
   Bitmap Indexes lock the full bitmap.
   Suppose I have a bitmap indexed column in table having values 1 or 2;
   If I update a row with value 1 then will it lock all the rows with value 1
   for other sessions ?

Thanks,
Followup:
no, they do not.  they lock an entry in the bitmap which might lock a range of
rows effectively, but they do not lock ALL of the values for a key, nor do they
lock the entire bitmap.

You do not use bitmap indexes in multi-user update situations.  never.  they
would kill your performance.  don't even consider it.
Rating: 5
Inserts and Bitmap Indexes March 23, 2004
Reviewer: Tony from Reading, UK
Hello Tom,

Thanks for a great site.

I'm still not sure when to use Bitmap Indexes. I have a table with 2 of the 15
columns having Bitmap Indexes. These columns can contain around 10 distinct
values and are used for reporting purposes. About 100 rows get inserted every
day.
Should I be using Bitmap Indexes for these columns or would a b-tree be better?
Can I have your opinion please?
Followup:
hard to say -- how many rows, what types of queries and are the rows bulk
inserted all at once or one row at a time....
Rating: 5
Inserts and Bitmap Indexes March 23, 2004
Reviewer: Tony from Reading, UK
The table is still quite new and only has around 1000 records in it a the moment
but as I say I expect it to grow by about 100 rows a day.
1 of the columns is a 'transaction_type' and will mainly be used to group counts
of transactions.
The rows are added 1 row at a time.

Also is there a more up to date version of the "Oracle8i Concepts Release 2
(8.1.6)", I mean 9i or 10g? I have seen you comment about it a few times and
intend to study it.

Thanks
Followup:
otn.oracle = source of all documentation, including 10g, 9i, 8i, 8.0, 7.3
even....

adding the rows 1 at a time will kill the bitmap.  read
http://dbazine.com/jlewis3.html
sounds more like a "MV" to me (what you are looking for) -- read about
materialized views in the data warehousing guide.
Rating: 5
Bitmap Indexes March 23, 2004
Reviewer: Tony from Reading, UK
Thanks Tom,

I not really trying to do anything special, just created a standard table but I
have just taken part 1(SQL) of my oracle exams(88%) which when reading the SQL
exam guide(by Jason Couchman) It says that bitmap indexes are good in this
situation(low-cardinality).
Maybe I misunderstood what he was saying, I will re read that section.

Thanks again for your help.
Followup:
they are "good" for low cardinality (and cardinality is a RELATIVE term) -
however they are TERRIBLE for single row modifications.
Rating: 5
The meaning of BLEVEL by bitmap index March 27, 2004
Reviewer: Jaromir D.B. Nemec from Austria
Hi Tom,

what is the meaning of BLEVEL by a bitmap index? I thought they are completelly
flat, but a saw recently one with level 14.

thx

Jaromir
Followup:
level 14 -- really?  that is deep.  it is the b*tree level, height minus one.
14 would be "exceptional".  Must be a really small block size and/or a really
really fat key and/or *lots* of records.

consider:

ops$tkyte@ORA9IR2> create table t ( x varchar2(40) );

Table created.

ops$tkyte@ORA9IR2> insert into t select dbms_random.string( 'a', 40 ) from
all_objects;

30638 rows created.

ops$tkyte@ORA9IR2> insert into t select dbms_random.string( 'a', 40 ) from t;

30638 rows created.

ops$tkyte@ORA9IR2> insert into t select dbms_random.string( 'a', 40 ) from t;

61276 rows created.

ops$tkyte@ORA9IR2> insert into t select dbms_random.string( 'a', 40 ) from t;

122552 rows created.

ops$tkyte@ORA9IR2> create index t_idx on t(x) compute statistics;

Index created.

ops$tkyte@ORA9IR2> analyze index t_idx validate structure;

Index analyzed.

ops$tkyte@ORA9IR2> select blevel, height
  2    from user_indexes, index_stats
  3   where user_indexes.index_name = 'T_IDX'
  4  /

    BLEVEL     HEIGHT
---------- ----------
         2          3

That means it'll take 3 LIO's to get from the root block -> branch block -> leaf
block.  The "height" of the b*tree is 3 there.  A height of 4 would mean 4 LIO's
and so on

if you have a blevel of 14, that means 15 LIO's to get to the data -- I
personally have not seen one that tall.  be interested in hearing more "about
it"
Rating: 5
tall BITMAP index March 29, 2004
Reviewer: Jaormir Nemec from A
Hi Tom,

no, we don’t have a 14 level tall B*tree index (as I know, only Jonathan Lewis
has such a thing:), it was bitmap index.
Also on the blocksize front wee are, I guess, with 16K OK.
The table has about 18 M rows with 100K blocks. The bitmapped index column has
70+ distinct values and in a initial state the bitmap index has less then 1000
blocks (same B*tree index would have nearly 20K blocks).
The system is not a OLTP, but there are some 10.000 single insert (in a cursor
loop) to the table per day. The number of distinct key is also increasing
slowly. The result after few months was as described (sorry the BLEVEL was 12
not 14 I recall it wrong).
YES I know that we used BITMAP index improperly, we “fixed” the problem with a
switch to B*tree index and plan to drop and recreate the BITMAP index after the
inserts (or invalidate and rebuild). The question was only what does the BLEVEL
mean in context of a bitmap index. Is a bitmap index B*tree like organized?

Thanks

Jaromir
Followup:
ahh, really fat entries (the big bitmaps are in there)

...some 10.000 single insert (in a cursor
loop) to the table per day....

that is like DEATH to a btree.  how about you put them into a TEMP TABLE row by
row (slow by slow) and then insert /*+ append */ them into the real table?

there are only b*trees really -- bitmaps are btrees :)  btrees with really fat
entries.... (they are special b*trees but just b*trees)
March 29, 2004 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.
Rating: 3
What about indexing date ranges ? February 18, 2004
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
Tom,

You have been discussing in great extent date range comparison. What about
indexing for performance date ranges?

For example , we have a table with start date and end date. We would like to be
able to pinpoint the rows where a specific moment is in the date range:

select * from <table>
where begin_date <= :moment
and end_date > :moment

What kind of index should we create to do this efficiently?

Creating a combined index (begin_date, end_date) results in a huge range_scan on
the index from begin_date, to the lowest date in the index. Is there any other
more efficient alternative ?
Followup:
It is alot like the "spatial" problem with lat/long's.   We had to invent a
special datatype for that stuff and geocode the data to make it "efficient".

It is a pretty hard problem when you think about it.  Suppose you have data:

01-jan-2003       02-jan-2003
01-jan-2003       03-jan-2003
.....
01-jan-2003       31-dec-2003
02-jan-2003       03-jan-2003
02-jan-2003       04-jan-2003
....
02-jan-2003       31-dec-2003
(and on for each day in 2003, eg:

ops$ora920@ORA920PC> create table t ( a date, b date );

Table created.

ops$ora920@ORA920PC>
ops$ora920@ORA920PC> insert into t
  2  select a,b
  3    from (select to_date('01-jan-2003')+rownum-1 a
  4            from all_objects
  5            where rownum <= 365 ),
  6         (select to_date('01-jan-2003')+rownum-1 b
  7            from all_objects
  8            where rownum <= 365 )
  9   where a < b;

66430 rows created.

Now, say we index like this (using high pctfree to really spread the data out)

ops$ora920@ORA920PC> create index t_idx on t(a,b) pctfree 95;
Index created.

Now, we query:

ops$ora920@ORA920PC> select *
  2    from t
  3   where a <= to_date( '01-jan-2004' )
  4     and b > to_date( '01-jan-2004' )
  5  /

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       8306  consistent gets
       8305  physical reads
          0  redo size
        266  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

it had to read the entire index -- to find no data.  so it seems that perhaps:

ops$ora920@ORA920PC> drop index t_idx;

Index dropped.

ops$ora920@ORA920PC> create index t_idx on t(b,a) pctfree 95;

Index created.

ops$ora920@ORA920PC>
ops$ora920@ORA920PC> select *
  2    from t
  3   where a <= to_date( '01-jan-2004' )
  4     and b > to_date( '01-jan-2004' )
  5  /

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          3  consistent gets
          0  physical reads
          0  redo size
        266  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

maybe we just had the index backwards?  but no, it is not to be true all of
the time:

ops$ora920@ORA920PC> select *
  2    from t
  3   where a <= to_date( '01-jan-2002' )
  4     and b > to_date( '01-jan-2002' )
  5  /

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       8306  consistent gets
       8304  physical reads
          0  redo size
        266  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

it depends on the inputs...  sometimes a,b is right -- sometimes b,a is
correct (and sometimes FULL_SCAN=true is correct)

Ok, so that does not help you too much yet (just explains why this is "hard").
If this is the "important query to tune", we can do a couple of things.

I could use using Oracle Text to index a "function", this function would take
two dates and return a string, something like:

input = 01-jan-2003, 14-mar-2003

012003 022003 032003

then we could:

select * from t where contains( dummy, to_char( :moment, 'mmyyyy' ) > 0 and
begin_date <= :moment
and end_date > :moment

that would have us just look at "the months" of interest.

We could also do this with a trigger and an IOT -- sort of a DIY index, you
would create an IOT with a date field and either a rowid or copy the primary key
from the original table.

A trigger would put into this IOT the dates, rowid/pk -- after computing the
relevant dates (eg: trunc start date to the month, insert that, add a month,
insert that until you have a date that exceeds your end date).

then you can query:

select * from t where rowid in ( select rid from iot where dt =
trunc(:moment,'mm')) and begin_date <= :moment
and end_date > :moment;

it'll do sort of the same thing.  Of course, you could trunc to the week and add
7 days instead of a month, trunc to the day and add a day - whatever -- to make
the index "more selective"
Rating: 4
Exactly February 19, 2004
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
Those are the exact problems I am facing. I did try the index in reverse too. I
finally had to take a piece of paper and write a few entries to understand why a
range scan from the start value to the end of the entire index was necessary.

You said:
<q>
It is alot like the "spatial" problem with lat/long's.   We had to invent a
special datatype for that stuff and geocode the data to make it "efficient".
</q>

I am not familiar with the solution in lat/long's. Before I dig into it, do you
think a similar solution would be possible in this case?
Followup:
No, i don't think a similar solution would work -- lats/longs are in a very
discrete, bounded, small set.

start_date, end_date is "infinite" in size -- not bounded.

unless you put artificial (or business) constraints on the start/end -- the
problem is just that they could be any pair of values with no end points. so the
stuff that works for lat/long -- won't work for start/end
Rating: 5
March 19, 2004
Reviewer: A reader
Rating: 3
Slightly different March 22, 2004
Reviewer: Sudha Bhagavatula from Buffalo, NY, USA
I have 2 tables contracts and member_providers each with member_id, eff_date and
end_date fields. The tabbe in contracts has data like this :

member id := 100
eff_date := 01/01/2003
end_date := 12/31/2003

The table member_providers has data like this:

member_id := 100
eff_date := 01/01/2003
end_date := 06/30/2003

member_id := 100
eff_date := 08/01/2003
end_date := 12/31/2003

I want to identify the break in range in member_providers table compared to the
contracts table in a SQL statement without using PL/SQL. Is it possible?
Followup:
you can find breaks using LAG() and LEAD() easily....

do you know about them?  (search for

rock roll

on this site -- lots of analytic examples)
Rating: 5
very interesting issue March 22, 2004
Reviewer: Muhammad Ibrahim from China
CB => c_beginning
PB => parameter start date
PE => parameter end date

CB1,CB2... different start dates in the table t
CE1,CE2... different end dates in the table t.

the situation looks like:

           PB                         PE
           |--------------------------|

                   |-----------|
                  CB1         CE1

           |--------------------------|
           CB2                      CE2

|----------------|
CB3             CE3

                   |------------------|
                   CB4             CE4

  |-----|
  CB5   CE5

                                            |--------|
                                           CB6      CE6

From the below query

Select *
From   t
Where ( CB Between PB And  PE        -- rule 1
        Or CE Between PB And PE         -- rule 2
        Or ( CB < PB And PE < CE ) );  -- rule 3

it will return the below date sets:

 CB1-CE1
 CB2-CE2
 CB3-CE3
 CB4-CE4
 CB5-CE5 -- *

the first four are correct but i want to eliminate the CB5-CE5 because either
start or end date doesnt falls in PB-PE.
And also same with CB6-CE6. How can i do this?

Thanks,
Ibrahim.
Followup:
how does that return cb5-c35 since

cb5 is not between pb and pe

ce5 is not between pb and pe

pe is NOT less than ce5
Rating: 5
yes you are correct it returns CB5 also but will it return CB7 new scenario too? March 22, 2004
Reviewer: A reader
    Thanks for your reply. One more simple question.Will the same query returns
for CB7 too?

CB => c_beginning
PB => parameter start date
PE => parameter end date

CB1,CB2... different start dates in the table t
CE1,CE2... different end dates in the table t.

the situation looks like:

           PB                         PE
           |--------------------------|

                   |-----------|
                  CB1         CE1

           |--------------------------|
           CB2                      CE2

|----------------|
CB3             CE3

                   |------------------|
                   CB4             CE4

  |-----|
  CB5   CE5

                                            |--------|
                                           CB6      CE6
|----------------------------------------------------------|
CB7                                                       CE7
From the below query

Select *
From   t
Where ( CB Between PB And  PE        -- rule 1
        Or CE Between PB And PE         -- rule 2
        Or ( CB < PB And PE < CE ) );  -- rule 3

it will return the below date sets:

 CB1-CE1
 CB2-CE2
 CB3-CE3
 CB4-CE4
 CB5-CE5
 CB7-CE7 -- ???

  If i want to get the CB7 also do i need to add some more condition? The reason
is CB7 is active eventhough it starts before PB date and ends behind PE date.

Thanks,
Ibrahim.
Followup:
for cb7

rule1 is false
rule2 is false
rule3 is false

(you know, you could load up some data or just evaluate the rules yourself! )

Yes, you would have to accomidate for cb7-ce7 if you wanted it, the original
problem statement did not want it.  would not be too hard to add.
Rating: 5
Thanks for your reply March 23, 2004
Reviewer: Muhammad Ibrahim from China
Dear Tom,

Select *
From   t
Where ( CB Between PB And  PE        -- rule 1
        Or CE Between PB And PE      -- rule 2
        Or ( CB < PB And PE < CE )   -- rule 3
        Or ( CB < PE And PE < CE );  -- rule 4

   The rule4 can overcome the CB7-CE7 but i am just wondering that whether any
other scenario can pass thro' PB and PE. Just a doubt :-)

   Thanks for your kind help to the oracle world.

Regards,
Ibrahim.
Followup:
at this point, it is just "logic", a predicate, a where clause.

you define what you want and the db will return it.

if that is what you want, there you go.....
Rating: 3
Optimizing joins on start-end ranges March 26, 2004
Reviewer: Bobby O from Palo Alto, California
Tom,
Can the above advice be expanded range-queries for self-joins of large tables,
on numeric rather than date values? For data-mining (i.e. not OLAP) queries?

As an example, below, I need to returns ids for pairs of start-end records that
overlap.  I run variants of the query below quite often, and the spatial overlap
step is an enormous, recurring bottleneck.

In my case, these are data-mining/data-analysis joins on a system dedicated to
these types of joins; pre-calculating materialized views, adding indexes,
partitioning, writing pipelined table functions, custom index functions etc.
would all be ok as a solution

Thanks,
  Bobby O

DETAILS:
--- the query:
SQL> edit
Wrote file afiedt.buf

  1  select c1.id as id1, c2.id as id2
  2  from chrom_interval c1
  3      , chrom_interval c2
  4  where c1.cstart < c2.cend
  5    and c1.cend   > c2.cstart
  6    and c1.chrom = c2.chrom
  7*   and c1.id < c2.id -- id is primary key; report each pair once

 ---- The table:
SQL> describe chrom_interval;
 Name                      Null?    Type
 ------------------------ -------- ---------------
 ID                        NOT NULL NUMBER
 CHROM                     NOT NULL VARCHAR2(255)
 CSTART                    NOT NULL NUMBER(10)
 CEND                      NOT NULL NUMBER(10)
SQL> select count(*), count(distinct chrom ) from chrom_interval;

  COUNT(*) COUNT(DISTINCTCHROM)
---------- --------------------
  24644323                   41

The (cstart,cstop) range of each record is small compared to the max( cstart )
and min( cstart ) for each chrom. E.g. overlaps are infrequent, with each record
overlapping ~0-10 other records.
Common query variants often include using the above join but with c1 & c1 from
two different tables with the same columns as chrom_interval, or restricting c1
to a range of ids comprising ~0.1-1% of all chrom_interval rows.
Followup:
the techniques would apply for numbers as well as dates.

just as you can expand dates out into months and text index them -- you can do
so with numbers as well.

but, there our goal was to reduce the search space and your query:

where c1.chrom=c2.chrom
  and c1.id < c2.id

would seem to already do that?

If you are looking for overlaps (record "1" overlaps record "2") -- analytics
might be best (single pass)....

It will not detect multiple overlaps as your does (eg; record 2 and record 3
overlap with record 1 -- you would show

1 2
1 3
2 3

I would show

1 2
2 3

not sure if that matters to you or not...)

ops$tkyte@ORA9IR2> select c1.id as id1, c2.id as id2
  2  from t c1
  3      , t c2
  4  where c1.cstart < c2.cend
  5    and c1.cend      > c2.cstart
  6    and c1.chrom = c2.chrom
  7    and c1.id < c2.id
  8  /

       ID1        ID2
---------- ----------
         3          4

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select id, next_id
  2    from (
  3  select id,
  4         lead(id) over (partition by chrom order by id) next_id,
  5         cend,
  6         lead(cstart) over (partition by chrom order by id) next_cstart
  7    from t
  8         )
  9   where next_cstart <= cend
 10  /

        ID    NEXT_ID
---------- ----------
         3          4
Rating: 2
getting all overlaps March 29, 2004
Reviewer: Bobby O from Palo Alto, CA
Tom,
thanks. I'm still not sure how to optimize the query in my previous post; I do
need all overlaps; e.g.
in the case you mentioned, I need the result:
1 2
1 3
2 3

; the other result, below, would not work.

1 2
2 3

; What is the optimal strategy for greatly accelerating this type of query when
comparing two very large tables? what index + what hint, or rather write custom
pl/sql, or.... ?
I'm willing to accept a less elegant solution if it is a lot faster. Currently,
the likely alternative is dumping the data out of the database & doing the
overlap test in perl. The query I noted runs too slow to be useful/acceptable. (
many hours to days). But I would think there is a way where Oracle is the
fastest route....?
Followup:
then you need what you asked for - a non-equijoin

the only way to "optimize it" in this case would be to change the question :)

you need to almost cartesian join at the "id" level in order to get the number
of rows you want.

You will not get me to believe that anything you write in perl would be faster
than anything you could do in SQL or PLSQL.  Sorry, but I would not "buy that".
If you want to benchmark it - please post the perl and we'll have a look see at
the assumptions you've made.

also, whats the output of an autotrace traceonly explain for that query?
March 29, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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.
Rating: 5
January 22, 2004
Reviewer: Valar from Littleton,MA
Hi Tom, Thanks for the simple and precise example of using an application
context and sys_context functions. Sounds like a good use when the predicate
clause is dynamic. I am trying to use this approach for one of my problems. My
question here is, would you use the same method to build the predicate clause,
if there are 40 input variables to the procedure.

My procedure would be something like the following,

create or replace context dem_ctx using sp_getqueryresults;

create or replace procedure sp_getqueryresults
(p_lastname    dem.lastname%type default null,            -- Search condition1
 p_firstname    dem.firstname%type default null,
 p_dob        dem.dob%type       default null,
 p_height    dem.height%type       default null,
 p_eyecolor    dem.eyecolor%type  default null,
 p_haircolor    dem.haircolor%type default null,
 ...
 ...
 p_address1    dem.address1%type  default null        -- Search condition 40

)
is
type rc is REF CURSOR;

t_demcursor rc;
t_query        varchar2(5000)
        default 'select a.demid a.lastname, a.firstname, a.dob, a.createdate
from dem a where 1=1';

cursor t_template is select * from dem;
t_demrec  t_template%rowtype;

begin
    -- Check for lastname
    if p_lastname is not null then
        dbms_session.set_context( 'DEM_CTX',
'LASTNAME','%'||upper(p_lastname)||'%');
              t_query := t_query ||    ' and lastname like sys_context(
''DEM_CTX'', ''LASTNAME'' ) ';
           dbms_output.put_line('Inside lastname');
     end if;

    -- Check for firstname
    if p_firstname is not null then
         dbms_session.set_context( 'DEM_CTX',
'FIRSTNAME','%'||upper(p_firstname)||'%');
         t_query := t_query || ' and firstname like sys_context( ''DEM_CTX'',
''FIRSTNAME'' ) ';
         dbms_output.put_line('Inside firstname');
        end if;

    /* Code for search criteria 3 upto search criteria 39

    ...
    ...
    */

    -- Check for address1
    if p_address1 is not null then
         dbms_session.set_context( 'DEM_CTX', 'ADDRESS1',p_address1);

         t_query := t_query ||' and contains(address1, sys_context( ''DEM_CTX'',
''ADDRESS1'' )) > 0';
         dbms_output.put_line('Inside address1');
        end if;

    p( t_query);

    open t_demcursor for t_query;
    loop
             fetch t_demcursor into t_demrec;
             exit when t_demcursor%notfound;

            dbms_output.put_line( t_demrec.demid || ',' ||
                                      t_demrec.lastname || ',' ||
                                   t_demrec.firstname );
    end loop;

    close t_demcursor;
end;
/

Looking at the above code, you would find that for the different search
criteria, we have to code atleast 40 "if-end if " blocks. Could you please
suggest ways to refine/minimise the above code ? My search conditions involve
number, date parameters, textdata (using contains) and also inlist type of
variables.

Thanks,
Valar
Followup:
depends - here I might - MIGHT -- consider a text index and index an XML
representation of that row and use "contains" with section searching as I doubt
you'd index everything otherwise.
Rating: 5
January 26, 2004
Reviewer: Valar from Littleton, MA
Thanks for the last follow-up. You are right that almost all search fields are
indexed. We are looking at atleast 35 individual indexes or so. I was further
reading on to see what the options of storing in xml are and how section
indexing worked. It's obvious if we go for such an option, we would have to
store the demographic data pertaining to each row in a seperate column in the
dem table. Is that right ?  (I think the answer would be 'Yes', as the data is
currently stored in seperate columns in the dem table.)

Before going more into that, I would like to know, what might influence your
decision to go for an xml representation with section searching ? Is it the
volume of data in the dem table or is it the type of transactions involved in
the dem table or Is it the required performance of search query ?

Currently this search procedure sp_getqueryresults is intended to provide core
functionality for one of our searching Components which should serve a wide
range of customers. The demographic records to search vary by each customer. The
lowest volume of records to be searched is 300,000 and the highest is 15 million
or so. We have to come up with a possible package/procedure to serve the varied
customer base and provide efficient searching. The type of transactions involved
are mostly 'insert-only'. Of course there are a few update transactions to
delink records, which have been linked due to mistyped customer number/ssn and
so forth.  Should we plan such that we need to come up with different search
routine packages/procedures depending upon the volume of data or so ?  Any other
suggestions/pointers ? Please let us know.
Thanks,
Valar
Followup:
it would be all about retrieval.  You want to search on N of M fields where N is
always changing.  I'd like to have a single index.

sp_, hmmm, a sqlserver stored procedure in disguise ;)

I would use the technique of indexing a function -- store the data relationally,
but index an xml representation of it (very close to what I do on asktom here.
I index "web pages" but the data is stored in 3 tables with master /detail
/detail relationships.  I index a "function" that returns the formatted page so
you think you are searching pages -- but you are really searching many rows
across many tables)

You would use the same technique for 1 row, or 15 billion rows.
Rating: 5
January 27, 2004
Reviewer: Valar from Littleton, MA
Thanks for pointing towards the technique of indexing a function and probably
using a single index rather than N number of indexes. Shall look further into
this approach and apply to this problem.

Regards usage of sp_, it so happened that in the place where I work, the usual
convention for referencing a stored procedure is sp_ and for packages it is
pack_. Here we do projects both in SQL Server and Oracle, so it's been like
that.

Thanks again !!
Valar
Rating: 5
January 28, 2004
Reviewer: Valar from Littleton, MA
Tom,

Based on your follow-up, I tried to generate an xml representation of a
simplistic dem table. Attached below are the sql outputs for the same. The
create context index scripts was throwing up an error and I wasn't sure, if I
was on the right path. Can you please suggest on this ? Thanks,
Valar
CTDLDBO@CTDL:CTTESTCIS> create table dem
  2  ( demid  number,
  3   lastname varchar(25),
  4   firstname varchar(10),
  5   address1 varchar(30)
  6  )
  7  tablespace data;

Table created.

CTDLDBO@CTDL:CTTESTCIS>
CTDLDBO@CTDL:CTTESTCIS>
CTDLDBO@CTDL:CTTESTCIS> insert into dem
  2  values (1,'MCDERMOTT', 'JIM', '30 Boston Road');

1 row created.

CTDLDBO@CTDL:CTTESTCIS>
CTDLDBO@CTDL:CTTESTCIS> insert into dem
  2  values (2,'G', 'VALAR', '50 Kendall Square');

1 row created.

CTDLDBO@CTDL:CTTESTCIS>
CTDLDBO@CTDL:CTTESTCIS> insert into dem
  2  values (3,'SMITH', 'BOB', '25 Taylor Street');

1 row created.

CTDLDBO@CTDL:CTTESTCIS> commit;

Commit complete.

CTDLDBO@CTDL:CTTESTCIS>
CTDLDBO@CTDL:CTTESTCIS>
CTDLDBO@CTDL:CTTESTCIS> create or replace package pack_testgenxml
  2  as
  3   function fn_gendemxml return clob DETERMINISTIC;
  4  end;
  5  /

Package created.

CTDLDBO@CTDL:CTTESTCIS>
CTDLDBO@CTDL:CTTESTCIS>
CTDLDBO@CTDL:CTTESTCIS> create or replace package body pack_testgenxml
  2  as
  3
  4  function fn_gendemxml return clob
  5  is
  6  tqueryctx dbms_xmlquery.ctxtype;
  7  tempdata clob;
  8  begin
  9   tqueryctx := DBMS_XMLQuery.newContext('select * from dem');
 10   tempdata := dbms_xmlquery.getxml(tqueryctx);
 11   DBMS_XMLQuery.closecontext(tqueryctx);
 12   return tempdata;
 13  end;
 14  end;
 15  /

Package body created.

CTDLDBO@CTDL:CTTESTCIS>
CTDLDBO@CTDL:CTTESTCIS> set autoprint on
CTDLDBO@CTDL:CTTESTCIS> variable x clob;
CTDLDBO@CTDL:CTTESTCIS> exec :x := pack_testgenxml.fn_gendemxml()

PL/SQL procedure successfully completed.

X
--------------------------------------------------------------------------------
<?xml version = '1.0'?>
<ROWSET>
   <ROW num="1">
      <DEMID>1</DEMID>
      <
LASTNAME>MCDERMOTT</LASTNAME>
      <FIRSTNAME>JIM</FIRSTNAME>
      <ADDRESS1>3
0 Boston Road</ADDRESS1>
   </ROW>
   <ROW num="2">
      <DEMID>2</DEMID>

 <LASTNAME>G</LASTNAME>
      <FIRSTNAME>VALAR</FIRSTNAME>
      <ADDRESS1>50 Ke
ndall Square</ADDRESS1>
   </ROW>
   <ROW num="3">
      <DEMID>3</DEMID>

<LASTNAME>SMITH</LASTNAME>
      <FIRSTNAME>BOB</FIRSTNAME>
      <ADDRESS1>25 T
aylor Street</ADDRESS1>
   </ROW>
</ROWSET>

CTDLDBO@CTDL:CTTESTCIS>
CTDLDBO@CTDL:CTTESTCIS> create index idx_dem_fngendemxml on
dem(pack_testgenxml.fn_gendemxml())
  2  indextype is ctxsys.context;
create index idx_dem_fngendemxml on dem(pack_testgenxml.fn_gendemxml())
                                        *
ERROR at line 1:
ORA-29958: fatal error occurred in the execution of ODCIINDEXCREATE routine
ORA-29960: line 1,
DRG-11304: function-based indexes are not supported by this indextype

CTDLDBO@CTDL:CTTESTCIS> 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

CTDLDBO@CTDL:CTTESTCIS>
Followup:
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:440419921146
that demonstrates how to index a function with TEXT.

What I do is I have three tables:

create table parent
create table detail1;
create table detail2;

and on detail1 and detail2 I put a trigger that touches parent with an update
when they are updated.  the index is on the parent table and it assembles (based
on it's primary key) the data from parent and detail1/detail2 and that is what
text indexes.
Rating: 5
Thanks January 29, 2004
Reviewer: Valar from Littleton, MA
Tom, The link to the text index creation proved to be very useful. I was able to
index a text representation of an xml output. I like the way you give such
explanatory examples and share your knowledge with the Oracle community.

-Valar
Rating: 5
bind variables?? February 20, 2004
Reviewer: dwl from uk
Tom

This thread has proved very interesting to me and very useful.
I understand that we need to use bind variables.  My procedure is below. As you
can see
i'm using several of your techniques!
I use your str2tble function to pass in a varying array of interger to the
procedure to run.

However can you just explain if/why the second version of the same procedure
does not use bind variables? All
the second version does is remove the sys_context call and replace it with the
bind variable:

PROCEDURE P_Get_SBE_ST (    P_Return_Date     IN    SBE_TABLEA.RETURN_DATE%TYPE,
                P_Org_String    IN    VARCHAR2 DEFAULT NULL,
                P_Cur         IN OUT     C_Return_Cursor    )

IS

l_query  varchar2(2000)
default 'SELECT    :P_Return_Date,
        o.Org_Id,
        o.Organisation_Code,
        CURSOR(SELECT    RETURN_DATE,
                PAYMENT_ORG_ID,
                T1_ST_COUNT,
                T2_OO_FULL,
                T2_OO_PART,
                T2_OMP_FULL,
                T2_OMP_PART,
                T2_TOTAL_OO,
                T2_TOTAL_OMP,
                T2_TOTAL_FULL,
                T2_TOTAL_PART,
                T2_TOTAL_DOM,
                T3_OO_ALL,
                T3_OMP_ALL,
                T3_TOTAL,
                T5A_ST_COUNT,
                T5B_DOM_COUNT
            FROM    SBE_TABLEA
            WHERE    Payment_Org_ID     = o.Org_ID
            AND    Return_Date     = :P_Return_Date) AS TABLEA,
        CURSOR(SELECT    RETURN_DATE,
                PAYMENT_ORG_ID,
                REASON,
                REASON_COUNT,
                REASON_DESCRIPTION
            FROM    SBE_TABLE4
            WHERE    Payment_Org_ID     = o.Org_ID
            AND    Return_Date     = :P_Return_Date) AS TABLE4
    FROM    ORGANISATION o
    WHERE     Type_Id IN (5, 7, 10)';

BEGIN

IF ( P_Org_String IS NOT NULL ) THEN

    DBMS_SESSION.SET_CONTEXT( 'MY_CTX', 'ORG_STRING', P_Org_String);
    l_query := l_query ||'     AND    o.Org_ID IN (    SELECT *
                FROM THE ( SELECT CAST( str2tbl( sys_context( ''MY_CTX'',
''ORG_STRING'' ) ) as tbl_Numbers ) from dual ) )  ';

END IF;

open P_Cur for l_query USING     P_Return_Date,
                P_Return_Date,
                P_Return_Date;

END P_Get_SBE_ST ;

The following is a copy of the above without using the context method:
(P_Org_String    just contains a string such as '1, 2, 3, 4, 5')

PROCEDURE P_Get_SBE_ST (    P_Return_Date     IN    SBE_TABLEA.RETURN_DATE%TYPE,
                P_Org_String    IN    VARCHAR2 DEFAULT NULL,
                P_Cur         IN OUT     C_Return_Cursor    )

IS

l_query  varchar2(2000)
default 'SELECT    :P_Return_Date,
        o.Org_Id,
        o.Organisation_Code,
        CURSOR(SELECT    RETURN_DATE,
                PAYMENT_ORG_ID,
                T1_ST_COUNT,
                T2_OO_FULL,
                T2_OO_PART,
                T2_OMP_FULL,
                T2_OMP_PART,
                T2_TOTAL_OO,
                T2_TOTAL_OMP,
                T2_TOTAL_FULL,
                T2_TOTAL_PART,
                T2_TOTAL_DOM,
                T3_OO_ALL,
                T3_OMP_ALL,
                T3_TOTAL,
                T5A_ST_COUNT,
                T5B_DOM_COUNT
            FROM    SBE_TABLEA
            WHERE    Payment_Org_ID     = o.Org_ID
            AND    Return_Date     = :P_Return_Date) AS TABLEA,
        CURSOR(SELECT    RETURN_DATE,
                PAYMENT_ORG_ID,
                REASON,
                REASON_COUNT,
                REASON_DESCRIPTION
            FROM    SBE_TABLE4
            WHERE    Payment_Org_ID     = o.Org_ID
            AND    Return_Date     = :P_Return_Date) AS TABLE4
    FROM    ORGANISATION o
    WHERE     Type_Id IN (5, 7, 10)';

BEGIN

IF ( P_Org_String IS NOT NULL ) THEN

    l_query := l_query ||'     AND    o.Org_ID IN (    SELECT *
                FROM THE ( SELECT CAST( str2tbl( :P_Org_String ) as tbl_Numbers
) from dual ) )  ';

END IF;

open P_Cur for l_query USING     P_Return_Date,
                P_Return_Date,
                P_Return_Date,
                :P_Org_String;

END P_Get_SBE_ST ;

I know you cannot run these procedures standalone but i am interested in the
reasons behind if/why method
2 does not use the bind variable P_Org_String, since it does use the bind
variable :P_Return_Date (or at least
i hope it does!!)  ??

Many thanks
Followup:
both use binds fully.
Rating: 3
Confused February 24, 2004
Reviewer: dwl from uk
Ok then so if both methods use binds then i don't understand the use of
sys_contexts.  I followed the other threads and examples from your book, and
utilised (i thought) the use of sys_context in the same way that you did.  So
why do i not need to use it here, if binds are used by my 2nd method what is the
use of sys_context??  Why did you use it in your methods and not do what i did
in my 2nd method??

Please explain i am getting confused here!
Followup:
it is a matter of compile time binding versus runtime binding.

Say you have a routine that accepts a query.  It can be ANY query on earth.  It
wants to use binds, you want to use native dynamic sql ( execute immediate or
open p_refcursor for ).

Well, you don't know at compile time whether it'll have 0, 1, 2, .... 1000 binds
right -- so you cannot code:

  open p_cursor for l_variable using A, B, C;

cause you don't know how many A, B, C's you'll need.  so the guy that sends you
the query can pass:

   select * from emp where ename like sys_context( 'my_ctx', 'ename_like' );

and would call dbms_session.set_context to put a value for ename like in there.

Now, say you have a routine that takes a query and wants to return rows N thru
M.

Now, you might code:

   open p_cursor for
     'select * from ( select a.*, rownum r from ( ' || their_query || ' ) a
where rownum <= :m ) where r >= :n' using l_max_row, l_min_row;

there, their query might use SYS_CONTEXT to bind dynamically, you use "static"
compile time binds -- since you know how many you have.

It is just different ways to achieve the same end goal.  static, compile time
binding is easier (less obscure) but doesn't give you the flexibility to bind 1,
2, 3 or 4 things depending on what you need at runtime (sys_context however
does)
Rating: 5
March 05, 2004
Reviewer: Mihail Bratu
Rating: 4
thanks but.... March 09, 2004
Reviewer: dwl from uk
Tom

Thanks for the reply above.  I think i understood what you meant here about
compile time and runtime binding, until that is i read this post:
http://asktom.oracle.com/pls/ask/f?p=4950:8:929564266780274490::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:4473634500618,
in there you advise the guy Boris that his code is not using bind variables
since he has coded things like:

   IF p_sname <>'' THEN
        IF p_searchname='1' THEN
            SQLQuery := SQLQuery || ' AND ACCOUNT_NAME LIKE '''|| p_sname ||
'%''';

but surely p_sname is a bind variable isn't it?  In that post you advised him to
change to sys_context as in:

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'' ) ';

But why? Don't both methods use binds?? I appreciate that sys_context maybe the
preferred better method but doesn't his first way still bind ?

Thanks
Followup:
No, p_sname is a variable that they were using to build a string that looked
like:

  and accountname like 'SMITH%'

In PLSQL if

a) you use static sql -- eg: "for x in ( select ....", "open CURSOR_NAME" -- it
is impossible to NOT USE binds (static sql = always bound correctly = best
thing)

b) you use DYNAMIC sql -- as they were in that example -- you must be careful to
bind, either using "execute immediate statement_in_a_string using
BIND_VARIABLE...." or using that sys_context trick
Rating: 5
how are ref cursors implemented? March 24, 2004
Reviewer: A reader
How is that using a ref cursor I can have a pointer
in my java client (say using JDBC for the sake of
example) to a shared pool where
sql cursors are stored?

Can you give a feel of how this may be implemented?
what happens (step by step) when we say
resultSet.next() to move the pointer in memory?

thanx!
Followup:
ref cursors are implemented the same as any cursor.  It takes more than a couple
of sentences to explain -- if you are really interested, I do go into this in
detail in either of

Effective Oracle By Design
Beginning Oracle Programming.

but you are pointing at an execution plan in the shared pool, the data is
processed by the dedicated or shared server (not in the 'sga').  cursors live in
the session -- not the shared pool.  the shared pool has parsed queries and
their plans.

As as for "what happens" when you got resultset.next() -- it "depends", it
totally depends.  But, at the end of the day -- you get your row.
Rating: 5
How does exists work March 25, 2004
Reviewer: A reader
SQL> ED
Wrote file afiedt.buf

  1  SELECT ENAME, EMPNO, SUM(SAL)
  2  FROM EMP
  3  WHERE EXISTS( SELECT NULL FROM DEPT
  4                   WHERE EMP.DEPTNO=DEPT.DEPTNO
  5                   AND EMP.DEPTNO=30)
  6* GROUP BY ENAME, EMPNO
SQL> /

ENAME           EMPNO   SUM(SAL)
---------- ---------- ----------
WARD             7521       1250
ALLEN            7499       1600
BLAKE            7698       2850
JAMES            7900        950
MARTIN           7654       1250
TURNER           7844       1500

In the above why is it that I'm not agetting a too many rows error.

Further in the below, I just want to check for the existenc e of deptno 30 in
the emp table, but using exists it is returning 6 rows. I read on your web site
that exists will check for the first instance of the existance of the row for
which we are validating and it returns. In that case I need to return only 1
row..

Wrote file afiedt.buf

  1  SELECT 'X'
  2  FROM EMP
  3  WHERE EXISTS( SELECT NULL FROM DEPT
  4                   WHERE EMP.DEPTNO=DEPT.DEPTNO
  5*                  AND EMP.DEPTNO=30)
  6  /

'
-
X
X
X
X
X
X

6 rows selected.

Please explain.
Followup:
where exists by its very very definition cannot get too many rows.  it looks for
the first one and STOPS (where exists -- stop when you see something exists...)

the subquery stops -- the query itself does not (that would be a bug)

if you just want the first random row, add "and rownum = 1"
Rating: 5
March 26, 2004
Reviewer: A reader
If that is the case then why is the above query returning 6 rows. Shouldnt it
return only one row, OR is it that it is checking for the existence of dept 30
once for each row returned by the outer query?

In the above query, if EMP has 1000 rows, and if deptno happens to be at rows 1,
100, and 500 will I be scanning the whole table for each row. How does it work.
Followup:
the query:

select * from t where exists ( subquery )

is conceptually like this:

  for x in ( select * from t )
  loop
      IF the subquery returns at least one row
      then
            OUTPUT X
      end if
  end loop

where exists checks each row in the outer query -- just like:

select * from t where x=5;

checks each row for "x=5", each row is checked to see if a row in the subquery
"exists"

where exists is really not different from "x=5" in that context.  It is just a
predicate applied to a row.
Rating: 3
Cursor bug March 26, 2004
Reviewer: Fan from Germany
Hi Tom,

Recently our database has crashed having problem with memory (Oracle v9.2).
Oracle support gave us an answer telling that we have hit oracle bug when a
query (cursor) in DB procedure accessing partitioned table use function new_time
and cursor_sharing is enabled => raise ORA-600.
But to me this look quite strange. To describe:
cursor_sharing is set to exact, query itself does not use function new_time
(procedure does) and system crashes only when in query where clause we use one
particular column (in that case query have a cost of 5000).
If we execute the same query under the same conditions but use another column
(in this case query have cost of 500 and return the same result set) system does
not crash.
In both cases query (actually cursor) use bind variables.
I have checked shared pool in second case and query has been parsed once and
executed 120 times. In first case is the same but only difference is
cost...query takes longer to finish.
This is confusing to me. If the bug is presistent, system will chrash in both
cases.
Does the query execution time play some role in this bug and could you please
give me some more information about this oracle bug if you have?
I do not know wheter my explanation was clear, but if you need some more
detailed info, I can post it.
Followup:
so, your database isn't crashing, a session is getting an ora-600 with
cursor_sharing...

but you say you are not using that -- so obviously -- support has either

a) misdiagnosed the issue
b) failed to explain that the but can happen due to other reasons.

without having access to the actual bug number and tar related information --
i'd be hard pressed to really "say more"
Rating: 5
very informative March 29, 2004
Reviewer: Susan from Watertown, MA
Tom,

I have a situation that sounds appropriate for ref cursors but would like your
thoughts on the suggested design.  We have certain table rows that only certain
users should be able to view.  We access all of these rows through a view.  I've
implemented a FGAC method of controlling who sees what.  Now, my manager wants
to add a permissions flag column on the view that will contain a string value
for all permission types (add, edit, browse, delete, settomissing).  The
application gets the string value and provides the appropriate access.  The goal
is to keep the logic of the permission flagging in a table (most of our
developers don't know pl/sql that well) and have a package that can dynamically
create the where statement based on the values (or lack of values) in the table.
 The logic will involve other tables (eg. form fm01 can be browsed by (select
user_id from users where site_id> 100), so we'll be using parameters.  We're
aiming to keep it flexible so that a form could have multiple conditions
regarding the same permission type.  I think ref cursors might be the approach
here, but, this is a little different in that the logic is table driven.  Thanks
for any suggestions.
Followup:
well, if you are using ref cursors to build a small finite set of performant SQL
queries......

instead of trying to use one huge generic "one size fits all query" that might
be not so performant.....

based on some inputs and maybe whose logged in currently, that works for me.
Rating: 4
Must create context after package is completely defined. March 29, 2004
Reviewer: Jim Nasby from Austin, TX
This just baffled me for a while... If you do something like:

CREATE PACKAGE foo AS
...

CREATE CONTEXT ctx_foo USING foo;

CREATE PACKAGE BODY foo AS
...

You will get

ORA-01031: insufficient privileges
ORA-06512: at "SYS.DBMS_SESSION", line 78

If you follow the exact procedure in the docs though, which is to create the
package AND package body, then create the context, it will work fine.
Followup:
give us a full "for example"

you can create the context and then everything else

you can create everything else and then the context

you can mix them in any order you want.....

(must have been a typo in your code I'm guessing...)

ops$tkyte@ORA9IR2> create package foo
  2  as
  3          procedure p;
  4  end;
  5  /

Package created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create context ctx_foo using foo
  2  /

Context created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create package body foo
  2  as
  3
  4  procedure p
  5  is
  6  begin
  7          dbms_session.set_context( 'ctx_foo', 'x', 55 );
  8  end;
  9
 10  end;
 11  /

Package body created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec foo.p

PL/SQL procedure successfully completed.
Rating: 4
Wonder March 30, 2004
Reviewer: Arangaperumal from Chennai,India
I really wonder about the techniques you are
using.
March 29, 2004 arunima -- Thanks for the question regarding "sql for heirarchical relationship", version 8.1.7
You Asked
Hi Tom,

Here is an example:

select   lpad(' ', (level - 1) * 2) || ename as padded_name, job, level
 from emp
 connect by prior empno = mgr
 start with mgr IS null;

PADDED_NAME          JOB            LEVEL
-------------------- --------- ----------
KING                 PRESIDENT          1
  JONES              MANAGER            2
    SCOTT            ANALYST            3
      ADAMS          CLERK              4
    FORD             ANALYST            3
      SMITH          CLERK              4
  BLAKE              MANAGER            2
    ALLEN            SALESMAN           3
    WARD             SALESMAN           3
    MARTIN           SALESMAN           3
    TURNER           SALESMAN           3
    JAMES            CLERK              3
  CLARK              MANAGER            2
    MILLER           CLERK              3

My question is :

I need to write a query wherein I can get the parents of 'CLERKS'. If in the
hierarchy 'CLERK' has a parent as 'ANALYST' then result should display that ot
else 'MANAGER'.

for example i want this result.

ADAM   SCOTT
SMITH  FORD
JAMES  BLAKE
MILLER CLARK

Thanks a lot for your help.
and we said...
scott@ORA817DEV.US.ORACLE.COM> select ename, (select ename from emp e2 where
empno = e1.mgr )
  2    from emp e1
  3   where job = 'CLERK'
  4  /

ENAME      (SELECTENA
---------- ----------
SMITH      FORD
ADAMS      SCOTT
JAMES      BLAKE
MILLER     CLARK

is one way to do that.

scott@ORA817DEV.US.ORACLE.COM> select e1.ename, e2.ename
  2    from emp e1, emp e2
  3   where e1.mgr = e2.empno
  4     and e1.job = 'CLERK'
  5  /

ENAME      ENAME
---------- ----------
SMITH      FORD
ADAMS      SCOTT
JAMES      BLAKE
MILLER     CLARK

is another...
Review & Followup

Rating: 2
heirarchy relationship February 01, 2002
Reviewer: arunima from NY,USA
Tom,

If the table looks like this then how will we do it. If in the hierarchy 'CLERK'
has a parent as 'ANALYST' then
result should display 'ANALYST' or else 'MANAGER'.

Since ANALSYT or MANAGER is not a immediate parent of 'CLERK' I was wondering
how to do that.

PADDED_NAME          JOB            LEVEL
-------------------- --------- ----------
KING                 PRESIDENT          1
  JONES              MANAGER            2
    SCOTT            ANALYST            3
      ADAMS1         SALESMAN           4
       ADAMS         CLERK              5
    FORD             ANALYST            3
      SMITH1         SALESMAN           4
      SMITH          CLERK              5
  BLAKE              MANAGER            2
    ALLEN            SALESMAN           3
    WARD             SALESMAN           3
    MARTIN           SALESMAN           3
    TURNER           SALESMAN           3
       JAMES            CLERK           4
  CLARK              MANAGER            2
    MILLER1          SALESMAN           3
    MILLER           CLERK              4

Thanks for your time.
Followup:
you are not making sense to me.  show an example of what you expect like you did
above (and I then supplied the answer for)
Rating: 4
heirarchy relationship February 01, 2002
Reviewer: arunima from NY,USA
Tom,

If the table looks like this then how will we do it. If in the hierarchy 'CLERK'
has a parent as 'ANALYST' then
result should display 'ANALYST' or else 'MANAGER'.

Since ANALSYT or MANAGER is not a immediate parent of 'CLERK' I was wondering
how to do that.

PADDED_NAME          JOB            LEVEL
-------------------- --------- ----------
KING                 PRESIDENT          1
  JONES              MANAGER            2
    SCOTT            ANALYST            3
      ADAMS1         SALESMAN           4
       ADAMS         CLERK              5
    FORD             ANALYST            3
      SMITH1         SALESMAN           4
      SMITH          CLERK              5
  BLAKE              MANAGER            2
    ALLEN            SALESMAN           3
    WARD             SALESMAN           3
    MARTIN           SALESMAN           3
    TURNER           SALESMAN           3
       JAMES            CLERK           4
  CLARK              MANAGER            2
    MILLER1          SALESMAN           3
    MILLER           CLERK              4

Thanks for your time.
Rating: 3
sql for heirarchical relationship February 01, 2002
Reviewer: arunima from NY, USA
Sorry Tom, If I was not detailed enough with my question. In this table
structure mentioned below I need to know the parents of CLERKS.

In the heirarchy of each CLERK if there is an ANALYST then my result should
display the ANALYST. If in the heirarchy of CLERK there are no ANALYST then the
result should be MANAGER.

PADDED_NAME          JOB            LEVEL
-------------------- --------- ----------
KING                 PRESIDENT          1
  JONES              MANAGER            2
    SCOTT            ANALYST            3
      ADAMS1         SALESMAN           4
       ADAMS         CLERK              5
    FORD             ANALYST            3
      SMITH1         SALESMAN           4
       SMITH          CLERK             5
  BLAKE              MANAGER            2
    ALLEN            SALESMAN           3
    WARD             SALESMAN           3
    MARTIN           SALESMAN           3
    TURNER           SALESMAN           3
       JAMES            CLERK           4
  CLARK              MANAGER            2
    MILLER1          SALESMAN           3
    MILLER           CLERK              4

This the the result I need.

MILLER   CLARK
JAMES    BLAKE
SMITH    FORD
ADAMS    SCOTT

I am not sure if it is possible at all with sql. Hope I could expalin it better
this time.

Thanks a lot for your time.
Followup:
scott@ORA817DEV.US.ORACLE.COM> column padded_name format a20
scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM> select   lpad(' ', (level - 1) * 2) || ename as
padded_name, job, level
  2   from emp
  3    connect by prior empno = mgr
  4     start with mgr IS null;

PADDED_NAME          JOB            LEVEL
-------------------- --------- ----------
KING                 PRESIDENT          1
  JONES              MANAGER            2
    SCOTT            ANALYST            3
      ADAMS          CLERK              4
    FORD             ANALYST            3
      SMITH          CLERK              4
  BLAKE              MANAGER            2
    ALLEN            SALESMAN           3
    WARD             SALESMAN           3
    MARTIN           SALESMAN           3
    TURNER           SALESMAN           3
    JAMES            CLERK              3
  CLARK              MANAGER            2
    MILLER           CLERK              3

14 rows selected.

scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM> select ename,
  2            substr( (select min( to_char( level, 'fm0009' ) || ename )
  3                           from emp e2
  4                          where job in ( 'ANALYST', 'MANAGER' )
  5                          start with e2.empno = e1.empno
  6                    connect by prior mgr = empno ), 5 )
  7   from emp e1
  8  where job = 'CLERK'
  9  /

ENAME      SUBSTR((SEL
---------- -----------
SMITH      FORD
ADAMS      SCOTT
JAMES      BLAKE
MILLER     CLARK

scott@ORA817DEV.US.ORACLE.COM>

should get that for you.
Rating: 5
excellent February 01, 2002
Reviewer: arunima from NY, USA
That was awesome. thanks a lot
Rating: 5
Don't know how to do it March 29, 2004
Reviewer: Neelam from CA,USA
I have a table eg:

create table temp(a number,b date);
Values are

select * from temp order by b desc,a;
         A B
---------- ---------
         1 01-FEB-04
         2 02-FEB-04
         1 14-JAN-04
         2 15-JAN-04
         1 01-JAN-04
         2 01-JAN-04

Now I want to create another table temp1(a number,b date, c date);

TEMP1 table should have the values based on TEMP as
         A B         C
---------- --------- ---------
         1 01-FEB-04 14-JAN-04
         2 02-FEB-04 15-JAN-04
         1 14-JAN-04 01-JAN-04
         2 15-JAN-04 01-JAN-04
         1 01-JAN-04
         2 01-JAN-04

Means for Class 1 C should be updated as the previous date for Class 1
14-JAN-04 is the next lower date for Class 1, so C should be updated with this
value

Same for Class 2. Hope I'm able to make it clear. If not please let me know.

I have done this using PL/SQL by opening a cursor.
I want to know whether this is possible in SQL statement?

I'm using Oracle9i Enterprise Edition Release 9.2.0.4.0
Thanks
Followup:
ops$tkyte@ORA9IR2> select a, b, lead(b) over (partition by a order by b desc) c
  2    from t
  3   order by b desc, a;

         A B         C
---------- --------- ---------
         2 02-FEB-04 15-JAN-04
         1 01-FEB-04 14-JAN-04
         2 15-JAN-04 01-JAN-04
         1 14-JAN-04 01-JAN-04
         1 01-JAN-04
         2 01-JAN-04

6 rows selected.
Rating: 5
Excellent March 29, 2004
Reviewer: Neelam from CA, USA
Thanks a lot.

Once again learned something new.

Great!!!!!!
March 29, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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.
Rating: 5
how to force index range scan February 23, 2004
Reviewer: A reader
Hi

i have a slow querye, it uses index fast full scan instead of range scan when
use bind variables...  How can I force it to use range scan? (it goes range scan
using literals)
Followup:
hows about an example.

what happens with "/*+ first_rows */"
Rating: 5
Trying to understand the behaviour March 29, 2004
Reviewer: Neelam from CA , USA
Tom,
I created an index on (col1 || col2) as per requirement from a colleague . Then
I tried to find out whether this index is being used or not and I came to
conclusion that its not using that index.

Query was something like this

Update table tab1
set col_id = (select tab2.col_id from tab2
              where tab1.col1||tab1.col2 = tab2.col1||tab2.col2 );

Note : There is non-unique index on both the tables for (col1||col2)

If I add some range to above query like
AND tab1.col1||tab1.col2 in('x','v')

it uses the index otherwise not.

Can you please explain me why it used the index with AND clause and why not
without it.

Thanks
Followup:
er, tab1.col1||tab1.col2 in ( 'x','v') doesn't "compute"

also -- indexes do not mean "fast=true".  If most of tab1 is going to be
updated, full scan = "fast=true".

hows about the entire example... like I give you...

(but why on earth would you use || in this??????????)

I would just be:

update ( select tab1.col_id col_id_t1, tab2.col_id col_id_t2
           from tab1, tab2
          where tab1.col1 = tab2.col1
            and tab1.col2 = tab2.col2 )
  set col_id_t1 = col_id_t2;

put unique constraint on tab2(col1,col2) and wah-lah.
Rating: 3
Trying to understand March 29, 2004
Reviewer: Neelam from CA,USA
Thanks for your time.
I used it the same way as you described me in your reply, by using
Update (SELECT)

but I want to know and understand the reason why it was not using that index in
join condition. So, that If I suggest this to somebody not to do like this way,
what reason should I give. Before giving a reason, atleast I should be clear
myself.
Followup:
give me the entire example (yours does not "compute").

I can explain "why" when i see "what" but not before.

be very aware that using || is very "not safe"

T1:

C1      C2
----    ------
A       AA
....

T2:

C1      C2
----    ------
AA      A

Maybe that is all you need --  to tell them that the risk of a wrong answer is
far too great with || :)
Rating: 3
Trying to understand March 29, 2004
Reviewer: Neelam from CA,USA
Here is the exact SQL

ALTER TABLE SP_JCT_STKH_ADDR
ADD (DC_ACCT_ID NUMBER(15));

UPDATE SP_JCT_STKH_ADDR A
SET    DC_ACCT_ID = (SELECT B.DC_ACCT_ID
             FROM SP_STKH B
             WHERE A.STKH_NBR     = B.STKH_NBR AND
                 A.STKH_ACCT_NBR = B.STKH_ACCT_NBR);

This is the SQL which I'm using now. Now it has a unique index on STKH_NBR
,STKH_ACCT_NBR on both the tables.

 But earlier one developer wrote the SQL like this :

UPDATE SP_JCT_STKH_ADDR A
SET    DC_ACCT_ID = (SELECT B.DC_ACCT_ID
             FROM SP_STKH B
WHERE A.STKH_NBR||A.STKH_ACCT_NBR     = B.STKH_NBR||B.STKH_ACCT_NBR);

And an index was created on (STKH_NBR||STKH_ACCT_NBR) for both the tables.

When I did autotrace, I saw that its not using the Index.
You have given a very good example where this combination will fail. I want to
know because of this reason its not using the Index or beacuse its updating
almost all the rows of SP_JCT_STKH_ADDR tables thats why optimizer finds the
FULL TABLE ACCESS is faster than INDEX ACCESS.
Whats the reason Index is not being hit.

Thanks
Followup:
I cannot say "why" -- more then likely you were using the RBO and RBO cannot see
function based indexes.

I can say "thank goodness", since it made you look at this and code it
correctly!  using the || 'trick' is a bad bad practice -- tell them to just stop
doing that period!

But this shows "it could" use an index if it wanted to.

ops$tkyte@ORA9IR2> create table t1 ( id int, c1 int, c2 int );

Table created.

ops$tkyte@ORA9IR2> create table t2 ( id int, c1 int, c2 int );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create index t2_idx on t2(c1||c2);

Index created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> alter session set optimizer_goal=first_rows;

Session altered.

ops$tkyte@ORA9IR2> delete from plan_table;

5 rows deleted.

ops$tkyte@ORA9IR2> explain plan for
  2  update t1
  3     set id = ( select id from t2 where t1.c1||t1.c2 = t2.c1||t2.c2 )
  4  /

Explained.

ops$tkyte@ORA9IR2> select * from table(dbms_xplan.display() )
  2  /

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
----------------------------------------

----------------------------------------------------------------------------
| Id  | Operation                    |  Name       | Rows  | Bytes | Cost  |
----------------------------------------------------------------------------
|   0 | UPDATE STATEMENT             |             |    82 |  3198 |     2 |
|   1 |  UPDATE                      | T1          |       |       |       |
|   2 |   TABLE ACCESS FULL          | T1          |    82 |  3198 |     2 |
|   3 |   TABLE ACCESS BY INDEX ROWID| T2          |     1 |    39 |     1 |
|*  4 |    INDEX RANGE SCAN          | T2_IDX      |     1 |       |     1 |
----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   4 - access(TO_CHAR("T2"."C1")||TO_CHAR("T2"."C2")=TO_CHAR(:B1)||TO_CHA
              R(:B2))

Note: cpu costing is off

18 rows selected.
Rating: 5
Thanks March 29, 2004
Reviewer: Neelam Sharma from CA, USA
Thanks very much.
Optimizer is CBO.
Both the tables were analyzed but still it was not using the Index but as per
you example I issued the statement

alter session set optimizer_goal=first_rows;

It worked. Now I would try myself to find out why this happened and if no luck
then I'll get back to you for your precious advice.
Followup:
worked is relative here.

You are updating EVERY row in the outer table.

if that table is "sizable" -- full scans = BEST THING EVER, indexes = NOT SUCH A
BRILLIANT THING

please don't fall into the "indexes = good" trap.
Rating: 5
Thanks for your help March 30, 2004
Reviewer: A reader
Rating: 5
Thanks March 30, 2004
Reviewer: Neelam Sharma from CA, USA
March 29, 2004 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)..
Rating: 4
'Cursor Sharing = force' not working March 29, 2004
Reviewer: Pramit from TX, USA
Hi Tom,
  Using Cursor sharing as a 'crutch' as you call it :) we are trying to make an
app run while getting the necessary code changes done..We have an after logon
trigger which enables the cursor sharing = force for a particular user. Now
observation is that while connecting as that user from sqlplus orcle uses cursor
sharing and replaces literals with system binds...But this doesn't happen while
running the application...Oracle doesn't use cursor sharing.
One possible reason I can think of is the code itself ....(native dynamic sql or
something that forces oracle to use an alternate execution plan)...
Could You suggest anything else I am missing out on......
We have the following parameters in the trigger`
cursor_sharing = force
optimizer = first_rows

and the version is 8.1.7
Followup:
give me a example of a query the client submits, the language the client is
written in, and the manner of submitting the query.... (eg: a small test case :)
Rating: 3
Sample Data March 29, 2004
Reviewer: Pramit from Somewhere where oracle 8i to 9i migration is not an option
Hi,
Thanks for an encouraging response. Let me attempt to explain the situation I am
currently facing without revealing *properietry* information :)

I have a java program ( uses oracle9i) thin driver. It executes a batch of
insert statements
con = mDataSource.getConnection();
stmt = con.createStatement();

for loop on the following code
stmt.addBatch("insert into TABLE_NAME values (1,1,1,1));

con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
                    con.setAutoCommit(false);

                    stmt.executeBatch();
conn.commit();

I am currently on oracle 8.1.7

Hope this helps.
Please let me know if you need some more information to analyze this issue.

Thanks and Regards,
Pramit
Followup:
who knows what your connection pool might be doing here -- if you write a small
test program that uses the same connection semantics, and you issue the alter
session -- does it work (if so, indicates your connection pool is playing games
on you)

should have guessed the language was going to be "java".  no binds -> one of two
languages typically.
Rating: 5
re: one of two... March 30, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
re: one of two languages typically

I think I am working on the second one...

- Mark
March 30, 2004 Dale -- Thanks for the question regarding "Replacing procedural code with SQL", version 9.2.0.4
You Asked
I have the following stored procedure, I wrote it like this because I'm a
programmer, and it's what I'm used to, I couldn't think of a way to do it in a
single SQL statement as you recommend. Can this be done in a better way?

The aim of this procedure is to find the first event with event_id = 50 and the
last event with event_id = 51 between startUTC and endUTC for each asset_id in
temp_asset.

PROCEDURE prc_get_ignition_on_off
(
   startUTC   IN DATE,
   endUTC     IN DATE,
   ignitions OUT SYS_REFCURSOR
)
IS
l_data typ_asset_ignition_table := typ_asset_ignition_table();
firstIgnOn     DATE;
lastIgnOff     DATE;
ignOnMiles     NUMBER;
ignOffMiles    NUMBER;
ignOnUserData  RAW(256);
ignOffUserData RAW(256);
BEGIN

   FOR assets IN ( SELECT asset_id FROM temp_asset )
   LOOP

      firstIgnOn     := NULL;
      lastIgnOff     := NULL;
      ignOnMiles     := NULL;
      ignOffMiles    := NULL;
      ignOnUserData  := NULL;
      ignOffUserData := NULL;

      BEGIN
          SELECT
             event_gmt,
             mileage,
             user_data_bin
          INTO
             firstIgnOn,
             ignOnMiles,
             ignOnUserData
          FROM
          (
             SELECT
                /*+ ordered INDEX(oem AI_OEM_TRKR_DT_PORT) */
                event_gmt,
                mileage,
                user_data_bin
             FROM
                tracker_installation ti,
                outgoing_event_msg oem
             WHERE ti.asset_id = assets.asset_id
               AND ti.tracker_id = oem.tracker_id
               AND oem.event_id = 50 /* ignition on */
               AND oem.event_gmt BETWEEN startUTC AND endUTC
               AND ( NVL( ti.removal_gmt, SYSDATE ) >= startUTC AND
ti.installation_gmt <= endUTC )
               AND ( oem.event_gmt BETWEEN ti.installation_gmt AND NVL(
ti.removal_gmt, SYSDATE ) )
             ORDER BY
                oem.event_gmt
          )
          WHERE ROWNUM = 1;
      EXCEPTION
         WHEN NO_DATA_FOUND THEN
            NULL;
      END;

      BEGIN
          SELECT
             event_gmt,
             mileage,
             user_data_bin
          INTO
             lastIgnOff,
             ignOffMiles,
             ignOffUserData
          FROM
          (
             SELECT
                /*+ ordered INDEX(oem AI_OEM_TRKR_DT_PORT) */
                event_gmt,
                mileage,
                user_data_bin
             FROM
                tracker_installation ti,
                outgoing_event_msg oem
             WHERE ti.asset_id = assets.asset_id
               AND ti.tracker_id = oem.tracker_id
               AND oem.event_id = 51 /* ignition off */
               AND oem.event_gmt BETWEEN startUTC AND endUTC
               AND ( NVL( ti.removal_gmt, SYSDATE ) >= startUTC AND
ti.installation_gmt <= endUTC )
               AND ( oem.event_gmt BETWEEN ti.installation_gmt AND NVL(
ti.removal_gmt, SYSDATE ) )
             ORDER BY
                oem.event_gmt DESC
          )
          WHERE ROWNUM = 1;
      EXCEPTION
         WHEN NO_DATA_FOUND THEN
            NULL;
      END;

      l_data.EXTEND;
      l_data(l_data.COUNT) := typ_asset_ignition( assets.asset_id, firstIgnOn,
ignOnMiles, ignOnUserData, lastIgnOff, ignOffMiles, ignOffUserData );
   END LOOP;

   OPEN ignitions FOR
      SELECT * FROM TABLE ( CAST(l_data AS typ_asset_ignition_table) );

END;

Thanks.
and we said...
Ok, I did not read the code -- I took this:

The aim of this procedure is to find the first event with event_id = 50 and the
last event with event_id = 51 between startUTC and endUTC for each asset_id in
temp_asset.

select *
  from (
select event_id, asset_id,
       event_gmt,
       min(decode(event_id,50,event_gmt)) over
                (partition by asset_id) min_event_gmt,
       max(decode(event_id,51,event_gmt)) over
                (partition by asset_id) max_event_gmt
  from t
 where event_id in ( 50, 51 )
   and event_gmt between l_startUTC and l_endUTC
   and asset_id in ( select * from temp_asset )
       )
 where (event_id = 50 and event_gmt = min_event_gmt)
    or (event_id = 51 and event_gmt = max_event_gmt)

we use analytics to find the min/max time BY asset_id.  we use decode to get the
right min/max.

We keep only the records that match your stated "goal"

T would be your multi-table join.......
Review & Followup

Rating: 5
Works well March 29, 2004
Reviewer: Dale Ogilvie from New Zealand
I'm a little disappointed you didn't read my code (it may not have been pretty
but it was my baby ;)

I have to expand my requirements a little:

The aim of this procedure is to find the first event with event_id = 50 and the
last event with event_id = 51 between startUTC and endUTC for each asset_id in
temp_asset *and* to return the mileage and times for these two events in a
single row per asset_id.

Taking your solution and adding a bit of pivot query stuff from elsewhere in
your site I came up with this:

select asset_id,
  max( decode( event_id, 50, min_event_gmt, null ) ) ignition_on,
  max( decode( event_id, 50, mileage, null ) ) ignition_on_mileage,
  max( decode( event_id, 51, max_event_gmt, null ) ) ignition_off,
  max( decode( event_id, 51, mileage, null ) ) ignition_off_mileage
from (
select
  *
from (
   select oem.event_id, oem.asset_id, oem.event_gmt, oem.mileage,
       min(decode(event_id,50,event_gmt)) over
                (partition by oem.asset_id) min_event_gmt,
       max(decode(event_id,51,event_gmt)) over
                (partition by oem.asset_id) max_event_gmt
   from
     temp_asset ta,
     outgoing_event_msg oem
   where event_id in ( 50, 51 )
     and event_gmt between :startUTC and :endUTC
     and oem.asset_id = ta.asset_id
       )
where (event_id = 50 and event_gmt = min_event_gmt)
or (event_id = 51 and event_gmt = max_event_gmt)
) group by asset_id;

I replaced the inline query of the temp_asset table with a straight join between
temp_asset and oem. I presume this is OK.

thanks!
Followup:
yes -- all we need to do was add whatever additional columns you wanted in the
inline view....

looks good to me....

I was just proving out a point I make in some of my talks.  When asked to
"tune", I always ask for the question -- not the solution.  Many times the
solution gets in the way of the best answer.

Your problem statement:

The aim of this procedure is to find the first event with event_id = 50 and the
last event with event_id = 51 between startUTC and endUTC for each asset_id in
temp_asset.

was awesomely perfect in its brevity, clarity and completness.  It was all
anyone needed to come up with the best answer.

The code would just "get in the way" -- without that problem statement, i would
have been scratching my head for a long time trying to figure out "so, what was
the question"!!

(this is why I totally ignore people who whine "but I don't understand that SQL
query, I'll write 1,000 lines of code instead".

a) if you see enough of the SQL, it'll be as familar as your code...
b) if you didn't comment the code, that 1,000 lines will look 'not so
understandable' 30 days from now and will be totally meaningless to anyone else
from day 1
c) if you code:

...
is
begin
    /*
     * The aim of this procedure is to find the first event with
     * event_id = 50 and the last event with event_id = 51 between
     * startUTC and endUTC for each asset_id in temp_asset.
     * we will use analytics to find the min/max time BY asset_id.
     * we use decode to get the right min/max.
     * the max(decode(event_id...) does that for us
     * We keep only the records that match your stated "goal", that
     * is accomplished in the outermost where clause
     */

open ignitions for
select *
  from (
select event_id, asset_id,
       event_gmt,
       min(decode(event_id,50,event_gmt)) over
                (partition by asset_id) min_event_gmt,
       max(decode(event_id,51,event_gmt)) over
                (partition by asset_id) max_event_gmt
  from t
 where event_id in ( 50, 51 )
   and event_gmt between l_startUTC and l_endUTC
   and asset_id in ( select * from temp_asset )
       )
 where (event_id = 50 and event_gmt = min_event_gmt)
    or (event_id = 51 and event_gmt = max_event_gmt);

that is understandable -- the comments make it so.  code is code and just
gibberish 30 days from now unless you describe textually what is going on...
March 30, 2004 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
Rating: 4
just to know how a column can be auto increase using sequence number February 10, 2004
Reviewer: vipin from India
Really a great solution
Rating: 4
error March 30, 2004
Reviewer: Ak from AK
Hi,
  I have couple of problem while using sql*loader for converting data from
access DB to oracle db.This is what i did..
1)Taken export of access table in .txt format. which has one field of memo data
type.
2)In oracle used long datatype(and tried clob as well) for equivalent column.
This two datatype doesn't seems to be working ...here is the error..

  Column Name                  Position   Len  Term Encl Datatype
------------------------------ ---------- ----- ---- ---- ---------------------
OVERFLOWID                          FIRST     *   ,  O(") CHARACTER
OVERFLOWTEXT                         NEXT     *   ,  O(") CHARACTER

Record 1: Rejected - Error on table RQOVERFLOWTEXT, column OVERFLOWTEXT.
Field in data file exceeds maximum length
Record 2: Rejected - Error on table RQOVERFLOWTEXT, column OVERFLOWTEXT.
Field in data file exceeds maximum length

3)Another problem for other table I have is ..when I export access db in txt
format ...for one table(second last field) it goes  into the next line..because
of which sqlloader fails.for an example my txt file will look like
1,2,3,"this is
testing",4

Is there any way in oracle with which i can tell sql loader to read next line.I
cann't change it manually becuase this file is very big.

Thanks

Cheers
  Ak
Followup:
in the control file, specify the maximum length -- else it is char(255)

c1,
c2,
c3  char(100000),
c4,
c5,
.....

in order to deal with that field that goes over lines, you would have to use a
record terminator.  If you can get the file to be generated like this:

1,2,3,"this is
testing",4^

(eg: end of line is ^\r\n  -- not just \r\n)  we can work with it.

If not, you might consider just setting up the hetergenous gateway so you can
load from access using SQL:

   insert into oracle_table select * from access_table;
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76960/hs_genco.htm#173
March 30, 2004 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')
Rating: 1
sour grapes January 19, 2004
Reviewer: CW from London
Rather than dis the competition why can't you concentrate on making a DB that
does stuff right 'out of the box'.

It's 1970s technology - ADA, jeezuz ! , it requires a degree in "Oracle" to make
it perform well, it co$t$ and is still designed so that you inevitably need
Oracle con$ultan$ to get it to do what you want.

If it were a car it would be a Formula one racer from the 1970s. You need a
mechanic to constantly tune the carbs and wipe it with an oily rag to get it to
work right.

SQL Server ain't perfect, but to imply Oracle is vastly superior is, at best,
misleading and is sour grapes. It's a shame Oracle employees devote too much
time to MSft bashing and not enough time fixing their DB.
Followup:
huh????

where are you coming from?

<quote>
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.
</quote>

where is that a "dis" -- thats a challenge.

dude, you are just so so sooooo far off base, you don't even know.  you don't
even know that using binds would make for better sqlserver applications.  sigh.

lead horse to water
can you make them drink.
sometimes yes.
sometimes no.

you like what you know.
you know what you like.

wish i had a time machine so i could transport you back to your early days with
sqlserver -- so you could remember how confusing that particular product was the
first time you saw it as well.

sigh.

sqlserver is written in C.  C is an old language.  does that make it archiac?
Have you even looked at transact sql?  wanna talk archiac?

I know lots of people who do Oracle just dandy without advance degrees or
consulting.  Perhaps they

like what they know.
know what they like.

and they would find sqlserver excessively "hard" as well.
Rating: 5
January 19, 2004
Reviewer: A reader
This is for CW from London.

SQL Server guys talk big really big but when you try to confront them with
Oracle guys or guys who have worked on different databases they dont even wana
talk/reply.

Recently I was going through "Inside SQL SERVER 2000" by Kalen Delancy on page
106 she emphasizes (Windows/SQL SERVER) performance and integration of sql
server with THE OS, i pointed her to Toms link about ubiquity (for her opinion)
which i agreee with Tom as to what will happen to MS guys if the OS changes, its
been more than a week she hasn't replied.

CW Don't get people started on that SQL SERVER CRAP!!!

Thanks
Rahul.
Rating: 4
so, is it a Yes or No? January 19, 2004
Reviewer: Gabriel from Toronto
<quote>sqlserver is written in C.  C is an old language.  does that make it
archiac?</quote>

I truly admire your ability to be un-ambiguous with your answers in 99% of the
cases ... but with this one ...

“C is an old language” … Ok I can take this since it is a statement of fact.
But … archaic because of being written in C?

If ‘No’ ==> guess wasn’t much point to it ... or my English fails me again!

If 'Yes' ==>
Isn’t the Oracle server written in C (and some C++ sure)?  … (and I’m not asking
about the Java GUI stuff for OEM, etc.)?
What happened to the “Java is just a language” … presumably the same goes for C,
C++, PL/SQL, etc.)?

In my view some applications are archaic before they even go production …
Transact SQL would’ve been archaic by the time they finished defining the
lexical analyzer and the parser … what does C have to do with anything (at least
in the context of multi-platform server development)?

For the record … I’ve never done anything worth mentioning in SQL Server and I
cannot look for more than 2 seconds at Transact SQL.

Thx
Followup:
I was making a reference to their dig on Ada (which by the way was/is a really
cool language.  had tons of really nice features)

I was making the point that the age of the language, in reference to what I
thought was a really silly comment:

It's 1970s technology - ADA, jeezuz !

has nothing to do with nothing.  Compare something on it's merits, not age.  Age
has nothing to do with anything here.

I'm from the 70's -- geez, actually i predate the 70's -- geez, does that make
me "old technology".  I sure hope not.

Let them compare PL/SQL (which is Ada like - pascal like - modula like - pl/1
like) to T-SQL -- the barest of scripting languages ever (well, since the DOS
batch/cmd scripting language that is)...

The age of something has nothing to do with anything.
Rating: 5
knew it was a slip ... couldn't resist though January 20, 2004
Reviewer: Gabriel from Toronto
<quote>I'm from the 70's -- geez, actually i predate the 70's -- geez, does that
make me "old technology".  I sure hope not.</quote>

Actually, it does … but you’ve proven in many occasions well in tune with the
"current technologies" … so, here you have it, I see the exposure to "older"
stuff as a huge advantage really.

Ada … wish I had the chance to use it (the US/UK military contractors were not
really hiring programmers on my side of the world at the time) … I read on it
after starting with PL/SQL and saw immediately where the PL/SQL syntax came from
… well, that was a good choice indeed … and fortunate I would say … Oracle
without PL/SQL (or with something poor like T-SQL) would’ve been just another
RDBMS.

As for the "sour grapes" chap from London, I would be curious to know what
exactly, he thinks, needs "fixing" with the Oracle DB to bring it on par
(!?!?!?!?) with SQL Server. The guy came swinging out of nowhere … Problem with
a patch or something? … Hired the wrong consultant? … Oracle doesn’t do the
programming for him?

Case in point: Nirmal back in May 2003 went away disappointed that Tom didn’t, I
guess, "appreciate" his PL/SQL code … well, there were so many things wrong with
it that there wasn’t much short of _hurting_ his pride:
a. the procedure listed would not even compile
b. buggy error handling (since it potentially hides the errors)
c. no asserting what-so-ever on the input parameters (those arrays better be the
same size)
d. one dynamic sql trying to bind the name of a table … and the bind variables
positionaly incorrect
e. the other dynamic sql unnecessarily dynamic
Ok … maybe he’s new to PL/SQL and Oracle and this is just some hastily put
together code … but …
f. the logic? … load arrays in Java, pass them to Oracle and … copy them to
local arrays in batches of 50 and bulk load them … why break the data into
smaller chunks? … how would this make it faster?
Was he the wrong person for … <quote>I am in the process of migrating from
SQLServer to Oracle and the condition is if Oracle runs faster than
SQLServer</quote>? Surely, with him, that Java application is still on SQL
Server (I cannot believe I used "SQL Server" and "Java" in the same sentence!)
and another believer that Oracle doesn’t do <quote>stuff right 'out of the
box'</quote> was born.
Rating: 2
You've missed the point January 21, 2004
Reviewer: CW from London
...and so the flame war begins.......

Let's calm down, see the world from both sides and try to understand why people
might get frustrated with Oracle. Isn't that reasonable ?

and I never said I was a SQL server guy. I'm an app developer so databases are a
means to an end, but I'm coming from Sybase and so, yes, Oracle is new.

So:
1. Why do Oracle require me to register to get any tech info from their website
?

...I'll take a shot in the dark here...perhaps it's so that their mktg dept can
get hold of my personal details ;-) nice.

2. If Oracle > SQL Server( or Sybase) AND PL/SQL > TSQL then...WHERE is the
Oracle for SQL Server programmers documentation? (and not the cr*p that's
published in the Oracle migration workbench docs - that's a joke)

3. Temp tables, where d'ya get that idea from ?

4. Stored Procs that return a resultset. In TSQL this is easy, in Oracle this
seems to require a cursor and a Package - which is like a struct ? Anyway, I'm
having to do alot more work to do something which is trivial in Sybase - now, it
may be that there is some logical explanation for this approach in Oracle, but I
haven't found one (in the still useless migration documentation).

5. Clustered index -> index organised ? If I create a PK on Sybase it defaults
to clustered & stores the data sorted by the PK. In Oracle I don't think it does
this, you have to specify index organised ? Either way is fine, but *personally*
I think it's more logical the Sybase way i.e the default behaviour is index
organised. Again - a decent document would have saved me alot of pain here and,
as yet, I still can't get the index organised syntax right !

6. Program too long error. Yoinks, more than 64k or is it 32k  is that DIANA
nodes or memory ? I think I last had 32k in my BBC micro and 64k in a Commodore
64 - that would have been about 1985. The phrase "sooo far off base" springs to
mind ;-) Have I lost the plot here ? Maybe my Sybase-esqe use of Oracle is "sooo
far off base" that I've made a huge coding mistake.....or maybe this limit is
from 1985 :P

7. It's all about the developers - I'm not a horse. Your job isn't to punch me
(& others) in the face until we agree that Oracle is best. Horses are really
quite dumb animals, programmers.....well ;-). Databases are generally back-ends
to somebody's front end. The worlds most popular front end is....Msft
Windows...like it or not. So, if you want to beat Msft you must play by their
rules and Msft invest time & money in application developers. They put effort
into trying to make programmers lives easier.

Programmers are generally *NOT* database experts BUT programmers do write DB
code SO progammers writing bad DB code will get poor performance SO they blame
the back end ;-)

Show me the water, explain to me why it's good to drink and then show me where
the tap is, I don't wanna drink from a hole in the ground ;-) Always, always,
always aim to make life easier for the app developer. Geek DBAs may get off on
all sorts of quirky syntax. An app developer will get bored,give up and do
something hideous to the database.

8. Binds - I think I've got the message on that one. Binds are good....maybe, or
are they? Yeah, they are :P

9. Back in the "Real World" I've very rarely (actually I don't think ever) seen
Msft people slate the competition. They will quote benchmarks, or (Msft funded
;-) research but they don't make unqualifed remarks about other peoples stuff.
You shouldn't either, if you really are better than them - it's sour grapes,
it's unprofessional. You should justify and qualify why Oracle is gonna scale
when SQL Server won't by FACTS (add links to Oracle funded research if you like
;-)

10 Commit OR commit suicide. Gotcha sucker ! Oracle doesn't auto commit (there
is some server param I think) anyway , I won't say how much time was spent
looking for my data, but on reflection it was a comedy moment. Again some
DOCUMENTATION would have helped.

So Tom & others,
Apologies for my rant, but I think if you could see the Oracle world through my
eyes you'd be annoyed ? frustrated ? p*s*ed off ? Fundamentally I know it's a
good product, but it is hard to use it *the right way* when you have little
experience. Make it easier for people migrating from other platforms, invest in
documentation / web pages. Produce a "100 most common SQLServer migration
mistakes" type of thing AND try to make the product easier & more intuitive to
use.

If anyone has links to good docs / web sites then please let me know.
Followup:
1) shots in the dark, hmm, susposition, paranoia on your part is what I supose.
Perhaps they just want to know who you are and keep track of what people use --
how they use it -- tie it all together.  You know, like *lots* of sites do (i've
accounts on MS sites -- can you spell "passport"?  I've accounts on IBM sites --
to get at their documentation.  I've accounts on Oracle sites as well -- and you
know what -- they don't actually spam you)

so bzzzt.  wrong answer.

2)

If Oracle > SQL Server( or Sybase) AND PL/SQL > TSQL then...WHERE is the
Oracle for SQL Server programmers documentation? (and not the cr*p that's
published in the Oracle migration workbench docs - that's a joke)

I don't know, why would you expect such a thing?  there should be a "Language X
for programmers of Language Y" book for every language?

tell me, whats the title of the "transact sql for plsql programmers" book?

Have you read the PL/SQL documentation?

Have you realized that perhaps there is not much mapping between the scripting
language that is T-SQL and the language that is PL/SQL?

that perhaps you need to learn to program in PL/SQL -- not how to emulate
sqlserver in Oracle (that -- that is where things fail.  But to be fair, the
opposite is true as well -- if you try to get SS to run an Oracle program --
well, good luck, they cannot -- way way too many differences in the concurrency
and locking model.  What works in Oracle failes in SQLServer and vice versa)

Have you bought any of the hundreds of PL/SQL books out there?  (there are ALOT)

3) good question -- no wait, bad question.

Tell me -- where'dya get the idea that temp tables are a "good idea" in the
first place?  (sqlserver makes you use them at the drop of a hat.  In SS it is
"a good idea".  In Oracle they are "not necessary, not used".  We got along fine
without them for many years before SS was even a database).

I've written lots of Oracle code.
I've used a temp table once (as an interface table).

I've taken dozens of multi-page SS stored procedures (wholly unreadable mess of
procedural logic) and turned it into a single SQL statement which it should have
been from the get go.

Here is an Oracle temp table done right:

select * from
  ( YOUR_QUERY you would have put into temp table 1) a,
  ( your_query you would have put into temp table 2) b,
   .....
 where

temp tables are a crutch needed by sqlserver -- temp tables are a tool you can
use in Oracle if and when needed -- you just define them as part of the
application itself.

Guess what -- this is in fact "Oracle", not "Sqlserver".  If we did things just
as they did -- what would be the point?  And why pray tell should the rest of
the world "do it just like MS"?  You do understand that MS is the latest entry
to the rdbms market right?

4) Ok,

create or replace function foo return sys_refcursor
as
   l_cursor is sys_refcursor;
begin
   open l_cursor for select * from emp;
   return l_cursor;
end;

hmmm, you're right, its really hard???  or not?  I don't know, as a coder, I
sort of like having formal named parameters for outputs or a clearly defined
return value from a function.

Tell me, in sqlserver:

   exec foo

does that return

a) 0 result sets
b) 1 result set
c) N result sets

and if b or c is true (but you cannot tell without reading the code), what do
those result sets mean?  what order are they in?

Now, Oracle

create procedure foo( p_input in empno, p_emp_result_set out sys_refcursor )

Well, I can sort of see the inputs and outputs and it is a bit "self
documenting"

In the client -- what extra code do you write? (i write no extra code, if you
are -- it is self inflicted).

In the server what extra code?  well, a declare -- that's it.

(in 8i, and before you must define once in your database a package like this:

create package types as type rc is ref cursor; end;

and then use the datatype types.rc instead of sys_refcursor)

again, I'll say this -- this is Oracle, it is different than SS.  SS is
different than Oracle.  I've never understood this ref cursor/result set thingy
here cause I've never had anyone show me line by line "what is so hard" about
this -- never (there a challenge -- show me the SS code and show me the Oracle
code and show me why the Oracle code is hard/obscure/ungainly/large)

5) Sybase does not default to a clustered index.

But anyway "why does a feeling" mean anything.  do you have the numbers, the
benchmarks, any facts that the overhead of their clustered index (which actually
makes inserting slower in their database BUT spreads the inserts out so their
extremely poor locking and concurrency model appears to sort of work in some
cases......) is a "good thing"

no - it does not exist (in the positive sense).  SS uses clustered indexes in an
attempt to spread inserts out all over a table because of extreme contention
they would otherwise experience.

So.... do you have bitmap indexes? hash clusters?  IOTs (without the 110%
overhead of emulating that in SS????)? b*tree clusters?  partitioning?  parallel
operations? (list goes on and on and on and on and the answers are "no")

6) you provide no example, but the only time I see this is when you have code
that goes on for pages and pages and pages with lots of if then else if yadda
yadda yadda (calls out for subroutines).  It is the parse tree -- not the size
in bytes of the source code.

It all does really (truly) come back to "good code".  you are using procedures
-- not packages (why -- because, well, tsql does not have this functionality,
its not a programming language, its a scripting language but I digress).

You have a single, monolithic, huge, page after page of code procedure.

It follows none of the "best practices" developers have been taught since the
dawn of computers (modularize your code, use the language to help you develop
modular, maintainable code)

tell you what -- how about you post the code that didn't compile, and let people
comment on whether it falls into "a good idea" or "really bad practice,
regardless of the language"?  I post code all of the time, no one will mind more
code....

7)  It is not all about the developers, you have taken a far too ethnocentric
view of YOUR world

I've said it before and I'll say it again.  its all about the DATA.  Your
applications, they will be born, live and die many times before the data goes
away.  the data we work with today was in the most part first developed in the
60's.  The data far outlives the applications.

So, if your applications screw up the data -- we've got a problem.
If you don't understand concurrency control, we've got a problem.

Tell me -- why do developers thing "tis OK to get magazines on my favorite
language, get training on it, go to developer days to learn neat gui things,
learn interesting algorithms" but -- even though their job is to develop DATA
BASED applications -- they ignore the very tool they are using????  I'll bet
you've had training in programming -- so, tell me database programmer -- how
many hours of training in relational databases have you logged?

Don't get me wrong, I'm a developer -- I started as a developer.  PL/1 (archiac
language from mainframes, mainframes coming from the 1960's means they must not
be used anymore).   Using SQL/DS, DB2, then ingress, gupta, dbase even.  Then
informix, sybase, oracle, sqlserver.  One thing I learned was a successful DATA
BASED application depends on someone (gasp) understanding a database (i'm
embarrassed by some of the bone headed mistakes and massive misunderstandings I
had when first learning this stuff).  I also learned that -- hey, these products
are different.

Windows is an operating system
Unix is an operating system
VM/CMS is an operating system

guess what -- they are different.  (they are all "posix" though, hmm, but if I
try to code on unix what I code on windows or vice versa -- guess what
happens??)

I can recommend a book -- you probably are not interested at this point, but
I'll offer it up.  It is called "expert one on one Oracle" -- accessible from my
home page.  I try to show you the water, and how and why to drink it.  (i even
cover many of the above topics believe it or not, even the clustered index
thingy)  anyway....

8) yes they are -- even in SS believe it or not.

9) you are kidding right?  you've never had them do an anti-oracle presentation?
 man, you are missing a great thing (i've got the slides, it is funny -- i love
to be in the room at the same time with them, just to help clear up some points
of their confusion).  give me a break.

and -- who is "slateing" them here.  I'm saying "HEY- we are not sqlserver, they
are not oracle -- we are different.  do not expect to take code that works in SS
and run it on oracle unchanged (and vice versa).  Not more then you would expect
to take windows code and run it on unix and vice versa.  it just don't fly"

Wait -- i don't have to diss SS, they do it themselves:
http://www.sql-server-performance.com/lock_contention_tamed_article.asp
<quote>

Granted, if you've only got a few occasional users, you won't have much trouble
with SQL Server's out-of-the-box behavior. You'll be hard pressed to see these
problems with simple in-the-office tests or deployments involving just a few
users. But throw a couple hundred concurrent users at your database and a
constant stream of INSERTS and UPDATES with quite a few DELETEs sprinkled in,
and you'll start reading Oracle literature and eyeing your war chest. However,
I've got a solution for you that will only cost you a code review, a few minor
tweaks, and a system test. You do have a system test procedure in place, right?
</quote>

and i love the "solutions" proposed in the following paragraphs (makes
interesting reading).

solution 1 includes this caveat:

...
For financial code and denormalized aggregates (those little counters of related
data that you stash away and try desperately to keep accurate), you should
play it safe and not use this technique.
.....

solution 2 includes this:

....
However, be aware that if you are wrong and lots of rows are affected, either
the database will take the initiative and escalate to page locks, or you'll have
a whole army of row locks filling your server's memory and bogging down
processing
.......

and closes up with this:

<quote>
Disclaimer
Use these techniques with caution and discretion. The way I approached it was to
look at all my stored procedures and ad hoc queries, and based on my
understanding of where and how they were used, I decided whether it would be
acceptable for the caller or user to get possibly incorrect results for
NOLOCK, and whether it was likely that more than a few dozen rows would be
locked with ROWLOCK. In almost all cases it was fine, but maybe for your code
you should be more careful. You might need to produce separate procedures
based on whether or to lock, and how to lock. There are other incantations
(PAGLOCK, TABLOCK) which you might want to use when you know the UPDATE or
DELETE query will affect many rows.
</quote>

so tell me how a PROGRAMMER who doesn't understand the database is going to
figure out this stuff??????  Remember -- this is from the "easy to use guys"
right -- but wait, is it really or have you been lulled into a sense of "this is
easy".  Under the covers I'll betcha you there are nasty old bugs you are not
even aware of in your data (i see it time after time after time -- not just in
SS but in Oracle and every database - -why? cause the programming professionals
did not bother to understand or learn the tool they are using)

The facts are in reality (i love it when people invoke "real world", as if we
live in a fantasy planet somewhere where all is good and lovely.  hmmmm).

Anyway, if you want to read it about it -- i wrote about it.  people write about
it.  Heck, even the sqlserver pros write about it -- how to get around their
concurrency model.

Here -- one more challenge.  SHOW ME WHERE on this page I've "dissed"
sqlserver?????

<quote>
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.
</quote>

does that "diss them" or does it point out "real world reality" that

a) code it for sqlserver and run it on oracle isn't going to work (not anymore
then code it for oracle and run it on sqlserver)

b) see which one scales -- please, benchmark it, test it -- the proof is in the
pudding.  SEE which one flies.

10) NOW, now I'm LOL, actually ROTFL

I'll play dictionary here:

auto commit:  mode used by databases that have severe contention and locking
issues.  makes it impossible to perform transactional processing, leading to
data integrity issues and corrupt data.  DBA's have a love hate fear of this
mode.  They love it because it doesn't lock up their database.  They fear it
because they know it will lead to data integrity issues.  It is not a feature,
it is a failing.

Documentation?  Have you

a) taken ANY training on relational databases (the tool you are using)
b) read the concepts guide (which does actually cover this stuff)
c) asked anyone with experience for help

The migration from SS to Oracle doc would be very short, here it is:

"forget everything you've 'learned'.  These are different beasts, with different
design spec's and different goals in mind.  You need to read the concepts guide,
the application developers guide and learn the tool you have at your disposal.
It is not something you will learn and master in a day, or even a week.  But the
same is true of SS -- just as you probably learned something new each day with
that tool -- you will with Oracle."

So, did SS make that investment for the other way?  do you have the 100 most
common PLSQL migration mistakes?  I'd love to see that myself
Rating: 5
January 21, 2004
Reviewer: A reader
Tom,

I think the major problem with MS users is they are so used to click click, the
moment they have to DIY then they start complaining about the product and
eventually loose interest.

Thanks.
Rating: 5
oh dear January 21, 2004
Reviewer: CW from leaving the Oracle world
Tom,

I refer you to the title of my previous post. Please don't reply, I'm concerned
that you might have a coronary.

It's before my time,but, during the Vietnam war I believe they said it was a
battle of hearts & minds......have a think about that.

bye.
Followup:
this is software, this is software design, this is stuff based on scientifically
proven fact.

this is not dogma.  this is not a war.

sorry -- i thought I asked good questions too.  no redress, oh well.

re-look at your first post and see who is coming in with dogma, sourgrapes,
et.al.  feel free to follow up some of my questions if you like.  Like -- other
then the fact you had to *learn* about ref cursors -- much like you *had to
learn* about how sqlserver did it at sometime -- Like how ref cursors are this
onerous, terrible, hard to code, many times cumbersome feature?  i'd really
really be interested -- really.

or maybe why "autocommit" is "a good thing" (it is the worst thing one can
imagine really -- read a bit about databases and transaction processing and
you'll sort of know "why")

or just point out where I've "dissed" the competition?  that's how this sort of
started....

or give us the example code that blows up -- just to see what it might look
like?

no coronary here.  fit and healthy.  no worries with that.

Vietnam was before my time as well -- but this isn't a philosophical thing here
either.
Rating: 4
Why Oracle, Why SS, Why Sybase January 21, 2004
Reviewer: NAM from UK
It interesting to watch you guys trying to see who's got the best relational
database technology.

I generally prefer the Versant object database because
- its much easier to use,
- faster to develop applications with,
- it typically has performance at least 2 orders of magnitude (and often 3 or 4)
above ANY relational database with very little effort.
- it doesn't need DBAs
- I don't need to worry about indexes,
- I can have strings of variable lengths (what a revolution!),
- I can have collections
- I can represent the data in a way that represents the real world and my
application instead of turning it inside out.  I don't need to worry about link
tables (or join tables or what ever its called when you try and mundge a
many-to-many relationship into a RD).
- I can have inheritance, polymorphism.
- I don't need a whole bunch of stored procedures that end up being a nightmare
to maintain.
- I can have huge transactions, hundreds of simultaneous connections and I
didn't need to configure a thing, it just comes like that.

At the end of the day I'm concerned with getting my applications to my users as
quickly as possible, with as much functionality as possible and in a way that
can be easily maintained through the life of the application. The database is
just a means to an end and I'm sorry but relational database technology just
doesn't do it for me, the sooner the technology is dumped the sooner we can get
on with giving the users what they want.
Followup:
hah hah, lol.

thanks for the might fine laugh.

tell me, can you build something like say "a banking system" on that?  or maybe
a data warehouse to mine all of the stuff wal mart sold last week?  (i can see
it now, an object per sale, message all objects).

appropriate for somethings perhaps -- but in general?  sorry.  I don't agree.

client server was giving the users what they wanted.  or at least what they
thought they wanted.  that -- well, we can see how "good" that idea was.

I see it all of the time "oh, well, we sort of modeled the objects from this
perspective.  sorry but your question cannot really be answered very efficiently
(if at all) from our model.  but that's ok, we'll just model it differently and
duplicate the data over".

well, I guess time will tell at the end of the day.
Rating: 5
January 21, 2004
Reviewer: A reader
NAM  from UK  so what are you doing here?? :-)))
Rating: 5
Nam - where are the TPC benchmarks? January 21, 2004
Reviewer: Jim from Portland, OR
I find it difficult to believe there is any database out there that is 3 or 4
orders of magnitude faster than Oracle (or SS or Sybase or DB2, etc.). (Assuming
that the code and use of the database are written effeciently.)  4 orders of
magnitude is 10,000 times faster.  Given the physics of the problem Nam is
blowing smoke. (to put it politely)

If not, then where are the audited industry recognized benchmarks demonstrating
such lightening speed?  I know, it is a conspiracy led by Microsoft, IBM,
Oracle, Sybase et al to supress such information.  Yes, of course, that's it;
Larry and Bill are in collusion. :-)
Rating: 3
Came from INGRES January 21, 2004
Reviewer: Gary from Sydney, Aus
I moved to Oracle environment from Ingres many years ago.
You do get used to any environment you work with long enough.

I was surprised that Oracle (at that time) didn't support index-organised
tables. Now, looking back, I remember that we had a monthly process that
re-organised one large table from one index key to another, processed it, and
re-organised it back (all for improved performance). And I thought there was
nothing 'wrong' in that.

However, I still dislike SQL*Plus for ad-hoc type queries, and have fond
memories of INGRES's ISQL.

PS. There probably would be a market for books for developers moving from Oracle
to SQL-Server and vice-versa, but it would need to be printed on asbestos
Followup:
i like the asbestos concept......

we did have b*tree clusters way back then -- they would (do) cluster data in a
manner very similar to IOT's.  Used quite extensively in the data dictionary to
keep all of the data about a segment physically colocated together.
Rating: 1
January 22, 2004
Reviewer: NAM from UK
“tell me, can you build something like say a banking system on that?”
:-  Built one dude, last seen looking after £300Billion Sterling of interest
rate swaps and futures and £1Billion Sterling cash.

“data warehouse”
:- go visit www.objectivity.com if you want to see a really big database!

“client server was giving the users what they wanted.  or at least what they
thought they wanted.  that -- well, we can see how "good" that idea was.”
:- User want loads of functionality, they want it all now and they want to
change the system whenever they feel like it.  Tell me, when did a system built
on a relation database ever deliver ANY of these?

Tell me also, if you write your application in an OO language, how do you get
data to and from a relational database (maybe you don’t believe in OO
programming either?).  Don’t tell me, let me guess, an object persistent layer?
 Joke!

“I find it difficult to believe there is any database out there that is 3 or 4
orders of magnitude faster”
:- I agree, but it is.  You have no joins, no cursors, no indexes, no SQL to
parse.  If you want data to back this up got visit www.objectstore.net,
www.objectivity.com or www.versant.com

“NAM  from UK  so what are you doing here”
:- Bating Grandpa
Followup:
given that these real financial systems are governed by international law and
real ones have constraints like -- you MUST ensure each transaction is commited
in two geographicaly separate databases (eg: to prevent something like a world
trade center attack from wiping out a million dollar transfer) -- I think we'd
be interested to hear about:

o the availability and recoverability requirements here.  Whats the disaster
recovery scenario here?  how did you accomplish that?  or is the data deemed
"losable" -- or is the time to recovery measured in days instead of minutes.

o how many users use this concurrently -- finance systems typically do hundreds
of TPS for hours on end.  How about this?

o how big is this (I can use one row to hold 30,000,000,000 worth of stuff)

come on, some facts here.  is this a mission critical, gotta run application or
something that is "nice to have, if it disappears, oh well".

giving a user, who is very "ethnocentric" if you will -- very focused on
themselves -- not the corporation as a whole -- is not always the "right thing".
 might make you a hero with that person, but a real pain with the company.  Say
that data is important, relevant, should be professionally managed so we can in
fact recover it, restore it, have it in the event of a real failure (this is not
a slam at the object database -- rather a slam at the notion of "hey, we'll just
wing this puppy -- no need for dba's or anything like that")....

did you know, lots of data out there predates our existence.  data is forever
(he who owns the data owns the keys to the city).  applications are like
mayflies.  they are born, live, breed, and die in a day.  they come, they go.
In the last 10 years -- I've seens dozens of applications come and go.  The data
underneath them?  well, i'll be darned if it has not "survived".

and you have read:
http://www.slac.stanford.edu/BFROOT/www/Public/Computing/Databases/
right?  that is not a single database, it is over 100 databases and took some 5
million lines of custom developed code to even fathom.

so, over 100 databases  -- not one.
5,000,000 lines of custom code.

all to retrieve a blob at the end of the day.

interesting.

ahh, but the very very lack of what you deem evil (sql) is what makes these
solutions "not very general purpose".

I deliver high speed, scalable, flexible as all get out old fashioned rdbms
applications all of the time.  all of the time.  Hey - guess what -- you are
using one right now (and look at amazon, yahoo, ebay, et. al.  or does those
guys just "not get it"?)
Rating: 1
ObjectStore? January 22, 2004
Reviewer: A reader
I know an Object-Guru ("Java is Mc Donalds OO") who developed with his own
company a big C++ ERP-system.

His only failure (he admits it) was, that he used ObjectStore for storing the
data. The most time he got phone calls from frustrated customers, because their
database hangs or crashed!
Rating: 5
January 22, 2004
Reviewer: A reader
“NAM  from UK  so what are you doing here
:- Bating Grandpa "

Kid this is not the forum for your imaginary talks :-))

Get evidence/results then well talk.. Here everybody
beleives in results proof.
Followup:
funny thing is, with regards to "grandpa"

1970 - year the System R paper from IBM was made.  described a language 'SQL'
and the RDBMS concept

1970 - the year smalltalk, "the" OO language was invented....

hmm -- maybe - just maybe - pot calling kettle black.

what is consider new and cutting edge may just well be "like really really old".

funny -- goes back to Ada doesn't it?  RDBMS = 70's = Ada = PL/SQL = Object
Oriented. they all be about the same age.  funny isn't it?

OH OH -- we need a new paradigm.  wait, that's not right, "paradigm" is a 1990's
term as well.  It is dated.  guess we just need something new?
Rating: 5
January 22, 2004
Reviewer: A reader
OH YEAH!!!! I Love this forum.
Rating: 5
The founder of the OOPL is Simula 67 January 23, 2004
Reviewer: Zlatko Sirotic from Croatia
"1970 - the year smalltalk, "the" OO language was invented...."

The founder of the OOPL is Simula 67, designed in 1967
by Kristen Nygaard and Ole-Johan Dahl
from the University of Oslo and the Norwegian Computing Center.

Best regards,
Zlatko Sirotic
Rating: 5
some genuine care please January 24, 2004
Reviewer: Gabriel from Toronto
CW said it ...

<quote>So, if you want to beat Msft you must play by their rules and Msft invest
time & money in application developers. They put effort into trying to make
programmers lives easier.</quote>

Right.

So, why can't we have what they have? I mean, you know I can be hard-headed at
times, so forget about spending time ... how about some money?

I guess, I could spend those money for a MSDN subscription in order to get SS
(never been able to find the link to their free developer download !?) ... but
I'm sure they otherwise really care.
Followup:
what do they have -- give me some info here.

what I heard from CW was "I want oracle to be exactly like ss.  I don't want to
have to learn anything about anything new.  I want everything to be like ss"

so -- throw me a bone.  give me a "for example", a list.  something to work
with.

We ain't going to mess up a really good concurrency model to be like them

We ain't going to dump plsql down to the level of t-sql.

We ain't going to remove our ability to scale really large.

But hey -- throw me a bone here.
Rating: 5
Forgot to put some :-) somewhere in there. January 25, 2004
Reviewer: Gabriel from Toronto
I was expressing the opinion (not very well I guess) that
"genuine care" between for-profit corporations and developers doesn’t quite
exist (not very original, I know)… but on the subject of "common interests" …
well … I was proposing the absence of a free-download for SS is maybe more
indicative of the way they treat their developers (I may be wrong though … I
don’t travel to the "MS world" too often … but when I did, could never find it).

In retrospect, I guess my comment wasn’t very constructive.
Rating: 5
is it possible to do this? February 02, 2004
Reviewer: A reader
Hi

I have a pl/sql script which receives shell variables, I want to process this
variable in a pl/sql array such as

filep="aa,ab"
DEFINE FILEPATH='&filep'
DECLARE
   type l_array is table of varchar2(256);
   l_file_path_ori   varchar2(1000);
   l_file_path       l_array;
   l_num_fs          number;
   l_nom_tabsp       varchar2(30);
   l_sql_str         varchar2(2000);
BEGIN
   l_file_path_ori := '''' || replace('&FILEPATH', ',', ''',''' ) || '''';
   l_file_path     := l_array(l_file_path_ori);
   dbms_output.put_line(l_file_path(1));
END;
/

'aa','ab'

I dont get aa... do you know why?
Followup:
should be "obvious"?

l_file_path := l_array( some_variable );

regardless of the CONTENTS of some_variable -- l_file_path can have at most one
element -- there is but one string in there!

You want l_file_path = l_array( &DEFINE_VARIABLE );  to get some literal string
replacement happening:

define filep="aa,ab"
column X new_val filepath

select '''' || replace('&filep', ',', ''',''' ) || '''' X from dual;

DECLARE
   type l_array is table of varchar2(256);
   l_file_path       l_array;
BEGIN
   l_file_path     := l_array(&FILEPATH);
   dbms_output.put_line(l_file_path(1));
END;
/
Rating: 4
Batch processing & Soft Parse March 15, 2004
Reviewer: Vinnie from Orlando
Tom,

We have a JAVA app that uses sendBatch & prepared Statments.
I have noticed in the trace file, that everytime
((OraclePreparedStatement)ps).sendBatch() is called the parse total is
incremented.  Is this expected?  The developers insist the statements is only
prepared once.

Thanks
Followup:
can you give me a small example to investigate with?  against emp and dept.  I
don't have any small "sendBatch"

(but the trace file would tell you for sure, you can read it, it'll show you
exactly what is being parsed and when)
Rating: 3
Trace March 15, 2004
Reviewer: Vinnie from Orlando
/*
 * This sample shows how to use the batching extensions.
 * In this example, we demonstrate the sue of the "sendBatch" API.
 * This allows the user to actually execute a set of batched
 * execute commands.
 *
 */

// You need to import the java.sql package to use JDBC
import java.sql.*;
import oracle.sql.*;

// You need to import oracle.jdbc.driver.* in order to use the
// API extensions.
import oracle.jdbc.driver.*;

class SendBatch
{
  public static void main (String args [])
       throws SQLException
  {
    // Load the Oracle JDBC driver
    DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());

    // Connect to the database
    Connection conn =
    DriverManager.getConnection
("jdbc:oracle:thin:@waracle1:1521:perfuitt","salernov","salernov");
    System.out.println("Connected");

    Statement stmt = conn.createStatement ();
    Statement stmt1 = conn.createStatement ();

    stmt1.execute ("truncate table dept");

    // Default batch value set to 50 for all prepared statements belonging
    // to this connection.
    ((OracleConnection)conn).setDefaultExecuteBatch (2000);
    stmt1.execute("alter session set events '10046 trace name context forever,
level 12'");
    PreparedStatement ps =
      conn.prepareStatement ("insert into dept (e,a,b,c,create_date) values (?,
rpad('X',400,'Z'), rpad('X',400,'Z'), rpad('X',400,'Z'), SYSDATE)");
    int tot_rows = 0;

    for (int i=1; i<1000;i++){
//     ps =
//     conn.prepareStatement ("insert into dept (e,a,b,c,create_date) values (?,
rpad('X',400,'Z'), rpad('X',400,'Z'), rpad('X',400,'Z'), SYSDATE)");
        tot_rows++;
        ps.setInt(1, i);
    // this execute does not actually happen at this point
    int rows = ps.executeUpdate ();
//    if ( rows >0 )
//        System.out.println ( i + "Insert Executed");
//
//    ps.close();
    }
    int rows = ps.executeUpdate ();

    rows = ((OraclePreparedStatement)ps).sendBatch();

for (int i=1; i<1000;i++){
//     ps =
//     conn.prepareStatement ("insert into dept (e,a,b,c,create_date) values (?,
rpad('X',400,'Z'), rpad('X',400,'Z'), rpad('X',400,'Z'),
//SYSDATE)");
                tot_rows++;
                ps.setInt(1, i);
    // this execute does not actually happen at this point
        int rows2 = ps.executeUpdate ();
//      if ( rows >0 )
//              System.out.println ( i + "Insert Executed");
//
//      ps.close();
        }
    int rows2 = ps.executeUpdate ();

    rows2 = ((OraclePreparedStatement)ps).sendBatch();

    ps.setInt(1, 1000);
    // this execute does not actually happen at this point
    int rows3 = ps.executeUpdate ();
    rows3 = ps.sendBatch();
    ps.setInt(1, 1000);
    // this execute does not actually happen at this point
    int rows3 = ps.executeUpdate ();
    rows3 = ((OraclePreparedStatement)ps).sendBatch();

    System.out.println ("Total Rows Inserted: " + tot_rows );

    // Execution of both previously batched executes will happen
    // at this point. The number of rows updated will be
    // returned by sendBatch.

    ps.close ();
    conn.close ();
  }
}

insert into dept (e,a,b,c,create_date)
values
 (:1, rpad('X',400,'Z'), rpad('X',400,'Z'), rpad('X',400,'Z'), SYSDATE)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        3      0.01       0.00          0          0          0           0
Execute      3      0.25       1.42          0        313       1246        2001
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        6      0.26       1.42          0        313       1246        2001

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 197

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                       5        0.00          0.00
  SQL*Net message from client                     5        0.19          0.24
  SQL*Net more data from client                   4        0.00          0.00
  log buffer space                                6        0.42          1.16
  log file sync                                   3        0.46          0.94
********************************************************************************

Is the parse count incremented everytime the
((OraclePreparedStatement)ps).sendBatch() is called?
Followup:
Yes, it appears that every explicit "sendBatch" or "commit" will cause a
subsequent soft parse.

If you set the batch size really low (send the executes way up) the parses stay
low.  If you add lots of commits, they go way up.  if you add lots of
sendBatches, they go way up.

Not sure "why", but using update batching -- you shouldn't necessarily need to
use sendBatch.  It is unfortunate (not sure of the implementation needs of this)
but better then parse/execute for each row.

To minimize the parsing, do not invoke sendBatch.  Also, consider having
session_cached_cursors set as well.
Rating: 3
Need your help March 16, 2004
Reviewer: Ralph from London,UK
Hi Tom,
 Suppose if I have an array like
 sql> declare
       type t is table of number;
       array t := t(1,2,3);
 I would like to pass this array to a procedure and get each
element of the array multiplied by a number i.e say 2.How to
do that?Do I have to use Java?Is there any way in PL/SQL to
accomplish that?
Need your help over there.
Please do reply.
Followup:
you have to make the type visible.  right now there is exactly ONE block of code
on the planet that can see it -- that anonymous block.  No one else knows about
that type.

You would code like this:

ops$tkyte@ORA9IR2> create or replace type numArray as table of number
  2  /

Type created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace procedure p( p_data in out numArray )
  2  as
  3  begin
  4          for i in 1 .. p_data.count
  5          loop
  6                  p_data(i) := p_data(i) * 2;
  7          end loop;
  8  end;
  9  /

Procedure created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2          l_data numArray := numArray(1,2,3);
  3  begin
  4          for i in 1 .. l_data.count
  5          loop
  6                  dbms_output.put_line( l_data(i) );
  7          end loop;
  8          p( l_data );
  9
 10          dbms_output.put_line( 'after....' );
 11          for i in 1 .. l_data.count
 12          loop
 13                  dbms_output.put_line( l_data(i) );
 14          end loop;
 15  end;
 16  /
1
2
3
after....
2
4
6

PL/SQL procedure successfully completed.

In this case, you can "hide" the type in a package as well if you like:

ops$tkyte@ORA9IR2> create or replace package demo_pkg
  2  as
  3          type numArray is table of number;
  4
  5          procedure p( p_data in out numArray );
  6  end;
  7  /

Package created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace package body demo_pkg
  2  as
  3
  4  procedure p( p_data in out numArray )
  5  as
  6  begin
  7          for i in 1 .. p_data.count
  8          loop
  9                  p_data(i) := p_data(i) * 2;
 10          end loop;
 11  end;
 12
 13  end;
 14  /

Package body created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2          l_data demo_pkg.numArray := demo_pkg.numArray(1,2,3);
  3  begin
  4          for i in 1 .. l_data.count
  5          loop
  6                  dbms_output.put_line( l_data(i) );
  7          end loop;
  8          demo_pkg.p( l_data );
  9
 10          dbms_output.put_line( 'after....' );
 11          for i in 1 .. l_data.count
 12          loop
 13                  dbms_output.put_line( l_data(i) );
 14          end loop;
 15  end;
 16  /
1
2
3
after....
2
4
6

PL/SQL procedure successfully completed.

No java needed or desired here...
Rating: 3
My Favorite MS Sql Feature March 16, 2004
Reviewer: Terry from FL, USA
SS Licenses can be traded in at par for Oracle Licenses.  Think of it like the
"Lemon Law" when you purchase a car that just does not work for you and the
other guy throws you a life preserver.

I have to admit I am watching a "crew" try to do a 1 TB SS Data warehouse and it
is funny.  No performance and everybody running around saying "can't you just
add an index or something".  It's a real bitch when you can't find anymore
registry keys to play with and time's up.  You gotta love the MS Sales idiot who
fills these guys full of BS about 32 Bit this, 64 Bit that, 2003 threading
models, yadda, yadda, yadda just long enough to get their money and make them
sink cash into new hardware as well.

The trick is to do the switch before you go broke or destroy the data.  The
problem is most of these guys die trying to make it work and the credability of
another MIS project goes down the drain.  But it is funny to watch ...
Rating: 3
"at the end of the day" - - commonly used here March 30, 2004
Reviewer: Jan

http://www.plainenglish.co.uk/pressrelease.html

Followup:
"Absolutely", I will work "24/7" to "address the issue" "going forward".  It was
"awesome" to have that pointed out I'll work to recitify it.  "Basically" you'll
have to "bear with me" while I try to "blue sky" a method to avoid cliches. It
"literally" "boggles the mind" how often it happens (sort of an "ongoing"
problem) -- but I feel I'm "between a rock and a hard place" at times (I know, I
know, "it's not rocket science") rushing to answer the questions in a
conversational tone, thats the "bottom line".  Guess I thought the cliches were
"value added" text -- making it sound more like a discussion.

"The fact of the matter is" however -- that "I hear what you are saying", we are
"singing from the same hymn sheet".  I'll work to "prioritise" this, but "to be
honest with you", I cannot come up with a "ballpark figure" as to how long the
problem might persist.  I'll try on a daily "basis" to cut down on their use,
but it'll be "pushing the envelope" to think it'll happen overnight.  "In terms
of" a reasonable timeline -- I'm thinking "around" 2 or 3 weeks, but I'd give it
"up to" a month or so.  Unless someone "moves the goal-posts" on me of course --
that would change everything.  I'm sure some "out of the box thinking" (maybe I
can put a trigger on the table to catch them....) might apply.  Guess it is a
"glass half full/empty" problem really -- some people would just read over them
as if they weren't there....

We'll "touch base" on this for sure.
Rating: 5
RE:"at the end of the day" - - commonly used here March 30, 2004
Reviewer: A reader
That was a good one :-)
Rating: 5
let`s hope that.... March 30, 2004
Reviewer: Jan
...  SQL and PL/SQL commands (SELECT, UPDATE,INSERT..)
will not become cliches.
March 30, 2004 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.
Rating: 5
Datatypes March 30, 2004
Reviewer: Martin from UK
Hi Tom,

couldn't find an answer to this elsewhere on your site, and it's a quick
question anyway.

Check out the return types of USERENV and SYS_CONTEXT for sessionid :

SQL>  select userenv('sessionid'), sys_context('USERENV', 'SESSIONID') from
dual;

USERENV('SESSIONID') SYS_CONTEXT('USERENV','SESSIONID')
--------------------
-------------------------------------------------------------------------------
                3050 3050

As you can see, SQL*Plus seems to be inferring that USERENV returns a numeric,
whereas SYS_CONTEXT is a string.

If sessionid is used in a query against a numeric, presumably it would be
"better" to use USERENV in this case? Seems like it's an ideal way to remove any
TO_NUMBERs from queries if this is the case.

Thanks in advance
Followup:
sys_context is prefered going forward.  Just to_number it when appropriate.
Rating: 5
Thanks March 30, 2004
Reviewer: Martin from UK
March 30, 2004 phil -- Thanks for the question regarding "isnumeric function", version 8.1.7
You Asked
Tom

I have a control table that contains various values, numeric, character and
dates. They are all stored as VARCHARs but when a value is numeric, can I
perform any easy check so that users do not enter illegal values? The table
exists like this because we are still adding in new parameters for the app.

In VB, there is a function "isnumeric()" which will accomplish this but does not
seem to be the same in pl/sql?

Thanks, Phil
and we said...
just assign the varchar2 to a number and catch any exceptions. An exception
indicates "not a number"

...
begin
    ......
    BEGIN
       l_test_number := l_string_that_might_hold_a_number;
       .... code that should run when it is in fact a number......
    EXCEPTION
       WHEN OTHERS
       then
          ..... code that should run when it is in fact NOT a number.....
    END;
    .......
end;
Review & Followup

Rating: 5
simple, instant response February 02, 2004
Reviewer: A reader
Thanks, couldn't be more precise!
Rating: 3
Perhaps I'm being picky but... February 02, 2004
Reviewer: Mike from England
I seem to remeber some highly regarded Oracle person saying that it is a bug if
you use a "when others" that is not followed by a raise...

Shurley it should be a "when invalid number" and or when numeric or value error
that should be used, or you could get any other error and wrongly assume that
its because it is not a number ;)
Followup:
I said it almost certainly is a bug -- not 100%, almost certainly :)

Here -- we are in FACT expecting an exception to be raised

AND

we can deal with it

MEANING

it isn't an error but an expected "condition" -- like NO_DATA_FOUND.  There are
two exceptions that could be raised -- hence the use of when others here --
since the ONLY thing that could be going wrong here is in fact "invalid number".
Rating: 5
Being picky... February 02, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
Surely you did not mean "shurley"?  (My typing is not better, btw)

But, on a more serious note, who is to say that "...code that should run when it
is in fact NOT a number..." does not include a RAISE?

- Mark
Rating: 5
I did mean Shurley February 02, 2004
Reviewer: A reader
I was refering to a "Shurley Shome Mishtake" from Private Eye a satirical mag
that you probably don't see a lot of outside of britain (but should)

however I do agree that any of the code included could raise an error and cause
the code to switch to thinking it's not a valid number.  another block would be
needed.  but I think this is getting quite pedantic now...
Rating: 5
Shurley! February 02, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
My mistake (I should know better - my wife is from Buckinghamshire).  Anyway,
pedantic can be good sometimes too! (But I agree)

Cheers,

Mark
Rating: 4
What is a number February 02, 2004
Reviewer: Fan from virginia
I recall in another forum a discussion about creating a function to determine if
a value is a number, and in the end, we decided, only the customer/coder can
determine what a number is

It came down to creating test data (and a fat finger), to discover that not all
numbers look like numbers. e.g., take the values 1500, 15e00 and 15.00 we know
that they are numbers (not all necessarily look like numbers).  The question is:

Does the customer want them ALL treated as numbers?
Rating: 1
I beg to differ... February 20, 2004
Reviewer: Mike from England
Perhaps it;s because it's Friday, but I seem to be deluded into thinking I might
be right.

Consider...

declare
  l_string varchar2(10) := '123';
  l_test_number number;
begin
  begin
    dbms_output.put_line('Examine String');
    l_test_number := l_string;
    dbms_output.put_line('It is a number');
    --code that should run when it is in fact a number......
    --
    -- lots of lots of code, perhpas it inserts into a table and violates a
check constraint or PK? or as
    --below does a select into that raises a NO_DATA_FOUND
    raise no_data_found;
  exception
  when others
  then
    dbms_output.put_line('It is not a number');
    --code that should run when it is in fact not a number.....
  end;
    --
end;

How can this code not "go wrong" without changing the when others?  in other
words how can you account for an exception being raised after the check and it
being handled correctly?? set some flags to know you went past the number check?
 The best I can think of is

declare
  l_string varchar2(10) := '123';
  l_test_number number;
begin
  begin
    dbms_output.put_line('Examine String');
    l_test_number := l_string;
    dbms_output.put_line('It is a number');
    --code that should run when it is in fact a number......
    --
    -- lots of lots of code, perhpas it inserts into a table and violates a
check constraint or PK? or as
    --below does a select into that raises a NO_DATA_FOUND
    raise no_data_found;
  exception
  when others
  then
    -- we must test both varsincase l_string
    -- was passed as null and we then raised an
    -- error, null is a valid number
    if l_test_number is not null
    or l_string is null
    then
      raise;
    else
      dbms_output.put_line('It is not a number');
      --code that should run when it is in fact not a number.....
    end if;
  end;
    --
end;

and I'm not really a fan of that ;)
Followup:
You are right, it was a bit sloppy -- i would normally create a function
"is_number" that returns boolean but short of that,

declare
  l_string varchar2(10) := '123';
  l_test_number number;
  l_is_number boolean := true;
begin
  begin
    l_test_number := l_string;
  exception
    when others then
        l_is_number := FALSE;
  end;

  if ( l_is_number )
  then
    --code that should run when it is in fact a number......
    --
  else
    --code that should run when it is in fact not a number.....
  end;
end;

would be "safe"
Rating: 2
Check if numeric February 20, 2004
Reviewer: Martin from Netherlands
Hi,

Although not very readable this seems to work fine:

replace(translate(<value>,'N01234567890','XNNNNNNNNNN'),'N',null)

If this expression is null then the value is a number,  otherwise it's not.
Followup:
a whole number sure, what about -1.12543
Rating: 4
March 11, 2004
Reviewer: Vicky from Canada
This should do for negative and numbers with decimal points.

replace(replace(replace(translate(contact_value,'N01234567890','XNNNNNNNNNN'),'N'
,null),'-'),'.')
Rating: 2
What about values like 1.2.3 or --1 March 19, 2004
Reviewer: A reader
cheers
Followup:
use the exception block, it'll catch absolutely EVERYTHING.
Rating: 3
alternate idea March 29, 2004
Reviewer: Tim W from Moline, IL USA
This is more of an algorithm than oracle code, because i have only been working
with oracle for about a month now.  But it should take any alphanumeric string
and report if it is a number.

Given T is a string of characters.

if (length(T) =
length(replace(T,'abcdefghijklmnopqrstuvyxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~!@#$%^&*()
_+=`[]{}\|;:,<>/?'))
{
    // the string contains only 0-9 . and -
    if( (length(T) - length(replace(T,'.'))) <= 1)
    {
        // the string contains only 0 or 1 "."
        if( (length(T) - length(replace(T,'-')) ) = 0) OR (  (length(T) -
length(replace(T,'-')) = 1)  AND substr(T,1,1) = "-"   )
        {
            // the string contains 0 or 1 "-" and if so it is in the first
position
            execute true statements here
        }
    }
}

the reason for this one is that the prior sugestion will accept 9-9.-9.9 as a
valid string which it is not. because a number may contain no more than one "."
and no more than one "-" which must be in the first position only.  thus
comparing the lengths of the replaced strings.
Followup:
how about:

declare
   l_num number;
begin
   l_num := to_number(T);
   code for when t is a number
exception
   code for when t is not a number
end;

or even "easier"

create function isnumber( p_string in varchar2 ) return boolean
is
   l_number number;
begin
   l_number := P_string;
   return true;
exception
   when others then return false;
end;
/

begin
   if (isnumber(T))
   then
      .....
Rating: 4
Built in function March 30, 2004
Reviewer: Tim W from Moline, IL
Well if there is a built in function use that.  No need to re-invent the wheel.
The impression i got was that there wasnt one built in.
Followup:
declare
   l_num number;
begin
   l_num := to_number(T);
   code for when t is a number
exception
   code for when t is not a number
end;

"to_number" is the builtin.  'isnumber' makes it easier to use...
March 30, 2004 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.
Rating: 4
help importing subpartioned table January 23, 2004
Reviewer: Fernando Sánchez from Madrid, Spain
I've tried to import a subpartitioned table in a 9.2.0 db using the same
parameters that worked well for a partioned table in a 8.1.7 db, but the only
thing I get is the 922 error.

Any idea about this?
Followup:
hmmm,

my car won't start this morning?  No, i won't tell you

a) what the gas gauge is reading
b) if it is making any noises
c) if the engine turns over
d) how cold it is outside
e) what I attempted to do to get it started...

any ideas for me?  ;)

how's about SHOWING us what parameters you mean eh?  cut and pastes are best of
all -- no edits.
Rating: 5
You are right January 23, 2004
Reviewer: Fernando Sánchez from madrid, SPAIN
Our parameter files are:
EXPORT ->
FILE=bck_historico.dmp
LOG=historico_exp.log
TABLES=(TB_HIS_OPER22)
CONSISTENT=Y
FEEDBACK=100
STATISTICS=NONE
IMPORT ->
FILE=bck_historico.dmp
LOG=historico_imp.log
TABLES=(TB_HIS_OPER22)
IGNORE=Y
COMMIT=Y
FEEDBACK=100

We detected that the problem is about subpartitions because if we try with a
test partitioned table are working but if import try with this sentence:
CREATE TABLE TB_HIS_OPER22 (
SQ_HOP_01 NUMBER(10, 0) NOT NULL ENABLE,
CO_MSDN_V VARCHAR2(9) NOT NULL ENABLE,
CO_OPER_V VARCHAR2(16) NOT NULL ENABLE,
DS_OPER_V VARCHAR2(64) NOT NULL ENABLE,
FX_OPER_D DATE NOT NULL ENABLE,
CO_USER_V VARCHAR2(15),
CO_REQU_V VARCHAR2(15) NOT NULL ENABLE,
DS_REAS_V VARCHAR2(64),
CO_TIP_PROD_V VARCHAR2(8) NOT NULL ENABLE,
NO_COD_PROD_V VARCHAR2(8) NOT NULL ENABLE,
DS_PROD_V VARCHAR2(32),
SQ_PROD_V VARCHAR2(10) NOT NULL ENABLE,
DS_INI_PROD_V VARCHAR2(64),
TO_QUAN_N NUMBER(10, 0),
FX_INI_VALI_D DATE,
FX_FIN_VALI_D DATE)
PCTFREE 5 PCTUSED 60 INITRANS 1 MAXTRANS 255 TABLESPACE TSP_HOP_D_01 LOGGING
PARTITION
BY RANGE (FX_OPER_D )  SUBPARTITION BY HASH (CO_MSDN_V )  SUBPARTITIONS
 10 SUBPARTITION TEMPLATE( SUBPARTITION SP1 VALUES () , SUBPARTITION SP2
 VALUES () , SUBPARTITION SP3 VALUES () , SUBPARTITION SP4 VALUES () ,
 SUBPARTITION SP5 VALUES () , SUBPARTITION SP6 VALUES () , SUBPARTITION
 SP7 VALUES () , SUBPARTITION SP8 VALUES () , SUBPARTITION SP9 VALUES
 () , SUBPARTITION SP10 VALUES ()  ) (PARTITION TB_HIS_OPER_MAX VALUES
LESS THAN (MAXVALUE)  PCTFREE 5 PCTUSED 60 INITRANS 1 MAXTRANS 255 STORAGE(
INITIAL 512000 NEXT 307200) TABLESPACE TSP_HOP_D_01 LOGGING (
SUBPARTITION TB_HIS_OPER_MAX_SP1 TABLESPACE TSP_HOP_D_01,
SUBPARTITION TB_HIS_OPER_MAX_SP2 TABLESPACE TSP_HOP_D_01,
SUBPARTITION TB_HIS_OPER_MAX_SP3 TABLESPACE TSP_HOP_D_01,
SUBPARTITION TB_HIS_OPER_MAX_SP4 TABLESPACE TSP_HOP_D_01,
SUBPARTITION TB_HIS_OPER_MAX_SP5 TABLESPACE TSP_HOP_D_01,
SUBPARTITION TB_HIS_OPER_MAX_SP6 TABLESPACE TSP_HOP_D_01,
SUBPARTITION TB_HIS_OPER_MAX_SP7 TABLESPACE TSP_HOP_D_01,
SUBPARTITION TB_HIS_OPER_MAX_SP8 TABLESPACE TSP_HOP_D_01,
SUBPARTITION TB_HIS_OPER_MAX_SP9 TABLESPACE TSP_HOP_D_01,
SUBPARTITION TB_HIS_OPER_MAX_SP10 TABLESPACE TSP_HOP_D_01 ) )
The problem appears.
How can we import subpartitioned tables ???
Thanks in advance.
Followup:
ummm, WHAT PROBLEM???

you say

"having a problem importing"

but you show us export parameters.

you say

"the problem appears"

but give us no clue as to exactly WHAT the problem is........
Rating: 4
trying to explain the problem January 23, 2004
Reviewer: Fernando Sánchez from Madrid, Spain
Sorry Tom I haven't explained OK.

The export was made with these contents in the parameter file:
FILE=bck_historico.dmp
LOG=historico_imp.log
TABLES=(TB_HIS_OPER22)
IGNORE=Y
COMMIT=Y
FEEDBACK=100

And the import with these ones:
FILE=bck_historico.dmp
LOG=historico_exp.log
TABLES=(TB_HIS_OPER22)
CONSISTENT=Y
FEEDBACK=100
STATISTICS=NONE

And the import failed. Trying to see why it had failed (error 922)we "extracted"
from bck_historico.dmp the create table script I shew before and we got the same
error (922), that's why we think the problem is there.

I hope it is clear.

Thanks again.
Followup:
hows about a step by step cut and paste of the error -- the entire thing.
Rating: 5
it seems to be aparse error January 26, 2004
Reviewer: Fernando Sánchez from Madrid, Sapin
Here's the entire thing (8.1.7):

1. EXPORT: exp USERID=user/pwd PARFILE=exp_parfile.txt
and this is the resulting log (everything seems OK)

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
Export done in US7ASCII character set and AL16UTF16 NCHAR character set
server uses WE8ISO8859P1 character set (possible charset conversion)

About to export specified tables via Conventional Path ...
. . exporting table                    TB_HIS_OPER
. . exporting composite partition TB_HIS_OPER_20031027
. . exporting subpartition    TB_HIS_OPER_20031027_SP1
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031027_SP2
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031027_SP3
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031027_SP4
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031027_SP5
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031027_SP6
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031027_SP7
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031027_SP8
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031027_SP9
                                                            0 rows exported
. . exporting subpartition   TB_HIS_OPER_20031027_SP10
                                                            0 rows exported
. . exporting composite partition TB_HIS_OPER_20031103
. . exporting subpartition    TB_HIS_OPER_20031103_SP1
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031103_SP2
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031103_SP3
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031103_SP4
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031103_SP5
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031103_SP6
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031103_SP7
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031103_SP8
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031103_SP9
                                                            0 rows exported
. . exporting subpartition   TB_HIS_OPER_20031103_SP10
                                                            0 rows exported

/* A LOT MORE LINES OF THE SAME KIND*/

                                                            0 rows exported
. . exporting composite partition TB_HIS_OPER_20040216
. . exporting subpartition    TB_HIS_OPER_20040216_SP1
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20040216_SP2
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20040216_SP3
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20040216_SP4
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20040216_SP5
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20040216_SP6
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20040216_SP7
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20040216_SP8
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20040216_SP9
                                                            0 rows exported
. . exporting subpartition   TB_HIS_OPER_20040216_SP10
                                                            0 rows exported
Export terminated successfully without warnings.

2. IMPORT: imp USERID=user/pwd PARFILE= imp_parfile.txt with this log:

Export file created by EXPORT:V09.02.00 via conventional path
import done in US7ASCII character set and AL16UTF16 NCHAR character set
import server uses WE8ISO8859P1 character set (possible charset conversion)
. importing LOGAN3_ADMIN's objects into LOGAN3_ADMIN
IMP-00017: following statement failed with ORACLE error 922:
 "CREATE TABLE "TB_HIS_OPER" ("SQ_HOP_01" NUMBER(10, 0) NOT NULL ENABLE, "CO_"
 "MSDN_V" VARCHAR2(9) NOT NULL ENABLE, "CO_OPER_V" VARCHAR2(16) NOT NULL ENAB"
 "LE, "DS_OPER_V" VARCHAR2(64) NOT NULL ENABLE, "FX_OPER_D" DATE NOT NULL ENA"
 "BLE, "CO_USER_V" VARCHAR2(15), "CO_REQU_V" VARCHAR2(15) NOT NULL ENABLE, "D"
 "S_REAS_V" VARCHAR2(64), "CO_TIP_PROD_V" VARCHAR2(8) NOT NULL ENABLE, "NO_CO"
 "D_PROD_V" VARCHAR2(8) NOT NULL ENABLE, "DS_PROD_V" VARCHAR2(32), "SQ_PROD_V"
 "" VARCHAR2(10) NOT NULL ENABLE, "DS_INI_PROD_V" VARCHAR2(64), "TO_QUAN_N" N"
 "UMBER(10, 0), "FX_INI_VALI_D" DATE, "FX_FIN_VALI_D" DATE)  PCTFREE 5 PCTUSE"
 "D 60 INITRANS 1 MAXTRANS 255 TABLESPACE "TSP_HOP_D_01" LOGGING PARTITION BY"
 " RANGE ("FX_OPER_D" )  SUBPARTITION BY HASH ("CO_MSDN_V" )  SUBPARTITIONS 1"
 "0 SUBPARTITION TEMPLATE( SUBPARTITION "SP1" VALUES () , SUBPARTITION "SP2" "
 "VALUES () , SUBPARTITION "SP3" VALUES () , SUBPARTITION "SP4" VALUES () , S"
 "UBPARTITION "SP5" VALUES () , SUBPARTITION "SP6" VALUES () , SUBPARTITION ""
 "SP7" VALUES () , SUBPARTITION "SP8" VALUES () , SUBPARTITION "SP9" VALUES ("
 ") , SUBPARTITION "SP10" VALUES ()  ) (PARTITION "TB_HIS_OPER_20031020" VALU"
 "ES LESS THAN (TO_DATE(' 2003-10-20 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NL"
 "S_CALENDAR=GREGORIAN'))  PCTFREE 5 PCTUSED 60 INITRANS 1 MAXTRANS 255 TABLE"
 "SPACE "TSP_HOP_D_08" LOGGING (SUBPARTITION "TB_HIS_OPER_20031020_SP1"  TABL"
 "ESPACE "TSP_HOP_D_08", SUBPARTITION "TB_HIS_OPER_20031020_SP2"  TABLESPACE "
 ""TSP_HOP_D_08", SUBPARTITION "TB_HIS_OPER_20031020_SP3"  TABLESPACE "TSP_HO"
 "P_D_08", SUBPARTITION "TB_HIS_OPER_20031020_SP4"  TABLESPACE "TSP_HOP_D_08""
 ", SUBPARTITION "TB_HIS_OPER_20031020_SP5"  TABLESPACE "TSP_HOP_D_08", SUBPA"
 "RTITION "TB_HIS_OPER_20031020_SP6"  TABLESPACE "TSP_HOP_D_08", SUBPARTITION"
 " "TB_HIS_OPER_20031020_SP7"  TABLESPACE "TSP_HOP_D_08", SUBPARTITION "TB_HI"
 "S_OPER_20031020_SP8"  TABLESPACE "TSP_HOP_D_08", SUBPARTITION "TB_HIS_OPER_"
 "20031020_SP9"  TABLESPACE "TSP_HOP_D_08", SUBPARTITION "TB_HIS_OPER_2003102"
 "0_SP10"  TABLESPACE "TSP_HOP_D_08" ), PARTITION "TB_HIS_OPER_20031027" VALU"
 "ES LESS THAN (TO_DATE(' 2003-10-27 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NL"
 "S_CALENDAR=GREGORIAN'))  PCTFREE 5 PCTUSED 60 INITRANS 1 MAXTRANS 255 TABLE"
 "SPACE "TSP_HOP_D_09" LOGGING (SUBPARTITION "TB_HIS_OPER_20031027_SP1"  TABL"
 "ESPACE "TSP_HOP_D_09", SUBPARTITION "TB_HIS_OPER_20031027_SP2"  TABLESPACE "
 ""TSP_HOP_D_09", SUBPARTITION "TB_HIS_OPER_20031027_SP3"  TABLESPACE "TSP_HO"
 "P_D_09", SUBPARTITION "TB_HIS_OPER_20031027_SP4"  TABLESPACE "TSP_HOP_D_09""
 ", SUBPARTITION "TB_HIS_OPER_20031027_SP5"  TABLESPACE "TSP_HOP_D_09", SUBPA"
 "RTITION "TB_HIS_OPER_20031027_SP6"  TABLESPACE "TSP_HOP_D_09", SUBPARTITION"
 " "TB_HIS_OPER_20031027_SP7"  TABLESPACE "TSP_HOP_D_09", SUBPARTITION "TB_HI"
 "S_OPER_20031027_SP8"  TABLESPACE "TSP_HOP_D_09", SUBPARTITION "TB_HIS_OPER_"
 "20031027_SP9"  TABLESPACE "TSP_HOP_D_09", SUBPARTITION "TB_HIS_OPER_2003102"
 "7_SP10"  TABLESPACE "TSP_HOP_D_09" ), PARTITION "TB_HIS_OPER_20031103" VALU"
 "ES LESS THAN (TO_DATE(' 2003-11-03 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NL"
 "S_CALENDAR=GREGORIAN'))  PCTFREE 5 PCTUSED 60 INITRANS 1 MAXTRANS 255 TABLE"
 "SPACE "TSP_HOP_D_10" LOGGING (SUBPARTITION "TB_HIS_OPER_20031103_SP1"  TABL"
 "ESPACE "TSP_HOP_D_10", SUBPARTITION "TB_HIS_OPER_20031103_SP2"  TABLESPACE "
 ""TSP_HOP_D_10", SUBPARTITION "TB_HIS_OPER_20031103_SP3"  TABLESPACE "TSP_HO"
 "P_D_10", SUBPARTITION "TB_HIS_OPER_20031103_SP4"  TABLESPACE "TSP_HOP_D_10""
 ", SUBPARTITION "TB_HIS_OPER_20031103_SP5"  TABLESPACE "TSP_HOP_D_10", SUBPA"
 "RTITION "TB_HIS_OPER_20031103_SP6"  TABLESPACE "TSP_HOP_D_10", SUBPARTITION"
 " "TB_HIS_OPER_20031103_SP7"  TABLESPACE "TSP_HOP_D_10", SUBPARTITION "TB_HI"
 "S_OPER_20031103_SP8"  TABLESPACE "TSP_HOP_D_10", SUBPARTITION "TB_HIS_OPER_"
 "20031103_SP9"  TABLESPACE "TSP_HOP_D_10", SUBPARTITION "TB_HIS_OPER_2003110"
 "3_SP10"  TABLESPACE "TSP_HOP_D_10" ), PARTITION "TB_HIS_OPER_20031110" VALU"
 "ES LESS THAN (TO_DATE(' 2003-11-10 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NL"
 "S_CALENDAR=GREGORIAN'))  PCTFREE 5 PCTUSED 60 INITRANS 1 MAXTRANS 255 TABLE"
 "SPACE "TSP_HOP_D_11" LOGGING (SUBPARTITION "TB_HIS_OPER_20031110_SP1"  TABL"
 "ESPACE "TSP_HOP_D_11", SUBPARTITION "TB_HIS_OPER_20031110_SP2"  TABLESPACE "
 ""TSP_HOP_D_11", SUBPARTITION "TB_HIS_OPER_20031110_SP3"  TABLESPACE "TSP_HO"
 "P_D_11", SUBPARTITION "TB_HIS_OPER_20031110_SP4"  TABLESPACE "TSP_HOP_D_11""
 ", SUBPARTITION "TB_HIS_OPER_20031110_SP5"  TABLESPACE "TSP_HOP_D_11", SUBPA"
 "RTITION "TB_HIS_OPER_20031110_SP6"  TABLESPACE "TSP_HOP_D_11", SUBPARTITION"
 " "TB_HIS_OPER_20031110_SP7"  TABLESPACE "TSP_HOP_D_11", SUBPARTITION "TB_HI"
 "S_OPER_20031110_SP8"  TABLESPACE "TSP_HOP_D_11", SUBPARTITION "TB_HIS_OPER_"
 "20031110_SP9"  TABLESPACE "TSP_HOP_D_11", SUBPARTITION "TB_HIS_OPER_2003111"
 "0_SP10"  TABLESPACE "TSP_HOP_D_11" ), PARTITION "TB_HIS_OPER_20031117" VALU"
 "ES LESS THAN (TO_DATE(' 2003-11-17 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NL"
 "S_CALENDAR=GREGORIAN'))  PCTFREE 5 PCTUSED 60 INITRANS 1 MAXTRANS 255 TABLE"
 "SPACE "TSP_HOP_D_12" LOGGING (SUBPARTITION "TB_HIS_OPER_20031117_SP1"  TABL"
 "ESPACE "TSP_HOP_D_12", SUBPARTITION "TB_HIS_OPER_20031117_SP2"  TABLESPACE "
 ""TSP_HOP_D_12", SUBPARTITION "TB_HIS_OPER_20031117_SP3"  TABLESPACE "TSP_HO"
 "P_D_12", SUBPARTITION "TB_HIS_OPER_20031117_SP4"  TABLESPACE "TSP_HOP_D_12""
 ", SUBPARTITION "TB_HIS_OPER_20031117_SP5"  TABLESPACE "TSP_HOP_D_12", SUBPA"
 "RTITION "TB_HIS_OPER_20031117_SP6"  TABLESPACE "TSP_HOP_D_12", SUBPARTITION"
 " "TB_HIS_OPER_20031117_SP7"  TABLESPACE "TSP_HOP_D_12", SUBPARTITION "TB_HI"
 "S_OPER_20031117_SP8"  TABLESPACE "TSP_HOP_D_12", SUBPARTITION "TB_HIS_OPER_"
 "20031117_SP9"  TABLESPACE "TSP_HOP_D_12", SUBPARTITION "TB_HIS_OPER_2003111"
 "7_SP10"  TABLESPACE "TSP_HOP_D_12" ), PARTITION "TB_HIS_OPER_20031124" VALU"
 "ES LESS THAN (TO_DATE(' 2003-11-24 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NL"
 "S_CALENDAR=GREGORIAN'))  PCTFREE 5 PCTUSED 60 INITRANS 1 MAXTRANS 255 TABLE"
 "SPACE "TSP_HOP_D_13" LOGGING (SUBPARTITION "TB_HIS_OPER_20031124_SP1"  TABL"
 "ESPACE "TSP_HOP_D_13", SUBPARTITION "TB_HIS_OPER_20031124_SP2"  TABLESPACE "
 ""TSP_HOP_D_13", SUBPARTITION "TB_HIS_OPER_20031124_SP3"  TABLESPACE "TSP_HO"
 "P_D_13", SUBPARTITION "TB_HIS_OPER_20031124_SP4"  TABLESPACE "TSP_HOP_D_13""
 ", SUBPARTITION "TB_HIS_OPER_20031124_SP5"  TABLESPACE "TSP_HOP_D_13", SUBPA"
 "RTITION "TB_HIS_OPER_20031124_SP6"  TABLESPACE "TSP_HOP_D_13", SUBPARTITION"
 " "TB_HIS_OPER_20031124_SP7"  TABLESPACE "TSP_HOP_D_13", SUBPARTITION "TB_HI"
 "S_OPER_20031124_SP8"  TABLESPACE "TSP_HOP_D_13", SUBPARTITION "TB_HIS_OPER_"
 "20031124_SP9"  TABLESPACE "TSP_HOP_D_13", SUBPARTITION "TB_HIS_OPER_2003112"
 "4_SP10"  TABLESPACE "TSP_HOP_D_13" ), PARTITION "TB_HIS_OPER_20031201" VALU"
 "ES LESS THAN (TO_DATE(' 2003-12-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NL"
 "S_CALENDAR=GREGORIAN'))  PCTFREE 5 PCTUSED 60 INITRANS 1 MAXTRANS 255 TABLE"
 "SPACE "TSP_HOP_D_14" LOGGING (SUBPARTITION "TB_HIS_OPER_20031201_SP1"  TABL"
 "ESPACE "TSP_HOP_D_14", SUBPARTITION "TB_HIS_OPER_20031201_SP2"  TABLESPACE "
 ""TSP_HOP_D_14", SUBPARTITION "TB_HIS_OPER_20031201_SP3"  TABLESPACE "TSP_HO"
 "P_D_14", SUBPARTITION "TB_HIS_OPER_20031201_SP4"  TABLESPACE "TSP_HOP_D_14""
 ", SUBPARTITION "TB_HIS_OPER_20031201_SP5"  TABLESPACE "TSP_HOP_D_14", SUBPA"
 "RTITION "TB_HIS_OPER_20031201_SP6"  TABLESPACE "TSP_HOP_D_14", SUBPARTITION"
 " "TB_HIS_OPER_20031201_SP7"  TABLESPACE "TSP_HOP_D_14", SUBPARTITION "TB_HI"
 "S_OPER_20031201_SP8"  TABLESPACE "TSP_HOP_D_14", SUBPARTITION "TB_HIS_OPER_"
 "20031201_SP9"  TABLESPACE "TSP_HOP_D_14", SUBPARTITION "TB_HIS_OPER_2003120"
 "1_SP10"  TABLESPACE "TSP_HOP_D_14" ), PARTITION "TB_HIS_OPER_20031208" VALU"
 "ES LESS THAN (TO_DATE(' 2003-12-08 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NL"
 "S_CALENDAR=GREGORIAN'))  PCTFREE 5 PCTUSED 60 INITRANS 1 MAXTRANS 255 TABLE"
 "SPACE "TSP_HOP_D_15" LOGGING (SUBPARTITION "TB_HIS_OPER_20031208_SP1"  TABL"
 "ESPACE "TSP_HOP_D_15", SUBPARTITION "TB_HIS_OPER_20031208_SP2"  TABLESPACE "
 ""TSP_HOP_D_15", SUBPARTITION "TB_HIS_OPER_20031208_SP3"  TABLESPACE "TSP_HO"
 "P_D_15", SUBPARTITION "TB_HIS_OPER_20031208_SP4"  TABLESPACE "TSP_HOP_D_15""
 ", SUBPARTITION "TB_HIS_OPER_20031208_SP5"  TABLESPACE "TSP_HOP_D_15", SUBPA"
 "RTITION "TB_HIS_OPER_20031208_SP6"  TABLESPACE "TSP_HOP_D_15", SUBPARTITION"
 " "TB_HIS_OPER_20031208_SP7"  TABLESPACE "TSP_HOP_D_15", SUBPARTITION "TB_HI"
 "S_OPER_20031208_SP8"  TABLESPACE "TSP_HOP_D_15", SUBPARTITION "TB_HIS_OPER_"
 "20031208_SP9"  TABLESPACE "TSP_HOP_D_15", SUBPARTITION "TB_HIS_OPER_2003120"
 "8_SP10"  TABLESPACE "TSP_HOP_D_15" ), PARTITION "TB_HIS_OPER_20031215" VALU"
 "ES LESS THAN (TO_DATE(' 2003-12-15 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NL"
 "S_CALENDAR=GREGORIAN'))  PCTFREE 5 PCTUSED 60 INITRANS 1 MAXTRANS 255 TABLE"
 "SPACE "TSP_HOP_D_16" LOGGING (SUBPARTITION "TB_HIS_OPER_20031215_SP1"  TABL"
 "ESPACE "TSP_HOP_D_16", SUBPARTITION "TB_HIS_OPER_20031215_SP2"  TABLESPACE "
 ""TSP_HOP_D_16", SUBPARTITION "TB_HIS_OPER_20031215_SP3"  TABLESPACE "TSP_HO"
 "P_D_16", SUBPARTITION "TB_HIS_OPER_20031215_SP4"  TABLESPACE "TSP_HOP_D_16""
 ", SUBPARTITION "TB_HIS_OPER_20031215_SP5"  TABLESPACE "TSP_HOP_D_16", SUBPA"
 "RTITION "TB_HIS_OPER_20031215_SP6"  TABLESPACE "TSP_HOP_D_16", SUBPARTITION"
 " "TB_HIS_OPER_20031215_SP7"  TABLESPACE "TSP_HOP_D_16", SUBPARTITION "TB_HI"
 "S_OPER_20031215_SP8"  TABLESPACE "TSP_HOP_D_16", SUBPARTITION "TB_HIS_OPER_"
 "20031215_SP9"  TABLESPACE "TSP_HOP_D_16", SUBPARTITION "TB_HIS_OPER_2003121"
 "5_SP10"  TABLESPACE "TSP_HOP_D_16" ), PARTITION "TB_HIS_OPER_20031222" VALU"
 "ES LESS THAN (TO_DATE(' 2003-12-22 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NL"
 "S_CALENDAR=GREGORIAN'))  PCTFREE 5 PCTUSED 60 INITRANS 1 MAXTRANS 255 TABLE"
 "SPACE "TSP_HOP_D_17" LOGGING (SUBPARTITION "TB_HIS_OPER_20031222_SP1"  TABL"
 "ESPACE "TSP_HOP_D_17", SUBPARTITION "TB_HIS_OPER_20031222_SP2"  TABLESPACE "
 ""TSP_HOP_D_17", SUBPARTITION "TB_HIS_OPER_20031222_SP3"  TABLESPACE "TSP_HO"
 "P_D_17", SUBPARTITION "TB_HIS_OPER_20031222_SP4"  TABLESPACE "TSP_HOP_D_17""
 ", SUBPARTITION "TB_HIS_OPER_20031222_SP5"  TABLESPACE "TSP_HOP_D_17", SUBPA"
 "RTITION "TB_HIS_OPER_20031222_SP6"  TABLESPACE "TSP_HOP_D_17", SUBPARTITION"
 " "TB_HIS_OPER_20031222_SP7"  TABLESPACE "TSP_HOP_D_17", SUBPARTITION "TB_HI"
 "S_OPER_20031222_SP8"  TABLESPACE "TSP_HOP_D_17", SUBPARTITION "TB_HIS_OPER_"
 "20031222_SP9"  TABLESPACE "TSP_HOP_D_17", SUBPARTITION "TB_HIS_OPER_2003122"
 "2_SP10"  TABLESPACE "TSP_HOP_D_17" ), PARTITION "TB_HIS_OPER_20031229" VALU"
 "ES LESS THAN (TO_DATE(' 2003-12-29 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NL"
 "S_CALENDAR=GREGORIAN'))  PCTFREE 5 PCTUSED 60 INITRANS 1 MAXTRANS 255 TABLE"
 "SPACE "TSP_HOP_D_01" LOGGING (SUBPARTITION "TB_HIS_OPER_20031229_SP1"  TABL"
 "ESPACE "TSP_HOP_D_01", SUBPARTITION "TB_HIS_OPER_20031229_SP2"  TABLESPACE "
 ""TSP_HOP_D_01", SUBPARTITION "TB_HIS_OPER_20031229_SP3"  TABLESPACE "TSP_HO"
 "P_D_01", SUBPARTITION "TB_HIS_OPER_20031229_SP4"  TABLESPACE "TSP_HOP_D_01""
 ", SUBPARTITION "TB_HIS_OPER_20031229_SP5"  TABLESPACE "TSP_HOP_D_01", SUBPA"
 "RTITION "TB_HIS_OPER_20031229_SP6"  TABLESPACE "TSP_HOP_D_01", SUBPARTITION"
 " "TB_HIS_OPER_20031229_SP7"  TABLESPACE "TSP_HOP_D_01", SUBPARTITION "TB_HI"
 "S_OPER_20031229_SP8"  TABLESPACE "TSP_HOP_D_01", SUBPARTITION "TB_HIS_OPER_"
 "20031229_SP9"  TABLESPACE "TSP_HOP_D_01", SUBPARTITION "TB_HIS_OPER_2003122"
 "9_SP10"  TABLESPACE "TSP_HOP_D_01" ), PARTITION "TB_HIS_OPER_20040105" VALU"
 "ES LESS THAN (TO_DATE(' 2004-01-05 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NL"
 "S_CALENDAR=GREGORIAN'))  PCTFREE 5 PCTUSED 60 INITRANS 1 MAXTRANS 255 TABLE"
 "SPACE "TSP_HOP_D_02" LOGGING (SUBPARTITION "TB_HIS_OPER_20040105_SP1"  TABL"
 "ESPACE "TSP_HOP_D_02", SUBPARTITION "TB_HIS_OPER_20040105_SP2"  TABLESPACE "
 ""TSP_HOP_D_02", SUBPARTITION "TB_HIS_OPER_20040105_SP3"  TABLESPACE "TSP_HO"
 "P_D_02", SUBPARTITION "TB_HIS_OPER_20040105_SP4"  TABLESPACE "TSP_HOP_D_02""
 ", SUBPARTITION "TB_HIS_OPER_20040105_SP5"  TABLESPACE "TSP_HOP_D_02", SUBPA"
 "RTITION "TB_HIS_OPER_20040105_SP6"  TABLESPACE "TSP_HOP_D_02", SUBPARTITION"
 " "TB_HIS_OPER_20040105_SP7"  TABLESPACE "TSP_HOP_D_02", SUBPARTITION "TB_HI"
 "S_OPER_20040105_SP8"  TABLESPACE "TSP_HOP_D_02", SUBPARTITION "TB_HIS_OPER_"
 "20040105_SP9"  TABLESPACE "TSP_HOP_D_02", SUBPARTITION "TB_HIS_OPER_2004010"
 "5_SP10"  TABLESPACE "TSP_HOP_D_02" ), PARTITION "TB_HIS_OPER_20040112" VALU"
 "ES LESS THAN (TO_DATE(' 2004-01-12 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NL"
 "S_CALENDAR=GREGORIAN'))  PCTFREE 5 PCTUSED 60 INITRANS 1 MAXTRANS 255 TABLE"
 "SPACE "TSP_HOP_D_03" LOGGING (SUBPARTITION "TB_HIS_OPER_20040112_SP1"  TABL"
 "ESPACE "TSP_HOP_D_03", SUBPARTITION "TB_HIS_OPER_20040112_SP2"  TABLESPACE "
 ""TSP_HOP_D_03", SUBPARTITION "TB_HIS_OPER_20040112_SP3"  TABLESPACE "TSP_HO"
 "P_D_03", SUBPARTITION "TB_HIS_OPER_20040112_SP4"  TABLESPACE "TSP_HOP_D_03""
 ", SUBPARTITION "TB_HIS_OPER_20040112_SP5"  TABLESPACE "TSP_HOP_D_03", SUBPA"
 "RTITION "TB_HIS_OPER_20040112_SP6"  TABLESPACE "TSP_HOP_D_03", SUBPARTITION"
 " "TB_HIS_OPER_20040112_SP7"  TABLESPACE "TSP_HOP_D_03", SUBPARTITION "TB_HI"
 "S_OPER_20040112_SP8"  TABLESPACE "TSP_HOP_D_03", SUBPARTITION "TB_HIS_OPER_"
 "20040112_SP9"  TABLESPACE "TSP_HOP_D_03", SUBPARTITION "TB_HIS_OPER_2004011"
 "2_SP10"  TABLESPACE "TSP_HOP_D_03" ), PARTITION "TB_HIS_OPER_20040119" VALU"
 "ES LESS THAN (TO_DATE(' 2004-01-19 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NL"
 "S_CALENDAR=GREGORIAN'))  PCTFREE 5 PCTUSED 60 INITRANS 1 MAXTRANS 255 TABLE"
 "SPACE "TSP_HOP_D_04" LOGGING (SUBPARTITION "TB_HIS_OPER_20040119_SP1"  TABL"
 "ESPACE "TSP_HOP_D_04", SUBPARTITION "TB_HIS_OPER_20040119_SP2"  TABLESPACE "
 ""TSP_HOP_D_04", SUBPARTITION "TB_HIS_OPER_20040119_SP3"  TABLESPACE "TSP_HO"
 "P_D_04", SUBPARTITION "TB_HIS_OPER_20040119_SP4"  TABLESPACE "TSP_HOP_D_04""
 ", SUBPARTITION "TB_HIS_OPER_20040119_SP5"  TABLESPACE "TSP_HOP_D_04", SUBPA"
 "RTITION "TB_HIS_OPER_20040119_SP6"  TABLESPACE "TSP_HOP_D_04", SUBPARTITION"
 " "TB_HIS_OPER_20040119_SP7"  TABLESPACE "TSP_HOP_D_04", SUBPARTITION "TB_HI"
 "S_OPER_20040119_SP8"  TABLESPACE "TSP_HOP_D_04", SUBPARTITION "TB_HIS_OPER_"
 "20040119_SP9"  TABLESPACE "TSP_HOP_D_04", SUBPARTITION "TB_HIS_OPER_2004011"
 "9_SP10"  TABLESPACE "TSP_HOP_D_04" ), PARTITION "TB_HIS_OPER_20040126" VALU"
 "ES LESS THAN (TO_DATE(' 2004-01-26 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NL"
 "S_CALENDAR=GREGORIAN'))  PCTFREE 5 PCTUSED 60 INITRANS 1 MAXTRANS 255 TABLE"
 "SPACE "TSP_HOP_D_05" LOGGING (SUBPARTITION "TB_HIS_OPER_20040126_SP1"  TABL"
 "ESPACE "TSP_HOP_D_05", SUBPARTITION "TB_HIS_OPER_20040126_SP2"  TABLESPACE "
 ""TSP_HOP_D_05", SUBPARTITION "TB_HIS_OPER_20040126_SP3"  TABLESPACE "TSP_HO"
 "P_D_05", SUBPARTITION "TB_HIS_OPER_20040126_SP4"  TABLESPACE "TSP_HOP_D_05""
 ", SUBPARTITION "TB_HIS_OPER_20040126_SP5"  TABLESPACE "TSP_HOP_D_05", SUBPA"
 "RTITION "TB_HIS_OPER_20040126_SP6"  TABLESPACE "TSP_HOP_D_05", SUBPARTITION"
 " "TB_HIS_OPER_20040126_SP7"  TABLESPACE "TSP_HOP_D_05", SUBPARTITION "TB_HI"
 "S_OPER_20040126_SP8"  TABLESPACE "TSP_HOP_D_05", SUBPARTITION "TB_HIS_OPER_"
 "20040126_SP9"  TABLESPACE "TSP_HOP_D_05", SUBPARTITION "TB_HIS_OPER_2004012"
 "6_SP10"  TABLESPACE "TSP_HOP_D_05" ), PARTITION "TB_HIS_OPER_20040202" VALU"
 "ES LESS THAN (TO_DATE(' 2004-02-02 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NL"
 "S_CALENDAR=GREGORIAN'))  PCTFREE 5 PCTUSED 60 INITRANS 1 MAXTRANS 255 TABLE"
 "SPACE "TSP_HOP_D_06" LOGGING (SUBPARTITION "TB_HIS_OPER_20040202_SP1"  TABL"
 "ESPACE "TSP_HOP_D_06", SUBPARTITION "TB_HIS_OPER_20040202_SP2"  TABLESPACE "
 ""TSP_HOP_D_06", SUBPARTITION "TB_HIS_OPER_20040202_SP3"  TABLESPACE "TSP_HO"
 "P_D_06", SUBPARTITION "TB_HIS_OPER_20040202_SP4"  TABLESPACE "TSP_HOP_D_06""
 ", SUBPARTITION "TB_HIS_OPER_20040202_SP5"  TABLESPACE "TSP_HOP_D_06", SUBPA"
 "RTITION "TB_HIS_OPER_20040202_SP6"  TABLESPACE "TSP_HOP_D_06", SUBPARTITION"
 " "TB_HIS_OPER_20040202_SP7"  TABLESPACE "TSP_HOP_D_06", SUBPARTITION "TB_HI"
 "S_OPER_20040202_SP8"  TABLESPACE "TSP_HOP_D_06", SUBPARTITION "TB_HIS_OPER_"
 "20040202_SP9"  TABLESPACE "TSP_HOP_D_06", SUBPARTITION "TB_HIS_OPER_2004020"
 "2_SP10"  TABLESPACE "TSP_HOP_D_06" ), PARTITION "TB_HIS_OPER_20040209" VALU"
 "ES LESS THAN (TO_DATE(' 2004-02-09 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NL"
 "S_CALENDAR=GREGORIAN'))  PCTFREE 5 PCTUSED 60 INITRANS 1 MAXTRANS 255 TABLE"
 "SPACE "TSP_HOP_D_07" LOGGING (SUBPARTITION "TB_HIS_OPER_20040209_SP1"  TABL"
 "ESPACE "TSP_HOP_D_07", SUBPARTITION "TB_HIS_OPER_20040209_SP2"  TABLESPACE "
 ""TSP_HOP_D_07", SUBPARTITION "TB_HIS_OPER_20040209_SP3"  TABLESPACE "TSP_HO"
 "P_D_07", SUBPARTITION "TB_HIS_OPER_20040209_SP4"  TABLESPACE "TSP_HOP_D_07""
 ", SUBPARTITION "TB_HIS_OPER_20040209_SP5"  TABLESPACE "TSP_HOP_D_07", SUBPA"
 "RTITION "TB_HIS_OPER_20040209_SP6"  TABLESPACE "TSP_HOP_D_07", SUBPARTITION"
 " "TB_HIS_OPER_20040209_SP7"  TABLESPACE "TSP_HOP_D_07", SUBPARTITION "TB_HI"
 "S_OPER_20040209_SP8"  TABLESPACE "TSP_HOP_D_07", SUBPARTITION "TB_HIS_OPER_"
 "20040209_SP9"  TABLESPACE "TSP_HOP_D_07", SUBPARTITION "TB_HIS_OPER_2004020"
 "9_SP10"  TABLESPACE "TSP_HOP_D_07" ) )"
IMP-00003: ORACLE error 922 encountered
ORA-00922: missing or invalid option
Import terminated successfully with warnings.

Thanks for your patience Tom.
Followup:
what version of the DB are you importing into
Rating: 5
version 8.1.7 January 26, 2004
Reviewer: Fernando Sánchez from Madrid, Spain
The same version than the export: 8.1.7 (in fact we are testing on the same DB
dropping the subpartioned table before the import, although the problem isn't
because the object already existed).
Followup:
Umm NO -- not the same version:

Export file created by EXPORT:V09.02.00 via conventional path
import done in US7ASCII character set and AL16UTF16 NCHAR character set
import server uses WE8ISO8859P1 character set (possible charset conversion)
. importing LOGAN3_ADMIN's objects into LOGAN3_ADMIN

You exported from 9.2

You are trying to import into 8.1.7

that'll NEVER work.  subpartition templates didn't exist back then.

You have to use 8i's export tool against 9i in order to generate a dmp file with
SQL that 8i can understand.
Rating: 5
sorry January 26, 2004
Reviewer: Fernando Sánchez from Madrid, Spain
Sorry Tom.

I haven't reported the problem right -meanwhile trying to solve this problem,
I'm working with another DB version 8.1.7, and that took me to say it bad- but
in my import problem both DB are 9.2.0 (the same DB)
Followup:
show me the header from import that proves it please.

And -- you are using EE (enterprise) to EE, not EE to SE (standard) right (SE
does not have partitioning available at all)
Rating: 5
here it is January 26, 2004
Reviewer: Fernando Sánchez from Madrid, Spain
1. Import log beginning ($more historico_imp.log)

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

Export file created by EXPORT:V09.02.00 via conventional path
import done in US7ASCII character set and AL16UTF16 NCHAR character set
import server uses WE8ISO8859P1 character set (possible charset conversion)
. importing LOGAN3_ADMIN's objects into LOGAN3_ADMIN
IMP-00017: following statement failed with ORACLE error 922:
 "CREATE TABLE "TB_HIS_OPER" ("SQ_HOP_01" NUMBER(10, 0) NOT NULL ENABLE, "CO_"
 "MSDN_V" VARCHAR2(9) NOT NULL ENABLE, "CO_OPER_V" VARCHAR2(16) NOT NULL ENAB"
 "LE, "DS_OPER_V" VARCHAR2(64) NOT NULL ENABLE, "FX_OPER_D" DATE NOT NULL ENA"
 "BLE, "CO_USER_V" VARCHAR2(15), "CO_REQU_V" VARCHAR2(15) NOT NULL ENABLE, "D"
 "S_REAS_V" VARCHAR2(64), "CO_TIP_PROD_V" VARCHAR2(8) NOT NULL ENABLE, "NO_CO"
 "D_PROD_V" VARCHAR2(8) NOT NULL ENABLE, "DS_PROD_V" VARCHAR2(32), "SQ_PROD_V"
 "" VARCHAR2(10) NOT NULL ENABLE, "DS_INI_PROD_V" VARCHAR2(64), "TO_QUAN_N" N"
 "UMBER(10, 0), "FX_INI_VALI_D" DATE, "FX_FIN_VALI_D" DATE)  PCTFREE 5 PCTUSE"
 "D 60 INITRANS 1 MAXTRANS 255 TABLESPACE "TSP_HOP_D_01" LOGGING PARTITION BY"
 " RANGE ("FX_OPER_D" )  SUBPARTITION BY HASH ("CO_MSDN_V" )  SUBPARTITIONS 1"
 "0 SUBPARTITION TEMPLATE( SUBPARTITION "SP1" VALUES () , SUBPARTITION "SP2" "
 "VALUES () , SUBPARTITION "SP3" VALUES () , SUBPARTITION "SP4" VALUES () , S"
 "UBPARTITION "SP5" VALUES () , SUBPARTITION "SP6" VALUES () , SUBPARTITION ""
 "SP7" VALUES () , SUBPARTITION "SP8" VALUES () , SUBPARTITION "SP9" VALUES ("
 ") , SUBPARTITION "SP10" VALUES ()  ) (PARTITION "TB_HIS_OPER_20031027" VALU"
 "ES LESS THAN (TO_DATE(' 2003-10-27 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NL"
 "S_CALENDAR=GREGORIAN'))  PCTFREE 5 PCTUSED 60 INITRANS 1 MAXTRANS 255 TABLE"
 "SPACE "TSP_HOP_D_09" LOGGING (SUBPARTITION "TB_HIS_OPER_20031027_SP1"  TABL"
 "ESPACE "TSP_HOP_D_09", SUBPARTITION "TB_HIS_OPER_20031027_SP2"  TABLESPACE "
 ""TSP_HOP_D_09", SUBPARTITION "TB_HIS_OPER_20031027_SP3"  TABLESPACE "TSP_HO"
 "P_D_09", SUBPARTITION "TB_HIS_OPER_20031027_SP4"  TABLESPACE "TSP_HOP_D_09""
 ", SUBPARTITION "TB_HIS_OPER_20031027_SP5"  TABLESPACE "TSP_HOP_D_09", SUBPA"
 "RTITION "TB_HIS_OPER_20031027_SP6"  TABLESPACE "TSP_HOP_D_09", SUBPARTITION"
 " "TB_HIS_OPER_20031027_SP7"  TABLESPACE "TSP_HOP_D_09", SUBPARTITION "TB_HI"
 "S_OPER_20031027_SP8"  TABLESPACE "TSP_HOP_D_09", SUBPARTITION "TB_HIS_OPER_"
 "20031027_SP9"  TABLESPACE "TSP_HOP_D_09", SUBPARTITION "TB_HIS_OPER_2003102"
 "7_SP10"  TABLESPACE "TSP_HOP_D_09" ), PARTITION "TB_HIS_OPER_20031103" VALU"
 "ES LESS THAN (TO_DATE(' 2003-11-03 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NL"
 "S_CALENDAR=GREGORIAN'))  PCTFREE 5 PCTUSED 60 INITRANS 1 MAXTRANS 255 TABLE"
 "SPACE "TSP_HOP_D_10" LOGGING (SUBPARTITION "TB_HIS_OPER_20031103_SP1"  TABL"
 "ESPACE "TSP_HOP_D_10", SUBPARTITION "TB_HIS_OPER_20031103_SP2"  TABLESPACE "
 ""TSP_HOP_D_10", SUBPARTITION "TB_HIS_OPER_20031103_SP3"  TABLESPACE "TSP_HO"
 "P_D_10", SUBPARTITION "TB_HIS_OPER_20031103_SP4"  TABLESPACE "TSP_HOP_D_10""
 ", SUBPARTITION "TB_HIS_OPER_20031103_SP5"  TABLESPACE "TSP_HOP_D_10", SUBPA"
 "RTITION "TB_HIS_OPER_20031103_SP6"  TABLESPACE "TSP_HOP_D_10", SUBPARTITION"
 " "TB_HIS_OPER_20031103_SP7"  TABLESPACE "TSP_HOP_D_10", SUBPARTITION "TB_HI"
 "S_OPER_20031103_SP8"  TABLESPACE "TSP_HOP_D_10", SUBPARTITION "TB_HIS_OPER_"

.......
.......
.......

2. Export log beginning (more historico_exp.log)

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
Export done in US7ASCII character set and AL16UTF16 NCHAR character set
server uses WE8ISO8859P1 character set (possible charset conversion)

About to export specified tables via Conventional Path ...
. . exporting table                    TB_HIS_OPER
. . exporting composite partition TB_HIS_OPER_20031027
. . exporting subpartition    TB_HIS_OPER_20031027_SP1
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031027_SP2
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031027_SP3
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031027_SP4
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031027_SP5
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031027_SP6
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031027_SP7
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031027_SP8
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031027_SP9
                                                            0 rows exported
. . exporting subpartition   TB_HIS_OPER_20031027_SP10
                                                            0 rows exported
. . exporting composite partition TB_HIS_OPER_20031103
. . exporting subpartition    TB_HIS_OPER_20031103_SP1
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031103_SP2
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031103_SP3
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031103_SP4
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031103_SP5
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031103_SP6
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031103_SP7
                                                            0 rows exported
. . exporting subpartition    TB_HIS_OPER_20031103_SP8
                                                            0 rows exported

.......
.......
.......
Followup:
contact support and reference <Bug:2806270>
Rating: 5
Export only triggers January 26, 2004
Reviewer: tanmoy datta choudhury from Bangalore
Is it possible to import only triggers from database.
Followup:
no, you can however do this:

ops$tkyte@ORA920PC> select trigger_name from user_triggers where table_name =
'T';

no rows selected

ops$tkyte@ORA920PC> !imp userid=/ tables=t ignore=y grants=n indexes=n rows=n
constraints=y

Import: Release 9.2.0.4.0 - Production on Mon Jan 26 09:44:42 2004

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

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
Import terminated successfully without warnings.

ops$tkyte@ORA920PC> select trigger_name from user_triggers where table_name =
'T';

TRIGGER_NAME
------------------------------
T_TRIGGER

1 row selected.
Rating: 5
Very helpful as always February 27, 2004
Reviewer: Steve Montgomerie from Florida
Tom,
We are doing a data center move which will take us from Windows to UNIX. I've
been working on nailling doing the exp/imp to build the new instance in short
time during
'go live' weekend. I grabbed a couple of really great suggestions from this
thread.

Thanks

Steve
Rating: 2
Copy Data from DB to Another DB on Same Network March 30, 2004
Reviewer: Richard from Oxfordshire, UK
Hi,

We have a live 8i DB and wish to upgrade to 9i. We have a data warehouse setup,
which entails populating fact tables each day. During the "trying all the new
bells & whistles" stage, we wish to load the 9i DB with data from the 8i DB on a
daily basis; the schemas in both DBs are almost identical, but some tables are
partitioned in the 9i DB, and not so in the 8i DB. We cannot populate the 9i DB
as we do the 8i DB, owing to various time and other constraints. Is it sane to
populate the 9i DB from the 8i DB using INSERT INTO statements, or is there a
better way to go?
Followup:
well for testing, why would you have to sync up the databases every day?  Just a
thought -- that perhaps a one time sync is all that is needed to see that
"things work" (or not).

You would also have to test the load procedures (which will be different since
you have slightly different schemas).  I would think it would take less time to
run the new load routines against 9i than it would to copy the entire database
from 8i into 9i? (so it seems like "time" would be on your side if you just
re-ran the loads against 9i?).  What better way to know the load routines work
than to test them daily on this new machine?

It would probably not be feasible to rebuild a DW (implies "sizable") over the
network using insert as selects.  Not without killing the network and slamming
the 8i database.
Rating: 4
Thanks for the Feedback! March 30, 2004
Reviewer: Richard from Oxfordshire, UK
Many thanks for the feedback.

It has been deemed by better folk than me to do the data loading on a daily
basis, so that's that.

I think I'll see how effective it would be to load the 9i DB from the CSV files
that the 8i DB is loaded from, then do something to see if they have same number
of rows in their tables, etc.

Cheers!
Followup:
that does seem to make more sense.  At least that way -- you'd be testing and
proving the new processes work in the same manner as the old stuff (and if not,
you'd be well positioned to figure out "why" before going production!)
March 30, 2004 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://download-east.oracle.com/docs/cd/A81042_01/DOC/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,'*') );
Rating: 3
How can you transfer normal table to IOT March 23, 2004
Reviewer: Jelena from Germany
IOT seems to be nice way to organize data in my case. We have 6 million rows in
a table, which is always accessed via one index, only 5 columns and 3 are
already in index, so IOT seems to be perfect solution. But i have two concerns,
how can i actually transfer this table to IOT?
if i create new table and do: insert into ... select * ... on 6 million rows it
will eventually crash (after eating up all rollback segments) so is there any
fancy way to change the organization. Or if not, if i export table, and drop and
create with the same name, but different organization, will import work?
Is there any reason (intensive insert/update...) or any other reason why i
should _not_ use IOT?
And one more question, can you partition IOT? does it make sense at all?
Thanks!
Jelena
Followup:
it will not crash, if you size your RBS for the work you need to do (i work with
10 million or more wide rows on my laptop frequently for heavens sake.  My
laptop!)

You can partition an IOT, yes.
Yes, it can make sense (but 6million rows is pretty small still)

If you use

"CREATE TABLE IOT ( columns..., primary key(...) ) organization index
as
select...."

You'll find you use just about, oh, 1 rollback block :)   you are worrying about
something that isn't going to happen (and in archive log mode, you could wip a
nologging in there after consulting your DBA to schedule a backup of this new
data asap, that would remove any redo log issues you might think you have)

big_table@ORA9IR2> desc test
 Name                          Null?    Type
 ----------------------------- -------- --------------------
 ID                                     NUMBER
 OBJECT_ID                     NOT NULL NUMBER
 CREATED                       NOT NULL DATE
 OWNER                         NOT NULL VARCHAR2(30)
 OBJECT_NAME                   NOT NULL VARCHAR2(30)

big_table@ORA9IR2> select count(*) from test;

  COUNT(*)
----------
   6000000

big_table@ORA9IR2> create table iot
  2  ( id, object_id, created, owner, object_name, primary
key(id,object_id,created))
  3  organization index
  4  as
  5  select * from test;

Table created.

Elapsed: 00:04:21.35

and it used one block of UNDO!
Rating: 4
Partitioning of an IOT March 25, 2004
Reviewer: Jelena from germany
Hi Tom,
thanks for your answer, it's really helpfull.
I was worried that i have to do
"insert into ... select..."
on 6 mil rows, which would mean for sure some downtime for our production (since
we are running 7x24 system, its not so easy to organize) "Create table ... as
select" looks perfect!
Besides that, you said that it makes sense to partition IOT (not in my case but
in general). My question is: since it's index organized table, and if you
partition it, does oracle create "index organization" in each partition (ie the
effect of local index) or it's still organized as global index, and in that case
what's the use of partitioning?
Thanks,
Jelena
Followup:
yes, it is many IOT's if you partition it.
Rating: 5
range partitioned, IOT, compressed table March 25, 2004
Reviewer: cosmin ioan from Florida
hi Tom,
on a 9iR2, I have a thin (3 column) table of about 1 billion rows and growing
fast.  I'm considering a range partitioned, compressed, IOT scenario.

Is this asking too much?  :-)
I've already build the partitioned IOT table and it looks like the performance
(and not only) is greatly improved, from many aspects which I won't dwell on.

I'll do some tests on the feasibility/speed etc of the COMPRESSED scenario (on
top of the above) but my main question is IF (if presuming this yields better
results) this is a STABLE long term, "production" solution, ie, if there are no
known bugs, "gotchas" or otherwise issues (for this scenario) that would be
considered as major showstoppers or of major future potential risk.
Followup:
3 skinny columns = perfect for IOT

1,000,000,000 rows = sounds like a partition candidate

if leading column(s) are repetitive -- compression = probable good idea....

I'm not aware of any show stopping gotchas, no.
Rating: 5
...just to be sure March 26, 2004
Reviewer: cosmin ioan from FL
hi Tom,
thanks much for the feedback but I want to make certain I've explained myself
clearly, as this is crital for our environment:

what I want it an INDEX ORGANIZED, RANGE PARTITIONED AND COMPRESSED table, all
in one, like below (I'll find the compressed syntax somewhere)  [and thanks for
the tip on the 'first repetitive column']

It sounded for a second, if I understood right, that you were advocating either
IOT or a partitioned table but I was thinking of wanting both AND the
compression and furthermore was concerned about long term chances of corruption,
etc.  We have plenty memory, bazillion of RAM so the compression time would not
be of concern, I believe (but still need to run a benchmark).  I'm more
concerned about corruption and the like, PLUS if I issue a TRUNCATE (which
incidentally I have yet to get the syntax on the truncation of a partition) if I
have to rebuild the ENTIRE table or just that IOT'ed partition :-)

Basically, has the above scenario been approached by any major companies with
success and little administrative headaches.
:-)

create table myparttable
    (
      field1        number(10),
      field2       number(10),
      field3       number(10),
   constraint table_pk primary key (field1,field2,field3)
     organization index nologging
    partition by range(field1)
    (partition p7 values less than  (8) tablespace yadayada.....

This alone will **vastly** improve our processing however, for the kicker, are
there new schema/schematic/storage features in 10g (other than compiled PL/SQL
which we try to use as little as we can-- mostly pure sql or analytical funcs
for speed purposes) that would be worth considering to make this even more of a
speed deamon as a "dream of a point in time" where we'd be swamped processing
tens if not hundreds of billions of operations.
Followup:
nope, I was saying -- in a building on previous statement sort of fashion --
that an IOT, that was partitioned and perhaps compressed would be a very good
idea.

I am aware of large partitioned IOTs (not sure on the compression, that is sort
of an after thought in many cases) and they are running successfully.
Rating: 5
partitioned IOT; compression vs cost March 29, 2004
Reviewer: cosmin from FL
hi Tom,
thx a bunch for all the info.
I've compressed the range partitioned IOT'ed table (with the most repeatable
value columns first) and yeah, it compresses to about 1/2 the size which is
really cool.  Now, one thing that I don't understand is why the actual cost (of
various simple operations; after 'analysis' of partitions) is about a
thousand-fold higher than on the non-compressed counterpart table.

Do I need to be looking into histograms and the like or is there something else,
more specific compression-related that I'm overlooking?

I'll play in the next few months w/ 10g, just for kicks but thought I'd inquire
about this on 9iR2 as this is the release we're deploying on next.
Followup:
do you have an example of what you mean?  I show full scan and index scan costs
that are lower for the compressed, partitioned IOT -- can you help me make this
example mimick what you are seeing?

ops$tkyte@ORA9IR2> create table t1
  2  ( object_type, owner, object_name, object_id,
  3    constraint t1_pk primary key(object_type,owner,object_name))
  4  partition by range (object_type)
  5  ( partition values less than ( 'S' ),
  6    partition values less than ( MAXVALUE )
  7  )
  8  as
  9  select object_type, owner, object_name, max(object_id)
 10    from all_objects
 11   group by object_type, owner, object_name
 12  /

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create table t2
  2  ( object_type, owner, object_name, object_id,
  3    constraint t2_pk primary key(object_type,owner,object_name))
  4  organization index compress 2
  5  partition by range (object_type)
  6  ( partition values less than ( 'S' ),
  7    partition values less than ( MAXVALUE )
  8  )
  9  as
 10  select * from t1
 11  /

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> analyze table t1 compute statistics for table for all
indexes;

Table analyzed.

ops$tkyte@ORA9IR2> analyze table t2 compute statistics for table for all
indexes;

Table analyzed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> set autotrace traceonly explain
ops$tkyte@ORA9IR2> select * from t1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=23 Card=30543 Bytes=1374435)
   1    0   PARTITION RANGE (ALL)
   2    1     TABLE ACCESS (FULL) OF 'T1' (Cost=23 Card=30543 Bytes=1374435)

ops$tkyte@ORA9IR2> select * from t2;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=16 Card=30543 Bytes=977376)
   1    0   PARTITION RANGE (ALL)
   2    1     INDEX (FAST FULL SCAN) OF 'T2_PK' (UNIQUE) (Cost=16 Card=30543
Bytes=977376)

ops$tkyte@ORA9IR2> select object_id from t1 where owner = 'X' and object_type =
'Y' and object_name='Z';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=44)
   1    0   TABLE ACCESS (BY GLOBAL INDEX ROWID) OF 'T1' (Cost=2 Card=1
Bytes=44)
   2    1     INDEX (UNIQUE SCAN) OF 'T1_PK' (UNIQUE) (Cost=1 Card=16890)

ops$tkyte@ORA9IR2> select object_id from t2 where owner = 'X' and object_type =
'Y' and object_name='Z';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=32)
   1    0   INDEX (UNIQUE SCAN) OF 'T2_PK' (UNIQUE) (Cost=1 Card=1 Bytes=32)

ops$tkyte@ORA9IR2> set autotrace off
Rating: 3
Deciding on IOT March 30, 2004
Reviewer: Dhrubo from Kolkata,India
Hi Tom,
       I was reading your book Expert one on One Oracle.I read about the four
main conditions when i can possibly use IOT.Say i have an Address table where i
store Address information both for my customers as well as sales force - the
structure of the table is like this :
AddressId NUMBER,
CustomerId NUMBER,
SaleForceId NUMBER,
Street1 VARCHAR2(100),
Street2 VARCHAR2(100),
Street3 VARCHAR2(100),
AddressType CHAR(1),
City NUMBER,
Zip VARCHAR2(10),
State NUMBER,
Country NUMBER

Now am i supposed to use IOT in this case.Or traditional heap organized table
with AddressId as my primary key and a bitmap index on AddressType which can
have possibly three value and my Address table can have millions of rows so the
cardinality is < 0.1.What is your suggestion?
Followup:
I cannot really answer that.

do you care that addressid = 55 is right next to addressid=56?  an IOT would
give you that....

that looks a bit fat for an IOT.  Probably a conventional heap table .

As for addresstype, why would you even need to index it????
Rating: 5
partitioned IOT; compression vs cost; followup March 30, 2004
Reviewer: cosmin from FL
hi Tom,
I was able to replicate your test/results almost identically; I rebuilt my table
and indeed the COST went down substantially, well below the cost of the
uncompressed table  (when I analyzed the index as well, in addition to the
table, I think that was the issue) HOWEVER, the actual elapsed time of a simple
select or a count(*) would not finish in minutes (vs. milliseconds).  I guess
toying w/ TKPROF might come in handy.  Also, the funny thing is that the space
taken by a simple compressed partition vs the identical non-compressed partition
slightly increased, even though I started the table definition w/ the most
frequent value columns first.

That being said, I don't think we'll go to compression this release as we're
sometimes doing intensive write IO and I don't think it's feasible, besides the
fact that we're doing the upgrade v. soon and we're now at 'crunch time' and a
lot more research is required on my part to get make this a strong case for our
management.

I'll toy w/ compression more in the future, perhaps also on 10g however for now,
Partitioned IOT should give us substantial performance increase;

I will come back w/ more feedback/questions, in the next few weeks I hope, after
I put out all the current fires and I document myself a bit more  :-)

thx much for all your help & looking forward for your book on 10g  :-)
Cosmin
March 30, 2004 -- 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
Rating: 3
connect 9.2 to 7.x January 15, 2004
Reviewer: Jones from Canada
you cannot, in production, connect 9.2 to 7.x

Is there any work around ?  or any suggestions ?
example - export the data into flat files from 7.x, ftp to the 9.2 box and load
the data files into 9.2

Your response is highly appreciated
Followup:
7.x has been out of support for so long -- so so long.  There is no way to
reliably connect 7.x and 9.2 (and above).
Rating: 4
March 16, 2004
Reviewer: A reader
I created db link using the below statement. (ver 9.2.0)

create public database link DL_DEVDMDB connect to GECIS_EDW identified by
"edw~123"  using
'(DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = giahyd004)(PORT = 1521))
    )
    (CONNECT_DATA = (SID = devdmdb)(SERVER=DEDICATED))
  )
'

I dont have the any entry related to this server in TNS ( Hope not required).

Pls confirm if i need to do any changes in the server setting or this is all set
to create a db link.

The problem is, i created the link 2 days back, but today i dont see it. Could
have been deleted by some one or i missed out anything.

Sorry for my ignorance. Thanks for your time and consideration
Followup:
if the link "isn't there" someone dropped it on you.

Yes, that works -- you do not need to have a tns entry, you can put it right in
there like that.
Rating: 5
Thanks ... March 16, 2004
Reviewer: A reader
Rating: 4
link puzzle March 30, 2004
Reviewer: robert from CT
tom, How come I have referenced link names not in the all_db_links list ?
thanks

SQL> select REFERENCED_LINK_NAME from dba_DEPENDENCIES where
REFERENCED_LINK_NAME is not null order by 1;

REFERENCED_LINK_NAME
--------------------------------------------------------------------------------
CTRACK.ZITE.COM
CTRACK.ZITE.COM
DBLINK_HFOD.ZITE.COM
JSH_EPDB_LNK.ZITE.COM
JSH_EPDB_LNK.ZITE.COM
JSH_EPDB_LNK.ZITE.COM
LNK_COMPANY.ZITE.COM
LNK_COMPANY.ZITE.COM
LOADPOC.ZITE.COM
LOADPOC.ZITE.COM

10 rows selected

SQL> select db_link from all_db_links order by 1;

DB_LINK
--------------------------------------------------------------------------------
JSH_EPDB_LNK.ZITE.COM
LNK_CNTXT_USER.ZITE.COM
LNK_COMPANY.ZITE.COM
LNK_HFOD_LIVE.ZITE.COM
LOADPOC.ZITE.COM
MANDAREGION_DBLINK.ZITE.COM
NEWS_DBLINK.ZITE.COM
TEST.ZITE.COM

8 rows selected
Followup:
all_db_links are dblinks you are allowed to see the definition of.

scott@ORA9IR2> select count(*) from all_db_links;

  COUNT(*)
----------
         0

scott@ORA9IR2> @connect /
Connected.
ops$tkyte@ORA9IR2> select count(*) from all_db_links;

  COUNT(*)
----------
         1

different people get to see different things.
March 30, 2004 Omar -- Thanks for the question regarding "Data Dict. vs Local tablespaces", version 8.1.6
You Asked
Hi Tom,
   Is it right that when are create your tablespaces as local ,
you no
longer bother with the Pctused, pctincrease when creating
objects in
those tablespace, and if is not in what cases you should bother
whith
them.

with best regards
and we said...
pctincrease is ignored in locally managed tablespaces.  PCTUSED
has to do with space utilization on a block and freelists and is
not affected by locally managed tablespaces (that is, pctused is
the same as it always was, pctincrease is ignored).

In a locally managed tablespace you either:

o have every extent be the same size.  This is UNIFORM
allocation.  Each and every extent will be the same.

o allow us to size the extents according to our algorithm.
pctincrease does not apply in this case.
Review & Followup

Rating: 4
pctused in not taking effect March 30, 2004
Reviewer: Manish Upadhyay from Madison ,WI, USA
Even if I created table t with pctused 40, Database does not register it. Is it
becuase SEGMENT_SPACE_MANAGEMENT  is AUTO?

MAN@EM9I> drop table t;

Table dropped.

MAN@EM9I> ed
Wrote file afiedt.buf

  1  CREATE TABLE T
  2  (
  3    X  INTEGER,
  4    Y  CHAR(1000 BYTE)                            DEFAULT 'x'
  5  )
  6  TABLESPACE USERS
  7  PCTUSED    40
  8  PCTFREE    10
  9  INITRANS   1
 10  MAXTRANS   255
 11  STORAGE    (
 12              INITIAL          64K
 13              MINEXTENTS       1
 14              MAXEXTENTS       2147483645
 15              PCTINCREASE      0
 16              BUFFER_POOL      DEFAULT
 17*            )
MAN@EM9I> /

Table created.

MAN@EM9I>  exec print_table('select * from user_tables where table_name=''T''');
TABLE_NAME                    : T
TABLESPACE_NAME               : USERS
CLUSTER_NAME                  :
IOT_NAME                      :
PCT_FREE                      : 10
PCT_USED                      :
INI_TRANS                     : 1
MAX_TRANS                     : 255
INITIAL_EXTENT                : 65536
NEXT_EXTENT                   :
MIN_EXTENTS                   : 1
MAX_EXTENTS                   : 2147483645
PCT_INCREASE                  :
FREELISTS                     :
FREELIST_GROUPS               :
LOGGING                       : YES
BACKED_UP                     : N
NUM_ROWS                      :
BLOCKS                        :
EMPTY_BLOCKS                  :
AVG_SPACE                     :
CHAIN_CNT                     :
AVG_ROW_LEN                   :
AVG_SPACE_FREELIST_BLOCKS     :
NUM_FREELIST_BLOCKS           :
DEGREE                        :          1
INSTANCES                     :          1
CACHE                         :     N
TABLE_LOCK                    : ENABLED
SAMPLE_SIZE                   :
LAST_ANALYZED                 :
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                 :
DEPENDENCIES                  : DISABLED
COMPRESSION                   : DISABLED
-----------------

PL/SQL procedure successfully completed.

MAN@EM9I>  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
DEF_TAB_COMPRESSION           : DISABLED
-----------------

PL/SQL procedure successfully completed.

MAN@EM9I>
Followup:
when using ASSM, freelists, freelist groups, pctused are "obsolete".  ASSM was
designed to make them "obsolete" (self managing)
March 30, 2004 Duke -- Thanks for the question regarding "Database Developer Certification", version 8.1.7
You Asked
Tom-- First, let me say that I became a big fan of yours because you advocate
and practice benchmarking.  When I first started as a database
modeler/developer/architect, I lost a some arguments because the DBAs at my
former employer collectively argued against my ideas, touting their lengthy
experience while sneering at the concept of benchmarking (I ultimately left
because of that attitude).  Benchmarking, to me, is the difference between
computer "science" and computer "quackery".

My question is this:  what certifications / training do you recommend for
becoming a database developer like yourself?  Oracle seems to offer lots of DBA
training, but the developer certification track seems more focused on Forms, and
doesn't seem to offer the depth your website and 2 books offer.  I've been
working my way through your books for the past 6 months (and I'm far from
completing them!).  It's made a world of difference to my professional
abilities, but are there any formal courses you'd recommend?  A deep and
respectful thank you for your work!
-- Duke
and we said...
I have no certifications myself, short of the "school of life".

I find answering questions, participating in the discussions, taking part in the
community to be the biggest benefit.

groups.google.com -> comp.databases.oracle.*

otn.oracle.com -> discussion forums

reading dbazine.com or any of the hundreds of sites out there and asking
questions (eg: read an article, find something you don't think "is right", setup
a test case, put it on the groups and see what people say).

That is the way I learn myself (learn, present tense)

I'll publish this to see if anyone wants to comment further.
Review & Followup

Rating: 4
What? You dont have certifications?? March 29, 2004
Reviewer: Venkatesh from India
Tom,

I am really startled when i read that you dont have any certifications.

Yes i do agree that, we can learn a lot from helping people in resolving their
technical issues, joining the discussions, reading articles, etc.

But many organizations ask certified people. What do you say about this? Even
people with strong technical skills are rejected just because they are not
certified.

I hope u have a justified reply.

Thanx n regards
Venkatesh
Followup:
no one that works for me has them either....
Rating: 5
My 2 cents March 29, 2004
Reviewer: Marcio from Brazil
My two cents on this thread.

o Have a good Mentor

o DIY (Do it yourself) as whenever as possible

o Try solve any problem post on any forums. This gave me knowledge to solve my
own problems when they happen.
Followup:
exactly -- the mentor part is very relevant.
Rating: 5
Thanks! March 29, 2004
Reviewer: Robert from Memphis, USA.
Tom,

Thanks for being a faithful and gracious mentor to a lot of us!

P.S. Duke: "computer science" vs. "computer quackery" good one! very funny.

Robert.
Rating: 3
practice makes perfect..... March 29, 2004
Reviewer: denni50 from NA
The only way to learn this stuff is 'use-it' and 'do-it'. I'm in my third year
working with Oracle and everyday is a "WOW" day for me...I learn something today
I didn't know yesterday. Tom's website and books(other Oracle forums as well)
provide real-world problems with real-world solutions that you can take and put
into practice yourself.

There's nothing like 'hands-on' training and that is what you get with Tom.

I would never be where I am today (which is a whole lot more than where I was 2
years ago) if there was no Tom, books or website.

I recall one Oracle Professor telling us students..."when you come across a
problem and need help there's only one website you need to go to 'AskTom.com'"
...we all scrambled to our class room workstations and logged on to see what
this AskTom website was all about.

for what it's worth...that's my 2 cents.
Rating: 5
March 29, 2004
Reviewer: Alberto Dell'Era from Milan, Italy
> I find answering questions, participating in the discussions, taking part in
> the community to be the biggest benefit.

I agree - I have started answering questions on a small italian-language forum
and i've discovered that I very often learn more by providing an answer than the
other way around. It seems illogical at first sight, but that's the way it works
("when teaching, the Teacher learns more than the Pupil" as we say in Italy).

I think that a question is a kind of small-scale work assignment - a way of
expanding or perfecting your knowledge on a small real-case problem, and so
getting more experienced (especially since i always provide a test case as you
do).
Then, of course, if the answer is well received, you get the satisfaction coming
from a work well done, and perhaps a bit of recognition.

Strange that no one else has yet commented on this; that's the main part of your
answer IMHO ...

Just my 2 Eurocents.
bye
Alberto
Followup:
....I agree - I have started answering questions on a small italian-language
forum and i've discovered that I very often learn more by providing an answer
than the other way around.

well said -- people don't believe me when they ask me "how did you learn so
much".  it was by answering questions on the newsgroups starting in 1994.  You
realize how much you DON'T know that way....

it is that i credit with much -- answering the questions.  It is like doing a
crossword puzzle -- gets the brain going in the morning.
Rating: 5
March 29, 2004
Reviewer: Alberto Dell'Era from Milan, Italy
> It is like doing a crossword puzzle -- gets the brain going in the morning.

Especially with SQL, it's like solving a differential equation - playing in a
perfect world of pure logic or mathematics (well, almost).

That's the reason I'm very fond of Oracle - it's always logical and predictable,
and open as Nature is - a test case being like an experiment in a Physics class,
and the kernel the subject under study; building a solution on it being like
exploiting the laws of mechanics to build a car.

It's one of the last sw platforms of this kind - almost all the others requiring
you to act like a Graduated Trained Monkey, pushing buttons here and there
without really knowing what you're doing. Please preserve this ecosystem ;-)

Alberto
Rating: 4
My 2.5 cents... March 29, 2004
Reviewer: Kashif from Reston, VA
Recently, while doing the interviewing rounds, I came across a company (actually
a financial giant) that used Brainbench.com as a skills assessment test. And I
must say that quite a few of those questions had me thinking, which is in stark
contrast to the Oracle Certification exams that an experienced developer can ace
blindfolded (maybe I exaggerate, but I'm sure other certified professionals know
what I mean). So my suggestion would be to find out how valuable that
certification is to the companies you're targeting, and get certified from them.

As far as becoming a better database developer goes, besides visiting the PL/SQL
developer's mecca i.e. Tom's website, I'd suggest spending a lot of time going
through the Oracle manuals, it's amazing what the documentation already
explains, and I always keep reminding myself to go back to the docs for a solid
understanding of various concepts.

And finally, maybe picking a book or two by Feuerstein, although don't take all
his words to be gospel, there's quite a bit of stuff he explains that is more
his preference than the correct way to do it. You might also want to check out
Connor's book, "Mastering Oracle PL/SQL: Practical Solutions". I haven't read it
myself, but if his postings over here and on Google are anything to go by then
that book's definitely worth the buy.

Enough said.

Kashif
Rating: 4
Database Developer Certification March 29, 2004
Reviewer: Gordon from Greensboro, NC
Kashif from Weston hit the nail on the head when he contrasted Brainbench with
the Oracle certification exams. I, too, have been out there looking lately and
have had similar thoughts about measurement metrics between the official Oracle
exams and the standards that companies are using to weed out potential
employees. I have taken all exams but 1Z0-033 on the 9i OCP track and felt a bit
intimidated by some of the Brainbench questions.

On the bright side, the comment Kashif had about the Oracle documentation I
couldn't more strongly agree with. I bought an Oracle press book for each of the
exams (bought them all at one time..) before I realized that the depth of the
Oracle 9i documentation is what is required to really learn the topics covered
on the certification exams. I have spent months actually reading and trying
examples from the Administrators Guide, the SQL Reference, etc prior to taking
the exams and I would not recommend going into an exam without that level of
preparation. Yeah, I do need a life, but that will come after certification...
Rating: 3
To Alberto March 29, 2004
Reviewer: denni50 from NA
Well I have to differ with you especially in the realm of Quantum
Mechanics..Albert Einstein was quoted as saying..."God does not play with dice".

The laws of nature are far from predictable and logical, they are random and
chaotic at this level of elementary particle physics.
Einstein remained forever conflicted with his dream of producing a Unified Field
Theory linking QM and the General Theory of Relativity.

how's that for getting the synapses going in the morning.
Rating: 5
to denni50 March 29, 2004
Reviewer: Alberto Dell'Era from Milan, Italy
Morning ? It's 12PM here in Italy ;-)

Well, definitely Oracle is built on a scale much bigger than that of an electron
or a quark, and much smaller than the Galaxy or the compressed space of a black
hole - i think that we can apply the (approximate) laws of classical mechanics
to it ... or to its cpus and ram devices ;-)

I've followed the joke, only to stress that anyone really interested in
Informatic as a science, not just as a way of earning your meals, is likely to
enjoy Oracle much more than other play-with-me-but-don't-open-my-hood
"platforms".

'nite
Alberto

PS May i recommend the book "Black Holes, Wormholes & Time Machines" by J. S.
Al-Khalili ? In that book, there are a few examples of QM and GR "links",
including the "black hole evaporation theory" by Stephen Hawkings ... very
intriguing.
Followup:
I enjoyed this one myself
http://www.amazon.com/exec/obidos/tg/detail/-/0767908171/
little bit of that science, but pretty broad.
Rating: 3
Alberto... March 29, 2004
Reviewer: denni50 from NA
read Brian Greene's book 'The Elegant Universe'...theory
about "superstrings"..the link between QM and GTR still
remains elusive.

enough of this topic on Tom's website before he admonishes
both of us.

:~)
Rating: 4
Certifications can simply do this March 29, 2004
Reviewer: Bob from Troy NY
In my opinion, the certification is only a plus when you are applying for work
at a place where they value it. Alone it's not much. But all other things being
equal, it's one more thing to weigh in your favor. Sometimes the higher ups,
with less technical experience than the underlings, look at it as a sign of a
continued pursuit of interest in the field. He went to college 10 years ago, but
since then, in addition to working he got his certifications in XYZ.
Rating: 3
A nice topic March 29, 2004
Reviewer: Muhammad Ibrahim from China
  A nice and different topic to read. I really enjoyed and felt the reality
while reading all your postings!

Regards,
Ibrahim.
Rating: 1
How about show others that certification is very easy!!!! March 29, 2004
Reviewer: sharmin from canada
Do you think that if you go for certification exam without any prior preparation
will pass it easily?

Does any certification whether DBA or Developer bear no value to you??
Followup:
bears no value to me, no.

I took my SATS some 20 odd years ago.  multiple guess tests, never much into
them.  Give me a real problem, not a bunch of factoids that I should be looking
up for clarity when I need them.
Rating: 5
certs not worth a lot March 29, 2004
Reviewer: Jim from Wilsonville, OR
Certificates in my mind are not worth a lot in and of themselves.  What even
makes them worth less is about a month ago I got an spam from somone trying to
sell me certs for Oracle, MS, Cisco, Novell, and somthing else.  All I had to do
was sign up for the test through Oracle, MS, et al and send these people a fee.
The manager of a testing center in India would have someone take the test for
me.

I forwarded as much information about this as possible to the companies. (never
heard anything back though)
Rating: 5
March 30, 2004
Reviewer: Mirjana from Barcelona
Certification is just another exam...past tense
And one has to learn every day...present tense (as Tom already said)

Nice topic & great site...
Rating: 4
Tom can't say it loud ! March 30, 2004
Reviewer: SR from New Delhi, India
The greatest virtue of this site is nothing but that it provides the
professionals (and even aspirants) with a chance to have an open discussion with
one of Oracle's most admired knowledge expert.  Tom has always been kind enough
to share his experiences and thus to guide and lead his "disciples" whenever
they are in professional dilemma, technically or otherwise - that's why this
thread is here. Tom is an indirect mentor and a role model for thousands of
Oracle professionals across the globe, and hence he is bound to be cautious in
his words, thanks to the possible impact it can bring about on the perception of
his readers.

I know we people should be smart enough to read between the lines of Tom's
answer to this thread. By revealing that he is not certified, Tom just wants to
say this: "I know how pointless certifications really are!” He has chosen to
restrain himself from being more blunt here; for such a remark from him will be
against the interests of the company he is serving.

Tom, one more thing - purely out of context, kindly excuse me for that - how do
you manage to get time to read other stuff like the works of Bill Bryson? I,
even with a substantially lower professional profile, am having a tough time in
finding some time to go through the morning paper, let alone the books in other
areas of my interest.  How are you managing your time?
Followup:
I fly alot :)

When on a plane, there is no network.  I either watch a movie on the laptop or
read books.
Rating: 4
Never thought I'd be giving Tom advice, but here goes... March 30, 2004
Reviewer: A reader
install Personal Oracle on your laptop, get the docs in PDF on to your laptop,
and lets get this party going! Though of course that's what I would do, tinker
with stuff during downtime, and since you're the Oracle oracle, I suppose you
could get away without following my suggestions... Rock on!
Followup:
[tkyte@tkyte-pc allOraDoc]$ du -ks *
411968  10g
58544   73
42572   80
234532  817
270492  9iR1
312296  9iR2

I only run EE on my laptop! but trust me, the Oracle docs are all there in pdf
and i've been known to read them as well on the plane (just working through the
10g docs which as you can see are slightly larger than prior releases :)

See, If I didn't read the docs myself, I wouldn't have stumbled on this tiny
demo I wrote up on the way to Puget Sound for the PSOUG conference last week...

The concept is "you found a bad query.  You know if you rewrote it -- it goes
really good.  However, it is buried in a 3rd party application.  You cannot
touch the code.  Now what?".... Well:

ops$tkyte@ORA10G> create table dept
  2  as
  3  select *
  4    from scott.dept;

Table created.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> create or replace view dept_v
  2  as
  3  select deptno, initcap(dname) dname, loc
  4    from dept
  5   order by loc;

View created.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> select * from dept;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 ACCOUNTING     NEW YORK
        20 RESEARCH       DALLAS
        30 SALES          CHICAGO
        40 OPERATIONS     BOSTON

that is our "problem query".  we KNOW if we rewrote it as "select * from
dept_v", it would be really cool...

ops$tkyte@ORA10G>
ops$tkyte@ORA10G>
ops$tkyte@ORA10G> pause

ops$tkyte@ORA10G> REM clear screen
ops$tkyte@ORA10G>
ops$tkyte@ORA10G> begin
  2          sys.dbms_advanced_rewrite.declare_rewrite_equivalence
  3          ( name             => 'DEMO_TIME',
  4            source_stmt      => 'select * from dept',
  5            destination_stmt => 'select * from dept_v',
  6            validate         => FALSE,
  7            rewrite_mode     => 'TEXT_MATCH' );
  8  end;
  9  /

PL/SQL procedure successfully completed.

there we go -- when we see select * from dept, rewrite it !

ops$tkyte@ORA10G> pause

ops$tkyte@ORA10G> REM clear screen
ops$tkyte@ORA10G> select * from dept;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        40 Operations     BOSTON
        30 Sales          CHICAGO
        20 Research       DALLAS
        10 Accounting     NEW YORK

Note the sort order and the initcap, we rewrote the query without rewriting
the query in the code...

ops$tkyte@ORA10G> select * from dept for_real;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 ACCOUNTING     NEW YORK
        20 RESEARCH       DALLAS
        30 SALES          CHICAGO
        40 OPERATIONS     BOSTON

it did it in this example by pure text matching only -- other queries won't be
affected...  just the problem query

ops$tkyte@ORA10G> pause

ops$tkyte@ORA10G> REM clear screen
ops$tkyte@ORA10G>
ops$tkyte@ORA10G> exec sys.dbms_advanced_rewrite.drop_rewrite_equivalence(
'DEMO_TIME' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> select * from dept;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 ACCOUNTING     NEW YORK
        20 RESEARCH       DALLAS
        30 SALES          CHICAGO
        40 OPERATIONS     BOSTON

ops$tkyte@ORA10G>
Rating: 5
connor's book March 30, 2004
Reviewer: A reader
Someone above had mentioned Connor's book, "Mastering Oracle PL/SQL: Practical
Solutions".  I have gotten this book and I am so happy that I waited to buy a
PL/SQL book.  I am only through a few chapters but it is exactly what I was
looking for.  It is excellent.
Rating: 5
define: sats March 30, 2004
Reviewer: Marcio from Brazil
sorry off-topic Tom, but I think I lost definition -- foreign people like me
doesn't know what SATS is.
Though, I found this five definition, wich one were you taking about?

google - define: sats

1) Standard Assessment Tests/Tasks.

2) short for saturation levels (of oxygen in the blood)

3) Statutory Assessment Tests. National Tests in English, Maths and Science
taken by all children at the end of Key Stages 1, 2 and 3.

4) Standard Assessment Tests

5) Standard Army Training System
Followup:
1) 3) and 4) would apply.  1) and 4) specifically....

sorry about that (but thanks for google'ing it!)
Rating: 5
dbms_advanced_rewrite.declare_rewrite_equivalence March 30, 2004
Reviewer: Alberto Dell'Era from Milan, Italy
That alone is worth upgrading to 10g (third-party applications  being my
nemesis) ...  really interesting.

Alberto
Rating: 3
so Tom are you saying.... March 30, 2004
Reviewer: denni50 from NA
with 10g using this:
dbms_advanced_rewrite.declare_rewrite_equivalence

you can take a 50+ column table like Gift(one of our
larger tables) and avoid using statements like:

Select * from gift....(which is not performance efficient)

using the below procedure:
begin
  2          sys.dbms_advanced_rewrite.declare_rewrite_equivalence
  3          ( name             => 'NEW_GIFT',
  4            source_stmt      => 'select * from gift',
  5            destination_stmt => 'select * from gift_v',
  6            validate         => FALSE,
  7            rewrite_mode     => 'TEXT_MATCH' );
  8  end;

to convert the 50+ columns & list individually into a view
then have third party apps read from the view instead of
select * from gift.....correct?
Followup:
No, if you try, you'd get:

ops$tkyte@ORA10G> begin
  2          sys.dbms_advanced_rewrite.declare_rewrite_equivalence
  3          ( name             => 'DEMO_TIME',
  4            source_stmt      => 'select * from dept',
  5            destination_stmt => 'select * from dept_v',
  6            validate         => FALSE,
  7            rewrite_mode     => 'TEXT_MATCH' );
  8  end;
  9  /
begin
*
ERROR at line 1:
ORA-30389: the source statement is not compatible with the destination
statement
ORA-01789: query block has incorrect number of result columns
ORA-06512: at "SYS.DBMS_ADVANCED_REWRITE", line 29
ORA-06512: at "SYS.DBMS_ADVANCED_REWRITE", line 176
ORA-06512: at line 2

You would have to at least NULL out the columns:

ops$tkyte@ORA10G> create or replace view dept_v
  2  as
  3  select deptno, initcap(dname) dname, NULL loc
  4    from dept
  5   order by loc;

View created.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> select * from dept;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 ACCOUNTING     NEW YORK
        20 RESEARCH       DALLAS
        30 SALES          CHICAGO
        40 OPERATIONS     BOSTON

ops$tkyte@ORA10G>
ops$tkyte@ORA10G>
ops$tkyte@ORA10G> pause

ops$tkyte@ORA10G> REM clear screen
ops$tkyte@ORA10G>
ops$tkyte@ORA10G> begin
  2          sys.dbms_advanced_rewrite.declare_rewrite_equivalence
  3          ( name             => 'DEMO_TIME',
  4            source_stmt      => 'select * from dept',
  5            destination_stmt => 'select * from dept_v',
  6            validate         => FALSE,
  7            rewrite_mode     => 'TEXT_MATCH' );
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA10G> pause

ops$tkyte@ORA10G> REM clear screen
ops$tkyte@ORA10G> select * from dept;

    DEPTNO DNAME          L
---------- -------------- -
        10 Accounting
        20 Research
        30 Sales
        40 Operations
Rating: 5
Mentor / Recommended books March 30, 2004
Reviewer: Duke Ganote from Warsaw, Indiana USA (Orthopaedic Capitol of the World)
Tom-- Thank you so much for your response(s) to my/our questions.  My follow up
question would be:  so who was your mentor?
-- Duke

BTW, I searched your site for your recommended books, and found your interesting
example of "Question Authority" at
http://asktom.oracle.com/pls/ask/f?p=4950:8:14723018644491555385::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:8128590522738,
My search found (beside your own books, of course) you recommended the following
books (or referenced the author positively).  Note that I don't claim to be
exhaustive or present these in any particular order:

"Oracle 9i Java Programming: Solutions for Developers Using PL/SQL and Java"
(you were technical contributor... I got it a month or so again, and am reading
this), e.g.
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5259303334653
Jonathan Lewis's "Practical Oracle8i", e.g.
http://asktom.oracle.com/pls/ask/f?p=4950:12:2858699005165820689::NO::F4950_P12_DATE_YYYYMMDD:20040304
Steve Muench's "Building Oracle XML Applications", which is copyrighted 2000, so
doesn't cover the latest in XML, like XML schemas (I got it a few months ago),
e.g.
http://asktom.oracle.com/pls/ask/f?p=4950:8:1010393454082213404::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:4061080732051,
Steve Adams is frequently mentioned for Oracle Internals, e.g.
http://asktom.oracle.com/pls/ask/f?p=4950:8:12515928662255914132::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:745985307605,
and has a book that you referenced entitled "Oracle8i Internal Services for
Waits, Latches, Locks, and Memory"
Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:13253348444918
Rating: 5
Very cool March 30, 2004
Reviewer: Dan Kefford from New Haven, CT
Tom...

Where is the metadata from DBMS_ADVANCED_REWRITE stored? That is, if you used
this to set up an equivalence between "select * from dept" and "select * from
dept_v", is that reflected in the data dictionary somewhere? (Haven't installed
10g yet to answer this question myself ;)

Also, does DBMS_ADVANCED_REWRITE have anything to do with how query rewrites are
used in materialized views?
Followup:
ops$tkyte@ORA10G> select * from DBA_REWRITE_EQUIVALENCES;

OWNER                          NAME
------------------------------ ------------------------------
SOURCE_STMT
------------------------------------------------------------------------
DESTINATION_STMT
------------------------------------------------------------------------
REWRITE_MO
----------
OPS$TKYTE                      DEMO_TIME
select * from dept
select * from dept_v
TEXT_MATCH

it uses the same underlying techniques as the "mv" query rewrite.
Rating: 3
Certifications have a positive side April 05, 2004
Reviewer: A reader from Canada
Hi,

I agree that getting certified does not guarantee that you will be able to solve
all your issues, but it has a positive side.
If you want to get ORACLE DBA certified you have to cover all the features of
the database. That does not mean that you know them all but when you have an
issue or you are implementing something you know that the features are there.
I met a girl who was certified that had trouble creating a table; on the other
hand I am the only certified dba in my group of 10 and I became the technical
lead.
Rating: 4
On another note... April 05, 2004
Reviewer: A reader
<Rant>
It's nice to know that even Tom has to read the documentation, although now that
I spend some of my own CPU's cycles thinking about it, it isn't like Oracle's
technology and its new features are revealed to him by some divine power...
don't know what I was thinking. And the note on EE above, I installed it once on
my Windows XP machine, and then un-installed it, and since then I have not been
able to re-install EE on my machine, it just makes the fan go into overdrive,
and at the end of it all, just happily stops working, as if I didn't command it
to install Oracle but rather offered it up as a suggestion, and it decided that
it was too much work. Anyway, I better get out of here in case I say something
about Microsoft I regret... Later all!
</Rant>
March 30, 2004 -- Thanks for the question regarding "Unable to change the mode of tablespace..", version 8.1.6.3
You Asked
Dear Tom,
I am unable to change the mode of tablespace to read only.The system is getting
hanged.This happened when I tried to do it for exporting it(Transportable
tablespaces).I am having few doubts regarding that
1)Is it necessary that there should not be any active transactions in the entire
database???
According to one document it states that there should not be any active
transactions in the corresponding rollback segment as well as database.
If that is the case is there any workaround for that because it is not possible
to take outage if it is a production database and there will be always some
active transactions in it.The purpose of transportable tablespace feature is
ineffective in this case.
Thanks,
Geeta
and we said...
For a brief instant in time, yes.

In order to change the tablespace from read write to read only, we must suspend
transactions.  When you issue the:

alter tablespace i1 read only;

new transactions will be prevented from starting.  As the last of the old
transactions finish, the alter tablespace will complete and everything will
become unblocked.

This should not take longer then your longest running transaction.  You need to
do this during a period of time when you are running "short" transactions, not
long running batch jobs.
Review & Followup

Rating: 4
Is there a workaround on 9i? March 30, 2004
Reviewer: Prakash from Germantown, TN USA
Tom
I plan to use the transportable tablespace feature to move data from OLTP to the
datawarehouse and there are some batch jobs that would be running on the OLTP
system in the window planned for transporting the tablespace from OLTP to DW.
Having the batch jobs not running during that window is not an option (business
constraints)
I really want to take advantage of the transport feature as I would be moving 2G
datafile across the network.
Is there no work around for this?
Thanks,
Prakash
Followup:
no, it is a requirement for transporting.

you could take a hot backup of the oltp system, restore just the tablespaces you
wanted and system/rollback.  do a cancel based recovery on the target system
(catching it up).  transport from that mini instance and you'd have what you
want as well -- no impact on the OLTP system in this case and you needed to
restore the files anyway....
March 30, 2004 Manoj -- Thanks for the question regarding "Unit Testing PL/SQL", version 9i
You Asked
Tom, In our company we are trying to automate all our unit tests using some
tools. for java we have decided to use JUNIT . Could you suggest any tool for
Oracle PL/SQL?

Do you think these tools will really add value to tests , add quality to product
and reduce bugs.  What Oracle corp uses to test PL/SQL? (;

What is limitations of these tools. Are there any tools for testing performance
?

Thanks in advance

Manoj
and we said...
in answer to

"Do you think these tools will really add value to tests , add quality to
product
and reduce bugs."

yes, yes, and yes.

We use a variety of methods, off of the shelf tools like loadrunner, in house
test tools, standalone sqlplus scripts with gred sed and awk at the back end to
verify results.  We run 100's of thousands of tests with each build of each
product.
http://oracle.oreilly.com/utplsql/
it something to look at (a simple google of

junit plsql

found it.  i've never used it personally, this is not an endorsement, just a
starting point)
Review & Followup

Rating: 5
PL/SQL Best Practices Oracle Magazine Article March 30, 2004
Reviewer: Doug from Mobile, AL
Here is a good article from oracle magazine that talks about some best practices
and mentions unit testing with PL/SQL.
http://otn.oracle.com/oramag/oracle/04-mar/o24tech_plsql.html

Rating: 3
Listing 3 March 30, 2004
Reviewer: A reader
you don't have raise in when others exception block in listing 3

 May be you have it in your record_and_stop procedure.
is this a right practice ? don't you want to "see" raise in the when others
block always ? what do you say tom ?
Followup:
that is *not* my code.  It is By Steven Feuerstein,  I believe his
"record_and_stop" would re-RAISE (hence the 'stop')
Rating: 3
wrong assumption ... March 30, 2004
Reviewer: A reader from Internet
To "A reader" (review #2) ...

<quote>
you don't have raise in when others exception block in listing 3

 May be you have it in your record_and_stop procedure.
is this a right practice ? don't you want to "see" raise in the when others
block always ? what do you say tom ?
</quote>

The article (hence Listing 3) belongs to Steven Feuerstein ... and not Tom.
Rating: 3
more info on utPL/SQL March 31, 2004
Reviewer: Patrick Barel from The Netherlands
check out
http://utplsql.oracledeveloper.nl/
That's the location of the forum for the userbase. Also has links to the
documentation and the download (sourceforge based)
March 30, 2004 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)
Rating: 5
How to exceed 4000 chars in predicate? February 04, 2004
Reviewer: Kurt from Springfield, VA USA
Tom,
We've implemented FGAC on a secure government project with lots of access
restrictions. We've continuously added more and more text to the predicate, and
now we're at the point where the predicate is over 4000 characters long. Since
the varchar2 return value from a PL/SQL packaged function can be no longer than
4000 chars, the predicate function raises an exception that causes a policy
error.

How can we address this limitation? Does Oracle support returning a CLOB from a
predicate function? We've considered moving some of the predicate text (queries)
into separate functions and making calls to those functions from within the
predicate text, but we're concerned about hurting performance. As you can
imagine, a predicate that is 4000 chars long already impacts performance.

Any suggestions would be appreciated.

Thanks, Kurt
Followup:
You can have multiple policy functions -- each returning a chunk, a piece.

they are all ANDED together.

A 40,000 byte predicate could be tons faster than a 100 byte predicate -- it all
just depends on what the predicate does :)
Rating: 4
column-level control March 30, 2004
Reviewer: robert from ct
hi Tom,
how can one implement column-level access ?

anyone can see a any employee info BUT only bosses can see
the salary ?

Thanks
Followup:
In 10g, this is added as part of VPD (fine grained access control)

In 9i and before, you would use a view to selectively hide the data.  EG:

select empno, ename,
       decode( sys_context( 'myctx', 'isboss' ), 'Y', sal, null ) sal
  from emp;

and use a logon trigger or some other technique to set the context myctx with a
variable of 'isboss'
Rating: 5
thanks for confirming March 30, 2004
Reviewer: robert from ct
>>In 9i and before, you would use a view to selectively hide the data.
Ha ! Thanks !!
That's exactly what I thought to be the easiest and had built into my web
app...almost all parameterized views and use context everywhere. time to buy
meself a brew...:)
March 30, 2004 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
Rating: 4
about collection and arrays March 30, 2004
Reviewer: A reader
Hi

This question is not related to this thread however I saw this pl/sql block

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;

I was wondering what difference it would make if I declare the arrays as type
ridArray is table of rowid;? Instead of using index by binary integer? What's
the difference?
Followup:
one is a collection, the other a plsql index by table.

The differences come into play when you try to add elements yourself.

   l_rid(l_rid.count+1) := .....;

will work if you use index by tables.  it'll fail if you use collections (since
you must explicitly ALLOCATE the slots in a collection using .extend)
March 30, 2004 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
Rating: 5
Is a good idea to move it to a procedure? March 17, 2004
Reviewer: A reader
Hi Tom, please
Only to be sure, do you think is a good idea to move this code to a trigger, if
it is executed frequently (some hundreds of time each day)
I think it can be more simple.

CREATE OR REPLACE TRIGGER daz.tgr_utl_tablas_me
 BEFORE
  INSERT
 ON daz.utl_tablas_me
REFERENCING NEW AS NEW OLD AS OLD
 FOR EACH ROW
BEGIN
IF :NEW.TBL_EMPRESAGESTION IS NULL THEN
 :NEW.TBL_EMPRESAGESTION := DB_UTL_ME;
END IF;
END;
/
Thanks Tom
Followup:
what is "db_utl_me"?

and what would you move into a procedure?

and you could use "when" in the declare to have this trigger only run "when"
that column is null
Rating: 5
Thanks Tom March 17, 2004
Reviewer: A reader
Rating: 3
A request.. March 17, 2004
Reviewer: Matt from Australia
"I like to introduce decode (and now case) lots myself...  as a tuning device
(to
remove unions most often)... "

Can you please provide a brief example of the above?

THanks,
Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:12982039780597

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6896450740607
Rating: 3
A question: March 30, 2004
Reviewer: Matt from Australia
From the links I see two possible approaches to re-writing queries that use a
union.

case 1: Unioning the same table (when there are two column values in a record
that must be treated as a 'attribute' value)

The following:

select pk, col1 from tab1
union all
select pk, col2 from tab1;

can be re-written as:

select case when rn=1 then col1
            when rn=2 then col2
             end unioned_col
 from tab1
     ,( select 1 rn from dual
        union all
        select 2 from dual );

case 2: Unioning multiple different tables

select pk, col1 from tab1
union all
select pk, col2 from tab2

can be re-written as:

select pk
      ,case when rn=1 then a.col1
            when rn=2 then b.col2
             end unioned_col
 from tab1 a
     ,tab2 b
     ,( select 1 rn from dual
        union all
        select 2 from dual )
  where a.pk = b.pk;

Do you have any idea when  either if these approaches become more favourable
than the alternative query with the union all (or even just union to remove
duplicates)?

Thanks.
Followup:
case 2 is not a "good idea" in general.  it doesn't work in general.

Almost anytime I can remove a UNION/ALL of the same table with itself -- I feel
better as I'm doing less work overall.
March 30, 2004 Peter -- Thanks for the question regarding "Concurrent upsert a problem?", version 9.2.0.5
You Asked
Hi Tom,

If I have several sessions doing upsert against the same target table:

1)  Is there an issue with two sessions inserting the same record causing a PK
violation?

2)  Is there a performance issue with concurrent upserts from multiple sessions?

3)  Should I handle this situation on the application tier and make sure that I
don't send duplicate records to different sessions?

Thanks,
-Peter
and we said...
1) well, one of them will get an duplicate key error and rollback.

so, from a consistency perspecitive, no issues

from a "end user experience", they might get mad at you for letting that happen.

2) no, no more than any other concurrent operation

3) that would be "nice" if possible.
Review & Followup

Rating: 3
Merge? March 31, 2004
Reviewer: Kristian Myllymäki from Sweden
There was a discussion about this in the oracle.server newgroup a week ago, see
http://groups.google.se/groups?hl=sv&lr=&ie=UTF-8&oe=UTF-8&threadm=5366fb41.04032
40847.60a86846%40posting.google.com&rnum=1&prev=/groups%3Fq%3Dinsert%2Bupdate%2Bd
bms_lock%26hl%3Dsv%26lr%3D%26ie%3DUTF-8%26oe%3DUTF-8%26scoring%3Dd%26selm%3D5366f
b41.0403240847.60a86846%2540posting.google.com%26rnum%3D1
March 31, 2004 minnie -- Thanks for the question regarding "how to print in unix monitor? -- extprocs, printf and fprintf", version oracle8i 8.1.5
You Asked
Hi,Tom:
   when i call OCI external procedure by pl/sql, i want to
display something out by "printf()" in oci program. But there
are some trouble to display it in out unix monitor(SUN solaris),
Sometimes the unix monitor can display them out, but now it
cann't , and nobody modify the configure. I tried to use
"fprintf" to a file, it seemed that OCI doesn't support it when
it is called by pl/sql(if only in OCI, it can). We contact your
company the day before yesterday, but don't get any answer yet.
Do you know how to configure something to make the unix display?

thanks.

minnie
and we said...
By display I'll have to assume you mean on the unix "console"
since there is no such thing as a "display"...

Extproc processes are started by the listener.  A printf issued
by the extproc will goto the console that the listener is
currently attached to.

If you start the listener as part of the startup process on your
machine -- it will be attached to the system "console".
Otherwise -- the listener will be attached to whatever pty
(psuedo tty) that happens to start it.  If you start the listner
in some telnet session -- LEAVE that session open and run your
extproc -- you should see your printfs goto that window.  They
will no longer goto the system console.

So, in your case, what probably happened is you restarted the
machine at some point and the messages were going to the
console.  Later, someone stopped and restarted the listner --
changing the "monitor" it would display to.  When that person
exited their telnet/xterm session -- their pty went away and the
printf now just go into a big bit bucket.  When they restarted
the listener -- they stopped the messages getting to the
console.  When they exited their session -- they stopped the
messages from being display anywhere.

OCI fully supports the ability to fprintf to a file in an
extproc -- I do it all of the time myself.  All of my
extprocs, and I've written quite a few, use fprintf heavily to
generate trace files.

You can also use the functions:

OCIFileOpen
OCIFileWrite
OCIFileClose

as well.  I use both successfully (and easily).  Your problem
with fprintf is undoubtably a permissions problem ( don't forget
-- the extproc runs as the user the listener is, not as "you".
If you try to open a file in "your" home directory or something,
the extproc probably will not be able to do that. )

Here is a snippet of code you might find useful.  I use it all
of the time -- it works great (given the above).

It implements a simple debugf routine that works like printf but
writes to a trace file suitable to be "tail -f /tracefile" in
another window (so your extproc output can be viewed in "real
time" as it happens).

Give this a try.

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>

#ifndef OCI_ORACLE
# include <oci.h>
#endif

static int currLineNo;
static char * currFileName;

void _debugf( char * fmt, ... );

#define debugf \
currFileName=__FILE__, \
currLineNo=__LINE__, \
_debugf

/* PUT YOUR CODE HERE!!!!! */

/* ------------------------------------- */
#undef debugf

void _debugf( char * fmt, ... )
{
va_list ap;
FILE    * fp;
char    * debug_modules = "all";
char    * debug_file    = "/tmp/tkyte.dbg";
time_t    theTime = time(NULL);
char    buffer[25];

    if ( !debug_modules ) return;

    if ( (fp = fopen( debug_file, "at" )) == NULL )
        fp = stderr;

    strftime( buffer, sizeof(buffer),
             "%y%m%d %H%M%S GMT", gmtime(&theTime) );

    fprintf( fp, "%s (%13s,%3d) ",
             buffer, currFileName, currLineNo );
    va_start(ap,fmt);
    vfprintf( fp, fmt, ap );
    va_end(ap);
    fprintf( fp, "\n" );

    if ( fp != stderr ) fclose(fp);
}
Review & Followup

Rating: 4
Writing to stderr from PL/SQL March 29, 2004
Reviewer: Kapil
Hi Tom,

Is there any mechanism to write debug statements to the stderr file stream from
PL/SQL procedures?
The reason I request clarification is that our main program is a Pro*C program
and we are calling PL/SQL procedures in it. While executing the main program the
file descriptor 2 is redirected to the debug file. I would like to continue
writing debug from PL/SQL into this debug file.
Please give your inputs on this.
Followup:
stderr is a client side phenomena.

plsql is a server side thingy

if you are willing to add a tad bit of code to pro*c, you can certainly use
dbms_output in plsql and make a call to dbms_output.get_lines in pro*c and have
pro*c dump to its stderr.
Rating: 4
Redirection of DBMS_OUTPUT.PUT_LINE March 29, 2004
Reviewer: Kapil
Hi Tom,

To access the debug generated from DBMS_OUTPUT.GET_LINES and then dump it to
stderr in Pro*C is a good idea but isnt there any way to redirect the debug
generated via DBMS_OUTPUT.PUT_LINE directly to stderr rather than the default
stream stdout?
Followup:
No

pro*c runs on client
stderr on client

plsql runs in server -- somewhere else on the planet
stderr not anywhere near this server
stderr quite simply "not available" there

the client is the ONLY thing with access to stderr.
the client is the ONLY thing that can write to stderr.
Rating: 3
Implicit write to stdout March 30, 2004
Reviewer: Kapil
Yes, I agree to that. I also understand that calls to DBMS_OUTPUT.PUT_LINE(text)
will write the text to some buffer and the text from this buffer can be got via
DBMS_OUTPUT.GET_LINES(...). But isnt there an implicit flush of the text from
this buffer to the stdout stream within the client? If it is done during or
immediately after the procedure has finished execution then will GET_LINES after
the procedure call retrieve any data from the buffer?
Followup:
No, that is what I'm trying to say -- unless the CLIENT calls GET_LINES and the
CLIENT (whatever the client is -- could be ANYTHING in the world) prints it out.

sqlplus for example calls get_lines and formats the output and prints it.  In
order for sqlplus (the client) to do this, you must tell sqlplus to do it via
set serveroutput on.  You can even add "format...." to that set command to tell
sqlplus how to format the output.  That *shows* that sqlplus is getting the
data, formatting the data and printing the data.

The oracle client software never prints anything out.  We are simply an API, we
don't own the console.  Imagine if we just started dumping stuff to your screen
whenever.  It would be a little messy.
Rating: 5
March 31, 2004
Reviewer: A reader
This clears it all.
Thanks very much.
March 31, 2004 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.
Rating: 5
ORA-04031: unable to allocate 4096 bytes of shared memory February 02, 2004
Reviewer: Kom from USA
Hi Tom,
We sometimes get this error ORA-04031. We have a mixed set of SQL ( Dynamic,
Bind and Stored procedures ).

I am confused with the following terminologies.
Shared Pool , Bind Variables and Dynamic SQl's.
You always say that to avoid shared pool problems one should write only Bind
variables.
1) By using Bind Variables will it completely remove the Shared pool problem ?
2) What if I am running 100,000+  unique SQL's which cannot be Bind ( Ad hoc )?
3) Does oracle take another route in handling Bind SQL's compared to dynamic
SQL's
4) My understanding is if I use Dynamic SQL shared pool will crash in 2 days but
If I use Bind variables eventually the shared memory will crash after 100 days,
considering that my application has ad-hoc query facilty. How do I avoid this
completely.
Regards,
Kom
Followup:
1) as long as the dynamic sql is generated in the same fashion (eg: same case,
same bind variable namings, etc).

2) make your shared pool small to allow for aging to take place in a reasonable
fashion.  do not make your shared pool HUGE to accomidate this.

3) nope.  they are the same.

4) umm, no -- that would not be true.  HTML/DB makes EXTENSIVE use of
dynamic sql  --- asktom is built on HTML/DB -- the site runs for months between
restarts.   Many months.
Rating: 5
Shared pool follow up February 03, 2004
Reviewer: kom from USA
you said :
>>> 2) make your shared pool small to allow for aging to take place in a
reasonable
fashion.  do not make your shared pool HUGE to accomidate this

1) How to arrive at a Shared pool size. Is there any calculation to determine
the shared pool size
2) How do I make sure the aging is taking place , is there a parameter or
setting which needs to be done ?

Thanks in advance,
Followup:
start at 50meg and use statspack to watch the utilization
Rating: 5
How to use bind variable when query is stored in Database? February 09, 2004
Reviewer: Shrikant Gavas. from INDIA
Hi Tom,
Thanks Alot for your precious time.

We are preparing where clause for a query at run time(in different session) and
storing it in a table. We are firing this sql using DBMS_SQL package and since
in the query bind variables are not used we are facing problem of shared pool
Similar type of queries are getting parsed again and again.
Basically we are finding COUNT from a fixed table using the stored WHERE_CLAUSE.

How to use bind variables in such cases ? Or is there any alternative for this.
Please Please HELP.

Regards

Shrikant Gavas
Followup:
alter session set cursor_sharing=force;
Rating: 5
February 23, 2004
Reviewer: A reader
Tom,

I 9i concepts guide chapter 7
<doc>
SGA Starting Address
The SHARED_MEMORY_ADDRESS and HI_SHARED_MEMORY_ADDRESS parameters specify the
SGA's starting address at runtime. These parameters are rarely used. For 64-bit
platforms, HI_SHARED_MEMORY_ADDRESS specifies the high order 32 bits of the
64-bit address.

<doc>

I understand it states it is rarely used, but when and why?

Thanks.
Followup:
This controls the attach address of the SGA on certain platforms only. This can
be useful if you get 'shmat()' type errors like ORA-7307 with an Invalid
Argument error.
Rating: 4
Bind variables rock! March 21, 2004
Reviewer: Duke Ganote from beautiful Warsaw, Indiana USA (Orthopaedic Capitol of the World)
Tom-- I think I'm "getting it" on bind variables (it was never quite clear to me
since I was embedding most of my SQL in PL/SQL for ETL, which did most of the
work for me).  I took your earlier 2 examples with dynamic SQL and included a
3rd static SQL rendition.

I also included counts from V$SQL, per your question on the V$SQL* views at
http://asktom.oracle.com/pls/ask/f?p=4950:8:8320208358217295959::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:385254862843,
The output is in the next 3 lines, and the code follows.
Thank you!

OUTPUT RESULTS:
Dynamic SQL, no bind vars took 53.6 seconds...394 SQL statements in shared pool
Dynamic SQL, w/ bind vars took 1.37 seconds...33 SQL statements in shared pool
_Static SQL, _autobinding took 1.29 seconds...32 SQL statements in shared pool

CODE:

GANOTEDP\dganote> ed
Wrote file afiedt.buf

  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  begin
  7      execute immediate 'alter system flush shared_pool';
  8      -- FIRST ITERATION:  NO BIND VARIABLE
  9      for i in 1 .. 1000 loop
 10          open l_rc for
 11          'select object_name
 12             from all_objects
 13            where object_id = ' || i; -- not a bind variable
 14          fetch l_rc into l_dummy;
 15          close l_rc;
 16      end loop;
 17      for r in ( select count(1) cnt from v$sqlarea ) loop
 18        dbms_output.put_line
 19      ( 'Dynamic SQL, no bind vars took '||round(
(dbms_utility.get_time-l_start)/100, 2 ) ||
 20        ' seconds...'||r.cnt||' SQL statements in shared pool' );
 21      end loop;
 22     execute immediate 'alter system flush shared_pool';
 23     -- SECOND ITERATION: BIND VARIABLE
 24       l_start := dbms_utility.get_time;
 25       for i in 1 .. 1000
 26       loop
 27           open l_rc for
 28             'select object_name
 29                from all_objects
 30               where object_id = :x' -- bind variable
 31             using i;
 32           fetch l_rc into l_dummy;
 33           close l_rc;
 34       end loop;
 35       for r in ( select count(1) cnt from v$sqlarea ) loop
 36         dbms_output.put_line
 37       ( 'Dynamic SQL, w/ bind vars took '||round(
(dbms_utility.get_time-l_start)/100, 2 ) ||
 38         ' seconds...'||r.cnt||' SQL statements in shared pool');
 39       end loop;
 40     -- THIRD ITERATION: AUTOBIND
 41      execute immediate 'alter system flush shared_pool';
 42      l_start := dbms_utility.get_time;
 43      for i in 1 .. 1000
 44      loop
 45         for rec in ( select object_name
 46               from all_objects
 47              where object_id = i ) loop
 48            l_dummy := rec.object_name;
 49         end loop;
 50      end loop;
 51      for r in ( select count(1) cnt from v$sqlarea ) loop
 52      dbms_output.put_line
 53      ( '_Static SQL, _autobinding took '||round(
(dbms_utility.get_time-l_start)/100, 2 ) ||
 54        ' seconds...'||r.cnt||' SQL statements in shared pool' );
 55       end loop;
 56* end;
GANOTEDP\dganote> /
Dynamic SQL, no bind vars took 53.6 seconds...394 SQL statements in shared pool
Dynamic SQL, w/ bind vars took 1.37 seconds...33 SQL statements in shared pool
_Static SQL, _autobinding took 1.29 seconds...32 SQL statements in shared pool

PL/SQL procedure successfully completed.

Elapsed: 00:01:01.06
Rating: 3
memory bug March 27, 2004
Reviewer: A reader
Sorry, it look to me that I was on wrong topic first ime.
Here is a link of my first question:
http://asktom.oracle.com/pls/ask/f?p=4950:61:2240880001785671395::::P61_ID:1288301763279
and text:
******************************
--------------------------------------------------------------------------------
Cursor bug  March 26, 2004
Reviewer:  Fan from Germany

Hi Tom,

Recently our database has crashed having problem with memory (Oracle v9.2).
Oracle support gave us an answer telling that we have hit oracle bug when a
query (cursor) in DB procedure accessing partitioned table use function new_time

and cursor_sharing is enabled => raise ORA-600.
But to me this look quite strange. To describe:
cursor_sharing is set to exact, query itself does not use function new_time
(procedure does) and system crashes only when in query where clause we use one
particular column (in that case query have a cost of 5000).
If we execute the same query under the same conditions but use another column
(in this case query have cost of 500 and return the same result set) system does

not crash.
In both cases query (actually cursor) use bind variables.
I have checked shared pool in second case and query has been parsed once and
executed 120 times. In first case is the same but only difference is
cost...query takes longer to finish.
This is confusing to me. If the bug is presistent, system will chrash in both
cases.
Does the query execution time play some role in this bug and could you please
give me some more information about this oracle bug if you have?
I do not know wheter my explanation was clear, but if you need some more
detailed info, I can post it.

Followup:
so, your database isn't crashing, a session is getting an ora-600 with
cursor_sharing...

but you say you are not using that -- so obviously -- support has either

a) misdiagnosed the issue
b) failed to explain that the but can happen due to other reasons.

without having access to the actual bug number and tar related information --
i'd be hard pressed to really "say more"

*******************************************

to continue:

Actually database is crashing since no one is able to log on to database after
running this procedure.
Here is original text that we got from Oracle support:

*****************************************
The stack trace information shows that you are hitting the Bug 2416262

Bug Details:
===========
ORA-600 [qbaopn1] / memory corruption can occur when using the
NEW_TIME function with CURSOR_SHARING enabled when the query involves
a partitioned table.

Fixed-Releases: 9205 A000
*****************************************

I do not know the tar number since all communication with Oracle support are
going over our System support and they also have no idea what is happening on
our system.

I do no expect that you give me 100% correct answer, but any hint in which
direction I should dig to find the cause will be useful. As I said, I am simply
not sure that this is a cause of our problem.
Question which comes out are like, for which value of cursor_sharing this is
happening?
Does the query have to have in where clause column on which this table is
partitioned in order for this to happen or can be any column from this
partitioned table?
Does new_time function have to be explicitly used in query or not? and so on....
Any info that you have will be valuable to me regarding this bug. I do not need
straight answer, only information which will help me to analyze and understand
the problem.

Thanks in advance.
Followup:
well, this is what your "systems support personnel" were hired to do isn't it?

IF you are in fact hitting this, there are patches for 9204, 9203 as well.  You
will really have to work this *via support*.

The symptoms in the bug are

cursor_sharing=force is set (force, probably similar as well)

Here is a test case to demonstrate.

ops$tkyte@ORA9IR2> drop table junk3;

Table dropped.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create table junk3(col date)
  2  partition by range (col)
  3  ( partition d1 values less than (to_date('20020101','YYYYMMDD')),
  4    partition d2 values less than (maxvalue) )
  5  /

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> alter session set cursor_sharing=exact;

Session altered.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select new_time( to_date('01011970','ddmmyyyy')
  2  + 1/24/60/60 * 1023297820 - 800, 'GMT', 'EDT' )
  3  from dual;

NEW_TIME(
---------
27-MAR-00

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select * from junk3
  2  where col = new_time( to_date('01011970','ddmmyyyy')
  3  + 1/24/60/60 * 1023297820 - 800, 'GMT', 'EDT' );

no rows selected

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> alter session set cursor_sharing=force;

Session altered.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select new_time( to_date('01011970','ddmmyyyy')
  2  + 1/24/60/60 * 1023297820 - 800, 'GMT', 'EDT' )
  3  from dual t1;

NEW_TIME(
---------
27-MAR-00

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select * from junk3 t2
  2  where col = new_time( to_date('01011970','ddmmyyyy')
  3  + 1/24/60/60 * 1023297820 - 800, 'GMT', 'EDT' );
select * from junk3 t2
*
ERROR at line 1:
ORA-03113: end-of-file on communication channel

You would see something like:

ORA-00600: internal error code, arguments: [qbaopn1], [185], [], [], [], [], [],
[]
Current SQL statement for this session:
select * from junk3 t2
where col = new_time( to_date(:"SYS_B_0",:"SYS_B_1")
+ :"SYS_B_2"/:"SYS_B_3"/:"SYS_B_4"/:"SYS_B_5" * :"SYS_B_6" - :"SYS_B_7",
:"SYS_B_8", :"SYS_B_9" )

in the trace file for that session on the server.  If you do not -- not the same
issue.

workaround is to not use cursor_sharing=force.
Rating: 3
Unable to allocate while logging on March 30, 2004
Reviewer: Randy from Mountain View
Above, someone was getting "ORA-04031 and you said:
   tell me what you were doing -- was it logging on.

He wasn't, but my code does.  I get:
ORA-00604: error occurred at recursive SQL level 1
ORA-04031: unable to allocate 4200 bytes of shared memory ("shared
pool","unknown object","sga heap","state objects")

The code uses almost all PreparedStatements, and I'm cleaning up the rest, which
are used infrequently.  But it creates/uses/closes connections constantly
instead of caching them or pooling them.  Could this be contributing to the
problem?

Thanks,
Randy
Followup:
they were large pool, not shared pool

so, does your application that uses prepared statements use bind variables?
are you MTS (shared server) or dedicated?
how big is your shared pool.
how many connections do you use.
Rating: 3
our configuration March 30, 2004
Reviewer: Randy from Mountain View
Yes, the PreparedStatements use bind variables,
   '?' instead of hard-coded values.
Dedicated server.
The shared pool size is the default,
  I think- like 50M for the shared pool
  and 2M for the shared pool reserved size.
We use about 12 connections, often fewer than that are open.
Thanks- Randy
Followup:
50m could well be "too small".

the default is 8m on 32bit, 64m on 64bit -- check it out.
March 31, 2004 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.
Rating: 3
Dear Tom, what am I mising here? March 11, 2004
Reviewer: Sami from NJ,USA
SQL> grant connect,resource to a identified  by a;

Grant succeeded.

SQL> grant plustrace to a;

Grant succeeded.

SQL> show user
USER is "SYS"
SQL> exit
Disconnected from 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

njd05dsmdbc01 oracle idb11 /opt/oracle
$ sqlplus a/a

SQL*Plus: Release 8.1.7.0.0 - Production on Thu Mar 11 13:33:31 2004

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

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> @?/rdbms/admin/utlxplan.sql

Table created.

SQL> set autotrace on
SP2-0618: Cannot find the Session Identifier.  Check PLUSTRACE role is enabled
SP2-0611: Error enabling STATISTICS report

SQL> select * from user_role_privs;

USERNAME                       GRANTED_ROLE                   ADM DEF OS_
------------------------------ ------------------------------ --- --- ---
A                              CONNECT                        NO  YES NO
A                              PLUSTRACE                      NO  YES NO
A                              RESOURCE                       NO  YES NO
PUBLIC                         PLUSTRACE                      NO  YES NO
Followup:
what is

select * from dba_tab_privs where grantee = 'PLUSTRACE';
Rating: 4
SELECT on v_$session should be granted to plustrace March 11, 2004
Reviewer: rsergio from Maceio - AL Brazil
I´ve got a similar problem (as the last post) and found that SELECT on
V_$SESSION is necessary to plustrace to function properly. It is not in the
plstrce.sql script.
Followup:
in 8i, it should look like this:

set echo on

drop role plustrace;
create role plustrace;

grant select on v_$sesstat to plustrace;
grant select on v_$statname to plustrace;
grant select on v_$session to plustrace;
grant plustrace to dba with admin option;

set echo off

in 9i, like this:

drop role plustrace;
create role plustrace;

grant select on v_$sesstat to plustrace;
grant select on v_$statname to plustrace;
grant select on v_$mystat to plustrace;
grant plustrace to dba with admin option;

set echo off

what could have happened is someone used the 9i plustrace in 8i to set it up.
thats why I wanted the dba_tab_privs.
Rating: 4
Working on 91 March 11, 2004
Reviewer: rsergio from Maceio - AL Brazil
Dear Tom,

I'm working on 9i (9.2.0.4) and I have executed plstrce.sql as SYS (the script
with grants on v_$statname, v_$mystat and v_$sesstat) but the only way I could
get plustrace doing its job (granting access to set autotrace on) was granting
also select on v_$session. Well, something must have been very wrong! Oh, God!
Time for worries have begun!

Thanks for your time and excuse my bad english.
Followup:
did you use v8i sqlplus?  if so, that is the issue, it (sqlplus) would be
looking for different v$ views.
Rating: 4
SQL*Plus 9.2.0.1.0 March 11, 2004
Reviewer: rsergio from Maceio - AL Brazil
Nope. Version of SQL*Plus is as the title says. Well, but it's not a big deal (I
hope) since autotrace is accessible to developers and things are going OK. I
just get a little worried because it is not a normal behavior. I've had a hope
this was a documented bug or something. I would appreciate if you could shed
more ligth on the subject. Anyway, life goes on. Thanks again.
Followup:
that's it

9201 -> 9204

9203 and up use v$mystat
9202 and before use v$session

9201 client and lower queries v$session
9203 client and higher queries v$mystat
Rating: 5
Great! March 11, 2004
Reviewer: rsergio from Maceio - AL Brazil
Peace reigns in the land of the sun.

Thanks.
Rating: 3
March 12, 2004
Reviewer: Varad from INDIA
Hi arun,

SQL> set autotrace on
Unable to verify PLAN_TABLE format or existence
Error enabling EXPLAIN report

when i tried this autotrace, it gave me this error and subsequently when i run
any query, it shows me this.

Statistics
----------------------------------------------------------
        680  recursive calls
         20  rows processed

This could be one of the problems you are facing.
Followup:
you didn't create a plan table, thats all

@?/rdbms/admin/utlxplan
Rating: 3
March 30, 2004
Reviewer: Philip Tao from Sydney, Australia
Hi Tom,

I have followed the given steps to enable autotrace and granted the plustrace
role to a database user called dbo.
but for some reason when i enable autotrace for the user
it still complains :

SQL> set autotrace traceonly;
SP2-0618: Cannot find the Session Identifier.  Check PLUSTRACE role is enabled
SP2-0611: Error enabling STATISTICS report

When i granted the plustrace role to user HR , the autotrace worked for HR.
Followup:
look up a couple of reviews.

I believe you have slightly mismatched versions of SQLPlus and database.

9203 and up use v$mystat
9202 and before use v$session

9201 client and lower queries v$session
9203 client and higher queries v$mystat

make sure plustrace has select on v$mystat and v$session or use the same version
of sqlplus as database.
March 31, 2004 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.
Rating: 4
Free 'kcrrlt' lock March 30, 2004
Reviewer: JHT from NJ USA
Thanks Tom for all the good information.  I have a similar problem is that an
archiver died abornormally and must have left this "kcrrlt" lock on one of my
redo logs #3 (I'm getting the "log actively being archived by another process").
 Now, I cannot get a new archiver to archive this log #3.  The db is smoothly
running and switching log files, but it always skips this log #3 because this
log is not yet archived.  How would I get rid of this lock and start using #3
again.  I'm guessing bouncing the DB will probably clear the lock, but it's a
production system.  Thanks.
Followup:
it would not skip it, it would get *stuck*.  lgwr would try to advance into this
log file and just halt the database until it was archived.

Something else is going on there.
Rating: 2
Free 'kcrrlt' lock March 30, 2004
Reviewer: JHT from NJ
Thanks Tom, but what else could be going on here?  Maybe some additional
information will be helpful.  The 9.2.0.3 primary database is also archiving
redo logs to a standby database.  The standby database server was down for an
extended period of time.  I see from the alert logs that the primary archiver is
getting the ORA-3113 (end-of-communication) to the standby db...that is
expected.  But because the standby archive destination is optional, the db
continues to run fine.

As expected, the alert logs shows a lot of "Log actively being archived by
another process", as I have 2 archivers. Of course, after one mention of this
error for a redo log, alert.log shows a "Completed archiving".

Finally, the 4 redo log groups are being switch in a round robin format as
expected, even as the archiver is failing with ORA-3113 when archiving to the
standby database.

Then all of a sudden, alert.log complains about redo log #3 with "Log actively
being archived by another process" continuously.  As before, the other 3 redo
log groups get this error once and then "completed archiving".  When I executed
"alter system switch logfile", the alert.log file shows that oracle switches
logs fine but it leaves log #3 out of the rotation.  In the meantime, alert.log
still complains about log #3 with the "Log actively being archived by another
process".

When I execute a "alter system archive log current" or "alter system archive log
all", the command hangs, as oracle cannot archive log #3.  I have tried to
restart the archivers with "alter system archive log stop" and "alter system
archive log start", but it doesn't resolve the problem.  I have also tried to
change the number of archivers down to 1 with the "max_archive_processes"
(forgot the exact parameter name), but to no avail.
Followup:
Please contact support for this.  They'll extract the relevant information they
need to diagnose if there is a problem or not.
March 31, 2004 Irene -- Thanks for the question regarding "Global variables vs parameters", version forms 6i
You Asked
Hi Tom,
could you explain advantages and disadvantages
of using global variables and parameters in Forms
in terms of efficiency of application and system resourses.
What would you recommend  for passing values between
blocks and program units in one form.
Thank you.
Irene
and we said...
Avoid global variables whenever possible (they are OK to some degree in a
package body to maintain state and because they use of them is confined to the
pacakge body).

Global Globals should be avoided whenever you can.  Use them only when you have
to.  They are a BUG just waiting to happen.

Formal parameters are the way to go.
Review & Followup

Rating: 5
June 28, 2001
Reviewer: Helena Markova from Bratislava, Slovakia
Rating: 3
I Agree with Tom June 28, 2001
Reviewer: Pichaimani Balasubramanian from San Jose, CA USA
In the past I've encountered some serious bugs because of Global Variables. Its
better to avoid it unless it is very necessary.
Rating: 4
pacakge body global variables September 05, 2002
Reviewer: john
are the variables declared on top of PACKAGE BODY that is the variables that are
global to a package body are also main tain state

i have :
SQL> create or replace package my_pack AS
  2
  3  PROCEDURE P1;
  4  END;
  5  /

SQL> Create or replace package BODY my_pack AS
  2
  3  v1 varchar2(1) := 'N';
  4
  5  PROCEDURE P1 AS
  6  BEGIN
  7   DBMS_OUTPUT.PUT_LINE(V1);
  8   IF v1 = 'N' THEN
  9    V1 := 'Y';
 10   END IF;
 11   DBMS_OUTPUT.PUT_LINE(V1);
 12  END;
 13  END;
 14  /

SQL> EXEC MY_PACK.P1;
N
Y

PL/SQL procedure successfully completed.

SQL> EXEC MY_PACK.P1;
Y
Y

any idea why is this happening
Followup:
any variable defined outside of a procedure/function is a global variable and
maintains its state for the duration of the session.
Rating: 4
constatnt global variables September 06, 2002
Reviewer: A reader
thanks tom
i understand now that we should try avoiding global varaibles.

in our project, to globally access the constants, we created a package that will
have only constatns declared. other packages can refer these constants and use
them in their body.

now will that create any problem, we are only in the development process, if
that is a problem we can remove it.
please guide.
Followup:
constants are constants - they are perfect for global variables.  Just define
them as constant!
Rating: 5
Global variables vs parameters September 06, 2002
Reviewer: Rama from CT , USA
Rating: 3
Global Variables March 31, 2004
Reviewer: phil from Paris
Hi Tom
I have a package spec as follows...

CREATE OR REPLACE PACKAGE inte AS

  c_exact     CONSTANT NUMBER      := 0.99;

END;

But when I try and access this from a trigger I get
and error on this line...
IF l_prev.it >= inte.c_exact THEN

The error is PLS-00302: component 'C_EXACT' must be declared

Is this not available from the trigger?
Followup:
ops$tkyte@ORA9IR2> create or replace package inte
  2  as
  3    c_exact  constant number := 0.99;
  4  end;
  5  /

Package created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> drop table t;

Table dropped.

ops$tkyte@ORA9IR2> create table t ( x int );

Table created.

ops$tkyte@ORA9IR2> create or replace trigger t_trigger
  2  before insert on t for each row
  3  begin
  4          if ( :new.x >= inte.c_exact )
  5          then
  6             null;
  7          end if;
  8  end;
  9  /

Trigger created.

please give me the full test case
March 31, 2004 Haranadh -- Thanks for the question regarding "Problem with BULK COLLECT with million rows ", version 9.0.1.4
You Asked
Hi,

We have a requirement where are supposed to load 58 millions of rows into a FACT
Table in our DATA WAREHOUSE. We initially planned to use Oracle Warehouse
Builder but due to performance reasons, decided to write custom code. We wrote a
custome procedure which opens a simple cursor and reads all the 58 million rows
from the SOURCE Table and in a loop processes the rows and inserts the records
into a TARGET Table. The logic works fine but it took 20hrs to complete the
load.

We then tried to leverage the BULK COLLECT and FORALL and PARALLEL options and
modified our PL/SQL code completely to reflect these. Our code looks very
simple.
1. We declared PL/SQL BINARY_INDEXed Tables to store the data in memory.
2. We used BULK COLLECT into FETCH the data.
3. We used FORALL statement while inserting the data.
We did not introduce any of our transformation logic yet.

We tried with the 600,000 records first and it completed in 1 min and 29 sec
with no problems. We then doubled the no. of rows to 1.2 million and the program
crashed with the following error:

*
ERROR at line 1:
ORA-04030: out of process memory when trying to allocate 16408 bytes (koh-kghu
call ,pmucalm coll)
ORA-06512: at "VVA.BULKLOAD", line 66
ORA-06512: at line 1

We got the same error even with 1 million rows.

We do have the following configuration:
SGA - 8.2 GB
PGA
- Aggregate Target - 3GB
- Current Allocated - 439444KB (439 MB)
- Maximum allocated - 2695753 KB (2.6 GB)
Temp Table Space - 60.9 GB (Total)
- 20 GB (Available approximately)

I think we do have more than enough memory to process the 1 million rows!!

Also, some times the same program results in the following error:
SQL> exec bulkload
BEGIN bulkload; END;

*
ERROR at line 1:
ORA-03113: end-of-file on communication channel

We did not even attempt the full load. Also, we are not using the PARALLEL
option yet.

Are we hitting any bug here? Or PL/SQL is not capable of mass loads? I would
appreciate any thoughts on this?

Thanks,
Haranadh

Following is the code:
--------------------------------------------------------------------------------

set echo off
set timing on

create or replace procedure bulkload as

-- SOURCE --
TYPE src_cpd_dt IS TABLE OF ima_ama_acct.cpd_dt%TYPE;
TYPE src_acqr_ctry_cd IS TABLE OF ima_ama_acct.acqr_ctry_cd%TYPE;
TYPE src_acqr_pcr_ctry_cd IS TABLE OF ima_ama_acct.acqr_pcr_ctry_cd%TYPE;
TYPE src_issr_bin IS TABLE OF ima_ama_acct.issr_bin%TYPE;
TYPE src_mrch_locn_ref_id IS TABLE OF ima_ama_acct.mrch_locn_ref_id%TYPE;
TYPE src_ntwrk_id IS TABLE OF ima_ama_acct.ntwrk_id%TYPE;
TYPE src_stip_advc_cd IS TABLE OF ima_ama_acct.stip_advc_cd%TYPE;
TYPE src_authn_resp_cd IS TABLE OF ima_ama_acct.authn_resp_cd%TYPE;
TYPE src_authn_actvy_cd IS TABLE OF ima_ama_acct.authn_actvy_cd%TYPE;
TYPE src_resp_tm_id IS TABLE OF ima_ama_acct.resp_tm_id%TYPE;
TYPE src_mrch_ref_id IS TABLE OF ima_ama_acct.mrch_ref_id%TYPE;
TYPE src_issr_pcr IS TABLE OF ima_ama_acct.issr_pcr%TYPE;
TYPE src_issr_ctry_cd IS TABLE OF ima_ama_acct.issr_ctry_cd%TYPE;
TYPE src_acct_num IS TABLE OF ima_ama_acct.acct_num%TYPE;
TYPE src_tran_cnt IS TABLE OF ima_ama_acct.tran_cnt%TYPE;
TYPE src_usd_tran_amt IS TABLE OF ima_ama_acct.usd_tran_amt%TYPE;

src_cpd_dt_array src_cpd_dt;
src_acqr_ctry_cd_array src_acqr_ctry_cd;
src_acqr_pcr_ctry_cd_array src_acqr_pcr_ctry_cd;
src_issr_bin_array src_issr_bin;
src_mrch_locn_ref_id_array src_mrch_locn_ref_id;
src_ntwrk_id_array src_ntwrk_id;
src_stip_advc_cd_array src_stip_advc_cd;
src_authn_resp_cd_array src_authn_resp_cd;
src_authn_actvy_cd_array src_authn_actvy_cd;
src_resp_tm_id_array src_resp_tm_id;
src_mrch_ref_id_array src_mrch_ref_id;
src_issr_pcr_array src_issr_pcr;
src_issr_ctry_cd_array src_issr_ctry_cd;
src_acct_num_array src_acct_num;
src_tran_cnt_array src_tran_cnt;
src_usd_tran_amt_array src_usd_tran_amt;

j number := 1;

CURSOR c1 IS
SELECT
cpd_dt,
acqr_ctry_cd ,
acqr_pcr_ctry_cd,
issr_bin,
mrch_locn_ref_id,
ntwrk_id,
stip_advc_cd,
authn_resp_cd,
authn_actvy_cd,
resp_tm_id,
mrch_ref_id,
issr_pcr,
issr_ctry_cd,
acct_num,
tran_cnt,
usd_tran_amt
FROM ima_ama_acct ima_ama_acct
ORDER BY issr_bin;

BEGIN

OPEN c1;

FETCH c1 bulk collect into
src_cpd_dt_array ,
src_acqr_ctry_cd_array ,
src_acqr_pcr_ctry_cd_array,
src_issr_bin_array ,
src_mrch_locn_ref_id_array,
src_ntwrk_id_array ,
src_stip_advc_cd_array ,
src_authn_resp_cd_array ,
src_authn_actvy_cd_array ,
src_resp_tm_id_array ,
src_mrch_ref_id_array ,
src_issr_pcr_array ,
src_issr_ctry_cd_array ,
src_acct_num_array ,
src_tran_cnt_array ,
src_usd_tran_amt_array ;

CLOSE C1;

FORALL j in 1 .. src_cpd_dt_array.count
INSERT INTO ima_dly_acct (
CPD_DT,
ACQR_CTRY_CD,
ACQR_TIER_CD,
ACQR_PCR_CTRY_CD,
ACQR_PCR_TIER_CD,
ISSR_BIN,
OWNR_BUS_ID,
USER_BUS_ID,
MRCH_LOCN_REF_ID,
NTWRK_ID,
STIP_ADVC_CD,
AUTHN_RESP_CD,
AUTHN_ACTVY_CD,
RESP_TM_ID,
PROD_REF_ID,
MRCH_REF_ID,
ISSR_PCR,
ISSR_CTRY_CD,
ACCT_NUM,
TRAN_CNT,
USD_TRAN_AMT)

VALUES (
src_cpd_dt_array(j),
src_acqr_ctry_cd_array(j),
null,
src_acqr_pcr_ctry_cd_array(j),
null,
src_issr_bin_array(j),
null,
null,
src_mrch_locn_ref_id_array(j),
src_ntwrk_id_array(j),
src_stip_advc_cd_array(j),
src_authn_resp_cd_array(j),
src_authn_actvy_cd_array(j),
src_resp_tm_id_array(j),
null,
src_mrch_ref_id_array(j),
src_issr_pcr_array(j),
src_issr_ctry_cd_array(j),
src_acct_num_array(j),
src_tran_cnt_array(j),
src_usd_tran_amt_array(j));
COMMIT;
END bulkload;
/
SHOW ERRORS
--------------------------------------------
and we said...
good gosh -- you aren't serious are you???

Use the LIMIT clause, bulk collect say 100 to 1000 rows -- process them, bulk
insert them, get the next 100/1000 rows.

You blew process memory -- not SGA.  Your process got bigger then your OS would
allow you (you hit an OS limit, might be ulimit related or whatever).

do something like this:

   open cursor;
   loop
       fetch c bulk collect into l_c1, l_c2, ....... LIMIT 1000;
       for i in 1 .. l_c1.count
       loop
            process....
       end loop;
       forall i in 1 .. l_c1.count
            insert into ..... values ( L_c1(i), .... );
       end loop;
       exit when c%notfound;
   end loop;

   close cursor

1000 would be extreme, 100 is reasonable.
Review & Followup

Rating: 5
Am I missing something obvious ? October 10, 2002
Reviewer: Connor McDonald from UK
Isn't this just

insert into x
select * from y

with some nologging, parallel, append etc etc thrown in if required?
Followup:
they have that unknown "process" in the middle.

If the process can be done in SQL, yes, this should be a single INSERT INTO
select * FROM ...
Rating: 2
LIMIT clause : Does it do things right October 10, 2002
Reviewer: Shivaji from UK
LIMIT clause will do one fetch less if the last fetch has less then [limit]
rows. in the above eg. 1000.
not sure if this is any version specfic.

The correct way would be to
exit when c%rowcount=0;
This was advised by a user on metalink forums. I am not a programmer. Keen to
know your view on it.
FYI, On my site guys use rownum clause to limit rows when using bulk-binds.
Followup:
I sure hope no one follows that advice, else they have just programmed that
rascally "infinite loop"

Consider:

ops$tkyte@ORA920.US.ORACLE.COM> declare
  2      type array is table of number index by binary_integer;
  3      l_data array;
  4      cursor c is select empno from emp;
  5  begin
  6      open c;
  7      loop
  8          fetch c bulk collect into l_data limit 5;
  9          /* process data here */
 10
 11          dbms_output.put_line( 'Looping, c%rowcount = ' || c%rowcount );
 12          exit when c%notfound;
 13      end loop;
 14      dbms_output.put_line( 'out of loop, c%rowcount = ' || c%rowcount );
 15      close c;
 16  end;
 17  /
Looping, c%rowcount = 5
Looping, c%rowcount = 10
Looping, c%rowcount = 14
out of loop, c%rowcount = 14

PL/SQL procedure successfully completed.

See, c%rowcount never "goes to zero", it goes up and up and stays at the total
number of rows fetched.  Exiting when it equals zero would work ONLY on a empty
table!!  all other tables - infinite loop.

Use the c%notfound at the bottom like I do and this won't happen.

Also, I utterly fail to see how you could use rownum to limit rows with bulk
binds, it it not possible.
Rating: 2
LIMIT clause : Does it do things right October 10, 2002
Reviewer: Shivaji from UK
LIMIT clause will do one fetch less if the last fetch has less then [limit]
rows. in the above eg. 1000.
not sure if this is any version specfic.

The correct way would be to
exit when c%rowcount=0;
This was advised by a user on metalink forums. I am not a programmer. Keen to
know your view on it.
FYI, On my site guys use rownum clause to limit rows when using bulk-binds.
Rating: 3
Limit on Bulk Collect ! October 10, 2002
Reviewer: Rama from CT, USA
Is 1000 the upper limit? or is it lot more faster to work with 100 and bulk
insert and then grab the next set ?
Followup:
It is a suggestion (from me).

You want to "stream" data -- get some, process some, write some, get some,
process some, write some.

You don't want to GET ALL, process all, WRITE ALL and flood one thing or the
other -- do a bit, process a bit, write a bit, start over.
Rating: 4
October 10, 2002
Reviewer: Haranadh from Foster City, CA USA
Thanks Tom. This is very useful. I will change the code to process 100 at a
time.

Thanks,
Haranadh
Rating: 4
October 10, 2002
Reviewer: Larry Liang from Canada
Excellent explanation.  I am rewriting a process to use bulk collect instead of
inserting into a temporary table.  This is great info.
Rating: 5
November 20, 2002
Reviewer: Sunil Nettyam from MA
Hi Tom,

The info is excelent, very usefull for us.
Just wanted to know
why for LIMIT
1000 would be extreme, 100 is reasonable.

Why cannot i say LIMIT 10000

Thanks
Sunil
Followup:
You can say it, you can say whatever you like.

To me -- 1000 rows in an array fetch, based on experience -- past performance --
memory usage -- it 10 times larger then I would recommend.
Rating: 5
Perhaps this is what Shivaji refers to December 08, 2002
Reviewer: Basil
A poster above, Shivaji, indicates that he uses rownum to limit the size of the
bulk bind. The Oracle docs on this *sort of* imply that rownum is the way to do
it:
http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96624/05_colls.htm#28329
(Search the page for "ROWNUM to limit the number of rows".)
Followup:
rownum would limit the TOTAL NUMBER OF ROWS IN THE ENTIRE RESULT SET.

LIMIT is used with bulk collect in order to fetch N rows at a time from a result
set.

So, rownum changes the answer -- limits total number of rows.

LIMIT on a bulk collect lets you get the entire answer, N rows at at time.
Rating: 4
December 09, 2002
Reviewer: Robert from PA
>>Also, I utterly fail to see how you could use rownum to >>limit rows with bulk
binds, it it not possible.

I think he meant "...using ROWNUM to limit rows returned by the SQL" like:
  WHERE ....
    AND ROWNUM < LEAST (maxrows, 1000);
'Cause he did say "...on my site....."
He also did say he's no programmer, so let's give'm a break...
Rating: 4
BULK Collect with dbms_sql December 21, 2002
Reviewer: Edgar from Riga, Latvia
Can we use bulk collect when we are performing select using dbms_sql package on
Oracle9iR2? Thank you.
Followup:
No, but you can use array fetching.  Read the dbms_sql docs in the supplied
packages guide for details.

bulk collect = native dynamic sql syntax.

array binds and array fetches = dbms_sql
Rating: 4
Periodically Commiting June 12, 2003
Reviewer: John Durst from Kansas City, MO USA
I am doing something very similar to this, but would like to commit changes
periodically during the bulk fetch.  I have commit logic in the loop, but Oracle
appears to be ignoring the commits.  Is this because (unlike single row cursors)
bulk fetch cursors do not allow fetches across commits?
Followup:
why do you say that?  do you have a test case.

we don't "ignore" things like that.
Rating: 4
Never Mind! June 12, 2003
Reviewer: John Durst from Kansas City, MO USA
Just a case of fast extraction of existing code and integration with another
person's code followed by insufficient review and testing.  (In other words, I
screwed up by not including the section that included the initialization of my
commit counter.)  What do you get when you add one to null???
Followup:
:)

answer: something different then when you concatenate a non-null string to a
null.
Rating: 4
Planning Something Similiar June 12, 2003
Reviewer: Brian from Phoenix
Currently, I have SQL code that I run when some data warehouse tables are
finished updating at the end of each month.

I would like to move my coding into a trigger that writes my results to a table
when the data warehouse tables are updated at the end of the month, but to do so
I would have to re-write all my code in PL/SQL (I think to use triggers), which
I do not want to do because I'm joining multiple tables, and I am using inline
views.

Is the answer you gave initially the only way to write results to a new table in
PL/SQL when processing in excess of a million rows?

Thanks,

Brian
Followup:
don't understand what you are saying/asking at all?

but -- if you have something written in SQL, that would be the most efficient
way to do it.  A single sql statement is much more efficient then lots of
procedural -- row by row code.  Stick with it.
Rating: 4
Long Day Yesterday June 13, 2003
Reviewer: Brian from Phoenix
Is there anyway to automate SQL code to run when tables are updated, created,
etc. without using Triggers that require PL/SQL?

Brian
Followup:
PLSQL runs SQL just fine.  I still don't understand why you think by that by
using a trigger, you would not be able to use SQL?
Rating: 5
We Don't "Ignore" Things Like That June 13, 2003
Reviewer: John Durst from Kansas City, MO
Have you ever put commits in a loop for a cursor that does a SELECT FOR UPDATE?
Followup:
I don't understand the context of this comment?
Rating: 4
SQL in PL/SQL June 16, 2003
Reviewer: Brian from Phoenix
Is there a way to copy and paste SQL code as is in between BEGIN and END in
PL/SQL without having to re-write the SQL code in PL/SQL?

I ask because my query looks like:

select x, y, z, sum(a), sum(b), sum(c)

from (

      select x, to_number(substr(y,1,5)), z, sum(a) a, sum(b) b, sum(c) c
      where  x = 200305
      from cl.x = vl.x

      group by x, y, z
)

where y = 20336

group by x, y, z
/

Thanks
Followup:
tell me, what would you have to "rewrite"  PLSQL is a programming language that
lets you embedd sql in it easily?

(i hope your query doesn't really look like that -- forget for a minute that it
isn't valid...)
Rating: 3
Sorry About the Typos June 17, 2003
Reviewer: Brian from Phoenix
I have a few typos in the above query.  I attempted from memory to duplicate the
general look of the query I had written, which is a lot longer, pulls off of 4
different tables, and has multiple joins.

Nevertheless, before I return back to this thread, let me make an attempt to
convert it over to PL/SQL.

I'm just having a hard time understanding how to integrate SQL into PL/SQL.

Thanks.
Followup:
It is trivial -- maybe you are making it too hard?  it is just

 for x in ( select .... )
 loop
    process.....
Rating: 5
Thanks June 19, 2003
Reviewer: Brian from Phoenix
I got hung up on the coding aspect of PL/SQL.

The CURSOR FOR loop worked great for my dbms_jobs.  Though, per your book, I
need to use ref cursors for analytic functions unless I have them in a view.

Realizing that the CURSOR FOR loop allowed me to do nightly inserts into tables
with query results, when would it be best to use to BULK COLLECT features as the
original question was using?

Thanks
Followup:
if you are processing more then about 100 rows, BULK collect makes a great deal
of sense.
Rating: 4
One Mode Doubt on Bulk Insert "FORALL" clause July 22, 2003
Reviewer: H.S.Anand from New Delhi, India
Hi Tom,
Thank you for the explanation. I am facing a problem while using Bulk Collect
and FORALL clause!
The error message that i get is : "Implementation Restriction".
What i do is that I create record type and then create a PL/SQL "index by
binary_integer" table based on that record type. I populate this table using the
normal loop. Now when I use forall ... for the insert, i get the error of
Implementation Restriction. Can you please clarify?
The sample code is as follows:
DECLARE
  rec_emp1 is record (
     empno   emp.empno%type,
     ename   emp.ename%type,
     job     emp.job%type
     );
  tb_emp1 is table of rec_emp1 index by binary_integer;
  tab_emp1 tb_emp1;
  x_ctr  binary_integer :=1;
BEGIN
  FOR i in (select empno,ename,job from emp) loop
    tab_emp1(x_ctr).empno := i.empno;
    tab_emp1(x_ctr).ename := i.ename;
    tab_emp1(x_ctr).job   := i.job;
    x_ctr := x_ctr+1;
  END LOOP;
  FORALL J IN tab_emp1.first..tab_emp1.last
   insert into emp2 (empno,ename,job) values
(tab_emp1(j).empno,tab_emp1(j).ename,tab_emp1(j).job);

 commit;
EXCEPTION
 WHEN OTHERS THEN
  dbms_output.put_line('Errors OCcurred:'||sqlerrm);
END;
/

---------
What is wrong with the above code?

Thanks,
Followup:
in earlier, pre9i releases, you could use a RECORD of ARRAYS

you could NOT use an ARRAY of RECORDS.
Rating: 5
Thanks July 22, 2003
Reviewer: Brian from Phoenix
I found the article 'Forall update - updating mulitple columns' to be quite
insightful as to how to implement the bulk collect feature when processing more
than 100 rows.

I just need to insert into instead of update.

Thanks!
Rating: 5
What is better bulk collect or simple for loop September 02, 2003
Reviewer: A reader
I have a temp table with appropriate indexes with 200,000 rows.
 Which is more effcient and faster.
for i in ( select * from temp_table order by id ) loop
conditionally insert in 3 master detail tables .
end loop;

Or
Use Bulk collect to load temp_table in plsql table with limit clause and
then use
  for i in 1..plsqltable.count loop
   conditionally insert in 3 Master detail  tables .
  end loop
 ( Cant use forall here as it involves 3 tables ..correct me if I am wrong
please )
Thanx Sir.
Followup:
wrong answer on both counts

insert into select <with conditions>

is better in all cases.

don't know why you think you cannot use "forall", you'll have to explain that a
bit.
Rating: 5
Explanation:What is better bulk collect or simple for loop September 02, 2003
Reviewer: A reader
Thanx for you feedback Sir.
I cant do a insert into select because I need to get the generated PK value from
the first table and use it as FK in the second insert.
 Then use the PK generated by the second insert and
used both ( table 1 and table 2 PKs) as FKs in the third table insert.
Q. given this case how can you use insert select method.

As I am inserting into 3 tables in specific order thats why  I think forall wont
work here
as forall is just tied to single dml statement. Correct ?

 By the way what is the best method here.If not insert select then which of the
above 2 would you recommend or is it still possible to use insert select method
?
Followup:
bulk colllect and forall inserts then.

so what if forall is tied to a single dml???  so is "insert a row at a time".
Rating: 5
db version is 817 September 02, 2003
Reviewer: A reader
Rating: 5
What is better bulk collect or simple for loop September 03, 2003
Reviewer: Mohan from Bangalore
You can do insert into <table_name> select

You may have to do it three times. Create a trigger to generate PK for the first
table and insert into the table. Create a trigger to generate PK for the second
table. Insert into this table using a select statement which joins the first
table and temp_table. Do the same procedure for the third table. Indexes should
be present for optimizing joins.

Mohan
Rating: 4
BULK Collect and BULK insert September 04, 2003
Reviewer: A reader
Hi Tom,

Does global temporary tables works as fast as collection objects for inserting a
set of rows. Inserting into GTT by using a single SQL statement vs fetch BULK
into the collection object.

Another option to transfer rows from collection object to a table other than
bulk insert is

insert into <tablename> select * from TABLE(select cast(ntp as numtype) from
dual);
Followup:
you cannot compare the two (gtts and collections) they are totally different
beasts.

for every case where you say "collections are better", i'll give you a use case
where a gtt is.

and vice versa.

they are both tools, nothing more, nothing less.  they both can be used in
different circumstances.
Rating: 4
bulk collect explain plan January 08, 2004
Reviewer: Bogy from Germany
Hi Tom,

Short question.
Does bulk collect clause in PL/SQL code make any changes on execution plan?
Example:
select a
  bulk collect
  into v_a
  from table_abcd t
 where t.col_1 = 'aaa'
   and t.col_2 = 5;

Let us say that we have index on a t.col_1 and t.col_2. Does this select
statement perform index scan (which should do) or do FTS which is wrong?

Thanks,

Bogy
Followup:
bulk collect won't make a difference however, your statement:

<quote>
Let us say that we have index on a t.col_1 and t.col_2. Does this select
statement perform index scan (which should do) or do FTS which is wrong?
</quote>

is *wrong*.

create table table_abcd ( col_1 varchar2(3), col_2 int, data char(80) );

insert into table_abcd select 'aaa', 5, object_name from all_objects;
insert into table_abcd select * from table_abcd;
insert into table_abcd select * from table_abcd;
insert into table_abcd select * from table_abcd;
insert into table_abcd select * from table_abcd;

insert into table_abcd values ( 'xyz', 6, 'hello world' );

Now, your query had better full scan, but my query:

select * from v_a from table_abcd where col_1 = 'xyz' and col_2 = 6;

better index range scan.
Rating: 4
Is element order in collections gauranteed? February 11, 2004
Reviewer: John from San Jose, CA
Hi Tom,

If I run this statement twice into 2 collection variables x and y

select table1.column2 bulk collect into x from table1, table2 where
table1.column1=table2.column1;
select table1.column2 bulk collect into y from table1, table2 where
table1.column1=table2.column1;

will x(i)=y(i) always?
My test seems to say yes. But I can not say for sure if this is always true. And
I recall reading somewhere that 'the order of collections is not gauaranteed'.
Can your comment on this? Thanks!

John
Followup:
no.

unless you have an ORDER BY, the order of results can be different.

this has to do with the order of rows from a query -- not anything with
collections really.
Rating: 3
February 11, 2004
Reviewer: Andrew McCallum from Sydney, Australia
Tom, In your sample:

> open cursor;
>   loop
>       fetch c bulk collect into l_c1, l_c2, ....... LIMIT 1000;
>       for i in 1 .. l_c1.count
>       loop
>            process....
>       end loop;
>       forall i in 1 .. l_c1.count
>            insert into ..... values ( L_c1(i), .... );
>       end loop;
>       exit when c%notfound;
>   end loop;
>   close cursor

I don't understand why you put the exit at the bottom of the loop.  I usually
put it immediately after the fetch.  Won't your loop process the forall
statements with an empty array on the last time through the loop?
Followup:
consider:

ops$tkyte@ORA9IR2> declare
  2      type array is table of number index by binary_integer;
  3      l_data array;
  4
  5      cursor c is select empno from emp;
  6  begin
  7      open c;
  8      loop
  9          fetch c bulk collect into l_data limit 10;
 10
 11          if ( c%notfound )
 12          then
 13              dbms_output.put_line
 14              ( 'Cursor returned NOT FOUND but array has ' || l_data.count
 15                 || ' left to process' );
 16          else
 17              dbms_output.put_line
 18              ( 'We have ' || l_data.count
 19                 || ' to process' );
 20          end if;
 21
 22          exit when c%notfound;
 23      end loop;
 24      close c;
 25  end;
 26  /
We have 10 to process
Cursor returned NOT FOUND but array has 4 left to process

PL/SQL procedure successfully completed.

if you exit when you get notfound, you end up leaving unprocessed records in the
array.  On the second bulk fetch from this 14 row table -- we get 4 records AND
get notified that we hit a notfound condition.

If we left the loop right then, you would have 4 records left over.....
Rating: 3
February 11, 2004
Reviewer: Andrew McCallum from Sydney, Australia
Tom, In your sample:

> open cursor;
>   loop
>       fetch c bulk collect into l_c1, l_c2, ....... LIMIT 1000;
>       for i in 1 .. l_c1.count
>       loop
>            process....
>       end loop;
>       forall i in 1 .. l_c1.count
>            insert into ..... values ( L_c1(i), .... );
>       end loop;
>       exit when c%notfound;
>   end loop;
>   close cursor

I don't understand why you put the exit at the bottom of the loop.  I usually
put it immediately after the fetch.  Won't your loop process the forall
statements with an empty array on the last time through the loop?
Rating: 5
I have the exit at the base of a loop in some of my production code! February 12, 2004
Reviewer: Neil from UK
But Andrew's got me thinking so I did a quick test:

DECLARE
  -- Rows retrieved exactly divisible by LIMIT
  -- EXIT WHEN c%NOTFOUND; at foot of loop
  -- Seems to process the last chunk twice
  TYPE ARRAY IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
  l_data ARRAY;
  CURSOR c IS SELECT object_id FROM ALL_OBJECTS WHERE ROWNUM < 101;
BEGIN
  OPEN c;
  LOOP
    FETCH c BULK COLLECT INTO l_data LIMIT 10;
    /* process data here */
    dbms_output.put_line( 'Looping, c%rowcount = ' || c%rowcount );
    EXIT WHEN c%NOTFOUND;
  END LOOP;
    dbms_output.put_line( 'out of loop, c%rowcount = ' || c%rowcount );
  CLOSE c;
END;
/
Looping, c%rowcount = 10
Looping, c%rowcount = 20
Looping, c%rowcount = 30
Looping, c%rowcount = 40
Looping, c%rowcount = 50
Looping, c%rowcount = 60
Looping, c%rowcount = 70
Looping, c%rowcount = 80
Looping, c%rowcount = 90
Looping, c%rowcount = 100
Looping, c%rowcount = 100 <===
out of loop, c%rowcount = 100

DECLARE
  -- Rows retrieved NOT divisible by LIMIT
  -- EXIT WHEN c%NOTFOUND; at foot of loop
  -- behavior ok
  TYPE ARRAY IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
  l_data ARRAY;
  CURSOR c IS SELECT object_id FROM ALL_OBJECTS WHERE ROWNUM < 100;
BEGIN
  OPEN c;
  LOOP
    FETCH c BULK COLLECT INTO l_data LIMIT 10;
    /* process data here */
    dbms_output.put_line( 'Looping, c%rowcount = ' || c%rowcount );
    EXIT WHEN c%NOTFOUND;
  END LOOP;
    dbms_output.put_line( 'out of loop, c%rowcount = ' || c%rowcount );
  CLOSE c;
END;
/
Looping, c%rowcount = 10
Looping, c%rowcount = 20
Looping, c%rowcount = 30
Looping, c%rowcount = 40
Looping, c%rowcount = 50
Looping, c%rowcount = 60
Looping, c%rowcount = 70
Looping, c%rowcount = 80
Looping, c%rowcount = 90
Looping, c%rowcount = 99
out of loop, c%rowcount = 99

DECLARE
  -- Rows retrieved exactly divisible by LIMIT
  -- EXIT WHEN c%NOTFOUND; after fetch
  -- behaviour ok
  TYPE ARRAY IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
  l_data ARRAY;
  CURSOR c IS SELECT object_id FROM ALL_OBJECTS WHERE ROWNUM < 101;
BEGIN
  OPEN c;
  LOOP
    FETCH c BULK COLLECT INTO l_data LIMIT 10;
    EXIT WHEN c%NOTFOUND;
    /* process data here */
    dbms_output.put_line( 'Looping, c%rowcount = ' || c%rowcount );
  END LOOP;
    dbms_output.put_line( 'out of loop, c%rowcount = ' || c%rowcount );
  CLOSE c;
END;
/
Looping, c%rowcount = 10
Looping, c%rowcount = 20
Looping, c%rowcount = 30
Looping, c%rowcount = 40
Looping, c%rowcount = 50
Looping, c%rowcount = 60
Looping, c%rowcount = 70
Looping, c%rowcount = 80
Looping, c%rowcount = 90
Looping, c%rowcount = 100
out of loop, c%rowcount = 100
DECLARE
  -- Rows retrieved NOT divisible by LIMIT
  -- EXIT WHEN c%NOTFOUND; after fetch
  -- Last 9 records not dealt with !
  TYPE ARRAY IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
  l_data ARRAY;
  CURSOR c IS SELECT object_id FROM ALL_OBJECTS WHERE ROWNUM < 100;
BEGIN
  OPEN c;
  LOOP
    FETCH c BULK COLLECT INTO l_data LIMIT 10;
    EXIT WHEN c%NOTFOUND;
    /* process data here */
    dbms_output.put_line( 'Looping, c%rowcount = ' || c%rowcount );
  END LOOP;
    dbms_output.put_line( 'out of loop, c%rowcount = ' || c%rowcount );
  CLOSE c;
END;
/
Looping, c%rowcount = 10
Looping, c%rowcount = 20
Looping, c%rowcount = 30
Looping, c%rowcount = 40
Looping, c%rowcount = 50
Looping, c%rowcount = 60
Looping, c%rowcount = 70
Looping, c%rowcount = 80
Looping, c%rowcount = 90
out of loop, c%rowcount = 99

So it appears that an exit immediately after the fetch will not deal with the
last chunk of rows if the number of rows is NOT divisible by LIMIT.
It also appears that an exit at the foot of the loop will deal with the last
chunk twice if the amount of records is divisible by the limit!
What's the official line?
Followup:
see above -- the last fetch in general will

a) signal the notdatafound
b) return some more rows to be processed

by definition.
Rating: 5
To Neil February 12, 2004
Reviewer: Kim Berg Hansen from Middelfart, Denmark
Hi

Last chunk is not processed twice - you get an empty array :

  1  DECLARE
  2    -- Rows retrieved exactly divisible by LIMIT
  3    -- EXIT WHEN c%NOTFOUND; at foot of loop
  4    -- Seems to process the last chunk twice
  5    TYPE ARRAY IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
  6    l_data ARRAY;
  7    CURSOR c IS SELECT object_id FROM ALL_OBJECTS WHERE ROWNUM < 101;
  8  BEGIN
  9    OPEN c;
 10    LOOP
 11      FETCH c BULK COLLECT INTO l_data LIMIT 10;
 12      /* process data here */
 13      dbms_output.put_line( 'Looping, c%rowcount = ' || c%rowcount || ' -
array count = ' || l_data.count);
 14      EXIT WHEN c%NOTFOUND;
 15    END LOOP;
 16      dbms_output.put_line( 'out of loop, c%rowcount = ' || c%rowcount );
 17    CLOSE c;
 18* END;
SQL> /
Looping, c%rowcount = 10 - array count = 10
Looping, c%rowcount = 20 - array count = 10
Looping, c%rowcount = 30 - array count = 10
Looping, c%rowcount = 40 - array count = 10
Looping, c%rowcount = 50 - array count = 10
Looping, c%rowcount = 60 - array count = 10
Looping, c%rowcount = 70 - array count = 10
Looping, c%rowcount = 80 - array count = 10
Looping, c%rowcount = 90 - array count = 10
Looping, c%rowcount = 100 - array count = 10
Looping, c%rowcount = 100 - array count = 0
out of loop, c%rowcount = 100

PL/SQL procedure successfully completed.

If you feel you don't wan't to "waste" time processing the empty array then you
might do :

DECLARE
  -- Rows retrieved exactly divisible by LIMIT
  -- EXIT WHEN c%NOTFOUND; at foot of loop
  TYPE ARRAY IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
  l_data ARRAY;
  CURSOR c IS SELECT object_id FROM ALL_OBJECTS WHERE ROWNUM < 101;
BEGIN
  OPEN c;
  LOOP
    FETCH c BULK COLLECT INTO l_data LIMIT 10;
    IF l_data.count > 0 THEN
      /* process data here */
    END IF;
    EXIT WHEN c%NOTFOUND;
  END LOOP;
  CLOSE c;
END;

Tom? I guess Neil's concern is whether this forall :
>       forall i in 1 .. l_c1.count
>            insert into ..... values ( L_c1(i), .... );
>       end loop;
is "bad" with an empty array, or what ?

Is it "worth it" to enclose the logic in "IF l_data.count > 0 THEN" or is it
allright to just let the forall statement decide that it's not going to do
anything at all?

My quess is, that it's probable not worth worrying about, as you almost always
will use some construct that references l_data.count anyway to process your
array.
Rating: 5
Correction to last post February 12, 2004
Reviewer: Kim Berg Hansen from Middelfart, Denmark
Sorry - made a typo

Please erase line :

"Tom? I guess Neil's concern is whether this forall :"

and replace with :

"Tom? I guess Andrew's concern is whether this forall :"

Sorry ;-)
Followup:
you could code:

  loop
     fetch c bulk collect into l_data limit N;
     exit when l_data.count = 0;
     process...
     exit when c%notfound;
  end loop;

to catch "both" conditions ASAP
Rating: 5
It can be done at either the head or the foot February 12, 2004
Reviewer: Neil from UK
But the test has to change

"the last fetch in general will

a) signal the notdatafound
b) return some more rows to be processed

by definition."

Each fetch must therefore do a read ahead to determine whether there are any
more rows to process (Of course it does, I remember reading it in 1 on 1 now!).
So, if the rowcount is tested at the head of the loop directly after the fetch,
one risks leaving the last n rows unprocessed. Putting the same test at the foot
of the loop will work correctly, beacuse the condition is not tested until after
the last rows are fetched and processed.

However, if I wanted to see the exit condition at the head of the loop, I could
code the following which seems to work:

DECLARE
  -- EXIT WHEN l_data.COUNT = 0; after fetch
  -- behaviour ok
  TYPE ARRAY IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
  l_data ARRAY;
  CURSOR c IS SELECT object_id FROM ALL_OBJECTS WHERE ROWNUM < 100;
BEGIN
  OPEN c;
  LOOP
    FETCH c BULK COLLECT INTO l_data LIMIT 10;
    EXIT WHEN l_data.COUNT = 0;
    /* process data here */
    dbms_output.put_line( 'Looping, c%rowcount = ' || c%rowcount );
  END LOOP;
    dbms_output.put_line( 'out of loop, c%rowcount = ' || c%rowcount );
  CLOSE c;
END;

The array must automatically be cleared before a fetch is made.
Followup:
see above -- you would "over fetch" technically -- we can skip all of the work
by using "two" exits.
Rating: 5
Behaviour of %FOUND and %NOTFOUND with BULK COLLECT February 12, 2004
Reviewer: Kim Berg Hansen from Middelfart, Denmark
Just got to test it a bit further.

I don't know if this surprises some :

  1  CREATE OR REPLACE PROCEDURE do_bulk(do_rows IN NUMBER)
  2  IS
  3    TYPE ARRAY IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
  4    l_data ARRAY;
  5    CURSOR c IS SELECT object_id FROM ALL_OBJECTS WHERE ROWNUM <= do_rows;
  6  BEGIN
  7    dbms_output.put_line( 'Before loop, do_rows: ' || do_rows );
  8    OPEN c;
  9    LOOP
 10      FETCH c BULK COLLECT INTO l_data LIMIT 10;
 11      dbms_output.put( '  Looping, rowcount: ' || c%rowcount || ' arraycount:
' || l_data.count );
 12      IF c%FOUND THEN
 13        dbms_output.put( ' found=true' );
 14      ELSE
 15        dbms_output.put( ' found=false' );
 16      END IF;
 17      IF c%NOTFOUND THEN
 18        dbms_output.put( ' notfound=true' );
 19      ELSE
 20        dbms_output.put( ' notfound=false' );
 21      END IF;
 22      dbms_output.new_line;
 23      EXIT WHEN c%NOTFOUND;
 24    END LOOP;
 25    dbms_output.put_line( 'Out of loop, rowcount: ' || c%rowcount || '
arraycount: ' || l_data.count );
 26    CLOSE c;
 27* END;
SQL> /

Procedure created.

SQL> call do_bulk(19);
Before loop, do_rows: 19
Looping, rowcount: 10 arraycount: 10 found=true notfound=false
Looping, rowcount: 19 arraycount: 9 found=false notfound=true
Out of loop, rowcount: 19 arraycount: 9

Call completed.

SQL> call do_bulk(20);
Before loop, do_rows: 20
Looping, rowcount: 10 arraycount: 10 found=true notfound=false
Looping, rowcount: 20 arraycount: 10 found=true notfound=false
Looping, rowcount: 20 arraycount: 0 found=false notfound=true
Out of loop, rowcount: 20 arraycount: 0

Call completed.

SQL> call do_bulk(21);
Before loop, do_rows: 21
Looping, rowcount: 10 arraycount: 10 found=true notfound=false
Looping, rowcount: 20 arraycount: 10 found=true notfound=false
Looping, rowcount: 21 arraycount: 1 found=false notfound=true
Out of loop, rowcount: 21 arraycount: 1

Call completed.

So c%FOUND is only true if the array has been filled !

So you WILL have errors if you test for %NOTFOUND at the top of the loop.

If your body of the loop in some way uses either a FOR or a FORALL that uses the
array.count - then you can safely test for %NOTFOUND at the BOTTOM of the loop.
Even when you hit a number of rows that's exactly divisible with the LIMIT, then
your array.count will just be zero and your FOR/FORALL will just do nothing.

Or you can just always test for array.count.
Rating: 5
Belt and Braces February 12, 2004
Reviewer: Neil from UK
Woah! Those replies were fast! I guess we overlapped a bit there, guys!
Two tests - to be sure to be sure!
-_*
Rating: 5
Removing collection elements and using forall February 20, 2004
Reviewer: Chris MillHill from Sweden
Tom,

How can I best use

* bulk collect into a collection
* loop and do some processing where I need to remove some of the collection
elements
* forall insert of the collection

Like:

   loop
       fetch c bulk collect into l_c1, l_c2, ....... LIMIT 1000;
       for i in 1 .. l_c1.count
       loop
            process....
            if some_condidtion then
                 l_c1.delete(i);
                 l_c2.delete(i);
            end if;
       end loop;
       forall i in 1 .. l_c1.count
            insert into ..... values ( L_c1(i), .... );
       end loop;
       exit when c%notfound;
   end loop;

Fully understandable, this runs into a ORA-22160 since some of the elements has
been removed. But how can I easily bypass the above problem? During the
processing I could build up a new collection of elements that should be
inserted, instead of removing some from the existing, but since only approx 1%
of the elements will be deleted, it doesn't feel like a good solution.

Thanks,
Followup:
before I answer in 9i and before terms, lets look at what 10g will let us do.
Basically 2 new things:

o "forall i in INDICES of collection_variable" -- iterate over a "sparse array"
with holes in it (caused by .delete(i))

o "forall i in values of some_collection_filled_with_subscripts" -- iterate over
the values in some collection -- using the values you find in that
array/collection as the subscripts.  sort of like saying "forall i in ( 1, 3, 5,
6, 9, 10, 11 )"  -- if you had those values in some collection, it would do
that.

Here are the examples:

ops$ora10g@ORA10G> create table emp as select * from scott.emp;
Table created.

ops$ora10g@ORA10G> declare
  2      type array is table of number;
  3      l_empnos   array;
  4      l_sals     array;
  5  begin
  6      select empno, sal
  7        bulk collect into l_empnos, l_sals
  8        from emp;
  9      for i in 1 .. l_empnos.count
 10      loop
 11          if ( l_sals(i) < 1000 )
 12          then
 13              l_empnos.delete(i);
 14          end if;
 15      end loop;
 16      forall i in INDICES OF l_empnos
 17          delete from emp where empno = l_empnos(i);
 18  end;
 19  /

PL/SQL procedure successfully completed.

there we forall'ed over all "filled in" elements in the l_empnos collection --
deleting 12 of 14 rows:

ops$ora10g@ORA10G> select empno, ename, sal from emp;

     EMPNO ENAME             SAL
---------- ---------- ----------
      7369 SMITH             800
      7900 JAMES             950

ops$ora10g@ORA10G> delete from emp;
2 rows deleted.

ops$ora10g@ORA10G> insert into emp select * from scott.emp;
14 rows created.

ops$ora10g@ORA10G> commit;
Commit complete.

Here we'll do the same logic sort of.  we still want to delete those guys, but
we want to update the other two.  we'll setup a pair of "subscript arrays" in
this case, one full of subscripts into the l_empnos collection to be deleted,
the other to be updated:

ops$ora10g@ORA10G> declare
  2      type array is table of number;
  3      type idx_array is table of binary_integer index by binary_integer;
  4      l_empnos   array;
  5      l_sals     array;
  6      l_update   idx_array;
  7      l_delete   idx_array;
  8  begin
  9      select empno, sal
 10        bulk collect into l_empnos, l_sals
 11        from emp;
 12
 13      for i in 1 .. l_empnos.count
 14      loop
 15          if ( l_sals(i) < 1000 )
 16          then
 17              l_update(l_update.count) := i;
 18          else
 19              l_delete(l_delete.count) := i;
 20          end if;
 21      end loop;
 22
 23      forall i in values of l_update
 24          update emp set sal = sal * 2 where empno = l_empnos(i);
 25
 26      forall i in values of l_delete
 27          delete from emp where empno = l_empnos(i);
 28  end;
 29  /

PL/SQL procedure successfully completed.

ops$ora10g@ORA10G> select empno, ename, sal from emp;

     EMPNO ENAME             SAL
---------- ---------- ----------
      7369 SMITH            1600
      7900 JAMES            1900

Ok, but what about 9i, how to achieve your goal without lots of duplication.
Well, we sort of have to keep the array "dense" in 9i and before.  that is
fairly easy with this technique:

ops$tkyte@ORA920PC> create table emp as select empno, ename from scott.emp where
1=0;

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> declare
  2      type array is table of varchar2(30) index by binary_integer;
  3
  4      l_empnos array;
  5      l_enames array;
  6      l_offset number := 0;
  7  begin
  8      select empno, ename
  9        bulk collect
 10        into l_empnos, l_enames
 11        from scott.emp;
 12
 13      for i in 1 .. l_empnos.count
 14      loop
 15          if ( l_enames(i) like '%A%' )
 16          then
 17              l_offset := l_offset+1;
 18          elsif ( l_offset > 0 )
 19          then
 20              l_empnos(i-l_offset) := l_empnos(i);
 21              l_enames(i-l_offset) := l_enames(i);
 22          end if;
 23      end loop;
 24
 25      forall i in 1 .. l_empnos.count-l_offset
 26          insert into emp (empno,ename)
 27          values (l_empnos(i), l_enames(i) );
 28  end;
 29  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select * from emp;

     EMPNO ENAME
---------- ----------
      7369 SMITH
      7566 JONES
      7788 SCOTT
      7839 KING
      7844 TURNER
      7902 FORD
      7934 MILLER

7 rows selected.

Just slide the "end of the array" over the deleted entries.  Alternatively, we
could use object types and skip forall:

ops$tkyte@ORA920PC> create or replace type myScalarType 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  )
 11  /

Type created.

ops$tkyte@ORA920PC> create or replace type myTableType as table of myScalarType
  2  /

Type created.

ops$tkyte@ORA920PC> declare
  2      l_data   myTableType;
  3  begin
  4      select myScalarType( empno, ename, job, mgr, hiredate, sal, comm,
deptno )
  5        bulk collect
  6        into l_data
  7        from scott.emp;
  8
  9      for i in 1 .. l_data.count
 10      loop
 11          if ( l_data(i).ename like '%A%' )
 12          then
 13              l_data.delete(i);
 14          end if;
 15      end loop;
 16
 17      insert into emp
 18      select *
 19        from TABLE( cast( l_data as myTableType ) );
 20  end;
 21  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select empno, ename from emp;

     EMPNO ENAME
---------- ----------
      7369 SMITH
      7566 JONES
      7788 SCOTT
      7839 KING
      7844 TURNER
      7902 FORD
      7934 MILLER

7 rows selected.
Rating: 3
Which is better February 22, 2004
Reviewer: A reader from NJ, USA
Tom,

In one of your earlier discussion you had mentioned the following logic for BULK
COLLECT :

Case 1 (Your logic)
======
loop
     fetch c bulk collect into l_data limit N;
     exit when l_data.count = 0;
     process...
     exit when c%notfound;
end loop;

I had a similar logic where I had to bulk fetch few records, process it, get the
next set and so on. I used the following logic.

Case 2
======
Declare
 ....
Begin
TotalKnt := 0;
CurrIterKnt := 0;

OPEN C1;
LOOP
   FETCH C1 BULK COLLECT INTO ....LIMIT n;
   CurrIterKnt := C1%RowCount - TotalKnt;
   TotalKnt    := C1%RowCount;
   EXIT WHEN C1%NOTFOUND;
   /* Rest of process logic goes here */
END LOOP;

/* will happen if records fetched is not exactly divisible by LIMIT size n */

IF (CurrIterKnt > 0)
THEN
   /* Rest of process logic (same as above) goes here */
END IF;

CLOSE C1;
End;
Which one of the given approaches you would recommend (performance wise) or both
approaches perform the same? Please clarify so I could change the code

Thanks
Followup:
case 1 is easiest.  You'll be iterating

   for i in 1 .. some_array.count

which, when divisible by LIMIT (rare, say 100% of the cases if limit is 100),
this loop will not execute, no worries.  When not divisible - this loop will.

just put the exit at the bottom.
Rating: 5
getting ora-04030 February 25, 2004
Reviewer: Praveen from Patiala, India
hi Tom
I am inserting duplicate record in a new table by giving this query
create table dup_xx_tab as select * from xx_tab where
rowid not in (select min(rowid) from xx_tab group by frm_srl_no)
but it gives the follwing error:-
"ora-04030 out of process memory when trying to allocate 1036288 bytes"

I have changed the sort area size from 50M to 500M but i got the same error.

For u'er information there are 1200000 approx. records and 60,000 of them are
duplicate.

kindly suggest the best solution at the earliest
Followup:
umm, you are sort of going the wrong way with sort area size there aren't you?

you ran out of ram so you increased the amount you are willing to allocate by an
order of magnitude???

You want to talk to your dba/sa to find out why 50m is "too big" on your system
(perhaps they have unreasonable ulimits set) or decrease the sort size, not
increase it!
Rating: 5
re February 25, 2004
Reviewer: praveen from patiala , india
our ulimlit is 4194302 what should be optimum
and what sort area should be set
Followup:
4194302 what ? bytes, kbytes?

if that is 4meg, that is "really small", you have limited processes to 4m of
memory.

I run unlimited, but really it is up to you, your DBA and your SA as to what is
truly appropriate for you in your circumstance with your hardware.
Rating: 5
ora 04030 February 26, 2004
Reviewer: Praveen from Patiala India
hi tom
now i have changed ulimit to unlimited and sort area size to 10M, but still
again i am getting the same problem.
is there any patch required on OS level? we are using 9i (RAC) on AIX 5L.
Followup:
not that I am aware of but then again, I don't run AIX myself -sooo, that would
be a question for support.

if you use a smaller SAS (i'd prefer pga_aggregate_target mostly myself), what
then?
Rating: 5
March 28, 2004
Reviewer: A reader
Rating: 5
How to handle exception if FORALL is used? March 31, 2004
Reviewer: Tony from India, Chennai
Tom, I would like to use FORALL to speed up insert, But don't know how to handle
exception for bulk inserts. I've given the code below. Is there any way to
optimize the code? Waiting for your valuable suggestions. Thanks in advance.

DECLARE

TYPE          ARRAY IS TABLE OF CIRTACC@EC_TO_CIR%ROWTYPE;
stgtbl        ARRAY;
g_MsgString   VARCHAR2(1000);
m_CIR_ID      NUMBER;
g_PgmId       VARCHAR2(4):= '23';

CURSOR c_InsCust IS
     SELECT acct_id, cir_id, sec_lbl,
            acct_cat, acct_cls, acct_create_ts
     FROM acc_tab SUBPARTITION(SP0402_2);

BEGIN
  OPEN c_InsCust;

  LOOP

    FETCH c_InsCust BULK COLLECT INTO stgtbl LIMIT 2000;
    EXIT WHEN stgtbl.COUNT = 0;

      FOR i IN  stgtbl.FIRST..stgtbl.LAST
      LOOP

     BEGIN
         INSERT INTO rt_acc@db_lin_acc
                 VALUES stgtbl(i) ;
         EXCEPTION
           WHEN DUP_VAL_ON_INDEX THEN

            g_MsgString := ' Oracle Error Code is: '
                   ||SQLCODE|| '-Oracle Error Msg is: '
                   ||SUBSTR(SQLERRM,1, 200);
            dbms_output.put_line(g_MsgString);

            BEGIN
        SELECT CIR_ID INTO m_CIR_ID
                FROM rt_acc@db_lin_acc
        WHERE ACCT_ID = stgtbl(i).ACCT_ID
        AND ACCT_CAT = stgtbl(i).ACCT_CAT
        AND CIR_ID <> stgtbl(i).CIR_ID
                AND stgtbl(i).ACCT_CAT IN ('CC','DL','DB');

        INSERT INTO ap_tabdb_lin_acc
              VALUES (m_CIR_ID, stgtbl(i).SEC_LBL,
                       stgtbl(i).ACCT_ID, stgtbl(i).CIR_ID,
                        stgtbl(i).ACCT_CAT) ;

            EXCEPTION
        WHEN DUP_VAL_ON_INDEX THEN

          g_MsgString := ' Oracle Error Code is: '
                     ||SQLCODE|| '-Oracle Error Msg is: '
                     ||SUBSTR(SQLERRM,1, 200);
                  dbms_output.put_line(g_MsgString);

                WHEN OTHERS THEN
                  g_MsgString := ' Oracle Error Code is: '
                  ||SQLCODE|| '-Oracle Error Msg is: '
                  ||SUBSTR(SQLERRM,1, 200);
                  dbms_output.put_line(g_MsgString);
            END;

         WHEN OTHERS THEN

        g_MsgString := ' Oracle Error Code is: '
                 ||SQLCODE|| '-Oracle Error Msg is: '
                 ||SUBSTR(SQLERRM,1, 200);
        dbms_output.put_line(g_MsgString);
     END;

     END LOOP;

   END LOOP;

   CLOSE c_InsCust;

END;
Followup:
search this site for

"save exceptions"

in quotes like that.  those articles show how to forall processing DML in bulk
and catch exceptions
March 31, 2004 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?
Rating: 5
Loading Numbers January 15, 2004
Reviewer: Martin Guillen from Buenos Aires, Argentina.
Tom:
I'm trying to load some data in the db from a flat file.
I've done some tests:

Test 1:
ordbas01 > connect load_test/load_test;
Connected.
ordbas01 > create table t (a number);

Table created.

oracle@martin:/opt/oracle/dba/load_test> cat data.dat
3,640

Here ',' is the decimal separator (the character used in spanish languages)

oracle@martin:/opt/oracle/dba/load_test> cat load_test.ctl
load data
infile '/opt/oracle/dba/data.dat'
into table t
fields terminated by whitespace
(
        a
)

oracle@martin:/opt/oracle/dba/load_test> sqlldr load_test/load_test
control=load_test.ctl

SQL*Loader: Release 9.2.0.1.0 - Production on Thu Jan 15 12:44:55 2004

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Commit point reached - logical record count 1

And from the sqlldr log:

   Column Name                  Position   Len  Term Encl Datatype
------------------------------ ---------- ----- ---- ---- ---------------------
A                                   FIRST     *  WHT      CHARACTER

Record 1: Rejected - Error on table T, column A.
ORA-01722: invalid number

Table T:
  0 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.

So:

oracle@martin:/opt/oracle/dba/load_test> export NLS_NUMERIC_CHARACTERS=',.'
oracle@martin:/opt/oracle/dba/load_test> sqlldr load_test/load_test
control=load_test.ctl

SQL*Loader: Release 9.2.0.1.0 - Production on Thu Jan 15 12:45:48 2004

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Commit point reached - logical record count 1

And now the log says:

   Column Name                  Position   Len  Term Encl Datatype
------------------------------ ---------- ----- ---- ---- ---------------------
A                                   FIRST     *  WHT      CHARACTER

Table T:
  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.

ordbas01 > select * from t;

         A
----------
      3,64

Test 2:

ordbas01 > l
  1  create table t_ext
  2  (
  3          a      number
  4  )
  5  organization external
  6  (
  7          type oracle_loader
  8          default directory load_test_data
  9          access parameters
 10          (
 11                  records delimited by newline
 12                  badfile load_test_data:'load_test_ext.bad'
 13                  logfile load_test_data:'load_test_ext.log'
 14                  fields terminated by whitespace
 15                  missing field values are null
 16                  (
 17                          a
 18                  )
 19          )
 20          location ('data.dat')
 21          )
 22* reject limit unlimited
ordbas01 > /

Table created.

ordbas01 > select * from t_ext;

no rows selected

And load_test_ext.log says:
    A                               CHAR (255)
      Terminated by whitespace
      Trim whitespace same as SQL Loader
error processing column A in row 1 for datafile
/opt/oracle/dba/load_test/data.dat
ORA-01722: invalid number

ordbas01 > select * from nls_session_parameters where
parameter='NLS_NUMERIC_CHARACTERS';

PARAMETER                      VALUE
------------------------------ ----------------------------------------
NLS_NUMERIC_CHARACTERS         ,.

  1* select * from nls_instance_parameters where
parameter='NLS_NUMERIC_CHARACTERS'
ordbas01 > /

PARAMETER                      VALUE
------------------------------ ----------------------------------------
NLS_NUMERIC_CHARACTERS         ,.

Whats wrong with the external table?

I think that the value of NLS_NUMERIC_CHARACTERS being ',.' in instance/session
should be fine but look:

  1* select * from nls_database_parameters where
parameter='NLS_NUMERIC_CHARACTERS'
ordbas01 > /

PARAMETER                      VALUE
------------------------------ ----------------------------------------
NLS_NUMERIC_CHARACTERS         .,

Could the value of NLS_NUMERIC_CHARACTERS in nls_database_parameters be the
problem?
From the docs (Oracle 9i Globalization Support Guide Page 3-9):

"NLS Data Dictionary Views
(...)
NLS_DATABASE_PARAMETERS shows the values of the NLS parameters that were used
when the database was *created*."

So the only way to change the values in nls_database_parameters is recreating
the db?

Thank you tom for your time,
Martin.

PS: My system is UnitedLinux 1.0 (SuSE Linux Enterprise Server) with Oracle 9i
Release 2 (9.2.0.1).
Followup:
the docs for external tables do say:

.....
Default Character Sets and Date Masks

For fields in a datafile, it is the client s NLS environment that determines the
default character set and date masks. For fields in external tables, it is the
server s NLS environment that determines the default character set and date
masks.
......

one option would be to use a varchar2(n) field instead of number and using
"to_number()" in the select list.
Rating: 4
Loading dates using External tables March 15, 2004
Reviewer: Duncan
Tom. Thanks for all the advice on the new 9i external table feature.

I just ahve one quick question regarding the loading of dates.

In my control file (.csv) the date field exists in the format of dd/mm/yy. I set
up my external table as follows:

CREATE TABLE External_Table_HR
(
  S_NUMBER  VARCHAR2(8 BYTE),
  NAME            VARCHAR2(100 BYTE),
  SEX             VARCHAR2(2 BYTE),
  DEPTID          VARCHAR2(6 BYTE),
  DEPT_DESCR      VARCHAR2(100 BYTE),
  GEO_LOCATION    VARCHAR2(100 BYTE),
  DESCR           VARCHAR2(100 BYTE),
  FULL_PART       VARCHAR2(1 BYTE),
  STD_HRS_WK      NUMBER,
  GRADE           VARCHAR2(30 BYTE),
  GRADE_DESCR     VARCHAR2(30 BYTE),
  DATA_DATE       DATE
)
ORGANIZATION EXTERNAL
  (  TYPE ORACLE_LOADER
     DEFAULT DIRECTORY DATA_DIR
     ACCESS PARAMETERS
       ( records delimited by newline
  NOLOGFILE
  BADFILE DATA_DIR:'EXTHR.bad'
  skip 1
  FIELDS TERMINATED BY ','
  OPTIONALLY ENCLOSED BY '"'
   )
     LOCATION (DATA_DIR:'EXTHR.csv')
  )
REJECT LIMIT 0
LOGGING
NOCACHE
NOPARALLEL;

With the date in the control file in the format it is, it will not load into the
external table and an exception is generated. When i change the date in the
control file to an Oracle standard format it works fine.

The question i have is this. When using SQl loader you could specify the date
mask to be used which would solve my problem. I am having a bit of difficulty
getting a similar thing to work using external tables. could you possibly
provide me with an example of how I could set a date mask to get round this
problem I am having.

Thanks for any help
Followup:
Say you had this ctl file t.ctl:

load data
INFILE x.dat
INTO TABLE t
APPEND
FIELDS TERMINATED BY ',' trailing nullcols
(a date "dd/mm/yy",
 b,
 c,
 d,
 ID "s.nextval")

and you wanted "external table" from it..  just run

$ sqlldr u/p t.ctl external_table=generate_only

you'll find in t.log:

CREATE TABLE "SYS_SQLLDR_X_EXT_T"
(
  A DATE,
  B NUMBER(38),
  C NUMBER(38),
  D NUMBER(38),
  ID VARCHAR(255)
)
ORGANIZATION external
(
  TYPE oracle_loader
  DEFAULT DIRECTORY SYS_SQLLDR_XT_TMPDIR_00000
  ACCESS PARAMETERS
  (
    RECORDS DELIMITED BY NEWLINE CHARACTERSET WE8ISO8859P1
    BADFILE 'SYS_SQLLDR_XT_TMPDIR_00000':'x.bad'
    LOGFILE 't.log_xt'
    READSIZE 1048576
    FIELDS TERMINATED BY "," LDRTRIM
    MISSING FIELD VALUES ARE NULL
    REJECT ROWS WITH ALL NULL FIELDS
    (
      A CHAR(255)
        TERMINATED BY ","
        DATE_FORMAT DATE MASK "dd/mm/yy",
      B CHAR(255)
        TERMINATED BY ",",
      C CHAR(255)
        TERMINATED BY ",",
      D CHAR(255)
        TERMINATED BY ",",
      ID CHAR(255)
        TERMINATED BY ","
    )
  )
  location
  (
    'x.dat'
  )
)REJECT LIMIT UNLIMITED

which also shows you how to do what you ask.  I find that using external_table
with sqlldr is an easy way to take working ctl files and make them into external
table creates...
Rating: 3
Nice Topic March 31, 2004
Reviewer: Srinivas from India
Hi Tom,

Can you Please tell me how to load Data in Excel which is spread across
different sheets into oracle tables.

For Example Sheet1 needed to be loaded into emp,sheet2 needed to be Loaded into
Dept.

Please Help me and Thanks in Advance.
Followup:
sorry - i know next to nothing about excel, specially when it comes to multiple
sheets.
March 31, 2004 sushant -- Thanks for the question regarding "using rownums & for clause in 9i", version 8.1.6
You Asked
we can use rownums in a lot of queries.
say
SELECT *
        FROM   (SELECT * FROM my_table ORDER BY col_name_1 DESC)
        WHERE  ROWNUM < 10;
rownum clause works fine with < , <= , in operators
what i want to know is why cant we use rownum with a >,= or not in operator.

i just saw something uncanny in oracle i was using a for loop in 9i pl/sql . i
was using isqlplus. the moment i entered the a + operator in the body & clicked
the execute button i got an error. when i saw the error the + operator always
vanishes whenever i execute
the  code

  declare

   vsum   number := 0;
begin

    for i in 1..&n

loop

    vsum := vsum + i;
end loop;
dbms_output.put_line(vsum);
end;
and we said...
rownum is incremented AFTER a row passes the predicate (and before any sorts
or aggregates). Hence, if you say "where rownum > 1" -- that will NEVER be true
since rownum starts at one and is incremented ONLY when the predicate is
satisfied -- and rownum > 1 will never be satisfied as long as rownum is one!!

Here is an example from another question I had once:

....
So, the query:

select l_id from prod.daily_def
 where rownum between 100 and 200;

is conceptually processed like this:

    rownum = 1
    for x in ( select * from prod.daily_def )
    loop
          if ( rownum between 100 and 200 )
          then
                  OUTPUT record
                  rownum := rownum + 1;
          end if;
    end loop

Since your predicate is NEVER satisfied, there is NO row 2 -- ever.

try:

   select * from ( select rownum r, * from t ) where r between 100 and 200;

instead.

.....

(me again)
Inline views are useful here.  Using your example, you can:

select *
from (
SELECT rownum rnum, a.*
        FROM   (SELECT * FROM my_table ORDER BY col_name_1 DESC) a
        WHERE  ROWNUM < 100
)
where rnum > 89;

for example to get rows 90..99 from a result set.

As for isqlplus, never used it -- don't have any facilities to replicate your
issue.  You can contact support for that one and open a TAR.
Review & Followup

Rating: 3
very useful..but.... July 17, 2002
Reviewer: arief zj from Malaysia
i did follow ur advice but to certain query it works, but when it come to a more
complex when ORA-00918 appears :(..
i don't know to tell u bcoz u're quite bz nowadays..

however, i hope u could spend some times for me :)..these are my query:

SQL> select * from (
  2  select aa.*,rownum rnum from (
  3  select distinct
  4  rownum rn,
  5  swi.site_wide_id,
  6  swi.object_id,
  7  decode(sign(sysdate-swi.user_checkoff_date),1,1,0,1,0) as published_p,
  8  to_char(swi.last_modified, 'Mon FMDDFM, YYYY') as last_modified,
  9  u.first_names || ' ' || u.last_name as owner,
 10  u.user_id as owner_id,
 11  swi.one_line_description as name,
 12  swi.overview,
 13  decode(swi.overview_html_p,'t',1,0) as overview_html_p,
 14  swi.last_modified,
 15  (select nvl(round(avg(gc.reuse_points),2),0) from general_comments gc
 16            where
 17            gc.on_which_table = 'sn_objects' and
 18            gc.on_what_id = swi.object_id) average_rating,
 19  (select count(*) from general_comments gc
 20            where
 21            gc.on_which_table = 'sn_objects' and
 22            gc.on_what_id = swi.object_id and not gc.reuse_points is null)
number_ratings,
 23  sn_linked_object_list(swi.site_wide_id) as linked_object_ids
 24  from
 25  site_wide_index swi, users u
 26  where
 27  u.user_id=swi.original_author_id and
 28  swi.expiration_date > sysdate and
 29  swi.object_type_id = 152 and
 30  sysdate - swi.last_modified < 365
 31  order by
 32  swi.last_modified desc
 33  ) aa where rownum <= 100
 34  )
 35  where rnum >= 51;

but it returns error saying that:

select aa.*,rownum rnum from (
          *
ERROR at line 2:
ORA-00918: column ambiguously defined

i hope u could answer it for me and to others as well
Followup:
select * from big_table where exists ( select
null from big_table bt2 where bt2.id = big_table.id );

your subquery has two columns with the same name -- it is ambigously defined.
Use aliases on the columns and it'll work
Rating: 5
Thanx a lot man!!! July 17, 2002
Reviewer: arief zj from Malaysia
wow.. how careless i am, the problem solved already,
thanx tom .. you da man :))
Rating: 5
rownum problem or may be my problem ..:) March 31, 2004
Reviewer: pasko from HH
Hi Tom,

Thanks for the great help that you are providing to the Oracle Community..

I have two queries here :
one uses rownum and the other one not.
both should return some rows , but the one which uses rownum does not give back
any rows.

Also the Explain Plan output has Tables that i have not used in my Queries.

Is this an Oracle Bug or programmer's Bug.

Your comment on this is greatly appreaciated.

Thanks in advance.

SQL> select count(*) from
   2  (
   3  select  e.errorno, e.FA_CODE, e.SUB_TEXT, e.TX_ID, e.FA_STR,
to_char(b.RQTIME,'dd.
 mm.yy hh24:mi:ss') RqDate,
   4       b.status Status,
   5   b.S_RTYP, b.S_RQ_ID, b.serviceid, b.tmoid,
   6   command Command,
   7   c.status Command_Status,
   8   to_char(c.executiontime,'dd.mm.yy hh24:mi:ss') executiondate, c.LINEH,
b.TEST_base_id, c.
 TEST_command_id
   9   from TEST_base_pending b, TEST_command_pending c, TEST_cmd_error_pending
e
  10   where e.endtime > sysdate - 10
  11   and e.TEST_command_id = c.TEST_command_id
  12   and c.TEST_base_id = b.TEST_base_id
  13   and b.status not in (2,3,4,5,6,7,8,106,107,108)  -- 7 , 9 , 107
  14   );

   COUNT(*)
 ----------
        225

 Abgelaufen: 00:00:03.02

 Ausführungsplan
 ----------------------------------------------------------
    0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1410 Card=1 Bytes=32
           )

    1    0   SORT (AGGREGATE)
    2    1     BITMAP CONVERSION (COUNT)
    3    2       BITMAP AND
    4    3         BITMAP MERGE
    5    4           BITMAP KEY ITERATION
    6    5             TABLE ACCESS (FULL) OF 'TEST_BASE_PENDING' (Cost=
           1095 Card=432 Bytes=8208)

    7    5             BITMAP CONVERSION (FROM ROWIDS)
    8    7               INDEX (RANGE SCAN) OF 'XTCCP_XTCBASEID' (NON-U
           NIQUE) (Cost=3)

    9    3         BITMAP MERGE
   10    9           BITMAP KEY ITERATION
   11   10             TABLE ACCESS (FULL) OF 'TEST_CMD_ERROR_PENDING' (
           Cost=55 Card=1103 Bytes=25369)

   12   10             BITMAP CONVERSION (FROM ROWIDS)
   13   12               INDEX (RANGE SCAN) OF 'XTCBID_PK' (UNIQUE) (Co
           st=2)

 SQL> select count(*) from
   2  (
   3  select  e.errorno, e.FA_CODE, e.SUB_TEXT, e.TX_ID, e.FA_STR,
to_char(b.RQTIME,'dd.
 mm.yy hh24:mi:ss') RqDate,
   4       b.status Status,
   5   b.S_RTYP, b.S_RQ_ID, b.serviceid, b.tmoid,
   6   command Command,
   7   c.status Command_Status,
   8   to_char(c.executiontime,'dd.mm.yy hh24:mi:ss') executiondate, c.LINEH,
b.TEST_base_id, c.
 TEST_command_id
   9   from TEST_base_pending b, TEST_command_pending c, TEST_cmd_error_pending
e
  10   where e.endtime > sysdate - 10
  11   and e.TEST_command_id = c.TEST_command_id
  12   and c.TEST_base_id = b.TEST_base_id
  13   and b.status not in (2,3,4,5,6,7,8,106,107,108)  -- 7 , 9 , 107
  14    and rownum <= 50
  15  ) ;

   COUNT(*)
 ----------
          0

 Abgelaufen: 00:00:00.00

 Ausführungsplan
 ----------------------------------------------------------
    0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1298 Card=1)
    1    0   SORT (AGGREGATE)
    2    1     VIEW (Cost=1298 Card=1)
    3    5         RECURSIVE EXECUTION OF 'SYS_LE_5_0'
    4    5         RECURSIVE EXECUTION OF 'SYS_LE_5_1'
    5    2       TEMP TABLE TRANSFORMATION
    6    5         COUNT (STOPKEY)
    7    6           HASH JOIN (Cost=1298 Card=1 Bytes=567)
    8    7             HASH JOIN (Cost=1295 Card=1 Bytes=143)
    9    8               TABLE ACCESS (BY INDEX ROWID) OF 'TEST_COMMAND_
           PENDING' (Cost=1292 Card=0 Bytes=4)

   10    9                 BITMAP CONVERSION (TO ROWIDS)
   11   10                   BITMAP AND
   12   11                     BITMAP MERGE
   13   12                       BITMAP KEY ITERATION
   14   13                         TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD
           9D670B_94E7FEC5' (Cost=2 Card=1 Bytes=10)

   15   13                         BITMAP CONVERSION (FROM ROWIDS)
   16   15                           INDEX (RANGE SCAN) OF 'XTCBID_PK'
           (UNIQUE) (Cost=2)

   17   11                     BITMAP MERGE
   18   17                       BITMAP KEY ITERATION
   19   18                         TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD
           9D670A_94E7FEC5' (Cost=2 Card=1 Bytes=10)

   20   18                         BITMAP CONVERSION (FROM ROWIDS)
   21   20                           INDEX (RANGE SCAN) OF 'XTCCP_XTCBA
           SEID' (NON-UNIQUE) (Cost=3)

   22    8               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D670A_94E
           7FEC5' (Cost=2 Card=50 Bytes=5550)

   23    7             TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D670B_94E7F
           EC5' (Cost=2 Card=50 Bytes=21200)
Followup:
please contact support with that one -- it is giving you the wrong answer.
March 31, 2004 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
Rating: 5
Example Reqd March 31, 2004
Reviewer: Srinivas from India
Hi Tom,

Thanks, You are doing a great work.

Please can you explain me how to load data from excel sheet into Oracle table
with an example.because Iam unable to search in OTN.

Thanks in Advance,
Srinivas
Followup:
why can't you search in otn?  if you can search here, you can search there.

but in any case, I would open the spreadsheet, file save as CSV and use sqlldr.
March 31, 2004 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.
Rating: 3
Exceptions INTO March 31, 2004
Reviewer: Dave Thompson from West Yorkshire, England.
Hi Tom,

With regards to the Exceptions Into we are considering the following:

Load the table
re-build indexes
Generate stats
re-enable constraints

We are generating the stats after the index rebuild to help the anti-join used
by the exceptions into clause.  By genertating the stats when the constraints
are all disabled could this mean that the stats are incomplete and that stats
should be generated after the constraints have been re-enabled?

Thanks.
Followup:
no, does not impact stats (existence or lackthereof of constraints)

the optimizer will be looking at the statistics AND constraints when developing
plans -- but stats are not dependent on constraints.
Rating: 4
Thanks Tom March 31, 2004
Reviewer: Dave Thompson from Sunny Yorkshire
March 31, 2004 Gladiola -- Thanks for the question regarding "Import/export", version 8.1,7
You Asked
Hi Tom!

I have a question regarding import and export.
We have to import tables with conditions
For One table is working ok
but when you  have more then one with each of them with condition it does not
work

this is the sample of parm file which line the table and the condition

TABLES=
GROUP_TABLE
,PLAN
QUERY=
"WHERE GRP_GROUP='V01'"
"where PLP_PLAN='ABS'"
It seems that allowed the same condition applyed for all the tables
does not  allow more than one

I have seen your sample in your book but it have only one table
Is there any way around ?

thanks
and we said...
you need to export each table individually, the query= is applied globally, to
all tables exported.

just run export twice.
Review & Followup

Rating: 2
Use Oracle 10g Rel 1's Datapump February 12, 2004
Reviewer: Ketan Popat from India
Plz Refer
http://otn.oracle.com/pls/db10g/portal.portal_demo3?selected=3
Utility's pdf.(b10825.pdf)
Followup:
i sort of assume that since they said "we are on 817", using 10g stuff would be
"not an option"
Rating: 3
February 12, 2004
Reviewer: Gladiola from canada
We have 817 version you are right, but still that import/export table condition
is not helpful because there  are lot of table which we have to propogate from
development to quality control database to production
One more clarification.Is the import append the  desired rows in the table for
sure?

thanks
Followup:
if they each have a different where clause -- you have no other choice really.

if you can type in a parameter file with a list of tables, and a list of where
clauses -- I do not see why you cannot have a script that has a list of exp
...table=... query=..... commands in it just as easily?

Not helpful i suppose is in the eye of the beholder here.

You have a requirement:  export tables with different where clauses.

there is only one way to do it: use multiple exp commands.

It really doesn't seem "that much harder" then setting up a big old control file
with matched up table names/where clauses.
Rating: 5
February 12, 2004
Reviewer: A reader
I like Ketans last name..POPAT!!!
Rating: 2
Probable Work arround February 13, 2004
Reviewer: Ketan Popat from INDIA
Create one new user say dev2qc

Create definitions of Tables / Objects  for which you want to transfer data via
export.

Write a pl/sql script to transfer necessary data from your development user to
quality control user, which transfer only data you want to export.

Make export of entire dev2qc user.

Import it using parameter fromuser=dev2qc  touser = <user> ignore = Y

Hopefully this work around will be useful.

I request tom to add if I am missing anything.

Regards.
Followup:
seems harder then "exp query=" a couple of times to me...
Rating: 4
Please Answer March 31, 2004
Reviewer: Srinivas from India
Can you tell me how to load excel data spread across multiple sheets into single
Oracle table.
Followup:
osh kosh by gosh -- why do you have to put this on the bottom of so many pages.

I sleep at night!  Your day is my night.
March 31, 2004 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....
Rating: 5
Does this work in window NT March 30, 2004
Reviewer: Jennifer Chen from Viena, VA USA
Hi Tom,

Thank you for teaching us new things everyday. I have a batch job invoke sqlldr
on windows, which loads data into DB every month, and would like to use your
solution.

Does it work in window platform? I modified init.ora:

SQL> show parameter os_authent_prefix

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
os_authent_prefix                    string      ops$

SQL> CREATE USER ops$chenje IDENTIFIED externally;

User created.

SQL> grant dba to ops$chenje;

Grant succeeded.

'chenje' is my NT login account.

When I tried:

C:\Documents and Settings\chenje>set ORACLE_SID=aliasdev

C:\Documents and Settings\chenje>sqlplus /

SQL*Plus: Release 9.2.0.4.0 - Production on Tue Mar 30 19:41:32 2004

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

ERROR:
ORA-01017: invalid username/password; logon denied

Enter user-name:

Did I miss something here?

Thanks again for your time and help.
Followup:

http://download-west.oracle.com/docs/cd/B10501_01/win.920/a95492/authen.htm#1004903
windows is not as easy as unix/linux

(i love saying that)
Rating: 5
sqlplus / March 31, 2004
Reviewer: Jennifer Chen from Viena, VA USA
Hi Tom,

I followed metalink Note:60634.1 (WIN: Setup O/S Authentication) to be able to:

C:\sqlplus /nolog
SQL>connect / as sysdba
connected.
SQL>

But what I really need is to be able to:

sqlplus /

OR

C:\sqlldr userid=/ as sysdba@aliasdev control='L:\fbifiles\FBIBILLING.CTL'
log='L:\fbifiles\FBIBILLING.LOG'

Do you know how from there?

Thank you.
Followup:
did you read through the above link i supplied?
Rating: 5
Thank You March 31, 2004
Reviewer: Jennifer Chen
Yeah. It's operating systems specific. I tried this and worked:

sqlldr '/ AS SYSDBA' control='L:\fbifiles\FBIBILLING.CTL'
log='L:\fbifiles\FBIBILLING.LOG'

After I got your idea, I am pretty much able to accomplish what I need.

Really appreciate your time and help.
March 31, 2004 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 )
Rating: 5
records per block March 31, 2004
Reviewer: reader
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'
);

In your above query, what is spare1 - 32767? Where did the value 32767 come
from? Thanks.
Followup:
powers of two.  you just have to "know" to do that to get the information out of
this field.
March 31, 2004 Ravi -- Thanks for the question regarding "Retrieve Dates Between Two Dates ", version 8.1.5
You Asked
Hello Tom ,
        I am trying to retrieve the dates between two given dates , though this
is possible by writing a pl/sql Block , I would like to achieve the same in a
Single Sql Statement , if possible .
Doing it this way ...
  declare
  ed number;
  bd date;
  begin
  select sysdate-30,sysdate-(sysdate-30) into bd,ed from dual;
  for i in 0..ed loop
  dbms_output.put_line(bd+i);
  end loop;
  end;
/
-------------------------------------------------------------
Also I would like to know if u are in the process or have already written a Book
like Expert One-on-One for Oracle 9i . I would like to buy the latest edition ,
so the question .

Thanx and Regards
R.Venkatesh
and we said...
select sysdate-30+(rownum-1)
  from all_objects
 where rownum <= 30
/

Expert One on One is the current book.  I have no current plans to do a 9i
release of that (frankly, 90% or more of the material in there applies to 9i or
even 7.3 as well).

I've been toying with the idea of a performance book but haven't signed on the
dotted lines yet.
Review & Followup

Rating: 3
Retrieve Dates Between Two Dates March 06, 2002
Reviewer: Bharath from NYC
How about this...

SELECT TO_DATE('01/01/2001','MM/DD/YYYY') + (ROWNUM - 1) DATEGEN
 FROM ALL_OBJECTS
WHERE TO_DATE('01/01/2001','MM/DD/YYYY') + (ROWNUM - 1) <=
TO_DATE('01/31/2002','MM/DD/YYYY')
;
Followup:
if you factor that predicate so that ROWNUM is on the LHS by itself -- it'll be
OK

rownum <=
TO_DATE('01/31/2002','MM/DD/YYYY')-TO_DATE('01/01/2001','MM/DD/YYYY')+1

otherwise, it'll evaluate that for every row in all_objects -- not just the 31
you want.

SELECT TO_DATE('01/01/2001','MM/DD/YYYY') + (ROWNUM - 1) DATEGEN
 FROM ALL_OBJECTS
WHERE TO_DATE('01/01/2001','MM/DD/YYYY') + (ROWNUM - 1) <=
TO_DATE('01/31/2002','MM/DD/YYYY')

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       28      0.74       0.72          0      14080          4         396
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       30      0.76       0.75          0      14080          4         396

SELECT TO_DATE('01/01/2001','MM/DD/YYYY') + (ROWNUM - 1) DATEGEN
 FROM ALL_OBJECTS
WHERE
rownum <=
TO_DATE('01/31/2002','MM/DD/YYYY') - TO_DATE('01/01/2001','MM/DD/YYYY') + 1

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       28      0.10       0.08          0       1636          4         396
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       30      0.12       0.10          0       1636          4         396
Rating: 3
Dates b/w two given dates .... March 07, 2002
Reviewer: Priya from Singapore
Tom,
  This is fine as long as the number of rows in DBA_objects or for that matter
any other table or view may have sufficient rows as as to return the dates . In
the case where the dates b/w 2 given dates may exceed the number of rows in for
eg : DBA_OBJECTS , we have a problem . Do we have a fix for this ?
Followup:
if you need more then 2 to 3 years of dates generated -- (given that all objects
in 73 is around 1000 rows and 20-30,000 for a java install in 8i), I question
what you are doing.  What would you do with 1,000 days in a list?

The solution of course would be for YOUR application to install and populate a
table that is big enough for you.  Part of your install would be:

create table rows ( x int );
begin
   for i in 1 .. 1000000 -- or whatever
   loop
       insert into rows values ( i );
   end loop;
   commit;
end;
/

In 9i, we can use a pipelined PLSQL function to generate as many rows as you
like on the fly.  See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2270447621346
for an example of that.
Rating: 4
Securtiy Implications March 07, 2002
Reviewer: A reader
Yesterday Eveing had a Long Argument with my friend when i told him this
solution he was saying that a DBA in a Production environment will not give
Rights to all_objects.

I Felt it was a Bit Crazy because AFAIK i know we wont have any security
Implication on Giving access to all_objects.

What are your thoughts on this.

Thanks.

Best Regards,
Ganesh R
Followup:
A DBA in production doesn't have to give the rights -- select on all_objects is
always granted to PUBLIC upon install.  Many Many products will not work without
it.  It is called "the data dictionary".

This is in catalog.sql:

grant select on ALL_OBJECTS to PUBLIC with grant option
/

all of the ALL_* and USER_* views are selectable by PUBLIC (anyone with CREATE
SESSION can see them)

Hopefully your friend is not a DBA for a production environment!
Rating: 5
Generating Dates... March 09, 2002
Reviewer: A reader
Hi Tom,

Thanks for the earlier Response.  I have another one for you. [This actuall was
the Main thread of the previous discussion that i had posted]

I Usually use this Generate Dates for a varitey of purposes.

Select '01-jan-2001' + rownum from all_objects where rownum < 100;

The Above Query Generates 100 Dates..

Now i heard from people that instead of using the all_objects view u should
create a Table that has 4000 Odd rows that we can use so that if all_objects
Privs have been revoked then also u'r function will work.

IMHO ... I feel there is no penality in using all_objects for this purpose.

Your thoughts on these...

Regards,
Ganesh R
Followup:
Read the above comments about all_objects and security.

If you every have a database where all_objects access has been revoked, you are
in a database that is

o not supported by Oracle support in all likelyhood.
o won't support most any 3rd party application

ALL_OBJECTS is *always* granted select on to PUBLIC.  If it isn't, the database
is broken and needs to be fixed.

The very idea that accessing ALL_OBJECTS is a security "risk" is utter nonsense.
 I would be very interested in hearing from others who think otherwise AND WHY
-- so I can correct their thinking.
Rating: 4
Question March 31, 2004
Reviewer: Florin from The Netherlands
Hi Tom,

I'd like to retrieve a list of dates between 2 given dates.
Ex:
Input: 20040331 and 20040325
Output:
20040325
20040326
20040327
20040328
20040329
20040330
20040331

Many thanks in advance,
Followup:
ok, I'd use BETWEEN then?

... .where column between :a and :b
Rating: 2
Thanks March 31, 2004
Reviewer: Florin from The Netherlands
Hi,

I don't have a table containing dates.
I'd like to retrieve a list of dates by giving 2 dates as an input (using dual
or something like this).
Followup:
oh, you mean like the original answer above??

select to_date(your_start)+rownum-1
  from all_objects
  where rownum <= to_date(your_end)-to_date(your_start)+1
Rating: 5
Brilliant !!! March 31, 2004
Reviewer: Florin from The Netherlands
Thanks a lot!!!
March 31, 2004 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.
Rating: 3
What about extracting pieces of the source March 31, 2004
Reviewer: David from USA
Hi, Tom.

Our requirement is to extract just the UPDATE statements from a package body.
The package is a series of updates wrapped in pl/sql that logs start and end
times and captures the sql%rowcounts.  For example:

------------------------------------------------------
begin -- step 4
 gv_cur_step := gv_cur_step + 1;
 scrubs.set_step(<args>);
 scrubs.start_step(<args>);

          update <table>
          set <..>
          where <where clause>;

 gv_rows_updated := sql%rowcount;
 scrubs.write_transaction(<args>);
 scrubs.end_step(<args>);

end; -- step 4
----------------------------------------------------------

I need to loop through the package body in dba_source and extract each instance
of "update.......;".  The goal is to get each UPDATE statement in a single row
(1 row for each statement) so they can be exported into a single Excel cell (1
cell per UPDATE).  Using windows (Word, notepad, textpad) and cutting and
pasting isn't efficient because the newlines inside the statements cause each
line to be dumped into a different row.

I think I can set up a nested loop from 1 to max(line) and build a string
appending the source starting at each "update" and ending at each ";", but there
has to be a better way.  We could also dump the whole package body into a text
file and use grep/sed, but my experience with those tools is limited.

Thanks as always.
Followup:
analytics never cease to amaze me :)

this gets some false positives, but the concept is there -- you can refine it as
needed:

ask_tom@ASKUS> column line format 9999
ask_tom@ASKUS> column name format a15
ask_tom@ASKUS> column   text format a90
ask_tom@ASKUS> column   marker format a6
ask_tom@ASKUS> set linesize 200
ask_tom@ASKUS>
ask_tom@ASKUS> select max_rn, name, line, text
  2    from (
  3  select name, line, replace( text, chr(10), ' ' ) text, max_rn,
  4             case when first_value(upper(text)) over (partition by name,
max_rn order by line) like '%UPDATE %' then 'X' end x
  5    from (
  6  select name, line, text, rn,
  7             max(rn) over (partition by name order by line) max_rn
  8    from (
  9  select name, text, line,
 10             case when upper(text) like '%UPDATE %' OR
 11                       trim( rtrim(lag(text) over (partition by name order by
line),chr(10)) ) like '%;'
 12              then row_number() over (partition by name order by line) end rn
 13    from user_source
 14   where type in ( 'PACKAGE BODY', 'PROCEDURE', 'FUNCTION' )
 15   order by name, line
 16             )
 17             )
 18             )
 19   where x = 'X'
 20  /

    MAX_RN NAME             LINE TEXT
---------- --------------- -----
---------------------------------------------------------------------------------
---------
        41 ASK_TOM_PKG        41 <br>Please update your bookmarks...
        41 ASK_TOM_PKG        42 </BODY>
        41 ASK_TOM_PKG        43 </HTML>
        41 ASK_TOM_PKG        44 ');
         4 P                   4 update t set x = x;
       245 WWC_ASK_API       245             update
wwc_ask_application_controls$
       245 WWC_ASK_API       246                set enabled = 'Y'
       245 WWC_ASK_API       247              where application_code = 'NONEW';
       337 WWC_ASK_API       337     update wwc_ask_submitted_questions$
       337 WWC_ASK_API       338        set email = p_email,
       337 WWC_ASK_API       339            fname = p_fname,
       337 WWC_ASK_API       340            lname = p_lname,
       337 WWC_ASK_API       341            location = p_location,
       337 WWC_ASK_API       342            title = p_job_title,
       337 WWC_ASK_API       343            org = p_org,
       337 WWC_ASK_API       344            category = p_category,
       337 WWC_ASK_API       345            version = p_version,
       337 WWC_ASK_API       346            years_exp = p_years,
       337 WWC_ASK_API       347            subject = p_subject,
       337 WWC_ASK_API       348            remote_addr = l_remote_addr,
       337 WWC_ASK_API       349            http_user_agent = l_http_user_agent,
       337 WWC_ASK_API       350            http_referer = l_http_referer,
       337 WWC_ASK_API       351            status = decode( status,
g_NEWNEVERREAD, g_NEWNEVERREAD, g_READAWAITINGINFO, g_
                                 READMOREINFOSUPPLIED ),

       337 WWC_ASK_API       352            question = empty_clob()
       337 WWC_ASK_API       353      where id = p_id
       337 WWC_ASK_API       354        and status in (g_NEWNEVERREAD,
g_READAWAITINGINFO )
       337 WWC_ASK_API       355            returning question into l_question;

and if we add a user defined aggregate:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:15637744429336
we can:

ask_tom@ASKUS> select name, up_date
  2    from (
  3  select max_rn, name, line,
  4             stragg( trim(text) || ' ') over (partition by name, max_rn order
by line) up_date,
  5             last_value(line) over (partition by name, max_rn) last_line
  6    from (
  7  select name, line, replace( text, chr(10), ' ' ) text, max_rn,
  8             case when first_value(upper(text)) over (partition by name,
max_rn order by line) like '%UPDATE %' then 'X' end x
  9    from (
 10  select name, line, text, rn,
 11             max(rn) over (partition by name order by line) max_rn
 12    from (
 13  select name, text, line,
 14             case when upper(text) like '%UPDATE %' OR
 15                       trim( rtrim(lag(text) over (partition by name order by
line),chr(10)) ) like '%;'
 16              then row_number() over (partition by name order by line) end rn
 17    from user_source
 18   where type in ( 'PACKAGE BODY', 'PROCEDURE', 'FUNCTION' )
 19   order by name, line
 20             )
 21             )
 22             )
 23   where x = 'X'
 24             )
 25   where line = last_line
 26  /

NAME
---------------
UP_DATE
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------
--------------------------------------
ASK_TOM_PKG
<br>Please update your bookmarks... ,</BODY> ,</HTML> ,');

P
update t set x = x;

WWC_ASK_API
update wwc_ask_application_controls$ ,set enabled = 'Y' ,where application_code
= 'NONEW';

WWC_ASK_API
update wwc_ask_submitted_questions$ ,set email = p_email, ,fname = p_fname,
,lname = p_lname, ,location = p_location, ,title = p_job_title, ,org = p_org,
,category = p_category, ,version = p_version,
,years_exp = p_years, ,subject = p_subject, ,remote_addr = l_remote_addr,
,http_user_agent = l_http_user_agent, ,http_referer = l_http_referer, ,status =
decode( status, g_NEWNEVERREAD, g_NEWNEVERREAD
, g_READAWAITINGINFO, g_READMOREINFOSUPPLIED ), ,question = empty_clob() ,where
id = p_id ,and status in (g_NEWNEVERREAD, g_READAWAITINGINFO ) ,returning
question into l_question;
.....
Rating: 4
I guess I should have specified the version March 31, 2004
Reviewer: David from USA
My apologies, we're running 8.0.6.

We have a 9i box (and this requirement is part of a 9i upgrade), so I can move
this source code over to the 9i box and try your solution.

Without much effort, do you have a solution that would work on 8.0.6?

Thanks, Tom.
Followup:
doh -- nope, it would be procedural code in 8.0 - pretty each, a big cursor for
loop that looked for %UPDATE % and then utl_file'ed the data until you found a
';' (utl_file so you can write a single line of text upto 32k)
March 31, 2004 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.
Rating: 5
ora-03113 March 31, 2004
Reviewer: atul from India
Hi,

When i was running a big update (60,000,00) rows on a table.
I got a error
ORA-03113,
My co-DBA says,this is because PGA is full.

Is that valid reason for mentioned error?if yes how to check
if PGA is full or not?

Thanks & Regards,
Atul
Followup:
check out the trace file.

pga getting full would return ora-4030

3113 means "something bad has happened", typically necessitates a call to
support.
March 31, 2004 -- Thanks for the question regarding "Remote DB connection using "proxy"", version 8.1.7
You Asked
DB is running on server C.
Listener running on server B.
SQL-PLus client on server A.

SQL-Net Network connectivity exists from A to B and B to C but
NOT directly from A to C (firewall).

I'd like to run SQL-plus client on A and connect to DB on C using listener on B.
  Can this be done and what are the necessary and sufficient config file entries
???
and we said...

http://download-west.oracle.com/docs/cd/A87860_01/doc/index.htm
You need to use connection manager (CMAN) on B.  Else the listener would just
redirect A to C directly.  The listener isn't involved in the networking once
the client is redirected.  CMAN works as a man in the middle and facilitates
exactly this setup.
Review & Followup

Rating: 4
April 06, 2004
Reviewer: Chuck Wright from Alpharetta GA
Thanks Tom
Will look at the connection manager to resolve this.
March 31, 2004 rajesh -- Thanks for the question regarding "logging transactions", version 8.1.7
You Asked
dear tom

i hope u may spend certain amount of time to me.

here is the list of two tables in my schema.

1.day_clos
(fields br_code number,day_dt date,opn_cash number,cl_cash number)
2.gl_balance
(fields br_code number,day_dt date,code number,gl_bal number)

After each day ending these tables are protected against updation/insertion from
form level.But if a dba user decides he can modify (insert/update data) these
tables using sql commands.

I would like to know following matters in this regards

1. How can i protect these tables against data base level updations ?
2. Is there any methods available with in Oracle to create log files
of transactions  (or listing which sql commands are done against these
tables )done at sql level.

exciting your help

sincerely

rajesh
and we said...
1)
http://asktom.oracle.com/~tkyte/article2/index.html
 fine grained access control does just that.  you can return the
predicate "where 1=0" after hours.  that will permit modifications ONLY during
your timeframe

2) the database has an AUDIT command, yes.
http://download-west.oracle.com/docs/cd/A87860_01/doc/index.htm
In 9i, we added fine grained auditing which allows you to capture the SELECTS
against the tables and to audit modifications you can use a trigger that
accesses the ora_sql_text builtin (new to 9i, not in 8i) to capture the
modification statement for logging.
March 31, 2004 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 || '%'
Rating: 5
A good estimate for 8i March 31, 2004
Reviewer: Jeff from Atlanta
Might be to explain all the queries in v$sqlarea? Would this be a bad idea?
Followup:
explain plan might not return the same plan as was really used.

bind variables, session settings all affect it.

You can do that (it would kill the system, pretty expensive and would flood the
shared pool) and it would be almost accurate.

capturing the query plans with query plan stability gets you the real info.
March 31, 2004 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.
Rating: 4
Can you check the current user's roles in a trigger? March 31, 2004
Reviewer: Nick from NYC
I have a requirement to check a user's role before allowing update of
a column. Briefly, some roles are always permitted to update, others
are permitted only if the "OLD" value is NULL.

I first tried a trigger [Oracle 8.1.7, Windows]
(here is the interesting snip):
-------------------
-- some roles have priv to enter a new HC_NUMBER
if( :OLD.HC_NUMBER is NULL ) then
 begin
  select NULL into cDummy from DBA_ROLE_PRIVS p
where p.GRANTEE = USER
and p.GRANTED_ROLE in
('ENROLLSUPER','FINANCE','ENROLL_R','ENROLL_RW') and rownum=1 ;

EXCEPTION
WHEN NO_DATA_FOUND THEN
    RAISE_APPLICATION_ERROR(-20100,'PERMISSION DENIED ON ENTRY OF HC
NUMBER ');
 end;

 else   -- user is updating an existing hc number
  .
  .

-----------

but this won't compile:
PLS-00201: identifier 'SYS.DBA_ROLE_PRIVS' must be declared

I really wanted to use USER_ROL_PRIVS, but of course the trigger is
owned by someone other that the current user so that won't work.

Any ideas on a fix or a better way?

Thanks
Followup:
http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html
March 31, 2004 hxl -- Thanks for the question regarding "using pl/sql to implement multiple option select ", version 8.1.7
You Asked
Tom,

Have you ever implemented multiple option select in pl/sql? I used to do it in
php, which is quite straightforward: you take the choices and assign it to an
array. I thought I should be able to do it in pl/sql too, using one of the
collection types to take the list of values, so I tried the following:

create type multi_option_t as varray (10) of varchar2(1);
/

create or replace procedure test (multi_option     IN     multi_option_t default
NULL,
                  p_submit    IN    varchar2 default NULL)
as
begin
    if (p_submit = 'Submit') then
        htp.p('foobar');
        for i in 1 .. multi_option.count
        loop
            htp.p('value for the ' || i || 'th element is ' || multi_option(i));
        end loop;
    else
        htp.p('<center><table width="98%"><tr>');
        htp.p('<form method="post" action="test" name="testform">');
        htp.p('<tr><td width="50%">Choose Status</td>');
        htp.p('    <td width="50%">
                <select name="multi_option" multiple>
                    <option value="0">0</option>
                    <option value="1">1</option>
                    <option value="2">2</option>
                    <option value="3">3</option>
                    <option value="4">4</option>
                    <option value="5">5</option>
                    <option value="6">6</option>
                    <option value="7">7</option>
                    <option value="8">8</option>
                    <option value="9">9</option>
                </select>
               </td></tr>');
        htp.p('<tr><td colspan="2"><input type="submit" name="p_submit"
value="Submit"></td></tr>');

        htp.p('</table></form>');
    end if;
end test;
/

But it did not do the trick. It compiled ok but at run time, error msg I got:
ORA-06550: line 7, column 2:
PLS-00306: wrong number or types of arguments in call to 'TEST'
ORA-06550: line 7, column 2:
PL/SQL: Statement ignored

PARAMETERS :
  ============
  MULTI_OPTION:
    2
  P_SUBMIT:
    Submit

The parameters for the varray always just show the 1st choice. Seems it is not
recognizing multi_option as type multi_option_t at all - it is still treated as
a scalar parameter. Is varray not the right type to use for this, or it is
something else?

Thanks a million!
and we said...
we use plsql table types for that (and use packages!!  they rock)

ops$tkyte@ORA9IR2> create or replace package my_pkg
  2  as
  3          type array is table of varchar2(4000) index by binary_integer;
  4          g_empty array;
  5
  6          procedure test( p_multi_option in array default g_empty,
  7                      p_submit       in varchar2 );
  8  end;
  9  /

Package created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace package body my_pkg
  2  as
  3
  4  procedure test( p_multi_option in array default g_empty,
  5                  p_submit in varchar2 )
  6  as
  7  begin
  8      if (p_submit = 'Submit')
  9      then
 10          htp.p('foobar');
 11          for i in 1 .. p_multi_option.count
 12          loop
 13              htp.p('value for the ' || i || 'th element is ' ||
p_multi_option(i));
 14          end loop;
 15      else
 16          htp.p('<center><table width="98%"><tr>');
 17          htp.p('<form method="post" action="test" name="testform">');
 18          htp.p('<tr><td width="50%">Choose Status</td>');
 19          htp.p('    <td width="50%">
 20                  <select name="p_multi_option" multiple>
 21                      <option value="0">0</option>
 22                      <option value="1">1</option>
 23                      <option value="2">2</option>
 24                      <option value="3">3</option>
 25                      <option value="4">4</option>
 26                      <option value="5">5</option>
 27                      <option value="6">6</option>
 28                      <option value="7">7</option>
 29                      <option value="8">8</option>
 30                      <option value="9">9</option>
 31                  </select>
 32                 </td></tr>');
 33          htp.p('<tr><td colspan="2"><input type="submit" name="p_submit"
value="Submit"></td></tr>');
 34
 35          htp.p('</table></form>');
 36      end if;
 37  end test;
 38
 39  end;
 40  /

Package body created.
Review & Followup

Rating: 5
You rock too! March 31, 2004
Reviewer: Pat from md usa
Thanks so much.

To other readers, there are a few typos in the code, like p_submit needs to be
default null in package header, but you get the idea.
March 31, 2004 Vlado -- Thanks for the question regarding "Missing query plan in trace file", version 9i
You Asked
In some of my trace files some queries do not contain query plans but others do.
The queries that have a query plan are from SYS(recursive). The trace files were
created like this:

alter session set timed_statistics=true;
alter session set events '10046 trace name context forever, level 8';
-- excute various queries
alter session set events '10046 trace name context off';

Any idea why this happens?
and we said...
You need to close cursors in order to get the STAT records written to the trace
file.

recursive sql closes the cursors.

You need to either:

a) close the cursors yourself (in plsql pretty much *impossible*)
b) exit the session which will close the cursors for you

and you need to close them before you turn off tracing!
Review & Followup

Rating: 5
Thanks. March 31, 2004
Reviewer: Vlado from Norristown, PA
March 31, 2004 Kun -- Thanks for the question regarding "Can session cached cursor improve the performance for queries with bind variable?", version 8.1.7
You Asked
Tom,

What I understand the session cached cursor is a cursor containing a query's
data(bind value, resultset ...) and cached in a session. When a new query with
bind variable is issued:

1. in order to match a cached cursor, does the query(literal text) and values
has to be both exactly same with the query and value that cached cursor
contained?

2. Or it just requires the query part same?

3. If the answer of question 1 is yes. Query with different binding value can
not take the softer parse benifit, is that right?

Thanks.
and we said...
session cached cursors only makes SENSE with bind variables! else each query
would be unique and used just once.

It just requires the same query be issued multiple times to benefit you.  Binds
are good.

ops$tkyte@ORA9IR2> 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('&S')||'%'
  5  /
old   4: and lower(a.name) like '%' || lower('&S')||'%'
new   4: and lower(a.name) like '%' || lower('session cursor cache hits')||'%'

NAME                                VALUE
------------------------------ ----------
session cursor cache hits             509

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2          l_var varchar2(5);
  3          l_cnt number;
  4  begin
  5          for i in 1 .. 1000
  6          loop
  7                  execute immediate
  8                  'select count(*)
  9                     from dual
 10                    where dummy = :x'
 11                  into l_cnt
 12              using l_var;
 13          end loop;
 14  end;
 15  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> @mystat2
ops$tkyte@ORA9IR2> select a.name, b.value V, b.value-&V diff
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&S')||'%'
  5  /
old   1: select a.name, b.value V, b.value-&V diff
new   1: select a.name, b.value V, b.value-       509 diff
old   4: and lower(a.name) like '%' || lower('&S')||'%'
new   4: and lower(a.name) like '%' || lower('session cursor cache hits')||'%'

NAME                                    V       DIFF
------------------------------ ---------- ----------
session cursor cache hits            1511       1002

1,000 more hits than when I started....
Review & Followup

Rating: 5
Very valuable information March 31, 2004
Reviewer: Kun from Redwood City, CA USA
The information help me make a very expensive tuning decision.
March 31, 2004 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://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76995/standbys.htm#29841
you need to read:
http://download-west.oracle.com/docs/cd/A87860_01/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://download-west.oracle.com/docs/cd/A87860_01/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
Rating: 3
Multiple instances, single ORACLE_HOME, different oracle startup user February 11, 2004
Reviewer: Tommy from Toronto, Ontario Canada
Hi Tom,

I have an Oracle installation with one ORACLE_HOME and multiple Oracle databases
(different ORACLE_SID). Is it possible to create different Unix user to startup
different ORACLE instances?
Followup:
the "dba" group is burned into the binary -- however

using SYSDBA (and not giving people access to the "oracle software account") you
can let people

connect u/p as sysdba

without being in the "dba" group to manage the database.

so - no you cannot do what you ask in the way you ask

however, you can accomplish your goal (of having different people being able to
start/stop individual databases)
Rating: 4
Difference Between SID and Service Name March 26, 2004
Reviewer: Dhrubo from Kolkata,India
Tom a silly question ... so i wanted to know whats the difference between SID
and service name?
Followup:
a service name is possibly a collection of instances.   many instances can
register as the same service.  we use it for load balancing in RAC.

a sid+oracle_home on a HOST identifies an instance on that host.
the sid is used to find default configuration files on that host.
Rating: 4
basic how to March 31, 2004
Reviewer: Fernando Sanchez from Madrid, Spain
Hello Tom.

I'm working with version 8.1.7 and I wonder how I can connect my windows sqlplus
to an instance running on a remote solaris machine whose ip address I know but
whose ORACLE_SID is equal to another ORACLE_SID running on a different machine
that I also need to connect to. I have written two entries in my windows
tnsnames.ora whose only difference is the host parameter. But when trying to
start a new sqplus session, how can I choose the machine?

Thanks in advance.
Followup:
you would have two tnsnames entries, one each for each instance you want to
connect to.

then

connect scott/tiger@string1

vs

connect scott/tiger@string2
March 31, 2004 Senthil -- Thanks for the question regarding "direct load not using temp tablespace", version 8.1.7
You Asked
Tom-

one of our client loading a huge data into database using sqlldr with direct
load option. After sometime the load process get started, it throws an error
message saying,

ORA-1652: unable to extend temp segment by 37171 in tablespace MY_TS

as we had stored data and index into 2 different tablespace, it fails on data
tablespace(MY_TS). i understand at the end of load process, it rebuilds index on
the same tablespace where index is stored. but my question is why its failing on
data tablespace. is there anyway to make use of temporary tablespace, as we have
2GB of free space in it. if i had missed any option to be included, please
enlighten.

thanks,
senthil
and we said...
When we load, we load into TEMP extents which upon successful completion we turn
into "REAL" extents.

We do this so that if the load fails for whatever reason (eg: kill -9 sqlldr),
SMON will clean up the orphaned temp extents for us automagically.

Hence, this message is telling you that you have insufficient space in MY_TS to
hold the data being loaded.  The temp segment is in fact your loaded data (it'll
be TEMP until we are done loading)
Review & Followup

Rating: 5
August 10, 2002
Reviewer: A reader
its very useful, thanks.
Rating: 5
August 10, 2002
Reviewer: A reader
Sorry Tom, I don't follow this. Could you clarify a little more?

You are saying, when sqlldr Direct option is used, the data is loaded to the
temp segments. And in this case the temp segment ran out of space, right? So
error message should have been on the TEMP tablespace and not on MY_TS (unless
MY_TS is his temp tablespace too.).

Or, are you saying that sqlldr direct would load data onto temp segments
situated in MY_TS? (is it possible?). In anycase, when he has 2GB freespace on
MY_TS, why is he getting an ORA-1652 on that tablespace?

Please explain. Thank you very much.
Followup:
I am saying when we load the segment we load into is marked and treated as a
TEMPORARY segment.

It is NOT in your temp tablespace, it is a temp segment.  This is so that if we
crash (system fails), when we restart SMON will see this temp segment in this
tablespace MY_TS and clean it up (get rid of it).  If it were a permanent
segment, SMON would not (and we would just have "junk" there).

So, when we load, the segment is marked "temporary".  When we are done with the
load into MY_TS, the "temporary" segment is marked "permanent".

They quite simply didn't have enough space in MY_TS to load their "huge" data
set.
Rating: 3
Direct Load and Additional extents with Auto extend ON March 29, 2004
Reviewer: KJ from MA, USA
Hi Tom,

 We have a Price Table with 65Million rows
CREATE TABLE PRODUCT_PRICE_FACT
(
  PRICE_DATE     DATE                           NOT NULL,
  CURRENT_PRICE  NUMBER(18,8),
  BID_PRICE      NUMBER(18,8),
  ASK_PRICE      NUMBER(18,8),
  PRODUCT_ID     NUMBER(9)                      NOT NULL
)PARALLEL ( DEGREE DEFAULT INSTANCES DEFAULT )

With PRICE_PKEY on ( Product_id, Price_date )
     INDEX_X1 on (product_id)
     INDEX_X2 on (price_date)

we have about 4.2GB of free space in the Index tablespace. One of the datafiles
is set to Autoextend ON.
Everyday about 390K rows are inserted into this table through the Informatica
job that uses SQLLdr and PARALLEL LOAD OPTION is Disabled.
 New extents are created on disk.. Why does the index not use the existing free
space? The largest Index is the PKey and it is about 1.5GB.  I would expect it
requires 1.5G + about 10M for the new data. But, it doesnt look like it is using
the existing free space. Since Autoextend is turned ON, it is using disk space.
This particular disk has only 120M left.

Can you please suggest as to what could be wrong here? We are using LMT.

Thanks for your time and help
Followup:
index_x1 looks like "wast of time, energy, and space" since the index for
price_pkey is going to have it indexed already... something to consider...

the index space is being used -- however, remember what an index is, a complex
data structure.

Think about what happens if you added lots of product_id = 1 (or small ones,
lots of small ones).  They'll all go on the left hand side of the index.  they
wont use any "interior space"

or think about all big product_ids, they go on the right hand side of the index.

so, it depends on your data as to where they go in the index (indexes are very
"organized" things -- data must go where it must go).

There is nothing "wrong" here.  You can "reclaim" space temporarily by
rebuilding/coalescing -- but i would stress TEMPORARILY - tomorrow as you load,
the data goes where it goes!!!

Perhaps by dropping the index index_x1 which seems redundant you'll find the
loads faster and you'll pick up a couple of bytes of disk space.
Rating: 3
Followup Question March 29, 2004
Reviewer: KJ from MA,USA
Hi Tom,
 Thanks for the immediate reply.
 I see that the first index is redundant.Would drop it.
 After the load, would all three indexes be built at the same time? What
determines that? I thought it should be 1 at a time and therefore, should use
the existing 4.5GB as it only requires 1.5GB + 10M approx for the TEMP Segment
and the merging.

 Can you please throw some light on this?
Thanks
Followup:
you are using what options to the sqlldr?

if you are using conventional path loads, then the data is put into the index an
array insert at a time (all indexes, at the same time)

if you are using direct paths, then the newly added data will create "mini
indexes" in temp (all at the same time) and these mini-indexes are merged into
the real indexes one at a time at the end of the load.
Rating: 4
Followup - Why it is using the disk space and not using freespace? March 29, 2004
Reviewer: KJ from MA,USA
Yes. Informatica is the tool used and it is configured to use Direct Path
Loading.

 INDEX_TS has about 4.5GB of free space. Everyday 390K rows get inserted. We
have the Autoextend ON. I would think, it should use the 4.5GB space for the
index merge. But, it is doing AutoExtending allocating the New extents in the
disk.

 If it is doing 1 index at a time, It has plenty of unused space already. Why
should it try to Auto Extend?

 Please let me know if you need additional details

Thanks
Followup:
if index_ts has free space (dba_free_space reports back "free space" in there),
it will not autoextend.  You would have to prove to me somehow that it is in
fact extending.

Something else is happening here.  If a tablespace has free space, the
tablespace will use that free space (ASSUMING of course the extents are the
right size, I'm assuming lots here -- I'm assuming you use LMT's and that you
haven't been using DMTs and that the extents that are free are big enough to be
used as a next extent by the index).

So, tell me -- what is the sum of free bytes from dba_extents

is the tablespace LMT or DMT

if DMT was is the largest free extent and what is NEXT on your index.....
Rating: 3
Followup March 29, 2004
Reviewer: KJ from MA,USA
Hi Tom,

 Tablespace is LMT

dba_free_space has 3962765312 Bytes free for FILE_ID 21

DBA_EXTENTS
 FILE_ID    SEGMENT_NAME    SUM(BYTES)
 21          PRODUCT_PRICE_PK    1785724928
 21     PRODUCT_PRICE_FACT_IX1    1275068416
 21     PRODUCT_PRICE_FACT_IX2    1476395008

I am sure it is Auto Extending because of the fact that the disk usage goes
down. I have been taking snapshots of the freespace and allocated over the past
2 weeks and notices that the allocated space went up from 7GB to almost 8GB Now.
Every other datafile has Autoextend OFF

It is confusing as to why it should AutoExtend. This is based on my guess that
the Direct Load does the index build one at a time.

Thanks
Followup:
are you REBUILDING or MAINTAINING the indexes???

if you maintain them, it just builds mini index off to the side and merges in.
free space would be reused.

if you rebuild them, you'll have 2x the space needed.
Rating: 3
Followup March 30, 2004
Reviewer: KJ from MA,USA
Hi

 An informatica job that users SQL Loader - Direct Load does the inserts into
this table. It should be a small merger, right? It is Direct Load with the
Parallel option disabled.

Thanks
Followup:
it is maintaining the indexes or not?

do you rebuild the indexes at the end or not?
Rating: 3
Followup March 30, 2004
Reviewer: KJ from MA,USA
The indexes are maintained. I do not issue a 'alter index rebuild' after the
job.

Tablespace has 4GB freespace. And everyday 390K rows are inserted by the SQL
Loader, Direct Load.

Only PRODUCT_INDEX_TS which is in /prdeis03 is set to AUTOEXTEND ON

before job: df -k /prdeis03 =       126000
            freespace in product_index_01.dbf = 3869888K

Job runs: inserts 390K rows
Size of the product_index_01.dbf increased by 61440K
            df -k /prdeis03 = 64560
            freespace in product_index_01.dbf = 4456640K

Thanks
Followup:
The documented fashion of the merge is:

Building Indexes

You can improve performance of direct path loads by using temporary storage.
After each block is formatted, the new index keys are put in a sort (temporary)
segment. The old index and the new keys are merged at load finish time to create
the new index. The old index, sort (temporary) segment, and new index segment
all require storage until the merge is complete. Then the old index and
temporary segment are removed.

for a mere 390k rows you could give serious consideration to a conventional path
insert.  You may well find it it more than fast enough.
Rating: 4
Followup March 31, 2004
Reviewer: KJ from MA,USA
Are all the indexes merged at the same time after the load?
it seems to be allocating the TEMP segments in the INDEX_TS and not in
TEMPORARY. Thats why it is auto extending I guess right?
Followup:
the merge goes like this

a) the new stuff is built up in TEMP

b) at the end, the new stuff from temp plus the old stuff in INDEX_TS is merged
together into temporary extents in INDEX_TS.  they are temporary so that if the
process failed right now, SMON would clean up for it upon restart

c) after the merge is complete, the temporary extents in INDEX_TS are converted
to permanent and the old index is dropped

that is why is extends -- you temporarily need 2x during the merge.
Rating: 4
Follow Up March 31, 2004
Reviewer: KJ from MA,USA
Thanks. That clarifies. But why does it have to create the Temporary extents in
INDEX_TS. Why cannot it do it in TEMP and move it to this tablespace?

Since it creates the Temporary extents in the index tablespace, at any time, we
always need to have 2X space right?

Also, We have three indexes in this table. ( I dropped the one that was
redundant ). Does it build all indexes at the same time after the Direct Data
Load or does is it one at a time. I thought you said One at a time in your
earlier reply

"if you are using direct paths, then the newly added data will create "mini
indexes" in temp (all at the same time) and these mini-indexes are merged into
the real indexes one at a time at the end of the load."

Thanks Tom!
Followup:
why would you want to

a) create data in temp
b) copy same data to index_ts

when you can just

b) create that data in index_ts and avoid having to read it all from temp and
write it again (eg: remove the reads of the entire thing and avoid 50% of the
writes!!!)

it would need 2x space if it did it in temp and moved them -- you cannot remove
the OLD indexes until the new ones are safely in place and the load is complete.
 If anything failed -- you would be left with junk.  Now if it fails, you are
left with your original setup intact.

so, it would stink if it wrote to temp and THEN copied from temp to index_ts
(when it could just write to INDEX_TS in the first place)

It'll do one at a time -- but must leave the old in place until the end of the
load in the event of failure.
March 31, 2004 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.
Rating: 5
Why CPU time is greater than elapsed time in tkprof report February 24, 2004
Reviewer: Jennifer Chen from USA
Here is the first part of my tkprof report:

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Trace file: aliastst_ora_2500.trc
Sort options: execpu
********************************************************************************
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
********************************************************************************

BEGIN MASTERINDEX_PKG.GET_MASTER_INDEX( :1,:2,:P_REFCURSOR ); END;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       44      0.03       0.01          0          0          0           0
Execute     44      3.00       2.96          0          0          0          44
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       88      3.03       2.98          0          0          0          44

Misses in library cache during parse: 1
Misses in library cache during execute: 1
Optimizer goal: CHOOSE
Parsing user id: 66
********************************************************************************

Also, can you please explain why the SP was parsed every time when it was
executed:

PROCEDURE get_master_index (
      p_mpi_number    IN       alias.people_index.mpi_number%TYPE,
      p_index_value   IN       CHAR,
      p_refcursor     OUT      sys_refcursor
   )
   AS
   BEGIN
      CASE UPPER (p_index_value)
         WHEN 'MNU'
         THEN
            OPEN p_refcursor
             FOR
                SELECT mpi_number, mnu_type, mnu_number, primary_value_flag,
                       sf_flag
                  FROM alias.name_mnu
                 WHERE mpi_number = p_mpi_number;
         WHEN 'DLN'
         THEN
            OPEN p_refcursor
             FOR
                SELECT mpi_number, dl_number, dl_state, primary_value_flag
                  FROM alias.name_dln
                 WHERE mpi_number = p_mpi_number;
         WHEN 'SMT'
         THEN
            OPEN p_refcursor
             FOR
                SELECT mpi_number, smt_number, primary_value_flag
                  FROM alias.name_smt
                 WHERE mpi_number = p_mpi_number;
         WHEN 'SOC'
         THEN
            OPEN p_refcursor
             FOR
                SELECT mpi_number, soc, primary_value_flag, sf_flag
                  FROM alias.name_soc
                 WHERE mpi_number = p_mpi_number;
         WHEN 'DOB'
         THEN
            OPEN p_refcursor
             FOR
                SELECT mpi_number, TO_CHAR (dob, 'MM/DD/YYYY'),
                       primary_value_flag, dob_count, sf_flag
                  FROM alias.name_dob
                 WHERE mpi_number = p_mpi_number;
         WHEN 'SEX'
         THEN
            OPEN p_refcursor
             FOR
                SELECT mpi_number, sex, primary_value_flag
                  FROM alias.name_sex
                 WHERE mpi_number = p_mpi_number;
         WHEN 'RAC'
         THEN
            OPEN p_refcursor
             FOR
                SELECT mpi_number, rac, primary_value_flag
                  FROM alias.name_rac
                 WHERE mpi_number = p_mpi_number;
         WHEN 'INDEX'
         THEN
            OPEN p_refcursor
             FOR
                SELECT mni_number, mpi_number, last_name_code,
                       first_name_code, middle_name_code,
                       last_name_group_num, first_name_group_num,
                       middle_name_group_num, last_name, first_name,
                       middle_name, other_name, real_name_flag,
                       name_type_code, sf_flag
                  FROM alias.name_index
                 WHERE mpi_number = p_mpi_number;
         WHEN 'PEOPLE'
         THEN
            OPEN p_refcursor
             FOR
                SELECT mpi_number, hgt, wgt, eye, hai, skn, fpc,
                       system_owner
                  FROM alias.people_index
                 WHERE mpi_number = p_mpi_number;
         ELSE
            raise_application_error (-20000, 'The Table Is Not In The List');
      END CASE;
   END get_master_index;

Thanks so much for your time and help.
Followup:
do you have "effective oracle by design"?  I go into this in more detail

but in a nutshell:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6904032233295#14588115938786
Rating: 5
hard parse and soft parses February 25, 2004
Reviewer: Jennifer Chen from USA
Hi Tom,

I read both your "effective oracle by design" and "Expert One-on-One Oracle".

Also, this morning I read metalink Note:32895.1 that explains parse further.

points 6 and 7 in the note:

6. Both the soft and hard parse register as a parse in tkprof. Hashing the
current statement updates the parse count.

7. Soft parse does not involve parsing the statement, it just hashes the
statement and compares this with the hashed value in the SQL area.

Since there was only one miss in library cache during parse, does that indicate
oracle has only done one hard parse and all the rest 43 times were just soft
parses?

Is there way that I can distinguish hard parse versus soft parse in TKPROF?

Parse time in this particular case is trivial, should I too concern about soft
parses here? By eliminating these parses, I can split this one stored procedure
to 9 seperate stored procedures. But I was trying to consolidate SPs since I
already have about 500 them in our application.

Your thoughts on this are greatly appreciated.

Thank you for taking time to respond my questions.
Followup:
7.) is wrong wrong wrong (effective Oracle by design goes into this, "beginning
Oracle programming" which I wrote the statement processing chapter in goes into
this in more depth.

I've sent an email to the author of that note requesting it be corrected.

see:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2588723819082
But yes, the misses are hard parses, that is how to tell.

Parses in single user mode are misleading.  It is the fact that they "latch" and
latches are locks and locks are serialization devices.  if you have a multi-user
system, excessive parsing will get in your way.

Actually in looking at this stored procedure -- it looks like it is part of a
massive slow by slow (row by row) process somewhere doing tons of lookups.

I would personally be fishing much further upstream -- looking for why I needed
such a routine in the first place.  Probably just need a single query somewhere
that "joins"
Rating: 5
different execution time between stand alone sql and stored procedure February 25, 2004
Reviewer: Jennifer Chen from MD, USA
Hi Tom,

Thank you so much for your quick response. Your previous note to the author was
very helpful too.

The routine stored procedure I wrote is for the developer to pass in ID and
table name index and get rows from the corresponding table because we are
eliminating stand alone sql in the application code. I tracked down a developer
who used a stand alone insert statement and compared it to the packaged stored
procedure:

They both have a hard parse since I restarted database and turned trace on. The
parse cpu time for the stand alone sql takes much longer. I guess this is
probably because the syntax check and semantics check for the stored procedure
are done during the compilation. But I am curious about why execution time for
the stored procedure is much less than that for the stand alone sql statement?
Forgive me if you already answered this question before. I am amazed that you
write faster than we can read.
Thank you for your public services.

INSERT INTO alias.chr_inquiry_log
                  (inquiry_log_number, criminal_justice_agency, ctn,
                   dln, dls, dob,
                   enter_date_time, fbi, inquiry_for, message_number,
                   mke, mnu_number, mnu_type, nam, oca, opr,
                   prn, pur, rac, remarks, req, response_count,
                   sex, SID, soc, tcn, ter, ucn
                  )
           VALUES (:b27, :b26, :b25,
                   :b24, :b23, TO_DATE (:b22, 'MM/DD/YYYY'),
                   SYSTIMESTAMP, :b21, :b20, :b19,
                   :b18, :b17, :b16, :b15, :b14, :b13,
                   :b12, :b11, :b10, :b9, :b8, :b7,
                   :b6, :b5, :b4, :b3, :b2, :b1
                  )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      1.09       1.08          0          0          0           0
Execute      1      0.53       0.56          4          1         30           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      1.62       1.65          4          1         30           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 66     (recursive depth: 1)
********************************************************************************

BEGIN UTILITY_PKG.ADD_CHR_INQUIRY_LOG( :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 ); END;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      1      0.18       0.17          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.20       0.17          0          0          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 66

Here is the content of UTILITY_PKG.ADD_CHR_INQUIRY_LOG

PROCEDURE add_chr_inquiry_log (
      p_inquiry_log_number        IN
alias.chr_inquiry_log.inquiry_log_number%TYPE,
      p_criminal_justice_agency   IN
alias.chr_inquiry_log.criminal_justice_agency%TYPE,
      p_ctn                       IN   alias.chr_inquiry_log.ctn%TYPE,
      p_dln                       IN   alias.chr_inquiry_log.dln%TYPE,
      p_dls                       IN   alias.chr_inquiry_log.dls%TYPE,
      p_dob                       IN   CHAR,
      p_fbi                       IN   alias.chr_inquiry_log.fbi%TYPE,
      p_inquiry_for               IN   alias.chr_inquiry_log.inquiry_for%TYPE,
      p_message_number            IN
alias.chr_inquiry_log.message_number%TYPE,
      p_mke                       IN   alias.chr_inquiry_log.mke%TYPE,
      p_mnu_number                IN   alias.chr_inquiry_log.mnu_number%TYPE,
      p_mnu_type                  IN   alias.chr_inquiry_log.mnu_type%TYPE,
      p_nam                       IN   alias.chr_inquiry_log.nam%TYPE,
      p_oca                       IN   alias.chr_inquiry_log.oca%TYPE,
      p_opr                       IN   alias.chr_inquiry_log.opr%TYPE,
      p_prn                       IN   alias.chr_inquiry_log.prn%TYPE,
      p_pur                       IN   alias.chr_inquiry_log.pur%TYPE,
      p_rac                       IN   alias.chr_inquiry_log.rac%TYPE,
      p_remarks                   IN   alias.chr_inquiry_log.remarks%TYPE,
      p_req                       IN   alias.chr_inquiry_log.req%TYPE,
      p_response_count            IN
alias.chr_inquiry_log.response_count%TYPE,
      p_sex                       IN   alias.chr_inquiry_log.sex%TYPE,
      p_sid                       IN   alias.chr_inquiry_log.SID%TYPE,
      p_soc                       IN   alias.chr_inquiry_log.soc%TYPE,
      p_tcn                       IN   alias.chr_inquiry_log.tcn%TYPE,
      p_ter                       IN   alias.chr_inquiry_log.ter%TYPE,
      p_ucn                       IN   alias.chr_inquiry_log.ucn%TYPE
   )
   AS
   BEGIN
      INSERT INTO alias.chr_inquiry_log
                  (inquiry_log_number, criminal_justice_agency, ctn,
                   dln, dls, dob,
                   enter_date_time, fbi, inquiry_for, message_number,
                   mke, mnu_number, mnu_type, nam, oca, opr,
                   prn, pur, rac, remarks, req, response_count,
                   sex, SID, soc, tcn, ter, ucn
                  )
           VALUES (p_inquiry_log_number, p_criminal_justice_agency, p_ctn,
                   p_dln, p_dls, TO_DATE (p_dob, 'MM/DD/YYYY'),
                   SYSTIMESTAMP, p_fbi, p_inquiry_for, p_message_number,
                   p_mke, p_mnu_number, p_mnu_type, p_nam, p_oca, p_opr,
                   p_prn, p_pur, p_rac, p_remarks, p_req, p_response_count,
                   p_sex, p_sid, p_soc, p_tcn, p_ter, p_ucn
                  );
   END add_chr_inquiry_log;
Followup:
what version/platform and what exactly was the sequence of steps here -- is this
tkprof from a single session?  why is the insert above the procedure call?
Rating: 5
PIO > LIO February 26, 2004
Reviewer: Vlado from Richmond, VA
Tom,

I have a Oracle 9.0.2 instance on Sun Solaris, on which I execute a multi-table
insert statement which is based on a join on three tables A, AC, FP.
- A & AC have 1 million rows each, they have the same primary key columns and
the join is on the primary key columns.
- Every row in A matches to a corespsonding row in AC.
- FP has approx. 25000 rows, and is referenced using an outer join.
- There are no filters, so the insert statement will insert a total of 2 million
rows into two tables (1 million into each table).

Below is part of a session trace I run on the insert statement (10046, level 8)
and analyzed with Trace Analyzer.
The number of PIOs (1941934) is what I'm concerend about as it is 7 times that
of the LIOs (119884 + 153987). The only explanation I have is that it could be
caused by the hash join, which might have to write to the temp tablespace.

call     count      cpu  elapsed     disk    query  current     rows  misses
------- ------  ------- -------- -------- -------- -------- -------- -------
Parse        1     0.13     0.15        1        3        0        0       1
Execute      1   570.99  2535.55  1941933   119881   153987  2000000       0
------- ------  -------  ------- -------- -------- -------- -------- -------
total        2   571.12  2535.71  1941934   119884   153987  2000000       1

   Rows Row Source Operation
------- ---------------------------------------------------
      2 MULTI-TABLE INSERT (cr=119875 pr=1941933 pw=156642 time=531.54)
1000000 .HASH JOIN (cr=105407 pr=1917909 pw=94885 time=2183.15)
1000000 ..HASH JOIN (cr=88404 pr=144013 pw=28140 time=135.64)
  43863 ...TABLE ACCESS FULL FP (cr=376 pr=374 pw=0 time=0.28)
1000000 ...TABLE ACCESS FULL A (cr=88028 pr=88031 pw=0 time=38.57)
1000000 ..TABLE ACCESS FULL AC (cr=17003 pr=17008 pw=0 time=12.61)

I found it strange that Oracle choose to first join FP & A, so I added a hint to
force Oracle to first join A & AC, and then FP. When I executed the query again,
the performance was much better:

call     count     cpu   elapsed     disk    query  current     rows   misses
------- ------ ------- --------- -------- -------- -------- -------- --------
Parse        1    0.40      0.49       10      174        0        0        1
Execute      1  552.77   1211.41   330686   119840   153959  2000000        0
------- ------ ------- --------- -------- -------- -------- -------- --------
total        2  553.17   1211.90   330696   120014   153959  2000000        1

   Rows Row Source Operation
------- ---------------------------------------------------
      2 MULTI-TABLE INSERT (cr=119834 pr=330685 pw=159897 time=450.74)
1000000 .HASH JOIN (cr=105407 pr=306716 pw=98118 time=133.24)
  43863 ..TABLE ACCESS FULL FP (cr=376 pr=374 pw=0 time=0.26)
1000000 ..HASH JOIN (cr=105031 pr=201454 pw=45338 time=710.34)
1000000 ...TABLE ACCESS FULL A (cr=88028 pr=88025 pw=0 time=38.79)
1000000 ...TABLE ACCESS FULL AC (cr=17003 pr=16977 pw=0 time=16.27)

So, I have two questions:
1. What is the cause for the PIO > LIO?
2. Why did the PIO count change so much between the two runs (assuming the only
difference was that I added a hint to force the join on A & AC first)
Followup:
1) You have PIO's to and from temp -- when the blocks are in TEMP, they do not
incurr LIO's (we don't have them in the buffer cache ever).  So, what you are
seeing is reads to temp.

2) different amounts of data put to temp caused that.  See the "pw" -- thats
writes to temp there.
Rating: 5
Thank you for looking into this February 26, 2004
Reviewer: Jennifer Chen from MD, USA
Hi Tom,

Thank you for looking into this. Here are answers to your questions:

1. what version/platform
We are running Oracle 9.2.0.4.0 on Windows 2000 SP4.

2. what exactly was the sequence of steps here --
I set sql_trace=TRUE in init.ora and bounced DB. Our developer restarted app
server, connected DB through ODBC, and run the application (name search feature)
via Brower.

3. is this tkprof from a single session?
Yes. I generated report:
...\udump> tkprof ora02500.trc aliastst_ora_2500.trc sort=execpu

4. why is the insert above the procedure call?
Our application codes are written in c++ and suppose to only invoke packaged
stored procedures to access data. According to the developer, when he did name
search, there should only insert one record into the chr_inquiry_log table. He
said he does not have stand alone sql in the code. Then, I must understand and
interpret the TKPROF report wrong.

Could you please explain how to interpret these two paragraphs in the TKPROF
report?

Thanks again for your time and help.
Followup:
Ok, the sort=execpu sorted the data for us.  that is why the insert appeared
above the procedure call.

I just ran this test on windows:

set echo on

                                         connect tkyte/tkyte

drop table t;
create table t (x int);

create or replace function f( x in number ) return number
deterministic
as
    l_n  number := 0;
begin
    for i in 1 .. x
    loop
        l_n := ln(i);
    end loop;
    return l_n;
end;
/

create index t_idx on t(f(x));

create or replace procedure p( x in number, y in number )
as
    l_n number := 0;
begin
    for i in 1 .. y
    loop
        l_n := ln(i);
    end loop;
    insert into t values( p.x );
end;
/

variable x number
variable y number
exec :x := 0; :y := 10000
exec p(:x,:y);

alter session set sql_trace=true;
exec p(:x,:y);
disconnect
connect tkyte/tkyte
alter session set sql_trace=true;
exec :x := 10000; :y := 0
exec p(:x,:y);
disconnect
connect tkyte/tkyte

So, we warm up everything and then execute p with 0, 10000 (means p the
procedure runs long, insert into t goes fast -- ln(i) is expensive to compute)

then we run p with 10000, 0 means p the procedure runs fast, insert into t goes
slow....

the tkprofs show (on windows)

BEGIN p(:x,:y); END;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.73       0.73          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.73       0.73          0          0          0           1

********************************************************************************

                                         INSERT into t values ( :b1 )

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          3           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        1      0.00       0.00          0          0          3           1

and then:

BEGIN p(:x,:y); 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
********************************************************************************
INSERT into t values ( :b1 )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.72          0          1          4           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.72          0          1          4           1

so, we can see tkprof is not rolling up the aggregate times from the sql
executed in a procedure into the procedure itself.

So, what your report above means is

a) the procedure "sans sql" runs in about 0.20 CPU seconds.

b) the SQL insert executed by that procedure took long to parse (probably lots
of indexes, constraints and foreign keys to get together) and long to execute
(complex indexes, triggers, constraints, fkeys, etc)

that's all -- it means the low hanging fruit to pick is:

a) make sure to avoid the parse as much as you can. PLSQL will go along way
there, no matter how many times in a session you call that procedure, it should
only show 1 parse in tkprof for the insert

b) look at the table and see if you have too many unnecessary indexes, what the
constraints are, if anything obviously could be "tuned up" a little.
Rating: 5
RE: I just ran this test on windows February 26, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
"I just ran this test on windows"

It must be getting unusually cold somewhere :)

(though I suspect VMWare was involved)

- Mark
Followup:
vmware, definitely.

It is ironically amusing to run windows in a window.  Very comforting in a way.
When I'm done, I can just kill -9 it all if i like :)
Rating: 5
THANK YOU February 26, 2004
Reviewer: A reader
Hi Tom,

You explained everything very, very clear.

Many many thanks!!!
Rating: 4
Re: PIO > LIO February 27, 2004
Reviewer: Vlado from Knoxville, TN
But why would there be different amounts of data put to temp, if all that
changed was the order of the joins?
Followup:
you are comparing apples to bananas here.  change the order of the tables and
everything is different.

also, it could be that you were using pga aggregate target and got different
amounts of ram each run (due to differing loads on your system)
Rating: 3
9i SQL Trace & Tkprof March 03, 2004
Reviewer: Denni50 from NA
Tom

I'm trying to determine why my 9i test db is running very
slow. I'm the only user that rus the system as I'm developing tables,
forms...etc

I noticed when I run a simple select query the CPU jumps
to 98-100% usage, the data should be whizzing off the
the screen but it kinda/sorta lumbers along.

I tried changing some of the init.ora parameters to see
if that would help. The init.ora file is the generic
file Oracle creates during installation.
I had to comment out #dispatchers(MTS) that was causing
start up problems.

anyway to make a long story short I ran a trace/tkprof
on a simple select statement:

TKPROF: Release 8.1.7.0.0 - Production on Wed Mar 3 14:39:36 2004

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

Trace file: ora00315.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: RULE
Parsing user id: 71
********************************************************************************

select distinct idnumber
from
 gift_orig where idnumber < 2000

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    156.25    2820.00        117        121          0         999
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       70    156.25    2820.00        117        121          0         999

Misses in library cache during parse: 1
Optimizer goal: RULE
Parsing user id: 71

Rows     Row Source Operation
-------  ---------------------------------------------------
    999  SORT UNIQUE
  20467   INDEX RANGE SCAN (object id 32275)

********************************************************************************

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          0          0           0
Fetch       68    156.25    2820.00        117        121          0         999
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       71    156.25    2820.00        117        121          0         999

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

    2  user  SQL statements in session.
    0  internal SQL statements in session.
    2  SQL statements in session.
********************************************************************************
Trace file: ora00315.trc
Trace file compatibility: 8.00.04
Sort options: default

       1  session in tracefile.
       2  user  SQL statements in trace file.
       0  internal SQL statements in trace file.
       2  SQL statements in trace file.
       2  unique SQL statements in trace file.
     104  lines in trace file.

The cpu/elapsed is way out there...this is on an NT4 OS
and the System Administrator states this is an Oracle
issue not NT4???...any clues(sigh)??

I'm sorry for so many questions..I'm trying to tackle
a zillion projects at one time and they each create their
own set of problems.
Followup:
use the 9i tkprof please -- we changed the timing information -- those numbers
are really teeny tiny as you'll see :)

                                      9i trace file with 8i tkprof:

select count(*)
from
 all_objects

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1    300.00     156.00          0          9          0           0
Execute      1      0.00       0.81          0          0          0           0
Fetch        2  18400.00   19010.04         78     163246          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4  18700.00   19166.85         78     163255          0           1

same trace file, using 9i tkprof:

select count(*)
from
 all_objects

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.03       0.01          0          9          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      1.84       1.90         78     163246          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      1.87       1.91         78     163255          0           1

You have to move your decimal point 4 to the left:

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          0          0           0
Fetch       68      0.01       0.28        117        121          0         999
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       71      0.01       0.28        117        121          0         999

is what you have for real (instant tuning!)
Rating: 4
Tom... March 04, 2004
Reviewer: denni50 from NA
duh!

I did the tkprof from my client machine which is
connected to the 817 db...gotta remember when I need
9i specifics I need to do it from the server.

Got 3 servers going here and I'm beginning to feel
like the Led Zep song "dazed and confused".

anyway will need to do some research on running a 9i db
on NT4.

thanks and have a great w/e!(off friday..need a break!)
Followup:
You should try 8ir3, 9ir2 on windows under vmware on the same box with 9ir1,
9ir2 and 10gr1 on linux :)
Rating: 3
9.0.1.1.1 Tkprof.exe Patch March 04, 2004
Reviewer: denni50 from NA
Tom
Oracle 9.0.1.1.1 tkprof tool is not included. I've searched the Oracle bin
directory.

I understand this is a bug with Oracle regarding tkprof
on this particular version and there is a patch.

We are not registered users with metalink and do not have
a Support Identifier Code due to reasons I'd rather not
discuss.

Is there any other way to get the patch?
thanks
Followup:
sorry -- I cannot get patches for you HOWEVER............

you can download 9ir2 and use it's tkprof, it can read 9ir1 trace files.
(otn.oracle.com)
Rating: 4
thanks Tom... March 04, 2004
Reviewer: denni50 from NA
was looking at the otn website to see if they had any patches...thought about
downloading 9.2..heck might
as well install that version over 9.0.1.1.1.

was reading up on this vmware software..sounds really
neat you can load different OS in a virtual mode, channel back and forth..etc.

Don't think I have enough powers of persuasion to ask
organization to buy me software so I can "play" on the
old server.

When I hit the lotto I'm getting myself that new compact
IBM e_server with the xeon processors...then I'll install
Linux...10i to 20i...windoz...and the whole shebang!

I know Tom I'm getting silly...and signing off for the week.

take care
:~)
Rating: 4
how do I kown my session has beening tracing? March 05, 2004
Reviewer: steven from BeiJing.China
after i alter session set sql_trace=true;
SQL> show parameters sql_trace

sql_trace  boolean    FALSE

just show system parameter;

where can i find sql_trace has opened for my session?
i know trace is process work.i can't find it on v$process.
Followup:
ops$tkyte@ORA9IR2> declare
  2     l_event number;
  3  begin
  4          sys.dbms_system.read_ev( 10046, l_event );
  5          dbms_output.put_line( 'result = ' || l_event );
  6  end;
  7  /
result = 0

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA9IR2> declare
  2     l_event number;
  3  begin
  4          sys.dbms_system.read_ev( 10046, l_event );
  5          dbms_output.put_line( 'result = ' || l_event );
  6  end;
  7  /
result = 1

PL/SQL procedure successfully completed.
Rating: 5
March 29, 2004
Reviewer: Pauline from NY,USA
Tom,
I try to use your example to see the return value by using
DBMS_SYSTEM and find something wrong after udgrading our
database from 8.1.7.4 to 9.2.0.4.
We have external user called ORACLE in Oracle 8.1.7.4 version of database. This
user could execute SYS's DBMS_SYSTEM or DBMS_UTILITY packages. See below:

<dbdev1:dev4:/appl/oracle/admin/sql> sqlplus /

SQL*Plus: Release 8.1.7.0.0 - Production on Mon Mar 29 12:14:55 2004

(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> sho user
USER is "ORACLE"
SQL> SELECT  *  FROM SYS.dba_role_privs
     WHERE grantee = 'ORACLE';

GRANTEE                        GRANTED_ROLE                   ADM DEF
------------------------------ ------------------------------ --- ---
ORACLE                         DBA                            NO  YES

SQL> desc sys.dbms_system
PROCEDURE DIST_TXN_SYNC
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 INST_NUM                       NUMBER                  IN
PROCEDURE KCFRMS
PROCEDURE KSDDDT
PROCEDURE KSDFLS
PROCEDURE KSDIND
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 LVL                            BINARY_INTEGER          IN
PROCEDURE KSDWRT
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 DEST                           BINARY_INTEGER          IN
 TST                            VARCHAR2                IN
PROCEDURE READ_EV
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 IEV                            BINARY_INTEGER          IN
 OEV                            BINARY_INTEGER          OUT
PROCEDURE SET_BOOL_PARAM_IN_SESSION
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 SID                            NUMBER                  IN
 SERIAL#                        NUMBER                  IN
 PARNAM                         VARCHAR2                IN
 BVAL                           BOOLEAN                 IN
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
PROCEDURE SET_INT_PARAM_IN_SESSION
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 SID                            NUMBER                  IN
 SERIAL#                        NUMBER                  IN
 PARNAM                         VARCHAR2                IN
 INTVAL                         BINARY_INTEGER          IN
PROCEDURE SET_SQL_TRACE_IN_SESSION
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 SID                            NUMBER                  IN
 SERIAL#                        NUMBER                  IN
 SQL_TRACE                      BOOLEAN                 IN
PROCEDURE WAIT_FOR_EVENT
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 EVENT                          VARCHAR2                IN
 EXTENDED_ID                    BINARY_INTEGER          IN
 TIMEOUT                        BINARY_INTEGER          IN
SQL> exec sys.dbms_system.set_sql_trace_in_session(14,3,true);

PL/SQL procedure successfully completed.

SQL> exec sys.dbms_system.set_sql_trace_in_session(14,3,false);

PL/SQL procedure successfully completed.

SQL> exec dbms_utility.analyze_schema(schema=>'STAGING', method=>'ESTIMATE',
estimate_percent=>'15' );

PL/SQL procedure successfully completed.

Once we upgrade to Oracle 9i, we give this user SELECT ANY DICTIONARY privilege
since 9i O7_DICTIONARY_ACCESSIBILITY default to FALSE. This user still has
EXECUTE ANY
PROCEDUE privilege. But it only can execute DBMS_UTILITY package as before but
can't execute DBMS_SYSTEM package.

See below:

<dbdev1:dev1:/appl/oracle/admin/sql> sqlplus /

SQL*Plus: Release 9.2.0.4.0 - Production on Mon Mar 29 12:32:26 2004

Copyright (c) 1982, 2002, Oracle Corporation.  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> sho user
USER is "ORACLE"
SQL> exec dbms_utility.analyze_schema(schema=>'STAGING', method=>'ESTIMATE',
estimate_percent=>'15' );

PL/SQL procedure successfully completed.

SQL>  exec sys.dbms_system.set_sql_trace_in_session(18,1,true);
BEGIN sys.dbms_system.set_sql_trace_in_session(18,1,true); END;

      *
ERROR at line 1:
ORA-06550: line 1, column 7:
PLS-00201: identifier 'SYS.DBMS_SYSTEM' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

SQL> desc SYS.DBMS_SYSTEM
ERROR:
ORA-04043: object SYS.DBMS_SYSTEM does not exist

Now let's login as SYS to query:

SQL> conn / as sysdba
Connected.
SQL> sho user
USER is "SYS"
SQL> desc DBMS_SYSTEM
PROCEDURE DIST_TXN_SYNC
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 INST_NUM                       NUMBER                  IN
PROCEDURE KCFRMS
PROCEDURE KSDDDT
PROCEDURE KSDFLS
PROCEDURE KSDIND
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 LVL                            BINARY_INTEGER          IN
PROCEDURE KSDWRT
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 DEST                           BINARY_INTEGER          IN
 TST                            VARCHAR2                IN
PROCEDURE READ_EV
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 IEV                            BINARY_INTEGER          IN
 OEV                            BINARY_INTEGER          OUT
PROCEDURE SET_BOOL_PARAM_IN_SESSION
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 SID                            NUMBER                  IN
 SERIAL#                        NUMBER                  IN
 PARNAM                         VARCHAR2                IN
 BVAL                           BOOLEAN                 IN
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
PROCEDURE SET_INT_PARAM_IN_SESSION
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 SID                            NUMBER                  IN
 SERIAL#                        NUMBER                  IN
 PARNAM                         VARCHAR2                IN
 INTVAL                         BINARY_INTEGER          IN
PROCEDURE SET_SQL_TRACE_IN_SESSION
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 SID                            NUMBER                  IN
 SERIAL#                        NUMBER                  IN
 SQL_TRACE                      BOOLEAN                 IN
PROCEDURE WAIT_FOR_EVENT
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 EVENT                          VARCHAR2                IN
 EXTENDED_ID                    BINARY_INTEGER          IN
 TIMEOUT                        BINARY_INTEGER          IN

SQL> select status from user_objects where object_name like 'DBMS_SYSTEM';

STATUS
--------
VALID
VALID

What is wrong? Your pinpoint is always appreciated.

Thanks very much.
Followup:
grant select on dbms_system to that user.
Rating: 3
March 31, 2004
Reviewer: Pauline from NY,USA
Tom,
1.Thanks very much for the response. After I grant execute on DBMS_SYSTEM to
this user as SYS and then connect to
this user,I can execute sys.dbms_system.set_sql_trace_in_session

But I still have suspecion about the privileges issue.
See below:
SQL> SELECT grantor, owner,table_name "Object", PRIVILEGE, grantable "Grant
Option?",  ' ' "By Role"
  FROM SYS.dba_tab_privs
 WHERE grantee = 'ORACLE'
 and grantor='SYS'
 and table_name like 'DBMS%'
UNION
SELECT grantor, owner,table_name "Object", PRIVILEGE, grantable "Grant Option?",
 grantee
  FROM SYS.dba_tab_privs
 WHERE grantee IN (SELECT granted_role
                     FROM SYS.dba_role_privs
                    WHERE grantee = 'ORACLE')
and grantor='SYS'
and table_name like 'DBMS%';

GRANTOR    OWNER                          Object
PRIVILEGE            Gra By Role
---------- ------------------------------ ------------------------------
-------------------- --- ------------------------------
SYS        SYS                            DBMS_CDC_UTILITY               EXECUTE
             NO  SELECT_CATALOG_ROLE
SYS        SYS                            DBMS_DEFER_QUERY               EXECUTE
             NO  DBA
SYS        SYS                            DBMS_DEFER_SYS                 EXECUTE
             NO  DBA
SYS        SYS                            DBMS_FLASHBACK                 EXECUTE
             NO  DBA
SYS        SYS                            DBMS_RESUMABLE                 EXECUTE
             NO  DBA
SYS        SYS                            DBMS_STORAGE_MAP               EXECUTE
             NO  DBA
SYS        SYS                            DBMS_SYSTEM                    EXECUTE
             NO

7 rows selected.

Why after upgraded to 9i, we have to explicitely grant execute on dbms_system to
this user who owned DBA role
plus we already grant select any dictionary to this
user? Why this user can execute DBMS_UTILITY even we don't
see the privilege from above query? What should be the
standard behavior for the user who has the DBA role and try
to execute SYS's procedure/package in Oracle 9i? I try to
grant this user execute_catalog_role as sys but it doesn't
work.

2. When I try to see what is return value by executing
sys.dbms_system.read_ev to compare before and after session
set sql_trace=true, I don't see any return value:

SQL> sho user
USER is "ORACLE"
SQL> declare
  2        l_event number;
   begin
  3    4             sys.dbms_system.read_ev( 10046, l_event );
           dbms_output.put_line( 'result = ' || l_event );
  5    6     end;
  7  /

PL/SQL procedure successfully completed.

SQL> alter session set sql_trace=true;

Session altered.

SQL> declare
  2        l_event number;
  3     begin
  4             sys.dbms_system.read_ev( 10046, l_event );
  5             dbms_output.put_line( 'result = ' || l_event );
  6     end;
  7  /

PL/SQL procedure successfully completed.

What could be wrong?

Thanks again.
Followup:
"07_DICTIONARY_ACCESSIBILITY default to FALSE."

it does that -- it protects all SYS owned code, makes it so that 'EXECUTE ANY
PROCEDURE' isn't "good" enough anymore.

It is a security thing

SQL> set serveroutput on

you need to do that to see dbms_output stuff.
Rating: 5
March 31, 2004
Reviewer: Pauline from NY,USA
Thanks so much.
March 31, 2004 Shyam -- Thanks for the question regarding "Returning values from Stored Procedures", version 8.1.7
You Asked
Can I execute a stored Procedure using ODBC which returns a value?

With the following procedure
CREATE or replace PROCEDURE sampleproc1
        ( EVENT_ID in INT,  found in out INT )
     AS
     varcount int;
BEGIN
     SELECT count(1) into varcount FROM table1 WHERE EVENT_ID=EVENT_ID ;
     IF varcount > 0 THEN
        found := 1;
     ELSE
        found := 0;
     END IF;
END sampleproc1;

I tried doing this in SQL*Plus

execute :found := sampleproc1(1, :found );

but I get

SP2-0552: Bind variable "FOUND" not declared.

I need to call this in C code using ODBC and be able to return the value into a
variable for further processing.

Thanks for all the help
-Shyam Venkatram
and we said...
well, you have a PROCEDURE, not a function -- so there is no return value there.

in sqlplus it would be:

SQL> variable found number
SQL> exec sampleproc( 1, :found );
SQL> print found

Yes, you can certainly bind in ODBC -- the odbc documentation has examples

HOWEVER, I would strongly encourage you to not "count" records like that.  What
a waste of computer resources!  Just pretend the row exists and try the
operation you would do if the row did exist -- and upon discovering "hey, row
doesn't exist", start doing what you would have otherwise.  At the very least --
code it like this:

create or relace procedure sampleproc( p_even_id in int, found out int )
as
begin
   select count(*) into found
     from table1
    where event_id = p_event_id
      AND ROWNUM = 1;
end;
/

and get into the habit of prefixing all variables with "P_" for parameters, "L_"
for local variables.

Look at your query:

SELECT count(1) into varcount FROM table1 WHERE EVENT_ID=EVENT_ID ;
                                                ^^^^^^^^^^^^^^^^^

think about what would happen there -- "where column = column" -- hmmmmmm :)

also -- count(1) is not nearly as semantically meaningful as count(*).  You
wanted to count records right?  that is count(*), count(1) is the count of the
number 1 in that table.  sure, returns the same answer but isn't meaningful.
count(*) is correct.

but at the end of the day -- there is ZERO chance I would have this procedure.
Your C code is doing something like:

   begin proc( ..., :found ); end;
   if ( found = 1 )
   {
       do this
   }
   else
   {
       do that
   }

I would simply code

   try to do this and if this didn't do anything (no records) then
      do that

less work all around. (that and who is to say that found's value doesn't change
between the time you ask for it and the time you look at it!!!)
Review & Followup

Rating: 2
March 31, 2004
Reviewer: Shyam Venkatram from Palo Alto, CA
Thank you for the response and hints on practice methods.

Could you please send me an URL for the ODBC examples.

Thanks
Shyam
Followup:
You are using an API and you don't have any docs?

I don't use ODBC, only know that ODBC does binds.  You'd refer to the
programming guides you have....
March 31, 2004 Munzer -- Thanks for the question regarding "SQL Query", version 8.1.7
You Asked
Tom:

I have a table that initially stores information about items in a warehouse
stored in different bins.  Table look like this where manual inventories are
done every 3 months.  Effective date is the sysdate when record is inserted.

Inventory:

Item_no   Qty  Bin  Effective_Date
AC006    10    DC001  2/1/2002
AC006    20    DC002  2/1/2002

AC006  100    DC001 5/1/2002
AC006  50     DC002  5/2/2002
AC006  30    DC003   5/3/2002
AC006  20    DC008   5/4/2002

I need to calculate two things:

1.    Total Qty of an item in inventory in a given bin.  which I basically did
by taking qty of the item in inventory with the
max(effective_Date)+total_received and stored in that bin since that
max(effective_date) – total shipped from that bin since that
max(effective_date).

2.    Total qty of an item in inventory.  Here I need to look at the highest
effective date for an item at a bin.  Bascially I need to run a SQL statement
that gives me 200(sum last 4 recrods)  for total of an item “AC006”.  How do you
formulate that statement and exclude all the previous inventories for that item
that are out of date.

3.    Also, let me say I have the following table.  Would it still work where I
basically stored the items in different bins.

Inventory:

Item_no   Qty  Bin  Effective_Date
AC006    10    DC001  2/1/2002
AC006    20    DC002  2/1/2002

AC006  100    DC003 5/1/2002
AC006  50     DC004  5/2/2002

The answer here should be 150 and not 180.

Thank you,
and we said...
Ok,  well

1) that sounds like you are giving me a statement of fact.  I don't see a
question there, only how you answered your own question.

2) Well, if we start with:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

ITEM_        QTY BIN   EFFECTIVE_
----- ---------- ----- ----------
AC006         10 DC001 02/01/2002
AC006         20 DC002 02/01/2002
AC006        100 DC001 05/01/2002
AC006         50 DC002 05/02/2002
AC006         30 DC003 05/03/2002
AC006         20 DC008 05/04/2002
AC007         10 DC001 02/01/2002
AC007         20 DC002 02/01/2002
AC007         77 DC001 05/01/2002
AC007         32 DC002 05/02/2002
AC007         52 DC003 05/03/2002
AC007         33 DC008 05/04/2002

12 rows selected.

I'll show you at least three different ways to get this.  The first is the
best in my opinion (easy to read, performs well)

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select item_no, sum(qty)
  2    from (
  3  select distinct item_no, bin,
  4         first_value(qty) over(partition by item_no, bin
  5                               order by effective_date desc) qty
  6    from t
  7    )
  8   group by item_no
  9  /

ITEM_   SUM(QTY)
----- ----------
AC006        200
AC007        194

it uses the analytic functions available with 816 EE and up...

The next uses a "max" trick i use alot to avoid correlated subqueries:

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select substr( data, 14, 5 ) item_no, sum(
to_number( substr( data, 19 ) ) ) qty
  2    from (
  3  select max( to_char( effective_date, 'YYYYMMDD' ) || rpad( bin, 5 ) ||
rpad( item_no, 5 ) || to_char(qty) ) data
  4    from t
  5   group by item_no, bin
  6         )
  7   group by substr( data, 14, 5 )
  8  /

ITEM_        QTY
----- ----------
AC006        200
AC007        194

and the last uses a correlated subquery:

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select item_no, sum(qty)
  2    from t
  3   where effective_date = ( select max(effective_date)
  4                              from t t2
  5                             where t2.item_no = t.item_no
  6                               and t2.bin = t.bin )
  7   group by item_no
  8  /

ITEM_   SUM(QTY)
----- ----------
AC006        200
AC007        194

ops$tkyte@ORA817DEV.US.ORACLE.COM>

to achieve the same...

3) No, not at all.  Why would it be 150 and not 180?  How would a computer know
that -- you are using some hidden piece of knowledge in this case.  I don't even
know what that hidden bit of knowledge is myself.  Basically you are saying
"hey, if there is some long period of time between sets of measurements, ignore
the old stuff".  Only if you could tell me exactly (procedurally) how to filter
this data (eg: only use data whose effective_date is within 5 days of the max
effective_date).  Otherwise -- it would seem to me that the answer is 180, not
150.
Review & Followup

Rating: 5
SQL query March 17, 2002
Reviewer: munz from reston, usa
Excellent as usual
Rating: 5
Why r u Using Distinct and then First_value here March 19, 2002
Reviewer: Pascal from germany,HH
Refer to ur Query just compied from above:

select item_no, sum(qty)
  2    from (
  3  select distinct item_no, bin,
  4         first_value(qty) over(partition by item_no, bin
  5                               order by effective_date desc) qty
  6    from t
  7    )
  8   group by item_no
  9  /

I have been reading ur Book and been trying these Analytic Functions myself..but
sometimes i had to use Distinct  to get correct results ---otherwise , it
doesn't work..

Can u please Clarify a bit more why r u using Distinct and then first_value
here..
Wouldn't first_value be sufficient ?

Thanks and Best Regards,

pascal
Followup:
Well, I'm actually using the ANALYTIC function and then the distinct.

We got -- for every row in T (where there are more then one row for each
ITEM_NO, BIN combination) the first_value of qty when sorted descending by date.
 That would give us 12 rows (using my example) with many duplicates.  The
distinct will remove the duplicate ITEM_NO/BIN combinations and then the sum
collapses out the BIN dimension.
Rating: 5
query June 24, 2003
Reviewer: mo
Tom:

I have this query:

ISD>  select a.request_id,a.shipment_id,b.item_number,b.stock_number,
  2   max(a.shipment_date) from shipment a, shipped_item b
  3   where a.shipment_id = b.shipment_id and b.disposition='B'
  4   group by a.request_id,a.shipment_id,stock_number,item_number;

REQUEST_ID SHIPMENT_ID ITEM_NUMBER STOCK_NUMB MAX(A.SHIPMENT_DATE)
---------- ----------- ----------- ---------- --------------------
       593        2074           2 AC010      24-jun-2003 00:00:00
       593        2074           4 BB005      24-jun-2003 00:00:00
       593        2075           2 AC010      25-jun-2003 00:00:00
       593        2075           4 BB005      25-jun-2003 00:00:00
       594        2076           1 AC010      24-jun-2003 00:00:00
       594        2076           2 BB005      24-jun-2003 00:00:00
       594        2076           3 MS193      24-jun-2003 00:00:00

7 rows selected.

What i am tring to get is the records of the shipment of the max date. so here
it would exclude the first two. how would you do this.
Followup:
select * (
select a.request_id,a.shipment_id,b.item_number,b.stock_number,
       a.shipment_date,
       max(a.shipment_date) over (partition by shipment_id) max_ship_date
  from shipment a, shipped_item b
 where a.shipment_id = b.shipment_id
   and b.disposition='B'
          )
 where shipment_date = max_ship_date
/
Rating: 5
actual query June 24, 2003
Reviewer: mo
Tom:

Please disregard previous query. here is the actual one. I have two tables:
shipment (PK is shipment_id) and shipped_item (PK is shipment_id,stock_number
and item_number). Every shipment record that has a backorder flag, I want to
read the quantity.  However if it is the same item and request id I am only
intersted in the last shipped one.  SO here the result should be so that I skip
the first and third records.

  1   select a.request_id,b.stock_number,b.backorder_quantity,
  2   a.shipment_date from shipment a, shipped_item b
  3   where a.shipment_id = b.shipment_id and b.disposition='B'
  5* order by 1,2
ISD> /

REQUEST_ID STOCK_NUMB BACKORDER_QUANTITY SHIPMENT_DATE
---------- ---------- ------------------ --------------------
       593 AC010                     190 24-jun-2003 00:00:00
       593 AC010                     145 25-jun-2003 00:00:00
       593 BB005                     380 24-jun-2003 00:00:00
       593 BB005                     300 25-jun-2003 00:00:00
       594 AC010                      90 24-jun-2003 00:00:00
       594 BB005                      50 24-jun-2003 00:00:00
       594 MS193                      50 24-jun-2003 00:00:00

7 rows selected.
Followup:
I think that is the same answer as the other one.
Rating: 5
June 25, 2003
Reviewer: mo
Tom:

Thanks a lot.  Is it any way possible using analytical functions too to get the
min(date) in the result set.  Your answer gave me what I need.  Now is it
possible to add the min(shipment_date) from the first record displayed in the
initial result set to the final result set which displays the record with
maximum(shipment_date)?
Followup:
(did you even think to try "min"?????)

just try adding a select of min(...) over (....)
Rating: 5
query June 25, 2003
Reviewer: mo
Tom:

I think you misunderstood me or maybe I misunderstood your last comment.

The max( ) over gave me the result set however I am trying to grab the lowest
ship date from the first record.  For example, for this data set, I need to grab
the record (quantity) from record highest shipment_date but then I want to grab
lowest shipment date (initial date).

REQUEST_ID STOCK_NUMB BACKORDER_QUANTITY SHIPMENT_DATE
---------- ---------- ------------------ --------------------
       593 AC010                     190 24-jun-2003 00:00:00
       593 AC010                     145 25-jun-2003 00:00:00
       593 BB005                     380 24-jun-2003 00:00:00
       593 BB005                     300 25-jun-2003 00:00:00
       594 AC010                      90 24-jun-2003 00:00:00
       594 BB005                      50 24-jun-2003 00:00:00
       594 MS193                      50 24-jun-2003 00:00:00

i WOULD GET  FOR AC010:

       593 AC010                     145 24-jun-2003
or

       593 AC010                     145 25-jun-2003  24-jun-2003
(here the min(date) is added as another column.
Followup:
failing to see the problem here, did you even TRY adding

select * (
select a.request_id,a.shipment_id,b.item_number,b.stock_number,
       a.shipment_date,
       max(a.shipment_date) over (partition by shipment_id) max_ship_date
       min(a.shipment_date) over (partition by shipment_id) min_ship_date
  from shipment a, shipped_item b
 where a.shipment_id = b.shipment_id
   and b.disposition='B'
          )
 where shipment_date = max_ship_date
/

that'll get you the MIN shipment date by shipment_id for dispositions = 'B'
You'll have max ship date, min ship date, etc etc et.
Rating: 5
Great. This solves lot of my Issues. June 27, 2003
Reviewer: A reader
Thank you Tom.
Rating: 5
sql query September 11, 2003
Reviewer: mo
Tom:

I have this query that gives me all storage codes for each stock item for
"WASHDC" warehouse.  Problem is that if a stock item is not stored anywhere in
"WASHDC" it will be excluded from the result set.  I want a master report of all
stock items that shows blank entries if a stock item does not have an entry.
ALl stock items are listed in stock_item table.

What can i do to get this "resultset" to union with all other stock items that
are not in the result set just to be displayed on the report.

select stock_number,description,
                 max(decode(seq,1,storage_code,null)) Loc#1,
                  max(decode(seq,1,qty_Available,null)) qty#1,
                  max(decode(seq,2,storage_code,null)) Loc#2,
                  max(decode(seq,2,qty_Available,null)) qty#2,
                  max(decode(seq,3,storage_code,null)) loc#3,
                  max(decode(seq,3,qty_Available,null)) qty#1,
                  max(decode(seq,4,storage_code,null)) loc#4,
                  max(decode(seq,4,qty_Available,null)) qty#4
                 from (
                select stock_number, storage_code,description,qty_available,
                row_number() over(partition by stock_number order by
stock_number nulls last) seq
               from (select a.stock_number, b.storage_code,a.description,

compute_qty_stored(b.warehouse_id,a.stock_number,b.storage_Code) qty_available
                    from stock_item a, physical_inventory b
                    where a.stock_number = b.stock_number(+)
                    and b.warehouse_id in (''WASHDC'')
                     and a.stock_number <> ''99999''
                 union all
                    select a.stock_number, b.storage_code,a.description,

compute_qty_stored(b.warehouse_id,a.stock_number,b.storage_Code) qty_available
                    from stock_item a, storage_receipt b
                    where a.stock_number = b.stock_number(+)
                     and b.warehouse_id in (''WASHDC'')
                                )
              group by stock_number,storage_Code,description,qty_available )
             where seq <=4

THanks,
Followup:
you don't need a union, you just need to outer join to a distinct set of all
stock items at the end.

take that query -- and outer join it to the distinct set of stock items.
Rating: 5
query September 11, 2003
Reviewer: mo
TOm:

1.  DO you mean doing like this :

select * from (query) a, stock_item b where a.stock_number=b.stock_number(+);

2.  or do the join in the innermost (warehouse column) query like :

select a.stock_number, b.storage_code,a.description,

compute_qty_stored(b.warehouse_id,a.stock_number,b.storage_Code) qty_available
                    from stock_item a, physical_inventory b
                    where a.stock_number = b.stock_number(+)
                    and b.warehouse_id(+) in (''WASHDC'')
                     and a.stock_number <> ''99999''

thanks
Followup:
select *
  from ( your_query ) a,
       ( query to get DISTINCT stock numbers ) b
 where a.stock#(+) = b.stock#
Rating: 3
Very Nice September 12, 2003
Reviewer: R.Chacravarthi from Chennai,India
Hello Tom,
I have a question for you regarding a query.It deals with
selecting min and max values in a query.For example:
 select ename,sal from emp where sal = (select min(sal) from
  emp ) or sal = (select max(sal) from emp);
It returns me the employees getting min and max sal values
in the table emp.My question is
  "Can this query be put using the In operator like
  select ename,sal from emp where sal in('query1','query2');" query1 deals with
min(sal) and query2
deals with max(sal).Is this possible?I tried but it throws errors.Could you
please help?
Thanks in advance.Please do reply.
P.S:) Please specify the other formats of the same query.
Followup:
ops$tkyte@ORA920>
ops$tkyte@ORA920> select ename
  2   from emp
  3   where sal in ( (select min(sal) from emp), (select max(sal) from emp) );

ENAME
----------
SMITH
KING

ops$tkyte@ORA920>
ops$tkyte@ORA920> select ename
  2   from emp
  3   where sal in (select min(sal) from emp union select max(sal) from emp);

ENAME
----------
SMITH
KING

both work
Rating: 3
what do u think of this query...still doing a table scan?? September 12, 2003
Reviewer: A reader
SQL> sELECT
  2           employees.USID,
  3           review_status,
  4           jobs.ID,
  5           jobs.STATUS,
  6           JOB_JAVA_PKG.GET_CHEMIST(jobs.ID) ASSIGNED_TO,
  7           jobs.TEST ,
  8           JOB_JAVA_PKG.get_review_status(jobs.ID) REVIEW_STATUS,
  9           jobs.CREATED_BY ,
 10           jobs.REASON_FOR_CHANGE,
 11           DECODE(STATUS,'CANCELLED',jobs.MODIFIED_BY,NULL) MODIFIED_BY
 12      FROM JOBS, job_chemists, employees, enotebook_reviews
 13          -- where jobs.id is not null AND jobs.RECORD_STATUS='CURRENT'
 14           where jobs.RECORD_STATUS='CURRENT'
 15           and jobs.id = job_fk_id
 16           and job_chemists.RECORD_STATUS='CURRENT'
 17           and enotebook_reviews.RECORD_STATUS='CURRENT'
 18           and job_chemists.employee_fk_id = employee_id
 19           and e_notebook_entity_id = jobs.id
 20           and job_fk_id = e_notebook_entity_id
 21  /

611 rows selected.

Elapsed: 00:00:06.04

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=30 Card=1 Bytes=172)
   1    0   NESTED LOOPS (Cost=30 Card=1 Bytes=172)
   2    1     HASH JOIN (Cost=29 Card=1 Bytes=155)
   3    2       TABLE ACCESS (FULL) OF 'JOBS' (Cost=11 Card=615 Bytes=
          47970)

   4    2       HASH JOIN (Cost=17 Card=709 Bytes=54593)
   5    4         TABLE ACCESS (FULL) OF 'JOB_CHEMISTS' (Cost=11 Card=
          611 Bytes=25051)

   6    4         INDEX (FAST FULL SCAN) OF 'LOU1' (NON-UNIQUE) (Cost=
          5 Card=2156 Bytes=77616)

   7    1     TABLE ACCESS (BY INDEX ROWID) OF 'EMPLOYEES' (Cost=1 Car
          d=485 Bytes=8245)

   8    7       INDEX (UNIQUE SCAN) OF 'EMPLOYEES_PK' (UNIQUE)

Statistics
----------------------------------------------------------
       1833  recursive calls
          0  db block gets
      42323  consistent gets
          0  physical reads
          0  redo size
      43153  bytes sent via SQL*Net to client
        498  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        611  rows processed
Followup:
why do you think a full scan is a bad thing??

looks awesome I guess -- unless you can show me something better or explain why
you think it is "bad"

I would guess most of the run time is in the stuff that has the word "java" in
it.
Rating: 4
Thanks!!! September 12, 2003
Reviewer: A reader
Thanks Tom,

do you think is a good idea to call a fuction like this
in side the select statement. The reason why I have it
througth my code is because I don't want to do a lot joins.
Please tell me if is a good idea to call it. Anyway,
here is the function I called get chemist from the previous
select. Any idea will be great to make it better!!!

FUNCTION get_chemist (p_job IN VARCHAR2)
      RETURN VARCHAR2
   IS
      v_emp_id   VARCHAR2 (35);
      v_emp      VARCHAR2 (50);
   BEGIN
      SELECT employee_fk_id
        INTO v_emp_id
        FROM job_chemists
       WHERE job_fk_id = TRIM (p_job) AND record_status = 'CURRENT';

      SELECT usid
        INTO v_emp
        FROM employees
       WHERE TRIM (employee_id) = TRIM (v_emp_id);

      RETURN v_emp;
   END
Followup:
umm, so you think this would be better then a join??????

it is a join -- just a really really really slow join, the slowest way to do it.

don't second guess the database engine, it is pretty good at doing what it does
-- joins!

I would not even consider calling such a function -- never in a billion years,
not a chance.

do it in 100% SQL
Rating: 4
SQL Statement to grab the next lowest value within a single row September 12, 2003
Reviewer: David
Hi Tom,

I have been trying hard to think of a way to do this with just one easy SQL
statement, as opposed to many PL/SQL code blocks.

The main issue is that we loaded data from a networked database to an Oracle 9i
database without a hitch.  During development, we discovered that in designing
the new tables prior to the load, we missed the need to add a key column in one
of the tables to establish a relationship with another.  This was a big mistake.
 Unfortunately, we do not have time now to unload and reload the data into
re-designed tables.  The population of the owner key can only be done during the
unload process, and there is simply no time left.  The only option we have is to
come up with a workable solution.  I prefer, if possible, a single SQL statement
that can retrieve the correct results.

So for example, in Table A:

SEQNO    EFFECTIVE_DATE    SERVICE_STATUS
2    20020426    ND
2    20020130    AB
2    20020129    AB
2    20011009    AB
2    20011002    AB
2    20010912    AB
2    20001028    AB
2    20001026    NP
1    20030328    AB
1    20020722    AB
1    20020720    AB
1    20020719    AB
1    20020717    NP

And in Table B:

SERIAL_NUMBER    SEQNO    EFFECTIVE_DATE
21814290378    2    20020426
21814290378    2    20020130
21814290378    2    20020129
21814290378    2    20011009
21814290378    2    20011002
21814317918    2    20010912
21814317918    2    20001028
9300134799    1    20030328
9300134799    1    20020722
9300134799    1    20020720
9300134799    1    20020719

There should be a SERIAL_NUMBER field in Table A so that we can pin-point the
EFFECTIVE_DATE.  This is the column that is missing.  Table A should look like:

SEQNO    EFFECTIVE_DATE    SERVICE_STATUS    SERIAL_NUMBER
2    20020426    ND        21814290378
2    20020130    AB        21814290378
2    20020129    AB        21814290378
2    20011009    AB        21814290378
2    20011002    AB        21814290378
2    20010912    AB        21814317918
2    20001028    AB        21814317918
2    20001026    NP
1    20030328    AB        9300134799
1    20020722    AB        9300134799
1    20020720    AB        9300134799
1    20020719    AB        9300134799
1    20020717    NP

So the way this works is that for a SEQNO, you can have multiple serial numbers.
 We need to produce a report that looks like:

SERIAL_NUMBER    SRV_BEGIN
------------------------------------
9300134799    20020717
21814290378    20011002
21814317918    20001026

The logic is this:  For each sequence number, list the effective dates of each
serial number.  The effective date should be the earliest date for that serial
number.  If the SERVICE_STATUS column in Table A is 'NP', then use the effective
date for that serial number.  If it isn't, then use the date that is in Table B.
 This seems simple, but is quite complicated.  I am hoping that  you can come up
with a single SQL statement to do this.

Thanks.

David
Followup:
isn't the logic more easily stated as:

for every row in table A where service_status <> NP, set serial_number = first
serial number for that sequence you find in table b?
Rating: 2
Whaa? September 12, 2003
Reviewer: David
Do you mean "where service_status = NP"?  How would you do it with one SQL
statement?
Followup:
no, i meant

for every row -- where the status is NOT "NP" -- update that column to the first
value for that sequence you find in the other table.

that matches your output.  is that is what you want -- just

update tablea
   set that_column = ( select that_column from tableb where tableb.seq =
tablea.seq and rownum = 1 )
where status <> 'NP';
Rating: 3
Nice September 13, 2003
Reviewer: R.Chacravarthi from Chennai,India
Thanks for your response.Sorry to disturb you again.I need a
query which returns deptno wise highest and lowest salary
getting employees.For ex. I expect the resultset to be as
follows:
 deptno  Min(sal) Minsal_ename  Max(sal)  Maxsal_ename
  10      800      Smith           5000       King
  20      1500                      ...       ...
  30      ...      ....            ....       ...
 I need a query like this?Could you please Provide a query?
Please specify different formats of the same query.
Followup:
please specify different formats?  do we get bonus points on homework for that
:)

tell me, what happens when two people (or thirty people, whatever) make the most
or least -- are we to pick on at random?

well, here are three ways -- note that the last query returns a different -- yet
equally valid -- answer due to this duplicity

ops$tkyte@ORA920> create table emp as select * from scott.emp order by ename;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select deptno,
  2         to_number(substr(xmax,1,14)) max_sal,
  3         substr(xmax,15) max_name,
  4         to_number(substr(xmin,1,14)) min_sal,
  5         substr(xmin,15) min_name
  6    from (
  7  select deptno,
  8         max( to_char(sal,'0000000000.00') || ename ) xmax,
  9         min( to_char(sal,'0000000000.00') || ename ) xmin
 10    from emp
 11   group by deptno
 12         )
 13  /

    DEPTNO    MAX_SAL MAX_NAME      MIN_SAL MIN_NAME
---------- ---------- ---------- ---------- ----------
        10       5000 KING             1300 MILLER
        20       3000 SCOTT             800 SMITH
        30       2850 BLAKE             950 JAMES

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select deptno,
  2         max(case when rn<>1 then sal else null end) max_sal,
  3         max(case when rn<>1 then ename else null end) max_ename,
  4         max(decode(rn,1,sal)) min_sal,
  5         max(decode(rn,1,ename)) min_ename
  6    from (
  7  select *
  8    from (
  9  select deptno,
 10         row_number() over ( partition by deptno order by sal ) rn,
 11         max(sal) over (partition by deptno) max_sal,
 12         ename,
 13         sal
 14    from emp
 15         )
 16   where rn = 1 or sal = max_sal
 17         )
 18   group by deptno
 19  /

    DEPTNO    MAX_SAL MAX_ENAME     MIN_SAL MIN_ENAME
---------- ---------- ---------- ---------- ----------
        10       5000 KING             1300 MILLER
        20       3000 SCOTT             800 SMITH
        30       2850 BLAKE             950 JAMES

ops$tkyte@ORA920>
ops$tkyte@ORA920> select deptno,
  2         max_sal,
  3         (select ename from emp where sal = max_sal and rownum = 1 )
max_ename,
  4         min_sal,
  5         (select ename from emp where sal = min_sal and rownum = 1 )
min_ename
  6    from (
  7  select deptno, min(sal) min_sal, max(sal) max_sal
  8    from emp
  9   group by deptno
 10         )
 11  /

    DEPTNO    MAX_SAL MAX_ENAME     MIN_SAL MIN_ENAME
---------- ---------- ---------- ---------- ----------
        10       5000 KING             1300 MILLER
        20       3000 FORD              800 SMITH
        30       2850 BLAKE             950 JAMES
Rating: 3
Excellent September 13, 2003
Reviewer: A reader from Cincinatti,USA
How to reverse the rows of a table?i.e.starting from the last row and proceeding
upwards.Can this be done in sql?I
tried in pl/sql using index by tables with attributes last
and prior.It works fine.Please show me a way how to do it
sql.
Thanks in advance.
Bye!
Followup:
there is no such thing as the "last row"...

order by DESC

and

order by ASC

are the only ways to have "first" and "last" rows.  so, if you want the "last
row" first and were using order by asc, use desc
Rating: 2
Ok September 15, 2003
Reviewer: Peter from Boulder,USA
Dear Tom,
Read your response.One of my Oracle University Tutors said
the following information."A row in the table can be the last row if it has the
maximum rowid and a row can be the
first if it has the minimum rowid".Is this correct information or misleading
one?I have a question also for you
and kindly bear with us.It is
 select * from emp where &N = (select ....);
 Keep the data as it is (i.e keeping data in the order of row insertion ) and
suppose if I enter "1" for N it should return the first row and if I enter "5"
it should return the fifth row.Is this possible in
sql?Please do reply.
Thanks in advance.
Followup:
it is incorrect.

first, what does it mean to be "first" or "last"??????? nothing, nothing at all
- not in SQL, not in relational databases.  But, anyway, let me demonstrate:

borrowing the example from:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4423420997870
I'll reuse it here:

ops$tkyte@ORA920> create table t ( x int primary key, a char(2000), b
char(2000), c char(2000), d char(2000), e char(2000) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t(x) values ( 1 );
1 row created.

ops$tkyte@ORA920> insert into t(x) values ( 2 );
1 row created.

ops$tkyte@ORA920> insert into t(x) values ( 3 );
1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> commit;
Commit complete.

ops$tkyte@ORA920> column a noprint
ops$tkyte@ORA920> column b noprint
ops$tkyte@ORA920> column c noprint
ops$tkyte@ORA920> column d noprint
ops$tkyte@ORA920> column e noprint
ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t;

         X
----------
         1
         2
         3

so, data comes out in the order it is inserted -- or not???  the "last row" is
3, first row is "1" right?  or is it??

ops$tkyte@ORA920>
ops$tkyte@ORA920> update t set a = 'x', b = 'x', c = 'x' where x = 3;

1 row updated.

ops$tkyte@ORA920>
ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> update t set a = 'x', b = 'x', c = 'x' where x = 2;

1 row updated.

ops$tkyte@ORA920>
ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> update t set a = 'x', b = 'x', c = 'x' where x = 1;

1 row updated.

ops$tkyte@ORA920>
ops$tkyte@ORA920> commit;

Commit complete.

just some updates right -- but carefully planned ones:

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t;

         X
----------
         3
         2
         1

now 3 is the "first" row and 1 is the "last row" but look at the rowids

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select x, rowid, min(rowid) over (), max(rowid) over (),
  2         decode( rowid, min(rowid) over (), 'min rowid' ),
  3         decode( rowid, max(rowid) over (), 'max rowid' )
  4    from t;

  X ROWID              MIN(ROWID)OVER()   MAX(ROWID)OVER()   DECODE(RO DECODE(RO
--- ------------------ ------------------ ------------------ --------- ---------
  3 AAAJ49AALAAAQaiAAC AAAJ49AALAAAQaiAAA AAAJ49AALAAAQaiAAC           max rowid
  2 AAAJ49AALAAAQaiAAB AAAJ49AALAAAQaiAAA AAAJ49AALAAAQaiAAC
  1 AAAJ49AALAAAQaiAAA AAAJ49AALAAAQaiAAA AAAJ49AALAAAQaiAAC min rowid

3 has max rowid, 1 has min rowid but -- 3 is first and 1 is last!!!

so, that shows the "maximum rowid theory" is false.....

As for the last question -- hopefully you see now that there is NO SUCH THING as
the n'th row in a relation database (unless you yourself number them!)

You can get the n'th row from a result set -- but the n'th row can and will
change over time.  Only by numbering the rows yourself and using order by can
you get the same n'th row time after time after time

select *
  from ( select a.*, rownum r
           from ( query-to-define-your-result-set ) a
          where rownum <= &n )
 where r = &n;

gets the n'th row from a result set (NOT a table, a result set)
Rating: 3
Problem with SQL Query September 15, 2003
Reviewer: Raghu
Hi TOm,

I don't understand the reason behind this behaviour.

SQL> desc dept
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 DEPTNO                                             NUMBER(2)
 DNAME                                              VARCHAR2(14)
 LOC                                                VARCHAR2(13)

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 * from dept
  2  where dname in
  3  (select dname from emp);

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 ACCOUNTING     NEW YORK
        20 RESEARCH       DALLAS
        30 SALES          CHICAGO
        40 OPERATIONS     BOSTON

DNAME column is missing in the emp table but still it does not give any error.
Followup:
that query is the same as:

select * from dept
where DEPT.dname in ( select DEPT.dname from emp );

you just did a correlated subquery.  as long as EMP has at least one row -- that
query is the same as:

select * from dept
where DEPT.dname is not null;

it is the defined, expected behaviour.
Rating: 4
Thanks!!! September 15, 2003
Reviewer: A reader
Thanks Tom,

do you think is a good idea to call a fuction like this
in side the select statement. The reason why I have it
througth my code is because I don't want to do a lot joins.
Please tell me if is a good idea to call it. Anyway,
here is the function I called get chemist from the previous
select. Any idea will be great to make it better!!!

FUNCTION get_chemist (p_job IN VARCHAR2)
      RETURN VARCHAR2
   IS
      v_emp_id   VARCHAR2 (35);
      v_emp      VARCHAR2 (50);
   BEGIN
      SELECT employee_fk_id
        INTO v_emp_id
        FROM job_chemists
       WHERE job_fk_id = TRIM (p_job) AND record_status = 'CURRENT';

      SELECT usid
        INTO v_emp
        FROM employees
       WHERE TRIM (employee_id) = TRIM (v_emp_id);

      RETURN v_emp;
   END
Followup:
it is a horrible idea.

joins are not evil.

calling plsql (or any function) from sql when you don't need to is.

joins are not evil.

databases were BORN to join

joins are not evil.

you are doing a join yourself -- that can only be slower then letting the
datbase do what it was programmed to do!

joins are NOT evil.

don't do this (and that abuse of trim() -- on a database column!! precluding
indexes in most cases!  you want to rethink that -- why would you need to trim()
??  that would indicate bad data -- something you want to fix on the ingest
process, not on the way out!
Rating: 5
simple sql question September 16, 2003
Reviewer: A reader
Hi Tom
Here is the test case:
9:25:11 test@ORA901> @test1
09:25:14 test@ORA901> drop table t1;

Table dropped.

09:25:14 test@ORA901> drop table t2;

Table dropped.

09:25:14 test@ORA901> create table t1 ( x int , y varchar2(30) );

Table created.

09:25:14 test@ORA901> create table t2 ( x int , y varchar2(30) );

Table created.

09:25:14 test@ORA901> insert into t1 values ( 1, 'xx' );

1 row created.

09:25:14 test@ORA901> insert into t1 values ( 2, 'xx' );

1 row created.

09:25:14 test@ORA901> select t1.x , t1.y from t1, t2
09:25:14   2  where not exists ( select null from t2 where t2.x = t1.x );

no rows selected

09:25:14 test@ORA901>
09:25:14 test@ORA901> insert into t2 values ( 1, 'xx' );

1 row created.

09:25:14 test@ORA901> select t1.x, t1.y from t1, t2
09:25:14   2  where not exists ( select null from t2 where t2.x = t1.x );

         2 xx

My question is:
Why in the first case we get "no rows selected" - I would
have expected all the rows of t1. When I have at least one row in t2 (matching
or not), then I get the desired result.
Basically, I want to select t1's values that don't exist
in t2 - . If t2 is empty I want to get all rows of t1.

Thank you!!!!
Followup:
why are you cartesian product'ing the tables together?

the query should be:

ops$tkyte@ORA920> select t1.x , t1.y from t1
  2  where not exists ( select null from t2 where t2.x = t1.x );

         X Y
---------- ------------------------------
         1 xx
         2 xx

that is what the query should be -- when you join to an empty table -- you get
no rows.
Rating: 5
thanx tom!! September 16, 2003
Reviewer: A reader
It was a stupid mistake - thanx for correcting me!
Rating: 3
Good September 18, 2003
Reviewer: Ferdinand Maer from CT,USA
Dear Sir,
Suppose if I have a string like "Metalink " ,How can I insert a blank space
between each character so that string
appears as "M e t a l i n k"?Is a query possible here?
Please do reply.
Thanks
Maer
Followup:
procedurally -- sure, its easy.  this would be one where I would write a small
plsql function to do it.
Rating: 2
OK September 18, 2003
Reviewer: Ferdinand Maer from CT,USA
Thanks for your reply.Please provide some hints to do the
coding.That's where we need your help.
Bye!
 with regards,
 Maer
Followup:
really?  seems pretty straight forward...

ops$tkyte@ORA920> create or replace function spacer( p_string in varchar2 )
return varchar2
  2  as
  3          l_string varchar2(4000);
  4  begin
  5          for i in 1 .. length(p_string)
  6          loop
  7                  l_string := l_string || substr(p_string,i,1) || ' ';
  8          end loop;
  9          return l_string;
 10  end;
 11  /

Function created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> column ename format a20
ops$tkyte@ORA920>
ops$tkyte@ORA920> select ename, spacer(ename) ename from emp;

ENAME                ENAME
-------------------- --------------------
ADAMS                A D A M S
ALLEN                A L L E N
BLAKE                B L A K E
CLARK                C L A R K
FORD                 F O R D
JAMES                J A M E S
JONES                J O N E S
KING                 K I N G
MARTIN               M A R T I N
MILLER               M I L L E R
SCOTT                S C O T T
SMITH                S M I T H
TURNER               T U R N E R
WARD                 W A R D

14 rows selected.
Rating: 5
query January 10, 2004
Reviewer: mo
Tom:

I would like to ask you about the best way to accomplish this query. It is used
to generate a report.

I have 5 tables: Library table stores all libraries.  Request table stores all
requests submitted by libraries.  Requested_Items stores all individual items
requested.  Shipment table stores all shipments sent to libraries. Shipped_Item
stores all shipped items sent to libraries.

Library (LibCode,Name)
Request(Request_ID,LibCode)
Requested_Items(Request_ID,Item_no,Material_ID)
Shipment(Shipment_ID,Request_ID,LibCode)
Shipped_Item(Shipment_ID,Item_no,material_id)

Physical relationships are as follows:
Request table is child table to parent table Library.
Requested_Items table is a child table to parent table Request.
Shipment table is child table to parent table Request.
Shipped_Item is child table to parent table Shipment.

I want to generate a columnar report as follows:

Library        Total        Total            Total        Total
             Request    Request Items        Shipments    Shipped Items

Here is how I want to do it. Simple way but may be inefficient due to the
several table scans.

For x in (select libcode from library)
Loop
    Select count(*) into tot_request from request where libcode = x.libcode;
    Select count(*) into tot_request_item from requested_items where request_id
in (select request_id from request where libcode =  x.libcode);
   Select count(*) into tot_shipment from shipment where libcode = x.libcode;
  Select count(*) into tot_shipped_item from shipped_items where shipment_id in
(select shipment_id from shipment where libcode = x.libcode);
 Htp.p(tot_request);
Htp.p(tot_request_item);
Htp.p(tot_shipment);
Htp.p(tot_shipped_item);
END LOOP;

Is this an efficient way of doing it or not.  If not how would you do it?
Followup:
select l.libcode, r.cnt1, r.cnt2, s.cnt1, s.cnt2
  from library L,
       (select request.libcode,
               count(distinct request.request_id) cnt1,
               count(*) cnt2
          from request, requested_items
         where request.request_id = requested_items.request_id(+)
         group by request.libcode ) R,
       (select shipment.libcode,
               count(distinct shipment.shipment_id) cnt1,
               count(*) cnt2
          from shipment, shipped_items
         where shipment.shipment_id = shipped_items.shipment_id(+)
         group by shipment.libcode ) S
  where l.libcode = r.libcode(+)
    and l.libcode = s.libcode(+)

ONLY use the (+) if necessary.  If the relations are not "optional" (eg:
every libcode is in request and shipment that is in library and every row in
request has at least a row in requested_items and every row in shipment has a
row in shipped_items -- remove them -- remove as many as you can)

that gets them all in one statement.
Rating: 2
SQL Query January 10, 2004
Reviewer: Natasha from UAE
Tom,

I have a query pls. have a look at that

Query :
Select * From <Tab> Where Session_ID = <Session_Number>
point_of_sale, origin,destination,decode(category,'Y',1,'F',2,'J',3),
fare_id,rid,mfid,mrid.

Result Set:

MRBD    BKGFRM    BKGTO    DEPFRM    DEPTO    FID    RID    MFID    MRID

M    3/5/03    6/15/03    4/1/03    6/15/03    149    154    146    5427

M    3/5/03    6/19/03    6/16/03    6/19/03    149    154    147    5430

H    3/5/03    6/19/03    4/1/03    6/19/03    149    154    12364    16149

M    3/5/03    7/15/03    6/20/03    7/15/03    150    155    147    5430

M    3/5/03    7/31/03    7/16/03    7/31/03    150    155    3492    5432

H    3/5/03    7/31/03    6/20/03    7/31/03    150    155    12364    16149

M    3/5/03    8/31/03    8/16/03    8/31/03    151    8351    3491    5429

M    3/5/03    8/15/03    8/1/03    8/15/03    151    8351    3492    5432

H    3/5/03    8/31/03    8/1/03    8/31/03    151    8351    12364    16149

M    3/5/03    12/9/03    12/1/03    12/9/03    152    8353    149    160

M    3/5/03    12/25/0312/10/0312/25/03152    8353    149    160
M    3/5/03    3/31/04    1/1/04    3/31/04    152    8353    150    161

M    3/5/03    12/31/0312/26/0312/31/03152    8353    150    161
M    3/5/03    11/19/039/1/03    11/19/03152    8353    3491    5429
M    3/5/03    11/30/0311/20/0311/30/03152    8353    3491    5429
H    3/5/03    12/9/03    12/1/03    12/9/03    152    8353    12364    16149

H    3/5/03    11/19/039/1/03    11/19/03152    8353    12364    16149

H    3/5/03    11/30/0311/20/0311/30/03152    8353    12364    16149
H    3/5/03    3/31/04    1/1/04    3/31/04    152    8353    12364    16149

H    3/5/03    12/31/0312/10/0312/31/03152    8353    12364    16149
M    3/5/03    8/31/03    8/16/03    8/31/03    4888    8394    3491    5429

M    3/5/03    8/15/03    8/1/03    8/15/03    4888    8394    3492    5432

H    3/5/03    8/31/03    8/1/03    8/31/03    4888    8394    12364    16149

M    3/5/03    12/9/03    12/1/03    12/9/03    4889    8395    149    160

M    3/5/03    12/25/0312/10/0312/25/034889    8395    149    160
M    3/5/03    3/31/04    1/1/04    3/31/04    4889    8395    150    161

M    3/5/03    12/31/0312/26/0312/31/03    4889    8395    150    161

M    3/5/03    11/19/039/1/03    11/19/03    4889    8395    3491    5429

M    3/5/03    11/30/0311/20/0311/30/03    4889    8395    3491    5429

H    3/5/03    12/9/03    12/1/03    12/9/03    4889    8395    12364    16149

H    3/5/03    11/19/039/1/03    11/19/03    4889    8395    12364    16149

H    3/5/03    11/30/0311/20/0311/30/03    4889    8395    12364    16149

H    3/5/03    3/31/04    1/1/04    3/31/04    4889    8395    12364    16149

H    3/5/03    12/31/0312/10/0312/31/03    4889    8395    12364    16149

M    3/5/03    6/15/03    4/1/03    6/15/03    15682    23867    146    5427

M    3/5/03    7/15/03    6/16/03    7/15/03    15682    23867    147    5430

M    3/5/03    12/25/0312/1/03    12/25/03    15682    23867    149    160

M    3/5/03    3/31/04    12/26/03    3/31/04    15682    23867    150    161

M    3/5/03    11/30/038/16/03    11/30/03    15682    23867    3491    5429

M    3/5/03    8/15/03    7/16/03    8/15/03    15682    23867    3492    5432

H    3/1/03    3/31/04    3/1/03    3/31/04    15682    23867    12364    16149

H    4/1/07    3/31/08    4/1/07    3/31/08    15756    23990    12454    16232

What I am looking for :

Tom, If you check FID, and their Departure From & Departure To dates...there
should be continuity. I mean the sequence of date breaks.

For Ex :-

FID    DEPFRM        DEPTO        MRBD
149    01/04/2003    15/06/2003    M
149    16/06/2003    19/06/2003    M
149    01/04/2003    19/06/2003    H

This is the perfect output according to the date break sequence. But When you
check FID '151', the date breaks are improper.

For Ex:-

FID    DEPFRM        DEPTO        MRBD
151    16/08/2003    31/08/2003    M
151    01/08/2003    15/08/2003    M
151    01/08/2003    31/08/2003    M

So the output should come as

FID    DEPFRM        DEPTO        MRBD
151    01/08/2003    15/08/2003    M
151    16/08/2003    31/08/2003    M
151    01/08/2003    31/08/2003    M

Pls. ensure that the date sequence should come for all the FID's.

I would appreciate, if you can answer my query.

thxs in advance.
Followup:
er?  sorry, don't understand what you might be looking for here.  don't know if
you are looking for a constraint to avoid overlaps, a query to filter them out,
whatever.

(example could be lots smaller too I think)
Rating: 1
Query to filter January 11, 2004
Reviewer: Natasha from UAE
Tom,

I need a query to filter the records according to the above examples I had
enclosed.

thxs in advance.
Followup:
don't get it.  filter what like what?

looks like "where" with "order by" to me.
Rating: 3
RE: Query to filter (Natasha) January 11, 2004
Reviewer: Marcio from br
<quote>
For Ex :-

FID    DEPFRM        DEPTO        MRBD
149    01/04/2003    15/06/2003    M
149    16/06/2003    19/06/2003    M
149    01/04/2003    19/06/2003    H

This is the perfect output according to the date break sequence. But When you
check FID '151', the date breaks are improper.

For Ex:-

FID    DEPFRM        DEPTO        MRBD
151    16/08/2003    31/08/2003    M
151    01/08/2003    15/08/2003    M
151    01/08/2003    31/08/2003    M

So the output should come as

FID    DEPFRM        DEPTO        MRBD
151    01/08/2003    15/08/2003    M
151    16/08/2003    31/08/2003    M
151    01/08/2003    31/08/2003    M
</quote>
PMJI, but you don't show the query, like Tom said -- I used just order by depto
and figured it out.
BTW: you have a 'H' on MRBD for 151, 01/08/2003, 31/08/2003.

ops$marcio@MARCI9I1>  select fid, depfrm, depto, mrbd
  2    from t
  3   where fid=151
  4*  order by depto
ops$marcio@MARCI9I1> /

       FID DEPFRM     DEPTO      M
---------- ---------- ---------- -
       151 01/08/2003 15/08/2003 M
       151 16/08/2003 31/08/2003 M
       151 01/08/2003 31/08/2003 H

regards,
Rating: 5
query February 26, 2004
Reviewer: mo
Tom:

1.  If I have two tables one for Requests (Orders) and one for shipments linked
by request_id and each table has a child table for items. Is it possible to run
a sql statement to get count of items in each order (requested and shipped).

Requests
(request_id  number(10),
 request_date date);

Requested_items
(request_id  number(10),
item_number  number(5),
stock_number  varcahr2(10),
quantity      number(8))

Shipment
(shipment_id  number(10),
request_id    number(10),
shipment_date  date)

Shipped_items
(shipment_id  number(10),
 item_number  number(10),
 stock_number  varchar2(10),
 quantity      number )

What i want is count the items requested versus items shipped and the quantities
for each request.  One request can have multiple shipments.

Request ID       Tot req Items  Tot req Qty
Tot ship Items  Tot Qty shipped

100    5   3    1000     800

Thank you

Thank you,
Followup:
aggregate everything up to the same level, just like we did above a while back.

use inline views, get things aggregated to the request_id level and join them.
Rating: 5
February 27, 2004
Reviewer: reader from USA
You give 3 way to get result. For performance, which one is better if we have a
big table?
Followup:
they are in relative order in the answer.
Rating: 5
QUESTION February 28, 2004
Reviewer: PRS from USA
Hi Tom,
   We have oracle 9.2.0.4 installed on solaris 9.
   I have 2000 oracle connections to the database at a
   given point of time. Means user may be doing something
   or they are idle.

   My question is how do I find out the number of
   concurrent users at the database level at a given second
   always doing something. We just want to find out the
   number of concurrent users on the database.
   Any help is appreciated.
Thanks,
PRS
Followup:
concurrently "active" or concurrent users.

select status, count(*) from v$session group by status

will in fact answer both - you'll see "inactive", "active" and perhas other
status depending on your configuration.  add them up and that is the number of
concurrent users.  if you are just interested in concurrent AND active users --
just use that number.
Rating: 5
query March 31, 2004
Reviewer: mo
Tom:

How would you do this query:

Requests
(request_id  number(10),
 request_date date);

Requested_items
(request_id  number(10),
item_number  number(5),
stock_number  varcahr2(10),
quantity      number(8))

Shipment
(shipment_id  number(10),
request_id    number(10),
shipment_date  date)

Shipped_items
(shipment_id  number(10),
 stock_number  varchar2(10),
 quantity      number,
 code      varchar2(1) )

I want to get a count of the backorder items that have been fullfilled in later
shipments.

Initially when items shipped they get a code of "A" if shipped in full or "B" if
it is on backorder. Later, if the backorder item was shipped it will get a code
of "A".

Let us say I have this in shipments(shipment_id,request_id,date)

100,1,04/01/04
105,2,04/05/04

and in shipped_items(shipment_id,stock_no,qty,code)

100,AC002,10,A
100,AC006,5,B
101,AC006,5,A (here backorder for request 1(item2) gets filled)
105,BB005,6,A
105,BB006,10,B (here backorder for request 2(item2) stays open).

When I run the query i should get a count of 1 since only 1 item has been marked
with A and then  (same item) with B.
Followup:
no idea how your tables relate to one another or how you are tracking data --
seems very strange to have shipped items table set up that way.  shipment id 101
doesn't see to go anywhere.

if that 101 was really supposed to be 100 then a simple

select shipment_id, stock_number
  from shipped_items
 group by shipment_id, stock_number
having count(distinct code) = 2

gets all of the rows that you then probably want to count

select count(*)
  from (
select shipment_id, stock_number
  from shipped_items
 group by shipment_id, stock_number
having count(distinct code) = 2
)
Rating: 5
query March 31, 2004
Reviewer: mo
No I mean 101 however I forgot to include it in the shipment table. One Request
can have many shipments.

Request and shipments are linked ussing Request_ID. Shipment and Shipped_items
are linked using shipment_id.

You gave me the idea though?

select count(*)
  from (
select a.request_id,b.stock_number,b.code
  from shipment a, shipped_items b
   where a.shipment_id = b.shipment_id
 group by request_id, stock_number
having count(distinct code) = 2
)

Thank you,


Listing

Recent

Archives

Advanced Search

FAQ by Category

Most Popular

Hot Articles

Your Questions

[ Listing ]

RSS Feed

Subject

Article

Last Updated (East coast time)

Redo Log switches

article only

Apr 01, 2004 09:37 am

default users

article only

Apr 01, 2004 09:38 am

what is a meaning of 'WEB ENABLED APPLICATION'

article only

Apr 01, 2004 09:39 am

Index Organised Tables Vs Ordinary Tables

article only

Apr 01, 2004 09:47 am

Query

article only

Apr 01, 2004 10:03 am

Bind Variables

article only

Apr 01, 2004 10:24 am

NOT IN in SQL

article only

Apr 01, 2004 10:32 am

Shared Collection Across Sessions

article only

Apr 01, 2004 10:36 am

Materialized View

article only

Apr 01, 2004 10:48 am

open cursors

article only

Apr 01, 2004 10:53 am

Selecting rows that have changed via timestamp problematic with non-blocking reads

article only

Apr 01, 2004 11:21 am

BULK COLLECT fetch is not faster

article only

Apr 01, 2004 11:24 am

design the table

article only

Apr 01, 2004 11:56 am

Loading a master detail file using external tables and multi-table insert.

article only

Apr 01, 2004 12:01 pm

Difference between automatic vs manual PGA setting

article only

Apr 01, 2004 12:08 pm

database links

article only

Apr 01, 2004 01:02 pm

Statspack report

article only

Apr 01, 2004 01:03 pm

global database name

article only

Apr 01, 2004 01:06 pm

Regular Delete vs. Bulk Delete

article only

Apr 02, 2004 09:34 am

DB Security and Roles

article only

Apr 02, 2004 09:35 am

alternative approach to passing a java resultset to plsql as a ref cursor

article only

Apr 02, 2004 09:36 am

extracting data to a flat file.

article only

Apr 02, 2004 09:37 am

synonyms

article only

Apr 02, 2004 09:44 am

what kind of analyze is best

article only

Apr 02, 2004 09:52 am

questions about rollbacks/snapshot too old

article only

Apr 02, 2004 10:02 am

Names for Database Links

article only

Apr 02, 2004 10:03 am

Procedures, roles and grants

article only

Apr 02, 2004 10:04 am

Evaluate Expression

article only

Apr 02, 2004 10:07 am

CONNECT BY CLAUSE

article only

Apr 02, 2004 10:16 am

nls_date_format

article only

Apr 02, 2004 10:20 am

How to drop a datafile ?

article only

Apr 02, 2004 10:21 am

Log buffer size tuning

article only

Apr 02, 2004 10:24 am

database sessions -- seeing their sql.

article only

Apr 02, 2004 10:26 am

Converting Longs to LOBS

article only

Apr 02, 2004 01:29 pm

Hierarchial query with cumulative factor

article only

Apr 02, 2004 01:43 pm

Merge - oracle 9i

article only

Apr 02, 2004 01:47 pm

Separating comma separated words

article only

Apr 02, 2004 01:58 pm

Slow processing on database using merge

article only

Apr 02, 2004 03:03 pm

Materialized View

article only

Apr 02, 2004 03:06 pm

getting rows N through M of a result set

article only

Apr 02, 2004 03:21 pm

Pivot Query

article only

Apr 02, 2004 03:41 pm

What is the best way to pass result sets between procedures (remote)

article only

Apr 02, 2004 04:07 pm

dense_rank vs. rank

article only

Apr 02, 2004 07:23 pm

tkprof report

article only

Apr 02, 2004 07:24 pm

Intermedia text and indexing

article only

Apr 03, 2004 08:48 am

Help on SQL Stmt

article only

Apr 03, 2004 08:49 am

Show_Space Procedure Usage Problem

article only

Apr 03, 2004 08:51 am

The New Ask Tom

article only

Apr 03, 2004 08:52 am

Java procedure for host calls on Unix environment

article only

Apr 03, 2004 08:53 am

When do blocks go on the free list

article only

Apr 04, 2004 08:40 am

rman question

article only

Apr 05, 2004 09:23 am

Validate sets of "consecutive" data entry sets (no overlaps).

article only

Apr 05, 2004 09:27 am

HTML DB

article only

Apr 05, 2004 09:35 am

Copying datafiles between different operating systems

article only

Apr 05, 2004 09:36 am

how to extract BLOB data back out again

article only

Apr 05, 2004 09:36 am

How to identify connection/server errors when sending email using utl_smtp?

article only

Apr 05, 2004 09:38 am

Exporting from 8.0.4 then importing in to 8.1.5

article only

Apr 05, 2004 09:40 am

Vacant Key Query

article only

Apr 05, 2004 09:42 am

How do I pass a CLOB value to a stored procedure?

article only

Apr 05, 2004 09:44 am

Performance poor after analyzeing table

article only

Apr 05, 2004 09:48 am

Recovery Manager ; Backup/Restore Issue

article only

Apr 05, 2004 09:50 am

difference between our db_block_buffers and db_cache_size

article only

Apr 05, 2004 09:52 am

10046 and tkprof

article only

Apr 05, 2004 09:54 am

using aggregate functions

article only

Apr 05, 2004 09:58 am

How to track logon & logout ...

article only

Apr 05, 2004 12:00 pm

opinion about new auto segment management

article only

Apr 05, 2004 12:02 pm

practice backup recovery and have a question

article only

Apr 05, 2004 12:04 pm

DECODE or NVL

article only

Apr 05, 2004 05:00 pm

Truly complex pivot

article only

Apr 05, 2004 05:09 pm

alter database set time_zone doesn't work

article only

Apr 05, 2004 05:16 pm

Numeric Or Value Error

article only

Apr 05, 2004 05:21 pm

Some clarifications...

article only

Apr 05, 2004 05:21 pm

Prioritizing processes differently in MTS

article only

Apr 05, 2004 05:22 pm

Creating a table using Dynamic SQL in stored procedure get error ORA-00942

article only

Apr 05, 2004 05:22 pm

dynamic sql in pl*sql in V8

article only

Apr 05, 2004 05:26 pm

Inserting a CLOB data

article only

Apr 05, 2004 05:28 pm

Why there is so much logging for "insert /+* append */ into tab select"?

article only

Apr 05, 2004 05:37 pm

Importaince of coding in begin block - reg

article only

Apr 05, 2004 08:38 pm

grand total

article only

Apr 06, 2004 08:59 am

Passing an ARRAY from Java to PL/SQL

article only

Apr 06, 2004 09:00 am

9i and SQL Server 2000

article only

Apr 06, 2004 09:02 am

db cache advice

article only

Apr 06, 2004 09:08 am

Statspack: Who waits on log file sync and LGWR wait for redo copy events?

article only

Apr 06, 2004 09:10 am

sqlplus hangs and takes for ever to do this

article only

Apr 06, 2004 09:15 am

Finding host name or connect string after database login.

article only

Apr 06, 2004 09:21 am

BEGIN anonymous; END;

article only

Apr 06, 2004 09:30 am

Resource Manager

article only

Apr 06, 2004 09:33 am

Creating test environment from prodution box.

article only

Apr 06, 2004 09:52 am

Adding Custom keywords to intermedia

article only

Apr 06, 2004 09:54 am

row(s) 1 - 89 of 89

April 01, 2004 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://download-west.oracle.com/docs/cd/A91202_01/901_doc/server.901/a90117/onlinere.htm#9454
Rating: 4
How to avoid unnecessary switching January 27, 2004
Reviewer: Dwayne Croteau from Calgary, Alberta
Hi Tom,

I am working on the same issue of trying to keep some standby databases
syncronized within 10 minutes of their primaries.  Some of our primary databases
go through extended periods of reduced activity/inactivity. Ideally, I would
like to switch logs only if there has been some activity.  Is there a way to
measure if there has been activity?  I was looking at the v$instance_recovery
view, but the value of actual_redo_blks does not seem to reflect what I
understand it to mean (i.e. amount of redo required for recovery).  Here's an
example:

sys@OMST> @v_instance_recovery

        RECOVERY           ACTUAL           TARGET
          EST IO      REDO BLOCKS      REDO BLOCKS
---------------- ---------------- ----------------
             370            9,975            9,999

sys@OMST> alter system switch logfile;

System altered.

sys@OMST> @v_instance_recovery

        RECOVERY           ACTUAL           TARGET
          EST IO      REDO BLOCKS      REDO BLOCKS
---------------- ---------------- ----------------
             370            9,977            9,999

I would expect that the "current actual number of redo blocks required for
recovery" would be 0 since the logswitch forced a checkpoint which would ensure
all changed buffers are written to the datafiles.

sys@OMST> alter system checkpoint;

System altered.

sys@OMST> @v_instance_recovery

        RECOVERY           ACTUAL           TARGET
          EST IO      REDO BLOCKS      REDO BLOCKS
---------------- ---------------- ----------------
              30            9,856            9,999

Nor did a checkpoint affect the number of actual redo blocks.

Is there another way to accurately measure current activity?  The database
version is 8.1.7.4

Thanks in advance,
Followup:
in 8i, no, you'd have to write a procedure that sort of remembered what the last
redo generated was, how much was generated in between and decide if you want to
switch or not.

In 9i, you can set archive_lag_target in the init.ora
Rating: 5
Redo log problem March 31, 2004
Reviewer: atul from India
Hi,

I had a situation for which i want some inputs from you.

I have 3 redo log groups with one member each,And i am in
archive log mode.

One of my redo log with status = "CURRENT" has been deleted.
Physical file has been deleted,and Database went down.

When i up the database in mount stage and gave command
"ALTER DATABASE CLEAR UNARCHIVED LOGFILE '/u06/oradata/mysid/redo_1a.log'"..

It gave error as unable to find file ..
Also if i try to drop the group it says you can't drop current group.

Kindly could you help me to come-out of this situation without doing incomplete
recovery..

Thank,
Atul
Followup:
one member each!!!  oh no.  thats a disaster (not waiting to happen, but
happened)

You may well be in an incomplete recovery scenario, pretty sure you are
actually.  Please contact support for your options.

Think about it.  The redo log has changes for committed transactions that were
NOT written to disk (to the datafiles).  Some of them might be written to disk
and some UNCOMMITTED transactions might have been written as well and the UNDO
we need to roll it back would have been protected by the redo that no longer
exists.

You are in an incomplete recovery situation almost certainly.
April 01, 2004 Reddi -- Thanks for the question regarding "default users", version 8.1.7
You Asked
When I do a search on dba_users, I see a long list of users( a couple of them
which I created), actually how many of them do I actually need to run a
database.

and who are the * marked users( i have put a * against these user names).

SQL> select username from dba_users;

USERNAME
------------------------------
SYSTEM
OUTLN*
AURORA$JIS$UTILITY$ *
AURORA$ORB$UNAUTHENTICATED*
ORDPLUGINS*
CTXSYS*
ADAMS
CLARK
TEST
MIKE

USERNAME
------------------------------

MTSSYS*
BLAKE
JONES
SCOTT
MDSYS
ORDSYS*
OSE$HTTP$ADMIN*
DBSNMP*
and we said...
adams, blake, clar, jones should be dropped.  They are demo accounts. if you
installed demos, they got installed.

outln -- for the Query Plan stability feature.  Should have the password
altered.

AURORA$JIS$UTILITY$
AURORA$ORB$UNAUTHENTICATED is an internal account used by the builtin java
capabilities.

ctxsys is the interMedia text owner.  It owns the context packages.  You should
change its password.

mdsys is the spatial (part of interMedia) owner, like ctxsys you should change
the password.

ordplugins and ordsys -- same as mdsys.  part of intermedia, generic stuff used
by them all.

dbsnmp is part of the Oracle intelligent agent.  If you change this, pick up
<Note:70174.1> from support first.  It goes through the steps for doing this and

what needs to be updated.

mtssys - account for microsoft transaction server support.

tracesvr - used for Oracle trace.  This password should be changed.  If you are
using Oracle trace, remember to update it as well.

OSE$HTTP$ADMIN -- oracle servlet engine http administrative account.
Review & Followup

Rating: 4
May 14, 2001
Reviewer: A reader
Rating: 5
All documentation should read this clearly July 31, 2001
Reviewer: Rob Kellington from Edmonton, Alberta
Great!
Rating: 5
Clear and concise December 18, 2001
Reviewer: A reader
Thank you for the clear and concise information provided.
Rating: 5
One more question on this November 29, 2002
Reviewer: Tajana from Austria, Vienna
Thanks, I searched for this matter ...

To complete the list of users: There are also
WKSYS
HR,OE,PM,SH
OLAPDBA,OLAPSVR,OLAPSYS
OSE$HTTP$ADMIN
QS,QS_ADM,QS_CB,QS_CBADM,QS_CS,QS_ES,QS_OS,QS_WS
PORTAL30,PORTAL30_DEMO,PORTAL30_PUBLIC,PORTAL30_SSO,PORTAL30_SSO_PS,PORTAL30_SSO_
PUBLIC

What are those users all for ? Some of them seem to contain example/demo data
(HR,OE,PM,SH).

What about tablespaces EXAMPLE, CWMLITE, DRSYS ?

Which of that users and data (=> tablespaces) can I remove from a production DB
?
Followup:
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5796841276215
(found by searching for some of the names:
QS_ADM,QS_CB,QS_CBADM,QS_CS,QS_ES,QS_OS
on this site.  contains pointers to the DOC that tells you what these are.
hence a search on OTN in the 9.2 /whatever doc set would have found the same)
Rating: 3
SYSTEM & SYS USERS March 31, 2004
Reviewer: Jayesh from Chennai, India
What is the purpose of having two different account SYS and SYSTEM in oracle
with both having DBA roles? Is there any special reason for using SYSTEM account
in Oracle? Which scenario is it helpful?
Followup:
the SYS account in 9i and above is inacessible by default (connect sys/pw will
fail)

SYSTEM can and should be LOCKED

and neither should be used by you except during installation/configuration or at
the request of support.
April 01, 2004 MANISH -- Thanks for the question regarding "what is a meaning of 'WEB ENABLED APPLICATION'", version
You Asked
1)what is a meaning of 'WEB ENABLED APPLICATION'? and
what it's require if somebody is using oracle8i? Do we need
Oracle Application Server for that? and if forms6 is a frontend
then how it works?
and we said...
Typically it means an application that is executed from a web
browser/applet viewer. It might be a forms application delivered
as a java applet to the client, it might be an HTML application
like Amazon.com.

If someone is using Oracle8i, that might be all they need.
Oracle8i allows you to do IIOP from a client directly into the
database.  Since IIOP is the protocol used by Enterprise Java
Beans (EJBs), this means you can deploy a java applet to client
and have that client connect directly to the database using
IIOP.

It is more typical to have an application server in the mix
however to serve up HTML pages and images and perhaps run some
dynamic pages as well using java servlets, plsql, perl, etc.

see
http://www.oracle.com/iplatform/
and
http://www.oracle.com/tools/InternetDevTools/index.html
for more info.
Review & Followup

Rating: 3
Apache Web Server March 31, 2004
Reviewer: Jayesh from India
Hello,

 The Apache Web Server provided as part of Oracle 9i. Is it using direct HTTP or
Oracle Net for connection.

 Also when I use HTTP or IIOP to connect to Oracle 9i how will I specify the
connection?
Followup:
Apache uses http

You use urls with those protocols.
April 01, 2004 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)
Rating: 4
Good information about IOTs February 06, 2004
Reviewer: Raj from San Diego, CA USA
Hi Tom,

After reading about IOTs chapter (7) in your book Effective Oracle by Design. It
looks like IOT is only effective if we have to query the table by its Primary
Key.

In my case we have many tables showing many to many relationship between tables.
But as a rule we are using first field ID as a primary key in all the tables
hence the composite key is not the primary key.

Eg. table A ( A_Id number, A_desc (varchar2(100))
    table B ( B_Id number, b_desc (varchar2(100))
    table C ( C_Id number, A_Id number, B_Id number )
     in all this table a_id, b_id, c_id is the primary key respectively.

It is not only that C_Id is used as rule over here but it is also use to group
other two id's multiple times hence it is important to have it in the table but
most of our queries run on A_Id and B_Id on table C. So my question is should I
make this table as IOT. If yes then can you please show me how should I create
the table and should I have extra indexes for A_Id and B_Id on table C or not.

Oracle 9iR2.
Followup:
elaborate on why C has it's own primary key again?  that was not clear at all.

"use to group 2 other 2 id's multiple times" doesn't compute with me.
Rating: 3
Who is killing me March 30, 2004
Reviewer: Dhrubo from Kolkata,India
Hi Tom,
     I have this table -
create table myheap (
  a number,
  b varchar2(10),
  c varchar(100),
constraint pk_my_heap primary key (a, b)
);
Now i do the following -
1)Run this anonymous block to insert 10000 rows
begin
     dbms_output.put_line('T1-->'||TO_CHAR(SYSTIMESTAMP,'SSSSS'));
     for i in 1..10000 loop
     insert into myheap values( i,'aa','vvvvvvvvvvvvvvvvvv-'||i);
     end loop;
     commit;
     dbms_output.put_line('T2-->'||TO_CHAR(SYSTIMESTAMP,'SSSSS'));
exception
         when others then
               rollback;
              dbms_output.put_line(sqlerrm);
end;
3)Note the timings :
T1-->72463
T2-->72467
4)Drop and recreate the table
5)Run the same script but this time i use a sequence "IOT_PK_SEQ"
begin
     dbms_output.put_line('T1-->'||TO_CHAR(SYSTIMESTAMP,'SSSSS'));
     for i in 1..10000 loop
     insert into myheap values(
IOT_PK_SEQ.nextval,'aa','vvvvvvvvvvvvvvvvvv-'||i);
     end loop;
     commit;
     dbms_output.put_line('T2-->'||TO_CHAR(SYSTIMESTAMP,'SSSSS'));
exception
         when others then
               rollback;
              dbms_output.put_line(sqlerrm);
end;
6)Note the time again
T1-->72528
T2-->72543
Now my question is why is the last one takes more time than
the first.Is the sequence killing me?If yes why
Followup:
well, you should use bulk binds.  (to see how much was "sequence", one would
simply drop the use of the sequence in the test case!)

IOT's force ORDER on the data -- all of the data.  the index blocks split more
frequently.

Fact: An IOT is slower to insert into.

Fact: An IOT can be amazing on speeding retrieval and making your queries more
scalable.

Ask yourself "how many times do I insert a row".  "how many times do I retrieve
it".  "is it OK to pay a penalty during INSERTION in order to make retrieval
super efficient".  "how do I really use this table".

the answers to those questions will lead you to the right implementation
Rating: 3
Check the timmings now March 31, 2004
Reviewer: Dhrubo from Kolkata,India
declare
       type t1 is table of number index by binary_integer;
       --type t2 is table of varchar2(200) index by binary_integer;
       a t1;
       --b t2;

begin
     dbms_output.put_line('T1-->'||TO_CHAR(SYSTIMESTAMP,'SSSSS'));
     for i in 1..10000 loop
          a(i):=i;
     end loop;
     dbms_output.put_line('T2-->'||TO_CHAR(SYSTIMESTAMP,'SSSSS'));
     forall i in 1..10000
     insert into myheap values( a(i),'aa','vvvvvvvvvvvvvvvvvv-');

     commit;
     dbms_output.put_line('T3-->'||TO_CHAR(SYSTIMESTAMP,'SSSSS'));
exception
         when others then
               rollback;
              dbms_output.put_line(sqlerrm);
end;

T1-->71414
T2-->71414
T3-->71415

===========================================================
declare
       type t1 is table of number index by binary_integer;
       --type t2 is table of varchar2(200) index by binary_integer;
       a t1;
       --b t2;

begin
     dbms_output.put_line('T1-->'||TO_CHAR(SYSTIMESTAMP,'SSSSS'));
     for i in 1..10000 loop
          SELECT IOT_PK_SEQ.nextval INTO a(i) FROM DUAL ;
     end loop;
     dbms_output.put_line('T2-->'||TO_CHAR(SYSTIMESTAMP,'SSSSS'));
     forall i in 1..10000
     insert into myheap values( a(i),'aa','vvvvvvvvvvvvvvvvvv-');

     commit;
     dbms_output.put_line('T3-->'||TO_CHAR(SYSTIMESTAMP,'SSSSS'));
exception
         when others then
               rollback;
              dbms_output.put_line(sqlerrm);
end;

T1-->71542
T2-->71553
T3-->71554

===========================================================
Now who is killing me .. the sequence ... what is the reason tom?
Followup:
what is the reason for what exactly?

what you have shown is that going to the database 10,000 times, one after the
other after the other, to request a sequence takes longer then simply assigning
a number in a loop.

so, why would you call nextval in a loop, just insert it.  also, consider the
effect of the sequence cache which at 20 by default is too low for many highly
active systems

ops$tkyte@ORA9IR2> /*
DOC>drop table t;
DOC>drop sequence iot_pk_seq;
DOC>create sequence iot_pk_seq;
DOC>create table t ( x int, y varchar2(2), z varchar2(25) );
DOC>*/
ops$tkyte@ORA9IR2> truncate table t;

Table truncated.

Elapsed: 00:00:00.71
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> set timing on
ops$tkyte@ORA9IR2> declare
  2         type t1 is table of number index by binary_integer;
  3         a t1;
  4             type t2 is table of varchar2(2) index by binary_integer;
  5             b t2;
  6  begin
  7       for i in 1..10000
  8           loop
  9            a(i):=i;
 10                    b(i):='aa';
 11       end loop;
 12       forall i in 1..10000
 13          insert into t values( a(i),b(i),'vvvvvvvvvvvvvvvvvv-');
 14       commit;
 15  end;
 16  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.08
ops$tkyte@ORA9IR2> /

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.09
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> alter sequence iot_pk_seq cache 20;

Sequence altered.

Elapsed: 00:00:00.00
ops$tkyte@ORA9IR2> declare
  2             type t2 is table of varchar2(2) index by binary_integer;
  3             b t2;
  4  begin
  5       for i in 1..10000
  6           loop
  7                    b(i):='aa';
  8       end loop;
  9       forall i in 1..10000
 10          insert into t values( iot_pk_seq.nextval,b(i),
 11                                     'vvvvvvvvvvvvvvvvvv-');
 12       commit;
 13  end;
 14  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.25
ops$tkyte@ORA9IR2> /

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.19
ops$tkyte@ORA9IR2> alter sequence iot_pk_seq cache 10000;

Sequence altered.

Elapsed: 00:00:01.02
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2             type t2 is table of varchar2(2) index by binary_integer;
  3             b t2;
  4  begin
  5       for i in 1..10000
  6           loop
  7                    b(i):='aa';
  8       end loop;
  9       forall i in 1..10000
 10          insert into t values( iot_pk_seq.nextval,b(i),
 11                                     'vvvvvvvvvvvvvvvvvv-');
 12       commit;
 13  end;
 14  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.12
ops$tkyte@ORA9IR2> /

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.11

there you go -- 0.11 vs 0.09/0.08.  no significant difference
April 01, 2004 Prashant -- Thanks for the question regarding "Query", version 8.1.7
You Asked
Hi,
   I have a table named INTERACTION_LOG with following details.

   specialist_id number(5)
   account_no    number(10)
   date_created  date

   Data
   Specialist ID   Account No    Date Created
   1               100           15-SEP-2002 09:10:10
   1               100           15-SEP-2002 09:11:10
   1               100           15-SEP-2002 10:10:10
   1               100           15-MAR-2001 09:10:10
   1               100           15-JUN-2002 09:11:10
   1               400           15-JUN-2002 09:11:11
   2               100           15-SEP-2002 09:12:10
   2               200           14-SEP-2002 10:10:10
   2               200           14-SEP-2002 10:11:10
   2               200           14-SEP-2002 19:10:10
   2               200           14-SEP-2001 10:10:10
   2               200           14-MAR-2001 19:10:10
   2               500           15-JUN-2002 09:11:11

   I need a listing of duplicate interaction within a difference
   of 5 minutes for a given speciliast for the same account number
   between a date range 10-SEP-2002 and 16-SEP-2002.

   My output for the query should be
   Specialist ID   Account No    Date Created
   1               100           15-SEP-2002 09:10:10
   1               100           15-SEP-2002 09:11:10
   2               200           14-SEP-2002 10:10:10
   2               200           14-SEP-2002 10:11:10

   Is it possible through a single query or inline views?
   Specialist ID number 1 is working on the same account number within
   5 minutes difference of date created field. Same is for specialist
   2 for account number 200.
   Your help is appreciated.
Thanks,
Prashant Shah
and we said...
ops$tkyte@ORA817DEV.US.ORACLE.COM> select       *
  2    from (
  3  select t.*,
  4        count(*)
  5         over (partition by specialist_id
  6               order by date_created
  7               range between 1/24/60*2.5 preceding and 1/24/60*2.5 following
) dups
  8    from t
  9   where date_created between to_date( '10-SEP-2002', 'dd-mon-yyyy' )
 10                          and to_date( '16-SEP-2002', 'dd-mon-yyyy' )
 11         )
 12   where dups > 1
 13  /

SPECIALIST_ID ACCOUNT_NO DATE_CREATED               DUPS
------------- ---------- -------------------- ----------
            1        100 15-sep-2002 09:10:10          2
            1        100 15-sep-2002 09:11:10          2
            2        200 14-sep-2002 10:10:10          2
            2        200 14-sep-2002 10:11:10          2

analytic functions totally rule, don't they.....
Review & Followup

Rating: 4
Where can I find more September 17, 2002
Reviewer: Guy from Israel
Tom, Great solution. When can find the documentaion for these special queries
(analytic functions)?
Followup:

http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76994/analysis.htm#11237
(if you have my book "Expert one on one Oracle" i cover them in depth in there
as well)
Rating: 5
Swell analytic function usage, but what about that implicit arithmetic grouping? September 17, 2002
Reviewer: Scott from Chicago, IL
Yes indeed, analytic functions rule.  However your use of implicit arithmetic
precidence kinda scares me:

  1/24/60*2.5

I have to either figure out what this is meant to resolve to from the context of
the question or I have to go dig into Oracle's operator precidence resolution
rules.  And do they vary between the sql engine and the pl/sql engine?  Ick.

  1/(24*60*2.5)

Whew.

Apart from that, much like analytic functions, you rule Tom (lest my nit-picking
be construed as undue criticism).

Cheers,
Scott
Rating: 5
Excellent implementation ! Hats off ! September 17, 2002
Reviewer: Rama from ct, usa
Rating: 3
Question September 17, 2002
Reviewer: Prashant
Can you please clarify "1/24/60*2.5"? Why you have 2.5?
Also I needed if account number is also duplicated.
Followup:
1/24 = 1 hour

1/24/60 = 1 minute

2.5 * (1 minute) = 2 and 1/2 minutes.

We wanted a 5 minute "window" around the current row -- and to count all of the
rows in that 5 minute window.  That is what:

  5         over (partition by specialist_id
  6               order by date_created
  7               range between 1/24/60*2.5 preceding and 1/24/60*2.5 following

does -- it breaks the result set up by specialist_id

Within each group of specialist-ids, it sorted by DATE_CREATED

The count(*) was then computed for all rows within + or -2 1/2 minutes of the
current rows date_created (the range is always based on the order by column)

You could either partition by specialist_id, account_no to get it by
specialist_id/acount_no
Rating: 4
Can we use self join for this... February 28, 2003
Reviewer: Anand from India
Tom, as usual learnt a lot. Can we use self join to get the same done?

select a.specialist_id,a.account_no,to_chara.date_created,'dd-mon-yy hh:mi:ss')
from testtable a, testtable b
where
a.specialist_id=b.specialist_id
and a.date_created <> b.date_created
and ((a.date_created - b.date_created)*24*60* 60)  between  -150 and 150
--Window of 5 minutes
and trunc(a.date_created) between '10-SEP-2002' and '16-SEP-2002'
order by 2,3
Followup:
I usually use self joins as part two of examples with analytics to show how
inhumanly inefficient they are to accomplish this task!

Sure -- you can do it.
But
It'll be WAY slow

put a couple thousand rows in there -- say 10-20k -- then check it out.

Note: word to the wise....

would you compare an apple to a toaster oven?

Then why compare a date to a string -- ALWAYS but ALWAYS compare dates to dates,
strings to strings, numbers to number and NEVER NEVER store a date as a
number/string or a number as a string.
Rating: 5
data type ? February 28, 2003
Reviewer: A reader
Hi tom,

 do we have any fn that will tell me the datatype of the variable >

 type ()  ?

if we try to create one how hard it is ?
Followup:
with the new inheritance system for object types -- yes, as long as you have a
routine that accepts the parent type -- you can ask "what type are you really"

for the scalar types -- it is pretty easy

ops$tkyte@ORA920> create or replace package what
  2  as
  3          function type_am_i( x in number ) return varchar2;
  4          function type_am_i( x in varchar2 ) return varchar2;
  5          function type_am_i( x in date ) return varchar2;
  6  end;
  7  /

Package created.

ops$tkyte@ORA920> create or replace package body what
  2  as
  3          function type_am_i( x in number ) return varchar2
  4          is begin
  5          return 'number'; end;
  6
  7          function type_am_i( x in varchar2 ) return varchar2
  8          is begin
  9          return 'varchar2'; end;
 10
 11          function type_am_i( x in date ) return varchar2
 12          is begin
 13          return 'date'; end;
 14
 15  end;
 16  /

Package body created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          a number;
  3          b varchar2(20);
  4          c date;
  5  begin
  6          dbms_output.put_line( what.type_am_i(a) );
  7          dbms_output.put_line( what.type_am_i(b) );
  8          dbms_output.put_line( what.type_am_i(c) );
  9  end;
 10  /
number
varchar2
date

PL/SQL procedure successfully completed.

But -- why?  the caller would already KNOW what type the thing was -- how could
it "not" know????
Rating: 5
just like that ! February 28, 2003
Reviewer: A reader
 if it annoied you, I am sorry
I was just wondering if I need to find  the type at run time or something.

 by the way the "LET US KNOW" link is a the bottom "NOW"
that's very cool and convinient
thanks :)
Followup:
you didn't annoy me -- i'm just wondering what piece of code you could have that
would need to find the datatype at runtime -- I cannot imagine where or how this
would be used.

I mean, you have a variable "X".

You wrote the code.
You defined X
You by definition know the datatype already.
Rating: 5
compare February 28, 2003
Reviewer: A reader
Wrote file afiedt.buf

  1  declare
  2            a number;
  3            b varchar2(20);
  4            c date;
  5    begin
  6      c := '01-JAN-2003';           -- 1
  7              dbms_output.put_line( what.type_am_i(c) ||' 1:'|| c);
  8      c := sysdate;                 --2
  9              dbms_output.put_line( what.type_am_i(c) ||' 2:'|| c);
 10      c := to_date('01-JAN-2003');   --3
 11              dbms_output.put_line( what.type_am_i(c) ||' 3:'|| c);
 12      c := to_date('01-JAN-2003','dd-MON-rrrr');  --4
 13              dbms_output.put_line( what.type_am_i(c) ||' 4:'|| c);
 14*  end;
SQL> /
date 1:01-JAN-03
date 2:28-FEB-03
date 3:01-JAN-03
date 4:01-JAN-03

PL/SQL procedure successfully completed.

so in above what is implicit conversion
(should I use it ?)
what is explicit conversion (should I use it ?)

explicitly with dates in chars
Followup:
1 - implicit
2 - no conversion
3 - explicit
4 - explicit

you should ALWAYS explicity convert a string to a date in my opinion.  Do this:

alter session set nls_date_format = 'dd/mm/yyyy';

and then run your block and see what happens.
Rating: 5
what's the point in doing that ? March 03, 2003
Reviewer: A reader
Hi tom,

 I executed the block with change in the sesssion date format . what is the
difference you were pointing ?

please explain !!

10:32:25 SQL> alter session set nls_date_format = 'dd/mm/yyyy';

Session altered.

Elapsed: 00:00:00.00
10:32:31 SQL>   declare
10:32:36   2              a number;
10:32:36   3              b varchar2(20);
10:32:36   4               c date;
10:32:36   5      begin
10:32:36   6        c := '01-JAN-2003';           -- 1
10:32:36   7                dbms_output.put_line( what.type_am_i(c) ||' 1:'||
c);
10:32:36   8        c := sysdate;                 --2
10:32:36   9                dbms_output.put_line( what.type_am_i(c) ||' 2:'||
c);
10:32:36  10        c := to_date('01-JAN-2003');   --3
10:32:36  11                dbms_output.put_line( what.type_am_i(c) ||' 3:'||
c);
10:32:36  12        c := to_date('01-JAN-2003','dd-MON-rrrr');  --4
10:32:36  13                 dbms_output.put_line( what.type_am_i(c) ||' 4:'||
c);
10:32:36  14    end;
10:32:36  15  /
date 1:01/01/2003
date 2:03/03/2003
date 3:01/01/2003
date 4:01/01/2003

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
Followup:
ok, try this one

ops$tkyte@ORA920> alter session set nls_date_format = 'yyyy/mm/dd';

Session altered.
Rating: 5
changed the nls_date format.. March 03, 2003
Reviewer: A reader
10:32:55 SQL> alter session set nls_date_format = 'yyyy/mm/dd';

Session altered.

Elapsed: 00:00:00.00
10:45:37 SQL>    declare
10:45:43   2               a number;
10:45:43   3               b varchar2(20);
10:45:43   4                c date;
10:45:43   5       begin
10:45:43   6         c := '01-JAN-2003';           -- 1
10:45:43   7             dbms_output.put_line( what.type_am_i(c) ||' 1:'|| c);
10:45:43   8         c := sysdate;                 --2
10:45:43   9             dbms_output.put_line( what.type_am_i(c) ||' 2:'|| c);
10:45:43  10         c := to_date('01-JAN-2003');   --3
10:45:43  11             dbms_output.put_line( what.type_am_i(c) ||' 3:'|| c);
10:45:43  12         c := to_date('01-JAN-2003','dd-MON-rrrr');  --4
10:45:43  13              dbms_output.put_line( what.type_am_i(c) ||' 4:'|| c);
10:45:43  14     end;
10:45:43  15   /
   declare
*
ERROR at line 1:
ORA-01858: a non-numeric character was found where a numeric was expected
ORA-06512: at line 6

Elapsed: 00:00:00.00
10:45:44 SQL>

/*****************************************************/

obviously, as my sesssion date format has changed and
if i assign c := '01-JAN-03';

 it is not going to work, I have to
      do something like this c := '03-JAN-01';
in the first case !! (because I selected to enter date in that format by
changing the date format)

/*****************************************************/

Wrote file afiedt.buf

  1     declare
  2               a number;
  3               b varchar2(20);
  4                c date;
  5       begin
  6         c := '2003-01-01';           -- 1
  7                 dbms_output.put_line( what.type_am_i(c) ||' 1:'|| c);
  8         c := sysdate;                 --2
  9                 dbms_output.put_line( what.type_am_i(c) ||' 2:'|| c);
 10         c := to_date('2003-01-01');   --3
 11                 dbms_output.put_line( what.type_am_i(c) ||' 3:'|| c);
 12         c := to_date('01-JAN-2003','dd-MON-rrrr');  --4
 13                  dbms_output.put_line( what.type_am_i(c) ||' 4:'|| c);
 14*    end;
10:50:57 SQL> /
date 1:2003/01/01
date 2:2003/03/03
date 3:2003/01/01
date 4:2003/01/01

PL/SQL procedure successfully completed.
Followup:
that is why I say --

you should ALWAYS explicity convert a string to a date in my opinion.
Rating: 5
Thanks !! March 03, 2003
Reviewer: A reader
Rating: 5
March 03, 2003
Reviewer: jim
Analytical functions are execellent....
I'm still gettting the hang of writing them
but they save ALOT of coding..

"Then why compare a date to a string -- ALWAYS but ALWAYS compare dates to
dates, strings to strings, numbers to number and NEVER NEVER store a date as a
number/string or a number as a string."

I run a small mailing list answering Oracle problems
and one of the most common issues are people
using to_date on a a date, comparing dates to strings
and storing dates as strings
Rating: 4
select a maximum date from table for update March 04, 2003
Reviewer: Charlie from Texas USA
Tom,
I have an update like,
Update emp a
set sal= 1.1*
where trunc(hire_date)=(select max(hire_date)
                        from emp b, dept d
                        where a.emp_id=b.emp_id
                        and b.dept_no = d.dept_no
                        and d.dept_no=10)

Is it possible to update this using analytic functions.
thanks
Followup:
sort of -- but the subquery doesn't make sense to me - emp_id, isn't that a
primary key....
Rating: 3
implicit and explicit Conversion November 13, 2003
Reviewer: Marcio from br
you said: you should ALWAYS explicity convert a string to a date in my opinion.

Is there any performance issue about use of implicity/explicity conversion?
Followup:
there are BUGS with regards (rely on implicit conversions and you have a bug)

other then that, nothing to consider here.  You always want to use explicit
conversions to avoid BUGS and erroneous code.
Rating: 4
flaw in query November 21, 2003
Reviewer: Daniel from Toronto
We can't assume a -2.5min ~ 2.5min window on current row, if the first two rows
in sample data as follow:
  1               100           15-SEP-2002 09:10:10
  1               100           15-SEP-2002 09:15:00

I think they meet the requirement, but Tom's query will not pick them up.

Here is another approach,

select specialist_id, account_no, to_char(date_created, 'dd-MON-yyyy
hh24:mi:ss') date_created
from (
select t.*,
       abs(date_created -
             nvl(lag(date_created)
                 over ( partition by specialist_id order by date_created ),
date_created-1)
       ) interval1,
       abs(date_created -
             nvl(lead(date_created)
                 over ( partition by specialist_id order by date_created ),
date_created+1)
       ) interval2
from interaction_log t
 where date_created between to_date( '10-SEP-2002', 'dd-mon-yyyy' )
                        and to_date( '16-SEP-2002', 'dd-mon-yyyy' )
)
where interval1 <= 5/24/60 or interval2 <= 5/24/60
/
Rating: 5
Question December 04, 2003
Reviewer: PRS from usa
Tom,
     I have a trading table named trade.
     TRADE
           CUST_ID
           TRADE_ID
           TRADE_DATE
     I want to find out the average trade time for a customer. Is there any
analytical function available to do this.

Thanks,
PRS
Followup:
Here is the "average time of creation of objects by owner" in my database.

substitute cust_id for owner, trade_date for created....

ops$tkyte@ORA920PC> select owner,
  2         avg( to_number( to_char(created,'sssss') ) ) ,
  3         to_char( to_date( round(avg( to_number( to_char(created,'sssss') ) )
), 'sssss' ), 'hh24:mi:ss' )
  4    from all_objects
  5   group by owner
  6  /

OWNER                     AVG(TO_NUMBER(TO_CHAR(CREATED,'SSSSS'))) TO_CHAR(
------------------------- ---------------------------------------- --------
AQ                                                      36189.4783 10:03:09
BIG_TABLE                                               40086.6667 11:08:07
CTW                                                          26908 07:28:28
CTXSYS                                                  52019.7778 14:27:00
DEMO                                                       42547.5 11:49:08

SSSSS is the number of seconds past midnight, get the average of that, convert
back to date and print it.
Rating: 5
question December 04, 2003
Reviewer: PRS from USA
I did not understand the query totally. I have the trade_date difference in
days. And you are printing it in HH24:MM:SS. Can you please clarify?
Thanks very much
Followup:
now I don't understand.

i would have thought that trade_date was an oracle date datatype -- a timestamp
if you will.

i took the time component (sssss) and averaged it.  turned it back into a date
and display the time portion (the average time of day they traded)

maybe you better explain your data better with an example
Rating: 5
Details December 04, 2003
Reviewer: PRS from USA
Sorry for not explained properly.
Let me put the data below.
   CUST_ID   TRADE_ID             TRADE_DATE
---------- ---------- ----------------------
         1          1  8/26/2003 11:03:13 AM
         1          2   9/5/2003 11:03:13 AM
         1          3  9/15/2003 11:03:13 AM
         1          4  9/25/2003 11:03:13 AM
         1          5 10/15/2003 11:03:13 AM
         1          6  5/18/2003 11:03:13 AM
         1          7  7/17/2003 11:03:13 AM
         1          8  6/17/2003 11:03:13 AM
         1          9  3/29/2003 11:03:13 AM
         2          1  8/26/2002 11:03:13 AM
         2          2   9/5/2002 11:03:13 AM
         2          3  9/15/2002 11:03:13 AM
         2          4  9/25/2003 11:03:13 AM
         2          5 10/15/2003 11:03:13 AM
         3          1  8/26/2000 11:03:13 AM
         3          2   9/5/2001 11:03:13 AM
         3          3  9/15/2002 11:03:13 AM
         3          4  9/25/2003 11:03:13 AM

    So the output should be.
    Cust Id           Average Trade Time (Days)
    1                 23.32  (Just an Example)
    2                 100.10
    3                 360

    I have just put the days as an example. It is not calculated for the above
data.

Thanks in advance.
PRS
Followup:
do you mean "the average number of days between consectutive trades by customer
id"????

ops$tkyte@ORA920PC> select cust_id, avg( next_trade_date - trade_date )
  2   from (
  3  select cust_id, trade_date, lead(trade_date) over (partition by cust_id
  4  order by trade_date) next_trade_date
  5   from t
  6       )
  7  group by cust_id
  8  /

   CUST_ID AVG(NEXT_TRADE_DATE-TRADE_DATE)
---------- -------------------------------
         1                              25
         2                          103.75
         3                             375
Rating: 5
Thanks......... December 04, 2003
Reviewer: PRS from USA
Thanks a lot. One of the best site for ORACLE questions. Really appreciate it.
PRS
Rating: 5
Question December 15, 2003
Reviewer: PRS from USA
I have a table named X and it has three columns LAST_NAME, FIRST_NAME,
MIDDLE_NAME. Following is the test data.
Last Name   First Name        Middle Name
------------------------------------------
WHITE       ROBERT            R
KENNY       JOHN              U
I want to wirte the query in such a way that the output shown below.

Text          Type
---------------------
WHITE         L
KENNY         L
ROBERT        F
JOHN          F
U             M
R             M

Appreciate your help.
Thanks,
PRS
Followup:
select decode( type, 'L', last_name, 'F', first_name, 'M', middle_name ),
       type
  from X, ( select 'L' type from dual UNION ALL
            select 'F' type from dual UNION ALL
            select 'M' type from dual )
Rating: 5
Ome more question December 15, 2003
Reviewer: PRS from USA
Also for the above query I need a output as shown below
Text     Type
WHITE    L
ROBERT   F
R        M
KENNY    L
JOHN     F
U        M
Youe help is appreciated.

PRS
Followup:
select decode( type, 'L', last_name, 'F', first_name, 'M', middle_name ),
       type
  from (select rownum r, x.* from X), ( select 'L' type from dual UNION ALL
            select 'F' type from dual UNION ALL
            select 'M' type from dual )
 order by r, decode( type, 'L', 1, 'F', 2, 'M' 3 )
Rating: 5
First query no order by December 15, 2003
Reviewer: PRS from USA
In the first query reply
select decode( type, 'L', last_name, 'F', first_name, 'M', middle_name ),
       type
  from X, ( select 'L' type from dual UNION ALL
            select 'F' type from dual UNION ALL
            select 'M' type from dual )

Middle Initial 'U' row does not come first. It come later. Can we just do an
order by descending order for last_name,
first_name, middle_name?

Thanks,
PRS
Followup:
add an appropriate order by.  I did not know "order" was relevant (wasn't
specified, in the second query is was more "obvious" since the only difference
was order)

but you seem to have attached some meaning to the order of the rows in the table
itself -- but there isn't any really, you seem to assume the rows will come out
in order of insertion or something -- they do not, they are just random.
Rating: 4
Date Breaks December 27, 2003
Reviewer: Suleiman Akbar from UAE
Tom,

I need ur help in solving this query.

looking for an output which breaks the dates. have a look at the query which is
in the view. I have to call this view and have to make the date breaks.

View :

CREATE OR REPLACE VIEW EH_AF_CSTMKTFARE_VW AS
SELECT  orgdst_type,
        point_of_sale,
        cst.point_of_sale
        cst.destination,
        mkp.booking_from_date,
        mkp.booking_to_date,
        mkp.travel_from_date,
        mkp.travel_to_date,
        cst.fare_id,
        cst.rule_id,
        mkt.market_fare_id,
        mkt.rule_id market_rule_id,
        mkt.booking_from_date mkt_booking_from_date,
        mkt.booking_to_date mkt_booking_to_date,
        mkt.travel_from_date mkt_travel_from_date,
        mkt.travel_to_date mkt_travel_to_date
FROM    eh_af_orgdst_cost cst, eh_af_markup mkp,
        eh_af_mktfare_fare mkt
WHERE   cst.fare_id = mkp.fare_id AND
        cst.point_of_sale = cst.point_of_sale AND
        cst.origin = mkt.origin(+) AND
        cst.destination = mkt.destination(+) AND
        cst.category = mkt.category(+) AND
        cst.active_flag = mkt.active_flag(+) AND
       ( (mkp.booking_from_date BETWEEN
          mkt.booking_from_date AND mkt.booking_to_date) OR
         (mkp.booking_to_date BETWEEN mkt.booking_from_date
          AND mkt.booking_to_date) OR
         (mkt.booking_from_date BETWEEN
          mkp.booking_from_date AND mkp.booking_to_date) OR
         (mkt.booking_to_date BETWEEN mkp.booking_from_date
          AND mkp.booking_to_date) OR
          mkt.booking_from_date IS NULL
       ) AND
       ( (mkp.travel_from_date BETWEEN mkt.travel_from_date
          AND mkt.travel_to_date) OR
         (mkp.travel_to_date BETWEEN mkt.travel_from_date
          AND mkt.travel_to_date) OR
         (mkt.travel_from_date BETWEEN mkp.travel_from_date
          AND mkp.travel_to_date) OR
         (mkt.travel_to_date BETWEEN mkp.travel_from_date
          AND mkp.travel_to_date) OR
          mkt.travel_from_date IS NULL
       )

WHAT iam looking for is.....

In the table EH_AF_MARKUP, will as the dates as follows:

BkgFrm       BkgTo         DepFrm         DepTo
01/04/2003  31/03/2004   01/04/2003     31/03/2004

Corresponding entries of dates exists in EH_AF_MARKET_FARE table as

BkgFrm       BkgTo         DepFrm         DepTo
01/04/2003  30/06/2003   01/04/2003     30/06/2003
01/04/2003  31/12/2003   01/07/2003     31/12/2003

Now I want the date result as

BkgFrm       BkgTo         DepFrm         DepTo
01/04/2003  30/06/2003   01/04/2003     30/06/2003
01/04/2003  31/12/2003   01/07/2003     31/12/2003
01/04/2003  31/03/2004   01/01/2004     31/03/2004

I really appreciate if you can answer either in SQL / PLSQL ...

Thxs in advance.
Suleiman Akbar.
Followup:
sorry -- a tad confusing here.  when I'm taking new questions, feel free to ask
there but you would want to provide a tiny example and explain how you get from
your inputs to your desired outputs.

unless the depfrm in the last row there in the outputs is a typo -- i don't get
it.

if it is a typo -- read about "union all".
Rating: 3
Follow-up for ur queries December 27, 2003
Reviewer: Suleiman akbar from UAE
Tom,

If the dates are like this in Table 1
BkgFrm       BkgTo         DepFrm         DepTo
01/04/2003  31/03/2004   01/04/2003     31/03/2004

And few dates are like this in Table 2
BkgFrm       BkgTo         DepFrm         DepTo
01/04/2003  30/06/2003   01/04/2003     30/06/2003
01/04/2003  31/12/2003   01/07/2003     31/12/2003

Now I want the date result as

BkgFrm       BkgTo         DepFrm         DepTo
01/04/2003  30/06/2003   01/04/2003     30/06/2003
01/04/2003  31/12/2003   01/07/2003     31/12/2003
01/04/2003  31/03/2004   01/01/2004     31/03/2004
(last row of Booking From & To is taken from the table1 and Departure From is
calculated as the end of the second row (departure to date) plus one
(01/01/2004) and it's (3rd row) departure to will be the greatest of Table 1's
or Table 2's Departure Date!

Pls. throw light with ur own example if u didn't get the above big query.

I really appreciate if you can answer either in SQL / PLSQL ...

Thxs in advance.
Suleiman Akbar.
Followup:
not fully baked -- don't understand 100% how you group the rows in t1 and t2
together, but I'll make the bold assumption that you group by bkgfrm (when
equal, these are a "group") and you order that group by bkgto.

if so:
ops$tkyte@ORA9IR2> create table t1 ( bf date, bt date, df date, dt date );

Table created.

ops$tkyte@ORA9IR2> create table t2 ( bf date, bt date, df date, dt date );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> alter session set nls_date_format = 'dd/mm/yyyy';

Session altered.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t1 values (
'01/04/2003','31/03/2004','01/04/2003','31/03/2004' );

1 row created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t2 values (
'01/04/2003','30/06/2003','01/04/2003','30/06/2003' );

1 row created.

ops$tkyte@ORA9IR2> insert into t2 values (
'01/04/2003','31/12/2003','01/07/2003','31/12/2003' );

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select bf, bt,
  2         decode( oc, 1, df, lag(dt+1) over (partition by bf order by bf, bt,
oc) ) df,
  3             dt
  4    from (
  5  select 2 oc, t1.* from t1
  6  union all
  7  select 1 oc, t2.* from t2
  8         )
  9   order by bf, bt, oc
 10  /

BF         BT         DF         DT
---------- ---------- ---------- ----------
01/04/2003 30/06/2003 01/04/2003 30/06/2003
01/04/2003 31/12/2003 01/07/2003 31/12/2003
01/04/2003 31/03/2004 01/01/2004 31/03/2004
Rating: 3
Confusion December 27, 2003
Reviewer: S.Akbar from UAE
Tom,

We are not using any Partioned Tables, I think the logic what u have enclosed
will work for me. Pls. assume that it's an ordinary table without any
partitions.

Got confused with "Lag". It's better if u can show the same results without
using partitions.

Thxs in advance.

S.Akbar
Followup:
lag is an analytic function.

the partition clause has nothing to do with physically partitioning a table.

it is just SQL (my example has not partitioned tables either!)

see the data warehousing guide for analytic functions or if you have my book
"Expert one on one Oracle" i have a lengthly chapter on them.  Coolest thing to
happen to sql since the keyword select.
Rating: 5
how to insert rows between empty rows and rowsdatavalues December 28, 2003
Reviewer: suresh from India
 Hi tom,
   In database table somebody inserting the rows into the table like this 1 to
10 rows values,next 20 to 30 rows are values,next 40 to 50 rows have values and
in between 10 to 20 is empty rows(nodata), 30 to 40 empty rows.
 Question :
  when i insert the row into the table my first row is inserted into 11 th,next
12 th....upto 20th.
  next 30 th row,next 31 st row......like this upto 40th row...

  How to insert my row values into the firstempty row in the table(insert 11 th
row,next 12 th row...like this?

  pls reply,

Regards
 suresh
Followup:
there is no such thing as the "N'th row in a table"

I hope you aren't thinking that data comes out in the order it went in or
anything like that.

In short -- i'm not following your question at all, but I think you think data
has "some order" associated with it which is 100% false.
Rating: 5
how to retrive the empty rows only. December 28, 2003
Reviewer: Suresh from India
hi tom,
  i created a sequence for one column in database table, i inserted some rows(2
rows,seq.no:1,2) into that dbtable and commited and again i inserted some rows(2
rows seq.no:3,4) and rollback these rows.
  again i inserted some rows(2 rows seq.no:5,6) and commit.
 to Query the dbtable(seq.no's 1,2,5,6....) rows are displayed only .

  this process is going on for some rows...

  Question :

    How to retrive the empty rows(null rows)
               (rows like seq.no : 3,4,7,8.....) ?

  pls reply,

Regards,
  Suresh.
Followup:
ahh, I see, you want to get all of the "gaps".

the most efficient is to actually just get the start/stop and let your "mind"
fill in the rest.  For example:

ops$tkyte@ORA9IR2> select * from t;

         X
----------
         1
         2
         5
         6
         8
         9
        15

7 rows selected.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select x+1, next_x-1
  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+1   NEXT_X-1
---------- ----------
         3          4
         7          7
        10         14

shows that 3, 4, 7, 10, 11, 12, 13, 14 are "missing" in this set.

Else, you have to synthesize a result set that is "as large as the difference
between the max and min values" and then join.  It could look like this:

ops$tkyte@ORA9IR2> create or replace type myTableType as table of number
  2  /

Type created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace function result_set(p_iterations in number)
return myTableType
  2  pipelined
  3  as
  4  begin
  5      for i in 1 .. p_iterations
  6      loop
  7          pipe row(i);
  8      end loop;
  9      return;
 10  end;
 11  /

Function created.

ops$tkyte@ORA9IR2> select * from table( result_set( (select max(x)-min(x)+1 from
t)) );

COLUMN_VALUE
------------
           1
           2
           3
           4
           5
           6
           7
           8
           9
          10
          11
          12
          13
          14
          15

15 rows selected.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select a.column_value
  2    from table( result_set( (select max(x)-min(x)+1 from t)) ) a
  3         left join t on ( a.column_value = t.x )
  4   where t.x is null
  5   order by a.column_value
  6  /

COLUMN_VALUE
------------
           3
           4
           7
          10
          11
          12
          13
          14

8 rows selected.
Rating: 3
OK December 28, 2003
Reviewer: Ram from Bangalore,India
Dear sir,
well and wish the same from you.I tried the query shown
below
 sql>select count(*) as cnt from dual
     where exists(select 'x' from emp where deptno = 10);
  cnt
 -----
   1
 The query output comes as 1 not as 3.I use version 8.0.
How to correct this?
Bye!
Followup:
how many rows does dual have?

1

you are counting the rows in dual., not in emp or dept.
Rating: 4
Why do you want to use dual? December 28, 2003
Reviewer: Sami
Can't you write
select count(*) from emp where deptno=10;
OR
select (select count(*) from emp where deptno=10) cnt from dual;
Rating: 2
OK December 29, 2003
Reviewer: Ram from Bangalore,India
Dear sir,
Thanks for your response.I wanted the value obtained from
the subquery to be passed to the dual table using that
EXISTS condition.Is that possible?
Bye!
Followup:
nope, that is not what exists does.
Rating: 5
How to invoke one form to another . January 08, 2004
Reviewer: Suresh from India
Hi tom,
   In a database(Forms 6i) User(A) developed one form(say form1), another
user(B) to developed the form(say form2) in another schema.

   How User A invokes form2(this is User B Form) through form1(User A Form). How
to do this ?

 It is possible through Open_Form(Parameters),Call_Form(),
  New_Form() ? what is the diff. of these 3 built-in's by
  Calling the above scenario.(Forms in diif. Schema) ?

Pls reply,

Regards,
  Suresh.
Followup:
sorry -- been over 8 years, going on 9 years (in about 2 months) since I've
actually used forms.

suggestion:

a) read documentation.  I know these three functions are still documented.

b) see otn.oracle.com -> discussion forums after reading the documentation if
you still cannot see the way.
Rating: 3
Nice January 09, 2004
Reviewer: Rosleyn from London,UK
Dear Tom,
I need a query in the following format.
 Use emp table in scott schema
 Format:
  Mgr_ename   Sub_ename
  ---------   ---------

Please do reply.
Bye!
Followup:
"join"

just "join" emp to emp by emp1.mgr = emp2.empno
Rating: 4
Dynamic parameter in query January 13, 2004
Reviewer: Natasha from UAE
How to dynamically select an Arithmatic Operator and join it to our sql query
for these below mentioned four column (Booking & Travel dates). Assume that a
table has got the structure as

Table EMP
--------
FLTNO          PLS_INTEGER,
FLTNAME          VARCHAR2(20),
AMOUNT          NUMBER(12,2),
BOOKING_FROM_DATE DATE,
BOOKING_TO_DATE      DATE,
TRAVEL_FROM_DATE  DATE,
TRAVEL_TO_DATE      DATE
)

And from the front-end, he passes all date parameters and their respective
arithematic operators, then how to properly set the sql statments in SQL query.

Booking_From_Date    =
Booking_To_Date        <=
Travel_From_Date    =
Travel_To_Date        <=

Currently I am using a variable by putting some conditions and concatenating the
desired arithematic operators (as per the parameter) and then passing it to REF
cursor variable which I don't want to do it.

Appreciate, if you can make it simpler.
Followup:
there is ONE and only ONE way to have a dynamic predicate like that.....

Using dynamic sql..

If you don't "like" a ref cursor:

   open l_cursor for some_query_youve_built;

you can use the dbms_sql package to accomplish the same, albeit with lots more
code.

the ref cursor approach is simple and elegant.
Rating: 5
Oracle 7.3.4 Gap finder January 14, 2004
Reviewer: Luis Soto from Dominican Republic
Do Know any way to find the gaps in Oracle 7.3.4

Thanks
Rating: 5
Question February 06, 2004
Reviewer: PRS from USA
Hi Tom,
     I have built a following view named PS_WSI_USER_BU_VW_FILL.

CREATE OR REPLACE VIEW ps_wsi_user_bu_vw (name1,
                                          person_id,
                                          business_unit,
                                          sales_user_type,
                                          first_name,
                                          last_name,
                                          rb_work_func_id,
                                          wsi_tls_grp_name,
                                          oprid,
                                          setid,
                                          provider_grp_id,
                                          wsi_group
                                         )
AS
   SELECT b.first_name || ' ' || b.last_name, a.person_id, e.business_unit,
          e.sales_user_type, b.first_name, b.last_name, c.rb_work_func_id,
          d.wsi_tls_grp_name, a.oprid, f.setid, f.provider_grp_id, g.descr
     FROM psopralias a,
          ps_rd_person_name b,
          ps_rb_worker_func c,
          ps_wsi_rd_prsn_fld d,
          ps_rsf_suser e,
          ps_rf_grp_member f,
          ps_rb_wrk_func_tbl g,
          ps_rb_worker h
    WHERE b.person_id = a.opraliasvalue(+)
      AND a.opraliastype = 'PER'
      AND b.primary_ind = 'Y'
      AND b.person_id = c.person_id(+)
      AND b.person_id = d.person_id
      AND b.person_id = e.person_id
      AND b.person_id = f.person_id(+)
      AND c.rb_work_func_id = g.rb_work_func_id
      AND b.person_id = h.person_id
/

   All tables are analyzed by percent 35. Indexes are analyzed by 100%. It is
using cost based optimizer.

   I have following query which comes back in 1 second. It used the right
indexes.
SQL> l
  1  SELECT
  2  fill.name1, fill.person_id, fill.business_unit, fill.sales_user_type,
  3         fill.first_name, fill.last_name, fill.rb_work_func_id,
  4         fill.wsi_tls_grp_name, fill.oprid, fill.setid, fill.provider_grp_id,
  5         fill.wsi_group
  6    FROM ps_wsi_user_bu_vw fill
  7   WHERE oprid = 'James Mayfield'
  8*     OR (oprid LIKE '%Branch %' AND provider_grp_id = '100841')
SQL> /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=745 Card=2 Bytes=348
          )

   1    0   CONCATENATION
   2    1     NESTED LOOPS (Cost=12 Card=1 Bytes=174)
   3    2       NESTED LOOPS (Cost=11 Card=1 Bytes=144)
   4    3         NESTED LOOPS (OUTER) (Cost=9 Card=1 Bytes=132)
   5    4           FILTER
   6    5             NESTED LOOPS (OUTER)
   7    6               NESTED LOOPS (Cost=6 Card=1 Bytes=81)
   8    7                 NESTED LOOPS (Cost=8 Card=1 Bytes=113)
   9    8                   NESTED LOOPS (Cost=5 Card=1 Bytes=63)
  10    9                     TABLE ACCESS (FULL) OF 'PSOPRALIAS' (Cost=2 Card=1
Bytes=37)

  11    9                     TABLE ACCESS (BY INDEX ROWID) OF 'PS_RD_
PERSON_NAME' (Cost=3 Card=1 Bytes=26)

  12   11                       INDEX (RANGE SCAN) OF 'PSDRD_PERSON_NAME'
(NON-UNIQUE) (Cost=2 Card=1)
  13    8                   INDEX (UNIQUE SCAN) OF 'PS_RB_WORKER' (UNIQUE)

  14    7                 TABLE ACCESS (BY INDEX ROWID) OF 'PS_RSF_SUSER'
(Cost=1 Card=1 Bytes=18)
  15   14                   INDEX (UNIQUE SCAN) OF 'PS_RSF_SUSER' (UNIQUE)

  16    6               TABLE ACCESS (BY INDEX ROWID) OF 'PS_RF_GRP_MEMBER'
(Cost=2 Card=1 Bytes=22)
  17   16                 INDEX (RANGE SCAN) OF 'PSARF_GRP_MEMBER' (NON-UNIQUE)
(Cost=1 Card=1)
  18    4           INDEX (RANGE SCAN) OF 'PS_RB_WORKER_FUNC' (UNIQUE)(Cost=1
Card=1 Bytes=19)
  19    3         TABLE ACCESS (BY INDEX ROWID) OF 'PS_WSI_RD_PRSN_FLD' (Cost=2
Card=1 Bytes=12)
  20   19           INDEX (UNIQUE SCAN) OF 'PS_WSI_RD_PRSN_FLD' (UNIQUE) (Cost=1
Card=1)
  21    2       TABLE ACCESS (BY INDEX ROWID) OF 'PS_RB_WRK_FUNC_TBL'(Cost=1
Card=1 Bytes=30)
  22   21         INDEX (UNIQUE SCAN) OF 'PS_RB_WRK_FUNC_TBL' (UNIQUE)
  23    1     NESTED LOOPS (Cost=12 Card=1 Bytes=174)
  24   23       NESTED LOOPS (Cost=11 Card=1 Bytes=144)
  25   24         NESTED LOOPS (OUTER) (Cost=9 Card=1 Bytes=132)
  26   25           NESTED LOOPS (Cost=8 Card=1 Bytes=113)
  27   26             FILTER
  28   27               NESTED LOOPS (OUTER)
  29   28                 NESTED LOOPS (Cost=6 Card=1 Bytes=81)
  30   29                   NESTED LOOPS (Cost=5 Card=1 Bytes=63)
  31   30                     TABLE ACCESS (BY INDEX ROWID) OF 'PSOPRALIAS'
(Cost=2 Card=1 Bytes=37)
  32   31                       INDEX (UNIQUE SCAN) OF 'PS_PSOPRALIAS' (UNIQUE)
(Cost=1 Card=4526)
  33   30                     TABLE ACCESS (BY INDEX ROWID) OF
'PS_RD_PERSON_NAME' (Cost=3 Card=1 Bytes=26)
  34   33                       INDEX (RANGE SCAN) OF 'PSDRD_PERSON_NAME'
(NON-UNIQUE) (Cost=2 Card=1)

  35   29                   TABLE ACCESS (BY INDEX ROWID) OF 'PS_RSF_SUSER'
(Cost=1 Card=1 Bytes=18)
  36   35                     INDEX (UNIQUE SCAN) OF 'PS_RSF_SUSER' (UNIQUE)

  37   28                 TABLE ACCESS (BY INDEX ROWID) OF 'PS_RF_GRP_MEMBER'
(Cost=2 Card=1 Bytes=22)
  38   37                   INDEX (RANGE SCAN) OF 'PSARF_GRP_MEMBER'
(NON-UNIQUE) (Cost=1 Card=1)
  39   26             INDEX (UNIQUE SCAN) OF 'PS_RB_WORKER' (UNIQUE)
  40   25           INDEX (RANGE SCAN) OF 'PS_RB_WORKER_FUNC' (UNIQUE)(Cost=1
Card=1 Bytes=19)
  41   24         TABLE ACCESS (BY INDEX ROWID) OF 'PS_WSI_RD_PRSN_FLD' (Cost=2
Card=1 Bytes=12)
  42   41           INDEX (UNIQUE SCAN) OF 'PS_WSI_RD_PRSN_FLD' (UNIQUE) (Cost=1
Card=1)
  43   23       TABLE ACCESS (BY INDEX ROWID) OF 'PS_RB_WRK_FUNC_TBL' (Cost=1
Card=1 Bytes=30)
  44   43         INDEX (UNIQUE SCAN) OF 'PS_RB_WRK_FUNC_TBL' (UNIQUE)
SQL> spool off

Now I have following query in which I add extra where clause with OR condition
and it takes more than 2 minutes. It changes the
entire explain plan. It does not use the index on PS_RD_PERSON_NAME which has
more than 3million rows.

SQL> SELECT
  2         fill.name1, fill.person_id, fill.business_unit,
fill.sales_user_type,
  3         fill.first_name, fill.last_name, fill.rb_work_func_id,
  4         fill.wsi_tls_grp_name, fill.oprid, fill.setid, fill.provider_grp_id,
  5         fill.wsi_group
  6    FROM ps_wsi_user_bu_vw fill
  7   WHERE oprid = 'James Mayfield'
  8      OR (oprid LIKE '%Branch %' AND provider_grp_id = '100841')
  9      OR provider_grp_id = '100841'
 10  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6947 Card=1 Bytes=174)

   1    0   NESTED LOOPS (Cost=6947 Card=1 Bytes=174)
   2    1     NESTED LOOPS (OUTER) (Cost=6946 Card=1 Bytes=144)
   3    2       NESTED LOOPS (Cost=6945 Card=1 Bytes=125)
   4    3         NESTED LOOPS (Cost=6943 Card=1 Bytes=113)
   5    4           NESTED LOOPS (OUTER) (Cost=6925 Card=9 Bytes=684)
   6    5             HASH JOIN (Cost=6907 Card=9 Bytes=486)
   7    6               TABLE ACCESS (FULL) OF 'PS_RSF_SUSER' (Cost=7 Card=3881
Bytes=69858)
   8    6               NESTED LOOPS (Cost=6898 Card=7962 Bytes=286632)

   9    8                 TABLE ACCESS (FULL) OF 'PS_RD_PERSON_NAME' ( Cost=6898
Card=3387373 Bytes=88071698)
  10    8                 INDEX (UNIQUE SCAN) OF 'PS_RB_WORKER' (UNIQUE)

  11    5             TABLE ACCESS (BY INDEX ROWID) OF 'PS_RF_GRP_MEMBER'
(Cost=2 Card=1 Bytes=22)
  12   11               INDEX (RANGE SCAN) OF 'PSARF_GRP_MEMBER' (NON-UNIQUE)
(Cost=1 Card=1)
  13    4           TABLE ACCESS (BY INDEX ROWID) OF 'PSOPRALIAS' (Cost=2 Card=1
Bytes=37)
  14   13             INDEX (RANGE SCAN) OF 'PSBPSOPRALIAS' (NON-UNIQUE) (Cost=1
Card=1)
  15    3         TABLE ACCESS (BY INDEX ROWID) OF 'PS_WSI_RD_PRSN_FLD' (Cost=2
Card=1 Bytes=12)
  16   15           INDEX (UNIQUE SCAN) OF 'PS_WSI_RD_PRSN_FLD' (UNIQUE) (Cost=1
Card=1)
  17    2       INDEX (RANGE SCAN) OF 'PS_RB_WORKER_FUNC' (UNIQUE) (Cost=1
Card=1 Bytes=19)
  18    1     TABLE ACCESS (BY INDEX ROWID) OF 'PS_RB_WRK_FUNC_TBL' (Cost=1
Card=1 Bytes=30)
  19   18       INDEX (UNIQUE SCAN) OF 'PS_RB_WRK_FUNC_TBL' (UNIQUE)

SQL> spool off

Can you please show how to resolve this 2nd query? It is just optimized is
unsing a different explain plan.

Thanks for your help.

PRS
Followup:
quick comment:

 WHERE b.person_id = a.opraliasvalue(+)
      AND a.opraliastype = 'PER'

hmmm, if a.* is "made up", a.* will all be null and a.opraliastype = 'PER' will
never be true (or false :).  Hence, the (+) on A is not only "not relevant", it
only stops the optimizer from considering other plans...

Anytime you see an outer join, ask yourself "why, do i really need it, and if
so, why do i really really need it".  In this case, you obviously do not need
it!

Hmm, another one:

      AND b.person_id = c.person_id(+)
...
      AND c.rb_work_func_id = g.rb_work_func_id

if c.* is made up, c.rb_work_func_id will be null and that'll never be equal to
g.rb_work_func_id so -- again, lose the outer join!

Lets start there first.
Rating: 4
Changed February 09, 2004
Reviewer: PRS from USA
Hi Tom,
   I totally agree with your suggestion on outer join. I removed all outer join
and recreated the view as shown below.
create or replace view ps_wsi_user_bu_vw as
SELECT b.first_name || ' ' || b.last_name name1, a.person_id, e.business_unit,
       e.sales_user_type, b.first_name, b.last_name, c.rb_work_func_id,
       d.wsi_tls_grp_name, a.oprid, f.setid, f.provider_grp_id, g.descr
wsi_group
  FROM psopralias a,
       ps_rd_person_name b,
       ps_rb_worker_func c,
       ps_wsi_rd_prsn_fld d,
       ps_rsf_suser e,
       ps_rf_grp_member f,
       ps_rb_wrk_func_tbl g,
       ps_rb_worker h
 WHERE b.person_id = a.opraliasvalue
   AND a.opraliastype = 'PER'
   AND b.primary_ind = 'Y'
   AND b.person_id = c.person_id
   AND b.person_id = d.person_id
   AND b.person_id = e.person_id
   AND b.person_id = f.person_id
   AND c.rb_work_func_id = g.rb_work_func_id
   AND b.person_id = h.person_id

   But the explain plan remains identical for the query 2. There is no change in
the plan.

I do not understand why optimizer is doing this way. Can you help?

Thanks,
PRS
Followup:
if you look at the two predicates, they are very very very very different.
Might not seem so to you -- but they are:

  7   WHERE oprid = 'James Mayfield'
  8      OR (oprid LIKE '%Branch %' AND provider_grp_id = '100841')
 10  /

versus

  7   WHERE oprid = 'James Mayfield'
  8      OR (oprid LIKE '%Branch %' AND provider_grp_id = '100841')
  9      OR provider_grp_id = '100841'
 10  /

now, you do understand "lots of tables", "no idea of what indexes are where",
"big query" makes this a bit hard

but what about just union all'ing

where oprid = 'xxxxx'

with

where provider_grp_id = '100841'

as the other predicate piece is "already covered" and just confusing the issue.
Rating: 5
Sorry February 09, 2004
Reviewer: PRS from USA
Hi Tom,
   I wrongly stated my answer in the previous reply. Sorry for that.

   But When I removed all outer joins 2nd query works fine.
But I cannot remove an outer join for following condition.
   AND b.person_id = f.person_id(+)
   As long as this outer join is there it is doing a full table scan on
PS_RD_PERSON_NAME(3+ million rows) and plan remains almost identical and query
takes 2 minutes. So I do not know what's going on.
Rating: 5
Is their any Standard pll or package available to generate web reports in Forms?? April 01, 2004
Reviewer: rajkumar from India
hi tom,

   Is their any standard PLL or Package available in
Forms 4.5/5.0/6i(Developer 2000) to generate the
excel-report in web by capturing the form values at runtime ?
  eg : i run dept-Form based on dept table, (on button click) at runtime to
capture all the form values(deptno-10,dname-Accounts,loc-India) including
lables(deptno,dname,loc) and these values to be displayed in IE-browser in
excel-format(grids)in the web? how to achieve this scenario ?

  Any Information related to above is their any Standard PLL available ? Pls
posted immd..

Thanks & Regards,
  rajkumar.
Followup:
don't know -- good question for the forms discussion forums on otn.oracle.com

I haven't touched forms since march, 1995 myself (when this web thing came into
being...)
April 01, 2004 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.
Rating: 4
February 24, 2004
Reviewer: A reader from NY,USA
Tom,
If the simple procedure likes:

CREATE OR REPLACE PROCEDURE INSERT_CUST_IN_ISS (  ZADD_TYPE IN CHAR,
          ZLNAME IN CHAR,
          ZFNAME IN CHAR,
          ZCNAME IN CHAR,
          ZACCOUNT_NO IN CHAR,
          ZADD1 IN CHAR,
          ZADD2 IN CHAR,
          ZADD3 IN CHAR,
          ZCITY IN CHAR,
          ZCOUNTY IN CHAR,
          ZSCODE IN CHAR,
          ZZIP IN CHAR,
          ZCTRY_CD IN CHAR,
          ZHPHONE IN CHAR,
          ZFAX IN CHAR,
          ZEMAIL IN CHAR,
          ZVAT_REG_NO1 IN CHAR,
          ZVAT_REG_NO2 IN CHAR,
          ZVAT_REG_NO3 IN CHAR,
          ZVAT_REG_NO4 IN CHAR,
          ZVIP_CODE IN CHAR,
          ZCREA_BY IN CHAR,
          ZMODIFIED_BY IN CHAR,
          ZSALUTATION IN CHAR,
          RTN OUT NUMBER ) IS
    BEGIN
    RTN := 0;
    INSERT INTO CUSTOMER_ITF (ADD_TYPE,
    LNAME,
    FNAME,
    CNAME,
    ACCOUNT_NO,
    ADD1,
    ADD2,
    ADD3,
    CITY,
    COUNTY,
    SCODE,
    ZIP,
    CTRY_CD,
    HPHONE,
    FAX,
    EMAIL,
    VAT_REG_NO1,
    VAT_REG_NO2,
    VAT_REG_NO3,
    VAT_REG_NO4,
    VIP_CODE,
    CREA_DATE,
    CREA_BY,
    MODIFIED_DATE,
    MODFIED_BY,
    SALUTATION) VALUES (ZADD_TYPE,
    ZLNAME,
    ZFNAME,
    ZCNAME,
    ZACCOUNT_NO,
    ZADD1,
    ZADD2,
    ZADD3,
    ZCITY,
    ZCOUNTY,
    ZSCODE,
    ZZIP,
    ZCTRY_CD,
    ZHPHONE,
    ZFAX,
    ZEMAIL,
    ZVAT_REG_NO1,
    ZVAT_REG_NO2,
    ZVAT_REG_NO3,
    ZVAT_REG_NO4,
    ZVIP_CODE,
    SYSDATE,
    ZCREA_BY,
    SYSDATE,
    ZMODIFIED_BY,
    ZSALUTATION);
    IF SQL%ROWCOUNT = 1 THEN
       COMMIT;
    ELSE
       ROLLBACK;
    END IF;
    RTN := SQL%ROWCOUNT;
    END;
/

and we want to pass all parameters as variables while execute the procedure
(instead of
explicity giving the value of parameters like:

variable rtn number;
exec INSERT_CUST_IN_ISS ('NY','UK',......,:rtn);  --explicity give the value of
parameters
print x;

What should we do?

Thanks.
Followup:
that is pretty useless - the sql%rowcount.  an insert values will always be one.

also a commit in a procedure like that -- bad idea.  how many transactions
consist of a single row like that?  I like the CLIENT deciding "commit" or
"rollback".  leads to reusable procedures that can be strung together into a
bigger transaction.

so, i would lose the rtn and the commit and the rollback.  none are needed or
even desirable........

In answer to your question -- in a real program (as opposed the to simplistic
command line interfact that is sqlplus) use bind variables for all inputs....
Rating: 5
February 25, 2004
Reviewer: A reader from NY,USA
Tom,
Could you provide the sample for use bind variables for all inputs in real
program?

Thanks in advance
Followup:
what language du jour would you like to see?

Here is pro*c

...
int a;
int b;
int c;

  exec sql execute
  begin
     proc( :a, :b, :c );
  end;
  end-exec;

.......
Rating: 3
February 25, 2004
Reviewer: A reader from NY,USA
Tom,
Let us say just use oracle PL/SQL program. How do it ? Should use another
procedure?

Thanks.
Followup:
variable rtn number;
exec INSERT_CUST_IN_ISS ('NY','UK',......,:rtn);  --explicity give the value of
parameters
print x;

declare
    a varchar2(2) := 'NY';
    b varchar2(2) := 'UK';
    ....
begin
    insert_cust_in_iss( a, b, .... );
end;
/

you still have to get the inputs there somehow.  I mean they won't magically
appear.

someone has to call plsql -- your 3gl, your application will.  That application
will BIND the inputs to the procedure insert_cust_in_iss.
Rating: 5
February 25, 2004
Reviewer: A reader from NY,USA
Thanks veru much for the response.
Rating: 5
What Bind Variable Rally is March 16, 2004
Reviewer: Subodh Deshpande from INDIA
Hi Tom,

Question:1   Please tell me what exactly a bind variable is? And what does a
query looks like with and without bind variable.

Question:2  Is it true that for each query without bind variable, oracle opens a
new cursor for every request to same query, and if that query uses bind
variable, oracle opens single cursor for all requests?

Thanx inadvance..
Followup:
If you have access to any of the first three books:
http://www.amazon.com/exec/obidos/search-handle-url/index=books&field-author=Thomas%20Kyte/103-3965780-7311008
I cover binds over and over and over and over and over (and then I go over them
again).

They are the single most important thing you will learn about in Oracle.

search this site for

"bind variables and parsing"
"bind variable"
"bind variables"

you'll see lots and lots of examples.  In a nutshell, a bind variable is a
parameter to a query.

Instead of submitting

select * from emp where empno = 1;
select * from emp where empno = 2;
select * from emp where empno = 3;
...
select * from emp where empno = 100322134;

to the database (each of which needs be compiled, optimized, consume lots of
resources) you would submit

select * from emp where empno = :x;

and at runtime plug in values 1, 2, 3, ..... 32343532 for :x.  compile the
statement once, execute and reuse that query plan over and over and over.

Without bind variables Oracle must compile each unique statement.  Not only is
this very resource intensive -- it is something that not a lot of people can do
simultaneously (eg: makes you not scale so good).   A cursor is a program thing,
a session thing.  Each session would open their own cursors regardless of binds
-- it is whether they are pointing to an already parsed and optimized SQL
statement in the shared pool (good thing, use binds) or whether they had to hard
parse a statement (bad thing, no binds)

read
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2588723819082
Rating: 4
OK March 17, 2004
Reviewer: Maer from CO,USA
Hi Tom,
I think you must be bored by saying "use bind variables,
use bind variables" over many years.
But I have a question and it is
 "Can an array be declared using a bind variable"?.Do you
have any simulated trick to do that, be it numeric array
or char array?
Followup:
in a program -- sure, absolutely.

pro*c supports binding collections and plsql tables
java/jdbc does
oci does
vb probably does too but I've never used it personally.

sqlplus however, does not have any facility to do that (as it is just a very
simple command line environment, not a programming environment)
Rating: 4
Dynamic reference a variable in plsql March 18, 2004
Reviewer: M Faisal from London
Can u help me how can we dynamically reference a variable in plsql (as we
reference in forms with NAME_IN built in).
here is simple code for explaination.
declare
 isd1 date;
 isd2 date;
 isd3 date;
begin
 for i in 1..3 loop
   update t1 c
    set c.qty = anyvalue /*changing dynamically for each loop */
   where c.dtd = isd1 ;/*(here i want to change variable name dynamically)*/
 end loop;
end;
/
Followup:
you cannot do that with local variables.  they would have to be package global
variables and you would use dynamic sql to access them.

sure looks like you want "an array" to me doesn't it?  then you have

isd(1)
isd(2)
isd(3)

which can be referenced via isd(i)....
Rating: 4
All about Bind variables March 29, 2004
Reviewer: Muhammad Ibrahim from China
Please clarify for my below questions. Eventhough i read your Expert one to one
and also read many of your answers in this forum still it is little bit
confusing.
Because i remeber in one of your example (may be in expert one to one or in
asktom
forum)you have given in for loop

For i 1..1000 Loop
  Insert Into t ( i );
                   -- instead of this you have asked to
                   -- use dynamic SQL with bind variable
                   --(USING clause)
End Loop;

Here is the question :-

Procedure Get_Something ( pName In Number )
Is
Begin

  For lRec1 In ( Select *
                 From t
                 Where  Name = pName
               )

    -- Now i do insert here

    Insert Into a ( Fk_Id )
            Values( lRec1.Id );

    Or

    Select 'Somthing'
    Into   Something
    Where  Some_Id = lRec1.Id;

    Or

    Update x
    Set    x.Id = lRec1.Id
    Where  Some other condition;

    Or

    update x
    Set    Something
    Where  x.Id = lRec1.Id;

    Or

    For lRec2 In ( Select *
                   From   abc
                   Where  Id = lRec1.Id )
    Loop
      ...
    End Loop;

  End Loop;

End Get_Something;

 Here lRec1.Id comes from the table t so the value will be keep changing. May be
thousands of records for lRec1. So here my first question if we use bind
variable using (Execute immediate) insteads of this static SQL will be faster? I
know your answer will use Static SQL but here i am feeling if i give Static Sql
lRec1.Id  will be keep changing so i am not sure whether it will be parsed each
time in the shared pool for each record of lRec1.Id if this is the case then if
do it using  Execute Immediare with USING clause which will be parsed only once.

 So if you say the above is the way then i feel the only possibility to use bind
variable is Dynamic sql where we might not know the table name or column name
etc which will be a  very rare situation(atleast inside PLSQL
functions/procedures). Is that right?

Thanks & Regards,
Ibrahim.
Followup:
static sql uses binds 100% in plsql (impossible to NOT use binds in static sql
in plsql)

my example used

  execute immediate 'insert into t values ( ' || i || ')';

vs

  execute immediate 'insert into t values ( :x )' using i;

I did not use static sql because that would have compared apples to bananas and
all i wanted to compare was "bind" vs "no bind"
Rating: 4
Please clarify March 29, 2004
Reviewer: Muhammad Ibrahim from China
Dear Tom,

1) Thats what my question why dont we use static sql instead you have
   used dynamic sql. That is in one of your example :
   Assume that below code is in a procedure inside PLSQL .

  For i 1..1000 Loop
   execute immediate 'insert into t values ( :x )' using i;

  End Loop;

  For i 1..1000 Loop
    Insert Into t ( i );
                   -- instead of this you have asked to
                   -- use dynamic SQL with bind variable
                   --(USING clause)
  End Loop;

2) My second question is

   " Here lRec1.Id comes from the table t so the value will be keep changing.
May be
thousands of records for lRec1. So here my first question if we use bind
variable using (Execute immediate) insteads of this static SQL will be faster? I

know your answer will use Static SQL but here i am feeling if i give Static Sql
lRec1.Id  will be keep changing so i am not sure whether it will be parsed each
time in the shared pool for each record of lRec1.Id if this is the case then if
do it using  Execute Immediare with USING clause which will be parsed only once
"

  Please consider all the DMLs and tell us if this will have some performance
issue
if we use like this.

Procedure Get_Something ( pName In Number )
Is
Begin

  For lRec1 In ( Select *
                 From t
                 Where  Name = pName
               )

    -- Now i do insert here

    Insert Into a ( Fk_Id )
            Values( lRec1.Id );

    Or

    Select 'Somthing'
    Into   Something
    Where  Some_Id = lRec1.Id;

    Or

    Update x
    Set    x.Id = lRec1.Id
    Where  Some other condition;

    Or

    update x
    Set    Something
    Where  x.Id = lRec1.Id;

    Or

    For lRec2 In ( Select *
                   From   abc
                   Where  Id = lRec1.Id )
    Loop
      ...
    End Loop;

  End Loop;

End Get_Something;

3) So if you say the above is the way then i feel the only possibility to use
bind
variable is Dynamic sql where we might not know the table name or column name
etc which will be a  very rare situation(atleast inside PLSQL
functions/procedures). Is that right?

eg(a piece of my real code):
---------------------------

lTable_Prefix   Varchar2(4)   := 'WIP_';
lWip_Table_Name Varchar2(100);
lSql_Statement    Varchar2(500);
lAction_Code    Varchar2(10)  := pAction_Code;

Begin
 For Prod_Table In Cfg_Api.C_Product_Tables( pProduct_Id )
  Loop
    lWip_Table_Name := lTable_Prefix||Prod_Table.Name;
    lSql_Statement  := 'Update '|| lWip_Table_Name || ' Set Action_Code = :v1
Where Contract_Id = :v2';
    Execute Immediate lSql_Statement Using lAction_Code, lContract_Id;
  End Loop;
End;

    Tom, You are the Leader for us to learn and make sure we are doing
correctly. Please clarify.
    Because like the above example Get_Something (eg:2)i have created many
procedures and funstions. So i just want to make sure i am in correct direction.

Thanks & Regards,
Ibrahim.
Followup:
1) I was trying to show the difference between

a) no binds
b) binds

if I used dynamic sql in plsql to do a) and then static sql in b) i would have
been showing the difference between

a) no binds, parses = executes (parse per execute) versus
b) binds, parse once, execute many

I would have been changing MANY things at a time.  I wanted to show binds/no
binds.  (later I show, "ok, now we are binding -- what else can we do -- hey,
Parse once, execute many)....

In testing things -- you change ONE thing at a time.  else you don't really know
WHAT lead to the change....

2) i don't really see a question -- however, when faced with:

should i use static or dynamic sql in plsql

the answer is STATIC SQL.  If you can use static sql (almost always true), DO
SO.

use dynamic sql only when you cannot (if you have my book Effective Oracle By
Design, i go into the many many reasons in detail)

3) yes, it is rare to be forced into dynamic sql.  static sql is superior.
Rating: 4
Thanks for your prompt reply March 29, 2004
Reviewer: Muhammad Ibrahim from China
Regarding the 2) point in my last posting :

We do it either :

a)

Procedure Get_Something ( pName In Number )
Is
Begin

  For lRec1 In ( Select *
                 From t
                 Where  Name = pName
               )

    -- Now i do insert here

    Insert Into a ( Fk_Id )
            Values( lRec1.Id );
  End Loop;
End Get_Something;

Or

b)

Procedure Get_Something ( pName In Number )
Is
Begin

  For lRec1 In ( Select *
                 From t
                 Where  Name = pName
               )

    -- Now i do insert here

    Execute Immediate 'Insert Into t Calues ( :x )' Using lRec1.Id;

  End Loop;
End Get_Something;

   In the above examples a) Insert is a Static Sql and b) Insert is a Dynamic
Sql. I agree "a" is the way to do it in PLSQL but basically i got a stupid doubt
in my head! If i use the b) it will parse only once (no need to parse for each
value of lRec1.Id right? ) but a) will
do for the each value lRec1.Id comes from record lRec1!!!
So eventhough we say Static is better but here b) parses the query only once.
Did i understand properly? Thatswhy i am curious why b) is not effective than a)
eventhough b) is a dynamic DML?

Regards,
Ibrahim.
Followup:
if you use B it will parse/execute and then parse/execute and so on.

native dynamic sql always parse/execute/closes the statement.

STATIC sql is what "doesn't parse" in plsql.  static sql is parsed ONCE per
session (given sufficient open_cursors of course).

STATIC SQL is good.
Dynamic SQL a tool to be used when FORCED.

The BEST way to code your routine is:

    insert into t select id from t where name = pName;

NO code!

Here is the proof:

ops$tkyte@ORA9IR2> create table t ( x int );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace procedure p1
  2  as
  3  begin
  4      for x in ( select object_id from all_objects )
  5      loop
  6          insert into t values ( x.object_id );
  7      end loop;
  8  end;
  9  /

Procedure created.

ops$tkyte@ORA9IR2> create or replace procedure p2
  2  as
  3  begin
  4      for x in ( select object_id from all_objects )
  5      loop
  6          execute immediate 'insert into t values ( :x )' using x.object_id;
  7      end loop;
  8  end;
  9  /

Procedure created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec runStats_pkg.rs_start;

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec p1

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec runStats_pkg.rs_middle;

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec p2

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec runStats_pkg.rs_stop(1000);
Run1 ran in 319 hsecs
Run2 ran in 687 hsecs
run 1 ran in 46.43% of the time

slower with dynamic sql... and even worse:

Name                                  Run1        Run2        Diff
STAT...redo size                 6,286,720   6,289,088       2,368
STAT...session cursor cache hi           3      28,106      28,103
STAT...opened cursors cumulati          19      28,125      28,106
STAT...parse count (total)              19      28,125      28,106
STAT...calls to get snapshot s      28,153      56,267      28,114
STAT...consistent gets             119,045     147,169      28,124
STAT...db block gets                29,636      85,895      56,259
LATCH.shared pool                      204      56,491      56,287
STAT...session pga memory           65,536           0     -65,536
STAT...session logical reads       148,681     233,064      84,383
LATCH.library cache pin                186     112,640     112,454
LATCH.library cache                    337     112,838     112,501
LATCH.cache buffers chains         380,558     550,695     170,137

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
456,104     910,068     453,964     50.12%

PL/SQL procedure successfully completed.

infinitely less scalable to boot -- look at the increased latching with
dynamic sql -- look at the parse count.

Here, if you HAD to use dynamic sql, you would use dbms_sql to avoid the parse
per execute overhead.
Rating: 5
How the procedure created? March 30, 2004
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
 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.

Riaz how the above procedure created as there is no into clause in the select
statement so it should generate error. Secondly in the
cr.claimant_sb_master@starr.lhr there is only one field?
Followup:
   insert into t SELECT ......;

is a valid sql statement.  he is inserting a single column into the into table
-- so the select can only have one column as well.
Rating: 3
OK March 30, 2004
Reviewer: Richard from Minneapolis,USA
Hi Tom,
Is it possible to create an array of bind variables in oracle?For example
I have a function f(dno in number) which takes the department number and returns
the enames working in that
dept.
sql>var enm varchar2(30)
sql>call f(20) into :enm

But it complains about more number of values.How to handle
this situation?
Please do reply.
Followup:
In "Oracle" -- yes, definitely.

In "SQLPlus" -- a simple command line tool -- No, it does not support complex
bind variables.

That function should return a ref cursor I would think, sqlplus can deal with
ref cursors.
Rating: 4
March 30, 2004
Reviewer: A reader
Hi Tom, is this true?

For optimal scalability, you really need to use DBMS_SQL.  Execute immediate,
even if properly implemented w/ binds, will do extra soft parsing.

Only w/ DBMS_SQL can you get the optimal parse once, execute many behavior.

Thanks
Followup:
if you are going to parse the same statement many times....

And you are not using a version that allows for bulk binds in native dynamic sql
(bulk binds offsets the parse issue).....
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96624/11_dynam.htm#16379
then DBMS_SQL (using the array interface as well!) can be more efficient yes.
If you have "Expert One on One Oracle", I cover this from a performance
perspective in the Dynamic SQL chapter.
Rating: 5
March 30, 2004
Reviewer: A reader
Thanks Tom
Rating: 5
March 30, 2004
Reviewer: A reader
Sorry, The same question Tom Again in Oracle 9i R2, gives the same answer ?

Hi Tom, is this true?

For optimal scalability, you really need to use DBMS_SQL.  Execute immediate,
even if properly implemented w/ binds, will do extra soft parsing.

Only w/ DBMS_SQL can you get the optimal parse once, execute many behavior.

Thanks
Followup:
dbms_sql, a procedural API, lets you parse once, execute over and over.

NDS (native dynamic sql), easier to code but parses ONCE per execute always.
Rating: 5
March 30, 2004
Reviewer: A reader
Thanks
Rating: 5
Thanks for your kind help March 30, 2004
Reviewer: Muhammad Ibrahim from China
Dear Tom,

  Thanks for giving us a clear understanding about Bind variables, Static and
Dynamic Sql.
  I understood very very very well that Static Sql will not be parsed in PLSQL
and DBMS_SQL will parse only once. So i was really curious to see the output
using Static SQL(where the value will be substituted each time from for implicit
cursor) and DBMS_SQL. My understanding from the below output both are taking
more or less similar time.  Even in this case we should use STATIC SQL thats
what my understanding frm you. Is that right?. Thanks a lot tom to make us
understand this bind variable stuff. You really got lot of patience to answer!

CREATE OR REPLACE procedure p1
as

Begin
  For x In ( Select Object_Id
                 From   All_Objects )
  Loop
    Insert Into ttt Values ( x.Object_Id );
  End Loop;
End;
/

CREATE OR REPLACE Procedure p3
As

lCursor    Int;
lStatus    Int;
Begin
  For x In ( Select Object_Id
                 From   All_Objects )
  Loop
    lCursor := Dbms_Sql.Open_Cursor;
    Dbms_Sql.Parse( lCursor,
                    'Insert Into ttt (x)
                    Values (:x)',
                    Dbms_Sql.Native );
    Dbms_Sql.bind_variable( lCursor, ':x', x.Object_Id );

    lStatus := Dbms_Sql.Execute( lCursor );
    Dbms_Sql.Close_Cursor(lCursor);
  End Loop;
End;
/

Customer@Ibm> exec runStats_pkg.rs_start;

PL/SQL procedure successfully completed.

Customer@Ibm> exec p1

PL/SQL procedure successfully completed.

Customer@Ibm> exec runStats_pkg.rs_middle;

PL/SQL procedure successfully completed.

Customer@Ibm> exec p3

PL/SQL procedure successfully completed.

Customer@Ibm> exec runStats_pkg.rs_stop(1000);
Run1 ran in 2209 hsecs
Run2 ran in 3571 hsecs
run 1 ran in 61.86% of the time

Name                                Run1      Run2      Diff
STAT...CPU used by this sessio       437     1,545     1,108
STAT...CPU used when call star       437     1,545     1,108
STAT...opened cursors cumulati         7    34,158    34,151
STAT...parse count (total)             7    34,158    34,151
STAT...consistent gets           138,606   172,783    34,177
STAT...session logical reads     174,606   208,798    34,192
LATCH.cache buffers chains       452,529   519,882    67,353
STAT...recursive calls            68,317   204,923   136,606
LATCH.library cache pin alloca       136   341,637   341,501
LATCH.shared pool                 34,454   375,959   341,505
LATCH.library cache pin           68,510   614,944   546,434
LATCH.library cache               68,844   854,203   785,359

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
705,876 2,787,058 2,081,182  25.33%

PL/SQL procedure successfully completed.

Customer@Ibm>
Followup:
youch.  static sql will be parsed *as little as possible* but at least once, and
maybe more.

dbms_sql will be parsed only when you tell us to parse it (at least once though
if you want to execute it)

native dynamic sql is parsed every time you "execute" or "open" it.

I'll say it as simple as I can, the rule is:

a) use static SQL if at all possible.  do not even consider dynamic sql unless
you *cannot do it statically*.  use static sql

b) use native dynamic sql if the statement will be executed a small number of
times in a session.  the programmer benefit of NDS far outweighs one or two
extra parses.  if you are just going to execute a statement "a couple of times",
AND you must use dyanmic sql, use NDS

c) use dbms_sql when you MUST use dynamic sql and you know you will be executing
the same statement many times in the same session.

Your procedure:

CREATE OR REPLACE Procedure p3
As

lCursor    Int;
lStatus    Int;
Begin
  For x In ( Select Object_Id
                 From   All_Objects )
  Loop
    lCursor := Dbms_Sql.Open_Cursor;
    Dbms_Sql.Parse( lCursor,
                    'Insert Into ttt (x)
                    Values (:x)',
                    Dbms_Sql.Native );
    Dbms_Sql.bind_variable( lCursor, ':x', x.Object_Id );

    lStatus := Dbms_Sql.Execute( lCursor );
    Dbms_Sql.Close_Cursor(lCursor);
  End Loop;
End;
/

it written entirely wrong.  it should be (MUST be)

CREATE OR REPLACE Procedure p3
As

lCursor    Int;
lStatus    Int;
Begin
    lCursor := Dbms_Sql.Open_Cursor;
    Dbms_Sql.Parse( lCursor,
                    'Insert Into ttt (x)
                    Values (:x)',
                    Dbms_Sql.Native );

  For x In ( Select Object_Id
                 From   All_Objects )
  Loop
    Dbms_Sql.bind_variable( lCursor, ':x', x.Object_Id );

    lStatus := Dbms_Sql.Execute( lCursor );
  End Loop;

    Dbms_Sql.Close_Cursor(lCursor);

End;
/

if you want to derive the benefits of parse once, execute many.
Rating: 3
How to make a function call March 31, 2004
Reviewer: Richard from Minneapolis,USA
Hi Tom,
I created a function which is as follows.But I run into problems while making
function calls.
Could you please provide a correct solution.

SQL> create or replace function myfunc(dno in number,rec_out out sys_refcursor)
  2  return sys_refcursor is
  3  l_cursor sys_refcursor;
  4  begin
  5   open l_cursor for select * from emp where deptno = dno;
  6  end;
  7  /

Function created.

SQL> var x refcursor
SQL> set autop on
SQL> exec dbms_output.put_line(myfunc(10,:x));
BEGIN dbms_output.put_line(myfunc(10,:x)); END;

      *
ERROR at line 1:
ORA-06550: line 1, column 7:
PLS-00306: wrong number or types of arguments in call to 'PUT_LINE'
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

How to make a function call for this type of function?Please
do reply.
Bye!
Followup:
umm, your function doesn't return anything....... and it assigned nothing to
rec_out.

see
http://asktom.oracle.com/~tkyte/ResultSets/index.html
i have examples there.
Rating: 3
OK April 01, 2004
Reviewer: Richard from Minneapolis,USA
Hi Tom,
Thanks for pointing out that error.I corrected that but get a problem with
function call.Please see below.

SQL> create or replace function myfunc(p_dno in number,p_out out
sys_refcursor)
  2  return sys_refcursor is
  3  l_cursor sys_refcursor;
  4  begin
  5   open l_cursor for select * from emp where deptno = p_dno;
  6   return l_cursor;
  7* end;
SQL> /

Function created.

SQL> var x refcursor
SQL> var y refcursor
SQL> call myfunc(10,:x) into :y
  2  /

Call completed.

SQL> print y

     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

But the following function call fails.How to correct this?Please do reply.

SQL> exec dbms_output.put_line(myfunc(10,:x))
BEGIN dbms_output.put_line(myfunc(10,:x)); END;

      *
ERROR at line 1:
ORA-06550: line 1, column 7:
PLS-00306: wrong number or types of arguments in call to 'PUT_LINE'
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

ERROR:
ORA-24338: statement handle not executed

SP2-0625: Error printing variable "x"

Thanks in advance.
Followup:
because what does it mean to print a ref cursor?  dbms_output takes strings,
numbers and dates and prints them.  it does not take ref cursors.

to print a ref cursor in sqlplus, you use "print"

you are not using that second parameter at all -- remove it. (but dbms_output
still won't 'print' it, sqlplus can fetch and print the results)
April 01, 2004 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.
Rating: 5
anti join March 31, 2004
Reviewer: A reader
Hi

I have this query to compare table structure differences

select table_name, column_name, data_type, data_length, data_precision
  from user_tab_columns a
 where column_name not in (select column_name
                             from user_tab_columns@oe_his b
                            where a.table_name = b.table_name)
   and table_name in (select nombre_tabla
                        from his_lista_tablas@oe_his)

this shows me the tables in my local database which has more columns than the
old tables using db link.

My question is I would like to show columns from user_tab_columns@VAN in my
query result as well. Since this is an anti join how can I accomplish this?
Followup:
in 9i, you could use a FULL OUTER join.

but, you don't have any versions :(  so I cannot tell you what to do....
Rating: 5
hi my version is 8.1.7.4 March 31, 2004
Reviewer: A reader
hi my version is 8.1.7.4

so we dont have full outer join :-(
Followup:
you would have to simiulate then

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
/

that is all the syntatic sugar of a "full outer join" does under the covers...

top half gets everything in T1 and if there is something in T2, gets it.  bottom
half gets everything in t2 that isn't in T1 since we already got that.
Rating: 5
hmm but I am trying to get this output April 01, 2004
Reviewer: A reader
Right now I have this output

TABLE_NAME         COLUMN_NAME  DATA_TYPE  DATA_LENGTH
----------------- ------------- ---------- -----------
OM_CUSTOMER_INST  COMMENTS      VARCHAR2          2000
OM_CUSTOMER_INST  COMMENTS      VARCHAR2          4000

I would like to have the following output which I dont think can be done using
full outer join :-?

TABLE_NAME         COLUMN_NAME  DATA_TYPE  DATA_LENGTH TABLE_NAME
COLUMN_NAME  DATA_TYPE  DATA_LENGTH
----------------- ------------- ---------- ----------- -----------------
------------- ---------- -----------
OM_CUSTOMER_INST  COMMENTS      VARCHAR2          2000 OM_CUSTOMER_INST
COMMENTS      VARCHAR2          4000
Followup:
a join is what is needed exactly to get that second output.

ops$tkyte@ORA9IR2> create table t1( tname varchar2(5), cname varchar2(5), dtype
varchar(10), dlen number );

Table created.

ops$tkyte@ORA9IR2> create table t2( tname varchar2(5), cname varchar2(5), dtype
varchar(10), dlen number );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t1 values ( 'T1', 'C1', 'VARCHAR2', 2000 );

1 row created.

ops$tkyte@ORA9IR2> insert into t1 values ( 'T1', 'C2', 'NUMBER', 38 );

1 row created.

ops$tkyte@ORA9IR2> insert into t2 values ( 'T1', 'C1', 'VARCHAR2', 4000 );

1 row created.

ops$tkyte@ORA9IR2> insert into t2 values ( 'T1', 'C3', 'DATE', null );

1 row created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select *
  2    from t1 full outer join t2 on ( t1.tname = t2.tname and t1.cname =
t2.cname )
  3  /

TNAME CNAME DTYPE            DLEN TNAME CNAME DTYPE            DLEN
----- ----- ---------- ---------- ----- ----- ---------- ----------
T1    C1    VARCHAR2         2000 T1    C1    VARCHAR2         4000
T1    C2    NUMBER             38
                                  T1    C3    DATE

ops$tkyte@ORA9IR2>
April 01, 2004 William -- Thanks for the question regarding "Shared Collection Across Sessions", version 9.1
You Asked
Tom,

I have a scenario which, through a complicated logic, I populate a set of
records in a collection which I want to make available to more than 1 session
(it is a web based applicaiton).

Is there a way of sharing such collection in a global sense?

William
and we said...
you would have to put it into a real database table to provide access to it
across sessions.  there are no database wide "globals" in that sense.

You can use a global application context if you like instead of a real database
table -- you'll have to live with the fact that the context is a rather "flat"
namespace (context, variable_name, variable_value -- 3 columns).

You could write a package to make this easier to set values -- but the concept
would be as follows:

ops$tkyte@ORA9IR2> create or replace context App_Ctx using My_pkg
  2  ACCESSED GLOBALLY
  3  /
Context created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace package my_pkg
  2  as
  3          procedure set_ctx( p_name       in varchar2,
  4                             p_value      in varchar2 );
  5  end;
  6  /
Package created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace package body my_pkg
  2  as
  3          procedure set_ctx( p_name       in varchar2,
  4                                             p_value      in varchar2 )
  5          as
  6          begin
  7                  dbms_session.set_context( 'App_Ctx', p_name, p_value );
  8          end;
  9  end;
 10  /

Package body created.

this is logic your code would want to automate -- the managing of the number
of rows, setting the variable names with the rownum in them:

ops$tkyte@ORA9IR2> exec my_pkg.set_ctx( 'nrows', 3 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec my_pkg.set_ctx( 'Var1_1', 'aVal1' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec my_pkg.set_ctx( 'Var2_1', 'aVal2' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec my_pkg.set_ctx( 'Var1_2', 'bVal1' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec my_pkg.set_ctx( 'Var2_2', 'bVal2' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec my_pkg.set_ctx( 'Var1_3', 'cVal1' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec my_pkg.set_ctx( 'Var2_3', 'cVal2' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> create or replace view v
  2  as
  3  select sys_context( 'app_ctx', 'var1_' || rownum ) var1,
  4         sys_context( 'app_ctx', 'var2_' || rownum ) var2
  5    from global_context
  6   where rownum <= sys_context( 'app_ctx', 'nrows' )
  7  /

View created.

we KNOW global context will have AT LEAST as many rows as we need (in fact, it
has lots more than we need since you have at least one column/row and maybe
more)

ops$tkyte@ORA9IR2> grant select on v to public;

Grant succeeded.

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

VAR1            VAR2
--------------- ---------------
aVal1           aVal2
bVal1           bVal2
cVal1           cVal2

there are our values -- log out and in:

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> @connect /
ops$tkyte@ORA9IR2> set termout off
ops$tkyte@ORA9IR2> set termout on
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select * from v;

VAR1            VAR2
--------------- ---------------
aVal1           aVal2
bVal1           bVal2
cVal1           cVal2

and they are still there, go in as another user:

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> @connect scott/tiger
ops$tkyte@ORA9IR2> set termout off
scott@ORA9IR2> set termout on
scott@ORA9IR2>
scott@ORA9IR2> select * from ops$tkyte.v;

VAR1            VAR2
--------------- ---------------
aVal1           aVal2
bVal1           bVal2
cVal1           cVal2

and there they are as well.
Review & Followup

Rating: 5
Thanks March 31, 2004
Reviewer: A reader
Where Oracle going to store this information? (In any table or in its memory
structures)

I tried to check the table name behind V_$GLOBALCONTEXT view and couldn't fine
any. Can you please let me how to check the table name behind this view?
Followup:
its in the SGA
Rating: 5
Is this a better way to implement DEBUG flags? March 31, 2004
Reviewer: Tom Best from PA
My inclination was to jump on this method as a faster way to check on debug
flags... concerned about the overhead of selecting from a table for every proc
call to see if debug is on.  Here it is, FYI, showing that selecting from the
application context takes about 5x longer than selecting from the cached table.
Is this a valid test?

This is based on the objects you've already created.

SQL> set timing on
SQL> begin
  2  declare i integer;
  3  var varchar2(8);
  4  begin
  5     for i in 1..100000 loop
  6             select var2 into var from v where var1 = 'bVal1';
  7     end loop;
  8  end;
  9  end;
 10  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:10.02

Now for the table version:

create table debug (var1 varchar2(12), var2 varchar2(12));

insert into debug select * from v;

commit;

SQL> begin
  2  declare i integer;
  3  var varchar2(8);
  4  begin
  5     for i in 1..100000 loop
  6             select var2 into var from debug where var1 = 'bVal1';
  7     end loop;
  8  end;
  9  end;
 10  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:02.08
SQL>
Followup:
I myself use a table for my debug.f routines.
Rating: 5
Thanks March 31, 2004
Reviewer: A reader
Thanks for your quick answer.

As I know that you can create a view on a table or synonym. I didn't see any
underlaying table for v_$globalcontext. How come V_$GLOBALCONTEXT got created on
a memory structure? Is it ok to create a view on memory structures?
Followup:
v$ tables are magic.
Rating: 5
Thanks again March 31, 2004
Reviewer: A reader
:)

I will greatly appreciate if you can direct me to any links that describe/solve
this magic.
Followup:
they are just magic, they are sometimes based on really magic x$ tables,
sometimes on real tables, sometimes a mixture of the two.

See the reference guide for what is in them.  What they "are" isn't nearly as
important (to us, not important at all).  Whey the "contain" -- very relevant.
Rating: 5
Thanks March 31, 2004
Reviewer: A reader
Got it. Thanks for your patience.
Rating: 4
Global Application Context April 01, 2004
Reviewer: Dean Becker from Rome, GA
Tom

I know that you know this, but I wanted to mention it for those who don't...

You mentioned the use of a global application context, and that is a fairly good
method of sharing non-transactional data *among a single instance*.
Unfortunately it doesn't work so well in a RAC environment; each instance has
its own "global context" value.

It's just a wrinkle that you have to watch out for if you ever move to a RAC
environement and want to use a global application context.
Followup:
good point, thanks for pointing it out -- it is in the Sga of that instance.
April 01, 2004 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
Rating: 5
Question Reg. MAterialized view export/import January 29, 2004
Reviewer: Sami from NJ,USA
Dear Tom,

Materialized View(MV1) has been created using the below query.

SELECT
    "object1"."ID" "ID",
    "object1"."FILEOBJECT" "FILEOBJECT",
    "object1"."LAST_CHG_TMS" "LAST_CHG_TMS",
    "object1"."LAST_CHG_USR_ID" "LAST_CHG_USR_ID"
FROM     "object1"@mydb_link1 "object1"
WHERE  EXISTS
    (
    SELECT     "object2"."INSTR_ID"
    FROM     "object2"@mydb_link1 "object2"
    WHERE     "object2"."INSTR_ID"="object1"."ID"
    AND      EXISTS
        (
        SELECT     "object3"."DISTRIBUTION_ID"
        FROM     "object3"@mydb_link1 "object3"
        WHERE     "object2"."DISTRIBUTION_ID"="object3"."DISTRIBUTION_ID"
        AND "object3"."NAME"='Dot Com'))

Exported from DB1 and imported into DB2 this into another schema.

We got one EXTRA COLUMN DISTRIBUTION_ID where i imported.

I do not understand this hehavior could you please enlighten me.

DB1
===

select * from dba_tab_columns where column_name='DISTRIBUTION_ID';
no rows selected.

select index_name from dba_ind_columns where column_name='DISTRIBUTION_ID';

INDEX_NAME
----------
SYS_I_SNAP

DB2
===

select * from dba_tab_columns where column_name='DISTRIBUTION_ID';
no rows selected.

select index_name from dba_ind_columns where column_name='DISTRIBUTION_ID';
INDEX_NAME
----------
SYS_I_SNAP
Followup:
umm, i don't see any differences between db1 and db2 here?
Rating: 5
Sorry Tom January 30, 2004
Reviewer: Sami
Dear Tom,

I hate to bother you but I did not get an answer anywhere else.

Please look at the correct/detailed infor from DB1 and DB2.
OBJECT1 is a materialized view created using

SELECT
    "object1"."ID" "ID",
    "object1"."FILEOBJECT" "FILEOBJECT",
    "object1"."LAST_CHG_TMS" "LAST_CHG_TMS",
    "object1"."LAST_CHG_USR_ID" "LAST_CHG_USR_ID"
FROM     "object1"@mydb_link1 "object1"
WHERE  EXISTS
    (
    SELECT     "object2"."INSTR_ID"
    FROM     "object2"@mydb_link1 "object2"
    WHERE     "object2"."INSTR_ID"="object1"."ID"
    AND      EXISTS
        (
        SELECT     "object3"."DISTRIBUTION_ID"
        FROM     "object3"@mydb_link1 "object3"
        WHERE     "object2"."DISTRIBUTION_ID"="object3"."DISTRIBUTION_ID"
        AND "object3"."NAME"='Dot Com'))

DB1 (From where I exported)
=============================

SQL> desc OBJECT1
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 ID                                        NOT NULL CHAR(10)
 FILEOBJECT                                         BLOB
 LAST_CHG_TMS                                       DATE
 LAST_CHG_USR_ID                                    VARCHAR2(16)

SQL> select table_name from dba_tab_columns where column_name='DISTRIBUTION_ID';

no rows selected  <==***

select index_name from dba_ind_columns where column_name='DISTRIBUTION_ID';

INDEX_NAME
----------
SYS_I_SNAP

DB2 (From where I imported)
=============================

SQL> desc OBJECT1
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 DISTRIBUTION_ID                                    CHAR(10) <==***
 ID                                        NOT NULL CHAR(10)
 FILEOBJECT                                         BLOB
 LAST_CHG_TMS                                       DATE
 LAST_CHG_USR_ID                                    VARCHAR2(16)

select table_name from dba_tab_columns where column_name='DISTRIBUTION_ID';

SQL> select table_name from dba_tab_columns where column_name='DISTRIBUTION_ID';

TABLE_NAME
------------------------------
OBJECT1  <==***

select index_name from dba_ind_columns where column_name='DISTRIBUTION_ID';

INDEX_NAME
----------
SYS_I_SNAP

SQL> select distinct DISTRIBUTION_ID from object1;

DISTRIBUTI
----------
2

SQL> select count(DISTRIBUTION_ID) from object1;

COUNT(DISTRIBUTION_ID)
----------------------
                 12881

Please enlighten me
1) why DB2 got extra column DISTRIBUTION_ID (which is not in DB1)

2)From where did it get teh value of "2" for all 12881 records.

Your help is really appreciated.
Followup:
do an imp .... show=y and show us the create materalized view command that pops
out (when you do show=y, it'll not touch the database, just print to screen what
it would do)
Rating: 5
show=y output January 30, 2004
Reviewer: Sami
Dear Tom,
As you instructed...

 "importing MY_SCHEMA's objects into MY_SCHEMA
 "ALTER SCHEMA = "MY_SCHEMA""
 "CREATE TABLE "OBJECT1" ("DISTRIBUTION_ID" CHAR(10),"
 ""ID" CHAR(10) NOT NULL ENABLE, "FILEOBJECT" BLOB, "LAST_CHG_TMS" DATE"
 ", "LAST_CHG_USR_ID" VARCHAR2(16))  PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRAN"
 "S 255 LOGGING STORAGE(INITIAL 52428800 NEXT 10485760 MINEXTENTS 1 MAXEXTENT"
 "S 515 PCTINCREASE 0 FREELISTS 2 FREELIST GROUPS 2 BUFFER_POOL DEFAULT) TABL"
 "ESPACE "NONCRM_DATA" LOB ("FILEOBJECT") STORE AS  (TABLESPACE "NONCRM_DATA""
 " ENABLE STORAGE IN ROW CHUNK 2048 PCTVERSION 10 NOCACHE  STORAGE(INITIAL 52"
 "428800 NEXT 10485760 MINEXTENTS 1 MAXEXTENTS 515 PCTINCREASE 0 FREELISTS 1 "
 "FREELIST GROUPS 1 BUFFER_POOL DEFAULT))"
 "GRANT SELECT ON "OBJECT1" TO "MY_SCHEMA_USER""
 "GRANT SELECT ON "OBJECT1" TO "HIBM_READ_ROLE""
 "ALTER SCHEMA = "MY_SCHEMA""
 "ALTER TABLE "OBJECT1" ADD  CONSTRAINT "GC_FILEOBJECT_PK" PRIMARY KEY "
 "("ID") USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 52428"
 "800 NEXT 10485760 MINEXTENTS 1 MAXEXTENTS 515 PCTINCREASE 0 FREELISTS 1 FRE"
 "ELIST GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "NONCRM_DATA" ENABLE "
 "COMMENT ON TABLE "OBJECT1" IS  'snapshot table for snapshot MY_"
 "SCHEMA.OBJECT1'"
 "ANALYZE  TABLE "OBJECT1"  ESTIMATE STATISTICS "
 "ALTER TABLE "OBJECT1" ENABLE CONSTRAINT "GC_FILEOBJECT_PK""
Import terminated successfully without warnings.
Followup:
something funny here -- an export of a materialized view would be -- well -- a
materialized view.

Sooooo , you must have exported the "base table" for the mv -- not the MV itself
and hence, you are getting what Oracle really created (which just happend to
include the necessary column for filtering - but would be HIDDEN when used in
the MV base table) to store and manage the MV data.

If you export the schema and import the schema (getting the MV instead of the
table supporting the MV), you won't see this.

Nothing wrong here -- other then you exported the table under the MV, not the MV
itself.
Rating: 5
Export and Import for MV based on DBLink February 04, 2004
Reviewer: Saminathan from NJ,USA
Dear Tom

<AskTom>
If you export the schema and import the schema (getting the MV instead of the
table supporting the MV), you won't see this.
</AskTom>

I did export and import the entier schema but still I see the same behavior.

In the source DB(DB1 -where I exported), the MV has been created based on the
tables in REMOTE DB (say DB3) using DB link (say DB_LNK1). But in the target DB(
DB2- when i imported) the DB LINK is not available.

Do you think it is because of missing DB link. I don't have right to create
DBLink  to remote site on DB2(when i imported)

Tom, As always I am really appreciate your response.
Followup:
well, not that I see what you see however.....

if you have no dblinks on the other site -- to what end is this going?  you
cannot create the mv anyhow????

I'd have to see the export log from the original export -- but I don't think you
dd what you say-- if you did, there would be a create materialized view command
in there, not a create table (the export is what puts stuff in the dmp file,
import just reads it.  export has no clue the dblinks won't exist elsewhere so
your theory about the dblinks does not work)
Rating: 5
materialized views caching March 31, 2004
Reviewer: A reader from US
Tom,
one more question on materialized views. We had a discussion with our DBA and he
told me that it's not possible to cache materialized view (I don't know what
does it mean). My understanding was that when you query MV it works exactly like
an ordinary table will. Could you please clarify this?
Thanky you very-very much for all you are doing for us!!!
Vlad
Followup:
it works just like a regular table -- because it is a regular table under the
covers.

It is as cacheable as ANYTHING else out there.
Rating: 5
some more details on previous question.. April 01, 2004
Reviewer: vll
Tom,
thanks a lot for your answer! We talked to our DBA and it looks like I didn't
provide you all the details of our problem. If you don't mind, I'm going to add
them to my original question :-)) So:
1. We have 2 systems: S1 and S2 on different boxes. Both systems are 24/7.
2. S1 has absolutely critical response time requirements.
3. S1 needs to access some data from one table on S2. Average frequency of
querying this table - 6 calls/second.
4. Due to #2 it's not acceptable for S1 to go over a DB link for this data.
Requirement is to have copy of this data local on S1.
5. Data in S2 changes not too often. (May be 10-50 record/day at max).
We are working on S2. Our initial suggestion for S1 was to use MV on S1 for
having local copy of this data. Objection was: currently S1 uses KEEP POOL for
caching and it is not possible to use KEEP POOL for MV. Now I think I provided
all the details. Tom, what do you think can be the best solution for this case?
Thank you very much!!!
Followup:
why isn't it possible?

did they try it and find it would not work?

and why would it need to be in the keep pool necessarily anyway (funny thing
about caches -- if you use it, stuff stays there.  if you do not use it, it goes
away)

It is a trivial trial (why more people just don't "try stuff" i don't know....)

ops$tkyte@ORA9IR2> create materialized view mv
  2  storage( buffer_pool keep )
  3  as
  4  select * from t;

Materialized view created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select table_name, BUFFER_POOL from user_tables where
table_name = 'MV';

TABLE_NAME                     BUFFER_
------------------------------ -------
MV                             KEEP

but supposing it didn't work -- SO WHAT?  whats the backup plan then?  and why
would it have to go into the keep pool.  has someone shown that if it did not,
it would be bad?  Heck, you could even set up an odd sized tablespace
(different, non-default blocksize) and put the mv in there and give it its OWN
buffer cache if you were in such a bind.
April 01, 2004 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://download-west.oracle.com/docs/cd/B10501_01/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>
Rating: 5
open_cursors April 01, 2004
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
I have Oracle 9i R2 application and 9i R2 database server. The open_curosrs were
default on db server. The application server stopped responding so it was
suggested by the application developer that open_cursors should be increased.
Now let me know,

1- Is there any calculations method for open_cursors?
2- If application is not using bind variables so is this the reason that we have
to increase open_cursors?
3- If the open_cursors are increased then what will be the performance impact on
the db server and the memory usage?

Thanks for your time.
Followup:
why would you increase open_cursors?

why wouldn't you figure out "what happened"?

Unless you are getting ora-1000 max open cursors exceeded, upping it won't fix
anything.

1) you need to set it high enough to not get ora-1000, it is dependent on the
applications needs

2) no, you have a bug in the code that the java developers MUST fix though.

3) if you are not hitting ora-1000, it will change nothing (since you are not
using the cursors you currently have)

why an app server "not responding" would give a gut reaction of "up open
cursors" is beyond me though..
Rating: 5
Nice info. April 01, 2004
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
Ya you are right that it should be only increased when the error hits. I would
like to know that what kind of bug do you think may be in the application,

2) no, you have a bug in the code that the java developers MUST fix though.

No use of baind variables may be one of the reasons?
Followup:
there are an infinite number of possible bugs.

not using bind variables in a transactional application is definitely a bug.
April 01, 2004 Graham -- Thanks for the question regarding "Selecting rows that have changed via timestamp problematic with non-blocking reads", version 8.1.7 and up
You Asked
I have a problem related to non-blocking reads with Oracle. Our users want to
periodically run queries (via our app) that get changes since the last time they
ran the query. We add a last_modified timestamp column to each table, and use
triggers to set that to the current system time whenever the row is inserted or
updated (we separately track deletes, but lets ignore that for now). For a DBMS
with blocking reads, we can just do:

SELECT ... WHERE last_modifed >= last_checked_by_this_user

However, doing the above with Oracle's non-blocking reads, we can miss changes,
if the the last_modified_time is earlier than last_checked_time but it was not
committed before last_checked_time. For example (where TXN1 is some other
transaction and TXN2 and TXN3 are consecutive runs for our application):

Time    Event
T0    row R1 has value X (and last_modified < last_checked_by_this_user)
T1    TXN1 begins
T2    TXN1 updates R1 from X to Y, setting last_modified to T2
T3    TXN2 begins (and notes T3 for next last_checked_by_this_user)
T4    TXN2 does SELECT ... WHERE last_modifed >= last_checked_by_this_user
    -> Correctly finds no changes for R1
T5    TXN2 COMMITs
T6    TXN1 COMMITS
T7    TXN3 begins (and notes T7 for next last_checked_by_this_user)
T8    TXN3 does SELECT ... WHERE last_modifed >= T3
    -> Incorrectly finds no changes for R1

I effectively need to set last_modified to the commit time (T6) for something
like the above to work with Oracle's non-blocking reads. I have to do it with
triggers, because we don't have control over the other transactions running
against the database. There are also many users, so we can't simply flag rows as
changed or not. I could set the next last_checked_by_this_user time to be the
start time of the oldest open transaction (via V$TRANSACTION), but then I could
get some of the same changes over again the next time. Note that I only want to
fetch the most recent value if there has been a change; I don't need to see each
change.

I think this must be a common problem for anyone who periodically wants to run
reports, without getting the same data repeated in subsequent reports, but I
haven't found or come up with a solution.

Perhaps I need to use SCNs instead of timestamps, but I'd prefer to use
timestamps if possible (so users can understand them if need be).

Any suggestions?

In case you are wondering if it would work at all with a blocking database,
here's some more clarification.

It does work in databases with blocking reads, because:
- the last_checked time is set at the beginning of the transaction.
- if another transaction updates a row after it was scanned, then it will have a
newer (or equal) timestamp. Note that the query used "greater than or equal to".
- if another transaction updates a row before (or at the same time) it is
scanned, then the read blocks until the update is committed, and the scan will
pick up the change.

So no changes are missed with blocking reads.

You could get the same change twice only if you repeated the scan within the
timestamp resolution. Theoretically that is more of a problem with Oracle's 1
second precision (other DBMSes we use have millisecond or microsecond timestamp
precision), but please assume that our business rules don't allow subsequent
queries to be that close together. (In fact, we can handle duplicates if they
arise, but saying it can happen if a query is re-run within a second is much
better than saying it can happen if the query is re-run within the time of the
longest transaction, which could be arbitrarily long depending on what else our
customer is doing with the database.)

Is there a way to query for changes since the last query with Oracle's
non-blocking reads?

It seems like userenv('commitscn') does something like what I'm after with SCNs,
but that's undocumented and unsupported, and I'd need a different unique
transaction identifier to put on each row (which was linked to the commitscn),
since the commitscn can only be in one row. Is there a built-in identifier that
I could use (that wouldn't wrap for the lifetime of the database)?
and we said...
Well, I beg to differ with your analysis of the blocking read.  You are assuming
ALL reads will be done via an index on the affected column.  If for any reason
that index is not used - the scenario I describe below is not only 100% possible
-- but 100% PROBABLE.  All that would have to happen is for a row to be inserted
on a block you've already processed.  Committed read in blocking databases =
wrong answers, this is one of the definite cases.

You've always been subjected to this UNLESS you are 100% sure you are ALWAYS
using an index on this timestamp field.

with blocking reads you have the same opportunity to "miss".

time          event
-------       -----------------------
t0            you start reading data looking for everything past NOON (last
              read time)
t1            you read along, you've processed block 1, block 2, block 3...
t2            another transaction comes along and inserts data.  data goes
              onto block 1.

well, you just missed that row didn't you.  you won't re-read block one again.

That assumes you set your timestamp for the next query to be the time AT THE END
of the query (again, you could be missing a second of modifications in there as
well since the getting of the timestamp and the getting of the last row are not
atomic operations)

If we assume your timestamp for the next query is to be the time AT THE
BEGINNING of this query -- then you definitely re-read data twice.

Anyway -- the approach in Oracle would be as follows.  the share mode lock will
allow concurrent reads -- but will prohit modifications and it'll wait for
outstanding modifications to complete.  So, the reads will block writes instead
of the other way around.  We use an autonomous transaction so we can give up the
lock almost immediately after getting it -- since we use multi-versioning and
the result set associated with a cursor is pre-ordained at the point in time we
opened the cursor (no, we don't copy the data anywhere, we use versioning to
provide a read consistent, correct result set as of the point in time you opened
the cursor as we fetch the data).  The commit will not affect any in-flight
transactions your session has.

This will allow you to use your timestamp (in 9i and above, use timestamp, it
goes to the millisecond -- not available in 8i) safely.

ops$tkyte@ORA9IR2> create table t ( x int, last_updated date );

Table created.

ops$tkyte@ORA9IR2> insert into t values ( 1, sysdate );

1 row created.

ops$tkyte@ORA9IR2> commit;

Commit complete.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace package my_pkg
  2  as
  3          type rc is ref cursor;
  4          procedure get_result_set_as_of( p_time in date, p_result_set out rc
);
  5  end;
  6  /

Package created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace package body my_pkg
  2  as
  3          procedure get_result_set_as_of( p_time in date, p_result_set out rc
)
  4          is
  5                  pragma autonomous_transaction;
  6          begin
  7                  lock table t in share mode;
  8                  open p_result_set for
  9                  select * from t where last_updated >= p_time;
 10                  commit;
 11          end;
 12  end;
 13  /

Package body created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> variable x refcursor
ops$tkyte@ORA9IR2> exec my_pkg.get_result_set_as_of( sysdate, :x );

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> print x

         X LAST_UPDA
---------- ---------
         1 01-APR-04
-------------------------------------------------------------------------
Review & Followup

Rating: 4
April 01, 2004
Reviewer: Graham from Cambridge, ON Canada
I accept your point about missing data with a last_read timestamp from the *end*
of a query. But I tried to be clear that we don't do that.

My example showed that our next last_checked timestamp is noted at the
*beginning* of the query's transaction, not the end. I also repeated that later
as a bullet. I think you'll agree that we don't miss any changes with blocking
read databases, but you're right that we can get some duplication between
successive queries. I forgot to mention that duplication can occur in the
following case:

- Changes with a timestamp the same as the last_checked timestamp, that were
commited before the last query ran. Those will have been fetched in the last
running of the query as well as in the current one.

As I said, we can deal with duplicates but it would be better if the duplication
window were as small as possible (i.e. 1 second with Oracle 8i and 1 microsecond
with Oracle 9i and above). However the best solution I had previously (of using
the start time of the oldest open transaction, via V$TRANSACTION) had a
duplication window of the longest transaction.

I disagree that I assumed indexed reads. That's irrelevant. Note that the
duplication I was talking about was between successive runs of the query by the
same user. Phantom rows will of course be possible unless serializable isolation
is used regardless of database type. (I was assuming Oracle's default of read
committed isolation.)

Thanks for your suggestion of using a share mode lock on the table and
autonomous_transaction. If I understand correctly, that gets the duplication
window down to the timestamp precision, as it is by default with blocking-read
databases. FYI, when a share mode lock is waiting "for outstanding modifications
to complete" that effectively turns the read with that lock into a blocking
read, so your suggestion is basically to use blocking reads.

Note that your suggestion of locking the whole table is more severe than the row
or page level locking that a blocking-read database will do to accomplish the
same thing. However putting it an autonomous transaction amelioriates that
somewhat.

So it seems to be a tradeoff between minimizing the duplication window and
performance issues from adding share mode table locking.

(Sorry to nitpick some more, but do you not consider the RBS a copy of the
data?)

Thanks again for your help!
Followup:
RBS is not a copy of the data - RBS provides a purpose.

In your other databases -- they put the RBS in the redo, we just keep it
separate.  Helps us perform better (we only write redo, we do not read redo
during normal operation -- as redo is a serious point of serialization in
databases -- anything we do to avoid having to ever read it is "a good thing
(tm)".  rollback is not a point of serialization.
Rating: 3
Why the headache? April 01, 2004
Reviewer: Gabriel from close to ... Cambridge, ON Canada
<quote>Our users want to periodically run queries (via our app) that get changes
since the last time they
ran the query.</quote>

My reasoning …
This is an application with named users … you have to persist the
last_checked_by_this_user date  … when you do that, rather than saving just the
DATE, save the result set of the query … next time round do the following:
1.    run the query to get the latest result set
2.    compare the new result set against the previous one
3.    if the same then return nothing
     if different then delete the old result set, store the new result set and
return the new result set

In essence you have to return  “<Latest ResultSet> Minus <Saved ResultSet>” and
“Merge the <Latest ResultSet> over <Saved ResultSet>”.

Going after v$transaction, SCN, RBS, non-blocking reads, redo or whatever is
like trying to split the atom … only thinking about them when building a
business application should raise a big red flag … I would argue that, for the
said requirement, there is no solution short of some sort of data versioning
(but not going down into the guts of Oracle … go the other way and snap some
copies).

Hope I didn’t oversimplify.
Rating: 4
Are you taking shortcuts here? April 02, 2004
Reviewer: Mike from Cleveland, OH USA
I agree that this seems to be getting too deep into the internals. The business
requirement should be more precisely stated.

This is more than a 'convenience' requirement to avoid forcing the users to see
results they had seen previously. If it was just a 'convenience', then you would
not be getting complaints about a few rows being displayed more than once.

Your requirement sounds like: log every row that is 'seen' by this user. Do not
show the row again unless it has a change. This makes the viewing of the data a
business transaction.

If this is the case, you should design to that requirement. It sounds like you
are trying to take shortcuts here.

You could:
- Have a child table of the viewed data, keyed by the key of the viewed data
plus the username
- When you view the data, exclude any that have a child for this user. Insert a
child row for every row that is displayed.
- When your trigger updates the timestamp, delete all of its children.

You might also ask questions like:
What if the user requests the data, but never scrolls all the way to the end?
April 01, 2004 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.
Rating: 5
Bulk collect slower March 12, 2004
Reviewer: A reader
Hi Tom, I take 2 days investigating (your book effective design helped me)

But after I move to bulk collect it is slower here are on example of the code
and the statistcs if you can please help me
I run twice to be sure there was not other reason
The sum of all this modifications had caused a notorious increment on time of
processing (instead of reducing it)
there were les cpu usage, but any way it took more time (The code is in a
function in the server ) 9ir2S.

I increased the parameter 20%, and the speed was the same than before
looking at the db file sequential read there were an important increase,
even when they are few records as you can see in the eaxmple.

Maybe I lost some point about the implementation, in bulk insert you have to add
more more memory?

The changes had been done in this way

---NO BBBUUULLKK COLLEEEECCCTT

 CURSOR uCli IS SELECT CLF_CODCLI, CUF_CTACORR, CLF_TIPOCLI, (CUF_DBCUO -
CUF_CRCUO) CUOTAS
                  FROM CLIENTESF, CUENTASF
                 WHERE CLF_CODCLI = CUF_CODCLI
                   AND ( CLF_LRETIVA = 'T'
FOR uCli1 IN uCli LOOP
     INSERT INTO FON.IVA_RW
      ( IVA_FECHA, IVA_CODCLI, IVA_CTACORR, IVA_MONTO, IVA_CUOTAS, IVA_DSC )
     VALUES
      ( dFecha, uCli1.CLF_CODCLI, uCli1.CUF_CTACORR, nMonto, nCuota, cDs );

     UPDATE CUENTASF SET
        CUF_IVA = NVL(CUF_IVA,0) + ABS(NVL(nCuota,0)),
        CUF_MONTO_IVA = NVL(CUF_MONTO_IVA,0) + ABS(NVL(nMonto,0)),
        CUF_CRCUO = CUF_CRCUO - nCuota
     WHERE CUF_CODCLI = uCli1.CLF_CODCLI
       AND CUF_CTACORR = uCli1.CUF_CTACORR;
END LOOP;

---BBBUUULLKK COLLEEEECCCTT

 tCLF_CODCLI   DBMS_SQL.NUMBER_TABLE;
 tCUF_CTACORR  DBMS_SQL.NUMBER_TABLE;
 tCLF_TIPOCLI  DBMS_SQL.VARCHAR2_TABLE;
 tCUOTAS       DBMS_SQL.NUMBER_TABLE;
 tNACIONALIDAD DBMS_SQL.VARCHAR2_TABLE;

 tMONTOIVA     DBMS_SQL.NUMBER_TABLE;
 tCUOTAIVA     DBMS_SQL.NUMBER_TABLE;

SELECT CLF_CODCLI, CUF_CTACORR, CLF_TIPOCLI, (CUF_DBCUO - CUF_CRCUO) CUOTAS,
CASE WHEN CLF_TIPOCLI = 'E' THEN
( SELECT EMP_NACIONALIDAD FROM EMPRESAS
  WHERE EMP_RUC = CLF_CODCLI )
 ELSE
(  SELECT PER_NACIONALIDAD FROM PERSONAS
  WHERE PER_CI = CLF_CODCLI ) END AS NACIONALIDAD
BULK COLLECT INTO
 tCLF_CODCLI  ,
 tCUF_CTACORR ,
 tCLF_TIPOCLI ,
 tCUOTAS      ,
 tNACIONALIDAD
FROM CLIENTESF, CUENTASF
WHERE CLF_CODCLI = CUF_CODCLI AND ( CLF_LRETIVA = 'T'

   FORALL i IN 1..tCUF_CTACORR.count
     INSERT INTO FON.IVA_RW
      ( IVA_FECHA, IVA_CODCLI, IVA_CTACORR, IVA_MONTO, IVA_CUOTAS, IVA_DSC )
     VALUES
      ( dFecha, tCLF_CODCLI(i), tCUF_CTACORR(i),  tMONTOIVA(i), tCUOTAIVA(i),
cDs );

   FORALL i IN 1..tCUF_CTACORR.count
     UPDATE CUENTASF SET
        CUF_IVA = NVL(CUF_IVA,0) + ABS(NVL(tCUOTAIVA(i),0)),
        CUF_MONTO_IVA = NVL(CUF_MONTO_IVA,0) + ABS(NVL( tMONTOIVA(i),0)),
        CUF_CRCUO = CUF_CRCUO - tCUOTAIVA(i)
     WHERE CUF_CODCLI = tCLF_CODCLI(i)
       AND CUF_CTACORR = tCUF_CTACORR(i);

--------STATSITICS BULK

INSERT INTO FON.IVA_RW
      ( IVA_FECHA, IVA_CODCLI, IVA_CTACORR, IVA_MONTO, IVA_CUOTAS, IVA_DSC )
     VALUES
      ( :b6, :b1, :b2,  :b3, :b4, :b5 )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.84      96.16       3603         31      32740        2311
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.84      96.16       3603         31      32740        2311

--------STATSITICS NO BULK

INSERT INTO FON.IVA_RW
      ( IVA_FECHA, IVA_CODCLI, IVA_CTACORR, IVA_MONTO, IVA_CUOTAS, IVA_DSC )
     VALUES
      ( :b6, :b5, :b4, :b3, :b2, :b1 )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute   2311      2.32      76.68       2711         15      37444        2311
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2312      2.32      76.68       2711         15      37444        2311

---- statistics bulk
********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       46      0.00       0.00          0          0          0           0
Execute    245      0.32       0.46          5       1206       1133          40
Fetch      203      0.65       2.75       1039       8460          0         203
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      494      0.98       3.22       1044       9666       1133         243

Misses in library cache during parse: 0

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                     370        0.00          0.00
  SQL*Net message from client                   369       66.26        123.39
  db file sequential read                        35        0.02          0.13
  SQL*Net more data to client                    37        0.00          0.00
  db file scattered read                        129        0.11          2.01
  SQL*Net message to dblink                     110        0.00          0.00
  SQL*Net message from dblink                   110        0.11          0.30
  SQL*Net more data from dblink                  50        0.00          0.00
  log file sync                                   4        0.11          0.20

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse     6858      0.26       0.29          1          0          2           0
Execute  84204     14.29     233.51       9997      24023     136841       15819
Fetch    84233    113.60     209.59      33324     635953          0       89223
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   175295    128.17     443.40      43322     659976     136843      105042

Misses in library cache during parse: 29

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  db file sequential read                     10712        0.22        192.55
  db file scattered read                       4152        0.36         70.31
  SQL*Net message to dblink                      94        0.00          0.00
  SQL*Net message from dblink                    94        0.07          0.21
  SQL*Net more data from dblink                  25        0.00          0.00
  log buffer space                                4        1.02          2.72
  log file switch (checkpoint incomplete)        13        1.03         12.60
  free buffer waits                              37        1.03         36.40

 6887  user  SQL statements in session.
   17  internal SQL statements in session.
 6904  SQL statements in session.
********************************************************************************
Trace file: E:\oraSFON\trace\user\sfon_ora_2844.trc
Trace file compatibility: 9.00.01
Sort options: default

       1  session in tracefile.
    6887  user  SQL statements in trace file.
      17  internal SQL statements in trace file.
    6904  SQL statements in trace file.
     205  unique SQL statements in trace file.
  496224  lines in trace file.

-- statistics no bulk
********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       44      0.73       0.77          0         45          0           0
Execute    167      0.28       0.69         26        863        809          28
Fetch      137      0.57       2.86       1045       6770          0         137
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      348      1.59       4.33       1071       7678        809         165

Misses in library cache during parse: 32

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                     280        0.00          0.00
  SQL*Net message from client                   279       57.24        121.91
  db file sequential read                        61        0.06          0.63
  SQL*Net more data to client                    45        0.00          0.00
  db file scattered read                        129        0.13          2.01
  SQL*Net message to dblink                      86        0.00          0.00
  SQL*Net message from dblink                    86        0.44          0.66
  SQL*Net more data from dblink                  32        0.00          0.00
  log file sync                                   4        0.00          0.01

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse     7195      0.60       0.96          9         72          2           0
Execute  91143     22.57     293.68      10002      48096     146424       15758
Fetch    88958    160.12     290.88      42724     617730          0       88874
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   187296    183.31     585.53      52735     665898     146426      104632

Misses in library cache during parse: 187

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
  db file sequential read                     11638        0.21        209.67
  db file scattered read                       5270        0.26         78.58
  SQL*Net message to dblink                     124        0.00          0.00
  SQL*Net message from dblink                   124        0.18          0.52
  SQL*Net more data from dblink                  43        0.00          0.00
  log buffer space                                3        1.02          1.05
  free buffer waits                             100        1.03        101.64
  log file switch (checkpoint incomplete)        12        1.03         11.12
  log file switch completion                      3        1.02          1.43

 6804  user  SQL statements in session.
  435  internal SQL statements in session.
 7239  SQL statements in session.
********************************************************************************
Trace file: E:\oraSFON\trace\user\usfon_ora_2784.trc
Trace file compatibility: 9.00.01
Sort options: default

       1  session in tracefile.
    6804  user  SQL statements in trace file.
     435  internal SQL statements in trace file.
    7239  SQL statements in trace file.
     222  unique SQL statements in trace file.
  608497  lines in trace file.
Followup:
you just did more physical IO when you ran the bulk -- it would happen JUST as
often with the single row.

this is one of those 'you got unlucky' but you would get just as unlucky (even
more so) with the single row process over time.

run each process "a couple of times", you'll see.
Rating: 5
March 15, 2004
Reviewer: A reader
Thanks Tom.
Rating: 4
BULK COLLECT QUERY NOT COMPILING April 01, 2004
Reviewer: Mahomed Suria from U.K
Hi Tom,

I am getting the following error whilst experimenting with BULK COLLECT but I
cannot understand why? :

marvin> sql

SQL*Plus: Release 9.2.0.4.0 - Production on Thu Apr 1 17:08:49 2004

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Release 9.2.0.4.0 - Production
JServer Release 9.2.0.4.0 - Production

SQL> set pages 200;
SQL> @sp_fixloc

Warning: Procedure created with compilation errors.

SQL> show errors
Errors for PROCEDURE SP_FIXLOC:

LINE/COL ERROR
-------- -----------------------------------------------------------------
48/3     PL/SQL: SQL Statement ignored
108/28   PL/SQL: ORA-00904: "ALLCNT": invalid identifier

Here is my stored procedure:
create or replace procedure sp_fixloc( p_prcnam   in varchar2,
                  p_update   in number,
                  p_whs      in rakloc.whscde%type,
                  p_blk      in rakloc.rakblk%type default null,
                  p_asl      in rakloc.rakasl%type default null,
                  p_col      in rakloc.rakcol%type default null,
                  p_lvl      in rakloc.raklvl%type default null,
                  p_sid      in rakloc.raksid%type default null)
is
--
--
TYPE t_rakusr IS TABLE OF rakloc.rakusr%type INDEX BY PLS_INTEGER;
TYPE t_raktyp IS TABLE OF rakloc.raktyp%type INDEX BY PLS_INTEGER;
TYPE t_whscde IS TABLE OF rakloc.whscde%type INDEX BY PLS_INTEGER;
TYPE t_rakblk IS TABLE OF rakloc.rakblk%type INDEX BY PLS_INTEGER;
TYPE t_rakasl IS TABLE OF rakloc.rakasl%type INDEX BY PLS_INTEGER;
TYPE t_rakcol IS TABLE OF rakloc.rakcol%type INDEX BY PLS_INTEGER;
TYPE t_raklvl IS TABLE OF rakloc.raklvl%type INDEX BY PLS_INTEGER;
TYPE t_raksid IS TABLE OF rakloc.raksid%type INDEX BY PLS_INTEGER;
TYPE t_rakocc IS TABLE OF rakloc.rakocc%type INDEX BY PLS_INTEGER;
TYPE t_rakfre IS TABLE OF rakloc.rakfre%type INDEX BY PLS_INTEGER;
TYPE t_maxdep IS TABLE OF rakloc.maxdep%type INDEX BY PLS_INTEGER;
TYPE t_numitr IS TABLE OF rakloc.numitr%type INDEX BY PLS_INTEGER;
TYPE t_numall IS TABLE OF rakloc.numall%type INDEX BY PLS_INTEGER;

l_rakusr t_rakusr;
l_raktyp t_raktyp;
l_whscde t_whscde;
l_rakblk t_rakblk;
l_rakasl t_rakasl;
l_rakcol t_rakcol;
l_raklvl t_raklvl;
l_raksid t_raksid;
l_rakocc t_rakocc;
l_rakfre t_rakfre;
l_maxdep t_maxdep;
l_numitr t_numitr;
l_numall t_numall;
l_rakcnt t_rakocc;
l_itrcnt t_numitr;
l_allcnt t_numall;

l_loc    varchar2(40);
l_msg    varchar2(120);

BEGIN
  select r.rakusr, r.raktyp, r.whscde, r.rakblk, r.rakasl,
         r.rakcol, r.raklvl, r.raksid, r.rakocc, r.rakfre,
         r.maxdep, r.numitr, r.numall,
         (select count(*) from pallet p
           where p.whscde = r.whscde
             and p.rakblk = r.rakblk
             and p.rakasl = r.rakasl
             and p.rakcol = r.rakcol
             and p.raklvl = r.raklvl
             and p.raksid = r.raksid
             and trnsts <> 5) rakcnt, --TRNSTS-ADVISED
         (select count(*) from palreq
           where ( piksts in (60,  -- PIKSTS-PICKING
                              20,      -- PIKSTS-ON_INPUT
                              30)  -- PIKSTS-CURRENT-MOVE
                   or  ( piksts = 10  -- PIKSTS-ALLOCATED
                        and piktyp <> 2)   -- PIKTYP-NORMAL
                 )
             and (  ( dstwhs = r.whscde
                     and dstblk = r.rakblk
                     and dstasl = r.rakasl
                     and dstcol = r.rakcol
                     and dstlvl = r.raklvl
                     and dstsid = r.raksid )
                   or ( palreq.intasl = r.rakasl
                        and palreq.intasl is not null
                       and r.raktyp in (40, --RAKTYP-DEPOSIT-BASE
                                        42) --RAKTYP-VIRTUAL-GRP
                      )
                   or ( palreq.intusr is not null
                        and palreq.intusr = r.rakusr)

                   )
                 ) itrcnt,
          (select count(*) from palreq
            where whscde = r.whscde
              and rakblk = r.rakblk
              and rakasl = r.rakasl
              and rakcol = r.rakcol
              and raklvl = r.raklvl
              and raksid = r.raksid
              and piksts in (-1, -- PIKSTS-SHORT-ALLOC
                             10,   -- PIKSTS-ALLOCATED
                             40,-- PIKSTS-BLOCKED-ALLOC
                             30) -- PIKSTS-CURRENT-MOVE
                 ) allcnt
      BULK COLLECT INTO
            l_rakusr,l_raktyp,l_whscde,l_rakblk,l_rakasl,l_rakcol,
            l_raklvl, l_raksid,l_rakocc,l_rakfre,l_maxdep,l_numitr,
            l_numall, l_rakcnt,l_itrcnt,l_allcnt

    from rakloc r
    where r.whscde = p_whs
      and (r.rakblk = p_blk or p_blk is null)
      and (r.rakasl = p_asl or p_asl is null)
      and (r.rakcol = p_col or p_col is null)
      and (r.raklvl = p_lvl or p_lvl is null)
      and (r.raksid = p_sid or p_sid is null)
      and (   r.rakocc <> rakcnt
            or r.numitr <> itrcnt
            or r.numall <> allcnt )
      and r.raktyp in (select raktyp from bkrtyp
                         where  blktyp = 2010)
    ;

  if ( p_update <> 0 ) then

      FORALL indx IN l_rakusr.FIRST .. l_rakusr.LAST
         update rakloc
            set rakocc = l_rakcnt(indx)
              , rakfre = l_maxdep(indx) - l_rakcnt(indx)
              , numall = l_allcnt(indx)
              , numitr = l_itrcnt(indx)
          where whscde = l_whscde(indx)
            and rakblk = l_rakblk(indx)
            and rakasl = l_rakasl(indx)
            and rakcol = l_rakcol(indx)
            and raklvl = l_raklvl(indx)
            and raksid = l_raksid(indx)
          ;
  else
      FOR indx IN l_rakusr.FIRST .. l_rakusr.LAST
      LOOP
       l_loc := 'Location [' || l_rakusr(indx) || ']' ;

       if ( l_rakcnt(indx) <> l_rakocc(indx)) then
         l_msg := trim(l_loc) || ' rakocc old=' || to_char(l_rakocc(indx)) ||
                                   ' new=' || to_char(l_rakcnt(indx));
         pkg_trc.outmsg(p_prcnam, l_msg);
       end if;
       if ( l_itrcnt(indx) <> l_numitr(indx)) then
         l_msg := trim(l_loc) || ' numitr old=' || to_char(l_numitr(indx)) ||
                                   ' new=' || to_char(l_itrcnt(indx));
         pkg_trc.outmsg(p_prcnam, l_msg);
       end if;
       if ( l_allcnt(indx) <> l_numall(indx)) then
         l_msg := trim(l_loc) || ' numall old=' || to_char(l_numall(indx)) ||
                                   ' new=' || to_char(l_allcnt(indx));
         pkg_trc.outmsg(p_prcnam, l_msg);
       end if;
      END LOOP;
  end if;
END;
/
Followup:
nothing to do with bulk collect

everything to do with normal scoping.  allcnt -- defined in the select list, is
not "in scope" in the body of the query yet.

ops$tkyte@ORA9IR2> select (select count(*) from dual ) allcnt
  2    from dual
  3   where allcnt <> 0
  4  /
 where allcnt <> 0
       *
ERROR at line 3:
ORA-00904: "ALLCNT": invalid identifier

ops$tkyte@ORA9IR2> select *
  2    from (
  3  select (select count(*) from dual ) allcnt
  4    from dual
  5         )
  6   where allcnt <> 0
  7  /

    ALLCNT
----------
         1
Rating: 5
Followup : Bulk Collect April 02, 2004
Reviewer: A reader
Thanks Tom.

Sorry , as you say it is not a Bulk Collect problem, but I was off the track
anyway and you solved it for me.
Thanks again.
Bye
April 01, 2004 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.
Rating: 5
table design question January 28, 2004
Reviewer: Sonali from waltham,ma
We have a resource table with 60 columns and 4 of them are varchar2(255) and
rest of them are varchar2(60) and below and some number columns. Should the row
size be of concern ? Would you call this a big table as far as number of columns
goes ?
We were asked if splitting this table into 2 and moving the addresses, email
adresses to other table would improve the performance as these columns would not
be that extensively used. We are careful in not using select * in the queries.
then would this make it any more efficient as it will be returning half the
amount of data if 100's of times a resource is read. I feel that if we are not
going to do select * and select all the columns then it would not make it half
the data if we split the table, would it ?
Whats your openion on this ?
Thanks
Followup:
if you index read and the rows fit on a block -- what you have is fine.

if you full scan and don't need all of the columns, factoring out will help.

but, if you are using indexes -- doesn't matter really.
Rating: 5
Design Question February 09, 2004
Reviewer: A reader
Hi Tom,

We have a design question here. We are developing an application for multiple
clients, and in some metadata tables some data are the same for all clients and
some are different. We have two choices here:

1). Reserve a section of ID’s in each base table for base data (e.g. 0-999) and
start the auto-sequencer for user data above this reserved section.

2). Duplicate the base tables (1 for base data, 1 for user data).

Which one you think is the right way?
Followup:
why do you need to do either of #1 or #2?  i'm confused on that point.
Rating: 5
re: Design Question February 09, 2004
Reviewer: A reader
One pointed I missed is that our application is tightly built on these metadata
tables. For example, in the application it may be hard-coded that:

if (grouptypeid ==1)
  {...}

Here grouptypeid 1 is the base data and must be the same for all clients.

However each client can defined their own group types which could be different
from each other.

Thanks for your comments.
Followup:
sure, start the sequence at some large number then.  no problem with that.

(don't like the id of hard coded values like that, but thats another story)
Rating: 3
Storage Clause February 09, 2004
Reviewer: A Reader
Dear Tom,

Can you please explain that how to determine different values of the parameters
in storage clause of Create Table statement. Secondly what issues should be
taken care while creating the table.

Thanks

Regards
Followup:
You should ONLY use

freelist
freelist groups
buffer pool

Use LMT's and forget about the rest -- they are not worth our time.  Using
an LMT makes it easy.

freelists would be set to non-default values for objects having highly
concurrent modifications against them (inserts especially, updates that migrate
rows).

buffer pool for doing the last mile of tuning.
Rating: 5
check constraint on raw column March 12, 2004
Reviewer: sonali from waltham, ma
I have created a table for storing salary..
SQLWKS> CREATE TABLE  test_Salary
      (
          Sal_ID number(10,0) PRIMARY KEY,
      Sal_Amount raw(16)  ,
          Sal_Start date NULL,
          Sal_Finish date NULL,
      constraint chk_sal check (Sal_Amount> 0)
     );
constraint chk_sal check (Sal_Amount> 0)
                                    *
ORA-00932: inconsistent datatypes: expected BINARY got NUMBER

I want to check if the salary amount entered is valid integer or not. I have
this column as RAW for encryption but the person entering the value should not
be anything but integer. How do I do this ? It seems this table is going to be
populated from flat file so I cannot have validation there.. they will have
their own program to load this so I cannot edit it. I have to do this in this
table or in my encryption procedure, which is a better place to do it ?

Thanks
Followup:
it is raw, it is not a number at all -- it is whatever 16 bytes of "stuff" you
stuff in there.

if it is encrypted "stuff", there is 0% chance that you can "check constrain" it
-- you have no idea what the value is.
Rating: 5
Will this work then ? March 12, 2004
Reviewer: sonali from waltham, ma
Suppose I create a trigger ( before insert on this table)..
fn_add is the function to encrypt this sal_amount column.

So my plan was to have a trigger that converts the number entered by some user
or program to character using this trigger and then encrypt it right away..

SQLWKS> CREATE or REPLACE TRIGGER tr_ETsalInsert
     2> BEFORE INSERT ON  Salary
     3> For Each Row
     4>
     5> Begin
     6>   :NEW.Sal_amount := fn_add(to_char(:NEW.Sal_amount));
     7>  commit;
     8> End tr_ETsalInsert;
     9> /
Statement processed.
SQLWKS> show errors
No errors for TRIGGER TR_ETSALINSERT
SQLWKS> insert into  salary
     2> values( Salary_Seq.nextval, 10, 400, null, null)
     3>
values( Salary_Seq.nextval, 10, 400, null, null)
                                 *
ORA-00932: inconsistent datatypes: expected BINARY got NUMBER

I can see this approach isn't working but thought this would be better than
adding one more numeric column, have this trigger do the conversion and delete
value from that column right after the conversion.

Thanks
Followup:
you have a RAW -- a raw is a raw.  The trigger would take a RAW as input and do
whatever it wants.

You do NOT want to make this "transparent" like that.  the application will

insert into salarly( s.nextval, 10, fn_add(sal_amount), null, null );

that'll be the only realistic way to do this.  You could play games with instead
of triggers and views but that would only

a) make it slower
b) make it more obscure
Rating: 5
this does not work either March 12, 2004
Reviewer: sonali
CREATE TABLE  Salary
(
    Sal_ID number(10,0) PRIMARY KEY,
    Sal_ETResID number(10,0),
    Sal_Amount raw(16),
            Sal_TempAmount number(10,0),
    Sal_Start date NULL,
    Sal_Finish date NULL,
constraint chk_sal check (Sal_TempAmount> 0)

);

CREATE or REPLACE TRIGGER tr_ETsalInsert
BEFORE INSERT ON  Salary
For Each Row
DECLARE strSal_amount varchar2(20);
Begin
strSal_amount := to_char(:NEW.Sal_TempAmount);

  :NEW.Sal_amount := fn_add(strSal_amount);

 Update  salary
 set Sal_TempAmount =0
 where sal_id = :NEW.sal_id;

 commit;
End tr_ETsalInsert;
/
show errors

SQLWKS> insert into  salary
     2> values( Salary_Seq.nextval, 10, null,50, null, null)
     3>
1 row processed.

SQLWKS> select SAL_AMOUNT, sal_tempamount from  salary
     2>
SAL_AMOUNT                       SAL_TEMPAM
-------------------------------- ----------
67BADC900CB03168                        600
09016723543F1792                         50
4 rows selected.

This does not update the sal_tempamount to 0.. I would guess thats because I
have before insert trigger, but then if I have after insert trigger then I will
not be able to set the values of :NEW.. if I split this code into 2 parts before
insert and after insert (Doing this update, it will not work either as it will
be mutating).. how do I do this ?
Followup:
see above
Rating: 4
Table Design April 01, 2004
Reviewer: Scot Armstrong from Milwuakee, WI USA
We are creating an application that will have 100 plus fields one of them being
a clob.  All of the fields needs to be searchable.

From a design and performance standpoint, what would be the best approach:

1. Create the table with all of the columns and use an Oracle Text index with a
multi_column_datastore with all of the columns.
2. Create the table with the primary key columns, and store the rest in a clob
and use Oracle Text.
3. Create the table with the primary key columns, and store the rest in an
xmltype inserting the data in xml format and use Oracle Text.  The thought for
this option was in case we needed to selectively search specific fields in the
future.
Followup:
or option 4 which is what I do on asktom -- index a function.....
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:440419921146
I would look at #1 and #4  -- i use #4 because my data i need to index (the data
that makes up this very page) is spread across a couple of tables -- i need to
assemble the page for Oracle text to index.
Rating: 4
Table Design April 06, 2004
Reviewer: Scot Armstrong from Milwaukee, WI USA
Thanks for the help.

I've created a function and indexed in other solutions involving a single table.
 I've looked at the article for indexing multiple tables as well, but am still
unclear how to set up the tables and the function.  Would it be possible to walk
through an example of specifically showing it with multiple tables?

Also regarding solution #3.  Based on how the design specifications come from
our business users, they will eventually want to know what column the results
from the query are coming from.  Would solution #3 be the most viable one for
such a requirement or is there a better one?
April 01, 2004 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.
Rating: 4
FIXED field length in EXTARNAL TABLE January 12, 2004
Reviewer: High from NY
Tom,
I can't figureout how to specify that if a field is null for a fixed length
extarnal table then put null. I know that in a delimited file you can use the
parameter "MISSING FIELD VALUES ARE NULL", I tried using in with FIXED with no
success. it seem to trim the blank spaces and the whole file get messed up.

I looked in Oracle doc and could not fine the answer to this.

Any ideas?

Thanks.
Followup:
just use SQL, but there are nullif's as well.

I would just use SQL and see if it is blanks and return NULL if so.  path of
least resistance.
Rating: 4
null in fixed field external table January 12, 2004
Reviewer: High from NY
Tom,
I understand what you mean but I have no idea what is the syntax for it. I tried
to look it up in Oracle doc but could not find it.
This is my create statement.

CREATE TABLE LEE_TEST(
    OBJECT_NAME    VARCHAR2 (70),
    OBJECT_ID VARCHAR2(20),
    SECONDARY      VARCHAR2 (22))
        ORGANIZATION EXTERNAL(
            TYPE ORACLE_LOADER
            DEFAULT DIRECTORY EXT_TBL
            ACCESS PARAMETERS(
                   RECORDS FIXED 92
                   BADFILE EXT_TBL:'lee_test.bad'
                   LOGFILE EXT_TBL:'lee_test.log'
                   FIELDS LRTRIM
                   MISSING FIELD VALUES ARE NULL(
                      OBJECT_NAME CHAR(50),
                      OBJECT_ID CHAR(20),
                      SECONDARY CHAR(20)))
             LOCATION ('lee_test_fixed.txt'))
    REJECT LIMIT UNLIMITED;

If one of the columns are null then it is being ignored and the hole record get
messed up. How can I say something like “if the value of OBJECT_ID is empty then
NULL”. It will be good if you can provide a NULLIF and sql statement example.

Thanks.
Followup:
You didn't understand what I was saying -- I was saying "fix it in SQL"

use a where clause
use a function in the select
whatever

let the external table load all of the fields -- then use SQL to
filter/transform the data however you need.
Rating: 3
Problem with external table fixed length record January 13, 2004
Reviewer: High from NY
Tom,
I think we are not on the same page, this is what I mean.

Given this data file:

Abc     123     2
Efg     456     3
Hij             4
Klm     789

When I load this data with the “create table” below I get:

COL1   COL2    COL3
Abc    123     2
Efg    456     3
Hij    4
Klm    789

And this is what it should be:
COL1   COL2    COL3
Abc    123     2
Efg    456     3
Hij            4
Klm    789

On the 3rd record the empty value is being ignored and the hole record is
shrinking and I can't really do anything with is. There must be a way to say
something like "if the field is empty just put null in this column".
I could not find how to fix this, I’m sure there is an easy solution but I can’t
figure it out.

This is my create statement:

CREATE TABLE LEE_TEST(
    COL1    VARCHAR2 (70),
    COL2    VARCHAR2(20),
    COL3    VARCHAR2 (22))
        ORGANIZATION EXTERNAL(
            TYPE ORACLE_LOADER
            DEFAULT DIRECTORY EXT_TBL
            ACCESS PARAMETERS(
                   RECORDS FIXED 92
                   BADFILE EXT_TBL:'lee_test.bad'
                   LOGFILE EXT_TBL:'lee_test.log'
                   FIELDS LRTRIM
                   MISSING FIELD VALUES ARE NULL(
                      COL1  CHAR(50),
                      COL2  CHAR(20),
                      COL3  CHAR(20)))
             LOCATION ('lee_test_fixed.txt'))
    REJECT LIMIT UNLIMITED;

Thanks.
Followup:
you should be using POSITITION(n:m) for a fixed width file like that.
Rating: 5
External table "bad" file conncateneting records January 21, 2004
Reviewer: Lee
Tom,
I have a freaky problem that I hope you can help me with.
I am using external  tables to load data from flat files into oracle.
Some files are bing rejected coz some enclosed charachters are not there (I'm
using oprinaly) with is fine. when the "bad" file is generated, some records are
concateneted, it seem like for some reason Oracle forget to put NEWLINE after a
record in the bad file.
This happend only sometimes.

Have you ever encounter this? this is driving me crazy and I really hope you can
help with it.

Thank you.
Followup:
never heard of it happening, no.  sorry -- cannot imagine it happening,
especially "sporadically"

if you do the same operation with an input file that puts the bad records in
without newlines -- does it reproduce? (eg: if you query the same file over and
over - is the bad file different each and every time???)
Rating: 5
Problem with external table bad file January 21, 2004
Reviewer: Lee
Tom,
First, thank you for the previous fast response, I have a presentation on Monday
trying to convince every one that Oracle external table loader will be faster
and more reliable then what we are using now which is Sagent.

I followed your suggestion and it seem like the bad file have a slightly
different number of records every time.

Let me give you all the details and maybe you’ll be able to help me figure out
what the hack is going on here.

Ok,
I have a file that the records are terminated by NEWLINE, field are delimited by
comma (,) and optionally enclosed by double quotes (“) and double quotes (“).

The first field in every record is a unique id so I can look for it in the text
file, the end result table and the bad table.

This is what I see:
if I look for the id that is “funny” in the bad file (concatenated with another
record) and in the original text file it looks fine as far NEWLINE terminator.
The record is rejected and is concatenated in the bad file.

This is a part of the original text file: (this is the rejected id
233818406920750100)
"233818401704260200","0000012.66","0000000002","0000014.54","0000000000","0000037
.93","0170426","02","20020719","20020729","38140681101","19400625","M","0034","00
34","59930150201","ISOSORBIDE MONONITRATE","ISOSORBIDE
MONONITRATE","30MG","1","1","48104","AED","HM","MI","1007208503","%","A2","0189",
"AP9002279","PATEL,BIPIN ISH","103022","2338184","PINCKNEY
PHARMACY","48169","A7B","241200","0","A","P","0000012.54"
"233818406920750100","0000019.24","0000000005","0000021.87","0000002.25","0000025
.73","0692075","01","20020717","20020729","38152528601","19500615","M","0100","00
30","08290841103","INSULIN SYRINGE","SYRINGE-NDL,INS
DISPOSABLE","29GX0.5"","1","0","94200","AED","HM","MI","1008267001","%","A2","018
9","BB1034115","BURNARD,KAREN S","101350","2338184","PINCKNEY
PHARMACY","48169","X2B","940000","0","A","P","0000019.12"
"233818401738960000","0000007.49","0000000005","0000010.12","0000002.25","0000002
1.4","0173896","00","20020713","20020729","38160678303","19951026","M","0017","00
14","59930156001","ALBUTEROL","ALBUTEROL","90MCG","1","1","20110","AED","HM","MI"
,"1015480001","%","A2","0189","AM9386245","UNKNOWN","118467","2338184","PINCKNEY
PHARMACY","48169","J5D","121200","0","A","P","0000007.37"
"233818401729890100","0000001.83","0000001.71","0000001.17","0000002.25","0000002
.94","0172989","01","20020716","20020729","38168123702","19590928","F","0030","00
10","49884003510","MECLIZINE HCL","MECLIZINE
HCL","25MG","1","1","18302","AED","HM","MI","1008267001","%","A2","0189","BM60134
07","MOLLER,WENDY MA","102185","2338184","PINCKNEY
PHARMACY","48169","H6J","562200","0","A","P","0000001.71"

This is the rejectd record from the bad file (1 record):
"233818406920750100","0000019.24","0000000005","0000021.87","0000002.25","0000025
.73","0692075","01","20020717","20020729","38152528601","19500615","M","0100","00
30","08290841103","INSULIN SYRINGE","SYRINGE-NDL,INS
DISPOSABLE","29GX0.5"","1","0","94200","AED","HM","MI","1008267001","%","A2","018
9","BB1034115","BURNARD,KAREN S","101350","2338184","PINCKNEY
PHARMACY","48169","X2B","940000","0","A","P","0000019.12"23"233824708514710000","
0000020.71","0000000002","0000020.34","0000002.25","0000023.93","0851471","00","2
0020715","20020729","38536063101","19380920","M","0100","0030","08290328430","INS
ULIN SYRINGE","SYRINGE-NDL,INS
DISPOSABLE","28GX0.5"","1","0","94200","AED","HM","MI","1007209001","%","A2","018
9","AB2738839","BEISON,RICHARD","101201","2338247","RITE-AID
#4510","48103","X2B","940000","0","A","P","0000020"23620"233876606357350200","000
0049.65","0000000002","0000049.28","0000002.25","0000057.98","0635735","02","2002
0716","20020729","37252985405","19871009","M","0200","0020","00169185250","NOVOFI
NE 30","NEEDLES, INSULIN
DISPOSABLE","30GX0.8"","1","0","94200","AED","HM","MI","1008067001","%","A2","018
9","BK5630478","KASA-VUBU,JOSEP","103672","2338766","ARBOR
#8042","48111","X2A","940000","0","A","P","0000049.53"

I will really appreciate your help.

Thanks.
Followup:
I'll ask again

given the same inputs -- does the same "bad" output result.

that is -- is this reproducible.
Rating: 4
Problems with external tables January 22, 2004
Reviewer: Lee
Tom,
I'm sorry, I was not clear enogh.
I ran the same load 3 times, each time I got difrent number of bad records (485,
461, 467). I looked for the offensing id in the bad tables and I found it in all
3 of them. In the first and the third the record looked similar (concatenated
with parts of other records) and the second run had the record rejected but not
concatenated.
Followup:
are you using parallel?
Rating: 5
External table problems January 22, 2004
Reviewer: Lee
Wow Tom, you are fast !!!

Yes I am using parallel.
Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96652/ch12.htm#1009486
that explains everything

you have N processes writing to the single bad file.  they are overwriting
eachother.

you need to add a %p or %a to the file spec to avoid that -- so each PQ agent
gets their own file to log bad records to.
Rating: 5
External table problem January 22, 2004
Reviewer: Lee
Just to be clear.
After I create the external table I do an alter to make it parallel (I know I
can do in the create table statment).
Rating: 5
External table problem January 22, 2004
Reviewer: Lee
Tom,
I'm sorry, I was not clear enogh.
I ran the same load 3 times, each time I got difrent number of bad records (485,
461, 467). I looked for the offensing id in the bad tables and I found it in all
3 of them. In the first and the third the record looked similar (concatenated
with parts of other records) and the second run had the record rejected but not
concatenated.

Un believable, why the hell I didn’t think of that &#61514;

The new problem is now I have an unknown bad file name and I need to create an
external table from the bad fine in order to handle rejected files (it is should
be created programmatically, manually will not be a problem).

Can I get the process id (%p) or agent number (%a) from somewhere inside Oracle?

P.S.
You made my freaking day, I’m battling this for almost 2 days.
Followup:
well, we can do a directory on that (ls/dir) using a java stored procedure to
get the names -- would that help:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:439619916584
you'd use that to generate the list of "bad" files with which you could then
dynamically generate a "create external table" command which could then be
processed dynamically (using dynamic sql in plsql)

or just use BFILES to read the files

if you don't want to use java, you could use bfiles and the %a -- since %a is
bounded by the max parallel query servers -- you could use bfiles to see if a
file exists in the range of 1.. max parallel query servers instead of listing
them with "ls"
Rating: 5
External table problem January 22, 2004
Reviewer: Lee
Tom,
I hate to kill ass but I have too....Y-O-U  A-R-E  T-H-E  M-A-N.
Rating: 5
external tables March 25, 2004
Reviewer: A reader
Hi tom
We are working a logging framework, and I was
toying with the idea of using external tables
for the log files (in a format we control.)
This gives us an sql based way of accessing information
in the log files. These files contain debug information from our instrumented
code (in pl/sql). The logging framework would be in pl/sql. (I am aware of your
debug.f -
we are planning to leverage some of the ideas from it.)

What are your thoughts?

Thanx!
Followup:
if you want to log to tables (permanently) sure.

not sure I'd want to use external tables frequently as a query source (as a
method to LOAD, definitely -- for ongoing queries, frequently, maybe not...)
Rating: 5
thanx Tom! March 26, 2004
Reviewer: A reader
Actually the idea is to *not* log into tables permanently.
So instead of "committing" on every log message during a
debug session, we use the utl_file to write to a
log file. The log file format is such that it
can be queried by creating an external table on it.
Since the dianostic session is not the very frequent
(so you hope:)) the fact that queries would not be
very fast is acceptable perhaps.
Rating: 5
continuing on logging March 30, 2004
Reviewer: A reader
Hi Tom
Regarding the above question on logging framework, there are two methods we are
considering:
1. Log the debug messages into tables:
   This means that when logging is turned on, the
   debug messages get inserted in a table. Each message
   insert results in a commit. (not a good idea but
   remember this is done only during diagnostic session.)
   May be a simple "batching" could alleviate this issue
   though )?
   Additionally, we also get the benefit of using sql on
   tables. We also need to have a purging mechanism to
   remove these messages (which would cause additional
   load on the system.)
2. Log the debug messages into a file in the utl_file_dir
   (smiliar to what debug.f does.) This has the advantage
   that it does not cause the load (redo/undo)on the system
   (except cpu consumption.) Regarding the capability for
   selects, we could potentially use external tables
   created on the log files to do that.

My questions are:
1. What are the implications if we use RAC? In alternative
   2, in particular, where do the messages go? Are there
   any additional gotchas you can think of when using
   RAC esp. for alternative 2?
2. I am not a RAC expert. In a stanalone non RAC database
   I feel that the second alternative is better (esp.
   if the idea of external tables works.) due to reasons
   stated. What do you think?
3. How does debug.f fare when used in conjunction with RAC?
   Has this been tried?
4. If RAC does have an issue with logging into files, then
   how come sql trace stil puts things into files - not
   databases. This seems to indicate that logging into
   files is a better alternative even in RAC?

Many thanx!
Followup:
a1) the messages from utl_file would goto a file system visible to the RAC
server.  Unless this filesystem was nfs'ed all around, it would be visible on
one server only.

a2) it is what I use.

a3) it works fine -- the trace goes to the filesystem that the instance running
the code sees.  You might add the instance number to the filename to avoid
conflicts if you use a shared filesystem of some sort.

a4) you goto the server to get the trace.  most of the time traces indicate
"database failure", it would not be "a good idea" to put this data into the
database!
Rating: 5
thanx Tom! March 30, 2004
Reviewer: A reader
So to confirm:
1. logging into a file versus tables has no additional
   advantage/disadvantage when it comes to using RAC,
   correct?
2. Do you have any particularly strong opinion on putting
   trace info in a trace file versus tables. Pros and
   cons plus final verdict? Would appreciate if you can
   think of any additional pros/cons for either apart
   from the ones I mentioned earlier.
3. In RAC, is there a chance of getting confused on
   where one should look for a particular code's
   trace file (as in which host)? I think you explained
   it but I am not getting it:) What will be the steps
   to identify the host where your messages go since you
   may not know which instance executed it (not sure about
   if the quetion is valid:))?

Also, the last point was somthing I should have thought of!
So that obviously does not apply in my case because
this logging framework is to log pl/sql messages:)

Thanx a bunch!!
Followup:
1) not really -- in RAC or not in RAC, you would need to be on "that instance"
to see the debug.

2) I'm into the file simply cause

o it can be zipped up and emailed to me easily
o if i needed to SQL it, external tables would work
o I can tail -f it and watch 'real time' <<<=== #1 reason for me.

the table has the advantage that it can be viewed from the client, any client.

3) log into the server the instance is running on and there should be no
confusion.  select * from v$instance will tell you "where you are"
Rating: 5
thanx Tom - few more qs... March 30, 2004
Reviewer: A reader
"1) not really -- in RAC or not in RAC, you would need to be on "that instance
to see the debug."

This does not seem to be true if you log into
tables? In other words
if I log into table, I dont need to know the instance,
right? (sorry again to be "RAC" ignorant.

When our code executes on RAC, is it possible that we dont
know which instance is executing it at a given time? If this is possible, then
this may be a pro for "tables" based
solution, perhaps?

"3) log into the server the instance is running on and there should be no
confusion.  select * from v$instance will tell you "where you are""
Again, if you are selecting from tables do we need to
know the instance in all cases in RAC?

thanx!
Followup:
that is correct -- i said as much above (about the table).  the table lets you
access it from anywhere.

v$instance tells you what instance exactly you are on.

if you use tables -- no, you don't need to be on the server as the data isn't
being written to a filesystem, but to a database table.
Rating: 5
yes but.. March 30, 2004
Reviewer: A reader
To confirm:

"When our code executes on RAC, is it possible that we dont
know which instance is executing it at a given time?"

So let us say I am debugging a pl/sql module. When it
is running, if I want to know which instance it is running
on I can simply do the select from v$instance,
know which server is executing it and then
I log into the server and look for the appropriate
file, correct?

If we are logging into a table, we dont need to
do this step since the messages are going into a
database table...

thanx a bunch, Tom!
Followup:
you are debugging a plsql module.

you are obviously logged in.

select * from v$instance will tell you where.

(you seem to be leaning towards a table -- I'm not telling you to go one way or
the other.  if you find the table easier -- go for it, it has pros and cons as
well.  I'm not trying to talk you out of anything here)
Rating: 5
thanx Tom! March 30, 2004
Reviewer: A reader
No, actually I am leaning towards files.
It is just that someone objected to the idea of files
stating that RAC may create confusion on where the
file is. You have clarified that bit perfectly.

What still confuses me is:

Is it possible that when a server is executing code
(pl/sql or sql or whatever in the database instance)
we don't know which instance the code is executing
on? In other words, when you obtain connection to
a RAC instance, do we have to specify which instance
we want to connect to or is that transparent to us?
When you obtain connection normally you specify a
service (apart from user/passwd.) Is RAC any
different?

I think this question may not be related to the
above discussion - but this is what confused me.

Thanx anyways and have a wonderful day!:)
Followup:
in rac you can either

a) specify a particular instance
b) a set of instances to try
c) let us pick one for you

totally up to you.
Rating: 5
well.. March 30, 2004
Reviewer: A reader
"in rac you can either

a) specify a particular instance
b) a set of instances to try
c) let us pick one for you"

well, that was my confusion. In case b and c you
don't know which instance your code will be executing
on. In case of logging to files, I guess you will
have to try loggin into (potentiall)
all the nodes  in such cases to see which instance
executed your code and thus where your log file is?
Do you have any such experience with case b or c?

Thanx!!
Followup:
or just "select * from v$instance"! it'll tell you what node you are on....
Rating: 5
hmm.. March 30, 2004
Reviewer: A reader
I am sorry - I do understand that - please bear
with me.
That select is useful in the case where, say you
log in (through sqlplus) and execute a pl/sql
code. In this case you can run the select
you mentioned.
In the general case, you have an application
that uses RAC. I just ran it (in debug mode)
and as part of the application it logged in and
executed my code and dumped the log into file.
I have no idea which instance the application used
(cases b / c apply.)

Now I want to know where my log file is.
Since to execute the "select" you mentioned,
I need to know the instance - it is a chicken
and egg situation. Hope I am making sense -
sorry to keep coming back with more questions.

I guess one way could be that the application always
tells you which instance it ran in (since it
has the info *when* it runs the pl/sql code.)
Followup:
last paragraph would be the "answer".

In my apps -- when the page is run in "debug" mode, it puts additional
information on the page itself.
Rating: 5
thanx Tom! March 31, 2004
Reviewer: A reader
For bearing with me coming back for questions!
Have a great day!!!
Rating: 5
comments in external table file? April 01, 2004
Reviewer: A reader
Tom
How can I put comments in external table file so that
the select statements on that external table ignore
lines starting from say "#"?

Thanx and have a wonderful vacation!!
Followup:
ORGANIZATION external
(
  TYPE oracle_loader
  DEFAULT DIRECTORY SYS_SQLLDR_XT_TMPDIR_00000
  ACCESS PARAMETERS
  (
    RECORDS DELIMITED BY NEWLINE CHARACTERSET WE8ISO8859P1
    LOAD WHEN ((1: 1) != "#")
    BADFILE 'SYS_SQLLDR_XT_TMPDIR_00000':'ulcase7.bad'
    DISCARDFILE 'SYS_SQLLDR_XT_TMPDIR_00000':'ulcase7.dis'
    LOGFILE 'test.log_xt'
Rating: 5
thank you!!!! April 01, 2004
Reviewer: A reader
April 01, 2004 -- Thanks for the question regarding "Difference between automatic vs manual PGA setting", version 9.2.0
You Asked
In Oracle 9.2.0, Can you please describe briefly the difference between PGA
manual and automatic modes? Why oracle recommends Auto allocation?
and we said...
One of the most perplexing things for a DBA can be setting the individual
init.ora parameters - especially parameters such as sort_area_size,
hash_area_size, and so on. Many times, I see systems running with incredibly
small values for these parameters; values so small that system performance is
massively impacted in a negative way. There is a lot of confusion over how big
or small these should be. Not only that, but the values you would like to use
for them might vary over time, as the day goes by. At 8am in the morning, with 2
users, a 50mb sort area size would be reasonable perhaps. However, at 12 noon
with 500 users, 50mb would be not be appropriate. This is where you can use the
new capability in Oracle9i release 1 and up - the workarea_size_policy = AUTO
setting and the corresponding pga_aggregate_target (amount of memory to be used
by process global areas, where Oracle does sorts, hashing and the like).

Historically, the DBA configured the amount of memory used by Oracle by setting
the size of the SGA (the buffer cache, the log buffer, the shared/large/java
pools). The remaining memory on the machine would then be used by the dedicated
or shared servers in the PGA region, and called as the process memory. The DBA
had little control over the amount of this memory that would or would not be
used. They could set the sort_area_size, but if there were 10 concurrent sorts,
then Oracle could use as much as 10 * sort_area_size bytes of RAM. If there were
only 100 concurrent sort, then Oracle would use 100 * sort_area_size bytes, for
1,000 concurrent sorts, 1,000 times sort_area_size and so on. Couple that with
the fact that there are other things that go into the PGA such as
hash_area_size, bitmap_merge_area_size, and so on - and you really don't have
good control over the maximal use of PGA memory on the system.

What you would like to have happen is for this memory to be used differently as
the load on the system grows and shrinks. The more users, the less RAM they
should use. The less users, the more RAM they should use. Setting
workarea_size_policy = AUTO is just such a way to achieve this. The DBA will
specify a single size now, the pga_aggregate_target size, the maximum amount of
PGA memory that the database should strive to use. Oracle will then distribute
this memory over the active sessions as it sees fit. Further, with Oracle9iR2
there is even PGA advisory, much like the buffer cache advisor. It will tell you
over time what the optimal pga_aggregate_target for your system would be. You
can use this to either dynamically change the PGA size online (if you have
sufficient RAM), or to decide whether you might need more RAM on your server to
achieve optimal performance.

One last word about automatic PGA memory management. On operating systems that
allow for it, the PGA memory is actually physically de-allocated, released back
to the operating system. Instead of using traditional process heap memory
allocated via the C routines malloc(), PGA workareas can be allocated via map()
and unmap() memory calls instead. This allows Oracle to give back the memory
when it is done with a sort explicitly, rather than waiting for the operating
system to page out unused memory. This can be much more efficient over the long
haul as memory is used, given back, and reused without restoring to paging at
the OS level.
Review & Followup

Rating: 5
Thanks March 21, 2003
Reviewer: A reader
Thanks for your great explanation.

I have a related question. I know there is no out of box numbers for deciding
buffer sizes. But, can you advise an initial size for pga_aggregate_target size?
I can use this parameter as starting value. I will use PGA advisory to find
right value on my system and I will change it if necessary.

Our server is an 8 processor with 4 GB memory. Oracle 9.2 is running using
dedicated server option. We are expecting 300 concurrent connections at a given
peak point of time.

Currently parameters are

shared_pool_size  100 Mb
db_cache_size      300 Mb
db_block_size      16 Kb
shared_pool_reserved_size 5Mb
hash_area_size      2 Mb
sort_area_size      1 Mb
sort_area_retained_size 1 Mb

Please comment on the above parameter if you find anything as non-appropriate.
Please let me know if I need to provide any other information.

Thanks in advance.
Followup:
I would start with 500 meg for the pga_aggregate_target -- 1+ meg/connection to
start.

assuming the database "owns" the machine -- you want to make sure your SGA+PGA
doesn't blow out real memory.
Rating: 5
Thanks again March 21, 2003
Reviewer: A reader
Thanks for your suggestion.

Yes, the DB is the owner for that machine and no other processes are running on
it.

I will go with 500 Mb.

As far as I know PGA advisory stores the memory usage history and based on that
it will advise the optimum number, Should I wait couple of days to get the
correct number?
Rating: 4
March 24, 2003
Reviewer: A reader
Hi Tom great answer.
You have said "the PGA memory is actually physically de-allocated" on some
systems. Do you know how it is work with
Win2000 Advanced Server?
Thanks
kiro.
Followup:
the PGA "workareas" are, yes.  this applies to sort areas and hash areas.

We may keep some around -- to share from session to session -- but they are
released by the session
Rating: 4
Great to learn so many things from you Tom March 25, 2003
Reviewer: Vijay Sehgal from India
Rating: 5
Performance April 01, 2003
Reviewer: Prasad
But since workarea_size_policy is configurable parameter(can be set to manual),
is there a possibility that setting of parameters like hash_area_size and
sort_area_size could be better than workarea_size_policy=auto.
I am asking this because i have seen performance degradation in few update
statements which use hash joins e.g
UPDATE
(
SELECT
a.col1,b.col2
from a,b
where a.id=b.id
)
set col1=col2

When work area policy is set to auto and pga_aggregate_target=220M, this
statement takes 1 minute to execute for 109,508
but if i set work area policy  to manual and hash_area_size=20M , the same
statement gets executed in 20 Seconds.
The results are consistent after several tests and no other sessions were
active. I am connecting through a dedicated connection
What could be the possible reasons?
How to find the optimum PGA_aggregate_target value?
Regards
Prasad
I am using Oracle 9.2
Followup:
We'll start with Oracle9iR1 techniques and work up to 9iR2 since the 9iR1
techniques are useful in 9iR2 just to "see what is happening".  The first view
we'll start with is the V$PGASTAT dynamic performance view.  It has two columns
in 9iR1 - NAME, holding the name of the statistic and VALUE holding a value for
it.  In 9iR2 there is a third column UNITS that describes what the number in
VALUE represents such as "bytes" or "percent".  Here is an example:

ops$tkyte@ORA9I> select name,
  2         to_char(decode( unit,
  3                  'bytes', value/1024/1024,
  4                           value ),'999,999,999.9') value,
  5         decode( unit, 'bytes', 'mbytes', unit ) unit
  6  from v$pgastat
  7  /

NAME                                     VALUE          UNIT
---------------------------------------- -------------- ----------
aggregate PGA target parameter                    432.0 mbytes
aggregate PGA auto target                         338.1 mbytes
global memory bound                                21.6 mbytes
total PGA inuse                                   249.0 mbytes
total PGA allocated                               285.0 mbytes
maximum PGA allocated                             293.8 mbytes
total freeable PGA memory                            .3 mbytes
PGA memory freed back to OS                 4,270,536.8 mbytes
total PGA used for auto workareas                 191.9 mbytes
maximum PGA used for auto workareas               195.1 mbytes
total PGA used for manual workareas                  .0 mbytes
maximum PGA used for manual workareas                .5 mbytes
over allocation count                         640,263.0
bytes processed                               664,782.7 mbytes
extra bytes read/written                       57,335.7 mbytes
cache hit percentage                               92.1 percent

16 rows selected.

The relevant values are:

o aggregate PGA auto target: amount of tunable PGA memory the system can use.
This is the memory left over for sorts, hash joins, bitmap operations and the
like.  The difference between this and the PGA target parameter (93.9 megabytes
in this example) represents parts of the PGA that are not tunable such as cursor
areas, PLSQL and/or Java memory, context information for the session and the
like.

o total PGA inuse: the total amount of PGA currently in use.  Details by process
may be found in V$PROCESS.PGA_MEM_USED

o total PGA used for auto workareas: The current amount of memory being used by
tunable workareas (sorts/hashes and such).  This display shows that I had 191.9
megabytes of memory in use by workareas when I ran this query.

o Maximum PGA used for auto workareas: The maximum memory used by this instance
since startup for these tunable workareas.

o Over allocation count (Oracle9iR2 and up): the number of times Oracle had to
allocate more PGA memory then the PGA_AGGREGATE_TARGET suggested.  This
indicates the PGA target was set too small to accommodate the untunable (session
context, cursor caches, etc) memory plus the tunable component.  This count
should be zero ideally.  On the system I was using, we started the
PGA_AGGREGATE_TARGET at 25megabytes and increased it to 250megabytes and finally
settled in on 432megabytes at the suggestion of "the database".

o Cache hit percentage (Oracle9iR2 and up): this is a measure of how effective
your PGA size is currently and is computed from instance startup on.  If all
work is done in memory - with no temporary space needed, this will be 100% (very
rare, maybe in pure OLTP environments this will be observed).  In this example,
the value of 92.1% is artificially low due to the initial setting of 25megabytes
on this system.

In Oracle9iR1 - you would be interested in reviewing the maximum PGA used for
auto workareas and comparing that to the aggregate PGA auto target.  This will
show you if you've been exceeding or hitting the ceiling on the PGA workarea
sizes.  If the maximum you've used is very close to the auto target - it would
indicate that you hit the ceiling on the amount of memory Oracle was allowed to
use for PGA's and you most likely resorted to disk based operations at that
point.

In Oracle9iR2 and up - there is another crucial view for measuring the
effectiveness of the PGA workareas - that is the V$SQL_WORKAREA_HISTOGRAM view.
This view breaks the PGA workarea sizes into discrete ranges (eg: executions
that used between X and Y bytes of PGA workarea) and keeps counters of how many
times:

o Operations were done in "optimal" mode, optimal being totally in memory
o Operations were done in "one pass" mode, some disk was used but used in a one
pass mode - limited IO was performed
o Operations were done in "multi-pass" mode, the operation was very disk
intensive.

So, for example, on my system we can see:

ops$tkyte@ORA9I> SELECT
  2     case when low_optimal_size < 1024*1024
  3          then to_char(low_optimal_size/1024,'999999') ||
  4               'kb <= PGA < ' ||
  5               (HIGH_OPTIMAL_SIZE+1)/1024|| 'kb'
  6          else to_char(low_optimal_size/1024/1024,'999999') ||
  7               'mb <= PGA < ' ||
  8               (high_optimal_size+1)/1024/1024|| 'mb'
  9          end pga_size,
 10         optimal_executions,
 11         onepass_executions,
 12         multipasses_executions
 13    from v$sql_workarea_histogram
 14   where total_executions <> 0
 15   order by low_optimal_size
 16  /

                                  Optimal   One Pass Multi Pass
PGA_SIZE                       Executions Executions Executions
------------------------------ ---------- ---------- ----------
      8kb <= PGA < 16kb          60725456          0          0
     16kb <= PGA < 32kb            128742          0          0
     32kb <= PGA < 64kb             55579          0          0
     64kb <= PGA < 128kb            36622          0          0
    128kb <= PGA < 256kb            31909          0          0
    256kb <= PGA < 512kb              756          0          0
    512kb <= PGA < 1024kb             402         33          0
      1mb <= PGA < 2mb                 76      24454          0
      2mb <= PGA < 4mb               4716        835          0
      4mb <= PGA < 8mb                734        723         20
      8mb <= PGA < 16mb                37          0        160
     16mb <= PGA < 32mb                 0          2         64
     32mb <= PGA < 64mb                 0          0         32

13 rows selected.

What we can see here is that the larger the requested workarea size - the more
frequently we resorted to using the disk - in fact for sorts/hashes that
consumed more than 16mb of RAM, we resorted to disk each time.  This would be an
indication that our PGA aggregate target was insufficient (the 24,454 one pass
executions for sorts beween 1 to 2m of RAM really indicates that).  In this case
- the tables didn't lie, I had the PGA aggregate target set to 25meg initially
and by correcting the problem and setting it to 250m and later 432m - the disk
usaged dropped off considerably (well, all together).

The last two views are very straightforward (and are only available in
Oracle9iR2 and up).  The first is V$PGA_TARGET_ADVICE and the other is
V$PGA_TARGET_ADVICE_HISTOGRAM.  V$PGA_TARGET_ADVICE has 14 different
PGA_AGGREGATE_TARGET sizes in it - from 12.5% of the current size on up to 800
of the current size.  It then tells you for each size what the estimated PGA
cache hit would be (all work done in memory gives 100%) and how many bytes it
estimates would be read/written to temporary space with that size.  Currently,
on the weekend, my system is telling me:

ops$tkyte@ORA9I> select
  2  trunc(pga_target_for_estimate/1024/1024)
                            pga_target_for_estimate,
  3  to_char(pga_target_factor * 100,'999.9') ||'%'
                            pga_target_factor,
  4  trunc(bytes_processed/1024/1024) bytes_processed,
  5  trunc(estd_extra_bytes_rw/1024/1024) estd_extra_bytes_rw,
  6  to_char(estd_pga_cache_hit_percentage,'999') || '%'
                            estd_pga_cache_hit_percentage,
  7  estd_overalloc_count
  8  from v$pga_target_advice;

                                     Est Est          Est
       PGA                         Extra PGA         Over
    Target Factor      MBytes     MBytes Cache      Alloc
        MB %            Proc.         RW Hit        Count
---------- ------- ---------- ---------- ----- ----------
        54   12.5%       2752       3146   47%          4
       108   25.0%       2752        835   77%          0
       216   50.0%       2752          0  100%          0
       324   75.0%       2752          0  100%          0
       432  100.0%       2752          0  100%          0
       518  120.0%       2752          0  100%          0
       604  140.0%       2752          0  100%          0
       691  160.0%       2752          0  100%          0
       777  180.0%       2752          0  100%          0
       864  200.0%       2752          0  100%          0
      1296  300.0%       2752          0  100%          0
      1728  400.0%       2752          0  100%          0
      2592  600.0%       2752          0  100%          0
      3456  800.0%       2752          0  100%          0

14 rows selected.

Currently, this is telling me that I could cut my PGA aggregate target in half
and retain the same level of "cache hit" - however, I did this report on a
Sunday afternoon - after 2 days of relative inactivity.  I know on Monday
afternoon - it will be a different story.  Which points out an important fact.
You want to inspect this view during your periods of peak activity - the values
will change over time to reflect what is happening in the system.  Also - if you
take the advice and set the values - you will find that the advice table
"resets".  For example:

ops$tkyte@ORA9I> alter system set pga_aggregate_target = 216m;
System altered.

ops$tkyte@ORA9I> select
  2  trunc(pga_target_for_estimate/1024/1024)
                        pga_target_for_estimate,
  3  to_char(pga_target_factor * 100,'999.9') ||'%'
                        pga_target_factor,
  4  trunc(bytes_processed/1024/1024) bytes_processed,
  5  trunc(estd_extra_bytes_rw/1024/1024) estd_extra_bytes_rw,
  6  to_char(estd_pga_cache_hit_percentage,'999') || '%'
                        estd_pga_cache_hit_percentage,
  7  estd_overalloc_count
  8  from v$pga_target_advice;

                                     Est Est          Est
       PGA                         Extra PGA         Over
    Target Factor      MBytes     MBytes Cache      Alloc
        MB %            Proc.         RW Hit        Count
---------- ------- ---------- ---------- ----- ----------
        27   12.5%          0          0    0%          0
        54   25.0%          0          0    0%          0
       108   50.0%          0          0    0%          0
       162   75.0%          0          0    0%          0
       216  100.0%          0          0    0%          0
       259  120.0%          0          0    0%          0
       302  140.0%          0          0    0%          0
       345  160.0%          0          0    0%          0
       388  180.0%          0          0    0%          0
       432  200.0%          0          0    0%          0
       648  300.0%          0          0    0%          0
       864  400.0%          0          0    0%          0
      1296  600.0%          0          0    0%          0
      1728  800.0%          0          0    0%          0

14 rows selected.

It is now saying we could go down to 27mb!  But, since we just reset that value
- we know we must wait a while - until some activity occurs - and query it
again.  For example - I just hit the system with 30 processes I know will take
about 6-7meg of sort area each.  After running that (and not much else), the
advice is:

                                     Est Est          Est
       PGA                         Extra PGA         Over
    Target Factor      MBytes     MBytes Cache      Alloc
        MB %            Proc.         RW Hit        Count
---------- ------- ---------- ---------- ----- ----------
        27   12.5%        432        824   34%          7
        54   25.0%        432        824   34%          1
       108   50.0%        432        824   34%          0
       162   75.0%        432        206   68%          0
       216  100.0%        432        141   75%          0
       259  120.0%        432          0  100%          0
       302  140.0%        432          0  100%          0
       345  160.0%        432          0  100%          0
       388  180.0%        432          0  100%          0
       432  200.0%        432          0  100%          0
       648  300.0%        432          0  100%          0
       864  400.0%        432          0  100%          0
      1296  600.0%        432          0  100%          0
      1728  800.0%        432          0  100%          0

14 rows selected.

So, now it is telling me that it is a little "short" on memory given this sudden
unexpected burst of activity I threw at it!  We need a little more PGA to be
optimum.
Rating: 5
Quite good and exhaustive explaination of the concept April 02, 2003
Reviewer: Prasad
Rating: 5
Excellent !!! April 02, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
Very important stuff.

Tom, can you comment on the following results:

NAME                                  VALUE          UNIT
------------------------------------- -------------- -------
aggregate PGA target parameter                  38.0 mbytes
aggregate PGA auto target                        4.0 mbytes
global memory bound                              1.9 mbytes
total PGA inuse                                163.4 mbytes
total PGA allocated                            182.6 mbytes
maximum PGA allocated                          186.6 mbytes
total freeable PGA memory                        1.1 mbytes
PGA memory freed back to OS                 72,224.0 mbytes
total PGA used for auto workareas                 .0 mbytes
maximum PGA used for auto workareas              4.8 mbytes
total PGA used for manual workareas               .3 mbytes
maximum PGA used for manual workareas            2.7 mbytes
over allocation count                       54,251.0
bytes processed                             29,567.0 mbytes
extra bytes read/written                     1,782.0 mbytes
cache hit percentage                            94.3 percent

As you can see, the TARGET parameter is set far too low from the actual need.

My tests showed that almost all sorts are executed in MANUAL mode, although the
workarea_size_policy is set to AUTO. And the sorts are affected by the
SORT_AREA_SIZE parameter. It seems that if the pga_aggregate_target parameter is
set far too low (or some other reason?), Oracle still uses SORT_AREA_SIZE.

What exactly represents (in this case) the "global memory bound" ? From the
manual it sais:
"This gives the maximum size of a work area executed in AUTO mode. " What
happens if you pass this bound? How is that measured ?

The advice statistics clearly shows what the TARGET value should be like:

PGA                                  ESTD
TARGET                              EXTRA
ESTIMATE PGA_TAR BYTES_PROCESSED BYTES_RW ESTD_ ESTD_OVERALLOC_COUNT
-------- ------- --------------- -------- ----- --------------------
      19   50.0%           12149    19720   38%                 1782
      28   75.0%           12149    19652   38%                 1771
      38  100.0%           12149      274   98%                 1727
      45  120.0%           12149      274   98%                 1678
      53  140.0%           12149      274   98%                 1636
      60  160.0%           12149      274   98%                 1611
      68  180.0%           12149      274   98%                 1577
      76  200.0%           12149      274   98%                 1555
     114  300.0%           12149      274   98%                  981
     152  400.0%           12149       68   99%                  424
     228  600.0%           12149       68   99%                    0
     304  800.0%           12149       68   99%                    0

But I cannot understand why the histogram looks relativelly well (although
strange):

                          OPTIMAL    ONEPASS    MULTIPASSES
PGA_SIZE                  EXECUTIONS EXECUTIONS EXECUTIONS
------------------------- ---------- ---------- ----------
      8kb <= PGA < 16kb       704255          0          0
     16kb <= PGA < 32kb        11135          0          0
     32kb <= PGA < 64kb        26254          0          0
     64kb <= PGA < 128kb        1718          0          0
    128kb <= PGA < 256kb          35          0          0
    256kb <= PGA < 512kb          20          0          0
    512kb <= PGA < 1024kb      21454         44          2
      1mb <= PGA < 2mb          2688         12          0
      2mb <= PGA < 4mb             0          0          2
      4mb <= PGA < 8mb             0          2          4
      8mb <= PGA < 16mb            0          0          7
     16mb <= PGA < 32mb            2          0          2
     32mb <= PGA < 64mb            4          0          4
     64mb <= PGA < 128mb           2          0          1

Anyway, the most important question here is:

When does Oracle start to use MANUAL (even when AUTO is specified) and thus
using SORT_AREA_SIZE?

Thanks.
Followup:
are  you using mts?
did you alter system to set the workarea size policy?

if so, the shared servers that were started -- still are manual, until they go
away and come back.
Rating: 2
Followup to previous April 03, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada.
No, I am not using MTS, all sessions are dedicated.

Yes I did:
alter system set workarea workarea_size_policy = AUTO;

Although it is in AUTO for the system, and i did 'show parameterr workarea'
which confirmed it is in AUTO. However, in V$SQL_WORKAREA I can see it at
MANUAL.
Followup:
then you are looking at old statistics -- from before you did the alter system.

run for a couple more days and the workarea's will outweight the manual way.

the stats don't "flush" at the alter system -- so you have people who used
manual and until you bounce, they'll stay in the v$ table that way.  (and if you
bounce, make sure the workarea size policy is set right in your init.ora or
spfile.
Rating: 2
Followup April 03, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
No, I am afraid this is not the case. I run a query, then find it in v$sql, get
the hash_value and query V$SQL_WORKAREA with that.

And I am 100% positive, that the query I just executed, is executed in MANUAL,
although I have AUTO for the session (and database). If you insist, I will do it
again and paste my steps here.

Besides, as I played arround with the SORT_AREA_SIZE I could see the change in
V$SQL_WORKAREA the allocated memory got changed, so I am absolutelly positive
that I had it auto but Oracle executed in MANUAL.

I think, that there is some condition, which when met Oracle starts executing in
manual (for example when target_pga is way off) I am just trying to find the
condition.
Followup:
tell you what -- set it so auto is in the spfile/init.ora and restart.  that'll
clear out ANY confusion.
Rating: 2
Followup April 03, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
That's how it's been setup for months. And the base is boucing every week (for
offline backup).

So I am certain, that it IS on auto. It's never been changed from the default.

I hope this helps (just did it):

SQL*Plus: Release 9.2.0.1.0 - Production on Thu Apr 3 22:10:53 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 and Oracle Label Security options
JServer Release 9.2.0.1.0 - Production

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
workarea_size_policy                 string      AUTO
SQL> show parameter pga_aggregate_target

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
pga_aggregate_target                 big integer 39845888
SQL>

SQL> declare
  2    CURSOR XCUR IS
  3      select 123 from activities_values
  4      order by free_value;
  5    P NUMBER;
  6  begin
  7    open XCUR;
  8    FETCH XCUR into P;
  9  end;
 10  /

PL/SQL procedure successfully completed.

SQL> select sql_text, sorts, loaded_versions, first_load_time,hash_value from
v$sql where sql_text like 'SELECT 123%';

SQL_TEXT
--------------------------------------------------------------------------------
     SORTS LOADED_VERSIONS FIRST_LOAD_TIME     HASH_VALUE
---------- --------------- ------------------- ----------
SELECT 123 from activities_values     order by free_value
         2               1 2003-04-04/06:14:36   28529089

SQL> select * from v$sql_workarea where hash_value = 28529089;
-- result has been altered, to be more readable
ADDRESS            511F06F0
HASH_VALUE        28529089
CHILD_NUMBER        0
WORKAREA_ADDRESS    528327BC
OPERATION_TYPE        SORT
OPERATION_ID        1
POLICY            MANUAL
ESTIMATED_OPTIMAL_SIZE    6018048
ESTIMATED_ONEPASS_SIZE    1011712
LAST_MEMORY_USED    484352
LAST_EXECUTION        2 PASSES
LAST_DEGREE        1
TOTAL_EXECUTIONS    2
OPTIMAL_EXECUTIONS    0
ONEPASS_EXECUTIONS    0
MULTIPASSES_EXECUTIONS    2
ACTIVE_TIME        1232350
MAX_TEMPSEG_SIZE
LAST_TEMPSEG_SIZE

SQL> alter session set sort_area_size = 1000000;

Session altered.

SQL>
SQL> declare
  2    CURSOR XCUR IS
  3      select 123 from activities_values
  4      order by free_value;
  5    P NUMBER;
  6  begin
  7    open XCUR;
  8    FETCH XCUR into P;
  9  end;
 10  /

PL/SQL procedure successfully completed.

SQL> select * from v$sql_workarea where hash_value = 28529089;
-- altered result again, we have 2 rows

ADDRESS            511F06F0
HASH_VALUE        28529089
CHILD_NUMBER        1           <----------- this is the SECOND run
WORKAREA_ADDRESS    51C67470
OPERATION_TYPE        SORT
OPERATION_ID        1
POLICY    MANUAL
ESTIMATED_OPTIMAL_SIZE    5990400
ESTIMATED_ONEPASS_SIZE    1010688
LAST_MEMORY_USED    909312      <----------- see it's double now
LAST_EXECUTION        1 PASS
LAST_DEGREE        1
TOTAL_EXECUTIONS    1
OPTIMAL_EXECUTIONS    0
ONEPASS_EXECUTIONS    1
MULTIPASSES_EXECUTIONS    0
ACTIVE_TIME        1184246
MAX_TEMPSEG_SIZE
LAST_TEMPSEG_SIZE
------------------------------------
ADDRESS            511F06F0
HASH_VALUE        28529089
CHILD_NUMBER        0          <----------- this is the FIRST run
WORKAREA_ADDRESS    528327BC
OPERATION_TYPE        SORT
OPERATION_ID        1
POLICY    MANUAL
ESTIMATED_OPTIMAL_SIZE    6018048
ESTIMATED_ONEPASS_SIZE    1011712
LAST_MEMORY_USED    484352
LAST_EXECUTION        2 PASSES
LAST_DEGREE        1
TOTAL_EXECUTIONS    2
OPTIMAL_EXECUTIONS    0
ONEPASS_EXECUTIONS    0
MULTIPASSES_EXECUTIONS    2
ACTIVE_TIME        1232350
MAX_TEMPSEG_SIZE
LAST_TEMPSEG_SIZE

Your time is highly appreciate, as always. Again, I am just trying to clarify
when (and why) it starts using manual when we have setup for auto.
Followup:
I'll have to ping you over to support on this one -- I cannot reproduce.

your init.ora seems to be set to auto and pga_aggregate_target is set.

ops$tkyte@ORA920> show parameter pga_agg

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
pga_aggregate_target                 big integer 16777216
ops$tkyte@ORA920> show parameter workarea

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
workarea_size_policy                 string      AUTO
ops$tkyte@ORA920> set autotrace traceonly
ops$tkyte@ORA920> select * from all_objects order by 1, 2,3, 4, 5, 6, 7, 8;

30515 rows selected.

Execution Plan
----------------------------------------------------------
ERROR:
ORA-01039: insufficient privileges on underlying objects of the view

SP2-0612: Error generating AUTOTRACE EXPLAIN report

Statistics
----------------------------------------------------------
        319  recursive calls
          7  db block gets
     148571  consistent gets
        886  physical reads
        120  redo size
    2183437  bytes sent via SQL*Net to client
      22873  bytes received via SQL*Net from client
       2036  SQL*Net roundtrips to/from client
         12  sorts (memory)
          1  sorts (disk)
      30515  rows processed

ops$tkyte@ORA920> select address from v$sql where sql_text like 'select * from
all_objects order by %';

ADDRESS
--------
55867C18

1 row selected.

ops$tkyte@ORA920> @printtbl8 'select * from v$sql_workarea where address =
"55867C18" '
ADDRESS                       : 55867C18
HASH_VALUE                    : 431721971
CHILD_NUMBER                  : 0
WORKAREA_ADDRESS              : 55847FA8
OPERATION_TYPE                : SORT
OPERATION_ID                  : 1
POLICY                        : AUTO     <<<<==========
ESTIMATED_OPTIMAL_SIZE        : 3898368
ESTIMATED_ONEPASS_SIZE        : 859136
LAST_MEMORY_USED              : 843776
LAST_EXECUTION                : 1 PASS
LAST_DEGREE                   : 1
TOTAL_EXECUTIONS              : 1
OPTIMAL_EXECUTIONS            : 0
ONEPASS_EXECUTIONS            : 1
MULTIPASSES_EXECUTIONS        : 0
ACTIVE_TIME                   : 5772269
MAX_TEMPSEG_SIZE              : 4194304
LAST_TEMPSEG_SIZE             : 4194304
-----------------

PL/SQL procedure successfully completed.
Rating: 4
April 09, 2003
Reviewer: Phil Quinn from uk
Good user-friendly explanation.
However, what is the 'PGA Advisory', and how do you use/access it ?
Followup:
You either query it (as I did -- the target advice view)

Or you use statspack and statspack tells you about it.

Or you use OEM and get a pretty chart about it.
Rating: 5
Why Oracle is choosing ONEPASS Execution eventhough I have enough free space? April 21, 2003
Reviewer: A reader
Here are the statistics from my server (9.2.0.1 version)

1. select name,
         to_char(decode( unit,
                  'bytes', value/1024/1024,
                           value ),'999,999,999.9') value,
         decode( unit, 'bytes', 'mbytes', unit ) unit
  from v$pgastat

aggregate PGA target parameter             500.0    mbytes
aggregate PGA auto target             444.0    mbytes
global memory bound                     25.0    mbytes
total PGA inuse                             6.7    mbytes
total PGA allocated                     35.9    mbytes
maximum PGA allocated                     234.0    mbytes
total freeable PGA memory               9.8    mbytes
PGA memory freed back to OS           6,583.1    mbytes
total PGA used for auto workareas                .0    mbytes
maximum PGA used for auto workareas             154.2    mbytes
total PGA used for manual workareas                .0    mbytes
maximum PGA used for manual workareas                .5    mbytes
over allocation count                .0
bytes processed          35,829.3    mbytes
extra bytes read/written           2,480.2    mbytes
cache hit percentage              93.5    percent

2. SELECT
     case when low_optimal_size < 1024*1024
          then to_char(low_optimal_size/1024,'999999') ||
               'kb <= PGA < ' ||
               (HIGH_OPTIMAL_SIZE+1)/1024|| 'kb'
          else to_char(low_optimal_size/1024/1024,'999999') ||
               'mb <= PGA < ' ||
               (high_optimal_size+1)/1024/1024|| 'mb'
          end pga_size,
         optimal_executions,
         onepass_executions,
         multipasses_executions
    from v$sql_workarea_histogram
   where total_executions <> 0
   order by low_optimal_size

PGA_SIZE        OPTIMAL_EXC    ONEPASS    MULTIPASSES
     16kb <= PGA < 32kb    996881    0        0
     32kb <= PGA < 64kb    34264        0        0
     64kb <= PGA < 128kb    13749        0        0
    128kb <= PGA < 256kb    2277        0        0
    256kb <= PGA < 512kb    1487        0        0
    512kb <= PGA < 1024kb    5465        0        0
      1mb <= PGA < 2mb    554        0        0
      2mb <= PGA < 4mb    350        0        0
      4mb <= PGA < 8mb    549        0        0
      8mb <= PGA < 16mb    168        0        0
     16mb <= PGA < 32mb    9        23        0
     32mb <= PGA < 64mb    1        9        0
     64mb <= PGA < 128mb    0        12        0
    128mb <= PGA < 256mb    0        4        0

Why the DB using OnePass (disk) execution even though I have enough free space
(memory)?

The maximum PGA allocated was 234 Mb. It means (aggregate PGA auto Target –
maximum PGA allocated, 444Mb – 234 Mb = 210 MB) the server has 210 Mb when it
was in peck also.

Why it used disk operations when the execution size is > 16 mb?

How to find out the process/sql query that uses >16 Mb pga size?

Thanks
Followup:
because a single process will NEVER use all memory -- it'll use a fraction of it
(where the fraction is less then one)
Rating: 5
Thanks, but still not clear :) April 22, 2003
Reviewer: A reader
Sorry for my dumb brain:) I guess that I was able to understand it partly. Can
you throw some more light?

What do you mean by “(where the fraction is less then one)?

And you didn’t answer my other question.

>>How to find out the process/sql query that uses >16 Mb pga size?

Is there any way to find out the queries/transaction that took >16 Mb PGA size
in my example.

Thanks
Followup:
100/1 is a fraction.  it is greater than 1

1/100 is a fraction.  it is less than 1.

the max pga memory used by a SESSION will be some fraction (that is less than
one) of the aggregate target.  no single session would ever use it ALL

You can use temp_space in v$sql_plan to find queries that are estimated to
exceed that.  Since a single query plan is shared -- and on one run it might use
1m, the next 5m and it might be run a million times -- we do not track it at the
exact sql level.
Rating: 5
Thanks for your comments April 22, 2003
Reviewer: A reader
Rating: 4
one process takes 5% of pga_aggregate_target April 28, 2003
Reviewer: Sravan from Boston, MA
" no single session would ever use it ALL "
Correct. A single process uses upto 5% of the pga_aggregate_target. If it uses
up all the 5%, then it does disk sorts(temp_space being used).
I believe there is a hidden parameter which controls this value for each process
not to exceed 5% of the pga_aggregate_target.
On further investigation I realised that in my case it did use more than 5%. On
further clarification, I learnt that though oracle controls the pga usage by
single process, it can go over the value(5%) and its not a very hard limit.

Want to add to this, anyone ?

Sravan
ps: great site, Tom !!!
Followup:
what case?  this is the first followup from you as far as I can tell.  no
context here at all.

it is really a function of two hidden parameters, not just a n% number at work.
Rating: 3
clarification on Over allocation count April 28, 2003
Reviewer: Sravan from Boston, MA
" Over allocation count "
I am trying to understand this concept. So, please bear with me.

So lets say, if A process uses more than a fraction(5%) of pga_aggregate_target,
it uses the temp_space for a disk sort. Right ?

And if more than 20 such processes(each using 5%) are run at the same time, then
oracle allocates more pga memory over and above the the pga_aggregate_target.

Am I correct in my understanding ?
Thanks again
Sravan
Followup:
No, not really -- it is as I said:

...
Over allocation count (Oracle9iR2 and up): the number of times Oracle had to
allocate more PGA memory then the PGA_AGGREGATE_TARGET suggested.  This
indicates the PGA target was set too small to accommodate the untunable (session

context, cursor caches, etc) memory plus the tunable component.  This count
should be zero ideally.  On the system I was using, we started the
PGA_AGGREGATE_TARGET at 25megabytes and increased it to 250megabytes and finally

settled in on 432megabytes at the suggestion of "the database".
....

we can control the sort/hash etc area sizes at run time.  We have no control
over these other untunable (by us at run time) things.  It can be that the sum
of these untunables + the workareas exceed the TARGET and that is an
overallocation.
Rating: 3
5 % ? April 28, 2003
Reviewer: Sravan from Boston, MA
Sorry Tom. My apologies about the "my case". I was not completely correct about
the 5% either.

From metalink article Note:147806.1
" PGA_AGGREGATE_TARGET limits both the global PGA consumption and the size of a
workarea i.e. the memory allocated to a single SQL operator is also limited  to
min(5% PGA_AGGREGATE_TARGET, 100MB) for serial operations, and to  30%
PGA_AGGREGATE_TARGET/DOP for parallel operations (DOP=Degree of Parallelism).  "

Sorry about the confusion !!
-Sravan
Rating: 5
Disabled parameters May 02, 2003
Reviewer: juancarlosreyesp@yahoo.com from Boliiva
Very interesting answer.

Tom, please
1) Which parameter should not be touched once you set
WORKAREA_SIZE_POLICY = AUTO
PGA_AGGREGATE_TARGET = xM

2) Under the previous setting, if you set one of that parameter does it affect
or Oracle simply ignores them.

:) Thanks
Followup:
1) not that they "shouldn't be" more that "won't affect anything"

*_size parameters

2) ignores it.
Rating: 5
please more specific May 02, 2003
Reviewer: juancarlosreyesp@yahoo.com from Bolivia
Thanks tom

Could you please be more specific

I did a select *_size and I got this
or did you want to say sort*_size

Thanks

select name from v$parameter where name like  '%_size'
NAME                                                        shared_pool_size
                                       sga_max_size
                  shared_pool_reserved_size
large_pool_size                                             java_pool_size
                                       java_max_sessionspace_size
                  db_block_size
db_keep_cache_size
db_recycle_cache_size                                       db_2k_cache_size
                                       db_4k_cache_size
                  db_8k_cache_size
db_16k_cache_size                                           db_32k_cache_size
                                       db_cache_size
                  global_context_pool_size
create_bitmap_area_size
bitmap_merge_area_size
parallel_execution_message_size                             hash_area_size
                                       max_dump_file_size
                  oracle_trace_collection_size
object_cache_optimal_size                                   sort_area_size
                                       sort_area_retained_size
                  olap_page_pool_size
Followup:
sort area
hash area
bitmap merge area

guess i should say "area%size"
Rating: 5
great architectural discussion May 02, 2003
Reviewer: Alberto Dell'Era from Milan, Italy
Rating: 5
May 02, 2003
Reviewer: A reader
thanks :)
Rating: 5
predicte pga_aggregate_target May 21, 2003
Reviewer: A reader
To predicte pga_aggregate_target, if the parameter PROCCESSES=200. AS your
advice, 1M for each proccesses, then we have:
pga_aggregate_target = 1*200=200M.

200M of the PGA is good for 200 concurrent users of a OLTP  (very little on
sorting)?

Thanks many
Followup:
it would be on the low end, but it would be a starting point and the pga advisor
will help you adjust it up or down if needed.
Rating: 5
June 04, 2003
Reviewer: A reader
Is there any side effects of setting pga_aggregate_target too high? Can it cause
performance degradation as long as we ensure PGA+SGA is within real memory and
does not page?
Followup:
it'll affect plans -- it'll be like setting sort_area_size really high.

but if you set it to the size you would like to use given REAL memory (really
available memory), that is what you want to do.
Rating: 2
Excellent June 04, 2003
Reviewer: Nasir Sarwar from TX USA
When I started like this new 9i feature, I found out that an HP known "BUG"
cause memory leakage when setting pga and work area policy to 'AUTO'. Though it
is fixed in 9.2.0.2 and later, these patch set would not allow to
install/de-install specific components at ease. Oracle mentioned that the new
release will be 10i, so I think I will have to wait to get the benefit of pga
and work area "AUTO" on HP. To test it, set pga like 25 MB and work area policy
to 'AUTO', Log in from SQL*Plus on a machine other than the machine on which the
HP-UX database resides many time till you hit ORA-27102: out of memory. Now
unset pga (comment out) and work area policy to manual. Log in to the database
as many time you like. You wll not hit ORA-27102.
Rating: 5
Thanks June 06, 2003
Reviewer: A reader
I didn't understand your answer.

>>it'll affect plans -- it'll be like setting sort_area_size really high.

What are the disadvantages when setting this memory too high?
Followup:
say you have 100meg of free memory.

say you set sort_area_size to 20m

say you know you have 100 users.

say you know that 10 of them will be active concurrently and could be doing
sorts.

you now have a system where you've given the users the OK to allocate 200m of
ram at the same time -- but you only have 100meg of ram.  swapping/paging
heavily = in your future.  that is not good.
Rating: 4
why sort_area_size? June 06, 2003
Reviewer: Reader
I thought if you set pga_aggregate_target and workarea_size_policy=auto then, no
need to set sort_area_size. Oracle will auto manage allocating sort area memory
to users dynamically depending upon the load/number of users etc. Is it correct?

I also thought oracle would ignore sort_area_size if i had set
pga_aggregate_target? Please correct me. Thanks.

If I had set pga_aggregate_target and did not set workarea_size_policy=auto
then, what Oracle would do? Thanks.
Followup:
correct.

correct.

oracle would ignore pga_aggregate_target if workare_size_policy = manual and use
the *_size settings instead., oracle will use pga_aggregate_target if it is auto
and ignore the *_size settings.
Rating: 5
workarea_size_policy=auto and query plan June 10, 2003
Reviewer: Justin from Australia
Is it possible that Oracle selects  different plan depending  on the available
memory, something like: enough memory – hash join, not enough memory - nested
loop?
Followup:
yes. just like dynamically changing the sort_area_size/hash_area_size would
Rating: 5
Excellent!!! August 04, 2003
Reviewer: Peter from Houston, TX USA
The gospel according to Tom...I see the light!

Thanks!!!
Rating: 3
PGA information November 04, 2003
Reviewer: bhavesh from india
hi;

The article was very usefule to me.  I have made some changes on the basis of
article. Let's see how fast i get benifits
Rating: 5
automatic PGA setting.. December 24, 2003
Reviewer: Nitin Kumar from Quincy MA
Hi Tom,

We are running 9.0.1.4 and I changed the pga_aggregate_target to 25 Mb.
This is what I see.

SYS@TSTPDB01 >ed
Wrote file afiedt.buf

  1  SELECT name, value
  2* FROM V$PGASTAT
SYS@TSTPDB01 >/

NAME                                          VALUE
---------------------------------------- ----------
aggregate PGA auto target                  10485760
global memory bound                            1280
total expected memory                             0
total PGA inuse                            42614784
total PGA allocated                        58347520
maximum PGA allocated                     207346688
total PGA used for auto workareas                 0
maximum PGA used for auto workareas        13384704
total PGA used for manual workareas               0
maximum PGA used for manual workareas       5666816
estimated PGA memory for optimal                  0
maximum PGA memory for optimal             61865984
estimated PGA memory for one-pass                 0
maximum PGA memory for one-pass            61865984

14 rows selected.

Since the maximum PGA used for auto workareas is more than aggregate PGA Auto
target does it mean I should increase my pga_aggregate_target?

Thanx
Followup:
you know that the pga aggregate target is for the INSTANCE, not a session -
so... 25m is teeny tiny!

see the review that contains:

We'll start with Oracle9iR1 techniques

above to get the advice on what to do.
Rating: 5
December 24, 2003
Reviewer: Nitin Kumar from Quincy MA
Thanx for the quick response Tom.
1) How do I find the opitmal value for pga_aggregate_target in 9iR1.

2) When I run this query, I see this.
optimal workarea executions is 99%.
Is this not good or shall I set paga_aggregate_value high so that the workarea
executions - multipass  is close to 0.

SYS@TSTPDB01 >l
  1  SELECT name profile, cnt, decode(total, 0, 0, round(cnt*100/total)) percent
age
  2  FROM (SELECT name, value cnt, (sum(value) over ()) total
  3  FROM V$SYSSTAT
  4* WHERE name like 'workarea exec%')
SYS@TSTPDB01 >/

PROFILE                                                                 CNT
---------------------------------------------------------------- ----------
PERCENTAGE
----------
workarea executions - optimal                                        127050
        99

workarea executions - onepass                                           701
         1

workarea executions - multipass                                         580
         0
Followup:
please read
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:8759826405304#8966909181192
it covers this.
Rating: 5
session settings January 03, 2004
Reviewer: A reader
Hi

In our DWH etl processes we give quite big hash_area_size for batch jobs, before
each process we do

alter session set sort_area_size=64000000;
alter session set hash_area_size=64000000;

We are going to migrate to 9i and use this new automatic PGA thing. My question
is if we use automatic PGA management will these "alter session " still be
valid???
Followup:
valid yes.

obeyed, or even looked at?

no.

if your workarea policy is auto, pga_aggregate_target is used.

if your workarea policy is manual, sort area, hash area are used.

You can set this at the session level for big batches if you like (the work area
policy)
Rating: 5
alter session January 03, 2004
Reviewer: A reader
Hi

Normally when we do alter session these sort and hash parameters most of the
times we modify CBO behaviour but these are necesary since a bigger hash* and
sort* make batch jobs faster. If we use automatic PGA management for batch jobs
and set a very high pga_aggregate_target value will Oracle be so clever and set
a high sort and hash areas when there are lots of free memory in
pga_aggregate_target? I mean if this is the case we are going to see batch jobs
change their plan probably everyday and become unstable?

Our database is a CRM, OLTP, Batch and some Reporting mixed.

Anyone share their real world experience using automatic PGA management in
Hybrid databases?

TIA
Followup:
if there are few other users, and the pga aggregate target is set, getting 64m
of SAS and HAS won't be an issue.
Rating: 5
yes but January 03, 2004
Reviewer: A reader
Hi

The problem is since if we are using AUTO pga management Oracle wont look at our
alter session statements so how will Oracle assign a 64M when there are
necessary? How can he know, obviously not. So I guess manual is the way to go
for Batch Jobs and DWH
Followup:
that is exactly the point.

the database looks at:
a) what you are asking to do
b) who else is asking to do things
c) how much ram you've said "i'm willing to dynamically allocate for the
database"

and gives you as much as it can, without giving you so much that the other users
would be starved.

it is an adaptable algorithm for memory allocation based on current workload.
You tell us what you want to divvy up, and we do so.
Rating: 4
from your last reply January 07, 2004
Reviewer: A reader
You said Oracle will look what we are asking for, how does he know that? I mean
query plans are dependent on PGA settings (at least in 8i), I am not sure how
can Oracle determine an optimum plan for a query if he does not know any PGA
settings to calculate the costs for sort/hash/nested loop joins? I guess he
takes pga_aggregate_target and subtracts the used memory and assuming the rest
can be used for a query?

Also in one of your replies you said you tested with 6 or 7 queries which you
know they need 6mb sort area size... How did you calculate that?!
Followup:
oracle DOES know the pga settings, it is simply called "pga_aggregate_target" in
9i instead of *_size.

which reply?  then I can answer that (probably used all_objects with a known key
size and a known number of rows and multiplied -- but refresh my memory and show
me which reply that was and I'll clarify)
Rating: 5
the reply which says January 07, 2004
Reviewer: A reader
It is now saying we could go down to 27mb!  But, since we just reset that value
- we know we must wait a while - until some activity occurs - and query it
again.  For example - I just hit the system with 30 processes I know will take
about 6-7meg of sort area each.  After running that (and not much else), the
advice is:
Followup:
you use the "advisors" when you are running at "normal load"

if you let the machine sit idle for a while, it is going to say "don't need very
much here"

if you monitor the machine when it is busy, it'll tell you "this is what you
need at this load"

(seems sensible no?  if you ask me how busy I am at 10pm at night, most of the
time I'll say "not very busy at all".  Ask me at 10am in the morning and most of
the time I'll say "man, I'm jammed -- totally swamped").  So, what do I need to
size my caloric intake, my stress levels, etc for?  10pm or 10am?)
Rating: 2
Max session memory? January 07, 2004
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
Tom,

I have tried to setup the PGA_AGGREGATE_TARGET instead of the *_size parameters,
however I ran into a strange issue.

It was refusing to give mor than ~90 Mb of memory for sorting of a given
session. Even thow I was setting PGA_AGGREGATE_TARGET to insane values like
16'000 Mb. Setting back to manual, and sort_area_size to 400 Mb, the same query
used ~150 Mb of memory.

Is there such a hard-coded limit of ~90 Mb ?
Followup:
the real question is - which performed better or more then good enough.
Rating: 5
PGA settings and Transaction Monitors January 07, 2004
Reviewer: A reader
Hi

I wonder what PGA management should we use in a environment where Transaction
Monitor (such as Bea Tuxedo) is used or where a connection pool is used? Should
we use Automatic or Manual? I am wondering because these two is very similar to
MTS
Followup:
if you are using MTS connections -- in 9i, it will always be manual.

so, the question comes does to "which type of connections are you using"
Rating: 1
Follow up for Tom January 07, 2004
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
> the real question is - which performed better or more then good enough

The One that was using automatic settings and used 90 Mb of memory for sorting
also used ~80 Mb of temporary space and was running for about 150 seconds.

The One that used manual settings and used ~160 Mb of memory for sorting, ran
for about 100 seconds.

The difference is significant in favor for manual settings.

I could probably reproduce the cases and paste here with full details.
Followup:
yes there are limits to the amount of ram used by a session with
pga_aggregate_target.

In my testing -- larger sort area sizes

a) sometimes go faster
b) sometimes go way slower (like a alot slower)

In most cases -- for ad-hoc queries, for DW type work -- pga_aggregate_target is
what you are looking for.

In a controlled environment, like "i'm the only guy running this batch right
now, no one else on" using fine tuned SAS/HAS might make sense.

But - consider what happens on your system when

a) you use pga_aggregate_target and have lots of users sorting (on my systems,
the sort workarea is backed off, everyone runs as good as they can)

vs

b) using a fixed size SAS with lots of users sorting (on my systems, we swap and
elapsed time goes through the roof)

that is the problem the pga aggregate target is solving -- what happens when you
have a varying number of users over the course of the day -- do you size SAS
for:

1) the fewest (meaning too big for peak and meaning really bad -- really bad
performance then)

2) the average (meaning at small usages -- not near optimal, at large usage --
really bad -- only during average is it good)

3) the peak (meaning really small sizes all of the time)
Rating: 3
. January 08, 2004
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
I wonder why this limit is not mentioned into the documenation. I am correct
that it is 90 Mb?

I would like to use pga_aggregate_target, but the issue I am facing is the
following.

The database is DW style, and has a connection pool. A lot if inactive sessions.
Experimentally, i have discovered that oracle divides pga_aggregate_target to
the number of sessions, and gives to a new session a maximum of the result. In
my case, I have 20 sessions, and pga_target at 1 Gb. That's 50 Mb per session.
My initial expectation were that non-sorting sessions would not be counted, and
in this case more memory would have been available.

What would recomend in my situation?
1. Use a larger then expected pga_aggergate_target, to compensate for idle
sessions
2. Use manual settings.

I understand, that any moment an idle session can start consuming memoring for
sorting, but it's almost the same as making a new connection to the database,
which will also cause memory re-distribution.
Followup:
well, as with ALL things "auto", the details are left -out- (else, it would not
be so auto anymore).

it is a % of the total pga which has also a cap and is subject to change and in
fact already HAS changed.

It was funny -- on my system, I did the test and found that whilst the
pga_aggregate_target used TEMP, it did so in a fashion that caused it to run 3
times faster then using a sort area size (that went to swap).....

use the 95/5 rule here -- 95% of the time (in 95% of the situations), pga
aggregate is more then appropriate, better than good enough.  in 5% of the
cases, we may find it is not appropriate.  You might be a case where you say
"nope, i don't want auto-pilot, i prefer to DIY"
Rating: 5
sort_area_size and optimal executions March 19, 2004
Reviewer: A reader
Tom,
you mentioned earlier in this thread the following :
-----
In Oracle9iR2 and up - there is another crucial view for measuring the
effectiveness of the PGA workareas - that is the V$SQL_WORKAREA_HISTOGRAM view.
This view breaks the PGA workarea sizes into discrete ranges (eg: executions
that used between X and Y bytes of PGA workarea) and keeps counters of how many
times:

o Operations were done in "optimal" mode, optimal being totally in memory
o Operations were done in "one pass" mode, some disk was used but used in a one
pass mode - limited IO was performed
o Operations were done in "multi-pass" mode, the operation was very disk
intensive
-----

I am on HP 9i with workarea_size_policy = MANUAL. I use SAS/HAS. I understand
that the above statistics are gathered for WORK_AREA_POLICY=AUTO setting.
But If I use SAS/HAS, how I can find out the # of passes for my operation ? I am
in the process of finding the optimal value for my sort_area_size

Thanks
Followup:
you cannot, you would have to be using auto.  else it is just "sorted to disk or
did not sort to disk"
Rating: 4
thanks March 20, 2004
Reviewer: A reader
Thanks Tom.
So, there is no alternate way to find out if query was sorted within
sort_area_size OR spilled over sort_area_size and went to disk ?
Then how would we tune Sort_area_size value ?
Followup:
you use pga_aggregate_target and automatic workarea policys :)

either that or you monitor for sorts to disk and watch
v$sort_segment/v$sort_usage to see what people are doing over time and up/down
it as needed.

very hard to manually tune SAS - is there one perfect number for all users on
your system?  probably not, that is what the pga target is all about.
Rating: 5
map()/unmap() for automatic PGA memory management only ? March 23, 2004
Reviewer: Alberto Dell'Era from Milan, Italy
> One last word about automatic PGA memory management. On operating systems that
> allow for it, the PGA memory is actually physically de-allocated,
> released back to the operating system. Instead of using traditional
> process heap memory allocated via the C routines malloc(),
> PGA workareas can be allocated via map() and unmap() memory calls instead.

Does this apply only to automatic PGA memory management, or to manual as well ?

tnx
Alberto
Followup:
only auto.
Rating: 4
need help regarding pga tuning April 01, 2004
Reviewer: reader from nj , usa
I am wondering about steps to tune PGA_AGGREGATE_SIZE.

like fist query v$pgastat.
Which gives u the current values.

Then look for may be

V$SQL_WORKAREA_ACTIVE ?
But u get only active query statistics  here?

I am little confused.
Can u give steps....to follow ?

I did check metalink note

Automatic PGA Memory Managment in 9i

Still not very clear abt steps.
Followup:
use the pga summary advisor in statspack or OEM.

they both tell you "if you use this setting, this will be the expected outcome"
April 01, 2004 ali -- Thanks for the question regarding "database links ", version oracle 8.0.5
You Asked
how can i create database links to access remote databases.
please tell me the procedure of creating database links.
and we said...
You need a tnsnames entry in your tnsnames.ora on the server.
If you can:

sqlplus scott/tiger@some_other_database

from the machine the server you want to create the database link
ON works -- you've gotten the first step done.

For example, I can:

$ sqlplus scott/tiger@ora8idev

SQL*Plus: Release 8.1.5.0.0 - Production on Thu Jul 20 09:16:25
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>

Once you have that setup, you log into the database (the local
database -- the one you want to create the database link in to
connect to the OTHER database) and issue the create database
link command (see the sql reference manual for complete syntax).
 For example I can:

scott@8i> create database link ora8idev
  2  connect to scott
  3  identified by tiger
  4  using 'ora8idev'
  5  /

Database link created.

scott@8i> select * from dual@ora8idev;

D
-
X

scott@8i>

I do not have to use the connect to and identified by clauses,
if I do not, it will use the login and password of the currently
connected user  to connect to the remote database.
Review & Followup

Rating: 4
tnsnames March 10, 2002
Reviewer: mohammad toaha from Dubai, UAE
My problem description is detailed below:

SQL>  CONNECT DEWADBA/DEWADBA      -- Connected to local database
Connected.
SQL>  CREATE TABLE T12 AS SELECT * FROM W_NODE@DISPDC;
 CREATE TABLE T12 AS SELECT * FROM W_NODE@DISPDC
                                          *
ERROR at line 1:
ORA-02019: connection description for remote database not found

SQL> CREATE TABLE T12 AS SELECT * FROM W_NODE@DISPDC;
CREATE TABLE T12 AS SELECT * FROM W_NODE@DISPDC
                                         *
ERROR at line 1:
ORA-02019: connection description for remote database not found

SQL> CONNECT DEWADBA/DEWADBA@DISPDC         -- connected to remote database
Connected.

Then,as an alternative, I tried to cretae a database link as below:

SQL> create database link dispdc1
  2  connect to dewadba identified by dewadba98
  3  using 'dispdc';

Database link created.

SQL> connect dewadba/dewadba
Connected.
SQL> create table t12 as select * from w_node@dispdc1;
create table t12 as select * from w_node@dispdc1
                                         *
ERROR at line 1:
ORA-02085: database link DISPDC1.world connects to ORACLE.WORLD

- I worked around the tnsnames.ora file in various ways but could not succeed.
Followup:
search for ora-02085 on this site and you'll see what needs be done for that.

For the first problem, the ora-02019 - you don't have a DATABASE LINK yet, so I
would not expect that to work.
Rating: 5
How to create synonym for a database link September 07, 2002
Reviewer: Suresh&kanagaraj from Chennai
Hi Tom,
   I have created a dblink as follows
  create PUBLIC database link ORACLE.US.ACME.COM
  connect to scott
  identified by tiger
  using 'VINSERVER'

 select * from emp@ORACLE.US.ACME.COM
 /

 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

Then i have created a synonym for this database link as follows

  1* create PUBLIC synonym syn_oracle for emp@ORACLE.US.ACME.COM
SQL> /

Synonym created.

Now when i describe the synonym i get the following error

SQL> DESC SYN_ORACLE;
ERROR:
ORA-02019: connection description for remote database not found

Can you pls help

Thanks
Suresh&Kanagaraj
Followup:
describe doesn't always work over a DBLINK.

My *preferred* method for doing this is:

create view V as select * from emp@whereever;

That has the added benefit that the metadata for V is pulled into our local
database (eg: you can select * from user_tab_columns where table_name = 'V' for
example).  More applications -- specially ones that rely on the data dictionary
-- can use V this way then if V was a public synonym (and I'm not very fond of
public synonyms in GENERAL -- they have performance implications and more
importantly -- they polute the namespace, leading to issues with more then one
application in a database.  What happens when app1 has an EMP table ana so does
app2?  Who gets the public synonym?  I rarely use them)
Rating: 4
time out tns February 04, 2003
Reviewer: Marcio from Brazil
Tom, What is wrong?

sre_d@SISNUM8I> exec print_table ( 'select * from user_db_links' );
DB_LINK                       : MRP.AQUARIUS.CPQD.COM.BR
USERNAME                      : SCOTT
PASSWORD                      : TIGER
HOST                          : mrp
CREATED                       : 04-feb-2003 13:50:59
-----------------

PL/SQL procedure successfully completed.

sre_d@SISNUM8I> select * from global_name;

GLOBAL_NAME
----------------------------------------------------------------------
SISNUM8I.AQUARIUS.CPQD.COM.BR

sre_d@SISNUM8I> select * from dual@mrp;
select * from dual@mrp
                   *
ERROR at line 1:
ORA-12535: TNS:operation timed out

sre_d@SISNUM8I> alter session set global_names = false;

Session altered.

sre_d@SISNUM8I> select * from dual@mrp;
select * from dual@mrp
                   *
ERROR at line 1:
ORA-12535: TNS:operation timed out

sre_d@SISNUM8I> alter session set global_names = true;

Session altered.

sre_d@SISNUM8I> select * from dual@mrp;
select * from dual@mrp
                   *
ERROR at line 1:
ORA-12535: TNS:operation timed out

sre_d@SISNUM8I>
sre_d@SISNUM8I> @conn scott/tiger@mrp
scott@MRP816>

C:\Oracle\Ora81\network\ADMIN>type sqlnet.ora
# SQLNET.ORA Network Configuration File:
c:\Oracle\Ora81\network\admin\sqlnet.ora
# Generated by Oracle configuration tools.

# NAMES.DEFAULT_DOMAIN = aquarius.cpqd.com.br

SQLNET.AUTHENTICATION_SERVICES= (NTS)

NAMES.DIRECTORY_PATH= (TNSNAMES, ONAMES, HOSTNAME)

C:\Oracle\Ora81\network\ADMIN>type tnsnames.ora
# TNSNAMES.ORA Network Configuration File:
c:\Oracle\Ora81\network\admin\tnsnames.ora
# Generated by Oracle configuration tools.

EXTPROC_CONNECTION_DATA.AQUARIUS.CPQD.COM.BR =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC0))
    )
    (CONNECT_DATA =
      (SID = PLSExtProc)
      (PRESENTATION = RO)
    )
  )

mrp =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = CPQD020496)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = mrp816)
    )
  )

sre =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = CPQD050068)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = SISNUM8I)
    )
  )
C:\Oracle\Ora81\network\ADMIN>type listener.ora
# LISTENER.ORA Network Configuration File:
c:\Oracle\Ora81\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 = CPQD020496)(PORT = 1521))
      )
    )
    (DESCRIPTION =
      (PROTOCOL_STACK =
        (PRESENTATION = GIOP)
        (SESSION = RAW)
      )
      (ADDRESS = (PROTOCOL = TCP)(HOST = CPQD020496)(PORT = 2481))
    )
  )

SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (SID_NAME = PLSExtProc)
      (ORACLE_HOME = c:\Oracle\Ora81)
      (PROGRAM = extproc)
    )
    (SID_DESC =
      (GLOBAL_DBNAME = mrp816)
      (ORACLE_HOME = c:\Oracle\Ora81)
      (SID_NAME = mrp816)
    )
  )
Followup:
well, the mrp in a database link has no connection, relationship or anything to
mrp used in "sqlplus scott/tiger@mrp"

the mrp "host" in the dblink is resolved using the SERVERS tnsnames.ora -- not
yours.

the mrp in the connect string scott/tiger@mrp is resolved using your
tnsnames.ora.

the two most likely have nothing to do with eachother.  You need to look at the
tnsnames.ora file on the server, figure out what MRP means there and then you
can determine why that server (which may will be different then your mrp server)
is not responding.
Rating: 5
Thank you February 05, 2003
Reviewer: Marcio from Brazil
ALWAYS -- You right.
Rating: 3
Fake DB Links and connections February 18, 2003
Reviewer: C. Parmentier from France
Hi Tom,

I have a few more questions about DB Links, how will Oracle8i behaves when he
sees a such SQL Request
(MYDB is NOT a DB Link, it's the name of the DB and no Loopback Links has been
created for it)

SQLPLUS /@MYDB
SELECT * FROM MY_TABLE@MYDB

IMHO It runs like :
1) Read the database link tables to see if it is a dblink if so make a
connection using that  FALSE
2) Look at the order of the sqlnet.ora file to see where it is picking it's
tnsname aliases from. In this case the format is TNSNAMES,ONAMES
3) Read tnsnames.ora to find out where MYDB is TRUE
4) Go out of the database and create a new oracle connection into the server and
database  - TRUE - resource used.

How can I really point this out using V$...

Thanks a lot for the help!

PS: You would say "Why ?, just remove the @MYDB of your request!" and you would
be right ;) But, our process has to work on a single DB or on 3 DB for each
areas...

PPS: You're really impossible to be reached at GMT+1 :) I should be working at
night!
Followup:
what is a "fake dblink"??

If your process has to work on a single db or 3 db's -- you should use VIEWS to
hide that fact.  You setup each database so the objects actually exist.  Views
can be used for location transparency very nicely.
Rating: 4
No views February 18, 2003
Reviewer: C. Parmentier from France
Well, i'll keep the views in mind, would you please explain the case I proposed:

how does oracle behaves when Selecting from table@<localdbname>, will a new
connection be created or will Oracle check that localdbname is not a Database
Link and thus, won't look out the TNS and so on.

Thanks and Regards,
Followup:
<localdbname> WILL BE a database link.  I don't know what you mean by "a fake
database link".
Rating: 4
March 02, 2003
Reviewer: green from China
Hi Tom,

I have database A,B,C.
I only have accounts for A and B, but in A I can use a public dblink to access
the table in C, right now I want to access the table in C from the database B.
Is it possible ?
Thanks!
Followup:
if you create the same public dblink in B sure, else B will have to query a view
on A that queries the table on C which would probably be about as slow as you
could make it go.
Rating: 4
March 03, 2003
Reviewer: green from China
If I have not priviledges to create a view in A which is a Production database (
I only have SELECT priviledges)and no public link from B to C, so is it possible
?

Thanks!
Followup:
You'll need it -- or create synonym privilege.

there is no way to say "select * from (t@some_other_site)@some_site"

you would need a view or a synonym at some_site so you can query

select * from v@some_site
select * from s@some_site.
Rating: 4
Global Name March 04, 2003
Reviewer: Abubaker Khered from Jeddah, Soudi Arabia
Hi Tom,

Is there any way to change global_name in DB 8.0.5., other then updating
global_name table using sqlplus?

Thanks,
Abubaker
Followup:
umm, you cannot do that.

you can however use the ALTER DATABASE command.
Rating: 3
March 04, 2003
Reviewer: jens
hello tom,

is there any way to set/get values of package variables on the remote database
through a database link (our application uses such variables as an "environment"
used within views).
Followup:
you need getter/setter routines.

You MUST have "get" routines -- that is what the views would use as a view
cannot just reference a PLSQL variable directly.

You should add "set" routines to set the values.

short of that, no not really:

ops$tkyte@ORA815> create synonym remote_pkg for
remote_pkg@ora817dev.us.oracle.com;

Synonym created.

ops$tkyte@ORA815> exec remote_pkg.variable := 55;
BEGIN remote_pkg.variable := 55; END;

*
ERROR at line 1:
ORA-06550: line 1, column 18:
PLS-00512: Implementation Restriction: 'REMOTE_PKG.VARIABLE': Cannot directly
access remote package variable or cursor
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

(well, you could use DBMS_SQL@remote to dynamically execute a remote block but
that would be painful)
Rating: 2
March 05, 2003
Reviewer: jens
is there any way to use packages variables on REMOTE site through set/get
routines (package states are LOCAL to a session)?
Followup:
yes, if you have a package with getter/setter routines on the remote site, it
works as normal (you HAVE a session on the remote site)
Rating: 4
Loopback dblink March 25, 2003
Reviewer: Dave from Prakash, India
Tom, could you please clarify what is a loopback db link and how it is useful?
Thanks as always.
Followup:
it is what it sounds like, a dblink that connects back to the SAME database.

It is useful for testing...

It is useful for connecting as another user in your current session (eg: the
loopback link can connect as scott/tiger whilst you are logged in as bob)...
Rating: 4
example please March 25, 2003
Reviewer: Dave
Tom, could you give an example? Thanks.
Followup:
example of what?  a create database link statement??

ops$tkyte@ORA920> create database link ora920@loopback
  2  connect to scott
  3  identified by tiger
  4  using 'ora920';

Database link created.

ops$tkyte@ORA920> select * from dual@ora920@loopback;

D
-
X

ops$tkyte@ORA920> select * from emp;

no rows selected

ops$tkyte@ORA920> select count(*) from emp@ora920@loopback;

  COUNT(*)
----------
        14
Rating: 4
Beautiful Information March 26, 2003
Reviewer: George Kennedy (GK) from USA
I've 4 databases A,B,C an D which are connected through one another through
database links. A single schema can have multiple links which connect to
different schemas on the remote database. All the links were created by using
the option 'connect to <username> identified by <password>'. Now if I change the
password of the user on database D which is referred by the links on A,B and C.

Can you please tell me
1. Do the links on A,B, and C become "INVALID" which access the particular
schema in database D.
2. Do I've to re-create the DB links again on A,B, and C ? or is there any other
way to recompile the links ?
3. How to identify the links which are affected on A,B and C by the password
change ?
Followup:
1) no, they just stop working.

2) yes, you need to recreate them.

3) select db_link, username from all_db_links;
Rating: 4
Database Link and FGAC March 28, 2003
Reviewer: Ray from Ottawa,On
I need to pull data from Server A to another server B periodically. Server A has
data protected via FGAC and application context. What has to be done to access
the data on server A?

tia for your help.
Followup:
just query it?  FGAC is designed to be transparent
Rating: 4
database links April 23, 2003
Reviewer: Rich from NJ
I was asked by my management to quantify the number of times a database link was
used to connect to a remote database. Is there a way?

Thanks.
Followup:
easiest way I can think is you hide the links with views (lots of good reasons
for doing that in any case) and audit selects on the views.  That'll tell you
pretty much "exactly"
Rating: 5
database link and NTS May 14, 2003
Reviewer: Oracle-Leo from India
Hi,

I just faced a peculiar problem while using database links.
I have two databases on one Win2K server and wanted one DB to be accessed by
another DB using database links. After creating the db link, when i tried to
access the table in second DB using the command 'select * from url@abc', it gave
me the error 'ORA-12640 Authentication adapter initialization failed'.

I found a note on web which stated that this could be resolved by
commenting/removing the sqlnet.authenication_services='NTS' in SQLNET.ora file.
It did solve my error, but was curious why did it matter. And how could I make
it work with sqlnet.authentication_service=NTS.

Thanks
Followup:
you would need windows "security stuff" like domains and such such up.  See the
windows Oracle admin guide (part of your doc set).  I run no windows, never set
it up myself.  It is security specific to that OS.
Rating: 4
hide the link????? May 14, 2003
Reviewer: Reader from Salt Lake City, UT
"easiest way I can think is you hide the links with views (lots of good reasons
for doing that in any case) and audit selects on the views.  That'll tell you
pretty much "exactly"".

Tom, what do you mean by hide the links with views? can you please clarify? I am
going blank on this idea. any example would be appreciated. Thanks.
Followup:
create view v as select * from t@dblink;
grant select on v to ....;
audit the selects on v.

people don't use the dblink, they use a view.
Rating: 5
performance implications? May 14, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
Can't "count" how many times I have been benefited by the discussions and
explanations here. Really learned a lot. This count query never returns - it's
still counting - no matter how much we tune it...

As for the question:
In your earlier reply to this thread you said this:

"(and I'm not very fond of
public synonyms in GENERAL -- they have performance implications"

Just curious about the performance issues, if any, of using synonyms as compared
to views? Apart from the maintenance issues and global confusion, Should we stay
away from synonyms (to remote objects) and just use views? What performance
issues should we be aware of?
Thanks,
Followup:
search for

steve adams public synonyms

on this site.
Rating: 3
db links on other database platforms. May 15, 2003
Reviewer: Alvin from Philippines.
Are DB links Oracle only ?

can i create a dblink to another db platform say

oracle ->mySql
oracle ->sqlserver
oracle -> (new database platform)

do you have a link that some docs on 'DATABASE STANDARDS'.]

Thanks
Followup:
a dblink is an oracle "device" if you will.

Others have similar things, but different.  Many other databases cannot do
distributed queries naturally -- they won't have dblinks or anything remotely
similar.

Yes, you can create dblinks to other databases from Oracle - either via gateways
(part of iAS) or via generic connectivity
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4406709207206
Rating: 5
links July 31, 2003
Reviewer: mo
Tom:

I copied a database to a laptop.  I created two databases because it had links.
When I exported tables  from the second database PAP and treid to import it on
the laptop it said it need DBA privileges so I imported them as system manager
rather than PAPADMIN account.

I went to the first database INS and created a database link to PAP using
system/manager since all tables are under that schema in the 2nd database.

When I tried to use it it gacve me an error:

ORA-12640: Authentication adapter initialization failed.

On another try with a different login  I got

ORA-02019: Connection description from remore database not found. tnsnames.ora
did have the service name.

Do you know what is causing these errors and what i have to do to fix it?

Thank you,
Rating: 5
links October 16, 2003
Reviewer: mo
Tom:

I have two servers A and B.  One server has oracle 8i and the other has 9i.
When I try to create a link I get an erros:

I execute this on server A (9i) in SQL*PLUS for windows.

SQL> create database link test
 connect to insadmin identified by insadmin
 using 'insd';

Database link created.

SQL> desc intreq@test;
ERROR:
ORA-12154: TNS:could not resolve service name

1.  Does this mean that service name 'insd' does not exist in the tnsnames.ora
on the server.

2.  When you create a link that goes from one server to another is the
definition in the tns entry where you key in the ip or the name of the other
server.

Thanks
Followup:
1) correct

2) the server the database you created the link is the one that needs to be able
to resolve the link
Rating: 3
ora-02019 November 17, 2003
Reviewer: dash
I created two DBLINK db1,db2.
(db1,db2 is right tnsname)

select * from test1@db1 is good.
select * from test2@db2 is good.
insert into test1@db1 values('..') is good.
insert into test2@db2 values('..') is good.

but
insert into test1@db1 (select * test2@db2) is error.

ORA-02019: connection description for remote database not found
ORA-02063: preceding stringstring from db(this db sid)
ORA-02063: 2 lines...(db1&#47196; ..)

how do I solve this problem?
Followup:
let's see a sqlplus cut and paste of this.
Rating: 4
Why display password? November 19, 2003
Reviewer: Praveen from India
Hi Tom,

     I found out from all the three dblinks view that ALL_DB_LINKS and
DBA_DB_LINKS doesn't display the password in literal text format. But
USER_DB_LINKS displays the password as text. Why is it so and is there any
workaround for hiding this password.

Thanks,
Praveen.
Followup:
umm, you own the link -- you can only see your OWN private links.  you already
know the password.
Rating: 5
November 21, 2003
Reviewer: Akhil from NJ
Hi Tom,

When the remote database password changes, how do we synch all the dblinks that
use this password. We have different groups handling different instances which
has dblinks pointing across each other. Our password policy requires frequent
change of passwords for all database accounts. How can we automate this? Do you
have any script / suggestions which will automatically synch the dblinks after
the remote db password change? Thanks verymuch for your time.

Regards,
Akhil
Followup:
if you use GLOBAL USERS, then accounts are managed centrally - no need for
passwords in each database.

if you use plain old user/passwords -- you are responsible for syncing them up.
There is no way we can (we don't know what databases have a dblink that point to
"us" -- we cannot sync them up)
Rating: 3
Similar situation as dash above December 04, 2003
Reviewer: A reader from NJ
Hi Tom,

We had a similar situation where the inserts with a subquery over a DB link are
not working. The individual SELECT queries are okay.

We had encountered it in the past, but dismissed it as something unusual since
we were selecting on a table over DBLink1 which is actually a synonym for
another table on DBLink2 as defined in DBLink1, and since it was new
development, we just used other options available.

This time however, we encountered it on existing code after migrating our DEV
environment from 8.1.7 to 9i.

Here's our environment information:

    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

We have created a workaround by prestoring the results of the subquery in a
local table, but was wondering why we have had to do this. The problematic query
was working in 8.1.7 before.

Thanks a lot for any insight you can provide on this matter. I have attached a
copy of the log file for the sample SQLPlus session, with the INSERT statement
in question added at the end.

    SQL>
    SQL> set timing on;
    SQL> set time on;
    08:40:08 SQL> set trimspool on;
    08:40:08 SQL>
    08:40:08 SQL> ------------------------------------------
    08:40:08 SQL> -- create test table
    08:40:08 SQL> ------------------------------------------
    08:40:08 SQL> drop table test_db_link;

    Table dropped.

    Elapsed: 00:00:00.01
    08:40:09 SQL>
    08:40:09 SQL> create table test_db_link
    08:40:09   2  as
    08:40:09   3  select tracknum, status, transdate, insseq
    08:40:09   4  from workhistory@wkl
    08:40:09   5  where rowid = '';

    Table created.

    Elapsed: 00:00:00.00
    08:40:09 SQL>
    08:40:09 SQL> desc test_db_link;
     Name                                      Null?    Type
     ----------------------------------------- --------
----------------------------
     TRACKNUM                                  NOT NULL VARCHAR2(18)
     STATUS                                             CHAR(1)
     TRANSDATE                                          DATE
     INSSEQ                                             NUMBER

    08:40:09 SQL>
    08:40:09 SQL> select * from test_db_link;

    no rows selected

    Elapsed: 00:00:00.00
    08:40:09 SQL>
    08:40:09 SQL> ------------------------------------------
    08:40:09 SQL> -- test subquery -- workhistory@wkl is indexed on insseq and
tracknum
    08:40:09 SQL> --    with updated statistics
    08:40:09 SQL> ------------------------------------------
    08:40:09 SQL> select distinct tracknum from workhistory@wkl where insseq =
142;

    TRACKNUM
    ------------------
    010600BAT51419PI

    Elapsed: 00:00:00.00
    08:40:09 SQL>
    08:40:09 SQL> ------------------------------------------
    08:40:09 SQL> -- test select with subquery
    08:40:09 SQL> ------------------------------------------
    08:40:09 SQL> select tracknum, status, transdate, insseq
    08:40:09   2  from workhistory@wkl
    08:40:09   3  where tracknum in (select distinct tracknum from
workhistory@wkl where insseq = 142);

    TRACKNUM           S TRANSDATE     INSSEQ
    ------------------ - --------- ----------
    010600BAT51419PI   2 06-JAN-00        142
    010600BAT51419PI   2 07-JAN-00        143
    010600BAT51419PI   3 07-JAN-00        144
    010600BAT51419PI   5 07-JAN-00        145

    Elapsed: 00:00:00.00
    08:40:09 SQL>
    08:40:09 SQL> ------------------------------------------
    08:40:09 SQL> -- test insert with select WITHOUT subquery
    08:40:09 SQL> ------------------------------------------
    08:40:09 SQL> insert into test_db_link
    08:40:09   2  select tracknum, status, transdate, insseq
    08:40:09   3  from workhistory@wkl
    08:40:09   4  where tracknum = '010600BAT51419PI'
    08:40:09   5  ;

    4 rows created.

    Elapsed: 00:00:00.00
    08:40:09 SQL>
    08:40:09 SQL> spool off;

    ------------------------------------------
    -- test insert with select WITH subquery
    --  WE HAD TO CANCEL THIS QUERY SINCE IT
    --  DOESN'T SEEM TO FINISH AT ALL, EVEN
    --  AFTER LEAVING IT RUNNING FOR SEVERAL
    --  MINUTES.
    ------------------------------------------
    insert into test_db_link
    select tracknum, status, transdate, insseq
    from workhistory@wkl
    where tracknum in (select distinct tracknum from workhistory@wkl where
insseq = 142);
Followup:
look at the query plans.
Rating: 3
Here's the Explain Plan December 04, 2003
Reviewer: A reader from NJ
Hi Tom,

We decided to wait for the INSERT to finish. It took more than 15 minutes with
the filter "insseq = 142". The actuak filter is "insseq >= :var".

Thanks a lot for your help. I will be sending in the TKPROF results as well.

SQL>
SQL> set autotrace traceonly explain;
SQL>
SQL> set timing on;
SQL> set time on;
10:08:57 SQL> set trimspool on;
10:08:57 SQL>
----------------------------------------------------------------------
10:09:03 SQL> -- test subquery
10:09:03 SQL>
----------------------------------------------------------------------
10:09:03 SQL> select distinct tracknum from workhistory@wkl where insseq = 142;
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT (REMOTE) Optimizer=CHOOSE (Cost=4 Card=1 By
          tes=21)

   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'WORKHISTORY' (Cost=2 Car ORANETRA
          d=1 Bytes=21)

   2    1     INDEX (UNIQUE SCAN) OF 'WKLHINSSEQ' (UNIQUE) (Cost=2 Car ORANETRA
          d=1140186)

10:09:03 SQL>
10:09:03 SQL>
----------------------------------------------------------------------
10:09:14 SQL> -- test select with subquery
10:09:14 SQL>
----------------------------------------------------------------------
10:09:14 SQL> select tracknum, status, transdate, insseq
10:09:14   2  from workhistory@wkl
10:09:14   3  where tracknum in (select distinct tracknum from workhistory@wkl
where insseq = 142);
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT (REMOTE) Optimizer=CHOOSE (Cost=4 Card=1 By
          tes=50)

   1    0   NESTED LOOPS (Cost=4 Card=1 Bytes=50)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'WORKHISTORY' (Cost=2 C ORANETRA
          ard=1 Bytes=21)

   3    2       INDEX (UNIQUE SCAN) OF 'WKLHINSSEQ' (UNIQUE) (Cost=2 C ORANETRA
          ard=1140186)

   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'WORKHISTORY' (Cost=2 C ORANETRA
          ard=1 Bytes=29)

   5    4       INDEX (RANGE SCAN) OF 'WKHTRACKNUMIDX' (NON-UNIQUE)    ORANETRA

10:09:14 SQL>
10:09:14 SQL>
----------------------------------------------------------------------
10:09:14 SQL> -- test insert with select WITHOUT subquery
10:09:14 SQL>
----------------------------------------------------------------------
10:09:14 SQL> insert into test_db_link
10:09:14   2  select tracknum, status, transdate, insseq
10:09:14   3  from workhistory@wkl
10:09:14   4  where tracknum = '010600BAT51419PI'
10:09:14   5  ;

4 rows created.

Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
   0      INSERT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=36)
   1    0   REMOTE* (Cost=4 Card=1 Bytes=36)                           WKL

   1 SERIAL_FROM_REMOTE            SELECT "TRACKNUM","STATUS","TRANSDATE","INSS
                                   EQ" FROM "WORKHISTORY" "WORKHISTORY"

10:09:14 SQL>
10:09:15 SQL>
----------------------------------------------------------------------
10:09:24 SQL> -- test insert with select WITH subquery
10:09:24 SQL>
----------------------------------------------------------------------
10:09:24 SQL> insert into test_db_link
10:09:24   2  select tracknum, status, transdate, insseq
10:09:24   3  from workhistory@wkl
10:09:24   4  where tracknum in (select distinct tracknum from workhistory@wkl
where insseq = 142);

4 rows created.

Elapsed: 00:15:36.00

Execution Plan
----------------------------------------------------------
   0      INSERT STATEMENT Optimizer=CHOOSE (Cost=172346 Card=57009 By
          tes=2052324)

   1    0   FILTER
   2    1     REMOTE* (Cost=1319 Card=57009 Bytes=2052324)             WKL
   3    1     REMOTE* (Cost=3 Card=1 Bytes=24)                         WKL

   2 SERIAL_FROM_REMOTE            SELECT "TRACKNUM","STATUS","TRANSDATE","INSS
                                   EQ" FROM "WORKHISTORY" "SYS_ALIAS_1"

   3 SERIAL_FROM_REMOTE            SELECT "TRACKNUM","INSSEQ" FROM "WORKHISTORY
                                   " "WORKHISTORY" WHERE "TRACKNUM"=:1

10:25:00 SQL>
10:25:00 SQL> spool off;
Followup:
so it is pulling both back and then processing.

if you use driving_site and push to the remote does it affect the plan (just use
autotrace traceonly explain, no need to run it)
Rating: 3
And here's the TKProf output December 04, 2003
Reviewer: A reader from NJ
Hi Tom,

Here's what we got from TKPROF.

Thanks.

TKPROF: Release 9.2.0.3.0 - Production on Thu Dec 4 10:21:54 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Trace file: metrics_ora_12441_TESTDBLINK.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 events '10046 trace name context forever, level 8'

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: 43

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.02          0.02
  SQL*Net message to dblink                      11        0.00          0.00
  SQL*Net message from dblink                    11        0.02          0.05
********************************************************************************

insert into test_db_link
select tracknum, status, transdate, insseq
from workhistory@wkl
where tracknum in (select distinct tracknum from workhistory@wkl where insseq =
142)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.03       0.15          0          4          1           0
Execute      1    617.98    1083.94          0          1          3           4
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2    618.01    1084.10          0          5          4           4

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 43

Rows     Row Source Operation
-------  ---------------------------------------------------
      4  FILTER  (cr=0 r=0 w=0 time=2977732 us)
1139808   REMOTE  (cr=0 r=0 w=0 time=90516637 us)
      1   REMOTE  (cr=0 r=0 w=0 time=62446423 us)

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to dblink                  868838        0.00          3.02
  SQL*Net message from dblink                868838        0.64        641.27
  SQL*Net more data from dblink               16095        0.02          2.57
  latch free                                      1        0.00          0.00
  SQL*Net message to client                       1        0.00          0.00
  SQL*Net message from client                     1        0.05          0.05
********************************************************************************

alter session set events '10046 trace name context off'

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: 43

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.03       0.15          0          4          1           0
Execute      3    617.98    1083.94          0          1          3           4
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5    618.01    1084.10          0          5          4           4

Misses in library cache during parse: 0

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.05          0.07
  SQL*Net message to dblink                  868849        0.00          3.02
  SQL*Net message from dblink                868849        0.64        641.32
  SQL*Net more data from dblink               16095        0.02          2.57
  latch free                                      1        0.00          0.00

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

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        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: 0

    3  user  SQL statements in session.
    1  internal SQL statements in session.
    4  SQL statements in session.
********************************************************************************
Trace file: metrics_ora_12441_TESTDBLINK.trc
Trace file compatibility: 9.00.01
Sort options: default

       1  session in tracefile.
       3  user  SQL statements in trace file.
       1  internal SQL statements in trace file.
       4  SQL statements in trace file.
       4  unique SQL statements in trace file.
 1753850  lines in trace file.
Rating: 3
Re-written Query December 04, 2003
Reviewer: A reader from NJ
Hi Tom,

We are not quite sure how to "use driving_site and push to the remote".

That did prod us to rethink our query, though, and we just combined the subquery
with the other one, eliminating both the temporary table and the subquery. We
just re-wrote the query to use SELECT DISTINCT and joins over the DBLink.

While we still don't quite understand what was wrong with the INSERT over a
DBLINK using a SELECT with a SUBQUERY, we are quite satisfied with the new
workaround. We just hope that we don't encounter this problem again later, as
this is the second time that we did.

Thanks again.

11:52:51 SQL>
11:52:51 SQL> set autotrace traceonly explain;
11:52:51 SQL>
11:52:51 SQL> set timing on;
11:52:51 SQL> set time on;
11:52:51 SQL> set trimspool on;
11:52:51 SQL>
11:52:51 SQL>
----------------------------------------------------------------------
11:52:51 SQL> -- test insert with JOINS on DB LINKS (exact INSSEQ)
11:52:51 SQL>
----------------------------------------------------------------------
11:52:51 SQL> insert into test_db_link
11:52:51   2  select  DISTINCT a.tracknum, a.status, a.transdate, a.insseq
11:52:51   3  from    workhistory@wkl a, workhistory@wkl b
11:52:51   4  where   a.tracknum = b.tracknum
11:52:51   5  and     b.insseq=142
11:52:51   6  ;

4 rows created.

Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
   0      INSERT STATEMENT Optimizer=CHOOSE (Cost=12 Card=1 Bytes=60)
   1    0   SORT (UNIQUE) (Cost=12 Card=1 Bytes=60)
   2    1     NESTED LOOPS (Cost=8 Card=1 Bytes=60)
   3    2       REMOTE* (Cost=3 Card=1 Bytes=24)                       WKL
   4    2       REMOTE*                                                WKL

   3 SERIAL_FROM_REMOTE            SELECT "TRACKNUM","INSSEQ" FROM "WORKHISTORY
                                   " "B" WHERE "INSSEQ"=142

   4 SERIAL_FROM_REMOTE            SELECT "TRACKNUM","STATUS","TRANSDATE","INSS
                                   EQ" FROM "WORKHISTORY" "A" WHERE "TR

11:52:51 SQL>
11:52:51 SQL>
----------------------------------------------------------------------
11:52:51 SQL> -- test insert with JOINS on DB LINKS (from last INSSEQ)
11:52:51 SQL>
----------------------------------------------------------------------
11:52:51 SQL> insert into test_db_link
11:52:51   2  select  DISTINCT a.tracknum, a.status, a.transdate, a.insseq
11:52:51   3  from    workhistory@wkl a, workhistory@wkl b
11:52:51   4  where   a.tracknum = b.tracknum
11:52:51   5  and     b.insseq >= 14500000
11:52:51   6  ;

29861 rows created.

Elapsed: 00:00:11.05

Execution Plan
----------------------------------------------------------
   0      INSERT STATEMENT Optimizer=CHOOSE (Cost=1402 Card=1139 Bytes
          =68340)

   1    0   SORT (UNIQUE) (Cost=1402 Card=1139 Bytes=68340)
   2    1     REMOTE* (Cost=1319 Card=1140186 Bytes=41046696)          WKL

   2 SERIAL_FROM_REMOTE            SELECT "A1"."TRACKNUM","A1"."STATUS","A1"."T
                                   RANSDATE","A1"."INSSEQ","A1"."TRACKN

11:53:03 SQL>
11:53:03 SQL> spool off;
Rating: 4
December 11, 2003
Reviewer: Kashif from Reston, VA
Hi Tom,

You mentioned in one of your responses:

"so it is pulling both back and then processing."

which I understood to mean that instead of executing the query remotely and
bringing down the result data, the statement brought the data down to the local
db and then did the work. Is that correct?

Secondly, how were you able to tell that that is what happened?

Finally, what does the term "SERIAL_FROM_REMOTE" in the Execution Plan mean? I
looked the term up in the Performance Tuning Guide, and here's what it said:
'Serial execution at a remote site'. Which I understand to mean that the query
was executed remotely, and then the results brought down to the local db. But
that does not sound like what you had concluded for that query.

Any feedback would be helpful. Thanks.

Kashif
Followup:
the query plan showed us that -- two remote operations followed by a filter.
gives us the remote query that was executed and all.

it is basically taking:

           2  select tracknum, status, transdate, insseq
10:09:24   3  from workhistory@wkl
10:09:24   4  where tracknum in (select distinct tracknum from workhistory@wkl
where insseq = 142);

and executing it like this:

  for x in ( select * from remote_table )
  loop
      query remote table again by tracknum to get tracknum and insseq

      if one of the the returned insseq's is 142
      then
          output record
      end if
  end loop

Execution Plan
----------------------------------------------------------
   0      INSERT STATEMENT Optimizer=CHOOSE (Cost=172346 Card=57009 By
          tes=2052324)

   1    0   FILTER
   2    1     REMOTE* (Cost=1319 Card=57009 Bytes=2052324)             WKL
   3    1     REMOTE* (Cost=3 Card=1 Bytes=24)                         WKL

   2 SERIAL_FROM_REMOTE            SELECT "TRACKNUM","STATUS","TRANSDATE","INSS
                                   EQ" FROM "WORKHISTORY" "SYS_ALIAS_1"

   3 SERIAL_FROM_REMOTE            SELECT "TRACKNUM","INSSEQ" FROM "WORKHISTORY
                                   " "WORKHISTORY" WHERE "TRACKNUM"=:1
Rating: 5
confusing on the db links January 07, 2004
Reviewer: mike
Tom,
I have a database DB_X on server X. I want to create the database links to two
remote databases DB_Y1/Y2 on two
different server Y1/Y2.
The tnsnames.ora:

DB_Y1 = (DESCRIPTION=
                (ADDRESS=(PROTOCOL=tcp)(HOST=serv_y1)(PORT=1526))
                (CONNECT_DATA=(SID=DB_Y1))
            )
DB_Y2 = (DESCRIPTION=
                (ADDRESS=(PROTOCOL=tcp)(HOST=serv_y2)(PORT=1531))
                (CONNECT_DATA=(SID=DB_Y2))
            )

To create the database links, for DBY1 the following works:
create database link DBY1 connect to user1 identified by user1 using 'DB_Y1'
/

However the db link for DBY2 does not work if the format of creation used as
above.

I have to create it as :
create database link DBY2 connect to user1 identified by user1
using '(DESCRIPTION=
(ADDRESS=(PROTOCOL=tcp)(HOST=serv_y2)(PORT=1531))(CONNECT_DATA=(SID=DB_Y2)))'
/

Question: for what reasons the full description of tnsname has to be used for
the DB_Y2/serv_y2?
Followup:
are you showing me the tnsnames.ora entry FROM THE DATABASE SERVER

or are you showing me YOUR tnsnames.ora entry, and the one on the server only
really has db_y1 in it.

the dblink cannot see "your" tnsnames.ora, it sees the one on the database
server.
Rating: 5
sorry for was not very clear January 08, 2004
Reviewer: A reader
The tnsnames.ora entry shown is on server X. DB_X is on server X.
Both DB_Y1 and DB_Y2 are on remote servers (two different servers or machines).
The db link in question is on DB_X to DB_Y2 on server Y2

Mike
Followup:
i don't see the "error" but tell you what.  log DIRECTLY INTO the server with
DB_X

show us a CUT AND PASTE of

$ tnsping db_y1
$ tnsping db_y2
$ sqlplus u/p
create database link to y1
select * from dual@y1
create database link to y2
select * from dual@y2

CUT AND PASTE it, no editing
Rating: 3
synonym for remote stored function doesn't work February 05, 2004
Reviewer: James Su from Toronto CA
SQL> create or replace function ff return number is begin return 200; end;
  2  /

Function created.

SQL> conn XXXX/XXXX@dev32
Connected.

SQL> create database link dbl_rep connect to XXXXXXX identified by XXXXXXXX
using 'dev2';

Database link created.

SQL> select * from dual@dbl_rep;

D
-
X

SQL> select ff@dbl_rep from dual;

        FF
----------
       200

SQL> create or replace synonym sy_test for ff@dbl_rep;

Synonym created.

SQL> select sy_test from dual;
select sy_test from dual
       *
ERROR at line 1:
ORA-00904: "SY_TEST": invalid identifier
Followup:
should work fine:

ops$tkyte@ORA9IR2> create or replace function ff return number
  2  as
  3  begin
  4  return 55;
  5  end;
  6  /

Function created.

ops$tkyte@ORA9IR2> select ff@ora9ir2@loopback from dual;

        FF
----------
        55

ops$tkyte@ORA9IR2> create or replace synonym sy_test for ff@ora9ir2@loopback;

Synonym created.

ops$tkyte@ORA9IR2> select sy_test from dual;

   SY_TEST
----------
        55

ops$tkyte@ORA9IR2>

Please contact support for this one.
Rating: 3
Incorrect Results on DB Link Query April 01, 2004
Reviewer: A reader from NJ
Hi Tom,

I was wondering whether you could explain this behavior to us. We already have a
correct, working query, but took us quite sometime figuring out what was wrong
with the initial query in the first place. We know we should always compare
strings to strings, numbers to numbers, but we were wondering why the first
query below was not returning any results, whereas a similar query with more
filters was returning some, but incorrect results:

-----------------------------------
-- w/base filter
-----------------------------------
    -----------------------------------
    --  Inner query produces MORE results
    -----------------------------------
    11:54:49 SQL>
    11:54:49 SQL> /* Formatted on 2004/04/01 11:01 (Formatter Plus v4.8.0) */
    11:54:49 SQL> SELECT  ftsroamid
    11:54:49   2  FROM    getstatus
    11:54:49   3  WHERE   ordernum LIKE 'FTSSV20040401%'
    11:54:49   4  ;

    FTSROAMID
    ----------------
    2004040100321000
    2004040100321000
    2004040100321000
    2004040100322000
    2004040100323000
    2004040100324000
    2004040100324000
    2004040100325000
    2004040100326000
    2004040100327000
    2004040100328000
    2004040100329000
    2004040100330000
    2004040100331000
    2004040100332000
    2004040100333000
    2004040100334000
    2004040100335000
    2004040100336000
    2004040100337000
    2004040100338000
    2004040100339000
    2004040100340000
    2004040100341000
    2004040100342000
    2004040100343000
    2004040100344000
    2004040100345000
    2004040100346000

    29 rows selected.

    Elapsed: 00:00:00.00

    -----------------------------------
    -- but final query DOES NOT RETURN ANY!
    -----------------------------------
    11:54:49 SQL>
    11:54:49 SQL> SELECT  TO_CHAR (time_key_stamp, '0000000000000000')
time_key_stamp
    11:54:49   2  FROM    fts_address_info@lk_roamprod
    11:54:49   3  WHERE   time_key_stamp IN
    11:54:49   4          (
    11:54:49   5          SELECT ftsroamid
    11:54:49   6          FROM   getstatus
    11:54:49   7          WHERE  ordernum LIKE 'FTSSV20040401%'
    11:54:49   8          )
    11:54:49   9  ;

    no rows selected

    Elapsed: 00:00:03.02

-----------------------------------
-- w/additional filter
-----------------------------------
    -----------------------------------
    -- Inner query produces LESS results
    -----------------------------------
    11:54:52 SQL>
    11:54:52 SQL> SELECT  ftsroamid
    11:54:52   2  FROM    getstatus
    11:54:52   3  WHERE   ordernum LIKE 'FTSSV20040401%'
    11:54:52   4  AND     getstatus = 'R'
    11:54:52   5  ;

    FTSROAMID
    ----------------
    2004040100321000
    2004040100321000
    2004040100321000
    2004040100322000
    2004040100323000
    2004040100324000
    2004040100324000

    7 rows selected.

    Elapsed: 00:00:00.00

    -----------------------------------
    -- but final query RETURNS SOME, BUT THE RESULTS ARE INCORRECT!
    -----------------------------------
    11:54:52 SQL>
    11:54:52 SQL> SELECT  TO_CHAR (time_key_stamp, '0000000000000000')
time_key_stamp
    11:54:52   2  FROM    fts_address_info@lk_roamprod
    11:54:52   3  WHERE   time_key_stamp IN
    11:54:52   4          (
    11:54:52   5          SELECT ftsroamid
    11:54:52   6          FROM   getstatus
    11:54:52   7          WHERE  ordernum LIKE 'FTSSV20040401%'
    11:54:52   8          AND     getstatus = 'R'
    11:54:52   9          )
    11:54:52  10  ;

    TIME_KEY_STAMP
    ----------------
     2004040100324000
     2004040100324000
     2004040100324000
     2004040100324000
     2004040100324000
     2004040100324000
     2004040100324000
     2004040100324000

    8 rows selected.

    Elapsed: 00:00:01.03

    -----------------------------------
    -- FINAL QUERY WITH EXPECTED RESULTS:
    -----------------------------------
    11:54:54 SQL>
    11:54:54 SQL>
    11:54:54 SQL> SELECT  TRIM (TO_CHAR (time_key_stamp, '0000000000000000'))
time_key_stamp
    11:54:54   2  FROM    fts_address_info@lk_roamprod
    11:54:54   3  WHERE   TRIM (TO_CHAR (time_key_stamp, '0000000000000000')) IN
    11:54:54   4          (
    11:54:54   5          SELECT ftsroamid
    11:54:54   6          FROM   getstatus
    11:54:54   7          WHERE  ordernum LIKE 'FTSSV20040401%'
    11:54:54   8          )
    11:54:54   9  ;

    TIME_KEY_STAMP
    ----------------
    2004040100321000
    2004040100321000
    2004040100322000
    2004040100322000
    2004040100323000
    2004040100323000
    2004040100324000
    2004040100324000

    8 rows selected.

    Elapsed: 00:00:00.05

    -----------------------------------
    -- we also tried TO_NUMBER/NVL on the inner query, but still failed.
    -----------------------------------
    11:54:54 SQL>
    11:54:54 SQL> SELECT  time_key_stamp
    11:54:54   2  FROM    fts_address_info@lk_roamprod
    11:54:54   3  WHERE   time_key_stamp IN
    11:54:54   4          (
    11:54:54   5          SELECT TO_NUMBER(NVL(ftsroamid,0))
    11:54:54   6          FROM   getstatus
    11:54:54   7          WHERE  ordernum LIKE 'FTSSV20040401%'
    11:54:54   8          )
    11:54:54   9  ;

    no rows selected

    Elapsed: 00:00:06.00

    -----------------------------------
    -- as well as TO_NUMBER/IS NOT NULL on the inner query, and still failed.
    -----------------------------------
    11:55:00 SQL>
    11:55:00 SQL> SELECT  time_key_stamp
    11:55:00   2  FROM    fts_address_info@lk_roamprod
    11:55:00   3  WHERE   time_key_stamp IN
    11:55:00   4          (
    11:55:00   5          SELECT TO_NUMBER(ftsroamid)
    11:55:00   6          FROM   getstatus
    11:55:00   7          WHERE  ordernum LIKE 'FTSSV20040401%'
    11:55:00   8          AND    ftsroamid IS NOT NULL
    11:55:00   9          )
    11:55:00  10  ;

    no rows selected

Here's our environment information:

    LOCAL
        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

    REMOTE
        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

Thanks a lot in advance.
Followup:
this indicates "bug" - please contact support:

  11:54:49 SQL> SELECT  TO_CHAR (time_key_stamp, '0000000000000000')
time_key_stamp
    11:54:49   2  FROM    fts_address_info@lk_roamprod
    11:54:49   3  WHERE   time_key_stamp IN
    11:54:49   4          (
    11:54:49   5          SELECT ftsroamid
    11:54:49   6          FROM   getstatus
    11:54:49   7          WHERE  ordernum LIKE 'FTSSV20040401%'
    11:54:49   8          )
    11:54:49   9  ;

    no rows selected

    11:54:52 SQL>
    11:54:52 SQL> SELECT  TO_CHAR (time_key_stamp, '0000000000000000')
time_key_stamp
    11:54:52   2  FROM    fts_address_info@lk_roamprod
    11:54:52   3  WHERE   time_key_stamp IN
    11:54:52   4          (
    11:54:52   5          SELECT ftsroamid
    11:54:52   6          FROM   getstatus
    11:54:52   7          WHERE  ordernum LIKE 'FTSSV20040401%'
    11:54:52   8          AND     getstatus = 'R'
    11:54:52   9          )
    11:54:52  10  ;

    TIME_KEY_STAMP
    ----------------
     2004040100324000
     2004040100324000
     2004040100324000
     2004040100324000
     2004040100324000
     2004040100324000
     2004040100324000
     2004040100324000

    8 rows selected.
April 01, 2004 -- 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;
Rating: 5
CPU used by a session January 29, 2004
Reviewer: atul from India
Hi,

How can i find out cpu usage of a particular session?
Is there any view for that?

When i took statpack snap for particular session id.
I'm not getting CPU usage in a statpack report.
Any option i have to give for that?

Thank,
Atul
Followup:
you cannot take a statspack for a particular session id, not sure at all what
you mean by that.

ops$tkyte@ORA9IR2> select sid, name, value
  2  from v$sesstat a, v$statname b
  3  where a.statistic# = b.statistic#
  4  and b.name like 'CPU%'
  5  order by sid
  6  /

       SID NAME                                VALUE
---------- ------------------------------ ----------
         1 CPU used when call started              0
         1 CPU used by this session                2
         2 CPU used when call started              0
         2 CPU used by this session                2
         3 CPU used when call started              0
         3 CPU used by this session                2
         4 CPU used when call started              0
         4 CPU used by this session                2
         5 CPU used when call started              0
         5 CPU used by this session                2
         6 CPU used when call started              0
         6 CPU used by this session                2
         7 CPU used when call started              0
         7 CPU used by this session                1
         8 CPU used when call started             17
         8 CPU used by this session               17

16 rows selected.

-----------------------------------------------
scratch that -- i see you can :)

what is probably happening then is that since the CPU statistic is updated AFTER
the call completes -- you are just seeing "zero"

that is, say you have

a) time T0 start a long running thing
b) time T1 take a snap
c) time T2 take a snap
d) time T3 long running thing completes

you will see ZERO cpu time contributed by long running thing since the cpu isn't
dumped in there until (d).

if you:

a) time T0 start a long running thing
b) time T1 take a snap
c) time T2 long running thing completes
d) time T3 take a snap

You will see ALL cpu between a) and c) dumped into the report for times b) and
d) -- which means that you might see "1,000 cpu seconds" (the delta between a
and c) in a snapshot window of "50 seconds" (the delta between b and d) which
can be equally as confusing.
Rating: 5
you can January 29, 2004
Reviewer: A reader
4.5.  Specifying a Session Id

  If you would like to gather session statistics and wait events for a
  particular session (in addition to the instance statistics and wait events),
  it is possible to specify the session id in the call to Statspack.  The
  statistics gathered for the session will include session statistics,
  session events and lock activity.  The default behaviour is to not to
  gather session level statistics.

    SQL>  execute statspack.snap(i_session_id=>3);

!!!
Followup:
neat.  see, learn something new each and every day. never used that before.
nice.
Rating: 4
Statspack on a Per Session Basis January 29, 2004
Reviewer: A reader
In fact, one can monitor a session in statspack by passing the session_id to the
snap() call
Rating: 4
statspack report is not showing session info? January 29, 2004
Reviewer: David from Dallas
Tom,

I used exec statspack.snap(i_session_id = xx). In the statspack report, I can't
see the session id monitored. Everything looked like a general statspack report.
Where can I tell this is a session statspack report?

By the way, I used tfsclock.sql script from metalink and used the sid caught in
this script report. However from OEM, this sid is inactive. How an INACTIVE sid
is helding lock in the database?

Thanks
Followup:
My statspack report has sections like:

^LSession Wait Events for DB: ORA9IR2  Instance: ora9ir2  Snaps: 193 -194
Session Id:       8  Serial#:       5
-> 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               354          0          1      2    354.0
log file sync                           1          0          0     21      1.0
control file sequential read           50          0          0      0     50.0
SQL*Net message from client            10          0         11   1106     10.0
SQL*Net message to client              10          0          0      0     10.0
          -------------------------------------------------------------
^LSession Statistics for DB: ORA9IR2  Instance: ora9ir2  Snaps: 193 -194
Session Id:       8  Serial#:       5

Statistic                                      Total     per Second    per Trans
--------------------------------- ------------------ -------------- ------------
active txn count during cleanout                  19            1.2
buffer is not pinned count                   279,098       17,443.6
buffer is pinned count                       532,714       33,294.6
....
^LSession Statistics for DB: ORA9IR2  Instance: ora9ir2  Snaps: 193 -194
Session Id:       8  Serial#:       5

Statistic                                      Total     per Second    per Trans
--------------------------------- ------------------ -------------- ------------
table scan rows gotten                           333           20.8

does not yours?

an inactive sid can easily have locks.

SQL> update emp set ename = ename;
14 rows....

SQL> <now just sit here>

you would be inactive
you would have every row in emp locked.
Rating: 5
For David January 30, 2004
Reviewer: Riaz Shahid from PRAL, Lahore, Pakistan
use exec statspack.snap(i_session_id => xx);

instead of

exec statspack.snap(i_session_id = xx)
Rating: 3
Tom....they finally ran a statspack report.... February 25, 2004
Reviewer: Denise from Virginia,USA
on those 10 records that took 10 minutes.
There are several different snaps based on 2 minute
intervals I was asked to perform...this is the first
of seven snaps and represents only a snippet of the report.

I noticed the Instance Efficiency ratio:
execute to parse %27.28...all others fell within target
range. I read in your book about the wait events and these
look excessively high. Just thought I'd let you know after
2 months of haggling they finally decided to look into why 10 records
take 10 minutes...even though they seem to think that
the execution plan is good...go figure.

STATSPACK report for

DB Name         DB Id    Instance     Inst Num Release     OPS Host
------------ ----------- ------------ -------- ----------- --- ------------
PLDG          3612538557 pldg                1 8.1.7.0.0   NO  LH4

                Snap Id     Snap Time      Sessions
                ------- ------------------ --------
 Begin Snap:          4 25-Feb-04 14:38:51       17
   End Snap:          5 25-Feb-04 14:41:33       17
    Elapsed:                   2.70 (mins)

Cache Sizes
~~~~~~~~~~~
           db_block_buffers:      43212          log_buffer:     819200
              db_block_size:       8192    shared_pool_size:  185000000

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:              2,049.65            166,022.00
              Logical reads:                184.07             14,909.50
              Block changes:                  4.60                372.50
             Physical reads:                 26.70              2,162.50
            Physical writes:                  0.64                 51.50
                 User calls:                  1.35                109.50
                     Parses:                  7.29                590.50
                Hard parses:                  0.22                 18.00
                      Sorts:                  1.12                 91.00
                     Logons:                  0.31                 25.00
                   Executes:                 10.02                812.00
               Transactions:                  0.01

  % Blocks changed per Read:    2.50    Recursive Call %:   98.86
 Rollback per transaction %:    0.00       Rows per Sort:   45.10

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:   99.75
            Buffer  Hit   %:   85.50    In-memory Sort %:   98.35
            Library Hit   %:   95.56        Soft Parse %:   96.95
         Execute to Parse %:   27.28         Latch Hit %:  100.00
Parse CPU to Parse Elapsd %:   95.78     % Non-Parse CPU:   94.56

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   13.28   14.29
    % SQL with executions>1:   55.41   53.43
  % Memory for SQL w/exec>1:   75.62   70.86

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
db file sequential read                             4,254        2,777   86.75
control file parallel write                            59          163    5.09
direct path write                                      17           64    2.00
control file sequential read                           51           61    1.91
log file switch completion                              1           49    1.53
          -------------------------------------------------------------
 Wait Events for DB: PLDG  Instance: pldg  Snaps: 4 -5
-> 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,254          0       2,777      7 ######
control file parallel write            59          0         163     28   29.5
direct path write                      17          0          64     38    8.5
control file sequential read           51          0          61     12   25.5
log file switch completion              1          0          49    490    0.5
log file parallel write                14          0          38     27    7.0
LGWR wait for redo copy                 1          0          17    170    0.5
db file parallel write                  1          0          16    160    0.5
direct path read                       27          0           4      1   13.5
log file single write                   4          0           4     10    2.0
log file sync                           1          0           4     40    0.5
refresh controlfile command             6          0           3      5    3.0
file identify                           4          0           1      3    2.0
file open                              21          0           0      0   10.5
log file sequential read                2          0           0      0    1.0
SQL*Net message from client            84          0      18,665   2222   42.0
SQL*Net message to client              83          0           0      0   41.5
          -------------------------------------------------------------
Followup:
looks like bad plans to me, looks like a massive index range scan that isn't
finding the data.

so, if they mean "when we see indexes being used, plans are good" they might be
right.

if they mean "plans really are good", they may well be wrong.

but 27 seconds of IO wait in 3 minutes with 17 sessions doesn't see too huge.
Rating: 4
I know Tom.... February 26, 2004
Reviewer: Denise from Virginia,USA
as limited as my knowledge is in this area of db administration(although have
learned a tremendous amount these past weeks understanding tkprof's...etc)...I'm
stumped as to why these more experienced developers and
dba's can't seem to 'see' the problem.

I look at the tables in question and I'm not kidding
when I say there are about 20 indexes(or more) on some
of these tables.

I now look at the tkprof's...and based what you have
explained and reading your book(other books too)...
I can see in the sections where sql statements are being
queried...insert,select there is massive cpu/elapsed
numbers with all the index range plans presented like the one below:

SELECT  distinct(p.idnumber)  FROM phone ph, salutations s1, salutations s2,
additional_demographics ad, prospect p, address_view a  WHERE p.idnumber !=
:p_id    and p.salutcode = s1.salutcode(+) and p.csalutcode = s2.salutcode(+)
and p.idnumber = ad.idnumber(+)    and
            p.idnumber = a.idnumber and
            p.idnumber = ph.idnumber(+)  and  p.idnumber in (select idnumber
from prospect p1  where p1.last like SUBSTR(:p_last, 1, :p_num_last)||'%'  UNION
  select idnumber from prospect p2  where p2.clast like SUBSTR(:p_last, 1,
:p_num_last)||'%') AND  (UPPER(NVL(a.line1, :p_def)) like UPPER(SUBSTR(:p_line1,
1, :p_num_line1))||'%' or UPPER(NVL(a.line2, :p_def)) like
UPPER(SUBSTR(:p_line1, 1, :p_num_line1))||'%' or UPPER(NVL(a.line3, :p_def))
like UPPER(SUBSTR(:p_line1, 1, :p_num_line1))||'%' ) AND  NVL(a.zip, :p_def)
like SUBSTR(:p_zip, 1, :p_num_zip)||'%' and rownum < 1000

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      8.06       8.44          0          0          0           0
Execute     16      0.48       0.52          0          0          6           0
Fetch      248     26.94     622.05     104314     930865        195         238
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      265     35.48     631.01     104314     930865        201         238

Misses in library cache during parse: 1
Optimizer goal: RULE
Parsing user id: 12  (GUI)   (recursive depth: 1)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: RULE
      0   SORT (UNIQUE)
      0    COUNT (STOPKEY)
      0     NESTED LOOPS (OUTER)
      0      NESTED LOOPS (OUTER)
      0       NESTED LOOPS (OUTER)
      0        NESTED LOOPS (OUTER)
      0         NESTED LOOPS (OUTER)
      0          NESTED LOOPS
      0           NESTED LOOPS (OUTER)
      0            NESTED LOOPS (OUTER)
      0             NESTED LOOPS (OUTER)
      0              NESTED LOOPS
      0               VIEW OF 'VW_NSO_1'
      0                SORT (UNIQUE)
      0                 UNION-ALL
      0                  TABLE ACCESS (BY INDEX ROWID) OF
                             'PROSPECT'
      0                   INDEX (RANGE SCAN) OF
                              'LNAME_INDEX' (NON-UNIQUE)
      0                  TABLE ACCESS (BY INDEX ROWID) OF
                             'PROSPECT'
      0                   INDEX (RANGE SCAN) OF
                              'CNAME_INDEX' (NON-UNIQUE)
      0               TABLE ACCESS (BY INDEX ROWID) OF
                          'PROSPECT'
      0                INDEX (UNIQUE SCAN) OF
                           'PK_PROSPECT_IDNUMBER' (UNIQUE)
      0              INDEX (UNIQUE SCAN) OF 'PK_AD_IDNUMBER'
                         (UNIQUE)
      0             INDEX (UNIQUE SCAN) OF
                        'PK_SALUTATIONS_SALUTC' (UNIQUE)
      0            INDEX (UNIQUE SCAN) OF 'PK_SALUTATIONS_SALUTC'
                       (UNIQUE)
      0           TABLE ACCESS (BY INDEX ROWID) OF 'ADDRESS'
      0            INDEX (RANGE SCAN) OF 'ADDRESS_IDNUMBER_INDEX'
                       (NON-UNIQUE)
      0          INDEX (UNIQUE SCAN) OF 'PK_STATES_STATEC' (UNIQUE)

      0         INDEX (UNIQUE SCAN) OF 'PK_ADDTAB1_CODE' (UNIQUE)
      0        INDEX (UNIQUE SCAN) OF 'PK_ADDRS_CODE' (UNIQUE)
      0       INDEX (UNIQUE SCAN) OF 'PK_COUNTRY_CODE' (UNIQUE)
      0      INDEX (RANGE SCAN) OF 'PHONE_IDNUMBER_INDEX' (NON-UNIQUE)

********************************************************************************
all the rows are zero?

Oh well...maybe one of the dba's will buy your book and
learn something.

thanks for everything Tom...can't express enough thanks
to you for helping me learn as much as I have this past year.
That someone of your stature and expertise is willing
to take the time to mentor and help others in this field.

...enough with the sappy stuff!

hey...got my partitioned table up and running(yeaaaay!)

have a great day!   :~)
Followup:
You did not "exit the application" -- that cursor is not closed.  the STAT
records are not recorded in the trc file until the cursor is closed.

That plan above is from an EXPLAIN=u/p

You want the plan just to appear without having to use explain= on the tkprof
line.
Rating: 5
Tom... February 26, 2004
Reviewer: Denise from Virginia,USA
it's what they wanted...
they want

tkprof  xxxxxx.trc xxxxxx.txt explain=user/pwd@hostname

so that's what I gave them.
Followup:
yes, well unless you actually EXIT the program or close the cursors -- rowcounts
will be zero (and explain plan LIES like a rug sometimes, so you cannot rely on
it)

I never use explain= anymore, never.

consider:

drop table t;

create table t as
select decode( rownum, 1, 1, 999 ) id, a.* from all_objects a;
create index t_idx on t(id);
analyze table t compute statistics for table for all indexes for all indexed
columns;

variable x number
exec :x := 1;

alter session set sql_trace=true;
select * from t where id = :x;
exit;

tkprof with explain= shows:

select *
from
 t 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          0          4          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          4          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 227  (OPS$TKYTE)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID OBJ#(43885) (cr=4 r=0 w=0 time=158 us)
      1   INDEX RANGE SCAN OBJ#(43886) (cr=3 r=0 w=0 time=116 us)(object id
43886)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'T'

the one in bold is supplied from the trace file.  It is correct.  The one with
"execution plan" is from explain plan.  It got it "wrong"

so, teach them to stop using explain= and make sure to exit the application
before running tkprof!!!
Rating: 5
Tom.. February 26, 2004
Reviewer: Denise from Virginia,USA
I definitely understand what your saying and your example
clearly shows that.

I know I read in your book not to use "explain=xxx"
in the tkprof output parameters...so I don't use it
when I'm doing my own tkprof's...but they sent us
the syntax they wanted us to use..so I did what they
instructed.

I'll re-run tkprof without "explain".

regarding the cursors left open???...the application
did terminate...I only showed you one section of the entire
plan.

anyway...maybe I'll send them your response in a link.
Followup:
well, all you would get then is a shorter, faster tkprof report.  the stat
records were NOT in the trace file....

make sure you set the max dump file size to a big enough setting, perhaps the
stat records were truncated (look at the last line of the trc and see if it says
"truncated"
Rating: 4
Tom.... February 27, 2004
Reviewer: Denise from Virginia,USA
I checked the .trc file and it was complete...no "truncate"
at the end. The max_dump_file_size = 10240, I believe
that's an adequate setting.

The trace file was only set up for the session running
the lockbox. My understanding is the statspack report
and trace were two separate entities...I guess I'm
confused when you comment the statspack numbers were
NOT in the trace file.

In order for the stats numbers to be in the trace should
I set up a system trace rather than a session trace?

Given your comments about the report and trace files...
and my wanting to learn how to do this correctly..may
I ask a favor of having you give me instructions on how
to set both stats and trace simultaneously and I'll
re-do the entire procedure.

The entire process took about 14 minutes...is it necessary
to take "snaps" at 2 minute intervals like they requested.
This method produced several separate snap reports.

I know you mention in your book about taking 15 minute
interval snaps rather than 1 hour or longer..or something
along those lines.

I'm curious to see what differences result doing it
your way vs their way.

thanks!
Followup:
not "statspack", but rather STAT records (that contain the plans used and the
statistics for those plans)

Nothing to do with statspack.  these stat records look like this:

STAT #1 id=1 cnt=14 pid=0 pos=1 obj=43621 op='TABLE ACCESS FULL OBJ#(43621)
(cr=4 r=2 w=0 time=22513 us)'

If you see none of those in your trc file, you did not exit the session that was
generating the trc.
Rating: 4
Tom... February 27, 2004
Reviewer: Denise from Virignia,USA
I went back to look at the trace file and found the following STAT #'s.
STAT #3 id=1 cnt=1 pid=0 pos=0 obj=16900 op='TABLE ACCESS FULL ORGANIZATION '
STAT #8 id=1 cnt=1 pid=0 pos=0 obj=16607 op='TABLE ACCESS BY INDEX ROWID
DEFINITION '
STAT #8 id=2 cnt=2 pid=1 pos=1 obj=16608 op='INDEX UNIQUE SCAN '
STAT #8 id=1 cnt=3 pid=0 pos=0 obj=16609 op='TABLE ACCESS BY INDEX ROWID
DEFINITIONFIELD '
STAT #8 id=2 cnt=41 pid=1 pos=1 obj=62136 op='INDEX RANGE SCAN '

the only difference I see between your sample and the above
is stats on time(yours has cr=4 r=2 w=0 time=22513 us), that
I don't see on my trace.

Could that be the reason the rows are "0"'s in the section
I sent you.
Followup:
look up in the trace file for your cursor, see what number it was and then look
down to see if the stat records exist or not for it (they won't)

this means the cursor you are interested in wasn't closed, the trace file was
not complete.  These are stat records for closed cursors.
Rating: 4
Tom... February 27, 2004
Reviewer: Denise from Virginia,USA
ok...I went back and looked at the "Parsing In Cursor #"
sections....obviously there are many separate cursors
being opened at any given time.
Some do NOT have STAT #'s while other do have them.

I looked at a trace file where the rows where not zero's
and except for the very first parse in cursor(which
was #3)..all subsequent cursors had STAT #'s...so having
done that...I now understand what your talking about(
translation from 'chinese' to english).

question:
what is the cause of this happening?...did I set the
session to 'FALSE' too soon?....did running statspack
have an impact on timing when all the cursors are set
to close at the end of the application run.

I know running the application took an additional 4
minutes to complete due to the overhead of trace and
statspack running in the background.Instead of the usual
10 minutes it was taking(with trace only)..it now took
14 minutes(with statspack & trace).

I am going to re-test the entire process again.

If you can translate any more of this 'chinese' stuff
to english for me that would be great!(chuckle)
Followup:
yes, if you turned off tracing BEFORE the cursors were closed, you won't have
their stat records (exiting the application is my prefered way of turning off
trace)

statspack incurrs 0% overhead except during the time you are executing ".snap",
statspack does not run "in the background" at all.

sql_trace however, can be very expensive to run :)
Rating: 5
Finally Tom... February 27, 2004
Reviewer: Denise from Virginia,USA
got it!

Statspack only causes overhead when a "snap' gets executed.

In my case I was asked to perform 7 separate snaps at
2 minute intervals(hence the 4 minutes added).

I executed the application through sqlplus and when the
message came back:
"P/L SQL procedure completed successfully"

I turned the sql_trace_session back to false.
What your saying is just simply exit SQLPlus...correct?

when I find some "play" time I'll re-do the trace and
statspack.

have a good w/e!
;~)
Followup:
yes, just exit plus
Rating: 4
Statspack Report March 01, 2004
Reviewer: Andrew from Toronto, Ontario Canada
What does it mean when Parse CPU to Parse Elapsd exceeds 100% ? Here is a
portion of a statspack report for an interval of 20 mins, with 150 sessions.

I have read Chapter 10 of your book several time, but I am not sure what this
ratio means when it exceeds 100%.

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:   99.98
            Buffer  Hit   %:   93.29    In-memory Sort %:   99.90
            Library Hit   %:   91.32        Soft Parse %:   64.16
         Execute to Parse %:   55.67         Latch Hit %:   99.99
Parse CPU to Parse Elapsd %:  133.75     % Non-Parse CPU:   70.24
Followup:
     ,'Parse CPU to Parse Elapsd %:'                  dscr
      , decode(:prsela, 0, to_number(null)
                      , round(100*:prscpu/:prsela,2))  pctval

it is parse CPU/elapsed time spent parsing

if CPU > Elapsed for an event, you just have to put it down to "timing errors".
Sort of like a tkprof report where CPU exceeds Elapsed.  When counting lots of
little events -- you can easily over or under contribute.

Something like this (which MAY be 100% "non-reproducible" on your machine --
more likely in 8i and before where the timings were less granular)

ops$tkyte@ORA817DEV> select max(decode( a.name, 'parse time cpu', b.value ))
parse_cpu,
  2         max(decode( a.name, 'parse time elapsed', b.value )) parse_elapsed
  3    from v$statname a, v$mystat b
  4   where a.statistic#= b.statistic#
  5  /

 PARSE_CPU PARSE_ELAPSED
---------- -------------
        82            84

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> declare
  2          type rc is ref cursor;
  3          c rc;
  4  begin
  5          for i in 1 .. 1000
  6          loop
  7                  open c for 'select * from t t' || i;
  8                  close c;
  9          end loop;
 10  end loop;
 11  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select a.*, decode( ela_diff, 0, 0, round(
100*cpu_diff/ela_diff, 2 ) ) pct
  2    from (
  3  select parse_cpu, parse_cpu-&C cpu_diff,
  4         parse_elapsed, parse_elapsed-&E ela_diff
  5    from (
  6  select max(decode( a.name, 'parse time cpu', b.value )) parse_cpu,
  7         max(decode( a.name, 'parse time elapsed', b.value )) parse_elapsed
  8    from v$statname a, v$mystat b
  9   where a.statistic#= b.statistic#
 10         )
 11             ) a
 12  /
old   3: select parse_cpu, parse_cpu-&C cpu_diff,
new   3: select parse_cpu, parse_cpu-        82 cpu_diff,
old   4:        parse_elapsed, parse_elapsed-&E ela_diff
new   4:        parse_elapsed, parse_elapsed-        84 ela_diff

 PARSE_CPU   CPU_DIFF PARSE_ELAPSED   ELA_DIFF        PCT
---------- ---------- ------------- ---------- ----------
       150         68           145         61     111.48
Rating: 4
how to interpret RAC reports March 01, 2004
Reviewer: Jasdeep Singh from patiala,india
hi tom
we are using oracle 9i RAC and takes statspack reports peroidically.
But couldn't prefectly interpret about global cache srvices and global enqueue
services parameter tuning can you guide about first interpreting and then tune
these parameters or give one or two examples.
Followup:
step 1) identify you have a problem.

have you done step 1 yet?  do you have an identifiable performance related issue
Rating: 5
Excellent March 15, 2004
Reviewer: Namita
Tom,

From the statspack report on two machines this is what I found

                                                     CPU      Elapsd
  Buffer Gets    Executions  Gets per Exec  %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
INSERT into test(id, value) values ('02',20)

         49,996       49,996            1.0   24.1     7.04      6.98 1053795750

another machine

CPU      Elapsd
  Buffer Gets    Executions  Gets per Exec  %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
Module: SQL*Plus
INSERT into test(id, value) values ('02',20)

         49,996       49,996            1.0   24.1     7.80      8.97 1053795750

Pls note that the CPU and elapsed time are different for the same amount of
work, in the second machine it takes more work to insert, both the machines have
4 CPUs of same speed. What could possibly cause this ? What are the next steps
to find out the difference ?

Thanks in advance.
Followup:
you are worried about a 0.76 difference in 49,996 executions?

that is "noise", those numbers are for all intents and purposes "identical".

that noise is:

ops$tkyte@ORA9IR2> select 0.76/49996 from dual;

0.76/49996
----------
.000015201

given the granularity of clocks on computers -- those numbers are in fact 'the
same'.

there is no difference to account for here.
Rating: 5
portal timed out many times March 31, 2004
Reviewer: Min
Hi Tom,
We have a portal application, and timed out many times during the peak. I
collected stats for the 30 min.
Do you think the waits for latch is excessive?

Thanks you a lot

Min.

STATSPACK report for

DB Name         DB Id    Instance     Inst Num Release     OPS Host
------------ ----------- ------------ -------- ----------- --- ------------
****OPP1       578142643 ****OPP1            1 8.1.7.4.0   NO  n39

                Snap Id     Snap Time      Sessions
                ------- ------------------ --------
 Begin Snap:       4307 31-Mar-04 21:29:04       79
   End Snap:       4311 31-Mar-04 21:59:55       79
    Elapsed:                  30.85 (mins)

Cache Sizes
~~~~~~~~~~~
           db_block_buffers:      94922          log_buffer:     163840
              db_block_size:       8192    shared_pool_size:  100000000

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:              1,351.59              5,105.70
              Logical reads:                227.17                858.13
              Block changes:                  4.50                 17.02
             Physical reads:                  4.00                 15.10
            Physical writes:                  4.24                 16.02
                 User calls:                  2.61                  9.87
                     Parses:                 21.01                 79.38
                Hard parses:                  0.00                  0.01
                      Sorts:                  0.73                  2.75
                     Logons:                  0.17                  0.63
                   Executes:                 25.10                 94.80
               Transactions:                  0.26

  % Blocks changed per Read:    1.98    Recursive Call %:   95.87
 Rollback per transaction %:    0.00       Rows per Sort:   47.54

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:   99.99       Redo NoWait %:  100.00
            Buffer  Hit   %:   98.24    In-memory Sort %:   99.11
            Library Hit   %:   99.95        Soft Parse %:   99.98
         Execute to Parse %:   16.26         Latch Hit %:   98.31
Parse CPU to Parse Elapsd %:   21.84     % Non-Parse CPU:   99.85

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   95.77   95.77
    % SQL with executions>1:   61.04   61.02
  % Memory for SQL w/exec>1:   70.37   70.35

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
latch free                                          3,736        2,754   30.90
log file sync                                         931        2,218   24.88
direct path write                                   2,931        1,963   22.02
log file parallel write                               736          580    6.51
direct path read                                    3,692          400    4.49
          -------------------------------------------------------------
 Wait Events for DB: ****OPP1  Instance: ****OPP1  Snaps: 4307 -4311
-> 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
---------------------------- ------------ ---------- ----------- ------ ------
latch free                          3,736      2,816       2,754      7    7.6
log file sync                         931          0       2,218     24    1.9
direct path write                   2,931          0       1,963      7    6.0
log file parallel write               736          0         580      8    1.5
direct path read                    3,692          0         400      1    7.5
Followup:
that is 27 seconds of wait in 30 minutes.  doesn't seem very large to me, no.

I'd be looking to enable SQL_TRACE in my application, trace a session and see
what is taking so long.
Rating: 4
"writes" in tablespace io stats April 01, 2004
Reviewer: JHT from NJ USA
From my statspack output (8.1.7), I see the the number of writes in a 5 hour
windows.  I verified that there is a log table in DATA tablespace thas gets a
lot of rows inserted into during that timeframe.  I don't understand now the
"writes" value is only 404.  Does that stand for 404 block writes or what?
Thanks.

Tablespace IO Stats for DB: **** Instance: **** Snaps: 229 -234
->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)
-------------- ------- ------ ------- ------------ -------- ---------- ------
DATA
       373,351      21    0.2     6.1          404        0     16,962    0.4
TEMP
         2,138       0    0.0     1.2        2,620        0          0    0.0
INDEXES
           202       0    0.1     1.0        4,280        0          0    0.0
RBS
            30       0    0.0     1.0        1,373        0          0    0.0
Followup:
we only do IO at the block level, yes.
April 01, 2004 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 ?
Rating: 4
hat portion of global name to use April 01, 2004
Reviewer: robert from ct
Quote >>
ops$tkyte@ORA920> select sys_context( 'userenv', 'db_name' ) sc, global_name
from global_name
  2  /

SC                   GLOBAL_NAME
-------------------- ------------------------------
ora920               FOOBAR.US.ORACLE.COM

<<
8.1.7
Tom, my global_name is also like yours ('domain dot com')

SQL> SELECT * FROM GLOBAL_NAME;

GLOBAL_NAME
--------------------------------------------------------------------------------
SIDA.ZITE.COM

so what portion of this do I use for dblink in the instance where I want to set
global_names=True ?
The whole line "SIDA.ZITE.COM" ?

CREATE PUBLIC DATABASE LINK "SIDA.ZITE.COM@whateveriwant"
    CONNECT TO "POC_ADMIN" IDENTIFIED BY "pwd"  USING 'testmanda'

where 'testmanda' is the service name for the db whose global name =
"SIDA.ZITE.COM"

Thanks

ENJOY your vacation !
Followup:
sida.zite.com would be the "safest", if everyones db_domain defaulted to
zite.com, sida would suffice

(testmanda is a tns connection, that might eventually get turned into that
service name)......
Rating: 4
Leaving out "Using..." clause in db link creation April 01, 2004
Reviewer: rob from ct
Thanks, Tom

In db where global_names=false
I found this link in user "foobar" schema:
DDL is:
SQL> CREATE  DATABASE LINK "SID7.ZITE.COM"  CONNECT TO "foobar" IDENTIFIED BY
"pwd"

How do I find out what this is pointing to ?
I didn't know one can leave the "using..." clause out.
Followup:
it is a global database link, see the net admin guide for details.
April 02, 2004 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
Rating: 3
Pls. help March 15, 2004
Reviewer: A reader from NJ, USA
Tom,

In the discussion above titled "How to Tune Slow Deleting", I am just wondering
how you figured out that there is an unindexed foreign key.
Also, I remember studying somewhere that there is issue with locking when there
is no index on the foreign key. Can you please eloborate

Thanks
Followup:
the facts as given:

o table that has over 2m rows. (not so relevant really)
o I deleted a row using delete from t where id=i; The id is pk column.
  (that is relevant, that should be instantaneous we would assume)
o This operation took 40 seconds to come back.
  (that is relevant, that is the problem)
o Because many tables (8) reference this table
  (ahh, that is the probable problem!)

I just guessed that if you were deleting a single parent row by a primary key
value, and it was slow, and there were 8 child tables -- one or more (or all) of
them might be getting full scanned to process the delete -- if there wasn't an
index in place.

see
http://asktom.oracle.com/~tkyte/unindex/index.html
Rating: 3
Help on locks March 15, 2004
Reviewer: A reader from NJ, USA
Tom,

Please clarify about locks :

Table level lock
----------------
DDLs always take a table lock and No DMLs can be performed

DML
---
DMLs can be made to take a table lock (using LOCK TABLE statement, but is not a
good practive). But Oracle's default locking mechanism is a row-level exclusive
lock (for all rows affected by the DML) and a table level share lock (ie. no
DDLs can be performed when a DML is taking place), but another transaction can
place a share lock (possibly to update other rows in the table).

But the Oracle documentation you mentioned has the following lines and I quote
here
<Quote>
Share locks allow reading only. Therefore, no INSERT, UPDATE, or DELETE
statements could be issued on the child table until the transaction containing
the UPDATE or DELETE was committed. Queries were allowed on the child table.
</Quote>

Please clarify the above statement. If my understanding is not correct, please
explain. The topics on locks is very confusing
Followup:
snippets from my "Book Expert One on One Oracle"

<quote>
There are three types of DDL locks:

o Exclusive DDL locks - This will prevent other sessions from gaining a DDL lock
or TM (DML) lock themselves. This means that a table against which DDL is
performed may be queried, but cannot be modified during the operation.

o Share DDL locks - This protects the referenced objects from being changed by
other sessions with DDL, but allows modifications to the data.

o Breakable parse locks - This allows objects such as a query plan cached in the
shared pool to register its reliance on some object. If you perform DDL against
that object, Oracle will review the list of objects that have registered their
dependence, and invalidate them.  Hence, these 'locks' are 'breakable'; they do
not prevent the DDL from occurring.

</quote>

So, the statement "ddl always take a table lock and no dmls can be performed" is
false.

The table level share lock is not a lock on the table really -- it is yet
another queueing mechanism to make sure the table isn't "changed structurally".
It is different than "lock table in share mode"

It took me about 30 pages to adequately describe locking in Oracle and impacts
on your life (and I still left bits out).  I won't be able to do it here, but if
you have access to Expert one on one Oracle -- i think it is pretty descriptive.
Rating: 3
Help March 15, 2004
Reviewer: A reader from NJ, USA
Tom,

Thanks much for your response. So is the statement given in the application
developer's guide within <quote>-</quote> ie. "share table locks prevents DMLs
from being performed on the table" in the above question correct or false.
Please let me know

Thanks
Followup:
in the proper context, it is accurate.

lock table in share mode.

no other dmls will be done.  that is a true table level share lock.

suggest you look here:
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96524/c21cnsis.htm#2841
and in paricular:
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96524/c21cnsis.htm#9975
(but goto the pdf, the table seems to be missing some headers for some reason --
all of the pdfs are on otn as well)
Rating: 4
Buld delete vs dynamic "in" delete March 23, 2004
Reviewer: Lee from NY, USA
Tom,
What would be faster, a FORALL ..... DELETE ....WHERE ROWID = T_TYPE();

OR

constructing a DELETE .....WHERE ROWID IN ();

Which delete will be faster? (not taking into account the time it takes to
construct the dynamic "in" delete).

Thanks.
Followup:
unless the IN used binds, forall probably.  (without binds, don't even consider
doing it)

(but, did you BENCHMARK IT????????)
Rating: 4
Buld delete vs dynamic "in" delete March 24, 2004
Reviewer: Lee
Tom,
I'm not sure what you suggest to use bind in this case.
The in is going to execute only once, it will look something like this (I will
build it dynamicly, concatenate the values with a loop on plsql table):

DELETE FROM MY_TABLE
WHERE ROWID IN ('DSAFASDFAS', 'ADFSAFDSA', 'ADSFASDFSAD', 'ADFASDFDSA');

about 800-1000 rowids in the in.

Thanks.
Followup:
and no bind variables...........

everyone says "only once", but they never really mean it.

but if only once, the difference in timing is so "not relevant" as it would be
measured in fractions of a second at best.  do which ever is "easiest" to do.
Rating: 4
Buld delete vs dynamic "in" delete March 24, 2004
Reviewer: Lee.
Tom,
I have another one for you.

I have to update 600 records at the time based on rowid, I can use
FORALL..UPDATE. Or I can build an update statment dynamicly that will look like
this(I'll do it with only 3 rowids here)

UPDATE (SELECT DATE_1,
               ROWID R_ID
        FROM MY_TABLE
        WHERE ROWID IN ('ASDFASD', 'ADFADSF', 'ADFDSA') SET
DATE_1 = CASE WHEN ROWID = 'ASDFASD' THEN
                   DATE_1 + 1
              WHEN ROWID = 'ADFADSF' THEN
                   DATE_1 - 1
              WHEN ROWID = 'ADFDSA' THEN
                   DATE_1 + 100
              END;

This will be for around 600 values.

Thanks.
Followup:
this would definitely use forall and two arrays

  forall i in 1..l_array.count
     update t set date_1 = l_array(i) where rowid = l_rowids(i);

Like I said -- DO WHAT IS EASIEST.  Here, filling an array procedurally looks to
me to be "obviously much easier than anything else"
Rating: 4
Buld delete vs dynamic "in" delete March 24, 2004
Reviewer: Lee
Tom,

I'm with you on the "easy" thing, however, I just tried it and to my surprise it
seem that the UPDATE (SELECT...) SET COL_1  CASE WHEN....      works much faster
then the FORALL...and I mean MUCH faster (1-5 seconds vs many minutes).

Since I was a little kid I was taught that FORALL would be faster in a case like
this and I’m surprise to find that it is not (it is definatly easier to write).
Could it be coz of hardware configuration? The main “bad” thing on my box is
that there is only 1 controller, could that be a factor.

Thanks.
Followup:
then you made "a mistake" and compared apples to toaster ovens

and in fact, if you had 600 rowids , you would have blown the 32k limit

Look:

ops$tkyte@ORA9IR2> create table t
  2  as
  3  select * from all_objects
  4  where rownum <= 600;

Table created.

Elapsed: 00:00:00.16
ops$tkyte@ORA9IR2> alter table t add Ive_been_updated char(1);

Table altered.

Elapsed: 00:00:00.09
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select count(*) from t where ive_been_updated = 'Y';

  COUNT(*)
----------
         0

Elapsed: 00:00:00.00
ops$tkyte@ORA9IR2> declare
  2          type ridArray is table of rowid index by binary_integer;
  3          type numArray is table of number index by binary_integer;
  4          l_rids  ridArray;
  5          l_nums  numArray;
  6  begin
  7          select rowid BULK COLLECT into l_rids from t;
  8
  9          for i in 1 .. l_rids.count
 10          loop
 11                  l_nums(i) := 1 + -2 * mod(i,2);
 12          end loop;
 13
 14          forall i in 1 .. l_rids.count
 15                  update t set created = created + l_nums(i),
ive_been_updated = 'Y' where rowid = l_rids(i);
 16  end;
 17  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.06
ops$tkyte@ORA9IR2> select count(*) from t where ive_been_updated = 'Y';

  COUNT(*)
----------
       600

Elapsed: 00:00:00.00

and if this is a one time thing -- so what?  if it is NOT a one time thing, you
must must must use binds.
Rating: 4
Buld delete vs dynamic "in" delete March 24, 2004
Reviewer: Lee from NY, USA
Tom,

I'm not sure why you think that I made a mistake.
I used 2 variables (v_sql varchar2(32500)) to constract the update query.

The end result was something like this:
EXECUTE IMMEDIATE 'UPDATE (SELECT ROWID R_ID, '||
                                          '                  DATE_TERMINATION
'||
                                          '        FROM F_PROVIDER PARTITION
(F_PROVIDER_1) '||
                                       '        WHERE ROWID IN
'||V_SQL_STM_2||') SET '||
                                       'DATE_TERMINATION = CASE
'||V_SQL_STM_3||' END';

One more thing, when I said "one time" I didn't mean 1 time for ever I ment 1
time per execution. I would want to use bind variables in this case, do I do it
just like a regular bind variable?

EXECUTE IMMEDIATE 'UPDATE (SELECT ROWID R_ID, '||
                                          '                  DATE_TERMINATION
'||
                                          '        FROM F_PROVIDER PARTITION
(F_PROVIDER_1) '||
                                       '        WHERE ROWID IN :V_BIND_1) SET
'||
                                       'DATE_TERMINATION = CASE :V_BINE_2 END'
USING V_SQL_STM_2, V_SQL_STM_3;
Followup:
show me the full example where by the forall took "minutes" for 600 rows and the
"update case" took seconds.

That is what I'm saying you made a mistake.  to delete 600 rows, via forall,
would take about the same time as with "case" -- one or the other didn't do what
you think it did.

That is what I mean.

Show us the ENTIRE test case - the whole thing.

and if this is "standard operating procedure", you MUST (as in HAVE TO) use
forall with binds.  No other way you even want to CONSIDER.
Rating: 4
Buld delete vs dynamic "in" delete March 24, 2004
Reviewer: Lee from NY, USA
Ok, you asked for it.......go easy on me if you find bad mistakes. (the FORALL
is commented close to the end of the code).

DECLARE
    V_NUM NUMBER := 1;
    V_COUNT NUMBER := 1;
    V_C1_COUNT NUMBER := 2;
    V_SQL_STM VARCHAR2(32500) := NULL;
    V_SQL_STM_2 VARCHAR2(32500) := 'ZZTOP';
    V_SQL_STM_3 VARCHAR2(32500) := NULL;
    TYPE T_R_ID_TYPE IS TABLE OF ROWID INDEX BY PLS_INTEGER NOT NULL;
    TYPE T_UPD_TERM_DATE_TYPE IS TABLE OF NUMBER INDEX BY PLS_INTEGER NOT NULL;
    T_DEL_R_ID T_R_ID_TYPE;
    T_UPD_R_ID T_R_ID_TYPE;
    T_UPD_TERM_DATE T_UPD_TERM_DATE_TYPE;
    -------------------------------------------
    CURSOR C1 IS SELECT DISTINCT
                              CASE WHEN --DO NOT DELETE

                                    DATE_EFFECTIVE < LEAD_DATE_EFFECTIVE AND
                                    DATE_TERMINATION > LEAD_DATE_EFFECTIVE THEN
                                        NULL
                            WHEN --DELETE
                                 (DATE_EFFECTIVE > LEAD_DATE_EFFECTIVE AND
                                  DATE_EFFECTIVE < LEAD_DATE_TERMINATION) OR
                                 (DATE_EFFECTIVE > LAG_DATE_EFFECTIVE AND
                                  DATE_EFFECTIVE < LAG_DATE_TERMINATION) THEN
                                         R_ID
                       END DEL_R_ID,
                       CASE WHEN DATE_EFFECTIVE < LEAD_DATE_EFFECTIVE AND
                                    DATE_TERMINATION > LEAD_DATE_EFFECTIVE THEN
                                        R_ID
                            WHEN (DATE_EFFECTIVE > LEAD_DATE_EFFECTIVE AND
                                  DATE_EFFECTIVE < LEAD_DATE_TERMINATION) OR
                                 (DATE_EFFECTIVE > LAG_DATE_EFFECTIVE AND
                                  DATE_EFFECTIVE < LAG_DATE_TERMINATION) THEN
                                         NULL
                       END UPD_R_ID,
                       CASE WHEN DATE_EFFECTIVE < LEAD_DATE_EFFECTIVE AND
                                    DATE_TERMINATION > LEAD_DATE_EFFECTIVE THEN

TO_NUMBER(TO_CHAR(TO_DATE(LEAD_DATE_EFFECTIVE, 'YYYYMMDD') - 1, 'YYYYMMDD'))

                            WHEN (DATE_EFFECTIVE > LEAD_DATE_EFFECTIVE AND
                                  DATE_EFFECTIVE < LEAD_DATE_TERMINATION) OR
                                 (DATE_EFFECTIVE > LAG_DATE_EFFECTIVE AND
                                  DATE_EFFECTIVE < LAG_DATE_TERMINATION) THEN
                                         NULL
                       END UPD_TERM_DATE
                FROM
                          (SELECT
                                 CLIENT_PROVIDER_ID,
                                 LEAD(CLIENT_PROVIDER_ID) OVER(ORDER BY
CLIENT_PROVIDER_ID, CLIENT_PROVIDER_AFF, DATE_EFFECTIVE)
LEAD_CLIENT_PROVIDER_ID,
                                 LAG(CLIENT_PROVIDER_ID) OVER(ORDER BY
CLIENT_PROVIDER_ID, CLIENT_PROVIDER_AFF, DATE_EFFECTIVE) LAG_CLIENT_PROVIDER_ID,

                                 CLIENT_PROVIDER_AFF,
                                 LEAD(CLIENT_PROVIDER_AFF) OVER(ORDER BY
CLIENT_PROVIDER_ID, CLIENT_PROVIDER_AFF, DATE_EFFECTIVE)
LEAD_CLIENT_PROVIDER_AFF,
                                 LAG(CLIENT_PROVIDER_AFF) OVER(ORDER BY
CLIENT_PROVIDER_ID, CLIENT_PROVIDER_AFF, DATE_EFFECTIVE)
LAG_CLIENT_PROVIDER_AFF,
                                 DATE_EFFECTIVE,
                                 DATE_TERMINATION,
                                    LEAD(DATE_EFFECTIVE) OVER(PARTITION BY
CLIENT_PROVIDER_ID, CLIENT_PROVIDER_AFF ORDER BY CLIENT_PROVIDER_ID,
CLIENT_PROVIDER_AFF, DATE_EFFECTIVE ASC) LEAD_DATE_EFFECTIVE,
                                    LEAD(DATE_TERMINATION) OVER(PARTITION BY
CLIENT_PROVIDER_ID, CLIENT_PROVIDER_AFF ORDER BY CLIENT_PROVIDER_ID,
CLIENT_PROVIDER_AFF, DATE_EFFECTIVE ASC) LEAD_DATE_TERMINATION,
                                 LAG(DATE_TERMINATION) OVER(PARTITION BY
CLIENT_PROVIDER_ID, CLIENT_PROVIDER_AFF ORDER BY CLIENT_PROVIDER_ID,
CLIENT_PROVIDER_AFF, DATE_EFFECTIVE ASC) LAG_DATE_TERMINATION,
                                 LAG(DATE_EFFECTIVE) OVER(PARTITION BY
CLIENT_PROVIDER_ID, CLIENT_PROVIDER_AFF ORDER BY CLIENT_PROVIDER_ID,
CLIENT_PROVIDER_AFF, DATE_EFFECTIVE ASC) LAG_DATE_EFFECTIVE,
                                 ROWID R_ID,
                                 LEAD(ROWID) OVER(PARTITION BY
CLIENT_PROVIDER_ID, CLIENT_PROVIDER_AFF ORDER BY CLIENT_PROVIDER_ID,
CLIENT_PROVIDER_AFF, DATE_EFFECTIVE ASC) LEAD_R_ID,
                                 CREATE_TSP,
                                 MIN(CREATE_TSP) OVER(PARTITION BY
CLIENT_PROVIDER_ID, CLIENT_PROVIDER_AFF) MIN_CREATE_TSP
                            FROM F_PROVIDER PARTITION (F_PROVIDER_1)
                            ORDER BY CLIENT_PROVIDER_ID, CLIENT_PROVIDER_AFF,
DATE_EFFECTIVE ASC)
                WHERE ((CLIENT_PROVIDER_ID = LEAD_CLIENT_PROVIDER_ID AND
                        CLIENT_PROVIDER_AFF = LEAD_CLIENT_PROVIDER_AFF)  OR
                       (CLIENT_PROVIDER_ID = LAG_CLIENT_PROVIDER_ID AND
                        CLIENT_PROVIDER_AFF = LAG_CLIENT_PROVIDER_AFF)) AND
                      (((DATE_EFFECTIVE >= LEAD_DATE_EFFECTIVE AND
                         DATE_TERMINATION <= LEAD_DATE_TERMINATION) OR
                        (DATE_EFFECTIVE <= LEAD_DATE_EFFECTIVE AND
                         DATE_TERMINATION >= LEAD_DATE_TERMINATION)) OR
                       ((DATE_EFFECTIVE >= LAG_DATE_EFFECTIVE AND
                        DATE_TERMINATION <= LAG_DATE_TERMINATION) OR
                       (DATE_EFFECTIVE <= LAG_DATE_EFFECTIVE AND
                        DATE_TERMINATION >= LAG_DATE_TERMINATION))) AND
                      CREATE_TSP = MIN_CREATE_TSP;
BEGIN
    WHILE V_C1_COUNT > 1
    LOOP
        OPEN C1;
           LOOP
               FETCH C1 BULK COLLECT INTO T_DEL_R_ID, T_UPD_R_ID,
T_UPD_TERM_DATE LIMIT 600;
               V_C1_COUNT := C1%ROWCOUNT;
               EXIT WHEN T_DEL_R_ID.COUNT < 2 AND T_UPD_R_ID.COUNT < 2;

               FOR I IN 1 .. T_DEL_R_ID.COUNT
               LOOP
                      IF T_DEL_R_ID(I) IS NULL THEN
                         T_DEL_R_ID.DELETE(I);
                   END IF;
               END LOOP;
               FOR I IN 1 .. T_UPD_R_ID.COUNT
               LOOP
                      IF T_UPD_R_ID(I) IS NULL THEN
                         T_UPD_R_ID.DELETE(I);
                      T_UPD_TERM_DATE.DELETE(I);
                   END IF;
               END LOOP;
               DBMS_APPLICATION_INFO.SET_MODULE (TO_CHAR(V_COUNT||':
'||T_DEL_R_ID.COUNT)||', '||TO_CHAR(T_UPD_R_ID.COUNT), TO_CHAR(SYSDATE,
'DD-MON-YY HH24:MI:SS'));
               --------------------------
               IF T_DEL_R_ID.COUNT > 0 THEN
                   FOR I IN T_DEL_R_ID.FIRST .. T_DEL_R_ID.COUNT
                   LOOP
                           V_SQL_STM := V_SQL_STM||CASE WHEN T_DEL_R_ID(I) IS
NOT NULL THEN

''''||T_DEL_R_ID(I)||''', '
                                                   END;
                   END LOOP;
               END IF;
               IF V_SQL_STM IS NOT NULL THEN
                   EXECUTE IMMEDIATE 'DELETE /*+ NOLOGGING
NOPARALLEL(F_PROVIDER) */ FROM F_PROVIDER '||
                                           'WHERE ROWID IN ('||RTRIM(V_SQL_STM,
', ')||')';
               END IF;
               COMMIT;
               V_SQL_STM := NULL;
               -------------------------
               V_SQL_STM_2 := T_UPD_R_ID.COUNT;
               IF T_UPD_R_ID.COUNT > 0 THEN
                     V_SQL_STM_2 := NULL;
                  V_SQL_STM_3 := NULL;
                     FOR I IN T_UPD_R_ID.FIRST .. T_UPD_R_ID.LAST
                  LOOP
                        IF T_UPD_R_ID(I) IS NOT NULL THEN
                             V_SQL_STM_2 :=
V_SQL_STM_2||''''||T_UPD_R_ID(I)||''', ';
                          V_SQL_STM_3 := V_SQL_STM_3||'WHEN R_ID =
'''||T_UPD_R_ID(I)||''' THEN '||
                                                           '
'||T_UPD_TERM_DATE(I)||' ';
                      END IF;
                  END LOOP;
                  V_SQL_STM_2 := '('||RTRIM(V_SQL_STM_2, ', ')||')';
                   EXECUTE IMMEDIATE 'UPDATE (SELECT ROWID R_ID, '||
                                           '                  DATE_TERMINATION
'||
                                           '        FROM F_PROVIDER PARTITION
(F_PROVIDER_1) '||
                                        '        WHERE ROWID IN
'||V_SQL_STM_2||') SET '||
                                        'DATE_TERMINATION = CASE
'||V_SQL_STM_3||' END';
                   COMMIT;
               END IF;
               --********************
               --IF T_UPD_R_ID.COUNT > 0 THEN
               --       FORALL I IN T_UPD_R_ID.FIRST .. T_UPD_R_ID.COUNT
               --   UPDATE /*+ NOLOGGING NOPARALLEL(F_PROVIDER) */ F_PROVIDER
SET
               --   DATE_TERMINATION = T_UPD_TERM_DATE(I)
               --   WHERE ROWID = T_UPD_R_ID(I);
               --   COMMIT;
               --END IF;
               --********************
               -------------------------
               V_COUNT := V_COUNT + 1;
           END LOOP;
        CLOSE C1;
    END LOOP;
    ----------------
    T_DEL_R_ID.DELETE;
    T_UPD_R_ID.DELETE;
    T_UPD_TERM_DATE.DELETE;
END;
/
Followup:
sorry -- way too big -- and I cannot even run it!!!!

the point of a test case is to make it so others can reproduce your findings.
Here I cannot even see how you timed something like this (tkprof, what?)

this code doesn't fly:

               IF T_DEL_R_ID.COUNT > 0 THEN
                   FOR I IN T_DEL_R_ID.FIRST .. T_DEL_R_ID.COUNT
                   LOOP
                           V_SQL_STM := V_SQL_STM||CASE WHEN T_DEL_R_ID(I) IS
NOT NULL THEN

''''||T_DEL_R_ID(I)||''', '
                                                   END;
                   END LOOP;
               END IF;

And if you .DELETED some of these -- you would be getting "NO DATA FOUND" like
this:

ops$tkyte@ORA9IR1> declare
  2          type array is table of number index by binary_integer;
  3          l_data array;
  4          l_string  long;
  5  begin
  6          for i in 1 .. 3
  7          loop
  8                  l_data(i) := i;
  9          end loop;
 10
 11          l_data.delete(2);
 12
 13          for i in l_data.first .. l_data.count
 14          loop
 15                  l_string := l_string || l_data(i) || ',';
 16          end loop;
 17  end;
 18  /
declare
*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at line 15

sorry -- but this code won't "run" in general.

you do know that "nologging" is "not an option" at all here (that nologging
'hint' is meaningless)

so, I think there is a good change the one piece of code well, it never actually
"ran" or is very different from the above.

Sooo, here is my complete from start to finish that shows forall = superior for
this example.

Also, if you do not bind -- you are dead in the water, just do not pass go, do
not collect $200.00, go directly to jail:

ops$tkyte@ORA9IR2> drop table t;

Table dropped.

Elapsed: 00:00:00.08
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create table t
  2  as
  3  select *
  4    from all_objects
  5   where rownum <= 1200;

Table created.

Elapsed: 00:00:00.13
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> alter table t add been_updated char(1);

Table altered.

Elapsed: 00:00:00.06
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select count(*) from t where been_updated='Y';

  COUNT(*)
----------
         0

Elapsed: 00:00:00.00
ops$tkyte@ORA9IR2> select count(*) from t;

  COUNT(*)
----------
      1200

Elapsed: 00:00:00.01
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2      type ridArray is table of rowid index by binary_integer;
  3      type dtArray is table of date index by binary_integer;
  4
  5      l_rids ridArray;
  6      l_rids_to_update ridArray;
  7      l_rids_to_delete ridArray;
  8      l_dates dtArray;
  9  begin
 10      select rowid
 11        bulk collect into l_rids
 12        from t;
 13
 14      for i in 1 .. l_rids.count
 15      loop
 16          if (mod(i,2) = 0)
 17          then
 18              l_rids_to_update(l_rids_to_update.count+1) := l_rids(i);
 19              l_dates(l_dates.count+1) := sysdate;
 20          else
 21              l_rids_to_delete(l_rids_to_delete.count+1) := l_rids(i);
 22          end if;
 23      end loop;
 24
 25      forall i in 1 .. l_rids_to_delete.count
 26          delete from t where rowid = l_rids_to_delete(i);
 27
 28      forall i in 1 .. l_rids_to_update.count
 29          update t set created = l_dates(i), been_updated = 'Y' where rowid =
l_rids_to_update(i);
 30  end;
 31  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.05
ops$tkyte@ORA9IR2> select count(*) from t where been_updated='Y';

  COUNT(*)
----------
       600

Elapsed: 00:00:00.00
ops$tkyte@ORA9IR2> select count(*) from t;

  COUNT(*)
----------
       600

Elapsed: 00:00:00.00

ops$tkyte@ORA9IR2> drop table t;

Table dropped.

Elapsed: 00:00:00.11
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create table t
  2  as
  3  select *
  4    from all_objects
  5   where rownum <= 1200;

Table created.

Elapsed: 00:00:00.10
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> alter table t add been_updated char(1);

Table altered.

Elapsed: 00:00:00.00
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select count(*) from t where been_updated='Y';

  COUNT(*)
----------
         0

Elapsed: 00:00:00.00
ops$tkyte@ORA9IR2> select count(*) from t;

  COUNT(*)
----------
      1200

Elapsed: 00:00:00.00
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2      type ridArray is table of rowid index by binary_integer;
  3      l_rids ridArray;
  4      l_to_delete   long;
  5      l_to_update1  long;
  6      l_to_update2  long;
  7  begin
  8      select rowid
  9        bulk collect into l_rids
 10        from t;
 11
 12      for i in 1 .. l_rids.count
 13      loop
 14          if (mod(i,2) = 0)
 15          then
 16              l_to_update1 := l_to_update1 || '''' || l_rids(i) || ''',';
 17              l_to_update2 := l_to_update2 || ' when rowid = ''' || l_rids(i)
|| ''' then sysdate ';
 18          else
 19              l_to_delete := l_to_delete || '''' || l_rids(i) || ''',';
 20          end if;
 21      end loop;
 22
 23      execute immediate 'delete from t where rowid in ( ' ||
rtrim(l_to_delete,',') || ')';
 24      dbms_output.put_line( sql%rowcount || ' rows deleted' );
 25
 26      execute immediate 'update t set been_updated = ''Y'', ' ||
 27          ' created = case ' || l_to_update2 || ' end where rowid in ( ' ||
 28                         rtrim( l_to_update1, ',' ) || ')';
 29      dbms_output.put_line( sql%rowcount || ' rows updated' );
 30  end;
 31  /
600 rows deleted
600 rows updated

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.17
ops$tkyte@ORA9IR2> select count(*) from t where been_updated='Y';

  COUNT(*)
----------
       600

Elapsed: 00:00:00.00
ops$tkyte@ORA9IR2> select count(*) from t;

  COUNT(*)
----------
       600

Elapsed: 00:00:00.00
Rating: 4
Delete not using PK index April 01, 2004
Reviewer: Oleg Oleander from Hungary
SQL> delete  from T_BILLS nb
  2  where nb.BILL_ID in (select ID from
TABLE(CAST(NT_ID_LIST_TY(ID_TY(1),ID_TY(2)) as NT_ID_LIST_TY)));

0 sor törölve.

Végrehajtási terv
----------------------------------------------------------
   0      DELETE STATEMENT Optimizer=CHOOSE (Cost=54934 Card=1 Bytes=1
          35)

   1    0   DELETE OF 'T_BILLS'
   2    1     NESTED LOOPS (SEMI) (Cost=54934 Card=1 Bytes=135)
   3    2       TABLE ACCESS (FULL) OF 'T_BILLS' (Cost=24 Card=3230 By
          tes=436050)

   4    2       COLLECTION ITERATOR (CONSTRUCTOR FETCH)

Why isnt the above query using PK on T_BILLS (the BILL_ID column)? Naturally I
bind the collection in reality, but its all the same. Its very slow. If I use
the FORALL trick previously seen in this article than it uses the index, and its
pretty fast. Please comment.

Oleg
Followup:
If I were coding this in PLSQL -- i would use the forall (already have the
collection, already in PLSQL -- same net effect but easier to understand and
code)

but see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3779680732446#15740265481549
for a discussion of what's happening when you use this technique and how to help
the optimizer understand what it is being asked to do.
April 02, 2004 -- Thanks for the question regarding "DB Security and Roles", version 8.1.6
You Asked
Can two roles access two separate views with the same name?

Role 1 granted to user A wants to see View x

Role 2 granted to user B wants to see View y

They both want to call it View z and get View x or View y back
according to which role is granted to a given user.

This did not seem possible unless a synonym was created at the
user level.

I set up some proof of concepts and public synonyms were out.

You should be able to access a resource via a synonym in your
schema as long as the role grants you privileges to acces that
resource.  It looks like there would need to be a script that
would grant the role to a user and then add the appropriate
synonym.
and we said...
Roles do not govern a namespace -- they only govern what is
available to each user.

You are correct in your analysis that user level synonyms would
be necessary.  User A would need role1 and a "create synonym z
for x".  User B would need role2 and a "create synonym z for y".
 Synonyms are for name resolution, roles are only for
authorization.
Review & Followup

Rating: 5
But what about synonyms? April 01, 2004
Reviewer: J from Vancouver
I am suprised that Oracle allows the creation of synonyms for many database
objects, but not for database roles. Surely, everyone likes "synonym roles".  :)

Happy April 1st, Tom!
Followup:
hehehe, very funny ;)
April 02, 2004 Ron -- Thanks for the question regarding "alternative approach to passing a java resultset to plsql as a ref cursor", version 8.1.6
You Asked
I read in chapter 19 of your book about the alternative of accessing other
databases from the Oracle server using Java JDBC drivers.

This would be a significantly better way for us to access our AS400 data. I
loaded our type 4 drivers into an Oracle db and, after a little learning curve,
got it to return one row of data.

However, handling more than 1 row is problematic.  Apparently you can pass a ref
cursor to java, but NOT pass a java result set to PLSQL  (refer to the 'expected
behavior' mentioned in bug 987753).

My objective was to provide a PLSQL procedure that would take a query as a
string and return a REF CURSOR.

Can you help with a alternative that will be almost as flexible without adding
to much overhead?

Thank You
Ron Plank
rwp24560@gsk.com
and we said...
Here is an approach you can use -- use a temporary table like this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create global temporary table result_set
  2  ( c1 varchar2(4000),
  3    c2 varchar2(4000),
  4    c3 varchar2(4000),
  5    c4 varchar2(4000)
  6  )
  7  on commit delete rows
  8  /

although you'll have LOTS of columns there (as many as you think you need)

Then, create a package like this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package get_java_result_set
  2  as
  3      type rc is ref cursor;
  4
  5      procedure doit( in_query in varchar2, ref_cursor out rc );
  6  end;
  7  /

Package created.

the doit procedure will call a java stored procedure that will connect to the
AS400, query the data out and stuff it into the temp table.  At the same time,
that java stored procedure will build a query that we can select with -- to get
the data out of the temp table.

Now the package body is very simple:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package body
get_java_result_set
  2  as
  3
  4  procedure jrc( in_query in varchar2, out_query out varchar2 )
  5  is language java
  6  name 'jrc.run_query( java.lang.String, java.lang.String[] )';
  7
  8  procedure doit( in_query in varchar2, ref_cursor out rc )
  9  is
 10      l_query long;
 11  begin
 12      jrc( in_query, l_query );
 13      open ref_cursor for l_query;
 14  end;
 15
 16  end;
 17  /

Package body created.

Here is the template java.  In this case, you would have to augment the code
to actually open the connection to AS400 and create the statment on that
connection.  Other then that -- it is "good to go" pretty much (enhancment for
you -- use SUBSTR() on the character fields to "trim" them to the right length
-- eg: instead of just selecting C1, select substr( c1, 1, 1 ) in the example
below since DUMMY in DUAL is a varchar2(1) -- use the result set metadata to
figure that out)

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace
  2     and compile java source named "jrc"
  3  as
  4  import java.sql.*;
  5  import java.io.*;
  6  import oracle.jdbc.driver.*;
  7
  8  public class jrc
  9  {
 10
 11  public static void run_query( String in_query, String[] out_query ) throws
Exception
 12  {
 13      Connection conn = new OracleDriver().defaultConnection();
 14
 15      Statement stmt = conn.createStatement();
 16
 17      ResultSet rset = stmt.executeQuery(in_query);
 18      ResultSetMetaData rsmd = rset.getMetaData();
 19
 20      int counter = rsmd.getColumnCount();
 21
 22      String insert_stmt ="insert into result_set ( c1";
 23      for(int i=2;i<=counter;i++) insert_stmt = insert_stmt + ", C"+i;
 24      insert_stmt = insert_stmt + ") values (";
 25
 26      String result_set_query = "select ";
 27
 28      for(int i=1;i<=counter;i++)
 29      {
 30          if ( i > 1 )
 31          {
 32              insert_stmt  = insert_stmt  + ",";
 33              result_set_query = result_set_query + ",";
 34          }
 35          insert_stmt = insert_stmt + "?";
 36          switch(rsmd.getColumnType(i))
 37          {
 38              case java.sql.Types.VARCHAR:
 39              case java.sql.Types.CHAR:
 40                  result_set_query = result_set_query + "C" + i;
 41              break;
 42              case java.sql.Types.NUMERIC:
 43              case java.sql.Types.DECIMAL:
 44              case java.sql.Types.DOUBLE:
 45              case java.sql.Types.SMALLINT:
 46              case java.sql.Types.TINYINT:
 47              case java.sql.Types.FLOAT:
 48              case java.sql.Types.REAL:
 49              case java.sql.Types.INTEGER:
 50                    result_set_query = result_set_query + "to_char( C" + i +
")";
 51              break;
 52              case java.sql.Types.DATE:
 53              case java.sql.Types.TIMESTAMP:
 54                  result_set_query = result_set_query + "to_date( C" + i +
")";
 55              break;
 56              default:
 57                  result_set_query = result_set_query + "C" + i;
 58              break;
 59          }
 60                  result_set_query = result_set_query + " \"" +
rsmd.getColumnName(i) + "\"";
 61      }
 62      insert_stmt = insert_stmt+")";
 63      out_query[0] = result_set_query + " from result_set";
 64
 65      System.out.println( result_set_query );
 66      System.out.println( insert_stmt );
 67
 68      PreparedStatement pstmt = conn.prepareStatement(insert_stmt );
 69
 70      while(rset.next())
 71      {
 72          for( int i=1;i<=counter;i++)
 73          {
 74            switch(rsmd.getColumnType(i))
 75            {
 76              case java.sql.Types.VARCHAR:
 77              case java.sql.Types.CHAR:
 78                pstmt.setString(i,rset.getString(i));
 79                break;
 80              case java.sql.Types.NUMERIC:
 81              case java.sql.Types.DECIMAL:
 82                pstmt.setBigDecimal(i,rset.getBigDecimal(i));
 83                break;
 84              case java.sql.Types.DOUBLE:
 85                pstmt.setDouble(i,rset.getDouble(i));
 86                break;
 87              case java.sql.Types.SMALLINT:
 88              case java.sql.Types.TINYINT:
 89                pstmt.setShort(i,rset.getShort(i));
 90                break;
 91              case java.sql.Types.FLOAT:
 92              case java.sql.Types.REAL:
 93                pstmt.setFloat(i,rset.getFloat(i));
 94                break;
 95              case java.sql.Types.INTEGER:
 96                pstmt.setInt(i,rset.getInt(i));
 97                break;
 98              case java.sql.Types.DATE:
 99              case java.sql.Types.TIMESTAMP:
100                pstmt.setDate(i,rset.getDate(i));
101                break;
102              default:
103                pstmt.setString(i,rset.getString(i));
104                break;
105            }
106            if(rset.wasNull())
107              pstmt.setNull(i,rsmd.getColumnType(i));
108          }
109          pstmt.executeUpdate();
110        }
111        rset.close();
112        pstmt.close();
113        stmt.close();
114  }
115  }
116  /

Java created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set serveroutput on
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_java.set_output( 1000000 );
PL/SQL procedure successfully completed.

and now to run it -- we'll see the dynamically generated INSERT and SELECT:

ops$tkyte@ORA817DEV.US.ORACLE.COM> variable x refcursor
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec get_java_result_set.doit( 'select dummy,
sysdate dt, rownum some_number from dual', :x )
select C1 "DUMMY",to_date( C2) "DT",to_char( C3) "SOME_NUMBER"
insert into result_set ( c1, C2, C3) values (?,?,?)

PL/SQL procedure successfully completed.

and now the data:

ops$tkyte@ORA817DEV.US.ORACLE.COM> print :x

DUMMY
---------------------------------------------------------------------------------
--------------------------------------------------
DT
---------
SOME_NUMBER
---------------------------------------------------------------------------------
--------------------------------------------------
X
07-AUG-02
1

ops$tkyte@ORA817DEV.US.ORACLE.COM>

 as you can see -- adding substr to the char fields would be a nice touch
Review & Followup

Rating: 5
Thanks! September 03, 2002
Reviewer: Ron Plank from Raleigh NC USA
That'll work great!
Rating: 5
Big Problem here April 01, 2004
Reviewer: Marcio from Brazil
I had a discussion with team leader java. It's all about insert on database.
Need your help.

Consider
you have a table call indicator like this

version_id int, id int, mgr_id int

and

1, 1, null
1, 2, 1
1, 3, 1
1, 4, 2
1, 5, 2
1, 6, 3
1, 7, 3

so... you have a tree on version 1.
        LEVEL ID
------------- ------------------------------
            1   1
            2     2
            3       4
            3       5
            2     3
            3       6
            3       7

version 1)
               1
             /   \
            2     3
           / \   / \
          4  5  6   7

version 2)
...

it will be another to version 2... and so on...

This tree is mounted on browser and so send all tree to me (database) to insert
through stored procedure. I have to receive all tree because it could be replace
some version in past.

But he said this is hard because doesn't know how to populate a refcursor or
OracleTypes.CURSOR to pass me back and propose use native updateBatch java api
to do that, it means slow-by-slow _ops_ row-by-row on tree. I contested him.
Don't think is hard to.

I'm looking for something right in this context to have a good implementation.

Would you have any comments about how to do that?
Followup:
sounds like he needs to pass you either:

the rows in a global temporary table (easiest method I believe)

the rows in a collection -- create type (......) create type as table of that
type and java sends you an array.
April 02, 2004 BELBAHRI -- Thanks for the question regarding "extracting data to a flat file.", version
You Asked
How can i extract data from flat file to oracle table
with plsql ?
and we said...
I'd probably use sqlplus myself (see;
http://osi.oracle.com/~tkyte/flat/index.html
for some tools).

PL/SQL can do it, it might look like:

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
AUTHID CURRENT_USER
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;
/

You would use that for example like this:

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;
/

Make sure to read about the INIT.ORA parameters (utl_file_dir) you need to setup
!!!
Review & Followup

Rating: 3
Do the PL/SQL one dump Date type with time part? March 29, 2002
Reviewer: Connie from ON. Canada
I am not sure if the PL/SQL procedure dump date type with time part? For
example, if there is a date column starttime, what the result of that field in
the output flat file?

Why do you use the line as:
dbms_sql.define_column( l_theCursor, 1, l_columnValue, 2000);
since you have already define columns in the loop above.

Thx,
Followup:
just issue:

alter session set nls_date_format = 'dd-mon-yyyy hh24:mi:ss'

before (or during) the execution of this routine.  Then dates will come out with
the time.
Rating: 5
Tom, this routine April 14, 2002
Reviewer: Kulguru
writes to the flat file on the server side

my requirement right now is to write to the flat file on the client side,

will it suffice to remove all the utl_file calls with text_io package

Further, your routine cannot handle 'select * from emp', we have to specifically
mention columns in the select.

Do you have a routine which accepts select * from emp.

Thank you
Followup:
TEXT_IO is only for forms.  So, unless you are doing this in forms -- you'll be
changing the utl_file to dbms_output and hoping you are never asked to put out
more then 255 bytes/row.

Further -- the routine can, does, and will handle select * quite well.  In fact
-- the example provided uses select *.
Rating: 5
The good question to another question April 15, 2002
Reviewer: Andrew from calgary, Canada
Tom,

His/her question is spelled strangely. He/she wants to EXTRACT FROM FLAT FILE TO
TABLE. (I.e. he wants to view beautiful sunset lokking towards the east) I.e.
he/she wants to load.

Accordingly he/she has to modify your technique, opening file for reading, (not
writing), parsing every line and executing sql to insert into table (instead of
select).

Why bother with plsql. Just use sql*loader.
Followup:
well, i have that one too
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:464420312302
Guess I read the subject "extracting data to a flat file." on this one and
answered that
....
Rating: 5
Will sqlplus accept Text_io April 16, 2002
Reviewer: Famy
1.Tom, sqlplus is also a client tool just like forms and reports, can I write a
script using text_io and execute it from sqlplus.

2.Further, I have no access to the database server, so the utlfile pl/sql
procedure and sqlldr solutions are out of questin.

What I want is a utility to write the data to the client side, and also a
utilitythat will pick it up from the client side and dump it into a table in my
schema.

Is this possible.
Kindly answer.
Followup:
1) no, sqlplus is NOT a client too "just like" forms and reports.  forms and
reports actually have a full fledged PLSQL runtime engine in them.  SQLPlus does
not.  TEXT_IO does not exist in sqlplus.  SPOOL does.
see

2) SPOOL  -- see
http://asktom.oracle.com/~tkyte/flat/index.html
for ideas.  As for ruling out sqlldr -- then you are basically "out of luck" --
or you are writing your OWN client program that parses an input and file loads
it.

writing data to the client side -- trivial (see flat).
Loading it is trivial as well -- if you use sqlldr -- if you rule it out, it's
hard.
Rating: 4
dump_csv August 08, 2002
Reviewer: Vasaku
Tom, I have modified your dump_csv using text_io, for using from forms.

Now..

I'm passing in the :system.last_query from forms, this also selects the rownum
in the query .. so how should I filter out the rownum.

I also want to filter our all the commas where should I put the REPLACE.

And lastly I want to pass TAB as the separator, but when I passed chr(9)  as the
separator it is not working?

Thanks
Followup:
well, if you know which column rownum is (it is probably the FIRST or the LAST
column right?? ) you can skip it.

What commas???

"it is not working?" -- neither is my car, can you fix it?  not working makes
little sense to me.  please please -- be more descriptive.
Rating: 5
different approach to write to flat file using text_io August 08, 2002
Reviewer: Vasaku
Tom,

Instead of using dump_csv(which uses a query as a input), if I want to just
write the contents of the block(in forms)
how can I do that.

Can you show a small example for an assumed example of a block containing 2
records and 2 columns.

Thx
Followup:
You would use something like:

   go_block( 'block name' )
   first_record
   loop
       text_io to write the :block.item1, :block.item2, ... :block.itemN out
       next_record
       exit when no more records
   end loop
Rating: 5
how to start writing from the secon or the 3rd line in the excel file August 08, 2002
Reviewer: Vasaku
Tom

With the help of your program I'm able to successfully write to the csv file.

But I need column headings.That is I need to have column headings , and
underneath it I need to write the data as per the user requirement.

Any idea how I can do it.

(Sorry for the above comment on 'not working' I got confused between column
separator and line separator)
Followup:
You can use describe_columns:

is
    l_desc_tab   dbms_sql.desc_tab;
    l_cols       number;
    l_cursor     number;
    l_query      long;
begin
    l_cursor := dbms_sql.open_cursor;
    dbms_sql.parse( l_cursor, p_query, dbms_sql.native );
    dbms_sql.describe_columns( l_cursor, l_cols, l_desc_tab );
    ....

to get an array of records that contain this data:

  type desc_rec is record (
        col_type            binary_integer := 0,
        col_max_len         binary_integer := 0,
        col_name            varchar2(32)   := '',
        col_name_len        binary_integer := 0,
        col_schema_name     varchar2(32)   := '',
        col_schema_name_len binary_integer := 0,
        col_precision       binary_integer := 0,
        col_scale           binary_integer := 0,
        col_charsetid       binary_integer := 0,
        col_charsetform     binary_integer := 0,
        col_null_ok         boolean        := TRUE);
  type desc_tab is table of desc_rec index by binary_integer;

See the Supplied Packages guide for the details.  That'll give you your column
headings.

If you use our OWA_SYLK utility (search this site for

owa_sylk utl_file

) you'll get column headings and lots more excell stuff easily.
Rating: 5
how to avoid rowid , which is the first column August 09, 2002
Reviewer: Vasaku
Tom

1.can you tell me the way to change your dump_csv to ignore the first column
i.e. the rowid.

2.If I want to capture the column names in the dump_csv , how should I do that ,
and where should I give the dbms_output.

(I cant use the owa_swlk, the DBA says it is not available)
Followup:
Umm, change 1 to 2 in the loop

       for i in 2 .. 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;

so it loops over columns 2, 3, 4, ....

Right above this comment -- I showed how to call dbms_sql.describe_columns.  You
would do that after you parse.  you would then use utl_file/dbms_output/whatever
to print out the column headings.

OWA_SYLK is as available as DUMP_CSV is.  OWA_SYLK is a utility posted on THIS
SITE -- it is something I wrote (along with Chris Beck).  Search for it (like I
said) and you'll find it.
Rating: 5
where did you code raise subprogram August 20, 2002
Reviewer: Sam
What is the purpose of the below

exception
            when others then
                if ( sqlcode = -1007 ) then exit;
                else
                    raise;
                end if;
 and I dont see that you have coded the subprogram raise , which you are calling
in the exception section.

Please explain.
Followup:
raise is part of the plsql language itself.  see the plsql guide.
Rating: 5
Extracting data to a flat file April 10, 2003
Reviewer: Anil from Malaysia
Dear Tom,
I had successfully used dump_csv function to write to a text file.

My code is like this
declare
l_rows number;
rpt_date date;
tran_date date;
begin
loop
select max(reportdate) into rpt_date from amlareport;
select max(txd_bus_dt) into tran_date from read.trandetail;
if tran_date > rpt_date
then rpt_date := rpt_date +1;
else exit;
end if;
l_rows := dump_csv('select * from read.trandetail where txd_bus_dt=(select
max(reportdate)+1 from amlareport)','c:\oracle\admin\dwdev\udump','FD
Report'||rpt_date);
insert into amlareport values ('FD PLACEMENT VIA CASH >= 250K',rpt_date);
commit;
exit when tran_date=rpt_date;
end loop;
end;
/

I want to have column headings in the text file.  Is there a need to change
dump_csv function. Please tell me how and where the code should be changed.

Regards,

Anil
Followup:
Look up a page or two where we talk about this already.
Rating: 5
SQL output in a text file April 26, 2003
Reviewer: TH from Manama, BAH
Dear Tom,

I need it in the reverse way.

What is the simple way to output the result of an SQL to a textfile. I will call
that query from my application. I won't use SQL Plus.

I can use that feature for custom generated reports.
Followup:
what do you mean "in the reverse way".  this is all about outputting the results
of a query to a file -- which seems to be what you are asking for.
Rating: 4
the dump_csv function and oracle forms May 21, 2003
Reviewer: Marla from Philippines
hi, i tried to use the dump_csv function in Oracle Forms but when I compiled it,
the following problems came out:

Encountered the symbol "AUTHID" when expecting one of the following:
.@%; is as character
The symbol ". was inserted before "AUTHID" to continue.

if i comment the AUTHID CURRENT_USER, the following error comes out:

Implementation Restriction: 'DBMS_SQL.NATIVE': cannot directly access remote
package variable or cursor

I was hoping to use the dump_csv function because this is what I need to do:

- Information entered in the current form will be saved in the database. (this
works already)
- When button pressed trigger, extract data from the table (data of the current
form w/c was just saved, primary key is sir_trans_no) into a text file delimited
by ';'
- :SIR_TRANS_NO is the textbox that holds the primary key

This was supposed to be my trigger:

/* trigger WHEN-BUTTON-PRESSED */
DECLARE

1_row NUMBER;

BEGIN

1_row := dump_csv('select * from sir where sir_trans_no =
:SIR_TRANS_NO','c:\temporary\requests', 'REQUEST');

END;

can you help me please?
Followup:
that is because it is written for the database, not for forms.

authid current user is a database feature, makes no sense in forms.

You would want to use

EXEC_SQL in forms instead of DBMS_SQL.
TEXT_IO in forms instead of UTL_FILE (to get the file on the client)

You need to take the concept but recode it to be a client side plsql
implementation.
Rating: 5
Stored Procedure in DUMP_CSV November 14, 2003
Reviewer: wor from US
Hi Tom,
I have a many stored procedures which takes some inputs parameters and returns a
refcursor.
I would like to dump the results of the refcusor into flat file using dump_csv
function. But I notice that dump_csv takes only sql queries.
Instead of passing sql's I would like to call dump_csv with different stored
procedures in a generic manner and based on the refcursor return values, the
flat should be created. I DO NOT want to specify array variables and do a fetch
into beacuse this removes the generic nature of the dump_csv functionality.
is this possible and how.
Followup:
you cannot -- a ref cursor is not "procedurally processable" by PLSQL.

PLSQL cannot decribe it
PLSQL cannot dynamically fetch column1, then column2 then column3 from it

PLSQL would need to know AT COMPILE time exactly what the query looked like

so, you CANNOT use a refcursor in this case, you must use a DBMS_SQL "cursor"
instead if you want this functionality.
Rating: 5
wor ( continue.. ) November 14, 2003
Reviewer: wor from US
If the above solution is possible in pro*c than please tell me the pro*C way.I
assume it will be faster in pro*C.

Thanks in advance,
Wor
Followup:
see the first url above, it has a pro*c "flat"
Rating: 3
OK January 09, 2004
Reviewer: Siva from Seattle,USA
Dear tom,
csv -> what does it stand for?
Bye!
Followup:
comma separated values
Rating: 4
Extracting Data to a unspecified directory February 04, 2004
Reviewer: Scott Pedersen from New Zealand
Hi Tom,

I am currently working on a system where the user will need to take a dump of
the data produced in Oracle Forms and save it as an Excel file.

I have read about the examples you have provided and it is just what I am
looking for, the only thing is having to stipulate a directory to write to using
UTL_FILE_DIR in the init.ora file, which from my understanding allows the user
to save a file to a directory on the Oracle Server.

What I would like them to be able to do is save the file to a directory of their
choosing on their workstation and not on the server. Is this possible?

Thanks in advance.

Regards,

Scott.
Followup:
use TEXT_IO, that writes on the "client"
Rating: 3
To create xls documents easy February 05, 2004
Reviewer: Peter Visscher from The Netherlands
Hi Tom,

I know a nice way to make Excel sheets from sqlplus. You can use the markup html
tag in sqlplus. Here's an example.

SET LINESIZE 4000
SET VERIFY   OFF
SET FEEDBACK OFF
SET PAGESIZE 999

SET MARKUP HTML ON ENTMAP ON SPOOL ON PREFORMAT OFF

SPOOL c:\temp\test_xls.xls

SELECT    object_type
,         SUBSTR( object_name, 1, 30 ) object
,         created
,         last_ddl_time
,         status
FROM      user_objects
ORDER BY  1, 2
/

SPOOL OFF

SET MARKUP HTML OFF ENTMAP OFF SPOOL OFF PREFORMAT ON

SET LINESIZE 2000 VERIFY ON FEEDBACK ON

Kind regards,

Peter Visscher
Amis Services BV
Rating: 5
April 01, 2004
Reviewer: A reader
why is your dump_csv a function, and then you call it in a procedure. Why cant
it be a procedure straight away? Very curious about this..
Followup:
you of course can obviously do whatever you want.

I wanted a function that returned the number of rows written.  I then call this
function from various procedures.
April 02, 2004 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?
Rating: 3
switching tables April 01, 2004
Reviewer: David from Usa
As stated in problem I have a huge table table1 in Oracle 9i standard edition
where I am storing live data about 600000 records in an hour and I want to purgr
data older than 30 days after each week.
1)I tried deleteing records from table it is taking too long and all other
running jobs using same table run extremely slow.
2) As live data is coming I tried to create new table table2 and start inserting
new data and 30 days data in t1 got error snapshot too old
3) Now I was thinking to use 3 different tables and insert  30 days data into
tables and truncate tables so that I alwas have atleast 30 days data and switch
the table names by creating /replacing synonym using scheduled job
1) What do you suggest
2) I am able to use create /replace synonym in sql but getting error ORA-01031:
insufficient privileges in pl/sql
Followup:
for #2 see
http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html
for #1, since you have SE and hence partitioning is not an option (partitioning
is what I use to implement a rolling window without code) -- you'll have to do
it yourself.

Another technique you can implement is

o you use two tables -- table_0 and table_1
o your loading code will use table_ || mod(to_char(sysdate,'mm'),2) (eg: it'll
have to dynamically prepare the insert statement
o you'll have to make sure that if the code runs near midnight on the last day
of the month -- it does the right thing

(you COULD play with insert of triggers on a view -- at 600k records/hour, it
might impact you though, you'd have to benchmark it -- if you can use instead of
triggers in a view like this:

ops$tkyte@ORA9IR2> create table t0 ( x int );

Table created.

ops$tkyte@ORA9IR2> create table t1 ( x int );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace view v
  2  as
  3  select * from t0
  4  union all
  5  select * from t1;

View created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace procedure do_insert( p_x in number )
  2  as
  3  begin
  4          if ( mod(to_char(sysdate,'mm'),2)=0 )
  5          then
  6                  insert into t0 values ( p_x );
  7          else
  8                  insert into t1 values ( p_x );
  9          end if;
 10  end;
 11  /

Procedure created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace trigger v_trigger instead of insert on v
  2  begin
  3          do_insert( :new.x );
  4  end;
  5  /

Trigger created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into v values ( 1 );

1 row created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select * from t0;

         X
----------
         1

ops$tkyte@ORA9IR2> select * from t1;

no rows selected

ops$tkyte@ORA9IR2> select * from v;

         X
----------
         1

) once you use the view, you can truncate t0 or t1 as needed.
April 02, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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.
Rating: 5
January 23, 2004
Reviewer: Dillip from CA USA
Tom,

I have one more question, if I use these two commands, will the second command
again collect the table stats ?
When I test I see the timestamp for the LAST_ANALYZED is changing, but not sure
if it is collecting the stats again ? If it's going to collect the table stats
again is there a way not to collect the without using
dbms_stat.gather_index_stats ?

(1)
execute dbms_stats.gather_table_stats(ownname => 'SCOTT', tabname => 'TEST');

(2)
execute dbms_stats.gather_table_stats(ownname => 'SCOTT', tabname => 'TEST',
method_opt => 'FOR ALL INDEXED COLUMNS', cascade => TRUE);
Followup:
table stats are always gathered with dbms_stats.gather_table_stats

it can additionally gather histograms and indexes.

you cannot use "gather table stats" (which by its very name says something!)
without gathering table stats.  if you only want index stats, well, you'd use
that api call.
Rating: 5
pl/sql numeric error February 17, 2004
Reviewer: A reader
Hi

I have following procedure (part of a package)

PROCEDURE ANALYZE_DAILY
   AS
      type array is table of varchar2(256);
      l_schema             array          := array('LSC', 'PERFSTAT');
      l_weekday            number;
      l_degree             number         := 1;
      l_percent            number         := 20;
      l_table_name         array;
      l_columns            array;
      l_monitoring         array;
      l_partitioned        array;
      l_last_analyzed      array;
      l_objlist            dbms_stats.objectTab;
      l_granularity        varchar2(10)   := 'DEFAULT';
      l_method             varchar2(50)   := 'FOR ALL INDEXED COLUMNS SIZE 75';

   BEGIN
      execute immediate 'alter session set nls_territory = ''SPAIN''';
      execute immediate 'alter session set sort_area_size = 33554432';
      l_weekday := to_char(sysdate, 'D');
      if l_weekday in (4) then
            for i in 1 .. l_schema.count
            loop
                  select a.table_name, b.columns, a.monitoring, a.partitioned,
a.last_analyzed
                    bulk collect into l_table_name, l_columns, l_monitoring,
l_partitioned, l_last_analyzed
                    from dba_tables a, dbstat_tab b
                   where a.owner = b.owner(+)
                     and a.table_name = b.table_name(+)
                     and a.owner = l_schema(i);
                  dbms_stats.gather_schema_stats(OWNNAME => l_schema(i),
                                                 OPTIONS => 'LIST STALE',
                                                 OBJLIST => l_objlist);
                  for x in l_table_name.first..l_table_name.last
                  loop
                        if l_monitoring(x) = 'NO' then
                           execute immediate 'alter table
'||l_schema(i)||'.'||l_table_name(x)||' monitoring';
                        end if;
                        if l_last_analyzed(x) is NULL then
                           analyze_table(p_owner       => l_schema(i),
                                         p_table       => l_table_name(x),
                                         p_percent     => l_percent,
                                         p_method      => 'FOR COLUMNS
'||l_columns(i)||' SIZE 75',
                                         p_granularity => l_granularity,
                                         p_degree      => l_degree);
                        end if;
                  end loop;
                  for y in 1..l_objlist.count
                  loop
                        analyze_table(p_owner       => l_schema(i),
                                      p_table       => l_objlist(y).objname,
                                      p_percent     => l_percent,
                                      p_method      => 'FOR COLUMNS
'||l_columns(i)||' SIZE 75',
                                      p_granularity => l_granularity,
                                      p_degree      => l_degree);
                  end loop;
            end loop;
      elsif l_weekday = 7 then
            for i in 1 .. l_schema.count
            loop
                  analyze_schema(p_owner       => l_schema(i),
                                 p_percent     => l_percent,
                                 p_method      => l_method,
                                 p_granularity => l_granularity,
                                 p_degree      => l_degree);
            end loop;
      else
            null;
      end if;
      END ANALYZE_DAILY;

it works well, however if I change

for y in 1..l_objlist.count

to

for y in l_objlist.first..l_objlist.last

I get an error saying

exec pkg_dbstat.analyze_daily;
BEGIN pkg_dbstat.analyze_daily; END;

*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "SYSTEM.PKG_DBSTAT", line 112
ORA-06512: at line 1

However it does what I need, analyze the tables.

What is the difference between

for y in 1..l_objlist.count
and
for y in l_objlist.first..l_objlist.last

???
Followup:
if l_objlist is "empty"

- count will be 0
- but first and last will be null

ops$tkyte@ORA920PC> begin
  2    dbms_output.put_line( 'before loop' );
  3    for i in 1 .. 0
  4    loop
  5          dbms_output.put_line( 'in loop' );
  6    end loop;
  7    dbms_output.put_line( 'after loop' );
  8  end;
  9  /
before loop
after loop

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> begin
  2    dbms_output.put_line( 'before loop' );
  3    for i in null..null
  4    loop
  5          dbms_output.put_line( 'in loop' );
  6    end loop;
  7    dbms_output.put_line( 'after loop' );
  8  end;
  9  /
before loop
begin
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at line 3

You cannot "loop" with NULL -- that is the error.
Rating: 4
reg table monitoring March 19, 2004
Reviewer: sam from NJ, USA
Tom,

How much of a overhead a table monitoring has on a table??

Is it advisable to have table monitoring on ??
Both on OLTP & DSS systems.

Or, if we know on our system , a order entry table has lot's of insert/update
etc. so let's not monitor that.(generate stats by default , since we know it's
changed a lot-  10%  may be )
Monitor other tables where the changes are random.
Is that good approach?
Followup:
pretty much nill overhead.

in 10g it'll actually be turned on for you pretty much.
Rating: 5
Delete Stats March 31, 2004
Reviewer: A reader
In what cases should I delete statistics ? What is it useful for ?
Followup:
you generally never need it.  there for completeness.  I use it for testing (get
rid of existing, gather in a different fashion).
Rating: 5
confused at analyze April 01, 2004
Reviewer: Jerry from US
Tom,

This site is really a wonderland.  Thank you so much for your hard work.

A follow up on the analyze question.  I found that different analyze options can
bring totally different results.  In my company we run analyze like this:

exec dbms_stats.gather_table_stats (ownname=>'XYZ',tabname=>
'EMPLOYEES',estimate_percent=>25,method_opt=> 'FOR ALL
COLUMNS',degree=>4,cascade=>TRUE );

It runs for all user tables.
My first question is why we need 'FOR ALL COLUMNS'.  At most, 'for all indexed
columns' should be enought, right?

Also If I run analyze using 'for all column' or using 'for all indexed columns'
in one database, one important query ran really slow because the execution plan
does not utilize a certain index.  If I delete the stats, then run analyze
without the method_opt, just

exec dbms_stats.gather_table_stats (ownname=>'XYZ',tabname=>
'EMPLOYEES',estimate_percent=>25,degree=>4,cascade=>TRUE );

Then the query uses the index and comes back fast.

However, the same analyze options tested in another database with exactly same
table/views but different data showed no different in results.

I looked at the query.  It's actually fairly simple, one subquery then
outer-join.  I don't think there is any other way I rewrite it to make it
simpler.

Is the analyze method the issue here?  How should I go about to diagnose this
type of issue?

Thank you!
Followup:
it doesn't run for all usre tables -- it should only run for one?

you'd have to tell ME why you need or want for all columns.  In a DW, it can be
relevant.  In a transactional system -- probably not.

that dbms_stats without the method opt is using for 'all columns size 1' by
default.

I'd need an example to look at.
Rating: 4
April 02, 2004
Reviewer: Jerry
This is an internal application we build.  Because we don't know what's data
would be like, there are lots of indexes built, on all foreign keys, all id
columns.  Many columns can have its own index and also be part of another
indexed.  Also the analyze is scripted and histogram info is collect for all
indexed columns.

The application is primarily OLTP but also hosts some DW types of queries, sort
of hybrid.  Ugly but for history and cost reason, not much can be done.

As for the query, please see below:
sql>analyze table MAPPING delete statistics;

Table analyzed.

sql>exec dbms_stats.gather_table_stats (ownname=>'TMP',tabname=> 'MAPP'
ING',estimate_percent=>25,method_opt=> 'FOR ALL COLUMNS',degree=>4,cascade=>TRUE
);

PL/SQL procedure successfully completed.

sql>explain plan for
  2  select * from shared;

Explained.

sql>@utlxpls

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
-------------------

---------------------------------------------------------------------------------
------------------
| Id  | Operation                  |  Name                        | Rows  |
Bytes |TempSpc| Cost  |
---------------------------------------------------------------------------------
------------------
|   0 | SELECT STATEMENT           |                              |   103K|
85M|       |   177K|
|   1 |  VIEW                      | SHARED                       |   103K|
85M|       |   177K|
|   2 |   SORT UNIQUE              |                              |   103K|
12M|    27M|   177K|
|*  3 |    HASH JOIN               |                              |   103K|
12M|       |   175K|
|   4 |     VIEW                   |                              |  1285 |
7710 |       |   161 |
|*  5 |      FILTER                |                              |       |
 |       |       |
|   6 |       SORT GROUP BY        |                              |  1285 |
12850 |       |   161 |
|*  7 |        INDEX FAST FULL SCAN| DEVICE_UNIQ                  | 49913 |
487K|       |    16 |
|   8 |     NESTED LOOPS OUTER     |                              | 87717 |
10M|       |   175K|
|   9 |      TABLE ACCESS FULL     | ALLOCATION                   | 87717 |
6852K|       |   240 |
|* 10 |      VIEW PUSHED PREDICATE |                              |     1 |
43 |       |     2 |
|* 11 |       HASH JOIN            |                              |   190 |
8170 |  1736K|   104 |
|  12 |        TABLE ACCESS FULL   | HOSTDEVICE                   | 41204 |
1247K|       |    74 |
|* 13 |        INDEX RANGE SCAN    | MAPPING_IDX1                 |   190 |
2280 |       |     2 |
---------------------------------------------------------------------------------
------------------

31 rows selected.

sql>analyze table MAPPING delete statistics;

Table analyzed.

sql>exec dbms_stats.gather_table_stats (ownname=>'TMP',tabname=> 'MAPP
ING',estimate_percent=>25,degree=>4,cascade=>TRUE );

PL/SQL procedure successfully completed.

sql>delete from plan_table;

14 rows deleted.

sql>commit;

Commit complete.

sql>explain plan for
  2  select * from shared;

Explained.

sql>@utlxpls

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
-------------------

---------------------------------------------------------------------------------
-------------------
| Id  | Operation                         |  Name                        | Rows
| Bytes |TempSpc| Cost  |
---------------------------------------------------------------------------------
-------------------
|   0 | SELECT STATEMENT                  |                              |
103K|    85M|       |   177K|
|   1 |  VIEW                             | SHARED                       |
103K|    85M|       |   177K|
|   2 |   SORT UNIQUE                     |                              |
103K|    12M|    27M|   177K|
|*  3 |    HASH JOIN                      |                              |
103K|    12M|       |   175K|
|   4 |     VIEW                          |                              |  1285
|  7710 |       |   161 |
|*  5 |      FILTER                       |                              |
|       |       |       |
|   6 |       SORT GROUP BY               |                              |  1285
| 12850 |       |   161 |
|*  7 |        INDEX FAST FULL SCAN       | DEVICE_UNIQ                  | 49913
|   487K|       |    16 |
|   8 |     NESTED LOOPS OUTER            |                              | 87717
|    10M|       |   175K|
|   9 |      TABLE ACCESS FULL            | ALLOCATION                   | 87717
|  6852K|       |   240 |
|* 10 |      VIEW PUSHED PREDICATE        |                              |     1
|    43 |       |     2 |
|  11 |       NESTED LOOPS                |                              |    12
|   516 |       |    14 |
|* 12 |        INDEX RANGE SCAN           | MAPPING_IDX1                 |    12
|   144 |       |     2 |
|  13 |        TABLE ACCESS BY INDEX ROWID| HOSTDEVICE                   |     1
|    31 |       |     1 |
|* 14 |         INDEX UNIQUE SCAN         | PK_HOSTDEVICE                |     1
|       |       |       |
---------------------------------------------------------------------------------
-------------------

32 rows selected.

The second query runs very fast.  The first one did not come back after 4-5
hours.  There is no data change on the static testing environment.  It seems
that oracle pick the wrong execution plan when given too much information.  The
mapping table are all id columns joining several tables together
April 02, 2004 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.
Rating: 5
delayed block cleanout and dbwr February 05, 2004
Reviewer: A reader
Hi

After reading some documents about delayed logging block cleanout some doubts
came to my mind.

When a block is modified and fast committed (by setting RBS transaction slot to
commit) this block wont be clean out until   it is visited again. I wonder if
the block is never visited again how Oracle make the change permanently? DBWR by
writing dirty buffers to disk? All the documents I have read none of them refers
dirty blocks written by dbwr which I think it performs a key role as well dont
you think so?
Followup:
if the block is cleaned out after the commit in the buffer cache, it will be
written clean by dbwr when it checkpoints it naturally later on.
Rating: 5
note 45895.1 February 05, 2004
Reviewer: A reader
Hi

this note says this:

Delayed block cleanout on old committed updates.  An update operation  completes
and commits; the updated blocks are not touched again until a  long-running
query begins.  Delayed Block Cleanout (DBC) has never been  done on the blocks.
This can result in a scenario which happens only  under specific circumstances
in VLDB, causing ORA-01555 errors when NO  updates or inserts are being
committed on the same blocks a query is  retrieving.
All of the following must be true for an ORA-01555 to occur in this case:

(i) An update completes and commits and the blocks are not       touched again
until...
(ii) A long query begins against the previously updated blocks.

Here it´s saying a block is never cleaned until it´s visited?!?! Is it true?!
Followup:
if the block cleanout was delayed -- that is correct.

If you have my book "Expert One on One Oracle" -- i go into this in some depth
and even set up cases to show you how to get these "delayed block cleanouts" to
happen in  a test scenario.
Rating: 5
ORA-01555 April 02, 2004
Reviewer: atul from India
Hi,

I'm getting this famous error(ORA-01555) while executing following
query,

set transaction use rollback segment trs;
select distinct(x.table_name) from dba_tables x , (
(select table_name,column_name,position from user_cons_columns
where constraint_name in
(select constraint_name from user_constraints x,user_snapshots y  where
constraint_type='P' and x.table_name=y.name and
master_link like '%QATLO%'  )  ));

Even if after giving "set transaction" its using other rollback segment..

I don't know why?
Also why select statement is using rollback?

Thanks,
Atul
Followup:
set transaction only counts for when you WRITE to rbs.

when you read (query) you need to read the RBS the others that have modified
your data wrote to.  You are reading their changes -- your query needs to read
perhaps EVERY rollback segment in the system!

Every query might use RBS to provide the read consistent (eg: correct) answer.

The odds of a 1555 are directly related to the size of your smallest RBS, your
RBS are too small for the work you do in your database.
April 02, 2004 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.
Rating: 4
Problem with DB-Link to access a procedure February 27, 2004
Reviewer: Raju from India-Bangalore
I have created the following script in Database A and database B
TYPE PLSQL_TAB IS TABLE OF VARCHAR2(100);

and created a DB link from B to A

and created the following procedure in A

CREATE OR REPLACE PROCEDURE proc_test(
out_tab OUT plsql_tab)
IS
BEGIN

--INSERT INTO TABLE CAST(out_tab AS plsql_tab)

SELECT ctry_cd
BULK COLLECT INTO out_tab
FROM ctry;

EXCEPTION
WHEN OTHERS THEN
NULL;
END;
/

and i made a synonym of
create or replace proc_test for proc_test@;

and i tried getting the table data accesing the proc from b by using the
following script

declare
a plsql_tab;
begin
proc_test(a);
end;
/

it gives me the following error

ERROR at line 1:
ORA-06550: line 4, column 1:
PLS-00306: wrong number or types of arguments in call to 'PROC_TEST'
ORA-06550: line 4, column 1:
PL/SQL: Statement ignored

Please let me know if there is any solution for this. And i think we cannot pass
the data thru refcursors also.

Thanks in advance
Raju
Followup:
instead of telling me what doesn't work - how about you state what you are
attempting to achieve? (then I can probably tell you how to do that)
Rating: 4
Problem with DB-Link to access a procedure March 01, 2004
Reviewer: Raju from India-Bangalore
I have created the following script in Database A and database B
TYPE PLSQL_TAB IS TABLE OF VARCHAR2(100);

and created a DB link from B to A

and created the following procedure in A

CREATE OR REPLACE PROCEDURE proc_test(
out_tab OUT plsql_tab)
IS
BEGIN

--INSERT INTO TABLE CAST(out_tab AS plsql_tab)

SELECT ctry_cd
BULK COLLECT INTO out_tab
FROM ctry;

EXCEPTION
WHEN OTHERS THEN
NULL;
END;
/

and i made a synonym of
create or replace proc_test for proc_test@;

and i tried getting the table data accesing the proc from b by using the
following script

declare
a plsql_tab;
begin
proc_test(a);
end;
/

it gives me the following error

ERROR at line 1:
ORA-06550: line 4, column 1:
PLS-00306: wrong number or types of arguments in call to 'PROC_TEST'
ORA-06550: line 4, column 1:
PL/SQL: Statement ignored

Please let me know if there is any solution for this. And i think we cannot pass
the data thru refcursors also.

Thanks in advance
Raju
Followup:
ops$tkyte@ORA920> @connect 'ops$tkyte/xxx@aria-dev.us.oracle.com'

ops$tkyte@ORA920> create or replace package my_pkg
  2  as
  3          type array is table of varchar2(100);
  4
  5          procedure proc_test( out_tab out array );
  6  end;
  7  /
Package created.

ops$tkyte@ORA920> create or replace package body my_pkg
  2  as
  3
  4  procedure proc_test( out_tab out array )
  5  is
  6  begin
  7          select username bulk collect into out_tab
  8            from all_users;
  9  end;
 10
 11  end;
 12  /
Package body created.

ops$tkyte@ORA920> @connect /

ops$tkyte@ORA920PC> create database link ora920
  2  connect to ops$tkyte
  3  identified by xxx
  4  using 'aria-dev.us.oracle.com'
  5  /

Database link created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create or replace synonym my_pkg for my_pkg@ora920
  2  /

Synonym created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> declare
  2          a  my_pkg.array;
  3  begin
  4          my_pkg.proc_test( a );
  5          dbms_output.put_line( a.count );
  6  end;
  7  /
41

PL/SQL procedure successfully completed.
Rating: 4
database link March 27, 2004
Reviewer: Mahesh Kumar from NEW DELHI , INDIA
Hi Tom

 I am trying make a database link to be used for replication
but the link wasn't working giving the following error

ORA-02085: database link REP.WORLD connects to ORCL

ORCL - MASTER SITE
REP  - REPLICATION TO BE SENT

I have created with the following command

CREATE DATABASE LINK rep.WORLD CONNECT TO repadmin IDENTIFIED BY repadmin USING
'rep.WORLD';

Required information :
SQL> select * from  global_name;

GLOBAL_NAME
-------------------
ORCL

* same for both SITE coz i am creating on same machine

PARAMETERS VALUES

GLOBAL_NAMES      TRUE
DB_DOMAIN         world

i also tried with GLOBAL_NAME
 CREATE DATABASE LINK orcl@rep CONNECT TO repadmin IDENTIFIED BY repadmin USING
'rep.WORLD'

It gives the following error
ORA-23378: connection qualifier "@REP" is not valid for
            object group "PUBLIC"."MYREPGRP"

urgent help is required from your site.
Followup:
global_names are meant to be globally UNIQUE - same machine or not.

rename the "rep" site using alter database rename global_name to rep; and it'll
work.
Rating: 5
database links March 28, 2004
Reviewer: Mahesh Kumar from New Delhi, India
Hi Tom

   Thanx, I alter my database global_name to REP.WORLD and links are working
fine.

Now i wanna know why links created wtihout using " CONNECT TO username
IDENTIFIED BY password" are retuns this error
ORA-01005: null password given; logon denied

e.g

SQL> CREATE DATABASE LINKS rep.world USING 'rep.world';
SQL> SELECT * FROM scott.emp@rep.world;

ORA-01005: null password given; logon denied
Pls tell me where i m wrong. Also tell how can we execute DDL using database
links.

Thanx & Regards
Followup:
show me a full cut and paste (a real one!), not working that way for me.
Rating: 4
DATABASE LINKS March 29, 2004
Reviewer: Mahesh Kumar from New Delhi, India
Hi Tom

             The full cut and paste is ......

ORCL:SQL> CONN / @ORCL.WORLD AS SYSDBA
connected
ORCL:SQL>select name from v$database;

NAME
---------
ORCL

ORCL:SQL>SHOW PARAMETER GLOBAL

ORCL:SQL>SHOW PARAMETER GLOBAL_NAME

NAME                                 TYPE        VALUE
------------------------------------ ----------- -----------global_names
                boolean     TRUE

ORCL:SQL>SELECT * FROM GLOBAL_NAME;

GLOBAL_NAME

ORCL.WORLD

ORCL:SQL>CONN / @REP.WORLD AS SYSDBA
Connected.

ORCL:SQL>select name from v$database;

NAME
---------
REP
ORCL:SQL>SHOW PARAMETER GLOBAL_NAME

NAME                                 TYPE        VALUE
------------------------------------ ----------- -----------global_names
                boolean     TRUE

ORCL:SQL>SELECT * FROM GLOBAL_NAME;

GLOBAL_NAME

REP.WORLD

ORCL:SQL>CONN / @ORCL.WORLD AS SYSDBA
Connected.

ORCL:SQL>CREATE DATABASE LINK REP.WORLD USING 'REP.WORLD';

Database link created.

ORCL:SQL>SELECT * FROM SCOTT.DEPT@REP.WORLD;
SELECT * FROM SCOTT.DEPT@REP.WORLD
                        *
ERROR at line 1:
ORA-01005: null password given; logon denied

ORCL:SQL>DROP DATABASE LINK REP.WORLD;

Database link dropped.

ORCL:SQL>CREATE DATABASE LINK REP.WORLD CONNECT TO SYSTEM
          IDENTIFIED BY MANAGER USING 'REP.WORLD';

Database link created.

ORCL:SQL>SELECT * FROM SCOTT.DEPT@REP.WORLD;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 ACCOUNTING     NEW YORK
        20 RESEARCH       DALLAS
        30 SALES          CHICAGO
        40 OPERATIONS     BOSTON

 Please, tell me why i am not able to fetch rows with database links created
without "CONNECT TO system IDENTIFIED BY manager" clause. If possible give me
cut and paste of syntax

Thanx $ Regards
Followup:
you are doing stuff "as sysdba"

stop it.  just stop doing that.  arg....

You have a null login/password when you connect "/".  Thats not allowed.

Log in as someone with a username and password.  THEN you can use the link
without a username password.

STOP doing things "as sysdba" unless you HAVE to (eg: startup, shutdown).
Pretend SYS and SYSTEM do not exist when you create things.  Use YOUR own
accounts.

Use sys and system only when directed to....  pretend they are not there
otherwise.
Rating: 5
database links March 30, 2004
Reviewer: Mahesh Kumar from New Delhi, India
Thanx Tom, my links are working. Can we execute ddl using database links, if yes
then how?

Thanx & Regards
Followup:
you can use DBMS_JOB to schedule the DDL to execute on the remote site after you
commit.

dbms_job.submit@remote_site( l_job, 'execute immediate ''create table t ( x int
)'';');
commit;
Rating: 5
Database Links March 31, 2004
Reviewer: Mahesh Kumar from New Delhi, India
Hi Tom

Now problem is that my database links in one database is working fine  but in
2nd database giving the error:

ORA-02085: database link ORCL.WORLD connects to REP.WORLD

For your information cut and paste of both database is:

Database: ORCL.WORLD

ORCL:SQL>CONN SYSTEM/MANAGER@ORCL.WORLD
Connected.
ORCL:SQL>SELECT NAME FROM V$DATABASE;

NAME
---------
ORCL

ORCL:SQL>CREATE DATABASE LINK REP.WORLD USING 'REP.WORLD';

Database link created.

ORCL:SQL>SELECT GLOBAL_NAME FROM GLOBAL_NAME@REP.WORLD;

GLOBAL_NAME

REP.WORLD

ORCL:SQL>SELECT GLOBAL_NAME FROM GLOBAL_NAME
  2  ;

GLOBAL_NAME

ORCL.WORLD

ORCL:SQL>show parameter global_names

NAME                      TYPE        VALUE
----------------------- ----------- -----------
global_names              boolean     TRUE

Database: REP.WORLD

REP:SQL>CONN SYSTEM/MANAGER@REP.WORLD
Connected.
REP:SQL>SELECT NAME FROM V$DATABASE;

NAME
---------
REP

REP:SQL>CREATE DATABASE LINK ORCL.WORLD USING 'ORCL.WORLD';

Database link created.

REP:SQL>SELECT GLOBAL_NAME FROM GLOBAL_NAME@ORCL.WORLD;
SELECT GLOBAL_NAME FROM GLOBAL_NAME@ORCL.WORLD
                                    *
ERROR at line 1:
ORA-02085: database link ORCL.WORLD connects to REP.WORLD

REP:SQL>SELECT GLOBAL_NAME FROM GLOBAL_NAME
  2  ;

GLOBAL_NAME

REP.WORLD

REP:SQL>show parameter global_names

NAME               TYPE        VALUE
---------------- ----------- --------------
global_names      boolean     TRUE

For your Reference entery is tnsnames.ora is :

REP.world =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = ORACLE)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SID =REP)
    )
  )

ORCL.world =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = ORACLE)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SID = ORCL)
    )
  )

Pleas help

Thanx & Regards
Followup:
REP:SQL>SELECT GLOBAL_NAME FROM GLOBAL_NAME@ORCL.WORLD;
SELECT GLOBAL_NAME FROM GLOBAL_NAME@ORCL.WORLD
                                    *
ERROR at line 1:
ORA-02085: database link ORCL.WORLD connects to REP.WORLD

says it all.

orcl.world on that server is pointing to rep.world, not your ORCL instance.

remember the tnsnames on the SERVER - on the database SERVER is what counts.
not your local copy.
Rating: 4
Database Links April 02, 2004
Reviewer: Mahesh Kumar from New Delhi, India
Hi Tom
      Yes ORCL.WORLD database link connects to REP database not to ORCL
database! but why i don't know. if i try to connect using

SQL> CONNECT system/manager@ORCL.WORLD
connected

SQL> SELECT name from V$DATABASE;
----------------
REP

I am in deep trobule why this happened, why not it connects to ORCL database ?.

but, a trick works  a new LISTENER is created with the folowing configuration
NAME : LISTENER1 HOST : oracle PORT : 1525

sample  tnsnames.ora
 ORCL.WORLD =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = ORACLE)(PORT = 1525))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = ORCL.WORLD)
    )
  )

now i am able to connnect to ORCL database using ORCL.WORLD service. My links
are also working.

but pls tell me why this type of thing happened also explian your statement ---
" local copy "

" remember the tnsnames on the SERVER - on the database SERVER is what counts.
not your local copy. "

Thanx & Regards
Followup:
when you connect via sqlplus with user/pass@connect_string

the connect_string is retrieved by default from the tnsnames.ora file on your
client workstation.

when you create a database link using 'connect_string' -- the connect string is
retrieved by default from the tnsnames.ora file on your database SERVER machine
-- not your desktop workstation.
April 02, 2004 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.
Rating: 5
How about the public role ?? March 04, 2004
Reviewer: Bart Verstegen from Netherlands
Hello Tom,

I'm having fun reading many topics.

About this topic, I'm lost a little.

In schema A I create a table 't':
 create table t (id varchar2(2));
Table created.

grant the rights of table 't' to the ROLE (??) PUBLIC:
 grant select on t to PUBLIC;
Grant succeeded.

In schema B I create a procedure:

 create or replace procedure p
 is
   l_id  a.t.id%type;
 begin
   null;
 end;

Procedure created.
(Heuh ?)

Is there something special about the PUBLIC role ? Because with self-defined
roles all works as expected (like the primary question of this topic). I came to
this by accident because I was revoking rights from our own role and could still
compile procedures (because of grants to PUBLIC, I was thrown back in my chair
quite heavily).

Btw:
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

Best regards.
Followup:
PUBLIC acts like a role but is really a schema -- and a schema that everyone
"inherits"

A grant to public is just like granting that grant to each individual user.

Be careful with PUBLIC grants -- everyone has them, regardless.
Rating: 5
Repost on my question March 04, 2004
Reviewer: Bart Verstegen from Netherlands
This says it all isn't it ? (...which count as well phrase)

(sorry for bothering then !)

<QUOTE>
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)
</QUOTE>
Rating: 3
OK April 02, 2004
Reviewer: Sue from Arlington,VA,USA
Dear Tom,
1)How to find out 'what are the privileges that make up a role'?
2)Is it possible to disable a privilege(either system or
object privilege) for a particular user with out revoking
the privilege from him or her?
Please do reply.
Bye!
Followup:
1) query dba_sys_privs and dba_tab_privs where grantee = 'THAT ROLE'

2) no, the way to disable that priv is to revoke it.
April 02, 2004 John -- Thanks for the question regarding "Evaluate Expression", version any
You Asked
Hi Tom,

How can I evaluate a character string ?

For example, if I have a character string '5 * (4 + 6)'
 how can I evaluate it to 50

 Or character string '1/3' to 0.3333333

 Thanks

Roop
and we said...
Here is one method.  It lets you pass a formula and upto 1 input
to the formual (you use the word "in_variable" in the formula
and I'll bind the value in for you).

ops$tkyte@ORA8I.WORLD> create or replace package eval
  2  as
  3      function do_it
  4      ( p_function in varchar2, p_var in number )
  5      return number;
  6
  7  end;
  8  /
Package created.

ops$tkyte@ORA8I.WORLD> create or replace package body eval
  2  as
  3
  4  g_theCursor        integer default dbms_sql.open_cursor;
  5
  6  function do_it
  7  ( p_function in varchar2, p_var in number )
  8  return number
  9  is
 10      l_rowsprocessed number  default 0;
 11      l_retval       number;
 12  begin
 13      dbms_sql.parse(g_theCursor,
 14                    'begin :ret_val := ' ||
 15         replace(p_function,'in_variable',':in_variable') ||
 16                      '; end;',
 17                     dbms_sql.native );
 18
 19      dbms_sql.bind_variable
               (g_theCursor,':ret_val',l_retval );
 20      begin
 21          dbms_sql.bind_variable
                 ( g_theCursor, ':in_variable', p_var );
 22      exception
 23          when others then
 24              if ( sqlcode = -1006 ) then
 25                  null;
 26              else
 27                  RAISE;
 28              end if;
 29      end;
 30      l_rowsprocessed := dbms_sql.execute(g_theCursor);
 31      dbms_sql.variable_value
             ( g_theCursor, ':ret_val', l_retval );
 32
 33      return l_retval;
 34  end do_it;
 35
 36
 37  end eval;
 38  /

Package body created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> begin
  2    dbms_output.put_line
  3    ( eval.do_it( '1 * (12.1 * POWER(in_variable,2.06))',
                5 ) );
  4  end;
  5  /
333.168228204034354868248961030690574583

PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> begin
  2     dbms_output.put_line
  3     ( eval.do_it( '2.06 * 1.77371', NULL ) );
  4  end;
  5  /
3.6538426

PL/SQL procedure successfully completed.
Review & Followup

Rating: 5
extend February 18, 2004
Reviewer: A reader
can I extend this to evaluate the following string and return true or false as
if it was part of an IF statement.....

'1 IN (1,2,3) AND 1 > -2'

so if I evaluated the above string as an expression I would get a true.
Followup:
sort of.

I used SQL which does not permit booleans (not part of sql, only part of plsql).

what you would do is

   'select count(*) from dual where ' || your_where_clause;

and if that returns 1, "true", if it returns 0, "false" -- write a procedure
that parses that, binds the output and returns:

    return (l_output = 1);
Rating: 5
February 19, 2004
Reviewer: A reader
Can you possibly show me an example where I can parse and bind the sql ...  the
string (where_clause) could vary , but always return true/false - for instance
it may be '1 IN (1,2,3) AND 1 > -2' or could be '1 > -2'.

i changed your procedure above to do the following to take care of the '1>1'
string...

CREATE OR REPLACE package body eval
    as

    g_theCursor        integer default dbms_sql.open_cursor;

    function do_it
    ( p_function in varchar2, p_var in number )
    return number
    is
       l_rowsprocessed number  default 0;
       l_retval       number;
   begin

       dbms_sql.parse(g_theCursor,
                     'begin if ' ||
          replace(p_function,'in_variable',':in_variable') ||
                       ' then :ret_val := 1; end if; end;',
                      dbms_sql.native );

       dbms_sql.bind_variable
             (g_theCursor,':ret_val',l_retval );
       begin
           dbms_sql.bind_variable
               ( g_theCursor, ':in_variable', p_var );
       exception
           when others then
               if ( sqlcode = -1006 ) then
                   null;
               else
                   RAISE;
               end if;
       end;
       l_rowsprocessed := dbms_sql.execute(g_theCursor);
       dbms_sql.variable_value
           ( g_theCursor, ':ret_val', l_retval );

       return l_retval;
   end do_it;

   end eval;

good or bad ?

thanks
Followup:
this is what I meant:

ops$tkyte@ORA920PC> create or replace function do_it( p_function in varchar2 )
return boolean
  2  is
  3     l_cnt       number;
  4  begin
  5          execute immediate
  6      'select count(*) from dual where ' || p_function
  7      into l_cnt;
  8
  9      return (l_cnt = 1);
 10  end;
 11  /

Function created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> begin
  2          if ( do_it( '1 IN (1,2,3) AND 1 > -2' ) )
  3      then
  4         dbms_output.put_line( '1 IN (1,2,3) AND 1 > -2 is TRUE' );
  5      else
  6         dbms_output.put_line( '1 IN (1,2,3) AND 1 > -2 is FALSE' );
  7      end if;
  8          if ( do_it( '1 < 0' ) )
  9      then
 10         dbms_output.put_line( '1 < 0 is TRUE' );
 11      else
 12         dbms_output.put_line( '1 < 0 is FALSE' );
 13      end if;
 14  end;
 15  /
1 IN (1,2,3) AND 1 > -2 is TRUE
1 < 0 is FALSE

PL/SQL procedure successfully completed.
Rating: 5
binding February 20, 2004
Reviewer: A reader
I guess this would be far more efficient if it was binding the values ? Would
that be possible considering not knowing how many values in the IN ...  it
probably is possible with dynamic sql, however if I am calling this function
many,many times with a different string, what is my best option?

x=100
for i in 1..100 loop
 x := x +1;
 p_utils.eval2( i || '>0 AND ' || x  || ' in (100,104,110)' );
end loop;

the execute immediate in your example above, would this be a soft parse ?

sorry if I am asking too much .. just in a dilema about how to handle a sticky
bit of the system that suprisingly "must perform" !
Followup:
two approaches to making this "bind variable friendly"

o application contexts
o cursor_sharing

ops$tkyte@ORA920PC> create or replace function do_it( p_function in varchar2 )
return boolean
  2  is
  3     l_cnt       number;
  4  begin
  5          execute immediate
  6      'select /* LOOK FOR ME */ count(*) from dual where ' || p_function
  7      into l_cnt;
  8
  9      return (l_cnt = 1);
 10  end;
 11  /

Function created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create or replace context my_ctx using my_proc;

Context created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create or  replace procedure my_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@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> declare
  2      x number := 100;
  3  begin
  4      for i in 1 .. 100
  5      loop
  6          x := x+1;
  7          my_proc( 'i', i );
  8          my_proc( 'x', x );
  9
 10          if (do_it( 'sys_context(''my_ctx'',''i'') > 0
 11                      and
 12                      sys_context(''my_ctx'',''x'') in (100,104,110)'))
 13          then
 14              dbms_output.put_line( 'i = ' || i || ' x = ' || x || ' are
TRUE' );
 15          end if;
 16      end loop;
 17  end;
 18  /
i = 4 x = 104 are TRUE
i = 10 x = 110 are TRUE

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> declare
  2      x number := 100;
  3  begin
  4      execute immediate 'alter session set cursor_sharing=force';
  5      for i in 1 .. 100
  6      loop
  7          x := x+1;
  8          if (do_it(  i || ' > 0 and ' || x || ' in (100,104,110)'))
  9          then
 10              dbms_output.put_line( 'i = ' || i || ' x = ' || x || ' are
TRUE' );
 11          end if;
 12      end loop;
 13      execute immediate 'alter session set cursor_sharing=exact';
 14  end;
 15  /
i = 4 x = 104 are TRUE
i = 10 x = 110 are TRUE

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select sql_text from v$sql where upper(sql_text) like
'SELECT /* LOOK FOR ME */%';

SQL_TEXT
---------------------------------------------------------------------------------
--------------------------------------------------
select /* LOOK FOR ME */ count(*) from dual where sys_context('my_ctx','i') > 0
                   and    sys_con
text('my_ctx','x') in (100,104,110)

select /* LOOK FOR ME */ count(*) from dual where :"SYS_B_0" > :"SYS_B_1" and
:"SYS_B_2" in (:"SYS_B_3",:"SYS_B_4",:"SYS_B_5")

very relevant question, don't be afraid you are asking too much -- bind
variables is something I don't hesitate to talk about :)

But -- on another note -- if the system "must perform" you might consider
"static CODE" over dynamic code.

That is, instead of storing strings to be parsed/executed dynamically -- store
strings that you run a "code generator against" that creates static procedures
that are compiled once.  You would regenerate the code after updating the
strings.  works well if you update strings upon "install" or "infrequent
configuration of the system".  will work lots better and more performant than
doing it on the fly.
Rating: 3
binding cont .. February 20, 2004
Reviewer: A reader
appologies, my p_utils.eval2 above is the same as your do_it function
Rating: 5
the above techniques April 02, 2004
Reviewer: A reader
the following code

declare
 v1 varchar2(500);
 begin
 select   high_value  into v1 from user_tab_partitions  where table_name =
'PTAB'  and rownum = 1;
 dbms_output.put_line(v1);
end;

returns me
TO_DATE(' 2004-04-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS',
'NLS_CALENDAR=GREGORIAN')

how can I have the above returned as
April 2004

thanks
Followup:
declare
 v1 varchar2(500);
 l_date date;
begin
 select   high_value  into v1 from user_tab_partitions  where table_name =
'PTAB'  and rownum = 1;

 execute immediate 'select ' || v1 || ' from dual' into l_date;
 dbms_output.put_line( l_date, 'Month YYYY' );
end;
Rating: 5
April 02, 2004
Reviewer: A reader
no need to answer the above, I have managed to sort out.

a simple execute immidiate will do.

just using this site gives inspiration !
April 02, 2004 Parag -- Thanks for the question regarding "CONNECT BY CLAUSE", version 8.1.6
You Asked
Hi Tom :

   I am trying to use Connect by clause with Joining of 2 tables say Emp & Dept.
It's giving me some Error.

    It means if u wanna to show All Employee & their Manager (Emp Table) with
Department Name (Dept Table) .. i can't show . Because it's giving some Oracle
Error.

   Exampel.

      Assume that i have standared EMP & DEPT table. Where Deptno is a Primary
Key of Dept Table & Forign Key of Emp Table.

      select empno , ename , mgr , d.dname  from emp , dept d
      where emp.deptno = d.deptno
      connect by mgr = prior empno
      start with mgr is null;

     ERROR at line 1:
    ORA-01437: cannot have join with CONNECT BY

    I am waiting for ur reply.

Thanks
- Parag
and we said...
instead of trying to join a connect by - to TO the connect by:

scott@ORA8I.WORLD> l
  1  select lvl, empno, rpad(' ',lvl-1,' ') || ename ename, mgr, d.dname
  2    from ( select emp.* , level lvl
  3                from emp
  4               start with mgr is null
  5             connect by mgr = prior empno ) emp,
  6         dept d
  7* where emp.deptno = d.deptno
scott@ORA8I.WORLD> /

       LVL      EMPNO ENAME                  MGR DNAME
---------- ---------- --------------- ---------- --------------
         1       7839 KING                       ACCOUNTING
         2       7782  CLARK                7839 ACCOUNTING
         3       7934   MILLER              7782 ACCOUNTING
         2       7566  JONES                7839 RESEARCH
         3       7788   SCOTT               7566 RESEARCH
         4       7876    ADAMS              7788 RESEARCH
         3       7902   FORD                7566 RESEARCH
         4       7369    SMITH              7902 RESEARCH
         2       7698  BLAKE                7839 SALES
         3       7499   ALLEN               7698 SALES
         3       7521   WARD                7698 SALES
         3       7654   MARTIN              7698 SALES
         3       7844   TURNER              7698 SALES
         3       7900   JAMES               7698 SALES

14 rows selected.

Or, select a select and avoid joining all together:

  1  select empno, ename, mgr, (select dname from dept where dept.deptno =
emp.deptno )
  2    from emp
  3   start with mgr is null
  4* connect by mgr = prior empno
scott@ORA8I.WORLD> /

     EMPNO ENAME                  MGR (SELECTDNAMEFR
---------- --------------- ---------- --------------
      7839 KING                       ACCOUNTING
      7566 JONES                 7839 RESEARCH
      7788 SCOTT                 7566 RESEARCH
      7876 ADAMS                 7788 RESEARCH
      7902 FORD                  7566 RESEARCH
      7369 SMITH                 7902 RESEARCH
      7698 BLAKE                 7839 SALES
      7499 ALLEN                 7698 SALES
      7521 WARD                  7698 SALES
      7654 MARTIN                7698 SALES
      7844 TURNER                7698 SALES
      7900 JAMES                 7698 SALES
      7782 CLARK                 7839 ACCOUNTING
      7934 MILLER                7782 ACCOUNTING

14 rows selected.
Review & Followup

Rating: 4
Query without join takes too long July 18, 2002
Reviewer: Charanjiv from India
Hi
the inner query (with the Connect by ) is a huge query returns about 27k rows.
With the join it comes to about 100. In this case the difference in time taken
by hte query is huge. Can u suggest any other way or preformace tips.

My SQL
SELECT at.componentid,
       at.instance,
       c.productclassid,
       c.sku,
       cd.bigheadings
  FROM (select componentid , componentofid , instance
         from tree_view
        where COMPONENTTYPE = 'slot'
        start with componentofid  = 'rkchn14'
      connect by prior componentid = componentofid ) at,
       Component c,
       ComponentDisplay cd
 where at.componentID = c.componentid
   AND c.productclassid = cd.productclassid
   and cd.languageid=1

I have an index on componentofid , componentid of hte base table and componentid
of component

Another thing the original table had a row where  componentofid = componentid
for each top level component thus i had to create view with a where clause of
componentofid <> componentid.

Oralce version 8.1.7
Followup:
not knowing what "tree_view" is (have to assume a view right), cannot really
comment.
Rating: 3
tree struc July 18, 2002
Reviewer: Charanjiv from India
oops missed that - it is a view from the tree table of the same structure.

COMPONENTID      NOT NULL VARCHAR2(10)
COMPONENTOFID    NOT NULL VARCHAR2(10)
INSTANCE         NOT NULL NUMBER(1)
COMPONENTTYPE    VARCHAR2(10)
ADDONFLAG      CHAR(1)
QUANTITY       NUMBER(10,2)
Followup:
don't see anything obvious, short of a two step (insert join into global
temporary table, select hierarchy from global temporary table)
Rating: 4
how to get the sum of qty column ? March 30, 2004
Reviewer: dmv from india
There are two tables Group and Quantity with following data:

Group                              Quantity

Grp id    Par_id    Name        Grpid    Qty
1        Null    G1                5     10
2         1      G2                6     10
3         1      G3                7     15
4         1      G4                8     5
5         4      G5
6         4      G6
7         2      G7
8         6      G8

Write a query to print the following report:

Group Name           Qty
--------------------------------------
        G8        5
    G6           15
    G5           10
G4               25
    G7           15
G2               15
G1               40
Followup:
ops$tkyte@ORA9IR2> select lpad('*',level,'*')||name hname,
  2         (select sum(qty)
  3            from t1, t2
  4           where t1.grp_id = t2.grp_id(+)
  5           start with t1.grp_id = O.grp_id
  6           connect by prior t1.grp_id = t1.par_id) qty
  7    from t1 O
  8   start with par_id is null
  9   connect by prior grp_id = par_id
 10  /

HNAME             QTY
---------- ----------
*g1                40
**g2               15
***g7              15
**g3
**g4               25
***g5              10
***g6              15
****g8              5

8 rows selected.
Rating: 4
will i get the same result in 8i also? April 02, 2004
Reviewer: dmv from india
Dear Tom
      Thanks for your reply. but is it possible to get this result in Oracle 8i?
because, in 8i we can't join tables with "start with connect by" clause.

Kinldy clarify!!!!

Thanks
Followup:
tkyte@ORA8I> l
  1  select lpad('*',level,'*')||name hname,
  2         (select sum((select qty from t2 where t2.grp_id = t1.grp_id))
  3            from t1
  4           start with t1.grp_id = O.grp_id
  5           connect by prior t1.grp_id = t1.par_id) qty
  6    from t1 O
  7   start with par_id is null
  8*  connect by prior grp_id = par_id
tkyte@ORA8I> /

HNAME             QTY
---------- ----------
*G1                40
**G2               15
***G7              15
**G3
**G4               25
***G5              10
***G6              15
****G8              5

8 rows selected.

tkyte@ORA8I> spool off
April 02, 2004 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"
Rating: 4
ORA-01843: not a valid month March 27, 2004
Reviewer: manhcuong from vietnam
Rating: 3
OK April 02, 2004
Reviewer: Maria from TX,USA
Dear Tom,
How to find out the international languages supported by
Oracle?Is there any data dictionary view that can be queried to find out the
languages supported by Oracle?
Please do reply.
Bye!
Followup:
see the globalization guide.
April 02, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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://download-west.oracle.com/docs/cd/B10501_01/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.
Rating: 5
How to drop a datafile ? February 06, 2004
Reviewer: Linda from Maryland, USA
Saved my tailend! This does not seem to be documented anywhere else.
Rating: 4
Difference between offline and offline drop. February 29, 2004
Reviewer: Alvin from Philippines
As per oracle 8i documents

---8<---

OFFLINE
 Specify OFFLINE to take the datafile offline. If the database is open, you must
perform media recovery on the datafile before bringing it back online, because a
checkpoint is not performed on the datafile before it is taken offline.

 DROP takes a datafile offline when the database is in NOARCHIVELOG mode.

---8<---

Say i've a tablespace named 't' it has 2 datafiles t1 and t2. When t1 becomes
corrupted and i need to recover it i would :

1. alter database datafile '/folder/t1' offline
2. replace os file from backup
3. perform recovery
4. bring it online

When do we use offline drop ? As per cut and pasted document above :

1. Does that mean that 'offline drop' is used on databases that are in
noarchivelog mode ??

2. And we use 'offline' only when we are in archivelog mode ?
Followup:
offline drop will let you "cut the file out of the database - taking the
tablespace with it".  It lets you remove the thing that is broken in
noarchivelog mode (or in archivelog mode when you have no backups you can use)
Rating: 4
In OMF corresponding db files are not deleting... March 01, 2004
Reviewer: Ashiq Shamsudeen A from Trichy, Tamil Nadu, India
Hi Tom,

   I've created OMF say test tablespace and assigned it scott as default
tablespace. Then i created a table on it.

 When i issue "drop tablespace test including contents and datafiles" ,its not
deleting the the corresponding db files. What's the reason for not deleting the
db files. See ..

sys@SHAM> create tablespace test;

Tablespace created.

sys@SHAM> sho parameters db_create_file_dest

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
db_create_file_dest                  string      e:\ashiq\ocp9i

sys@SHAM> alter user scott default tablespace test;

User altered.

 Then i connected to scott and created a table to use newly created tablespace.

scott@SHAM> create table emp1 as select * from emp;

Table created.

scott@SHAM> select table_name ,tablespace_name from user_tables;

TABLE_NAME                     TABLESPACE_NAME
------------------------------ ---------------
BONUS                          SYSTEM
DEPT                           SYSTEM
EMP                            SYSTEM
EMP1                           TEST
SALGRADE                       SYSTEM

5 rows selected.

 So, now emp1 uses test tablespace..

  then i got connected to sys and drop the new tablespace using the clause
"including contents and datafiles"

sys@SHAM> drop tablespace test;
drop tablespace test
*
ERROR at line 1:
ORA-01549: tablespace not empty, use INCLUDING CONTENTS option

sys@SHAM> drop tablespace test including contents and datafiles;

Tablespace dropped.

  So to verify whether it deleted the corresponding db files in OS,

sys@SHAM> host

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

E:\oracle\ora92\bin>cd \

E:\>cd ashiq\o*

E:\ashiq\ocp9i>dir
 Volume in drive E has no label.
 Volume Serial Number is 34FE-64C8

 Directory of E:\ashiq\ocp9i

03/01/2004  05:58p      <DIR>          .
03/01/2004  05:58p      <DIR>          ..
03/01/2004  06:04p         104,865,792 O1_MF_TEST_046C3900_.DBF
               1 File(s)    104,865,792 bytes
               2 Dir(s)  16,003,325,952 bytes free

E:\ashiq\ocp9i>exit

  See still the db file exists there. Why it is so?

   Oracle 9.2.0 running on W2k professional.
Followup:
I cannot reproduce (the and datafiles is not needed with OMF actually, it'll do
that by default)

ops$tkyte@ORA920PC> create tablespace test;

Tablespace created.

ops$tkyte@ORA920PC> column file_name new_val f
ops$tkyte@ORA920PC> select file_name from dba_data_files where tablespace_name =
'TEST';

FILE_NAME
------------------------------
/d02/oradata/ora920/o1_mf_test
_046hl0fk_.dbf

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create table t ( x int ) tablespace test;

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> drop tablespace test;
drop tablespace test
*
ERROR at line 1:
ORA-01549: tablespace not empty, use INCLUDING CONTENTS option

ops$tkyte@ORA920PC> host ls -l &f
-rw-rw----    1 ora920   ora920   104865792 Mar  1 08:44
/d02/oradata/ora920/o1_mf_test_046hl0fk_.dbf

ops$tkyte@ORA920PC> drop tablespace test including contents and datafiles;

Tablespace dropped.

ops$tkyte@ORA920PC> host ls -l &f
ls: /d02/oradata/ora920/o1_mf_test_046hl0fk_.dbf: No such file or directory

I had the same response on my 9201 on windows running under vmware as well.
Rating: 5
One little question regarding documentation-links; March 01, 2004
Reviewer: Victor from The Netherlands
Hi tom,

I've got a little question about the links you post here to
Oracle-documentation, e.g.
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96572/recoscenarios.htm#11689
when I click this, or others like it, I always end up at
http://otn.oracle.com/documentation/index.html
Eventually i'm able to find the document you mean by looking at the URL, but
still..
Has this something to do with a re-organisation of the Oracle-site?
Oh yeah, ofcourse I'm registered with Oracle, so that's not the problem.

Not really looking for a "solution" for this thingie, just wanted to let you
know..

Greetings,
Victor Oosterbaan
Followup:
yes, they moved all of the links on me unfortunately -- every single one of
them.
Rating: 4
not deleting in W2k professional and Windows 2003 server... March 02, 2004
Reviewer: Ashiq Shamsudeen A from Trichy, Tamil Nadu, India
Hi tom,

  I tried in W2k professional, windows 2003 server and Sun SPARC 9 (in all
machine db is oracle 920) , but in windows machine its not deleting the
corresponding db files and in sun its doing. First i'll show in W2k machine

SQL> connect / as sysdba
Connected.
SQL> sho parameter db_create_file_dest

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
db_create_file_dest                  string      e:\ashiq\ocp9i

SQL> create tablespace test datafile size 100m, size 100m;

Tablespace created.

   Then i got into OS to check

SQL> host

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

E:\oracle\ora92\bin>cd \

E:\>cd ashiq\ocp9i

E:\ashiq\ocp9i>dir
 Volume in drive E has no label.
 Volume Serial Number is 34FE-64C8

 Directory of E:\ashiq\ocp9i

03/02/2004  12:43p      <DIR>          .
03/02/2004  12:43p      <DIR>          ..
03/02/2004  12:43p         104,865,792 O1_MF_TEST_048F0100_.DBF
03/02/2004  12:43p         104,865,792 O1_MF_TEST_048F0700_.DBF
               2 File(s)    209,731,584 bytes
               2 Dir(s)  15,898,402,816 bytes free

E:\ashiq\ocp9i>exit

  I got connected to scott and created a table ..

scott@SHAM> conn scott/tiger@sham
Connected.

scott@SHAM> create table ashiq (name varchar2(10)) tablespace test;

Table created.

scott@SHAM> insert into ashiq values ('ashiq');

1 row created.

scott@SHAM> commit;

Commit complete.

  then I dropped the tablespace test

SQL> drop tablespace test;
drop tablespace test
*
ERROR at line 1:
ORA-01549: tablespace not empty, use INCLUDING CONTENTS option

SQL> drop tablespace test including contents and datafiles;

Tablespace dropped.

SQL> host

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

E:\oracle\ora92\bin>cd \

E:\>cd ashiq\ocp9i

E:\ashiq\ocp9i>dir
 Volume in drive E has no label.
 Volume Serial Number is 34FE-64C8

 Directory of E:\ashiq\ocp9i

03/02/2004  12:45p      <DIR>          .
03/02/2004  12:45p      <DIR>          ..
03/02/2004  12:45p         104,865,792 O1_MF_TEST_048F0100_.DBF
               1 File(s)    104,865,792 bytes
               2 Dir(s)  16,003,264,512 bytes free

E:\ashiq\ocp9i>exit

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>

   Its deleting only one db file ,not all. Other one i've to remove it manually.

  Any reason for this ?

  I did the same thing in SUN machine , its doing what it has supposed to do.
See..

SQL> connect / as sysdba
Connected.

SQL> sho parameters db_create_file_dest

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
db_create_file_dest                  string      /oracle/oracle/ashiq/ocp9i

SQL> create tablespace test datafile size 100m, size 100m;

Tablespace created.

SQL> host
$ ls -las
total 409892
   2 drwxr-xr-x   2 oracle   dba          512 Mar  2 12:30 .
   2 drwxr-xr-x   3 oracle   dba          512 Mar  2 10:52 ..
   0 -rw-r--r--   1 oracle   dba            0 Mar  2 11:06 a
204944 -rw-r-----   1 oracle   dba      104865792 Mar  2 12:31
o1_mf_test_049k6fmc_.dbf
204944 -rw-r-----   1 oracle   dba      104865792 Mar  2 12:31
o1_mf_test_049k6jm6_.dbf
$ exit

 Then i got into another user and created a table and assigned test tablespace
to it.

SQL> create table ashiq (name varchar2(10)) tablespace test;

Table created.

SQL> insert into ashiq values('ashiq');

1 row created.

SQL> commit;

Commit complete.

SQL> select tablespace_name, table_name from user_tables
  2  where table_name = 'ASHIQ';

TABLESPACE_NAME                TABLE_NAME
------------------------------ ------------------------------
TEST                           ASHIQ

 Then i dropped the tablespace

SQL> drop tablespace test;
drop tablespace test
*
ERROR at line 1:
ORA-01549: tablespace not empty, use INCLUDING CONTENTS option

SQL> drop tablespace test including contents and datafiles;

Tablespace dropped.

SQL> host
$ ls -las
total 4
   2 drwxr-xr-x   2 oracle   dba          512 Mar  2 12:32 .
   2 drwxr-xr-x   3 oracle   dba          512 Mar  2 10:52 ..
   0 -rw-r--r--   1 oracle   dba            0 Mar  2 11:06 a
$ exit

   Its deleting the corresponding the db files.

 SQL> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.1.0 - 64bit 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

SQL>

  What is the problem in Windows ,when its doing fine in SUN ?
Followup:
I love that last sentence :)  but I won't touch it....

I cannot reproduce on my vmware system -- I'll have to refer you to support for
this one:

SQL*Plus: Release 9.2.0.1.0 - Production on Tue Mar 2 08:05:00 2004

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

tkyte@ORA9IR2W>
tkyte@ORA9IR2W>
tkyte@ORA9IR2W> create tablespace test
  2  datafile size 1m, size 1m;

Tablespace created.

tkyte@ORA9IR2W>
tkyte@ORA9IR2W> column file_name new_val f1
tkyte@ORA9IR2W> select file_name
  2    from (
  3  select file_name, row_number() over (order by file_name) rn
  4    from dba_data_files
  5   where tablespace_name = 'TEST'
  6         )
  7   where rn=1;

FILE_NAME
------------------------------
E:\ORACLE\ORADATA\ORA9IR2W\O1_
MF_TEST_0491ON00_.DBF

tkyte@ORA9IR2W>
tkyte@ORA9IR2W> column file_name new_val f2
tkyte@ORA9IR2W> select file_name
  2    from (
  3  select file_name, row_number() over (order by file_name) rn
  4    from dba_data_files
  5   where tablespace_name = 'TEST'
  6         )
  7   where rn=2;

FILE_NAME
------------------------------
E:\ORACLE\ORADATA\ORA9IR2W\O1_
MF_TEST_0491ON01_.DBF

tkyte@ORA9IR2W>
tkyte@ORA9IR2W> host dir &f1 &f2
 Volume in drive E is New Volume
 Volume Serial Number is C8B4-71EF

 Directory of E:\ORACLE\ORADATA\ORA9IR2W

03/02/2004  08:05 AM         1,056,768 O1_MF_TEST_0491ON00_.DBF

 Directory of E:\ORACLE\ORADATA\ORA9IR2W

03/02/2004  08:05 AM         1,056,768 O1_MF_TEST_0491ON01_.DBF
               2 File(s)      2,113,536 bytes
               0 Dir(s)   1,047,625,728 bytes free

tkyte@ORA9IR2W>
tkyte@ORA9IR2W> create table t ( x int ) tablespace test;

Table created.

tkyte@ORA9IR2W>
tkyte@ORA9IR2W> drop tablespace test;
drop tablespace test
*
ERROR at line 1:
ORA-01549: tablespace not empty, use INCLUDING CONTENTS option

tkyte@ORA9IR2W> host dir &f1 &f2
 Volume in drive E is New Volume
 Volume Serial Number is C8B4-71EF

 Directory of E:\ORACLE\ORADATA\ORA9IR2W

03/02/2004  08:05 AM         1,056,768 O1_MF_TEST_0491ON00_.DBF

 Directory of E:\ORACLE\ORADATA\ORA9IR2W

03/02/2004  08:05 AM         1,056,768 O1_MF_TEST_0491ON01_.DBF
               2 File(s)      2,113,536 bytes
               0 Dir(s)   1,047,625,728 bytes free

tkyte@ORA9IR2W> drop tablespace test including contents and datafiles;

Tablespace dropped.

tkyte@ORA9IR2W> host dir &f1 &f2
 Volume in drive E is New Volume
 Volume Serial Number is C8B4-71EF

 Directory of E:\ORACLE\ORADATA\ORA9IR2W

 Directory of E:\ORACLE\ORADATA\ORA9IR2W

File Not Found
Rating: 4
Just small query April 02, 2004
Reviewer: A reader
Hi Tom,

Just small query :

What is the difference between

ALTER DATABASE DATAFILE <datafile name> OFFLINE DROP  and ALTER DATABASE
DATAFILE <datafile name> OFFLINE

regards
Followup:
offline drop shouldn't be used unless you know what you are doing (as the
tablespace containing that file is basically "toast" at that oint)

the other one is "safe".  you can online it again.
April 02, 2004 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)
Rating: 4
log buffer size April 01, 2004
Reviewer: Venkat from India
Dear Tom,

Don Burleson says that the maximum size recommended for log buffer is 500k or
128k * CPU count. Also, he says that the log_buffer has been set too high
(greater than 1 meg), causing performance problems because the writes will be
performed synchronously because of the large size (log sync wait events are
high).

Is the above right? Kindly let us know.

Thanks
Venkat
Followup:
funny, the max default size is larger then that on most ports.

No, if he said that, it is wrong.  Log buffers in the 10's of megabytes --
probably not useful, but into the megabytes -- very useful.

log file sync wait events happen upon commit.  Since lgwr is is flushing
whenever:

a) 1meg of data appears in the log buffer
b) every 3 seconds
c) when it is 1/3 full
d) upon commit

the data is constantly trickling out in the background.  If your log buffer is
too small -- you'll be waiting for lgwr to write the data not only when you
commit, but when as you generate redo entries as well.  You need sufficient
space for you to be ADDING to the redo buffer as lgwr is writing it out.
Rating: 4
Confusing documentation April 02, 2004
Reviewer: Mark J. Bobak from Ann Arbor, MI
Well, it's not the first time I've disagreed w/ something Don
said, but, this time, he may not be entirely to blame.

In the 8.1.7 Reference manaul, with regard to log_buffer,
we find this:
Default value:  Operating system specific. Maximum: 500K or 128K * CPU_COUNT,
whichever is greater

Now, this is saying that the default is the OS specific, and
it's the greater of 500k or 128k*CPU_COUNT.  But, the way
it's phrased, with that "Maximum:" in there, it's a little
confusing.  It's easy to see how someone may read that and
think, oh, Max is 500K or 128k*CPU_COUNT.  But, that's not
the case.

Note that in the 9i doc, it has:
Default value  512 KB or 128 KB * CPU_COUNT, whichever is greater

So, they dropped the "Maximum:", which makes it a bit
clearer and easier to understand.

I think Steve Adams mentions this documentation issue
somewhere on his website, but I did a quick search and I
was unable to find it.

As to a practical max value, it seems to me it's got to be
3MB.  If the buffer flushes at 1/3 full or at 1MB full,
whichever comes first, well, once you're over 3MB, 1MB full
will always come first, and you'll never hit the 1/3 full,
(unless you mess with _log_io_size).

-Mark
Followup:
No, 3meg would not be the practical max.

remember

1/3
1m
commit
3 seconds

are TRIGGERS to lgwr to begin writing.  they do not stop us from generating
more.

lgwr gets triggered to write because it is 1m full -- ok fine, whilst it writes
that 1m we generate 4m more.  lgwr will then write that 4m, ok fine, whilst is
writes that 4m, we generate 8m more and so on

they are triggers -- not "stop" points. we can keep writing to the log buffer
while lgwr is flushing other parts of it.
April 02, 2004 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"
Rating: 2
Seeing SQL for killed sessions April 02, 2004
Reviewer: Faheem Rao from Trenton , NJ ,USA
Tom,

I  am trying to find out the Sql executed by the session which are currently not
active, they have been killed or ended.
V$sql gives you the statistics for the SQL executed , I can join V$seesion and
v$sql putting V$session.sql_address = v$sql.address, which will give me sqls
which are being currently executing. Once they are done(means their status comes
to inactive), sql_address in v$session will be '00' , but in v$sql I still can
see that sql , but I will be missing the session info(who executed that sql).
There is no session info in v$sql.

Can you please help me trying to find out sqls executed by killed or inactive
sessions.

Thanks
Followup:
v$sql is a common cache we all share.

we don't have a list of all sql's executed by a session.  that list could be
quite "huge"
April 02, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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.
Rating: 5
why Oracle still uses LONG? February 20, 2004
Reviewer: A reader
Hi

Oracle have been teling users to migrate long to LOBs since Oracle 8. Ironically
I still see Oracle uses LONG in data dictionary, even in 10g. Why so? Isnt it
very easy to migrate long to lobs?
Rating: 3
open rc1 for l_sql_clob March 25, 2004
Reviewer: Justin from PA
When building a dynamic sql statement in PL/SQL using native dynamic sql, what
would you use for the datatype of the variable storing the statement? I have
seen you use LONG in some examples on this site. I would rather use CLOB, but I
get "PLS-00382 expression is of wrong type" when I try to open the ref cursor
passing it a CLOB.

eg.
OPEN rc1 FOR l_sql;  -- w/ l_sql being a CLOB)
brings up the 00382.

So, I decided to say

OPEN rc1 FOR TO_CHAR(l_sql);

and it worked fine. Would you consider that a fine approach?

Also, is it processing intensive to convert a CLOB to a CHAR for the system? It
seemed to execute just as fast as using LONG.

Thanks much for your help.
Followup:
long and if longer than 32k -- a plsql table and DBMS_SQL.

(but I'd be questioning any QUERY (select) that approaches 32k in the first
place!)
Rating: 4
thanks much April 02, 2004
Reviewer: Justin from PA
Do you have any anxiety issues with what I did using the CLOB?

I won't give the long winded reason why LONG is a problem for us, but in short,
it's strictly a debug issue...
Followup:
officially, it should not work as execute immediate takes a plsql variable as
input and the are officially 32k.

it is working by accident for strings >32k and that accident might "change" in
the future.

the only support for sql statements >32k in plsql would be dbms_sql with the
parse interface that takes a plsql table type.
Rating: 5
Gotcha April 05, 2004
Reviewer: Justin from PA
Indeed, we have no statments close to 32k, and as such will continue on.

Very good help. Thanks!
April 02, 2004 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.
Rating: 4
A little different question February 24, 2004
Reviewer: Sudhanshu from Bangalore
I have a slightly different question.
I have data in table items like below :-

TEM_MADE      ITEM_REQD     QTY
----------    ----------    ----------
Bicycle       bell           1
Bicycle       wheel          2
Bicycle       frame          1
bell          steel          20
wheel         spoke          80
wheel         tyre           2
spoke         steel          10
frame         screws         18
frame         steel          1000
screws        steel          45
steel         Iron           70
steel         Manganese      30

Now I have to know how much grams of steel I require to make a bicycle through a
single query ???

The answer I am expecting to get is :-
(1*20)+(2*80*10)+(1*18*45)+(1*1000)
=3430

I tried solving this through below query but I couldn't,
SELECT     item_made,
           item_reqd,
           DECODE (item_reqd,
                   'steel', (SELECT     EXP (SUM (LN (is2.qty)))
                               FROM items is2
                         START WITH is2.item_reqd = abc.item_reqd
                         CONNECT BY PRIOR is2.item_made = is2.item_reqd),
                   TO_NUMBER (NULL)
                  ) factor2
      FROM items
START WITH item_made = 'Bicycle'
CONNECT BY PRIOR item_reqd = item_made;
Followup:
Here is a start, hurts my head thinking about these types of queries sometimes
:)

ops$tkyte@ORA920PC> select a.*, sum(x) over () sumx
  2    from (
  3  select rpad( '*', level, '*' ) || item_made im, item_reqd, qty ,
  4         sys_connect_by_path( qty, '/' ) scbp,
  5             decode( item_reqd, 'steel',
  6                             (select exp(sum(ln(qty))) from t t2
  7                    start with t2.rowid = t.rowid
  8                  connect by prior t2.item_made = t2.item_reqd ), 0 ) x
  9    from t
 10   start with item_made = 'Bicycle'
 11  connect by prior item_reqd = item_made
 12         ) a
 13  /

IM              ITEM_REQD         QTY SCBP                     X       SUMX
--------------- --------------- ----- --------------- ---------- ----------
*Bicycle        bell                1 /1                       0       3430
**bell          steel              20 /1/20                   20       3430
***steel        Iron               70 /1/20/70                 0       3430
***steel        Manganese          30 /1/20/30                 0       3430
*Bicycle        wheel               2 /2                       0       3430
**wheel         spoke              80 /2/80                    0       3430
***spoke        steel              10 /2/80/10              1600       3430
****steel       Iron               70 /2/80/10/70              0       3430
****steel       Manganese          30 /2/80/10/30              0       3430
**wheel         tyre                2 /2/2                     0       3430
*Bicycle        frame               1 /1                       0       3430
**frame         screws             18 /1/18                    0       3430
***screws       steel              45 /1/18/45               810       3430
****steel       Iron               70 /1/18/45/70              0       3430
****steel       Manganese          30 /1/18/45/30              0       3430
**frame         steel            1000 /1/1000               1000       3430
***steel        Iron               70 /1/1000/70               0       3430
***steel        Manganese          30 /1/1000/30               0       3430

18 rows selected.
Rating: 5
Slight Problem February 26, 2004
Reviewer: Sudhanshu from India
SQL> select * from abc;

ITEM_MADE ITEM_REQD        QTY
--------- ---------        ----------
A         B                1
A         C                2
A         D                3
C         E                3
B         C                2
D         B                3

Now from above table if I try to find out the qty of item C required to make
item A using below query, it gives

 SELECT a.*,
        SUM (x) OVER () sumx
   FROM (SELECT RPAD ('*', LEVEL, '*') || item_made im,
                item_reqd,
                qty,
                SYS_CONNECT_BY_PATH (qty, '/') scbp,
                DECODE (item_reqd,
                        'C', (SELECT EXP(SUM(LN(qty)))
                                FROM abc t2
                          START WITH t2.ROWID = abc.ROWID
                    CONNECT BY PRIOR t2.item_made = t2.item_reqd), 0 ) x
FROM abc
START WITH item_made = 'A'
CONNECT BY PRIOR item_reqd = item_made) a;
-----------------------------------
*A    B 1 /1           38
**B   C 2 /1/2         38
***C  E 3 /1/2/3       38
*A    C 2 /2           38
**C   E 3 /2/3         38
*A    D 3 /3           38
**D   B 3 /3/3         38
***B  C 2 /3/3/2       38
****C E 3 /3/3/2/3     38

The expected answer should have been 22((1*2)+(2)+(3*3*2)).

Now in the output line 2 contributes 18,line 4 contributes 2 & line 8 again 18,
which makes the total 38(18+2+18).
The flaw which I see is at line 2 of the output. When it saw that it has reached
'C', then it backtracks the path to reach 'A' but it takes the longer path at
line 8 i.e. C->B->D->A which makes it 18; whereas it should have taken C->B->A
path which would have contributed 2 thus making the total 22.

Can you suggest a way out from this ?
Followup:
I don't see anything *wrong* with it.  You have three hierarchies in your base
table in the first place.  Three "a's"

In order to make "A" you need:

a->b->c
a->c
a->d->b->c
Rating: 5
More Information February 26, 2004
Reviewer: Sudhanshu Kumar from India
Last time I missed to provide column x values :-
                     x   sum
----  - - ------    --   ----
*A    B 1 /1         0   38
**B   C 2 /1/2      18   38
***C  E 3 /1/2/3     0   38
*A    C 2 /2         2   38
**C   E 3 /2/3       0   38
*A    D 3 /3         0   38
**D   B 3 /3/3       0   38
***B  C 2 /3/3/2    18   38
****C E 3 /3/3/2/3   0   38

a->b->c      This should give 2.In above result it is giving 18. Wrong
a->c         This should give 2.In above result it is giving 2. Correct
a->d->b->c   This should give 18.In above result it is giving 18. Correct

My point is for the hierarchy a->b->c in second row of above output, the control
backtracks using c->b->d->a in the query in the select column list; whereas it
should have backtracked in c->b->a thus giving us product value 2.

Any solutions ??

By the Way, sometime before when I used to post a question I used to receive a
mail saying my question has been answered with a link to the answer. Have you
removed this feature?
Followup:
the easiest thing for us will be to write a small function I think to take the
sys connect by path information and multiply.  something like:

ops$tkyte@ORA920PC> create or replace function multiply( p_str in varchar2,
p_delim in varchar2 )
  2  return number
  3  as
  4          l_str long := substr( p_str, 2 ) || p_delim;
  5          l_res number;
  6          l_n   number;
  7  begin
  8          loop
  9          l_n := instr( l_str, p_delim );
 10          exit when (nvl(l_n,0) = 0);
 11                  l_res := nvl(l_res,1)*substr(l_str,1,l_n-1);
 12          l_str := substr( l_str, l_n+1 );
 13      end loop;
 14      return l_res;
 15  end;
 16  /

Function created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select multiply( '/1/2', '/' ) from dual;

MULTIPLY('/1/2','/')
--------------------
                   2

ops$tkyte@ORA920PC> select multiply( '/3/3/2', '/' ) from dual;

MULTIPLY('/3/3/2','/')
----------------------
                    18

might even be better than re-walking the tree in this case.

(when you ask a question via the "ask a question", you get an email.  these are
"reviews", "followups", you are not asking a question here really ;)
Rating: 5
Tree April 02, 2004
Reviewer: Marcio from Brazil
Suppose you have a tree like this:

id, id_up
1, null
1,2
1,3
2,4
2,5
3,2
2,1

        1
       / \
      2   3
     / \   \
    4   5   2
             \
              1

How can I get result like
1
1/2
1/3
1/2/4
1/2/5
1/3/2
1/3/2/1

in just one statement sql? I mess up with this!
Followup:
until 10g, that'll give you a connect by loop -- 1->3->2->1->3->2->1->.....
April 02, 2004 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;
Rating: 3
unique constraint violation when using Merge January 29, 2004
Reviewer: A reader
I am using merge statment to update/insert from one table to another table. The
data is coming from flat file,
intially i load it into staging tables as such -  using sqlloader. Then i use
merge to upsert with prd table (final table).
It so happened that two records were simillar in staging table and so the merge
statment failed when trying to insert.

SQL> @/u01/MWP_DB_Mart/LoadRoot/util/CPM_Customer_Merge.sql
merge into tb_prd001_customer dst using tb_stg001_cpmcustomer src
*
ERROR at line 1:
ORA-00001: unique constraint (MWPDBA.CN_PRD001_PK) violated

So, the whole load for the day got failed. What should be done to avert this
problem. I dont have any validation
at staging table. and the prd table (final table) has a unique constraint.

merge into tb_prd001_customer dst using tb_stg001_cpmcustomer src on
(dst.customer_num_dsc=src.customer_num_dsc)
when matched then
update set
    dst.customer_nam=src.customer_name_dsc,
    dst.customer_address1_dsc=src.customer_address1_dsc,
    dst.customer_address2_dsc=src.customer_address2_dsc,
    dst.customer_address3_dsc=src.customer_address3_dsc,
    dst.customer_address4_dsc=src.customer_address4_dsc
when not matched then
insert (
    dst.customer_num_dsc,
    dst.customer_nam,
    dst.customer_address1_dsc,
    dst.customer_address2_dsc,
    dst.customer_address3_dsc,
    dst.customer_address4_dsc)
values (
        src.customer_num_dsc,
        src.customer_name_dsc,
        src.customer_address1_dsc,
        src.customer_address2_dsc,
        src.customer_address3_dsc,
        src.customer_address4_dsc
    );
commit;

Please help me to resolve this issue Thanks for your consideration...
Followup:
similar??? seems they were "the same" as far as the customer number was
concerned.  You had duplicate data.

You could filter this in the loading query but -- which record is which -- which
gets loaded -- it would be rather "arbitrary".
Rating: 4
You could... January 29, 2004
Reviewer: Peter from UK
From the above it looks like you have a clear need to validate your dataload
data.

Lots of things you could do - but here is a suggestion. You will need to create
constraints on your staging table based on your business rules, in your case
customer is unique. You will need to create an exceptions table - this is
documented by Oracle. In your case you will need to create a 'reject' table like
your staging table (maybe, add a couple of columns for timestamp and reject
reason.
To use this setup.
1) Disable constraints on the stage table.
2) Load data.
3) Enable constraints with an "exceptions into" clause.
4) Inspect the exceptions table and copy the rows that fail validation from the
stage table to your reject table, if you have the columns defined add timestamp
and reason
5) Delete the distinct rows that have failed validation from the stage table. If
you have more than one rule it is possible that a row fails for multiple reasons
and will appear several times in the exceptions table
6) publish your clean data
7) clear down the exceptions table.
8) investigate why you have rows in the reject table.

Apart from step 8 the whole lot can be automated. We do this with our DWH
dataload - the odd duff record is sidelined until support staff can investigate
/ fix. But the 5 million-odd good records are published. In our case we add
checks to count the rejects - too many and we have a problem that alerts the
duty DW team.
Rating: 4
thanks .... January 30, 2004
Reviewer: A reader
Thanks a lot Peter - you made me clear on how i should approach...

tom ...
 ... but -- which record is which -- which gets loaded -- it would be rather
"arbitrary".

i could not understand that and as well. i did not understand how merge works in
this case. will it not insert the first or second one (of the dups) and then
take the next and update?...

Thanks a lot
Followup:
if you have duplicates -- and you wanted the merge to go forward somehow without
failing -- tell me, which of the two "duplicate" keys should be used.  that is
what i meant.
Rating: 3
Which record January 30, 2004
Reviewer: Peter from UK
Ah..

As soon as I submitted my note I thought I should have added...
If you use the enable constraints exceptions into approach you will find that
find for a duplicate key error *BOTH* rows will be exceptions table. So if you
remove the error rows from STAGE neither row will be there. Of course you will
be able to look at both rows in your reject table and decide which one (if any!)
should be there. It is up to you what to do next.
Rating: 5
Thanks ... January 30, 2004
Reviewer: A reader
Ok, i got it...Thanks for sharing your thoughts ..

Thanks tom ...
Thanks peter...
Rating: 4
One more query February 23, 2004
Reviewer: A reader from Manchester, UK
Hi Tom,

 In my insert clause, I want something like
Insert into table1 select table2.*, sysdate from table2.

I am getting ORA-00926 when trying to do this.
But if I just run
Insert into table1 select table2.*, sysdate from table2 from SQL prompt, it
works fine.

Regards,
Su
Followup:
umm, so where are you running it when it does not work.
Rating: 4
Getting ORA-00926 in Merge statement February 23, 2004
Reviewer: A reader from Manchester, UK
Hello Tom,

 My apologies for incomplete question.

I am running following Merge statement at SQL prompt.

c019074@WDMDE01A> MERGE INTO CPT_UABADJE USING DELTA_UABADJE
  2  ON (CPT_UABADJE.UABADJE_AR_TRANS = DELTA_UABADJE.UABADJE_AR_TRANS)
  3  WHEN MATCHED THEN UPDATE
  4  SET CPT_UABADJE.UABADJE_ACTIVITY_DATE =
DELTA_UABADJE.UABADJE_ACTIVITY_DATE,
  5  CPT_UABADJE.UABADJE_ADJM_CODE = DELTA_UABADJE.UABADJE_ADJM_CODE,
  6  CPT_UABADJE.UABADJE_ADJR_CODE = DELTA_UABADJE.UABADJE_ADJR_CODE,
  7  CPT_UABADJE.UABADJE_ALLOWANCE_CONSUMPTION =
DELTA_UABADJE.UABADJE_ALLOWANCE_CONSUMPTION,
  8  CPT_UABADJE.UABADJE_BALANCE = DELTA_UABADJE.UABADJE_BALANCE,
  9  CPT_UABADJE.UABADJE_BHST_TRAN_NUM = DELTA_UABADJE.UABADJE_BHST_TRAN_NUM,
 10  CPT_UABADJE.UABADJE_BILLED_CHG = DELTA_UABADJE.UABADJE_BILLED_CHG,
 11  CPT_UABADJE.UABADJE_BUDGET_VARIANCE =
DELTA_UABADJE.UABADJE_BUDGET_VARIANCE,
 12  CPT_UABADJE.UABADJE_CHARGE = DELTA_UABADJE.UABADJE_CHARGE,
 13  CPT_UABADJE.UABADJE_CONSUMPTION_ADJ =
DELTA_UABADJE.UABADJE_CONSUMPTION_ADJ,
 14  CPT_UABADJE.UABADJE_DATE = DELTA_UABADJE.UABADJE_DATE,
 15  CPT_UABADJE.UABADJE_END_DATE = DELTA_UABADJE.UABADJE_END_DATE,
 16  CPT_UABADJE.UABADJE_EXPIRED_IND = DELTA_UABADJE.UABADJE_EXPIRED_IND,
 17  CPT_UABADJE.UABADJE_FEED_DOC_CODE = DELTA_UABADJE.UABADJE_FEED_DOC_CODE,
 18  CPT_UABADJE.UABADJE_FEED_SEQ_NUM = DELTA_UABADJE.UABADJE_FEED_SEQ_NUM,
 19  CPT_UABADJE.UABADJE_GL_IND = DELTA_UABADJE.UABADJE_GL_IND,
 20  CPT_UABADJE.UABADJE_INSTAL_IND = DELTA_UABADJE.UABADJE_INSTAL_IND,
 21  CPT_UABADJE.UABADJE_OA_IND = DELTA_UABADJE.UABADJE_OA_IND,
 22  CPT_UABADJE.UABADJE_ORIGIN_AR_TRANS =
DELTA_UABADJE.UABADJE_ORIGIN_AR_TRANS,
 23  CPT_UABADJE.UABADJE_PPD_DUE_DATE = DELTA_UABADJE.UABADJE_PPD_DUE_DATE,
 24  CPT_UABADJE.UABADJE_PRINTED_DATE = DELTA_UABADJE.UABADJE_PRINTED_DATE,
 25  CPT_UABADJE.UABADJE_PRINTED_IND = DELTA_UABADJE.UABADJE_PRINTED_IND,
 26  CPT_UABADJE.UABADJE_REFERENCE = DELTA_UABADJE.UABADJE_REFERENCE,
 27  CPT_UABADJE.UABADJE_START_DATE = DELTA_UABADJE.UABADJE_START_DATE,
 28  CPT_UABADJE.UABADJE_TRANSFEROR_AR_TRANS =
DELTA_UABADJE.UABADJE_TRANSFEROR_AR_TRANS,
 29  CPT_UABADJE.UABADJE_USER_ID = DELTA_UABADJE.UABADJE_USER_ID,
 30  CPT_UABADJE.UABADJE_VAT_REVENUE_AMT =
DELTA_UABADJE.UABADJE_VAT_REVENUE_AMT,
 31  CPT_UABADJE.UABADJE_W_O_BALANCE = DELTA_UABADJE.UABADJE_W_O_BALANCE
 32  WHEN NOT MATCHED THEN INSERT INTO CPT_UABADJE SELECT DELTA_UABADJE.*,
(SELECT RUN_DATE FROM AC_
IMPLEMENTATION_RUN WHERE RUN_STATUS = 'C') FROM DELTA_UABADJE;
WHEN NOT MATCHED THEN INSERT INTO CPT_UABADJE SELECT DELTA_UABADJE.*, (SELECT
RUN_DATE FROM AC_IMPLE
                             *
ERROR at line 32:
ORA-00926: missing VALUES keyword

If I run just 'INSERT INTO CPT_UABADJE SELECT DELTA_UABADJE.*, (SELECT RUN_DATE
FROM AC_
IMPLEMENTATION_RUN WHERE RUN_STATUS = 'C') FROM DELTA_UABADJE;', it works
perfect without any error.

Not able to understand why getting error when using same insert statement as
part of merge.

Thanks.

Cheers,
Su
Followup:
merge does not support insert as select -- it is sort of "not sensible" even.

You use insert values in merge only, merge is like a row by row subroutine.  It
says "take the USING set and row by row see if it exists in the INTO set.  If
so, do the ROW update, if not do the ROW insert"

you use insert values.
Rating: 5
Make sure to fully reference in the VALUES clause, too February 25, 2004
Reviewer: Jim Nasby from Austin, TX
  1  MERGE INTO reference_backup b
  2  USING (SELECT * FROM reference) o ON (o.reference_id = b.reference_id)
  3  WHEN MATCHED THEN UPDATE SET b.REFERENCE_NAME = o.REFERENCE_NAME, b.URL =
o.URL
  4* WHEN NOT MATCHED THEN INSERT VALUES(REFERENCE_ID, REFERENCE_NAME, URL)
ops$decibel@DEV> /
MERGE INTO reference_backup b
*
ERROR at line 1:
ORA-00904: "B"."REFERENCE_ID": invalid identifier

ops$decibel@DEV> ed
Wrote file afiedt.buf

  1  MERGE INTO reference_backup b
  2  USING (SELECT * FROM reference) o ON (o.reference_id = b.reference_id)
  3  WHEN MATCHED THEN UPDATE SET REFERENCE_NAME = o.REFERENCE_NAME, URL = o.URL
  4* WHEN NOT MATCHED THEN INSERT VALUES(o.REFERENCE_ID, o.REFERENCE_NAME,
o.URL)
ops$decibel@DEV> /

3 rows merged.

ops$decibel@DEV>
Rating: 3
Help March 10, 2004
Reviewer: Ketan Popat from INDIA
Hi tom,

I am using

 merge into emp
 using dual on ( empno = 7782 )
 when matched then
 update set SAL = SAL+123
 when not matched then
 insert ( EMPNO,ENAME,deptno)
 values ('123','KETAN',10);

"when matched " part of code works normally
but "But not matched " parts do not even
employee no i.e. in above case 7782 does not exists
in table EMP

regards
Ketan
Followup:
what do you mean the "but not matched parts do not work?"

ops$tkyte@ORA9IR2> create table t ( x int, y int, z int );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t values ( 1, 1, 1 );

1 row created.

ops$tkyte@ORA9IR2> commit;

Commit complete.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> merge into t
  2  using dual on ( x = 55 )
  3  when matched then
  4  update set z = z+1
  5  when not matched then insert (x,y,z) values ( 2, 2, 2 );

1 row merged.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select * from t;

         X          Y          Z
---------- ---------- ----------
         1          1          1
         2          2          2

so there, x=55 did not exist, the one row was inserted..

ops$tkyte@ORA9IR2> rollback;

Rollback complete.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t values (55, 1, 1 );

1 row created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> merge into t
  2  using dual on ( x = 55 )
  3  when matched then
  4  update set z = z+1
  5  when not matched then insert (x,y,z) values ( 2, 2, 2 );

1 row merged.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select * from t;

         X          Y          Z
---------- ---------- ----------
         1          1          1
        55          1          2

there x=55 did exist and got updated

although i just tested in 9.0 instead of 9.2 and it did not update, perhaps that
is it?  (if so, you'll want to file a tar with support for that)
Rating: 3
OK March 25, 2004
Reviewer: A reader
Dear Tom,
Can a single table be *merged to itself*?Do you have any simple example for
that?Please do reply.
Bye!
Followup:
Yes you "can"

Why you "would" escapes me :)  doesn't really seem to make sense (eg: when would
you not have a "match" ?)

but anyway, simple test shows "yes"

ops$tkyte@ORA9IR2> create table t ( x int, y int );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t values ( 1, 1 );

1 row created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> merge into t t1
  2  using t t2
  3  on ( t1.x = t2.x )
  4  when matched then update set t1.y = t2.y
  5  when not matched then insert  values ( t2.x, t2.y );

1 row merged.
Rating: 5
Excellent March 26, 2004
Reviewer: Nafeesah from NYC, NY, USA
You are the best, Tom!
Rating: 5
ORA-00904 after analyze April 02, 2004
Reviewer: Tony from Canada
Tom,
   Thanks a lot for all your help to DBA community. My question is, when I
analyze the schema I start getting ORA-00904 error for some queries,

ORA-00604: error occurred at recursive SQL level 1
ORA-00904: "tab"."column1": invalid identifier

I tried to use both dbms_stats and dbms_utility to analyze the schema and used
different values for estimate_percent(like 20, 40 , dbms_stats.auto_sample_size
etc) but still getting the error.
if i delete the stats the query just works fine.
please help!!!
Followup:
Abstract  : ORA-904 WHEN DBMS_STATS.GATHER_SCHEMA_STATS IS PERFORMED.
Rediscovery Information : ORA-904 was sometimes raised when
dbms_stats.gather_schema_stats was performed against the table whose column name
includes 1 or more multi-byte characters.

any chance that is your case?
Rating: 5
ORA-00904 April 02, 2004
Reviewer: Tony from Canada
star_transformation_enabled init.ora parameter was set to true and setting this
value to false resolved the issue, but I'm still wondering why is that?
Followup:
you'd want to file a tar on that -- I remember something about that, you
gathered stats, it started doing a star transformation and did it "wrong"
leading to the issue.
Rating: 5
ORA-00904: invalid identifier April 05, 2004
Reviewer: Botmund from Finland
Excellent! I have never used MERGE before and Metalink did not have any
explanation, none I found at least. Well now I know the reason.
April 02, 2004 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
Rating: 4
split function March 05, 2004
Reviewer: dxl
Is there not an equivalent function to the SQLSERVER SPLIT function for
character strings somewhere out there for ORACLE?
Someone must've written one by now?
I keep meaning to do it but haven't found the time yet!
Followup:
search this site for str2table
Rating: 4
Pluck only first, second, seventh, and eighth columns only March 15, 2004
Reviewer: A reader
Tom

I have a string which has 10 values delimited by commas. I have to pluck only
first, second, seventh, and eighth  columns.

Actually I have to loop though hundred such rows and insert into table t. If
there are duplicate rows ( based on the values of first and second columns),
then I will be doing the dup_val_on_index error condition and updating the rest
of the columns.

What is the simplest way of doing it, how can I pluck just those columns from
the string. Your generic solution does not work for us, as I have been asked not
to create the bad_log table.

If the string is

'once,43434,third,fourth,fifth,sixth,234,66'
'once,43434,third,fourth,fifth,sixth,234,33'

Further I have to ignore rows with 33 value for the last column and pick up the
other ones.

Please help.
Followup:
well, i certainly hope you don't have to "loop" over anything.  If these are in
a flat file -- use an external table (9i) or sqlldr (8i and before) so you can
query this data.

so, assuming you loaded this into t1, or have an external table t1, all you need
to do is "substr"

ops$tkyte@ORA9IR2> create table t1 ( data varchar2(100) );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t1 values (
'once,43434,third,fourth,fifth,sixth,234,66' );

1 row created.

ops$tkyte@ORA9IR2> insert into t1 values (
'once,43434,third,fourth,fifth,sixth,234,33' );

1 row created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create table t2 ( c1 varchar2(10), c2 varchar2(10), c3
varchar2(10), c4 varchar2(10) );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t2
  2  select substr( data, 1, comma1-1 ) c1,
  3         substr( data, comma1+1, comma2-comma1-1 ) c2,
  4         substr( data, comma6+1, comma7-comma6-1 ) c3,
  5         substr( data, comma7+1, comma8-comma7-1 ) c4
  6    from (
  7  select instr(data,',',1,1) comma1,
  8         instr(data,',',1,2) comma2,
  9         instr(data,',',1,3) comma3,
 10         instr(data,',',1,4) comma4,
 11         instr(data,',',1,5) comma5,
 12         instr(data,',',1,6) comma6,
 13         instr(data,',',1,7) comma7,
 14         length(data)+1 comma8,
 15         data
 16    from t1
 17   where data NOT LIKE '%,33'
 18         )
 19  /

1 row created.

ops$tkyte@ORA9IR2> select * from t2;

C1         C2         C3         C4
---------- ---------- ---------- ----------
once       43434      234        66
Rating: 4
March 16, 2004
Reviewer: A reader
Well, that is the only option left, we are using forms9i, the flat file will on
user desktop
and we cannot use utl_file, external tables as they require the file to be on
the database server,
and we cannot use sqlloader as this functionality needs to be triggered of by
the user from the
application whose access is through a web browser.

You have anything to comment on this. What I'm saying is what you have opined
else where on your
web site
Rating: 1
you missed the point March 16, 2004
Reviewer: A reader
Tom, the data is in the csv file and not in a table, you solution will work when
it is in the table. Can you please demo the case where it is in the flat file
and we have to get it into the table i.e. the first, second, seventh and eight
columns.
Followup:
you missed my main points:

<quote>
well, i certainly hope you don't have to "loop" over anything.  If these are in
a flat file -- use an external table (9i) or sqlldr (8i and before) so you can
query this data.

</quote>

else, use whatever programming language you like to "loop" over the data, parse
it and insert it.  it becomes a simple "programming 101" problem at that point.

Heck, just read the file and insert each row into a table with a trigger, let
the trigger do the substring (just as coded above) and insert into the real
table if you like.
Rating: 4
Interesting scenario March 17, 2004
Reviewer: A reader
'once,43434,third,fourth,fifth,sixth,2344,66'

The 234 above sometimes can also be like 2,344. Now I should not consider that
comma, as the data's intent is that it is 2,344 and not 2 and 344 separately.

How can I disregard the commas within the column values, I have to do that
before I pluck the columns apart.
Followup:
good luck with that!  have fun (someone has "messed up" big time here)

Tell me, given:

1,200,100,300,2,1,3,2,1

is that

1
200,100
300
2
1
3
2
1

or

1
200
100,300
.....

hmmm.... you cannot get there from here as far as I'm concerned unless you want
garbage in...
Rating: 3
tab delimited March 17, 2004
Reviewer: A reader
Tom , if I'm using a tab delimited file, then how should I represent a tab in my
code, will a ' ' represent the tab. I have to capture the tab positions.
Followup:
chr(9) is a tab
Rating: 4
next best option March 17, 2004
Reviewer: A reader
Tom I guess the next best option would be to request for a tab separated file,
and then translate, replace all the commas with ''.

What is your opinion.

Further, I see that when I save a xls file as tab delimited some of the values
are within "    ", what does that signify?
Followup:
if you have a tab delimited file, you are back in business.

you would just use to_number(substr(....),'999,999,999,999')

if you have things in quotes that means they (the strings) contain the
delimiter.

See
http://asktom.oracle.com/~tkyte/SkipCols/index.html
the delimited package might be of interest.
Rating: 4
to_number(substr(....),'999,999,999,999') March 17, 2004
Reviewer: A reader
Why use to_number(substr(....),'999,999,999,999')

why cant I just do the substr and do replace, translate and remove the commas,
and insert into my loading table.

Further..

SQL> select to_number( '42,4324', '999,999,999,999') from dual;
select to_number( '42,4324', '999,999,999,999') from dual
                  *
ERROR at line 1:
ORA-01722: invalid number
Followup:
you just gave the perfect reason for using to_number.

what the HECK is 42,4324

I call it "garbage" if it is supposed to be a number.  I'd really like it to
fail so I can get the correct data.

If you just do a replace -- you would turn that garbage, an obvious data error,
into 424,324 silently.

garbage in, garbage out...
Rating: 2
March 18, 2004
Reviewer: A reader
Tom

when I substr my string the seventh column happens to be mapping to a number
column in my table into which I need to load the data.

When I substr it I get something like "1,992" ( with the quotes), I then do a
replace, translate and load the data into the table.

My flat file is a tab delimited file.

I tried doing a replace, translate on the new line as soon as I get it( i'm
using text_io), but that somehow messes up my data when I do the substr I'm not
aboble to pluck the 1,992.

What else can be the approach.
Followup:
ops$tkyte@ORA9IR2> select to_number( trim( '"' from '"1,999"' ), '999,999,999' )
from dual;

TO_NUMBER(TRIM('"'FROM'"1,999"'),'999,999,999')
-----------------------------------------------
                                           1999
Rating: 4
March 19, 2004
Reviewer: A reader
why trim, why not replace and translate, why cant I just remove the comma and
insert into my number database column.

can you please discuss the basics behind your insistence on your converting it
into a number.
Followup:
you asked me and i answered.

replace and translate would accept garbage and give you garbage but silently
give it to you.

trim removes only leading and trailing "'s (only place you expect them)
to_number accepts only numbers

"11111111,111111111111"

is not a number, you would accept it, i would not.
Rating: 5
check for the occurance of a value in a string March 31, 2004
Reviewer: A reader
Tom

in sql I need to check if a database column value contains 'zz' at any place, or
at the end of the string i.e. the last 2.

what is the best way to do this validation
Followup:
create table t
( ...
  x   varchar2(50) check ( x like '%zz%' ),
  ...
)
Rating: 2
String Parsing - Use Java API (i.e. StringTokenizer) March 31, 2004
Reviewer: A reader
PLSQL does not have many built-in packages for String parsing.

On the other hands, you can use Java APIs for very power string parsing.  For
example, you can write a Java Stored Procedure that using Java's StringTokenizer
to parsing your string.

I haven't seen any existing PLSQL packages that have the same features of
StringTokenizer.

People should use Java APIs.  You can't beat a proven and robust Java classes.
Followup:
well, there is regexp in 10g and i have personally never seen anything done in
java with strings that I cannot do as easily in plsql

so -- show me, prove me wrong.  give us some java that does something really
cool, in a compact fashion.  make sure to provide the specs in english (like you
would in a production piece of code) and we'll see!
Rating: 4
RE: Java API April 01, 2004
Reviewer: Frank from NL
<quote>People should use Java APIs.  You can't beat a proven and
robust Java classes.
</quote>

Sure, we'll go out and hire a Java programmer immediately, 'cause we SHOULD use
Java APIs.
Or as someone put it:
Java is just a language
Java is just a language
...
(repeat this)
;)
Followup:
you know, one of my favorite C functions of all times is 'strtok' (the printf
family of functions is not too far behind)

therefore, we should all be writing code in C

Wait, COBOL makes doing reporting pretty easy.
.......
Rating: 2
Here Is the An Example of Java StringTokenizer Vs. PLSQL April 01, 2004
Reviewer: A reader
Hi Tom,

Here is my example of how Java StringTokenizer class vs. PLSQL.  It will show
that implementing with Java is easier, faster, and more robust than writing your
own PLSQL package.  I have not seen any Oracle built-in PLSQL packages (maybe
except 10g RegEx) have the same features as StringTokenizer.

Propose:
Parse the string into multiple tokens, delimited by space, semicolon, comma,
colon, etc.

Package Specs:
PACKAGE cms_util is
    function get_token (in_str in varchar2)
        return varchar2array;

    function get_tokens (p_str in varchar2)
        return varchar2array as
        language java
        name 'com.han.cms.utilities.StringUtilities.getTokens(java.lang.String)
         return com.han.cms.plsql.Varchar2Array';

    function get_tokens (p_str in varchar2, p_delims in varchar2)
        return varchar2array as
        language java
        name 'com.han.cms.utilities.StringUtilities.getTokens(java.lang.String,
        java.lang.String) return com.han.cms.plsql.Varchar2Array';
end;

PLSLQ Implementation In Body (See how long the code):
PACKAGE BODY cms_util is
    /**
        Disclaimer: Taken this function from some web site.
                    Sorry, I forget the original author
     */
    function get_token (in_str in varchar2)
        return varchar2array is
        str_left                    varchar2 (32767);
        str_end                     pls_integer;
        sp1                         pls_integer;          /* For space      */
        sc1                         pls_integer;          /* For semi-colon */
        st1                         pls_integer;          /* For stop       */
        cm1                         pls_integer;          /* For comma      */
        cn1                         pls_integer;          /* For colon      */
        c_greatest_value   constant pls_integer      := 2147483647;
        v_tokens                    varchar2array    := varchar2array ();
        v_count                     integer          := 1;
    begin
        /* Use this if tabs are to be treated as tabs */
        str_left := in_str;
        /* Use this if tabs are to be treated as spaces */
        str_left := replace (in_str, chr (9), ' ');

        loop
            /* Finding first occurence of respective characters */
            sp1 := instr (str_left, ' ', 1);
            sc1 := instr (str_left, ';', 1);
            cm1 := instr (str_left, ',', 1);
            st1 := instr (str_left, '.', 1);
            cn1 := instr (str_left, ':', 1);

            /* If the character doesn't occur set first_occurence to max value
*/
            if sp1 = 0 then
                sp1 := c_greatest_value;
            end if;

            if sc1 = 0 then
                sc1 := c_greatest_value;
            end if;

            if cm1 = 0 then
                cm1 := c_greatest_value;
            end if;

            if st1 = 0 then
                st1 := c_greatest_value;
            end if;

            if cn1 = 0 then
                cn1 := c_greatest_value;
            end if;

            str_end := least (sp1, sc1, cm1, st1, cn1);

            if str_end = c_greatest_value then
                if str_left is not null then
                    dbms_output.put_line (str_left);
                    v_tokens.extend;
                    v_tokens (v_count) := str_left;
                    v_count := v_count + 1;
                end if;

                exit;
            end if;

            if str_end = sp1 then
                /* We don't want to print out spaces. Spaces appear
                   in dbms_outputif we
                   "set serveroutput on size 1000000 FORMAT WORD_WRAPPED"
                 */
                if (substr (str_left, 1, str_end - 1) != ' ') then
                    dbms_output.put_line (substr (str_left, 1, str_end - 1) );
                end if;

                str_left := substr (str_left, str_end + 1);
                v_tokens.extend;
                v_tokens (v_count) := str_left;
                v_count := v_count + 1;
            else
                /* We don't want to print out spaces. Spaces appear in
                   dbms_outputif we
                   "set serveroutput on size 1000000 FORMAT WORD_WRAPPED" */
                if (substr (str_left, 1, str_end - 1) != ' ') then
                    dbms_output.put_line (substr (str_left, 1, str_end - 1) );
                end if;

                str_left := substr (str_left, str_end);
                dbms_output.put_line (substr (str_left, 1, 1) );
                str_left := substr (str_left, 2);
                v_tokens.extend;
                v_tokens (v_count) := str_left;
                v_count := v_count + 1;
            end if;

            if str_left is null then
                exit;
            end if;
        end loop;

        return v_tokens;
    end;
end;

Java Class Inside The Database:
public class StringUtilities {
public static Varchar2Array getTokens(String values, String delimiters,
                                          boolean returnDelims) {
        StringTokenizer strTokenizer = new StringTokenizer(values, delimiters,
                                                           returnDelims);
        String[] tokens = null;
        int count = strTokenizer.countTokens();

        if (count > 0) {
            tokens = new String[count];

            for (int i = 0; i < count; i++) {
                tokens[i] = strTokenizer.nextToken();
            }
        }

        //return tokens;
        return new Varchar2Array(tokens);
    }
}

Note: Varchar2Array is a wrapper class generated by JPublisher based on the
varchar2array nested table.

CREATE OR REPLACE
type varchar2array is table of varchar2(32676);

Conclusion:
1. PLSQL lacks some basic built-in libraries, e.g. String Tokenizer
2. Use Java J2SE to supplement PLSQL when PLSQL lacks the features (i.e. NOT
code some long PLSQL to do things that are already done by Java; I would even
argue that it is more robust to use Java proven APIs than newly written code).

Please let me know if you need more prove.

Disclaimer: I have not reviewed 10g RegEx.  Will 10g RegEx be available in
previous Oracle versions?  If not, what is your recommendation to existing
customers who want to use RegEx?  I would recommend they use Java Stored
Procedure that call Java RegEx (either the JDK 1.4 RegEx or other RegEx
libraries, like Apache).

Thanks.
Followup:
now, benchmark it in SQL using it like people use substr

Let us know how that goes....
Rating: 2
.. and here a PL/SQL example April 02, 2004
Reviewer: eugene
This PL/SQL code was run with Oracle 8.0, 8i, 9iR2.

BANNER
----------------------------------------------------------------
Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production

SQL> create or replace procedure strToken(line IN varchar2, tokenChar IN
varchar2 default ';')
  2  is
  3    TYPE  tokenTableType is TABLE of varchar2(4000)   -- table for
Stringtoken
  4      index by binary_integer;
  5    --
  6    tokens    tokenTableType;
  7    vCnt      integer := 1;
  8    myLine    varchar2(4000) := null;
  9    vPos      integer := 1;
 10    --
 11  begin
 12    while (vPos <= length(line))
 13    loop
 14      if (length(replace(tokenChar,substr(line, vPos, 1), '')) =
length(tokenChar)) then
 15        myLine := myLine || substr(line, vPos, 1);
 16      elsif (myLine is not NULL) then
 17        tokens(vCnt) := myLine;
 18        myLine := null;
 19        vCnt := vCnt + 1;
 20      end if;
 21      vPos := vPos + 1;
 22    end loop;
 23    --
 24    if (myLine is not NULL) then
 25      tokens(vCnt) := myLine;
 26    end if;
 27    --
 28    vCnt := 1;
 29    for vCnt in 1..tokens.count()
 30    loop
 31      dbms_output.put_line(vCnt||'  '||tokens(vCnt));
 32    end loop;
 33    --
 34  end;
 35  /

Procedure created.

SQL>
SQL> set serveroutput on
SQL>
SQL> set timing on
SQL> exec strToken('ADBC;ERFG.khjf,hjkfhsd POt',';. ');
1  ADBC
2  ERFG
3  khjf,hjkfhsd
4  POt

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.46
SQL> exec strToken('ADBC;ERFG.khjf,hjkfhsd POt',';, ');
1  ADBC
2  ERFG.khjf
3  hjkfhsd
4  POt

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.47
SQL> exec strToken('ADBC;ERFG.khjf,hjkfhsd POt',';,.');
1  ADBC
2  ERFG
3  khjf
4  hjkfhsd POt

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.47
SQL>
SQL>
Rating: 2
RE: Here Is the An Example of Java StringTokenizer Vs. PLSQL April 02, 2004
Reviewer: A reader
<quote>
now, benchmark it in SQL using it like people use substr

Let us know how that goes....
</quote>

I think you are missing these points:

1.  Oracle lacks some built-in PLSQL package (e.g. String Tokenizenier).

2.  If there is NO existing built-in PLSQL package, one should try to use other
existing proven library, like Java, NOT trying to spend hours of coding and
testing when you can use Java inside the database.

yes, PLSQL probably can be coded to solve many problems.  However, do people
want to reinvent the wheel?  If yes, why people use Oralce built-in package?
They should just code everything on their own.

Or, is the wheel only implied as Oracle?  Do people think that they need to
invent when Oracle does not have certain features in its built-in packages?  If
yes, they are NOT living in the real world.  The wheel is bigger than Oracle.
So, learn more than PLSQL please.  Java is NOT difficult.

Tom, isn't Oracle using use Java inside its database?  Some built-in PLSQL
packages just call Java.

Isn't Oracle want people to use Java inside the database?

Isn't Oracle still the ONLY database that can run Java?  I don't think DB2 or
SQL Server even have any plan to run Java.  I remember maybe only Postgres plans
to Java JVM inside its database.  Anyway, Postgres isn't a database to many of
us.

Thanks.
Followup:
1) i've never really missed it -- instr, substr, et.al really do seem to cover
it.

2) i don't spend hours.  it literally takes seconds to instr/substr.

do you see me writing a string tokenizer?  no.  do you see me using the builtins
that exist to get the answer I need in a second (efficiently)?  yes.

so, there you go.

No, I don't want you to call plsql or even less java from SQL - that'll only
slow it down.  you call plsql from sql if and when there is quite simply *no
other way*.

Now, if you had a couple documents AND you needed to parse them THEN java would
be a good idea.

If you have a couple of simple strings AND they are columns in a table AND you
needed to extract data from them THEN substr/instr et.al. are a good idea

So, it is not that java is evil, or to be avoided - just that the heinous
overheads of calling plsql or java (specially the latter) from SQL far outweighs
the keystrokes you may or may not save.
Rating: 3
RE: Here Is the An Example of Java StringTokenizer Vs. PLSQL April 02, 2004
Reviewer: A reader
Tom,

Thank you for your reply.

<quote>
1) i've never really missed it -- instr, substr, et.al really do seem to cover
it.
<quote>

Agree.  Use built-in functions if they cover your problem.

<quote>
2) i don't spend hours.  it literally takes seconds to instr/substr.
</quote>

Yes, I have no doubt that you probably spend seconds.  However, I see many other
PLSQL programmers write many lines of code trying to do things that are already
done by some APIs.

<quote>
No, I don't want you to call plsql or even less java from SQL - that'll only
slow it down.  you call plsql from sql if and when there is quite simply *no
other way*.
</quote>

Sorry, I wasn't thinking about calling PLSQL from SQL.  I was thinking about
other applications calling the PLSQL package that execute some logics (include
select) and do DML statements.  You do recommend the use of PLSQL package in
this scenarios, right?  I see too many Java programmers don't use package,
resulting in many DB connection calls.

<quote>
Now, if you had a couple documents AND you needed to parse them THEN java would
be a good idea.

If you have a couple of simple strings AND they are columns in a table AND you
needed to extract data from them THEN substr/instr et.al. are a good idea

So, it is not that java is evil, or to be avoided - just that the heinous
overheads of calling plsql or java (specially the latter) from SQL far outweighs

the keystrokes you may or may not save.
</quote>

I totally agree with you on this point.

Thank you.
April 02, 2004 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.
Rating: 4
Still a problem in 9.2.0.5 March 26, 2004
Reviewer: Peter Tran from Houston, TX USA
Jasbir Kular,

It's still a problem in 9.2.0.5.

-Peter
Rating: 4
Hi April 02, 2004
Reviewer: A reader from pittsburgh,pa
I have a situation that a table (say FACT_BLEND) has 2 columns that makes a
record unique and 4 other columns to store the measure values.  Currently there
is no primary key defined on that. The fact_blend table has 1 billion records.
Two of the measure values are for current month and other two are for the
previous month.
Say for example current month is march and previous month  is feb 2004.
I bring in the april data into my fact table.  The process that gets the april
data is from a very big table (1.7 billion records)joined with couple of
dimension tables each on an average 4 million records.   The chances of new set
of records are minimum ( I would say 10%).
My job needs to update the existing keys with the april months data in the
measure columns (overwrite feb month data) and insert the records if they are
new.

Now I have to make sure the MERGE helps me here.  Do I need to have primary keys
on my fact_blend table to perform MERGE?
What is a good idea to achive this fast?

Thanks a lot
Followup:
you do not need a primary key for merge and to merge such large sets with
eachother, indexes will be "not useful", what will be useful will be a large
hash area size (8i) or pga_aggregate_target (9i) - but even in 9i, i might set
workarea policy to manual and set a large hash area size -- larger than pga
aggregate target might let me use otherwise.
April 02, 2004 Ameena -- Thanks for the question regarding "Materialized View", version 8.1.7.4.0
You Asked
Hi Tom,

I'm a regular visitor of your site and learns a lot through the different types
of real time issues.

Well, my application has lot of materialized views and we usually refresh them
through jobs running during night.

Some times the refresh fail giving unable to extend rollback segment.

My question is ,is there any way to commit the transaction after certain number
of records.

there are around 5 mviews and each has around 100,8674 of records.

I would really appreciate any suggestions from your side.

Thanks

Ameena
and we said...
You really just need to size your RBS for the size of the jobs you are
performing.

I can 100% assure you that the time you spent looking at this issue, the time
spent correcting the failed jobs already cost your company many many many times
more than the cost of attaching the appropriate amount of storage on your
system.

No, MV's cannot "partially commit", that would not only not fix the problem
(could still run out of space) but would introduce an entirely new and truly bad
problem -- that of a PARTIALLY (eg: inconsistent, incorrect, not "whole") MV.
An MV that contains data that never ever existed in your database at any point
in time.
Review & Followup

Rating: 5
Suggentions for tuning MV query... February 21, 2004
Reviewer: Balaji from Chennai
Tuning this query...
Hi Tom,

I need you suggestion and your great input in tuning this query.

Question on tuning this query...

Count of records per table.
---------------------------
CCRT_CALENDAR_DIM - 7304 rows
PORTFOLIOS_DIM - 17 rows
ACCOUNT_MONTHLY_FACT - 39691552 rows PARTITIONED BY MONTH (has 38 partitions)

This create mv script (attached below) takes 2 hrs 30 minutes and I am doin a
COMPLETE REFRESH since I am using ANALYTICAL FUNCTIONS, I cannot use FAST
REFRESH option.

Can you throw me some light? Guide me what are all the parameters that I should
look into.

Wht type of indexes will do the trick, How to re-write the sql in a much
betterway.

I need/share your inputs/guidance and all your experience in tunning. All I need
an example to start with.
So that I can start tuning.

How to start and proceed from here... Steps that are to be considered while
identifying the bottle neck.

I need your suggestions to get into a good solution.

This is test data 39 million. The actual size of ACCOUNT_MONTHLY_FACT is 520
million rows.

CREATE MATERIALIZED VIEW MV_CREDIT_MEASURES AS
SELECT
B.PORTFOLIO_GROUP_ID,
A.RISK_LEVEL_ID,
A.SUB_PORTFOLIO_ID,
A.SECURITIZATION_IND,
A.BRAND_ID,
(CASE WHEN GROUPING_ID
(B.PORTFOLIO_GROUP_ID,
A.RISK_LEVEL_ID,
A.SUB_PORTFOLIO_ID,
A.SECURITIZATION_IND,
A.BRAND_ID,
TRIM(D.CAL_QTR_STR),
TRIM(C.CAL_MONTH_STR),
A.MONTHS_ON_BOOK) = 0 THEN
TRIM(D.CAL_QTR_STR)
WHEN GROUPING_ID
(B.PORTFOLIO_GROUP_ID,
A.RISK_LEVEL_ID,
A.SUB_PORTFOLIO_ID,
A.SECURITIZATION_IND,
A.BRAND_ID,
TRIM(D.CAL_YEAR_STR),
TRIM(C.CAL_MONTH_STR),
A.MONTHS_ON_BOOK) = 0 THEN
TRIM(D.CAL_YEAR_STR)
ELSE
TRIM(D.CAL_MONTH_STR)
END) VINTAGE_PERIOD,
TRIM(C.CAL_MONTH_STR) REPORT_PERIOD,
A.MONTHS_ON_BOOK,
SUM(INT_AND_FEES) INT_AND_FEES,
SUM(OPENING_BAL) OPENING_BAL,
SUM(CLOSING_BAL) CLOSING_BAL,
SUM(NUM_ACCOUNTS_OPEN) NUM_ACCOUNTS_OPEN,
SUM(NUM_ACCOUNTS_VOLUNTARY_ATTR) NUM_ACCOUNTS_VOLUNTARY_ATTR,
SUM(NUM_ACCOUNTS_INVOLUNTARY_ATTR) NUM_ACCOUNTS_INVOLUNTARY_ATTR,
SUM(PRINCIPAL_CHGOFF) PRINCIPAL_CHGOFF,
SUM(GROSS_CHGOFF) GROSS_CHGOFF,
SUM(NET_CHGOFF) NET_CHGOFF,
SUM(TWO_PLUS_CLOSING_BAL) TWO_PLUS_CLOSING_BAL,
SUM(THREE_PLUS_CLOSING_BAL) THREE_PLUS_CLOSING_BAL,
SUM(FOUR_PLUS_CLOSING_BAL) FOUR_PLUS_CLOSING_BAL
FROM (SELECT
A.RISK_LEVEL_ID,
A.SUB_PORTFOLIO_ID,
A.PORTFOLIO_ID,
A.SECURITIZATION_IND,
A.BRAND_ID,
A.MONTHS_ON_BOOK,
A.VINTAGE_DATE,
A.MONTH_END_DATE,
(CASE WHEN A.DELINQUENCY_STATUS_ID <> 10 THEN
NVL(A.PURCHASE_FIN_CHG, 0) + NVL(A.CASH_ADV_FIN_CHG,0) + NVL(A.ANNUAL_FEE, 0) +
NVL(A.FRONT_END_FEE ,0) + NVL(A.INSURANCE_FEE, 0) + NVL(A.INTERCHANGE_FEE, 0) +
NVL(A.LATE_FEE, 0) + NVL(A.OTHER_FEE, 0) + NVL(A.OVERLIMIT_FEE, 0)
ELSE 0
END) INT_AND_FEES,
LAG(CASE WHEN A.DELINQUENCY_STATUS_ID <> 10 THEN NVL(A.MTD_CLOSING_BAL,0) ELSE 0

END)
OVER (PARTITION BY
A.ACCOUNT_ID
ORDER BY
A.MONTH_END_DATE)
OPENING_BAL,
(CASE WHEN A.DELINQUENCY_STATUS_ID <> 10 THEN
NVL(A.MTD_CLOSING_BAL, 0)
ELSE 0
END) CLOSING_BAL,
(CASE WHEN A.DELINQUENCY_STATUS_ID <> 10 THEN
1
ELSE 0
END) NUM_ACCOUNTS_OPEN,
(CASE WHEN A.ACCOUNT_STATUS_ID = 1 THEN
1
ELSE 0
END) NUM_ACCOUNTS_VOLUNTARY_ATTR,
(CASE WHEN A.ACCOUNT_STATUS_ID = 2 THEN
1
ELSE 0
END) NUM_ACCOUNTS_INVOLUNTARY_ATTR,
(CASE WHEN A.DELINQUENCY_STATUS_ID = 10 THEN
NVL(A.PURCHASE_CHGOFF, 0) + NVL(A.CASH_ADV_CHGOFF, 0)
ELSE 0
END) PRINCIPAL_CHGOFF,
(CASE WHEN A.DELINQUENCY_STATUS_ID = 10 THEN
NVL(A.TOTAL_CHGOFF, 0)
ELSE 0
END) GROSS_CHGOFF,
(CASE WHEN A.DELINQUENCY_STATUS_ID = 10 THEN
NVL(A.TOTAL_CHGOFF, 0) - NVL(A.TOTAL_RECOVERY_AMT, 0) -
(NVL(A.PURCHASE_FIN_CHG_CHGOFF, 0) + NVL(A.CASH_ADV_FIN_CHG_CHGOFF, 0) +
NVL(A.MISC_CHGOFF, 0))
ELSE 0
END) NET_CHGOFF,
(CASE WHEN A.DELINQUENCY_STATUS_ID NOT IN (0, 1, 10) THEN
NVL(A.MTD_CLOSING_BAL, 0)
ELSE 0
END) TWO_PLUS_CLOSING_BAL,
(CASE WHEN A.DELINQUENCY_STATUS_ID NOT IN (0, 1, 2, 10) THEN
NVL(A.MTD_CLOSING_BAL, 0)
ELSE 0
END) THREE_PLUS_CLOSING_BAL,
(CASE WHEN A.DELINQUENCY_STATUS_ID NOT IN (0, 1, 2, 3, 10) THEN
NVL(A.MTD_CLOSING_BAL, 0)
ELSE 0
END) FOUR_PLUS_CLOSING_BAL
FROM ACCOUNT_MONTHLY_FACT A) A,
PORTFOLIOS_DIM B,
CCRT_CALENDAR_DIM C,
CCRT_CALENDAR_DIM D
WHERE A.PORTFOLIO_ID = B.PORTFOLIO_ID
AND A.MONTH_END_DATE = C.CAL_DAY_DT
AND A.VINTAGE_DATE = D.CAL_DAY_DT
GROUP BY GROUPING SETS (
(B.PORTFOLIO_GROUP_ID,
A.RISK_LEVEL_ID,
A.SUB_PORTFOLIO_ID,
A.SECURITIZATION_IND,
A.BRAND_ID,
TRIM(D.CAL_MONTH_STR),
TRIM(C.CAL_MONTH_STR),
A.MONTHS_ON_BOOK),
(B.PORTFOLIO_GROUP_ID,
A.RISK_LEVEL_ID,
A.SUB_PORTFOLIO_ID,
A.SECURITIZATION_IND,
A.BRAND_ID,
TRIM(D.CAL_QTR_STR),
TRIM(C.CAL_MONTH_STR),
A.MONTHS_ON_BOOK),
(B.PORTFOLIO_GROUP_ID,
A.RISK_LEVEL_ID,
A.SUB_PORTFOLIO_ID,
A.SECURITIZATION_IND,
A.BRAND_ID,
TRIM(D.CAL_YEAR_STR),
TRIM(C.CAL_MONTH_STR),
A.MONTHS_ON_BOOK)
);

Thanks in advance.

Cheers.
Balaji
Followup:
I saw this on the other page.

basically -- no suggestions.  Indexing won't help here.  big
pga_aggregate_target (in 9i and up) or hash area size in 8i and before might.

look at the query -- every row in every table must by definition be touched,
joined, aggregated, analyzed.

parallel query
sufficient ram

would be things to look at (besides things like "why would I need to trim
strings, why is the data stored with trailing blanks"
Rating: 5
Thanks for the Inputs tom February 21, 2004
Reviewer: Balaji from Madras
HI Tom,

"I saw this on the other page." - Sorry I posted twice.

The DB is 9i. I came see these HINTS. When to you these hints? (I know that you
always say trust the CBO and never use hints). Still I need to know for my
understanding.

/*+ ORDERED use_hash(AD) USE_NL(SM A2) */
/*+ MERGE(A) */
/*+ EXPAND_GSET_TO_UNION */
/*+ FULL(A) PARALLEL(A, 8) */

I tried using /*+ FULL(A) PARALLEL(A, 8) */.
MV which were running for 18 minutes came down to 3 minutes.

When I Execute/Refresh MV parallely... The MV which ran for 3 minutes runs for 1
hour. Can you tell me the reason? I Don't have the DBA access to get the TRACE
files. Weekend no one works.

Please give me some inputs.

Thanks in advance.
Balaji
Followup:
hints are documented, you can read about them in the performance guide.

with the level detail here "one mv went from 18 to 3, another went from 3 to 60,
why" -- no, no one can say.
Rating: 4
Thanks for the valuable inputs February 26, 2004
Reviewer: Ameena Vasim from USA
Hi tom Thanks for your time and valuable inputs for my question on materialized
view .
Rating: 5
MV CONSIDER FRESH OPTION March 02, 2004
Reviewer: Balaji from Chennai, India
Hi Tom,

I got some doubts in Materialized View Consider Fresh Option.

Its works with FAST REFRESH OPTION and NOT with COMPLETE REFRESH.

This is my test case,

CREATE table test
(time_id number, num number)
PARTITION BY RANGE (time_id)
(
PARTITION month1 VALUES LESS THAN (121999),
PARTITION month2 VALUES LESS THAN (122000),
PARTITION month3 VALUES LESS THAN (122001),
PARTITION month4 VALUES less than (maxvalue)
);

alter table test add primary key(time_id, num);

insert into test values('121998', 1);
insert into test values('121999', 2);
insert into test values('122000', 3);
--insert into test values('121998', 4);
--insert into test values('121999', 5);
commit;

create MATERIALIZED VIEW log on test with primary key INCLUDING NEW VALUES;

CREATE MATERIALIZED VIEW test_mv  PARALLEL
PARTITION BY RANGE (time_id)
(
PARTITION month1 VALUES LESS THAN (121999),
PARTITION month2 VALUES LESS THAN (122000),
PARTITION month3 VALUES LESS THAN (122001),
PARTITION month4 VALUES less than (maxvalue)
)
REFRESH FAST
AS
SELECT time_id, num FROM test;

select PARTITION_NAME, PARTITION_POSITION, NUM_ROWS from dba_tab_partitions
where table_name = 'TEST_MV';

1) Suppose the oldest month is to be removed from the table.
ALTER TABLE test DROP/TRUNCATE PARTITION month1;

--IF YOU INSERT/UPDATE AFTER DROP/TRUNCATE A PARTITION,
    THEN YOU HAVE TO RUN THIS COMMAND BEFORE YOUR INSERT/UPDATE OPERATION "ALTER
MATERIALIZED VIEW test_mv CONSIDER FRESH".

RESULTS:
--------
a) from table after dropping month1 partition.

select * from test;
   TIME_ID        NUM
---------- ----------
    121999          2
    122000          3

select * from test_mv;
   TIME_ID        NUM
---------- ----------
    121999          2
    121998          1
    122000          3

b) from table after inserting new record 129000, 9

ONCE YOU DROP THE PARTITION AND TRY TO INSERT... YOU GET THE FOLLOWING ERROR.
IN THIS CASE, YOU HAVE TO REBUILD THE INDEX TO BRING THE INDEX STATE USABLE.

  1*  insert into test values(129000, 9)
10:38:50 SQL> /
 insert into test values(129000, 9)
*
ERROR at line 1:
ORA-01502: index 'SAKSOFT.SYS_C006525' or partition of such index is in unusable
state

Elapsed: 00:00:00.20
10:38:50 SQL> commit;

Commit complete.

Elapsed: 00:00:00.00
10:38:54 SQL> alter index SYS_C006525 rebuild;

Index altered.

Elapsed: 00:00:00.60
10:39:11 SQL> insert into test values(129000, 9);

1 row created.

Elapsed: 00:00:00.20
10:39:18 SQL> commit;

select * from test;
   TIME_ID        NUM
---------- ----------
    121999          2
    122000          3
    129000          9

c) from mview
  select * from test_mv
   TIME_ID        NUM
---------- ----------
    121999          2
    121998          1
    122000          3

2) Use CONSIDER FRESH to declare that the materialized view has been refreshed.
ALTER MATERIALIZED VIEW test_mv CONSIDER FRESH;

3) Execute the mview refresh
EXECUTE DBMS_MVIEW.REFRESH('TEST_MV', 'F',   '', TRUE, FALSE,0,0,0,FALSE);

select * from test
   TIME_ID        NUM
---------- ----------
    121999          2
    122000          3
    129000          9

select * from test_mv
   TIME_ID        NUM
---------- ----------
    129000          9
    122000          3
    121999          2
    121998          1

*********************************************************************************
*********
Test REFRESH with UPDATING some records.

13:37:56 saksoft:ccdw>   select * from test;

   TIME_ID        NUM
---------- ----------
    121999          2
    122000          3
    129000          9

3 rows selected.

Elapsed: 00:00:00.00
13:42:26 saksoft:ccdw> insert into test values('121998', 4);

1 row created.

Elapsed: 00:00:00.00
13:44:28 saksoft:ccdw> commit;

Commit complete.

Elapsed: 00:00:00.00
13:44:30 saksoft:ccdw> select * from test;

   TIME_ID        NUM
---------- ----------
    121998          4
    121999          2
    122000          3
    129000          9

4 rows selected.

Elapsed: 00:00:00.00
13:44:34 saksoft:ccdw> EXECUTE DBMS_MVIEW.REFRESH('TEST_MV', 'F',   '', TRUE,
FALSE,0,0,0,FALSE);

PL/SQL procedure successfully completed.

Elapsed: 00:00:03.00
13:44:54 saksoft:ccdw> select * from test_mv;

   TIME_ID        NUM
---------- ----------
    129000          9
    121998          1
    121999          2
    122000          3
    121998          4

5 rows selected.

Elapsed: 00:00:03.01
13:45:04 saksoft:ccdw> update test set time_id = 111999 where num = 4;

1 row updated.

Elapsed: 00:00:00.00
13:46:07 saksoft:ccdw> commit;

Commit complete.

Elapsed: 00:00:00.00
13:46:10 saksoft:ccdw> select * from test;

   TIME_ID        NUM
---------- ----------
    111999          4
    121999          2
    122000          3
    129000          9

4 rows selected.

STATS BEFORE REFRESH OF THE MVIEW.

  1* select * from test_mv
13:47:19 saksoft:ccdw> /

   TIME_ID        NUM
---------- ----------
    121998          1
    121999          2
    122000          3
    129000          9
    121998          4

5 rows selected.

13:47:20 saksoft:ccdw>  EXECUTE DBMS_MVIEW.REFRESH('TEST_MV', 'F',   '', TRUE,
FALSE,0,0,0,FALSE);

PL/SQL procedure successfully completed.

Elapsed: 00:00:05.02
13:48:35 saksoft:ccdw> l
  1* select * from test_mv
13:48:37 saksoft:ccdw> /

   TIME_ID        NUM
---------- ----------
    121998          1
    111999          4
    121999          2
    122000          3
    129000          9

Thanks in Advance.
Followup:
when I'm taking questions, this looks like a good one (i guess - i really didn't
read all of the way through).  it really isn't a followup on the original thread
here at all and is quite "large"
Rating: 5
MV CONSIDER FRESH March 03, 2004
Reviewer: Jegan from Chennai, India
Hi Tom,
Thanks for the reply. Sorry Tom I don't know how to start a new thread. Since I
have post a question on MV in this thread I though I can use the same thread.

In my earlier post I asked abt the performance issue.

Now I am facing a different issue.

Here we have a FACT table with 550 million rows partitioned by YEAR_MONTH. We
have 36 partitions for 36 months. If I used a MV to generate the summary tables,
I need the whole data from DAY one till DAY. Say, my FACT table will hold just
36 months of data (JAN 2000 till DEC 2002). Every month end the old data is
moved into the HISTORY table from the FACT table (MONTHS_BETWEEN CURRENTMONTH
YEAR is greater than 36 months).

If I have a FAST REFRESH I am able to hold the DATA in my MVs that doesn't exist
in the FACT table (i.e.) The data that has been archived from the FACT table. I
have pasted an example in the previous post which work fine with FAST REFRESH.
Since I am using Analytical functions I am not able to use FAST REFRESH option.

All I need is to retain the data in the summary table even if they are not
present in the FACT table.

FACT
----
2000FEB
2000MAR
...
2003JAN
MV
--
2000JAN
2000FEB
2000MAR
...
2003JAN

Hope I have explained clearly.

Jegan
Followup:
when I take new questions -- it'll be on the home page.

MV's can only reflect what is in the base tables.  they cannot have "more" data
then the base table.  they do not work that way.
Rating: 5
MV CONSIDER FRESH March 03, 2004
Reviewer: Jegan from Chennai, India
Hi Tom,
Sorry again. In my previuos post is missed an important stuff.

"If I have a FAST REFRESH I am able to hold the DATA in my MVs that doesn't
exist
in the FACT table (i.e.) The data that has been archived from the FACT table. I
have pasted an example in the previous post which work fine with FAST REFRESH.
Since I am using Analytical functions I am not able to use FAST REFRESH option."

In the above paragraph, I missed to say that CONSIDER FRESH OPTION works with
FAST REFRESH and NOT with COMPLETE FRESH. Please Throw some ideas.

Thanks
Rating: 5
MV CONSIDER FRESH March 04, 2004
Reviewer: Jegan from Chennai, India
Hi Tom,
Sorry to trouble you again
[Quote]when I take new questions -- it'll be on the home page.[/Quote]
Thanks for the Info.

[Quote]MV's can only reflect what is in the base tables.  they cannot have
"more" data
then the base table.  they do not work that way. [/Quote]

CONSIDER FRESH OPTION works WITH Materialized View FAST REFRESH. All I need to
know how Materialized View FAST REFRESH option can hold data that doesn't exist
in the master table? (I think it should fetch from the Materialized View Logs).
Now I am in a dengerous position when I will loose the DATA of my previous
months if I do a COMPLETE REFRESH. I need some ideas/input/expert advice from
you (My small brain doesn't work).

Need your input on this.
My example in my previous post shows FAST REFRESH WORKS WITH CONSIDER FRESH
OPTIOn.

Thanks a million

Jegan
Followup:
MV's are not intended or designed or support what you are trying to do.  sorry
-- they just don't "do that"

You'll have to "do it yourself", the consider fresh is so you can manually drop
partitions from "master" and "mv" and tell the database "hey, consider the mv
fresh, i did the work to ensure they are in sync"

It is not in support of what you are after.
Rating: 5
MV CONSIDER FRESH March 04, 2004
Reviewer: Jegan from Chennai, India
Thank you very much Tom.

Even I came to this conclusion long time back. I told my guys to convert this
into a table (Anyway I goin to convert the MVs to tables). But, I just wanna get
a confirm word from my Guru.

Thanks for all your help and inputs.

Is that possible to move a partion before refresh and add the same after the
refresh? (Looks foolish. Will take lots of maintenance activity.). I will try
this and revert with the testcase.

Thanks You.

Reragds,
Jegan
Followup:
don't try to fake out the MV -- it doesn't work that way.

You would be able to

a) have your partitioned detail table
b) have a partitioned MV on that detail table

when you goto roll the window (purge the old data) you would:

a) alter table detail drop partition
b) alter table MV EXCHANGE partition, to convert the full partition into a full
table and convert an empty table into an empty partition
c) alter table MV drop partition and tell us to "consider it fresh"
d) add that full table as a partition to another history table
Rating: 5
MV CONSIDER FRESH OPTION March 04, 2004
Reviewer: Jegan from Chennai, India
Hi Tom,
Thank you very much for your support.

My Detail table and MVs are partitioned.

I will do a test as you said and revert with the results tomorrow.

Thank you very much for your support and help.

Regards,
Jegan
Rating: 5
Alternative To materialized view April 02, 2004
Reviewer: Ameena Vasim from USA
Hi tom ,

We have a scenario where we use materialized view to make the reports faster.
This is a complex materialized view and could not be fast refreshed, is there
any other way to replace the materialized view and still maintain the
performance. This particular report has lots of calculated formulaes , I tried
making a regular view but still it's not fast.
Followup:
well, a materialized view runs the query once and lets you query the answer over
and over.

a 'real' view runs the query each and every time you ask the question.

There are way to 'replace' the MV -- indexing strategies, physical design, using
analytic functions when appropriate (eg: knowing everything you can about what
you have access to)
April 02, 2004 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)

********************************************************************************
Rating: 4
Re: Catastrophic Performance Degredation February 11, 2004
Reviewer: T Truong from Long Beach, CA
Mr. Kyte,
We are having the same performance problem as with reviewer Doh!

The following query (provided in your first post of this thread) works perfectly
prior to our database upgrade from 8.1.7 to 9.2.0.

select *
  from ( select a.*, rownum rnum
           from ( YOUR_QUERY_GOES_HERE -- including the order by ) a
          where rownum <= MAX_ROWS )
 where rnum >= MIN_ROWS
/

After the upgrade to 9.2.0, we have the performance problem with the above
query.

Please continue this thread to determine the cause.

Best Regards,
Followup:
hows about your example? your query, your 8i tkprf and your 9i one as well
Rating: 4
Re: Catastrophic Performance Degredation February 11, 2004
Reviewer: T Truong from Long Beach, CA
Mr. Kyte,
Thank you for your prompt response.

As a developer, don't have access to tkprof utilities to check out the query
statistics and we're not getting much time from our DBAs so far to run tkprof;
though we will be getting some of their time soon (hopefully next week).

So far, we know that if we set the initial parameter OPTIMIZER_FEATURES_ENABLE
to 8.1.7, then the query runs just as fast as it was prior to the database
upgrade.

We don't have any other 8i sandbox to regenerate the explan plan for the query,
but here's the query and its explain plan in our only 9i 9.2.0 sandbox

select
       x.ccbnumber
      ,x.chgtype
      ,x.pkgid
      ,x.project_status
      ,x.title
      ,x.projleadname
 from (
    select a.*, rownum rnum
     from (
           select
                  pkg_info.chgty_code || pkg_info.pkg_seq_id ccbnumber
                 ,pkg_info.chgty_code chgtype
                 ,pkg_info.pkg_seq_id pkgid
                 ,(
                   select decode(pkg_info.sty_code,'INPROCESS','In-Process'
                                                  ,'CREATED','Created'
                                                  ,decode(
                                                          min(

decode(pvs.sty_code,'COMPLETE',10

,'CANCEL',20

,'APPROVE',30

,'APPROVEDEF',30

,'INPROCESS',40

,'DISAPPROVE',50

,'DISAPPRXA',50

,'VOID',60

,'CREATED',70

,null
                                                                    )
                                                             ),10,'Complete'
                                                              ,20,'Cancelled'
                                                              ,30,'Approved'
                                                              ,40,'In-Process'
                                                              ,50,'Disapproved'
                                                              ,50,'Disapproved
XA'
                                                              ,60,'Void'
                                                              ,70,'Created'
                                                         )
                                ) project_status
                     from cms_pkg_ver_statuses pvs
                         ,cms_pkg_vers pv
                    where pv.pkg_seq_id = pkg_info.pkg_seq_id
                      and pv.pkg_seq_id = pvs.pkgver_pkg_seq_id
                      and pv.seq_num = pvs.pkgver_seq_num
                      and pvs.sty_scty_dp_code = 'PKG'
                      and pvs.sty_scty_code = 'STATE'
                      and pvs.create_date =
                             (select max(create_date)
                                from cms_pkg_ver_statuses
                               where pkgver_pkg_seq_id = pvs.pkgver_pkg_seq_id
                                 and pkgver_seq_num = pvs.pkgver_seq_num
                                 and sty_scty_dp_code = 'PKG'
                                 and sty_scty_code = 'STATE'
                             )
                      and pvs.create_date =
                             (select max(create_date)
                                from cms_pkg_ver_statuses a
                               where a.pkgver_pkg_seq_id = pvs.pkgver_pkg_seq_id
                                 and a.pkgver_seq_num = pvs.pkgver_seq_num
                                 and a.sty_scty_dp_code = 'PKG'
                                 and a.sty_scty_code = 'STATE'
                                 and a.create_date =
                                        (select max(create_date)
                                           from cms_pkg_ver_statuses
                                          where pkgver_pkg_seq_id =
a.pkgver_pkg_seq_id
                                            and pkgver_seq_num =
a.pkgver_seq_num
                                            and sty_scty_dp_code = 'PKG'
                                            and sty_scty_code = 'STATE'
                                        )
                             )
                  ) project_status
                 ,pkg_info.title title
                 ,emp.user_name projleadname
             from pit_pkg_info pkg_info
                 ,emp_person emp
            where pkg_info.projmgr_emp_employee_num = emp.emp_no(+)
              and pkg_info.title like '%AIR%'
            order by pkgid
          ) a
     where rownum <= 100
      ) x
where x.rnum >= 51
;

OPERATION                   OPTIONS         OBJECT_NAME         COST POSITION
--------------------------- --------------- ----------------- ------ --------
SELECT STATEMENT                                                   4        4
  VIEW                                                             4        1
    COUNT                   STOPKEY                                         1
      VIEW                                                         4        1
        NESTED LOOPS        OUTER                                  4        1
          NESTED LOOPS      OUTER                                  3        1
            TABLE ACCESS    BY INDEX ROWID  PIT_PKG_INFO           3        1
              INDEX         RANGE SCAN      PIT_PKG_PKGVER_I       2        1
            INDEX           UNIQUE SCAN     STY_PK                          2
          TABLE ACCESS      BY INDEX ROWID  EMP_PERSON             1        2
            INDEX           UNIQUE SCAN     SYS_C001839                     1

11 rows selected.

Follows are the 9i initial parameters

SQL> show parameters
O7_DICTIONARY_ACCESSIBILITY          boolean     FALSE
_trace_files_public                  boolean     TRUE
active_instance_count                integer
aq_tm_processes                      integer     0
archive_lag_target                   integer     0
audit_file_dest                      string      ?/rdbms/audit
audit_sys_operations                 boolean     FALSE
audit_trail                          string      TRUE
background_core_dump                 string      partial
background_dump_dest                 string      /u01/app/oracle/admin/U50DAMC/
                                                 bdump
backup_tape_io_slaves                boolean     FALSE
bitmap_merge_area_size               integer     1048576
blank_trimming                       boolean     FALSE
buffer_pool_keep                     string
buffer_pool_recycle                  string
circuits                             integer     0
cluster_database                     boolean     FALSE
cluster_database_instances           integer     1
cluster_interconnects                string
commit_point_strength                integer     1
compatible                           string      9.2.0.0
control_file_record_keep_time        integer     3
control_files                        string      /np70/oradata/U50DAMC/cr1/cont
                                                 rol01.ctl, /np70/oradata/U50DA
                                                 MC/cr2/control02.ctl, /np70/or
                                                 adata/U50DAMC/cr3/control03.ct
                                                 l
core_dump_dest                       string      /u01/app/oracle/admin/U50DAMC/
                                                 cdump
cpu_count                            integer     4
create_bitmap_area_size              integer     8388608
cursor_sharing                       string      EXACT
cursor_space_for_time                boolean     FALSE
db_16k_cache_size                    big integer 0
db_2k_cache_size                     big integer 0
db_32k_cache_size                    big integer 0
db_4k_cache_size                     big integer 0
db_8k_cache_size                     big integer 0
db_block_buffers                     integer     6000
db_block_checking                    boolean     FALSE
db_block_checksum                    boolean     TRUE
db_block_size                        integer     8192
db_cache_advice                      string      OFF
db_cache_size                        big integer 0
db_create_file_dest                  string
db_create_online_log_dest_1          string
db_create_online_log_dest_2          string
db_create_online_log_dest_3          string
db_create_online_log_dest_4          string
db_create_online_log_dest_5          string
db_domain                            string      lgb.ams.boeing.com
db_file_multiblock_read_count        integer     8
db_file_name_convert                 string
db_files                             integer     1024
db_keep_cache_size                   big integer 0
db_name                              string      U50DAMC
db_recycle_cache_size                big integer 0
db_writer_processes                  integer     4
dblink_encrypt_login                 boolean     FALSE
dbwr_io_slaves                       integer     0
dg_broker_config_file1               string      ?/dbs/dr1@.dat
dg_broker_config_file2               string      ?/dbs/dr2@.dat
dg_broker_start                      boolean     FALSE
disk_asynch_io                       boolean     TRUE
dispatchers                          string
distributed_lock_timeout             integer     60
dml_locks                            integer     800
drs_start                            boolean     FALSE
enqueue_resources                    integer     2389
event                                string
fal_client                           string
fal_server                           string
fast_start_io_target                 integer     0
fast_start_mttr_target               integer     0
fast_start_parallel_rollback         string      LOW
file_mapping                         boolean     FALSE
filesystemio_options                 string      asynch
fixed_date                           string
gc_files_to_locks                    string
global_context_pool_size             string
global_names                         boolean     FALSE
hash_area_size                       integer     10000000
hash_join_enabled                    boolean     TRUE
hi_shared_memory_address             integer     0
hpux_sched_noage                     integer     0
hs_autoregister                      boolean     TRUE
ifile                                file
instance_groups                      string
instance_name                        string      U50DAMC
instance_number                      integer     0
java_max_sessionspace_size           integer     0
java_pool_size                       big integer 50331648
java_soft_sessionspace_limit         integer     0
job_queue_processes                  integer     4
large_pool_size                      big integer 16777216
license_max_sessions                 integer     0
license_max_users                    integer     0
license_sessions_warning             integer     0
local_listener                       string
lock_name_space                      string
lock_sga                             boolean     FALSE
log_archive_dest                     string
log_archive_dest_1                   string      location=/np70/oradata/U50DAMC
                                                 /arch MANDATORY REOPEN=60
log_archive_dest_10                  string
log_archive_dest_2                   string      location=/u01/app/oracle/admin
                                                 /altarch/U50DAMC OPTIONAL
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
log_archive_dest_state_1             string      enable
log_archive_dest_state_10            string      enable
log_archive_dest_state_2             string      defer
log_archive_dest_state_3             string      defer
log_archive_dest_state_4             string      defer
log_archive_dest_state_5             string      defer
log_archive_dest_state_6             string      enable
log_archive_dest_state_7             string      enable
log_archive_dest_state_8             string      enable
log_archive_dest_state_9             string      enable
log_archive_duplex_dest              string
log_archive_format                   string      U50DAMC_%T_%S.ARC
log_archive_max_processes            integer     2
log_archive_min_succeed_dest         integer     1
log_archive_start                    boolean     TRUE
log_archive_trace                    integer     0
log_buffer                           integer     1048576
log_checkpoint_interval              integer     10000
log_checkpoint_timeout               integer     1800
log_checkpoints_to_alert             boolean     FALSE
log_file_name_convert                string
log_parallelism                      integer     1
logmnr_max_persistent_sessions       integer     1
max_commit_propagation_delay         integer     700
max_dispatchers                      integer     5
max_dump_file_size                   string      10240K
max_enabled_roles                    integer     148
max_rollback_segments                integer     40
max_shared_servers                   integer     20
mts_circuits                         integer     0
mts_dispatchers                      string
mts_listener_address                 string
mts_max_dispatchers                  integer     5
mts_max_servers                      integer     20
mts_multiple_listeners               boolean     FALSE
mts_servers                          integer     0
mts_service                          string      U50DAMC
mts_sessions                         integer     0
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
object_cache_max_size_percent        integer     10
object_cache_optimal_size            integer     102400
olap_page_pool_size                  integer     33554432
open_cursors                         integer     500
open_links                           integer     100
open_links_per_instance              integer     4
optimizer_dynamic_sampling           integer     0
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
oracle_trace_collection_name         string
oracle_trace_collection_path         string      ?/otrace/admin/cdf
oracle_trace_collection_size         integer     5242880
oracle_trace_enable                  boolean     FALSE
oracle_trace_facility_name           string      oracled
oracle_trace_facility_path           string      ?/otrace/admin/fdf
os_authent_prefix                    string      ops_
os_roles                             boolean     FALSE
parallel_adaptive_multi_user         boolean     FALSE
parallel_automatic_tuning            boolean     FALSE
parallel_execution_message_size      integer     2152
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
pga_aggregate_target                 big integer 25165824
plsql_compiler_flags                 string      INTERPRETED
plsql_native_c_compiler              string
plsql_native_library_dir             string
plsql_native_library_subdir_count    integer     0
plsql_native_linker                  string
plsql_native_make_file_name          string
plsql_native_make_utility            string
plsql_v2_compatibility               boolean     FALSE
pre_page_sga                         boolean     FALSE
processes                            integer     600
query_rewrite_enabled                string      false
query_rewrite_integrity              string      enforced
rdbms_server_dn                      string
read_only_open_delayed               boolean     FALSE
recovery_parallelism                 integer     0
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
replication_dependency_tracking      boolean     TRUE
resource_limit                       boolean     FALSE
resource_manager_plan                string
rollback_segments                    string      r01, r02, r03, r04, r05, r06,
                                                 r07, r08
row_locking                          string      always
serial_reuse                         string      DISABLE
serializable                         boolean     FALSE
service_names                        string      U50DAMC.lgb.ams.boeing.com
session_cached_cursors               integer     0
session_max_open_files               integer     10
sessions                             integer     665
sga_max_size                         big integer 386756664
shadow_core_dump                     string      partial
shared_memory_address                integer     0
shared_pool_reserved_size            big integer 10066329
shared_pool_size                     big integer 201326592
shared_server_sessions               integer     0
shared_servers                       integer     0
sort_area_retained_size              integer     5000000
sort_area_size                       integer     5000000
spfile                               string
sql92_security                       boolean     FALSE
sql_trace                            boolean     FALSE
sql_version                          string      NATIVE
standby_archive_dest                 string      ?/dbs/arch
standby_file_management              string      MANUAL
star_transformation_enabled          string      FALSE
statistics_level                     string      TYPICAL
tape_asynch_io                       boolean     TRUE
thread                               integer     0
timed_os_statistics                  integer     0
timed_statistics                     boolean     TRUE
trace_enabled                        boolean     TRUE
tracefile_identifier                 string
transaction_auditing                 boolean     TRUE
transactions                         integer     200
transactions_per_rollback_segment    integer     5
undo_management                      string      MANUAL
undo_retention                       integer     900
undo_suppress_errors                 boolean     FALSE
undo_tablespace                      string
use_indirect_data_buffers            boolean     FALSE
user_dump_dest                       string      /u01/app/oracle/admin/U50DAMC/
                                                 udump
utl_file_dir                         string
workarea_size_policy                 string      AUTO

Hope you can spot something out of these

Best Regards,
Thomas
Followup:
I dont like your DBA's then.   Really, they are preventing everyone from doing
*their job*.  arg.....

anyway, that plan "looks dandy" -- it looks like it would get first rows first.

We really need to "compare" plans.

Can you at least get an autotrace traceonly explain out of 8i (or at least an
explain plan)

can you tell me "how fast it was in 8i", "how slow it is in 9i" and are the
machines you are testing on even remotely similar.
Rating: 5
Very handy March 08, 2004
Reviewer: Sajid Anwar from London
Hi Tom,
Just simple one about your SPECIAL QUERY for paging. I am using your method for
paging.

select *
  from ( select a.*, rownum rnum
           from ( select * from t ) a
          where rownum <= 5
       ) b
 where rnum >= 2;

This gives me everything plus one extra column rnum that I dont want. How do I
get rid of it in the same query?

Many thanks in advance.

Regards,
Sajid
Followup:
besides just selecting the columns you want in the outer wrapper?  nothing

select a, b, c, d
  from ......

instead of select *
Rating: 3
Regarding post from "T Truong from Long Beach, CA" March 08, 2004
Reviewer: Matt from Australia
Tom,

There are various ways of getting access to trace files on the server, some of
which have been documented on this site.

However, once the developer has the raw trace they need to access TKPROF. As far
as I am aware this is not part of the default Oracle client distribution.
Installing the DB on each desktop would be nice but an administrators nightmare.

As there any licencing restrictions that might prevent copying the required
libraries and executables for tkprof and placing these on a desktop for
developer use? I tested this (though not exhaustively) and it appears to work.

Do you see any problems with this approach?

Cheers,
Followup:
why an admins nightmare?  developers cannot install software?

i'm not aware of any issues with getting people access to the code they have
licensed.
Rating: 5
how do I display rows with out using /* + first_rows */ hint ? March 09, 2004
Reviewer: A reader
Hi tom we use
saveral applications to brows the data from oracle.
one of them is toad.

 They show a part of the data as soon as it is available on the screen and don't
wait for the complete result set to be returned.

  I checked the v$sql, v$sqlarea there is not any stmt with the first rows hint
. It show the exact sql stmt that we "user" passed. How can I do that in my
custom application ? I don't what to wait for 4k record and then show it to
user, I need first rows hint functinality with out changing the stmt ? possible
? how ? is pagging involded ?

and yes we are using Java 1.4 + classes12.jar
and to display results, we use JTable
Followup:
you can alter your session to set the optimizer goal if you like.
Rating: 3
Response to Tom March 09, 2004
Reviewer: Matt from Australia
>> Why an admins nightmare?  developers cannot install software?

I intending to create a developer/administrator divide here - software
development is a team effort. Of course developers can install software however,
I would prefer that everyone runs the same patched version of the DB and
managing this when there are multiple desktop DB's I see as being problematic.

>>i'm not aware of any issues with getting people access to the code they have
licensed.

This is the issue, I guess. Is a patched desktop version of the DB that is used
for development "licenced for development" (ie: free) or is there a licence
payment required?

I understand that the "standalone" tkprof might fall into a different category.
But, if a patched desktop version may be licenced for development, I don't see
an issue.

Ta.
Followup:
i don't know what you mean by a patched desktop version?
Rating: 5
Very Useful March 16, 2004
Reviewer: shabana
I had problems populating large result sets. The Query helped me in fetching the
needed rows keeping a page count from web tier
Rating: 5
"order by" April 01, 2004
Reviewer: A reader
hi tom
"select *
  from ( select a.*, rownum rnum
           from ( YOUR_QUERY_GOES_HERE -- including the order by ) a
          where rownum <= MAX_ROWS )
 where rnum >= MIN_ROWS
/
"

Does not the order by force you to go through the
entire rows anyways = which is pretty much
the same overhead as a "count(*)" thus defeating
the purpose? And I think in most cases, users
do want to sort their result in some order.
(Also in order by case the FIRST_ROWS hint also
is useless...
thanx!
Followup:
No it doesn't

think "index"

also, using rownum with the order by trick above has special top-n
optimizations, so in the case where it would have to get the entire result set
-- it is much more efficient then asking Oracle to generate the result set and
just fetching the first N rows (using the rownum kicks in a special top-n
optimization)

This is NOT like count(*).  count(*) is something we can definitely 100% live
without and would force the entire result set to be computed (most of the times,
we don't need to get the entire result set here!)

ORDER BY is something you cannot live without -- we definitely 100% need it in
many cases.
Rating: 5
thanx! April 02, 2004
Reviewer: A reader
I tried it out myself and I am getting the results
that you say. I believe the "count stopkey" indicates
the rownum based top n optimization you talked
about (t1 is a copy of all_objects with some 30,000
rows one index on all columns of t1 being selected.)
select * from
(
  select /*+ FIRST_ROWS */ a.*, rownum rnum
  from
  (
    /* OUR QUERY */
    select owner, object_name, object_type, rownum
    from t1
    where owner = 'PUBLIC'
    order by owner, object_name, object_type
  ) a
  where rownum <= 10
)
where rnum >= 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.02          0          0          0           0
Execute      1      0.01       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          4          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.03       0.02          0          4          0          10

Misses in library cache during parse: 1
Optimizer mode: FIRST_ROWS
Parsing user id: 64

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  VIEW  (cr=4 pr=0 pw=0 time=486 us)
     10   COUNT STOPKEY (cr=4 pr=0 pw=0 time=427 us)
     10    VIEW  (cr=4 pr=0 pw=0 time=373 us)
     10     COUNT  (cr=4 pr=0 pw=0 time=309 us)
     10      INDEX RANGE SCAN OBJ#(56907) (cr=4 pr=0 pw=0 time=270 us)(object id
56907)

Thanx!!!!
Followup:
it also applies in sorting unindexed stuff as well (full details in expert one
on one)

basically if you

select *
  from ( select * from really_huge_table order by something_not_indexed )
 where rownum < 10

oracle will get the first record and put it into slot 1 in a result set

it'll get the second record and if that is less than the one in one, it'll push
it down to two and put this in one, else this goes into two

and so on for the first 10 records -- we now have 10 sorted records -- now it'll
get the 11th and either

a) the 11th exceeds the one in the 10th slot -- this new record is discarded
b) the 11th is less than one of the existing 10 -- the current 10th goes away
and this gets stuffed in there.

lots more efficient to sort the top N, than it would be to sort the entire
result set into temp, merge it all back together -- just to fetch the first
10...
Rating: 5
wow! April 02, 2004
Reviewer: A reader
awesome - thanx a lot!!! ( not sure if you are on
vacation or is this your idea of vacation?;))

Regards
Rating: 5
i think April 02, 2004
Reviewer: A reader
"(full details in expert one
on one)"
You meant effective oracle by design (page 502)
thanx!
Followup:
doh, you are right.
Rating: 5
so here is the second test (without indexes) April 02, 2004
Reviewer: A reader
thought I would share with others since I anyways
ran it.
------schema
spool s3
set echo on
drop table t2;
create table t2
as select owner, object_name, object_type
from all_objects;
insert into t2
select * from t2;
commit;

analyze table t2 compute statistics for table for all indexes for all
indexed columns;
-------------------
notice we have no indexes created
--------- selects ran - one with rownum and one without

set termout off
alter session set timed_statistics=true;
alter session set events '10046 trace name context forever, level 12';
select * from
(
  select /*+ FIRST_ROWS */ a.*, rownum rnum
  from
  (
    /* OUR QUERY  ROWNUM ABSENT */
    select owner, object_name, object_type, rownum
    from t2
    where owner = 'PUBLIC'
    order by object_name
  ) a
);

select * from
(
  select /*+ FIRST_ROWS */ a.*, rownum rnum
  from
  (
    /* OUR QUERY  ROWNUM PRESENT */
    select owner, object_name, object_type, rownum
    from t2
    where owner = 'PUBLIC'
    order by object_name
  ) a
  where rownum <= 10
)
where rnum >= 1;

  --------tkprof results----
-- FIRST CASE - ROWNUM ABSENT
select * from
(
  select /*+ FIRST_ROWS */ a.*, rownum rnum
  from
  (
    /* OUR QUERY  ROWNUM ABSENT */
    select owner, object_name, object_type, rownum
    from t2
    where owner = 'PUBLIC'
    order by object_name
  ) 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     2528      2.02       4.10        515        500          7       37904
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2530      2.02       4.10        515        500          7       37904

Misses in library cache during parse: 0
Optimizer mode: FIRST_ROWS
Parsing user id: 64

Rows     Row Source Operation
-------  ---------------------------------------------------
  37904  VIEW  (cr=500 pr=515 pw=515 time=3587213 us)
  37904   COUNT  (cr=500 pr=515 pw=515 time=3413040 us)
  37904    VIEW  (cr=500 pr=515 pw=515 time=3295049 us)
  37904     SORT ORDER BY (cr=500 pr=515 pw=515 time=3144606 us)
  37904      COUNT  (cr=500 pr=0 pw=0 time=613868 us)
  37904       TABLE ACCESS FULL T2 (cr=500 pr=0 pw=0 time=281939 us)

--- second case ROWNUM present
select * from
(
  select /*+ FIRST_ROWS */ a.*, rownum rnum
  from
  (
    /* OUR QUERY  ROWNUM PRESENT */
    select owner, object_name, object_type, rownum
    from t2
    where owner = 'PUBLIC'
    order by object_name
  ) a
  where rownum <= 10
)
where rnum >= 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.33       0.49          0        500          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.33       0.49          0        500          0          10

Misses in library cache during parse: 0
Optimizer mode: FIRST_ROWS
Parsing user id: 64

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  VIEW  (cr=500 pr=0 pw=0 time=495126 us)
     10   COUNT STOPKEY (cr=500 pr=0 pw=0 time=494955 us)
     10    VIEW  (cr=500 pr=0 pw=0 time=494864 us)
     10     SORT ORDER BY STOPKEY (cr=500 pr=0 pw=0 time=494817 us)
  37904      COUNT  (cr=500 pr=0 pw=0 time=262446 us)
  37904       TABLE ACCESS FULL OBJ#(56928) (cr=500 pr=0 pw=0 time=129898 us)

Elapsed time in first case: 4.10 seconds
Elapsed time in second case (what would be our query) : 0.49 seconds

the second option is 8 times faster.
Rating: 5
April 05, 2004
Reviewer: A reader from Marlboro, MA
Invaluable information.

thank you Tom.
April 02, 2004 Puneet -- Thanks for the question regarding "Pivot Query", version 8.1.5
You Asked
Please state an example of pivot query.
I know that in cardinality I've 2 columns, so how can I show
them in columnar form and I know the cardinality is 4.
Please also illustrate method of Varray and Nested Tables.

Regards
Puneet
and we said...
Here is an example of a pivot query.  Say you have the following
set of data:

scott@DEV816> select job, deptno, count(*)
  2    from emp
  3   group by job, deptno
  4  /

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

9 rows selected.

And you would like to make DEPTNO be a column.  We have 4
deptno's in EMP, 10,20,30,40.  We can make columns dept_10,
dept_20, dept_30, dept_40 that have the values that are
currently in the count column.  It would look like this:

scott@DEV816>
scott@DEV816> select job,
  2         max( decode( deptno, 10, cnt, null ) ) dept_10,
  3         max( decode( deptno, 20, cnt, null ) ) dept_20,
  4         max( decode( deptno, 30, cnt, null ) ) dept_30,
  5         max( decode( deptno, 40, cnt, null ) ) dept_40
  6    from ( select job, deptno, count(*) cnt
  7             from emp
  8            group by job, deptno )
  9   group by job
 10  /

JOB          DEPT_10    DEPT_20    DEPT_30    DEPT_40
--------- ---------- ---------- ---------- ----------
ANALYST                       2
CLERK              1          2          1
MANAGER            1          1          1
PRESIDENT          1
SALESMAN                                 4

That has pivoted the CNT column by deptno across job.

That works if you know the domain of deptno's.  What if you
didn't though.  What if you wanted JOB to be the column instead
and leave deptno in the rows?  You might not know of all of the
possible jobs, or there might be 100's of them.  We can use
object types to pivot then:

scott@DEV816> create or replace type myScalarType as object
  2  ( job  varchar2(30),
  3    cnt  number
  4  )
  5  /

Type created.

scott@DEV816> create or replace type myArrayType as table of
myScalarType
  2  /

Type created.

scott@DEV816>
scott@DEV816> column x format a40 word_wrapped
scott@DEV816> select deptno,
  2         cast ( multiset( select job, count(*) cnt
  3                            from emp
  4                           where emp.deptno = dept.deptno
  5                           group by job ) as myArrayType ) x
  6    from dept
  7  /

    DEPTNO X(JOB, CNT)
---------- ----------------------------------------
        10 MYARRAYTYPE(MYSCALARTYPE('CLERK', 1),
           MYSCALARTYPE('MANAGER', 1),
           MYSCALARTYPE('PRESIDENT', 1))

        20 MYARRAYTYPE(MYSCALARTYPE('ANALYST', 2),
           MYSCALARTYPE('CLERK', 2),
           MYSCALARTYPE('MANAGER', 1))

        30 MYARRAYTYPE(MYSCALARTYPE('CLERK', 1),
           MYSCALARTYPE('MANAGER', 1),
           MYSCALARTYPE('SALESMAN', 4))

        40 MYARRAYTYPE()
Review & Followup

Rating: 4
how about the other way round May 13, 2002
Reviewer: A reader
Hi Tom

Can we use decode to convert a column into a row?. For example

ename   dname
------  -------
FORD    FINANCE

into

details
--------
FORD
FINANCE

this is probably a strange requirement but it has been asked :(
Followup:
yes.

  1  select decode( r, 1, to_char(deptno), 2, dname, 3, loc )
  2  from dept, (select rownum r from all_objects where rownum <= 3 )
  3* where deptno = 10
ops$tkyte@ORA817DEV.US.ORACLE.COM> /

DECODE(R,1,TO_CHAR(DEPTNO),2,DNAME,3,LOC
----------------------------------------
10
ACCOUNTING
NEW YORK
Rating: 4
Similar Problem May 13, 2002
Reviewer: Mike Wilson from Los Angeles, CA
I have a simlar problem to the above of pivoting tables where I don't know the
domain of the pivot columns (or they fluctuate).  Because of this I wrote a
small domain lookup function that uses auto-binding and looks up the column
values.  I create a lookup table that lists the field names to be retreived and
use a cartesian join to do my lookups and then exclude all of the null values.
It forces auto-binding also.

CREATE OR REPLACE  FUNCTION DLOOKUP (vField in varchar2,
    vTable in varchar2, vWhere in varchar2 default NULL) return
    varchar2
    AUTHID CURRENT_USER
    as
       vVar varchar2(4000);
       vQuery varchar2(4000);
    begin
       execute immediate 'alter session set cursor_sharing=force';
       vQuery := 'select '||vField||' from '||vTable;
       if (vWhere is not NULL) then
            vQuery := vQuery ||' where '||vWhere;
       end if;
       execute immediate vQuery into vVar;
       return vVar;
       exception when others then
         return null;
   end;
;

This works pretty well, although the cartesian join is very expensive.  With the
example above though using an array, how would you get it to actually look like
a table?  Is there a view you can wrap around the multiset output to make it
look like an actual table?
Followup:
show me how this pivots?  I don't get it -- seems to just do a select into of a
single row/column

I don't see any cartesian products here at all?

If you have my book -- the last section of the analytic functions chapter shows
how to pivot any query using a ref cursor and some simple query rewrite
techniques.
Rating: 5
theory behind pivot query May 13, 2002
Reviewer: A reader
Hi Tom

I noticed you said carteasian product, the trick or theory behind pivoting a
result set is by having a carteasian product the decode the columns, am I
correct?

so basically from your example

select decode( r, 1, to_char(deptno), 2, dname, 3, loc )
from dept, (select rownum r from all_objects where rownum <= 3 )
where deptno = 10

we first get

1, 10,  accounting, new york
2, 10,  accounting, new york
3, 10,  accounting, new york

then we decode 1 to deptno, 2 to accounting, 3 to new york to get

10
ACCOUNTING
NEW YORK

is my understanding correct?

cheers
Followup:
Exactly, dead on.
Rating: 4
Un-pivot example for comment ... May 13, 2002
Reviewer: Mike Wilson from Los Angeles, CA
Sorry, for the previous incomplete post.  Hit enter while incomplete in thought.
 You are correct, un-pivoting a table in the above case doesn't require a
cartesian join.

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 I would have to have a different version of
the view per department and our department structures and calculation models
change monthly.

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.

As I so ineptly responded before, rather than hard-coding a view I wrote a small
domain lookup function that allows me flexibility (at the expense of compute
time).  I simply wanted to share the function with the readers and see if they
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.  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
id    a    b
=======================================
1    1    2
2    4    5

Per department, per column this is the calculation that each one wants.

temp_lkup table
id    col    calc
=======================================
1    a    a
1    b    b*2
2    a    a+6
2    b    b*3

desired output
id    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

<snip>
create table temp (id 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 (id 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.id, temp_lkup.col, temp_lkup.calc,
  to_number(nvl(dlookup_bind_one(col, 'temp', 'id=:a', temp.id),0)) orig,
  to_number(nvl(dlookup_bind_one(calc, 'temp', 'id=:a', temp.id),0)) val
  from temp, temp_lkup
  where temp.id = temp_lkup.id
/
</snip>
        ID 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
Rating: 3
May 16, 2002
Reviewer: Sagi from India
Once again it was great tom.

I executed the below Query:

select decode( r, 1, to_char(deptno), 2, dname, 3, loc )  || chr(10) "Col2Row "
from dept, (select rownum r from all_objects where rownum <= 3)
order by deptno

Output:
=======
10
ACCOUNTING
NEW YORK
20
RESEARCH
DALLAS
30
SALES
CHICAGO
40
OPERATIONS
BOSTON

Question: How do we get say 2 empty blank lines after each deptno. I tried using
CHR(10) or CHR(13). But could not get the output.

Thanx in advance.

REgards.
Followup:
ops$tkyte@ORA817DEV.US.ORACLE.COM> select decode( r, 1, to_char(deptno)  ||
chr(10), 2, dname, 3, loc )  || chr(10) "Col2Row "
  2  from dept, (select rownum r from all_objects where rownum <= 3)
  3  order by deptno
  4
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> /

Col2Row
------------------------------------------
10

ACCOUNTING
NEW YORK
20

RESEARCH
DALLAS
30

SALES
CHICAGO
40

OPERATIONS
BOSTON

12 rows selected.
Rating: 4
general purpose pivot function May 16, 2002
Reviewer: Mikito Harakiri
Mike, could you please demonstrate what you achieve on emp table -- I don't get
it.

It might be possible to write a general purpose pivot table function. The
problem is the return type, which we can't declare of being any specific type.
Maybe anydata/anydtaset might help...
Followup:
I did write a general purpose pivot function -- its in the analytic function
chapter of my book.  Uses a simple query rewrite to pivot and returns a
dynamically opened ref cursor (works in 8i and up).
Rating: 3
got your code, thank you May 17, 2002
Reviewer: Mikito Harakiri
begin
  my_pkg.pivot
  (p_max_cols => 4,
   p_query => 'select job, count(*) cnt, deptno,
                  row_number() over (partition by job order by deptno) rn
                  from emp
                  group by job, deptno',
   p_anchor => my_pkg.array('JOB'),
   p_pivot  => my_pkg.array('DEPTNO', 'CNT'),
   p_cursor => :x );
end;

This is not quite what I meant. What if pivoting is just a subquery of a larger
query? Is there a way to call your function from a sql query?
Followup:
I cannot see how pivoting is could be a "subquery" of a larger query.  That just
doesn't compute with me.
Rating: 5
good point May 20, 2002
Reviewer: Mikito Harakiri
Rating: 5
Can we have the above output using Multiset and ArrayType July 29, 2002
Reviewer: Yamani from KSA
Hi Tom,

Can we have the same out put generated on the above example:

JOB          DEPT_10    DEPT_20    DEPT_30    DEPT_40
--------- ---------- ---------- ---------- ----------
ANALYST                       2
CLERK              1          2          1
MANAGER            1          1          1
PRESIDENT          1
SALESMAN                                 4

But using the multiset and ArrayType.
If yes can you pleas show us how?

Thanks,
Followup:
they won't be columns, you would have 2 columns -- the JOB and then a single
array that is the list of counts by deptno.

It would be "ugly" but it can be done.  The array would have to be an array of
object types that had the deptno and cnt in them (an array of records in
effect).

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace type myScalarType as object
  2  ( deptno number,
  3    cnt       number
  4  )
  5  /

Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace type myArrayType as table
of MyScalarType
  2  /

Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> column data format a40
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select job,
  2     cast( multiset( select myScalarType( deptno, count(*) )
  3                       from emp e2
  4                      where e2.job = emp.job
  5                      group by deptno ) as myArrayType ) data
  6    from (select distinct job from emp ) emp
  7  /

JOB       DATA(DEPTNO, CNT)
--------- ----------------------------------------
ANALYST   MYARRAYTYPE(MYSCALARTYPE(20, 2))
CLERK     MYARRAYTYPE(MYSCALARTYPE(10, 1), MYSCALA
          RTYPE(20, 2), MYSCALARTYPE(30, 1))

MANAGER   MYARRAYTYPE(MYSCALARTYPE(10, 1), MYSCALA
          RTYPE(20, 1), MYSCALARTYPE(30, 1))

PRESIDENT MYARRAYTYPE(MYSCALARTYPE(10, 1))
SALESMAN  MYARRAYTYPE(MYSCALARTYPE(30, 4))

ops$tkyte@ORA817DEV.US.ORACLE.COM>
Rating: 5
Got it !! August 26, 2002
Reviewer: A reader
Pivot queries, had absolute no idea on how to write one.
Add a dash of AskTom and I have cooked up some awesome
queries that are keeping users happy and Managers off my back.

You are absolute legend Tom...
Rating: 4
January 16, 2003
Reviewer: A reader
Hi Tom, please help to find out what is wrong in my query:
select dept,
max(decode(to_char(dateused,’DY’), ‘MON', cnt, null)) "mon",
max(decode(to_char(dateused,’DY’), ‘TUE', cnt, null)) "tue",
max(decode(to_char(dateused,’DY’), ‘WED', cnt, null)) "wed",
max(decode(to_char(dateused,’DY’), ‘THU', cnt, null)) "thu",
max(decode(to_char(dateused,’DY’), ‘FRI', cnt, null)) "fri",
max(decode(to_char(dateused,’DY’), ‘SAT', cnt, null)) "sat",
max(decode(to_char(dateused,’DY’), ‘SUN', cnt, null)) "sun"
from (select dept,
               decode(to_char(dateused,’DY’),
                 'MON’, 'mon',
                 'TUE', 'tue',
                 'WED', 'wed',
                 'THU', 'thu',
                 'FRI', 'fri',
                 'SAT', 'sat',
                 'SUN’, 'sun') t,
               count(*) cnt
          from visits v, users u
where v.ss = u.ss
and to_date(v.dateused, 'DD-MON-RR') > = to_date('1-AUG-02', 'DD-MON-RR')
and to_date(v.dateused, 'DD-MON-RR')  <  to_date('1-SEP-02', 'DD-MON-RR')
group by dept,
decode(to_char(dateused,’DY’),
                   ‘MON’,’mon’,
                 'TUE', 'tue',
                 'WED', 'wed',
                 'THU', 'thu',
                 'FRI', 'fri',
                 'SAT', 'sat',
                 'SUN’, 'sun'      ))
  group by dept;
ERROR at line 8:
ORA-00904: invalid column name
SQL> desc visits
 ID         NOT NULL VARCHAR2(7)
 LOCATION   NOT NULL NUMBER(15,2)
 DATEUSED   NOT NULL DATE
SQL> desc users
 ID         NOT NULL VARCHAR2(7)
 DEPT       NOT NULL VARCHAR@(80)

Thanks
L.
Followup:
well, you got that v.ss = u.ss in there as well -- so this is a hacked example
at best.... but...

line 8 has

max(decode(to_char(dateused,'DY'), 'MON', cnt, null)) "mon",

but by the time you get there, dateused doesn't exist.

dept, t, cnt

they exist, but dateused -- long gone.
Rating: 3
Can this pivoting apply here too? June 20, 2003
Reviewer: Godwin from Ghana
Hi Tom, I have this records in table A as
stno   course code
----   -----------
1000   SOCI
1000   ECONS
1000   MATHS
1000   ANAT
1002   SOCI
1002   ECONS
1002   MATHS
1002   ANAT

and I want the above record to be inserted into a
table B(stno,subj1,subj2,subj3,subj4) in a row format such as :
stno  subj1  subj2  stubj3  stubj4
----  ----   -----  ------  -------
1000  SOCI   ECONS   MATHS  ANAT

How can the pivoting be performed on this?
If it is impossible, is there any way out?
Thanks for your usual co-operation.
Followup:
if there is a fixed maximum number of course codes per stno, yes

select stno,
       max(decode(rn,1,course_code)) s1,
       ...
       max(decode(rn,N,course_code)) sN
  from ( select stno, course_code,
                row_number() over (partition by stno order by course_code )
           from t )
 group by stno
/
Rating: 3
Great-the answer is right inside your book-expert-one-on-one June 23, 2003
Reviewer: Godwin from Ghana
Hi posted the question to you before I realized the answer was in your book. I
just got your book a few days ago and am now studying it.
But permit me to ask for a clarification on the answer you just posted. What if
the maximum course code per stno is not known? that is if there are more than
thousands course codes? In this case, I believe the selected records can't be
inserted into a table since the column in the inserting table may not be known.
But can the query to display the records in this case be possible? can you show
with example?
Thanks.
Followup:
see the original answer -- you can use a COLLECTION, or you could even use a
cursor variable.

select stno, CURSOR( select course_code from t t2 where t2.stno = t.stno )
  from (select distinct stno from t ) T
/

like this;

scott@ORA920> select deptno, Cursor(select ename from emp e2 where e2.deptno =
emp.deptno)
  2  from (select distinct deptno from emp ) emp;

    DEPTNO CURSOR(SELECTENAMEFR
---------- --------------------
        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
Rating: 5
Thanks Tom July 15, 2003
Reviewer: Sirisha Swahari from ColoradoSprings, CO USA
This example simplified my work a lot.
Keep up the good work Tom.
Rating: 4
July 25, 2003
Reviewer: ARC from Singapore
Very good explanation on pivot query.
I have info in table like this.
Table: TT
s date,
a number,
b number,
c nuber
SQL> select * from tt;
S                 A         B         C
--------- --------- --------- ---------
25-JUL-01       100      1000      2000
20-JUL-01       200      3000      4000

I would like to display info as below
25-JUL-01
1  100
2  1000
3  2000

20-JUL-01
1  200
2  3000
3  4000

No of columns are not fixed.

Please help me in regard. Thanking you in advance.
Followup:
the number of columns MUST be known, else you cannot write the query and to
write the query is easy:

select dt, decode(r,1,a,2,b,3,c)
  from t, (select rownum r from all_objects  where rownum <= 3 )
/
Rating: 5
pivot August 01, 2003
Reviewer: ARC from Singapore
Hi Tom,
Need your help again.
I have query like below.

break on report
compute sum of deptno_10 on report
compute sum of deptno_20 on report
compute sum of deptno_30 on report
compute sum of total on report

select     job,
    max(decode(deptno, 10, cnt, 0)) DEPTNO_10,
    max(decode(deptno, 20, cnt, 0)) DEPTNO_20,
    max(decode(deptno, 30, cnt, 0)) DEPTNO_30,
    max(decode(deptno, 10, cnt, 0)) +
    max(decode(deptno, 20, cnt, 0)) +
    max(decode(deptno, 30, cnt, 0)) TOTAL
from    (select deptno deptno, job job, count(*) cnt from emp
    group by deptno, job)
group by job;

JOB       DEPTNO_10 DEPTNO_20 DEPTNO_30     TOTAL
--------- --------- --------- --------- ---------
ANALYST           0         2         0         2
CLERK             1         2         1         4
MANAGER           1         1         1         3
PRESIDENT         1         0         0         1
SALESMAN          0         0         5         5
          --------- --------- --------- ---------
sum               3         5         7        15

1)Is it possible to display 'TOTAL' insted of 'sum' in above report?

2)If deptno column values are changing how to handle above query in more
generic?

Thanks you very much.
Followup:
1) don't think so, plus is what it is -- very very rudimentary, simple reporting
tool.

2) if you have my book "Expert one on one Oracle" -- see the chapter on analytic
functions.  I have a stored procedure there.  In short, you need to run a query
to find the domain of deptnos so you can then write a dynamic sql query to pivot
based on that set of values.
Rating: 4
for ARC: use LABEL to change sum to TOTAL August 04, 2003
Reviewer: Barbara Boehmer from Norwalk, CA USA
In response to ARC's question # 1:

Yes, you can use LABEL to change sum to TOTAL, as demonstrated below:

SQL> break on report
SQL> compute sum LABEL 'TOTAL' of deptno_10 on report
SQL> compute sum of deptno_20 on report
SQL> compute sum of deptno_30 on report
SQL> compute sum of total on report
SQL> select      job,
  2           max (decode (deptno, 10, cnt, 0)) deptno_10,
  3           max (decode (deptno, 20, cnt, 0)) deptno_20,
  4           max (decode (deptno, 30, cnt, 0)) deptno_30,
  5           max (decode (deptno, 10, cnt, 0)) +
  6           max (decode (deptno, 20, cnt, 0)) +
  7           max (decode (deptno, 30, cnt, 0)) total
  8  from        (select deptno, job, count (*) cnt
  9            from   emp
 10            group by deptno, job)
 11  group by job
 12  /

JOB        DEPTNO_10  DEPTNO_20  DEPTNO_30      TOTAL
--------- ---------- ---------- ---------- ----------
ANALYST            0          2          0          2
CLERK              1          2          1          4
MANAGER            1          1          1          3
PRESIDENT          1          0          0          1
SALESMAN           0          0          4          4
          ---------- ---------- ---------- ----------
TOTAL              3          5          6         14

SQL>
Followup:
awesome, thanks much!
Rating: 4
for ARC: another generic method when deptno column values are changing August 04, 2003
Reviewer: Barbara Boehmer from Norwalk, CA USA
In response to ARC's question # 2:

The following SQL to create SQL can be used to dynamically generate the query.
If you run the script below, it will create another script (query.sql) with the
desired query, allowing for whatever values of deptno exist, then execute that
script.  Since you seem to want to do this in SQL*Plus, you may like this
method.

store set saved_settings replace
set echo off feedback off heading off pages 0 verify off
spool query.sql
prompt break on report
prompt compute sum label 'TOTAL' of deptno_10 on report
prompt compute sum of deptno_20 on report
prompt compute sum of deptno_30 on report
prompt compute sum of total on report
prompt select   job
select ', max (decode (deptno, ' || deptno || ', cnt, 0)) deptno_' || deptno
from   (select distinct deptno from emp)
/
select ', max (decode (deptno, ' || min_deptno || ', cnt, 0))'
from   (select min (deptno) min_deptno from emp)
/
select '+ max (decode (deptno, ' || other_deptnos || ', cnt, 0))'
from   (select distinct deptno other_deptnos from emp where deptno >
        (select min(deptno) from emp))
/
prompt as total
prompt from     (select   deptno, job, count (*) cnt
prompt           from     emp
prompt           group by deptno, job)
prompt group by job
prompt /
spool off
start saved_settings
start query.sql
Rating: 4
Pivot August 04, 2003
Reviewer: ARC from Singapore
Thank you very much Mr.Barbara Boehmer.
Rating: 5
Another Pivot August 15, 2003
Reviewer: A reader
I would like to turn below query into Pivot. But anything I do gives me an error
[ ORA-00904: invalid column name ] pointing somewhere in main FROM clause.
Could you help?
I was trying to use example in your book but did not get far. I can't figure out
what is going wrong. Error as you can see is not very descriptive and
misleading.
Thank you in advance.

This:
select status,
       to_char( trunc( status_date,'MM' ), 'Month') Month,
       count(1)
  from loanapplication a
 where a.status_date > to_date('12/31/02','MM/DD/YY')
   and exists (select 1
                 from property b
                where  a.rec = b.loan_rec
                  and  b.addr_rec IN (select rec
                                        from address
                                       where zip >= '94101' and zip <= '94199')
               )
group by status, to_char(trunc(status_date,'MM'),'Month')
order by Month

Into:
  select a.status,
         decode (to_char (trunc (a.status_date,'MM'),'Month'), January, cnt,
null) January,
         decode (to_char (trunc (a.status_date,'MM'),'Month'), February, cnt,
null) February,
         decode (to_char (trunc (a.status_date,'MM'),'Month'), March, cnt, null)
March,
         decode (to_char (trunc (a.status_date,'MM'),'Month'), April, cnt, null)
April,
         decode (to_char (trunc (a.status_date,'MM'),'Month'), May, cnt, null)
May,
         decode (to_char (trunc (a.status_date,'MM'),'Month'), June, cnt, null)
June,
         decode (to_char (trunc (a.status_date,'MM'),'Month'), July, cnt, null)
July,
         decode (to_char (trunc (a.status_date,'MM'),'Month'), August, cnt,
null) August
    from (select status,
                 status_date,
                 count(*) cnt
            from loanapplication
           group by status, status_date
          ) a
   where a.status_date > to_date('12/31/02','MM/DD/YY')
     and exists (select 1
                   from property b
                  where a.rec = b.loan_rec
                    and b.addr_rec IN (select rec
                                         from address
                                        where zip >= '94101' and zip <= '94199')
                 )
   group by a.status, a.status_date
Followup:
         decode (to_char (trunc (a.status_date,'MM'),'Month'), August, cnt,
null) August
                                                               *
ERROR at line 9:
ORA-00904: "AUGUST": invalid identifier

i get that myself in 9i and:

         decode (to_char (trunc (a.status_date,'MM'),'Month'), August, cnt,
null) August
                                                               *
ERROR at line 9:
ORA-00904: invalid column name

in 8i and before.

the * pointed me right to it.  Perhaps you need quotes about the 'August', and
other months?

might be easier to just

decode( to_char(a.status_date, 'mm' ), 1, cnt, null ) Jan,

?
Rating: 5
Same Pivot problem August 15, 2003
Reviewer: A reader
Thank you for your recomendation. But I am still getting the error. It points to
a column that is valid.
I am running 8.1.7.4 on Solaris 2.8.

 1   select a.status,
  2          decode( to_char(a.status_date, 'mm' ), 01, cnt, null ) "Jan",
  3          decode( to_char(a.status_date, 'mm' ), 02, cnt, null ) "Feb",
  4          decode( to_char(a.status_date, 'mm' ), 03, cnt, null ) "Mar",
  5          decode( to_char(a.status_date, 'mm' ), 04, cnt, null ) "Apr",
  6          decode( to_char(a.status_date, 'mm' ), 05, cnt, null ) "May",
  7          decode( to_char(a.status_date, 'mm' ), 06, cnt, null ) "Jun",
  8          decode( to_char(a.status_date, 'mm' ), 07, cnt, null ) "Jul",
  9          decode( to_char(a.status_date, 'mm' ), 08, cnt, null ) "Aug"
 10     from (select status,
 11                  status_date,
 12                  count(*) cnt
 13             from mm_loanapplication
 14            group by status, status_date
 15          ) a
 16      where a.status_date > to_date('12/31/02','MM/DD/YY')
 17        and exists (select 1 from mm_property b
 18  where  a.rec = b.loan_rec
 19  and  b.addr_rec IN (select rec
 20  from am_address
 21  where zip >= '94101' and zip <= '94199')
 22  )
 23* group by a.status, a.status_date
ENVPRD@PROD> /
group by a.status, a.status_date
                               *
ERROR at line 23:
ORA-00904: invalid column name
Followup:
it won't even get that far on my machine --  since a.rec does not exist -- you
grouped it out.

ops$tkyte@ORA920> select a.status,
  2          decode( to_char(a.status_date, 'mm' ), 01, cnt, null ) "Jan",
  3          decode( to_char(a.status_date, 'mm' ), 02, cnt, null ) "Feb",
  4          decode( to_char(a.status_date, 'mm' ), 03, cnt, null ) "Mar",
  5          decode( to_char(a.status_date, 'mm' ), 04, cnt, null ) "Apr",
  6          decode( to_char(a.status_date, 'mm' ), 05, cnt, null ) "May",
  7          decode( to_char(a.status_date, 'mm' ), 06, cnt, null ) "Jun",
  8          decode( to_char(a.status_date, 'mm' ), 07, cnt, null ) "Jul",
  9          decode( to_char(a.status_date, 'mm' ), 08, cnt, null ) "Aug"
 10     from (select status,
 11                  status_date,
 12                  count(*) cnt
 13             from loanapplication
 14            group by status, status_date
 15          ) a
 16    where a.status_date > to_date('12/31/02','MM/DD/YY')
 17      and exists (select 1
 18                    from property b
 19                   where a.rec = b.loan_rec
 20                     and  b.addr_rec IN (select rec
 21                                           from address
 22                                          where zip >= '94101'
 23                                            and zip <= '94199')
 24                 )
 25  group by a.status, a.status_date
 26  /
                 where a.rec = b.loan_rec
                       *
ERROR at line 19:
ORA-00904: "A"."REC": invalid identifier

so, it is just not possible what you have there....  look deeper.
Rating: 5
August 16, 2003
Reviewer: A reader
Rating: 5
pivot two columns September 10, 2003
Reviewer: mo
Tom:

I am trying to pivot two columns and can not get it to work. Do you have any
hints. The quantity for each storage is not coming out right.  It is defaulting
everything to the first value which is zero.

select stock_number, storage_code,qty_available,
                row_number() over(partition by stock_number order by
stock_number nulls last) seq
               from (select a.stock_number, b.storage_code,

compute_qty_stored(b.warehouse_id,a.stock_number,b.storage_Code) qty_available
                    from stock_item a, physical_inventory b
                    where a.stock_number = b.stock_number(+)
                    and a.stock_number = 'AC006'
                    union all
                    select a.stock_number, b.storage_code,

compute_qty_stored(b.warehouse_id,a.stock_number,b.storage_Code) qty_available
                    from stock_item a, storage_receipt b
                    where a.stock_number = b.stock_number(+)
                    and a.stock_number = 'AC006'
                   )
                  group by stock_number,storage_Code,qty_available

STOCK_NUMB STORAGE_CO
---------- ----------
QTY_AVAILABLE        SEQ
------------- ----------
AC006      ABC1
            0          1

AC006      ABC2
           50          2

AC006      ABC3
           50          3

AC006      ABC4
            0          4

AC006      ABC5
            0          5

AC006      ABC6
          250          6

AC006
            0          7

7 rows selected.

Then
select stock_number,
                  max(decode(seq,1,storage_code,null)) Loc#1,
                  max(decode(seq,1,qty_Available,null)) qty#1,
                  max(decode(seq,2,storage_code,null)) Loc#2,
                  max(decode(seq,1,qty_Available,null)) qty#2,
                  max(decode(seq,3,storage_code,null)) loc#3,
                  max(decode(seq,1,qty_Available,null)) qty#3,
                  max(decode(seq,4,storage_code,null)) loc#4,
                  max(decode(seq,1,qty_Available,null)) qty#4
                 from (
                select stock_number, storage_code,qty_available,
                row_number() over(partition by stock_number order by
stock_number nulls last) seq
               from (select a.stock_number, b.storage_code,a.description,

compute_qty_stored(b.warehouse_id,a.stock_number,b.storage_Code) qty_available
                    from stock_item a, physical_inventory b
                    where a.stock_number = b.stock_number(+)
                    and a.stock_number = 'AC006'
                    union all
                    select a.stock_number, b.storage_code,a.description,

compute_qty_stored(b.warehouse_id,a.stock_number,b.storage_Code) qty_available
                    from stock_item a, storage_receipt b
                    where a.stock_number = b.stock_number(+)
                     and b.warehouse_id(+) like 'NLS%' and a.stock_number =
'AC006'
                                 )
             group by stock_number,storage_Code,qty_available)
             where seq <=4 and stock_number='AC006'
             group by stock_number
/

STOCK_NUMB LOC#1           QTY#1 LOC#2           QTY#2 LOC#3           QTY#3
LOC#4           QTY#4
---------- ---------- ---------- ---------- ---------- ---------- ----------
---------- ----------
AC006      ABC1                 0 ABC2                 0 ABC3                0
ABC4                0

1 row selected.
Followup:
look closely as your select list.

look at the decode on the seq.

you always use 1 for qty#
you use 1, 2, 3, 4 for the others.
Rating: 5
pivot September 11, 2003
Reviewer: mo
Tom:

You are right thanks.

1.  The result set has four locations and four quantites.  If quantity is zero I
do not want to print the location. Since I am printing this in an HTML table, I
have to check for 4x3x2=24 different possibilites so that the values will be
placed starting the first cell. Is there a way easier than this? This is what I
mean

If (qty1 <> 0 and qty2 <> 0 and qty3 <> 0 and qty4 <> 0) THEN
htp.p('<TD>'||loc1||</TD>');
htp.p('<TD>'||loc2||</TD>');
htp.p('<TD>'||loc3||</TD>');
htp.p('<TD>'||loc4||</TD>');
END IF;

IF (qty1 <> 0 and qty2 = 0 and ......)

2.  Instead of finding all quantities and then deciding not to print the
quantities with "0" I modifed the query so that it will exculde those in the
result set.

Problem here is that it exculded the stock numbers from the report and I wanted
to show all of them?  Is there a wrokaround for this or I am stuck with option
1?

Thank you
Rating: 5
Pivot analytic results including nulls October 07, 2003
Reviewer: Robert from St Louis, Mo.
Tom,

We need a query to calculate the sum of invoices for a particular month by
customer and product as well as the sum of invoices for a year ago.  This is
what I have came up with so far:

create table t (month_and_year   date,
                customer         varchar2(20),
                product          varchar2(30),
                invoice          number)
/

insert into t values (to_date('01-May-2002','DD-MON-YYYY'),     'Central',
'Dog Chow',     '100');
insert into t values (to_date('01-May-2002','DD-MON-YYYY'),     'West',
'Dog Chow',     '100');
insert into t values (to_date('01-May-2002','DD-MON-YYYY'),     'East',
'Dog Chow',     '100');
insert into t values (to_date('01-May-2002','DD-MON-YYYY'),     'North',
'Dog Chow',     '100');
insert into t values (to_date('01-May-2002','DD-MON-YYYY'),     'Central',
'Cat Chow',     '50');
insert into t values (to_date('01-May-2002','DD-MON-YYYY'),     'West',
'Cat Chow',     '50');
insert into t values (to_date('01-May-2002','DD-MON-YYYY'),     'East',
'Cat Chow',     '50');
insert into t values (to_date('01-May-2002','DD-MON-YYYY'),     'North',
'Cat Chow',     '50');
insert into t values (to_date('01-Jun-2002','DD-MON-YYYY'),     'Central',
'Dog Chow',     '75');
insert into t values (to_date('01-Jun-2002','DD-MON-YYYY'),     'West',
'Dog Chow',     '75');
insert into t values (to_date('01-Jun-2002','DD-MON-YYYY'),     'East',
'Dog Chow',     '75');
insert into t values (to_date('01-Jun-2002','DD-MON-YYYY'),     'North',
'Dog Chow',     '75');
insert into t values (to_date('01-Jul-2002','DD-MON-YYYY'),     'Central',
'Cat Chow',     '25');
insert into t values (to_date('01-Jul-2002','DD-MON-YYYY'),     'West',
'Cat Chow',     '25');
insert into t values (to_date('01-Jul-2002','DD-MON-YYYY'),     'East',
'Cat Chow',     '25');
insert into t values (to_date('01-Jul-2002','DD-MON-YYYY'),     'North',
'Cat Chow',     '25');
insert into t values (to_date('01-May-2003','DD-MON-YYYY'),     'Central',
'Dog Chow',     '200');
insert into t values (to_date('01-May-2003','DD-MON-YYYY'),     'West',
'Dog Chow',     '200');
insert into t values (to_date('01-May-2003','DD-MON-YYYY'),     'East',
'Dog Chow',     '200');
insert into t values (to_date('01-May-2003','DD-MON-YYYY'),     'North',
'Dog Chow',     '200');
insert into t values (to_date('01-May-2003','DD-MON-YYYY'),     'Central',
'Cat Chow',     '150');
insert into t values (to_date('01-May-2003','DD-MON-YYYY'),     'West',
'Cat Chow',     '150');
insert into t values (to_date('01-May-2003','DD-MON-YYYY'),     'East',
'Cat Chow',     '150');
insert into t values (to_date('01-May-2003','DD-MON-YYYY'),     'North',
'Cat Chow',     '150');
insert into t values (to_date('01-May-2003','DD-MON-YYYY'),     'Central',
'Beggin Strips','60');
insert into t values (to_date('01-May-2003','DD-MON-YYYY'),     'West',
'Beggin Strips','60');
insert into t values (to_date('01-May-2003','DD-MON-YYYY'),     'East',
'Beggin Strips','60');
insert into t values (to_date('01-May-2003','DD-MON-YYYY'),     'North',
'Beggin Strips','60');

select cy.month_and_year,
       cy.customer,
       cy.product,
       cy.cy_invoices,
       ly.ly_invoices
  from (select month_and_year,
               customer,
               product,
               SUM(invoice) over
                  (partition by month_and_year, customer, product
                       order by month_and_year) cy_invoices
          from t) cy,
       (select month_and_year,
               customer,
               product,
               SUM(invoice) over
                  (partition by month_and_year, customer, product
                       order by month_and_year) ly_invoices
          from t) ly
 where trunc(cy.month_and_year,'MONTH') = add_months(trunc(sysdate,'MONTH'),-5)
   and trunc(cy.month_and_year,'MONTH') =
add_months(trunc(ly.month_and_year,'MONTH'),+12)
   and (cy.customer = nvl(ly.customer,cy.customer) or ly.customer =
nvl(cy.customer,ly.customer))
   and (cy.product = nvl(ly.product,cy.product) or ly.product =
nvl(cy.product,ly.product))
 order by cy.cy_invoices desc

This returns:

MONTH_AND_YEAR      CUSTOMER     PRODUCT            CY_INVOICES LY_INVOICES
------------------- ------------ ------------------ ----------- -----------
2003-05-01 00:00:00 Central      Cat Chow                   150          50
2003-05-01 00:00:00 Central      Dog Chow                   200         100
2003-05-01 00:00:00 East         Cat Chow                   150          50
2003-05-01 00:00:00 East         Dog Chow                   200         100
2003-05-01 00:00:00 North        Cat Chow                   150          50
2003-05-01 00:00:00 North        Dog Chow                   200         100
2003-05-01 00:00:00 West         Cat Chow                   150          50
2003-05-01 00:00:00 West         Dog Chow                   200         100

8 rows selected.

I am missing the 4 rows for beggin strips because of missing year ago data.

Can you point out what I am missing?

Thanks.
Followup:
don't think you want analytics at all here -- just a simple pivot.  this creates
a CY column of summed invoices for the data for the year we are in, and a LY
column for last year.  Then, group by takes care of squishing out the extra rows
by month.

If you have many years of data -- the where clause would be different -- to
include a where on the years of interest of course.

ops$tkyte@ORA920> select to_char( month_and_year, 'Mon' ),
  2         customer,
  3             product,
  4             sum( decode(to_char(month_and_year,'yyyy'),
to_char(sysdate,'yyyy'), invoice, null ) ) cy,
  5             sum( decode(to_char(month_and_year,'yyyy'),
to_char(add_months(sysdate,-12),'yyyy'), invoice, null ) ) ly
  6    from t
  7   where to_char(month_and_year,'mon') = 'may'
  8   group by to_char( month_and_year, 'Mon' ), customer, product
  9  /

TO_ CUSTOMER             PRODUCT                                CY         LY
--- -------------------- ------------------------------ ---------- ----------
May East                 Cat Chow                              150         50
May East                 Dog Chow                              200        100
May East                 Beggin Strips                          60
May West                 Cat Chow                              150         50
May West                 Dog Chow                              200        100
May West                 Beggin Strips                          60
May North                Cat Chow                              150         50
May North                Dog Chow                              200        100
May North                Beggin Strips                          60
May Central              Cat Chow                              150         50
May Central              Dog Chow                              200        100
May Central              Beggin Strips                          60

12 rows selected.
Rating: 5
Perfect October 07, 2003
Reviewer: Robert Ware from St Louis, Mo.
Tom,

Thats exactly what we needed.

Thanks!
Followup:
Oh -- one thing I wanted to add

THANK YOU
THANK YOU
THANK YOU

for giving me a create table and insert into statements !!  you have no idea how
much easier that is for me!

sometimes I get "nothing"

sometimes I get a describe (needs editing) and a select * from (needs LOTS of
editing)

getting a ready to run script -- that's awesome.
Rating: 4
Let's upload files.... November 07, 2003
Reviewer: Robert from CT
>>THANK YOU
>for giving me a create table and insert into >statements !!  you have no idea
how
>much easier that is for me!

you know, maybe you should consider allowing reader file-upload and like display
an icon next to the message so the page is shorter
Rating: 4
Consecutive Numbers November 18, 2003
Reviewer: Robert from USA
SQL> select MIN(c_year), MAX(c_year)
  2  from T
  3  ;

MIN(C_YEAR)   MAX(C_YEAR)
------------- --------------
1996          2003

There may be gaps in c_year value....How can I return
a list of c_year in CONSECUTIVE...
1996
1997
1998
1999
2000
2001
2002
2003

Thanks
Followup:
ops$tkyte@ORA920LAP> select min(c_year) min_c_year, max(c_year) max_c_year from
t;

MIN_C_YEAR MAX_C_YEAR
---------- ----------
      1996       2003

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select (select min(c_year) from t)+rownum-1
  2    from all_objects
  3   where rownum <= (select max(c_year)-min(c_year)+1 from t)
  4  /

(SELECTMIN(C_YEAR)FROMT)+ROWNUM-1
---------------------------------
                             1996
                             1997
                             1998
                             1999
                             2000
                             2001
                             2002
                             2003

8 rows selected.
Rating: 5
Pivoting example using ref cursor December 31, 2003
Reviewer: David from SF, CA
Tom, in this posting above you say:
"If you have my book -- the last section of the analytic functions chapter shows
how to pivot any query using a ref cursor and some simple query rewrite
techniques"

I bought your book "Effective Oracle by Design" (enjoy it tremendously :D ) and
looking at pages 514-534 for this example but can't see it... Did I look into
the wrong book?
Followup:
at the point in time of this original posting, "my book" was unambigous -- there
was only one.

Now there are three.... I was refering to Expert One on One Oracle. Here is a
snippet to get you interested in the rest of that book:

.....
One other thing we must know is the MAXIMUM number of rows per partition we
anticipate. This will dictate the number of columns we will be generating.
Without it - we cannot pivot. SQL needs to know the number of columns and there
is no way around that fact. That leads us into the next more generic  example of
pivoting. If we do not know the number of total columns until runtime, we'll
have to use dynamic SQL to deal with the fact that the SELECT list is variable.
We can use PL/SQL to demonstrate how to do this - and end up with a generic
routine that can be reused whenever you need a pivot. This routine will have the
following specification:

scott@TKYTE816> create or replace package my_pkg
  2  as
  3      type refcursor is ref cursor;
  4      type array is table of varchar2(30);
  5
  6      procedure pivot( p_max_cols       in number   default NULL,
  7                       p_max_cols_query in varchar2 default NULL,
  8                       p_query          in varchar2,
  9                       p_anchor         in array,
 10                       p_pivot          in array,
 11                       p_cursor in out refcursor );
 12  end;
 13  /

Package created.

Here, you must send in either P_MAX_COLS or P_MAX_COLS_QUERY.  SQL needs to know
the number of columns in a query and this parameter will allow us to build a
query with the proper number of columns.  The value you should send in here will
be the output of a query similar to:

scott@TKYTE816> select max(count(*)) from emp group by deptno, job;

That is - it is the count of the discrete values that are currently in ROWS that
we will put into COLUMNS.  You can either send in the query to get this number,
or the number if you already know it.

The P_QUERY parameter is simply the query that gathers your data together.
Using the last example from above the query would be:

 10    from (  select deptno, job, ename, sal,
 11                   row_number() over ( partition by deptno, job
 12                                           order by sal, ename ) rn
 13              from emp
 14             )

The next two inputs are arrays of column names.  The P_ANCHOR tells us what
columns will stay CROSS RECORD (down the page) and P_PIVOT states the columns
that will go IN RECORD (across the page).  In our example from above, P_ANCHOR =
( `DEPTNO', `JOB' ) and P_PIVOT = (`ENAME','SAL').  Skipping over the
implementation for a moment, the entire call put together might look like this:

scott@TKYTE816> variable x refcursor
scott@TKYTE816> set autoprint on

scott@TKYTE816> begin
  2      my_pkg.pivot
  3      ( p_max_cols_query => 'select max(count(*)) from emp
                                 group by deptno,job',
  4        p_query => 'select deptno, job, ename, sal,
  5                           row_number() over ( partition by deptno, job
  6                                               order by sal, ename ) rn
  7                      from emp a',
  8        p_anchor => my_pkg.array( 'DEPTNO','JOB' ),
  9        p_pivot  => my_pkg.array( 'ENAME', 'SAL' ),
 10        p_cursor => :x );
 11  end;
 12  /

PL/SQL procedure successfully completed.

DEPTNO JOB       ENAME_ SAL_1 ENAME_2    SAL_2 ENAME_3    SAL_3 ENAME_ SAL_4
------ --------- ------ ----- ---------- ----- ---------- ----- ------ -----
    10 CLERK     MILLER  1300
    10 MANAGER   CLARK   2450
    10 PRESIDENT KING    5000
    20 ANALYST   FORD    3000 SCOTT       3000
    20 CLERK     SMITH    800 ADAMS       1100
    20 MANAGER   JONES   2975
    30 CLERK     JAMES     99
    30 MANAGER   BLAKE     99
    30 SALESMAN  ALLEN     99 MARTIN        99 TURNER        99 WARD      99

9 rows selected.

As you can see - that dynamically rewrote our query using the generalized
template we developed.  The implementation of the package body is
straightforward:

scott@TKYTE816> create or replace package body my_pkg
  2  as
  3
  4  procedure pivot( p_max_cols          in number   default NULL,
  5                   p_max_cols_query in varchar2 default NULL,
  6                   p_query          in varchar2,
  7                   p_anchor         in array,
  8                   p_pivot          in array,
  9                   p_cursor in out refcursor )
 10  as
 11      l_max_cols number;
 12      l_query    long;
 13      l_cnames   array;
 14  begin
 15      -- figure out the number of columns we must support
 16      -- we either KNOW this or we have a query that can tell us
 17      if ( p_max_cols is not null )
 18      then
 19          l_max_cols := p_max_cols;
 20      elsif ( p_max_cols_query is not null )
 21      then
 22          execute immediate p_max_cols_query into l_max_cols;
 23      else
 24          raise_application_error(-20001, 'Cannot figure out max cols');
 25      end if;
 26
 27
 28      -- Now, construct the query that can answer the question for us...
 29      -- start with the C1, C2, ... CX columns:
 30
 31      l_query := 'select ';
 32      for i in 1 .. p_anchor.count
 33      loop
 34          l_query := l_query || p_anchor(i) || ',';
 35      end loop;
 36
 37      -- Now add in the C{x+1}... CN columns to be pivoted:
 38      -- the format is "max(decode(rn,1,C{X+1},null)) cx+1_1"
 39
 40      for i in 1 .. l_max_cols
 41      loop
 42          for j in 1 .. p_pivot.count
 43          loop
 44              l_query := l_query ||
 45                  'max(decode(rn,'||i||','||
 46                              p_pivot(j)||',null)) ' ||
 47                              p_pivot(j) || '_' || i || ',';
 48          end loop;
 49      end loop;
 50
 51      -- Now just add in the original query
 52      l_query := rtrim(l_query,',')||' from ( '||p_query||') group by ';
 53
 54      -- and then the group by columns...
 55
 56      for i in 1 .. p_anchor.count
 57      loop
 58          l_query := l_query || p_anchor(i) || ',';
 59      end loop;
 60      l_query := rtrim(l_query,',');
 61
 62      -- and return it
 63      execute immediate 'alter session set cursor_sharing=force';
 64      open p_cursor for l_query;
 65      execute immediate 'alter session set cursor_sharing=exact';
 66  end;
 67
 68  end;
 69  /

Package body created.

It only does a little string manipulation to rewrite the query and open a REF
CURSOR dynamically.  In the likely event the query had a predicate with
constants and such in it, we set cursor sharing on and then back off for the
parse of this query to facilitate bind variables (see the section on tuning for
more information on that).  Now we have a fully parsed query that is ready to be
fetched from.
Rating: 5
RE: Pivoting example using ref cursor December 31, 2003
Reviewer: David from SF, CA
Thank you Tom!
I thought Expert One-on-One Oracle is mostly conceptual book vs. Effective by
Design which is mostly practical, but i guess I'll have to think again :)
6 stars as always ;)

David
Rating: 5
Dear Tom, Happy New Year ! December 31, 2003
Reviewer: Sami from NJ,USA
<asktom>
at the point in time of this original posting, "my book" was unambigous -- there

was only one.
</asktom>

I thought FOUR
3)"Beginning Oracle Programming"
4)"Professional Oracle 8i Application Programming with Java, PL/SQL and XML "
Followup:
I don't count 4) -- i had a single chapter in it :)
Rating: 3
Is this pivoting? January 09, 2004
Reviewer: Trevor Welch
Hi Tom,

I have a table with say stock intradata

DATE        STOCK_CODE    LAST_PRICE  LAST_TRADE_TIME
01-JAN-2004 ABC           2.40        09:00
01-JAN-2004 ABC           2.41        09:05
01-JAN-2004 ABC           2.43        09:10
01-JAN-2004 ABC           2.39        10:00
01-JAN-2004 ABC           2.32        11:00
01-JAN-2004 ABC           2.41        11:31
01-JAN-2004 ABC           2.43        11:35
01-JAN-2004 ABC           2.40        12:00
01-JAN-2004 ABC           2.41        12:05

My requirement is for each half hour of the day to
be assigned a Character.
9:00 - 9:30   A
9:31 - 10:00  B
10:01 - 10:30 C
10:31 - 11:00 D
11:01 - 11:30 E
11:31 - 12:00 F
12:01 - 12:31 E
etc ... to 17:00

So the report required is like this

2.32 D
2.39 A
2.40 AF
2.41 AEF
2.43 F

Can you give me some pointers on how to do this please?

Thanks Trevor
Followup:
that is not "pivoting", that is aggregation.

questions -- why is there a 'date' and a 'last trade time'.  that is really just
a single column in 'real life' right.

Also, how do 2.40, 2.41, 2.43 become 2.39???? (letter A)

also, your time ranges are "strange"

9am sharp
but 10:01, 11:01 after that.

Hows about this

09:00 <= DT < 09:30 -> A
09:30 <= DT < 10:00 -> B
10:00 <= DT < 10:30 -> C

and so on...

And lastly, not getting that output in the least.  AF?  AEF??  huh whats up with
that.

As i'll be away for a while you can start your endeavor using this technique:

ops$tkyte@ORA9IR2> select dt,
  2         trunc(to_char(dt,'sssss')/(30*60)),
  3             chr( ascii('A')+trunc(to_char(dt,'sssss')/(30*60))-18 )
  4    from t;

DT                TRUNC(TO_CHAR(DT,'SSSSS')/(30*60)) C
----------------- ---------------------------------- -
01-jan-2004 09:00                                 18 A
01-jan-2004 09:05                                 18 A
01-jan-2004 09:10                                 18 A
01-jan-2004 10:00                                 20 C
01-jan-2004 11:00                                 22 E
01-jan-2004 11:31                                 23 F
01-jan-2004 11:35                                 23 F
01-jan-2004 12:00                                 24 G
01-jan-2004 12:05                                 24 G
01-jan-2004 13:29                                 26 I
01-jan-2004 13:30                                 27 J
01-jan-2004 13:31                                 27 J
01-jan-2004 13:59                                 27 J
01-jan-2004 14:00                                 28 K
01-jan-2004 14:01                                 28 K

15 rows selected.

It gets your A, B, C assigned as per my method -- from there, i'm not sure what
the logic is
Rating: 3
A bit more info January 17, 2004
Reviewer: Trevor Welch
Tom,

Thanks for your reply.

First of sorry, I didn't quite explain my requirements and there was an error in
it so I'll have another go with
and actual screen capture of "Market Profile" in action

You are quite correct the Date column and last trade time are actually the same
column.

I liked your technique however what I actually need is a "graph" of when the
prices occured per each half hour eg

I am basing this request of the Chicago board of trade "Market Profile" Tool.

The Market Profile day is split into half-hour time periods, A is 0800 to 0830,
B is 0830 to 0900, etc.

Soybeans open at 0930 (D). In the profile below, the opening half hour ranged
from 6664 to 6690.
The next period, E, covered 6680 to 6690; and so on for the rest of the day.

SN     SOYBEANS (CBOT)         JUL 98 2-MAR-98
     6710    F      |   <== Upper Extreme price
     6704    F      |
     6700    F      |
     6696    FG       | Upper Range Extension
     6694    EFG        |                 |
     6690    DEFGH      |                 | Initial Balance
     6686    DEGH       |                 | (6694 to 6664)
     6684    DEGHIJ     | Value Area      |
     6682    DEGHIJ     | 70% of Trade    | <== POC 6682
     6680    DEHIJK     | (6694 - 6672)   |
     6676    DHIJK      |                 |
     6674    DHIJK      |                 |
     6672    DHK        |                 |
     6670    DHK                          |
     6664    DK                           |
     6654    K      |
     6650    K      |
     6646    K      |
     6644    K      | Lower
     6630    K      | Tail (single prints, 6654 - 6620)
     6624    K      |
     6620    K      |   <==Lower Extreme price

You said >>Also, how do 2.40, 2.41, 2.43 become 2.39???? (letter A)
2.40 2.41 2.43 don't become 2.39 they are assigned a letter representing each
half hour period they occured in.

Does the above example make it any clearer?

Regards
Trevor
Followup:
not really -- a little -- but not really (i don't have a set of inputs, desired
outputs).

sorry -- don't think I'll be able to answer your question here in the
review/followup section -- when I'm taking quesitons -- go for it there, this is
bigger than a breadbox.
Rating: 3
Pivot on columns from table March 16, 2004
Reviewer: Marcio from Brazil
Consider this:

create table t(
    p    VARCHAR2(1) constraint pk primary key,
    a        number(13,9),
    b        number(13,9),
    k        number(13,9)
);

insert into t values ('A', 10, 15.59040617, 5.493248938);
insert into t values ('B', 10.80674214, 16.71941596, 5.333903907);
insert into t values ('C', 10, 1516.238149, 14.64789478);
insert into t values ('D', 10, 15.59040617, 5.493248938);
insert into t values ('E', 10.80674214, 16.71941596, 5.333903907);
commit;

select 'a' p,
       max(decode(p, 'A', a, null)) a,
       max(decode(p, 'B', a, null)) b,
       max(decode(p, 'C', a, null)) c,
       max(decode(p, 'D', a, null)) d,
       max(decode(p, 'E', a, null)) e
  from t
union
select 'b' p,
       max(decode(p, 'A', b, null)) a,
       max(decode(p, 'B', b, null)) b,
       max(decode(p, 'C', b, null)) c,
       max(decode(p, 'D', b, null)) d,
       max(decode(p, 'E', b, null)) e
  from t
union
select 'k' p,
       max(decode(p, 'A', k, null)) a,
       max(decode(p, 'B', k, null)) b,
       max(decode(p, 'C', k, null)) c,
       max(decode(p, 'D', k, null)) d,
       max(decode(p, 'E', k, null)) e
  from t
/

The result:
~~~~~~~~~~~

P          A          B          C          D          E
- ---------- ---------- ---------- ---------- ----------
a         10 10,8067421         10         10 10,8067421
b 15,5904062  16,719416 1516,23815 15,5904062  16,719416
k 5,49324894 5,33390391 14,6478948 5,49324894 5,33390391

The Question:
~~~~~~~~~~~~~
I would like know if is possible to do the query above access just once the
table T.

The plan show me 3 fts.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (UNIQUE)
   2    1     UNION-ALL
   3    2       SORT (AGGREGATE)
   4    3         TABLE ACCESS (FULL) OF 'T'
   5    2       SORT (AGGREGATE)
   6    5         TABLE ACCESS (FULL) OF 'T'
   7    2       SORT (AGGREGATE)
   8    7         TABLE ACCESS (FULL) OF 'T'

Thank you,
Marcio
Followup:
ops$tkyte@ORA9IR2> set autotrace on explain
ops$tkyte@ORA9IR2> select decode( x, 1, 'a', 2, 'b', 3, 'k' ) pp,
  2         max(decode(p,'A',decode(x,1,a,2,b,k))) a,
  3         max(decode(p,'B',decode(x,1,a,2,b,k))) b,
  4         max(decode(p,'C',decode(x,1,a,2,b,k))) c,
  5         max(decode(p,'D',decode(x,1,a,2,b,k))) d,
  6         max(decode(p,'E',decode(x,1,a,2,b,k))) e
  7    from t, (select 1 x from dual union all
  8             select 2 x from dual union all
  9             select 3 x from dual )
 10   group by decode( x, 1, 'a', 2, 'b', 3, 'k' )
 11  /

P          A          B          C          D          E
- ---------- ---------- ---------- ---------- ----------
a         10 10.8067421         10         10 10.8067421
b 15.5904062  16.719416 1516.23815 15.5904062  16.719416
k 5.49324894 5.33390391 14.6478948 5.49324894 5.33390391

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (GROUP BY)
   2    1     NESTED LOOPS
   3    2       VIEW
   4    3         UNION-ALL
   5    4           TABLE ACCESS (FULL) OF 'DUAL'
   6    4           TABLE ACCESS (FULL) OF 'DUAL'
   7    4           TABLE ACCESS (FULL) OF 'DUAL'
   8    2       TABLE ACCESS (FULL) OF 'T'
Rating: 5
Thank you March 17, 2004
Reviewer: Marcio from Brazil
Rating: 2
Still Confused How to Apply it April 02, 2004
Reviewer: Mary W from VA
i have a resutls set that looks lieke this:

email    qNumber     letterSequence      Text
m@m.com  1           a                   null
m@m.com  2           d                   null
t@t.com  1           c                   null
t@t.com  1           b                   null
t@t.com  2           a                   txt
t@t.com  2           d                   null

i need the RS to look like:
email   q1a      q1b   q1c    q2a     q2b   q2c     q2d
m@m.com  a                                           d
t@t.com  c        b            txt                   d
Followup:
select email,
       max( decode( qnumber, 1, decode(letterSequence,'a','a') ) ) q1a,
       max( decode( qnumber, 1, decode(letterSequence,'b','b') ) ) q1b,
       max( decode( qnumber, 1, decode(letterSequence,'c','c') ) ) q1c,
.... same for 2 ....
 from t
 group by email;
April 02, 2004 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)...
Rating: 5
Schema Scope - Nested Table April 02, 2004
Reviewer: A reader
Hi Tom,

<quote>
create or replace package ora815_pkg
as
    type tblType is table of dept%rowtype index by binary_integer;

    procedure p( x in tblType );

end;
/
</quote>

In your example, the "tblType" nested table is declare within the package scope.

If the "tblType" nested table is declared in the schema scope (i.e. schema
object), can it still possible to REMOTELY call this procedure p()?

If yes, what is the syntax to delcare the "tblType" variable?  I get an error
when I try this sytnax:

v_tbls scott scott.tblType@ora815;

Thanks.
Followup:
In my example tblType is a PLSQL INDEX BY TABLE type (not a nested table)....

You can remotely call this procedure.  On the remote site, i would use a synonym
to make it easier:

create synonym remote_pkg for ora815_pkg;

and on the remote site I can:

declare
   l_data remote_pkg.tblType;
begin
   remote_pkg.p( l_data );
end;
/
Rating: 5
RE: Schema Scope - Nested Table April 02, 2004
Reviewer: A reader
<quote>
In my example tblType is a PLSQL INDEX BY TABLE type (not a nested table)....
</quote>

You are right.  It is an index by table.

Sorry, I didn't clearly explain my question.  I would like to know if it is
possible to remotely call the package when the "tblType" INDEX BY TABLE type
OUTSIDE of the package (i.e. in the schema).

In other words, if I change your example to:

create or replace type tblType is table of dept%rowtype index by binary_integer
/
create or replace package ora815_pkg
as
    procedure p( x in tblType );
end;
/

Here is the remote code:
create or replace synonym remote_pkg for ora815_pkg@ora815db
/
declare
   /*
      How do you declare the tblType variable???
      It is not part of the package.
      You cannot care your own local index by table type
      because of type mismatch.
    */
   l_data remote_pkg.tblType;
begin
   remote_pkg.p( l_data );
end;
/

Thanks.
Followup:
object types are not excessively "dblink friendly".  getting better over time,
but not in 8i for sure.
April 02, 2004 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
Rating: 3
DENSE_RANK() Bug? April 02, 2004
Reviewer: A reader from NJ, USA
Tom,

I have a table T which has 1 column and 5 rows in it.

Select Col1 from T

Col1
----
5
6
7
8
5

Case 1
======
select * from
(
select     rank() over(order by col1) r,
       dense_rank() over(order by col1) dr,
       col1
  from T
)

Output
------
R DR Col1
=========
1 1  5
1 1  5
3 2  6
4 3  7
5 4  8

In the above case, the Rank() function did a skip when there was a tie whereas
Dense_Rank() didn't which is expected.

Case 2
======
select * from
(
select rank() over(order by col1 desc) r,
       dense_rank() over(order by col1 desc) dr,
       col1
  from T
)

Output
------
R DR Col1
=========
1 1  8
2 2  7
3 3  6
4 4  5
4 4  5

Why is Rank() and Dense_Rank() behaving the same in this case. The only thing I
changed is to reverse the ordering of COL1 in the RANK() and DENSE_RANK()
function.
I expected the R column in the last row to have a 5 instead of 4 because of a
tie.

Is my understanding wrong or is it the way RANK() and DENSE_RANK() supposed to
work. Please clarify.
Followup:
think about it.

in the second case, you have a first, second, third and fourth place winner.

the difference would come into play when you went for FIFTH place.  rank -- no
5th place, dense rank -- 5th place.
Rating: 5
dense_rank() vs rank() April 03, 2004
Reviewer: a reader from UK
As Tom explained in his answer.

insert into t values (4);

SQL> select * from t;

      COL1
----------
         5
         5
         6
         7
         8
         4

6 rows selected.

select * from
(
select rank() over(order by col1 desc) r,
       dense_rank() over(order by col1 desc) dr,
       col1
  from t
)
/

         R         DR       COL1
---------- ---------- ----------
         1          1          8
         2          2          7
         3          3          6
         4          4          5
         4          4          5
         6          5          4

6 rows selected.
Rating: 4
answer to the query asked by Mach from KC April 03, 2004
Reviewer: dmv from india
Hi Mark,
      just check this query, I hope this will answer your question :

select ename, deptno, sal, rk,
         decode(nvl(ldrk,0)-rk, 0, 'Tie', decode(rk-nvl(lgrk,0), 0 , 'Tie', rk)
) status
from
(
select ename, deptno, sal, rk,
      lead(rk) over (partition by deptno order by rk) ldrk,
      lag(rk) over (partition by deptno order by rk) lgrk
from  (select ename, deptno, sal,
              rank() over (partition by deptno order by    sal desc) rk

       from emp
       )
);

ENAME          DEPTNO        SAL         RK STATUS
---------- ---------- ---------- ---------- ---------------

KING               10       5000          1 1
CLARK              10       2450          2 2
MILLER             10       1300          3 3
FORD               20       3000          1 Tie
SCOTT              20       3000          1 Tie
JONES              20       2975          3 3
ADAMS              20       1100          4 4
SMITH              20        800          5 5
BLAKE              30       2850          1 1
ALLEN              30       1600          2 2
TURNER             30       1500          3 3
MARTIN             30       1250          4 Tie
WARD               30       1250          4 Tie
JAMES              30        950          6 6
April 02, 2004 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://download-east.oracle.com/docs/cd/A81042_01/DOC/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"
Rating: 4
Load test February 11, 2004
Reviewer: sonali from waltham, ma
We ran the load test of our application, below is the TKPROF output..
SELECT TL_Position,TL_Department,TL_Proc_Level,TL_Schedule,TL_GRADE,
  TL_Comment_Ind,TL_Adjustable,TL_Bill_Sent,TL_Pay_Sent,TL_Type,TL_Mod_Date,
  TL_Mod_ID
FROM
  TimeL

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse      290      0.02       0.02          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      290      0.02       0.02          0          0          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 40

How come there is no where clause in this select in TKPROF output where as the
actual query in the application does have a where clause. Also As this is a
select statement and as I read your book Parse and Select should have been
greater than 0 and not just parse..What is this query doing ?
We were doing the load test by setting the CURSOR_Sharing= force, exact and
similar.. this is the output when we set it to SIMILAR.

Thanks
Followup:
it would appear you are parsing but never actually RUNNING this query....

probably, your application is just parsing this statement in order to "describe"
these columns in table TIMEL.

there is no where clause cause your programmers didn't use one.
Rating: 4
Bound variable February 11, 2004
Reviewer: sonali from waltham, ma
I was under the impression till now that all the cursors as a part of procedure
would convert the variables to bind variables.. but looking at the exaple you
gave above in this link which shows the REF Cursors are not bound, how do I know
what would be bound and what would not in the procedure ?

Thanks
Followup:
DYNAMIC SQL is the only time in plsql where the auto-binding (cannot) does not
take place.

If you use STATIC sql -- it ALWAYS does.
Rating: 5
Errors descriptions in Trace file March 31, 2004
Reviewer: Wor from USA
Hi Tom,
We got some database errors. On checking the alert log file
I saw
Mon Mar 29 11:38:40 2004
Errors in file /ora8bin/ctia/admin/udump/ora_186150_ctiacom.trc:
Mon Mar 29 11:47:47 2004
Errors in file /ora8bin/ctia/admin/udump/ora_226102_ctiacom.trc:

How do I read the error description from the trc files. ?

If I run tkprok <trc file> < output file > it gives me the following output.

TKPROF: Release 8.1.6.0.0 - Production on Wed Mar 31 17:15:41 2004

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

Trace file: ora_186150_ctiacom.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
********************************************************************************
Trace file: ora_186150_ctiacom.trc
Trace file compatibility: 8.00.04
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.
   20606  lines in trace file.

How do I read the error descriptions from the trc files ?
I have oracle 8.1.7 Win 2K ( client )

Please help,
Thanks in advance
Followup:
you do not use tkprof on a trace file generated as a result of an error.
(tkprof formats sql_trace trace files only)

You use gzip so when you mail it to support, it is smaller.  It is useful for
the support analyst to diagnose the error.
Rating: 5
in addition March 31, 2004
Reviewer: Wor from USA
Hi Tom,

1) How do I read the description ?
2) Does oracle prevent users from reading the error descriptions of trc files ?

Thanks in advance,
Manish
Followup:
there is no "description" really, just a bunch of trace data -- you can vi the
trace file and see what is in there.

no, you are not prevented from reading it, just not very much you can do with
it.  It'll show you the sql statement that was being executed (usually) but
thats about the extent of the information useful to you in there
Rating: 4
tkprof Report April 02, 2004
Reviewer: HM from NJ USA
Tom,
I notice you I join a table to an Oracle collection, on tkprof, the CPU an query
on the fetch are very high. Is this data related or just the nature of joining
to an Oracle Collection?

PS, you can ignore this if its not relevant to this thread.
Followup:
using the collection is a 100% cpu based operation pretty much, yes.
Rating: 4
tkprof report April 02, 2004
Reviewer: HM from NJ USA
Tom,
Does this look right? It appears that the cpu is pretty steep. I am joining a
78,000 row table
with a collection of 100 rows.

SELECT a.group_id
                FROM a,TABLE(:b1) b
                WHERE a.member_id=b.column_value
                AND a.member_type='G'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute     16      0.01       0.01          0          6          0           0
Fetch       16     19.65      20.65          0     704616          0         707
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       32     19.66      20.66          0     704622          0         707
Followup:
please -- look at the other link I sent you to -- with rownum and cardinality.

that'll explain LOTS to you.
Rating: 4
Number of QUERY BLOCKs April 06, 2004
Reviewer: A reader
Dear Tom,

My question may be silly. I am trying to understand the QUERY BLOCKS and CURRENT
BLOCKS in tkprof output.

I have
1) created table T2
2) Enabled sql trace
3) did run SELECT statemt

Why does my tkprof report shows 3113 QUERY BLOCK?
No one did modify my table? Why is it reading from ROLLBACK segments(QUERY
BLOCK). I have only single version of this entire table data.

Could you kindly explain this to me?

SQLPLUS>sqlplus scott

SQL*Plus: Release 8.1.7.0.0 - Production on Tue Apr 6 18:10:28 2004

(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> create table t2 as select * from dba_objects;

Table created.

SQL>

$ cat k1.sql
alter session set timed_statistics=true;
alter session set max_dump_file_size=unlimited;
alter session set tracefile_identifier='new_query';
alter session set events '10046 trace name context forever, level 12';
select * from t2;

********************************************************************************
select * from t2;

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     1719      0.97       1.60       1479       3113         15       25770
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1721      0.98       1.61       1479       3113         15       25770

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 129

Rows     Row Source Operation
-------  ---------------------------------------------------
  25770  TABLE ACCESS FULL T2

********************************************************************************
April 03, 2004 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' );
Rating: 5
Diff between ctx_ddl & dbms_utility March 19, 2004
Reviewer: Sami from NJ,USA
Dear Tom,

Thanks for all your help/support in the past. I visit this site everyday.

Q1) What is the difference between method (A) & (B)?
Q2) Which is the better/efficient way to go in 8i?
Q3) How to identify "exec ctx_ddl.optimize_index" is required?
Q4) Do you prefer to keep all the DOMAIN index in a tablespace which is created
with NOLOGGING option?

(A)
exec ctx_ddl.sync_index('USER1.DOMAIN_INDEX1');
exec ctx_ddl.optimize_index('USER1.DOMAIN_INDEX1','FAST');

(B)
exec dbms_utility.exec_ddl_statement('alter index USER1.DOMAIN_INDEX1 rebuild
online parameters(''sync'')');
exec dbms_utility.exec_ddl_statement('alter index USER1.DOMAIN_INDEX1 rebuild
online parameters(''optimize fast'')');
Followup:
q1) one is easier to code then the other.

q2) see #1

q3) if you maintain statspacks over time and you see your lios going up
measurably for you text queries, time to take a look.  if you have a system
whereby you bulk add lots of stuff at once -- that would be a good time to look
as well.

q4) no, i do not use nologging very much.  it is the grand EXCEPTION, not the
rule.   I use it when it is necessary (really big load that I can schedule a
backup after)
Rating: 4
"alter index rebuild online" locking rows April 02, 2004
Reviewer: Tri
Hi Tom,

Thank you for this wonderful site.

Some background info:

We have an intermedia index (Oracle version 8.1.7.4) created like this:

create index members_im_search_idx
  on members_intermedia(intermedia_dummy_indicator)
  indextype is ctxsys.context
  parameters('datastore members_con_mcd
              storage members_con_bs
              filter ctxsys.null_filter
              section group members_con_bsg
              wordlist members_con_wl
              memory 50M');

Please let me know if you need to see how some of these parameters are set (I
assume you don't need them for my questions below). This "members_intermedia"
table is pretty much a shadow table for the real "members" table (btw, each of
these tables contains millions of rows). It has a few columns from the real
members table that we want to search upon, plus a dummy column that the index is
built upon (as you can see in the create statement above). Rows in this
members_intermedia table is synched with the real members table via update and
insert triggers, and the dummy column value is reset/set for such rows. We chose
to go with this shadowing approach so we can deploy the index without any
downtime (we basically didn't turn on the update/insert triggers until the index
creation is done initially) and if we decide to add more columns to the 2 tables
in the future, we don't need to rebuild the index from scratch again.

We currently synch the index every hour via a dbms_job with:

dbms_utility.exec_ddl_statement('ALTER INDEX MEMBERS_IM_SEARCH_IDX REBUILD
ONLINE PARAMETERS(''SYNC MEMORY 50M'')');

No other work has been done to the index. The index has been up and running for
3+ months now.

Sometimes, there are thousands of new or updated rows to be synched with. What
we are seeing is during the index resynch process, a few handful of transactions
into the real members table are being blocked. Also, the synch is taking a lot
of gets (about 1.5 millions), which may or may not be a big deal given we have
lots of rows for the index to work with.

My questions:

1) How are the rows being blocked and is the blocking that we are seeing
preventable at all? (if so, please tell)

2) From what I've read on this site, it looks like we need to "optimize" the
index in order to lower the LIO for future synchs?

Any other observations you may have is greatly appreciated.
Followup:
1) to diagnose the blocking, we'd need to see the implementation - how is that
shadow table that the index is really on maintained.  what operations are being
blocked on the main table -- and are all operations of that type being blocked.

2) given your batch sizes -- maybe, maybe not (i tend to sync much less data
much more often -- leading to a break down over time, that and I UPDATE the
indexed data leading to false positives that are discovered after fetching the
source document).  I would do this -- get a measure of the average LIOs for your
big queries over time (stats pack would be a good way) and compare the
before/afters.
Rating: 4
Additional Information April 02, 2004
Reviewer: Tri
Here's the implementation you requested:

CREATE TABLE members_intermedia AS
SELECT
        member_id,
        'A' as intermedia_dummy_indicator,
        SYSDATE AS last_update,
        last_name,
        first_names,
        email
FROM
        members
;

ALTER TABLE members_intermedia add constraint members_intermedia_pk primary key
(member_id);

This shadow table members_intermedia is maintained by 2 triggers on the real
members table. Here's the meat of the update trigger:

        -- ... lengthly logic (see description below)

        IF intermedia_bool
        THEN

                UPDATE  members_intermedia
                SET
                        intermedia_dummy_indicator = 'A',
                        last_update = SYSDATE,
                        last_name = :NEW.last_name,
                        first_names = :NEW.first_names,
                        email = :NEW.email
                WHERE
                        member_id = :NEW.member_id
                ;

        END IF;

intermedia_bool is set to true when :OLD.last_name <> :NEW.last_name, or
:OLD.first_names <> :NEW.first_names, or :OLD.email <> :NEW.email. There are
other unrelated logic happening in the trigger to insert into a separate audit
table when these and other column values are changed. I assume that's all
irrelevant to this discussion.

The meat of the insert trigger:
INSERT INTO members_intermedia (
                member_id,
                intermedia_dummy_indicator,
                last_update,
                last_name,
                first_names,
                email
        ) VALUES (
                :NEW.member_id,
                'A',
                SYSDATE,
                :NEW.last_name,
                :NEW.first_names,
                :NEW.email
        );

We notice the blocking transactions seems to be exactly the update statement in
the trigger above:

UPDATE MEMBERS_INTERMEDIA
  SET INTERMEDIA_DUMMY_INDICATOR='A',LAST_UPDATE=SYSDATE,
  LAST_NAME=:b4,FIRST_NAMES=:b5,EMAIL=:b6 WHERE MEMBER_ID = :b7

These update statements are blocked while this SQL is executing:
  insert into "GV2"."DR$MEMBERS_IM_SEARCH_IDX$I"
  values (:token, :ttype, :first, :last, :count, :data)
This SQL is not in our application source code, but I
guess that it is part of what Oracle itself does during the index rebuid.
Followup:
that would indicate that Oracle Text is indexing a row that someone it trying to
update at that point in time.

The indexing routine has most likely locked the rows it is working on -- this
can be confirmed by running a sync.  In fact, I just did and

ops$tkyte@ORA9IR2> create index t_idx on t(y) indextype is ctxsys.context;
Index created.

ops$tkyte@ORA9IR2> insert into t values ( 1, 'how now brown cow' );
1 row created.

ops$tkyte@ORA9IR2> commit;
Commit complete.

ops$tkyte@ORA9IR2> @trace
Session altered.

ops$tkyte@ORA9IR2> alter index t_idx rebuild online parameters( 'sync memory
50m' );

the trace file ultimately shows:

...
select 1
from
 "OPS$TKYTE"."T" where rowid = :RID for update nowait

So, the syncing is locking the rows it is processing to ensure they are stable
(so we don't miss a change made during this sync - we wouldn't be able to see
the uncommitted changes)

Now, if this is ongoing problem -- one thing we can do is "background" the
shadow table maintenance.  since the index is updated infrequently in the first
place, deferring the shadow table maintainance should not be an issue.

You can either

a) put the information needed to do the update into a 'scratch job table', and
submit a job to process it, eg:

    dbms_job.submit( l_job, 'update_shadow(JOB);' );
    insert into scratch (pk, otherdata....) values ( l_job, ..... );

and have update_shadow:

procedure shadow( p_job in number )
is
   l_rec scratch%rowtype;
begin
   select * into l_rec from scratch where pk =p_job;
   do the update, so what if it blocks for a bit, no humans involved;
   delete from scratch where pk = p_job;
   commit;
end;

b) have the trigger do a select for update on the row it wants to update and if
it gets it -- do the update, else do the above. (so you are using jobs as little
as you need to)
Rating: 4
Additional Information April 02, 2004
Reviewer: Tri
Forgot to mention that the insert trigger is:
AFTER INSERT ON members FOR EACH ROW
while the update trigger is:
BEFORE UPDATE ON members FOR EACH ROW
Rating: 4
Gr8 April 05, 2004
Reviewer: Kiran Kumar Sabbisetti from Noida, India
This thread is very useful to understand the CTX_DDL.
April 03, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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.
Rating: 3
Finding Starting Balance... January 26, 2004
Reviewer: Robert from CT
-- Oracle8.1.7
hi, tom,
I am trying to write a SQL that result in something like the transaction history
one sees on bank statement...

Starting Balance
 Transaction Items...
 ......
Ending Balance

so I need 2 parameters to start: start_date and end_ date
then I use analytics to compute the running total UPTO the end_date:

CREATE OR REPLACE VIEW
V_TRANSACTION_HISTORY AS
SELECT
rowidtochar(rowid) id,
clientname,
SUM(amount) OVER
   (ORDER BY TransactionDate) running_total,
transactiondate,
amount
FROM ClientTransactions
 where TransactionDate <= nvl(to_date(SYS_CONTEXT('cont_wict','enddate'),
'mmddyyyy'), SYSDATE)
ORDER BY TransactionDate DESC

How do I proceed from this point ?
How can I get the single row PRECEDING the start_date ?

Thanks
Followup:
sorry -- i don't see a start date in there?  not sure what you are asking for --
why do you need something preceding the report parameters?
Rating: 4
LAG function re-discoverd January 26, 2004
Reviewer: Robert from CT
>>why do you need something preceding the report parameters?

Sorry for not being clear...I need data PRECEDING the parameter because I need
the PREVIOUS ending balance
I want to write the query based on the VIEW I coded above.

But reviewing ch 12 of "Expert 1-1",  I "rediscoverd" the function LAG...so I
came up with this

SELECT * FROM
  (SELECT transactiondate,
          running_total,
          LAG(running_total) over (ORDER BY transactiondate)
prev_run_total
          FROM
          (SELECT * FROM V_CLIENT_CVCS_HISTORY))
WHERE transactiondate >=  to_date(SYS_CONTEXT('cont_wict','startdate'),
'mmddyyyy') ;

Looks like this is the query i want.

Thanks
Rating: 5
Lag Function not supported in Reports ? March 30, 2004
Reviewer: Subodh Deshpande from India
Hi Tom,

are these lag/lead functions supported in d2k reports, am receviving an error
rep 0496 Internal Error Unable to parse the query

where as at the sql prompt the result is as below

SQL> desc bal
 Name                            Null?    Type
 ------------------------------- -------- ----
 SRNUM                                    NUMBER
 RECEIPT                                  VARCHAR2(5)
 AMT                                      NUMBER(5)

SQL> select srnum, receipt, amt, lag(amt) over(order by srnum) from bal
  2  ;

     SRNUM RECEI        AMT LAG(AMT)OVER(ORDERBYSRNUM)
---------- ----- ---------- --------------------------
         1 R             10
         2 I              5                         10
         3 R             15                          5
         4 I             50                         15

what can be the reason,

also if i use decode in lag then also am receiving the error

SQL> select srnum, receipt, amt, lag(decode(receipt,'R',amt,'I',amt*-1) over
(srnum) from bal;
select srnum, receipt, amt, lag(decode(receipt,'R',amt,'I',amt*-1) over (srnum)
from bal
                                *
ERROR at line 1:
ORA-30483: window  functions are not allowed here

what should i do..please guide..
Followup:
if your reports version is really old and reports is trying to parse the query -
yes, you might have an issue.

the workarounds would be:

a) use dynamic sql (so reports isn't parsing it)
b) use a view to hide the construct from reports
c) return a ref cursor from a stored procedure (sort of like a view)

you are missing a closing parenthesis in the lag.  after the -1, before the
over... and "over (srnum)" doesn't compute -- not valid syntax -- you mean maybe
"over (PARTITION by srnum)"
Rating: 5
opening and closing balace April 01, 2004
Reviewer: Subodh Deshpande from India
Hi Tom,

below is structure, sql and ouput which is wrong !!!

as opening +/- amt = closing
Business Rule is if receipt(R) add, is issue(I) then subtract and the closing is
next opening

SQL> desc bal
 Name                            Null?    Type
 ------------------------------- -------- ----
 SRNUM                                    NUMBER
 RECEIPT                                  VARCHAR2(5)
 AMT                                      NUMBER(5)

first query

  1  select srnum, receipt, nvl(lag(decode(receipt,'R',amt,'I',amt*-1)) over
(order by srnum),0) opening,
  2  amt,
  3  nvl(lag(decode(receipt,'R',amt,'I',amt*-1)) over (order by srnum),0) +
(decode(receipt,'R',amt,'I',amt*-1)) closing
  4* from bal
SQL> /

     SRNUM RECEI    OPENING        AMT    CLOSING
---------- ----- ---------- ---------- ----------
         1 R              0         10         10
         2 I             10          5          5
         3 R             -5         15         10
         4 I             15         50        -35
         5 R            -50         10        -40
         6 R             10         10         20
         7 I             10         20        -10
         8 R            -20         10        -10
         9 I             10         10          0
        10 I            -10         20        -30

10 rows selected.

second query

  1  select srnum, receipt, nvl(lag(decode(receipt,'R',amt,'I',amt*-1)) over
(order by srnum),0) +
  2  (decode(receipt,'R',amt,'I',amt*-1)) opening,
  3  amt,
  4  nvl(lag(decode(receipt,'R',amt,'I',amt*-1)) over (order by srnum),0) +
(decode(receipt,'R',amt,'I',amt*-1)) closing
  5* from bal
SQL> /

     SRNUM RECEI    OPENING        AMT    CLOSING
---------- ----- ---------- ---------- ----------
         1 R             10         10         10
         2 I              5          5          5
         3 R             10         15         10
         4 I            -35         50        -35
         5 R            -40         10        -40
         6 R             20         10         20
         7 I            -10         20        -10
         8 R            -10         10        -10
         9 I              0         10          0
        10 I            -30         20        -30

10 rows selected.

3) i have created view and the result is

  1  select srnum, receipt,
  2  lag(opbal + decode(receipt,'R',amt,'I',amt*-1)) over (order by srnum)
opbal,
  3  amt,
  4  opbal + decode(receipt,'R',amt,'I',amt*-1) clbal
  5* from opbal
SQL> /

     SRNUM RECEI      OPBAL        AMT      CLBAL
---------- ----- ---------- ---------- ----------
         1 R                        10         10
         2 I             10          5          5
         3 R              5         15         20
         4 I             20         50        -35
         5 R            -35         10         60
         6 R             60         10         20
         7 I             20         20        -10
         8 R            -10         10         30
         9 I             30         10          0
        10 I              0         20        -10

10 rows selected.

SQL> select * from opbal;

     SRNUM RECEI        AMT      OPBAL
---------- ----- ---------- ----------
         1 R             10          0
         2 I              5         10
         3 R             15          5
         4 I             50         15
         5 R             10         50
         6 R             10         10
         7 I             20         10
         8 R             10         20
         9 I             10         10
        10 I             20         10

10 rows selected.

Tom, probably, i have not understtod correctly this construct, please gude me
Followup:
do you mean something like this?

ops$tkyte@ORA9IR2> select srnum, receipt, amt, sum(last_amt) over (order by
srnum) opening
  2    from (select t.*,
  3                 lag(decode(receipt,'R',1,-1)*amt) over (order by srnum)
last_amt
  4            from t) t
  5  /

     SRNUM R        AMT    OPENING
---------- - ---------- ----------
         1 R         10
         2 I          5         10
         3 R         15          5
         4 I         50         20
         5 R         10        -30
         6 R         10        -20
         7 I         20        -10
         8 R         10        -30
         9 I         10        -20
        10 I         20        -30

10 rows selected.
Rating: 5
Help on Opening and closing balances April 01, 2004
Reviewer: Subodh Deshpande from India
Hi Tom,

Yes absolutely, and when i run the query i got this

SQL> select srnum, receipt, amt, sum(last_amt) over (order by
  2  srnum) opening
  3  from (select t.*,
  4  lag(decode(receipt,'R',1,-1)*amt) over (order by srnum)
  5  last_amt
  6  from t) t
  7  /
from t) t
     *
ERROR at line 6:
ORA-00942: table or view does not exist

then I write as below

  1  select srnum, receipt, amt, sum(last_amt) over (order by srnum) opening,
  2  sum(last_amt) over (order by srnum) + (decode(receipt,'R',1,-1)*amt)
closing
  3  from (select bal.*, lag(decode(receipt,'R',1,-1)*amt) over (order by srnum)
  4  last_amt
  5* from bal) t
SQL> /

     SRNUM RECEI        AMT    OPENING    CLOSING
---------- ----- ---------- ---------- ----------
         1 R             10
         2 I              5         10          5
         3 R             15          5         20
         4 I             50         20        -30
         5 R             10        -30        -20
         6 R             10        -20        -10
         7 I             20        -10        -30
         8 R             10        -30        -20
         9 I             10        -20        -30
        10 I             20        -30        -50

10 rows selected.

Now my questions are

1) why i was welcomed with 942 and u were not..

2)sum(last_amt) over (order by srnum) this is called as what

3) select t.*,lag(decode(receipt,'R',1,-1)*amt) over (order by srnum) last_amt
from t) t, you are aliasing it to t and also have used t.*, what do i understand
by this, what is it, where should it be used, why and what is the purpose of
this..

thanx in advance..
Followup:
1) because when I test things i always use table T -- ESPECIALLY WHEN I AM NOT
given a create table and insert statements to test with!!!!!

(should have been obvious no?  that I used table "T" to test...)

2) that is called an analytic function?

3) it is called a correlation name -- i wanted all columns from the table plus
some others -- so t.* was used and later I wanted to refer to that inline view
as T again, so I used the correlation name.

that is just "standard basic sql"
Rating: 5
opening balance and closing balance April 02, 2004
Reviewer: Subodh Deshpande from India
Hi Tom,

in the same thread, i informed about the error rep-496 in reports, the report
version i am using is report builder 3.0.5.8.0, is it old one..

thanx in advance..

have a nice weekend and great week ahead..subodh
Rating: 5
Analytical Functions April 02, 2004
Reviewer: Subodh Deshpande
hi Tom,

Thank you very much for the detaled explanation.

I mean,
I was knowing sum, avg, min, max as multirow functions, and the construct of sum
with over is new to me, this 'over' is also used with lag and lead, so i want to
know what does this over do, inwhich types of functions it can be used, is there
any other 'keyword' which can be used to do exactly opposite of it..please
explain..

learning basic from the Guru is advanced learning so did i asked..:-)

thanx once again..subodh
Followup:
if you have Expert One on One Oracle -- i have a chapter on that, else there is
a chapter in the Data Warehousing Guide, part of the standard doc set, on
otn.oracle.com
April 03, 2004 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.
Rating: 4
Lob Segment Size & Chunk Size January 15, 2004
Reviewer: Vinnie from Orlando
Tom,

If I run Show space on a LOB segment in which I just inserted 1 row I show the
following:
Total Blocks ... 4
Total Bytes .... 65536
...
...
My db Block size is 16k
The blob I inserted was just a tad over 4k.
Is there anyway to have control of the allocation?
I tried changing the chunk to 32k & nothing seemed to change.
Thanks
Followup:
and the full example we can all reproduce with is??  the whole thing -- create
table, etc.
Rating: 4
Lob Space Example January 16, 2004
Reviewer: Vinnie from Orlando
create table t (x int primary key, y clob, z varchar2(20))
lob (y) store as (tablespace lob_test chunk 32768);

declare
      l_string long := rpad( '*', 3000, '*' );
      l_clob clob;
      l_abc  varchar2(5) := 'abc';
begin
      insert into t values ( 2, l_string, '****' );
      l_string := l_string || l_string;
      insert into t values ( 3, l_string, '********' );
      insert into t values ( 4, empty_clob(), l_abc ) returning y into L_clob;
      dbms_lob.writeAppend( l_clob, length(l_abc), l_abc );
end;
/

select a.segment_name
from user_segments a, user_lobs b
where a.segment_name = b.segment_name
and b.table_name = 'T';

SEGMENT_NAME
------------------------
SYS_LOB000005319C00002$$

exec show_space('SYS_LOB000005319C00002$$',<user>,'LOB')

Total blocks ...8
Total Bytes ....131072
Unused Blocks ..0
Unused Bytes ...0
Last Used Block 4

select dbms_lob.getlength(y) from t;

DBMS_LOB.GETLENGTH(Y)
---------------------
                3000
                6000
                   3

My question is:
How can I get the LOB not to allocate so much?
My avg. LOB size is between 16k - 20k.
Thanks again

PS. I had to hand type everything because of a closed net issue. Hope this is
enough.
Followup:
you haven't show "free" blocks.  I have 8 blocks allocated -- 2 free.

now, of the three rows you have -- only one is "out of line".  It takes at least
2 blocks (32k chunk) in your 16 tablespace.  The other 4 blocks - lets call them
"overhead" -- the lob segment is using them.

second insert of the 6000 byte row added 2 blocks (one chunk), using 8 blocks.
Rating: 4
Lob Segments January 16, 2004
Reviewer: Vinnie from Orlando
Dosen't the
exec show_space('SYS_LOB000005319C00002$$',<user>,'LOB')

Total blocks ...8
Total Bytes ....131072
Unused Blocks ..0
Unused Bytes ...0
Last Used Block 4
show the LOB Segement alone?
If so, why would I see a total of 8 Blocks alocated with 0 unused blocks for
only a 6k CLOB?
Followup:
consider it overhead (and consider it "not reproduced everywhere" -- i don't see
the same thing you do)
Rating: 3
Error -- April 03, 2004
Reviewer: Reader from ind
hi,

Version :- Oracle 9.2.0 on Sun box.

SQL> select * from user_role_privs;

GRANTED_ROLE                   ADM DEF OS_
------------------------------ --- --- ---
CONNECT                        NO  YES NO
DBA                            NO  YES NO
RESOURCE                       NO  YES NO

SQL> 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    p_partition in varchar2 default NULL )
  7  authid current_user
  8  as
  9      l_free_blks                 number;
 10
 11      l_total_blocks              number;
 12      l_total_bytes               number;
 13      l_unused_blocks             number;
 14      l_unused_bytes              number;
 15      l_LastUsedExtFileId         number;
 16      l_LastUsedExtBlockId        number;
 17      l_LAST_USED_BLOCK           number;
 18      procedure p( p_label in varchar2, p_num in number )
 19      is
 20      begin
 21          dbms_output.put_line( rpad(p_label,40,'.') ||
 22                                p_num );
 23      end;
 24  begin
 25      for x in ( select tablespace_name
 26                   from dba_tablespaces
 27                  where tablespace_name = ( select tablespace_name
 28                                              from dba_segments
 29                                             where segment_type = p_type
 30                                               and segment_name = p_segname
 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      end loop;
 42
 43      dbms_space.unused_space
 44      ( segment_owner     => p_owner,
 45        segment_name      => p_segname,
 46        segment_type      => p_type,
 47            partition_name    => p_partition,
 48        total_blocks      => l_total_blocks,
 49        total_bytes       => l_total_bytes,
 50        unused_blocks     => l_unused_blocks,
 51        unused_bytes      => l_unused_bytes,
 52        LAST_USED_EXTENT_FILE_ID => l_LastUsedExtFileId,
 53        LAST_USED_EXTENT_BLOCK_ID => l_LastUsedExtBlockId,
 54        LAST_USED_BLOCK => l_LAST_USED_BLOCK );
 55
 56      p( 'Free Blocks', l_free_blks );
 57      p( 'Total Blocks', l_total_blocks );
 58      p( 'Total Bytes', l_total_bytes );
 59      p( 'Total MBytes', trunc(l_total_bytes/1024/1024) );
 60      p( 'Unused Blocks', l_unused_blocks );
 61      p( 'Unused Bytes', l_unused_bytes );
 62      p( 'Last Used Ext FileId', l_LastUsedExtFileId );
 63      p( 'Last Used Ext BlockId', l_LastUsedExtBlockId );
 64      p( 'Last Used Block', l_LAST_USED_BLOCK );
 65  end;
 66  /

Warning: Procedure created with compilation errors.

SQL> sho user
USER is "JAG"
SQL> show err
Errors for PROCEDURE SHOW_SPACE:

LINE/COL ERROR
-------- -----------------------------------------------------------------
24/14    PL/SQL: SQL Statement ignored
27/50    PL/SQL: ORA-00942: table or view does not exist

I have resource,connect and DBA role.
but It could not select dba_* tables.
Followup:

http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html
April 03, 2004 CJ -- Thanks for the question regarding "The New Ask Tom", version 8.1.7.4
You Asked
Why do parts of the new Ask Tom appear in another language? e.g. on the list of
questions asked previously:

Kedd Február 18, 2003 (H. Feb 17, 2003, 0 days old)

Thanks.
and we said...
We pick up the language from the browser now and set the NLS_LANG appropriately
-- dates, numbers, etc should appear in your locale now.
Review & Followup

Rating: 4
Looks good, but... February 19, 2003
Reviewer: Eugen
Hi Tom,

Good to know, that you are using the NLS_LANG.

I found an other minor "problem".
The email-search in the "Your Questions" Tab is case sensitive :-(

Regards,
Eugen
Followup:
not anymore.
Rating: 3
Foreign languages February 19, 2003
Reviewer: Ed from Cheshire, UK
I like the new features, especially the ability to page forward from the front
page now.

However, the NLS stuff doesn't seem to be working consistently with me.  I've
had at least 4 different languages today. Still, at least I'm learning a lot -
today is Onsdag 19 Februari in Swedish, you know! :-)
Followup:
yeah yeah, shows what happens with connection caching - we've rolled that back
until we figure out what exactly to do permanently ;)
Rating: 4
Nice new features....however, small bug? February 19, 2003
Reviewer: Martin from The Netherlands
Hi Tom,

I 'm not sure if this is a bug or intended behavior, but if you have perfomed a
search earlier and then click the asktom logo to go back to asktom home, the
search results are shown again, instead of a 'clean page' with recent questions.
Followup:
fixed
Rating: 5
Foreign Languages February 19, 2003
Reviewer: CJ from USA
I've been through several languages today, although I'm from the good 'ol USA
and only speak English. :-) I've seen French, Spanish, and a couple I don't
recognize at all.
Rating: 5
Today is Çar¿Amba ¿Ubat 19, 2003 February 19, 2003
Reviewer: A reader
Rating: 3
I liked the other one better... February 19, 2003
Reviewer: Al from Florida
Before, when I searched and paged forward (or back), the majority of the page
contained search results.  Now, the results are at the bottom of the page and
cannot be seen without additional key strokes or mouse action.  That won't stop
me from using Ask Tom but it's just not as convenient.

Thanks so much for being available with consistently good answers and advice.  I
believe that all of us who come here benefit greatly from it, I know I do.
Followup:
well, we moved the search to the top, pushed the messages to the bottom -- how
does that look now?
Rating: 4
Put the search on the bottom February 19, 2003
Reviewer: Jeremy Smith from Salt Lake City
I assume you put the search on the main page to encourage its use.  Usability
wise I think it would be better to move the search to the bottom of the page or
to the side.
1.  It gets 'lost' visually in the announcements.
2.  It allows all the changes from the 'old' version to be at the edges (title
bar and bottom) for those who are used to the old look.
3.  dead center of the page is probably the rarest placement for a search
capability (unless you're a search engine exclusively)

Also, a checkbox on the "Your Questions" that also searched for questions on
which we have commented would be cool.
Followup:
how about the top?

I agree, dead center was probably "inappropriate" ;)

as for the your questions to find reviews -- sorry, but I don't keep an email
there.  To find your questions you've reviewed -- search for your name "Jeremy
Smith"
Rating: 5
Like the new features....... February 19, 2003
Reviewer: A Reader from UK
....looks like the Your Questions will be useful - would it be possible to
update that in future to include reviews of others questions ?  Sometimes
(although we shouldn't) we ask a question via the reviews (it might be 100%
related to the question already asked) and this wouldn't allow us to track that
currently.

Damn users...always want more !!!
Followup:
See above -- if you use your NAME (or something unique in the name field), you
can find them...

I don't get an email address in the followups...
Rating: 5
February 19, 2003
Reviewer: A reader
Congratulations :) nice
Rating: 4
You ask for an email in the form for the follow-ups. February 19, 2003
Reviewer: Jeremy Smith from Salt Lake City, UT
Do you just "black-hole" it?
Followup:
ok, if you want to point out the blindingly obvious to me :)

Yes, it is there after all (just forgot about it, i don't use the review screen
too often myself you see)....

Added -- you now get your posted questions and your reviewed questions IF You
put an email in there...
Rating: 5
several languages February 25, 2003
Reviewer: Paul Druker from Fairfax, VA USA
I clicked several times on the askTom logo, and saw several languages today as
well. Bit it's not consistent - most of the times I see English version. I'm
using IE 5.50, and I have only one language [en-us] defined.
Followup:
Hey, its a feature -- I thought I would expose everone to different languages ;)

seriously -- they are working on it, the marvel team introduced a "bug" we
didn't find since we all do english ourselves.  they are missing an alter
session -- leaving the language set by the last person.
Rating: 4
Re several languages February 25, 2003
Reviewer: Helen from UK
Tom

Ta muchly for a response on this at last.

Not improved my knowledge of foreign languages much, no idea what Pzt ¿Ub 24,
2003 is in!!
Followup:
ok, ok -- it should be fixed now!
Rating: 3
Language lesson February 25, 2003
Reviewer: Ed from Cheshire, UK
Helen,

That'll be Turkish.  Try running this for a language lesson:

BEGIN
  FOR r IN (SELECT value
              FROM v$nls_valid_values
             WHERE parameter = 'LANGUAGE'
               AND value NOT LIKE '% %'
          ORDER BY value) LOOP
      DBMS_OUTPUT.PUT_LINE (initcap(r.value)||' - '||
                            TO_CHAR(SYSDATE,'fmDay DD
Monthfm','NLS_DATE_LANGUAGE='||r.value));
  END LOOP;
END;
Rating: 3
Yo, Helen, what is this ??? February 25, 2003
Reviewer: robert from PA
>> Ta muchly for a response on this at last.

And I thought you still speak English there in the UK.
Boy, things change so fast....
Rating: 3
Yo ??? February 26, 2003
Reviewer: Adrian from UK
Robert

You can't take the moral highground over "ta muchly" when you start your
campaign with "Yo"... ;)

Adrian
Rating: 5
Number of records found in search February 26, 2003
Reviewer: Pratap from India
Hi Tom

I searched for dbms_trace. There are total 11 records. The page displays -

{dbms_trace} Approximately 17 records found. Because you are asking intermedia
for the approximate count.

But look at the combo below the search results - It shows -

1-10 of 11. There are exactly 11 records. Now how is it able to do that? Are you
again asking for the exact count?

Thanks

Pratap
Followup:
it is a bug that it is temporarily working that way.  it won't be soon i hope.
Rating: 4
search box on every page February 27, 2003
Reviewer: A reader
would like search box on every page
Rating: 4
Thanks for adding new features on Ask Tom Site February 27, 2003
Reviewer: Pauline from NY,U.S.A.
I like one of the new feature which is I can retrieve my own questions by click
'Your Question'. Thanks for you putting the efforts on it.
Rating: 1
please send me A book of oracle February 28, 2003
Reviewer: shaukat from pakistan
this is not enough to understand so please send me A n oracle boook thank you
my postal address
shaukat ali otho
A/59 Abdullah town qasimabad
hyderabad sindh
pakistan
Followup:
sure -- i'll get right on that -- not.

Goto
http://otn.oracle.com/
click on documentation.  read away.
Rating: 5
I could use a library myself February 28, 2003
Reviewer: Simon Holt from Dublin, Ireland
Tom, a lot of my books on Oracle are a little out of date now, and I can't be
bothered to use the net or do any research, or send myself on training courses.

Whilst you are at it for the gentleman above, can you send me a load of 9i books
and anything on 10i that you might have kicking around?

Thanks - it's really appreciated ;-)
Rating: 5
Can you do that? February 28, 2003
Reviewer: A Reader
Tom,
If you can move the "Was this response helpful to you? Let us know!" to the end
of the last post, not the end of your first answer, it would save readers some
time to go up and find the "Let us know", especially when there are a lot of
discussions.  Most "let you know" are related to the latest post.
Thank you.
Followup:
already done
Rating: 5
Please include me as well February 28, 2003
Reviewer: Joel from Columbus, OH USA
Tom, since it appears you're going to have the TKyte shipping department busy,
could you also include me on your list?  I would appreciate a full 9i
documentation set, a copy of your recent book, and any additional copies of TV
Guide or ESPN magazine sitting around your house.

Thanks in advance!
Rating: 5
Me tooooooooooooo February 28, 2003
Reviewer: Alex from New York, NY USA
Tom,

Can you send to me the same things that you will send to Joel( ESPN magazine is
a nice touch ) but it will be really nice of you if you can ask Larry E. to send
me couple of Oracle shares.
Thanks in advance ! :-)
Rating: 4
Return button February 28, 2003
Reviewer: Holger Baer from Germany
Tom,

I definitly like the new askTom. Especially the little return button that
appears when reviewing my questions is a nifty feature! (not that there are that
many so far) ;-)

It would be nice to have it on the other pages, too. Of course, there's always
the 'Back' button from the browser, but then, someone put that button in for a
purpose...

Holger
Rating: 4
Its cool ,but One Request February 28, 2003
Reviewer: Ashiq Shamsudeen from Trichy , TamilNadu ,India
New Ask Tom is damn cool .Keep it up.
  One request , U should provide us to save the questions in differenct formats
like .txt,rft or doc.Because i used to save the many important queries from ur
site to a file .For this i've select it and after pasting it in notepad ,
theni've to do some alingmetns to make it proper.
Instead of hitting your site i can get in my file itself.
    It'll be very useful for others also( i mean for enitre oracle world)if you
provide this feature .And it'll be damn good to store the archives ,like day
wise ,week wise ,and month wise .
  I sincerely believe  you will consider my request .
Followup:
well, I considered it and ruled it out.

You can cut and paste HTML directly into a word processor -- they mostly accept
it -- then the formatting is "preserved".

file save as TXT in the browser does the txt already.

So, to get to TXT --> file save
to get DOC/RFT --> cut and paste right into word
Rating: 5
Cleaning old questions March 02, 2003
Reviewer: I'm just curious
Tom, I observed that some old questions are not in your site database any more.
It is understandable, for example, that you may want to get rid of old Orcacle7
questions. It's my belief there are no more questions from 1999 backwards.

What criteria do you use to purge old questions ? Do you maintain a "window" of
current questions (say, from 2000 up till now) ?

Thanks
Followup:
It is not your belief that questions from 1999 backwards are not to be found.

asktom started in january 2000 ;)

I haven't purged a thing.  As of March 2, 2003, I have:

New, Never Read                       1
Read, Not Answered                       0
Read, Awaiting More Info              26
Read, More Info Has Been Supplied       0
Answered, Do not publish           13456
Answered, Publish                    7178
Deferred, answer later                       0

questions -- so there are 7,178 you can see.  13,456 I answered but didn't
publish.  26 waiting for more information from the askee.  and 1 in the queue I
haven't gotten to yet....
Rating: 4
search March 02, 2003
Reviewer: mo
Tom:

are you still working in the problems for search on the site.

i have a problem finding old questions/followups i posted. as search for "mo
reports server" and I can not find an article you answered for me.
Followup:
well,

mo reports server

returns 10 hits for me and

"mo reports server"

returns a hit -- so, unless you can be a little more specific (eg: show me a
document that should be returned by providing the URL and the search that
doesn't find it)  I don't know what to say....

Also -- you do know you can plug in your email and see all questions you asked
or "reviewed" right.
Rating: 3
New TOM Architecture March 03, 2003
Reviewer: Harry from India
Hi Tom!
  Are you really shipping books? I will provide you the details if you are
starting one! Can you add me? Are you using Intermedia/Oracle Text for doing the
search?
  Can you explain the search facility briefly?
Thanks
Harry
Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:350017644883

Rating: 5
Hey - Mr "I'm just curious" March 03, 2003
Reviewer: Steve from UK
Some of us are still having to work with 7.3.4 databases!!
Rating: 5
Could "article only" be placed between question title and date? March 03, 2003
Reviewer: Jukka from Finland
Thank you very much for this superb site!

Usually I read AskTom with "by the week" -view. I open interesting questions
with "Article only" links(middle click: tabbed browsing with Mozilla rocks!).
However, it's quite difficult to click on right line, because there's a lot of
space between question title and "Article only" link(at least for my eyes). I
think it would be easier, if "Article only" link was located between question
title and date field.
Followup:
check it out -- rearranged and color coded...
Rating: 5
Could "article only" be placed between question title and date? March 03, 2003
Reviewer: Jukka from Finland
Wow! I think it's about 1 hour since I proposed this, and now it's done! Looks
and works just great.

Thank You very much!
Rating: 5
search March 04, 2003
Reviewer: mo
Tom:

if you search for 'Database Free Space' which is the title for an article you
have it lists the found artivel as #24 on hte third page.  Would not you list
the exact match as #1 instead.
Followup:
we've had this discussion before.  read
http://download-west.oracle.com/docs/cd/B10501_01/text.920/a96518/ascore.htm#272
it describes the scoring algorithm.  Now, if you search for

"database free space"

see what happens.  The text, just because it is in the "subject" is no more (or
less even) relevant then if it is in the body, a review, a followup by me --
anywhere.  I index a document, the "subject" is just text in a document.
Rating: 5
"n days old" calculation March 05, 2003
Reviewer: Tony Andrews from London, UK
When searching old postings I noticed that the listing diplays something like
this:

October 24, 2001 (October 21, 2001, 3 days old)

i.e. the "days old" refers to how old the question was when it was last updated.
 Should this not be showing how long ago (from today) it was created?
Followup:
yeah, its the days between updates, i'll change that a bit soon
Rating: 5
Nice ! Excellent ease of navigation March 05, 2003
Reviewer: J from ma
But, I think it may be time to get rid of the 'LET US KNOW'!
Top 30 questions today and only 7 are in 2003. It's already March!!

I can understand the need (i get most of the questions that I have answered by
the time I page through) but I think you should limit it to the original poster
and question. Got a sub question, post a new one. Some of these have started as
one question/portion and by the last letusknow post, it has zip to do with the
original.

Then there is Mo and Makito(one wants you to do his/her work and the other
questions everything with no stats)

Dude, I can see your answering questions 365 days of the year (a geek like me)
minimum 30 (3 lots of ten like you originally set about to do) but you gotta be
up to 50 or more.

Take a break, we need you out here.
Followup:
I don't understand this part:

But, I think it may be time to get rid of the 'LET US KNOW'!
Top 30 questions today and only 7 are in 2003. It's already March!!

??
Rating: 5
Dates on search result pages March 05, 2003
Reviewer: tom from PIT PA USA
What's with the 'days old' dates on search result pages?  What date is the base
date for these?  Is this a bug or is there some sort of date calc that I am not
aware of?

(Actual screenprint of search results screen converted to text)

Search
{global} and {temporary} Approximately 179 records found.

1.  insert into Temporary tables Score(99) Fri Feb 28, 2003 (Tue May 08, 2001,
661 days old)
2.  Temporary tables (comparision with SQLServer) Score(59) Thu Sep 05, 2002
(Tue May 02, 2000, 855 days old)
3.  Creating Temporary Table Score(47) Thu Jan 16, 2003 (Mon Nov 27, 2000, 779
days old)
4.  Temporary tables and indexes Score(46) Mon Nov 12, 2001 (Thu Oct 19, 2000,
388 days old)
5.  Best way to copy From Global TEMP Table to an empty Standard Table Score(40)
Tue Oct 22, 2002 (Fri Oct 18, 2002, 3 days old)
6.  create global temporary table foo as subquery produces no rows ! Score(40)
Mon Jan 14, 2002 (Mon Jan 14, 2002, 0 days old)
7.  Global Temporary Table Score(33) Fri Jun 28, 2002 (Fri Jun 28, 2002, 0 days
old)
8.  How do I pass a CLOB value to a stored procedure? Score(33) Thu Jun 13, 2002
(Tue May 02, 2000, 771 days old)
9.  Connection pools and global temporary tables Score(26) Mon Jul 08, 2002 (Sun
Jul 07, 2002, 0 days old)
10.  pl/sql Tables Score(26) Sat Dec 08, 2001 (Sat Dec 08, 2001, 0 days old)

 ==============

(Fri Oct 18, 2002, 3 days old)
(Mon Jan 14, 2002, 0 days old)
(Fri Jun 28, 2002, 0 days old)
(Sun Jul 07, 2002, 0 days old)
(Sat Dec 08, 2001, 0 days old)

(Tue May 02, 2000, 771 days old)
    select sysdate - 771 from dual = 1/23/2001
Followup:
it was (past tense now) the difference between the time the article was first
submitted and the day of the last update -- confusing at best.

It is now the period of time from today to when the article was first submitted.
Rating: 5
Sorry to keep using the LET US KNOW but, March 05, 2003
Reviewer: j from ma
what I meant was
              (date in paran is original date)
1. New ask tom  05 Mar 2003 (19 Feb 2003) 2 weeks old

and at the time I checked in today
posted questions
1. to  30. had only 7 new ones
(the rest were older questions with reviews that were new questions)
Rating: 3
Joel on Building Communities with Software March 05, 2003
Reviewer: A reader
A bit off topic but interesting reading.

Joel Spolsky writes how forum software encourages or  discourages certain kind
of behaviour with design.
http://www.joelonsoftware.com/articles/BuildingCommunitieswithSo.html
Rating: 5
book mark March 06, 2003
Reviewer: A reader
when I try to book mark the page, It marks with the question id instead of
subject ??
Followup:
try it now ;)

it'll be the first 40 characters of the subject now.  good idea.
Rating: 5
Congrats... March 06, 2003
Reviewer: Spawn from Philippines
Congratulations Tom on your very successful site. I think everythings ironed up.
A little more time and everything will be perfect. I congratulate you and your
team for this.

And by the way, since your shipping books, why dont you have your own shipping
company and post orders here in your site. (just kidding). I just cant believe
that people are really thinking that you'd send them books.

Congrats once more. More power.
Rating: 5
Very Cool ...... March 06, 2003
Reviewer: VL from HK
Hi Tom,

I'm interested at the part "Your Questions", how can u implement it? track the
same IP and show question histories?

Rgs,
VL
Followup:
it is done by email address.

When you ask a question, I get your email (if I don't, you lose, no answer for
you).

When you add a review, you have the opportunity to put in your email address.

If you do, I can find "your questions".

If you do not, I cannot.

the ip address doesn't work -- firewalls make many people look "the same"
Rating: 5
book mark March 07, 2003
Reviewer: v from NY,NY
Thanks for the book mark Tom :)
Rating: 5
Dates again March 10, 2003
Reviewer: A Reader from Carmen, San Diego
The last update date (the last date *you* responded, not a reader) is also quite
useful, sometimes.  You seem to have put both this date and the date created in
columns - at least that's what I think the first date is.

Can you please label the columns?  And, if you wish, put a delimiter between the
two dates?  Thanks

Followup:
it was (past tense now) the difference between the time the article was first
submitted and the day of the last update -- confusing at best.

It is now the period of time from today to when the article was first submitted.
Rating: 5
Subject width March 10, 2003
Reviewer: Scott from Perth, Western Australia
G'day

For the review regarding subject column width on the "Recent" page (re: recent q
with looong subject)...
ditto on the truncation for the "Your Questions"... long subjects rather skew
the page to be not as readable.

Thanks.
Followup:
done.
Rating: 5
Ability to search just the subject or body or both March 11, 2003
Reviewer: Logan Palanisamy from Santa Clara, CA USA
Tom,

It will be nice if there is way to restrict the scope of the search like
 - The subject headings only
 - The body of the message only
 - or both

The default could be both.
Rating: 5
IE "Refresh" button March 12, 2003
Reviewer: Tony Andrews from London, UK
I'm not sure if this is an issue with AskTom or with IE, but I find that this
happens:
1) I go to AskTom using Favorites
2) AskTom is displayed from cache, i.e. may be stale
3) I press Refresh
4) AskTom is refreshed, but now applying the search criteria from last time I
performed a search.  I then have to clear the seach box and re-query.
Followup:
I guess -- don't use refresh.  Just click on my icon on the top of the page.
Rating: 5
Alt text on recent article links March 12, 2003
Reviewer: Howard Hammermann from Arlington, VA USA
I like the alt text that pops up for the links to recent articles. It's nice to
get a quick look a the question/response without having to follow the link. You
might want to try it with a longer substring.
Rating: 3
timeout March 12, 2003
Reviewer: A reader
1.

Hi Tom, I was writing a question, it took some time, while I'm working now, but
when I tried to send I got

Cannot accept new questions at this time. Please try again later.

I think there should be a timeout, this is once you are writing you question,
and don't sumbmit the review in 20 minutes, then if there are not space for
asking question then it is rejected.
But otherwise no, or I'm wrong?

2.

I think it could be a good idea to put a cookie that save the name, email, and
where in the world you are, because, I had seen that many people including me,
fill it the first times, but after sometimes no.

Thanks :)
Rating: 3
An error? March 14, 2003
Reviewer: Joe
I received following error on the home page :)
report error:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
Rating: 3
March 18, 2003
Reviewer: A reader
Congratulations for you new advanced search.

if you need some ideas, a good idea can be, to login, this gives more
seriousness.

and record of the questions you read, to avoid reading again.

A qualification ( 1-5 ) to the questions, but from you.

( like metalink )

the posibility to add a category, because some topics are really dificult to
find, because you search a word that is used in almost every article. ( import,
export, materialized views, etc. )

Thanks :)
Followup:
to login implies "hey tom, i forgot my password, what is it", more email, more
complexity.

I rather like the anonymous nature of it all actually.
Rating: 5
"Advanced Search" Really rocks. Great!!!! March 19, 2003
Reviewer: Logan Palanisamy from Santa Clara, CA USA
Rating: 3
include in review database version March 20, 2003
Reviewer: A reader
Hi Tom, I think additionaly to the name, email and where are you, user should
add the database version, because now people to a question about database 8.1.7
add question about database 9.x or 7.x.

Maybe you should not accept a review without all that data entered.
Rating: 3
Search rows - results mismatch March 20, 2003
Reviewer: Sameer
Tom: I searched for the word "stragg" and the result said "{stragg}
Approximately 11 records found." -- but only 1-6 rows are displayed.

Why is this mismatch ?
Followup:
Main Entry: 1ap·prox·i·mate
Pronunciation: &-'präk-s&-m&t
Function: adjective
Etymology: Late Latin approximatus, past participle of approximare to come near,
from Latin ad- + proximare to come near -- more at PROXIMATE
Date: 15th century
1 : located close together <approximate leaves>
2 : nearly correct or exact <an approximate solution>
- ap·prox·i·mate·ly adverb

I look at www.google.com as the worldwide and shining example of the correct way
to do these things.  I mean -- they got it right.  They do not count the exact
hits, they approximate.

goto www.google.com and enter:

polly wolly doodle all day oracle

At the top, on March 20, 2003 I saw:

Results 1 - 9 of about 11. Search took 0.12 seconds.

1-9 of about 11, hmmm -- they guesstimate too ;)
Rating: 1
new ask tom March 20, 2003
Reviewer: A reader from learner
plze the link for new asktom
Followup:
hmm, your already here -- it was just an upgrade.

the link is, was, has been, will be

asktom.oracle.com
Rating: 5
thanks March 21, 2003
Reviewer: A reader
LOL, thanks
Rating: 5
April 01, 2003
Reviewer: A reader
whay are we not using the horizontal place to the
asktom logo ?
Rating: 5
Problem with Search? April 03, 2003
Reviewer: Sri from NJ
Dear Tom,

  Something strange happening with search.  I had reviewed and followed up a
topic "Error in creating .dat with SQL", I infact placed a question for you
there.  Later, When I wanted to see whether you have responded or not I put the
string "Error in creating .dat with SQL" (with that double quotes) in that
search text box and hit 'Go'.  It responded with "{error in creating .dat with
sql} Approximately 1 records found.", but I am not able to see the record that
it found.  I even tried Advanced Search, but no luck.  How can I see your
response now Tom?
Followup:
got it - but in your case, you could have gone to the "your questions tab" as
well...
Rating: 3
Adding the release to the Review April 03, 2003
Reviewer: A reader
Several times the original post is for example for Oracle 8.1.7 and there are
several reviews, but not all uses 8.1.7,
it could be useful to ask this information in the review, when the reviewer is
not who post the original post.
Rating: 5
April 07, 2003
Reviewer: A reader
Only a idea, sometimes could be interesting to set underline, italic or bold
some words.

How about emoticons. you type :( and a sad icon appears. It helps to express.
Rating: 3
adding bookmarks April 25, 2003
Reviewer: A reader
Hi Tom,
when bookmarking a page, by default it could be a good idea to put the name of
the question
thanks
Followup:
it does doesn't it?  not sure what you mean?
Rating: 5
enlarging the text box horizontally ? April 25, 2003
Reviewer: Alberto Dell'Era from Milan, Italy
Hi Tom,

what about enlarging horizontally the box that we can use to enter the text of
the question, to match the actual width of the screen (perhaps moving away the
"Enter you review" text that is currently sitting on the left) ?

This may help in trying to obtain a Good Look for the question, without jumping
forward and backward by the "Preview Review" and "Edit Review" button ...
especially for sqlplus output.

Today I missed a Question Round because, after jumping around, when i hit the
"Submit review" the site kicked me away (I was the 11th, probably).

Besides, you know how much we Italians love to have a Good Look ;-)

Bye
Alberto
Rating: 5
What is Bookmark supposed to do? April 25, 2003
Reviewer: A reader
This probably seems like a dumb question, but what is the effect of hitting the
Bookmark this page with the link HERE, supposed to do? When I click it nothing
seems to happen, it just redraws the current page. Is there a my Bookmarks
somewhere that I am not finding? I use MS IE 6.0.

Thanks
Followup:
Look at the URL.

If you bookmark the "big" url - it has a sessionid in it.  Eventually, that link
will cease to function and will just take you to the home page.  The "bookmark
this page" link gives you a link that is stable enough to bookmark.
Rating: 4
back ground color for search word April 25, 2003
Reviewer: A reader from kuwait
hi tom

before when i submit a search for word it appeare in defrent background color in
the article search result but now the search word is not high lited with
background color
Followup:
that was a lllooonggg time ago -- the hilight function was not "url" friendly --
it broke more pages then it worked on so I took it out.
Rating: 3
not approximate records found in advanced search May 07, 2003
Reviewer: A reader
Hi Tom,
there is no no approximate records found in advanced search
I don't know if this is wrong or if this had been always in this way.
Followup:
it was never there.
Rating: 5
:) Thanks May 07, 2003
Reviewer: juancarlosreyesp@yahoo.com
Rating: 4
hope you " DO NOT REMOVE ANYTHING " from your database May 07, 2003
Reviewer: UMESH from bangalore india
Rating: 4
DBA May 08, 2003
Reviewer: Minnie from USA
I have difficult to bookmark. After bookmark, where could I find all I've
bookmarked?
Followup:
i don't know do you use the "bookmark" link I have?

what that does it takes you to a URL you can then bookmark with your browser.
If you try to bookmark a normal page (which includes a session), when the
session expires -- poof, there goes that page.

so you click on my link and then you can safely bookmark that link.
Rating: 5
"hidden" folders May 12, 2003
Reviewer: A reader
Hi Tom
Where you can see a list of the "hidden" folder you have like
http://asktom.oracle.com/~sdillon/
thanks
Followup:
there are no "hidden" folders.

That is Sean Dillons home directory.  There is a ~tkyte where I keep some stuff
as well.
Rating: 5
May 12, 2003
Reviewer: A reader
thanks
Rating: 5
export of all the articals May 13, 2003
Reviewer: A reader
Hi tom,

 1.) I think you store all the article in a table
 if this is true can you give us an export
of this table so that I can import it in my local laptop
oracle db and I can read it/browse/search it off line ?
Followup:
nope, but you can goto the archives -- download an entire week and read that
anytime you want.

Every week is there.
Rating: 5
Downloading the Archive works !!!! May 13, 2003
Reviewer: A reader
Yup. The download works. Each week's archive comes to about 1 to 2MB.

I am planning to download all the weekly archives and burn it into a CD and take
"asktom" wherever I go. I need not have an internet connection to look for a tip
or something.

With this asktom CD and Oracle documentation CD, you are kind of covered.

"Asktom to go". That is the way to go.
Rating: 3
do I have to down load everything one by one May 14, 2003
Reviewer: A reader
Followup:
every week, yes.

there are over 7,500 articles right now and growing.  I publish by the week in
the archives.  I am not going to put it out in any other sort of format.  by
week is pretty big already.
Rating: 5
How to change the URL for index to local path if save the weekly archive to a HTM file May 14, 2003
Reviewer: Minnie from MA, USA
Hi Tom,
I just want to say "Thank you for your wonderful work here'.
I don't want you to answer my following question because I know you are too busy
for other important questions and it's not worth to waste your time.

Hi, a Reader upstair or anybody else,
I like your idea about burning CD. But, if the weekly archive is save to a html
file, it's hard to locate one article because the link to it still points to
asktom.oracle.com. Does anybody have any idea?
Rating: 4
a book on ask tom May 14, 2003
Reviewer: Reader from houston, TX
Have you thought about publishing a book on tips to resolve issues based on
asktom questions and answers? I know your book addresses some of them especially
"why my indexes are not used?" however, you handle so many issues day by day and
it would be helpful to publish a paper back with the best tips for oracle users
on most common issues. I will be the first one to buy!
Rating: 5
open links in new window May 14, 2003
Reviewer: vijay from MI.USA
hi: tom,

how about having the feature of opening the search result links in new window.
would ease viewing.

showing all results on a page.

regards

vijay
Followup:
ctl-click

I hate it when web sites decide for me what I should be doing as far as windows
go.  Me, i use mozilla, it is all about tabbed browsing.  I use a single window
- no more, no less.  I have 15-20 tabs open maybe not 15-20 windows.

I would HATE it if I opened a window/link.

You can shift or ctl-click as you see fit.  It is what I do.
Rating: 5
How to search on the downloaded weekly archive May 14, 2003
Reviewer: A reader
Hi "Minnie  from MA, USA"

If you click on the html link in the downloaded weekly archive file, it will
come up with 'the page can not be displayed' error. But if you search on the
Subject using the browser's "Edit -> Find", it will take you to the text below
in the same html file.

Also, I copied all the weekly archives to a folder and searched using Windows
File Explorer's "Search" (Ctrl + F) to locate all the files containing a
particular text.

Of course, it is not as robust as the "Advanced Search" on Tom's web-site. But
it does work.
Rating: 5
Tool bar at the bottom May 14, 2003
Reviewer: Logan Palanisamy from Santa Clara, CA USA
Tom,

It will be good to have the tool bar "Recent | Archives | Advanced Serach | .. |
.. | Question Review" at the bottom of some of the lengthy threads, right below
where it says

    "Was this response helpful to you? Let us know!"
    "Bookmark this page with the link HERE"

That way we don't have to scroll up to the tool bar at the top to navigate.

No doubt, your site is a gold-mine.
Followup:
Just hit "home"?
Rating: 5
This is helpful May 15, 2003
Reviewer: Minnie from MA, USA
"A reader",
Thank you very much
Rating: 4
How it archived? May 15, 2003
Reviewer: A reader from USA
Hi Tom,
1. How did you put the article to weekly archives? According to Original Date or
latest updated date?
I could not find this article that is original created in Feb 19,2003 in the
weekly archive of Feb 19 2003. I tried to find some other articles in archives
according to their original date but failed.
2. the suggestion of Logan Palanisamy is very good, I think.
If hint the "Home", one still needs to scroll up to the top of the page because
HOME is also on the top.

Thanks in advance,
Followup:
1) last update

2) Hint "home" meant hit the "home" key on your keyboard.

end -> end of page
home -> top of page

so, do the little "top" links on the page do it for you?
Rating: 5
Security check! May 15, 2003
Reviewer: Mark J. Bobak from Ann Arbor, MI
Hey Tom,

Looks like you made some changes so that folks who try
to "save" the "Submit a Question" button can no longer do so?

Some new security check in place?  I suppose it's more fair
that way.

-Mark
Rating: 5
I did not understand May 16, 2003
Reviewer: A reader
I did not understand the purpose of you "top" link. I click on it but nothing
seems to happen. Could you explain ? I'm using IE 6.
Followup:
it should take you to the top of the page. the link has #TOP in it (if you hover
over it, you should see it).

At the top of my page, I have a TOP anchor.

If you are already at the top -- it will appear to do nothing (other then add
#TOP to the URL)

Followup Part 2
----------------
I fixed the issue with the top link not working in IE6. For some reason using an
anchor named "TOP" did not work, whereas renaming it to PAGETOP worked fine...
(insert Microsoft dig here)

Thanks for the feedback,
A "Friend of Tom"
Rating: 2
Strange... May 16, 2003
Reviewer: Same as above
I see "top" works in this IE5 browser I'm using right now, but didn't work with
my IE6 at work...
Followup:
sorry, I only test with open systems browsers, not ones limited to a single
operating system ;)

Seriously though -- it is just standard HTML, if it doesn't work in IE6, that
would be a definite "bug" -- but I don't have access to IE6 on any computer I
own to test with.

Followup Part 2 (repost)
------------------------
I fixed the issue with the top link not working in IE6. For some reason using an
anchor named "TOP" did not work, whereas renaming it to PAGETOP worked fine...
(insert Microsoft dig here)

Thanks for the feedback,
A "Friend of Tom"
Rating: 5
I like top..how about a 'latest' link? May 17, 2003
Reviewer: Paul from Ottawa, ON, Canada
I like the top idea, I find I scroll down far more than up, would it be
difficult to a a latest link to jump to your most recent comments on a thread?
Rating: 5
Change Request May 18, 2003
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
Dear TOM I use my own machine to browse ASKTOM so in case of any review or new
post I have to enter Name, Email and Where in the world are you?: again and
again. Is there any way that if once I entered should be stored in the cookies
so next time the default values will be picked. This request is as you are
improving it these days by adding new features. I hope you will not mind for
this request.
Rating: 5
A suggestion May 19, 2003
Reviewer: David
I like very much the idea of going to the latest review. However, I think this
should be located together with the "top" and "bookmark" links with every
review. You can call it "bottom" -- making it intuitive and concise and
contrasting with "top".

It would be better then, since it would be accessible everywhere on the page,
not only on the top, where you are trying to put it.

Also, I would suggest you put the "bookmark this page" and "let us know" with
them too, making it easier to access from anywhere. After all, a review can
relate to the original question or to other reviews and followups. One can get
lost when trying to find them on the bottom.

Greetings
Followup:
Ask and you shall receive (kind of).  I added the bottom link because I think it
makes sense.  We are up to 3 links on that bar now, anymore would "muddy the
waters" too much, so I am going to leave it at that for now and wait for more
feedback from other users.

Thanks for your feedback,
A "Friend of Tom"
Rating: 5
One More Link on the bar May 19, 2003
Reviewer: Ganesh Raja from Singapore
Hi,

Can your also Put a Back to Questions Link there so that when we hit the back
button on the browser we dont need to go thru all the links that we clicked on
that page. ;)

Lazzzzzzzzzzzzzzzzzy.....

Thanks.

Regards,
Ganesh R
Rating: 4
Why 'Do Not Publish' ? May 20, 2003
Reviewer: Ashiq Shamsudeen from Trichy, Tamil Nadu, India
Hi Tom,

 just for a curious , from your followup
<
Answered, Do not publish           13456
>

 Why you didn't published (though you've answered) these questions.Is any reason
for this .Or you believe these questions doesn't help others??
Followup:
and as of today, the numbers are:

Status     Count
New, Never Read     15
Read, Not Answered     0
Read, Awaiting More Info     19
Read, More Info Has Been Supplied     4
Answered, Do not publish     14209
Answered, Publish     7455

basically, for every one you see, there are two more you don't.  Many are of the
form "hey, my database is slow, why?" -- my answer is along the lines of "hey,
my car won't start -- why?"

or they ask the same question that has been asked -- and I just ask them "well,
when I searched for <their subject> I found N hits -- what happened when you did
that search?"

or they ask an in depth question about some tool I don't use -- and I send them
to technet
Rating: 4
why ? May 20, 2003
Reviewer: robert from PA
why is it that asktom never has a link to orace.com ?
Followup:
because i don't know what orace.com is...

but if you mean www.oracle.com -- then a simple search for www.oracle.com on
asktom.oracle.com returns  lots of hits.  I reference it all of the time.
Rating: 5
small feature request August 02, 2003
Reviewer: Alberto Dell'Era from Milan, Italy
Why not sending an automatic e-mail confirmation when a new question is
submitted (or, better, making it appear under the "Your Questions" tab having a
"new" status) ?

I've just submitted a new question and i'm not sure whether it has been accepted
by the server; say the Internet Sea Monster has eaten it ...
Followup:
I've asked the guys who work on it to make it more obvious that "yes, we got
your answer", thanks.

addition to the addition:

and they very quickly did.  when you submit a question now, you are directed to
a page saying "yup, question is in there and tom will be looking at it"
Rating: 5
My Questions August 02, 2003
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
The tab labeled "Your Questions" should be "My Questions". As when I click on
this tab so I am going to view my questions.
Followup:
think of it from MY perspective.

They are YOUR questions ;)

They are not 'my' questions.  It is me speaking to you.
Rating: 5
Default values August 10, 2003
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
On the new thread and on reply screens we have to enter name, email and location
everytime. Is there any way to set my default values as I am only using my
machine. So I should enter this info again and again.
Rating: 5
One suggestion August 22, 2003
Reviewer: A reader
For a new question I get an opportunity to be sent an email
every time you follow up. Can the same be done for
any page. Say if I review a thread and ask a question
and want to be notified as soon as you follow up.

WIll this be too much overhead on the system?

Thanx - LOVE YOUR SITE!!!

Menon:)
Followup:
we'll keep it in mind.  not the first request for it.

but, it bumps into the other request for "i want to know about updates of this
question for the next N weeks" insead of "forever"

maybe we can do both sometime soon...
Rating: 5
Re: One suggestion August 22, 2003
Reviewer: A reader
oops - i guess I forgot about the fact that you don't
know about our emails. You could get it registered
through another link on all pages though?
Too much load though methinks!

Menon:)
Followup:
when you followup, you can supply that.
Rating: 5
yet another small feature request September 07, 2003
Reviewer: Alberto Dell'Era from Milan, Italy
I'm not sure if i'm asking the same as Sikandar Hayat above, but when I submit a
"review" by clicking on the link "Was this response helpful to you ? Let us
know!" at the bottom of every  page, I have to reenter name, email, and location
each time, while when I ask a new question, the same infos are pre-loaded
automatically in the form. What about extending this feature to the review form?

PS My thanks to the developers for having implemented my previous "small feature
request" as per August 02, 2003.
Rating: 5
fyi - caption of your new book on home page ... September 08, 2003
Reviewer: Alberto Dell'Era from Milan, Italy
... still reads "new book coming soon".

That may distract junior asktommers ;-)
Followup:
thanks, got it.
Rating: 5
Something for the future perhaps? September 12, 2003
Reviewer: A reader
Love your site! However, I was thinking what would be nice is "next" and
"previous" buttons as you're reading messages, instead of having to go back to
the list of messages to select one. Hope I'm being clear.

Thanks for this great site.
Rating: 4
Max Length of Message September 18, 2003
Reviewer: Robert from ct
Tom,
what is the max on messages to/from us ?
Is each single message stored in a single row in a single column (cell) typed
LONG ?

Thanks
Followup:
the original question is upto a 32k LOB...
the answer is a 32k lob as well...

each followup is a 32k lob
each of my followups to a followup is a 32k lob

the structure is a little like this:

create table questions ( id primary key, ...., question lob, answer lob );
create table followups ( id primary key, question_id references questions, text
lob );
create table toms_followup( id primary key, followup_id references followups,
text lob );
Rating: 4
32K limit September 18, 2003
Reviewer: robert from CT
>> upto a 32k LOB...

That's because of PLSQL's limitation, right ?
Hmm..I wonder what happens if one enters more than that...
you got a "Hey! you entered too much data !" error page ?

Thanks
Followup:
no, you get a "request failed" sort of page :)

if someone puts in more then 32k -- they are probably posting an entire
statspack report or trace file -- i don't want that!
Rating: 3
32k... September 18, 2003
Reviewer: Gary from Sydney
Hey, we've got a view that's more than 32K.

[20 queries UNIONed together, 'cos we don't have CREATE TYPE privilege to turn
col1a,col1b,col2a,col2b,col3a,col3b...
into multiple rows of
col1a, col1b,
col2a, col2b
]
Followup:
can you explain further -- why do you need a create type to avoid a union?

you just need a JOIN????

take the query:

select col1a, col1b, col2a, col2b, ....., colNa, colNb
  from ......

Call that "Q"..  then:

select decode( r, 1, col1a, 2, col2a, 3, col3a, ..., N, colNa ) colA,
       decode( r, 1, col1b, 2, col2b, 3, col3b, ..., N, colNb ) colB
  from ( Q ),
       ( select rownum R from all_objects where rownum <= N );

??
Rating: 4
Large view September 22, 2003
Reviewer: Gary from Syndey, Australia
Doing the object route (as below) runs significantly faster than the
decode(rownm...).
If we are rewriting the code, we want to switch to the faster solution, or get
the DBAs to come up with a reason why we shouldn't.

create or replace type fred is object
  (col_1 char(1) ,col_2 varchar2(26))

create type fred_tab as table of fred

CREATE OR REPLACE VIEW freddo as
SELECT tab_1.colx, tab_2.coly, tab_3.colz
       ,t2.*
  FROM
       tab_1, tab_2, tab_3,
    ,TABLE(fred_tab(fred(tab_1.col_1a,tab_1.col_2a),
                  fred(tab_1.col_1b,tab_1.col_2b),
                  fred(tab_1.col_1c,tab_1.col_2c),
...
Rating: 5
GET IP ADDRESS FOR THE HOST COMPUTER September 24, 2003
Reviewer: Nihad Saleh from Sana'a, Yemen
HI TOM >>

HOW I CAN GET THE IP ADDRESS OF THE HOST COMPUTER ? OR
IS THERE ANY WAY TO KNOW THE IP ADDRESS OF THE HOST COMPUTER ?
Followup:
host computer?  is that the server?  you can get the hostname from v$instance
and then use utl_inaddr to convert that.

is that the client? then sys_context( 'userenv', 'ip_address' ) gets that.
Rating: 5
is there any way else September 25, 2003
Reviewer: Nihad Saleh from Sana'a, Yemen
HI TOM
THANK YOU FOR YOUR ANSWER AND IT IS WORK , BUT I WNAT TO KNOW IF THERE IS ANY
WAY TO KNOW IP ADDRESS FOR CLIENT COMPUTER BY DEVELOPER , OR YOU CAN GIVE ME
SOMETHING TO HELP ME . THANK YOU AGAIN
Followup:
sys_context( 'userenv', 'ip_address' )

gives you the ip address of the client -- with webforms that is the app server.

if you want the terminal ip address of the terminal where the gui is -- you'll
have to pop on over to otn.oracle.com and get into the discussion forums there.
I haven't used forms since about 1995 myself (way before webforms) and don't
know if you can get it or not (or in the days of dhcp, nat, etc etc of what real
use it would be)
Rating: 5
how i use again document file stored in database ...?!! October 03, 2003
Reviewer: Nihad Saleh from San'a, Yemen
hi Tom
thank you for tell me how to save document file in database and it's work , but
i don't know how i can restore it from database agin ..
let me explain the problem for you , i have a document file had stored on
c:\nihad.doc and i save it  in database. now the file had deleted from c:\
"partion", now i want to restore it from database to use it again , how i can
restore it to c:\ partion again, please explain your answer for me step by step
if you can , and thank you again
Followup:
you have to write a client application.  There is no "unload to filesystem"
option for blobs.

If you have my book "Expert One On One Oracle" -- i have a pro*c external
procedure example in there that will unload to a directory on the DATABASE
server (never the client)

You could do the same with a java stored procedure on the DATABASE server.

if you want it on the client -- whatever client you are using will have to
retrieve it.

If you are using mod_plsql, you can use filedownload to do it easily, you would
just return a mime type and lob locator to the mod_plsql module and it'll stream
it to the client.
Rating: 5
where are you ? October 04, 2003
Reviewer: Nihad Saleh from San'a,Yemen
hi Tom
where are ?!!!! why you don't answer me , but i have to know how i restore
document file from database to my hard disk again, please if you can ... answer
me ok.
Followup:
geez, give me a break.

you do know that there really is "me", one person?

I've got a life sometimes too.
Rating: 3
Yo, chill out...Mr Nihad Saleh October 06, 2003
Reviewer: King of Yemen from CT
Dude, what are you ? Prince Nihad of the kingdom of Yemen ? (giggle..) in case
you forgot, but this is a FREE forum.
That means NOONE is obligated to ask or answer a question.
But maybe you're just used to have your own army of advisors & consultants....

We may be living in a savage & crazy world but let's keep this little corner of
the web civilized & professional, OK ?
Rating: 5
who are you Mr ... Savage ? October 17, 2003
Reviewer: Nihad Saleh from Sana'a, Yemen
hi man..
   who are you Mr Savage >? but i want to tell you i'm not a Prince of Republic
of Yemen  .. but thank you man ? but i want ask you why you say that for me ..?
can you answer me .
any way .. thank you again Man >>>
Rating: 5
how i can find solution in minimume statment? October 23, 2003
Reviewer: Esam Ebrahim from Amman,Jordan
hi tom

   i have two tables , the relation ship between them as the following
tableone A  (                    tabletwo B(
no number         not null,      no number not null,
dep number        not null,      name varchar2(60),
kind number,                     dep number not null)
age number)
assume we have these tables ok.
table b is the master table and assume we have in b this records
1   tom  1
1   esam 1
and assume table include this records
1 1 1 30
1 1 2 25
now , i want to select from B the name and from A all data but the records have
kind =1 and note that relation ship between two tables
Followup:
umm, simple join... very straightforward.
Rating: 5
How I can find relation ship ? October 25, 2003
Reviewer: Nihad Saleh from San'a, Yemen
hi Tom
  i want to write function takes two parameters and return varchar2 , this
function must find relation ship between two tables(it is the parameters ) and
the result must be like that :
 table1.column_name = table2.column_name.
now , how i can find this relation ship ?
Followup:
query the data dictionary -- user_constraints, user_cons_columns.

I hope this isn't for some generic routine to construct queries on the fly is
it?
Rating: 5
It is for report builder? October 25, 2003
Reviewer: Nihad Saleh from San'a, Yemen
Hi Tom
   thank you , but i ask if you can help me to write the function return
relation ship between two tables , i know about dba_constrints, or
user_constraints but i want if you can help me to write this function in simple
way .. i need this function for building dynamic report using the forms builder
, user can choose the tables and columns which want to display in his report , i
will make the sql and relation ship and where clause for his choice and then
execute the sql and return the result to the txt file or html file or pass this
sql to the report builder ,
this form is ready now, but it is still this function ,
 can you help me .. ?
Followup:
You can start with this query and modify it as you see fit.  Bear in mind is
makes the assumption:

a child is joined to a parent via the PRIMARY KEY and never just a unique
constraint -- although that is not always the case.  Up to you to take this
further if you see fit

ops$tkyte@ORA920> create table p ( x int, y int, primary key(x,y) );

Table created.

ops$tkyte@ORA920> create table c1 ( a int, b int, foreign key (a,b) references p
);

Table created.

ops$tkyte@ORA920> create table c2 ( m int, n int, foreign key (m,n) references p
);

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> variable child varchar2(30)
ops$tkyte@ORA920> variable parent varchar2(30)
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec :child := 'C2'; :parent := 'P';

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> column fkey format a80 word_wrapped
ops$tkyte@ORA920> select decode( parent.position, 1, 'where ', ' and ' ) ||
parent.cname || ' = ' || child.cname
  2  from
  3  ( select '"' || a.table_name || '"."' || column_name || '"' cname,
  4           position
  5      from user_cons_columns a, user_constraints b
  6     where a.constraint_name = b.constraint_name
  7       and b.constraint_type = 'R'
  8           and a.table_name = :child ) child,
  9  ( select '"' || a.table_name || '"."' || column_name || '"' cname,
 10           position
 11      from user_cons_columns a, user_constraints b
 12     where a.constraint_name = b.constraint_name
 13       and b.constraint_type = 'P'
 14           and a.table_name = :parent ) parent
 15     where child.position = parent.position
 16     order by parent.position
 17  /

DECODE(PARENT.POSITION,1,'WHERE','AND')||PARENT.CNAME||'='||CHILD.CNAME
---------------------------------------------------------------------------------
--------------------------------------------------
where "P"."X" = "C2"."M"
 and "P"."Y" = "C2"."N"
Rating: 5
links problem October 26, 2003
Reviewer: A reader
Tom
the links in oracle documentation have moved considerably
disabling the links in many pages. Would it be a good idea
to use another level of indirection for the links that
get generated (they get generated dynamically.) That
way you have the info at one place and can automatically
update them when somethings like this happens.
Not sure how complicated your system gets if you do this.
Rating: 5
It is work October 29, 2003
Reviewer: Nihad Saleh from Sana'a, Yemen
Hi Tom
  Thank you for your solution and it is work , but if the table ( master table )
has more than relation ship with many tables it does not work,and it's return
all relation ship with master table but I added sqlstatment to your answer like
this (
select DISTINCT decode( parent.position, 1, ' ', ' and ' ) ||
                parent.cname || ' = ' || child.cname RELATION
                 from

                ( select  a.table_name || '.' || column_name  cname,position
                 from user_cons_columns a, user_constraints b
                 where a.constraint_name = b.constraint_name
                 and  b.constraint_type = 'R'
                 and a.table_name = child AND
                 R_CONSTRAINT_NAME IN (  -- TO GET FIELD WHICH JOIN WHIT MASTER
TABLE
                                        SELECT CONSTRAINT_NAME FROM
USER_CONSTRAINTS
                                        WHERE TABLE_NAME=PARENT AND
                                        CONSTRAINT_TYPE='P') ) child,

                 (select  a.table_name || '.' || column_name  cname,position
                  from user_cons_columns a, user_constraints b
                  where a.constraint_name = b.constraint_name
                  and b.constraint_type = 'P'
                  and b.table_name = parent ) parent
                  where child.position = parent.position ;
) and it is working now , Thank you again and it is mazing idea from you ...
Followup:
HUH?

my example explicity had 2 child tables????
Rating: 5
Thank You again Tom October 30, 2003
Reviewer: Nihad Saleh from Sana'a, Yemen
Hi Tom
   Thank you Tom, I had add that's statment to tell you what i added to your
answer, and i want to tell you that your answer was very simple and very good
idea , and without your answer may be i have a complex solution ,but with your
answer i have small code and easy solution .
thank you again , and i hope if i can be in touch with you .
Rating: 5
search December 10, 2003
Reviewer: A reader
Hi tom, can you explain the search page on your site ?

 I have similler search and java coders are coding the query !! and I want to
explain them in their manner ( front-end point of view) how to query when they
are using like operator.

 right now they have only one query(huge and complex) that query's all the data
no matter what fields are blank and it takes long time.

 do you think there should be seperate query.
 do you think after the serarch is performat all the
    related (child data) should be pulled to gether
    or wait for user selection and then query the related
    data ?
    e.g. let's say query returned 20 result do you think
         they shold query all related data for all 20
          records or wait for user to select one of 20
          and then pull the related data ?

 please guide

Thanks,

to
Followup:
i don't use like

i use Oracle text to index the page and then use the sql contains operator to
efficiently search.

using like isn't going to get them very far.
Rating: 3
How does the bookmark work? December 19, 2003
Reviewer: A reader
Tom, Sorry to bug you on this, but I am unable to use the bookmark feture of
your site. Please explain what happens when I bookmark a page, where to go and
see that?
Followup:
you hit bookmark to get a URL that will exist forever (he says confidently)

If you bookmark a page with a sessionid in the URL, it'll stop working in a
while.

So, the "bookmark this page" link gives you a page that you can then really
bookmark and get to next year.
Rating: 5
need your help again January 05, 2004
Reviewer: Nihad Saleh from San'a, Yemen
HI TOM
 I WANT YOUR HELP TO WRITE SELECT STATMENT THAT GIVE ME ANSWER FOR MY PROBLEM .
MY PROBLEM IS ... I HAVE TABLE AS THE FOLLOWING
( PTYPE     PNO   EMPNO  ENAME   PVALUE )
ASSUME THAT THE  PTYPE , PNO , EMPNO IS THE PRIMARY KEY
NOW I HAVE RECORDS INSERTED INTO THIS TABLE AS THE FOLLOWS :
PTYPE      PNO    EMPNO   ENAME    PVALUE
  1         1      110    TOM       1000
  1         2      110    TOM       1100
  1         3      110    TOM       1500
  2         1      110    TOM       700
  2         2      110    TOM       500
  2         3      110    TOM       9000
  2         4      110    TOM       450
  1         1      111    NIHAD     900
AND SO ON , NOTE THAT ( PTYPE ) JUST TWO KIND (1 OR 2 ) BUT FOR EVERY KIND MANY
(PNO) AND (PVALUES) NOW I WANT THE SELECT STATMENT DISPLAY THE RESULT LIKE THIS
:-
 ( FOR EVERY KIND )
ENAME    PNOVALUE1   PNOVALUE2  PNOVALUE3  ...
TOM      1000         1100       1500       ...
TOM      700          500        9000       ...
NIHAD    900          ...        AND SO ON FOR ALL ENAME AS PTYPE .

NOW HOW I CAN WRITE SELECT STATMENT TO DISPLAY THIS RESULTE ? IF I CAN'T HOW I
CAN DO IT WITH USING VIEW ?
I NEED THE SOLUTION ..
  THANK YOU TOM  AND HAVE NICE DAY ...

NIHAD SALEH
Followup:
(your shift lock key seems to be stuck)

But anyway, is there a resonable upper bound to the number of records for an
empno?  (you sort of need to know the number of columns).

that is, what is the:

select max(cnt) from ( select count(*) cnt from t group by empno );

and is that max(cnt) that is returned the true "max".

If not, if you do not know the maximum max, we cannot do this as a static query
-- we would have to write a procedure that

a) runs that select max(cnt) query
b) dynamically builds a query with the proper number of columns
c) returns a result set (ref cursor)
Rating: 5
do you want Max to sort it as one row January 05, 2004
Reviewer: Nihad Saleh from San'a, Yemen
HI TOM
  THANK YOU TO ANSWER ME FASTER SO WHY YOU WANT TO KNOW MAX OF EMPNO IF YOU WANT
TO KNOW IT FOR TO SORT IT LIKE THIS
ENAME   PNOVALUE1 PNOVALUE2 PNOVALUE3 ...
THEN I THINK YOU WANT TO KNOW THE MAX OF PNO FOR PTYPE, IS THIS RIGHT , IF THIS
IS RIGHT ASSUME YOU HAVE 20 PNO FOR EVERY KIND , AS YOU KNOW WE HAVE JUST TWO
KIND OF PTYPE .

I DON'T KNOW HOW I CAN EXPLAIN THE PROBLEM MORE CLEARLY BUT I CAN SAY WE HAVE TO
TYPE  EVERY TYPE HAS ( PNO )1,2,3,4,...,20 OK , EVERY (PNO) HAS PVALUE NOW THE
EMPLOYEE MAY BE TAKE ONE PTYPE AND MANY OF ( PNO) AND (PVALUE ) OF THE PTYPE SO
I WANT TO DISPLAY EMPLOYEE( SORTING AS PTYPE ) AND ALL THE (PVALUE) WHICH HE
TOOK, THE RESULT MUST BE AS THE FORM :
TOM    PVALUE1   PVALUE2 ... AS PTYPE 1
TOM    PVALUE1   PVALUE2 ... AS PTYPE 2
NIHAD  PVALUE1   ...     ... AS ....   IF EMPLYEE HAS RECORDS FOR ANY PTYPE , I
WANT TO DISPLAY IT LIKE FORM ABOVE .

I HOPE EXPLANE MY PROBLEM GOOD >>!?
BUT REALY YOU ARE GOOD MAN ...!!!!! I'M VERY HAPPY TO ASK YOU.
THANK YOU TOM
Followup:
shift key -- hit it.  yours is stuck in the caps lock position.

you need to now the max number of columns -- that is what the max is for.
Rating: 5
assume it 100 January 09, 2004
Reviewer: Nihad Saleh from San'a,Yemen
hi tom
  assume that is the max of culomns in 100 ok , how i can write the select
statment.
Followup:
use this technique.

ops$tkyte@ORA920PC> select deptno,
  2         max( decode(rn,1,ename) ),
  3         max( decode(rn,2,ename) ),
  4         max( decode(rn,3,ename) ),
  5         max( decode(rn,4,ename) ),
  6         max( decode(rn,5,ename) ),
  7         max( decode(rn,6,ename) ),
  8         max( decode(rn,7,ename) ),
  9         max( decode(rn,8,ename) )
 10    from (select deptno, ename, row_number() over (partition by deptno order
by ename) rn
 11            from emp )
 12   group by deptno
 13  /

    DEPTNO MAX(DECODE MAX(DECODE MAX(DECODE MAX(DECODE MAX(DECODE MAX(DECODE
MAX(DECODE MAX(DECODE
---------- ---------- ---------- ---------- ---------- ---------- ----------
---------- ----------
        10 CLARK      KING       MILLER
        20 ADAMS      FORD       JONES      SCOTT      SMITH
        30 ALLEN      BLAKE      JAMES      MARTIN     TURNER     WARD
Rating: 5
detracked..... January 10, 2004
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
I think the discussion is going away from the subject of this topic.
Rating: 5
how i can use oracle tables in c++ program January 28, 2004
Reviewer: Nihad Saleh from San'a, Yemen
Hi Tom
  how i can use oracle tables from C++ program to insert or delete and others
operation
Followup:
read about OCI or Pro*C -- all documentation is on otn.oracle.com
Rating: 5
How to bookmark February 26, 2004
Reviewer: Gabriel from Montreal, Canada
Hello Tom,

Can you please tell me how can we bookmark pages on your website using the
bookmark links that you have on the page.
EX:
Bookmark this page with the link HERE
Bookmark Review
Bookmark Page
Followup:
You click on those links to get a "bookmark friendly" (static, unchanging) URL.

That url you can in fact "bookmark"

if you bookmark the nasty big urls with session ids in them, they "disappear"
tomorrow.  so, you click on the link to bookmark and then you can safely
bookmark
Rating: 3
Advance search by email not returning hits on reviews February 26, 2004
Reviewer: Mark from USA
I have always used the same email address when submitting Reviews.  Search on
this yields no hits.
Followup:
yes, it is not part of this page, the emails are not exposed to the public.

if you could search them, you'd actually be able to see them here and that would
be "bad"
Rating: 4
?????? February 27, 2004
Reviewer: linchao from ZheJiang province of China
Hi Tom ,I cann't install Oracle9iClient on Win2000FamilySever. Could you tell me
why?
Everytime I have installed it, it always tell
me "you install failed".
Thank you for answer my question!
Followup:
what is a "family server"?

I would guess you are using some "for home use without all of the features of a
real server" edition of windows and that is not a supported, supportable
configuration.
Rating: 5
February 27, 2004
Reviewer: A reader
Tom, after a long time of using asktom I understand the problem behind several
fool questions, the reason is the following:
Sometimes you need urgently something, if yuo would have time, you could read
all the documentation, test and do it, even more there are times this is not
urgent, but you really really don't have time, and is better to search in the
internet.

So there is the need for how tos, ordered and clasified, ask tom is full of
them, the point is, Oracle takes a lot of time and ivnestment (including asktom)
to help customer to use better ( not to say only use correctly oracle), why not
to start to do a
howto's by Oracle release, but seriously and planned as asktom is. Maybe as an
additoinal metalink service.

The source can be ASK TOM, user input , who periodically be
inserting new how tos.

This is only another of my %)ideas.
Followup:
you mean obe's

otn.oracle.com/obe
Rating: 5
February 27, 2004
Reviewer: A reader
Exactly, but there you can't find for example
Java, run os comand in windows, linux, etc., etc., etc., etc., etc., etc., etc.,
etc., etc., etc., etc., etc., etc., etc., etc., etc.

And now there are too few topics and  examples.
Rating: 5
March 01, 2004
Reviewer: A reader
Sorry Tom, but I think in this way, I'm right.?
what do you think, honestly about this idea?
I'm nut or this is not in what Oracle is t hinking to do now.?
Last time I ask it :)

Why there is an asktom site?

The reason I think is somewhere here, the problems are:
  People works without read the documents
  People don't have time to read the docuents
  There is the need of an Oracle official works
    where several dbas says the opossite.
  One is not sure if "there is something more",
    because when one have to apply something in
    the production database wants to be sure
    about what is happening.
  Release and platform specific questions
  There is the need of how to's, even when you know you
    want to know if what you do is right.
  Information and tested techniques in new
    technologies Java is the best example, most don't know
    java but wants to use it.
  The information about one topic is spreaded in too
    oracle documents.

I think asktom comes to solve some of this problems, but after one see the same
question asked frequently 5 times, It took to me 4 hours to search in several
questions if in one question was not the answer I was looking for.
And you are losing your precious times answered questions.

I think asktom is the first step, but only the first,
The question is

If Oracle wants to give the help to customer to use technologies without having
a deep knowledge on them.
Doing more detailed how-tos

I'm not telling, Oracle MUST do this, I'm telling, the problem Oracle has is the
misuse of his technologies, because Oracle database covers too much
technologies, and dbas even when they want don't have time, or don't have money
to learn, or are not sure about them or don't have enough knowledge about them,
don't know they exists.

Most people can help, if there would be a place where I could paste my how-tos
takeme sometimes days to find, I will do. And this will be helpful to other
people.
Forum don't covers this kind of needs, there is a big difference between a place
where you can paste your how-tos and share with other people, and a place where
you ask anything.
Ordered by topic and goal, example Database:create database
And categorized by database release, type (standar, lite, enterprise)
Database:odbc, etc.

And this can be really a very interesting support for new customer, say them
"here we have a database where you can find the most asked how-tos"
Rating: 5
about NOT IN March 02, 2004
Reviewer: adnan Taha from Jordan
dear sir
i have question for u
i have this case
i have table A and Table B
i wanna write select statment to select data from table A , where the row not in
table B
like this select c1 ,c2 from a where c1 not in
(select c1 from b)

but i dont want to use Not IN cause in because it make retieve time is slow ,
isnt it right

so can u help me to do it
Followup:
on the contrary, not in makes things blindingly fast

if you use the CBO
if the c1 values in B are "not null"

read
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7565644027229

Rating: 4
methods and object types March 28, 2004
Reviewer: Adnan taha from Jordan
dear sir

i am started to work in OODB with oracle

and i built oject type obj_info

  1  create type obj_info as object (
  2  obj_name varchar2(100),
  3  Obj_creation_date   DATE,
  4  Obj_Realease_no     NUMBER(3),
  5  Object_ojective        varchar2(150)
  6* );
SQL> /

and its done

also and i want to creat method on the data base
  1  create type func_type as object (
  2  wrd varchar2(200),
  3  member function IsSpace(word varchar2) RETURN boolean
  4* );
SQL> /

Type created.

SQL> create or replace type body func_type  as
  2  member function IsSpace(word varchar2) RETURN boolean is
  3  BEGIN
  4    if word = ' ' then
  5     return true;
  6    else
  7     return false;
  8    end if;
  9  END;
 10  end;
 11  /

now i have type called func_type
contains one method
IsSpace

then i created table
1  create table program
  2  (
  3   prg_id varchar2(100),
  4    prg_info obj_info   ,
  5    analyze_prog   func_type
  6* )
using the tow object types
now
HOW I CAN INVOKE (CALL) THE METHOD ISSPACE WHICH IS PART OF TABLE
- IN THE SQL
- IN THE FORM
regards
Followup:
we have OR, no OO.  But anyway.....

have you read the Application Developers Guide for Object Relational Features?
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96594/toc.htm
But in answer to the question, you cannot invoke that method from SQL since it
returns BOOLEAN and BOOLEAN is not a sql type at all.  You can invoke it in
plsql:

declare
   l_data func_type := func_type('hello');
begin
   if ( l_data.isSpace() ) then....
Rating: 5
wrt:I've updated the documentation links (finally) in the Q&A's March 28, 2004
Reviewer: A reader
Thanx a million.
I think Oracle is so popular because of TOM.
Rating: 4
about Method and object Type March 29, 2004
Reviewer: Adnan Taha from jordan
okey sir about ur reply and thank u
and i have the book u told me about
but
i have also tow comment
1- if its return numer or date how i can call the method in sql
2- u declared varaible using this objtype but without
refering to the table ,, I mean now the method is part of the table , how i can
revoke it using the table ,
i want to use the concept of encapsulation in OO .. the method and attribute r
part of table , so how can i accses the method throgh the table
regards
Followup:
1) lots of examples (hundreds) in that book!

select o.obj.method() from table o;

2) read that book!
Rating: 5
Using Object types In form 6 April 03, 2004
Reviewer: Adnan Taha from Jordan
dear Sir
thank u very much for ur help

now i have my OODB , i designed all enttities and functions ,
BUT when i tried to use one of the functions which its a part of Object type IN
THE FORM BUILDER
THIS MESSAGE APPERS

RPCs cannot use variable with schema level user-defined types in this release

Notes that i am using Form6
Forms [32 Bit] Version 6.0.8.22.1 (Production)

so please  tell me how to call and use functions and object types from inside
the
form builder

regards
Followup:
i haven't touched forms since march 1995, sorry -- i don't work with that tool

suggest you try otn.oracle.com -> discussion forums
April 03, 2004 Henk-Jan -- Thanks for the question regarding "Java procedure for host calls on Unix environment", version 8.1.6
You Asked
Steve,

I'm looking for a Java-procedure executing host calls on a Unix
environment from the Oracle-server. I know the standard way of
doing it by means of ProC (for Oracle versions before 8.1) but I
do not have the ProC compiler available.

I do not know Java or ProC (nor do I currently have the time to
learn them). I've found several examples of these scripts but
the "Runtime Power" script on this site does not seem to do
anything at all and the other script I've found in an Oracle
newsgroup seems to miss some "#import" statements in order to
compile correctly.

I would greatly appreciate your help on this one.
Henk-Jan
and we said...
Here is how to do this in java in 8.1.6.  8.1.6 added lots of
new security features so this would be a little different in
8.1.5 but mostly the same.

We need to start by granting some privs.  I'm going to grant as
little as I have to get allow us to execute the program
/usr/bin/ps.  As SYS or some appropriately priveleged user, we
will execute:

sys@DEV816> begin
  2      dbms_java.grant_permission
  3      ('RT_TEST',
  4       'java.io.FilePermission',
  5       '/usr/bin/ps',
  6       'execute');
  7
  8      dbms_java.grant_permission
  9      ('RT_TEST',
 10       'java.lang.RuntimePermission',
 11       '*',
 12       'writeFileDescriptor' );
 13  end;
 14  /

PL/SQL procedure successfully completed.

That allows our user RT_TEST to successfully execute that
program.  We could have allowed it to execute /usr/bin/* or * or
whatever -- I'm just letting it execute that one program.

Now, RT_TEST would create in its schema:

rt_test@DEV816> create or replace and compile
  2  java source named "Util"
  3  as
  4  import java.io.*;
  5  import java.lang.*;
  6
  7  public class Util extends Object
  8  {
  9
 10    public static int RunThis(String[] args)
 11    {
 12    Runtime rt = Runtime.getRuntime();
 13    int        rc = -1;
 14
 15    try
 16    {
 17       Process p = rt.exec(args[0]);
 18
 19       int bufSize = 4096;
 20       BufferedInputStream bis =
 21        new BufferedInputStream(p.getInputStream(), bufSize);
 22       int len;
 23       byte buffer[] = new byte[bufSize];
 24
 25       // Echo back what the program spit out
 26       while ((len = bis.read(buffer, 0, bufSize)) != -1)
 27          System.out.write(buffer, 0, len);
 28
 29       rc = p.waitFor();
 30    }
 31    catch (Exception e)
 32    {
 33       e.printStackTrace();
 34       rc = -1;
 35    }
 36    finally
 37    {
 38       return rc;
 39    }
 40    }
 41  }
 42  /

Java created.

rt_test@DEV816> create or replace
  2  function RUN_CMD( p_cmd  in varchar2) return number
  3  as
  4  language java
  5  name 'Util.RunThis(java.lang.String[]) return integer';
  6  /

Function created.

To make this callable as a procedure (ignoring the return code),
we'll create a procedure:

rt_test@DEV816>
rt_test@DEV816> create or replace procedure RC(p_cmd in
varchar2)
  2  as
  3    x number;
  4  begin
  5    x := run_cmd(p_cmd);
  6  end;
  7  /

Procedure created.

And now to run it:

rt_test@DEV816> set serveroutput on size 1000000
rt_test@DEV816> exec dbms_java.set_output(1000000)
PL/SQL procedure successfully completed.
rt_test@DEV816> exec rc('/usr/bin/ps -ef');
UID   PID  PPID  C    STIME TTY      TIME CMD
root     0     0  0   Aug 17 ?        0:06 sched
root     1     0  0   Aug 17 ?        1:19 /etc/init -
root     2     0  0   Aug 17 ?        0:23 pageout
.....

PL/SQL procedure successfully completed.

rt_test@DEV816>

See
http://java.sun.com/j2se/1.3/docs/api/java/lang/RuntimePermission.html

http://java.sun.com/j2se/1.3/docs/api/java/security/SecurityPermission.html

http://java.sun.com/j2se/1.3/docs/api/java/io/FilePermission.html
and
http://download-east.oracle.com/docs/cd/A81042_01/DOC/java.816/a81353/perf.htm#1001971
From the “Java Developer’s Guide”, Part No. A81353-01, Chapter
5:

Table 5–1 Permission Types
n java.util.PropertyPermission
n java.io.SerializablePermission
n java.io.FilePermission
n java.net.NetPermission
n java.net.SocketPermission
n java.lang.RuntimePermission
n java.lang.reflect.ReflectPermission
n java.security.SecurityPermission
n oracle.aurora.rdbms.security.PolicyTablePermission
n oracle.aurora.security.JServerPermission
Review & Followup

Rating: 5
May 30, 2001
Reviewer: Nagarajan from USA
very useful material. Thanks for the same
Rating: 5
Exactly what I needed to see June 04, 2001
Reviewer: B.Wisniewski from Columbus, OH USA
This is exactly what I needed to see in an example of interacting with the
operating system.  Trying to persuade the developers to use Java vs Perl and
this is what I needed.

Thanks again.
Rating: 5
July 27, 2001
Reviewer: Mark Penny from Thetford, Norfolk UK.
I had never imagined that this could be done so easily. Thank you.
Rating: 5
great answer October 03, 2001
Reviewer: Sharon from Israel
Great material , it's exactlly what I was looking for
10x you a lot
Rating: 5
November 21, 2001
Reviewer: A reader
hi tom
your reply on extrnal procedure works
Thanks

I am also trying to run executable from java
i am getting this error

I am running Pesonal Oracle 8.1.7 on windows 98

SQL>  exec rc('c:\windows\command /c dir');
java.io.IOException
at oracle.aurora.java.lang.OracleProcess.create(OracleProcess.java)
at oracle.aurora.java.lang.OracleProcess.construct(OracleProcess.java)
at java.lang.Runtime.execInternal(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:11)

PL/SQL procedure successfully completed.
Followup:
Ok, is there a c:\windows\command.exe ?

do a dir c:\windows\command.*

show us the entire example from the dbms_java calls to the create or replace and
compile java source to the call you are doing itself.
Rating: 5
November 22, 2001
Reviewer: A reader
hi tom

it was dir c:\windows\command.com

when i run
exec rc('c:\windows\command.com /c dir');

it never come back

i examined udump file it looks like

Dump file d:\oracle\admin\swami\udump\ORA10653.TRC
Thu Nov 22 09:34:37 2001
ORACLE V8.1.7.0.0 - Production vsnsta=0
vsnsql=e vsnxtr=3
Windows 95 V4.10, OS V192.0, CPU type 586
Oracle8i Personal Edition Release 8.1.7.0.0 - Production
JServer Release 8.1.7.0.0 - Production
Windows 95 V4.10, OS V192.0, CPU type 586
Instance name: swami

Redo thread mounted by this instance: 1

Oracle process number: 13

Windows thread id: -710243, image: ORACLE.EXE

*** 2001-11-22 09:34:37.720
*** SESSION ID:(17.10) 2001-11-22 09:34:37.610
Specified COMMAND search directory bad   Microsoft(R) Windows 98
(C)Copyright Microsoft Corp 1981-1998.  D:\Oracle\Ora81\DATABASE>

any idea.

Thanks
Followup:
Well, it looks like it just started up a command shell -- did not process the
"/c dir" part of it (hence the apparent HANG -- it was waiting for someone to
type "exit".

what happens if you just enter:

c:\windows\command.com /c dir

on the dos prompt itself.  does that work?

what if you create a script:

do_dir.bat:

@echo off
dir

and run that instead?
Rating: 5
but.... November 22, 2001
Reviewer: Connor from Cold cold Scotland
who is Steve ?

:-)
Followup:
Thats a darn good question -- never noticed that Henk-Jan called me "steve" :)
Rating: 5
November 22, 2001
Reviewer: A reader
hi tom,

i ran

c:\windows\command.com /c dir

on dos prompt it works fine
and i also created do_dit.bat and it also works fine

thanks
Followup:
did you call the do_dir.bat from the java stored procedure -- if so, thats your
solution.  You see -- this is purely a "command.com" issue here -- we are
getting a command.com error "search path invalid (sounds like a bad COMSPEC or
something, been a really long time since I worked with dos stuff like that).
Rating: 5
re: steve November 22, 2001
Reviewer: Mark A. Williams from Indianapolis, IN USA
I think 'Steve' is Tom's alter ego -- you know, the one who is answering
questions and such on Thanksgiving!  Thought you (Tom) were taking a break?  OK,
so I'm *reading* AskTom on Thanksgiving...  but that's just because I haven't
had the turkey yet.
Rating: 5
November 22, 2001
Reviewer: A reader
hi tom
Sorry for disturbance on thanks giving but my problem is critical i have to
implement this on coming MONDAY

when i run
exec rc('c:\windows\command.com /c dir');

it never come back

THIS TIME I SET SQL_TRACE = TRUE

AND LOG FILE IS AS FOLLOWS

I KILLED SESSION FOR THREE TIMES AND CHECK LOG FILE EACH TIME. IT IS SIMILER
EVERY TIME

Dump file d:\oracle\admin\swami\udump\ORA51445.TRC
Thu Nov 22 11:16:57 2001
ORACLE V8.1.7.0.0 - Production vsnsta=0
vsnsql=e vsnxtr=3
Windows 95 V4.10, OS V192.0, CPU type 586
Oracle8i Personal Edition Release 8.1.7.0.0 - Production
JServer Release 8.1.7.0.0 - Production
Windows 95 V4.10, OS V192.0, CPU type 586
Instance name: swami

Redo thread mounted by this instance: 1

Oracle process number: 10

Windows thread id: -341771, image: ORACLE.EXE

*** 2001-11-22 11:16:57.070
*** SESSION ID:(11.3) 2001-11-22 11:16:57.010
APPNAME mod='SQL*Plus' mh=3669949024 act='' ah=4029777240
=====================
PARSING IN CURSOR #1 len=35 dep=0 uid=18 oct=42 lid=18 tim=0 hv=2767092717
ad='4332a9c'
alter session set sql_trace = true
END OF STMT
EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=4,tim=0
=====================
PARSING IN CURSOR #1 len=87 dep=0 uid=18 oct=47 lid=18 tim=0 hv=3739629728
ad='433c588'
BEGIN RC('c:\windows\command.COM /c exp scott/tiger file=c:\xx.dmp tables=emp');
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=0
=====================
PARSING IN CURSOR #2 len=64 dep=1 uid=0 oct=2 lid=0 tim=0 hv=3686129482
ad='477a920'
insert into javasnm$(short, longname, longdbcs) values(:1,:2,:3)
END OF STMT
PARSE #2:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=0,tim=0
=====================
PARSING IN CURSOR #3 len=46 dep=1 uid=0 oct=3 lid=0 tim=0 hv=2044415509
ad='475d6b4'
select longname from javasnm$ where short = :1
END OF STMT
PARSE #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
FETCH #3:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
FETCH #3:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
FETCH #3:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
FETCH #3:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
FETCH #3:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
FETCH #3:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
FETCH #3:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
FETCH #3:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
FETCH #3:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
=====================
PARSING IN CURSOR #4 len=57 dep=1 uid=0 oct=3 lid=0 tim=0 hv=467722929
ad='46be564'
SELECT max(version) FROM "SYS"."JAVA$POLICY$SHARED$TABLE"
END OF STMT
PARSE #4:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
EXEC #4:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=1,cu=4,mis=0,r=1,dep=1,og=4,tim=0
STAT #4 id=1 cnt=1 pid=0 pos=0 obj=0 op='SORT AGGREGATE '
STAT #4 id=2 cnt=2 pid=1 pos=1 obj=16803 op='TABLE ACCESS FULL
JAVA$POLICY$SHARED$TABLE '
=====================
PARSING IN CURSOR #4 len=57 dep=1 uid=0 oct=3 lid=0 tim=0 hv=467722929
ad='46be564'
SELECT max(version) FROM "SYS"."JAVA$POLICY$SHARED$TABLE"
END OF STMT
PARSE #4:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
EXEC #4:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=1,cu=4,mis=0,r=1,dep=1,og=4,tim=0
STAT #4 id=1 cnt=1 pid=0 pos=0 obj=0 op='SORT AGGREGATE '
STAT #4 id=2 cnt=2 pid=1 pos=1 obj=16803 op='TABLE ACCESS FULL
JAVA$POLICY$SHARED$TABLE '
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
FETCH #3:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
FETCH #3:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
FETCH #3:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
=====================
PARSING IN CURSOR #4 len=147 dep=1 uid=0 oct=3 lid=0 tim=0 hv=3013728279
ad='468ad64'
select privilege#,level from sysauth$ connect by grantee#=prior privilege# and
privilege#>0 start with (grantee#=:1 or grantee#=1) and privilege#>0
END OF STMT
PARSE #4:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
EXEC #4:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=3,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=6,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=6,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=3,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=6,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=3,cu=0,mis=0,r=0,dep=1,og=4,tim=0
STAT #4 id=1 cnt=37 pid=0 pos=0 obj=0 op='CONNECT BY '
STAT #4 id=2 cnt=20 pid=1 pos=1 obj=0 op='CONCATENATION '
STAT #4 id=3 cnt=1 pid=2 pos=1 obj=102 op='INDEX RANGE SCAN '
STAT #4 id=4 cnt=20 pid=2 pos=2 obj=102 op='INDEX RANGE SCAN '
STAT #4 id=5 cnt=19 pid=1 pos=2 obj=83 op='TABLE ACCESS BY USER ROWID SYSAUTH$ '
STAT #4 id=6 cnt=55 pid=1 pos=3 obj=102 op='INDEX RANGE SCAN '
=====================
PARSING IN CURSOR #4 len=147 dep=1 uid=0 oct=3 lid=0 tim=0 hv=3013728279
ad='468ad64'
select privilege#,level from sysauth$ connect by grantee#=prior privilege# and
privilege#>0 start with (grantee#=:1 or grantee#=1) and privilege#>0
END OF STMT
PARSE #4:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
EXEC #4:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=3,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=6,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=6,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=3,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=6,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=3,cu=0,mis=0,r=0,dep=1,og=4,tim=0
STAT #4 id=1 cnt=37 pid=0 pos=0 obj=0 op='CONNECT BY '
STAT #4 id=2 cnt=20 pid=1 pos=1 obj=0 op='CONCATENATION '
STAT #4 id=3 cnt=1 pid=2 pos=1 obj=102 op='INDEX RANGE SCAN '
STAT #4 id=4 cnt=20 pid=2 pos=2 obj=102 op='INDEX RANGE SCAN '
STAT #4 id=5 cnt=19 pid=1 pos=2 obj=83 op='TABLE ACCESS BY USER ROWID SYSAUTH$ '
STAT #4 id=6 cnt=55 pid=1 pos=3 obj=102 op='INDEX RANGE SCAN '
=====================
PARSING IN CURSOR #4 len=147 dep=1 uid=0 oct=3 lid=0 tim=0 hv=3013728279
ad='468ad64'
select privilege#,level from sysauth$ connect by grantee#=prior privilege# and
privilege#>0 start with (grantee#=:1 or grantee#=1) and privilege#>0
END OF STMT
PARSE #4:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
EXEC #4:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=3,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=0,dep=1,og=4,tim=0
STAT #4 id=1 cnt=14 pid=0 pos=0 obj=0 op='CONNECT BY '
STAT #4 id=2 cnt=4 pid=1 pos=1 obj=0 op='CONCATENATION '
STAT #4 id=3 cnt=1 pid=2 pos=1 obj=102 op='INDEX RANGE SCAN '
STAT #4 id=4 cnt=4 pid=2 pos=2 obj=102 op='INDEX RANGE SCAN '
STAT #4 id=5 cnt=3 pid=1 pos=2 obj=83 op='TABLE ACCESS BY USER ROWID SYSAUTH$ '
STAT #4 id=6 cnt=25 pid=1 pos=3 obj=102 op='INDEX RANGE SCAN '
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
FETCH #3:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
=====================
PARSING IN CURSOR #4 len=147 dep=1 uid=0 oct=3 lid=0 tim=0 hv=3013728279
ad='468ad64'
select privilege#,level from sysauth$ connect by grantee#=prior privilege# and
privilege#>0 start with (grantee#=:1 or grantee#=1) and privilege#>0
END OF STMT
PARSE #4:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
EXEC #4:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=3,cu=0,mis=0,r=1,dep=1,og=4,tim=0
FETCH #4:c=0,e=0,p=0,cr=4,cu=0,mis=0,r=0,dep=1,og=4,tim=0
STAT #4 id=1 cnt=14 pid=0 pos=0 obj=0 op='CONNECT BY '
STAT #4 id=2 cnt=4 pid=1 pos=1 obj=0 op='CONCATENATION '
STAT #4 id=3 cnt=1 pid=2 pos=1 obj=102 op='INDEX RANGE SCAN '
STAT #4 id=4 cnt=4 pid=2 pos=2 obj=102 op='INDEX RANGE SCAN '
STAT #4 id=5 cnt=3 pid=1 pos=2 obj=83 op='TABLE ACCESS BY USER ROWID SYSAUTH$ '
STAT #4 id=6 cnt=25 pid=1 pos=3 obj=102 op='INDEX RANGE SCAN '

IT HANGS HERE EVERY TIME
Followup:
I already diagnosed this!  It is NOT HANGNING IN ORACLE.  You put up this dump
file before:

i examined udump file it looks like

.....
Dump file d:\oracle\admin\swami\udump\ORA10653.TRC
Thu Nov 22 09:34:37 2001
ORACLE V8.1.7.0.0 - Production vsnsta=0
vsnsql=e vsnxtr=3
Windows 95 V4.10, OS V192.0, CPU type 586
Oracle8i Personal Edition Release 8.1.7.0.0 - Production
JServer Release 8.1.7.0.0 - Production
Windows 95 V4.10, OS V192.0, CPU type 586
Instance name: swami

Redo thread mounted by this instance: 1

Oracle process number: 13

Windows thread id: -710243, image: ORACLE.EXE

*** 2001-11-22 09:34:37.720
*** SESSION ID:(17.10) 2001-11-22 09:34:37.610
Specified COMMAND search directory bad   Microsoft(R) Windows 98
(C)Copyright Microsoft Corp 1981-1998.  D:\Oracle\Ora81\DATABASE>

any idea.

.......

When you are running "command /c dir"  -- dos is messing up.  You see the error
message there "Specified COMMAND search directory bad" and a command prompt.
That is your HANG.

command.com is running,
It is messing up.
It is dumping you at the command prompt -- waiting for you to type something in.

Unfortunately for you - this command prompt is in the background, you cannot see
it, you cannot type into it.  It will sit there forever.

This is NOT hanging in the database. This is hanging in DOS.

Did you try the .bat file as I suggested.  If that works -- that is in fact your
solution.  If it doesn't, you'll have to find out what that error message is and
how to fix it.  I don't have windows98 to test with.
Rating: 5
November 24, 2001
Reviewer: A reader
hi tom

finally i am able to run OS command from pl/sql.but from
Windws NT not Windows 98.

Now I can  run os command using extrnal procedure
and Java stored procedure.

Thank you very much for your co-opration
Rating: 3
Encountering some limitations November 29, 2001
Reviewer: Andrew from ca, usa
Tom
Thanks for the great code, but I'm hitting limitations using '>', '|' and other
metacharacters. Please could you explain why my attempts fail. I can only
overcome the problem by writing the string I want to execute to a script (using
utl_file, doing a chmod +x on that and then executing it). As you can see the
use of '|' returns and error, the use of '>' is ignored.

1.) Granted the following privs from sys:
begin
    dbms_java.grant_permission
    ('SCOTT',
     'java.io.FilePermission',
     '/tmp/*',
     'execute');
    dbms_java.grant_permission
    ('SCOTT',
     'java.io.FilePermission',
     '/usr/bin/*',
     'execute');
    dbms_java.grant_permission
    ('SCOTT',
     'java.lang.RuntimePermission',
     '*',
     'writeFileDescriptor' );
-- try to accomodate input redirection
    dbms_java.grant_permission
    ('SCOTT',
     'java.lang.RuntimePermission',
     '*',
     'readFileDescriptor' );
-- try to accomodate input/output redirection
    ('SCOTT',
     'java.io.FilePermission',
     '/tmp/*',
     'read,write');
-- commit;
end;
/

2.) changed RC to output the return code:
CREATE OR REPLACE procedure RC(p_cmd in varchar2)
as
  x number;
begin
  x := run_cmd(p_cmd);
  DBMS_OUTPUT.PUT_LINE ( 'run_cmd returned : '||rpad(x, 3, ' ')||' for '||
p_cmd);
end;
/

3.) the results
SQL> set serveroutput on
SQL>
SQL> set serveroutput on
SQL>
SQL> declare
  2    dummy varchar2(50);
  3  begin
  4    dbms_output.enable(1000000);
  5    dbms_java.set_output(1000000);
  6
  7    -- the following work fine
  8    -- rc('/usr/bin/ps -ef');
  9    -- rc('/usr/bin/pwd');
 10    -- rc('/usr/bin/touch /tmp/t.txt');
 11
 12    -- the following don't work
 13    rc('/usr/bin/ps -ef | /usr/bin/grep oracle');
 14    rc('/usr/bin/pwd > /tmp/pwd.log');
 15    -- rc('/usr/bin/ksh -c "/usr/bin/pwd > /tmp/pwd.log"');
 16  end;
 17  /
run_cmd returned : 1   for /usr/bin/ps -ef | /usr/bin/grep oracle
/apps/oracle/product/8.1.7/dbs
run_cmd returned : 0   for /usr/bin/pwd > /tmp/pwd.log

PL/SQL procedure successfully completed.

4.) From Unix:
ls -ltr /tmp/pwd.log
/tmp/pwd.log: No such file or directory
Followup:
What I believe your issue is is that we wrote this tiny routine to run "A"
program.  You are not running "A" program -- you are running a shell specific
command.  This is like someone on windows trying to run "dir", "dir" is not a
program -- its an internal command.  similarly, ps -ef | grep oracle is not a
command -- its an internal command to your shell that says "start ps, redirect
its stdout, start grep, redirect its stdin".

you would need to run a SHELL that understands "|" and ">", something like:

sh -c 'ps -ef | grep oracle'

BUT -- then you would need execute on /bin/sh, with that you can run ANY command
as the user "oracle" (or who ever starts the database).  That would let you for
example "sh -c rm -rf /*" and erase everything oracle is allowed to erase.  So,
my real suggestion would be to create a script that has the commands you want to
execute and you run THAT script from the database.  Give your self the
permission to run that one command -- not /bin/sh
Rating: 5
Tom, sorry for the mixup. December 17, 2001
Reviewer: Henk-Jan Korpel from Veenendaal, the Netherlands
He Tom,

Sorry, for the mixup with Steve (Feuerstein, that is) whose stuff I was studying
at that time. I just saw it today, hope this clears it.

Regards
Henk-Jan
Rating: 4
"Java procedure for host calls on Unix environment", version 8.1.6 January 16, 2002
Reviewer: Randy Bradley from Montgomery, AL USA
Tom,
I found this information to be very helpful. The step-by-step instructions were
easy to follow. I have a question about using the ftp command instead of the ps
command. When I run the ps command everything work fine, but when I run the ftp
command ("/usr/bin/ftp -vin < /public/parfile.txt"), the procedure just hangs.

What's seems to be causing this problem. I did assign execute permissions via
the dbms_java package.

One more thing... I'm running 8.1.7 (64-bit) on HP-UX 11.00 (64-bit).
Followup:
ftp is waiting for the username and password to be entered and ftp refuses to
read this from stdin as you are trying (i assume you have the username/password
in the parfile.txt)

Do a man on netrc to see how to setup a .netrc (in the ORACLE account -- could
be somewhat "dangerous").

A better approach might be to use some java classes that let you PROCEDURALLY do
an ftp from java -- in that fashion, you can alleviate the inherit security
issues with the .netrc file solution.
Rating: 3
special characters >, >>, |, < etc. January 17, 2002
Reviewer: A reader from ca, usa
I bet the above reader will need to do the FTP from a shell script if he wants
to use '<'. I had a similar problem higher up in this thread.
Followup:
Absolutely correct -- I didn't even read that far into the command -- that will
be the next problem they hit.
Rating: 4
Java procedure for host calls on Unix environment", version 8.1.6 January 17, 2002
Reviewer: Randy Bradley from Montgomery, AL USA
Your information about using .netrc worked to some degree. I woks when I run the
ftp command from the unix prompt, but it still hangs when I attempt to execute
from SQL*PLUS. I hope you can help me resolve yet another issue.

Thanks!
Followup:
See above...
Rating: 5
Java procedure for host calls on Unix environment", version 8.1.6 January 17, 2002
Reviewer: Randy Bradley from Montgomery, AL USA
I created shell script and I execute the shell script from with the procedure.
Thanks for all the help.

Randy
Rating: 3
Standard shell script... January 17, 2002
Reviewer: A reader from ca, usa
In my comments higher up I referred to writing the command to a file, doing a
chmod +x etc. Obviously that's only if you don't know the command you want to
run. Creating a standard ftp shell script with predefined input parms is better.
Maybe the way you implemented it?? From what I can remember the DBMS_PIPE
solution (see this site) doesn't have the special character limitation. Java
solution just seems neater.
Rating: 5
9i ? February 07, 2002
Reviewer: Connor from UK
Apologies for my total lack of java knowledge (I'm getting there...), but your
example (which worked fine in 8i) appears to bomb out under my 9i version.
Could you post if the same code works under one of your 9i versions?

SQL> variable x number;
SQL> set serveroutput on
SQL> exec dbms_java.set_output(100000);
SQL> exec :x := RUN_CMD('/usr/bin/ls');
java.lang.ArrayIndexOutOfBoundsException
at Util.RunThis(Util:14)

Cheers
Connor
Followup:
I just removed the String array definition on the PL/SQL wrapper
function and the Java method. I've included all the code again (and an
example) below. I do not fully know why it worked in 8i.
My assumption is that the JVM-PL/SQL data type pickler may have
automatically promoted non-array types to arrays when required. Or 8i
was less exacting that in 9i.

ops$tkyte@ORA9I.WORLD> begin
  2    dbms_java.grant_permission
  3    ('RT_TEST',
  4     'java.io.FilePermission',
  5     '/usr/bin/ls',
  6     'execute');
  7
  8    dbms_java.grant_permission
  9    ('RT_TEST',
 10     'java.lang.RuntimePermission',
 11     '*',
 12     'writeFileDescriptor' );
 13  end;
 14  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD> connect rt_test/rt_test
Connected.
ops$tkyte@ORA9I.WORLD> @login

rt_test@ORA9I.WORLD> create or replace and compile
  2  java source named "Util"
  3  as
  4  import java.io.*;
  5  import java.lang.*;
  6
  7  public class Util extends Object
  8  {
  9    public static int RunThis(String args)
 10    {
 11    Runtime rt = Runtime.getRuntime();
 12    int        rc = -1;
 13
 14    try
 15    {
 16       Process p = rt.exec(args);
 17
 18       int bufSize = 4096;
 19       BufferedInputStream bis =
 20        new BufferedInputStream(p.getInputStream(), bufSize);
 21       int len;
 22       byte buffer[] = new byte[bufSize];
 23
 24       // Echo back what the program spit out
 25       while ((len = bis.read(buffer, 0, bufSize)) != -1)
 26          System.out.write(buffer, 0, len);
 27
 28       rc = p.waitFor();
 29    }
 30    catch (Exception e)
 31    {
 32       e.printStackTrace();
 33       rc = -1;
 34    }
 35    finally
 36    {
 37       return rc;
 38    }
 39    }
 40  }
 41  /

Java created.

rt_test@ORA9I.WORLD> create or replace
  2  function RUN_CMD(p_cmd in varchar2) return number
  3  as
  4  language java
  5  name 'Util.RunThis(java.lang.String) return integer';
  6  /

Function created.

rt_test@ORA9I.WORLD> create or replace procedure RC(p_cmd in varchar2)
  2  as
  3    x number;
  4  begin
  5    x := run_cmd(p_cmd);
  6  end;
  7  /

Procedure created.

rt_test@ORA9I.WORLD> variable x number;
rt_test@ORA9I.WORLD> set serveroutput on
rt_test@ORA9I.WORLD> exec dbms_java.set_output(100000);

PL/SQL procedure successfully completed.

rt_test@ORA9I.WORLD> exec :x := RUN_CMD('/usr/bin/ls /tmp');
10514.ksh
10565.sql
...
xxx.dat
xxx.dbf
zz

PL/SQL procedure successfully completed.

rt_test@ORA9I.WORLD>
Rating: 5
March 04, 2002
Reviewer: A reader from italy
Rating: 5
Any workaround on 8.1.5 April 29, 2002
Reviewer: Senthil from Massachusetts, USA
Tom-

procedure dbms_java.grant_permission is available from 8.1.6. Is there any
workaround available in 8.1.5 to achieve the same functionality. I am using
8.1.5 on windows NT. It throws the following error if i try to assign permission
to any user...

PLS-00302: component 'GRANT_PERMISSION' must be declared

thanks,
senthil
Followup:
There is JAVASYSPRIV (a role) you can grant but beware -- it is "all powerful".
The granular privilege model added in 816 and up is far superior.  Make sure to
lock down this account so this role cannot be abused.
Rating: 5
I think this what I need May 08, 2002
Reviewer: Adam Hapworth from ME USA
I am looking at making a Java-procedure like the ones you have shown to run a
program on our Unix server.  The question I have is will I be able to have a
user on a client machine run this java-procedure from a Form(4.5 now 6 coming)
and get results back from the program on the server.  I have seen a number of
other news group posts regarding this with no answer and thought I would ask
here where the experts are.
Followup:
The answer is simply "YES".

The reason, all Java stored procedures are called through a wrapper, like this:

rt_test@DEV816> create or replace
  2  function RUN_CMD( p_cmd  in varchar2) return number
  3  as
  4  language java
  5  name 'Util.RunThis(java.lang.String[]) return integer';
  6  /

Function created.

that maps the java types to SQL types.  So, externally, it looks like PLSQL to
the caller, but underneath, it is java.
Rating: 4
Java procedure for host calls on Unix environment June 06, 2002
Reviewer: Sandeep Lekhwani from CA, USA
Hi Tom,
Content proved to be very useful.
But, I am facing a problem.
The ksh I am calling from oracle using this utility, has some parameters. Now,
some of the parameters contain the spaces. While running the ksh from unix we
just put these parameters in double quotes. But, when calling from oracle, it
identifies them as delimiters and hence treat as separate parameters. Is there a
way that I can make it avoid the spaces which are inside double quotes. Or can I
specify some other delim? Pls let me know. It's really urgent.
Followup:
We are not parsing anything -- I don't understand what you mean? We are just
executing a string -- passing it to the command interpreter.

There undoubtably exists a more sophisticated java api as well that lets you set
an array of parameters instead of just string -- you could look that up as well.
Rating: 3
help debugging, PLEASE :-) June 19, 2002
Reviewer: George Liblick from Chester Springs, PA USA
Tom,

I cut and past your solution, then modified the procedure
names and changed the grant_permission to /usr/ucb/ps because that is what I
found through the which command
on my box (/usr/bin/ps appears to be identical anyway.)
But when I execute the command, I am getting an error that
I don't know where to begin debugging...

SQL> exec cmd( 'ps -f' );
java.lang.ArrayIndexOutOfBoundsException
at MyCommand.RunThis(MYCOMMAND:14)

PL/SQL procedure successfully completed.

.. it works the same for /usr/ucb/ps ..

You have been a great help in the past - thanks - and I
hope you will help one more time.
Followup:
search this question from the top for ArrayIndexOutOfBoundsException and you'll
see the fix for 9i.
Rating: 5
Thanks June 20, 2002
Reviewer: George from Chester Springs, PA USA
I hate when the answer implies I didn't do my home work :-(
Both yesterday and today I searched for ArrayIndexOutOfBoundsException
throughout the archives, but the search failed to return anything... I never
thought the
answer would be right under my nose :-(  I wonder why your site search failed to
find this?

Anyway, it is working now and you have been most gracious - thanks.
Followup:
partly my fault.

My index is on the q&a.  Not the comments.

I have to code a function in this style:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:440419921146
in order to assemble the question, answer and all comments and responses into a
clob and let intermedia index that!  Just haven't done it yet, will do it soon
(bothers me too)
Rating: 5
that why i like oracle .. u can do almost anything August 21, 2002
Reviewer: Nitin Gulati from Singapore
Hi tom,
I am facing a problem of similar type.
I need to change permission in few files which are made from utl_file though
oracle onli. I think this solution will help me. But when i scearch dbms_java in
all objects i cannot find there. Is there anything wrong in my oracle
installation. When i scearh for dbms_java in /$oracle_HOME/rdbms/admin i cant
find there also so how to load this ?
But no doubt the explanation is really good
cheers,
nitin
Followup:
If you do not have java installed, your DBA can install it via initjvm.sql found
in $ORACLE_HOME/javavm/install
Rating: 4
great response, but... October 10, 2002
Reviewer: craig from Lowell, AR USA
Tom -

Your examples work great, but I'm having some trouble running my particular
command using your script.

I'm trying to use scp to move files from the database to another box.  I can run
this from the shell a-ok, but when run via your class, it returns a 1 (failure)
every time. Indeed, the file does not exist in the new location when it is all
said and done.  Can you help me out?

Again, the following works from the shell on the db box:
/usr/bin/scp /u01/home/cnelson/flat_files/CSV2.txt
cnelson@pspdwhp1:/u01/home/cnelson/CSV2.txt

Thanks again for your sage advice.
Followup:
Well, you ran that script as YOU (cnelson) where as the java ran as ORACLE

different user
different environment
different everything

suggestion:

a) log in as Oracle and test this scp, make sure Oracle can run it (permissions)

b) start out with a script /tmp called "test.sh"

In there put in a call to

/usr/bin/env

and make sure the environment is OK (paths and everything else are totally
differnet)

Then add the call to /usr/bin/scp in there (eg: incrementally add in things --
try to figure out where it is going awry)
Rating: 4
Very Useful - How about returning output greater than 32K? November 08, 2002
Reviewer: Greg DelVecchio from So. California
I managed to alter your example so that it returns the command line output as a
varchar2.  However, some of my commands output more than 32K of data.  Is there
a way to pass more than 32K of data from Java to PL/SQL?  Could you pass the
command line output as a CLOB or break it into a varray?  Thanks for any
suggestions you might have.

(NOTE: For others that may try this in 8.1.7.  There is a bug (#1322857) that
limits the string length that may be returned by a java stored function to 4K.
If you write it as a java stored procedure you may pass a string up to 32K in
length)
Followup:
You'll have to use a CLOB  - the caller could allocate a temporary clob and the
java could write to it.
Rating: 5
Problem when passing string parameters with lots of white spaces November 20, 2002
Reviewer: Mahomed Suria from U.K
I am running a unix script exec_cmd.sh using rc.exec as per your example. The
script is simply
#!/bin/sh
$@
#finish

When a send several parameters including one that is a string with lots of white
spaces , the string is being treated as several parameters rather than just one,
even when I hav enclosed the string in double qoutes.

 rc ('/usr/users/cdb_env/exe/exec_cmd.sh /usr/users/cdb_env/exe/
qsnd 1 triggr yrdman 1 "+000000001+000000001+000000001DC1  DL1  steve
PC" ' )

When I list the parameters that exec_cmd.sh is receiving  I get the following :
/usr/users/cdb_env/exe/qsnd
1
triggr
yrdman
1
"+000000001+000000001+000000001DC1
DL1
steve
PC"

It seems like the quoted string is being split into several parameters. I want
the string to be a single parameter.

Can you help? I do not know what the problem is?
Followup:
You'll have to read up on java -- the answer would lie there.  You could use a
lower level java api then the rather simple one i have here (no, I don't know
the name or even if it exists)

As a workable workaround -- suggest you pass a single string for that parameter
using an _ where you have whitespace and then using sed to remove it.  EG:

$ cat /tmp/test.sh

#!/bin/sh

/usr/bin/echo '$1 = ' $1
X="`echo $1 | sed 's/_/ /g'`"
echo '$X = ' $X
/usr/bin/echo ---------------

and then:

rt_test@ORA817DEV.US.ORACLE.COM> exec rc('/tmp/test.sh a_b ' )
$1 =  a_b
$X =  a b
---------------
return code = 0 from /tmp/test.sh a_b

so, your script would use $X instead of $1 or you could just use

  `echo $1 | sed s/_/ /g`

in your script where currently you just have $1
Rating: 3
Calling OS command using Java procedure in PL/SQL generates NON-EXISTANT PROCESS on NT December 19, 2002
Reviewer: Alain Labonte from Quebec - Canada
Hi, i use a java procedure mostly similar to this one, but when the command is
terminated, a NON-EXISTANT PROCESS stays on the server (NT 4.0).  Those
processes seem to take some memory.  So when i execute many jobs, they take a
lot of memory and slow down my server.  When i stop my database, they disapear,
but i can't stop my database at anytime.

Do you have any idea of what might causes that?
Followup:
I don't know what a "non existant process on nt" means.
Rating: 4
need to run a script January 07, 2003
Reviewer: tong siew pui from singapore
The info is very useful. Many thanks.
I am able to follow the example and get the results as stated.

But what I needed is to run a unix script (with different unix commands),
instead of a unix command.

I have a script 'tsp1.txt' in
    /u01/home/sinvb/tong

tsp1.txt
--------
ls -la

when I run
SQL> exec rc('/u01/home/sinvb/tong/tsp1.txt');
PL/SQL procedure successfully completed.

but the files are not listed.

when I run the command
'tsp1.txt' at '/u01/home/sinvb/tong'
the files get listed.

I tried different alternative
1) modified tsp1.txt to
    sh -c 'ls -la'
2) copy the file tsp1.txt to /usr/bin
   and
    exec rc('/usr/bin/tsp1.txt');
both show the same result
PL/SQL procedure successfully completed.

pls adv what is the pbm and what must I do.
the ultimate aim is to prepare tsp1.txt as
a unix script which contains various unix command.

thanks for your help in advance.

i also noticed that if i run
exec rc('/usr/bin/ps')
nothing is displayed.

but i got results if i run
exec rc('/usr/bin/ps -ef');

but if i execute the command 'ps',
i get something displayed.
is there a reason why ???
Followup:
time for you to do a little debugging eh?

did you check the permissions?  can ORACLE run your script?  is the r-x flag on
the entire path to this file? is ls in the rarified environment these scripts
run in?  what is the path?  did you put some

/usr/bin/echo $PATH > /tmp/$$.debug

type statements in there?
is the script getting run at all?

did you read the "great response, but... " comment above and do what I suggested
there?

.......
Rating: 3
January 09, 2003
Reviewer: Stephan from Netherlands
Try to execute the following:

exec rc('/usr/bin/sh /u01/home/sinvb/tong/tsp1.txt');

Note the sh command to open a shell and your script as 'parameter'. Be sure you
have execute permissions for sh (with dbms_java.grant_permission).

This should output the desired result.
Followup:
as I said above however -- that would be a really bad idea.

you have to give privs on sh

exec rc( '/usr/bin/sh rm -rf /u01/apps/oracle/oradata/your_sid/*' )

is as easy to execute as tsp1.txt would be!  caveat emptor.
Rating: 1
Using Java January 16, 2003
Reviewer: Abdal-Rahman Agha from Yemen
Hi Tom,
- Sorry, I am interesting to use Java in Oracle, can you advice on a book or any
good place to learn about Oracle Java quickly?
- I have Win2k, Oracle9 R2: Please, explain this output:

SQL> exec rc('E:\WINNT\command.* /c dir');
java.lang.ArrayIndexOutOfBoundsException
at Util.RunThis(Util.java:14)

PL/SQL procedure successfully completed.
Followup:
that error just means you got really excited -- stopped reading the article and
went to play with it!  read on -- ctl-f for ArrayIndexOutOfBoundsException and
you'll see the cause.

you might be interested in
http://www.amazon.com/exec/obidos/ASIN/1861006020/
Rating: 5
Is it possible to call os comand of another server January 18, 2003
Reviewer: Reader from UAE
Hi Tom

This is excellent piece of Code.

i have one clarification.  Is it possible to call a particular command in other
unix server.

Ie My oracle DB is in Unix server A
My ETL tool is running in Unix server B
I want to call the ETL Process as Commandline from Java procedure.  Will it need
to login into Server B. If so How to do it.
Followup:
if you can do it from sever A on the command line (outside of oracle), we can do
it inside the database.

Solve that problem and we'll go from there.  Oracle cannot bypass the OS and do
magical things beyond what you've set up at the OS level.

We can use C based extprocs in 9i to do this as the extproc is allowed to run on
another machine all together.  but basically, you would need an agent on server
b one way or another.
Rating: 3
error on excuting java February 03, 2003
Reviewer: Nikunj
hi tom,

i have oracle ee 8.1.7 on windows nt 4.0 with service pack 6a. i want to execute
host command from database.

so i follow ur steps as below.

SQL> CONNECT SYSTEM@BABUL
Enter password: *******
Connected.
SQL> begin
  2  dbms_java.grant_permission
  3  ('SCOTT',
  4  'java.io.FilePermission',
  5  'C:\WINNT',
  6  'execute');
  7
  8  dbms_java.grant_permission
  9  ('SCOTT',
 10  'java.lang.RuntimePermission',
 11  '*',
 12  'writeFileDescriptor' );
 13  end;
 14  /

PL/SQL procedure successfully completed.

SQL> connect scott@babul
Enter password: *****
Connected.
SQL>
SQL> create or replace and compile
  2  java source named "Util"
  3  as
  4  import java.io.*;
java source named "Util"
                  *
ERROR at line 2:
ORA-29536: badly formed source: Encountered "<EOF>" at line 1, column 17.

Was expecting:

";" ...

why i am geting this error.

thanks nikunj
Followup:
what version of sqlplus do you have.

This:

ops$tkyte@ORA817DEV> create or replace and compile
  2  java source named "Util"
  3  as
  4  import java.io.*;
java source named "Util"
                  *
ERROR at line 2:
ORA-29536: badly formed source: Encountered "<EOF>" at line 1, column 17.
Was expecting:
";" ...

is what I get when I use 8.0 sqlplus which didn't understand "java source".  It
just sucked the code upto the first ';' and submitted it (since sqlplus didn't
know back then that java source

a) existed
b) could contain ';' like plsql can

suggest you get your client software updated.
Rating: 5
Java Permisson February 25, 2003
Reviewer: Steve from NYC, USA
Hi Tom,

I just tried the answer here but I got the following messssage. What should I
do? Thanks!  --steve

SQL> call testjava.run_cmd('ls') into :y;
java.security.AccessControlException: the Permission (java.io.FilePermission
<<ALL FILES>> execute)
has not been granted by dbms_java.grant_permission to
SchemaProtectionDomain(SRS|PolicyTableProxy(SRS))
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 Util.RunThis(Util:13)

Call completed.
Followup:
did you use dbms_java grant permission to grant yourself the needed privs?  like
my example does?
Rating: 4
Why it is not able to run java class March 13, 2003
Reviewer: Umesh Nimbalkar from Mumbai, India
Dear Tom,
     I want to run class placed in one directory. For this i am giving
permissions as follows

begin
        dbms_java.grant_permission
        ('SALES',
         'java.io.FilePermission',
         '/usr/java130/jre/bin/java',
         'execute');

        dbms_java.grant_permission
        ('SALES',
        'java.lang.RuntimePermission',
        '*',
        'writeFileDescriptor' );
   end;
/

now when i am trying to execute as below
exec rc('/usr/java130/jre/bin/java -version');

it is not giving any output. but if i run like
exec rc('/usr/java130/jre/bin/java');
it is iving output.
Pls let me know why it is not accepting parameters.
Followup:
maybe the -version is writing to stderr.  mine does:

$ java -version > xxx
java version "1.3.1_02"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.1_02-b02)
Java HotSpot(TM) Client VM (build 1.3.1_02-b02, mixed mode)

I only caught stdout.
Rating: 4
Oracle Application Developer March 19, 2003
Reviewer: Dawar from CA USA
Is there a way to call unix command in oracle programs/ packages?

Dawar
Followup:
umm, you mean like this:

rt_test@DEV816> exec rc('/usr/bin/ps -ef');
UID   PID  PPID  C    STIME TTY      TIME CMD
root     0     0  0   Aug 17 ?        0:06 sched
root     1     0  0   Aug 17 ?        1:19 /etc/init -
root     2     0  0   Aug 17 ?        0:23 pageout
.....

PL/SQL procedure successfully completed.

which is the example from above???
Rating: 4
Having small problem April 24, 2003
Reviewer: Rob from Chicago, USA
I was able to run your example ps -ef with no problem.

I create a simple shell script on UNIX to copy a file rfk.scr:
cp /apps/actappl/rfk.test /apps/actappl/rfk.test2

I can run this with no problem using the Oracle account on UNIX.

When I try to run it using the Java callout nothing happens, no errors no copy.

DECLARE
t NUMBER(9);
BEGIN

t := act_dw_sys_events.run_cmd('/apps/actappl/rfk.scr');
END;

Any suggestions?

Thanks Rob
Followup:
scripts can be tricky.

did you grant on it.
is is executable by the oracle software owner.
is the path to it traversable by the oracle software owner.
can the oracle software owner write to that directory.
can it read that file.
what was the return code.

look for (cnelson) on this page (had the same issue).  ideas are there as well.
Rating: 4
interesting response but do not answer to my problem April 24, 2003
Reviewer: Rene Paradis from Canada,Quebec
I want to execute a second perl script into the perl script called by the Java
procedure like:
system("/path/perl_script.pl argv");
But
But the java grant permissions seem to allow only the first perl script to be
executed and this one ends when the system command is called.
here is the granted permissions
 begin
           dbms_java.grant_permission
           ('SUPER_MAN',
            'java.io.FilePermission',
            '/path/first_script.pl',
            'execute,read ,wrtie');

           dbms_java.grant_permission
           ('SUPER_MAN',
           'java.lang.RuntimePermission',
           '*',
           'writeFileDescriptor' );
      end;
      /
 begin
           dbms_java.grant_permission
           ('SUPER_MAN',
            'java.io.FilePermission',
            '/path/second_script.pl',
            'execute,read ,wrtie');

           dbms_java.grant_permission
           ('SUPER_MAN',
           'java.lang.RuntimePermission',
           '*',
           'writeFileDescriptor' );
      end;
      /
if it is not possible to call a second perl script inside the first one, I
planned to call it separatly:
declare
      x number;
    begin
      x := run_cmd('/path/first_script.pl argv');
      x := run_cmd('/path/second_script.pl argv');
    end;
    /
Followup:
I missing your point?

if first_script.pl calls second_script.pl -- you need not grant on it (the
Oracle JVM has *no clue* what first script might do, and it is allowed to do
anything it wants really as far as running other commands go)

if first_script.pl doesn't call second_script.pl, but you want to -- you just
need to grant on it.

So, I'm not understanding what you are saying here.
Rating: 5
excellent April 25, 2003
Reviewer: A reader
Rating: 2
did not solve my problem (first and second perl script) April 25, 2003
Reviewer: Rene Paradis from Canada, Quebec
Hi again Tom,
here is my part of code inside the first perl script that calls the second perl
script:
...
$fichier = shift @ARGV;
print("test1 tahaa/mascot/data/make_input_txt.pl $fichier\n");
#we build a txt file here
$x = system("/tahaa/mascot/data/make_input_txt.pl $fichier");
print("x value is $x\n");
...

the dbms.output would be:
test1 /seq/murin/proteo/make_input_txt.pl
/tahaa/mascot/data/20030327/F006236.dat

PL/SQL procedure successfully completed.

The x value is not printed. but when I execute the command manually
(/seq/murin/proteo/make_input_txt.pl /tahaa/mascot/data/20030327/F006236.dat),
the script works. So it really seems that the first script cannot execute the
system command, altough it does when it is executed manually. What should I do
to make it work?
thank you very much for your help
Followup:
this looks like a classic PERMISSION problem at the OS level.

put some debug in.  remember -- as coded, this catches ONLY stdout -- you might
want yet another wrapper -- a script that runs perl1 (which runs perl2) but
redirects stderr to stdout so you can see it.

UNDOUBTABLY perl is pumping out an error to stderr and you just haven't seen it
yet.
Rating: 4
May 07, 2003
Reviewer: dk
Hi Tom,

I am a dveloper.
When I try to creat a java stored procedure in database, I met the following
message, what happend ? What requirement I need
to ask DBA?

Thanks!

DLIV>create or replace and compile
  2  java source named "Util"
  3  as
  4     import java.io.*;
java source named "Util"
                  *
ERROR at line 2:
ORA-29547: Java system class not available: oracle/aurora/rdbms/Compiler
Followup:
search for

ORA-29547

on this site
Rating: 4
It almost works perfectly May 15, 2003
Reviewer: Mark from USA
Tom,

I will appologize in advance because I think this might go a bit beyond the
scope of the original question.

I modified your code to work on Windows and after some dumb mistakes on my part
I can now execute commands at will from Oracle 9i stored procedures.

Thanks

There is one problem though that I have not been able to resolve yet.  When I
execute a batch file and that batch file then executes another program (not
internal command) I get no output.  It's like standard out has been redirected
to the bit bucket.  If I execute an internal command like 'dir' or 'set' I get
the output in the stored procedure.  I can also redirect that output to a file
from within the batch file called from the stored procedure.  But when I execute
an external program, for example a "Hello World" java program or grep (windows
version) to get some text, there is no output.  The ERRORLEVEL indicates the
program ran.

Any ideas what could be going on here?
Followup:
windows isn't unix and concepts like stdin, stderr, stdout are so foriegn to it.

they do not all share the same "tty" if you will.  don't know what to tell you
other then use redirection and then "type" the output.
Rating: 4
Can't get permissions to set up correctly.. May 16, 2003
Reviewer: Mahomed from U.K
Oracle9i Release 9.2.0.1.0 - Production
JServer Release 9.2.0.1.0 - Production

SQL> connect / as sysdba

begin

   dbms_java.grant_permission
      ('CDB_TRAIN',
      'java.io.FilePermission',
      '/data4/cdb_train/exe/exec_cmd',
      'execute');

   dbms_java.grant_permission
      ('CDB_TRAIN',
       'java.lang.RuntimePermission',
       '*',
       'writeFileDescriptor');
 end;
/
PL/SQL procedure successfully completed.

exec dbms_java.grant_permission(
'CDB_TRAIN','SYS:java.io.FilePermission','/usr/cdb_train/exe/exec_cmd',
'execute' );

PL/SQL procedure successfully completed.

exec :x := pkg_utils.sp_cmd('test',3,'/data4/cdb_train/exe/db_locks');
java.security.AccessControlException: the Permission (java.io.FilePermission
/usr/cdb_train/exe/exec_cmd execute) has not been granted to CDB_TRAIN. The
PL/SQL to grant this is dbms_java.grant_permission( 'CDB_TRAIN',
'SYS:java.io.FilePermission', '/usr/c
db_train/exe/exec_cmd', '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 MyCommand.RunThis(MyCommand.java:14)

PL/SQL procedure successfully completed.
Followup:
so show us a TRUE cut and paste, nothing doctored as above.
Rating: 5
Another compile problem... May 18, 2003
Reviewer: Jack Wells from Fort Lauderdale, FL USA
Tom,
I'm getting the following error when I try to create the Util Java source:

SQL> sho err
Errors for JAVA SOURCE Util:

LINE/COL ERROR
-------- -----------------------------------------------------------------
0/0      Util:4: Superclass object of class Util not found.
0/0      Info: 1 errors
SQL>

I get the same error on an 8.1.7.4 HP/UX instance, a Linux 9.2.0.3 instance and
a 9.2.0.1 Windows instance.  Running the dbms_java.grant_permission, however,
works fine on all three.  What is the "superclass object" of Util?  Is it one of
the two imports just above the Util class declaration?
Followup:
hows about the entire example -- never saw that myself.
Rating: 5
Another compile problem (full example)... May 18, 2003
Reviewer: Jack Wells from Fort Lauderdale, FL USA
Tom,
Here is the whole example:

SQL> connect sys@ncgd
Enter password: *****
Connected.
SQL> select version from v_$instance;

VERSION
-----------------
8.1.7.4.0

1 row selected.

SQL> CREATE OR REPLACE AND COMPILE
  2      JAVA SOURCE NAMED "Util"
  3     AS
  4      import java.io.*;
  5      import java.lang.*;
  6
  7      public class Util extends object
  8      {
  9        public static int runthis(string args)
 10       {
 11       runtime rt = runtime.getruntime();
 12       int        rc = -1;
 13
 14       try
 15       {
 16          process p = rt.exec(args);
 17
 18          int bufsize = 4096;
 19          bufferedinputstream bis =
 20           new bufferedinputstream(p.getinputstream(), bufsize);
 21          int len;
 22          byte buffer[] = new byte[bufsize];
 23
 24          // echo back what the program spit out
 25          while ((len = bis.read(buffer, 0, bufsize)) != -1)
 26             system.out.write(buffer, 0, len);
 27
 28          rc = p.waitfor();
 29       }
 30       catch (exception e)
 31       {
 32          e.printstacktrace();
 33          rc = -1;
 34       }
 35       finally
 36       {
 37          return rc;
 38       }
 39       }
 40     }
 41  /

Warning: Java created with compilation errors.

SQL> sho err
Errors for JAVA SOURCE Util:

LINE/COL ERROR
-------- -----------------------------------------------------------------
0/0      Util:4: Superclass object of class Util not found.
0/0      Info: 1 errors
SQL>
Followup:
object

should be

Object

initcap, java is case sensitive.

rt_test@DEV816> create or replace and compile
  2  java source named "Util"
  3  as
  4  import java.io.*;
  5  import java.lang.*;
  6
  7  public class Util extends Object
  8  {
  9
Rating: 5
Duh... May 19, 2003
Reviewer: Jack Wells from Fort Lauderdale, FL USA
Oh.

After correcting that faux pax, there were a few other "spelling" mistakes I
needed to correct...  works like a charm now!

p.s. Note to self: don't use PL/Formatter on Java code!
Rating: 5
Got an error May 19, 2003
Reviewer: A reader
I copied the same code but got an error

SQL> exec rc('/usr/bin/ps -ef');
java.lang.ArrayIndexOutOfBoundsException
at Util.RunThis(UTIL.java:14)

What is problem?
Rating: 4
What are the pre-requisites to run this? June 10, 2003
Reviewer: Emelyn from Hayward, CA
In order to run your Java program, do I need to add entries to listener.ora and
tnsnames.ora or any preparation or set-up to be done?
I'm running version 8.1.5.
Thanks in advance!
Followup:
just have java installed in the database.
Rating: 4
What are the pre-requisites to run this? June 10, 2003
Reviewer: Emelyn from Hayward, CA
Here's what I got when I ran sql*plus:

SQL*Plus: Release 8.0.5.0.0 - Production on Tue Jun 10 14:52:27 2003

(c) Copyright 1998 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

With the phrase: With the Partitioning and Java options
Does it mean, Java is installed?
Followup:
yes.

you'll have problems creating java with the 805 sqlplus client however --
sqlplus "parses" statements before submitting them.  it won't recognize 'create
or replace java ....' and will most likely "barf"
Rating: 3
Some points regarding to pass parameters to your shell June 11, 2003
Reviewer: Erdal from Seattle
I have been using the Java Stored Procedures a lot to replace the Pro*C Codes
and all the other workarounds we did in the past to execute a command on the
operating system (mostly UNIX).
There's a very good article here
http://www.mountainstorm.com/publications/javazine.html
It helped me a lot for passing parameters to UNIX Shell.
{"/bin/sh", "-c", "Your Command") format has no surprises when executing
anything on UNIX.
Followup:
unless of course, someone gives you

"rm -rf /usr/oracle/oradata"

to run.  /bin/sh would not be something I would want "open" to run.

/bin/ps, sure
/bin/sh (run anything you like), not so sure I'd like that.
Rating: 4
about username for dbms_java.grant_permission June 18, 2003
Reviewer: Jianhui from DC
The username must be uppercase or it will report user or role doesnt exist
error. This may need to be improved since pl/sql is not case sensitive
traditionally.
Rating: 5
Issue with 9i June 19, 2003
Reviewer: Steve from UK
Hi Tom

I've successfully run this in 8.1.7 and have put your amended code into 9.0.1.4
but get an error.  Could you glance at it and tell me where I've gone wrong
please?

ltop_owner@EU0084P> conn sys/eu0084psys@eu0084p as sysdba
Connected.
ltop_owner@EU0084P> ed
Wrote file afiedt.buf

  1  begin
  2        dbms_java.grant_permission
  3        ('LTOP_OWNER',
  4         'java.io.FilePermission',
  5         'e:\orant_eu0084p\scripts\bandvisit.bat',
  6         'execute');
  7        dbms_java.grant_permission
  8        ('LTOP_OWNER',
  9        'java.lang.RuntimePermission',
 10        '*',
 11        'writeFileDescriptor' );
 12*    end;
ltop_owner@EU0084P> /

PL/SQL procedure successfully completed.

ltop_owner@EU0084P> conn ltop_owner/prodltop@eu0084p
Connected.
ltop_owner@EU0084P> ed
Wrote file afiedt.buf

  1  create or replace and compile
  2     java source named "Util"
  3     as
  4     import java.io.*;
  5     import java.lang.*;
  6     public class Util extends Object
  7     {
  8       public static int RunThis(String args)
  9       {
 10       Runtime rt = Runtime.getRuntime();
 11       int        rc = -1;
 12       try
 13       {
 14          Process p = rt.exec(args);
 15          int bufSize = 4096;
 16          BufferedInputStream bis =
 17           new BufferedInputStream(p.getInputStream(), bufSize);
 18          int len;
 19          byte buffer[] = new byte[bufSize];
 20          // Echo back what the program spit out
 21          while ((len = bis.read(buffer, 0, bufSize)) != -1)
 22             System.out.write(buffer, 0, len);
 23          rc = p.waitFor();
 24       }
 25       catch (Exception e)
 26       {
 27          e.printStackTrace();
 28          rc = -1;
 29       }
 30       finally
 31       {
 32          return rc;
 33       }
 34       }
 35*    }
 36  /

Java created.

ltop_owner@EU0084P> ed
Wrote file afiedt.buf

  1  create or replace
  2      function RUN_CMD(p_cmd in varchar2) return number
  3      as
  4      language java
  5*     name 'Util.RunThis(java.lang.String) return integer';
ltop_owner@EU0084P> /

Function created.

ltop_owner@EU0084P> ed
Wrote file afiedt.buf

  1  create or replace procedure RC(p_cmd in varchar2)
  2      as
  3        x number;
  4      begin
  5        x := run_cmd(p_cmd);
  6*     end;
  7  /

Procedure created.

ltop_owner@EU0084P> conn ltop_owner/prodltop@eu0084p
Connected.
ltop_owner@EU0084P> variable x number
ltop_owner@EU0084P> set serveroutput on
ltop_owner@EU0084P> exec dbms_java.set_output(100000);

PL/SQL procedure successfully completed.

ltop_owner@EU0084P> exec :x := RUN_CMD('/usr/bin/ls /tmp');
java.io.IOException: The handle is invalid.
at oracle.aurora.java.lang.OracleProcess.create(OracleProcess.java)
at oracle.aurora.java.lang.OracleProcess.construct(OracleProcess.java)
at java.lang.Runtime.execInternal(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:11)

PL/SQL procedure successfully completed.

ltop_owner@EU0084P>
Followup:
you seem to be on windows (e:\ ...)

but you seem to be trying to use the power of unix (/usr/bin/ls...) ???
Rating: 5
Oops....just checking you were awake!! June 19, 2003
Reviewer: steve from UK
Hide my shame!!!!  I'll just wait for the ground to open up and swallow me!

Yes the server is on Windoze and if I run the correct command it runs as it
should.  The power of cut 'n paste!!!

"The DBA formerly known as Steve."
Rating: 5
OS Output June 21, 2003
Reviewer: Jack Wells from Boca Raton, FL
Tom,

I'm having trouble passing the OS output back in a parameter to the calling
PL/SQL binding function.  I'd like to do this rather than rely on the
dbms_output approach.  I've added the following to the function call (per your
instruction in your book):

      public static int RunOsCmd(java.lang.String OsCmdLine,
                                 java.lang.String[] CmdOutput)

Now I need to get the routine to stuff its output in the "CmdOutput" parameter.
How would I do this?
Followup:
you'll have to write a little java to concatenate the output instead of using
system.out to write it "on screen" and then assign that to CmdOutput[0] before
you return.
Rating: 5
How to concatenate output June 21, 2003
Reviewer: Jack Wells from Boca Raton, FL
Tom,

Here is my PL/SQL binding code and Java code and output from running the
program.  I can't seem to get the output in the return variable.  As I'm not a
Java dude, I suspect I'm not concatenating correctly or not handling the
"buffer" variable correctly.  What is the problem?

  FUNCTION run_command_java (
    fv_command   IN   VARCHAR2,
    fv_output    OUT  VARCHAR2
  )
    RETURN NUMBER
  AS
    LANGUAGE JAVA
    NAME 'NcgOSj.runOsCmd( java.lang.String,
                           java.lang.String[] ) return integer';

  PROCEDURE run_command_java (
    fv_command   IN       VARCHAR2,
    fv_output    OUT      VARCHAR2
  )
  IS
    ln_return_code   NUMBER;
  BEGIN
    ln_return_code    := run_command_java (fv_command, fv_output);
  END run_command_java;

CREATE OR REPLACE AND COMPILE
    JAVA SOURCE NAMED "NcgOSj"
   AS
    import java.io.*;
    import java.lang.*;

    public class NcgOSj extends Object
    {
      public static int runOsCmd(String OsCmdLine,
                                 String[] CmdOutput)
     {
     Runtime rt = Runtime.getRuntime();
     int     rc = -1;
     String  coutput = "";

     if (OsCmdLine.length() < 1) {
         System.out.println("USAGE: java NcgOSj.runOsCmd \'cmd\'
OutputStringVariable");
         System.exit(1);
     }
     try
     {
        // The exec() method in java.lang.Runtime is overloaded and
        // can be called in different forms.  The following use illustrates
        // the simplest form of exec() which takes a string as a parameter.
        //
        // Execute the command using the Runtime object variable 'rt' and
        // get the process 'p' which controls this command
        Process p = rt.exec(OsCmdLine);

        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);
           coutput = coutput + buffer;
        }
        // Wait for the process 'p' to finish and get the return code
        // from the process
        rc = p.waitFor();
     }
     catch (Exception e)
     {
        e.printStackTrace();
        rc = -1;
     }
     finally
     {
        CmdOutput[0] = coutput;
        return rc;
     }
     }
   }
/

VARIABLE outpt VARCHAR2(2000)
SET serveroutput on
EXEC dbms_java.set_output(100000);
EXEC run_command_java ('/bin/ls /', :outpt);
bin
boot
dev
etc
home
initrd
lib
lost+found
misc
mnt
ncg01
ncg02
ncg03
ncg04
ncglx01
ncglx01_cp.log
ncglx01_cp.sh
ncglx01_mount.sh
nohup.out
opt
proc
root
sbin
tmp
tmpdir
usr
var

PL/SQL procedure successfully completed.

PRINT outpt

OUTPT
-----------------------------------------------------------
[B@e8955a6a
Followup:
we will have to hope a java dude comes along.
Rating: 5
Convert... June 22, 2003
Reviewer: Kamal Kishore from New Jersey, USA
The byte array needs to be converted to a String before assigning to the OUT
variable.
The while (read...) loop needs to be changed to the following:

        // Echo back what the program spit out
        while ((len = bis.read(buffer, 0, bufSize)) != -1) {
           System.out.write(buffer, 0, len);
           ot = new String(buffer, 0, len) ;
           coutput = coutput + ot ;
        }

Where "ot" is a String variable declared in the program above like this:

     Runtime rt = Runtime.getRuntime();
     int     rc = -1;
     String  coutput = " ";
     String  ot = " " ;

This seems to work when run on my system.
Followup:
Thanks, appreciate that...
Rating: 5
Works like a charm! June 22, 2003
Reviewer: Jack Wells from Boca Raton, FL
Thanks Kamal!  Made those changes and it works just as expected.

Tell me, do you know how I could just convert the byte array variable "buffer"
to a string inline instead of declaring the new variable "ot"?  Something like:

   // capture the output in a string variable
   while ((len = bis.read(buffer, 0, bufSize)) != -1) {
      coutput = coutput + toString (buffer, 0, len);
   }

Wouldn't this use up less memory than allocating "new" memory for each iteration
for the "ot" variable?
Rating: 5
Hangs on sendmail June 23, 2003
Reviewer: Jack Wells from Boca Raton, FL
Tom,

Do you have any idea why the program above would hang on a call to sendmail?  As
long as I have done the dbms_java.grant_permission, the program works fine for
/bin/ls, /bin/rm, etc., but when I issue the following command from SQL*Plus, it
just hangs:

EXEC run_command_java (/usr/sbin/sendmail jwells@nichegroup.com <
/home/oracle/smtest.txt', :outpt);

The server is on Red Hat Linux AS 2.1 and 9.2 of the database.  Issuing the
command line as oracle at the Unix prompt works fine.  Does it work on your
Linux box?
Followup:
sendmail must be waiting for you to type something in.  If you goto the OS and
kill it, and it returns, that is what is happening.

write a script, call the script perhaps.

(or heck, just use UTL_SMTP eh?)

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

Oh I see it, search for

you would need to run a SHELL that understands...

on this page.  you sent a SHELL command (redirect).  you are thinking "oh,
anything that runs in shell, runs here", but it doesn't.  redirection is a
"shell thing"
Rating: 4
June 24, 2003
Reviewer: Yong from CA, USA
Jack,

You cannot pass more than one parameters in this case. As tom suggested: write a
script and run the script. You can use pl/sql to write a script and change run
mode and call the script.

BTW, why not use pl/sql send mail with attachemet?
Rating: 5
You were right July 07, 2003
Reviewer: Jack Wells from Boca Raton, FL
Tom,
You were right, it was the "<" redirection... a shell thing.  In my zeal to
ensure only granular permissions were issued, I had avoided giving execute
permission on /usr/sh.  When I did, though, it worked fine.  Now, the only thing
that bothers me is this execute priv on /usr/sh...
Followup:
so -- write a SCRIPT that you invoke that does the redirection.
Rating: 4
"Java procedure for host calls on Unix environment" August 11, 2003
Reviewer: Raja
Hi Tom,
We are looking to call a unix shell script from Oracle after an
update/insert/delete happens on a table in the database.  The above solution
looks kind of neat, however, just wondering whether the procedure RC above could
be called from a database trigger and made to execute a shell script (lets say
test.sh) OR do you have any other suggestions which might help? We are using
Oracle version 8.1.7

Thanks in advance!

Raja
Followup:
lets back up a second.

what exactly would this shell script do?  this sounds like a fairly "bad idea"
for many reasons.  Once I know what the script does, I'll give you a "for
example"
Rating: 4
"Java procedure for host calls on Unix environment" August 11, 2003
Reviewer: Raja
Hi Tom,
In simple words, what we are trying to do here is just notify the java webserver
that for example an update has occured, based on which it will initiate other
necessary actions on its side on the webserver.

Actually, I do not code java. Our java developers just gave me an idea of what
they want (for example to be able to execute host calls - shell scripts from
Oracle which will notify the webserver for either executing something on the
webserver with respect to the database update event on a table or maybe run
shell script which could FTP some files or other information for each updated)

Let me know if this is enough or need more details.

Regards,
Raja
Followup:
well, the update hasn't really happened -- NOT until you commit anyhow.

what about this:

update t set x = 5;  -- update 1,000,000 records
ROLLBACK;

whoops -- so sorry, I just ran 1,000,000 sh scripts to notify someone about

a) an even they cannot see (since I did not commit, they can never have seen the
updated data)

b) that didn't really happen.

Another interesting on -- read this article:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:11504247549852
to see an update that updates 4 rows BUT fires the row trigger 6 times!  what
about that?

You want to read about AQ (advanced queues) I believe and they want to use JMS
to be notified about events.

It is nice and transactional.  they'll only get notified of REAL ACTUAL
committed events.
Rating: 4
"Java procedure for host calls on Unix environment" August 12, 2003
Reviewer: Raja
Hi Tom,
Thanks a lot for a prompt reply.  This is certainly helping.  Actually, just
wanted to know if JMS supported in Oralce 8.1.7 as, I just happened to go to
metalink (let me know if I am looking at the right article).....
http://metalink.oracle.com/metalink/plsql/ml2_documents.showDocument?p_database_id=NOT&p_id=232301.1
and it mentions.....
The following requirements need to be satisfied when using JMS-AQ:

OC4J 9.0.3.0.0
Supported RDBMS version
Oracle9i Database 9.0.1.4 (Patch: 2517300)
Oracle9i Database 9.2.0.2 (Patch: 2632931)
For further information, see:

Note:230372.1

Send JMS Messages to OJMS Queue Fails with PLS-00306

You,ve been very helpful!
Regards,
Raja
Followup:
read
http://download-west.oracle.com/docs/cd/A87860_01/doc/appdev.817/a76938/adq03pr7.htm#62580
Rating: 4
is there any security issue August 26, 2003
Reviewer: please help from New York, USA
Hi Tom,

Thanks for this nice solution, currently I am using it to call sqlldr to load a
file into a table from plsql, since it will run sqlldr as user oracle, my boss
worrys about any possible security hole here, please give me some your comments

Thanks
Followup:
what is your boss worried about exactly?

using java with very very specific grants like this (eg: this user is allowed to
run /home/oracle/bin/sqlldr and nothing else) is much more secure then the other
methods of:

o using an extproc.  that can run any code the user running the extproc listener
can run

o using "java_sys_priv" or some other broad ranging privs.

if you have a specific concern, we can address that.
Rating: 5
Trigger again September 16, 2003
Reviewer: Warren from Canada
Tom,

I noticed that you had put the person off who asked about using triggers, but I
have a similar question.  OK, I have a third party app that is inserting or
updating into a table, tableA.  I would like to set up a trigger on these
actions such that it will call a batch file (say devtest.bat, sorry I had to use
Windows for these guys).  Can I use your method to pass a column name (call it
Project_Name) from tableA to the devtest.bat file?

The insert/update is automatically committed by the application when the click
on the OK button.  No rollback opportunity, and no other ad-hoc application
(i.e. sqlplus) access to the database.

I have your method working calling the devtest.bat file directly, but can't get
a trigger working for this.

Thanks!
Followup:
tell you want -- read this:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:11504247549852
and see if you REALLY want to call a bat file from a trigger. pretend where I
use a sequence - you called your bat file.  think about the ramifications....

you want to use a stored procedure, invoked by dbms_job, AFTER the transaction
commits.  It is the only transactional method that works.

yes, you can pass a parameter to a script.
but I don't have a windows machine anywhere to play with to test for you.
you might have to invoke "cmd /c ....." in order to get it to go.
but that means you need to be able to run "cmd.exe" which means you can run
ANYTHING.  (not good)
Rating: 4
Works nicely December 02, 2003
Reviewer: Mike from St. Louis
Tom's code works well.  Question: is it possible to expand the solution to also
populate the DIR_LIST table with file attributes such as size and last-mod date?
 If so, any code snippets to share?
Rating: 5
Return value into a variable January 07, 2004
Reviewer: Highlander from NY
Tom,
Can you post an axample of the "Util" java that will return the output to a
variable or lob instead of dbms_output.

Thank you.
Followup:
on the road, in a car right now (not conducive to massive coding :)

ask some other time when I'm taking questions (or search the site for java
examples that show how to return strings and other such things -- they are out
there, I had them all in my book "Expert one on one Oracle" as well)
Rating: 4
AUTHID CURRENT_USER - privileges problem January 08, 2004
Reviewer: Andriy Terletskyy from Germany
Hi Tom,

I have two Questions:

1. I defined the java source and wrapper function RUN_CMD with invoker-rights
AUTHID CURRENT_USER in SCHEMA1 (GLOBAL) and granted the execute right on RUN_CMD
to SCHEMA2 (WP).

After that I granted the execute and writeFileDescriptor permission with
DBMS_JAVA.GRANT_PERMISSION to user WP (SCHEMA2).

If I trying to execute this functiom with user WP (SCHEMA2) :

...
 GLOBAL.run_cmd('/oracle/u01/app/oracle/admin/BBSE/appscripts/ls.sh')
...

FEHLER in Zeile 1:
ORA-29532: Java call terminated by uncaught Java exception:
java.security.AccessControlException: the Permission (java.io.FilePermission
/oracle/u01/app/oracle/admin/BBSE/appscripts/ls.sh execute) has not been
granted to GLOBAL. The PL/SQL to grant this is dbms_java.grant_permission(
'GLOBAL', 'SYS:java.io.FilePermission',
'/oracle/u01/app/oracle/admin/BBSE/appscripts/ls.sh', 'execute' )
ORA-06512: at "GLOBAL.RUN_CMD", line 0
ORA-06512: at line 5

What's wrong?

2. How I can write the wrapper function based on Java-class another user?

WP@BBSE> create or replace
  2    function RUN_CMD( p_cmd  in varchar2) return number
  3    as
  4    language java
  5    name 'GLOBAL:Util.RunThis(java.lang.String[]) return integer';
  6  /

Warnung: Funktion wurde mit Kompilierungsfehlern erstellt.

WP@BBSE> show error
Fehler bei FUNCTION RUN_CMD:

LINE/COL ERROR
-------- -----------------------------------------------------------------
0/0      PL/SQL: Compilation unit analysis terminated
4/3      PLS-00311: the declaration of
         "GLOBAL:Util.RunThis(java.lang.String[]) return integer" is
         incomplete or malformed

Thanks
Followup:
1) worked for me.  See full example below (shows that the invoker OPS$TKYTE
needs the priv, not the owner RT_TEST)

2) this works:

ops$tkyte@ORA920PC> create synonym "Util" for rt_test."Util"
  2  /

Synonym created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create or replace
  2  function RUN_CMD(p_cmd in varchar2) return number
  3  as
  4  language java
  5  name 'Util.RunThis(java.lang.String) return integer';
  6  /

Function created.

And in this case -- OPS$TKYTE is the true invoker and OPS$TKYTE would need
execute on ls (not RT_TEST)

ops$tkyte@ORA920PC> begin
  2    dbms_java.grant_permission
  3    ('RT_TEST',
  4     'java.io.FilePermission',
  5     '/bin/ls',
  6     'execute');
  7
  8    dbms_java.grant_permission
  9    ('RT_TEST',
 10     'java.lang.RuntimePermission',
 11     '*',
 12     'writeFileDescriptor' );
 13  end;
 14  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> @connect rt_test/rt_test
ops$tkyte@ORA920PC> set termout off
rt_test@ORA920PC> REM GET afiedt.buf NOLIST
rt_test@ORA920PC> set termout on
rt_test@ORA920PC>
rt_test@ORA920PC> create or replace and compile
  2  java source named "Util"
  3  as
  4  import java.io.*;
  5  import java.lang.*;
  6
  7  public class Util extends Object
  8  {
  9    public static int RunThis(String args)
 10    {
 11    Runtime rt = Runtime.getRuntime();
 12    int        rc = -1;
 13
 14    try
 15    {
 16       Process p = rt.exec(args);
 17
 18       int bufSize = 4096;
 19       BufferedInputStream bis =
 20        new BufferedInputStream(p.getInputStream(), bufSize);
 21       int len;
 22       byte buffer[] = new byte[bufSize];
 23
 24       // Echo back what the program spit out
 25       while ((len = bis.read(buffer, 0, bufSize)) != -1)
 26          System.out.write(buffer, 0, len);
 27
 28       rc = p.waitFor();
 29    }
 30    catch (Exception e)
 31    {
 32       e.printStackTrace();
 33       rc = -1;
 34    }
 35    finally
 36    {
 37       return rc;
 38    }
 39    }
 40  }
 41  /

Java created.

rt_test@ORA920PC> grant execute on "Util" to public
  2  /

Grant succeeded.

rt_test@ORA920PC>
rt_test@ORA920PC> create or replace
  2  function RUN_CMD(p_cmd in varchar2) return number
  3  authid current_user
  4  as
  5  language java
  6  name 'Util.RunThis(java.lang.String) return integer';
  7  /

Function created.

rt_test@ORA920PC> show errors
No errors.
rt_test@ORA920PC>
rt_test@ORA920PC> create or replace procedure RC(p_cmd in varchar2)
  2  authid current_user
  3  as
  4    x number;
  5  begin
  6    x := run_cmd(p_cmd);
  7  end;
  8  /

Procedure created.

rt_test@ORA920PC> grant execute on rc to public
  2  /

Grant succeeded.

rt_test@ORA920PC>
rt_test@ORA920PC> variable x number;
rt_test@ORA920PC> set serveroutput on
rt_test@ORA920PC> exec dbms_java.set_output(100000);

PL/SQL procedure successfully completed.

rt_test@ORA920PC>
rt_test@ORA920PC>
rt_test@ORA920PC> exec :x := RUN_CMD('/bin/ls /tmp');
afiedt.buf
BEQ2113
DCE2113
DEC2113
foo.dat
hostname.txt
ISPX2113
ITCP2113
kde-tkyte
ksocket-tkyte
log
LU622113
net2113
NMP2113
nscopy.tmp
nsmail-1.tmp
nsmail-2.tmp
nsmail-3.tmp
nsmail-4.tmp
nsmail-5.tmp
nsmail.eml
nsmail.html
nsmail.tmp
orbit-tkyte
RAW2113
SPX2113
ssh-XXDMz0hl
TCP2113
TCPS2113
US2113
VI2113

PL/SQL procedure successfully completed.

rt_test@ORA920PC>
rt_test@ORA920PC> @connect /
rt_test@ORA920PC> set termout off
ops$tkyte@ORA920PC> REM GET afiedt.buf NOLIST
ops$tkyte@ORA920PC> set termout on
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create synonym "Util" for rt_test."Util"
  2  /

Synonym created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create or replace
  2  function RUN_CMD(p_cmd in varchar2) return number
  3  as
  4  language java
  5  name 'Util.RunThis(java.lang.String) return integer';
  6  /

Function created.

ops$tkyte@ORA920PC> show errors
No errors.
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create or replace procedure RC(p_cmd in varchar2)
  2  as
  3    x number;
  4  begin
  5    x := run_cmd(p_cmd);
  6  end;
  7  /

Procedure created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> variable x number;
ops$tkyte@ORA920PC> set serveroutput on
ops$tkyte@ORA920PC> exec dbms_java.set_output(100000);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> exec :x := RUN_CMD('/bin/ls /tmp');
java.security.AccessControlException: the Permission (java.io.FilePermission
/bin/ls 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', '/bin/ls', 'execute' )
at
java.security.AccessControlContext.checkPermission(AccessControlContext.java:283)

at java.security.AccessController.checkPermission(AccessController.java:399)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:545)
at
oracle.aurora.rdbms.SecurityManagerImpl.checkPermission(SecurityManagerImpl.java:
267)
at java.lang.SecurityManager.checkExec(SecurityManager.java:800)
at java.lang.Runtime.exec(Runtime.java:548)
at java.lang.Runtime.exec(Runtime.java:418)
at java.lang.Runtime.exec(Runtime.java:361)
at java.lang.Runtime.exec(Runtime.java:325)
at Util.RunThis(Util.java:13)

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> exec :x := rt_test.RUN_CMD('/bin/ls /tmp');
java.security.AccessControlException: the Permission (java.io.FilePermission
/bin/ls 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', '/bin/ls', 'execute' )
at
java.security.AccessControlContext.checkPermission(AccessControlContext.java:283)

at java.security.AccessController.checkPermission(AccessController.java:399)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:545)
at
oracle.aurora.rdbms.SecurityManagerImpl.checkPermission(SecurityManagerImpl.java:
267)
at java.lang.SecurityManager.checkExec(SecurityManager.java:800)
at java.lang.Runtime.exec(Runtime.java:548)
at java.lang.Runtime.exec(Runtime.java:418)
at java.lang.Runtime.exec(Runtime.java:361)
at java.lang.Runtime.exec(Runtime.java:325)
at Util.RunThis(Util.java:13)

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
Rating: 4
Thanks for the question regarding "Java procedure for host calls on Unix environment", version 8.1.6 January 08, 2004
Reviewer: fakhri from Tunisia
hi tom,

i applied this script and i have error message :

SQL> ed
Wrote file afiedt.buf

  1  begin
  2        dbms_java.grant_permission
  3        ('scott',
  4         'java.io.FilePermission',
  5         '/usr/bin/ps',
  6         'execute');
  7       dbms_java.grant_permission
  8        ('scott',
  9         'java.lang.RuntimePermission',
 10         '*',
 11         'writeFileDescriptor' );
 12*   end;
SQL> /
begin
*
ERROR at line 1:
ORA-29532: appel Java arrêté par une exception Java non interceptée :
oracle.aurora.vm.IdNotFoundException: scott is not a user or role
ORA-06512: à "SYS.DBMS_JAVA", ligne 0
ORA-06512: à ligne 2

can you help me

thanks
Followup:
'scott' isn't a user

'SCOTT' probably is.
Rating: 4
AUTHID CURRENT_USER - privileges problem January 09, 2004
Reviewer: Andriy Terletskyy from Germany
Hi Tom,

my two Guestions in not answered:

1. It is well that I can see the invoker need the priv. But if I give the priv
to invoker and revoke that from owner - i see that:
ORA-29532: Java call terminated by uncaught Java exception:
java.security.AccessControlException: the Permission (java.io.FilePermission
/oracle/u01/app/oracle/admin/BBSE/appscripts/ls.sh execute) has not been
granted to GLOBAL.

The Permission of this script must be granted to the OWNER too? That is wrong.

2. Wrapper function based on Java-class another user.
That work, if java class is not in package, but need also owner execute
privs(see 1.)

But, if I put the class in package, that dosn't work:

WP@BBSE> @conn global@bbse
Kennwort eingeben: *****
GLOBAL@BBSE> create or replace and compile java source named util as
  2  package de.berenbergbank.global.tools;
  3
  4  import java.io.BufferedReader;
  5  import java.io.BufferedInputStream;
  6  import java.io.InputStream;
  7  import java.io.InputStreamReader;
  8  import java.io.IOException;
  9
 10  import java.util.Collections;
 11  import java.util.LinkedList;
 12  import java.util.List;
 13
 14
 15  /**
 16   * Created by IntelliJ IDEA.
 17   * User: Terletskyy
 18   * Date: 06.01.2004
 19   * Time: 13:26:35
 20   * To change this template use Options | File Templates.
 21   */
 22  public class Util {
 23
 24    public static String run_cmd(String command) throws Exception
 25    {
 26      StringBuffer myBuffer = new StringBuffer();
 27      Process p = Runtime.getRuntime().exec(command);
 28      int out;
 29
 30      int bufSize = 4096;
 31      BufferedInputStream bis = new BufferedInputStream(p.getInputStream(),
bufSize);
 32      byte buffer[] = new byte[bufSize];
 33      int len;
 34
 35      // Echo back what the program spit out
 36      while ( (len = bis.read(buffer, 0, bufSize)) != -1) {
 37        myBuffer.append(new String(buffer, 0, len));
 38      }
 39
 40      if ((out=p.waitFor()) != 0 ){
 41        throw new Exception("Error: "+out);
 42      }
 43
 44      return myBuffer.toString();
 45    }
 46
 47  }
 48  /

Java wurde erstellt.

GLOBAL@BBSE> GRANT EXECUTE ON GLOBAL."de/berenbergbank/global/tools/Util" TO
PUBLIC;

Benutzerzugriff (Grant) wurde erteilt.

WP@BBSE> create synonym "de/berenbergbank/global/tools/Util" for
GLOBAL."de/berenbergbank/global/tools/Util"
  2  /

Synonym wurde angelegt.

WP@BBSE> CREATE OR REPLACE FUNCTION RUN_CMD (in_command IN VARCHAR2) RETURN
VARCHAR2
  2   AS LANGUAGE JAVA NAME
'de.berenbergbank.global.tools.Util.run(java.lang.String) return
java.lang.String';
  3  /

Funktion wurde erstellt.

WP@BBSE> variable x Varchar2(4000);
WP@BBSE> set serveroutput on
WP@BBSE> exec dbms_java.set_output(100000);

PL/SQL-Prozedur wurde erfolgreich abgeschlossen.

WP@BBSE> exec :x :=  RUN_CMD
('/oracle/u01/app/oracle/admin/BBSE/appscripts/ls.sh');
java.lang.NoSuchMethodException: No applicable method found
at
oracle.aurora.util.JRIExtensions.getMaximallySpecificMethod(JRIExtensions.java)
at
oracle.aurora.util.JRIExtensions.getMaximallySpecificMethod(JRIExtensions.java)
BEGIN :x :=  RUN_CMD ('/oracle/u01/app/oracle/admin/BBSE/appscripts/ls.sh');
END;

*
FEHLER in Zeile 1:
ORA-29531: no method run in class de/berenbergbank/global/tools/Util
ORA-06512: at "WP.RUN_CMD", line 0
ORA-06512: at line 1

Thanks,
Andriy
Rating: 4
AUTHID CURRENT_USER - privileges problem January 09, 2004
Reviewer: Andriy Terlerskyy from Gernamy
Sorry it must be

WP@BBSE> CREATE OR REPLACE FUNCTION RUN_CMD (in_command IN VARCHAR2) RETURN
VARCHAR2
  2  AS LANGUAGE JAVA NAME
'de.berenbergbank.global.tools.Util.run_cmd(java.lang.String) return
java.lang.String';
  3  /

Funktion wurde erstellt.

WP@BBSE> show error
Keine Fehler.
WP@BBSE> variable x Varchar2(4000);
WP@BBSE> set serveroutput on
WP@BBSE> exec dbms_java.set_output(100000);

PL/SQL-Prozedur wurde erfolgreich abgeschlossen.

WP@BBSE> exec :x :=
RUN_CMD('/oracle/u01/app/oracle/admin/BBSE/appscripts/ls.sh');
java.security.AccessControlException: the Permission (java.io.FilePermission
/oracle/u01/app/oracle/admin/BBSE/appscripts/ls.sh execute) has not been granted
to GLOBAL. The PL/SQL to grant this is dbms_java.grant_permission( 'GLOBAL',
'SYS:java.io.FilePer
mission', '/oracle/u01/app/oracle/admin/BBSE/appscripts/ls.sh', '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
:267)
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 de.berenbergbank.global.tools.Util.run_cmd(UTIL.java:26)
BEGIN :x :=  RUN_CMD('/oracle/u01/app/oracle/admin/BBSE/appscripts/ls.sh'); END;

*
FEHLER in Zeile 1:
ORA-29532: Java call terminated by uncaught Java exception:
java.security.AccessControlException: the Permission (java.io.FilePermission
/oracle/u01/app/oracle/admin/BBSE/appscripts/ls.sh execute) has not been
granted to GLOBAL. The PL/SQL to grant this is dbms_java.grant_permission(
'GLOBAL', 'SYS:java.io.FilePermission',
'/oracle/u01/app/oracle/admin/BBSE/appscripts/ls.sh', 'execute' )
ORA-06512: at "WP.RUN_CMD", line 0
ORA-06512: at line 1
Followup:
yes, it appears that both need the grant -- if you feel this is "a bug", please
open a tar with support -- I cannot change the code.
Rating: 3
Java procedure for host calls on Unix environment January 09, 2004
Reviewer: fakhri from tunisia
hi tom,

thanks for your help, all the procedures are succefull, but when I execute :

SQL>  exec rc('/usr/bin/ps -ef');
java.lang.ArrayIndexOutOfBoundsException
at Util.RunThis(Util.java:11)

PL/SQL procedure successfully completed.

I have Oracle 9i database.

thx for you help.
fakhri
Followup:
and read the entire page........  ctl-f for arrayindexoutofbounds
Rating: 5
win-2003 January 09, 2004
Reviewer: RR from Richmond
In redhat linux AS 2.1  with oracle 9i-2 your code worked fine.But while I am
trying the following in win-2003 with oracle 9i-2 its giving the following
error. Please help.
SQL> begin
  2   dbms_java.grant_permission
  3       ('DEVEL',
  4        'java.io.FilePermission',
  5        'c:\oracle\backup\abc.bat,
  6        'execute');
  7  end;
  8  /
begin
*
ERROR at line 1:
ORA-04021: timeout occurred while waiting to lock object
SYS.JAVA$POLICY$SHARED$0000001d
ORA-06512: at "SYS.DBMS_JAVA", line 0
ORA-06512: at line 2
Followup:
well, it is not "my code", it is just a standard API call.

read through:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:292016138754#14645479504811
where we just worked through how to find out who is blocking you when you get
that 4021.
Rating: 5
RE: Win 2003 January 09, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
'c:\oracle\backup\abc.bat

Is missing the closing tick - is that correct?  Just an observation...

- Mark
Rating: 5
January 09, 2004
Reviewer: rr from richmond
file is present.It just hangs & giving the following error.
Same thing is happening with '*' too.

SQL> begin
  2   dbms_java.grant_permission
  3       ('DEVEL',
  4        'java.io.FilePermission',
  5        'c:\oracle\backup\*,
  6        'execute');
  7  end;
  8  /
begin
*
ERROR at line 1:
ORA-04021: timeout occurred while waiting to lock object
SYS.JAVA$POLICY$SHARED$0000001d
ORA-06512: at "SYS.DBMS_JAVA", line 0
ORA-06512: at line 2
Rating: 5
January 09, 2004
Reviewer: rr from richmond
closing tick ' is present in the actual code. I just made a typo after pasting
it here.thanks!
Rating: 5
Line 5 January 09, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
Is line 5 correct?

'c:\oracle\backup\abc.bat,

It looks like it is missing the closing tick:
i.e., 'c:\oracle\backup\abc.bat',

Or is it just getting dropped in the copy and paste?

- Mark
Rating: 5
January 09, 2004
Reviewer: A reader
AS its my test database , I just shutdown & restarted the database & everything
seems to be normal now. Thanks!
Rating: 5
January 09, 2004
Reviewer: A reader
The Beauty of Windows shutdown and restart, everything works fine.

:-))))))
Rating: 5
Only as 'oracle' o/s user? January 21, 2004
Reviewer: Robert from Memphis, USA.
Tom,

Is there any way to run this so that the o/s command is run as another user
besides the 'oracle' user?

We want to implement this to run users' application scripts, but would like to
limit potential security risks of users' scripts running as 'oracle'.

Thanks,

Robert.
Followup:
using a C based external procedure and a listener running "as that other user",
yes.

Using Java -- no.
Rating: 4
UnsatisfiedLinkError January 27, 2004
Reviewer: Janick Reynders from Belgium
Hi Tom,

A few months ago I tried to run a program from within Oracle using the procedure
you described here,
(Actually it was a shell script) and it worked great! But just recently I tried
to do the same thing on another machine, and now I get an exception:

java.lang.UnsatisfiedLinkError
at oracle.aurora.java.lang.OracleProcess.create(OracleProcess.java)
at oracle.aurora.java.lang.OracleProcess.construct(OracleProcess.java)
at java.lang.Runtime.execInternal(Runtime.java)
at java.lang.Runtime.exec(Runtime.java)
at java.lang.Runtime.exec(Runtime.java)
at java.lang.Runtime.exec(Runtime.java)
at UsBblLoadingWrapper.load(UsBblLoadingWrapper:13)

Until now I have not been able to find the problem. Any ideas what could be
going wrong?

Thanks,
Janick

this is the setup:
------------------
 Oracle9i

- The permissions:
    dbms_java.grant_permission('AMICUS', 'java.io.FilePermission',
'/home/ami35/bin/loading_script.sh', 'execute');
    dbms_java.grant_permission('AMICUS', 'java.lang.RuntimePermission', '*',
'writeFileDescriptor');

- The class:

create or replace and compile
java source named "UsBblLoadingWrapper"
as
import java.io.*;
import java.lang.*;

public class UsBblLoadingWrapper extends Object {

    public static int load() {
        Runtime rt = Runtime.getRuntime();
        int rc = -1;
        System.out.println("\n\n ---START--- \n\n");

        try {
            Process p = rt.exec("/home/ami35/bin/loading_script.sh");

            int bufSize = 4096;
            BufferedInputStream bis = new
BufferedInputStream(p.getInputStream(), bufSize);

            int len;
            byte buffer[] = new byte[bufSize];
            System.out.println("\n\n--stdout--\n");

            // Echo back what the program spit out
            while ((len = bis.read(buffer, 0, bufSize)) != -1) {
                System.out.write(buffer, 0, len);
            }

            BufferedInputStream errbis = new
BufferedInputStream(p.getErrorStream(), bufSize);

            System.out.println("\n\n--stderr--\n");
            while ((len = errbis.read(buffer, 0, bufSize)) != -1) {
                System.out.write(buffer, 0, len);
            }

            rc = p.waitFor();
        }
        catch (Throwable e) {
               e.printStackTrace();
               rc = -1;
        }
        finally    {
               return rc;
        }
    }
}
/

- the function:

create or replace
function TEST_EXTERNAL_PROGRAM return number
as
language java
name 'UsBblLoadingWrapper.load() return integer';
/

- the script:

#!/bin/sh
echo "test"

- the call:

SQL> set serveroutput on size 1000000
exec dbms_java.set_output(1000000)
DECLARE
           rc NUMBER;
BEGIN
         rc := TEST_EXTERNAL_PROGRAM();
         DBMS_OUTPUT.PUT_LINE('function returncode ' || rc);
END;
/SQL>
PL/SQL procedure successfully completed.

SQL>   2    3    4    5    6    7
---START---
java.lang.UnsatisfiedLinkError
at oracle.aurora.java.lang.OracleProcess.create(OracleProcess.java)
at oracle.aurora.java.lang.OracleProcess.construct(OracleProcess.java)
at java.lang.Runtime.execInternal(Runtime.java)
at java.lang.Runtime.exec(Runtime.java)
at java.lang.Runtime.exec(Runtime.java)
at java.lang.Runtime.exec(Runtime.java)
at UsBblLoadingWrapper.load(UsBblLoadingWrapper:13)
function returncode -1

PL/SQL procedure successfully completed.

SQL>
Followup:
if you are on version 9013 or 9014, contact support and ask them to peek at bug
2598380
Rating: 4
February 16, 2004
Reviewer: Sarada Priya from Chennai, India
Hi Tom,

I found this very useful, but I have a question to ask. I have a requirement
wherein the Database Server, 9iAS server & Reports Server(We put all the reports
output files in this after the nightly batch jobs) all in a networked
environment and the Client system is web-enabled Forms9i forms. We need to build
an interface wherein the user needs to see all the files available under a
designated folder in the Reports Server and should be allowed to print the same.
We are perplexed how to get the List of files from the Reports server. The above
problem(for which you have answered) deals with the DB server itself, but this
one???? Any idea? Please send in.
Thanks and Regards
Sarada Priya
Followup:
sorry, I don't work with reports but it seems to me that "mount" is the answer?
just attach a network drive and go at it -- whatever client you have would read
the directory.
Rating: 3
How can we diagnose problems? February 19, 2004
Reviewer: Ben from Quebec
Tom,
We've successfully implemented this on one database, and now I'm trying to
implement it on a new one, but it seems the thing doesn't work. Is there an
error log somewhere that would show me what went wrong?
Here's the code for the procedure that I implemented:
create or replace and compile java source named "Util" as
  import java.io.*;
  import java.lang.*;

  public class Util 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 'Util.RunThis(java.lang.String[]) return integer';
  /

As the SYS user, I granted the following privileges:
begin
      dbms_java.grant_permission
      ('ROCQ',
       'java.io.FilePermission',
       '/tahiti/bin/test.pl',
       'execute');

      dbms_java.grant_permission
      ('ROCQ',
       'java.lang.RuntimePermission',
       '*',
       'writeFileDescriptor' );
  end;
  /

The procedure that executes the system call:
CREATE OR REPLACE PROCEDURE rocq.test_runcmd IS

    resultat_rp        NUMBER;

BEGIN

    dbms_output.put_line('Beginning.');
    resultat_rp := RUN_CMD('perl /tahiti/bin/test.pl');
    dbms_output.put_line(resultat_rp);
    dbms_output.put_line('Ending.');

END;
/
show errors;

And the script that is called:
#!/usr/bin/perl -w

system "echo allo >allo";

Finally, the execution:
SQL> @ test_runcmd.pls

Procedure created.

No errors.
SQL> exec test_runcmd
Beginning.
-1
Ending.

PL/SQL procedure successfully completed.

Any ideas?
Thanks for your help!
Followup:
you granted execute on the script.

you cannot run "perl", no grant on "perl"

if you do this:

set serveroutput on size 1000000
exec dbms_java.set_output(1000000)

you should see the java output "on screen"
Rating: 3
February 19, 2004
Reviewer: Ben from Quebec
Just tried these grants:
begin
      dbms_java.grant_permission
      ('ROCQ',
       'java.io.FilePermission',
       'perl',
       'execute');

       dbms_java.grant_permission
      ('ROCQ',
       'java.io.FilePermission',
       '/usr/bin/perl',
       'execute');

      dbms_java.grant_permission
      ('ROCQ',
       'java.lang.RuntimePermission',
       '*',
       'writeFileDescriptor' );
  end;
  /

But the host call still doesn't work:

SQL> set serveroutput on size 1000000
exec dbms_java.set_output(1000000)
SQL>
PL/SQL procedure successfully completed.

SQL> exec test_runcmd
Beginning.
java.lang.ArrayIndexOutOfBoundsException
at Util.RunThis(Util:8)
-1
Ending.

PL/SQL procedure successfully completed.
Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:952229840241#2717585194628
Rating: 3
Permissions February 19, 2004
Reviewer: Ben from Quebec
Just did as you suggested, but now I'm running into a classic Permissions
problem.
SQL> begin
  2    dbms_java.grant_permission
  ('ROCQ',
   'SYS:java.io.FilePermission',
   'perl',
   'execute');

  dbms_java.grant_permission
  ('ROCQ',
   'SYS:java.io.FilePermission',
   '/usr/bin/perl',
   'execute');

  dbms_java.grant_permission
  ('ROCQ',
   'SYS:java.io.FilePermission',
  3    4    5    6    7     '/tahiti/bin/test.pl',
   'execute');

  dbms_java.grant_permission
  ('ROCQ',
   'SYS:java.lang.RuntimePermission',
   '*',
   'writeFileDescriptor' );
end;
/

PL/SQL procedure successfully completed.

SQL>
SQL> exec test_runcmd
Beginning.
java.security.AccessControlException: the Permission (java.io.FilePermission
<<ALL FILES>> execute) has not been granted to ROCQ. The PL/SQL to grant this is
dbms_java.grant_permission( 'ROCQ', 'SYS:java.io.FilePermission', '<<ALL
FILES>>', 'execute' )
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.checkExec(SecurityManager.java:788)
at java.lang.Runtime.exec(Runtime.java:270)
at java.lang.Runtime.exec(Runtime.java:195)
at java.lang.Runtime.exec(Runtime.java:152)
at Util.RunThis(Util:10)
-1
Ending.

PL/SQL procedure successfully completed.
Followup:
sorry -- i cannot reproduce on 9ir2 myself (did you log out and back in after
granting?)

ops$tkyte@ORA920PC> grant dba to rt_test identified by rt_test;

Grant succeeded.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> begin
  2    dbms_java.grant_permission
  3    ('RT_TEST',
  4     'java.io.FilePermission',
  5     '/usr/bin/perl',
  6     'execute');
  7
  8    dbms_java.grant_permission
  9    ('RT_TEST',
 10     'java.lang.RuntimePermission',
 11     '*',
 12     'writeFileDescriptor' );
 13  end;
 14  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> @connect rt_test/rt_test
ops$tkyte@ORA920PC> set termout off
rt_test@ORA920PC> REM GET afiedt.buf NOLIST
rt_test@ORA920PC> set termout on
rt_test@ORA920PC>
rt_test@ORA920PC> create or replace and compile
  2  java source named "Util"
  3  as
  4  import java.io.*;
  5  import java.lang.*;
  6
  7  public class Util extends Object
  8  {
  9    public static int RunThis(String args)
 10    {
 11    Runtime rt = Runtime.getRuntime();
 12    int        rc = -1;
 13
 14    try
 15    {
 16       Process p = rt.exec(args);
 17
 18       int bufSize = 4096;
 19       BufferedInputStream bis =
 20        new BufferedInputStream(p.getInputStream(), bufSize);
 21       int len;
 22       byte buffer[] = new byte[bufSize];
 23
 24       // Echo back what the program spit out
 25       while ((len = bis.read(buffer, 0, bufSize)) != -1)
 26          System.out.write(buffer, 0, len);
 27
 28       rc = p.waitFor();
 29    }
 30    catch (Exception e)
 31    {
 32       e.printStackTrace();
 33       rc = -1;
 34    }
 35    finally
 36    {
 37       return rc;
 38    }
 39    }
 40  }
 41  /

Java created.

rt_test@ORA920PC>
rt_test@ORA920PC>
rt_test@ORA920PC> create or replace
  2  function RUN_CMD(p_cmd in varchar2) return number
  3  as
  4  language java
  5  name 'Util.RunThis(java.lang.String) return integer';
  6  /

Function created.

rt_test@ORA920PC>
rt_test@ORA920PC>
rt_test@ORA920PC> create or replace procedure RC(p_cmd in varchar2)
  2  as
  3    x number;
  4  begin
  5    x := run_cmd(p_cmd);
  6  end;
  7  /

Procedure created.

rt_test@ORA920PC>
rt_test@ORA920PC>
rt_test@ORA920PC> variable x number;
rt_test@ORA920PC> set serveroutput on
rt_test@ORA920PC> exec dbms_java.set_output(100000);

PL/SQL procedure successfully completed.

rt_test@ORA920PC>
rt_test@ORA920PC>
rt_test@ORA920PC> exec :x := RUN_CMD('/usr/bin/perl /home/tkyte/test.pl' );
Hello World

PL/SQL procedure successfully completed.
Rating: 2
February 19, 2004
Reviewer: Ben from Quebec
Yes - actually, the grant was in one terminal window as the SYS user, and the
script execution was in another terminal, as a regular user. I just tried to
disconnect/reconnect, and it still fails, but now I've lost the error messages:

@tahiti/export/home/hebben01>sqlplus

SQL*Plus: Release 9.0.1.4.0 - Production on Thu Feb 19 13:25:34 2004

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

Enter user-name: rocq
Enter password:

Connected to:
Oracle9i Enterprise Edition Release 9.0.1.4.0 - Production
JServer Release 9.0.1.4.0 - Production

SQL> set serveroutput on size 1000000
exec dbms_java.set_output(1000000)
SQL>
PL/SQL procedure successfully completed.

SQL> exec test_runcmd
Beginning.
-1
Ending.

PL/SQL procedure successfully completed.
Followup:
put some System.out.println( "hello world" ) messages in your java.

I think you have gotten around your "permission issue" (else we'd see that) and
it is now failing 'for some other reason'
Rating: 2
February 19, 2004
Reviewer: Ben from Quebec
This is what I tried:

create or replace and compile java source named "Util" as
  import java.io.*;
  import java.lang.*;

  public class Util extends Object {
    public static int RunThis(String args) {
    Runtime rt = Runtime.getRuntime();
    int        rc = -1;
    try    {
        System.out.println("Hello world 1");
       Process p = rt.exec(args);

       int bufSize = 4096;
       BufferedInputStream bis =
        new BufferedInputStream(p.getInputStream(), bufSize);
       int len;
       byte buffer[] = new byte[bufSize];
        System.out.println("Hello world 2");

       // Echo back what the program spit out
       while ((len = bis.read(buffer, 0, bufSize)) != -1)
          System.out.write(buffer, 0, len);
        System.out.println("Hello world 3");

       rc = p.waitFor();
    }
    catch (Exception e)  {
        System.out.println("Hello world 4");
       e.printStackTrace();
       rc = -1;
    }
    finally {
       return rc;
    }
   }
  }
/

Re-granted privileges as the sys user:
SQL> begin
      dbms_java.grant_permission
      ('ROCQ',
       'SYS:java.io.FilePermission',
       '/usr/bin/perl',
       'execute');

     dbms_java.grant_permission
      ('ROCQ',
       'SYS:java.lang.RuntimePermission',
       '*',
       'writeFileDescriptor' );
  end;
/

PL/SQL procedure successfully completed.

Checked permissions at the OS level to make sure Oracle had the required
privileges to execute the script.
But I'm still getting the same result:
SQL> exec test_runcmd
Beginning.
-1
Ending.

I'm not seeing the "hello world" messages.
Followup:
are you sure you made the call to dbms_java to enable output (else it gets
written to a trace file on the database server).

Until you get "output", nothing is working -- get it to get output on your
screen first.  Just write a hello world java snippet if you have to.
Rating: 2
February 19, 2004
Reviewer: Ben from Quebec
Disregard my last posting. I had neglected to logout/login again.
The new Java:
create or replace and compile java source named "Util" as
  import java.io.*;
  import java.lang.*;

  public class Util extends Object {
    public static int RunThis(String args) {
    Runtime rt = Runtime.getRuntime();
    int        rc = -1;
    try    {
        System.out.println("Hello world 1");
       Process p = rt.exec(args);
        System.out.println("Hello world A");

       int bufSize = 4096;
       BufferedInputStream bis =
        new BufferedInputStream(p.getInputStream(), bufSize);
       int len;
       byte buffer[] = new byte[bufSize];
        System.out.println("Hello world 2");

       // Echo back what the program spit out
       while ((len = bis.read(buffer, 0, bufSize)) != -1)
          System.out.write(buffer, 0, len);
        System.out.println("Hello world 3");

       rc = p.waitFor();
    }
    catch (Exception e)  {
        System.out.println("Hello world 4");
       e.printStackTrace();
       rc = -1;
    }
    finally {
       return rc;
    }
   }
  }
/

Here is what I'm seeing:
SQL> exec test_runcmd
Beginning.
Hello world 1
-1
Ending.

PL/SQL procedure successfully completed.
Followup:
put something in the finally block -- it is not making sense yet.  If the
rt.exec was failing -- it would jump to the catch block -- but apparently - it
is not.
Rating: 2
February 19, 2004
Reviewer: Ben from Quebec
    finally {
         System.out.println("Goodbye cruel world");
      return rc;
   }

SQL> exec test_runcmd
Beginning.
Hello world 1
Goodbye cruel world
-1
Ending.
Followup:
don't know -- sorry, i'll have to refer you to support.  we know the rt.exec was
working at some point (you got the permissions thing) but now is not.

Maybe try "ls" like I did to remove perl and scripting from the equation first
tho
Rating: 2
February 19, 2004
Reviewer: Ben from Quebec
ok
Thanks for all your help, Tom.
Rating: 5
how to send message? March 09, 2004
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
Hi,
I want to send message to different users when any insert in the table. I am
using Oracle 9i r2 on Windows 2003 env. Please guide me as I am trying to use

net send sikandar A record inserted....

But unable to use net send command in the trigger.
Followup:
did you install the above code that lets you run host commands?
Rating: 5
3x for help!!! March 21, 2004
Reviewer: dibo from China
so good.
haho.
Rating: 4
Can PL/SQL submit a nohup job in unix shell April 02, 2004
Reviewer: Stephen from Hong Kong
I try to submit a nohup shell script from pl/sql, but the session still wait
unit the script end. How can I submit a nohup jobs in pl/sql?

Please advise!
Followup:
the script you run would call the script you want to run in the background

instead of you running:

  rc( 'some_script' );

you would run

  rc( 'some_other_script' );

and some_other_script would be

#!/bin/sh
some_script &
Rating: 4
Can PL/SQL submit a nohup job in unix shell April 02, 2004
Reviewer: Stephen from Hong Kong
Hi,

I had tried the following method already in the some_other_script:

1)
#!/bin/sh
some_script &

2)
#!/bin/sh
nohup some_script &

But the session in PL/SQL still wait until the last nohup job completed.

Should you have any idea?  Please advise.

Thanks,
Stephen
Followup:
maybe its the waitFor() call in the Process class.

in fact, google'ing waitfor process confirms.

your choices

o learn more java to find the exact solution you want
o remove the p.waitfor call so as to not block and hope that is the solution you
want.
April 04, 2004 Rob -- Thanks for the question regarding "When do blocks go on the free list", version 8.1.7
You Asked
Tom:

I have a data warehouse table that is loaded with parallel dml,
i.e. insert select stmt with append hint. Before the load, I
truncate the table. Is there any advantage to using the reuse
storage clause in thee truncate. This table is only loaded once, no other dml
takes
place. I am assuming from info that I have gleaned from searching
your archive that there should be no free list for this table,
therefore the truncate table reuse storage will cause all blocks
to be below the high water mark. I am wondering if blocks go on
the free list only from delete and update stmts? Would doing a
standard insert and then truncate table reuse storage cause blocks
to go on the free list? Do doing a truncate table drop storage
get rid of the free list?

Thanks as always,

Rob
and we said...
the blocks will only go on free lists dues to updates and deletes and NORMAL
non-append inserts (an insert of a new row that finds the freelists empty will
get some blocks from beyond the HWM and put them on the freelist if the new row
does not exceed the pctfree) -- truncate resets the HWM and obliterates the
freelists (regardless of how you use truncate).

You should be using locally managed tablespaces and I would not save the extents
(i'd truncate back to nothing) and then insert.  The newly allocated extents
(only as many as you need) will be allocated near the "front" of the existing
datafiles effectively reusing the space as needed.
Review & Followup

Rating: 3
Further question March 22, 2002
Reviewer: Rob Balter from Pittsburgh, PA
If I have a table with 100 allocated extents and I do
a truncate table reuse storage, will the direct path insert
reuse the 100 extents for data or create an additonal
100 extents.

Does a partitioned table have freelists per partition or
per table.
Followup:
direct path INSERTS insert above the HWM, into existing allocated but not used
space.  So, yes, it'll reuse them if they are "empty" and above the HWM.

each partition will have its own list since a block can only belong to one
partition (so they must be separated out)
Rating: 4
March 25, 2002
Reviewer: Irfan from Kuwait
Hi Tom,
a) I would like to know how the one block is created in free list as normally
after deletion it goes into free list.

SQL> create table t ( x int, y char(2000) default '*' )
  2  storage ( initial 40k next 40k minextents 5 )
  3  tablespace system;

Table created.

SQL> insert into t (x) values ( 1 );

1 row created.

SQL> analyze table t compute statistics;

Table analyzed.

SQL> compute sum of blocks on report
SQL> break on report
SQL> select extent_id, bytes, blocks
  2  from user_extents
  3  where segment_name = 'T'
  4  and segment_type = 'TABLE';

 EXTENT_ID      BYTES     BLOCKS
---------- ---------- ----------
         2      61440         15
         3     102400         25
         4     143360         35
         0      40960         10
         1      40960         10
                      ----------
sum                           95

SQL>  clear breaks
breaks cleared
SQL> select blocks, empty_blocks, avg_space, num_freelist_blocks
  2  from user_tables
  3  where table_name = 'T';

    BLOCKS EMPTY_BLOCKS  AVG_SPACE NUM_FREELIST_BLOCKS
---------- ------------ ---------- -------------------
         1           93       1971                   1

b) In one of your followup you have mentioned the first block of each table is
used by the system. So it means
totally 96 blocks are allocated to this table?

c) Your solution
(an insert of a new row that finds the freelists empty will
get some blocks from beyond the HWM and put them on the freelist if the new row
does not exceed the pctfree)
Can you please explain - put them on the freelist if the new row does not exceed
the pctfree -

Thanks.
Followup:
a) blocks are on the freelist UNTIL they exceed their pctfree.  Assuming an 8k
blocksize and 10 pctfree, you would have to insert about 3 rows before that
block would be taken OFF of the freelist in the first place (all blocks
originate on the freelist for normal inserts like that).

It stays on the freelist till pctfree is exceeded, then comes back on after it
hits pctused.  If you have my book -- I go over this in some detail in the
chapter on tables.

b) search this site for show_space.  It will show you exactly what is allocated,
used, on the freelist, etc.  You have 95 blocks.  One of them is in use by the
system.  One of them is in use by you.
Rating: 4
March 25, 2002
Reviewer: Irfan from Kuwait
Hi Tom,

I checked the followin link; you have explained clearly;
http://asktom.oracle.com/pls/ask/f?p=4950:8:627321::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:231414051079,%7Bshow_space%7D
but still I could not able to make out, considering the  followup (example), if
one block is used by system, then the EMPTY_BLOCKS should be 92. Becuase one is
used by me, one in freelist and the one by the system. Can you please clear me.

Thanks...
Followup:
One is used by you AND it is the same one that is on the freelist.  The block
with your single row is the same block as is on the freelist.

You are double counting your block.
Rating: 4
Empty Freelist July 08, 2002
Reviewer: Vikas Sharma from Delhi India
I read in your book and in this thread also, that the block which are above the
high water marks are included in to freelist when freelist are empty. Can you
please tell when freelist get empty.
Followup:
when you use them and they exceed their pctfree and get taken off of the
freelist.  So, when a table is "full" and the very next insert would cause it to
allocate another extent.
Rating: 3
Freelist July 09, 2002
Reviewer: Vikas Sharma from Delhi India
Thanks Tom it clears my doubt.
Thanks
Rating: 5
More clarifications April 04, 2004
Reviewer: Sanjaya Balasuriya from Colombo, Sri Lanka
Hi Tom,

Say we set free lists to 20 for a table.
Then 20 free block are kept reserved for the table ?

And what is the impact of free list groups on a table ?

Since to set the value for free list we should know the concurrent access
(updates or inserts), how can I know the number of concurrent access ? Is there
any views to get this information ?

Thanks in advance.
Followup:
no, 20 free blocks are not kept, it means you have 20 pointers to 20 lists.

do you have my book Effective Oracle by Design?  see:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:12209165088566
for a short extract on freelist groups.

You (as the designer of the application) needed to have that sort of information
during design time (in order to properly build the application).  If you did
not, you just have to "guess" -- no, there are no v$ views that'll tell you the
maximum number of concurrent transactions -- but you as the designer should have
a pretty good gut feel.
April 05, 2004 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://download-east.oracle.com/docs/cd/A81042_01/DOC/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://download-east.oracle.com/docs/cd/A81042_01/DOC/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
Rating: 5
RMAN in 10g March 29, 2004
Reviewer: Shankar from Houston, TX
If I used create controlfile syntax and recreated the controlfile in 9i, I loose
all RMAN info. Is this the same behavior in 10g? Also, why does not oracle keep
RMAN info after recreating the controlfile? Thanks.
Followup:
you have recreated, as in "from scratch"...

how do you expect the create controlfile to "make up" the missing data?

that would be like saying "i've drop and recreated my table and now it is empty"

you took a file full of stuff (the control file)
removed it
recreated it blank -- from scratch.

any "extra goodies" that were stuffed in there will naturally be gone -- it is
starting over.

Use the RMAN catalog, not the controlfile, to get the full benefits of RMAN and
not worry about this situation.
Rating: 5
Thanks. Understood. March 29, 2004
Reviewer: Shankar
Rating: 4
controlfile and catalog March 31, 2004
Reviewer: michael from USA
Tom,

I was told there exist a configuration where I can store backup information in
BOTH catalog and target database controlfile.  However, as much as I have dug, I
am unable to find such a configuration.

In essence, I was to perform:
  rman target / catalog rman/rman@ran
   and
  rman target / nocatlog
in one rman session.  Is this possible?

Thank you.
Followup:
the control file is maintained in both and there are commands to sync the
catalog with the current state of the control file if you sometimes do just
control file oriented operations.
Rating: 5
rman and pipe April 03, 2004
Reviewer: reader
Tom, what is the advantage of using PIPE in rman backups? In what circumstances
one would use PIPE to backup and restore? Thanks.
Followup:
it lets 3rd parties (anyone really) build an alternative interface to RMAN as it
permits anyone that can connect to an Oracle instance to control RMAN
programatically.
Rating: 4
Allocate Channel & RMAN session April 04, 2004
Reviewer: Sami

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96566/rcmarchi.htm#457442
<Doc>
The Oracle9i server ensures that only one RMAN session accesses a snapshot
control file at any point in time. This safeguard is necessary to ensure that
two RMAN sessions do not interfere with each other's use of the snapshot control
file.
</Doc>

ALLOCATE CHANNEL c1;
ALLOCATE CHANNEL c2;

Will it open 2 RMAN sessions?
Followup:
yes.
April 05, 2004 Matt -- Thanks for the question regarding "Validate sets of "consecutive" data entry sets (no overlaps).", version 9.2.0
You Asked
This sprang out of the following post:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:474221407101
you asked for more info and it started to look like an entirely new question so
I have posted it as such. Here we go...

[My first Post]
I need to load lots of data very quickly from an external table the data looks
like:

id,value,from_date,to_date

I need to load this data subject to the following requirements:

1) There can be no overlaps for a particular id (so one records to_date is equal

to the next records from_date.
2) There must be a full set of data from a given historic date date (roughly
three years in the past).
3) All the data for each id must span the period from the historic date as close

to the current date as possible (given the data available).

For example:

If historic date is 01/01/2003 (DD/MM/YYYY), this is the data that I want to
load (I need to discard data that does not marry the previous records to_date to

the next records from date:

id  value from_date    to_date
1   10    01/01/2003   01/02/2003
1   12    01/02/2003   21/05/2003
1   19    21/05/2003   01/06/2003
1   25    01/06/2003   01/12/2003
1   25    01/12/2003   01/02/2004

How would one generate a select to pull out data that marries like this?

Also, I need to constrain the schema so that only the data that satifies these
rules will be accepted (similar to the original post). Does the method described

in this post still apply to 9.2 or is there a better way?

Best Regards,
[END]

[Your Response]
tell me, what if the inputs do have overlaps.  what logic decides what rows are
actually kept (and why)

what does "there must be a full set of data" mean actually?
[END]

[My final post, with some additional information provided to the original]

(I'll answer your Q's in a sec).

I'm working at this problem from two ends.

1) a query to pull out the valid data from the external table subject to my
requirements in order to create a table

2) a loader to go through the data that was not retreived by the query. The
loader will bulk load these values and constraints will identify what the
problem was with the data (so it can be investigated and fixed)

So, I think I need

a) A suitable query to gather the data according to the requirements (more
detail will follow).

b) A mechanism to constrain the target table so that 'bad' data cannot get in
(and a constraint or trigger will raise an error that may be caught and logged)

The logic for keeping records is this:

a) The record with the earliest from_date for a particular id must have a
from_date (GREATER THEN OR EQUAL TO) 01/01/2003 (DD/MM/YYYY) (the historic date
in the example above).
b) The set of records for a particular id must completely span the period (with
no gaps - ie: it fishtails so that current from_date=previous to_date) that
starts on 01/01/2003 (the historic date) and continues up to the present.
However;
   provided that the data fishtails throughout the period it is allowable for
the most recent record to finish on a date before the current date.
d) there can be no duplicate data (even if the rows are the same eg: two records

with the same from and to dates.)
e) no to_date can be strictly greater than the current date.

So, you can see (I hope) that the logic  requires me to valiodate "sets" of rows

(defined by the id) such that the following are "sets" are accepted:

       HistDate                              Now
          |                                   |
ID: 1     |-----|
ID: 1           |-----------|
ID: 1                       |------|
ID: 1                              |----------|
ID: 2     |---------|
ID: 2               |---|
ID: 2                   |-----|
ID: 2                         |---------|
ID: 3     |---------------------------------|
ID: 4     |-----------------------------------|
ID: 5              |------------------|
ID: 5                                 |-|

and the following sets would be rejected:

       HistDate                              Now
          |                                   |
ID: 6   |-----|                                  <- before HistDate
ID: 7     |-----------|                            <- a gap
ID: 7                     |------|
ID: 7                            |------------|
ID: 8     |---------|
ID: 8               |-----|                        <- overlap
ID: 8                   |-----|
ID: 8                         |---------|
ID: 9     |-------------------------------------|  <- greater than current date
ID: 9     |---------|
ID: 9               |--------------------|
ID: 9               |--------------------|         <- duplicate
ID: 9                                    |----|

A "full set of data" means a consistent set of data for the id.

When loading the data I could order the loads by id and from_date to simplify
the validation. In that the data would be loaded in a (from_date) time ordered
fashion, which would satify the logic described above.

Best Regards,
[END]

"Is there only one possible "covering set" per id?"

-->Yes, there must be only one such covering set.

"what I mean -- is for id 1 -- does that represent ALL of the rows in the input
set or did we have to filter out some/many of the rows with id=1 in order to
find this nice covering set?"

-->Exactly. For id 1. you see the results of filtering out rows for id 1 that do
not satisfy the described constraints. THe result is the covering set.
and we said...
this is pretty easy with LAG and LEAD functionality.  The following demonstrates
the approach:

ops$tkyte@ORA920PC> select id, d1, d2, max(what) over (partition by id)
  2    from (
  3  select id, d1, d2, last_d1, last_d2, next_d1, next_d2,
  4         case when d1 < to_date( '01-jan-2003' )
  5                  then 'Before Hist Date'
  6                          when d2 > trunc(sysdate)
  7                          then 'Greater than current date'
  8                          when max_d2 != trunc(sysdate)
  9                          then 'Not upto End date'
 10                          when d2 < next_d1
 11                          then 'Gap'
 12                          when d2 != next_d1
 13                          then 'Overlap'
 14                  end what
 15    from ( select id, d1, d2,
 16                  lag(d1) over ( partition by id order by d1) last_d1,
 17                  lag(d2) over ( partition by id order by d2) last_d2,
 18                  lead(d1) over ( partition by id order by d1) next_d1,
 19                  lead(d2) over ( partition by id order by d2) next_d2,
 20                                  max(d2) over (partition by id ) max_d2
 21             from t
 22             )
 23             )
 24   order by 1, 2, 3
 25  /

        ID D1        D2        MAX(WHAT)OVER(PARTITIONBY
---------- --------- --------- -------------------------
         1 01-JAN-03 15-JAN-03
           15-JAN-03 29-JAN-03
           29-JAN-03 29-JAN-04
           29-JAN-04 09-FEB-04

         6 31-DEC-02 15-JAN-03 Before Hist Date
           15-JAN-03 09-FEB-04 Before Hist Date

         7 01-JAN-03 14-JAN-03 Gap
           15-JAN-03 29-JAN-03 Gap
           29-JAN-03 29-JAN-04 Gap
           29-JAN-04 09-FEB-04 Gap

         8 01-JAN-03 15-JAN-03 Overlap
           15-JAN-03 29-JAN-03 Overlap
           20-JAN-03 29-JAN-04 Overlap
           29-JAN-04 09-FEB-04 Overlap

         9 01-JAN-03 15-JAN-03 Not upto End date
           15-JAN-03 29-JAN-03 Not upto End date
           29-JAN-03 29-JAN-04 Not upto End date
           29-JAN-04 10-FEB-04 Not upto End date

        10 01-JAN-03 15-JAN-03 Overlap
           15-JAN-03 29-JAN-03 Overlap
           29-JAN-03 29-JAN-04 Overlap
           29-JAN-03 29-JAN-04 Overlap
           29-JAN-04 09-FEB-04 Overlap

        11 01-JAN-03 15-JAN-03 Not upto End date
           15-JAN-03 29-JAN-03 Not upto End date
           29-JAN-03 29-JAN-04 Not upto End date
           29-JAN-04 08-FEB-04 Not upto End date

27 rows selected.

I have not 100% completed the CASE statement -- you'll need to fine tune that --
but you see the idea...
Review & Followup

Rating: 3
I have the idea.. February 09, 2004
Reviewer: Matt from Australia
The query looks good, thanks - I think this does what I need (or at least gets
me 90% of the way there). Are you still considering writing that book about
analytics?

I also need to constrain the schema so that only the data that satifies these
rules will be accepted into the table. Is the approach in 9.2 the same as in
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:474221407101
?
Followup:
yes it would be similar -- you remember what ID's are put in and run this
"query" against that id looking for a non-null "what"
Rating: 3
Getting ORA-04091: table X.Y is mutating, trigger/function may not see it March 02, 2004
Reviewer: Matt from Australia
So, I implemented the query as a "before insert on <table>  for each row
trigger". Basically before inserting the data into the table the trigger carries
out the select (similar to above) to validate the data. If a no-null value is
returned - I raise an exception.

I was surprised to encounter ORA-04091. Reading,
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96590/adg13trg.htm#786
I understand that this is causes by the table inserts occuring in parallel
whilst the query in the trigger is running (my inserts occur in bulk).

I also read "Avoiding Mutating Tables":
http://asktom.oracle.com/~tkyte/Mutate/index.html
It seems to me that I fall into "Case 1 - you only need to access the :new
values". However, I am only concerned with one table and so I am a little
confused as to how I get this approach to work.

Thinking out load...I can't see a different way to implement my requirement. I
need to query the table into which I am inserting in order to verify that the
:new values will not break my validation 'rules'. I need to validate the :new
data before it gets into the table ie: a before insert trigger....I also can't
see how I can use the approach in "Avoiding Mutating Tables".

Can you please summarise how this could work....?

Below is the trigger code:

create or replace trigger mre_validate_fishtail_trg
  before insert on grms_mig_meter_reading_engy
  for each row
declare
  v_classification varchar2(50);
  v_historic_start_date constant date := globals.get_hsd;
  v_extract_date constant date := globals.get_extract_date;
begin

select classification
  into v_classification
  from ( select max(record_classification) over (partition by mirn)
classification
--             ,mirn
--             ,mirn_chksum
--             ,reading_type
--             ,start_date
--             ,end_date
--             ,energy_value
           from ( select mirn
--                      ,mirn_chksum
--                      ,reading_type
--                      ,start_date
--                      ,end_date
--                      ,energy_value
                        ,case when start_date < trunc(v_historic_start_date)
then 'Start Date Before Historical Start Date'
                              when end_date > trunc(v_extract_date) then 'End
Date Greater than Extract Date' -- extract date
                              when max_ed > trunc(v_extract_date) then 'Max End
Date Greater than Extract Date' -- extract date
                              when end_date < next_sd then 'Data Set has Gap'
                              when end_date != next_sd then 'Data Set has
Overlap'
                          end record_classification
                    from ( select mirn
--                                 ,mirn_chksum
--                                 ,reading_type
                                 ,start_date
                                 ,end_date
--                                 ,energy_value
--                               ,lag(start_date) over ( partition by id order
by start_date) last_sd
--                               ,lag(end_date) over ( partition by id order by
end_date) last_ed
                                 ,lead(start_date) over ( partition by mirn
order by start_date) next_sd
--                               ,lead(end_date) over ( partition by id order by
end_date) next_ed
                                 ,max(end_date) over (partition by mirn ) max_ed
                             from ( select mirn
--                                          ,mirn_chksum
--                                          ,reading_type
                                          ,start_date
                                          ,end_date
--                                          ,energy_value
                                      from grms_mig_meter_reading_engy
--extern_meter_reading_engy
                                     where mirn = :new.mirn
                                   union all
                                    select :new.mirn
--                                          ,:new.mirn_chksum
--                                          ,:new.reading_type
                                          ,:new.start_date
                                          ,:new.end_date
--                                          ,:new.energy_value
                                     from dual
                                  )
                          )
                 )
        ) where rownum = 1;

  if v_classification = 'Start Date Before Historical Start Date' then
    -- raise an exception
    exception_pkg.throw(exception_pkg.startdate_too_early);
  elsif v_classification = 'End Date Greater than Extract Date' then
    -- raise an exception
    exception_pkg.throw(exception_pkg.enddate_too_late);
  elsif v_classification = 'Max End Date Greater than Extract Date' then
    -- raise an exception
    exception_pkg.throw(exception_pkg.max_enddate_too_late);
  elsif v_classification = 'Data Set has Gap' then
    -- raise an exception
    exception_pkg.throw(exception_pkg.reading_makes_gap);
  elsif v_classification = 'Data Set has Overlap' then
    -- raise an exception
    exception_pkg.throw(exception_pkg.reading_makes_overlap);
  elsif v_classification is null then
    -- record is to be accepted
    null;
  else
   -- nothing else can be returned
   null;
  end if;

exception
  when others then
    raise;
end gi_populate_gatepoint_id_trg;
Followup:
well, it would not work row by row at all -- think about it.

How many GAPS would you see "artificially" cause the data comes in "some order".
 Unless the data came in perfectly (something you really cannot control), you
would see gaps.

the mutating table error is there to protect you really -- it is saying "hey,
the table is totally inconsistent and violates RI at this point, you cannot see
it"

it is not until all rows have been inserted by the insert can you begin to
validate.

Suggest you log the "mirn" value in a plsql table in the row trigger (as shown
in the workaround) and in an AFTER trigger, validate the MIRNS either in one big
sql or mirn by mrin.
Rating: 3
Response March 02, 2004
Reviewer: Matt from Australia
I understand what you are saying - but as the data is loaded in bulk from a file
(via an external table) I can 'sort' the input and control how the data arrives
to avoid errors. There will be no ongoing requirement to keep the data
consistent once this load has occurred (I am loading into an intermediate,
"temporary" area that will not be maintained after the data has been loaded and
validated - once validated the data is moved elsewhere).

So, in terms of the mutating table problem and your solution, let me clarify.
You suggest:

1) create a  "before insert or update on <tab> for each row trigger" to log the
mirn (and the rest of the records data) into a temp or plsql table.
2) create an "after insert or update on <tab>" statement level trigger to
validate the records in the table that were created by step 1)

So, if I understand correctly the bulk insert into my table will instead insert
into another "temp" table (in the before trigger) before doing all the
validation in the after trigger. This "temp" table would need to be a copy of
the target table excluding check constraints etc (any constraints on the target
table woudl be verified when the after trigger tries to insert the data).

Is the above correct?

I am concerned that if the after trigger is to process the data in bulk then it
will need to handle constraint violation errors raised by the bulk insert and
this may cause rows to be lost (and as I also log the errors and the data that
caused the error, I may lose these too).

Is this a vaild concern?

Thanks.
Followup:
1) and 2) are correct but the conclusion is wrong.

You will insert into the real table.  Instead of trying to validate row by row,
you'll just remember the mirn's you wish to validate.

AFTER all rows have been inserted, you'll process the mirn's against the live
data (the AFTER "not for each row" trigger can read the table, the data is
consistent)

By doing it in an AFTER trigger, you'll still have ACID properties (all rows
loaded, or no rows loaded).
Rating: 3
What about the errors? March 03, 2004
Reviewer: Matt from Australia
Please bear with me. I just about have this...

1) create a  "before insert or update on <tab> for each row trigger" to log the
mirn (and the rest of the records data) into a temp or plsql table.
2) create an "after insert or update on <tab>" statement level trigger to
validate the records in the table that were created ( in the temp or plsql table
by step 1)

When the "after insert or update on <tab>" trigger has fired, the rows will be
in the "real" table and I will be able to validate them. I will identify the
records to validate using my "temp or pl/sql table" which will store the info I
need to identify the data I need to validate.

I assume I must store all the data records I need to validate in my plsql table
(since there will be no rowids to log from the before trigger)?

My after trigger must then validate the data I have loaded and either reject the
whole set of data or individual rows (according to how I decide to implement
this). The after trigger can then delete any rows that fail my validation and
raise an appropriate exception.

I think this is correct. I'm just struggling a bit conceptualising the order
that triggers fire and what can be "seen" from each trigger.
Followup:
1) just mirn, that is all you need

2) mirn by mirm, just as you did in the row by row attempt

You just need to store the unique MIRNs as that is what you were validating, all
records associated with a MIRN.

The after trigger, if it "fails" -- raises an exception -- will automagically
rollback the work done by the insert.  you need do NOTHING other then

a) succeed (data stays)
b) fail (data goes away)
Rating: 4
Same scenario March 04, 2004
Reviewer: Muhammad Ibrahim from Hongkong
Dear Tom,

My table is

Sql> Create Table t ( Block_Id, Master_Id, Start_Date, End_Date );

INSERT INTO T ( MASTER_ID, BLOCK_ID, START_DATE, END_DATE ) VALUES (
1, 1,  TO_Date( '07/01/2003 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date(
'12/31/2003 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'));
INSERT INTO T ( MASTER_ID, BLOCK_ID, START_DATE, END_DATE ) VALUES (
1, 1,  TO_Date( '01/01/2003 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date(
'12/31/2003 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'));
INSERT INTO T ( MASTER_ID, BLOCK_ID, START_DATE, END_DATE ) VALUES (
2, 1,  TO_Date( '01/01/2003 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date(
'12/31/2003 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'));
INSERT INTO T ( MASTER_ID, BLOCK_ID, START_DATE, END_DATE ) VALUES (
3, 1,  TO_Date( '04/01/2003 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date(
'03/31/2004 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'));
INSERT INTO T ( MASTER_ID, BLOCK_ID, START_DATE, END_DATE ) VALUES (
4, 1,  TO_Date( '01/01/2004 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date(
'12/31/2004 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'));
INSERT INTO T ( MASTER_ID, BLOCK_ID, START_DATE, END_DATE ) VALUES (
1, 1,  TO_Date( '09/01/2003 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date(
'12/31/2003 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'));
COMMIT;

My result set should look like below ( Simply i need to split the intervals for
a
given Block_Id and get all the intervals )

Start_Date        End_Date

01/01/2003          31/3/2003  -- First Interval because of MAster_Id 1
start_date (first start date)
                             --  and since Master id 3 starts in 01/04/2003 so
the end date will
                             --  be one day before that.
01/04/2003        30/06/2003 -- Second Interval because of Master Id 3
start_date  (first start_date)
01/07/2003          31/08/2003 -- Third Interval because of MAster_Id 1
start_date ( second start_date)
01/09/2003          31/12/2003 -- Fourth Interval because of MAster_Id 1
Start_date( third start_date)
01/01/2004          31/3/2004     -- Fivth Interval because of Master_Id 4
Start_Date ( First start_date ) and
                                and Master_Id 3 end_date which ends in 31/3/2004
so the next interval
                                starts as 01/04/2004
01/04/2004          31/12/2004 -- There is no other start or end date falls in
between any of these intervals
                             -- if either start or end date falls i need to
split again

I tried to use lead but i didnt get the exact result

below is my query

Select Dates.Start_Date,
       Lead( Dates.Start_Date ) Over ( Partition By Dates.Block_Id Order By
Dates.Start_Date ) - 1 End_Date
From   ( Select Distinct Start_Date Start_Date, Block_Id
         From   t
         Union
         Select Distinct End_Date   Start_Date, Block_Id
         From   t
         Order By 1 ) Dates
Order By 1;

the output is

1/1/2003    3/31/2003
1/4/2003    30/6/2003
1/7/2003    31/8/2003
1/9/2003    30/12/2003  -- this is wrong instead i need 01/09/2003  31/12/2003
31/12/2003  31/12/2003  -- i dont need this
1/1/2004    30/3/2004   -- this is wrong i need 01/01/2004  31/3/2004
then i need final interval as
1/4/2004    31/12/2004

Please let me know how can i acheive this.

Thanks & Regards,
Ibrahim.
Followup:
Not really following (UNION does a sort distinct -- you should drop the distinct
in inline views with a union and the order by inside of there isn't relevant
either)

but -- i don't understand what you are doing with the union in the first place.

"Simply i need to split the intervals for a given Block_Id and get all the
intervals"

might be "simply" to you -- but it doesn't make sense to me.
Rating: 5
Exact Scenarios March 04, 2004
Reviewer: Muhammad Ibrahim from HongKong
Well, i can give a real time example. This is for reinsurance retention split
of all the affected policies in the given block(for block accumulation ie to
control the treaty capacity)

Here is a fine example:

  In our datamodel each Insurance policy will have a Contract_Id and if we do
endorsement will be a next version of that particular contract_Id.

  For each fire policy there will be a location in turn will be attached to a
block
identified by Block_Id.

  I have a table to store the reinsurance information by situation wise. Here is
an
example.

Ri_Table:
---------

Product_Id  Number; -- Part of Primary key
Block_Id    Number; -- Part of Primary key
Contract_Id Number; -- Part of Primary key
Start_Date  Date; -- Part of Primary key
End_Date    Date; -- Part of Primary key
Status      Varchar2(1); --

1) If suppose Policy1( Contract_Id 1) enters the block A first (assume there is
no other policies in the block) and assume the inception date of the policy1
starts at 01/01/2003 and ends at 31/12/2003.

the data will be :

Product_Id Block_Id  Contract_Id Start_Date  End_Date

3          A         1           01/01/2003  31/12/2003

2) When second policy enters in the same block 'A' eg: Policy2, Contract_Id 2
incepts at
01/04/2003 and ends at 31/03/2004.

Now the situation will be split as

Product_Id Block_Id  Contract_Id Start_Date End_Date  Status

3          A         1           01/01/2003  31/12/2003 D
-- the old entry becomes invalid!

3          A         1           01/01/2003  31/03/2003 A
-- Only Contract_Id = 1 is active

3          A         1           01/04/2003  31/12/2003 A
3          A         2           01/04/2003  31/12/2003 A
-- both Contract_Id 1,2 are active
-- at this situation because of second policy enters in
-- the same block

3          A         2           01/01/2004  31/03/2004 A
-- Only Contract_Id = 2 is active

3) Now there might be situation for back dated policies means there is a
posibility
another policy might enter into the same block with back dated inception date
for
example it might incepts in 01/08/2002 and ends in 31/07/2003 say Contract_Id 3

then the split should take place accordingly.

Product_Id Block_Id  Contract_Id Start_Date End_Date  Status

3          A         3           01/08/2002  31/12/2002 A
-- Only Contract_Id 3 is active at this time.

3          A         1           01/01/2003  31/03/2003 A
3          A         3           01/01/2003  31/03/2003 A
-- Splits one day before Contract_Id 2 starts!
-- only Contract_Id 1 and 3 are active

3          A         1           01/04/2003  31/07/2003 A
3          A         2           01/04/2003  31/07/2003 A
3          A         3           01/04/2003  31/07/2003 A

-- both Contract_Id 1,2 and 3 are active
-- at this situation because of second policy enters in
-- the same block
-- only Contract_Id 1,2 and 3 are active but if you
-- compare this link
-- with the above scenario the end date will be different
-- because Contract_Id 3 ends in 31/07/2003! So i need to
-- invalidate those

-- old above links from point (2) then create this new
-- situation. the below will be second split.

3          A         1           01/08/2003  31/12/2003 A
3          A         2           01/08/2003  31/12/2003 A
-- Only Cotract_Id 1 and 2 are active!

3          A         2           01/01/2004  31/03/2004 A
-- Only Contract_Id = 2 is active because first policy
-- expires.

Before i do the above splitting i need to invalidate the old entries for the
same block and the period falls.

4) This might happen for future dated policies which enters in the same block
eg: start date as 25/03/2004 and 24/03/2005. Then the above split should take
place accordingly.

  Thanks alot for your kind help. I hope atleast or finally i make somebody to
understand
my situation! Please give me a good solution. I know only people like Tom can
give me a good
solution to solve these kind of complicated situation to do it in oracle.
Rating: 4
Great stuff March 12, 2004
Reviewer: Arian Stijf from Netherlands, Europe
Hi Tom,

Thanks to this and some of other posts, I could answer a requirement without
using pl/sql (my first idea).
For social security, we need to know everybody who has been on sick-leave for 13
weeks. In calculating the 13 weeks, a recovery for <4 weeks is considered ill.
So 5 weeks ill, 1 week recovered, 7 weeks ill, would be considered 13 weeks of
sick-leave.

I solved it by using a GTT, inserting:
insert into test_absence
select person_id
,        date_end
,        case when last_end > date_start-28
               then last_end+1
            else
                 date_start+0
       end date_start
from (select person_id
     ,         date_start
     ,         date_end
     ,          lag(date_end) over (partition by person_id order by date_end)
last_end
     from per_absence_attendances paa1)

and finally selecting:
select distinct person_id
,       (select min(date_start)
        from   test_absence paa1
        start with paa1.person_id=paa.person_id
        and       paa1.date_start=paa.date_start
        connect by prior date_start-1=date_end
        and       person_id=paa.person_id) st
,       (select max(date_end)
        from   test_absence paa2
        start with paa2.person_id=paa.person_id
        and    paa2.date_start=paa.date_start
        connect by prior date_end+1=date_start
        and     person_id=paa.person_id) end
from  test_absence paa
where person_id=2385
order by 1

However, can you give any clues to more efficient methods?
And do you know how to avoid errors, on overlapping periods?

Thanks,

Arian
Followup:
well, without ripping your data model apart and trying to reverse engineer it -
I'd suggest looking into range windows and just counting?  Not sure tho -- lots
of complex things happening there (could just 12 lags in the same row tell you
what you need to know? you can lag back a row, 2 rows, N rows whatever)
Rating: 3
Regarding three trigger implementation to avoid mutating table March 31, 2004
Reviewer: Matt from Australia
Looking more closely at the implementation described earlier in this post...

Can you please explain what happens when there is a BI, BIFER, and AI trigger is
used to avoid the mutating table error and a bulk insert takes place? I am
trying to understand what happens in two cases:

1) When a bulk insert is successful
2) When a bulk insert fails for each row.

Imagine we have an array size of 5 for the bulk insert.

In case 1) I understand the following order of events:

* the BI trigger fires once only
* All 5 rows are inserted into the table
* the AIFER trigger fires 5 times
* the AI trigger fires validates each of the 5 rows and the data is loaded

In case 2) I understand the following order of events:

* the BI trigger fires once only
* All 5 rows are inserted into the table
* the AIFER trigger fires 5 times
* the AI trigger fires validates row 1 and raises an exception
* Oracle rollsback the entire 5 row insert
* The last 4 rows are re-inserted
* the BI trigger fires once only
* The remainig 4 rows are inserted into the table
* the AIFER trigger fires 4 times
* the AI trigger fires validates row 1 and raises an exception
* Oracle rollsback the entire 4 row insert
..oracle continues with the remaing 3..then 2 rows...etc  until there is no more
data to process.

Is my understanding correct? Can you please clarify what does actually happen in
these circumstances?

THanks again,
Followup:
ops$tkyte@ORA9IR2> create table t ( x int );

Table created.

ops$tkyte@ORA9IR2> create or replace trigger t_bi before insert on t
  2  begin
  3          dbms_output.put_line( 'before' );
  4  end;
  5  /

Trigger created.

ops$tkyte@ORA9IR2> create or replace trigger t_bifer before insert on t for each
row
  2  begin
  3          dbms_output.put_line( 'before for each row' );
  4  end;
  5  /

Trigger created.

ops$tkyte@ORA9IR2> create or replace trigger t_ai after insert on t
  2  begin
  3          dbms_output.put_line( 'after' );
  4          raise_application_error( -20001 , 'you lose' );
  5  end;
  6  /

Trigger created.

ops$tkyte@ORA9IR2> declare
  2          type array is table of number;
  3          l_data array := array(1,2,3,4,5);
  4  begin
  5          forall i in 1 .. l_data.count
  6                  insert into t values ( l_data(i) );
  7  end;
  8  /
before
before for each row
before for each row
before for each row
before for each row
before for each row
after
declare
*
ERROR at line 1:
ORA-20001: you lose
ORA-06512: at "OPS$TKYTE.T_AI", line 3
ORA-04088: error during execution of trigger 'OPS$TKYTE.T_AI'
ORA-06512: at line 5

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select * from t;

it is atomic, you either succeed or fail.
Rating: 3
This is not what I am seeing happening March 31, 2004
Reviewer: Matt from Australia
I am seeing the inserts restart, as I described. I think that this may be a bug
in my code. Does the following pseudo-coded make sense?

I am trying to catch errors and then log them in a table. However, I think that
I may have misunderstood how sql%rowcount is populated in the case of errors. I
seem to be re-starting the insert at the point just after the failure occurred.
ie: I am re-processing data that I should have thrown out.

The code does a bulk fetch into arrays that then feeds into the following.

        loop
           begin
             --New Template
             forall i in l_start .. l_source_colid1.count
              insert into t values ( ... );

               dbms_output.put_line( 'This insert processed rows = ' ||
to_char(sql%rowcount));
             exit;
           exception
             when others then
--               dbms_output.put_line(to_char(l_start));
               dbms_output.put_line('');
               dbms_output.put_line( 'This insert error number = ' ||
to_char(l_start));
               dbms_output.put_line( 'This insert rows processed = ' ||
to_char(sql%rowcount));
               dbms_output.put_line( 'This insert bad row index = ' ||
(l_start+sql%rowcount));

               dbms_output.put_line( 'Bad row index = ' ||
(l_start+sql%rowcount) || ' ' || sqlerrm );

               l_start := l_start + sql%rowcount + 1;
               dbms_output.put_line( 'This insert restarting at ' ||
to_char(l_start)||' ,finishing at '||to_char(l_source_colid1.count));
               -- log errors here
           end;
        end loop;
      dbms_output.put_line('Outer loop');
      l_start := 1;
      l_fetch_count := l_fetch_count + 1;
      exit when c%notfound;
    end loop;
Followup:
give me a concrete, concise, yet 100% complete example to work with please (like
I give you :)
Rating: 3
A concrete example March 31, 2004
Reviewer: Matt from Australia
Here you go..using your test above. I have added comments to identify what
happens in the real code (a loop that fetches data and stops when no more data
to process). THe output is as I described.

declare
--        a cursor defined here
          type array is table of number;
          l_data array := array(1,2,3,4,5);
          l_start number := 1;
begin
--    loop -- a loop to keep getting data from a cursor
      -- a fetch in here
      loop
        begin
          forall i in l_start .. l_data.count
                  insert into t values ( l_data(i) );
           exit;
           exception
             when others then
               l_start := l_start + sql%rowcount + 1;
               -- log errors here
        end;
      end loop;
      l_start := 1;
--       l_fetch_count := l_fetch_count + 1; -- keep count of how many fetches
--       exit when c%notfound; -- to exit when fetch returns no rows.
--   end loop; -- a loop to keep getting data from the cursor (until no more
data
end;
/

before
before for each row
before for each row
before for each row
before for each row
before for each row
after
before
before for each row
before for each row
before for each row
before for each row
after
before
before for each row
before for each row
before for each row
after
before
before for each row
before for each row
after
before
before for each row
after
Followup:
ahh, here you are getting an error and sql%rowcount is really "zero" so you are
just saying "first row is in error" -- when in fact it could be ANY of the rows
since it is a trigger erroring out.

So, what you might do is

  exception
     when others then
           if (sqlcode=-20001) -- or whatever you raise in your trigger
           then
                log the batch, they are not going in if the after trigger failed
           else
                l_start := l_start + sql%rowcount + 1;
           ......
Rating: 3
More questions... April 01, 2004
Reviewer: Matt from Australia
I just re-read this in light of the recent posts:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:15457509245121#16128347343143
The behaviour I require is the following:

1) Data (from a bulk fetch) is bulk inserted into the table. This data may
contain many "mirn" records.
2) I want to validate the data for each "mirn" and throw out only the data for
the mirn that fails the validation.

For example if the first "mirn" is in the array insert considered "inconsistent"
then it should be thrown out, but the second "mirn" in the array insert may be
valid, and so should be accepted into the table.

I have just come to realise (after the most recent posts) that in the example
above both sets of "mirn" data would be thrown out by the trigger (since all
data is thrown out after the first exception is raised).

If my bulk insert contains lots of mirn then any failure may mean that valid
data is discarded. Worse still is that all the data will be thrown out based on
the same exception (some data might fail for a different reason but this reason
would be lost).

Do I understand all of this correctly?

If so, is there a better way to meet my requirements?

Thanks.
Followup:
instead of have the AFTER trigger "fail", have the after trigger "delete" the
bad data and log it.
Rating: 3
Another problem... April 04, 2004
Reviewer: Matt from Australia
I am seeing a row get into my state_pkg pl/sql table which should not be there.
The row in question should be failing on an FK constraint violation. However, it
appears in this table (despite not existing in the heap table into which I am
loading). As a result I get a no data found when I carry out any validation for
the row.

I am trying to get together a stand alone test case to identify the problem.

In the mean time, any ideas?

Thanks.
Followup:
lots of debug code :)
April 05, 2004 David -- Thanks for the question regarding "HTML DB", version 10
You Asked
Tom -

I read a little bit about the new features in 10g for HTML DB. I'm pretty
excited about it if it effectively delivers on these new features (e.g., the
whole meta-data driven framework, the Application Builder, Session Management,
Templates, Security, etc.)

I have been thinking for awhile that there is opportunity in smaller markets to
role out rock solid applications with a single Oracle database running
mod/plsql. I was considering all the infrastructure services that would need to
be built in order to quickly build one or more applications using mod/plsql.
With HTML DB, it is looking like I don't have to worry about building these
services on my own anymore, and that is great news.

A feature that I didn't see that I would also consider very useful...
A security framework where you can map Users to one or more Roles (I know this
is in Oracle), then further mapping one or more Roles to one or more defined
"Transaction codes" (e.g., CreateOrder, ViewOrder, DeleteOrder), and finally
associating a group of "HTML Pages" to a transaction. This kind of framework
could apply to many information system applications. It would both enforce at
the server as well as influence the look of the application (e.g., limited menu
options based on Roles.)

Anyway, seeing these new HTML DB features along with 10g's ability to
transparently scale in its clustering/grid configurations, Oracle 10g with HTML
DB could be a solution for any size application.

Finally some questions....
1.) Does Oracle have any plans to build out-of-box applications/products on top
of HTML DB?
2.) Do you see any potential confusion from your customers as to whether they
should use HTML DB vs. the iAS?

I did Java development for a few years and I like using core java a lot. I still
struggle to decide whether or not the EJB architecture is that good. I think
I've come to the conclusion that if you are building an "information system"
application where most of the work is selecting,inserting,updating,and deleting
from the database, then a simpler architecture is better, and HTML DB may be
onto something here. At the end of the day I would think companies want to get
ROBUST applications working QUICKLY, and J2EE is not necessarily the right
choice in all cases.

A couple more questions...
3.) Will HTML DB be available in the Standard edition? As I said before, I see
opportunity in smaller markets for HTML DB as replacements for spreadsheets,
MS-Access, and SQLServer applications, however, the cost of Oracle could
prohibit this. Does Oracle have any interest in this market?
4.) Maybe I am just paranoid, but I see a strategy here. An alternative to .NET
or J2EE, simpler and faster approach (for certain types of "information system
apps"), it gets Oracle in the door, and allows them to sell additional licenses
(10g grid) as the customers' applications need to scale further. Is this a
stretch?

Always curious,
Dave
and we said...
I asked Sergio Leunissen, the product manager for HTML DB, and he said:

Thank you for your question, Dave:

1) Not as of yet.
2) I don't see a lot of potential for confusion.  I think it's a matter of the
skills and experience of the person or team building the application.  Oracle
already serves customers very well with JDeveloper, allowing professional
developers to build applications using such technologies as J2EE and web
services.  HTML DB complements Oracle's offering by catering to those who don't
develop applications for a living, don't have a computer science degree but
still have to build something.

The nature of the application, as you are correctly point out, is also
important.  How long are we going to use the application?  When does it need to
be up and running?  Tomorrow?  Next year sometime? Do we need to integrate many
legacy data sources or call existing chunks of logic running on existing
systems?  Will the world as we know it fall apart when the application is not
available one day?  In other words, is the application absolutely strategic and
mission critical?  Is it really worth it to assign a few professional developers
working on this for a few months?  The answers to these questions may
automatically guide you to the appropriate technology.

3) Yes, HTML DB will be availabe with standard edition.  I can't really speak to
Oracle's aspirations in the market you describe, but, last time I checked, we've
never been allergic to growing market share.

4)  I agree that HTML DB is an alternative to J2EE and .NET and we hope that
those folks that are overwhelmed by these and other technologies will consider
HTML DB.
Review & Followup

Rating: 4
September 30, 2003
Reviewer: Robert from CT
I am curious as to why Oracle "seems" to downplay or being "conservative" about
the capability of HTML DB in a way that's rather un-Oracle-like.

I've listened to the OW2003 guru sessions by people like Mendelsohn & Dwight and
they seem to position HTML DB as something that's perfect for simple, small,
workgroup projects, something that's meant to compete/replace with dev tools
like Access, Excel, ASP...something quick & easy & like no serious programmers
required.

Someone back from OW also told me that he specifically asked around & talked to
the Marvel people and he got the same impression.

Is this because this is the very first prod release & Oracle
chooses to be conservative until the thing has gone thru a real "baptism of
fire" ?
Tom, your thoughts please.

Thanks
Followup:
Well, I'm sort of one of the marvel people myself (they are right down the hall
from me, been working with some of them for going on 11 years now)...

that is what html db is all about -- but it can go much much larger.  For
example, we wrote an entire calendar/pim in it -- hosted for the entire company.
 Gets tons of traffic (we are switching off of that to collaboration suites
calendar now) but it gives you an idea of the scale this can go to.

tell me, do you consider asktom "small"? html db is perfect for it -- it is the
quinessential html db application.  It is fairly small in scope (just a couple
of pages, nothing huge - single developer sort of application).  It does just
one thing.  I do about 1,000,000 transactions a month (and asktom is just one of
about 5 applications written in html db running on that machine).  Is that
smallish?

It is my belief that easily 80-90% of total applications developed in Java or
some other 3gl could (should) be developed in a non-3gl environment like this.
That is because 80/90% of applications really are "small" relatively speaking.
They will never need to scale to 15 app servers.
Rating: 4
About AskTom Transactions... September 30, 2003
Reviewer: Robert from CT
>>I do about 1,000,000 transactions a month

Tom, transactions on AskTom are all INSERTS, each message to/from AskTom being a
single automic record ??
Do you have to guard against things like "Lost Updates" ?
If you also UPDATE, what kind of locking mechanism , optimistic ?
AskTom never DELETEs any record, does it ?

Thanks
Followup:
html db does optimistic locking "out of the box", it does it for me, i don't
have to deal with it.  lost updates are taken care of by html db.  just like
forms.

The transactions are larger then that -- each page -- even this one you are
looking at -- had a couple of inserts/updates in it.  for auditing, performance
tracking.

this page has:

5 inserts
2 updates

for example

I do not delete, correct.
Rating: 4
You've got options September 30, 2003
Reviewer: Dave from Lexington, MA
Just noticed that you can download it now, so I did that last night and
installed it. I may have more comments as I get to know it.

One thing I forgot to mention...Oracle is providing a more "wizard driven
approach" here. This is good for non-technical people, but it is also good for
technical people in terms of rapid application development.

The thing I like about Oracle (versus other vendors at times) is that they are
providing this RAD without taking away access to the underlying technology, so
that you can get the benefit of rapid development in combination with the power
of Oracle technology.

Dave
Rating: 3
Where can I download HTML DB ? September 30, 2003
Reviewer: A reader
Dave,

where did you find HTML DB download?
I cannot find it in OTN download page:
http://technet.oracle.com/software/content.html

Rating: 3
This is where I found it September 30, 2003
Reviewer: David from Lexington, MA
http://marvel.oracle.com/
Then choose: "Download Preview Release"
Rating: 3
HTML DB vs. Oracle Portal September 30, 2003
Reviewer: Jens from Germany
One Question about HTML DB:

It looks like the old WEB-DB or Oracle Portal. Is it so much new or is it a
Portal-Light without the need of 9iAS? How will Oracle seperate it from Portal?
Followup:
Portal is an integration/content management tool.

HTML DB is an application development tool

You might use portal to aggregate the front page of html db generated
applications.

You could use html db to write your own portal tool if you so wished (portal
being an application)

they are somewhat orthogonal.  Web DB was the precursor to HTML DB (written by
the same group as marvel, but the guys who took it over liked the portal idea
better then an app development tool -- they sort of let it "stay the same for
years").
Rating: 3
Thanks David for link! October 01, 2003
Reviewer: A reader
Rating: 4
Marvel Native-compilation October 02, 2003
Reviewer: Robert from CT
Tom, is AskTom's running on native-compiled Marvel engine ?
If not, why (fast enough?) ?

Thanks
Followup:
fast enough...  just never turned it on, ran faster then fast enough.
Rating: 4
Criteria for an HTML DB App candidate October 07, 2003
Reviewer: Robert from CT
>>It is my belief that easily 80-90% of total applications developed in Java or
some other 3gl could (should) be developed in a non-3gl environment like this.
<<

Tom, surely you've just about seen all kinds of apps - can you provide some
pointers on what make an (Oracle-centric, of course) app a good canidate for
HTML DB implementation ?

I mean when you analyze an existing app or business requirements, security
requirements - what outstanding qualities make you say "Sure, Yes, HTML DB can
deliver the requirements" or conversely "Hmmm, this should better be written in
3gl/other web technologies" ?

Thanks
Followup:
Ok -- opinion coming

all apps that you would develop for internal use at your company are HTML DB
candidates.

I make that bold statement because I firmly believe anything "really really
really big" should be "bought", not "built" (eg: HR, finapps, crm, etc etc etc)

Would I use it to build amazon.com?  probably not.

Would I use it to build something on the scale of asktom.oracle.com -- we
absolutely did (among many many others)
Rating: 4
draw the line... October 07, 2003
Reviewer: Robert from CT
>>
anything "really really
really big" should be "bought", not "built" (eg: HR, finapps, crm, etc etc etc)
<<

Not if you're the prez of TomMart - the big chain retail stores - and the
financial app code is untouchable & doesn't use bind & the model is kludgy &
tries to be "platform-independent" & totally complex & vendor wants a blank
check....other sins...?
Rating: 2
How to decide? October 07, 2003
Reviewer: Mark Wooldridge from Vienna, VA.
Tom,

Your comment about amazon, 'Would I use it to build amazon.com?  probably not'
is interesting.

What is it that amazon.com does that would not be suited for mod_plsql?

We are currently looking to shift focus from j2ee to mod_plsql on our project
and your input would help (Wendy is familiar with our goals and can give you and
idea of what we are trying to accomplish).

Thanks
Followup:
amazon.com is of a certain scale.  It has more then just 10's of thousands of
users.  It is really "big" -- way way way beyond what most of us will encounter
for most all of our applications.
Rating: 3
Dave's opinions follow.... October 08, 2003
Reviewer: David from Lexington, MA
Mark -

I will offer my opinions if you don't mind, though I must admit, I am still
investigating HTML DB.

To shed some light on any bias I might have...I have done some J2EE before, I
love using Oracle, I've used mod_plsql, and I am very interested in learning how
to best utilize HTML DB.

I'd also be interested in hearing a "real world" example of why someone would
move away from J2EE to mod_plsql. Can you provide some reasons in your case? It
would be really interesting to here.

So why wouldn't you build amazon.com with mod_plsql? Well here are some things
that come to my mind:

1. Dynamic content - HTML is always dynamically generated with mod_plsql. In a
high volume system you want to exploit caching and static content as much as
possible. This may be easier in a J2EE architecture.

2. Media content - Amazon uses a lot of media on the site (images, sound,
etc.)...perhaps this is possible in HTML DB (surely Oracle can store this
information or reference a file name), but I think HTML DB is more geared toward
delivering traditional textual and numeric information typical of applications
such as asktom or an order entry system. (Though Tom did mention an email system
Oracle built with it I think...which might include attachments.)

3. Resources - It may be easier to put together a large team of Java developers
with a couple of database developers versus a large team of database developers.
When I joined a product company a few years back, I was surprised to see how
many programmers treat the database as a black box...or worse they fear it!

4. External Interfaces - Integrating with external systems (e.g., credit card
verification, UPS tracking, external security authentication) is probably easier
to do in J2EE architecture rather than in HTML DB.

5. Code organization / maintainability - People may argue code organization for
a large project is better maintained in an OO world rather than in procedural
(PL/SQL) code (or the metadata layer of HTML DB.) I'm not sure I can back this
up with proof, but I'm sure this opinion is out there.

6. Look and Feel - HTML DB provides you with some standard templates. I'm not
sure yet how custom you can make the look and feel of the apps (maybe you can
develop your own templates, I don't know.) But my guess is that the further you
get from the standard layouts, the more work is involved, and the less benefit
you might get from the RAD of HTML DB. So...for very custom layouts that you may
need in complex applications, HTML DB might not be the easiest approach.

7. Testability - In large applications, you might want to build test clients
that simulate user activity. For instance, you could setup programs to send XML
documents containing transaction data that by-passes the JSP code and interacts
directly with EJBs to simulate users executing transactions. You don't have this
type of ability/flexibility with HTML DB.

8. Asynchronous - It is relatively easy to develop threaded code in Java. You
can do it in Oracle with DBMS_JOBS, but if you have some complex asynchronous
requirements, Java is probably better.

9. Scalability - How does Amazon.com prefer to scale, by buying multiple Oracle
instances in a cluster (running HTML DB), or using one Oracle instance with many
application servers (which are probably cheaper.)

So are you building something on the scale of Amazon.com or are you building an
internal application for your company?

If you have a business problem where you can envision the data model behind it,
and you basically need the ability to allow users to intuitively view, create
and update the data in some sort of transactional application, then HTML DB is
probably just fine and may be better than J2EE.

Dave
Followup:
1) disagree -- the java environment would dynamically generate the page as well
using dynamic content here and there (personalization) and ESI (edge server
include) tags for cached data.  mod_plsql can do the same

2) the rich media is just a LINK -- an href or whatever tag.  the programming
language used to generate the page is not relevant -- the "programming language"
could be vi to generate a static page for what its worth here.

3) i've yet to see a team of java developers with database developers :)  but in
the end -- it is all about who you hire.

4) soap?  web services?  we were doing this with utl_http over ssl a long time
ago.

5) thats debatable, we'll let that one slide...

6) thats correct -- it is the predominant strength of HTML DB in my opinion.
every app looks "the same".  Every Oracle application (hr, etc) could have been
build using HTML DB UI wise -- and they would all work the same

7) kidding right?  asktom for example is built using html db on an API.  the
logic is in packages (submit a question -- thats a package that does that).
HTML DB is just providing in this case state mgmt, navigation, all the yucky
stuff that is just a pain to code over and over.  the core of asktom is an API,
testable in a standalone fashion.  Sure, if I create a simple CRUD screen using
HTML DB -- it'll not be an API but then again -- it'll work as advertised (does
what it does).  the code you need to test is your code and that'll be separated
out as it would be in ANY good program

8) that is not async -- that is multi-threading.  the end user is still waiting.
 Most (many) programmers cannot build reliable threaded programs (and frankly i
don't see it being used that much -- not in a jsp environment anyhow).  If you
mean ASYNC -- you would be talking something like JMS which in Oracle is built
on top of AQ which is implemented in PLSQL and fully accessible to PLSQL --
meaning PLSQL is quite adept at doing it as well!

9) exactly - amazon.com is of a scale beyond where most of us will ever go.  at
that point, alternatives are to be looked at.

At the end of the day -- you have to just keep saying "java is just a language,
java is just a language, java is just a language (and so is plsql)"

You have to get back to the architecture -- what should the architecture be,
simple small and more then scalable enough for what you are doing or big,
distributed, lots of moving pieces that can potentially (when done right) scale
really big.
Rating: 3
Good info October 08, 2003
Reviewer: David from Lexington, MA
Figured I would get feedback.

1) Yes, dynamic but some times coming from cached objects rather than database
calls. I'm not familiar with this functionality in mod_plsql.

4) Web services would certainly make integration more open.

7) Ok, developing your API outside of HTML DB is a good point, I was thinking
from the perspective of using the HTML DB to build the whole app via the web
gui.

8) I meant threading. I suppose you might not see much need for building
threaded code into your web site. Perhaps you are correct, many programmers
cannot build reliable threaded code, but if you need to build it, then you
better find some. Threaded coding in Java is much easier than in C in my
opinion, but the same perils of deadlocking always exist regardless of language.

Thanks for the info.

Dave
Followup:
Tyler, friend of Tom and HTML DB developer here,

Just wanted to follow-up on a few of your points here.

4) We include a number of sample applications you can install, then reverse
engineer.  As soon as you login to HTML DB, look on the right side in the
"Tasks" region for "Review Demonstration Applications". One of them is "Web
Services".  Just click on install.  You can then disect a working web services
app to see how it is constructed.

6) Templates:  You can acheive ANY HTML based look in HTML DB.  Our templates
are simply HTML with #SOMETHING# substitution strings.  While a lot of the
built-in ones look the same, they are COMPLETELY customizable.  Below is some
sample code for a region template similar to the "Information" region found on
the right side of this page:

<table style="border:1px black solid;border-collapse:collapse;">
  <tr><td style="background:red;color:white;">#TITLE#</td></tr>
  <tr><td style="padding:4px;"># BODY#</td></tr>
</table>

You can see examples of other looks at promedmail.org and think.com

Hope this helps,
Tyler
Rating: 5
Do we need to pay extra? October 08, 2003
Reviewer: A Reader from USA
Hi Tom,
Is HTML DB included in the database itself (say, when we buy Oracle 10g
Enterprise Edtion or Standard Edition) or we need a seporate license?
Thank you.
Followup:
This is Tyler, friend of Tom and one of the HTML DB developers

It will be included as a (drum-roll please) NO COST option with 10g, both
Standard and EE edition.  It will also be formally supported back to 9iR2 when
10g ships, though you can download and install it now from
http://htmldb.oracle.com/
I would STONGLY suggest you signup for a "Workspace" on htmldb.oracle.com which
will give you your own virtual private database and development area where you
can "kick the tires" before you have to go through the effort of installing it
(though the install is 30 min or less).  This also demostrates the hosting
capabillity of HTML DB.  Several large companies and goverment organizations are
hosting it on internal servers now where their employees can signup and develop
in their own area without installing new client or server-side software: All you
need is a WEB BROWSER!

Thanks,
Tyler
Rating: 4
RE: Do we need to pay extra? October 08, 2003
Reviewer: robert from CT
Similar Q was asked on OTN forum (Pricing with Oracle 9i ). Here is the answer
from
the monitor Raju Mattamal of Oracle:

"my current best understand of this is that Oracle HTML DB is to be free of
charge. after it's released with oracle 10g, you should be able to download a
copy w/o charge to run against oracle database versions 9.2.0.2 or better"
Rating: 5
Nice October 08, 2003
Reviewer: Dave from Lexington, MA
Tyler -

Thanks for the info, I will check out the web services demo.

Also good to see the sample sites to demonstrate how customizable the look and
feel can be.

When will the askHTMLDB.oracle.com be up? ;)

Dave
Followup:
We're still debating on which tool we should build it in ;)  But seriously, we
had exactly that, but thought it would be best if we moved to the OTN Forum as
we went from "pet project" to product.  Raj Mattmal does a great job of
answering any questions you may have.  He's not a support analyst, he's actually
a developer on the HTML DB team so he knows it as well as any.  Since we're a
relatively small development team, only 13 people, he knows exactly who wrote
what piece and can call on the rest of us for help should the question be
outside of his area of expertise.  Actually, until 10g is released, Raj IS our
suuport organization.

Tyler
Rating: 5
html DB October 15, 2003
Reviewer: A reader
Tom,
Can Oracle HTML DB be used on an oracle 9.2.0.4 db?  What tools are required for
using Oracle HTML DB?  Is there a white paper from Oracle on HTML DB?

Thank you
Followup:
htmldb.oracle.com
yes, works on 9203 and up and will be supplied for 9203 and above.
Tom
-------------------------------------------------------------------
Tyler here,

Tom is correct on the version, though we may be moving to 9204 as our lowest
supported version as there are some LOB issues with different character sets
that limit importing and exporting your application in 9203 to the SQL PLus,
rather thatn being able to upload it through a web browser.  Stick with 9204+
and you will be fine.

As far as tools, all you NEED is a web browser.  This allows any of your
developers to type in the URL and get going immediately. I love it for teaching
classes as there is NO setup on the client side.  I can suggest some other 3rd
party tools that I find generally useful though:
UltraEdit (ultraedit.com) is a great text editor if you are not a vi person
(like Tom).
CSE (htmlvalidator.com) is amazing for html and accessibility compliance
testing.
We use CVS (cvshome.org) for our source code control with various clients,
including tortoise (tortoisecvs.org)

We are currently working on White Papers and tutorials which should be posted
soon.  Until then, you can check out:
http://otn.oracle.com/oramag/oracle/03-sep/o5310gcover_html.html
and
http://otn.oracle.com/oramag/oracle/03-sep/o53asktom.html
for more information.

Hope this helps,
Tyler
Rating: 5
HTML DB October 16, 2003
Reviewer: A reader
Rating: 4
HTML DB - can't download or register for a workspace October 16, 2003
Reviewer: Phil from UK
This looks great, I'm really excited about this and the true RAD capabilities
but I get an error trying to register for the workspace and can't find the
download either (:
Followup:
Rating: 3
How to diff CLOB-s? October 16, 2003
Reviewer: msc
Hi Tyler and Tom,

I installed HTML DB and it looks great! Major step forward after webDB. Really
nice.

This is probably not the right place to ask but...

We have internal wiki-wiki-web (PHP & mySQL) and we are considering moving it
onto Oracle (like all other systems we have).

I can't find a way to make diff between two CLOB-s. I would like to save only
version deltas into database and we really need [visual] diff to see the
changes. Workspace manager is not the right tool (tracks row changes) and I have
not found java implementation eigther.

I could use existing PHP code but I would like to use HTML DB for this
application. Then I need probably diff implementation inside database?

Maybe there is other ways and I am just blind.
Followup:
Tyler here,

Is your goal to save on disk space?  If so, have you done the math on the cost
of implementing the dif code, then combining the deltas with the original to get
the current version again?  Then take that cost and compare it to the cost of
additional disk that you will actually need?  Considering you can buy a terabyte
for under $1000, maybe the simplest solution is the best.  Of course maybe there
is a business need to store deltas and I'm just barking up the wrong tree here.

Tyler
Rating: 5
WAP October 17, 2003
Reviewer: Max from Moscow, Russia
Hi Tom, hi Tyler.
Thanx for your answers, would you, please, answer one more question?

I haven`t seen HTML DB yet, so, may be, the question is lame ;-)
What about using HTML DB for building WAP sites? I do understand, that we can
set up some kind of mapping between HTML and WML and recieve WAP pages insted of
HTML, but is it the only way? Does the HTML DB team cares about WAP developers?
Thanks.
Followup:
Hi Max,

You are correct, we do not care about WAP developers.  OK, the real reason HTML
DB does not do WAP is that we chose to do one thing and do it well.  We've
worked very hard for 3+ years on doing this with a small number of developers
(3-13).  Doing WAP would have diluted our talent pool working on the primary
product.  Does it mean we will not do WAP in future realeases? No, but it has
not been one of primary goals.  Adding WAP would mean just adding a feature, it
would be a re-write of a subset of our code, a fork in the road if you will.
This would likely mean tossing aside one of our critical success factors as
well, which is to "Write the tool in itself.".  Since we use the tool every day
to build the tool, we tend to 1. Catch more bugs before they make it to the
public and 2. Add more shortcuts and features to increase productivity, as we
all want to do more in less time.

You should look to 9iAS Wireless for your WAP needs.

Thanks,
Tyler
Rating: 5
Which version does HTML support? October 17, 2003
Reviewer: A Reader from USA
Hi Tom,
I want to test the HTML DB and I downloaded the HTML DB.  Before I start, I read
the read me file and the following confused me.
"Server Hardware Requirements
Any computer capable of running an Oracle 9.2.0.2 or better database. Installing
with less than 128M of memory is not recommended.
Software Requirements
HTML DB requires the following software:
Oracle 9.2.0 or better database
Apache with mod_plsql"
One says 9.2.0.2 or better, the other says 9.2.0 or better.  I currently have an
Oracle version of 9.2.0.1.0.
Can I do HTML DB or cannnot?  Should I upgrade first?  I tried to find download
of 9.2.0.4 and failed to.
Thank you for your help.
Followup:
they only test on 9202 and above (i think it will be 9204 and above in the end)

metalink.oracle.com is where you get patch sets.
Rating: 4
October 17, 2003
Reviewer: Fred from France
Hi Tom,

I tried a bit HTML DB on
http://htmldb.oracle.com/
and that really rox ! really intuitive, really
easy to use and as i already know Oracle (datatype, SQL, PL/SQL, ...), i think i
will make some things with HTML DB rather than PHP as i did before !

But how make some web site with HTML DB because i dont think that space provider
will install HTML DB wheras they already had PHP installed...

Is it possible to subscribe some space at htmldb.oracle.com (as i did) and make
a web site with HTML DB on htmldb.oracle.com ?

Thx by advance,

Fred
Followup:
Hi Fred,

Thanks for the feedback.  As a developer, it's good to hear that all of the hard
work over the past 3+ years is helpful to others.

You can develop applications on htmldb.oracle.com.  If you need more space,
request it through the admin section.  People are running production apps there,
though I don't reccomend this as we are not providing a Service Level Agreement
(SLA).

I see you are from France.  If you have are going to be at OracleWorld Paris
next week, look for the HTML DB pod in the Database section of the Demo Grounds.
 Mike Hichwa (VP and creator of HTML DB), Sergio Leunissen (Product Manager),
and Marc Sewtz (developer) will be manning the pod.  You could not ask for a
better group of people to learn from, so I encourage you to seek them out.

Bonne Chance,
Tyler
Rating: 4
October 18, 2003
Reviewer: A reader
Hi Tyler,
I installed did the installation on Oracle 9.2.0.2 @ linux - there were no
errors during the installation. However, when I try to login on page
.../htmldb_admin I can see in the lower left corner of the Internet explorer
(accessing from windows Int explorer version 6.00) - "Error on page" - login
button is disabled and nothing happens. Have you experienced something like
this.
Thanks and regards.
Rating: 5
HTMLDB it´s a marvel ;) October 19, 2003
Reviewer: Cristian Perez from Dominican Republic
Well, after seeing some of the open world presentations and reading this thread,
I decided to give HTMLDB a try. I´m really glad that a I did.

Great work guys !!!!!!!!!!

I can see a lot of potential here. If for example it´s included when you buy
"Standard Edition One" as with the Standard and Enterprise, you'll have a lot of
bang for your buck.
Followup:
it's included in SE -- and available for 9iR2 as well.
Rating: 4
October 20, 2003
Reviewer: Fred from France
Hi Tyler,

I can't go to OW in Paris, my boss dont *think* it's a useful thing :-(

And as i am a DBA, it's difficult for me to ask time to work on HTML DB (boss
are a bit strange in France, they are not *strategic*, they are afraid to try
new things => that's a huge difference between USA and France).

But no problem, i will work it by myself (as usual in France :-)), finding
ressources on HTML DB on this marvellous web site, in the documentation and
probably recommand using HTML DB on the future to some customers...

Thx for the great job and the informations :-)

Fred
Rating: 5
HTMLDb October 20, 2003
Reviewer: Tom Swift from Lincolnshire, England
To the reader who got "Error on page" when they tried to run HTMLDb, I got the
same problem. It's because you haven't set up a virtual directory in Apache to
the Docs folder. (See the install documentation).
As soon as you do this and restart the HTTP server it will work.

Cheers,

Tom
Rating: 3
"Error on page" October 20, 2003
Reviewer: msc
To Reader,

I got same "Error on page" with IE6 but it worked fine with FireBird and Opera.
Rating: 5
for Tom Swift - "Error on page" October 20, 2003
Reviewer: A reader
Thanks!!!
Rating: 5
how t report bugs on html db October 21, 2003
Reviewer: A reader
I ran into a sql error when attempting to create
an application.
Followup:
I would suggest you try the OTN forum for now at:
http://forums.oracle.com/forums/forum.jsp?forum=137
When 10g is released, you can use MetaLink or call support (assuming of course
you have a valid CSI), but until then, Raj on the OTN forums is our support
organization.

Thanks,
Tyler
Rating: 5
I figured out when the bug is shown October 22, 2003
Reviewer: A reader
It is not a common case - so you are ok.. i will
try to post it in otn when i get a chance..

thanc Tyler, Tom!
Rating: 5
How can we install mod_PLSQL? October 22, 2003
Reviewer: A Reader from USA
Tom,
I have installed Oracle 9.2.0.4 database and Apache server.  But I don't have
mod_plsql installed.  Can you please direct me to where I can find more
information about installing mod_plsql, or can you please list the steps for
installing mod_plsql on Windows NT platform?
Thank you for your help.
Followup:
Actually, if Apache was installed from the Oracle distribution, you have
mod_plsql installed.  You can verify this by navigating to
$ORACLE_HOME\Apache\modplsql\ .  If that directory exists, you have mod_plsql.
You cannot easily add mod_plsql to a non-Oracle Apache distribution.

Tyler
Rating: 4
AskTom.com Development October 22, 2003
Reviewer: Robert from CT
Tom,
Have you actually been doing AskTom development
on Marvel/HTML DB yourself or you got people doing
it for you and you just answer the questions ?
Followup:
I wrote the first asktom (it was "blue")

Joel Kallman wrote AskUS -- the framework asktom is built on (a generic
application, with my template).  I touch things up here and there -- I wrote the
underlying API that touches the data, but Joel did everything you "see"
Rating: 3
mod_plsql on non-Oracle Apache distribution October 23, 2003
Reviewer: msc
<quote>
You cannot easily add mod_plsql to a non-Oracle Apache distribution.
</quote>

I use mod_plsql with different Apache distribution and had no difficulties to
get it work. I still have Oracle Apache distribution installed into hard-disk
but it is not running.

I had to copy following line into Apache httpd.conf file:

include "d:\oracle\ora92\Apache\modplsql\cfg\plsql.conf"

As you can see this is a Windows distro, maybe undex Linux it is complicated.
Rating: 4
your HTML DB expertise October 26, 2003
Reviewer: Robert from CT
Tom,

1)Is AskTom running on the latest release of HTML DB ?

2)Are you familiar & comfy with HTML DB to the degree that you can build an app
by yourself from scratch w/o calling the guys down the hall ?
Hypothetically, you think you can handle AskTomHTMLDB.com ?

3) Did you do regions of the type "Function returning SQL"

This (in Region Source) does not work for me:
return mytestpkg.testsqlstring ;

where mytestpkg.testsqlstring returns just 'select * from emp'

I get :
"report error:
ORA-01403: no data found"
Followup:
1) yes i believe it is -- Tyler Muth/Joel Kallman would know for sure...

2) I don't do gui's anymore :)  i build server stuff -- the apis. So, for
asktom, I built the original api's that deal with the data and Joel built the
gui.

3) yes, we use them heavily here.  do you have an emp table? does it have any
data?

Tom
------------------------------------------------------------------------------
1) Yes, AskTom is running the latest and greatest.  It's actually been part of
our development process to upgrade our all of our internal production systems
1-2 weeks before we release an upgrade to the public.  Since the tool is written
in itself, it's actually been used/tested on our development instances for
substancially longer.  We really do "Eat our own dogfood", and we believe that
it's critical to delivering a quality product to you, the customer.

2) I'm not going to touch this one, other than to say Tom knows more than he
takes credit for.  He actually wrote some of our most complex code, such as how
we parse SQL and PL/SQL.

3) Try creating a region on the same page that is of type SQL Query and test
your query that way.  Function Returning SQL Query regions are great for dynamic
predicates, but if you don't have a specific need for them, start off with the
basics, it's much easier.

Hope this helps,
Tyler
Rating: 5
Some HTML DB questions October 29, 2003
Reviewer: A reader
Tom,
I downloaded HTML DB and set it up all on one computer for trial. It works
great, right out of the box. I have few questions for a more structured
implementation:
a) We do not want to install everything on one computer. Our databases are
already installed on Sun boxes and we want to use them. We want to install
Apache and everything else on a Windows box. Can we do this without installing
the entire Oracle server software?
b) I want to bring data in from multiple database in my application. It is
possible or am I restricted to designing against the database I connect to using
the DAD?

Thanks.
Followup:
a)  Yes, you should be able to do this with a custom installation.
b)  HTML DB is made for one database.  You applications execute as a db user in
a single database.  The only way to consolidate data from multiple instances is
to use db links, which as you know aren't the most performant way of doing
things.  You could do materialized views over db links to improve speed.

You should consider using the OTN Forum for HTML for future questios at:
http://forums.oracle.com/forums/forum.jsp?forum=137
Good luck and glad it is working well for you,
Tyler
Rating: 5
HTML DB looks very powerful framework October 30, 2003
Reviewer: Mihail Daskalov from Sofia, Bulgaria
Is there any intention to generate HTML DB Application from Oracle Designer?

Thank you Tom
 for always providing the best answers.
Rating: 5
HTMLDB looks really good October 31, 2003
Reviewer: A reader
This product looks really good. I agree with you, Tom that most applications
could be created in this environment.

What I think it needs now is more reporting capabilities. Many applications have
reporting requirements that could not be met, or would be very tough to meet
without a development tool specifically built for reporting. Like Oracle
Reports.

So I think the combination of HTML DB and Oracle Reports would handle a big
percentage of application development requirements.

Now the problem is, in order to use Oracle Reports you have to license and
install the whole Oracle 9ias Enterprise Edition.

Expensive, complicated, high maintenance, Just to have reporting capabilities.

I am looking into other reporting tools (Crystal, Webfocus) as an alternative.

Do you or does anybody have any suggestions for reporting tools with the
capability of Oracle Reports but a simpler environment and lower licensing
costs?
Rating: 4
AskTom & HTML DB October 31, 2003
Reviewer: Robert from CT
>tell me, do you consider asktom "small"? html db is >perfect for it -- it is
the
>quinessential html db application......I do about >1,000,000 transactions a
month (and asktom is just one of
>about 5 applications written in html db running on that >machine).  Is that
smallish?

Just re-read this thread...
I agree that the AskTom app is the perfect demonstration of what HTML DB can do
- in terms of dynamically publishing data from db to the web readers/users.

Because except this form page I am typing into, it does not have the data entry,
entry validation requirements like a typical C/S app.

What I'm saying is that AskTom is really cool but does not really "test" HTML DB
in the area of user interactivity, data input...that sort of thing.
Followup:
sure it does -- what do you think i use??

what are you using here?

how do you ask questions? (so it is not "except for this form page").  there is
alot of interaction here.  even just the search screen.  most every page accepts
some form of input or another.

we built an entire web calendar/pim system in earlier versions of html db.  very
very client serverish type of application.

our employee review system is written in it.

among dozens of others.  it works dandy for user interaction.  In fact -- due to
the 100% consistency of the look and feel and the simplicity of the html
generated, it is perhaps phenomenal for that.  It works on all browsers (like
amazon and yahoo -- but not most MS websites :), it's interface is somewhat
intuitive (no one goes to amazon.com training, no one goes to asktom.oracle.com
training), and it is simple to deploy and re-deploy.
Rating: 5
Reporting? November 02, 2003
Reviewer: A reader
This product looks really good. I agree with you, Tom that most applications
could be created in this environment.

What I think it needs now is more reporting capabilities. Many applications have

reporting requirements that could not be met, or would be very tough to meet
without a development tool specifically built for reporting. Like Oracle
Reports.

So I think the combination of HTML DB and Oracle Reports would handle a big
percentage of application development requirements.

Now the problem is, in order to use Oracle Reports you have to license and
install the whole Oracle 9ias Enterprise Edition.

Expensive, complicated, high maintenance, Just to have reporting capabilities.

I am looking into other reporting tools (Crystal, Webfocus) as an alternative.

Do you or does anybody have any suggestions for reporting tools with the
capability of Oracle Reports but a simpler environment and lower licensing
costs?
Followup:
so, what kind of report could you not accomplish with HTML DB and analytic
functions in sql??

give me a "for example"
Rating: 4
"pls" for mod_plsql call November 02, 2003
Reviewer: Robert from CT
>
http://asktom.oracle.com/pls/ask
Tom, are you aware if it's possible to configure the http listener or something
else so that one can use a different alias than "pls" in mod_plsql call ? any
way to mask that ?

It's like hanging a sign out to the world  saying "this app is running against
Oracle DB, come and ...."

Thanks
Followup:
absolutely you can -- it is simply a configuration setting in the apache virtual
paths.

but -- so what about the "sign hanging out there".  they'd see it is Oracle, not
a .asp and go away ;)

but pretty much every single website out there is running a database and many of
them are in fact Oracle.  The databases themselves are not getting hacked in as
much as the APPLICATIONS in front of them are.
Rating: 3
about url's rewriting November 03, 2003
Reviewer: Andrey Prokopenko from Moscow, Russia
Since we're supposing here Apache as a web server, there is a very useful
feature, contained in one of many Apache modules. I mean mod_rewrite.
By using it you can completely alter url to any diffirent location you want.
See more at
http://httpd.apache.org/docs/mod/mod_rewrite.html
Rating: 5
An Example Report November 03, 2003
Reviewer: A reader
Purchase order.
Requirements - Based upon number of pages- sometimes up to 20, margins have to
be different on middle pages, signature has to be exactly 1 inch from the bottom
of the lower right corner.
If there is a word processing document attached, possibly Word, possibly PDF,
possibly Visio, it has to print starting at the next to last page, but the
signature and some other info has to print on the last page in a specific
location.
1st page prints landscape out of 2nd bin in printer, the rest print portrait out
of 1st bin
--
If all reports could be handled by HTML, there would be little demand for
products like Oracle Reports, Cognos, WebFocus, Crystal.
--
Talk to the developers in the Oracle Reports group, I am sure they could give
you lots of other examples.
--
Followup:
All reports should be handled by HTML, printed stuff is so 1970's :)

You are talking about format -- form, not content.  As a report generator, for
people serious about getting information, html db is awesome

for pretty printing in PDF, you are going to have to go beyond and buy a tool.
I consider this less a reporting issue then a formatting issue.  There isn't any
report you cannot generate in HTML, you just don't like the way it looks.
Rating: 4
Working with htmldb.oracle.com November 03, 2003
Reviewer: Gus Spier from Wash, DC
This promises to be a terrific feature.

Does everyone fumble around on the first page of htmldb.oracle.com and try to
login with incomplete/contrived credentials?  It seems almost as if there is an
entrance exam (let's see if they are smart enough to click on the tasks in the
correct order before we let them in) before getting close to requesting a
service.
Rating: 5
Report Formatting November 03, 2003
Reviewer: A reader
>for pretty printing in PDF, you are going to have to go >beyond and buy a tool.

>I consider this less a reporting issue then a formatting >issue.  There isn't
any
>report you cannot generate in HTML, you just don't like >the way it looks.

It's not that I don't like the way it looks that counts. It's whether or not the
people who sign my paycheck like the way it looks that counts.

That may be customers, business partners, CFO's, board of directors. Philosophy
and theory are nice, but they won't pay my mortgage.
Followup:
then they have to pay the bills for the extra software :)

What i've discovered is that people can be QUITE flexible when they understand
all of the factors.

"I want X"

"well, X means this, Y seems to satisfy everything and Y means this other thing"

"Oh, well of course, Y it is, 50% of the cost and it does meet my basic needs,
go for it"

I see this over and over (might not apply here, we are just talking about a
reporting tool after all).  I remember trying to help someone tune a multi-table
join of 10's of thousands of records all to get a random 6 records.  We could
not use SAMPLE since it was a complex query.  So, the query basically had to
assemble 10's of thousands of records -- sort it randomly and get the first six.

The reason?  A manager thought it would be cool to have the home page hilight a
random 6 employees.  This query would be executed for every hit on the home
page.  the person tasked with this just tried to implement it.  Not until
someone showed that 99% of the capacity of the server would be spent executing
this query over and over and over (for every view of the home page) and
explained that to the requestor of this "feature" did they say "well, obviously,
we shouldn't do that.  it was a nice idea but hey, lets be reasonable here"

This I think goes beyond philosophy and theory and into the CRUX of what we
should be doing -- not blindly following requirements developed by people who
"don't understand the ramifications of such things entirely", but rather gently
pushing back with provable, scientific information that says "hey, look at this,
this is the COST of this function -- do you really really want it?  you are
after all paying for it"
Rating: 5
More on Reporting (formatting + distribution of printed material) November 04, 2003
Reviewer: A reader
I am a consultant. Reviewing the applications that I have helped developed or
worked on over the last several years, I think that HTML DB would have been able
to satisfy the data manipulation requirements of over 90% of them. The other 10%
probably could have been redesigned or done in another way.

However, the reporting ( formatting and distribution of printed material )
requirements would be extremely challenging and I suspect costly ( in
development time ) to accomplish with HTML DB.

Oracle has a product ( Oracle Reports ) that in conjuction with HTML DB, would
have satisfied probably 95%+ of all of the requirements for all of these
applications.

It's too bad that you have to license the Enterprise Edition of 9iAS in order to
get that functionality. Aside from just the licensing cost, there is a pretty
hefty maintenance cost of 9iAS Enterprise Edition, if you include learning and
management time. It also throws a complicated infrastructure of hardware and
software into the mix that can and does break periodically.

I compare this situation with what I am perceiving in Oracle's embracement of
Linux. If companies are able to spend less on hardware and operating systems,
they will have more available for Oracle licenses.

Well, if companies spend less on Oracle licenses, which they could if
applications could be entirely developed in HTML DB, they would have more
available to spend on me, in application development.

I am looking into other reporting tools. So far, Crystal Reports is looking the
best to me. From what I have seen so far it would meet most of the formatting
and distribution requirements and the total cost of deployment appears to be
about 10-20% of 9iAS Enterprise Edition.

If Oracle sold Oracle Reports as a standalone product, it would be a no brainer
for me. HTML DB + Oracle Reports.

Sorry for rambling.

Again, I really like this product.
Rating: 5
Your opinion is important November 04, 2003
Reviewer: juancarlosreyesp@yahoo.com
Hi Tom, could you give brief a comparison between the PHP and HTML DB (About
main features)
Thanks :)

P.D. Reeaaally Sorry Tom for this, but about the previous post I agree I have
started a post and continued with other loooong post there is is otn formus, I
think there should be a license only to run reports and forms runtime in IAS,
5,000 U$ is too much to pay when you only need runtime for forms and reports 9i
:) sorry for the post but  we'll not surrender easily I would like to use forms
9i, but I cant because the previous $$ problem. :(
I you think as us, please do a post here
http://forums.oracle.com/forums/thread.jsp?forum=82&thread=161385&msRange=15
There seems to be good news in 10G, but...
Followup:
I cannot compare html db to php as I've never looked at php.
Tom
---------------------------------------------------------------
I recently began developing in PHP a bit to put together a personal web site.
The reason I chose PHP is that is what is supported by the majority of the free
hosting companies.  After working with HTML DB for almost 4 years, it was really
hard to go back to static files.  My biggest problem was navigation.  I ended up
writing a function in php file that I included on every page to do my
navigation, that way I could change it one place, not on every page.  In HTML
DB, you define a template for your navigational elements, then declaratively
create them.

Next problem was the look and feel.  There were several family memebers opinions
involved in how the site would look.  I tried to mock-up on page and get right
there, but people wanted to interact with the site to get a feel for what they
wanted it to look like.  I ended up changing a lot of HTML on a lot of pages, no
matter how much UI I did in CSS.  In HTML DB, every UI element is controlled by
a template: pages, regions, items and they're labels, reports, lists, menus,
etc.  When you want to change the look of something, you change it's template
and instantly your change is reflected in every element that uses that template

Then I got into handling form data. I had to create a file to handle my form and
declare variables to accept the data that was submitted to it.  In HTML DB, you
simply create an item, such as the "text area with spellcheck" that I am typing
in, then you can reference it's value anywhere else in the application using
bind variable syntax.  The process that handles this followup I'm typing might
look like:
update followups
   set message_text = :P5_MESSAGE_TEXT
 where id = :P5_MSG_ID;
Assuming P5_MESSAGE_TEXT was an item on page 5 that captured this text and
P5_MSG_ID was a hidden item that contained the primary key of this followup.
Also notice I did not include any database connection info.  You don't have to
connect to the database, HTML DB lives IN the database, so you can issue SQL
statements at will without managing any connections.

Display conditions HTML DB are very powerful.  They allow you to conditionally
display items, regions, buttons, etc base on some other logic; maybe you only
display the employee fields once a user has selected a department.  This would
all have to be hand-coded in PHP.

Validations are another powerful feature of HTML DB. Validations allow me to
define the values that a user must enter in an item.  Yes, you can do this in
JavaScript, but how about checking to make sure the value exists in a 1 million
row table.  Are you going to pull all the rows down to the client?  (I hope
not).  Once you see them in action, you'll understand their power.

I could go on for hours on this topic, but my best suggestion is just to try
HTML DB and see for yourself.

Thanks,
Tyler
Rating: 5
:) Thanks November 04, 2003
Reviewer: A reader
Rating: 5
Agree with post on reporting November 04, 2003
Reviewer: A reader
Hi Tom, any possibility on getting comment from someone in Licensing on the
issues raised in that post?
Rating: 4
Do educate us, Tyler... November 04, 2003
Reviewer: Robert from CT
>I could go on for hours on this topic, but my best suggestion is just to try
>HTML DB and see for yourself.
>Thanks,
>Tyler

Tyler...we wanna hear it...put that into documentation & maybe create some more
demo apps with features you want to show us ;)
Followup:

http://otn.oracle.com/products/database/htmldb/howtos/index.html
Also see my comments in this thread at:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:12691263416650#13591095312386
This is just the beginning.  We will be posting more content as soon as we
complete it.

Thanks,
Tyler
Rating: 5
Thanks Tyler too for your explanation November 04, 2003
Reviewer: A reader
Rating: 5
Tom, You have been lying on your resume November 04, 2003
Reviewer: A reader
>This I think goes beyond philosophy and theory and into the CRUX of what we
>should be doing -- not blindly following requirements developed by people who
>"don't understand the ramifications of such things entirely", but rather gently

>pushing back with provable, scientific information that says "hey, look at
>this,
>this is the COST of this function -- do you really really want it?  you are
>after all paying for it"

I know now that you were the marketing director for Apple back in the 80's.

Don't give customers what they want, give them what's best.
Rating: 4
Documentation November 04, 2003
Reviewer: A reader from USA
Can someone please point me to where I can find documentation regarding HTML DB?
More like users guide or something.

Thank you in advance
Followup:
it's forthcoming -- new feature of 10g that will be supported for 9iR2.
http://htmldb.oracle.com/
download the preview release, most of the documentation is there as integrated
documentation.
Rating: 3
users guide November 05, 2003
Reviewer: hannibal from styria/austria/europe
look at here
http://htmldb.oracle.com/i/doc/toc.htm
and if you download html-db from oracle it also comes with some documenation
about administration and installation.

hope this helps

hannibal
Rating: 5
how about regexps ? November 05, 2003
Reviewer: Andrey Prokopenko from Russia, Moscow
HTML DB indeed is a long awaited option for Oracle.
But i very interested in one IMHO very important fearure.
I mean reqular expressions.
Work with text data became very light and robust, since regexps stepped into a
play.
Now in oracle exists PL/SQL implementation of regexps functions named
OWA_PATTERN, but it's awfully slow and has many limitations.
Dear Tom, since you IMHO no doubts are most skilled and competent in upcoming
HTML DB release plans, could you please answer, does Oracle have any plans to
implement more workable solution for regexps among other HTML features ?
Followup:
there is 100% full regular expression support in 10g -- native, compiled into
the kernel, fast regular expressions.
Tom
-----------------------------------------------------------------------------
In HTML DB you can use regular expressions in validations. You will only see
this option when you install in a 10g database of course.  Below is an example
phone number validation included with HTML DB:

^\(?[[:digit:]]{3}\)?[-. ][[:digit:]]{3}[-. ][[:digit:]]{4}$

Try that one in PL/SQL ;)

Thanks,
Tyler
Rating: 3
Where to get Oracle 9.2.0.3 requiremet for html db November 06, 2003
Reviewer: A reader
Hi Tom, where you can get Oracle database
9.2.0.3,

"Any computer capable of running an Oracle 9.2.0.3 or better database"
or it is 9.2.0.1 plus patch to upgrade to 9.2.0.3 .
or which is the real database requeriment.

:) Thanks
Followup:

http://metalink.oracle.com/
There you can download the patch to get to 9.2.0.3.
Thanks,
Tyler
Rating: 5
:) Thank you. November 06, 2003
Reviewer: A reader
Rating: 5
Move OTN Forums to HTML DB November 06, 2003
Reviewer: A reader
What are the chances of this happening?

Performance on the OTN forums site lately has been horrible.

I look at the url and see jsp.

I understand.
Followup:
Tyler here,

We have considered (as has been reccomended several times before) creating a
version of AskTom for HTML DB.  The format is great and between you and I, I
think he gets a little annoyed with all the HTML DB traffic here :)  It's a
great suggestion and I'll be happy to credit "A reader" with suggesting it if we
impliment it.

Thanks,
Tyler
Rating: 5
I agree!!!!!! November 06, 2003
Reviewer: A reader
At least move the HTML DB forum to askhtmldb.oracle.com.
Rating: 2
RE: Move OTN Forums to HTML DB November 06, 2003
Reviewer: VT from USA
Now a days part of E-bay is running on J2EE. If J2EE is good for E-bay it would
be good for anything. Do not say JSPs are slow without knowing the stuff. It
depends on several factors. ( bad design/coding/un-tuned etc..). I wonder which
one is true for OTN forums?
Rating: 5
J2EE November 06, 2003
Reviewer: A reader
I don't think J2EE is slow. I just think the environment is very complex. I
would guess that the people that work on the forums site are scrambling today
trying to figure out the problems.

There is probably finger pointing, the problem is with the database, no the
problem is with one of the appservers, no a different appserver, no the
infrastructure, no its hardware, no its something else.

If they were using HTML DB, there would be one place to look. The Database, and
we all know how reliable that is.
Rating: 5
Another Plug for askhtmldb.oracle.com November 07, 2003
Reviewer: A reader
I posted a question on asktom this morning (Your new book : Expert one-on-one
performance by design) about the Web Seminar he did on Nov. 5th. The site was
saying it hadn't started yet.

Within an hour, maybe less, that's how long it was from the time I posted until
I checked back, my question was answered and his home page changed reflecting
that the URL would be posted soon.

I wanted to post a question on the HTML forum. Sure enough, I get 'Error:
Document not found'.
Rating: 5
HTML DB HowTo's Posted November 10, 2003
Reviewer: Tyler D. Muth from Reston, VA
We have posted the first round of "HowTo's" for HTML DB on OTN.  We are working
on more and will post them as they are completed.  You can find them at:
http://otn.oracle.com/products/database/htmldb/howtos/index.html
If you have others you'd like to see, please request them on the OTN forum at:
https://forums.oracle.com/forums/forum.jsp?forum=137

I know we have a number of White Papers under way as well.  I'll let you know
when and where they are posted.

Thanks for all the great feedback (and praise),
Tyler
Rating: 3
workspace, download link November 10, 2003
Reviewer: Igor from France
hi

 I'm a bit lost.
 What is workspace on htmldb.oracle.com?
 How could I get one?
 Also, I don't find where is link to download...
Followup:
A workspace is basically your little slice of the development environment.  It
allows the environment to be "hosted".  So, you can sign up for the workspace
"IGOR_INC" on
http://htmldb.oracle.com/
.  You can only see YOUR applications and your schema.
Customers can also host this internally, thus allowing different development
groups to work on applications independantly.  So, the HR group can be
developing their HR apps in the HR workspace, and on the same machine in the
same db instance, the financials group can be developing thier apps in the
financials workspace.

The download link has been removed again because we cut a new build and upgraded
htmldb.oracle.com.  We will run it for a few more days before we release this
code to the world.  It allows to test it with a REALLY large population and
still catch any bugs before people have the code.

You should really try HTML DB on the hosted site before you download and install
it.  This way you can focus on evaluating the important aspects of it such as
its ability to built the type of application you are looking for and not have to
focus on installation details such as modifying your httpd.conf file.  Not
trying to scare you off with the install (I did it in 4.5 minutes on a fast
machine 2 weeks ago), just want to show you the quikest path to success.

Thanks,
Tyler
Rating: 4
Workspace and DB Schema November 10, 2003
Reviewer: robert from CT
Are the following statements corrert ?

1) "A HTMLDB Workspace mapes to ONE Oracle Schema"
2) "A schema can be associated with different Workspaces"
Followup:
1) Almost.  More than one schema can be associated with a workspace.
2) Correct.

Essentially you have a many-to-many mapping, though the typical use is a
one-to-one mapping in both cases.

Thanks,
Tyler
Rating: 5
Thanks November 11, 2003
Reviewer: Igor
Rating: 4
error list? November 13, 2003
Reviewer: hannibal from austria/europe
first i must place my praise to the new HTML-DB. This is a real greath feature
... i it very helpfull to us.

i would like to ask if there is a list of error-codes the html-db is returning
to me available (like the error-list for oracle ORA-<number>) so we can find out
what the software wants to tell me.

f.e. currently i try to pass over values from one page to an other.
i call from one page:
http://PC:PORT/pls/meins/f?p=109:9:2366173999985397015::::EMPNO:1307:
and get
Error ERR-1002 Unable to find item ID for item "EMPNO" in application "109".
from the other instead of using the :EMPNO in my
select * from emp where empno = :EMPNO

but i cant figure out what i am doing wrong ...
a list of the ERR-<number>'s would be very helpfull i belive.

thx in advance

hannibal
Followup:
Hannibal,

I suspect you are not using the EXACT name of the item you are trying to set.
Typically item names default to P#_NAME, so if this item appears on page 9, it's
name is likely P9_ENAME.  We do not have a list of error codes available right
now, but I will file this as a bug.

If you have not found it already, you can find the URL sytax documentation by
clicking on the help icon > index > f > f?p syntax.

Hope this helps,
Tyler
Rating: 3
working on tab November 13, 2003
Reviewer: hannibal from austria/europe
tyler,
i am working on a table -> emp as you surely guessed. and i
want to use the column EMPNO directly by calling an url...
from my query in the report
select fname||' '||lname name, empno from emp
i create a table and i set in
HTML DB Home>Builder - Application 109>Page Definition>Report Attributes>Column
Attributes
for the column ENAME
in the section 'Column Link'
the values for
application : &APP_ID.
link text : #NAME#
URL : f?p=&APP_ID.:9:#APP_SESSION#::::EMPNO:#EMPNO#:
all other fields are blank in this section('Column Link'). ( i want to create
links on the names to call an other URL with the EMPNO as attripute/item ).
i looked for my link and i think it should be o.k. and complete (?)
may be it would help me if you could explain this error number to me. i am not
sure, does this error come up because the #EMPNO# is unknown at the calling site
(8) or the :EMPNO
does not fit to anything at the called site (9). for my experience if the bind
varaibale (:EMPNO) is not found, it gets a NULL value for the next excution - so
i guess the error comes up before the html-db gets to the parsing - may be
before the site 8 gets called (?).
i also tried to run in debug mode but by calling the page
i get nothing more but the short error message but no
trace from the debug (which is very good and helpfull in all the other
situations where i had to use it BTW).
error message :
----------------
Error ERR-1002 Unable to find item ID for item "EMPNO" in application "109".
OK

Unexpected error, unable to find item name at application or page level.
---------------------
is there a way to look into the application to see what item he expects or would
be ready to accept?
mfg
hannibal
Rating: 3
followup November 13, 2003
Reviewer: hannibal
tyler,
by creation an explicit hidden item of the same name in the same page it worked
:-) . so i guess the application does check if there is such an item defined in
the application.
do i always have to create such an hidden item when passing a value i want to us
as bind-variable on the next site?
thanks in advance
hannibal
Followup:
Hannibal,

I'll try and cover both of your posts with this response.

You are setting the value of an item through the URL.  If the item does not
exist, you will get this error.  Keep in mind that item names MUST BE UNIQUE for
your application, which is why we default them to P#_XXX.  This way you could
have P9_ENAME and P38_ENAME.

It does not matter whether the item is hidden or not.  You could change it's
type to "Display as Text (saves state)" and it would work just fine.

The best thing I can tell you to do to understand these concepts better is to
click on the "Wizards" tab in the builder and choose the "Report with links to
form on a table (2 Pages)" wizard to build a two page application on emp.  You
can then reverse engineer these two pages to see how they work.  Basically, the
report on the first page will have a link to a form on the second page.  The
link will populate a hidden item (for empno in this case as it is the primary
key) on the second page.  There will be a "FETCH" process on the second page to
query the row automatically to populate the other fields (where empno =
:PX_EMPNO).  There will also be a "Process" process (no way to make that sound
right) that will insert/update/delete a row based on the button you click.

Also check out the forum at:
https://forums.oracle.com/forums/forum.jsp?forum=137

and some "How To's" at:
http://otn.oracle.com/products/database/htmldb/howtos/index.html
Hope this helps,
Tyler
Rating: 5
New version of HTML DB November 13, 2003
Reviewer: A reader
Sorry, I would have posted this on the Forum, but again I cannot access it.

Besides, I like this site so much I check it more often.

I see there is a new download, is it possible to get the bug fix list and
enhancements for this version, much like in the readme's for patches on
Metalink? Just want to know if I should upgrade or not.

By the way, I LOVE this product. It has become somewhat like a religion to me.
Everywhere I go, I preach about this "next great program language". Too bad that
the converts are again going to completely convert all of their middle tier code
again. Oh well, I think that when the "next great program language" after HTML
DB comes along, it will also be storing it's code in the database, so my guess
is the conversion will be easier.
Followup:
"A Reader",

We have not yet generated a list of bugs fixed, etc. with any of our releases.
I will add this as an enhancement request and see where it goes as I beleive it
to be a great suggestion.  Not sure what version you are running, but from 1.4.x
on it has been nothing but bug fixes.  I don't believe we've added a single
feature since 1.3.9.  A lot of the bugs have been internationalization issues,
but there have been some others that you may experience.  I would suggest
upgrading, as it puts you one step closer to the version that will be formally
supported with 10g.  The upgrade process is reasonably painless and is
reverseable if anything goes wrong.  Basically we:
- Install the newer version in a new schema
- Copy all of the meta data over from the old version
- Run a report showing the number of rows in each table, comparing the old
schema to the new (old: 4 applications -- new: 4 applications = good, old: 193
pages -- new: 190 pages = BAD).
- At this point you can choose to recreate the public synonyms for HTML DB which
would then point to the new schema.  That's it.
- If things aren't working as expected, such as a tablspace filling up when the
meta data is being copied over, you simply call a package to point the public
synonyms back at the old schema.
After a couple of days, you can drop the old schema as it is no longer needed.

Glad you like the product so much!  Our team enjoyed your quote, it was a nice
break from fixing obscure multi-byte character set bugs ;)

Thanks,
Tyler
Rating: 5
New Version of HTML DB November 13, 2003
Reviewer: A reader
Sorry, I meant "next great programming paradigm"
Rating: 4
HTMLDB November 14, 2003
Reviewer: Joerg from Germany
Sorry,I'm not finding the download link.
Cann someone help me about this?
Followup:
Joerg,

If you haven't tried building anything already on the hosted site, I would start
there.  It saves you the time of downloading, installing, configuring Apache,
etc.  So you can get to what you really wanted to do, which is evaluate the
product.  The code that you download and the code that is running on
htmldb.oracle.com is EXACTLY the same.  The only thing we change is to add a
link or two on the login page.

Good luck,
Tyler
Rating: 4
HTMLDB November 14, 2003
Reviewer: Joerg from germany
Hy,
OK I found it out now it was not available because
my primary language in the browser was german and
the translation option of the website(HTMLDB??)
is cutting some links allthough the download link
Followup:
are you sure?  they HTML DB guys just tested that and had no issues?  does it
still reproduce?
Rating: 5
Just think HTML DB should be at the top of all lists November 19, 2003
Reviewer: A reader
Rating: 3
Can I publish webservice using HTML DB? December 03, 2003
Reviewer: msc
Hello Tom,

is it possible to receive SOAP message via mod_plsql and then process it in
PL/SQL (Oracle 9i)? It seems to me that it would require SOAP support from
mod_plsql part?

All OTN examples of publishing SOAP-binded webservices use App Serve Java based
approach but I would like skip this part.

The HTML DB seems to support only consuming webservices (and then rendering them
into page region)?
Followup:
Unfortunately there is no way to publish web services using HTML DB, only
consume, as you mentioned.

Sorry for the lack of help here,
Tyler
Rating: 4
Thanks Tyler! December 03, 2003
Reviewer: msc
Rating: 3
webservice != SOAP December 05, 2003
Reviewer: msc
Tyler,

you mention:

<quote>
Unfortunately there is no way to publish web services using HTML DB, only
consume, as you mentioned.
</quote>

The WSDL specification
http://www.w3.org/TR/wsdl
states:

<qouote>
Abstract
.... in this document describe how to use WSDL in conjunction with SOAP 1.1,
HTTP GET/POST, and MIME.
</quote>

and chapter "4. HTTP GET & POST Binding" shows how to make use webservices
without the SOAP envelope.

So... the SOAP is *not* required to publish the web-service. It is just one way
of doing it.

This implies that the HTML DB and mod_plsql/owa can be used as web-services
provider for HTTP GET & POST binding.

We just cannot use the HTML DB based web-serviec to accept SOAP *request* but
still we can provide SOAP wrapped response.

Do I get it right? Or do I leave something out? Why this SOAP is useful (or so
much used) anyway :-/
Followup:
Hiya msc, Sean Dillon here.

Yes, it's true, SOAP is all about Web Services but Web Services are not
necessarily all about SOAP.  Therefore, a web service could indeed be
implemented using some other form of HTTP- (or other protocol believe it or not)
-based infrastructure.  Nothing is keeping a savvy developer from writing such a
custom web service that is not based on the SOAP >standard<.

The issue with that, however, is that it isn't based on standards...  This is
okay if you only ever plan on talking to yourself or to other business partners,
customers, etc. that plan on implementing your service architecture... but for
the other 99.9% of the world, this is unacceptable.

Oracle builds it Web Services support based on standards.  Even the HTML DB
implementation of the Web Services consumer model is based on SOAP.

I don't think that SOAP not being required to publish web services IMPLIES that
HTML DB and the PLSQL Web Toolkit can be used as a web services provider for
HTTP GET & POST binding.  I think that it implies that Apache's HTTPD listener
along w/ some crafty custom development using the PL/SQL Web Toolkit could
indeed implement a custom web services architecture/implementation.  HTML DB has
nothing at all to do with that.

Now, this doesn't mean we can't expose Web Services in the database that you
build through HTML DB.  In 10g, we are going to have the ability to publish and
subscribe to web services from within the 10g database instance.  No iAS... no
HTML DB... no .NET... just the database.  It sounds like this is something you
should look into once the docs are available or you can get a public release of
the 10g database.

Why's SOAP used so much/useful?  Again... standards.  Everybody agrees on
standards, then whenever I do something, you can take advantage of it easily,
quickly, lower cost, etc.

Hope that helps!

_smd_
Sean Dillon
Rating: 5
Thanks Sean! December 06, 2003
Reviewer: msc
<quote>
In 10g, we are going to have the ability to publish and
subscribe to web services from within the 10g database instance.  No iAS... no
HTML DB... no .NET... just the database.
</quote>

This is interesting :) One good reason to wait for 10g...
Followup:
Along with:
- Flashback ANYTHING
- Regular Expressions
- Install in 20 minutes from 1 CD
- PL/SQL Performance

The last one had a big impact on HTML DB as it is written entirely in PL/SQL.
Same hardware 9i vs 10G we saw around 30% increase in performance.  We would
have been happy with 5, or even 10, but 30!  Of course my favorite feature is
HTML DB, as we've been working on it for almost 4 years now.  Everyone who tried
it loved it, but there was no marketing behind it to get the word out to the
masses.

Tyler
Rating: 5
How to uninstall HTMLDB? December 06, 2003
Reviewer: Saminathan Seerangan from NJ,USA
Could you please tell me how to uninstall HTML DB which I installed over my
9.2.0.4 DB running on Windows NT.
First installation did not go thru successfully so I tried to re-install 2nd
time. But is is giving me the following error.

>> Which directory should be used for the log file?       [./] htmldblog
>> Is this a (1) New install or an (2) Upgrade?            [1] 1
>> What is your connect string  (Enter for none)?           [] sami
>> What is your Oracle SYS password?       [CHANGE_ON_INSTALL]

Error:
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
It appears that this version of Oracle HTML DB already exists in the database.
To upgrade Oracle HTML DB, you must use a newer version than you currently
have installed.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

begin
*
ERROR at line 1:
ORA-00900: invalid SQL statement
ORA-06512: at line 11

Disconnected from 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

L:\daily\120603\htmldb\htmldb>

Thanks
Sami
Followup:
You just need to drop the two schemas associated with it.  They are:
FLOWS_0104XX  where XX is the minor version number and
FLOWS_FILES.

So, if you are running version 143, you would connect via SQL Plus as system (or
some other privildged user) and execute:
drop user FLOWS_010403 cascade;
drop user FLOWS_FILES cascade;

That's it.  Simply perform a new install and your all set.

Good luck,
Tyler
Rating: 5
htmldb passwords December 08, 2003
Reviewer: Mark A. Williams from Indianapolis, IN USA
Are passwords submitted via the htmldb login screen passed as clear text across
the network?  If so, what recommendations for securing them?  I have been
tinkering with the Apache directive SSLRequireSSL...

Thanks,

Mark
Followup:
SSL is the way to encrypt this stuff yes.
Rating: 5
creating DAD for another database December 08, 2003
Reviewer: Saminathan Seerangan from NJ,USA
Tyler,
Thanks for your earlier reply.
I did setup HTML DB on my Oracle 9.2.0.4 db which is running on Windows NT.
Everything is working fine.

From here(where I installed HTML DB),

(1) Is it possible to create DAD for other 9.2.0.4 database where we DO NOT have
HTML DB installed.

(2) Is it possible to create DAD(where we provide username,password and connect
String) for another database (version 8.1.7.4).

I like to access all our databases thru SQL Workshop.

Thanks to Tyler & Tom.
Followup:
1)  Creating the DAD is not the problem, that's easy.  All a DAD (Database
Access Descriptor) does is determine what database to connect to and which
credentials to use (plus a few additional attributes).  If the target database
does not have HTML DB installed, you are out of luck.

2)  Not sure I understand what you are asking, but I doubt this will help as I
understand your end goal.  The target database MUST have HTML DB installed.

I know these are probably not the answers you wanted, but it is what it is.  SQL
Workshop is really just an enabler, allowing a developer building a web
application to manage db objects as well.  Is it the replacement for TOAD or EM?
No.  Is it an easy way to manage objects in a database which has HTML DB
installed?  Well, that is subjective, but I think it is.

Tyler
Rating: 3
Client-side scripting in Framework December 09, 2003
Reviewer: David from Lexington, MA
Are there any plans to incorporate client-side scripting into the framework in
future releases? (I know I can write my own now.)

For instance, within the same framework of showing an item conditionally, you
could choose server-side vs. client-side. Of course there are trade-offs to both
but the obvious advantage on the client-side would be that you are not
re-executing the whole page. On the flip-side, complexity increases and page
size increases.

Thoughts?

Thanks,
Dave
Followup:
I'll give my 2cents (tom).

java script is evil.
java script is what makes things work in IE but not netscape, netscape but not
mozilla.

I hope "client side scripting" is not paid much attention to.

just my 2cents.  nothing makes me madder then going to a site and getting an
error message cause I'm not running IE 6.0.2.3.5.2.3.5.432.2 with patch 55 --
but rather that i have th audacity to run mozilla or something else that runs on
something other then windows.
Rating: 3
Followup on client-side scripting December 09, 2003
Reviewer: Dave from Lexington, MA
I hear you.

I'm not very up on all this versioning, etc. but could you run into the same
thing with HTML or is that more standards-based than java script? If so,
client-side scripting could really benefit from standards, like most stuff I
guess.

Actually, I'm a little surprised you are so against it given your interest in
performance/scalability. (Awaiting your rebuke.)

Hey, how about giving the user the ability to choose client vs. server-side on
the fly. Or HTML DB checking the browser version and switching to server-side
when client-side is not supported. Now that would be cool. ;)

Dave
Followup:
Tyler here. My opinion may differ a bit from Tom's, but I get equally annoyed at
scripts that only support one browser.  There are standards for JavaScript which
most browsers support, but there are exceptions.  I personally code to Mozilla
which has 2 benefits.  1) I am not tempted to use IE specific functions or
attributes 2)  The errors returned by the JavaScript console in Mozilla (and
variants such as Firebird) are much better than those returned by IE.

As far as including some client-side options in HTML DB, we've talked about it
several times, but we always reach the same conclusion:  It's a nightmare trying
to support all of the different browsers out there and we'd rather have it work
100% of the time.

We do have conditions available to developers based on which browser the
end-user is running.  You'll find them in the conditions list along with "Item
not Null" etc.

Thanks,
Tyler
Rating: 5
Re:creating DAD for another database December 10, 2003
Reviewer: Saminathan Seerangan from NJ,USA
Tyler,
Thanks for your response. You made it very clear.
(1)HTML DB is not a replacement for TOAD or OEM
(2)The target database MUST have HTML DB installed (Implicitly the tage DB must
be 92.03 or higher). So I acnnot connect to my 8i db.

Thanks again.
Rating: 5
What kind of configuration do I need? December 10, 2003
Reviewer: A Reader
Hi Tom,
I installed HTMLDB on 9.2.0.4, succsessfully, according to the log.  When I get
to localhost I cannot get to admin page as stated in the manual.  Instead, I was
directed to the Apache's home page. I tried to type the admmin's page on the
address and get error, page cannot be dispalied. From here, what kind of
configuration should I do?
Thank you for your help.
Followup:
I would suggest you move these types of support questions to the OTN forum at:
https://forums.oracle.com/forums/forum.jsp?forum=137

I suspect the source of your problems lies in either your DAD configuration or
Apache configuration.  Sounds like you didn't create a DAD for HTML DB yet.
Have you read the installation guide yet?  It talks you through each step of the
process.  It can be found by opening readme.html from the HTML DB source
directory you unzipped.  Then click on "Installation Guide".

Hope this helps,
Tyler
Rating: 4
Let's Bet ! December 15, 2003
Reviewer: Robert from CT
I'm doing research on whether my co should move on to
9iAS/10gAS from 8i/OAS4 or none at all if HTMLDB fulfills our
authentication requirements. (we are in the research info publishing business
via pl/sql web app).

My head is *spinning*...but I am willing to bet my savings right now that the
next reincarnation of HTMLDB is gonna end up in the next AS like
WebDB...Portal...
Anyone wanna bet against me ? :)
Followup:
I will.
Rating: 4
Personalization/Customize Feature December 16, 2003
Reviewer: Robert from CT
Please comment on how a "Personalization/Customize" feature can be built into an
HTMLDB app. Not on the level of sophistication as "OracleAS Personalization"'s
predictive, real-time, data-mining model but somewhat like Yahoo's "pick your
choices"

Thanks
Followup:
Robert,

I see you cross-posted in the forums (again), so I'll just provide a link to
your post and Raj's answer ;)   :

https://forums.oracle.com/forums/thread.jsp?forum=137&thread=220424&tstart=0&tran
ge=15

Tyler
Rating: 5
Hurray December 16, 2003
Reviewer: Matt from UK
I have just installed HTMLDB pre-release, and have had my first play about with
the software.

Can I just say first of all the installation notes, and installation itself were
excellent - HTLMDB flew in first time. No messing.

I have used Portal in the past and the whole experience was quite different -
especially with the bulky architecture.

Excellent job HTMLDB chaps - I look forward to posting many questions in the
near future (on the technet forum of course !!)
Rating: 4
URL in HTML DB? January 06, 2004
Reviewer: Tarun Babu from India
Hi tom,
     Thanks for updating us on HTML DB. But what is this URL signify? I didn't
get it. What are the pls/ask/f?p= etc. Can you elaborate.
http://asktom.oracle.com/pls/ask/f?p=4950:8:3842325187651538321::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:12691263416650,

Followup:
it is just a url, a uniform resource locator.  it contains sufficient data for
me to find what you are asking for.

You don't have to "get it" :)

f is a procedure in my database (part of html/db actually).

It is taking a single input -- that input name is P

P is set to that long colon delimited string that F will parse and process.  The
first number, 4950 is the application (asktom), the second is the page (page 8),
then the rest is just inputs into that page (the big number -- thats the display
id, primary key, of the data you wanted)
Rating: 5
January 06, 2004
Reviewer: A reader
Tom,

You really have lot of patience, though the question that "Tarun Babu" from
India asked has been answered n number of times, you answered it patiently
rather than pointing him to links. :-)
Rating: 4
HTML DB URL January 07, 2004
Reviewer: Tarun from India
Hi Tom,
 Thanks for your answer. Its very clear now. I do not know that its answered n
no of times. Sorry, if I troubled you.
Rating: 5
uninstall htmldb January 15, 2004
Reviewer: Stewart W. Bryson from Atlanta, GA
I downloaded the preview version of htmldb and didnt' realize I had to have all
the Java and XML packages in place before installing. So I got several errors
when I ran the install script. Now that I have corrected all those issues, I
want to reinstall htmldb, but there is functionality in the "ins.sql" script to
detect if it's ben run before and disallow another install. Any ideas?

Thanks Tom.
Followup:
Stewart,

Assuming you have only one version of HTML DB installed in your instance, your
best bet to fully uninstall Oracle HTML DB and start again is to drop the two
schemas created by the HTML DB installation.  You'll want to drop the
FLOWS_FILES schema and also the FLOWS_01XXXX schema.  If you installed Oracle
HTML DB Preview Release 1.4.4.00.32, the second schema name will be
FLOWS_010404.
Rating: 5
re:uninstall htmldb January 16, 2004
Reviewer: Sami from NJ,USA
look at "How to uninstall HTMLDB?" in the same thread.
You just need to drop the two schemas associated with it.  They are:
FLOWS_0104XX  where XX is the minor version number and
FLOWS_FILES.
Rating: 4
mod_plsql in 10g March 10, 2004
Reviewer: Sami
Is mod_plsql available in 10g? if so where can I see the configuration part?
Followup:
yes, it is on the "extra stuff" (companion) cd with apache and htmldb.

Tom
-----------------------------------------------------------------------
What config info were you looking for?  I'd be happy to post any aspect of the
mod_plsql config for 10g, as it has changed, but please be more specific as to
the file, etc.

Thanks,
Tyler
Rating: 4
Do we have DAD configuration as part of HTMLDB? March 13, 2004
Reviewer: Sami
I am specifically looking for DAD configuration in MOD_PLSQL. I have "upload
file" application in 9i and like to do the same in 10g.
Do we have DAD configuration as part of HTMLDB?
Followup:
100%, htmldb sits "behind" mod_plsql

Tom
--------------------------------------------
Sami,

In 10g, the Oracle Universal Installer takes care of Apache configuration for
you.  Here is how it configured my machine:

- A call in httpd.conf to include:
$ORACLE_HOME\Apache\Apache\conf\oracle_apache.conf
- A call in oracle_apache.conf to include:
$ORACLE_HOME\Apache\modplsql\conf\marvel.conf
- The following is everyline from marvel.conf:
Alias /i/ "c:\oracle\product\10.1.0\db_2/marvel/images/"
        <Location /pls/htmldb>
         SetHandler pls_handler
         Order deny,allow
         Allow from all
         AllowOverride None
         PlsqlDatabaseUsername      HTMLDB_PUBLIC_USER
         PlsqlDatabasePassword      @BQmKDACx01VE/f53/s3rN9k=
         PlsqlDatabaseConnectString tmuth-pc:1521:orcl ServiceNameFormat
         PlsqlDefaultPage           htmldb
         PlsqlDocumentTablename     wwv_flow_file_objects$
         PlsqlDocumentPath          docs
         PlsqlDocumentProcedure     wwv_flow_file_manager.process_download
         PlsqlAuthenticationMode    Basic
         PlsqlNLSLanguage           AMERICAN_AMERICA.AL32UTF8
        </Location>

Not sure if this is of any value, but it does show the new format for mod_plsql
dad configuration.  But, as I said before, this is all take care of by the OUI
if you are installing in 10g and you obviously already know how to do it 9i.

Hope this helps,
Tyler
Rating: 4
Page like marvel.oracle.com March 17, 2004
Reviewer: Sami
How do I get page like
http://marvel.oracle.com/
using my 10g DB.

Installed 10g HTMLDB and my marvel.conf looks like below.

Alias /i/ "/u01/app/oracle/product/10gc/marvel/images/"
                <Location /pls/htmldb>
                 SetHandler pls_handler
                 Order deny,allow
                 Allow from all
                 AllowOverride None
                 PlsqlDatabaseUsername      HTMLDB_PUBLIC_USER
                 PlsqlDatabasePassword      @BaaBwwUA/3UDOmKmvRD1lTY=
                 PlsqlDatabaseConnectString 192.168.0.151:1521:ORCL
ServiceNameFormat
                 PlsqlDefaultPage           htmldb
                 PlsqlDocumentTablename     wwv_flow_file_objects$
                 PlsqlDocumentPath          docs
                 PlsqlDocumentProcedure
wwv_flow_file_manager.process_download
                 PlsqlAuthenticationMode    Basic
                 PlsqlNLSLanguage           AMERICAN_AMERICA.WE8ISO8859P1
                </Location>
~

I like to access my db thru browser. What would be my url. My Pracle HTTP server
is running on port # 7777.

What is the URL to configure DAD (from browser)
Followup:
Sami,

First of all, these specific setup questions should probably be directed to the
OTN forum for HTML DB found at:
http://forums.oracle.com/forums/forum.jsp?forum=137
To answer your first question, you should be able to get to your installation
at:
http://yourservername:7777/pls/htmldb/htmldb
As for your second question, I was not able to find a web interface to create a
DAD in the build of 10G I am running.  It is not a production build, but I
suspect it may not exist anymore. I suspect the dad that was created for you
will work fine.  There is also a file called dads.readme in the
Apache/modplsql/conf directory.  It's very detailed and should answer most if
not all of your questions.  You might also want to read dadtool.readme as it
describes the dadtool app which is used for obfuscating passwords in your dad.

Tyler
Rating: 3
Schema browser March 17, 2004
Reviewer: A reader
Is it possible to expose a graphical interface to the data dictionary in a
schema using HTML DB?

In other words, generate a tree-list of all my tables, views, columns, etc.
Clicking on a column name will take me to a list of all tables having that
column. Clicking on a FK constraint will take me to the refernced PK, etc.

There is a lot of metadata (including COMMENTS on tables/columns) buried inside
the data dictionary that I would like a easy way to expose via  browser.

Is this possible using HTML DB? Thanks
Followup:
Hi Vikas,

I believe your question was answered over on the OTN forum at:
http://forums.oracle.com/forums/thread.jsp?forum=137&thread=233768&tstart=0&trange=15
I will follow-up Scott's answer with more details there.

Thanks,
Tyler
Rating: 4
Thanks, Tyler March 18, 2004
Reviewer: A reader
Thanks, Tyler, I did put a watch & email notification on OTN but didnt get it so
I was not aware that my question had been answered.

I will continue the discussion on the OTN forum, thanks again.
Rating: 5
Oracle HTTP Server Administrator's Guide March 19, 2004
Reviewer: Joel from Columbus, OH USA
The Web interface to directly create/configure a Database Access Descriptor for
the Oracle HTTP Server bundled with 10g is not available.

Documentation for the definition of all of the DAD parameters can be found in
the Oracle HTTP Server Administrator's Guide 10g Release 1.  A link to this
documentation that works for me is:
http://download-west.oracle.com/docs/cd/B12037_01/server.101/b12255/confmods.htm#1014712

Rating: 5
Fantastic thread March 20, 2004
Reviewer: Mike from St. Louis, MO
Tyler:

Much earlier in this thread you wrote:

> We use CVS (cvshome.org) for our source code
> control with various clients, including tortoise (tortoisecvs.org)

I'm sorry for this tangent question but I really value your opinion and Tom's so
I'm wondering if I could ask why you use free CVS server and client tools vs. a
commercial source configuration management (SCM) product?  I'm not judging your
decision to use CVS, rather I wish to learn more from you since we are shopping
around for an SCM tool.  Is your CVS choice predominantly a cost saving choice,
a feature choice, both, other?

Also, do you use the original CVS server or a CVSNT server (as Tortoise
recommends using)?  Thanks for any elaboration you might have time to share.
Followup:
Mike,

Well, I can't take credit for choosing CVS.  I believe this one was Joel
Kallman's decision, and I believe it to be a exceptionally wise one.

One of our key requirements was that we needed a cross-platform solution for the
client.  We have developers running windows, Linux, and OS X, all are supported.
We also wanted to host it on a Solaris machine as none of our windows or Linux
machines were "professionally managed", ie backed up on a regular basis.  CVS
fit well with this, and when our server went from Solaris to RedHat, it was no
big deal.

We also wanted ease of use, as the more trouble something is to use, the less
likely people are to use it.  Tortoise CVS was a great windows client that
integrated seemlessly with the OS.  I also used the WinCVS client to get a
graphical roadmap of changes and associated comments for a file.

CVS was also very scriptable, both on the client and server side.  I set up my
textpad of choice, UltraEdit, such that I could highlight a comment I wrote in
the head of a file and press a function key.  That would call cvs with some
command line parameters to checkin the file and use the text I highlighted as
the comment in CVS.  On the server-side, Joel wrote scripts to export our HTML
DB applications and check them into CVS on a nightly basis.

To sum up the benifits (IMHO):
- Cross platform, both client and server side
- Extremely easy to use on the client side
- Easy to script, both client and server side
- Free.  I don't know how much this played into the decision.  Our team is
pretty frugal (good for the stock price), but I think if there were a product
out there that were significantly better, we would have paid for it.
- You don't need to lock files to use it effectively
- Web interface to show map of files and all changes to them
- Lots of client-side third-party support
- Well documented and tested.  This is not some hot new tool with a bunch of
bugs to work out.  It's been around and it works.

We are using the original CVS server.  I have not used, nor heavily researched
CVSNT.  Since I haven't used I can't say much about.  My knee-jerk reaction is
that the tradional version of CVS is the safe solution and IMHO, does everything
we need and more.  I still use it on a daily basis for any project that involves
more than 1 developer.

Sorry this response went on so long, but I hope you find it useful.

Thanks,
Tyler
Rating: 5
Thank you March 21, 2004
Reviewer: Mike from St. Louis, MO
> Sorry this response went on so long, but
> I hope you find it useful.

Tyler.  I found it VERY useful and helpful and I'm thankful you found time in
your busy schedule to reply.  Thanks for sharing all this great information --
we shall pilot CVS first, I think, given its $0 cost and praise from respected
folks like you.  Thanks again!
Rating: 5
better product March 21, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
> I think if there were a product out there that were significantly better

Guess the Oracle SCM team needs to get on the ball then?

- Mark
Followup:
Mark,

As embarassing as it is, I have never used SCM.  My Oracle knowledge does not
extend much beyond the datbase and HTML DB.  I looked at the specs and a number
of the viewlet demos and it looks pretty good.  I will not reccomend a solution
that I have not tried before, but I am a big fan of products built on the
database.  This also has a huge advantage over CVS in that when you back your
database, you also back up your source code.  Another compelling advantage is
that it is integrated with JDeveloper.

I don't want this thread to spiral into an SCN discussion forum, so I would
reccomend that you check out other peoples experience on the OTN forums at:
http://forums.oracle.com/forums/forum.jsp?forum=85
or the product page at:
http://otn.oracle.com/products/repository/index.html
Thanks again for keeping me on my toes Mark,
Tyler
Rating: 5
mod_plsql scalability March 22, 2004
Reviewer: A reader
Hi

You said mod_plsql is probably not suited for a website such as Amazon due to
it's high number of transactions/users. When I used to support a mod_plsql
application a couple of years ago I used to see hundreds of session in the
database (the app had around 500 concurrent users), it seemed to me that
mod_plsql kept an constant session in the database and not able to work with
connection pools. May be this make mod_plsql not scalable?

I guess HTML DB would have same problem (high number of sessions if there are
high number of users), am I correct?
Followup:
that you saw mod_plsql keeping constant sessions shows that mod_plsql in fact
implemented connection pooling!!!.

It is what makes mod_plsql scalable in fact.
Rating: 5
Thanks Tyler March 22, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
Tyler,

I understand about you not wanting this thread to spiral off course, and I agree
with that...  I have used Designer/SCM (as well as Visual SourceSafe), but not
CVS...

Always a pleasure,

- Mark
Rating: 5
where oh where oh where... March 30, 2004
Reviewer: Dave from Paris
Is the book on HTML DB? I need a for dummies like tutorial to slowly help me
move away from pl/sql to this new (and better from what I can see) way of
developing...
Followup:
Dave,

There is no book yet on HTML DB, although a couple are in the works (that I'm
aware of).

In addition to the documentation (which is not a tutorial), there are a number
of HOWTO documents that explain how to accomplish some simple and some
sophisticated tasks in HTML DB.  They are available at:
http://otn.oracle.com/products/database/htmldb/howtos/index.html
There is also an Oracle By Example series for HTML DB, which is quite detailed.
You may wish to check this out at:
http://otn.oracle.com/obe
Just follow the path of Oracle Database -> Oracle Database 10g -> Application
Development.

Hope this helps.

Joel
Rating: 5
Thanks March 31, 2004
Reviewer: A reader
I need a more structured approach though maybe using a case study from start to
end.
Also, I am looking at making a system that allows users to add in fields but in
a way above HTML DB if that makes sense? They would be able to add in company
specific properties to a page but not at the html db level. Would this be
technically feasible? EG via an api call via pl/sql?
Followup:
"A reader",

More information and whitepapers about HTML DB will be made available on the
HTML DB corner on OTN in the upcoming months.

It's unclear what you mean by "add in fields" or "add in company specific
properties to a page."  HTML DB does support region types of PL/SQL, and you can
emit any HTML using the PL/SQL Web Toolkit (e.g., htp.p, htp.prn, etc.).

Joel
Rating: 4
Thanks March 31, 2004
Reviewer: Dave from Paris
Joel
Thanks - I am looking to replace an app that is used by various companies which
allows some customization, eg adding in a few new fields and changing titles and
so on. Would this be possible in html db? I presume as the data is defined
seperately to the presentation layer this may not be the best technology but
wonder if you could elaborate?
Regards
Dave
Followup:
Dave,

Your assessment is quite accurate.  Being a declarative development environment,
HTML DB is well suited for this style of programming where you "drive" a browser
to build your application.  The result is a definition of a page stored in the
database (and the defintion contains things like your reports, your items on the
page, the labels for the items, your templates for the page, regions, reports,
etc).

HTML DB does also contain APIs which enable you to render items on a page
programatically.  If you take a look at the Query By Example page in the SQL
Workshop in HTML DB, this is a query page dynamically built in HTML DB from the
definition of a table.

But HTML DB does not currently publish APIs for you to programatically *define*
the attributes of a page.  That is, you could not build an application yourself
that collects input from the user and then when they hit 'Apply', they've built
a new page in their application.

So...could I build an application in HTML DB that renders items on a page, given
the definiton or preferences from a user?  Yes.  Could I change the region title
at runtime, based upon the preferences of a user?  Yes.  But while you can
conditionally show regions at runtime, you can't dynamically *create* regions at
runtime.  I'm not sure if this covers the "and so on."

Joel
Rating: 4
How to capture checkbox value and store into tables April 01, 2004
Reviewer: Sami
I am trying to use checkbox in my application like below.
could you please show me how I should write "write_info" procedure to store
checkbox value into table. I am having difficult time handling checkboxes.
--------------------------
<html>
<head>
<title>test</title>
</head>
<body>
 <FORM enctype="multipart/form-data" action="plsqlapp/write_info" method="POST">
         <p>Author's Name:<INPUT type="text" name="who">
         <p>Book Name:<INPUT type="text" name="book"><br>
     <p>topic covered:<INPUT type="checkbox" name="rman"><br>
     <p>topic covered:<INPUT type="checkbox" name="performance"><br>
     <p>topic covered:<INPUT type="checkbox" name="network"><br>
     <p>topic covered:<INPUT type="checkbox" name="replication"><br>

         <p><INPUT type="submit">
         </FORM>
 </body>
 </html>
-----------------------
Followup:
you would be getting an array.... (since you can have 0, 1, or in your case 4
values) and you would want to use the same name for all with different VALUES
typically (else you have to define a variable for each -- you are missing a
value to be sent when the checkbox is checked -- you need to add that
regardless.

if you don't use an array -- but keep the names as you have them (add values!!!
so you can tell when they are checked) your prototype could be:

  write_info( who in varchar2, book in varchar2,
              rman in varchar2 default null,
              performance in varchar2 default null,
               .... and so on ... );

strongly encourage you to prefix your parameters with P_ so as to distiguish
them from COLUMNS in tables!!
Rating: 5
How to get the usename from Basic Authentication April 04, 2004
Reviewer: Sami
Dear Tom,

I did setup an Apache Basic Authentication for mod_plsql application and it is
working fine. Now I have to display different page based on the username(like
how metalink works).

How do I capture the username(who logged in) in my mod_plsql application?

It would be really helpful if you provide me code snippet/sample/hint.
Followup:
create procedure p
as
   l_user  varchar2(30) default USER;
begin
   htp.p( 'hello there ' || l_user );
end;
April 05, 2004 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.
Rating: 3
Copying datafiles April 05, 2004
Reviewer: APL from India
Can i copy a datafile from red hatlinux to windows 2000 and make use of that
file?
Followup:
In 10g, yes that is supported using cross platform transportable tablespaces.

In 9i and before, that is not supported.
April 05, 2004 Peter -- Thanks for the question regarding "how to extract BLOB data back out again", version 9.2
You Asked
My BLOB data consists of images files (GIF,JPEG, etc...), word documentation
files, spreadsheet, PDF files etc...
How to I extract these files back out into a directory for viewing?
and we said...
you would write a client application in C, java, VB, whatever -- see
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96591/toc.htm
if you have mod_plsql & apache, you can use plsql and a web browser easily
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:232814159006#6891533553691
Review & Followup

Rating: 2
how to extract blob data back out again March 24, 2004
Reviewer: Peter from Quantico, VA
all of your example does no good for me.  I needed a way or at the least Oracle
need to put "enhancement" on the pl/sql to extract BLOB out on a simple select
statement.  How hard could it be?
Followup:
plsql runs on the *server*
plsql can write to the *server*
plsql cannot in any way shape or form write to the *client*

plsql can deliver to the client easily so the client can write to the client.

(think about it -- what would the implication of the database server being able
to write to just any client -- think about it.  YOU NEED a client application --
the easiest would be your BROWSER as you probably have one of those)
Rating: 1
how to extract BLOB data back out again March 25, 2004
Reviewer: peter from quantico, va
what if users decided to have a quick and dirty way of extracting files from
BLOB back out again.  (Just like a SELECT statement...) either from the client
or the server.
.
.
All that I want is a SIMPLE WAY(nothing more... nothing less...) to extract BLOB
data back out again.  why make it difficult...
.
.
we have a SQLLDR to load MASSIVE DATA into database.  Why not have 'BLOBEXTRACT'
or 'BLOBLOADER' (not to confuse w/ the export/import commands) to extract BLOB
data back out again or to import BLOB back in again
Followup:
blobs are BINARY DATA -- think about it.

what tools does a client have...... hmmmm.... sqlplus.  what does sqlplus do?
binary?  no, it displays text.

sqlldr loads blobs (so there is a blobloader)

sorry, what you are looking for just "doesn't exist".  a client needs to handle
BINARY data.

now, if you want a clob out -- sqlplus does that, binary -- not possible.
Rating: 4
TOAD can be used March 25, 2004
Reviewer: Roger from Sweden
TOAD is a client that handles BLOB data and can create files on the client
computer. It's possible to write a select statement in the SQL Editor, run it
and in the result grid, right click and choose Export Blobs. A file/row will be
created. There also exists other possibilities to create files from BLOB columns
and also loading a file into a BLOB column.
Followup:
TOAD is a client piece of software, MANY of them can do this (as i've been
trying to sort of say)....
Rating: 4
Is this a database issue? April 02, 2004
Reviewer: Susan
Tom,

We store an image files in a long raw column (our driver doesn't support blobs,
we'll be changing soon), view them as JPEGs from our application.  We discovered
that some of the JPEGs were not viewable by our app. I export/imported the data,
and then our app could view them.  After reading this thread I tried viewing the
problem images with TOAD and could (I don't that it's really doing an export b/c
I doing this from a user that doesn't have export rights). I'm not sure now if
this is database problem or if it relates to how we retrieve the image to our
application. We're running a 8.1.7 database. Thanks for any suggestions.
Followup:
what driver doesn't support blobs (available in oracle since version 8.0 a long
long time ago)?.?.?

Are you using "strings" to export the long raws?  if so, you are getting some
character set conversion issues.

what version of the DB are you using?
what is its character set
what is your CLIENTS character set (nls_lang)
Rating: 4
Not sure.. April 02, 2004
Reviewer: Susan from Watertown, MA
Microsoft ODBC for Oracle :(

I'm not sure what you mean by are you using "strings" to export the data, we
upload from the db to app in an asp page that selects the image column and then
uses a GetChunk method to "read" the data from the recordset

our NLS_LANGUAGE setting on the database is AMERICAN
on the client (webserver) it's "NA" (in the registry)

we've got 600 images and we're only seeing this problem on 3 or 4 of them.

I'm guessing it's a problem with our asp method b/c it's happening on both devl
and production, we can't reproduce it yet.  The developer is convinced it's a db
problem b/c deleting the image and reloading the XML file (sometime more than
once) fixed the problem.  Also, as I mentioned export/import "fixed" the
problem.
Followup:
you need to use singe that is "RAW", not a "string" -- strings (varchar's,
char's) are subject to character set conversion.

american is the language, the character set is something more obscure like
US7ASCII...

if just delete + reload "fixed it", highly unlikely to be a "db problem".
something like that leads me more to "application issue"
Rating: 4
sorry April 02, 2004
Reviewer: Susan
forgot the version 8.1.7.3 (we'll be upgrading soon to 9i)
Followup:
also, take the problem images

load them, dump them and compare them -- see what is different.
Rating: 4
sorry still not getting it April 04, 2004
Reviewer: Susan from Watertown, MA
what does singe mean?

<quote>
you need to use singe that is "RAW", not a "string"
</quote>

we're using an ado method getChunk that returns binary data

load the problem files (that sometimes fixes the problem) dump them, compare
them -- compare those the binary files to what?

I think people are pointing at the database b/c reloading or export/import fixes
the problem, without touching the application code they're assuming there's some
type of corruption or storage problem on the db side

Thanks for your help Tom.
Followup:
compare the unloaded files to the files you loaded -- see what is "different"
about them.

there isn't a corruption issue on the DB side, I'll point the figure at the
developed code (and reloading 'works' because the program is loaded into a
different place in memory) OR a configuration issue (eg: not really using a
binary interface after all)

I remember working with a C programmer.  there was this huge static variable
declared but not used anywhere.  When asked "why" he responded -- "it is well
known that compilers have bugs, the solution is to occasionally define really
large variables when you hit a compiler bug.  you define the variable as big as
you need until the problem goes away"

Flabergasted I was -- of course what they had done was just move memory around
enough so as to not have the buffer overruns they had all over the place corrupt
"real" memory in such a way as to crash the program.  Here they were just
corrupting big chunks of static memory.
April 05, 2004 -- Thanks for the question regarding "How to identify connection/server errors when sending email using utl_smtp?", version 9.0.2
You Asked
I have been using utl_smtp to send emails from Oracle.
And I followed your advice and made it run as a job.

Now I have a question about the exception handling
of the utl_smtp package.  Basically I don't want
the email job to be re-tried for 16 times if
the exception is caused by the erroneous use
of utl_smtp such as wrong parameters are passed.
One such example is when the sent-from email address
is bogus -- this is possible since I read it from database
-- utl_smtp would raise an exception like "invalid domain".
In other words, I only want the email
job to be re-tried only when there is a connection issue,
or the smtp server is temporarily jammed, or
other situations that re-tries might overcome.

However, I don't know if it is possible to catch those
exceptions caused by errors (or those that
worth re-trying).  I have the list of all possible
excpetions from utl_smtp, but I cannot figure out how
I can do what I need reliably.  I wonder if you
have any solutions or tips on this issue.

Thanks a lot!  I really appreciate your help.
and we said...
yes, you can catch them.

here is an example straight from the utl_smtp specification:

  *
  * EXAMPLES
  *   Retrieve the home page from
http://www.acme.com/
  *
  *   DECLARE
  *     c utl_smtp.connection;
  *
  *     PROCEDURE send_header(name IN VARCHAR2, header IN VARCHAR2) AS
  *     BEGIN
  *       utl_smtp.write_data(c, name || ': ' || header || utl_tcp.CRLF);
  *     END;
  *
  *   BEGIN
  *     c := utl_smtp.open_connection('smtp-server.acme.com');
  *     utl_smtp.helo(c, 'foo.com');
  *     utl_smtp.mail(c, 'sender@foo.com');
  *     utl_smtp.rcpt(c, 'recipient@foo.com');
  *     utl_smtp.open_data(c);
  *     send_header('From',    '"Sender" <sender@foo.com>');
  *     send_header('To',      '"Recipient" <recipient@foo.com>');
  *     send_header('Subject', 'Hello');
  *     utl_smtp.write_data(c, utl_tcp.CRLF || 'Hello, world!');
  *     utl_smtp.close_data(c);
  *     utl_smtp.quit(c);
  *   EXCEPTION
  *     WHEN utl_smtp.transient_error OR utl_smtp.permanent_error THEN
  *       BEGIN
  *         utl_smtp.quit(c);
  *       EXCEPTION
  *         WHEN utl_smtp.transient_error OR utl_smtp.permanent_error THEN
  *           NULL; -- When the SMTP server is down or unavailable, we don't
  *                 -- have a connection to the server. The quit call will
  *                 -- raise an exception that we can ignore.
  *       END;
  *       raise_application_error(-20000,
  *         'Failed to send mail due to the following error: ' || sqlerrm);
  *   END;
  */
Review & Followup

Rating: 4
Thanks! November 27, 2002
Reviewer: Henry Xie
Tom,

Thanks for your prompt reply!

So I just need to re-raise utl_smtp.transient_error
and suppress utl_smtp.permanent_error, after
logging all of them, inside the email job, so that
the email job will re-try upon transient_error only.
Cool.  Thanks again.
Rating: 5
April 05, 2004
Reviewer: Ahmad from Dubai
Hello Tom,

We are using the utl_smtp for more than 2 years and it was working perfectly.
After we upgraded the database to 9.2.0.2.4. Some emails reach the recipient and
other don't.

My question: is there any trace file for example where we can see the emails
that has been submitted successfully but did not reach the recipient?

Thanks a lot for your help
Best Regards,
Followup:
yes, smtp leaves trace messages in files like '/var/log/messages' by default.

(seriously doubt it has anything to do with utl_smtp if you are not getting any
error messages -- if smtp recieved it, it is out of our hands.  you don't have a
"when others" in there hiding errors do you)
Rating: 5
April 06, 2004
Reviewer: Ahmad from Dubai
Thank you Tom for your quick response,

Actually, the problem was not related to the database.

Recently, we have installed a Spam prevention solution (SPS) and that is
creating some problems.

Kind Regards,
April 05, 2004 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.
Rating: 3
export/import April 05, 2004
Reviewer: Anurag from INDIA
Hi!

I want to migrate my data from Oracle 8i on Windows NT to Linux OS. Can I use
imp/exp? I've a doubt about filesystem. Can U provide solution.

Thanks
Followup:
yes you can use export import, in addition to sqlplus copy, custom dump and
load, database links...
April 05, 2004 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.
Rating: 5
not working accurate January 15, 2004
Reviewer: omer from Pakistan
I agree with ur comments about gaps, but i m also facing the same problem.
In Human resource system I have to find absents day of employee in a month e.g.
Attendance of ali

03-dec-03
04-dec-03
and so on
He has not come  on 1st and 2nd december.
Ur last query does not work in this criteria
if u plz help in this problem? I can use pl/sql.
regards
Followup:
wow -- your keyboard is failing in a horrible way, very important letters
(mostly vowels it seems) are not getting from your keyboard into your computer.
might want to check that out.

just outer join that hr set of records to a set of records that have the dates
of interest to you.  consider (you can generate the set of dates - my "X"
subquery - using any table with 31 or more records of course)

ops$tkyte@ORA9IR2> create table t ( empno number, dt date );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t values ( 1234, '03-dec-2003' );

1 row created.

ops$tkyte@ORA9IR2> insert into t values ( 1234, '04-dec-2003' );

1 row created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> variable x refcursor
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2      l_date date := to_date( '01-dec-2003', 'dd-mon-yyyy' );
  3      l_empno number := 1234;
  4  begin
  5      open :x for
  6
  7      select l_empno, decode( t.dt, null, 'Absent' ), x.dt
  8        from ( select l_date+rownum-1 dt
  9                 from (select 1
 10                         from dual
 11                        group by cube(1,2,3,4,5) )
 12                where rownum <= to_char(last_day(l_date),'dd') ) x,
 13             ( select *
 14                 from t
 15                where empno = l_empno ) t
 16       where x.dt = t.dt (+)
 17        ;
 18  end;
 19  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> print x

       :B1 DECODE DT
---------- ------ ---------
      1234 Absent 01-DEC-03
      1234 Absent 02-DEC-03
      1234        03-DEC-03
      1234        04-DEC-03
      1234 Absent 05-DEC-03
      1234 Absent 06-DEC-03
      1234 Absent 07-DEC-03
      1234 Absent 08-DEC-03
      1234 Absent 09-DEC-03
      1234 Absent 10-DEC-03
      1234 Absent 11-DEC-03
      1234 Absent 12-DEC-03
      1234 Absent 13-DEC-03
      1234 Absent 14-DEC-03
      1234 Absent 15-DEC-03
      1234 Absent 16-DEC-03
      1234 Absent 17-DEC-03
      1234 Absent 18-DEC-03
      1234 Absent 19-DEC-03
      1234 Absent 20-DEC-03
      1234 Absent 21-DEC-03
      1234 Absent 22-DEC-03
      1234 Absent 23-DEC-03
      1234 Absent 24-DEC-03
      1234 Absent 25-DEC-03
      1234 Absent 26-DEC-03
      1234 Absent 27-DEC-03
      1234 Absent 28-DEC-03
      1234 Absent 29-DEC-03
      1234 Absent 30-DEC-03
      1234 Absent 31-DEC-03

31 rows selected.
Rating: 5
trigger January 15, 2004
Reviewer: A reader
If those missing keys are so important, why not insert them in another table IF
you delete/change them. Next time you want to use a new one pick smallest from
that table and delete it, if none generate next.

just another thought
Rating: 5
terrible requirement for sure ... but January 15, 2004
Reviewer: Gabriel from Toronto
Talking about solutions ...

Dave's pl/sql approach, for the mentioned NUMBER(8) column, would use rather too
much memory to load that integer array + local array ==> re-loaded with every
single call + multiple sessions ==> even more memory. It will bring all the
missing keys for sure ... but, is there a need for more than a handful? The
mentioned IOT would help ... although at 99,999,999 rows ... rather large.

Assuming the range [1-100] ... Tom's SQL ...

select t1.x+1
from  (select x from t) t1
where NOT EXISTS ( select null
                   from   (select x from t) t2
                   where  t2.x = t1.x+1 )

should probably be expanded a bit to ...

select t1.x+1
from  (select x from t union select 0 x from dual) t1
where NOT EXISTS ( select null
                   from   (select x from t union select 0 x from dual) t2
                   where  t2.x = t1.x+1 )
and    t1.x < 100

in order to ensure:
1. a return value when t is empty
2. when mising, the lowest value in the range is also returned
3. a value greater than the highest value in the range is not returned

Similar considerations for the analytic query ...

select 'missing '||to_char(x+1)||'..'||to_char(next_x-1)
from   ( select x, lead(x) over ( order by x ) next_x from t )
where  x+1 <> next_x
;

tiny enhancement ...

select 'missing '||to_char(x+1)||'..'||to_char(next_x-1)
from   ( select x, lead(x) over ( order by x ) next_x
         from (select x from t union select 0 x from dual union select 100 x
from dual)
       )
where  x+1 <> next_x
;

Thx.
Rating: 4
0 for No rows in EMP Table April 05, 2004
Reviewer: Rajashekhar GANGA from Mumabi, INDIA
Dear Tom

we have a martrix report with Dept and Job Title as rows and columns

for Dept which does not have a specfic job title emp. there
should be a 0 in the cell.

ex:

Research Dept has no Sales Persons so, that cell should show 0.

i have come with the following solution,
can please suggest a better one than this
....

select b.deptno||'-'||dname dept,a.job,
(select count(x.empno) from emp x where
   deptno=b.deptno and job=a.job) no_of_emp
from
(select distinct job from emp ) a,dept b

Thanks for Reading my Problem.
Raj GANGA
Followup:
well, you cannot dynamically "make up the columns" so joining to the distinct
jobs doesn't make sense.

you can QUERY up the distinct jobs in a query and then dynamically build a sql
statement that looks like this:

select deptno,
       count( decode( job, 'JOB1', empno ) ) JOB1,
       count( decode( job, 'JOB2', empno ) ) JOB2,
....
       count( decode( job, 'JOBn', empno ) ) JOBn
  from emp
 group by deptno;

where JOB1, JOB2, ... JOBn are the names of your jobs.
April 05, 2004 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.
Rating: 4
Copying Varchar2 to CLOB February 12, 2004
Reviewer: Yogesh from Pune, India
I have a table with a VARCHAR2 column. I want to change its datatype to CLOB,
But as this is not possible I added one new column as CLOB. How can I copy data
from varchar column to CLOB ?
Followup:
update set clob_column = varchar2_column;

ops$tkyte@ORA9IR2> alter table emp add ename_clob clob;

Table altered.

ops$tkyte@ORA9IR2> update emp set ename_clob = ename;

14 rows updated.

ops$tkyte@ORA9IR2> alter table emp drop column ename;

Table altered.

ops$tkyte@ORA9IR2> alter table emp rename column ename_clob to ename;

Table altered.

ops$tkyte@ORA9IR2> set linesize 60
ops$tkyte@ORA9IR2> desc emp
 Name                          Null?    Type
 ----------------------------- -------- --------------------
 EMPNO                         NOT NULL NUMBER(4)
 JOB                                    VARCHAR2(9)
 MGR                                    NUMBER(4)
 HIREDATE                               DATE
 SAL                                    NUMBER(7,2)
 COMM                                   NUMBER(7,2)
 DEPTNO                                 NUMBER(2)
 ENAME                                  CLOB
Rating: 4
ORA-03120 February 13, 2004
Reviewer: Yogesh from Pune, India
Thanks Tom, I did the same thing. But when I use to select rows from that table
I was getting

SQL> select * from test;
ERROR:
ORA-03120: two-task conversion routine: integer overflow

My Server is sun Solaris and I was trying this from my XP client.  When I saw
the explanation, it was saying something about heterogeneous system. So I logged
in to my Solaris server using telnet and did the same thing. It worked.

I could not understand, if something is working from server, why it should fail
from client.

Your comments Please.
Followup:
what version of plus do you have on windoze and which version of the database do
you have on sun?
Rating: 3
ORA-03120 February 16, 2004
Reviewer: Yogesh from Pune, India
SQL*Plus: Release 8.1.7.0.0
Oracle8i Enterprise Edition Release 8.1.7.0.0 - 64bit Production
Followup:
are you utf8 in the server.
Rating: 4
UTF8 February 17, 2004
Reviewer: Yogesh from Pune, India
Yes you are right,  NLS_CHARACTERSET is set to UTF8
Rating: 4
Clob retrival and keyword search February 24, 2004
Reviewer: djecal from Richmond,VA
Hi Tom, is it a way to run a keyword search in Clob file data type? After
writing a procedure to load a clob file can we load more than one file? I failed
to retrieve file using dbms_lob.read (). These are my statements:

select dbms_lob.read (state_code,seq_num,resp) from narrative_tb;
ERROR at line 1:
ORA-00904: invalid column name

SQL> select dbms_lob.read(resp,4000,1) from narrative_tb;
select dbms_lob.read(resp,4000,1) from narrative_tb;
                *
ERROR at line 1:
ORA-00904: invalid column name

SQL> select dbms_lob.substr (resp,4000,1) from narrative_tb;
The last query was successful. Can you help me with query in case the file is
over 4000? Also, how would search for keyword?
Thank you
Followup:
No idea what you are trying to do with read there..

select clob_column from table;

will work or select dbms_lob.substr( col_column, for_char, from_char ) from t;
will get a substring of it.

if the file is over 4000, just select clob_column from t; in sqlplus use "set
long 500000" to set the max that plus will display (only applies to sqlplus)
Rating: 5
keyword search on multiple flat files in oracle February 24, 2004
Reviewer: Dams from Orlando,FL
Hello,
we have 1600 files stored on a directory of our server. What will be the easiest
way to do a keyword search through all thes files?
Thanks
Followup:
Oracle Text, just index them.  Then you can use SQL.
http://otn.oracle.com/pls/db92/db92.docindex?remark=homepage#index-TEX
Rating: 5
keyword search February 25, 2004
Reviewer: Dams from Orlando, FL
Hi Tom,
can you help me out with this please?
I wanted to create this index in Oracle InterMedia and got this error message.
I can I solve it?

CREATE INDEX MASTERDB.MASTERDB_IDX
    ON MASTERDB.NARRATIVE_TB(RESP) INDEXTYPE IS CTXSYS.CONTEXT
    PARAMETERS (' DATASTORE CTXSYS.FILE_DATASTORE')
/
                *
ERROR at line 2:
ORA-04045: errors during recompilation/revalidation of CTXSYS.CONTEXT
ORA-29835: ODCIGETINTERFACES routine does not return required interface(s)
Followup:
looks like an upgrade or install issue -- please contact support for assistance
on that one.
Rating: 3
combining file_datastore with user_datastore March 26, 2004
Reviewer: Parag from Munich, Germany
Hi Tom,

We are running full text search based on a file_datastore for the files residing
on the filesystem. Moreover, we have some simple OR conditions with main file
content clause,for search of other attributes like:

select .. from text_files
where contains(file, 'plan') > 0
or filename like 'plan'
or extension = 'plan'

As now the search requirenments grows, we have to add lot of other attributes to
the search criteria(owner, description, categories, keywords ...)

So on a trial basis, I have created a User_datastore for all the other
attributes. New query would look like:

select ..
from text_files, file_attributes
where text_files.objectid = file_attributes.objectid
and (contains(file, 'plan') > 0
    or
    contains(fileAttr, 'plan') > 0
    )

Now the question is, is it possible to combine these two datastores together so
that I can issue only one contains clause?

(I tried appending the File_datastores's 'file' column(on which the index is
created) in the procedure created for User_datastore. Procedure creation went
fine but select statement did not returned any result.
I can provide my test case as well if you want to see, but before that, I just
want to know wheather such thing is possible at all or not?)
)

thanks a lot!
Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:440419921146

Rating: 2
No results by combining two datastores! March 30, 2004
Reviewer: parag from Germany
Hi Tom,

Here is my test code for creating a File_datastore and User_datastore.

conn test/test

drop table file_content;
create table file_content(id int primary key, filePath varchar2(255));

insert into file_content values(1, 'C:\data\abc.html');

-- abc.html is result of conversion from
-- abc.doc to abc.html by ctxhx
-- stored in C:\data directory

begin
 ctx_ddl.drop_preference('FILE_PREFERENCE');
end;
/

begin
 ctx_ddl.create_preference('FILE_PREFERENCE','FILE_DATASTORE');
end;
/

CREATE INDEX IDX_FILE_CONTENT ON file_content(filePath)
INDEXTYPE IS CTXSYS.CONTEXT
parameters ('datastore FILE_PREFERENCE filter ctxsys.null_filter');

drop table tt;

create table tt (id number(10), varchar2_col varchar2(50), dummy_col char(1) );

insert into tt values(1,'jungle', '');
insert into tt values(2, 'forest', '');

commit;

grant select any table to ctxsys;

conn ctxsys/ctxsys

create or replace procedure pr_one(rid in rowid, tlob in out clob) is
begin
     for c1 in ( select varchar2_col, nvl(dummy_col,' ') dummy_col, filepath
               from test.tt, test.file_content
               where test.tt.id = test.file_content.id
               and test.tt.rowid = rid)
     loop
          dbms_lob.writeappend(tlob, length(c1.varchar2_col)+1,
c1.varchar2_col||' ');
          dbms_lob.writeappend(tlob, length(c1.filepath)+1, c1.filepath||' ');
          dbms_lob.writeappend(tlob, length(c1.dummy_col), c1.dummy_col);
     end loop;
end;
/

grant execute on pr_one to test;

conn test/test@parag2

exec ctx_ddl.drop_preference('one_user_datastore');
exec ctx_ddl.drop_preference('one_lexer');

begin
     ctx_ddl.create_preference( 'one_user_datastore', 'user_datastore' );
     ctx_ddl.set_attribute( 'one_user_datastore', 'procedure','pr_one' );
  end;
/

begin
     ctx_ddl.create_preference( 'one_lexer', 'BASIC_LEXER' );
     ctx_ddl.set_attribute( 'one_lexer', 'base_letter', 'YES');
     ctx_ddl.set_attribute( 'one_lexer', 'mixed_case', 'NO' );
end;
/

create index idx_tt on tt(dummy_col)
  indextype is ctxsys.context
  parameters( 'datastore one_user_datastore lexer one_lexer');

-- test File_datastore index individually
-- 'training' is a word appearing in abc.html file
Prompt 'Testing FILE DATASTORE with contains(filepath,'training')'
Select * from file_content
where contains(filepath,'training') > 0;

/*
Result I get
ID FILEPATH
-- -----------------
1  C:\data\abc.html
*/

prompt 'Testing USER_DATASTORE'
Select * from tt
where contains(dummy_col,'jungle') > 0;

/*
  ID VARCHAR2_COL  D
---- ------------- -
   1 jungle
*/

prompt 'Testing Combined FILE_DATASTORE AND USER_DATASTORE'
Select * from tt
where contains(dummy_col,'training') > 0;

/*
no rows selected
*/

So if I try to search a by putting the real path which is in filepath column
then I get the result:

Select * from tt
where contains(dummy_col,'abc') > 0;

   ID VARCHAR2_COL                                       D
----- -------------------------------------------------- -
    1 jungle

Index on File_datastore is not getting used at all!

How can solve this problem?
Followup:
of course not?  why would it?

You indexed the return value of a function.  that function indexed:

          dbms_lob.writeappend(tlob, length(c1.varchar2_col)+1,
c1.varchar2_col||' ');
          dbms_lob.writeappend(tlob, length(c1.filepath)+1, c1.filepath||' ');
          dbms_lob.writeappend(tlob, length(c1.dummy_col), c1.dummy_col);

so, when did the word 'training' ever get returned by that function?  you
indexed the NAME of a file.  You did not index the CONTENTS of the file.

Perhaps, you meant to load the file into the blob for indexing?  like a
dbms_lob.loadfromfile call would do??
Rating: 3
Combined datastore April 02, 2004
Reviewer: Parag from Germany
well, as I see from dbms_lob.loadfromfile which copy the content of the file
into our temp clob. And the content will be returned by the function to check
against contains clasue. This will surely give my results.

but how is the index working then? If I have million number of files then
function will load all the files, concat the content to other attributes and
return the resulting string to check whether it contains the specified term.
This is too expensive, and I guess that's why FILE_DATASTORE is presented so as
to keep the file content-data as indexed one.Any query against this index will
not check the whole content of file but just a indexed data.

What I need is to join the indexed data from file contents to the other
attributes!!
Followup:
it'll be the same

the document is in the file system -- the contents of the document in the index.

whether you do it yourself in the function OR you let text read the document --
the same stuff ends up in the index!! the document -- it is always in the file
system, the index -- that is always in the database.
Rating: 4
combined datastore April 02, 2004
Reviewer: Parag from Germany
ok, just forget the previous comment. I got it what you mean by load the file
before indexing.(function based index..)

But I see one problem here. Normally file is a static data on our platform, that
means once it is uploaded it is used by all the users on the platform. It is not
changed. BUT, metadata of a file(which is also part of index), like categories,
keywords, permissions they change all the time. This means, this function based
index will always be synchronized whenever there is a change in metadata, right?

Do you see any other way of implementing the same?
Followup:
correct -- the index would be syncronized.

and the problem with that would be what exactly? (other than you get the right
answer..... when you query....)
Rating: 3
Datastore April 05, 2004
Reviewer: Parag from Germany
(Wish you a nice holydays!!)
In case you see this,what do you mean by:
(other than you get the right
answer..... when you query....) ??

If the file is too big, synchronization of index could be expensive in that
case. Moreover as I said, we do not expect the file to be changed, but the
metadata.
So my question was that is there a way to index the file content only once and
use it with other metadata in a function?
Followup:
what i mean is

- if you update the data
- and we DID NOT sync the index
- then you get the wrong answer (as text would not find documents it should and
it would find documents it shouldnt)

You are indexing the "row", either the entire row or nothing, in your case.
Rating: 4
Datastore April 05, 2004
Reviewer: Parag from Germany
Thanks a lot Tom. I see now there is no way I can avoid synchronizing of index
every time the metadata changed. But as I already have documents parsed (to
convert them to htmls and that was also a expenssive task) on my hard disk,
hopefully, synchronizing of index should be fast enough.
(for a really big file, god help my users!!)

thanks again!
April 05, 2004 -- 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://download-west.oracle.com/docs/cd/A87860_01/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....)
Rating: 2
How often should I udpate the Histograms February 13, 2004
Reviewer: Ivan from USA
I have a table that contains 8 million rows.  Each night I add roughly 10
thousand new entries.  I know the indexes take care of themselves and are
updated on each new entry. But what about the histograms?  Do they get
automatically updated with the new entries?

Thanks,
Ivan
Followup:
No, you need to gather stats as you change the data over time.  As 10,000 rows
out of 8,000,000 is a proverbial "drop in the bucket" i would suggest you

a) alter table T monitoring (for all tables in your schema)
b) gather STALE stats only (the database will decide when a table is a candidate
for regathering stats, when it has "changed" enough)
Rating: 4
dbms_stats.Gather_table_stats and subpartitions March 03, 2004
Reviewer: KJ from MA, USA
Hi Tom
  This is a ODS database. we have an account_balance table that has around
20Million rows. Everyday on an average 500000 rows get added to this. And every
month partitions will be added. We have this table analyzed on sundays;

2/29/04 - Table was analyzed with
"dbms_stats.gather_table_stats('eis_admin',account_balance,NULL,DBMS_STATS.
AUTO_SAMPLE_SIZE,FALSE,'FOR ALL COLUMNS SIZE AUTO',
NULL,'DEFAULT',TRUE,NULL,NULL,NULL); "

Partition P200403 had no data

03/01/04 & 03/02/04 - March 01 and 02 data was loaded.
The jobs checks for balances for sysdate-2. Therefore on 01 and 02 it was still
looking at Feb partitions.

03/03/04 - Checks for March data that was not analyzed.
The job that updates this table kept waiting. Someone did an analyze on the
account_balance table and the job continued fine.

My question is:
 1. Should we analyze at the subpartition level?

 2.dbms_stats.gather_table_stats - DEFAULT granularity updates just all_tables
and all_tab_partitions. "Analyze" updated all_tab_subpartitions only.
 TABLENAME             LAST_ANALYZED
 all_tables            2/29/04
 all_Tab_partitions    2/29/04
 all_tab_subpartitions 3/03/04

 3. on 02/29/04 when the table was analyzed it had 0 rows for March partition.
So in today's load, when the data for March 01 was being checked, it just hung
up. The dba saw no activity. He did an analyze since the num_rows for Partition
P200403 had 0. The "Analyze" update the all_tab_subpartitions. The job went
through fine after that. It took 4hrs Vs 30mins.Any suggestion why this must
have happened?

Thanks for any suggestion
Followup:
if your queries employ partition elimination and the optimizer can tell based on
the query the exact (single) partition that will be accessed, partition level
stats are a good idea.

sorry -- but you can diagnose "a hang".  After the fact -- when you've restarted
everything, it is really hard.  I cannot say.
Rating: 4
Pls explain March 03, 2004
Reviewer: A reader
b) gather STALE stats only (the database will decide when a table is a candidate

for regathering stats, when it has "changed" enough) .

Do we need to schedule dbms_stats as a daily job?
What are the steps to gather stale stats when table monitoring is on
Any small example will help.
Thanx sir very very much.
Followup:
"need" vs "want" here.  it is up to you.  does your data change rapidly enough?
if so, sure.  if not, still "sure" (gather stale won't do anything).

you just use the gather stale option in the dbms_stats.gather_schema_stats call,
thats all.

search for

dbms_stats  "gather stale"

on this site or check out the supplied packages guide for details.
Rating: 2
Oracle Problem March 04, 2004
Reviewer: Fakhry from Kuwait
How to monitor per user CPU utilization in oracle. For example i have 2 oracle
users i want to monitor per user CPU utilization.Oracle Enterprise manager gives
overall CPU utilization but not per user CPU usage.
Followup:
enable auditing and you'll get session_CPU recorded amongst other bits of data.
Rating: 4
RULE hint solves the problem? March 30, 2004
Reviewer: Jelena from Germany
Hi Tom,
in my case rule based optimizer gives me much better performances then cost, and
i don't know how i should collect statistics to make clear to oracle that it
should use the index :)
So, i have 2 tables,
BENUTZERGRUPPE- 22915 rows       (in german groups)
BENUTZER_BENUTZERGRUPPE- 107865  (in german user in groups)
(this has index on BENUTZERID and 8000 distincts values equally distributed)

Now, i need to see for one users all his groups, so i have a query:
SELECT   t1.name, t1.loeschdatum, t1.id, t1.eigentuemerid
  FROM BENUTZER_BENUTZERGRUPPE t0, BENUTZERGRUPPE t1
  WHERE t0.benutzerid = 9495335
  AND t1.id = t0.benutzergruppeid
  AND t1.LOESCHDATUM IS NULL;

I analyzed both tables (and index) with all possible ways and combinations for
bucket size that came to my mind. And always the explain plan with cost based
gives me full table scan and takes long:

Elapsed: 00:00:00.89

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=12 Card=264 Bytes=92
          40)
   1    0   NESTED LOOPS (Cost=12 Card=264 Bytes=9240)
   2    1     TABLE ACCESS (FULL) OF 'BENUTZERGRUPPE' (Cost=12 Card=21
          921 Bytes=548025)
   3    1     INDEX (UNIQUE SCAN) OF 'BEN_BENGR_PK' (UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      22032  consistent gets

if i put RULE hint, it has (correct and faster) explain plan:
Elapsed: 00:00:00.57

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: RULE
   1    0   NESTED LOOPS
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'BENUTZER_BENUTZERGRUPPE'
   3    2       INDEX (RANGE SCAN) OF 'BEN_BENGRUPPE_BENID_IND' (NON-UNIQUE)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'BENUTZERGRUPPE'
   5    4       INDEX (UNIQUE SCAN)OF 'BENUTZERGRUPPE_PK_ID' (UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         15  consistent gets

So, my first question is if values are equally distributed and i have 8000 out
of 100.000 distinct values, that means that 1 hit with index returns 8% of the
table, i think i read somewhere that oracle uses the index when this ratio is
below 10%. So if it is the case, then even without 'compute statistics on all
indexed columns' it should use the index, right?

Now, my real problem is that this is TOPLINK generated query, and i can't put
any hints or change order of columns. So i have only the option to delete
statistics (which doesn't sound too sophisticated to me) :)
Plus it would affect the rest of the queries, so i would rather make it run
correctly based on statistics.
So, do you think it's possible?
Thanks,
Jelena
Followup:
have you read about optimizer_index_cost_adj and optimizer_index_caching?

search for those terms on this site and give them some consideration.
Rating: 4
optimizer_index_cost_adj = 10 -> FTS March 30, 2004
Reviewer: Jelena from Germany
Hi Tom,
thanks for your answer.
I did try to set those parameters:
optimizer_index_caching is set to 16 ( i think this is the default value).
And optimizer_index_cost_adj does force index scan but only if the value is set
to lower then 10 (but difference in consistence gets is huge!).
It is strange for me that it has to be so low to force index scan? Is it because
the table of 22000 is considered small enough so that full table scan is soo
much cheeper then unique index?

Jelena
Followup:
lets see the AUTOTRACE TRACEONLY EXPLAIN output for that.  I want to see what
the optimizer thinks it is going to get row-wise.
Rating: 4
optimizer_index_cost_adj again March 31, 2004
Reviewer: Jelena from Germany
Ok, now db settings are:
optimizer_index_cost_adj = 25,
optimizer_index_caching = 90

SQL> desc benutzergruppe
 Name                          Null?    Type
 ----------------------------- -------- -------------
 ID                            NOT NULL NUMBER(10)
 NAME                                   VARCHAR2(128)
 EIGENTUEMERID                          NUMBER(10)
 LOESCHDATUM                            DATE

nr of rows from statistics: 22933

SQL> desc benutzer_benutzergruppe
 Name                          Null?    Type
 ----------------------------- -------- -------------
 BENUTZERID                    NOT NULL NUMBER(10)
 BENUTZERGRUPPEID              NOT NULL NUMBER(10)

nr of rows from statistics: 107750
primary key (benutzerid, benutzergruppeid)
and nr of unique benutzerid values 7900.

Both tables analyzed with
DBMS_STATS.GATHER_TABLE_STATS
with option 'FOR ALL INDEXED COLUMNS SIZE AUTO '

Now, if i execute:
SQL> r
  1  SELECT   t1.name, t1.loeschdatum, t1.id, t1.eigentuemerid,t0.benutzerid
  2    FROM BENUTZER_BENUTZERGRUPPE t0, BENUTZERGRUPPE t1
  3    WHERE t0.benutzerid = 9495335
  4    AND t1.id = t0.benutzergruppeid
  5*   AND t1.LOESCHDATUM IS NULL

Elapsed: 00:00:00.83

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=14 Card=247 Bytes=26
          182)
   1    0   NESTED LOOPS (Cost=14 Card=247 Bytes=26182)
   2    1     TABLE ACCESS (FULL) OF 'BENUTZERGRUPPE' (Cost=12 Card=11
          47 Bytes=107818)
   3    1     INDEX (UNIQUE SCAN) OF 'BEN_BENGR_PK' (UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      22032  consistent gets
          0  physical reads
          0  redo size
        752  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)
          4  rows processed

Now, i change:
SQL> alter session set optimizer_index_cost_adj=5;

Session altered.

SQL> SELECT   t1.name, t1.loeschdatum, t1.id, t1.eigentuemerid,t0.benutzerid
FROM BENUTZER_BENUTZERGRUPPE t0, BENUTZERGRUPPE t1
WHERE t0.benutzerid = 9495335  AND t1.id = t0.benutzergruppeid AND
t1.LOESCHDATUM IS NULL;
  2    3
Elapsed: 00:00:00.53

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=14 Card=247 Bytes=26
          182)

   1    0   NESTED LOOPS (Cost=14 Card=247 Bytes=26182)
   2    1     INDEX (RANGE SCAN) OF 'BEN_BENGR_PK' (UNIQUE) (Cost=3 Card=247
Bytes=2964)
   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'BENUTZERGRUPPE' (Cost=2 Card=1
Bytes=94)
   4    3       INDEX (UNIQUE SCAN) OF 'BENUTZERGRUPPE_PK_ID' (UNIQUE)

So the real problem is that this second explain plan (when forced with index
hint or rule for example) is much faster then the first one.
I think that setting optimizer_index_cost_adj to 5 is too low, it would probably
prevent most of the FTS which is maybe not the best idea?
What do you think?
Thanks again!
Jelena
Followup:
I've said in the past -- there are two ways to set these parameters.

way to the right (the default)
way to the left (opposite of what they are)
Rating: 3
Alter table xxx cache? April 05, 2004
Reviewer: Jelena from Germany
Hi Tom,
at some point in time, some of the tables were altered:
alter table ... cache.
Now i tried to alter nocache, and analyzed tables again and it seems to use
correct plan (more via indexes)...
It seems that 'cache' has big effect on explain plan, favourizing full table
scan, is that correct?
Thanks,
Jelena
Followup:
"and analyzed the table again"

alter table cache didn't really affect it, "and analyzed the tables again"
would/could have a dramatic affect on a plan.  (perhaps you analyzed when tables
were small, now they are big, and the optimizer had no idea)

big_table@ORA9IR2> set autotrace traceonly explain
big_table@ORA9IR2> select count(status) from big_table;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1379 Card=1 Bytes=5)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1379 Card=999620
Bytes=4998100)

big_table@ORA9IR2> alter table big_table cache;

Table altered.

big_table@ORA9IR2> select count(status) from big_table x;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1379 Card=1 Bytes=5)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1379 Card=999620
Bytes=4998100)

cost -- did not change at all.
April 05, 2004 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://download-east.oracle.com/docs/cd/A81042_01/DOC/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://download-west.oracle.com/docs/cd/B10501_01/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://download-west.oracle.com/docs/cd/B10501_01/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.
Rating: 3
rman 9i's and 8.1.7 February 24, 2004
Reviewer: hossam seif from egypt
can 9i's version of RMAN backup 8.1.7 databases?
and how ?

PLEASE TELL ME
Followup:
rman is backwards compatible.  the backup is done by the server really, rman is
just a "command line interface", you can do whatever the server could do in its
time.
Rating: 4
Rman restore April 05, 2004
Reviewer: Reader from ind
hi,
   I have gone through with Oracle Docs.
But still some points is not clear to me.

I have a database 9.2 on linux
in archive mode.

I have to backups.

First =>  at mount stage "rman> backup database"
Second => at open stage "rman>backup database"

Now I want to restore and check my both backups.

what steps will be nedded for restore these backups
without shutting down the running database.

just tell me the steps.

Thanks
Followup:
only one step

step one)  reread documentation

you must be reading a different set of docs then I am -- for the steps that you
ask for are in mine.  (chapter 10 specifically in the 9.2 rman users guide)
April 05, 2004 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.
Rating: 5
db_cache_size February 27, 2004
Reviewer: Mike from Germany
Tom,
We are moving from 8i (8.1.7.4) to 9i (9.2.0.4) on HP UNIX.
In 8i..I was looking at the DB_BLOCK_BUFFER and it was 24576 (no of buffers).
db_block_size is 8192
In 9i...I did not specify db_cache_size..and it came out to be 33554432.
Shared_pool_size in both was about 157 megs (database is small..like 3 gigs)
Now according to the definition...In 9i should I set db_cache_size to 24576 *
8192 = 201326592...to get it to be same as 8i..it seems a big number..

Thanks.
Followup:
if you want it to be the same as before, that is what you would do.  it was that
big before after all.
Rating: 4
db_cache_size April 05, 2004
Reviewer: Venkat from India
Dear Tom,

I understand that the advantages of having different block cache size like
2,4,8,16 and 32k are:

- when the db block size for a table needs to be changed, a tablespace of that
block size can be created and the table be transported to that tablespace.

- create TEMP and INDEX tablespaces  with a larger block size.

Am I correct in my understanding and apart from the above , is there other
advantage which I have failed to learn?

Thanks,
Venkat
Followup:
not sure what the TEMP is about (or why)

and be careful with blanket "put indexes in bigger blocksizes" (think
concurrency on big blocks, ask "why does oracle use a 2k blocksize on tpc-c's?)

the sole designed purpose of multiple block size databases was and is for
transporting data from a small block size database (OLTP) to a large blocksize
database (DW).

I would not give them too much consideration as a tuning tool -- not what they
were designed for.
April 05, 2004 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.
Rating: 5
I Tried to Trigger a Trace on User System After Logon February 25, 2004
Reviewer: Doug Wingate from New Orleans, LA USA
Tom,

I wanted to trace the database activity done on behalf of an Import process.  I
wanted to trace the entirety of the session associated with the Import process
rather than start the importation and then use Set_Ev to start the trace in the
middle of the action.  So I created an after-logon trigger on user System and I
immediately disabled it until I was nearly ready to start the Import process.

CREATE OR REPLACE TRIGGER System.Start_Extended_Trace
  AFTER LOGON
  ON System.SCHEMA
  BEGIN
    IF USER = 'SYSTEM' THEN
      EXECUTE IMMEDIATE
        'ALTER SESSION SET max_dump_file_size = UNLIMITED';
      EXECUTE IMMEDIATE
        'ALTER SESSION SET EVENTS ''10046 trace name context forever, level
8''';
    END IF;
  END;
/
ALTER TRIGGER System.Start_Extended_Trace DISABLE;

When I was ready, I enabled the trigger and started the Import process, which
logged on as user System.  After the process had finished, I disabled the
trigger so that other administrators who were testing imports wouldn't also
generate traces.  When I went to get my trace from the dump destination, I
discovered to my horror that every background process was generating a trace
file and had been from the moment I created immediately disabled the trigger.
The only way I could find to stop all those traces was to bounce the database.
My questions are

(1) How did it happen that my after-logon trigger caused tracing in all of those
background processes?  I didn't realize that "logon" was a category applicable
to background processes, for one thing, and for another, I think I would have
supposed that if "logon" is a category applicable to background processes, those
processes were already logged on and therefore wouldn't have triggered tracing.

(2) What could I have done to shut off tracing without bouncing the instance?

Thanks.
Followup:
1) how did you determine it was backgrounds -- they write to a different
directory then where you would have gone.

Where these sessions using a username OTHER THAN system?

I could not reproduce your findings at all in 9204.

2) dbms_system.set_ev to turn it back off.
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6793026818923#7781074432964
Rating: 4
#0 March 02, 2004
Reviewer: Vinnie from ORlando
What does the #0 refer to in the RAW trace file?
Followup:
show me one in context.
Rating: 3
#0 * Trace March 03, 2004
Reviewer: Vinnie from Orlando
Tom,

Here is my Trace file:

UPDATE EVENT_10 SET STATUS = :1 WHERE ID = :2 AND CLASS_NAME = :3 AND TYPE = :4
AND TIME_ID = :5 AND ELAPSED_TIME = :6
END OF STMT
PARSE #14:c=0,e=400,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=2411694204303
BINDS #14:
 bind 0: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac5cdf0 bln=4000 avl=01 flg=05
   value="P"
 bind 1: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac5be10 bln=4000 avl=34 flg=05
   value="TESTID"
 bind 2: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac5ae30 bln=4000 avl=35 flg=05
   value="id.test"
 bind 3: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac59e50 bln=4000 avl=01 flg=05
   value="U"
 bind 4: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=48
offset=0
   bfp=ffffffff7ac5e5f8 bln=22 avl=02 flg=05
   value=10
 bind 5: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=0
offset=24
   bfp=ffffffff7ac5e610 bln=22 avl=03 flg=01
   value=8227
EXEC #14:c=0,e=2179,p=0,cr=3,cu=1,mis=0,r=1,dep=0,og=4,tim=2411694206811
WAIT #14: nam='SQL*Net message to client' ela= 3 p1=1413697536 p2=1 p3=0
WAIT #14: nam='SQL*Net message from client' ela= 1015 p1=1413697536 p2=1 p3=0
XCTEND rlbk=0, rd_only=0
WAIT #0: nam='log file sync' ela= 21890 p1=2460 p2=0 p3=0
WAIT #0: nam='SQL*Net message to client' ela= 3 p1=1413697536 p2=1 p3=0
WAIT #0: nam='SQL*Net message from client' ela= 9924 p1=1413697536 p2=1 p3=0

1. What is a #0, system level cursor?
...
...
BINDS #14:
 bind 0: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac5cdf0 bln=4000 avl=01 flg=05
   value="P"
 bind 1: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac5be10 bln=4000 avl=34 flg=05
   value="TESTID2"
 bind 2: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac5ae30 bln=4000 avl=35 flg=05
   value="id.test"
 bind 3: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac59e50 bln=4000 avl=01 flg=05
   value="U"
 bind 4: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=48
offset=0
   bfp=ffffffff7ac5e5f8 bln=22 avl=02 flg=05
   value=10
 bind 5: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=0
offset=24
   bfp=ffffffff7ac5e610 bln=22 avl=03 flg=01
   value=8227
EXEC #14:c=0,e=131592,p=0,cr=3,cu=2,mis=0,r=1,dep=0,og=4,tim=2411697170266
WAIT #14: nam='SQL*Net message to client' ela= 3 p1=1413697536 p2=1 p3=0
WAIT #14: nam='SQL*Net message from client' ela= 537 p1=1413697536 p2=1 p3=0
XCTEND rlbk=0, rd_only=0

2. Why is the e-time so high with no WAITs?
..
..
bind 0: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac5cdf0 bln=4000 avl=01 flg=05
   value="P"
 bind 1: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac5be10 bln=4000 avl=34 flg=05
   value="TESTID3"
 bind 2: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac5ae30 bln=4000 avl=35 flg=05
   value="id.test"
 bind 3: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=4000
offset=0
   bfp=ffffffff7ac59e50 bln=4000 avl=01 flg=05
   value="U"
 bind 4: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=48
offset=0
   bfp=ffffffff7ac5e5f8 bln=22 avl=02 flg=05
   value=10
 bind 5: dty=2 mxl=22(21) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=0
offset=24
   bfp=ffffffff7ac5e610 bln=22 avl=03 flg=01
   value=8297
WAIT #14: nam='db file sequential read' ela= 1319396 p1=77 p2=14780 p3=1
EXEC #14:c=0,e=1321771,p=1,cr=3,cu=1,mis=0,r=1,dep=0,og=4,tim=2411728135779
WAIT #14: nam='SQL*Net message to client' ela= 3 p1=1413697536 p2=1 p3=0
WAIT #14: nam='SQL*Net message from client' ela= 725 p1=1413697536 p2=1 p3=0
XCTEND rlbk=0, rd_only=0

You can see from the last update we had to wait on a sequential read for
1319396.   WE have others waits for Why db file sequential reads that are very
fast!  Why would some waits for db file sequential reads be very fast & others
very slow?

Thanks again.
Followup:
1) the wait is wait not associated with any cursor.

log file sync = wait during "commit".  it is associated with your

XCTEND rlbk=0, rd_only=0

record (commit) right above.  The next two waits are also not associated with a
cursor, this for example:

WAIT #0: nam='SQL*Net message from client' ela= 9924 p1=1413697536 p2=1 p3=0

just means you "sat there after you committed and did nothing for a while"

2) different loads = different waits.  sometimes disk is fast, sometimes not.
Biggest differences could be due to this file being on a buffered OS file
system.  You could be comparing:

a) physical IO the OS provided you the answer with from its cache versus
b) physical IO the OS actually truly had to goto disk to read versus
c) physical IO under little or no contention versus
d) physical IO under heavy contention
Rating: 3
even in a SAN the location of datasets counts March 04, 2004
Reviewer: idai from Germany
I experienced that even in a SAN Storage System the location of some specific
Oracle Datasets counts ...

We figured out that on our FAStT 900 Storage Subsystem controlfile and redologs
where put on the same spindel in a multivolume LUN. That caused our performance
decrease  enormously
Rating: 4
more WAIT event but very little "ela=" time March 11, 2004
Reviewer: Sami from NJ,USA
Dear Tom,

Thanks for your help & support in the past.

One of the SQL query is getting timed out in the application. We generated
EXTENDED trace, it has almost 9000+ WAIT events but for most of them it has ela=
0.

Could you kindly give some hint what could be wrong here OR where should I look
in for more detail.
The wait event sequence is mostly

1)rdbms ipc reply
2)global cache freelist wait
3)global cache cr request
4)db file sequential read

1)TRACE file content
===================
WAIT #3: nam='global cache freelist wait' ela= 0 p1=0 p2=0 p3=0
WAIT #3: nam='db file sequential read' ela= 0 p1=7 p2=1178443 p3=1
WAIT #3: nam='rdbms ipc reply' ela= 0 p1=33 p2=120 p3=0
WAIT #3: nam='global cache freelist wait' ela= 0 p1=0 p2=0 p3=0
WAIT #3: nam='db file sequential read' ela= 1 p1=7 p2=1178453 p3=1
WAIT #3: nam='rdbms ipc reply' ela= 0 p1=33 p2=120 p3=0
WAIT #3: nam='global cache freelist wait' ela= 0 p1=0 p2=0 p3=0
WAIT #3: nam='global cache cr request' ela= 0 p1=7 p2=1178474 p3=18591
WAIT #3: nam='db file sequential read' ela= 0 p1=7 p2=1178474 p3=1
WAIT #3: nam='rdbms ipc reply' ela= 0 p1=33 p2=120 p3=0
WAIT #3: nam='global cache freelist wait' ela= 0 p1=0 p2=0 p3=0
WAIT #3: nam='global cache cr request' ela= 0 p1=7 p2=1178544 p3=18592
WAIT #3: nam='db file sequential read' ela= 0 p1=7 p2=1178544 p3=1
WAIT #3: nam='rdbms ipc reply' ela= 0 p1=33 p2=120 p3=0

2)TOTAL WAIT count
===================

$ grep WAIT prod1_ora_16860_sql_2.trc|wc -l
    9245

3)STAT
===================

STAT #3 id=1 cnt=1724 pid=0 pos=0 obj=0 op='SORT ORDER BY '
STAT #3 id=2 cnt=1724 pid=1 pos=1 obj=0 op='NESTED LOOPS '
STAT #3 id=3 cnt=1749 pid=2 pos=1 obj=0 op='MERGE JOIN CARTESIAN '
STAT #3 id=4 cnt=2 pid=3 pos=1 obj=951537 op='TABLE ACCESS BY INDEX ROWID
COUNTRIES '
STAT #3 id=5 cnt=2 pid=4 pos=1 obj=1131957 op='INDEX RANGE SCAN '
STAT #3 id=6 cnt=1749 pid=3 pos=2 obj=0 op='SORT JOIN '
STAT #3 id=7 cnt=1748 pid=6 pos=1 obj=5839 op='TABLE ACCESS BY INDEX ROWID
PRIMARYTABLE '
STAT #3 id=8 cnt=1750 pid=7 pos=1 obj=1010125 op='INDEX RANGE SCAN '
STAT #3 id=9 cnt=1724 pid=2 pos=2 obj=951553 op='TABLE ACCESS BY INDEX ROWID
SECTABLE '
STAT #3 id=10 cnt=3496 pid=9 pos=1 obj=951554 op='INDEX UNIQUE SCAN '

4)QUERY
===========

SELECT PRIMARYTABLE.PRIMARYTABLE,
PRIMARYTABLE.USERID,
PRIMARYTABLE.LAST_NAME,
PRIMARYTABLE.FIRST_NAME,
PRIMARYTABLE.COMPANY_NAME,
PRIMARYTABLE.NAME,
PRIMARYTABLE.BUSINESS_COUNTRY_ID,
PRIMARYTABLE.CAMLEVELID,
PRIMARYTABLE.SEARCH_LAST_NAME,
PRIMARYTABLE.SEARCH_FIRST_NAME,
PRIMARYTABLE.SEARCH_COMPANY_NAME,
PRIMARYTABLE.REGION,PRIMARYTABLE.PROCESSED_BY,
SECTABLE.COMPANYINCCOUNTRY,
SECTABLE.USERSTATUSID,
SECTABLE.UPDATEDBY,
SECTABLE.CUSTOMERID,
SECTABLE.LASTUPDATEDATE,
countries.COUNTRYNAME
FROM     PRIMARYTABLE,
    SECTABLE,
    countries
WHERE     PRIMARYTABLE.PRIMARYTABLE=SECTABLE.PRIMARYTABLE
AND     PRIMARYTABLE.BUSINESS_COUNTRY_ID=countries.COUNTRYABBREV
AND     COMPANY_user_category in ('VALUE1','VALUE2')
AND     SECTABLE.USERSTATUSID IN (1,2,3,4,5,6,7,8,9)
AND     PRIMARYTABLE.BUSINESS_COUNTRY_ID IN ('USA')
ORDER BY SECTABLE.LASTUPDATEDATE desc
END OF STMT
Followup:
so, is this a query that stands a chance at all to "perform" -- what is the
application, what TIME does it currently take (in sqlplus, get the app out of
the loop for a minute), what time does it NEED to take, what does the tkprof
report look like (it'll do the adding up of wait events for you, you don't need
to grep).

timed statistics is turned on right?
Rating: 4
elapsed time greater than CPU time April 02, 2004
Reviewer: A reader
Tom,

I have cut off few similiar lines from tkprof output.
In this i see the cpu time is  0.33  and elapsed time is 6.04 . If i am correct,
it should be nearly equal for a better tuned application ... if that is the
case, where could my problem be? .. also disk:1172.

Please let me know, how i can improve on this. Fetch being 9, i feel i should
use, bulk fetch ( as i read from your book) ... I will do that, immd. and please
let me know how still i can make this better..

how can better write this part of sql
And  (  groupnames
  like '%CF6-80C2_fn_rli_000106%'  OR  groupnames like
  '%CF6-80C2_fn_rw_000050%'  OR  groupnames like '%CF6-80C2_fn_rw_000107%'
  OR  groupnames like '%CF6-80E_fn_rw_000089%'  OR  groupnames like
  '%CF6-80E_fn_rwk_000090%'  OR  groupnames like '%GE90_fn_rwdwk_000133%'  OR
   groupnames like '%GE90_rw_000187%'  OR  groupnames like
  '%GP7000_rw_000199%'  OR  groupnames like '%eng_rl_000100%'  OR  groupnames
  like '%eng_rl_000156%'  OR  groupnames like '%eng_rl_000157%'  OR
  groupnames like '%npss_rlidwka_000121%'  OR  groupnames like
  '%npss_rlidwka_000159%'  OR  groupnames like '%sys_rl_000079%'  OR
  groupnames like '%sys_rl_000098%'  )

...
Thanks a lot for your time and considerations ...

select filename
from
 NPSS_TEST_2 where filename in
  ('/aef/.ae.ge.com/npss/npssSys/src/models/GE90/sub_dir1/sub_dir2/sub_dir3_d
  ir3_dir3_dir3_dir3/4/filenames/100000000166610.java',
  '/aef/.ae.ge.com/npss/npssSys/src/models/GE90/sub_dir1/sub_dir2/sub_dir3_di
  r3_dir3_dir3_dir3/4/filenames/100000000038827.java',
  .
  .
  .
  .
  . ### 1000 files were searched
  .
  .
  .
  .

  '/aef/.ae.ge.com/npss/npssSys/src/models/GE90/sub_dir1/sub_dir2/sub_dir3_di
  r3_dir3_dir3_dir3/4/filenames/100000000229069.java',
  '/aef/.ae.ge.com/npss/npssSys/src/models/GE90/sub_dir1/sub_dir2/sub_dir3_di
  r3_dir3_dir3_dir3/4/filenames/100000000003230.java',
  '/aef/.ae.ge.com/npss/npssSys/src/models/GE90/sub_dir1/sub_dir2/sub_dir3_di
  r3_dir3_dir3_dir3/4/filenames/100000000031842.java',
  '/aef/.ae.ge.com/npss/npssSys/src/models/GE90/sub_dir1/sub_dir2/sub_dir3_di
  r3_dir3_dir3_dir3/4/filenames/100000000106922.java') And  (  groupnames
  like '%CF6-80C2_fn_rli_000106%'  OR  groupnames like
  '%CF6-80C2_fn_rw_000050%'  OR  groupnames like '%CF6-80C2_fn_rw_000107%'
  OR  groupnames like '%CF6-80E_fn_rw_000089%'  OR  groupnames like
  '%CF6-80E_fn_rwk_000090%'  OR  groupnames like '%GE90_fn_rwdwk_000133%'  OR
   groupnames like '%GE90_rw_000187%'  OR  groupnames like
  '%GP7000_rw_000199%'  OR  groupnames like '%eng_rl_000100%'  OR  groupnames
  like '%eng_rl_000156%'  OR  groupnames like '%eng_rl_000157%'  OR
  groupnames like '%npss_rlidwka_000121%'  OR  groupnames like
  '%npss_rlidwka_000159%'  OR  groupnames like '%sys_rl_000079%'  OR
  groupnames like '%sys_rl_000098%'  )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.09       0.11          0          0          0           0
Execute      1      0.02       0.01          0          0          0           0
Fetch        9      0.22       5.92       1172       4980          0         996
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       11      0.33       6.04       1172       4980          0         996

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 5

Rows     Row Source Operation
-------  ---------------------------------------------------
    996  CONCATENATION
      1   TABLE ACCESS BY INDEX ROWID NPSS_TEST_2
      2    INDEX UNIQUE SCAN (object id 3245)
      1   TABLE ACCESS BY INDEX ROWID NPSS_TEST_2
      2    INDEX UNIQUE SCAN (object id 3245)
      1   TABLE ACCESS BY INDEX ROWID NPSS_TEST_2
      2    INDEX UNIQUE SCAN (object id 3245)
      1   TABLE ACCESS BY INDEX ROWID NPSS_TEST_2
      2    INDEX UNIQUE SCAN (object id 3245)
      1   TABLE ACCESS BY INDEX ROWID NPSS_TEST_2
      2    INDEX UNIQUE SCAN (object id 3245)
      1   TABLE ACCESS BY INDEX ROWID NPSS_TEST_2
      2    INDEX UNIQUE SCAN (object id 3245)
      1   TABLE ACCESS BY INDEX ROWID NPSS_TEST_2
      2    INDEX UNIQUE SCAN (object id 3245)
      1   TABLE ACCESS BY INDEX ROWID NPSS_TEST_2
      2    INDEX UNIQUE SCAN (object id 3245)
      1   TABLE ACCESS BY INDEX ROWID NPSS_TEST_2
      2    INDEX UNIQUE SCAN (object id 3245)
      1   TABLE ACCESS BY INDEX ROWID NPSS_TEST_2
      2    INDEX UNIQUE SCAN (object id 3245)
      .
      .
      .
      .
      .
      .
      . ## like this nearl 2*1000(appr) times

      2    INDEX UNIQUE SCAN (object id 3245)
      1   TABLE ACCESS BY INDEX ROWID NPSS_TEST_2
      2    INDEX UNIQUE SCAN (object id 3245)
      1   TABLE ACCESS BY INDEX ROWID NPSS_TEST_2
      2    INDEX UNIQUE SCAN (object id 3245)
      1   TABLE ACCESS BY INDEX ROWID NPSS_TEST_2
      2    INDEX UNIQUE SCAN (object id 3245)
      1   TABLE ACCESS BY INDEX ROWID NPSS_TEST_2
      2    INDEX UNIQUE SCAN (object id 3245)
      1   TABLE ACCESS BY INDEX ROWID NPSS_TEST_2
      2    INDEX UNIQUE SCAN (object id 3245)
      1   TABLE ACCESS BY INDEX ROWID NPSS_TEST_2
      2    INDEX UNIQUE SCAN (object id 3245)
      1   TABLE ACCESS BY INDEX ROWID NPSS_TEST_2
      2    INDEX UNIQUE SCAN (object id 3245)
      1   TABLE ACCESS BY INDEX ROWID NPSS_TEST_2
      2    INDEX UNIQUE SCAN (object id 3245)
      1   TABLE ACCESS BY INDEX ROWID NPSS_TEST_2
      2    INDEX UNIQUE SCAN (object id 3245)
Followup:
the relationship of cpu to elapsed is not indicative of tuned vs not tuned.

you could have the best tuned SQL on the planet -- run it on a machine with too
many other concurrent things and you'll wait for CPU

or run it without have the data cached and it'll wait for IO

or <infinite number of things possible here>

in your case it is probably the physical IO that was required to process your
query (run it again and see what happens)

PLEASE, use bind variables!
Rating: 5
April 04, 2004
Reviewer: A reader
Tom,

We are using  java pgm and that as you suggested in your book - we are using
bulkfetch (rowsperfetch) and pepared statement - we are using bind variable, one
thing i noticed it is - after i restart my server this pgm runs for 15 secs,
then 2nd time it takes 8, then 7,6,5, ... it keeps decreasing.

Please let me know, hot to decrease the physical IO, and reasons why everytime i
see a reduce by 1 sec.

Thanks a lot
Followup:
you mean it goes to zero and then negative?

there is some lower bound, what you are seeing is the natural "we got warmed up
and everything is dandy" sort of effect one expects after shutting down and
starting.

Just like your browser takes a long time to start after you reboot --but goes
faster the second time.
Rating: 5
how can i reduce physical i/o April 05, 2004
Reviewer: A reader
Tom,

what could be the best way to decrease the physical IO, given 1 million records,
if i search 1000 records from that, randomly - how can i reduce physical i/o in
any system

Please let me know this, sorry for my ignorance.

thannks
Followup:
for pure randomness - you have to assume the entire table would be processed and
no indexing scheme or physical organization scheme would do anything for you.

indexing techniques are useful for solving some issues.

physical storage (clustering, iot's, hash tables) are useful
April 05, 2004 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://download-east.oracle.com/docs/cd/A81042_01/DOC/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://download-west.oracle.com/docs/cd/B10501_01/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
Rating: 3
Nice February 27, 2004
Reviewer: Michael from PA,USA
Dear Tom,
Can the analytic functions *Lag or Lead * be used to replace
a self join?Is it possible to use them for a self join like
 emp e1,emp e2
  e1.mgr = e2.empno;
Do you have any other simple example for that?
Please do reply.
Bye!
Followup:
Yes and No.

Yes, lag and lead can be used to replace a self join.

No, lag and lead cannot be used to replace a join of EMP to itself to get the
MGR's record attached to EMP record.

lag and lead let you look back/forward for a record.  Here, you want every
record with mgr=5 to be joined to a single record empno=5.  not applicable.
Rating: 4
Anyway to do an analytical function on another one? March 02, 2004
Reviewer: She from US
I want to do analytical function on another one. So I want to do different
partitioning clause on the sum of the sum. I dont think its possible, but just a
question. I cant do the inner query as a inline view becuase a third party tool
that I use doesnt allow inline views.

Ex.

select

sum(
sum( UPF1.QUANTITY)
           over(partition by

           HAP.PERIOD_DATE , UPF1.ISSUER_ID,UPF1.ISSUE_ID,
           decode(UPF1.optiontype, 'C','C','P','P','N'),
           UPF1.gsp_product_key, UPF1.ENTITY_ID) )
)      over(partition by

           HAP.PERIOD_DATE , UPF1.ISSUER_ID,UPF1.ISSUE_ID,
           UPF1.ENTITY_ID) )

from
  UPF UPF1,
  HOLDING_AUDIT_POSITION HAP

WHERE
    ( HAP.MASTER_POSITION_FACT_ID=UPF1.MASTER_POSITION_FACT_ID  )
  AND  ( HAP.REPORTABLE_HOLDING_RULE_ID= 164)
     AND   HAP.issuer_id = 24178
  and HAP.PERIOD_DATE = '5-FEB-2004'
Followup:
confusing -- you are asking (apparently) to sum at a HIGHER level of detail,
something you've already summed to a lower level of detail?

I'm not getting it.  can you provide a concrete example to work from (with less
columns and all - you should only need two partition keys to demonstrate your
intent)
Rating: 3
Anyway to do an analytical function on another one? March 04, 2004
Reviewer: She from She
I have a table with 4 columns with values like below:
Issr Region Product cost
------------------------
213   US   PROD1   20
213   US   PROD1   30
213   US   PROD2   50
213   US   PROD2   -200
213   US   PROD3   -200

213   CAN  PROD1   50
213   CAN  PROD1   150
213   CAN  PROD2   -350

213   UK   PROD2   50
213   UK   PROD3   120

I want the results to look like this
-------------------------------------
Issr Region Product cost  Qty 1    Qty2
------------------------ --------  -----
213   US   PROD1   20      1         0
213   US   PROD1   30      1         0
213   US   PROD2    50     0         0
213   US   PROD2   -200    0         0
213   US   PROD3   -200    1         0

213   CAN  PROD1   -50    1        -50
213   CAN  PROD1   150    1        150
213   CAN  PROD2   -350   0          0

213   UK   PROD2   50     1        50
213   UK   PROD3   120    1       120

How I get Qty 1
----------------
If sum(qty) for a region = -ve and product in (1,2), Qty1 = 0, else Qty1 = 1
(for each row)

select
Issr, region, product,
( case
when
    (product = 1 or product = 2) and
    sum(cost) over(partition by Issr, Region, PRODUCT) > 0
then
    1
when
    product = 3
then
    1
else
0 end
)Qty
from table1

How I would like to get Qty2
----------------------------
I would like to multiple cost * sum(Qty) for a region and if -ve, make Qty = 0,
else Qty = cost

How i would like to achieve the above Qty 2
---------------------------------------------
select
Issr,
region,
product,
case
when
    sum(cost * qty1 ) over(partition by Issr,Region ) > 0 THEN
    cost
else
0
end )Qty2
from table1

So we are trying to apply aggregation at 2 different levels
The 3rd party tool I use, doesnt allow inline views. So I cant use the following
option :

select
(case
when
    sum(cost * Qty1) over(partition by Issr,Region ) > 0
then
    cost
else
    0
end) Qty2
from table1 ,
(
  select Issr, region, product,
  ( case
   when
    (product = 1 or product = 2) and
    sum(cost) over(partition by Issr, Region, PRODUCT) > 0
   then     1
   when  product = 3
   then    1
   else   0
   end
  )Qty1
from table1
) view1
where view1.Issr = table1.Issr
and   view1.Region = table1.Region
and   view1.PRODUCT = table1.PRODUCT
Followup:
i do not see a "qty" to sum anywhere, but you want to look into using CASE
Rating: 3
OK March 19, 2004
Reviewer: Siva from Hyderabad,AP,India
Dear Tom,
I created a function and it is as follows.
SQL>  create function test_func(arg in number,p_sqr out number,p_cube out
number,p_sqrt out
  2   number) return number is
  3   begin
  4    p_sqr := arg*arg;
  5    p_cube := p_sqr*arg;
  6    p_sqrt := sqrt(arg);
  7    return arg;
  8   end;
  9  /

Function created.

SQL> declare
  2   x number;
  3   y number;
  4   z number;
  5  begin
  6   dbms_output.put_line('The square,cube and square root values of'||
  7                        test_func(4,x,y,z)||'are'||x||chr(9)||y||chr(9)||z);
  8  end;
  9  /
The square,cube and square root values of4are16    64    2

PL/SQL procedure successfully completed.

 But when I use bind variables, It comes as

SQL> SQL> var x number

SQL> SQL> var y number

SQL> SQL> var z number

SQL> SQL> set autop on

SQL> select test_func(10,:x,:y,:z) from dual
  2         *
  3  ERROR at line 1:
  4  ORA-06572: Function TEST_FUNC has out arguments
  5  ;
 How to correct this?Can bind variables not used for OUT parameters?
Please do reply.
Bye!
Followup:
no, functions with out parameters cannot be called from SQL.

SQL> exec dbms_output.put_line( test_func(10,:x,:y,:z) );

will work dandy.
Rating: 3
OK April 04, 2004
Reviewer: Catherine from OH,USA
Dear Tom,
How to obtain the row maximum value in a table? For Example if I have
a table like

SQL> select * from t;
       a  b  c
       -------
       1  2  3
       4  5  6
I want to obtain a computed column Row_max as

SQL> select * from t;
      a  b  c  Row_max
      ----------------
      1  2  3    3  <-- Max in the row
      4  5  6    6  <-- Max in the row

 I want to have result set like this.Is it possible in SQL?
Could you please with the query?
Please do reply.
Bye!
Followup:
select t.*, greatest( a,b,c) row_max from t;
Rating: 4
Just learning Analytical functions.. April 05, 2004
Reviewer: Muraalee from Chennai, India
select distinct first_value(sal) over(partition by deptno order by sal desc
nulls last) max_sal,
first_value(empno) over(partition by deptno order by sal desc nulls last)
max_empno,
first_value(deptno) over(partition by deptno order by sal desc nulls last)
max_deptno,
first_value(ename) over(partition by deptno order by sal desc nulls last)
max_name,
first_value(job) over(partition by deptno order by sal desc nulls last) max_job,
first_value(hiredate) over(partition by deptno order by sal desc nulls last)
max_dat
from emp
order by max_deptno
/

SQL> @t2_max_analytical

   MAX_SAL  MAX_EMPNO MAX_DEPTNO MAX_NAME   MAX_JOB    MAX_DAT
---------- ---------- ---------- ---------- ---------- ---------
      5000       7839         10 KING       PRESIDENT  17-NOV-81
      3000       7788         20 FORD       ANALYST    12-DEC-80
      2850       7698         30 BLAKE      MANAGER    01-MAY-81

SQL> l
  1  select * from (
  2     select sal,
  3            empno,
  4            deptno,
  5            ename,
  6            job,
  7            hiredate,
  8            rank() over(partition by deptno order by sal desc nulls last) rk
  9     from emp
 10     order by deptno
 11  )
 12* where rk=1
SQL>
       SAL      EMPNO     DEPTNO ENAME      JOB        HIREDATE          RK
---------- ---------- ---------- ---------- ---------- --------- ----------
      5000       7839         10 KING       PRESIDENT  17-NOV-81          1
      3000       7788         20 SCOTT      ANALYST    19-APR-87          1
      3000       7813         20 MAX GARD   ANALYST    12-DEC-80          1
      3000       7814         20 MAX SQE    ANALYST    12-APR-87          1
      3000       7812         20 MAX DUPE   ANALYST    12-DEC-81          1
      3000       7902         20 FORD       ANALYST    03-DEC-81          1
      2850       7698         30 BLAKE      MANAGER    01-MAY-81          1

7 rows selected.

>> Just observed the hiredate returned by first query has 'Hiredate' which is
not that of 'Ford'. Also the empno for FORD is 7902
>> maybe, this is what someone up the page tried to highlight, but it also
looked like his/her browser problem missing '1' in '81' of hiredate
Followup:
you are correct, would have had to have sorted by sal, rowid or sal,
empno.......

otherwise order by sal returns one of the N records with the highest sal
randomly.
April 05, 2004 -- 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://download-east.oracle.com/docs/cd/A81042_01/DOC/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://download-east.oracle.com/docs/cd/A81042_01/DOC/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?
Rating: 5
record all activites done by DBA role February 06, 2004
Reviewer: Junior from VA, USA
Tom:

"audit connect" only able to get log on/log off time. However, my requriement is
" record all the activities done by DBA role, and ignore any other users." e.g.
If users in DBA role (inclued sys, and system), when they update/delete/modify
table or tablespace .... whatever they did to the database must be record in
details. Can Oracle support that? Thank you for your answer.
Followup:
yup, in 9i we audit sys and sysdba (not before).

You would use the normal audit command (which has lots of documented variations)
to audit these things

AND make sure to use the OS audit trail (for hopefully "obvious" reasons....)
not the DB audit trail :)
Rating: 4
logon and logoff numbers confuse me February 23, 2004
Reviewer: jianhui from DC
Tom,
I turn the AUDIT SESSION on and here is what i get after running a few days over
the weekend (i updated the actual account names for my privacy). Acct5 is the
application owner account and used by the middle tier.
1)Could you explain what LOGOFF, LOGOFF BY CLEAN UP are?
2)Why is LOGON much smaller than LOGOFF? Isnt LOGOFF supposed be smaller than
LOGON, otherwise where does LOGOFF come from if there is no corresponding LOGON?
3)It seems Oracle 9i document isnt clear about this, i checked REFERENCE and DBA
Guide, no detail information was found with regard to ACTION_NAME. If you have a
link for the apporiate document, could you please post here.

SQL> select ACTION_NAME, count(*) from dba_audit_session
  2  where username='ACCT05'
  3  group by action_name;

ACTION_NAME                   COUNT(*)
--------------------------- ----------
LOGOFF                          878651
LOGOFF BY CLEANUP                  474
LOGON                               13
Followup:
the logoff by cleanup indicates there was  an improper exit from the database.
Same behavior exhibited with a ROLLBACK WORK RELEASE, or with the complete
ommission of a COMMIT or ROLLBACK upon "exit".

Now, for the logon/logoff -- can you tell me more about your middle tier.  does
it use a connection pool, if so -- whose, and do you notice that there are
entries in v$process that have no match in v$session (indicating the middle tier
is connecting ONCE and keeping the circut open -- but auth/deauth over and over
for each request)
Rating: 4
about middle tier auth/deauth February 24, 2004
Reviewer: Jianhui from DC
Quote "(indicating the middle tier
is connecting ONCE and keeping the circut open -- but auth/deauth over and over
for each request) "

It's hard to understand. Doesn't auth or deauth close the session? If the
session is closed, how come there is a v$process entry(dedicated server)? If
there is not SERVER process, how to keep the circuit open?

Thanks Tom!
Followup:
don't confuse a connection (physical circut) with a session.

it is a many to many relationship!

A session can use many processes (shared server)
A single process can have many (0, 1, or more) sessions using it.

a true "logon" is a two step

a) connect (physical circut)
b) create session (can be done over and over with the same connection, either
one after the other or many at a time)
Rating: 1
even more confused February 24, 2004
Reviewer: jianhui from DC
Quote
A session can use many processes (shared server)
A single process can have many (0, 1, or more) sessions using it.

Tom,
I can understand one server process can serve more than one session in shared
server mode. How can a session use many server processes in this mode?

My case is dedicated server mode, and my understanding is session vs. server
process is 1 to 1 relationship. (here the session is the middle tier to the
database , not the session of the end user to the middle tier).
Followup:
read
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5671284058977
Rating: 4
That answered half of my question February 25, 2004
Reviewer: Jian from DC
Tom,
My first half question was how a session used more than one process?
Thanks
Followup:
shared server.  you answered it already.

each call to the server can and will use a different process -- hence a single
session can and will use many processes.
Rating: 3
I see what you mean February 25, 2004
Reviewer: A reader
I was wondering how came a session could use more than one server process at the
SAME time.
Followup:
parallel query :) conceptually as well.  although technically the pq slaves are
sessions unto themselves.
Rating: 4
auditing unknown user March 17, 2004
Reviewer: vinodh from India,Chennai
Hi Tom,
i need to identify the unknown user logon  attempts my date.

i actually enabled audit session by user1,user2 .....  for all unknown user .

i was not sure i need to use audit session or audit connect.

kindly could you clarify what's different between these two and how to track the
user after enabling the audit.

Thanks,
Followup:
both will collect the information you want in the same fashion.

dba_audit_trail is how you "track".
Rating: 5
audit connect March 18, 2004
Reviewer: A reader
Tom,
I have a mult-tier applications. The users suppose to login through application,
not by others such as sqlplus.
Can the audit trail (audit connect) show who used sqlplus to login the
databases?
Thank you so much, you are great.

Mike
Followup:
well, why not just make it so the only things that can net into the database are
the middle tier machines?

but short of that, you would capture the 'program' in v$session in a logon
trigger -- but you would have to just be aware that i can fake the program
simply by copying sqlplus to some other application name easily.
Rating: 5
March 18, 2004
Reviewer: A reader
"
but short of that, you would capture the 'program' in v$session in a logon
trigger -- but you would have to just be aware that i can fake the program
simply by copying sqlplus to some other application name easily.
"
We have been inforcing the application login.
But we want to audit if there were any other ways intruders' logining.

"you would capture the 'program' in v$session in a logon
trigger"
Do you mean the functionality built with audit trail? Or I have to write it? I
want to take advantage of exist of it.
Followup:
login trigger on the database -- a DIY solution.
Rating: 2
what's "a DIY solution" March 18, 2004
Reviewer: A reader
Followup:
Do
It
Yourself
Rating: 4
Tracking create session by access for unknow user March 22, 2004
Reviewer: Vinodh from India,Chennai
Hi Tom,
i would like to track the user turnon create session.
iam trying to audit create session by access.

1)how to turn this for specific users.
2)if they succesfully logged in then need to track there info to disable the
user.

Thanks in advance
Followup:
have you read the chapter on auditing in the admin guide?  it has lots of
examples and shows how to turn on/off for specific users.

not sure what point #2 means.
Rating: 4
auditing create session March 23, 2004
Reviewer: Vinodh from India, Chennai
Hi tom,
I will make clear, I read the chapter in admin guide.

i need to audit the create session.

audit create session by access;

but i need to do that for specific users like, tom,tomkyte,kyte.

how to do that.

Kindly could you explain with example.

Thanks in Advance
Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96521/audit.htm#9992
(i asked because I knew the chapter on auditing has an example right in it....)
Rating: 5
Got it March 23, 2004
Reviewer: Vinodh from India, Chennai
Tom,

Thanks i got it.

Audited successfully.

audit created session by user1 by access;

Thanks,
Rating: 5
Still searching for what i needed. March 24, 2004
Reviewer: Vinodh from India, Chennai
Hi tom,
i have enabled audit create session by unknownuser1,2 by access;

now i need to track who are the unknown user logged in.

how to do that.

Kindly could you pls give it with example tom

Thanks,
Followup:
ok, can you tell me what an "unknown user" is exactly????
Rating: 5
audited info March 24, 2004
Reviewer: Vinodh from India,Chennai
Hi Tom,
iam trying to get logged on unknown user by

select * from dba_audit_session
where action_name = 'LOGON';

is that correct way, if not not kindly correct me.

Thanks,
Rating: 5
Logon unknown user March 28, 2004
Reviewer: Vinodh from India, Chennai
Hi Tom ,
Yes unknown user means , currently we are creating user with there emp id. in
oldder day we did with out that some are using some or not . some user are out
from company ex. emp. user apart from ex. emp and current users are all unknown
user. we would like to track who is using the unknown ids.

Hope it was clear now.

thanks,
Followup:
clear as "mud"

"unknown ids"?  seems they are "known".

how could the database know which are "unknown"
Rating: 4
ok let me explain March 29, 2004
Reviewer: vinodh from India, Chennai
Hi Tom,

Ok as you said think that there are 1000 users currently.

500 users active user with emp id attached with there handle.
300 users already left the organisation.

remaining 200 users are unknown users. In that 200 handle may be the old handle
of active users and some may be old handle of ex-emp.

i would like to audit for those 200 user.
1)when they logon.
2)How many times they are logging on.

Thats it.
"Clear like pure water now"
Followup:
nope, not getting it (is anyone else? am i missing something obvious?)
Rating: 5
RE: Is anyone else? March 29, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
Not sure I am following, but here is a stab at it...

Is this close?

1000 users in the user population

500 active users have an id that is known
300 users with a known id leave the organization

You know that the user ids for those 300 individuals are "inactive" or can be
accounted for (excuse the dangling preposition).

You are not sure about the remaining 200 user ids.  It could be that "User 101"
currently has a user id that previously belonged to "User 201" before "User 201"
left the organization?  Are user ids being recycled?  You want to audit for
logon of a set of user ids?

- Mark
Followup:
if that is it -- that is simply "audit username" 200 times for the ones in
question.
Rating: 5
RE: Is anyone else? March 29, 2004
Reviewer: Kalita
Not sure if I got it right but anyway..
There are 1000 users. The known users are
500 active users which were created using the emp-ids
300 users that left the company ( created using emp_id)

Out of the remaining 200 users, it could be
users created without using emp_id but those guys are still working for the
company
or
users created without using emp_id but those guys have left the company.

So, I think he wants to find out those people still using their old ids or ids
that are still being used even after those employees have left the company.

Sounds like all users which are not linked to emp_ids to me.

Is that any good? :-)

Cheers
Followup:
if so, they need to generate a list of users they want to audit and just issue
"audit" on that user.
Rating: 4
how to audit failed logon/off ONLY? April 02, 2004
Reviewer: jian from DC
Tom,
1) Is it possible to audit failed logon/off ONLY? I mean that I dont want to
have successful logon/off data in the audit trail.

2) If 1) is not possible, what are the options to clean the successful logon/off
data in the audit trail since it grows too fast. Now, I'm using scheduled job to
delete them, i.e.

SQL> select what from user_jobs;

WHAT
-----------------------------------------------------------------------
-- This job is used to clean AUDIT TRAIL
begin
 -- remove sussessful actions like LOGOFF, LOGFF BY CLEAN UP
 delete aud$ where returncode=0 AND action# IN (101,102);
 commit;
end;

do you have any better options with better performance? I was thinking about
TRUNCATE but since the system logon/off is pretty active, it's not practical to
lock the table for TRUNCATE.

Thanks!
Followup:
ops$tkyte@ORA9IR2> select count(*) from system.aud$;

  COUNT(*)
----------
         0

ops$tkyte@ORA9IR2> audit connect whenever not successful;

Audit succeeded.

ops$tkyte@ORA9IR2> connect /
Connected.
ops$tkyte@ORA9IR2> select count(*) from system.aud$;

  COUNT(*)
----------
         0

ops$tkyte@ORA9IR2> connect foo/bar
ERROR:
ORA-01017: invalid username/password; logon denied

Warning: You are no longer connected to ORACLE.
ops$tkyte@ORA9IR2> connect bar/foo
ERROR:
ORA-01017: invalid username/password; logon denied

ops$tkyte@ORA9IR2> connect /
Connected.
ops$tkyte@ORA9IR2> select count(*) from system.aud$;

  COUNT(*)
----------
         2
Rating: 5
Audit April 02, 2004
Reviewer: A reader
audit session
or
audit connect ?

what is the difference ? which is better ?

i see only audit session in documented in the admin guide. i can see a subset of
the audit trail in dba_audit_session.
Followup:
they are basically "the same"
Rating: 5
April 05, 2004
Reviewer: A reader
I'm using "audit session".

How to purge audit records older than, say, 3 months ?

Thanks
Followup:
you'd have to delete from aud$, please contact support for any caveats regarding
that.
April 05, 2004 -- 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
Rating: 5
February 10, 2004
Reviewer: Dillip
Tom,

In one database I have LMT and the other DMT.

LMT
---
DATA8K          PERMANENT ONLINE    LOGGING   LOCAL      UNIFORM   AUTO

DMT
---
DATA            PERMANENT ONLINE    LOGGING   DICTIONARY USER      MANUAL

The inserts on LMT takes more time compared to DMT, what is the issue ?

LMT
---
SQL> insert into test1 select * from test;

25953 rows created.

Elapsed: 00:00:08.08

Execution Plan
------------------------------------------------------
   0      INSERT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (FULL) OF 'TEST'

Statistics
------------------------------------------------------
         68  recursive calls
       3127  db block gets
        764  consistent gets
          0  physical reads
    2523660  redo size
        380  bytes sent via SQL*Net to client
        312  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      25953  rows processed

DMT
---
SQL> insert into test1 select * from test;

21911 rows created.

Elapsed: 00:00:03.00

Execution Plan
----------------------------------------------------------
   0      INSERT STATEMENT Optimizer=RULE
   1    0   TABLE ACCESS (FULL) OF 'TEST'

Statistics
----------------------------------------------------------
         86  recursive calls
       1456  db block gets
        613  consistent gets
          0  physical reads
    2133304  redo size
        368  bytes sent via SQL*Net to client
        381  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      21911  rows processed
Followup:
slower disks perhaps, different cache size perhaps, smaller logs leading to
checkpionts perhaps -- dozens of reasons, not of them related to LMT vs DMT of
course.

do it in the same database, a couple of times, average it out:

ops$tkyte@ORA817DEV> create table t1 tablespace testing as select * from
all_objects where 1=0;

Table created.

Elapsed: 00:00:00.27
ops$tkyte@ORA817DEV> create table t2 tablespace users as select * from
all_objects where 1=0;

Table created.

Elapsed: 00:00:00.17
ops$tkyte@ORA817DEV> select tablespace_name, extent_management, allocation_type
  2  from dba_tablespaces
  3  where tablespace_name in ( 'USERS', 'TESTING' )
  4  /

TABLESPACE_NAME                EXTENT_MAN ALLOCATIO
------------------------------ ---------- ---------
TESTING                        DICTIONARY USER
USERS                          LOCAL      UNIFORM

Elapsed: 00:00:00.03
ops$tkyte@ORA817DEV> set timing on
ops$tkyte@ORA817DEV> insert into t1 select * from all_objects;

26886 rows created.

Elapsed: 00:00:09.56
ops$tkyte@ORA817DEV> insert into t2 select * from all_objects;

26886 rows created.

Elapsed: 00:00:09.53
ops$tkyte@ORA817DEV>

(you need to do it a couple of times to remove things like "one got caught in a
checkpoint, one waited for PIO, etc)

But as you can see -- pretty much "the same" there.
Rating: 5
February 12, 2004
Reviewer: Dillip
Thanks Tom. I did a test as you suggested on DMT and all possible LMT
configuration in the same box here is the result.

I created a tablespace with 100MB space for each type.
case insert 23k rows from dba_objects.

Why the db block gets is high in case of auto segment space management ? and
higher recursive calls in the 2nd 3rd iteration in LMT in general ?

Type       Time   Rec Calls   DB bocks Cons Gets Phy Reads
DMT It-1   7.11   1061        1704     108953    0
    It-2   5.31   9           1653     104256    0
    It-3   5.31   9           1664     104260    0

LMT It-1   5.40   349         1930     104371    0
    It-2   5.42   49          1696     104259    0
    It-3   5.38   78          1721     104281    0
(System
Manual)

LMT It-1   5.36   58          1711     104266    0
    It-2   5.36   49          1696     104260    0
    It-3   5.37   78          1719     104280    0
(Uniform
 Manual)

LMT It-1   5.36   58          3022     104357    0
    It-2   5.36   58          3057     104400    0
    It-3   5.38   69          3056     104430    0
(Uniform
 Auto)
Followup:
you can enable sql_trace=true to see the recursive calls (they will be quota
related I believe -- hard to say as I don't see your actual test case so I
cannot tell if you truncated, deleted, whatever -- but the recursive sql is
probably quota checks and you had more extents in your LMTS than DMTS and hence
did the check more often)

the db block gets are part of the automation of ASSM - it is just the "way it
works", it is doing space mgmt in a much more sophisticated fashion to alleviate
the burden of your having to set freelists, freelist groups, pctused and so on.
Rating: 5
February 13, 2004
Reviewer: Dillip
Thanks Tom. I do not know if this info is enough for you to comment but want to
give a try. In a database under load
I see a high enqueue waits (TX).

SQL> select * from v$enqueue_stat order by CUM_WAIT_TIME desc;

   INST_ID EQ TOTAL_REQ# TOTAL_WAIT#  SUCC_REQ# FAILED_REQ# CUM_WAIT_TIME
---------- -- ---------- ----------- ---------- ----------- -------------
         1 TX    3506702      363001    3505068        1629     373683590
         1 FB      33216        1433      33216           0       8839433
         1 HW      45198       14252      45198           0       6281436
         1 SQ     396713      347108     396713           0        538687
         1 US     136839         628     136838           0         11124
         1 CF      47720           0      47720           0             0
         1 CI      16246           0      16246           0             0
         1 CU        570           0        570           0             0
         1 IA          1           0          1           0             0
         1 MR      19480           0      19480           0             0
         1 SR          1           0          1           0             0

and simple inserts are just slowwwww

ADDRESS                HASH      CHILD       EXEC      ETIME/exec
CPUTIME/exec SQL_TEXT
---------------- ---------- ---------- ---------- ---------- ----------
----------------------------------------------------------------
C0000001C8D981B0 2424192392          0     704379 28043.3578     102.29   INSERT
INTO test.table  (       col1, col2,col3, col4, col5, col6, col7, col8, col9),
VALUES (:1, :2, :3, :4, :5, :6, :7, :8, :9)

What could be the issue, could this be because of LMT with auto segment space
management ?
Followup:
not with enqueue waits, no.

You have application logic at fault here.  You have blockers and blockees.

Maybe you have a unique index on that table that people are trying to stuff the
same values into.

Maybe you have foreign keys that are not indexed.

You have simple "blocking/locking" problems.  Look to your application logic.
As you execute that simple insert -- see who is blocking you and what they are
doing.
Rating: 5
February 13, 2004
Reviewer: Dillip
Thanks a lot Tom for the clarification.

SQL> @getseswait 10

       SID EVENT                     P1TEXT                                 P1
P1RAW    SECONDS_IN_WAIT
---------- ------------------------- ------------------------------ ----------
-------- ---------------
        10 enqueue                   name|mode                      1415053318
54580006              18
Rating: 5
assm March 18, 2004
Reviewer: reader
(1) Is there a view to find how many BMBs a segment has been allocated?
(2) What is the wait event asscoiated with contention for BMB that I can look
for in the statspack report for example?

Thanks.
Followup:
dbms_space can do that.

dbms_space.space_usage will count up the number of fs1..fs4 and full blocks.

dbms_space.unused_space will tell you how many blocks are "there" (sum blocks
from dba_extents) and how many are unused.

total_blocks - unused_blocks - sum(fs1..fs4,full blocks) = our blocks used to
manage the space.

"buffer busy waits"
Rating: 5
unformatted blocks March 18, 2004
Reviewer: David
During FTS of a table in ASSM tablespace, does oracle read unformatted blocks
below HWM? Thanks.
Followup:
it reads around them.

there is the lhwm (low high water mark) where it just reads all

then there is the hhwm (high hwm) where it reads "more carefully" if you will,
using the bmb's.
Rating: 4
client wants to keep using RBS April 05, 2004
Reviewer: David from Washington, DC
I'm a DBA supporting government agency (the client).  App vendor (IQ) asked me
to "add a rollback segment called RB01 to the database," because their stored
procedures are hard-coded to use RB01.

Our 9.2.0.3 database was created to use automated space management/undo.  My
inkling is to tell the customer "no, U can't" but since saying "no" to clients
is frowned upon, I want to be sure I have the correct answer.  I am going to
recommend that the developer remove the "set transaction use rollback segment
..." statements from the packages.  To add a rollback segment is a HUGE
configuration change, and I might even have to recreate the datbase(?) in order
to implement it.  Is this correct?
Followup:
you can set undo_suppress_errors instead to placate that application (the use
rollback segment request will succeed and they will be none the wiser)

you would not have to recreate the database - you would just create rollback
segments, add them to the init.ora (or make them public), turn off undo
management a restart the database.
April 05, 2004 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://download-east.oracle.com/docs/cd/A81042_01/DOC/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://download-west.oracle.com/docs/cd/B10501_01/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.
Rating: 5
Oh no February 10, 2004
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
Sorry if you felt any insult,

"i think i've just been insulted"

As you helped me in windows environment so I mentioned.
Rating: 5
recovery when tablespace April 04, 2004
Reviewer: jasdeep from patiala,india
hi tom ,

we have a scenario in which
a new tablespace was created after online physical backup was taken and then
database crashed and we have to recover it. should we create tablespace first or
shouldn't it be created by recover command as all the entries are in
redologs/archive.
thanks in advance
Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96572/recoscenarios.htm#14480
Rating: 5
April 05, 2004
Reviewer: A reader
Rating: 5
buuls eye April 05, 2004
Reviewer: jasdeep from patiala,india
that was bulls eye
 u r master
i cant resist the tempation of asking another question if instead of tablespace
database name is changed and then crash occurs and we have to recover it, can u
plz tell the steps involved.
thanks
Followup:
which database name did you 'change' -- many people call different things the
'database name'

(and you know what is fun, to install oracle on your own desktop and try things
out!!  you'll learn more that way then you will any any any other way)
Rating: 3
db_name April 05, 2004
Reviewer: A reader
Followup:
so, you did a recreate of the controlfiles right?
Rating: 4
yes April 05, 2004
Reviewer: A reader
Rating: 4
just April 06, 2004
Reviewer: A reader
asking just out of curiosity this question
i havent this problem
April 05, 2004 Sateesh Kumar -- Thanks for the question regarding "DECODE or NVL", version 8.1.5
You Asked
Hi Tom,
  The question is whether to use a DECODE or NVL to cmpare a
null value.
Foe ex :
  declare
   name varchar2(10);
  begin
  Update emp
     set emp_name= nvl(name,emp_name);
  end;

I ran the above query with sql_trace and after viewing the
results using tkprof showed no difference.
and we said...
6 one way, 1/2 a dozen the other.

No difference - NVL() says it a little better (it makes more
sense) when reading the query so I'd use that.
Review & Followup

Rating: 5
What about performance of NVL() versus Decode() January 15, 2004
Reviewer: steve from montreal
Could there be a difference from a performance point of view?

I was unpleasantly surprised to find out that in the
call to NVL(expr1, expr2), expr2 seems to always be
evaluated. We were using NVL() in our nightly load to
the warehouse like this:

   insert into table
    select ...
           NVL(field_x, expensive_function()),

field_x is very rarely NULL, but when it is, we need to
correct it by calling a somewhat 'heavy' function.

Here is a test I did to validate what I think is happening:

SQL> create or replace function sleep_now return number  is
 i number;
 begin
   i :=0;
   while i < 1000000
   loop
      i := i + 1;
   end loop;
   return i;
 end;

SQL> create table t (i int);

SQL> insert into t values (1);
SQL> insert into t values (1);
SQL> insert into t values (1);
SQL> commit;

SQL> set timing on

SQL> select * from t;

         I
----------
         1
         1
         1

Elapsed: 00:00:00.01

SQL> select nvl(i,sleep_now()) from t;

NVL(I,SLEEP_NOW_FNN())
----------------------
                     1
                     1
                     1

Elapsed: 00:00:07.58       <==============

It still happens even with a constant!!

SQL> select nvl(9,sleep_now()) from t;

NVL(9,SLEEP_NOW())
------------------
                 9
                 9
                 9

Elapsed: 00:00:08.27   <===============

But decode doesn't seem to have the same problem:

SQL> select decode(i , NULL, sleep_now(), 9) from t;

DECODE(I,NULL,SLEEP_NOW(),9)
----------------------------
                           9
                           9
                           9

Elapsed: 00:00:00.00

SQL> select decode(i , 1, sleep_now(), 9) from t;

DECODE(I,1,SLEEP_NOW(),9)
-------------------------
                  1000000
                  1000000
                  1000000

Elapsed: 00:00:09.03

Am I missing something? Does Nvl() always exaluate expr2?
If so, why?

Steve
Followup:
yes, decode and case both "short circut"

nvl() does not (in this case, nvl against a column is 6 one way -- 1/2 dozen the
other)

drop table t;

create table t
as
select object_name name1, object_name name2
  from all_objects
/
@trace
declare
   l_name varchar2(30);
begin
    update t nvl_null_value set name1 = nvl(l_name,name1);
    rollback;
    update t decode_null_value set name1 = decode(l_name,null,name1,l_name);
    rollback;
    l_name := 'abc';
    update t nvl_not_null_value set name1 = nvl(l_name,name1);
    rollback;
    update t decode_not_null_value set name1 = decode(l_name,null,name1,l_name);
   rollback;
end;
/

and tkprof says:

UPDATE t nvl_null_value set name1 = nvl(:b1,name1)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.44       2.79        223        227      31585       30718
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.44       2.79        223        227      31585       30718
********************************************************************************
UPDATE t decode_null_value set name1 = decode(:b1,null,name1,:b1)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.42       1.47          0        227      31585       30718
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.42       1.47          0        227      31585       30718
********************************************************************************
UPDATE t nvl_not_null_value set name1 = nvl(:b1,name1)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.47       1.78          0        231      31827       30718
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.47       1.79          0        231      31827       30718
********************************************************************************
UPDATE t decode_not_null_value set name1 = decode(:b1,null,name1,:b1)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.47       1.53          0        231      31827       30718
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.47       1.53          0        231      31827       30718
Rating: 3
OK February 24, 2004
Reviewer: Mike from CT,USA
Dear Tom,
Can coalesce function be used instead of nvl()?Do you find
any performance trade off there?
Please do reply.
Followup:
coalesce would be useful to replace nested nvl's, not necessary here.
Rating: 5
http://www.oracledba.co.uk/tips/plsql_nvl_costs.htm April 05, 2004
Reviewer: A reader
Is it not the same issue?I was thinking the to_date in nvl functions is making
the difference and if I remove the date comparisions the results would be
comparable. But it is not.

SQL>create or replace
  2  procedure P1 ( a number, b number, c date, d date, e varchar2, f varchar2)
is
  3    x number;
  4  begin
  5    if a != b or ( a is null and b is not null ) or
  6       ( a is not null and b is null ) then x := 1; end if;
  7  /*
  8    if c != d or ( c is null and d is not null ) or
  9       ( c is not null and d is null ) then x := 1; end if;
 10    if e != f or ( e is null and f is not null ) or
 11       ( e is not null and f is null ) then x := 1; end if;
 12  */
 13  end;
 14  /

Procedure created.

Elapsed: 00:00:00.01
SQL>create or replace
  2  procedure P2 ( a number, b number, c date, d date, e varchar2, f varchar2)
is
  3    x number;
  4  begin
  5    if nvl(a,-1) != nvl(b,-1) then x := 1; end if;
  6  /*
  7    if nvl(c,to_date('0001','yyyy')) != nvl(d,to_date('0001','yyyy')) then x
:= 1; end if;
  8    if nvl(e,' ') != nvl(f,' ' ) then x := 1; end if;
  9  */
 10  end;
 11  /

Procedure created.

Elapsed: 00:00:00.01
SQL>declare
  2    d date := sysdate;
  3    v varchar2(10) := '123123';
  4  begin
  5  for i in 1 .. 1000000 loop
  6    p1(i,i,d,d,v,v);
  7  --  p1(null,null,null,null,null,null);
  8  end loop;
  9  end;
 10  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:04.05
SQL>declare
  2    d date := sysdate;
  3    v varchar2(10) := '123123';
  4  begin
  5  for i in 1 .. 1000000 loop
  6    p2(i,i,d,d,v,v);
  7  --  p2(null,null,null,null,null,null);
  8  end loop;
  9  end;
 10  /

PL/SQL procedure successfully completed.
Elapsed: 00:00:09.01
SQL>
Followup:
calling a function has "overhead"

what you have to look at here is -- you did this 1,000,000 times.  any small
difference in time (we are talking 5 ONE MILLIONTHS of a second, that's small --
but anything small one million times, adds up)....
April 05, 2004 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
Rating: 5
Pivot January 23, 2004
Reviewer: Ratan from IND
Hi Tom,
I have data in table as follows.
SQL> select * from t1;

A          B
---------- ----------
A          ABC
A          PQR
A          XYZ
1          123
1          234
1          345

I am trying for following output.
A    1
ABC    123
XYZ    234
PQR    345

Please help me to write query to getthe above output.
Thanks in advance.
Ratan.
Followup:
well, hmmm -- i guess you'd have to clue us into the "logic" here since it is
definitely not "obvious"
Rating: 5
about last post: Pivot March 29, 2004
Reviewer: Marcio from Brazil
About last post (Ratan - Pivot)
would be this?

ops$t_mp00@MRP920> select * from t;

A B
- ---
A ABC
A PQR
A XYZ
1 123
1 234
1 345

6 rows selected.

ops$t_mp00@MRP920>
ops$t_mp00@MRP920> column rn noprint
ops$t_mp00@MRP920>
ops$t_mp00@MRP920> select rn, max("A") "A", max("1") "1"
  2    from (
  3  select row_number() over (partition by a order by a) rn,
  4         decode(a, 'A', b, null) "A",
  5         decode(a, '1', b, null) "1"
  6    from t
  7         )
  8   group by rn
  9  /

A   1
--- ---
ABC 123
PQR 234
XYZ 345

3 rows selected.
Rating: 5
Pivots are great! April 05, 2004
Reviewer: Michael Wexler from Lexington, MA USA
MS SQL ("Yukon") will have a PIVOT command built into the next version of SQL
Server.
http://builder.com.com/5100-6388-5161769.html?tag=sc
  I am sure Oracle will include one later on.  Til then, Tom's pivot tips are
one of the few things keeping me sane when trying to do advanced analysis in
Oracle.
Followup:
hmm, looks HARDER than the max() trick doesn't it? (to me, yes, yes it does)

doesn't seem to add anything?

I mean, how is:

SELECT *
FROM SVGProperties AS MyDerivedTable
    PIVOT (
        MAX(SVGPropertyValue)
        FOR SVGProperty IN([Line0001], [Line0002], [Line0003], [Line0004],
[Line0005]
    ) AS MyPIVOT
WHERE ImageID in (1,2,3,6)

any easier, more functional or better than the SQL:

SELECT
    ImageID,
    MAX(CASE WHEN SVGProperty='Line0001' THEN SVGPropertyValue END) as Line0001,
    MAX(CASE WHEN SVGProperty='Line0002' THEN SVGPropertyValue END) as Line0002,
    MAX(CASE WHEN SVGProperty='Line0003' THEN SVGPropertyValue END) as Line0003,
    MAX(CASE WHEN SVGProperty='Line0004' THEN SVGPropertyValue END) as Line0004,
    MAX(CASE WHEN SVGProperty='Line0005' THEN SVGPropertyValue END) as Line0005
 FROM SVGProperties
 WHERE ImageID IN(1,2,3,6)
 GROUP BY ImageID

More proprietary perhaps, but not any more efficient, easier...
April 05, 2004 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.
Rating: 4
Timezone April 05, 2004
Reviewer: Suraj from India
The explaination given here solved most of my queries regarding timezone. I will
change the database timezone and bounce back.
Let see the result.

Thanks a lot for helping us and most important is that you are alway prompt in
replying. Keep the work going on....

Still one query is left out.
My requirement is to display the date / or pass the date in the format
'05-APR-04 05.06.12.663999 PM GMT'.

How should I get it?

when I query, I am getting as

select systimestamp from dual;

SYSTIMESTAMP
---------------------------------------------------------------------------
05-APR-04 05.06.12.663999 PM +00:00

Please advice.
Followup:
you'll need a date that is in that time zone first.  +00:00 isn't "gmt" yet.

just like +05:00 isn't EST/EDT/or Indiana.  that is an offset from GMT, to get
GMT, you have to have some GMT times.
April 05, 2004 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
Rating: 2
Numeric Error Inconsistant March 18, 2004
Reviewer: Rob from Winnipeg, MB Canada
I have created this function that given 2 locations (centroids in decimal) it
will return the distance in either miles or kilometers (geomapping distance).
The problem is that it inconsistantly fails with a ora-06512 error:

ERROR at line 4:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "SYS.STANDARD", line 452
ORA-06512: at "DIST", line 17

where line 17 is the return.  I assume that its overflowing on the NUMBER
datatype, but I'm not sure where

Here is the function.

Create or Replace Function dist
  ( from_lat IN number,  --centroid_y
    from_long IN number, --centroid_x
    to_lat IN number, --centroid_y
    to_long IN number, --centroid_x
    units IN char) -- KM or MI
  RETURN number IS
    radians number :=  round(atan2(1,1) * 4/180,6); --atan2(1,1) * 4 = pi
--  convert 4 input values from degrees to radians
    w_from_lat number := from_lat * radians;
    w_from_long number := from_long * radians;
    w_to_lat number := to_lat * radians;
    w_to_long number := to_long * radians;
    r number := case upper(units) when 'KM' then 6378 else 3963 end;
BEGIN

--    calculate the return value in miles or kilometers
    return acos(
                cos(w_from_lat)*cos(w_from_long)*cos(w_to_lat)*cos(w_to_long)
              + cos(w_from_lat)*sin(w_from_long)*cos(w_to_lat)*sin(w_to_long)
              + sin(w_from_lat)*sin(w_to_lat)) * r;
END;

Some Data points
CITY_TOWN                      CENTROID_Y CENTROID_X
"Darlingford"                  49.2063    -98.4395
"Morden"                       49.1925    -98.1085

Is there a spatial MDSYS function that is already created for this?  I cannot
find anything similar.
Followup:

http://otn.oracle.com/oramag/oracle/03-may/o33asktom.html
Rating: 2
Spatial Data March 18, 2004
Reviewer: Rob from Winnipeg MB, Canada
That code does not work,

SQL> alter table CITY_TOWN add (geometry mdsys.sdo_geometry);

Table altered.

SQL> update CITY_TOWN a
  2    set a.geometry =
  3      mdsys.sdo_geometry(2001,null,
  4         mdsys.sdo_point_type(a.centroid_x,
  5                              a.centroid_y,
  6                            null),
  7         null, null)
  8   where a.centroid_y <>0;

3630 rows updated.

SQL> commit;

Commit complete.

SQL> insert into user_sdo_geom_metadata
  2  values ('CITY_TOWN','GEOMETRY',
  3     mdsys.sdo_dim_array(
  4        mdsys.sdo_dim_element
  5        ('X',-180,180,.00005),
  6        mdsys.sdo_dim_element
  7        ('Y',-90,90,.00005)), null)
  8  ;

1 row created.

SQL> commit;

Commit complete.

SQL> create index indx_city_town_geo on CITY_TOWN(geometry) indextype is
mdsys.spatial_index;

Index created.

SQL> select A.CITY_TOWN FROM_CITY, B.CITY_TOWN TO_CITY
  2  from
  3  (select CITY_TOWN, geometry from CITY_TOWN where CITY_TOWN = 'Darlingford')
A,
  4  CITY_TOWN B
  5  where MDSYS.LOCATOR_WITHIN_DISTANCE
  6      (b.geometry, a.geometry,
  7       'distance = 25,unit=mile') = 'TRUE';
select A.CITY_TOWN FROM_CITY, B.CITY_TOWN TO_CITY
*
ERROR at line 1:
ORA-29902: error in executing ODCIIndexStart() routine
ORA-13207: incorrect use of the [No UNIT support without SRID] operator
ORA-06512: at "MDSYS.SDO_INDEX_METHOD_9I", line 368
ORA-06512: at line 1

You cannot use the units parameter without specifying an SRID in the insert for
the metadata.  Removing the Units does not return accurate results (it returns
everything).

Oh ya, its unit not units.
Followup:
what do you mean by

"Oh ya, its unit not units."
Rating: 1
Units March 19, 2004
Reviewer: Rob H from Winnipeg MB, Canada
In the example shown by Clarke Colombo there were some syntax errors.

(Z2.AVG_GEOM, TA.GEOMETRY,
     'distance = '||:dist||',units=mile') <---- here
                              = 'TRUE';

"Units" is incorrect syntax, it's "unit".  Plus the example will not work
because in the meta data insert:

insert into user_sdo_geom_metadata
values ('B','GEOMETRY'
   mdsys.sdo_dim_array(
      mdsys.sdo_dim_element
      ('X',-180,180,.00005),
      mdsys.sdo_dim_element
      ('Y',-90,90,.00005)), null);

the last null should be a SRID value if you want to use the unit parameter with
MDSYS.LOCATOR_WITHIN_DISTANCE.

Thank you for pointing me in the right direction.  I will continue to follow it.
 I would appreciate it if Clark could do some more examples (yes I read the
docs, that's how I found out the example was not working and why).

Regardless, my inital problem of Numeric error was resolved due to the fact the
the formula from my initial post created a value witihn the acos() of
1.00000000000000000001.

Executing:
SQL> select acos(1.00000000000000000001) from dual;
select acos(1.00000000000000000001) from dual
             *
ERROR at line 1:
ORA-01428: argument '1.00000000000000000001' is out of range

So the numeric error was derived from an out of range formula. It shouldn't be
possible that the formula returns a value >1 however I'm looking into it.
Followup:
I asked Clarke about it and he says:

Tom,

I know what happened. The original code was based on Locator in Oracle8i, which
did not require SRIDs and supported the UNITS parameter.

Below are the corrections for Oracle9i. In Oracle9i, LOCATOR_WITHIN_DISTANCE is
now synonymous with SDO_WITHIN_DISTANCE, which does require a SRID and UNITS
becomes UNIT.

Since the data is latitude/longitude, I used one of the more common geodetic
coordinates systems. The 8307 SRID specifies the World Geodetic System 1984. If
WGS84 was not the correct coordinate system the user could find the correct SRID
by querying the mdsys.cs_srs table.

alter table b add
(geometry mdsys.sdo_geometry);

update b a
 set a.geometry =
   mdsys.sdo_geometry(2001,8307,
      mdsys.sdo_point_type(a.longitude,
                         a.latitude,
                         null),
      null, null)
where latitude is not null;

insert into user_sdo_geom_metadata
values ('B','GEOMETRY'
  mdsys.sdo_dim_array(
     mdsys.sdo_dim_element
     ('X',-180,180,.005),
     mdsys.sdo_dim_element
     ('Y',-90,90,.005)), 8307);

create index b_sidx on b(geometry)
 indextype is mdsys.spatial_index;

select substr(Z2.ZIPP, 1, 7) ZIP
 from A PT, B TA, C Z2
where (PT.REGION || PT.AREA ||
      PT.DISTRICT || PT.LOCATION) = :str
  and PT.LOCATION_ID = TA.LOCATION_ID
  AND MDSYS.LOCATOR_WITHIN_DISTANCE
   (Z2.AVG_GEOM, TA.GEOMETRY,
    'distance = '||:dist||',unit=mile')
                             = 'TRUE';
Rating: 3
Whats wrong with this? April 05, 2004
Reviewer: Hash from Pakistan
Hi Tom,
Look at this code:
PACKAGE test IS
    PROCEDURE HOME;
END;

PACKAGE BODY TEST IS

PROCEDURE FORM1(N VARCHAR2 ) IS
BEGIN
    HTP.P('HELLO '||N);
END;
PROCEDURE HOME IS
BEGIN
    HTP.FORMOPEN('test.form1','POST');
    HTP.FORMTEXT('N');
    HTP.FORMSUBMIT;
    HTP.FORMCLOSE;
END;

END;

now i enter the url:
http://hashmat/pls/mail/test.home
the page is displayed with a text box and a button, i enter any value and press
the submit button.the url chages to
http://hashmat/pls/mail/test.form1
and the browser says 'THE PAGE CANNOT BE DISPLAYED'
Now if i make the form1 procedure public, again it does the same but if i
refresh the browser it displays
HELLO WHATEVER VALUE.
can you tell me whats wrong
thanks
Followup:
umm, is form1 calleble at the sqlplus prompt?

no, it is not published in the spec.

put it in the spec if you want to call it from outside of the package body..
Rating: 5
Cursor opened for LONG datatype gives ORA-6502 April 06, 2004
Reviewer: Anirudh from NJ, USA
Hi Tom,
I have a cursor which select a long column but when I open it and loop through,
after looping few times it throws numeric or value error.

code goes:

set long 500000
set serveroutput on
DECLARE
v_id_prod number;
BEGIN
FOR i IN (SELECT pa.pie_id_prod p_id_prod,pda.document document,pda.doc_class_cd
doc_class_cd
          FROM product_document_au pda,pie_association pa
          WHERE pa.hed_id_prod=pda.id_prod)
LOOP
--DELETE FROM product_document_au where id_prod IN (select pie_id_prod from
pie_association);
--commit;
--INSERT INTO
product_document_au(id_prod,DOC_CLASS_CD,document,create_user,row_create_date)
--VALUES(c1rec.pie_id_prod,c2rec.doc_class_cd,c2rec.document,'PIEMKTUPD',sysdate)
;
--commit;
v_id_prod:=i.p_id_prod;
dbms_output.put_line(i.p_id_prod);
END LOOP;
exception
when others then
         dbms_output.put_line('pie_id_prod '||v_id_prod||'err'||sqlerrm);
END;
/
when run:
pie_id_prod 358621errORA-06502: PL/SQL: numeric or value error

PL/SQL procedure successfully completed.

------------
the cursor is giving problems with the long data type
pda.document in this case
please help
April 05, 2004 -- 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)
Rating: 5
Return x Returning April 01, 2004
Reviewer: Marcio from Brazil
Are They same?
I tried either SQL and pl/sql
Consider:

ops$t_mp00@MRP920> create table t1 ( x int );

Table created.

ops$t_mp00@MRP920> create sequence s;

Sequence created.

ops$t_mp00@MRP920>
ops$t_mp00@MRP920> var seq number;
ops$t_mp00@MRP920>
ops$t_mp00@MRP920> set autoprint on
ops$t_mp00@MRP920>
ops$t_mp00@MRP920> insert into t1 values ( s.nextval ) returning x into :seq;

1 row created.

ops$t_mp00@MRP920>
ops$t_mp00@MRP920> insert into t1 values ( s.nextval ) return x into :seq;

1 row created.

ops$t_mp00@MRP920>
ops$t_mp00@MRP920>
ops$t_mp00@MRP920> declare
  2          l_seq   number;
  3  begin
  4          insert into t1 values ( s.nextval ) returning x into l_seq;
  5          dbms_output.put_line( 'Returning - '|| l_seq );
  6          insert into t1 values ( s.nextval ) return x into l_seq;
  7          dbms_output.put_line( 'Return - '|| l_seq );
  8  end;
  9  /
Returning - 3
Return - 4

PL/SQL procedure successfully completed.
Followup:
looks like it.
Rating: 4
database crash April 02, 2004
Reviewer: Calo from NY,NY,USA
Hi Tom,
Iam wondering what might be causing mydatabase to crash on an update command. I
made changes to the data through dba studio and when I clicked apply it hangs. I
tried to make the change using an update command in the sql prompt and it hangs
when I push enter.

Please help!!
Followup:
umm, that is hardly a database crash.

sounds more like you're locking yourself.

perhaps you'd care to share more details (like "i'm updating a million row
table..." or something)
Rating: 4
database crash April 02, 2004
Reviewer: A reader
Tom,
I am actually updating 3 rows at the most.
After failing, I tried to update one row and had the same result.
Followup:
well -- it sounds like you are locked up with yourself (eg: you tried in a
window and it is still lurking around back there)

do you know how to detect blockers and lockers?  have you looked at v$lock and
v$session.
Rating: 4
database crash April 05, 2004
Reviewer: A reader
I was able to issue a desc command on the v$lock view, but when I issue a select
statement on the same view, the query hangs indefinitely. How can I correct this
problem using v$lock and v$session as you were trying to say?

Thank you!
Followup:
time for you to contact support and see if you can sort it out.
April 05, 2004 Mohit -- Thanks for the question regarding "Prioritizing processes differently in MTS ", version 8.1.x
You Asked
Hi Tom,

I would like to know if there is any way we can assign "priorities" to processes
in an Oracle MTS environment so that the Dispatchers allocate more time and
resources to those processes as opposed to others they may have.

I know that the MTS architecture is mainly meant to be used in an online
environment with a large user base... with the underlying assumption that all
processes are equal, but I was wondering if it is possible to specify some
processes as being more equal than others (kind of "First among Equals"!)

Many thanks for the time you spend resolving all the queries here. I found out
about your site quite by accident and have been a regular fan since!

Bext regards,

Mohit.
and we said...
I don't think you want to prioritize PROCESSES -- but you might want to
prioritize sessions.

See
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76956/part6.htm#435958
prioritizing a process, especially in MTS, would be bad.  From call to call
you'll get different shared servers.  So one time you would be "high priority",
one time "low" -- for the same session.
Review & Followup

Rating: 4
A followup.... February 26, 2002
Reviewer: Mohit Dubey from UK
Many thanks for the prompt and lucid reply, Tom!

The reason why I asked this question was that we are hitting the wall with
maximum allowable sessions on our 8.1.7 db here (the system is an online/batch
mix, leaning a bit towards the batch side) and I do not want to keep on
increasing the SESSIONS/PROCESSES ad infinitum. All processes log in to the same
schema.

I suggested we go in for an MTS/dedicated hybrid where batch processes get in
via the dedicated connect string and the online via the MTS one.

However, some bright minds want to put EVERYTHING on MTS and then assign a
higher priority to the batch processes.

As I see it, the "resource" approach would mean associating all new connections
to a "normal" group, then running a script to get sid/ser# of all running batch
processes and switching them to a high priority group (if they are not in
there).

My gut feel says to go with the hybrid approach rather tahn mess about with
resource groups. Comments?

The Oracle manual (rightly, in my opinion) cautions against using MTS for
batch-type processes. Based on your experience, what is the avg. performance hit
if batch-processes are run in MTS mode as against dedicated?

Thanks once again.

Mohit.
Followup:
batch processes are NOT suitable for MTS (shared server).  They will tend to
monopolized the SHARED server (they are not very good at sharing, batch
processes are GREEDY).

The hybrid approach is the CORRECT answer, couple with resource mgmt if you want
finer grained control.

All you need is a couple of long running statements to take over and monopolize
a SHARED server and your online people are hosed.

If you have my book -- I go into this in some detail (it's one of my first
examples of how to fail -- use MTS for things that take a long time to run).
That should squash all discussion.  It is not performance of statements in as
much as starvation of end users -- they, the humans, get no resources whilst the
batch processes consume them all.  Humans get mad when this happens.
Rating: 5
some more explanation please.. February 26, 2002
Reviewer: Yogeeraj from Mauritius
hello
So is it true that in general, for systems similar to what our friend, Mohit, is
describing here we should implement a hybrid MTS/Dedicated configuration?

But, by going to dedicated server processes, each user will allocate their own
memory on the server instead of sharing memory. This increased memory
consumption can lead to paging which can be a performance killer. In general,
what  are the typical resource management parameters for finer grained control?

By the way, the shortcut to your "new book" does not seem to be working. (
http://asktom.oracle.com/pls/ask/z?p_url=http%3A%2F%2Fwww.wrox.com%2FACON11.asp%3FWROXEMPTOKEN%3D181202ZLbcpHDVSYHk38gI4FRM%26ISBN%3D186100690x&p_cat=BEG_ORACLE&p_company=10
Best Regards
Yogeeraj
Followup:
Yes, batch processes that connect to the database, use the connection pretty
much 100% of the time and disconnect should get a dedicated server (they are in
fact going to make use of that resource heavily, they won't "share" nicely).

End user processes (clients) that connect to the database, use the connection
for brief periods with long periods of inactivity (as you might see in any
application with end user interaction -- its called "think time") can use MTS
nicely.  They "share" the resource.

Now, as for the MEMORY.... Thats interesting.  First -- I am saying only that
BATCH PROCESSES get a dedicated server (not that EVERYONE gets one).  I would
envision on a system like this you have 1,000 "humans" connected and many 10
batch processes (i mean -- how many batches do you run typically -- not
thousands, but rather 10's).  So, we are talking about have a very few dedicated
servers, some shared servers and 1,000s of users.

Also interesting is the belief that MTS will DRAMATICALLY reduce the memory
consumption.  It may reduce it -- but in my experience *not dramatically*.
There are two pieces of memory to consider

o UGA - the user global area
o PGA - process global area

In MTS, the UGA is moved from the PGA (where it resides in a dedicated server
environment) into the SGA.  You configure the LARGE_POOL to be big enough to
hold all of your UGA's (and this is considerable in size).  The sum of the sizes
of the UGA's do not GO DOWN at all in MTS and in fact, you must ensure you have
sized the large pool large enough at startup time in order to support the number
of concurrent sessions!  The UGA is *not* shareable memory -- its your session
state.

Now, that leaves the PGA.  We'll have less PGA's this is true.  How much memory
this saves depends on many factors. For example, sort_area_size is setting that
affects the size of a PGA.  When we sort, we'll allocate UPTO sort_area_size
bytes of memory in the PGA to sort.  As soon as we are done sorting -- the data
is moved into the uga (sort_area_retained_size), and we'll release that PGA
memory and use it for other things.  So, if you have only 10 people sort at the
same time (the premise behind using MTS is you have lots of users, only a
certain % of which are concurrently active at the same time so you can use a
shared server) having 10 shared servers will consume just about the same amount
of memory.

What MTS buys you mainly is LESS processes/threads on the system, less context
switching going on, less for the os to manage.  The memory savings are there,
but they are not as big as people seem to make them be out to be.  Its mostly in
the reduced NUMBER OF PROCESSES that the OS has to manage.

So, I would stand by this answer:

o batch = dedicated server
o online = shared server, if and when the OS has run out of resources.

See
http://asktom.oracle.com/pls/ask/z?p_url=http%3A%2F%2Fwww.wrox.com%2FACON11.asp%3FISBN%3D186100690x&p_cat=BEG_ORACLE&p_company=10
for a link that should work (appears wrox had some session information in the
other url and that session "expired") .  I updated the home page as well.
Rating: 4
Thanks Tom! February 27, 2002
Reviewer: Mohit Dubey from UK
Thanks for the reply Tom. Your comments were on the exact same lines as my
thinking!

I think the people here need a little more convincing, though... so I now plan
to setup a database with MTS enabled and have them run a couple of batch process
and online screens simultaneously.... oh well!

Best Regards,

Mohit.
Followup:
or, just get them my book and have them read it.  maybe, just maybe, once they
understand HOW it works, they will see the light.

At least it is extremely easy to fix if they proceed.  You'll only need to
change a connect string for the batch processes.
Rating: 5
a helpful discussion of MTS. October 02, 2002
Reviewer: Cynthia from Denver, USA
Rating: 5
Maybe MTS might help against DOS attacks April 05, 2004
Reviewer: A reader
Tom, my situation is as follows:

I want to defend myself against "in-house" DOS attacks. Let me tell you the
story...

There is one "in-house" application that for some unknown reason (we're working
on fixing it) begins to open connections and reaches the maximum process number
(500) presently set. And then, no one else has access to Oracle.

I have tried to set the maximum number of sessions in its profile to 30, but
that wouldn't help, since they log on and out very rapidly and there aren't in
fact many simultaneous oracle db sessions for that app. But the server processes
"accumulate".

I have to do that at the network layer really.

When I use "netstat -a". I see all incoming "established" (near 500) connections
come from the app's machine.

I would simply like some solution to limit the number of network connections
this application might reach.

Presently I'm using only dedicated processes.

I thought of using MTS for that, but I still haven't figured out how, exactly.
Something like setting the maximum number of connections for a dispatcher
exclusively for that app, or what...

I know the best thing to do is fix the applicaton, but I want to defend myself
from eventual "DOS" attacks like this.

I have asked the security guys here to set some firewall rules for that, but
that seems complicated. Yet, I'd rather enforce it in the database/oracle net.

What do you recommend ?

Thank you very much !
Followup:
why are the server processes "accumulating"??  if they log out, it should go
away, if they are not -- there is something fundementally wrong.

You can (and always do) run MTS and dedicated server at the same time (you
cannot have an MTS only database).

You can setup an MTS service (this app will have its own tns connect string) and
set the max servers to some reasonable number.  It'll not spawn a process for
each connection in this fashion.

I'd recommend however that if you are seeing orphaned processes after someone
disconnects you contact support and get that fixed.
Rating: 5
Continuning from above... April 05, 2004
Reviewer: A reader
Maybe setting connections in dispatchers ?
Rating: 5
Additional dispatchers April 05, 2004
Reviewer: A reader
I'm having a hard time configuring additional dispatchers.

I already have:

SQL> show parameter dispatchers

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
dispatchers                          string      (PROTOCOL=TCP) (SERVICE=orades
                                                 XDB)

If I go:
SQL> alter system set
dispatchers='(pro=tcp)(service=oradesXDB),(pro=tcp)(disp=1)';
alter system set dispatchers='(pro=tcp)(service=oradesXDB),(pro=tcp)(disp=1)'
*
ERROR at line 1:
ORA-00101: invalid specification for system parameter DISPATCHERS

which seems to be the documented way do add 1 more dispatcher, it doesn't work.

Any help ?
Followup:
where did you see it documented like that?

and have you read through the net services admin guide?
Rating: 5
mts April 05, 2004
Reviewer: A reader
if i have shared server enabled, which is the default in tnsnames.ora if i do
not specify ?

(server=dedicated) or (server=shared) ?
Followup:
depends on the service you connect to.  the service drives that.
April 05, 2004 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!
Rating: 4
April 05, 2004
Reviewer: HM
Tom,

What privileges does a user require to be able to do ddl using execute
immediate? I am getting Insufficient privileges when trying to create view
wihtin a stored procedure.
Followup:

http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html
grant CREATE VIEW
April 05, 2004 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.
Rating: 5
Searched a lot but did not find answer April 05, 2004
Reviewer: sonali from waltham, ma
I am using Oracle 9i
I have a table called work.

I want to dynamically select the columns from the same table depending on
work_level value..

In other words I want to select work_par2 column if work_level =2 and work_par3
column if work_level is 3 so on til work_par9 column the work_level is 9.

We usually do DECODE or CASE to do this...
But it becomes very expensive when its in cursor or subquery etc.

    Declare cursor main_cursor
    IS
    Select AuthAttr_ID, to_date( to_char(AuthAttr_Start_Date, 'YYYY-MM-DD
"00:00:00.000"'), 'YYYY-MM-DD "00:00:00.000"')
    from Authattrib
    where  AuthAttr_Work_ID IN (
        Select Work_ID From Work
        Where DECODE(inwork_level, 2, Work_Par2 ,3, Work_Par3 ,4,
            Work_Par4,5,Work_Par5 ,6,Work_par6 ,7,Work_par7 ,
            8, Work_par8, 9, Work_Par9) = inProjectID
        );

I came up with this type of dynamic sql, but don't know which is more efficient
and how to do this better...

I set inSAPPENT03 value before hand in this query -
execute immediate'        Update mwebwork
                 set                 Work_Baseline_Finish = Work_Plan_Finish
                 where Work_Par'||inSAPPENT03 || '='||  inWorkId;
Followup:
why are we comparing a select vs update?

and the update should be

... where  work_par' || variable || ' = :x' using inWorkId;

at worst.
April 05, 2004 Bhavesh -- Thanks for the question regarding "Inserting a CLOB data", version 4.0.8
You Asked
Hi Tom,
    This is in continuation of my question on how can I insert streams of
characters coming from HTML form into a oracle database table CLOB column.
    Actually I am passing some free flow text (series of characters say
something like what I am writing in this question) from html form. When this
column is passed into the procedure which inserts the record what should be the
datatype and how can I insert this incoming text into a CLOB column.
Regards

Bhavesh
and we said...
If you are calling a stored procedure, VARCHAR2.

tkyte@TKYTE816> create table t ( x int, y clob );
Table created.

tkyte@TKYTE816>
tkyte@TKYTE816> create or replace procedure p( p_x in int, p_new_text in
varchar2 )
  2  as
  3  begin
  4          insert into t values ( p_x, p_new_text );
  5  end;
  6  /

Procedure created.

tkyte@TKYTE816>
tkyte@TKYTE816> exec p(1, rpad('*',32000,'*') );

PL/SQL procedure successfully completed.

tkyte@TKYTE816> exec p(2, 'hello world' );

PL/SQL procedure successfully completed.

tkyte@TKYTE816>
tkyte@TKYTE816> select x, dbms_lob.getlength(y) from t;

         X DBMS_LOB.GETLENGTH(Y)
---------- ---------------------
         1                 32000
         2                    11

2 rows selected.

followup to the comments below about the 4000 characters...

The difference is I used a BIND VARIABLE in the above example.  PLSQL limits my
bind variable to 32k.

In SQL, the limit is 4,000 characters.  Using straight SQL like that, without a
bind variable, you'll be limited to 4,000 characters.

See
http://osi.oracle.com/~tkyte/Misc/LargeStrings.html
for another example of this.

Followup to comment below...

In Oracle8.0, the behavior of PLSQL is different then in 8.1 (which is what I
used in my example.

In Oracle8.0, you would code:

  1  create or replace procedure p( p_x in int, p_new_text in varchar2 )
  2  as
  3     l_clob clob;
  4  begin
  5     insert into t values ( p_x, empty_clob() ) returning y into l_clob;
  6     dbms_lob.write( l_clob, 1, length(p_new_text), p_new_text );
  7* end;
scott@ORA806.WORLD> /

Procedure created.

scott@ORA806.WORLD> exec p( 1, rpad('*',32000,'*') );

PL/SQL procedure successfully completed.

scott@ORA806.WORLD> select dbms_lob.getlength(y) from t;

DBMS_LOB.GETLENGTH(Y)
---------------------
                32000

instead.
Review & Followup

Rating: 3
Inserting a CLOB data March 22, 2001
Reviewer: Nirmal kumar from Doha, Qatar
Hi. tom,

I tried your answer in to my oracle 8.1.6 database on NT. The insert statement
allows me to specicy any size for the cLOB column, but in select statement, it
shows only 4000 as size for the CLOB columns. The following is the one i tested.

sql> create table ddreg(file# number, ftext CLOB);

table created;

sql> Insert into ddreg(1, rpad('n',50000,'-'));
1 row inserted;

sql> Select dbms_lob.getlength(ftext) from ddreg;

DBMS_LOB.GETLENGTH(FTEXT)
------------------------
                    4000

Why it's like this.
Rating: 3
Inserting a CLOB data March 22, 2001
Reviewer: Bhavesh from Singapore
I have tried this solution but to my surprise, when I see the
dbms_lob.getlength(col2) using select statement the length shown is 4000 only
whereas the data inserted was many times bigger than this. I am using oracle
8.1.6 with NT as OS.  Can you please explain as to why this is happening.
Rating: 3
Inserting a CLOB data March 22, 2001
Reviewer: Bhavesh from Singapore
Post Follow up:
   When I tried to run exec p I got following error.

SQL> exec p(1, rpad('*',32000,'*') );
BEGIN p(1, rpad('*',32000,'*') ); END;

*
Error at line 1:
ORA-01461: can bind a LONG value only for insert into a LONG column.
ORA-06512: at "TEST.P", line 3
ORA-06512: at line 1
Rating: 4
It is great, but November 06, 2001
Reviewer: Lucy from NYC, USA
it didn't work in our 8.1.6.3.1 database when the cursor_space_for_time=true, we
got ora 7445 in the server and ora 3113 in the client (sqlplus), after I turn
off cursor_space_for_time as false, it works, could you tell me why, and if I
set it as false, is any performance impack.

Thanks
Rating: 2
Clobs larger than 32k February 01, 2002
Reviewer: Femi from UK
Tom,

Could you please show an example of how to insert CLOB data larger than 32K,
using PL/SQL or is this not possible? From you previous eaxmples I have iserted
upto 32K worth of data.

create or replace procedure lob_ins(p_id IN number, p_text IN varchar2 )
as
        l_clob clob;
        BEGIN
           insert into demo values (p_id, empty_clob() ) returning y into
l_clob;
           dbms_lob.write( l_clob,length(p_text), 1,p_text);
        END;
/
Followup:
You have to chunk it up and use dbms_lob.writeappend.

PLSQL will not (does not) support strings >32k.  P_text as an in parameter
cannot exceed 32k.  You will have to "chunk up" the input data.

something like (i did NOT compile this, the gist is there tho...)

create package lob_pkg
as
   procedure lob_ins( p_id in number, p_text in varchar2 );
   procedure add_more( p_text in varchar2 );
end;
/

create package body lob_pkg
as

g_clob clob;

procedure lob_ins(p_id IN number, p_text IN varchar2 )
as
begin
     insert into demo values (p_id, empty_clob() ) returning y into g_clob;
     dbms_lob.write( g_clob,length(p_text), 1,p_text);
end;

procedure add_more( p_text in varchar2 )
as
begin
    dbms_lob.writeappend( g_clob, length(p_text), p_text );
end;

end;
/

and then call

exec ins_lob( 1, 'hello' );
exec add_more( ' world' );

passing upto 32k of data at a time.
Rating: 4
Where will i store the image files? March 27, 2002
Reviewer: Chidambara Barathi from Bangalore
Hi Tom,

Where will i save the image files in client or in server?

sothat i can read the file and update in the table...

Thanks.
Followup:
Umm, you need to tell us where you want to store the images.

Me, I would put them in the database for all to access.  On the client means one
person can get it.  You would retrieve if from the DB for them when they need
it.
Rating: 4
inserting clob July 10, 2003
Reviewer: Venkat from India
Tom,

When I tried with the given procedure,  I could insert upto 32700. When I
entered 32800, it has thrown the error
ERROR at line 1:
ORA-00600: internal error code, arguments: [ksmovrflow], [kokleva unicode buf],
[], [], [], [], [],
[]

Can you please explain this phenomena? Oracle version is 8.1.7.4

Regards,
Venkat
Followup:
32k at a time, 32k is the limit for a plsql variable - with utf8, that could be
significantly LESS then 32k CHARACTERS (as each utf8 character could take 1, 2,
3 or more bytes to represent)
Rating: 4
inserting clob July 10, 2003
Reviewer: Venkat from India
Tom,

When I tried with the given procedure,  I could insert upto 32700. When I
entered 32800, it has thrown the error
ERROR at line 1:
ORA-00600: internal error code, arguments: [ksmovrflow], [kokleva unicode buf],
[], [], [], [], [],
[]

Can you please explain this phenomena? Oracle version is 8.1.7.4. db character
set is UTF8

Regards,
Venkat
Rating: 4
I could only insert up to 4k clob January 05, 2004
Reviewer: June from MD, USA
I tried to use above procedure, but got error when I tried to insert over 4k:

 SQL> exec p(1, rpad('*',4000,'*'));

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.01
SQL> exec p(1, rpad('*',5000,'*'));
BEGIN p(1, rpad('*',5000,'*')); END;

*
ERROR at line 1:
ORA-00600: internal error code, arguments: [koklgloc: arrlen], [], [], [], [],
[], [], []
ORA-06512: at "P", line 4

and when I tried to use dbms_lob.write/writeappend, got following error:
RROR at line 1:
ORA-22275: invalid LOB locator specified
ORA-06512: at "SYS.DBMS_LOB", line 722
ORA-06512: at " LOB_INS", line 8  -- where is following statement:
dbms_lob.write(v_clob, length(p_text), 1, p_text);

ORA-06512: at line 1

version 8.1.7.4

what should I do? using bind variables?
Followup:
Lets see this cut and paste from your system -- this too is 8174 (and my example
already does use binds for the lobs)

ops$tkyte@ORA817DEV> create table t ( x int, y clob );

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace procedure p( p_x in int, p_new_text in
varchar2 )
  2  as
  3  begin
  4          insert into t values ( p_x, p_new_text );
  5  end;
  6  /

Procedure created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> exec p(0, rpad('*',5000,'*') );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> exec p(1, rpad('*',32000,'*') );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> exec p(2, 'hello world' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> select x, dbms_lob.getlength(y) from t;

         X DBMS_LOB.GETLENGTH(Y)
---------- ---------------------
         0                  5000
         1                 32000
         2                    11

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
Rating: 4
Writing/Reading web page to/from CLOB February 02, 2004
Reviewer: Robert from CT
8.1.7
To make my web app seem blinding fast, I'm thinking to load/reading a whole
webpage (non-changing historical report) 40K + to a CLOB column indexed with the
report id...how can I achieve this ?

Im thinking in stead of htp.p-ing, I dump to my own app buffer first, then write
to the LOB, then HTP it out...please provide code snippet on how to write to LOB
in looping thru my plsql table buffer and how to read it back.

Thanks
Followup:
well, does this site seem fast?

I did not resort to creating lobs of each page - each page is constructed as it
is requested.

I'd focus on writing good code personally.

But, if you want to do this, why not just publish to flat files?
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:347617533333
then, let the web server just return a page.

or, just change that code to use dbms_lob.writeappend instead of utl_file.  upon
retrieval, you would just select clob into l_clob from t where id = :x and then
use dbms_lob.substr to htp.p 32k chunks of it.
Rating: 4
no flat files laying around... February 03, 2004
Reviewer: robert from CT
>> why not just publish to flat files?

hey, I want EVERYTHING in the database, ok ? :)

thanks
Rating: 5
April 05, 2004
Reviewer: A reader
Rating: 5
Very helpful response... April 05, 2004
Reviewer: A reader from Sunnyvale, CA, USA
Is there any way to find out using a plsql package if the string returned by

 rpad('*',64000,'*'));

function exceeds 32K?

- Karthik
Followup:
ops$tkyte@ORA9IR2> exec dbms_output.put_line( length(rpad('*',64000,'*')) );
BEGIN dbms_output.put_line( length(rpad('*',64000,'*')) ); END;

*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character string buffer too
small
ORA-06512: at line 1
April 05, 2004 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://download-west.oracle.com/docs/cd/A87860_01/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.
Rating: 4
insert append and unusable indexes March 17, 2004
Reviewer: A reader
Hi

I am inserting a table using insert /*+ append */, the table has 7 indexes and
they are set to unusable. I am not expecting any large amount of redo to be
generated nor more than 1 used_ublk in v$transaction however I see this

SQL> r
  1* select used_ublk from v$transaction

 USED_UBLK
----------
      6642

SQL> r
  1* select used_ublk from v$transaction

 USED_UBLK
----------
      6643

SQL> @sesstat
Nombre de usuario:  [SYSTEM]
SID:  [9] 14
Statistic name:  [NONE] redo size

   SID USERNAME         NAME
            VALUE
------ ----------------
------------------------------------------------------------ -------------
    14 SYSTEM           redo size
       1321028904

1 row selected.

SQL> @sesstat
Nombre de usuario:  [SYSTEM]
SID:  [9] 14
Statistic name:  [NONE] redo size

   SID USERNAME         NAME
            VALUE
------ ----------------
------------------------------------------------------------ -------------
    14 SYSTEM           redo size
       1321233112

SQL> @sesstat
Nombre de usuario:  [SYSTEM]
SID:  [9] 14
Statistic name:  [NONE] redo size

   SID USERNAME         NAME
            VALUE
------ ----------------
------------------------------------------------------------ -------------
    14 SYSTEM           redo size
       1331016744

It generated over 1gb redo already (I ran this queries after 1.5 hour the
process was running)

SQL> select status from dba_indexes where table_name='OM_TECHNICAL_INST';

STATUS
--------
UNUSABLE
UNUSABLE
UNUSABLE
UNUSABLE
UNUSABLE
UNUSABLE
UNUSABLE

SQL> select logging from dba_tables where table_name = 'OM_TECHNICAL_INST';

LOGGING
---------
NO

How so???
Followup:
that is just 50m of undo (rollback) assuming an 8k blocks.

However, insert /*+ append */ does NOT bypass REDO.  NOLOGGING combined with
APPEND can.

I do not have a full example here, so, I cannot comment in any way.  I cannot
even see the insert append statement.  don't know if the table is in fact
'nologging'.  don't know if you are using append correctly.
Rating: 5
sorry forgot to add this March 17, 2004
Reviewer: A reader
Hi

here is the statement,

insert /*+ APPEND */ into swb.OM_TECHNICAL_INST b
select /*+ parallel(a 2) */ *
  from swb.OM_TECHNICAL_INST_BK a;

and it´s still going after 3 hours! (the table is 8g, only 2gb done so far)

As for logging attribute, the table is nologging as attached previously

I just ran again to check redo size and the value is negative now!!!!

SQL> @sesstat
Nombre de usuario:  [SYSTEM]
SID:  [9] 14
Statistic name:  [NONE] redo size

   SID USERNAME         NAME
            VALUE
------ ----------------
------------------------------------------------------------ -------------
    14 SYSTEM           redo size
      -1859404656
Followup:
select force_logging from v$database;
Rating: 5
it´s 8.17.4 March 17, 2004
Reviewer: A reader
I think it may because my database is in noarchive log?

SQL> select LOG_MODE from v$database;

LOG_MODE
------------
NOARCHIVELOG

Will this insert go faster if I put the database in archive log and bypass redo
generation?
Followup:
archive log mode GENERATES redo with insert append

In noarchive log mode, it DOES NOT generate redo

you've got something else happening here, the insert append in a noarchive log
mode database does not generate redo.
Rating: 4
rollback March 17, 2004
Reviewer: A reader
I always thought direct insert only uses 1 block used_ublk in v$transaction?
Followup:
(something else is happening here -- this is not a direct path insert, i don't
see the actual sql out of v$sql, but I don't believe it is direct pathing)
Rating: 5
I TKPROFED and this is the output March 17, 2004
Reviewer: A reader
Hi

I tried with 100000 rows (table has 100 million rows) and it generates tons of
redo...

insert /*+ APPEND */
  into swb.OM_TECHNICAL_INST
select *
  from swb.OM_TECHNICAL_INST_BK
 where rownum < 100001

Execution Plan
----------------------------------------------------------
   0      INSERT STATEMENT Optimizer=RULE
   1    0   LOAD AS SELECT
   2    1     COUNT (STOPKEY)
   3    2       TABLE ACCESS (FULL) OF 'OM_TECHNICAL_INST_BK'

Statistics
----------------------------------------------------------
          0  recursive calls
     472934  db block gets
       2168  consistent gets
       1141  physical reads
    6430016  redo size
        643  bytes sent via SQL*Net to client
        569  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
     100000  rows processed

insert /*+ APPEND */
  into swb.OM_TECHNICAL_INST
select /* parallel(a 2) */ *
  from swb.OM_TECHNICAL_INST_BK
 where rownum < 100001

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.01          0          0          0           0
Execute      2      5.89      13.41       1160       2202     472998      100000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      5.89      13.42       1160       2202     472998      100000

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: 5  (SYSTEM)
error during parse of EXPLAIN PLAN statement
ORA-26028: index SWB.RL_OM_TECHNICAL_INST_CMFID_FK initially in unusable
  state

The indexes are unusable, table is nologging, hint is correct, there are no
triggers on this table... I am totally lost.
Rating: 5
solved but is this how it works? March 17, 2004
Reviewer: A reader
The APPEND works, the problem is the table had 4 FKs enabled and that generates
redo. I didnt know the table must not have FKs in order for append to work.....
I disable the FKs and now it runs fast

Is this in the docs?
Followup:
well, sort of.  it mentions that "referential integrity constraints are
ignored", but I think it really should say "with direct path loads they are
ignored but a direct path insert will be conventional if you have RI enabled"
(cause that is what happens.
Rating: 4
two appends to a table April 01, 2004
Reviewer: A reader
Sorry to ask again, there is not a way to avoid this error.
( even some secret hint)
because I need to do two or more appends to a table
before commiting or rollbacking.
Thanks

SQL> CREATE TABLE TEST( A NUMBER );

Tabla creada.

SQL> INSERT /*+ APPEND */ INTO TEST SELECT ROWNUM FROM DBA_OBJECTS WHERE ROWNUM<
5;

4 filas creadas.

SQL> INSERT /*+ APPEND */ INTO TEST SELECT ROWNUM FROM DBA_OBJECTS WHERE ROWNUM<
5;
INSERT /*+ APPEND */ INTO TEST SELECT ROWNUM FROM DBA_OBJECTS WHERE ROWNUM<5
                          *
ERROR en lÝnea 1:
ORA-12838: no se puede leer/modificar un objeto despuÚs de modificarlo en
paralelo
Followup:
insert /*+ append */ into test ( .... )
query1
UNION ALL
query2
UNION ALL
query3
...........
Rating: 5
April 05, 2004
Reviewer: A reader
Thanks Tom, but they are two independent inserts.

insert /*+ append */ into test
select * from query1
(several processes in other tables and then in query1, then )
insert /*+ append */ into test
select * from query1

I investigated, this seems to be a limitation in Oracle 9.2,
and there is no way to do it :)
Followup:
well, that would be a strange requirement -- but yes, it can in fact be done.

ops$tkyte@ORA9IR2> column SCN new_val S

ops$tkyte@ORA9IR2> select dbms_flashback.get_system_change_number SCN from dual;

       SCN
----------
  20527895

ops$tkyte@ORA9IR2> drop table t;
Table dropped.

ops$tkyte@ORA9IR2> create table t as select * from scott.emp where 1=0;
Table created.

ops$tkyte@ORA9IR2> update scott.emp set empno = -empno;
14 rows updated.

ops$tkyte@ORA9IR2> insert /*+ append */
  2  into t
  3  select * from scott.emp as of scn &s
  4  union all
  5  select * from scott.emp;
old   3: select * from scott.emp as of scn &s
new   3: select * from scott.emp as of scn   20527895

28 rows created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select empno from t;
select empno from t
                  *
ERROR at line 1:
ORA-12838: cannot read/modify an object after modifying it in parallel

proved it was an append...

ops$tkyte@ORA9IR2> commit;

Commit complete.

ops$tkyte@ORA9IR2> select empno from t;

     EMPNO
----------
      7369
      7499
      7521
      7566
      7654
      7698
      7782
      7788
      7839
      7844
      7876
      7900
      7902
      7934
     -7369
     -7499
     -7521
     -7566
     -7654
     -7698
     -7782
     -7788
     -7839
     -7844
     -7876
     -7900
     -7902
     -7934

28 rows selected.

and for flashback table before flashback table :)

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> delete from scott.emp;

14 rows deleted.

ops$tkyte@ORA9IR2> insert into scott.emp select * from scott.emp as of scn &s;
old   1: insert into scott.emp select * from scott.emp as of scn &s
new   1: insert into scott.emp select * from scott.emp as of scn   20527895

14 rows created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> commit;

Commit complete.
Rating: 5
April 06, 2004
Reviewer: A reader
Sorry Tom and really thanks for your help in your vacations.

Maybe I'm stupid but I don't understand, could you giveme an example with this,
please.

Some experts toldme this was not possible, so I think this is really THE TIP.

I have to execute, in this example, two times

  INSERT /*+ append */ INTO  FON.HICUENTASF_RW(
   HCF_FECHA, HCF_CODCLI, HCF_CTACORR, HCF_DBCUO, HCF_CRCUO,
   HCF_IVA, HCF_MONTO_IVA, HCF_STATUS, HCF_CIUDAD )
  SELECT
    sysdate,CUF_CODCLI, CUF_CTACORR, CUF_DBCUO, CUF_CRCUO,
   CUF_IVA, CUF_MONTO_IVA, CUF_STATUS, CUF_CIUDAD
  FROM CUENTASF;

 --- Table CUENTASF is updated based on several process
     and parameter users defines in the moment (no possible a union all, I tried
to do it.), then the second insert.

  INSERT /*+ append */ INTO  FON.HICUENTASF_RW(
   HCF_FECHA, HCF_CODCLI, HCF_CTACORR, HCF_DBCUO, HCF_CRCUO,
   HCF_IVA, HCF_MONTO_IVA, HCF_STATUS, HCF_CIUDAD )
  SELECT
    dFechaHoy+1,CUF_CODCLI, CUF_CTACORR, CUF_DBCUO, CUF_CRCUO,
   CUF_IVA, CUF_MONTO_IVA, CUF_STATUS, CUF_CIUDAD
  FROM CUENTASF;

Thanks :)
Rating: 5
April 06, 2004
Reviewer: A reader
This is a simpler example of what I want to do. and the error I got :)

SQL> create table test ( a varchar2(1));

Tabla creada.

SQL> iNSERT /*+ append */ into test select 'a' from dual;

1 fila creada.

SQL> iNSERT /*+ append */ into test select 'a' from dual;
iNSERT /*+ append */ into test select 'a' from dual
                          *
ERROR en lÝnea 1:
ORA-12838: no se puede leer/modificar un objeto despuÚs de modificarlo en par
lo

SQL>
April 05, 2004 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.
Rating: 5
superseded by a machine ?? January 22, 2004
Reviewer: Gabriel from Toronto
<quote>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.</quote>

OK, OK ... but rewrite what?
How does Oracle know the semantics of the query I'm writing? ... unless machine
and beast start from a common "understanding"?
It has always been my assumption (obviously wrong now) that an inline view is
semantically equivalent with a persistent view ... you said <quote>... to
"build" on prior work</quote> ... "prior" as in "has happened" as in "as if the
inline view were temporarily materialized".
I don't have a problem with my query being rewritten ... as long as its
semantics are not altered.
This is not a case of relying on the order of evaluation for the predicates ...
I always expected a query to be deterministic in its return data set ... as you
will see, I'm getting different results when I simply shuffle the columns in my
select list.

Before going on though ... assuming you don't find anything inherently wrong
with the described behavior and maintain <quote>They (inline views) do not,
cannot imply "order of operation"</quote>... would you be able to show a similar
example not involving analytic functions?

SQL> drop table temp_two;

Table dropped.

SQL> drop table temp_one;

Table dropped.

SQL>
SQL> create table temp_one
  2  ( id       number(22)  not null
  3   ,region   char(1)     not null
  4   ,filter   char(1)     not null
  5   ,constraint test_one_pk primary key (id)
  6   ,constraint test_one_ck check (filter in ('Y','N'))
  7  );

Table created.

SQL>
SQL> begin
  2   for i in 1..19
  3   loop
  4     insert into temp_one(id,region,filter)
  5     values (i, chr(65+mod(i,4)), decode(mod(i,3),0,'N','Y'));
  6   end loop;
  7   commit;
  8  end;
  9  /

PL/SQL procedure successfully completed.

SQL>
SQL> ---> I'm looking for ... counts by region, filtered and unfiltered,
cumulated order by region ...
SQL> -- query #1
SQL> select x.region, x.cnt_all, x.cumm_cnt_all, y.cnt_yes, y.cumm_cnt_yes
  2  from   ( select a.region
  3                 ,a.cnt_all
  4                 ,sum(a.cnt_all) over(order by a.region) cumm_cnt_all
  5           from   ( select region, count(0) cnt_all
  6                    from   temp_one
  7                    group by region
  8                  ) a
  9         ) x
 10        ,( select f.region
 11                 ,f.cnt_yes
 12                 ,sum(f.cnt_yes) over(order by f.region) cumm_cnt_yes
 13           from   ( select region, count(0) cnt_yes
 14                    from   temp_one
 15                    where  filter = 'Y'
 16                    group by region
 17                  ) f
 18         ) y
 19  where  x.region = y.region;

R    CNT_ALL CUMM_CNT_ALL    CNT_YES CUMM_CNT_YES
- ---------- ------------ ---------- ------------
A          4            4          3            3
B          5            9          4            7
C          5           14          3           10
D          5           19          3           13

SQL> ---> Now ... if I change the first query a bit ... it is still OK ...
SQL>
SQL> -- query #2
SQL> select a.region
  2        ,sum(a.cnt_all) over(order by a.region) cumm_cnt_all
  3        ,sum(f.cnt_yes) over(order by f.region) cumm_cnt_yes
  4  from   ( select region, count(0) cnt_all
  5           from   temp_one
  6           group by region
  7         ) a
  8        ,( select region, count(0) cnt_yes
  9           from   temp_one
 10           where  filter = 'Y'
 11           group by region
 12         ) f
 13  where  a.region = f.region;

R CUMM_CNT_ALL CUMM_CNT_YES
- ------------ ------------
A            4            3
B            9            7
C           14           10
D           19           13

SQL> ---> But ... if I further change (seems inoffensive enough) to ...
SQL>
SQL> -- query #3a
SQL> select x.region
  2        ,sum(x.cnt_all) over(order by x.region) cumm_cnt_all
  3        ,sum(x.cnt_yes) over(order by x.region) cumm_cnt_yes
  4  from   ( select a.region, a.cnt_all, f.cnt_yes
  5           from   ( select region, count(0) cnt_all
  6                    from temp_one
  7                    group by region
  8                  ) a
  9                 ,( select region, count(0) cnt_yes
 10                    from   temp_one
 11                    where  filter = 'Y'
 12                    group by region
 13                  ) f
 14           where  a.region = f.region
 15         ) x
 16  ;

R CUMM_CNT_ALL CUMM_CNT_YES
- ------------ ------------
A            3            3
B            7            7
C           10           10
D           13           13

SQL> ---> The results changed !!
SQL>
SQL> ---> Just changing the order in the select list ...
SQL>
SQL> -- query #3b
SQL> select x.region
  2        ,sum(x.cnt_yes) over(order by x.region) cumm_cnt_yes
  3        ,sum(x.cnt_all) over(order by x.region) cumm_cnt_all
  4  from   ( select a.region, a.cnt_all, f.cnt_yes
  5           from   ( select region, count(0) cnt_all
  6                    from temp_one
  7                    group by region
  8                  ) a
  9                 ,( select region, count(0) cnt_yes
 10                    from   temp_one
 11                    where  filter = 'Y'
 12                    group by region
 13                  ) f
 14           where  a.region = f.region
 15         ) x
 16  ;

R CUMM_CNT_YES CUMM_CNT_ALL
- ------------ ------------
A            4            4
B            9            9
C           14           14
D           19           19

SQL> ---> And changed again !!
SQL>
SQL> ---> But if I'm to materialize the inline view ...
SQL>
SQL> create table temp_two as
  2  select a.region, a.cnt_all, f.cnt_yes
  3  from   ( select region, count(0) cnt_all
  4           from temp_one
  5           group by region
  6         ) a
  7        ,( select region, count(0) cnt_yes
  8           from   temp_one
  9           where  filter = 'Y'
 10           group by region
 11         ) f
 12  where  a.region = f.region
 13  ;

Table created.

SQL>
SQL> -- query #4
SQL> select x.region
  2        ,sum(x.cnt_all) over(order by x.region) cumm_cnt_all
  3        ,sum(x.cnt_yes) over(order by x.region) cumm_cnt_yes
  4  from   temp_two x;

R CUMM_CNT_ALL CUMM_CNT_YES
- ------------ ------------
A            4            3
B            9            7
C           14           10
D           19           13

SQL>
SQL> ---> The results are of course OK ...
SQL>
SQL> ---> Now, I know this one is the better way ... and it always gives the
correct results (as do #1 and #2) ...
SQL>
SQL> -- query #5
SQL> select x.region
  2        ,x.cnt_all
  3        ,sum(x.cnt_all) over(order by x.region) cumm_cnt_all
  4        ,x.cnt_yes
  5        ,sum(x.cnt_yes) over(order by x.region) cumm_cnt_yes
  6  from  ( select region, count(0) cnt_all, sum(decode(filter,'Y',1,0))
cnt_yes
  7          from   temp_one
  8          group by region
  9        ) x;

R    CNT_ALL CUMM_CNT_ALL    CNT_YES CUMM_CNT_YES
- ---------- ------------ ---------- ------------
A          4            4          3            3
B          5            9          4            7
C          5           14          3           10
D          5           19          3           13

SQL>
SQL> ---> But still my question remains on why queries #3a and #3b (which only
introduce an inline view over #2) are not getting
SQL> ---> what one (well I) would expect.
SQL>
SQL> ---> Surprise, surprise ... if I analyze the table ... the queries are
suddenly working ...
SQL>
SQL> analyze table temp_one compute statistics;

Table analyzed.

SQL>
SQL> -- query #3b
SQL> select x.region
  2        ,sum(x.cnt_all) over(order by x.region) cumm_cnt_all
  3        ,sum(x.cnt_yes) over(order by x.region) cumm_cnt_yes
  4  from   ( select a.region, a.cnt_all, f.cnt_yes
  5           from   ( select region, count(0) cnt_all
  6                    from temp_one
  7                    group by region
  8                  ) a
  9                 ,( select region, count(0) cnt_yes
 10                    from   temp_one
 11                    where  filter = 'Y'
 12                    group by region
 13                  ) f
 14           where  a.region = f.region
 15         ) x
 16  ;

R CUMM_CNT_ALL CUMM_CNT_YES
- ------------ ------------
A            4            3
B            9            7
C           14           10
D           19           13

Thanks
Followup:
it rewrites using rules.  for example

"where exists can be rewritten as 'in'"
"in's can be turned into semi-joins"
"views can be merged into"

and so on.  nothing about semantics -- purely about "equivalance of operations"

if the semantics are altered, that means the ANSWER would be different (from the
non-rewritten query) and that -- well that would be a P1 bug.

the result sets from two equivalent queries should be the same.

select * from t where exists ( select null from t2 where t2.x = t1.x )

is equivalent to

select * from t where t1.x in ( select t2.x from t2 )

is equivalent to

select * from t SEMI JOIN t2 on ( t1.x = t2.x ) /* made up syntax!!! */

In your examples -- ANALYTICS explicity prohibit in most cases the merging of
views as it would in fact change the answer.  You, by manually merging into
views -- changed the answer.  the database would therefore NOT merge into the
view, would NOT rewrite the query.

If a query rewrite results in a different answer than a non-rewritten query, you
have found a bug.
Rating: 4
lingering doubts January 22, 2004
Reviewer: Gabriel from Toronto
<quote>If a query rewrite results in a different answer than a non-rewritten
query, you have found a bug.</quote>

Bear with me for one more minute please …

You left a bit of a doubt in my mind … is this a bug or not? … is it something I
should be aware of (well, I am) and avoid till next version?

Since query rewrite happens behind the scene (hence I shouldn’t really be aware
of it) … how can I find out if rewriting has taken place and which was the
actual incarnation of the executed statement? I mean I can look at the plan …
but would that be enough?

Analytics aside, you still seeded some doubts in my understanding of inline
views … building on the example you’ve shown previously … is this an "unsafe"
query?

it5oltp@IPSMIG> create table temp_six as
  2  select rownum-5 x from user_objects where rownum < 11;

Table created.

Elapsed: 00:00:00.31
it5oltp@IPSMIG>
it5oltp@IPSMIG> select * from temp_six where x>0 and 1/x > 0;
select * from temp_six where x>0 and 1/x > 0
                                      *
ERROR at line 1:
ORA-01476: divisor is equal to zero

Elapsed: 00:00:00.16
it5oltp@IPSMIG> select * from temp_six where 1/x > 0 and x>0;

         X
----------
         1
         2
         3
         4
         5

Elapsed: 00:00:00.00
it5oltp@IPSMIG>---> this one --->
it5oltp@IPSMIG> select t2.f
  2  from   ( select 1/t1.x f
  3           from   (select * from temp_six where x>0) t1
  4           where  1/t1.x > 0
  5         ) t2
  6  ;

         F
----------
         1
        .5
.333333333
       .25
        .2

I get the results I expect … but was it by fluke?
Followup:
query rewrites have been taking place for years and years and years in Oracle --
you can in fact not entirely turn it off.

there is the query rewrite for MV's and function based indexes -- that you
control.

there is the query rewrite we just do, and you have no real say in the matter.
It just happens.  It is part of the optimizer.

All of the above queries are unsafe.  the only safe versions would use CASE or
DECODE

select * from t
  where x>0 and decode( x, 0, to_number(null), 1/x) > 0;

is "safe"
Rating: 3
wrong expectations ... January 22, 2004
Reviewer: Gabriel from Toronto
I was searching for another "safe" query ... and was again surprised ...

it5oltp@IPSMIG> select t2.f
  2  from   ( select 1/t1.x f
  3           from   (select * from temp_six where x>0 order by 1) t1
  4           where  1/t1.x > 0
  5         ) t2
  6  ;
         where  1/t1.x > 0
                 *
ERROR at line 4:
ORA-01476: divisor is equal to zero

I thought by adding the "order by" in the inner inline view I'm "forcing" its
evaluation to happen as is and first ... strangely enough (for me at least) now
I'm hitting the error. I mean, I know the ordering is high on the priority list
... why would Oracle "reduce" that priority level?

Whatever you would say to the effect of "this kind of how it works" ... I still
believe something is amiss. There is something to those round brackets
"resembling" the ones used in arithmetic operations .... x+(a-b)*y=x+ay-by ...
and != x+a-b*y ... I would be willing to bet (shot in the dark of course 'cause
it can't be measured) that most people using Oracle are "assuming" inline views
bring a certain order of operation ... and they write queries that way. To turn
it around ... is it any way I can "force" the order of operation when using
inline views? If not, I might have to shy away from them since they are (or at
least, appear to be) unpredictable.

Anyway ... it was (scary) fun going through this stuff.

Thanks.
Followup:
sql is 100% free to reorder everything by the very definition of the language.

order by MIGHT materialize the inline view
it does not have to

decode and case are the only 100% safe way...
Rating: 4
January 22, 2004
Reviewer: Dave from Colorado Springs
Gabriel, I seem to recall that adding the predicate "rownum > 0" inside the
inline view will do this ...

select t2.f
from   ( select 1/t1.x f
        from   (select * from temp_six where x>0 and rownum > 0 order by 1) t1
          where  1/t1.x > 0
       ) t2

I couldn't say that it's any kind of "official" method though.
Rating: 5
Some clarifications required January 22, 2004
Reviewer: A reader from UK
Tom,

Not quite followed why the change in select clause will give a different result
as mentioned in the above post by Gabriel.
Without building an intermediate table, how can one build an inline view to get
the correct result? Though tried to understand your explanation of not assuming
any order in SQL, not quite sure what one needs to consider to write a bug free
code in such cases.

From reading in other threads my impression was inline view gets resolved first
before returning the resultset.

Could you help please.

Many thanks,

<<<<<<<<<<<<<<<<<<<<paste from the earlier post>>>>>>>>>>>>>>>
SQL> -- query #3a
SQL> select x.region
  2        ,sum(x.cnt_all) over(order by x.region) cumm_cnt_all
  3        ,sum(x.cnt_yes) over(order by x.region) cumm_cnt_yes
  4  from   ( select a.region, a.cnt_all, f.cnt_yes
  5           from   ( select region, count(0) cnt_all
  6                    from temp_one
  7                    group by region
  8                  ) a
  9                 ,( select region, count(0) cnt_yes
 10                    from   temp_one
 11                    where  filter = 'Y'
 12                    group by region
 13                  ) f
 14           where  a.region = f.region
 15         ) x
 16  ;

R CUMM_CNT_ALL CUMM_CNT_YES
- ------------ ------------
A            3            3
B            7            7
C           10           10
D           13           13

SQL> ---> The results changed !!
SQL>
SQL> ---> Just changing the order in the select list ...
SQL>
SQL> -- query #3b
SQL> select x.region
  2        ,sum(x.cnt_yes) over(order by x.region) cumm_cnt_yes
  3        ,sum(x.cnt_all) over(order by x.region) cumm_cnt_all
  4  from   ( select a.region, a.cnt_all, f.cnt_yes
  5           from   ( select region, count(0) cnt_all
  6                    from temp_one
  7                    group by region
  8                  ) a
  9                 ,( select region, count(0) cnt_yes
 10                    from   temp_one
 11                    where  filter = 'Y'
 12                    group by region
 13                  ) f
 14           where  a.region = f.region
 15         ) x
 16  ;

R CUMM_CNT_YES CUMM_CNT_ALL
- ------------ ------------
A            4            4
B            9            9
C           14           14
D           19           19

SQL> ---> And changed again !!
Followup:
that looks like a bug -- the cnt_yes/cnt_all columns are the "same" which ever
comes first is being used as "both"

running in 10g returns "the correct answer"

so, looks like a fixed bug.  but a bug.
Rating: 5
Inline view January 22, 2004
Reviewer: A reader from UK
Many Thanks Tom, for your prompt reply.
Gabriel also mentions that by analyzing the table he could get the desired
result, though before analyzing the table one of the inline view solution was
giving him incorrect result. Does that mean analyze corrected the problem or
this was just a matter of chance?
Followup:
it means it was an RBO bug.  using the CBO "fixed" it.
Rating: 5
January 23, 2004
Reviewer: Gabriel from Toronto
Hi Tom,

The bug potentially goes back to 8.1.6 ... hope those versions get (if not
already) patched up soon ... data integrity ... ouch!

<quote>inline views are a syntactic nicety. They (inline views) do not, cannot
imply "order of operation"</quote>
Got my head clear and understood where you coming from with this one.

All of these queries:
  select /* qry_1 */ iv.* from (select * from pd where a=b) iv
  create view vw as select * from pd where a=b
  select /* qry_2 */ vw.* from vw
  select /* qry_3 */ pd.* from pd where a=b
are semantically equivalent.

That is, given the same inputs, irrespective of having or not insight into the
data (i.e. stats) ... or any other factors for that matter, they should
consistently return the same results.

Where the misunderstanding starts is wrongly assuming that, since qry_3 and
qry_1 are semantically equivalent, the execution of qry_1 has to essentially
work like a pipe  ... that is, that it somehow has to assemble in memory that
intermediate image of the data.

I doesn't of course  ... and it is now clear to me why it would be a bad idea to
"burn" that step into the execution algorithm ...

Given this query ...

select iv.* from (select g, count(0) c from t group by g) iv where iv.g = 0

the optimal execution path may well be:
a. filter all records from t where g=0
b. count them

Should the step of in-memory-materializing the inline view been "burnt" into the
algorithm, the execution path would've always been:
a. do the group by over the entire table t and "pipe" the results through
b. filter the one record where g=0

So yes, why build a restriction in.

Now ... I do have a question though ... you said somewhere above ...
<quote>ROWNUM is the culprit here.  ROWNUM (like aggregates, distincts,
intersect, minus, union, .... ) causes the inner view to be
materialized.</quote>
are you saying in this example of mine the "group by" will always be executed?

Thx.
Followup:
yes, as it stands now, yes -- the rownum will prevent merging and pushing of
predicates (as they would/could change the semantic meaning)
Rating: 5
wasn't about rownum January 23, 2004
Reviewer: Gabriel from Toronto
I was asking about a query like:
  "select iv.* from (select g, count(0) c from t group by g) iv where iv.g = 0"
in the light of: <quote>... like aggregates ... cause the inner view to be
materialized.</quote>

Not that I would rely on it in my code … since it may not be true in the next
version ... curios, nonetheless.

Thanks.
Followup:
i should have said "may cause the inner view"
Rating: 5
some more clarifications January 26, 2004
Reviewer: a reader
Tom,

Could you please let me know whether this query is bug free(executing on oracle
8i) - considering the bug you mentioned that is being fixed in 10g in the above
post.

Thanks,

select a.account_status,
       ah2.acc_no,
       ah2.balance,
       ah2.prev_bal prev_bal,
       ah2.prev_prev_bal,
       ah2.year_month,
       ah2.arrears,
       ah2.past_due,
       ah2.sum_past_due
from   acc a,
       (select ah1.acc_no,
               ah1.balance,
               ah1.prev_bal prev_bal,
               ah1.prev_prev_bal,
               ah1.year_month,
               ah1.arrears,
               ah1.past_due,
               ah1.sum_past_due
               from (select acc_no,
                     year_month,
                     balance,
                     lag(balance, 1) over (PARTITION BY acc_no ORDER BY
year_month  ) prev_bal,
                     lag(balance, 2) over (PARTITION BY acc_no ORDER BY
year_month  ) prev_prev_bal,
                     arrears,
                     past_due,
                     SUM(past_due) over (PARTITION BY acc_no ORDER BY
year_month) sum_past_due
                     from (select acc_no,
                                  year_month,
                                  balance,
                                  arrears,
                                  past_due
                                  from acc_hist
                                  where year_month >=:past_year_month) ah
                    ) ah1
             where ah1.year_month =:current_year_month
       )ah2
where a.account_no=ah2.acc_no
/

CREATE TABLE ACC
(
  ACCOUNT_NO      NUMBER(16)                    NOT NULL,
  ACCOUNT_STATUS  VARCHAR2(1)
)
;

CREATE TABLE ACC_HIST
(
  ACC_NO         NUMBER(16)                NOT NULL,
  BALANCE        NUMBER(11,2),
  PAST_DUE       NUMBER(9,2),
  ARREARS        NUMBER(9,2),
  YEAR_MONTH     NUMBER(6)                 NOT NULL
);

insert into acc values(7962301, 1);
insert into acc values(7966500, 1);

SQL> select * from acc;

ACCOUNT_NO A
---------- -
   7962301 1
   7966500 1

insert into acc_hist values(7962301,400.00,2.00,5.00,200211);
insert into acc_hist values(7966500,0.00,6.00,0.00,200211);
insert into acc_hist values(7962301,200.00,4.00,556.00,200212);
insert into acc_hist values(7962301,500.00,1.00,3.00,200301);
insert into acc_hist values(7966500,45.00,55.00,55.00,200301);
insert into acc_hist values(7962301,300.00,3.00,444.00,200302);
insert into acc_hist values(7962301,0.00,5.00,0.00,200303);
insert into acc_hist values(7966500,55.00,25.00,5.00,200303);

SQL> SQL> select * from acc_hist  order by acc_no, year_month;

    ACC_NO    BALANCE   PAST_DUE    ARREARS YEAR_MONTH
---------- ---------- ---------- ---------- ----------
   7962301        400          2          5     200211
   7962301        200          4        556     200212 ===>
   7962301        500          1          3     200301
   7962301        300          3        444     200302
   7962301          0          5          0     200303 ===> current month
   7966500          0          6          0     200211
   7966500         45         55         55     200301
   7966500         55         25          5     200303

8 rows selected.

I want to get the balance, previous balance(balance from the pevious month -
200302 ),
previous previous balance(balance from - 200301).

similarly, sum_past_due = sum (past_due for last 4 months)

SQL> exec :past_year_month :=200212

PL/SQL procedure successfully completed.

SQL> exec :current_year_month :=200303

PL/SQL procedure successfully completed.

SQL> @t

A     ACC_NO    BALANCE   PREV_BAL PREV_PREV_BAL YEAR_MONTH    ARREARS
PAST_DUE SUM_PAST_DUE
- ---------- ---------- ---------- ------------- ---------- ----------
---------- ------------
1    7962301          0        300           500     200303          0
5           13
1    7966500         55         45                   200303          5
25           80
Followup:
did you get the right, expected answer?

thats alot of stuff for me to parse through -- given that you are pretty close
to the data here, you would be best equipped to say "yup, it is ok".

If you look at the above issue -- it was duplicating a column -- I don't seem to
see any duplication here.
Rating: 5
January 26, 2004
Reviewer: A reader
Thanks very much Tom,

I did get the desired output but just wanted to check with you whether the query
looks alright or not, since sometime you may get expected result but there may
be a bug.
Rating: 5
Missing something January 27, 2004
Reviewer: Sajid Anwar from London
Hi Tom,
First of all, let me tell you that you explain in such a way that even a novice
can understand the toughest thing.

One thing that I couldnt find in your response.
You ran this for proc2

exec dbms_profiler.stop_profiler;

Then you said

"I find :"

I couldnt follow this. How did you get the following result. What do I have to
do get the result that you have got in the reply.

Thanks in advance.
Followup:
the i find was just a pre-cursor to the report that followed it immediately.

...

I find:

GRAND_TOTAL
-----------
        .64

=
=
====================
Total time spent on each run

RUNID RUN_COMMENT    SECS
----- ----------- -------
    3 proc1           .32
    4 proc1           .32

the stuff in bold is what I found.
Rating: 4
Inline query April 05, 2004
Reviewer: A reader from CA,USA
Is it possible to convert the below given pl/sql code to single statement?
If yes, how and which one is good to use, PL/SQL or single statement, which
would be faster ?

DECLARE
CURSOR Cur_Acct IS
    SELECT      DC_ACCT_ID,SP_ADDR_DBKEY_DISPLAY,MAX(SP_STKH_JCT_ADDR_SEQ)
    FROM      SP_JCT_STKH_ADDR
    WHERE      DC_ACCT_ID IS NOT NULL
    GROUP BY DC_ACCT_ID,SP_ADDR_DBKEY_DISPLAY;

BEGIN
    FOR acct_rec IN cur_acct
    LOOP
        UPDATE SP_ADDR
        SET    DC_ACCT_ID = acct_rec.DC_ACCT_ID
        WHERE  SP_ADDR_DBKEY_DISPLAY = acct_rec.SP_ADDR_DBKEY_DISPLAY;
    END LOOP;
    COMMIT;

    EXCEPTION
    WHEN others THEN
          DBMS_OUTPUT.PUT_LINE('Error: '||SQLERRM);
END;

Thanks
Followup:
AHHH, a when others without a RAISE, thats a bug and a half.  delete that code
IMMEDIATELY.....

yes, it is possible.... but first - a question for you.

is DC_ACCT_ID unique by SP_ADDR_DBKEY_DISPLAY

that is, is the output of:

select dc_acct_id, SP_ADDR_DBKEY_DISPLAY
  from SP_JCT_STKH_ADDR
 group by SP_ADDR_DBKEY_DISPLAY
having count(distinct dc_acct_id) > 1;

the NULL SET?  if not, your procedure is horribly flawed.  If so, the update is
pretty easy.
Rating: 4
Inline Query April 05, 2004
Reviewer: A reader from CA, USA
Forgot to mention the Oracle version in my last post. Its
Oracle9i Enterprise Edition Release 9.2.0.4.0
Rating: 5
Inline Query April 05, 2004
Reviewer: A reader from ca, usa
Thanks a lot.

The query
select dc_acct_id, SP_ADDR_DBKEY_DISPLAY
  from SP_JCT_STKH_ADDR
 group by SP_ADDR_DBKEY_DISPLAY,dc_acct_id
having count(distinct dc_acct_id) > 1

will return a null set.
Followup:
then:

ECLARE
CURSOR Cur_Acct IS
    SELECT      DC_ACCT_ID,SP_ADDR_DBKEY_DISPLAY,MAX(SP_STKH_JCT_ADDR_SEQ)
    FROM      SP_JCT_STKH_ADDR
    WHERE      DC_ACCT_ID IS NOT NULL
    GROUP BY DC_ACCT_ID,SP_ADDR_DBKEY_DISPLAY;

BEGIN
    FOR acct_rec IN cur_acct
    LOOP
        UPDATE SP_ADDR
        SET    DC_ACCT_ID = acct_rec.DC_ACCT_ID
        WHERE  SP_ADDR_DBKEY_DISPLAY = acct_rec.SP_ADDR_DBKEY_DISPLAY;
    END LOOP;
    COMMIT;

    EXCEPTION
    WHEN others THEN
          DBMS_OUTPUT.PUT_LINE('Error: '||SQLERRM);
END;

could be

update sp_addr
   set dc_acct_id = (select dc_acct_id
                       from SP_JCT_STKH_ADDR
                where sp_addr_dbkey_display = sp_addr.sp_addr_dbkey_display )

if all rows in sp_addr have a mate in SP_JCT_STKH_ADDR or

update sp_addr
   set dc_acct_id = (select dc_acct_id
                       from SP_JCT_STKH_ADDR
                where sp_addr_dbkey_display = sp_addr.sp_addr_dbkey_display )
where sp_addr_dbkey_display in ( select sp_addr_dbkey_display from
SP_JCT_STKH_ADDR )

if not and if a unique key/primary on SP_JCT_STKH_ADDR(sp_addr_dbkey_display)
exists then

update ( select a.dc_acct_id old, b.dc_acct_id new
           from sp_addr a sp_jct_stkh_addr b
          where a.sp_addr_dbkey_display = b.sp_addr_dbkey_display )
set old = new;
Rating: 3
Inline query April 06, 2004
Reviewer: A reader from CA,USA
SP_JCT_STKH_ADDR is having this data

DC_ACCT_ID SP_ADDR_DBKEY_DISPLAY SP_STKH_JCT_ADDR_SEQ
---------- --------------------- --------------------
     41076            1200015008                    1
     41076            1200008017                    2
     41076            1200008018                    3
     41076            1200009067                    4

SP_ADDR is having
DC_ACCT_ID SP_ADDR_DBKEY_DISPLAY
---------- ---------------------
                      1200009067
                      1200008018
                      1200008017
                      1200015008

Now I want to find the DC_ACCT_ID ,SP_ADDR_DBKEY_DISPLAY corresponding to
maximum SP_STKH_JCT_ADDR_SEQ in SP_JCT_STKH_ADDR i.e

     41076            1200009067                    4

and then update DC_ACCT_ID in SP_ADDR for that particular
SP_ADDR_DBKEY_DISPLAY
i.e
update sp_addr set dc_acct_id = 41076 where
SP_ADDR_DBKEY_DISPLAY = '1200009067';

Thats what I was trying to do in anonymous block.
Can I have one update statement for this.
April 06, 2004 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
Rating: 3
anything new in 9i April 05, 2004
Reviewer: john
Tom,

in the example quoted below,

 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)

say, if i don't want sub totals for job, i need totals only based on deptno.
can't we do this without using having clause in 9i?
Followup:
  1  select deptno, job, sum(sal), decode(grouping(job),1,'<<<==' ) grp
  2  from emp
  3* group by grouping sets((deptno,job),(deptno))
scott@ORA10G> /

    DEPTNO JOB         SUM(SAL) GRP
---------- --------- ---------- -----
        10 CLERK           1300
        10 MANAGER         2450
        10 PRESIDENT       5000
        10                 8750 <<<==
        20 CLERK           1900
        20 ANALYST     10781.24
        20 MANAGER         2975
        20             15656.24 <<<==
        30 CLERK            950
        30 MANAGER         2850
        30 SALESMAN        5600
        30                 9400 <<<==

12 rows selected.
April 06, 2004 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.
Rating: 4
Limitation on array size? January 23, 2004
Reviewer: Dazza from uk
Tom,

when i use the example with 10 elements in the array it works. if i try 11 i
get:

ORA-00932: inconsistent datatypes

on the call to the Java.

am i doing something wrong here?
Rating: 4
Dazza January 23, 2004
Reviewer: A reader from uk
Tom,

please ignore that previous comment.

In my java file if one of the elements of the array turns out to be "" (ie a
zero length java string) it crashes Oracle. if i put a bit of code that said:
if (arrayInput[i] == ""){arrayInput[i] = null}

the problem goes away.
wierd?
Rating: 4
varray February 10, 2004
Reviewer: Venkat from India
Tom,

Our developeres feel that java API can be used to handle varrays in Java instead
of oracle or weblogic API to eliminate dependency of oracle/weblogic on this
part.

Is this the best way keeping in view the future implementation of oracle?

Regards,
Venkat
Followup:
don't understand what the goal is or how they would remove "what dependency".
not sure at all what they mean.
Rating: 5
Brilliant April 06, 2004
Reviewer: Matthew from Canberra, ACT, Australia
Tom,

Once again you prove to be an absolute godsend.   Worked perfectly in both our
8i & 9i databases :)

Only one comment: I can't see what you're illustrating with the
'demo_passing_pkg.pass' example.

The Java side is declared to be String[], but the PL/SQL side is merely
"Varchar2", and the result is the Java stored procedure receives a
single-element String[].

Is this just to demonstrate a convenient feature of the PL/SQL-Java interface
(that single-element arrays can be manipulated without the need for
oracle.sql.ARRAY)... or did I miss the real point of that particular example.

cheers,

Matthew
Followup:
I was demonstrating IN OUT parameters.

To do "out" you need to use an array so you can change what the array points to.
April 06, 2004 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.
Rating: 4
Paper Oracle Standard Edition vs SQL Server March 30, 2004
Reviewer: A reader
Hi Tom, I don't know if you could please helpme pointing to a paper, who
compares sql server vs. oracle Standard Edition, specially showing sql server
don't have hot backup, if you can please :)
Rating: 4
March 30, 2004
Reviewer: A reader
Mean while I found this really good one, thanks to Dennis Williams
http://www.tcoug.org/Archive/Winter2004/SQL-9i.ppt
almost 100 pages
Rating: 3
Switch back from EE to SE? April 05, 2004
Reviewer: Jelena from Germany
Hi Tom,
can you switch between Enterprise and Standard edition? Is there any
script/paramter/stg... or you need to reinstall oracle?
Thanks,
Jelena
Followup:
you'll want to contact support/metalink for the details but basically - you'll
have to ensure you are not using anything EE oriented in your DB, then (after
backing up everything of course) you'll reinstall the SE software.

My suggestion would be to get a play/test machine and restore the EE database
there with the SE software to test - making sure you turned off anything that
could cause a problem and then remove the software from the "real box",
reinstall and restore.

but -- contact support, there are support notes outlining howto do this.
Rating: 4
Decision Value April 06, 2004
Reviewer: kamran from Pakistan
I read that article (Microsoft comparing Oracle with SQL Server), it seems much
emphasis on price. First of all do you think your decision value is less than
the price of oralce-application, f so than no need to use(surely it is expensive
then). A good oracle-users can bring you information from stored data in
enormous directions which provides great help in decision making(businnes
offcourse)

I had have used SQL Server and using Oracle now a days. Does SQL Server offers
the following features:
analytical functions (e.g; lead, lag etc with extended options)[remarks: fast,
do what sql can't or hard to do],
minus/intersect operators [remarks: clear, easy to code and easy to unserstand],
different index-types (function-based/hash,bitmap etc) [remarks: speedup your
query-processing with your given hints (control)],
encrypted procedures [security/secracy],
inline views [ease in dealing with required result-sets, and a way to have
"order by" views],
easy and clear syntax to join more than 2 tables [not an ANSI standard (oracle
supports both its native syntax and in 9i, the ANSI syntax],
partioned tables, object-relational database support,support for xml, java, c,
html etc.
and finally fast, efficient to use resources, excellent backup/recovery and fine
tuning features.
you may disagree on some issues, and there are many more features I didn't
discuss.
Followup:
(not only that but the entire price argument is *false* -- list price, we are
basically "the same")....
Rating: 5
9i and 10g vs SQL 2000 April 06, 2004
Reviewer: Martin from UK
Hi Tom,

It's easy to have a tit-for-tat discussion about the benefits of Oracle over
SQL*Server (and vice-versa, of course), believe me, I work in primarily a
Microsoft shop, and trying to explain to even fellow developers why doing
something in a single SQL statement using OLAP functions beats their "I need to
use 287652 T-SQL temp tables, oh and don't SELECT too much from this table,
other processes are trying to UPDATE" is a hard slog. Don't get me wrong,
SQL*Server does have some great features, DTS and it's greater integration with
Windows, does have appeal when dealing with Windows apps (and a lot of
organisations do).

It is unfair to completely dismiss SQL*Server, it DOES have an element of XML
support (no pun intended), although it is VERY basic, you can define "ORDER BY"
views (although, there's no support for inline views), supports ANSI joins /
CASE etc, and it does it's job for the smaller applications. I think Microsoft
would be the first to admit, however unofficially, that SQL*Server just can't
compete with Oracle on the larger scale stuff.

Ultimately, of course, it is down to the skills you have in your organisation.
It'll take longer to develop an app sitting on Oracle than it will be to sit it
on SQL*Server, if you have no Oracle people, and a lot of organisations simply
are "Microsoft" (or IBM, or...) shops.

We all know that the features of Oracle which really "make the difference" to an
apps scalability and performance, are down to the architectural design features
which are used by applications which haven't been coded correctly, such as
multi-versioning, allowing the app to function "correctly" even if the basic
concurrency controls have not been followed (or imagined), and the absence of
any "lock manager", which is still the bugbear of SQL*Server and DB2's design,
one which Yukon (the next version) will HAVE to address if it's going to even
attempt to compete with 9i and 10g.

I find the hardest thing, though, is going from Oracle to SQL*Server projects.
Having to write T-SQL when just written PL/SQL code to do the same thing in
about 1000th of the size is VERY frustrating!

Just my 0.02 pence (British, of course) ;-)
Rating: 4
Clustering and 10g April 06, 2004
Reviewer: Oleksandr Alesinskyy from Germany
does 10g still use mostly "share-all" clustering or "g" (grid)means moving
toward to damned "share-nothing" clustering?

No offence, just curiosity.
April 06, 2004 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.
Rating: 5
then when should we increase db cache? February 07, 2004
Reviewer: A reader
Hi

After reading the thread I am getting worried. There is no way to know when
should we increase db cache in 8i...?!

I dont advice anyone use v$db_cache_advice by the way, everyday you see a
different advice (and database was not shutdown)!
Followup:
if you believe your sql to be "as good as it gets", use the cache advisor to
size the buffer cache for your working set.

you can use it before then, but as you identify and tune your sql -- just refer
to the cache advisor to see how much you can trim off over time.
Rating: 3
DB CACHE April 06, 2004
Reviewer: Jayesh from India
----------------------------------------
session logical reads
physical reads
physical reads direct
physical reads direct (lob)
----------------------------------------
  Can u please explain the above terms? I went thru documentationbut could'nt
get the proper meaning? What u mean by bypassing buffer cache and direct read?
Followup:
well, they are what they sound like --

session logical reads -- LIO's performed by a session -- reads from the buffer
cache.

physical reads - physical IO's from the data files, reading blocks into the
buffer cache.

the last two are the only ones that might be not 100% clear but....

physical reads direct -- reads from temp segments for sorting, no buffer cache.
physical reads direct (lob) -- reads from LOB segments where the lobs are
NOCACHE (no buffer cache)
April 06, 2004 Miroslav -- Thanks for the question regarding "Statspack: Who waits on log file sync and LGWR wait for redo copy events?", version 9.2.0
You Asked
Hello Tom,

I apologize in advance for not being able to make my question shorter.

Here is the portion of the statspack output:

            Snap Id     Snap Time      Sessions Curs/Sess Comment
            ------- ------------------ -------- --------- -------------------
Begin Snap:     207 10-Mar-04 08:39:47       36 581,793.7
  End Snap:     208 10-Mar-04 08:50:28       36 583,582.8
   Elapsed:               10.68 (mins)

...

Top 5 Timed Events
~~~~~~~~~~~~~~~~~~                                                     % Total
Event                                               Waits    Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
CPU time                                                        3,747    66.52
enqueue                                             2,129       1,350    23.97
db file sequential read                            37,842         286     5.07
log file sync                                       9,743          65     1.15
LGWR wait for redo copy                            58,067          61     1.08

I look at the CPU time--it is 3,747s on a system that has 4 HT CPUs (effectively
8 CPUs show up in Windows task manager).  This means that for one second real
time, there is 5.85s of CPU time spent by many concurrent sessions loading data
into the database.

Now, the question on the log file sync and LGWR wait for redo copy -- if I see
65 seconds wait time, and that, I believe, is time that the background process
spent waiting.  If background process waited that long, I wonder how much it
affected the user sessions (how much did they wait in the same time)...  (or,
perhaps, this time is in fact the time that end-user sessions waited because
redo had to be copied).  In other words, is this the time the background
processes wait, or is it the time that user sessions wait for the background
processes to finish?

We have just turned our disk cache buffer to write-only as suggested in Jonathan
Lewis's "Practical Oracle 8i" (it used to be 50%read/50%write).  For the very
similar load on the system we have seen the decrease in these statistics (both
in the number and in time):

log file sync                                       5,464          21      .44
LGWR wait for redo copy                            22,215          18      .38

To ask a question in a different way--if LGWR waited 3 seconds for redo to be
copied, but no user session waited because of that (if that's even possible),
does that still show up in the timed events?

Regards,
--Miroslav.
and we said...
watch out with CPU time.  if you have long running calls, it will easily over
and UNDER count cpu time.

 x-----------------x 50cpu seconds
       x----------------------x 50cpu seconds
            x-----------------------------------------------------x 500cpu secs
                               x----------------x 30cpu secs
                         x----------------------------------------x 60cpu secs
                    ^                                 ^
                    |                                 |
                    |------- snapshots taken here ----| 1 minute window

so you have a 1 minute snapshot.  Say you had the above "executions" going on.
statspack would report 80cpu seconds used (50+30) because the CPU stat is
gathered when the call completes.  so the 2cnd and 4th lines above contribute
their cpu time -- but none of the others do (they did not complete in the
window).  So, beware.

log file sync is a client wait event.  It is the wait event your clients wait on
when they say "commit".  It is the wait for LGWR to actually write their redo to
disk and return back to them.  You can "tune" this by making lgwr faster (no
raid 5 for example) and committing less frequently and generating less redo
(BULK operations generate less redo than row by row do)

the other one is a background wait. LGWR is waiting for forgrounds to finish a
current copy affecting the data LGWR is about to process.

HOWEVER, that said, tuning either of these will have no noticable affect on your
systems performance whatsoever!  It certainly looks like "enqueue" is your wait
and that is all about application design - those are heavyweight locks induced
by the application logic itself.  You would be best served by looking at the
*application* not at the "system" at this point.

do a level 12 10046 trace event on your application and see what it is truly
waiting for.  it is all about tuning the application.
Review & Followup

Rating: 4
Still don't understand the LGWR wait for redo copy March 16, 2004
Reviewer: Miroslav Dzakovic from Santa Clara, CA
Thanks for the useful answer!  Can you, please, clarify what exactly LGWR waits
for -- which "forgrounds to finish a current copy" of what?

Clear on CPU.  Our snapshot is 10 minutes, yet most transactions (hence all
statement calls) finish in about a minute or less => under/over count
possibility is +/-10%.

Clear on Equeues (I've seen this comment coming, but I didn't want to hide it
from the statspack report).  However, enqueues, while they look bad, are not
causing problems in our system, only because they are there *by design*.  Our
system is designed so that some background jobs do the post-processing of data
after it's been loaded.  And, sometimes the background processes wait for the
foreground processes to finish (using SELECT...FOR UPDATE) to ensure
consistency.  The thing is--while a foreground load jobs may take up to a minute
per transaction, those background jobs take only a couple of seconds.  Most of
the time our job queue processes (6 of them) are idle, and when they wake up
(each minute) to run newly submitted post-processing jobs, those jobs may find
that they still need to wait for some other loads to finish.  No harm done--even
if some JQ processes are tied down because of that, others are free to process
other jobs, and in the worst case it merely delays post-processing by a fraction
of a minute.  So, while it would be possible to eliminate "Enqueues" by timing
the post-processing jobs differently, it would probably not improve overall
system performance.

Finally, you are right that tuning LGWR and log file sync doesn't improve the
overall system performance by much in our case (obviously, we are talking about
some 2% of overall time).  This wasn't really a tuning exercise, as much as a
learning exercise, and the proof of Jonathan's point (setting the read/write
cache% on disks takes one minute).
Followup:
copy of redo information -- foregrounds moving stuff around (as lgwr is trying
to flush it out due to one of the triggers that cause lgwr to do that -- 3
seconds, 1/3 full, 1meg full, commit).

but seriously -- these waits are *nothing*, I would encourage you to ignore them
UNLESS they show up in an application trace!

You comments on enqueues is a perfect reason why tuning by statspack is "not the
best way" and why you need to look at the application.  anyone looking at that
statspack would gravitate right to the enqueues -- but you know that in your
case, they are "idle-ing waits" :)
Rating: 4
Seriously, what foregrounds are moving what stuff around? March 16, 2004
Reviewer: Miroslav Dzakovic
I get your point, and I'm not asking for tuning anymore.  For the sake of
knowledge you are so passionately sharing, please clarify.  Can you give an
example of a "foreground" (user sessions, or something else) and an example of
"stuff" it is moving around (data, log buffers, or something else)?

Another one that's a puzzle to me is this one:

Event                               Waits   Timeouts   Time (s)    (ms)     /txn
---------------------------- ------------ ---------- ---------- ------- --------
log file sequential read              849          0         10      12      0.0

Why does Oracle read the log file (other than during recovery, which we didn't
do in this statspack report time)?
Followup:
LGWR is about to write a set of log blocks to disk. It has to wait until the
foreground (us, you and me) processes complete any current copy operations which
affect the buffers we are about to write out.  It used to be the 'redo copy
latch' but there were performance changes made in 8i and up to make this
"faster".

so it is log related data.

be you in archive log mode??

ops$tkyte@ORA9IR2> select * from v$system_event where event like 'log file
sequential%'
  2  /

EVENT                     TOTAL_WAITS TOTAL_TIMEOUTS TIME_WAITED AVERAGE_WAIT
------------------------- ----------- -------------- ----------- ------------
log file sequential read            4              0           0            0

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> @connect "/ as sysdba"
ops$tkyte@ORA9IR2> set termout off
sys@ORA9IR2> REM GET afiedt.buf NOLIST
sys@ORA9IR2> set termout on
sys@ORA9IR2> alter system switch logfile;

System altered.

sys@ORA9IR2> archive log all;
1 log archived.
sys@ORA9IR2> @connect /
sys@ORA9IR2> set termout off
ops$tkyte@ORA9IR2> REM GET afiedt.buf NOLIST
ops$tkyte@ORA9IR2> set termout on
ops$tkyte@ORA9IR2> select * from v$system_event where event like 'log file
sequential%'
  2  /

EVENT                     TOTAL_WAITS TOTAL_TIMEOUTS TIME_WAITED AVERAGE_WAIT
------------------------- ----------- -------------- ----------- ------------
log file sequential read           69              0          26            0

;)
Rating: 5
Thank you! March 16, 2004
Reviewer: Miroslav Dzakovic
Nice and clear!

:-)
Rating: 4
CPU Time event in Oracle 9i Statspack April 06, 2004
Reviewer: Rathy from India
Hi Tom

Please give your expert guidance about how to address the "CPU time" event,
which is coming at the top in “Top 5 time event” in Statspack. In my case it's
coming at the top of the time event.

            Snap Id     Snap Time      Sessions Curs/Sess Comment
            ------- ------------------ -------- --------- -------------------
Begin Snap:      44 01-Apr-04 09:17:08       50       4.5
  End Snap:      47 01-Apr-04 10:47:16       50       3.7
   Elapsed:               90.13 (mins)

Cache Sizes (end)
~~~~~~~~~~~~~~~~~
               Buffer Cache:        32M      Std Block Size:         8K
           Shared Pool Size:       160M          Log Buffer:       768K

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:             43,420.37              3,849.72
              Logical reads:             17,304.43              1,534.24
              Block changes:                223.17                 19.79
             Physical reads:                215.36                 19.09
            Physical writes:                  8.05                  0.71
                 User calls:              1,380.56                122.40
                     Parses:                359.46                 31.87
                Hard parses:                  1.52                  0.13
                      Sorts:                 26.28                  2.33
                     Logons:                  1.78                  0.16
                   Executes:                470.49                 41.71
               Transactions:                 11.28

  % Blocks changed per Read:    1.29    Recursive Call %:     8.07
 Rollback per transaction %:    6.84       Rows per Sort:   470.84

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:  100.00
            Buffer  Hit   %:   98.78    In-memory Sort %:   99.98
            Library Hit   %:   99.81        Soft Parse %:   99.58
         Execute to Parse %:   23.60         Latch Hit %:   99.87
Parse CPU to Parse Elapsd %:   92.14     % Non-Parse CPU:   96.18

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   93.22   93.44
    % SQL with executions>1:   67.19   48.72
  % Memory for SQL w/exec>1:   43.79   47.68

Top 5 Timed Events
~~~~~~~~~~~~~~~~~~                                                     % Total
Event                                               Waits    Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
CPU time                                                        2,302    84.60
log file sync                                      57,524         291    10.71
db file parallel write                                692          31     1.14
db file scattered read                            147,666          29     1.07
db file sequential read                           210,120          16      .57
          -------------------------------------------------------------

Thanks for your service
Followup:
in 90 minutes on a single cpu machine you have 5400 cpu seconds.

you used less than half of that

that is all that is saying.  it is not a wait event, it is TIMED events, we are
just showing you the amount of cpu you used in that period of time.  this is
useful when looking at performance -- for if you used 100% of your CPU (the most
you can use), then you might have a problem (since you probably wanted to really
use (100+n)% of your CPU and hence people were waiting on cpu.
Rating: 1
How did you get 5400 ? April 06, 2004
Reviewer: Jan from Europe
Rating: 5
RE: How did you get 5400 April 06, 2004
Reviewer: Mark A. Williams from Indianapolis, IN USA
RE: How did you get 5400...

From the statspack report: 90.13 (mins)

90 (mins) * 60 (secs) = 5400 (secs)

- Mark
April 06, 2004 Nag -- Thanks for the question regarding "sqlplus hangs and takes for ever to do this ", version 8.1.5
You Asked
Tom

These days Iam facing a huge problem which is wasting a lot of mytime.

I have this table t , which has a million records.

For my work, I need to keep checking the number of total records , many number
of times in a day.

when I do a select count(*) the sqlplus hangs up , and never returns it is as if
it is going on for ever and ever.

How should I deal with it. How should I know what is happening within, whether
the sqlplus has hanged up , or it it is going to return with the total number of
records.

I have other tables in which I have 2 to 10 million records. I need a solution
becasue this is taking toomuch of effort and time.

I know it is going to take time to return the total number, but what I want is a
routine which can tell me how many records have been counted at a particular
given point of time, or whether the count has stopped inbetween due to some
reason.

How should I do it, is there a script which I can write, to do this.
Please help, I need it desperately.

thank you
and we said...
Well, sqlplus isn't hanging, its just waiting for the answer.

I do not see why you would need to count the totally number of records ( would
need to know more about your processing to suggest a way to AVOID having to do
that ) but you can monitor the progress.  Anything that takes more then a couple
of seconds will start to populate the v$session_longops table.  You can simply
open another session, use my showsql.sql to find your session (search for
showsql on my site) and then query v$session_longops like this:

 1  begin
  2     print_table( 'select *
  3                     from v$session_longops
  4                    where sid =16
  5                      and time_remaining > 0 ' );
  6* end;

(search my site for print_table as well)

It'll show you something like:

SID                           : 16
SERIAL#                       : 1556
OPNAME                        : Table Scan
TARGET                        : OPS$TKYTE.T
TARGET_DESC                   :
SOFAR                         : 30735
TOTALWORK                     : 38337
UNITS                         : Blocks
START_TIME                    : 16-aug-2001 19:32:51
LAST_UPDATE_TIME              : 16-aug-2001 19:33:03
TIME_REMAINING                : 3
ELAPSED_SECONDS               : 12
CONTEXT                       : 0
MESSAGE                       : Table Scan:  OPS$TKYTE.T: 30735 out of 38337
Blocks done
USERNAME                      : OPS$TKYTE
SQL_ADDRESS                   : 82E2A364
SQL_HASH_VALUE                : 737329258
QCSID                         : 0

which shows how long it took, how long it will probably take and where it is in
the processing..
Review & Followup

Rating: 5
A possible alternative August 17, 2001
Reviewer: Connor from UK
If you are not adding too many records per day, but counting them very
frequently, you could have a row-trigger than adds/subtracts deltas and stores
the count in a secondary table.  (Presumably a materialised view could achieve
the same thing)
Rating: 4
Connor , tell us more about that August 17, 2001
Reviewer: A reader
How should we do what you are suggesting
Followup:
See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:424949760671
for a materialized view exmaple.

alternatively you can:

create table COUNTER ( x int );
insert into counter select count(*) from t;

create or replace trigger t_trigger
after insert or delete on T for each row
begin
    if ( insertng ) then update counter set x = x+1;
    else                 update counter set x = x-1;
    end if;
end;

Beware -- both of the above would imply necessarily DECREASED concurrency on the
table T.  You would in effect be serializing inserts/deletes on T since every
insert/delete must update a single row.
Rating: 4
Analyze table August 17, 2001
Reviewer: Andrew from CA, USA
If you don't need an exact number, then consider analyzing the table using
"estimate".
analyze table ABC estimate statistics;
select table_name, num_rows from user_tables where table_name = 'ABC';
Rating: 5
Connor's solution adds work and that may not help August 17, 2001
Reviewer: Harrison from Haymarket, VA USA (sadly, not England)
Hi:  Connor's solution (a row level trigger) will add some processing to the
database, and that may not be something the users can afford.  It might be
better to use table growth to estimate the number of rows (after you have a
million, that should be pretty easy).  Anyway, the hard part of this question is
what can possibly be the difference between 1 million rows and 1.09 (the other
hard question is why a smart person like Conner wants to leave a nice place like
England for a colony? ).  Anyway, Tom's suggestion is interesting, another table
that I did not know existed, and a neat script to use it.  With a slight bit of
magic, we might make a web page updates the progress, and quiets the the
complainers who need that number (when "A lot!" would do.)
Rating: 5
but not working August 18, 2001
Reviewer: Mohammed Ahmed from Saudi Arabia
I searched and found print_table script which you use 8.0 and below. It doesnt
work.

It gives the following error:

PLS-00302: component 'DESC_TAB' must be declared
Followup:
The URL you reference is:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:584023239495
That has the script printtbl8.sql that I use in 8.0 (due to no autonomous
transactions).

In 7.3 and before, which you must be using, it'll not work since DBMS_SQL did
not have a "describe" functionality in that release.  I use that functionality
to get the column names -- without it, I cannot do that.

I don't have a print_table script for 7.3 and before for this reason.
Rating: 4
Alternative to PRINT_TABLE August 20, 2001
Reviewer: Andrew from CA, USA
I haven't tested this in 7.x, but it looks like it will be OK.
http://www.oracle.com/oramag/code/tips2000/index.html?120400.html

Rating: 4
Alternative to PRINT_TABLE August 20, 2001
Reviewer: Andrew from CA, USA
Whoops the URL didn't come out:

www.oracle.com/oramag/code/tips2000/index.html?120400.html
Rating: 4
what about DDL monitoring? September 27, 2002
Reviewer: Dean from Nashville
I am trying to create an index for datawarhousing that will be several gigs in
size.  When I query v$session_longops, I can't see it.

How can I track the progress of building a big index before it becomes a perm
segment?

TIA
Followup:
It should be in v$session_longops.

I fired up a session (sid = 15).  I started a create unique index in it.  Popped
over to another window and:

ops$tkyte@ORA817DEV.US.ORACLE.COM> begin  print_table( 'select * from
v$session_longops where sid = 15' );
  2  end;
  3  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> /
SID                           : 15
SERIAL#                       : 100
OPNAME                        : Table Scan
TARGET                        : BIG_TABLE.BIG_TABLE
TARGET_DESC                   :
SOFAR                         : 1600
TOTALWORK                     : 22822
UNITS                         : Blocks
START_TIME                    : 27-sep-2002 16:47:07
LAST_UPDATE_TIME              : 27-sep-2002 16:47:19
TIME_REMAINING                : 159
ELAPSED_SECONDS               : 12
CONTEXT                       : 0
MESSAGE                       : Table Scan:  BIG_TABLE.BIG_TABLE: 1600 out of
22822 Blocks done
USERNAME                      : BIG_TABLE
SQL_ADDRESS                   : 01A7FBF4
SQL_HASH_VALUE                : 0
QCSID                         : 0
-----------------

PL/SQL procedure successfully completed.

It takes a couple of seconds -- but it shows up. In fact, I cannot get it to
"not" show up.
Rating: 4
query is hanging April 05, 2004
Reviewer: Kapil
Hi Tom,

I am having a problem where a query of the form:
(SELECT * FROM table) is hanging after giving 5 rows but when I give a (SELECT
COUNT(*) FROM table) it returns 57 records. The problem is specifically for this
table. When I fire a SELECT * from any other table its fine.

What could be the reason? What should I check to debug this? Should I run any
scripts to give you data to help me debug this problem?
Followup:
hmmm, select * vs count(*) with no predicate...

or are we only seeing the tip of the iceberg that is your question.

I've a feeling we are comparing

select * from <something really complex>

vs

select count(*) from <something really complex>

you do understand that count(*) can do things entirely index based (using
indexes as tall skinny tables without ever going to the table, or just skipping
the table access by index rowid) vs count(*)

count(*) isn't very realistic as a "query performance evaluation"
Rating: 3
No Entry in V$SESSION_LONGOPS too April 06, 2004
Reviewer: Kapil
The table is small with a few columns and normal data types like char, number
etc.

This is the execution plan for the slect * against the select count(*)

SQL> select count(*) from if_ctc_summary;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'IF_CTC_SUMMARY'

So its not doing an index scan for the count(*)

SQL> select * from if_ctc_summary;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (FULL) OF 'IF_CTC_SUMMARY'

SQL> set autotrace off
SQL> select count(*) from if_ctc_summary;

  COUNT(*)
----------
        57

and when I a do * from if_ctc_summary it returns 5 rows and then just hangs

There is no entry for this session in V$SESSION_LONGOPS

I have taken the dump for this session from V$SESSION ... Here it is

SADDR                         : 000000038023BFB0
SID                           : 181
SERIAL#                       : 50352
AUDSID                        : 343247
PADDR                         : 00000003801AE3A8
USER#                         : 816
USERNAME                      : OR4
COMMAND                       : 3
OWNERID                       : 2147483644
TADDR                         : 0000000380512F40
LOCKWAIT                      :
STATUS                        : INACTIVE
SERVER                        : DEDICATED
SCHEMA#                       : 816
SCHEMANAME                    : OR4
PROCESS                       : 18602
TERMINAL                      : pts/24
TYPE                          : USER
SQL_ADDRESS                   : 000000038CD9A278
SQL_HASH_VALUE                : 1960482032
PREV_SQL_ADDR                 : 000000038CD9A278
PREV_HASH_VALUE               : 1960482032
MODULE                        : SQL*Plus
MODULE_HASH                   : 3669949024
ACTION                        :
ACTION_HASH                   : 4029777240
CLIENT_INFO                   :
FIXED_TABLE_SEQUENCE          : 61
ROW_WAIT_OBJ#                 : -1
ROW_WAIT_FILE#                : 0
ROW_WAIT_BLOCK#               : 0
ROW_WAIT_ROW#                 : 0
LAST_CALL_ET                  : 448
PDML_ENABLED                  : NO
FAILOVER_TYPE                 : NONE
FAILOVER_METHOD               : NONE
FAILED_OVER                   : NO
RESOURCE_CONSUMER_GROUP       : DEFAULT_CONSUMER_GROUP
PDML_STATUS                   : DISABLED
PDDL_STATUS                   : ENABLED
PQ_STATUS                     : ENABLED

I have taken the dump for this session from V$PROCESS also ... Here it is

ADDR                          : 00000003801AE3A8
PID                           : 188
SPID                          : 18613
SERIAL#                       : 27
TERMINAL                      : pts/24
TRACEID                       :
BACKGROUND                    :
LATCHWAIT                     :
LATCHSPIN                     :

Does this indicate anything? Or should I run some other scripts?
Followup:
status is inactive, if it is hanging, it is more likely a client issue --
inactive means "the server is waiting for client to tell it to do something"

perhaps you have "pause on" in sqlplus or something?  did you hit "enter"?
Rating: 3
April 06, 2004
Reviewer: Kapil
the set pause is not on

the strange part is that it doesnt happen with the other tables which have even
more rows i.e. it doesnt hang and all the rows are displayed

what else could it be?
April 06, 2004 malcolm -- Thanks for the question regarding "Finding host name or connect string after database login.", version Oracle 7 and/or 8
You Asked
From SQL*Plus, how can I determine the host to which I am
connected?

I commonly work on a set of distributed systems, which, combined
with different server versions and mainenance accounts, adds up
to about 200 different systems I might be working on, and many
of them look very similar.

I often wish I could reconfirm which system I am working on.

It is super easy to recheck the userid and the server version,
which helps, but I have yet to find a way (short of explicitly
re-connecting) to confirm what host system I am working on.

A way to find the original connect string would be best, but any
host identifying detail would be useful.

Many systems are running 7.3.4.1.0.  They are being switched,
slowly, to version 8.  Currently that seems to mean 8.1.6.0.0.
(I can use SQL*Plus 3.3.2.0.2, or  8.0.5.0.0, or could install
another I'm sure, if that makes any difference.)

Thanks for any infomation you could provide.
and we said...
I'm in the same boat (lots of databases, wanna know which one
I'm in).

I use a login.sql with SQLPlus.  I also make sure each and every
database has a unique global name (alter database rename
global_name to <some name>) or unique SID (i like the global
name myself).

In my login.sql i have:

....
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> '
.....

That makes my sqlprompt look like:

ops$tkyte@DEV8I.WORLD>

So, now I have my username and database in the prompt.  I just
have to remember to use an @connect -- NOT connect when
connecting since the login.sql is run ONCE, not after every
connect.  My @connect is simply:

...
connect &1
@login
...

If you want to use the ORACLE_SID instead of the global database
name, just "select instance global_name from v$thread" in the
above.
Review & Followup

Rating: 4
Now I always know where I am. May 30, 2001
Reviewer: Johann Lodina from Germany
Thank you for this hint, it is exactly what I looked for.
Rating: 5
September 13, 2001
Reviewer: A reader
Rating: 5
It was a solution I really needed!! Thanks!! October 17, 2001
Reviewer: Shashank Sinha from Waukesha, WI USA
This site itself is pretty great, and the search engine is neat!!
This solution in particular was something that will save a lot of my hair...!!
Rating: 5
Instance and hostname on SQL*Plus login September 26, 2003
Reviewer: Angus McTavish aged 75 from Cumbria, England
Sometimes you might want more info than you can get from global_name, for
example the instance and the hostname for the database. Problem with that is
that the user may not have access to the v$ tables. I use a modified version of
your script which first gets the info from global_name and then tries to get
info from v$instance for example. If the user doesn't have access to v$instance
at least the global_name info will be displayed. If you simply use your script
(but replace global_name with v$instance), when you switch from a database where
you do have access to v$instance to one where you don't, the login will work but
you will display the login details from the first database. Know what I mean?

Here is an example of the modified script anyway:

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;
select lower(user) || ':'|| instance_name ||'@'|| host_name global_name from
v$instance;
set termout on
set sqlprompt '&gname> '

angus:TRFG@angustest> @connect angus/x@pan1
Connected.
angus:pan1@PANSYS01> @connect noddy/noddy@pan1
Connected.
noddy@PAN1.US.ORACLE.COM>
Rating: 4
Placing this SQL file March 17, 2004
Reviewer: A reader
Hi,

I've placed the connect.sql in the same directory where I have glogin.sql (using
Oracle 9i). But for some reason, when I try to execute @connect, I get the error
message - connect.sql is not found.

How can I set the default directory to be ora9i\sqlplus\admin.

Please suggest
Followup:
set your SQLPATH environment variable.  that is where sqlplus looks for scripts.
Rating: 5
sqlplus /nolog March 17, 2004
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom,
If I do a sqlplus /nolog, login.sql asks for a value for gname since I am not
yet connected to any database. Is there a workaround?
Thanks
Followup:
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> '

will default the prompt to "idle" in the event the query cannot run
Rating: 4
Oracle Mode Details March 18, 2004
Reviewer: Dhimant Antani from Ankleshwar, INDIA
Additionally how can I know which mode the server is running in i.e Dedicated or
Shared ??? Any help will be most appreciated

Regards,

Dhimant Antani
Followup:
the "server" does not "run in dedicated or shared"

the "server" can accept both connection types.

select server, count(*) from v$session;

will show you at a point in time how many of each kind you have.

dedicated = dedicated server
shared,none = shared server
Rating: 5
connect as sysdba March 18, 2004
Reviewer: Arun Gupta from Harrisburg, PA USA
One more problem with connect as sysdba:

idle> @connect sys/xyz@trng as sysdba
ERROR:
ORA-28009: connection to sys should be as sysdba or sysoper

Thanks...
Followup:
@connect "sys/.... as sysdba"

my script only uses &1
Rating: 5
10g sqlplus March 18, 2004
Reviewer: Dave
10g sqlplus will re-read the login file, so no need for @connect now hopefully
Followup:
correct

ops$tkyte@ORA10G> connect scott/tiger
Connected.
scott@ORA10G>
Rating: 4
ora_database_name April 06, 2004
Reviewer: Muraalee from Chennai, India
Tom
I wonder if I could use 'ora_database_name' in place of 'global_name'

rmurali@ORA9R2> select global_name from global_name;

GLOBAL_NAME
------------------------------------------------------
ORA9R2.WORLD

Elapsed: 00:00:00.00
rmurali@ORA9R2> select ora_database_name from dual;

ORA_DATABASE_NAME
------------------------------------------------------
ORA9R2.WORLD

Elapsed: 00:00:00.00
rmurali@ORA9R2>

Actually, my question is -
what is the difference between using ora_database_name and global_name

Thanks.
Followup:
ora_database_name is a function that returns the global name.

ops$tkyte@ORA9IR2> exec dbms_output.put_line( ora_database_name );
ORA9IR2.US.ORACLE.COM

PL/SQL procedure successfully completed.

global_name is a column in a table.

ops$tkyte@ORA9IR2> desc global_name;
 Name                                 Null?    Type
 ------------------------------------ -------- -------------------------
 GLOBAL_NAME                                   VARCHAR2(4000)

ops$tkyte@ORA9IR2> select * from global_name;

GLOBAL_NAME
------------------------------------------------------------------------
ORA9IR2.US.ORACLE.COM

yes, you can.
April 06, 2004 -- 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.
Rating: 5
hard parsing again and database bit buckes April 05, 2004
Reviewer: bob from PA
Tom,

Related to this thread and many others I have read on this topic. I have a
tkprof report that shows the following:

begin dbms_lob.write(:1,:2,:3,:4); end;

call   count   cpu   elapsed
----   ------  -----  -------
Parse   91471  2964    5033
Execute 91471  3287    6760

Misses in Library Cache during parse: 71310
Misses in Library Cache during execute: 19678

begin dbms_lob.getchunksize(:2); end;

call   count   cpu   elapsed
----   ------  -----  -------
Parse   12266  234    295
Execute 12266  236    341

Misses in Library Cache during parse: 11798
Misses in Library Cache during execute: 468

begin dbms_lob.read(:1,:2,:3,:4); end;

call   count   cpu   elapsed
----   ------  -----  -------
Parse   12266  191    208
Execute 24532  415    525

Misses in Library Cache during parse: 12006
Misses in Library Cache during execute: 12455

This is the trace file from one of five connections of a jdbc connection pool.

My question is why are there all the hard parses?
I haven't seen the code yet, but you can see bind variables are being used, but
hard parses are through the roof and cpu time for parsing is equal to execution
time, as can be expected from excessive parsing.  I suspect the parse=execute is
a result of the architecture requirements, and not bad code that repeatedly
parses the same statement.

anonymous blocks can be soft parsed, so is it possible the shared pool is too
small?

I guess I need to look at one of my statspack snaps during this period to see
how many times it was executed in a 15 minute window.

This tkprof represents a trace of a session representing a connection pool that
was running for a couple days on a dual cpu sun.

Any guesses why the read and writes for lobs are so high in this system?  Java
Object Serialization  (database=bit bucket) sad sad sad.

Without having seen the code yet (other than data structures showing they are
storing beans as blobs in the db), I can't say a lot, but I suspect they are
also against using the oraclecallablestatement extensions for reading and
writing BLOBS due to "database independence" nonsense.
Followup:
tracing via a connection pool in 9i and before is practically impossible.

look at statspack and see if it even closely resembles what you see here as far
as parse/execute.
Rating: 3
Does 10g's trcsess work on 9i trace files ? April 05, 2004
Reviewer: Gary from Sydney, Aus
Will the 10g trcsess utility work with 9i trace files, or is 10g putting
additional info in the trace that allows it to work ?

IE would it be possible to have 10g installed in, for example, a test
environment and use that to scan trace files from a production 9i environment ?
Followup:
it is looking for special stuff injected into the trace files in 10g -- new with
10g.
Rating: 5
"query" in tkprof April 06, 2004
Reviewer: Martin from UK
Hi Tom,

just need something clarified if you will. In the above example comparing
procedure calls with anonymous PL/SQL blocks, the resultant tkprof output shows
different results in the "query" columns for the statements in both cases, and I
was wondering what the reason for this disparity was?

Thanks in advance
Followup:
roles are not enabled during the execution of stored procedures.

hence, the counts returned by querying all_objects are different.

if you use an authid current_user (invoker right) routine -- they would be the
same.  the stored procedure counted lots less rows.

you could also just "set role none" before doing the anonymous block in this
case to make them to same.
Rating: 5
Forgot about roles, thanks! April 06, 2004
Reviewer: Martin from UK
April 06, 2004 -- 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://download-west.oracle.com/docs/cd/B10501_01/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://download-west.oracle.com/docs/cd/B10501_01/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.
Rating: 5
I/O March 30, 2004
Reviewer: Gabriel
Hello Tom,

Is there a way to manage I/O quotas between users the same way we can manage CPU
with the resource manager?
Followup:
You can limit IO usage using a PROFILE, otherwise since all IO more or less
turns into LIO and LIO is all about CPU -- throttleing the CPU throttles IO
Rating: 5
April 06, 2004
Reviewer: A reader
This discussion is very interesting.

I have this issue: I would not like to divide my users is groups in fact. I
would simply like that no single user gets more than -- say -- 70% of cpu when
concurring with others. Hou could I accomplish that ? By creating a resource
group for each user ?

Profiles do are not in order here I because, I far as I understand, they
terminate the session exceeding the limit and I would like them to be scheduled,
not aborted.
Followup:
resource limits kick in only when the resource is in fact almost exhausted.

So, with CPU, you would need be near 100% utilization.  If you were and multiple
people were COMPETING for this resource -- the OS would make it so that each
gets their slice.

You cannot use resource limits to CAP a group

You can only use resource limits to make sure a certain group gets X% of
resources (eg: you cannot limit someone to 70%, you can ask that they get 70% or
more of the resources)

You can "nice" down people consuming lots of resources for long periods -- that
would probably accomplish your goal.
Rating: 5
April 06, 2004
Reviewer: A reader
Tom,

If we want to clear all the resource settings that we made and set it back to
default what are the steps.

Thanks.
April 06, 2004 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
Rating: 4
Is there a Size Limit with IMP/EXP? April 06, 2004
Reviewer: Richard from Oxfordshire, UK
Hi,

I need to get an exact copy of our production 8.1.6 DB from an NT machine onto
our W2K server (boo!), which has 9i on it (hooray!).

As the Operating Systems differ, IMP/EXP appears to be the only way to go.

The data I wish to move around is 57GB in size. Is this too much for IMP/EXP?
Followup:
NT to W2k are file compatible -- they are the same.  Ok to backup and restore.

but you can use files= and filesize= in order to generate N files of M bytes
each (eg: split them up)
Rating: 5
Thank You!!!! April 06, 2004
Reviewer: Richard from Oxfordshire, UK
Hi Tom,

Many thanks for your help.

I thought (wrongly) that Oracle was ULTRA picky about OSs, and am delighted that
an NT to W2K restore will be OK.

Cheers!
Rating: 3
Moving data from one Server to New Server(or Test Server) April 06, 2004
Reviewer: denni50 from NA
We did our replication of our 20g Production DB to a new
faster IBM Server differently.

We went from an NT4 to Windows 2000 Server OS.
Everything was backed up and we did perform a full export
just in case.

1) We created the same directory structure on the new
   server as the source server.
2) shutdown db and all Oracle Services on source server
3) Physically copied all the datafiles/logfiles/controlfiles
   to their same locations on the new server.
4) copied the init<sid>.ora to the new server.

5) installed Oracle and configured the new server.

We were able to accomplish this in much less time and without any glitches.
April 06, 2004 Raj -- Thanks for the question regarding "Adding Custom keywords to intermedia", version 8.1.7
You Asked
Hi Tom!
   We have created this keyword table in our schema in which the user enters his
keywords assosiated with the article that is created. We are going to using
oracle intermedia as our search tool. Is there anyway we can have intermedia use
these keywords.
and we said...
Assuming that "have intermedia use these keywords" means you want
queries that are issued against the articles to also be issued
against the keyword table, then the answer is yes, you can
exploit this using interMedia Text.  The simplest way to "include"
these keywords in your search is via an additional query.

As an example, let's say your articles were contained in
ARTICLES.THETEXT.  And let's also assume that the keywords associated
with these articles are stored in ARTICLES.KEYWORDS.  Then if you
had an interMedia Text index created on each of these, you could
issue a query similar to:

    SELECT id
      FROM ARTICLES
     WHERE CONTAINS( thetext,'searchterm' ) > 0
        OR CONTAINS( keywords, 'searchterm' ) > 0;

another option is to index a function and have the function return the text and
the keywords "glued together" in a clob.  see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:440419921146
for an example

follow up to comment

index a function.  Have the function return a temporary clob that has the
original text AND the keywords.  I answered that.

see
http://download-east.oracle.com/docs/cd/A81042_01/DOC/inter.816/a77063/cexec3.htm#9186
for ctxload documentation.
Review & Followup

Rating: 2
May 30, 2001
Reviewer: A reader
Hi Tom!
    I needed Intermedia to consider those keywords PART OF a document! Also do
you have any links to explain the ctxload package!
Rating: 3
OK April 06, 2004
Reviewer: Richard from Minneapolis,USA
Hi Tom,
While querying the v$reserved_words view,I came across some
809 reserved words and most of them intimidates me like
 1)Without
 2)Add
 3)Mirror
 4)Exempt
 5)Undrop
 6)Broadcast
Where do we use these keywords?Are they used internally by the Oracle RDBMS?
Please do reply.
Bye!
Followup:
why do they intimidate you?

add -- alter table ADD x number; for example.

some of them are rman, asm, and other tools -- not all are "plain sql".